refactor: cave and river (#22)

* refactor(chunk): add ChunkInfo and switch to shared_mutex for chunk access

Introduced ChunkInfo struct to expose chunk metadata (position, seed, biome). Replaced std::mutex with std::shared_mutex for chunk map to allow concurrent read access. Added World::get_chunk_info() method. Temporarily disabled cave/river cleanup and debug biome reporting.

* refactor(cave,river,chunk): use ChunkPos as key for paths and track cave existence

* refactor(gameplay): use deterministic origin-based cave and river generation

Compute cave and river paths on-the-fly per chunk from a deterministic origin instead of storing them globally. Remove concurrent hash map storage, shared mutexes, and related cleanup methods. This simplifies concurrency and reduces memory overhead.

* refactor(world): remove unused chunk generation progress tracking
This commit is contained in:
zhenyan121
2026-06-22 16:43:22 +08:00
committed by GitHub
parent 7ffc349eb3
commit 7ecdab08fc
19 changed files with 318 additions and 374 deletions

View File

@@ -2,7 +2,9 @@
#include "Cubed/app.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/tools/log.hpp"
#include <imgui.h>
@@ -33,8 +35,8 @@ constexpr int AMPLITUDE_MAX = 80;
constexpr float TREE_FREQ_MIM = 0.001f;
constexpr float TREE_FREQ_MAX = 0.3f;
constexpr float PATH_PROBABILITY_MIN = 0.005f;
constexpr float PATH_PROBABILITY_MAX = 0.1f;
// constexpr float PATH_PROBABILITY_MIN = 0.005f;
// constexpr float PATH_PROBABILITY_MAX = 0.1f;
constexpr float RADIUS_XZ_MIN = 1.0f;
constexpr float RADIUS_XZ_MAX = 50.0f;
constexpr float RADIUS_Y_MIN = 1.0f;
@@ -291,11 +293,11 @@ void DevPanel::show_time_table_bar() {
}
void DevPanel::show_cave_table_bar() {
auto& cave_carcer = m_app.world().cave_carcer();
// auto& cave_carcer = m_app.world().cave_carcer();
ImGui::Text("Total Cave Sum %d", cave_carcer.cave_sum());
ImGui::SliderFloat("Cave Probability", &cave_carcer.cave_probability(),
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
// ImGui::Text("Total Cave Sum %d", cave_carcer.cave_sum());
// ImGui::SliderFloat("Cave Probability", &cave_carcer.cave_probability(),
// PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
ImGui::SliderFloat("Radius XZ Min", &CavePath::radius_xz_min(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius XZ Max", &CavePath::radius_xz_max(),
@@ -315,11 +317,11 @@ void DevPanel::show_cave_table_bar() {
}
void DevPanel::show_river_table_bar() {
auto& river_wrom = m_app.world().river_worm();
// auto& river_wrom = m_app.world().river_worm();
ImGui::Text("Total River Sum %d", river_wrom.river_sum());
ImGui::SliderFloat("River Probability", &river_wrom.river_probability(),
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
// ImGui::Text("Total River Sum %d", river_wrom.river_sum());
// ImGui::SliderFloat("River Probability", &river_wrom.river_probability(),
// PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
ImGui::SliderFloat("Radius XZ Min##river", &RiverPath::radius_xz_min(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius XZ Max##river", &RiverPath::radius_xz_max(),
@@ -338,6 +340,20 @@ void DevPanel::show_river_table_bar() {
PATH_STEP_MAX);
}
void DevPanel::show_chunk_table_bar() {
auto& world = m_app.world();
auto& player = world.get_player("TestPlayer");
auto info = world.get_chunk_info(player.get_player_pos());
ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z);
ImGui::Text("Seed: %u", info.seed);
ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str());
ImGui::Text("First Random %u", info.first_random);
ImGui::Text("%s",
std::format("Has Cave Start {}", info.has_cave_start).c_str());
ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str());
}
void DevPanel::show_settings_tab_item() {
if (ImGui::BeginTabItem("settings")) {
if (ImGui::SliderFloat("FOV", &m_config.fov, 1.0f, 140.0f)) {
@@ -505,13 +521,15 @@ void DevPanel::show_world_tab_item() {
m_app.world().stop_gen_thread();
}
}
ImGui::Text("Chunk Build Progress\n");
ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
// ImGui::Text("Chunk Build Progress\n");
// ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
show_chunk_table_bar();
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
show_time_table_bar();
ImGui::EndTabItem();
}
/*
if (ImGui::BeginTabItem("Cave")) {
show_cave_table_bar();
ImGui::EndTabItem();
@@ -519,11 +537,12 @@ void DevPanel::show_world_tab_item() {
if (ImGui::BeginTabItem("River")) {
show_river_table_bar();
ImGui::EndTabItem();
}
}*/
if (ImGui::BeginTabItem("Biome")) {
show_biome_table_bar();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::EndTabItem();

View File

@@ -1,67 +1,55 @@
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/cubed_random.hpp"
namespace Cubed {
CaveCarver::CaveCarver() {}
CaveCarver::CaveHashMap& CaveCarver::paths() { return m_paths; }
void CaveCarver::init(unsigned world_seed) {
m_seed = world_seed;
m_random.init(m_seed);
}
void CaveCarver::init(unsigned world_seed) { m_world_seed = world_seed; }
void CaveCarver::reload(unsigned world_seed) {
m_seed = world_seed;
m_paths.clear();
m_world_seed = world_seed;
init(world_seed);
}
void CaveCarver::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, CavePath{chunk_seed, m_seed, pos});
bool CaveCarver::has_origin_fast(const ChunkPos& pos) const {
unsigned h = HASH::combine_32(HASH::combine_32(pos.x, pos.z), m_world_seed);
return (h & 0xFFFF) < static_cast<unsigned>(m_cave_probability * 0xFFFF);
}
void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
{
CaveHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
PathOrigin CaveCarver::get_origin(const ChunkPos& origin_chunk) const {
// Quickly check if there is an origin point without constructing Random
if (!has_origin_fast(origin_chunk)) {
return {false, {}, 0};
}
unsigned chunk_seed =
HASH::chunk_seed_hash(origin_chunk.x, origin_chunk.z, m_world_seed);
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_cave_probability))) {
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;
int max_y = std::min(CHUNK_MAX_Y, 40);
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
int y = random.random_int(CHUNK_MIN_Y + 1, max_y);
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
add_path(glm::vec3{x, y, z}, chunk_seed);
}
const int CHUNK_MIN_X = origin_chunk.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = origin_chunk.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;
int max_y = std::min(CHUNK_MAX_Y, 40);
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
int y = random.random_int(CHUNK_MIN_Y + 1, max_y);
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
return {true, {x, y, z}, chunk_seed};
}
void CaveCarver::cleanup_finished_caves() {
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::search_radius() const {
float max_displacement =
3.0f * std::sqrt(static_cast<float>(CavePath::step_max())) *
CavePath::step_len();
return static_cast<int>(
std::ceil((max_displacement + CavePath::radius_xz_max()) / CHUNK_SIZE));
}
int CaveCarver::cave_sum() const { return m_paths.size(); }
float& CaveCarver::cave_probability() { return m_cave_probability; }
std::shared_mutex& CaveCarver::path_mutex() { return m_path_mutex; }
unsigned CaveCarver::world_seed() const { return m_world_seed; }
float CaveCarver::cave_probability() const { return m_cave_probability; }
} // namespace Cubed

View File

@@ -1,6 +1,5 @@
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/math_tools.hpp"
@@ -21,15 +20,12 @@ CavePath::CavePath(unsigned int chunk_seed, unsigned world_seed,
m_points.reserve(m_step + 1);
m_points.push_back(m_start_path_point);
collect_path_points();
precompute_chunk_coverage();
}
void CavePath::collect_path_points() {
for (int i = 0; i < m_step; i++) {
m_yaw = std::fmod(m_yaw, 360.0f);
if (m_yaw < 0.0f)
m_yaw += 360.0f;
m_pitch = std::clamp(m_pitch, -90.0f, 90.0f);
float dx = std::cos(glm::radians(m_pitch)) *
@@ -58,30 +54,7 @@ void CavePath::collect_path_points() {
}
}
void CavePath::precompute_chunk_coverage() {
for (const auto& point : m_points) {
float rad = point.rad_xz;
const glm::vec3& center = point.pos;
int min_cx =
static_cast<int>(std::floor((center.x - rad) / CHUNK_SIZE));
int max_cx =
static_cast<int>(std::floor((center.x + rad) / CHUNK_SIZE));
int min_cz =
static_cast<int>(std::floor((center.z - rad) / CHUNK_SIZE));
int max_cz =
static_cast<int>(std::floor((center.z + rad) / CHUNK_SIZE));
for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
}
}
void CavePath::clear_chunk(const ChunkPos& pos) { m_pending_chunks.erase(pos); }
const std::vector<PathPoint>& CavePath::points() const { return m_points; }
bool CavePath::is_finished() const { return m_pending_chunks.empty(); }
float& CavePath::radius_xz_min() { return m_radius_xz_min; }
float& CavePath::radius_xz_max() { return m_radius_xz_max; }
@@ -91,5 +64,5 @@ float& CavePath::delta_angle_min() { return m_delta_angle_min; }
float& CavePath::delta_angle_max() { return m_delta_angle_max; }
int& CavePath::step_min() { return m_step_min; }
int& CavePath::step_max() { return m_step_max; }
int CavePath::step_len() { return m_step_len; }
} // namespace Cubed

View File

@@ -24,7 +24,7 @@ Chunk::Chunk(Chunk&& other) noexcept
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
m_blocks(std::move(other.m_blocks)),
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed),
m_conditions(other.m_conditions) {}
m_conditions(other.m_conditions), m_info(std::move(other.m_info)) {}
Chunk& Chunk::operator=(Chunk&& other) noexcept {
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
@@ -41,6 +41,7 @@ Chunk& Chunk::operator=(Chunk&& other) noexcept {
m_need_upload = other.m_need_upload.load();
m_seed = other.m_seed;
m_conditions = other.m_conditions;
m_info = std::move(other.m_info);
return *this;
}
@@ -269,6 +270,13 @@ unsigned Chunk::seed() const {
BiomeConditions& Chunk::conditions() { return m_conditions; }
ChunkInfo Chunk::get_info() const {
if (m_gening) {
return ChunkInfo{};
}
return m_info;
}
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}};
@@ -488,9 +496,22 @@ void Chunk::gen_chunk() {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
gen_vertex_data(neightbor_blocks);
// collect chunk info for debugging
m_info.biome = m_biome;
m_info.pos = m_chunk_pos;
m_info.seed = m_seed;
Random r(m_seed);
unsigned first = r.engine()();
m_info.first_random = first;
r.init(m_seed);
m_info.has_cave_start = r.random_bool(DEFAULT_CAVE_PROBABILITY);
m_info.has_cave = m_has_cave;
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
bool Chunk::is_temp_chunk() const { return m_temp_chunk.load(); }
bool& Chunk::has_cave() { return m_has_cave; }
} // namespace Cubed

View File

@@ -7,7 +7,9 @@
#include "Cubed/gameplay/builders/plain_builder.hpp"
#include "Cubed/gameplay/builders/river_builder.hpp"
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/gameplay/tree.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
@@ -16,6 +18,84 @@
#include "Cubed/tools/perlin_noise.hpp"
namespace Cubed {
namespace {
template <typename F>
void carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
F&& 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) {
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));
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);
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) {
if (wy == 0)
continue;
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;
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;
int x = wx - CHUNK_MIN_X;
on_hit(x, wy, wz - CHUNK_MIN_Z);
}
}
}
}
}
} // namespace
using enum BiomeType;
constexpr int BLEND_RADIUS = 8;
@@ -642,94 +722,27 @@ void ChunkGenerator::make_biome_builder() {
void ChunkGenerator::ocean_build() { m_biome_builder->ocean_water_build(); }
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) {
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));
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);
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) {
if (wy == 0)
continue;
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;
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;
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();
{
std::shared_lock lock(cave_carver.path_mutex());
for (auto& [id, path] : paths) {
auto& carver = m_chunk.world().cave_carcer();
int search_r = carver.search_radius();
for (int dx = -search_r; dx <= search_r; dx++) {
for (int dz = -search_r; dz <= search_r; dz++) {
ChunkPos origin_pos{chunk_pos.x + dx, chunk_pos.z + dz};
auto origin = carver.get_origin(origin_pos);
if (!origin.exists)
continue;
// Deterministically reconstruct this path (lightweight: only
// compute points, no storage).
CavePath path{origin.seed, carver.world_seed(), origin.pos};
carve_worm(path.points(), chunk_pos,
[&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
m_chunk.has_cave() = true;
if (blocks[idx] == 7)
return;
if (y < WORLD_SIZE_Y - 1 &&
@@ -737,30 +750,34 @@ void ChunkGenerator::generate_cave() {
return;
blocks[idx] = 0;
});
if (!m_chunk.is_temp_chunk()) {
path.clear_chunk(chunk_pos);
}
}
}
}
void ChunkGenerator::generate_river() {
if ((m_chunk.biome() == BiomeType::DESERT) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
return;
}
auto& river_worm = m_chunk.world().river_worm();
auto& paths = river_worm.paths();
const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks();
bool is_river = false;
{
std::shared_lock lock(river_worm.paths_mutex());
for (auto& [id, path] : paths) {
if ((m_chunk.biome() == BiomeType::DESERT) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
if (!m_chunk.is_temp_chunk()) {
path.clear_chunk(chunk_pos);
}
int search_r = river_worm.search_radius();
for (int dx = -search_r; dx <= search_r; dx++) {
for (int dz = -search_r; dz <= search_r; dz++) {
ChunkPos origin_pos{chunk_pos.x + dx, chunk_pos.z + dz};
auto origin = river_worm.get_origin(origin_pos);
if (!origin.exists)
continue;
}
// Deterministically reconstruct this path (lightweight: only
// compute points, no storage).
RiverPath path{origin.seed, river_worm.world_seed(), origin.pos};
carve_worm(path.points(), chunk_pos,
[&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
@@ -774,9 +791,6 @@ void ChunkGenerator::generate_river() {
}
blocks[idx] = 7;
});
if (!m_chunk.is_temp_chunk()) {
path.clear_chunk(chunk_pos);
}
}
}

View File

@@ -251,13 +251,6 @@ void Player::check_player_chunk_transition() {
if (cur_pos != m_player_chunk_pos) {
m_world.need_gen();
m_player_chunk_pos = cur_pos;
auto chunk = m_world.get_chunk(cur_pos);
if (chunk == nullptr) {
DebugCollector::get().report("biome", "Biome: Unknown");
} else {
DebugCollector::get().report(
"biome", "Biome: " + get_biome_str(chunk->get_biome()));
}
}
}

View File

@@ -1,4 +1,3 @@
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/math_tools.hpp"
@@ -24,16 +23,11 @@ RiverPath::RiverPath(unsigned int chunk_seed, unsigned world_seed,
m_points.reserve(m_step + 1);
m_points.push_back(m_start_path_point);
collect_path_points();
precompute_chunk_coverage();
}
void RiverPath::collect_path_points() {
for (int i = 0; i < m_step; i++) {
m_yaw = std::fmod(m_yaw, 360.0f);
if (m_yaw < 0.0f)
m_yaw += 360.0f;
float dx = std::cos(glm::radians(m_pitch)) *
std::sin(glm::radians(m_yaw)) * m_step_len;
float dy = std::sin(glm::radians(m_pitch)) * m_step_len;
@@ -60,33 +54,7 @@ void RiverPath::collect_path_points() {
}
}
void RiverPath::precompute_chunk_coverage() {
for (const auto& point : m_points) {
float rad = point.rad_xz;
const glm::vec3& center = point.pos;
int min_cx =
static_cast<int>(std::floor((center.x - rad) / CHUNK_SIZE));
int max_cx =
static_cast<int>(std::floor((center.x + rad) / CHUNK_SIZE));
int min_cz =
static_cast<int>(std::floor((center.z - rad) / CHUNK_SIZE));
int max_cz =
static_cast<int>(std::floor((center.z + rad) / CHUNK_SIZE));
for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
}
}
void RiverPath::clear_chunk(const ChunkPos& pos) {
m_pending_chunks.erase(pos);
}
const std::vector<PathPoint>& RiverPath::points() const { return m_points; }
bool RiverPath::is_finished() const { return m_pending_chunks.empty(); }
float& RiverPath::radius_xz_min() { return m_radius_xz_min; }
float& RiverPath::radius_xz_max() { return m_radius_xz_max; }
float& RiverPath::radius_y_min() { return m_radius_y_min; }
@@ -95,4 +63,5 @@ float& RiverPath::delta_angle_min() { return m_delta_angle_min; }
float& RiverPath::delta_angle_max() { return m_delta_angle_max; }
int& RiverPath::step_min() { return m_step_min; }
int& RiverPath::step_max() { return m_step_max; }
float RiverPath::step_len() { return m_step_len; }
} // namespace Cubed

View File

@@ -1,65 +1,59 @@
#include "Cubed/gameplay/river_worm.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/tools/cubed_hash.hpp"
namespace Cubed {
RiverWorm::RiverWorm() {}
RiverWorm::~RiverWorm() {}
RiverWorm::RiverHashMap& RiverWorm::paths() { return m_paths; }
void RiverWorm::init(unsigned world_seed) {
m_seed = world_seed;
m_world_seed = world_seed;
m_random.init(m_seed);
m_random.init(m_world_seed);
}
void RiverWorm::reload(unsigned world_seed) {
m_seed = world_seed;
m_paths.clear();
m_world_seed = world_seed;
init(world_seed);
}
void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, RiverPath{chunk_seed, m_seed, pos});
bool RiverWorm::has_origin_fast(const ChunkPos& pos) const {
unsigned h = HASH::combine_32(HASH::combine_32(pos.x, pos.z), m_world_seed);
return (h & 0xFFFF) < static_cast<unsigned>(m_probability * 0xFFFF);
}
void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
{
RiverHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
PathOrigin RiverWorm::get_origin(const ChunkPos& origin_chunk) const {
// Quickly check if there is an origin point without constructing Random
if (!has_origin_fast(origin_chunk)) {
return {false, {}, 0};
}
unsigned chunk_seed =
HASH::chunk_seed_hash(origin_chunk.x, origin_chunk.z, m_world_seed);
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_probability))) {
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;
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
int y = SEA_LEVEL + 2;
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
add_path(glm::vec3{x, y, z}, chunk_seed);
}
const int CHUNK_MIN_X = origin_chunk.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = origin_chunk.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;
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
int y = SEA_LEVEL + 2;
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
return {true, {x, y, z}, chunk_seed};
}
void RiverWorm::cleanup_finished_rivers() {
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::search_radius() const {
float max_displacement =
3.0f * std::sqrt(static_cast<float>(RiverPath::step_max())) *
RiverPath::step_len();
return static_cast<int>(std::ceil(
(max_displacement + RiverPath::radius_xz_max()) / CHUNK_SIZE));
}
int RiverWorm::river_sum() const { return m_paths.size(); }
float& RiverWorm::river_probability() { return m_probability; }
std::shared_mutex& RiverWorm::paths_mutex() { return m_paths_mutex; }
unsigned RiverWorm::world_seed() const { return m_world_seed; }
float RiverWorm::river_probability() const { return m_probability; }
} // namespace Cubed

View File

@@ -63,7 +63,7 @@ World::get_look_block_pos(const std::string& name) const {
return it->second.get_look_block_pos();
}
/*
const Chunk* World::get_chunk(const ChunkPos& pos) const {
std::lock_guard lk(m_chunks_mutex);
auto it = m_chunks.find(pos);
@@ -71,7 +71,7 @@ const Chunk* World::get_chunk(const ChunkPos& pos) const {
return nullptr;
}
return &it->second;
}
}*/
Player& World::get_player(const std::string& name) {
auto it = m_players.find(HASH::str(name));
@@ -133,13 +133,10 @@ ChunkPos World::get_chunk_pos(int world_x, int world_z) {
void World::gen_chunks_internal() {
// Logger::info("gen_chunks_internal");
m_chunk_gen_fraction = 0.0f;
m_chunk_gen_finished = false;
ChunkPosSet required_chunks;
ChunkPairVector temp_neighbor;
compute_required_chunks(required_chunks, temp_neighbor);
compute_required_chunks(required_chunks);
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
@@ -148,43 +145,17 @@ void World::gen_chunks_internal() {
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
Logger::info("Temp Chunks sum {}", temp_neighbor.size());
if (need_gen_chunks_pos.empty()) {
m_could_gen = true;
m_chunk_gen_fraction = 1.0f;
return;
}
m_chunk_gen_fraction = 0.1f;
for (auto& pos : need_gen_chunks_pos) {
new_chunks.emplace(pos, Chunk(*this, pos));
}
auto t1 = system_clock::now();
{
std::scoped_lock lock{m_cave_carcer.path_mutex(),
m_river_worm.paths_mutex()};
auto pool_ptr = m_gen_thread_pool.load();
if (!pool_ptr) {
return;
}
parallel_do(*pool_ptr, temp_neighbor.begin(), temp_neighbor.end(),
pool_ptr->thread_sum(),
[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_cave_carcer.cleanup_finished_caves();
m_river_worm.cleanup_finished_rivers();
}
auto t2 = system_clock::now();
Logger::info("Temp Neighbor Add Path Consum {}",
duration_cast<milliseconds>(t2 - t1));
m_chunk_gen_fraction = 0.9f;
m_chunk_gen_fraction = 1.0f;
submit_new_chunks();
m_chunk_gen_finished = true;
}
@@ -194,8 +165,7 @@ void World::sync_player_pos(glm::vec3& player_pos) {
player_pos = m_gen_player_pos;
}
void World::compute_required_chunks(ChunkPosSet& required_chunks,
ChunkPairVector& temp_neighbor) {
void World::compute_required_chunks(ChunkPosSet& required_chunks) {
glm::vec3 player_pos;
sync_player_pos(player_pos);
@@ -213,17 +183,6 @@ void World::compute_required_chunks(ChunkPosSet& required_chunks,
}
}
}
int max_path_len = std::max(CavePath::step_max(), RiverPath::step_max());
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};
temp_neighbor.emplace_back(pos, Chunk(*this, pos));
}
}
}
}
void World::sync_and_collect_missing_chunks(
@@ -407,7 +366,7 @@ void World::need_gen() {
int World::get_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::lock_guard lk(m_chunks_mutex);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
@@ -425,7 +384,7 @@ int World::get_block(const glm::ivec3& block_pos) const {
bool World::is_solid(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::lock_guard lk(m_chunks_mutex);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
@@ -447,7 +406,7 @@ bool World::is_solid(const glm::ivec3& block_pos) const {
bool World::can_pass_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::lock_guard lk(m_chunks_mutex);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
@@ -465,7 +424,7 @@ bool World::can_pass_block(const glm::ivec3& block_pos) const {
BlockType World::get_block_tpye(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::lock_guard lk(m_chunks_mutex);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
@@ -629,7 +588,7 @@ void World::rebuild_world() {
m_cave_carcer.reload(ChunkGenerator::seed());
m_river_worm.reload(ChunkGenerator::seed());
{
std::scoped_lock lk(m_chunks_mutex);
std::lock_guard lk(m_chunks_mutex);
m_chunks.clear();
m_new_finished_chunk.clear();
}
@@ -676,8 +635,6 @@ glm::vec3 World::sunlight_dir() const {
return glm::normalize(-dir);
}
float World::chunk_gen_fraction() const { return m_chunk_gen_fraction.load(); }
int World::rendering_distance() const { return m_rendering_distance.load(); }
void World::rendering_distance(int rendering_distance) {
@@ -732,4 +689,15 @@ void World::set_chunk_load_style(int id) {
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
}
ChunkInfo World::get_chunk_info(const glm::vec3& world_pos) const {
ChunkPos pos = get_chunk_pos(world_pos.x, world_pos.z);
std::shared_lock lock(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
return ChunkInfo{};
}
return it->second.get_info();
}
} // namespace Cubed