From bab2f73c25aec6cf45d48cc21a5c1949a76a18f2 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Fri, 8 May 2026 19:42:38 +0800 Subject: [PATCH] feat: add cave generate --- CMakeLists.txt | 2 + include/Cubed/gameplay/cave_carver.hpp | 22 ++++++ include/Cubed/gameplay/cave_path.hpp | 67 ++++++++++++++++++ include/Cubed/gameplay/chunk.hpp | 1 + include/Cubed/gameplay/chunk_generator.hpp | 1 + include/Cubed/gameplay/world.hpp | 5 ++ include/Cubed/tools/cubed_hash.hpp | 4 ++ include/Cubed/tools/cubed_random.hpp | 2 + include/Cubed/tools/math_tools.hpp | 5 +- src/dev_panel.cpp | 4 +- src/gameplay/cave_carver.cpp | 54 ++++++++++++++ src/gameplay/cave_path.cpp | 82 ++++++++++++++++++++++ src/gameplay/chunk.cpp | 1 + src/gameplay/chunk_generator.cpp | 58 ++++++++++++++- src/gameplay/player.cpp | 5 +- src/gameplay/world.cpp | 10 ++- src/tools/cubed_random.cpp | 10 ++- src/tools/math_tools.cpp | 10 ++- 18 files changed, 332 insertions(+), 11 deletions(-) create mode 100644 include/Cubed/gameplay/cave_carver.hpp create mode 100644 include/Cubed/gameplay/cave_path.hpp create mode 100644 src/gameplay/cave_carver.cpp create mode 100644 src/gameplay/cave_path.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b7ab7d..a6fdcfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,8 @@ add_executable(${PROJECT_NAME} src/gameplay/builders/river_builder.cpp src/gameplay/builders/desert_builder.cpp src/gameplay/builders/forest_builder.cpp + src/gameplay/cave_carver.cpp + src/gameplay/cave_path.cpp ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/include/Cubed/gameplay/cave_carver.hpp b/include/Cubed/gameplay/cave_carver.hpp new file mode 100644 index 0000000..7ba4928 --- /dev/null +++ b/include/Cubed/gameplay/cave_carver.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "Cubed/gameplay/cave_path.hpp" +namespace Cubed { +class CaveCarver { +public: + CaveCarver(); + std::unordered_map& paths(); + void init(unsigned world_seed); + void reload(unsigned world_seed); + void add_path(const glm::vec3& pos); + void try_to_add_path(const ChunkPos& pos); + void cleanup_finished_caves(); + + int cave_sum() const; + +private: + std::unordered_map m_paths; + unsigned m_seed = 0; + int m_sum = 0; + Random m_random; +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/cave_path.hpp b/include/Cubed/gameplay/cave_path.hpp new file mode 100644 index 0000000..3c61a0c --- /dev/null +++ b/include/Cubed/gameplay/cave_path.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/tools/cubed_random.hpp" + +#include +#include +namespace Cubed { + +struct PathPoint { + glm::vec3 pos; + glm::vec3 tangent{0.0f, 0.0f, 1.0f}; + float rad_xz; + float rad_y; + PathPoint(const glm::vec3& p, float rx, float ry) + : pos(p), rad_xz(rx), rad_y(ry) {} + bool contains(const glm::vec3& other_pos) const { + glm::vec3 to_point = other_pos - pos; + + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + + glm::vec3 right = glm::normalize(glm::cross(tangent, world_up)); + + if (glm::length(right) < 0.001f) { + glm::vec3 alt_up(1.0f, 0.0f, 0.0f); + right = glm::normalize(glm::cross(tangent, alt_up)); + } + + glm::vec3 up = glm::normalize(glm::cross(right, tangent)); + + float horizontal_dist = glm::dot(to_point, right); + float vertical_dist = glm::dot(to_point, up); + + float a = rad_xz; + float b = rad_y; + if (a <= 0.0f || b <= 0.0f) + return false; + + float check = (horizontal_dist * horizontal_dist) / (a * a) + + (vertical_dist * vertical_dist) / (b * b); + return check <= 1.0f; + } +}; + +class CavePath { +public: + CavePath(unsigned int world_seed, int path_id, const glm::vec3& start_pos); + const std::vector& points() const; + void clear_chunk(const ChunkPos& pos); + bool is_finished() const; + +private: + int m_path_id = 0; + unsigned int m_seed = 0; + float m_yaw = 0.0f; + float m_pitch = 0.0f; + int m_max_step = 0; + float m_step_len = 1.0f; + PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f}; + Random m_random; + + std::vector m_points; + std::unordered_set m_pending_chunks; + void collect_path_points(); + void precompute_chunk_coverage(); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/chunk.hpp b/include/Cubed/gameplay/chunk.hpp index 9bd93d0..23b1603 100644 --- a/include/Cubed/gameplay/chunk.hpp +++ b/include/Cubed/gameplay/chunk.hpp @@ -98,6 +98,7 @@ public: void biome(BiomeType b); HeightMapArray& heightmap(); std::vector& blocks(); + World& world(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/chunk_generator.hpp b/include/Cubed/gameplay/chunk_generator.hpp index 0cfc93d..f57eead 100644 --- a/include/Cubed/gameplay/chunk_generator.hpp +++ b/include/Cubed/gameplay/chunk_generator.hpp @@ -55,6 +55,7 @@ private: bool is_cur_chunk_ins = false; std::array m_neighbor_biome; void make_biome_builder(); + void generate_cave(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/world.hpp b/include/Cubed/gameplay/world.hpp index b338389..1a6de5c 100644 --- a/include/Cubed/gameplay/world.hpp +++ b/include/Cubed/gameplay/world.hpp @@ -1,5 +1,6 @@ #pragma once #include "Cubed/AABB.hpp" +#include "Cubed/gameplay/cave_carver.hpp" #include "Cubed/gameplay/chunk.hpp" #include @@ -53,6 +54,8 @@ private: std::vector> m_new_chunk; std::vector> m_new_chunk_queue; + CaveCarver m_cave_carcer; + void init_chunks(); void gen_chunks_internal(); @@ -106,6 +109,8 @@ public: void rendering_distance(int rendering_distance); void start_gen_thread(); void stop_gen_thread(); + + CaveCarver& cave_carcer(); }; } // namespace Cubed diff --git a/include/Cubed/tools/cubed_hash.hpp b/include/Cubed/tools/cubed_hash.hpp index b45061a..4981b8a 100644 --- a/include/Cubed/tools/cubed_hash.hpp +++ b/include/Cubed/tools/cubed_hash.hpp @@ -27,6 +27,10 @@ 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 \ No newline at end of file diff --git a/include/Cubed/tools/cubed_random.hpp b/include/Cubed/tools/cubed_random.hpp index de1b83f..9e90ad0 100644 --- a/include/Cubed/tools/cubed_random.hpp +++ b/include/Cubed/tools/cubed_random.hpp @@ -11,6 +11,8 @@ public: unsigned seed(); void init(unsigned seed); + int random_int(int min, int max); + float random_float(float min, float max); private: unsigned int m_seed = 0; diff --git a/include/Cubed/tools/math_tools.hpp b/include/Cubed/tools/math_tools.hpp index b367560..61f20d5 100644 --- a/include/Cubed/tools/math_tools.hpp +++ b/include/Cubed/tools/math_tools.hpp @@ -4,8 +4,11 @@ namespace Cubed { namespace Math { + void extract_frustum_planes(const glm::mat4& mvp_matrix, std::vector& planes); -} + +float smootherstep(float edge0, float edge1, float x); +} // namespace Math } // namespace Cubed \ No newline at end of file diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index ed6283b..6bd9f0d 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -354,7 +354,8 @@ void DevPanel::show_settings_tab_item() { void DevPanel::show_world_tab_item() { if (ImGui::BeginTabItem("world")) { if (m_text_editing.perlin_seed) { - if (ImGui::InputText("Perlin Noise Seed", perlin_noise_input_buffer, + if (ImGui::InputText("ChunkGenerator Seed", + perlin_noise_input_buffer, sizeof(perlin_noise_input_buffer), ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_EnterReturnsTrue, @@ -395,6 +396,7 @@ void DevPanel::show_world_tab_item() { m_app.world().stop_gen_thread(); } } + ImGui::Text("Cave Sum %d", m_app.world().cave_carcer().cave_sum()); ImGui::Text("Chunk Build Progress\n"); ImGui::ProgressBar(m_app.world().chunk_gen_fraction()); show_biome_table_bar(); diff --git a/src/gameplay/cave_carver.cpp b/src/gameplay/cave_carver.cpp new file mode 100644 index 0000000..372aef6 --- /dev/null +++ b/src/gameplay/cave_carver.cpp @@ -0,0 +1,54 @@ +#include "Cubed/gameplay/cave_carver.hpp" + +#include "Cubed/constants.hpp" + +namespace Cubed { +CaveCarver::CaveCarver() {} + +std::unordered_map& CaveCarver::paths() { return m_paths; } + +void CaveCarver::init(unsigned world_seed) { + m_seed = world_seed; + m_sum = 0; + m_paths.emplace(m_sum, + CavePath{m_seed, m_sum, glm::vec3{0.0f, 20.0f, 0.0f}}); + m_sum++; + m_random.init(m_seed); +} + +void CaveCarver::reload(unsigned world_seed) { + m_seed = world_seed; + m_paths.clear(); + init(world_seed); +} + +void CaveCarver::add_path(const glm::vec3& pos) { + m_paths.emplace(m_sum, CavePath{m_seed, m_sum, pos}); + m_sum++; +} + +void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos) { + + if (m_random.random_bool(0.05)) { + 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 = m_random.random_int(CHUNK_MIN_X, CHUNK_MAX_X); + int y = m_random.random_int(CHUNK_MIN_Y + 1, max_y); + int z = m_random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z); + add_path(glm::vec3{x, y, z}); + } +} + +void CaveCarver::cleanup_finished_caves() { + std::erase_if(m_paths, + [](const auto& kv) { return kv.second.is_finished(); }); +} + +int CaveCarver::cave_sum() const { return m_sum; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/cave_path.cpp b/src/gameplay/cave_path.cpp new file mode 100644 index 0000000..c7fb443 --- /dev/null +++ b/src/gameplay/cave_path.cpp @@ -0,0 +1,82 @@ +#include "Cubed/gameplay/cave_path.hpp" + +#include "Cubed/constants.hpp" +#include "Cubed/tools/cubed_hash.hpp" +#include "Cubed/tools/math_tools.hpp" + +#include +namespace Cubed { +CavePath::CavePath(unsigned int world_seed, int path_id, + const glm::vec3& start_pos) { + m_path_id = path_id; + m_seed = HASH::combine_32(world_seed, path_id); + 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); + m_start_path_point.pos = start_pos; + m_start_path_point.rad_xz = m_random.random_float(5.0f, 15.0f); + m_start_path_point.rad_y = m_random.random_float(4.0f, 10.0f); + m_max_step = m_random.random_int(10, 400); + m_points.reserve(m_max_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_max_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)) * + std::sin(glm::radians(m_yaw)) * m_step_len; + float dy = std::sin(glm::radians(m_pitch)) * m_step_len; + float dz = std::cos(glm::radians(m_pitch)) * + std::cos(glm::radians(m_yaw)) * m_step_len; + + m_points[i].tangent = glm::normalize(glm::vec3{dx, dy, dz}); + + float t = Math::smootherstep(0, m_max_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); + 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(-5.0f, 5.0f); + m_pitch += m_random.random_float(-5.0f, 5.0f); + } + auto n = m_points.size(); + if (n >= 2) { + m_points[n - 1].tangent = m_points[n - 2].tangent; + } +} + +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(std::floor((center.x - rad) / CHUNK_SIZE)); + int max_cx = + static_cast(std::floor((center.x + rad) / CHUNK_SIZE)); + int min_cz = + static_cast(std::floor((center.z - rad) / CHUNK_SIZE)); + int max_cz = + static_cast(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({cx, cz}); + } +} +void CavePath::clear_chunk(const ChunkPos& pos) { m_pending_chunks.erase(pos); } +const std::vector& CavePath::points() const { return m_points; } +bool CavePath::is_finished() const { return m_pending_chunks.empty(); } +} // namespace Cubed diff --git a/src/gameplay/chunk.cpp b/src/gameplay/chunk.cpp index 0fdfd0d..b933dd5 100644 --- a/src/gameplay/chunk.cpp +++ b/src/gameplay/chunk.cpp @@ -307,4 +307,5 @@ void Chunk::biome(BiomeType b) { m_biome = b; } HeightMapArray& Chunk::heightmap() { return m_heightmap; } std::vector& Chunk::blocks() { return m_blocks; } +World& Chunk::world() { return m_world; } } // namespace Cubed diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index 855207e..d9ced3b 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -7,9 +7,9 @@ #include "Cubed/gameplay/builders/river_builder.hpp" #include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/tree.hpp" +#include "Cubed/gameplay/world.hpp" #include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/perlin_noise.hpp" - namespace Cubed { using enum BiomeType; @@ -356,6 +356,7 @@ void ChunkGenerator::generate_terrain_blocks() { } m_chunk.blocks().assign(CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y, 0); m_biome_builder->build_biome(); + generate_cave(); } void ChunkGenerator::blend_surface_blocks_borders( @@ -511,6 +512,61 @@ void ChunkGenerator::make_biome_builder() { } } +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(); + 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 (auto& [id, path] : paths) { + for (const auto& point : path.points()) { + + const glm::vec3& center = point.pos; + float rad_xz = point.rad_xz; + float rad_y = point.rad_y; + + int min_x = static_cast(std::floor(center.x - rad_xz)); + int max_x = static_cast(std::floor(center.x + rad_xz)); + int min_z = static_cast(std::floor(center.z - rad_xz)); + int max_z = static_cast(std::floor(center.z + rad_xz)); + int min_y = static_cast(std::floor(center.y - rad_y)); + int max_y = static_cast(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(wx), + static_cast(wy), + static_cast(wz)); + if (point.contains(pos)) { + if (y == 0) { + continue; + } + blocks[Chunk::get_index(x, y, z)] = 0; + } + } + } + } + } + path.clear_chunk(chunk_pos); + } +} + Chunk& ChunkGenerator::chunk() { return m_chunk; } Random& ChunkGenerator::random() { return m_random; } diff --git a/src/gameplay/player.cpp b/src/gameplay/player.cpp index ab5dd5c..483d59f 100644 --- a/src/gameplay/player.cpp +++ b/src/gameplay/player.cpp @@ -237,10 +237,7 @@ void Player::update_front_vec(float offset_x, float offset_y) { m_yaw = std::fmod(m_yaw, 360.0); - if (m_pitch > 89.0f) - m_pitch = 89.0f; - if (m_pitch < -89.0f) - m_pitch = -89.0f; + m_pitch = std::clamp(m_pitch, -89.0f, 89.0f); m_front.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch)); m_front.y = sin(glm::radians(m_pitch)); diff --git a/src/gameplay/world.cpp b/src/gameplay/world.cpp index e40fa2b..1dd0a90 100644 --- a/src/gameplay/world.cpp +++ b/src/gameplay/world.cpp @@ -65,6 +65,7 @@ Player& World::get_player(const std::string& name) { } void World::init_world() { + m_cave_carcer.init(ChunkGenerator::seed()); m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); auto t1 = std::chrono::system_clock::now(); @@ -113,6 +114,7 @@ void World::init_chunks() { } } for (auto& [pos, chunks] : m_chunks) { + m_cave_carcer.try_to_add_path(pos); chunks.gen_phase_one(); } for (auto& [pos, chunks] : temp_neighbor) { @@ -251,6 +253,8 @@ void World::init_chunks() { sync.store(1, std::memory_order_release); sync.load(std::memory_order_acquire); + m_cave_carcer.cleanup_finished_caves(); + std::vector pending_gen_data; pending_gen_data.reserve(m_chunks.size()); for (auto& [pos, chunk] : m_chunks) { @@ -355,6 +359,7 @@ 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) { + m_cave_carcer.try_to_add_path(pos); chunk.gen_phase_one(); } for (auto& [pos, chunk] : temp_neighbor) { @@ -500,6 +505,7 @@ void World::gen_chunks_internal() { m_new_chunk_queue.emplace_back(std::move(x)); } } + m_cave_carcer.cleanup_finished_caves(); m_chunk_gen_fraction = 1.0f; } @@ -819,7 +825,7 @@ void World::rebuild_world() { } m_is_rebuilding = true; stop_gen_thread(); - + m_cave_carcer.reload(ChunkGenerator::seed()); { std::scoped_lock lk(m_chunks_mutex, m_new_chunk_queue_mutex); m_chunks.clear(); @@ -841,4 +847,6 @@ void World::rendering_distance(int rendering_distance) { m_rendering_distance = rendering_distance; } +CaveCarver& World::cave_carcer() { return m_cave_carcer; } + } // namespace Cubed \ No newline at end of file diff --git a/src/tools/cubed_random.cpp b/src/tools/cubed_random.cpp index ae2b017..424223c 100644 --- a/src/tools/cubed_random.cpp +++ b/src/tools/cubed_random.cpp @@ -1,7 +1,5 @@ #include "Cubed/tools/cubed_random.hpp" -#include "Cubed/tools/log.hpp" - namespace Cubed { Random::Random() {} @@ -19,5 +17,13 @@ void Random::init(unsigned seed) { m_seed = seed; m_engine.seed(seed); } +int Random::random_int(int min, int max) { + std::uniform_int_distribution dist(min, max); + return dist(m_engine); +} +float Random::random_float(float min, float max) { + std::uniform_real_distribution dist(min, max); + return dist(m_engine); +} } // namespace Cubed \ No newline at end of file diff --git a/src/tools/math_tools.cpp b/src/tools/math_tools.cpp index 0d9f29b..2533c44 100644 --- a/src/tools/math_tools.cpp +++ b/src/tools/math_tools.cpp @@ -1,10 +1,11 @@ #include "Cubed/tools/math_tools.hpp" +#include #include - namespace Cubed { namespace Math { + void extract_frustum_planes(const glm::mat4& mvp_matrix, std::vector& planes) { if (planes.size() != 6) { @@ -37,6 +38,13 @@ void extract_frustum_planes(const glm::mat4& mvp_matrix, } } +float smootherstep(float edge0, float edge1, float x) { + + x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + + return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f); +} + } // namespace Math } // namespace Cubed \ No newline at end of file