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

@@ -58,6 +58,21 @@ if (WIN32)
if(TARGET freetype)
add_library(Freetype::Freetype ALIAS freetype)
endif()
set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS ON)
FetchContent_Declare(
onetbb
GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git
GIT_TAG v2023.0.0
)
set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE)
FetchContent_MakeAvailable(onetbb)
set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED})
unset(_BUILD_SHARED_LIBS_SAVED)
endif()
FetchContent_Declare(
@@ -83,7 +98,6 @@ FetchContent_MakeAvailable(tomlplusplus)
add_subdirectory(third_party/imgui)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
add_executable(${PROJECT_NAME}
@@ -164,10 +178,11 @@ target_link_libraries(${PROJECT_NAME}
Freetype::Freetype
tomlplusplus::tomlplusplus
imgui
tbb
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_link_libraries(${PROJECT_NAME} PRIVATE tbb)
# target_link_libraries(${PROJECT_NAME} PRIVATE tbb)
endif()
if (UNIX AND NOT APPLE)
@@ -187,3 +202,19 @@ if (UNIX AND NOT APPLE)
target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER})
endif()
if (WIN32)
foreach(TBB_LIB IN ITEMS tbb tbbmalloc tbbmalloc_proxy)
if(TARGET ${TBB_LIB})
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${TBB_LIB}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying ${TBB_LIB}.dll"
)
else()
message(STATUS "Target ${TBB_LIB} not found, skipping copy")
endif()
endforeach()
endif()

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

View File

@@ -5,11 +5,10 @@
namespace Cubed {
CaveCarver::CaveCarver() {}
std::unordered_map<unsigned, CavePath>& CaveCarver::paths() { return m_paths; }
CaveCarver::CaveHashMap& CaveCarver::paths() { return m_paths; }
void CaveCarver::init(unsigned world_seed) {
m_seed = world_seed;
m_sum = 0;
m_random.init(m_seed);
}
@@ -20,16 +19,18 @@ void CaveCarver::reload(unsigned world_seed) {
}
void CaveCarver::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, CavePath{m_seed, m_sum, pos});
m_sum++;
m_paths.emplace(chunk_seed, CavePath{chunk_seed, m_seed, pos});
}
void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed);
if (it != m_paths.end()) {
{
CaveHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
}
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_cave_probability))) {
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
@@ -47,10 +48,17 @@ void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
}
void CaveCarver::cleanup_finished_caves() {
std::erase_if(m_paths,
[](const auto& kv) { return kv.second.is_finished(); });
std::vector<unsigned int> finished_keys;
for (const auto& pair : m_paths) {
if (pair.second.is_finished()) {
finished_keys.push_back(pair.first);
}
}
for (const auto& key : finished_keys) {
m_paths.erase(key);
}
}
int CaveCarver::cave_sum() const { return m_sum; }
int CaveCarver::cave_sum() const { return m_paths.size(); }
float& CaveCarver::cave_probability() { return m_cave_probability; }
} // namespace Cubed

View File

