summaryrefslogtreecommitdiffstats
path: root/game
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-03-19 18:07:51 +0500
committeruntodesu <kirill@untode.su>2025-03-19 18:07:51 +0500
commitfddd7f761176bb45cfdd41eeccaeadac22d33ddf (patch)
tree96fdc781f2cd96e769c148e850e411a90ff1ad5b /game
parent1c138d80ac08dfc48f0916c568f780e15db73834 (diff)
downloadvoxelius-fddd7f761176bb45cfdd41eeccaeadac22d33ddf.tar.bz2
voxelius-fddd7f761176bb45cfdd41eeccaeadac22d33ddf.zip
Fix things and improve worldgen
- Client-side now actually deletes invisible chunks - Improved world generation to use a second noise generator alongside changing how caves are generated (ie what noise they use)
Diffstat (limited to 'game')
-rw-r--r--game/client/chunk_mesher.cc5
-rw-r--r--game/client/chunk_renderer.cc3
-rw-r--r--game/client/chunk_visibility.cc148
-rw-r--r--game/client/chunk_visibility.hh12
-rw-r--r--game/client/game.cc4
-rw-r--r--game/client/session.cc8
-rw-r--r--game/server/overworld.cc37
-rw-r--r--game/server/overworld.hh2
8 files changed, 83 insertions, 136 deletions
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;