diff options
| -rw-r--r-- | core/pch.hh | 2 | ||||
| -rw-r--r-- | core/vectors.hh | 49 | ||||
| -rw-r--r-- | game/client/chunk_mesher.cc | 5 | ||||
| -rw-r--r-- | game/client/chunk_renderer.cc | 3 | ||||
| -rw-r--r-- | game/client/chunk_visibility.cc | 148 | ||||
| -rw-r--r-- | game/client/chunk_visibility.hh | 12 | ||||
| -rw-r--r-- | game/client/game.cc | 4 | ||||
| -rw-r--r-- | game/client/session.cc | 8 | ||||
| -rw-r--r-- | game/server/overworld.cc | 37 | ||||
| -rw-r--r-- | game/server/overworld.hh | 2 |
10 files changed, 133 insertions, 137 deletions
diff --git a/core/pch.hh b/core/pch.hh index a2199a2..9bd9c43 100644 --- a/core/pch.hh +++ b/core/pch.hh @@ -34,8 +34,8 @@ #include <glm/vec3.hpp> #include <glm/vec4.hpp> -#include <glm/gtc/quaternion.hpp> #include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/quaternion.hpp> #include <glm/gtc/type_ptr.hpp> #include <physfs.h> diff --git a/core/vectors.hh b/core/vectors.hh new file mode 100644 index 0000000..e6e185a --- /dev/null +++ b/core/vectors.hh @@ -0,0 +1,49 @@ +#ifndef CORE_VECTORS_HH +#define CORE_VECTORS_HH 1 +#pragma once + +// cxvectors.hh - because NO ONE would POSSIBLY +// need integer-based distance calculations in a +// game about voxels. That would be INSANE! :D + +namespace cxvectors +{ +template<typename value_type> +constexpr static inline const value_type length2(const glm::vec<2, value_type> &vector); +template<typename value_type> +constexpr static inline const value_type length2(const glm::vec<3, value_type> &vector); +template<typename value_type> +constexpr static inline const value_type distance2(const glm::vec<2, value_type> &vector_a, const glm::vec<2, value_type> &vector_b); +template<typename value_type> +constexpr static inline const value_type distance2(const glm::vec<3, value_type> &vector_a, const glm::vec<3, value_type> &vector_b); +} // namespace cxvectors + +template<typename value_type> +constexpr static inline const value_type cxvectors::length2(const glm::vec<2, value_type> &vector) +{ + static_assert(std::is_arithmetic_v<value_type>); + return (vector.x * vector.x) + (vector.y * vector.y); +} + +template<typename value_type> +constexpr static inline const value_type cxvectors::length2(const glm::vec<3, value_type> &vector) +{ + static_assert(std::is_arithmetic_v<value_type>); + return (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z); +} + +template<typename value_type> +constexpr static inline const value_type cxvectors::distance2(const glm::vec<2, value_type> &vector_a, const glm::vec<2, value_type> &vector_b) +{ + static_assert(std::is_arithmetic_v<value_type>); + return cxvectors::length2(vector_a - vector_b); +} + +template<typename value_type> +constexpr static inline const value_type cxvectors::distance2(const glm::vec<3, value_type> &vector_a, const glm::vec<3, value_type> &vector_b) +{ + static_assert(std::is_arithmetic_v<value_type>); + return cxvectors::length2(vector_a - vector_b); +} + +#endif /* CORE_VECTORS_HH */ diff --git a/game/client/chunk_mesher.cc b/game/client/chunk_mesher.cc index 1eb14c1..011d6a7 100644 --- a/game/client/chunk_mesher.cc +++ b/game/client/chunk_mesher.cc @@ -10,7 +10,6 @@ #include "shared/voxel_registry.hh" #include "client/chunk_quad.hh" -#include "client/chunk_visibility.hh" #include "client/globals.hh" #include "client/session.hh" #include "client/voxel_atlas.hh" @@ -222,9 +221,9 @@ void GL_MeshingTask::finalize(void) } } - if(has_no_submeshes_b && has_no_submeshes_nb) + if(has_no_submeshes_b && has_no_submeshes_nb) { globals::dimension->chunks.remove<ChunkMeshComponent>(m_entity); - else chunk_visibility::update_chunk(m_entity); + } } bool GL_MeshingTask::vis_test(voxel_id voxel, const VoxelInfo *info, const local_pos &lpos) const diff --git a/game/client/chunk_renderer.cc b/game/client/chunk_renderer.cc index 97e88a6..1b038b1 100644 --- a/game/client/chunk_renderer.cc +++ b/game/client/chunk_renderer.cc @@ -10,7 +10,6 @@ #include "client/camera.hh" #include "client/chunk_mesher.hh" #include "client/chunk_quad.hh" -#include "client/chunk_visibility.hh" #include "client/game.hh" #include "client/globals.hh" #include "client/outline.hh" @@ -107,7 +106,7 @@ void chunk_renderer::render(void) timings[1] = globals::window_frametime_avg; timings[2] = voxel_anims::frame; - const auto group = globals::dimension->chunks.group<ChunkComponent>(entt::get<ChunkMeshComponent, ChunkVisibleComponent>); + const auto group = globals::dimension->chunks.group<ChunkComponent>(entt::get<ChunkMeshComponent>); if(depth_sort_chunks.get_value()) { // FIXME: speed! sorting every frame doesn't look diff --git a/game/client/chunk_visibility.cc b/game/client/chunk_visibility.cc index df0148c..eb4a7b0 100644 --- a/game/client/chunk_visibility.cc +++ b/game/client/chunk_visibility.cc @@ -2,7 +2,9 @@ #include "client/chunk_visibility.hh" #include "core/config.hh" +#include "core/vectors.hh" +#include "shared/chunk_aabb.hh" #include "shared/chunk.hh" #include "shared/dimension.hh" #include "shared/protocol.hh" @@ -11,124 +13,74 @@ #include "client/globals.hh" #include "client/session.hh" -static chunk_pos cached_cpos; -static unsigned int cached_dist; -static std::vector<chunk_pos> requests; +// Sending a somewhat large amount of network packets +// can easily overwhelm both client, server and the network +// channel created between the two. To prevent this from happening +// we throttle the client's ever increasing itch for new chunks +constexpr static unsigned int MAX_CHUNKS_REQUESTS_PER_FRAME = 16U; -static void request_chunk(const chunk_pos &cpos) -{ - protocol::RequestChunk packet; - packet.cpos = cpos; - protocol::send(session::peer, protocol::encode(packet)); -} +static ChunkAABB current_view_box; +static ChunkAABB previous_view_box; +static std::vector<chunk_pos> requests; -// Go through the list of chunk positions that should -// be visible client-side but seem to not exist yet -static void request_new_chunks(void) +static void update_requests(void) { - auto cmin = cached_cpos - static_cast<chunk_pos::value_type>(cached_dist); - auto cmax = cached_cpos + static_cast<chunk_pos::value_type>(cached_dist); - requests.clear(); - for(auto cx = cmin.x; cx <= cmax.x; ++cx) - for(auto cy = cmin.y; cy <= cmax.y; ++cy) - for(auto cz = cmin.z; cz <= cmax.z; ++cz) { - if(globals::dimension->find_chunk({cx, cy, cz})) { - // The chunk already exists, we don't need - // to request it from the server anymore - continue; - } + for(auto cx = current_view_box.min.x; cx != current_view_box.max.x; cx += 1) + for(auto cy = current_view_box.min.y; cy != current_view_box.max.y; cy += 1) + for(auto cz = current_view_box.min.z; cz != current_view_box.max.z; cz += 1) { + auto cpos = chunk_pos(cx, cy, cz); - requests.push_back(chunk_pos(cx, cy, cz)); + if(globals::dimension->find_chunk(cpos)) + continue; + requests.push_back(cpos); } - std::sort(requests.begin(), requests.end(), [](const chunk_pos &ca, const chunk_pos &cb) { - auto dir_a = ca - cached_cpos; - auto dir_b = cb - cached_cpos; - - const auto da = dir_a[0] * dir_a[0] + dir_a[1] * dir_a[1] + dir_a[2] * dir_a[2]; - const auto db = dir_b[0] * dir_b[0] + dir_b[1] * dir_b[1] + dir_b[2] * dir_b[2]; - + std::sort(requests.begin(), requests.end(), [](const chunk_pos &cpos_a, const chunk_pos &cpos_b) { + auto da = cxvectors::distance2(cpos_a, camera::position_chunk); + auto db = cxvectors::distance2(cpos_b, camera::position_chunk); return da > db; }); } -static bool is_chunk_visible(const chunk_pos &cpos) -{ - const auto dx = cxpr::abs(cpos.x - cached_cpos.x); - const auto dy = cxpr::abs(cpos.z - cached_cpos.z); - - if((dx <= cached_dist) && (dy <= cached_dist)) - return true; - return false; -} - -void chunk_visibility::update_chunk(entt::entity entity) -{ - if(auto component = globals::dimension->chunks.try_get<ChunkComponent>(entity)) { - if(is_chunk_visible(component->cpos)) - globals::dimension->chunks.emplace_or_replace<ChunkVisibleComponent>(entity); - else globals::dimension->chunks.remove<ChunkVisibleComponent>(entity); - } -} - -void chunk_visibility::update_chunk(const chunk_pos &cpos) +void chunk_visibility::update_late(void) { - if(auto chunk = globals::dimension->find_chunk(cpos)) { - const auto &component = globals::dimension->chunks.get<ChunkComponent>(chunk->get_entity()); - if(is_chunk_visible(component.cpos)) - globals::dimension->chunks.emplace_or_replace<ChunkVisibleComponent>(chunk->get_entity()); - else globals::dimension->chunks.remove<ChunkVisibleComponent>(chunk->get_entity()); + current_view_box.min = camera::position_chunk - static_cast<chunk_pos::value_type>(camera::view_distance.get_value()); + current_view_box.max = camera::position_chunk + static_cast<chunk_pos::value_type>(camera::view_distance.get_value()); + + if(!session::is_ingame()) { + // This makes sure the previous view box + // is always different from the current one + previous_view_box.min = chunk_pos(INT32_MIN, INT32_MIN, INT32_MIN); + previous_view_box.max = chunk_pos(INT32_MAX, INT32_MAX, INT32_MAX); + return; } -} -void chunk_visibility::update_chunks(void) -{ - const auto view = globals::dimension->chunks.view<ChunkComponent>(); - - for(const auto [entity, chunk] : view.each()) { - if(is_chunk_visible(chunk.cpos)) - globals::dimension->chunks.emplace_or_replace<ChunkVisibleComponent>(entity); - else globals::dimension->chunks.remove<ChunkVisibleComponent>(entity); + if((current_view_box.min != previous_view_box.min) || (current_view_box.max != previous_view_box.max)) { + update_requests(); } - request_new_chunks(); -} - -void chunk_visibility::cleanup(void) -{ - cached_cpos = camera::position_chunk + 1; - cached_dist = camera::view_distance.get_value() + 1; - requests.clear(); -} - -void chunk_visibility::update(void) -{ - if(session::is_ingame()) { - if((cached_cpos != camera::position_chunk) || (cached_dist != camera::view_distance.get_value())) { - cached_cpos = camera::position_chunk; - cached_dist = camera::view_distance.get_value(); - chunk_visibility::update_chunks(); - return; + for(unsigned int i = 0U; i < MAX_CHUNKS_REQUESTS_PER_FRAME; ++i) { + if(requests.empty()) { + // Done sending requests + break; } - for(int i = 0; i < 16; ++i) { - if(requests.empty()) { - // Done sending requests - break; - } + protocol::RequestChunk packet; + packet.cpos = requests.back(); + protocol::send(session::peer, protocol::encode(packet)); - request_chunk(requests.back()); + requests.pop_back(); + } - requests.pop_back(); - } - - cached_cpos = camera::position_chunk; - cached_dist = camera::view_distance.get_value(); - return; + auto view = globals::dimension->chunks.view<ChunkComponent>(); + + for(const auto [entity, chunk] : view.each()) { + if(current_view_box.contains(chunk.cpos)) + continue; + globals::dimension->remove_chunk(entity); } - - cached_cpos = camera::position_chunk + 1; - cached_dist = camera::view_distance.get_value() + 1; + + previous_view_box = current_view_box; } diff --git a/game/client/chunk_visibility.hh b/game/client/chunk_visibility.hh index 317d83f..70352c9 100644 --- a/game/client/chunk_visibility.hh +++ b/game/client/chunk_visibility.hh @@ -4,19 +4,9 @@ #include "shared/types.hh" -struct ChunkVisibleComponent final {}; - -namespace chunk_visibility -{ -void update_chunk(entt::entity entity); -void update_chunk(const chunk_pos &cpos); -void update_chunks(void); -} // namespace chunk_visibility - namespace chunk_visibility { -void cleanup(void); -void update(void); +void update_late(void); } // namespace chunk_visibility #endif /* CLIENT_CHUNK_VISIBILITY_HH */ diff --git a/game/client/game.cc b/game/client/game.cc index f7a6349..72e1dc5 100644 --- a/game/client/game.cc +++ b/game/client/game.cc @@ -513,8 +513,6 @@ void client_game::update(void) chunk_mesher::update(); - chunk_visibility::update(); - client_chat::update(); experiments::update(); @@ -550,6 +548,8 @@ void client_game::update_late(void) gamepad::update_late(); + chunk_visibility::update_late(); + if(client_game::vertical_sync.get_value()) glfwSwapInterval(1); else glfwSwapInterval(0); diff --git a/game/client/session.cc b/game/client/session.cc index 3371889..4c4d06b 100644 --- a/game/client/session.cc +++ b/game/client/session.cc @@ -72,8 +72,6 @@ static void on_disconnect_packet(const protocol::Disconnect &packet) globals::player = entt::null; globals::dimension = nullptr; - chunk_visibility::cleanup(); - message_box::reset(); message_box::set_title("disconnected.disconnected"); message_box::set_subtitle(packet.reason.c_str()); @@ -184,8 +182,6 @@ void session::invalidate(void) delete globals::dimension; globals::player = entt::null; globals::dimension = nullptr; - - chunk_visibility::cleanup(); } void session::connect(const char *host, std::uint16_t port, const char *password) @@ -238,8 +234,6 @@ void session::connect(const char *host, std::uint16_t port, const char *password delete globals::dimension; globals::player = entt::null; globals::dimension = nullptr; - - chunk_visibility::cleanup(); globals::gui_screen = GUI_PLAY_MENU; }); @@ -272,8 +266,6 @@ void session::disconnect(const char *reason) delete globals::dimension; globals::player = entt::null; globals::dimension = nullptr; - - chunk_visibility::cleanup(); client_chat::clear(); } diff --git a/game/server/overworld.cc b/game/server/overworld.cc index 46ee466..eb32d3f 100644 --- a/game/server/overworld.cc +++ b/game/server/overworld.cc @@ -67,7 +67,7 @@ Overworld::Overworld(const char *name) : Dimension(name, -30.0f) { m_bottommost_chunk.set_limits(-64, -4); m_terrain_variation.set_limits(16, 256); - compute_tree_feature(5U, m_feat_tree, game_voxels::oak_log, game_voxels::oak_leaves); + compute_tree_feature(32U, m_feat_tree, game_voxels::oak_log, game_voxels::oak_leaves); } void Overworld::init(Config &config) @@ -83,6 +83,11 @@ void Overworld::init_late(std::uint64_t global_seed) { std::mt19937 twister(global_seed); + m_fnl_variation = fnlCreateState(); + m_fnl_variation.seed = static_cast<int>(twister()); + m_fnl_variation.noise_type = FNL_NOISE_PERLIN; + m_fnl_variation.frequency = 0.001f; + m_fnl_terrain = fnlCreateState(); m_fnl_terrain.seed = static_cast<int>(twister()); m_fnl_terrain.noise_type = FNL_NOISE_OPENSIMPLEX2S; @@ -93,12 +98,21 @@ void Overworld::init_late(std::uint64_t global_seed) m_fnl_caves_a = fnlCreateState(); m_fnl_caves_a.seed = static_cast<int>(twister()); m_fnl_caves_a.noise_type = FNL_NOISE_PERLIN; - m_fnl_caves_a.frequency = 0.0075f; + m_fnl_caves_a.fractal_type = FNL_FRACTAL_RIDGED; + m_fnl_caves_a.frequency = 0.0125f; + m_fnl_caves_a.octaves = 1; m_fnl_caves_b = fnlCreateState(); m_fnl_caves_b.seed = static_cast<int>(twister()); - m_fnl_caves_b.noise_type = FNL_NOISE_PERLIN; - m_fnl_caves_b.frequency = 0.0075f; + m_fnl_caves_b.noise_type = FNL_NOISE_OPENSIMPLEX2S; + m_fnl_caves_b.fractal_type = FNL_FRACTAL_RIDGED; + m_fnl_caves_b.frequency = 0.0125f; + m_fnl_caves_b.octaves = 1; + + m_fnl_nvdi = fnlCreateState(); + m_fnl_nvdi.seed = static_cast<int>(twister()); + m_fnl_nvdi.noise_type = FNL_NOISE_OPENSIMPLEX2S; + m_fnl_nvdi.frequency = 1.0f; m_metamap.clear(); } @@ -136,15 +150,15 @@ bool Overworld::generate(const chunk_pos &cpos, VoxelStorage &voxels) bool Overworld::is_inside_cave(const voxel_pos &vpos) { - auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, 1.5f * vpos.y, vpos.z); - auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, 1.5f * vpos.y, vpos.z); - auto noise_combined = noise_a * noise_a + noise_b * noise_b; - return noise_combined < (1.0f / 1024.0f); + auto noise_a = fnlGetNoise3D(&m_fnl_caves_a, vpos.x, vpos.y * 2.0f, vpos.z); + auto noise_b = fnlGetNoise3D(&m_fnl_caves_b, vpos.x, vpos.y * 2.0f, vpos.z); + return (noise_a > 0.95f) && (noise_b > 0.85f); } bool Overworld::is_inside_terrain(const voxel_pos &vpos) { - auto variation = m_terrain_variation.get_value(); + auto variation_noise = fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z); + auto variation = m_terrain_variation.get_value() * (1.0f - (variation_noise * variation_noise)); auto noise = variation * fnlGetNoise3D(&m_fnl_terrain, vpos.x, vpos.y, vpos.z) - vpos.y; return noise > 0.0f; } @@ -189,11 +203,10 @@ const Overworld_Metadata &Overworld::get_or_create_metadata(const chunk_pos_xz & } } - // FIXME: make this into a configuration value - constexpr static unsigned int TREE_DENSITY = 4U; + auto tree_density = static_cast<unsigned int>(fnlGetNoise2D(&m_fnl_nvdi, cpos.x, cpos.y) * 2.0 + 2.0f); // Generate tree locations for this chunk - while(metadata.trees.size() < TREE_DENSITY) { + while(metadata.trees.size() < tree_density) { auto lpos = local_pos_xz((twister() % CHUNK_SIZE), (twister() % CHUNK_SIZE)); auto is_unique = true; diff --git a/game/server/overworld.hh b/game/server/overworld.hh index 78844ce..2f7b67e 100644 --- a/game/server/overworld.hh +++ b/game/server/overworld.hh @@ -45,9 +45,11 @@ private: emhash8::HashMap<chunk_pos_xz, Overworld_Metadata> m_metamap; private: + fnl_state m_fnl_variation; fnl_state m_fnl_terrain; fnl_state m_fnl_caves_a; fnl_state m_fnl_caves_b; + fnl_state m_fnl_nvdi; private: Feature m_feat_tree; |