@@ -6,10 +6,9 @@
#include <algorithm>
namespace Cubed {
CavePath::CavePath(unsigned int world_seed, int path_id,
CavePath::CavePath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos) {
m_path_id = path_id;
m_seed = HASH::combine_32(world_seed, path_id);
m_seed = HASH::combine_32(chunk_seed, world_seed);
m_random.init(m_seed);
m_yaw = m_random.random_float(0.0f, 360.0f);
m_pitch = m_random.random_float(-10.0f, 10.0f);
@@ -75,7 +74,8 @@ void CavePath::precompute_chunk_coverage() {
for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert({cx, cz});
m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
}
}

View File

@@ -103,8 +103,7 @@ int Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z);
}
void Chunk::gen_vertex_data(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data) {
return;
}
@@ -265,8 +264,7 @@ unsigned Chunk::seed() const {
BiomeConditions& Chunk::conditions() { return m_conditions; }
void Chunk::gen_vertices(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
@@ -299,8 +297,9 @@ void Chunk::gen_vertices(
World::chunk_pos(world_nx, world_nz);
auto is_culled =
[&](const std::vector<BlockType>* chunk_blocks) {
if (chunk_blocks == nullptr) {
[&](const std::optional<std::vector<BlockType>>&
chunk_blocks) {
if (chunk_blocks == std::nullopt) {
return true;
}
int x, y, z;

View File

@@ -22,7 +22,7 @@ constexpr int BLEND_RADIUS = 8;
ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) {
ASSERT_MSG(is_init, "ChunksGenerator is not init");
ChunkPos pos = m_chunk.get_chunk_pos();
unsigned seed = HASH::mix_hash(pos.x, pos.z, m_generator_seed);
unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed);
m_random.init(seed);
m_chunk_seed = seed;
}
@@ -642,29 +642,29 @@ void ChunkGenerator::make_biome_builder() {
void ChunkGenerator::ocean_build() { m_biome_builder->ocean_water_build(); }
void ChunkGenerator::generate_cave() {
auto& cave_carver = m_chunk.world().cave_carcer();
auto& paths = cave_carver.paths();
const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks();
void ChunkGenerator::carve_worm(
const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
std::function<void(int /*x*/, int /*y*/, int /*z*/)> on_hit) {
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1;
for (const auto& point : points) {
for (auto& [id, path] : paths) {
for (const auto& point : path.points()) {
if ((m_chunk.biome() == BiomeType::RIVER) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
path.clear_chunk(chunk_pos);
continue;
}
const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz;
float rad_y = point.rad_y;
if (center.x + rad_xz < CHUNK_MIN_X ||
center.x - rad_xz > CHUNK_MAX_X ||
center.z + rad_xz < CHUNK_MIN_Z ||
center.z - rad_xz > CHUNK_MAX_Z || center.y + rad_y < CHUNK_MIN_Y ||
center.y - rad_y > CHUNK_MAX_Y) {
continue;
}
int min_x = static_cast<int>(std::floor(center.x - rad_xz));
int max_x = static_cast<int>(std::floor(center.x + rad_xz));
int min_z = static_cast<int>(std::floor(center.z - rad_xz));
@@ -679,32 +679,61 @@ void ChunkGenerator::generate_cave() {
min_y = std::max(min_y, CHUNK_MIN_Y);
max_y = std::min(max_y, CHUNK_MAX_Y);
for (int wx = min_x; wx <= max_x; ++wx) {
int x = wx - CHUNK_MIN_X;
for (int wz = min_z; wz <= max_z; ++wz) {
int z = wz - CHUNK_MIN_Z;
glm::vec3 right_raw =
glm::cross(point.tangent, glm::vec3(0.0f, 1.0f, 0.0f));
if (glm::dot(right_raw, right_raw) < 1e-6f)
right_raw = glm::cross(point.tangent, glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 right = glm::normalize(right_raw);
glm::vec3 up = glm::normalize(glm::cross(point.tangent, right));
float inv_a2 = 1.0f / (point.rad_xz * point.rad_xz);
float inv_b2 = 1.0f / (point.rad_y * point.rad_y);
for (int wy = min_y; wy <= max_y; ++wy) {
int y = wy;
glm::vec3 pos(static_cast<float>(wx),
static_cast<float>(wy),
static_cast<float>(wz));
if (point.contains(pos)) {
if (y == 0) {
if (wy == 0)
continue;
}
if (blocks[Chunk::index(x, y, z)] == 7) {
float dy = static_cast<float>(wy) - point.pos.y;
float vy_contrib = dy * up.y;
float vy2 = vy_contrib * vy_contrib * inv_b2;
if (vy2 >= 1.0f)
continue;
}
if (y < WORLD_SIZE_Y - 1 &&
blocks[Chunk::index(x, y + 1, z)] == 7) {
for (int wx = min_x; wx <= max_x; ++wx) {
float dx = static_cast<float>(wx) - point.pos.x;
for (int wz = min_z; wz <= max_z; ++wz) {
float dz = static_cast<float>(wz) - point.pos.z;
glm::vec3 to_point(dx, dy, dz);
float h = glm::dot(to_point, right);
float v = glm::dot(to_point, up);
if (h * h * inv_a2 + v * v * inv_b2 > 1.0f)
continue;
}
blocks[Chunk::index(x, y, z)] = 0;
}
int x = wx - CHUNK_MIN_X;
on_hit(x, wy, wz - CHUNK_MIN_Z);
}
}
}
}
}
void ChunkGenerator::generate_cave() {
auto& cave_carver = m_chunk.world().cave_carcer();
auto& paths = cave_carver.paths();
const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks();
for (auto& [id, path] : paths) {
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
if (blocks[idx] == 7)
return;
if (y < WORLD_SIZE_Y - 1 && blocks[Chunk::index(x, y + 1, z)] == 7)
return;
blocks[idx] = 0;
});
path.clear_chunk(chunk_pos);
}
}
@@ -715,64 +744,27 @@ void ChunkGenerator::generate_river() {
auto& paths = river_worm.paths();
const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks();
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1;
bool is_river = false;
for (auto& [id, path] : paths) {
for (const auto& point : path.points()) {
if ((m_chunk.biome() == BiomeType::DESERT) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
path.clear_chunk(chunk_pos);
continue;
}
const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz;
float rad_y = point.rad_y;
int min_x = static_cast<int>(std::floor(center.x - rad_xz));
int max_x = static_cast<int>(std::floor(center.x + rad_xz));
int min_z = static_cast<int>(std::floor(center.z - rad_xz));
int max_z = static_cast<int>(std::floor(center.z + rad_xz));
int min_y = static_cast<int>(std::floor(center.y - rad_y));
int max_y = static_cast<int>(std::floor(center.y + rad_y));
min_x = std::max(min_x, CHUNK_MIN_X);
max_x = std::min(max_x, CHUNK_MAX_X);
min_z = std::max(min_z, CHUNK_MIN_Z);
max_z = std::min(max_z, CHUNK_MAX_Z);
min_y = std::max(min_y, CHUNK_MIN_Y);
max_y = std::min(max_y, CHUNK_MAX_Y);
for (int wx = min_x; wx <= max_x; ++wx) {
int x = wx - CHUNK_MIN_X;
for (int wz = min_z; wz <= max_z; ++wz) {
int z = wz - CHUNK_MIN_Z;
for (int wy = min_y; wy <= max_y; ++wy) {
int y = wy;
glm::vec3 pos(static_cast<float>(wx),
static_cast<float>(wy),
static_cast<float>(wz));
if (point.contains(pos)) {
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
if (y > SEA_LEVEL) {
blocks[Chunk::index(x, y, z)] = 0;
continue;
blocks[idx] = 0;
return;
}
is_river = true;
if (blocks[Chunk::index(x, y, z)] == 0) {
continue;
}
blocks[Chunk::index(x, y, z)] = 7;
}
}
}
}
if (blocks[idx] == 0) {
return;
}
blocks[idx] = 7;
});
path.clear_chunk(chunk_pos);
}
if (is_river) {

View File

@@ -5,12 +5,15 @@
#include <algorithm>
namespace Cubed {
RiverPath::RiverPath(unsigned int world_seed, int path_id,
RiverPath::RiverPath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos) {
m_path_id = path_id;
m_seed = HASH::combine_32(world_seed, path_id);
m_seed = HASH::combine_32(chunk_seed, world_seed);
m_random.init(m_seed);
m_yaw = m_random.random_float(0.0f, 360.0f);
m_initial_yaw = m_yaw;
m_pitch = 0.0f;
m_start_path_point.pos = start_pos;
m_start_path_point.rad_xz =
@@ -41,14 +44,15 @@ void RiverPath::collect_path_points() {
float t = Math::smootherstep(0, m_step - 1, i);
float drad_xz = m_start_path_point.rad_xz * (1.0f - t);
float drad_y = m_start_path_point.rad_y * (1.0f - t);
float drad_xz = m_start_path_point.rad_xz * t;
float drad_y = m_start_path_point.rad_y * t;
drad_xz = std::max(drad_xz, 4.0f);
drad_y = std::max(drad_y, 4.0f);
m_points.emplace_back(m_points[i].pos + glm::vec3{dx, dy, dz}, drad_xz,
drad_y);
m_yaw += m_random.random_float(m_delta_angle_min, m_delta_angle_max);
m_yaw = std::clamp(m_yaw, m_initial_yaw - 10.0f, m_initial_yaw + 10.0f);
}
auto n = m_points.size();
if (n >= 2) {
@@ -72,7 +76,8 @@ void RiverPath::precompute_chunk_coverage() {
for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert({cx, cz});
m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
}
}

View File

@@ -5,11 +5,11 @@
namespace Cubed {
RiverWorm::RiverWorm() {}
std::unordered_map<unsigned, RiverPath>& RiverWorm::paths() { return m_paths; }
RiverWorm::RiverHashMap& RiverWorm::paths() { return m_paths; }
void RiverWorm::init(unsigned world_seed) {
m_seed = world_seed;
m_sum = 0;
m_random.init(m_seed);
}
@@ -20,16 +20,17 @@ void RiverWorm::reload(unsigned world_seed) {
}
void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, RiverPath{m_seed, m_sum, pos});
m_sum++;
m_paths.emplace(chunk_seed, RiverPath{chunk_seed, m_seed, pos});
}
void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed);
if (it != m_paths.end()) {
{
RiverHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
}
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_probability))) {
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
@@ -44,10 +45,17 @@ void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
}
void RiverWorm::cleanup_finished_rivers() {
std::erase_if(m_paths,
[](const auto& kv) { return kv.second.is_finished(); });
std::vector<unsigned> finished_keys;
for (const auto& pair : m_paths) {
if (pair.second.is_finished()) {
finished_keys.push_back(pair.first);
}
}
for (const auto& key : finished_keys) {
m_paths.erase(key);
}
}
int RiverWorm::river_sum() const { return m_sum; }
int RiverWorm::river_sum() const { return m_paths.size(); }
float& RiverWorm::river_probability() { return m_probability; }
} // namespace Cubed

View File

@@ -5,6 +5,8 @@
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include <execution>
namespace Cubed {
struct ChunkRenderData {
@@ -89,6 +91,7 @@ void World::init_world() {
void World::init_chunks() {
hot_reload();
while (!m_chunk_gen_finished) {
// Logger::info("World Spawn: {:.2f}%", m_chunk_gen_fraction.load());
std::this_thread::sleep_for(std::chrono::microseconds(200));
}
}
@@ -319,7 +322,7 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.0f;
m_chunk_gen_finished = false;
ChunkPosSet required_chunks;
ChunkHashMap temp_neighbor;
ChunkPairVector temp_neighbor;
std::vector<ChunkPos> need_gen_temp_chunks_pos;
compute_required_chunks(required_chunks, temp_neighbor,
need_gen_temp_chunks_pos);
@@ -340,7 +343,7 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.1f;
ChunkUpdateList new_chunks;
ChunkPairVector new_chunks;
ChunkHashMap new_temp_chunks;
for (auto& pos : need_gen_chunks_pos) {
new_chunks.push_back({pos, Chunk(*this, pos)});
@@ -357,20 +360,29 @@ void World::gen_chunks_internal() {
// build new chunk, but the neighbor in m_chunks also need to re-build
for (auto& [pos, chunk] : new_chunks) {
std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[this](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed());
}
for (auto& [pos, chunk] : new_temp_chunks) {
});
std::for_each(new_temp_chunks.begin(), new_temp_chunks.end(),
[](std::pair<const ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one();
}
});
// precompute path to ensure the continuity of the path
for (auto& [pos, chunk] : temp_neighbor) {
std::for_each(std::execution::par, temp_neighbor.begin(),
temp_neighbor.end(),
[this](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed());
}
});
m_chunk_gen_fraction = 0.2f;
/*
@@ -407,9 +419,12 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.3f;
for (auto& [pos, chunks] : new_chunks) {
std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[](std::pair<ChunkPos, Chunk>& pair) {
auto& [pos, chunks] = pair;
chunks.gen_phase_three();
}
});
for (auto& [pos, chunk] : new_temp_chunks) {
chunk.gen_phase_three();
}
@@ -417,8 +432,6 @@ void World::gen_chunks_internal() {
// chunks.gen_phase_three();
// }
m_chunk_gen_fraction = 0.4f;
/*
for (int i = 0; i < 4; i++) {
for (auto& [pos, chunks] : temp_neighbor) {
@@ -462,22 +475,27 @@ void World::gen_chunks_internal() {
}
}
*/
m_chunk_gen_fraction = 0.4f;
m_chunk_gen_fraction = 0.5f;
for (auto& [pos, chunks] : new_chunks) {
chunks.gen_phase_five();
}
m_chunk_gen_fraction = 0.45f;
for (auto& [pos, chunk] : new_temp_chunks) {
chunk.gen_phase_five();
}
m_chunk_gen_fraction = 0.5f;
/*
for (auto& [pos, chunks] : temp_neighbor) {
chunks.gen_phase_five();
}
*/
std::array<std::optional<std::vector<BlockType>>, 4> neighbor_blocks_data;
for (auto& [pos, chunks] : new_chunks) {
std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
new_chunks_surface_blend_data(new_chunks.size());
for (size_t idx = 0; idx < new_chunks.size(); idx++) {
auto& [pos, chunk] = new_chunks[idx];
new_chunks_surface_blend_data[idx].first = &chunk;
{
// std::lock_guard lk(m_chunks_mutex);
for (int i = 0; i < 4; i++) {
@@ -486,53 +504,77 @@ void World::gen_chunks_internal() {
if (it == new_chunks_neighbor.end()) {
auto it = new_temp_chunks.find(neighbor_pos);
if (it == new_temp_chunks.end()) {
neighbor_blocks_data[i] = std::nullopt;
new_chunks_surface_blend_data[idx].second[i] =
std::nullopt;
Logger::warn(
"Can't find neighbor for chunk surface blend");
continue;
}
neighbor_blocks_data[i] = it->second.get_chunk_blocks();
new_chunks_surface_blend_data[idx].second[i] =
it->second.get_chunk_blocks();
continue;
}
neighbor_blocks_data[i] = it->second->get_chunk_blocks();
new_chunks_surface_blend_data[idx].second[i] =
it->second->get_chunk_blocks();
}
}
chunks.gen_phase_six(neighbor_blocks_data);
}
for (auto& [pos, chunks] : new_chunks) {
chunks.gen_phase_seven();
}
std::for_each(
std::execution::par, new_chunks_surface_blend_data.begin(),
new_chunks_surface_blend_data.end(),
[](std::pair<Chunk*, OptionalBlockVectorArray>& new_chunk_data) {
auto& [chunk, neighbor_data] = new_chunk_data;
chunk->gen_phase_six(neighbor_data);
});
m_chunk_gen_fraction = 0.55f;
std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_seven();
});
m_chunk_gen_fraction = 0.6f;
std::array<const std::vector<BlockType>*, 4> neighbor_block;
for (auto& [pos, chunk] : new_chunks) {
std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
new_chunk_vertices_data(new_chunks.size());
for (size_t idx = 0; idx < new_chunks.size(); idx++) {
auto& [pos, chunk] = new_chunks[idx];
new_chunk_vertices_data[idx].first = &chunk;
for (int i = 0; i < 4; i++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
if (it != new_chunks_neighbor.end()) {
neighbor_block[i] = &(it->second->get_chunk_blocks());
new_chunk_vertices_data[idx].second[i] =
(it->second->get_chunk_blocks());
} else {
neighbor_block[i] = nullptr;
new_chunk_vertices_data[idx].second[i] = std::nullopt;
}
}
chunk.gen_vertex_data(neighbor_block);
}
std::for_each(
std::execution::par, new_chunk_vertices_data.begin(),
new_chunk_vertices_data.end(),
[](std::pair<Chunk*, OptionalBlockVectorArray>& new_chunk_data) {
auto& [chunk, neighbor_data] = new_chunk_data;
chunk->gen_vertex_data(neighbor_data);
});
m_chunk_gen_fraction = 0.7f;
build_neighbor_context_for_affected_neighbors(affected_neighbor,
new_chunks_neighbor);
m_chunk_gen_fraction = 0.8f;
OptionalBlockVectorArray neighbor_block;
for (auto& [pos, chunk] : affected_neighbor) {
for (int i = 0; i < 4; i++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
if (it != new_chunks_neighbor.end()) {
neighbor_block[i] = &(it->second->get_chunk_blocks());
neighbor_block[i] = (it->second->get_chunk_blocks());
} else {
neighbor_block[i] = nullptr;
neighbor_block[i] = std::nullopt;
}
}
chunk->gen_vertex_data(neighbor_block);
@@ -559,7 +601,7 @@ void World::sync_player_pos(glm::vec3& player_pos) {
}
void World::compute_required_chunks(
ChunkPosSet& required_chunks, ChunkHashMap& temp_neighbor,
ChunkPosSet& required_chunks, ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos) {
glm::vec3 player_pos;
sync_player_pos(player_pos);
@@ -567,33 +609,44 @@ void World::compute_required_chunks(
int x = std::floor(player_pos.x);
int z = std::floor(player_pos.z);
auto [chunk_x, chunk_z] = chunk_pos(x, z);
int radius = m_rendering_distance;
int r2 = radius * radius;
required_chunks.reserve(radius * radius);
required_chunks.reserve(m_rendering_distance * m_rendering_distance);
int half = m_rendering_distance / 2;
for (int u = chunk_x - half; u <= chunk_x + half; ++u) {
for (int v = chunk_z - half; v <= chunk_z + half; ++v) {
required_chunks.emplace(u, v);
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
}
}
int new_half = half + 1;
for (int u = chunk_x - new_half; u <= chunk_x + new_half; ++u) {
for (int v = chunk_z - new_half; v <= chunk_z + new_half; ++v) {
auto it = required_chunks.find({u, v});
}
int new_radius = radius + 1;
int new_r2 = new_radius * new_radius;
for (int dx = -new_radius; dx <= new_radius; ++dx) {
for (int dz = -new_radius; dz <= new_radius; ++dz) {
if (dx * dx + dz * dz <= new_r2) {
int nx = chunk_x + dx;
int nz = chunk_z + dz;
auto it = required_chunks.find({nx, nz});
if (it == required_chunks.end()) {
need_gen_temp_chunks_pos.push_back({u, v});
need_gen_temp_chunks_pos.push_back({nx, nz});
}
}
}
}
int max_path_len = std::max(CavePath::step_max(), RiverPath::step_max());
half = std::ceil(static_cast<float>(max_path_len) / CHUNK_SIZE) * 2;
for (int u = chunk_x - half; u <= chunk_x + half; ++u) {
for (int v = chunk_z - half; v <= chunk_z + half; ++v) {
ChunkPos pos{u, v};
radius = max_path_len / 2;
r2 = radius * radius;
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
ChunkPos pos{chunk_x + dx, chunk_z + dz};
auto it = required_chunks.find(pos);
if (it != required_chunks.end()) {
continue;
}
temp_neighbor.emplace(pos, Chunk(*this, pos));
temp_neighbor.emplace_back(pos, Chunk(*this, pos));
}
}
}
}
@@ -620,7 +673,7 @@ void World::sync_and_collect_missing_chunks(
void World::build_neighbor_context_for_new_chunks(
ConstChunkMap& new_chunks_neighbor, ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks) {
const ChunkPairVector& new_chunks) {
{
std::lock_guard lk(m_chunks_mutex);
for (auto& [pos, chunk] : new_chunks) {
@@ -865,13 +918,13 @@ void World::update(float delta_time) {
for (auto& [pos, chunk] : m_chunks) {
if (chunk.is_dirty()) {
// the curial fator influence
std::array<const std::vector<BlockType>*, 4> neighbor_block;
OptionalBlockVectorArray neighbor_block;
for (int i = 0; i < 4; i++) {
auto it = m_chunks.find(pos + CHUNK_DIR[i]);
if (it != m_chunks.end()) {
neighbor_block[i] = &(it->second.get_chunk_blocks());
neighbor_block[i] = (it->second.get_chunk_blocks());
} else {
neighbor_block[i] = nullptr;
neighbor_block[i] = std::nullopt;
}
}
chunk.gen_vertex_data(neighbor_block);

View File

@@ -5,8 +5,15 @@ namespace Cubed {
Random::Random() {}
Random::Random(unsigned seed) { init(seed); }
bool Random::random_bool(double probability) {
std::bernoulli_distribution dist(probability);
return dist(m_engine);
if (probability <= 0.0)
return false;
if (probability >= 1.0)
return true;
const double MAX_VAL = 4294967295.0;
unsigned threshold = static_cast<unsigned>(probability * MAX_VAL);
return m_engine() <= threshold;
}
std::mt19937& Random::engine() { return m_engine; }
@@ -18,12 +25,23 @@ void Random::init(unsigned seed) {
m_engine.seed(seed);
}
int Random::random_int(int min, int max) {
std::uniform_int_distribution<int> dist(min, max);
return dist(m_engine);
unsigned range = static_cast<unsigned>(max - min) + 1;
const unsigned LIMIT =
(std::numeric_limits<unsigned>::max() / range) * range;
unsigned r;
do {
r = m_engine();
} while (r >= LIMIT);
return min + static_cast<int>(r % range);
}
float Random::random_float(float min, float max) {
std::uniform_real_distribution<float> dist(min, max);
return dist(m_engine);
}
unsigned r = m_engine() >> 8;
float t = static_cast<float>(r) * (1.0f / 16777216.0f);
float result = min + t * (max - min);
return result;
}
} // namespace Cubed