refactor: world generation (#17)

* refactor: use TBB for concurrent hash maps and parallelize chunk processing

* fix: tbb link fail

* refactor(chunk): remove biome check for caves in rivers and oceans

* refactor(random): replace std distributions with custom implementations

Avoid overhead and platform-dependent behavior of `<random>` distributions by using direct engine operations and integer arithmetic. This ensures deterministic, cross-platform results and improves performance.

* refactor(generation): use chunk seed for cave and river paths

- Use per-chunk seed instead of global path_id for cave and river generation.
- Remove unused m_sum variables and m_path_id members.
- Clamp river yaw within 10 degrees of initial direction.
- Fix river radius interpolation (use t instead of 1-t).
- Lower sea level from 64 to 63.
This commit is contained in:
zhenyan121
2026-06-14 11:36:37 +08:00
committed by GitHub
parent 932463663f
commit f4114c2699
19 changed files with 384 additions and 239 deletions

View File

@@ -6,7 +6,7 @@ namespace Cubed {
constexpr int WORLD_SIZE_Y = 256;
constexpr int CHUNK_SIZE = 16;
constexpr int SEA_LEVEL = 64;
constexpr int SEA_LEVEL = 63;
constexpr int MAX_UI_NUM = 1;
constexpr int MAX_BLOCK_STATUS = 1;

View File

@@ -1,10 +1,14 @@
#pragma once
#include "Cubed/gameplay/cave_path.hpp"
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CaveCarver {
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
public:
CaveCarver();
std::unordered_map<unsigned, CavePath>& paths();
CaveHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed);
@@ -15,9 +19,8 @@ public:
float& cave_probability();
private:
std::unordered_map<unsigned, CavePath> m_paths;
CaveHashMap m_paths;
unsigned m_seed = 0;
int m_sum = 0;
Random m_random;
float m_cave_probability = 0.035f;
};

View File

@@ -5,12 +5,16 @@
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <unordered_set>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CavePath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public:
CavePath(unsigned int world_seed, int path_id, const glm::vec3& start_pos);
CavePath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const;
void clear_chunk(const ChunkPos& pos);
bool is_finished() const;
@@ -34,7 +38,6 @@ private:
static inline int m_step_min = 10;
static inline int m_step_max = 400;
int m_path_id = 0;
unsigned int m_seed = 0;
float m_yaw = 0.0f;
float m_pitch = 0.0f;
@@ -44,7 +47,7 @@ private:
Random m_random;
std::vector<PathPoint> m_points;
std::unordered_set<ChunkPos, ChunkPos::Hash> m_pending_chunks;
ChunkPosSet m_pending_chunks;
void collect_path_points();
void precompute_chunk_coverage();
};

View File

@@ -14,6 +14,8 @@ class World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
@@ -46,8 +48,7 @@ private:
BiomeConditions m_conditions;
void clear_dirty();
void gen_vertices(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id);
@@ -97,8 +98,7 @@ public:
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void gen_vertex_data(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
void upload_to_gpu();
GLuint get_normal_vao() const;

View File

@@ -4,6 +4,7 @@
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <atomic>
@@ -61,6 +62,9 @@ private:
unsigned m_chunk_seed = 0;
void make_biome_builder();
void
carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
std::function<void(int /*x*/, int /*y*/, int /*z*/)> on_hit);
};
} // namespace Cubed

View File

@@ -16,7 +16,14 @@ struct ChunkPos {
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
}
};
struct TBBHash {
std::size_t hash(const ChunkPos& p) const {
return ChunkPos::Hash{}(p);
}
bool equal(const ChunkPos& a, const ChunkPos& b) const {
return a == b;
}
};
ChunkPos operator+(const ChunkPos& pos) const {
return ChunkPos{x + pos.x, z + pos.z};
}

View File

@@ -5,13 +5,16 @@
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <unordered_set>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class RiverPath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public:
RiverPath(unsigned int world_seed, int path_id, const glm::vec3& start_pos);
RiverPath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const;
void clear_chunk(const ChunkPos& pos);
bool is_finished() const;
@@ -32,12 +35,12 @@ private:
static inline float m_radius_y_max = 8.0f;
static inline float m_delta_angle_min = -3.0f;
static inline float m_delta_angle_max = 3.0f;
static inline int m_step_min = 150;
static inline int m_step_min = 200;
static inline int m_step_max = 400;
int m_path_id = 0;
unsigned int m_seed = 0;
float m_yaw = 0.0f;
float m_initial_yaw = 0.0f;
float m_pitch = 0.0f;
int m_step = 0;
float m_step_len = 1.0f;
@@ -45,7 +48,7 @@ private:
Random m_random;
std::vector<PathPoint> m_points;
std::unordered_set<ChunkPos, ChunkPos::Hash> m_pending_chunks;
ChunkPosSet m_pending_chunks;
void collect_path_points();
void precompute_chunk_coverage();
};

View File

@@ -4,13 +4,15 @@
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <unordered_map>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class RiverWorm {
using RiverHashMap = tbb::concurrent_hash_map<unsigned, RiverPath>;
public:
RiverWorm();
std::unordered_map<unsigned, RiverPath>& paths();
RiverHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed);
@@ -21,9 +23,8 @@ public:
float& river_probability();
private:
std::unordered_map<unsigned, RiverPath> m_paths;
RiverHashMap m_paths;
unsigned m_seed = 0;
int m_sum = 0;
Random m_random;
float m_probability = 0.01f;
};

View File

@@ -31,8 +31,10 @@ class Player;
class TextureManager;
class World {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
using ChunkUpdateList = std::vector<std::pair<ChunkPos, Chunk>>;
using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
using ConstChunkMap =
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
@@ -72,14 +74,14 @@ private:
void sync_player_pos(glm::vec3& player_pos);
void
compute_required_chunks(ChunkPosSet& required_chunks,
ChunkHashMap& temp_neighbor,
ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&);
void
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks);
const ChunkPairVector& new_chunks);
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
ConstChunkMap&);

View File

@@ -7,7 +7,17 @@ namespace HASH {
inline std::size_t str(std::string_view value) {
return std::hash<std::string_view>{}(value);
}
inline uint32_t mix_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
inline uint32_t combine_32(uint32_t seed, uint32_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
inline uint32_t chunk_seed_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
uint32_t seed =
combine_32(combine_32(fixed_seed, (uint32_t)a), (uint32_t)b);
return seed;
}
/*
inline uint32_t chunk_seed_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
uint32_t h = fixed_seed;
h ^= (uint32_t)a * 0xcc9e2d51u;
@@ -27,10 +37,8 @@ inline uint32_t mix_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
return h;
}
inline uint32_t combine_32(uint32_t seed, uint32_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
*/
} // namespace HASH
} // namespace Cubed