diff --git a/include/Cubed/config.hpp b/include/Cubed/config.hpp index 6945c96..a20a2da 100644 --- a/include/Cubed/config.hpp +++ b/include/Cubed/config.hpp @@ -1,4 +1,5 @@ #pragma once +#include namespace Cubed { @@ -13,7 +14,7 @@ constexpr int MAX_CHARACTER = 128; constexpr float NORMAL_FOV = 70.0f; constexpr int MAX_BIOME_SUM = 4; - +using HeightMapArray = std::array, CHUCK_SIZE>; constexpr float VERTICES_POS[6][6][3] = { // ===== front (z = +1) ===== 0.0f, 0.0f, 1.0f, // bottom left diff --git a/include/Cubed/gameplay/biome.hpp b/include/Cubed/gameplay/biome.hpp index 77fc12d..8837347 100644 --- a/include/Cubed/gameplay/biome.hpp +++ b/include/Cubed/gameplay/biome.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace Cubed { @@ -15,7 +16,8 @@ enum class Biome { PLAIN = 0, FOREST, DESERT, - MOUNTAIN + MOUNTAIN, + NONE }; struct BiomeHeightRange { @@ -23,11 +25,16 @@ struct BiomeHeightRange { int amplitude; }; +struct BiomeNonAdjacent { + Biome first; + std::vector second; + Biome replace; +}; + std::string get_biome_str(Biome biome); Biome get_biome_from_noise(float temp, float humid); std::array get_noise_frequencies_for_biome(Biome biome); BiomeHeightRange get_biome_height_range(Biome biome); Biome safe_int_to_biome(int x); int get_interpolated_height(float world_x, float world_z, float temp, float humid); - } diff --git a/include/Cubed/gameplay/chunk.hpp b/include/Cubed/gameplay/chunk.hpp index ec63845..3fb2f7a 100644 --- a/include/Cubed/gameplay/chunk.hpp +++ b/include/Cubed/gameplay/chunk.hpp @@ -15,18 +15,28 @@ class World; // if want to use, do init_chunk(), gen_vertex_data() and class Chunk { private: + static constexpr int SIZE_X = CHUCK_SIZE; + static constexpr int SIZE_Y = WORLD_SIZE_Y; + static constexpr int SIZE_Z = CHUCK_SIZE; + + static inline const std::vector NON_ADJACENT {{ + {Biome::PLAIN, {Biome::NONE}, Biome::PLAIN}, + {Biome::FOREST, {Biome::DESERT}, Biome::PLAIN}, + {Biome::DESERT, {Biome::MOUNTAIN, Biome::FOREST}, Biome::PLAIN}, + {Biome::MOUNTAIN, {Biome::DESERT}, Biome::PLAIN} + } + }; + using HeightMapArray = std::array, SIZE_X>; std::atomic m_dirty {false}; std::atomic m_need_upload{true}; std::atomic m_is_on_gen_vertex_data {false}; std::atomic m_vertex_sum = 0; std::mutex m_vertexs_data_mutex; - static constexpr int SIZE_X = CHUCK_SIZE; - static constexpr int SIZE_Y = WORLD_SIZE_Y; - static constexpr int SIZE_Z = CHUCK_SIZE; - Biome m_biome = Biome::PLAIN; + std::atomic m_biome = Biome::PLAIN; ChunkPos m_chunk_pos; World& m_world; + HeightMapArray m_heightmap; // the index is a array of block id std::vector m_blocks; GLuint m_vbo = 0; @@ -51,10 +61,22 @@ public: Biome get_biome() const; const std::vector& get_chunk_blocks() const; - + HeightMapArray get_heightmap() const; static int get_index(int x, int y, int z); static int get_index(const glm::vec3& pos); void init_chunk(); + // Generate Biome + void gen_phase_one(); + // Adjust Biome + void gen_phase_two(const std::array& adj_chunks); + // Generate Heightmap + void gen_phase_three(); + // Adjust Height + void gen_phase_four(const std::array, 4>& neighbor_heightmap); + // Generate Block + void gen_phase_five(); + // Generate Structure + void gen_phase_six(); //void gen_vertex_data(); // 0 : (1, 0) // 1 : (-1, 0) diff --git a/include/Cubed/gameplay/world.hpp b/include/Cubed/gameplay/world.hpp index 666f301..4abbf34 100644 --- a/include/Cubed/gameplay/world.hpp +++ b/include/Cubed/gameplay/world.hpp @@ -26,7 +26,10 @@ private: using ChunkPtrUpdateList = std::vector>; using ChunkUpdateList = std::vector>; using ConstChunkMap = std::unordered_map; - using ChunkPosSet = std::unordered_set; + using ChunkPosSet = std::unordered_set; + + bool m_could_gen = true; + glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f}; std::unordered_map m_chunks; std::unordered_map m_players; @@ -48,6 +51,8 @@ private: std::vector> m_new_chunk; std::vector> m_new_chunk_queue; + void init_chunks(); + void gen_chunks_internal(); void sync_player_pos(glm::vec3& player_pos); void compute_required_chunks(ChunkPosSet& required_chunks); diff --git a/src/gameplay/biome.cpp b/src/gameplay/biome.cpp index 8826fe4..3fec711 100644 --- a/src/gameplay/biome.cpp +++ b/src/gameplay/biome.cpp @@ -24,10 +24,13 @@ std::string get_biome_str(Biome biome) { case MOUNTAIN: str = "Mountain"; break; + case NONE: + str = "Unknown"; + break; } return str; }; - +/* Biome get_biome_from_noise(float temp, float humid) { auto weight = [](float t, float h, float ct, float ch) -> float { float dt = t - ct; @@ -45,7 +48,14 @@ Biome get_biome_from_noise(float temp, float humid) { if (w_d >= w_m && w_d >= w_p && w_d >= w_f) return Biome::DESERT; return Biome::FOREST; } - +*/ +Biome get_biome_from_noise(float temp, float humid) { + using enum Biome; + if (temp < 0.5f && humid < 0.5f) return PLAIN; + if (temp < 0.5f && humid >= 0.5f) return FOREST; + if (temp >= 0.5f && humid < 0.5f) return DESERT; + return MOUNTAIN; +} std::array get_noise_frequencies_for_biome(Biome biome) { using enum Biome; switch (biome) { @@ -57,6 +67,9 @@ std::array get_noise_frequencies_for_biome(Biome biome) { return {0.003f, 0.010f, 0.020f}; case MOUNTAIN: return {0.006f, 0.015f, 0.030f}; + case NONE: + ASSERT_MSG(false, "Chunk Biome is None"); + throw std::invalid_argument{"Chunk Biome is None"}; } Logger::warn("Unknown Biome"); return {0.003f, 0.015f, 0.06f}; @@ -73,6 +86,9 @@ BiomeHeightRange get_biome_height_range(Biome biome) { return {61, 12}; case MOUNTAIN: return {70, 70}; + case NONE: + ASSERT_MSG(false, "Chunk Biome is None"); + throw std::invalid_argument{"Chunk Biome is None"}; } Logger::warn("Unknown Biome"); return {62, 4}; diff --git a/src/gameplay/chunk.cpp b/src/gameplay/chunk.cpp index 8f97993..fa2c014 100644 --- a/src/gameplay/chunk.cpp +++ b/src/gameplay/chunk.cpp @@ -31,9 +31,10 @@ Chunk::Chunk(Chunk&& other) noexcept : m_need_upload(other.m_need_upload.load()), m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()), m_vertex_sum(other.m_vertex_sum.load()), - m_biome(other.m_biome), + m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)), m_world(other.m_world), + m_heightmap(std::move(other.m_heightmap)), m_blocks(std::move(other.m_blocks)), m_vbo(other.m_vbo), m_vertexs_data(std::move(other.m_vertexs_data)) @@ -42,13 +43,15 @@ Chunk::Chunk(Chunk&& other) noexcept : } Chunk& Chunk::operator=(Chunk&& other) noexcept { + //Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&& other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z, static_cast(&other)); m_vbo = other.m_vbo; other.m_vbo = 0; m_chunk_pos = std::move(other.m_chunk_pos); + m_heightmap = std::move(other.m_heightmap); m_blocks = std::move(other.m_blocks); m_dirty = other.is_dirty(); m_vertexs_data = std::move(other.m_vertexs_data); - m_biome = other.m_biome; + m_biome = other.m_biome.load(); m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load(); m_need_upload = other.m_need_upload.load(); m_vertex_sum = other.m_vertex_sum.load(); @@ -56,13 +59,18 @@ Chunk& Chunk::operator=(Chunk&& other) noexcept { } Biome Chunk::get_biome() const { - return m_biome; + return m_biome.load(); } const std::vector& Chunk::get_chunk_blocks() const{ return m_blocks; } +HeightMapArray Chunk::get_heightmap() const { + //Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x, m_chunk_pos.z, static_cast(this)); + return m_heightmap; +} + int Chunk::get_index(int x, int y, int z) { ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUCK_SIZE || y >= WORLD_SIZE_Y || z >= CHUCK_SIZE)); if ((x * WORLD_SIZE_Y + y) * CHUCK_SIZE + z < 0 || (x * WORLD_SIZE_Y + y) * CHUCK_SIZE + z >= CHUCK_SIZE * CHUCK_SIZE * WORLD_SIZE_Y) { @@ -209,6 +217,187 @@ void Chunk::init_chunk() { resolve_blocks(); } +void Chunk::gen_phase_one() { + resolve_biome(); +} + +void Chunk::gen_phase_two(const std::array& adj_chunks) { + for (auto& chunk : adj_chunks) { + if (chunk == nullptr) { + continue; + } + Biome biome = chunk->get_biome(); + for (const auto& non : NON_ADJACENT) { + if (m_biome != non.first) { + continue; + } + for (auto b : non.second) { + if (b == biome) { + m_biome = non.replace; + return; + } + } + } + } +} + +void Chunk::gen_phase_three() { + for (int x = 0; x < CHUCK_SIZE; x++) { + for (int z = 0; z < CHUCK_SIZE; z++) { + + float world_x = static_cast(x + m_chunk_pos.x * CHUCK_SIZE); + float world_z = static_cast(z + m_chunk_pos.z * CHUCK_SIZE); + + auto sample_height = [&](Biome b) -> float { + auto range = get_biome_height_range(b); + auto [f1, f2, f3] = get_noise_frequencies_for_biome(b); + float n = + 1.00f * PerlinNoise::noise(world_x * f1, 0.5f, world_z * f1) + + 0.50f * PerlinNoise::noise(world_x * f2, 0.5f, world_z * f2) + + 0.25f * PerlinNoise::noise(world_x * f3, 0.5f, world_z * f3); + n /= 1.75f; + return range.base_y + n * range.amplitude; + }; + m_heightmap[x][z] = sample_height(m_biome); + } + } +} + +void Chunk::gen_phase_four(const std::array, 4>& neighbor_heightmap) { + // Width of interpolation influence (in number of cells) + constexpr int BLEND_RADIUS = 8; + + for (int x = 0; x < SIZE_X; x++) { + for (int z = 0; z < SIZE_Z; z++) { + float h = static_cast(m_heightmap[x][z]); + float total_weight = 1.0f; + float blended = h; + + // --- Right neighbor neighbor[0]: (1, 0) --- + // Blend when x is close to SIZE_X-1 + if (neighbor_heightmap[0] != std::nullopt) { + int dist = (SIZE_X - 1) - x; // distance from right border + if (dist < BLEND_RADIUS) { + // Neighbor's boundary row is its x=0 column + float neighbor_h = static_cast((*neighbor_heightmap[0])[0][z]); + float t = 1.0f - static_cast(dist) / BLEND_RADIUS; // larger weight when closer + // Use smoothstep for a more natural transition + t = t * t * (3.0f - 2.0f * t); + blended += t * neighbor_h; + total_weight += t; + } + } + + // --- Left neighbor neighbor[1]: (-1, 0) --- + if (neighbor_heightmap[1] != std::nullopt) { + int dist = x; // distance from left border + if (dist < BLEND_RADIUS) { + float neighbor_h = static_cast((*neighbor_heightmap[1])[SIZE_X - 1][z]); + float t = 1.0f - static_cast(dist) / BLEND_RADIUS; + t = t * t * (3.0f - 2.0f * t); + blended += t * neighbor_h; + total_weight += t; + } + } + + // --- Front neighbor neighbor[2]: (0, 1) --- + if (neighbor_heightmap[2] != std::nullopt) { + int dist = (SIZE_Z - 1) - z; + if (dist < BLEND_RADIUS) { + float neighbor_h = static_cast((*neighbor_heightmap[2])[x][0]); + float t = 1.0f - static_cast(dist) / BLEND_RADIUS; + t = t * t * (3.0f - 2.0f * t); + blended += t * neighbor_h; + total_weight += t; + } + } + + // --- Back neighbor neighbor[3]: (0, -1) --- + if (neighbor_heightmap[3] != std::nullopt) { + int dist = z; + if (dist < BLEND_RADIUS) { + float neighbor_h = static_cast((*neighbor_heightmap[3])[x][SIZE_Z - 1]); + float t = 1.0f - static_cast(dist) / BLEND_RADIUS; + t = t * t * (3.0f - 2.0f * t); + blended += t * neighbor_h; + total_weight += t; + } + } + + m_heightmap[x][z] = static_cast(blended / total_weight); + } + } +} + +void Chunk::gen_phase_five() { + // bottom + m_blocks.assign(CHUCK_SIZE * CHUCK_SIZE * WORLD_SIZE_Y, 0); + for (int x = 0; x < CHUCK_SIZE; x++) { + for (int y = 0; y < 5; y++) { + for (int z = 0; z < CHUCK_SIZE; z++) { + m_blocks[get_index(x, y, z)] = 3; + } + } + } + + for (int x = 0; x < CHUCK_SIZE; x++) { + for (int z = 0; z < CHUCK_SIZE; z++) { + int height = static_cast(m_heightmap[x][z]); + for (int y = 5; y < height - 5; y++) { + m_blocks[get_index(x, y, z)] = 3; + } + if (m_biome == Biome::MOUNTAIN) { + for (int y = height - 5; y <= height - 1; y++) { + if (y > 110) { + m_blocks[get_index(x, y, z)] = 3; + } else { + m_blocks[get_index(x, y, z)] = 2; + } + + } + if (height > 110) { + m_blocks[get_index(x, height - 1, z)] = 3; + } else { + m_blocks[get_index(x, height - 1, z)] = 1; + } + } else if (m_biome == Biome::DESERT) { + for (int y = height - 5; y <= height; y++) { + m_blocks[get_index(x, y, z)] = 4; + } + } else { + for (int y = height - 5; y <= height - 1; y++) { + m_blocks[get_index(x, y, z)] = 2; + } + for (int y = height; y <= height; y++) { + m_blocks[get_index(x, y, z)] = 1; + } + } + } + } + +} + +void Chunk::gen_phase_six() { + if (m_biome == Biome::FOREST) { + std::array x_arr; + std::iota(x_arr.begin(), x_arr.end(), 0); + std::shuffle(x_arr.begin(), x_arr.end(), Cubed::Random::get().engine()); + std::array z_arr; + std::iota(z_arr.begin(), z_arr.end(), 0); + std::shuffle(z_arr.begin(), z_arr.end(), Cubed::Random::get().engine()); + for (auto x : x_arr) { + for (auto z : z_arr) { + if (Cubed::Random::get().random_bool(0.1)) { + build_tree(*this, {x, static_cast(m_heightmap[x][z]), z}); + } + + } + } + } + + mark_dirty(); +} + void Chunk::upload_to_gpu() { ASSERT(is_need_upload()); diff --git a/src/gameplay/world.cpp b/src/gameplay/world.cpp index 8515ea8..0e3a571 100644 --- a/src/gameplay/world.cpp +++ b/src/gameplay/world.cpp @@ -11,7 +11,6 @@ namespace Cubed { - static constexpr ChunkPos CHUNK_DIR[] { {1, 0}, {-1, 0}, {0, 1}, {0, -1} }; @@ -75,6 +74,7 @@ Player& World::get_player(const std::string& name){ } void World::init_world() { + m_chunks.reserve(DISTANCE * DISTANCE); auto t1 = std::chrono::system_clock::now(); for (int s = 0; s < DISTANCE; s++) { for (int t = 0; t < DISTANCE; t++) { @@ -86,28 +86,21 @@ void World::init_world() { m_chunks.emplace(pos, Chunk(*this, pos)); } } - /* - for (auto& chunk_map : m_chunks) { - auto& [chunk_pos, chunk] = chunk_map; - chunk.init_chunk(); - - } - // After block gen fininshed - - std::array*, 4> neighbor_block; - - for (auto& [pos, chunk] : m_chunks) { - 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()); - } else { - neighbor_block[i] = nullptr; - } - } - chunk.gen_vertex_data(neighbor_block); - } - */ + + Logger::info("Max Support Thread is {}", std::thread::hardware_concurrency()); + init_chunks(); + auto t2 = std::chrono::system_clock::now(); + auto d = std::chrono::duration_cast(t2 - t1); + Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); + // init players + m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer")); + Logger::info("TestPlayer Create Finish"); + + start_gen_thread(); + +} +/* +void World::init_chunks() { std::vector chunk_ptrs; chunk_ptrs.reserve(m_chunks.size()); for (auto& [pos, chunk] : m_chunks) { @@ -148,14 +141,81 @@ void World::init_world() { chunk.upload_to_gpu(); } - auto t2 = std::chrono::system_clock::now(); - auto d = std::chrono::duration_cast(t2 - t1); - Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); - // init players - m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer")); - Logger::info("TestPlayer Create Finish"); +} +*/ - start_gen_thread(); +void World::init_chunks() { + for (auto& [pos, chunks] : m_chunks) { + chunks.gen_phase_one(); + } + std::array neighbor_chunks; + for (auto& [pos, chunks] : m_chunks) { + for (int i = 0; i < 4; i++) { + auto neighbor_pos = pos + CHUNK_DIR[i]; + auto it = m_chunks.find(neighbor_pos); + if (it == m_chunks.end()) { + neighbor_chunks[i] = nullptr; + continue; + } + neighbor_chunks[i] = &it->second; + + } + chunks.gen_phase_two(neighbor_chunks); + } + + for (auto& [pos, chunks] : m_chunks) { + chunks.gen_phase_three(); + } + std::array, 4> neighbor_chunk_heightmap; + for (auto& [pos, chunks] : m_chunks) { + for (int i = 0; i < 4; i++) { + auto neighbor_pos = pos + CHUNK_DIR[i]; + auto it = m_chunks.find(neighbor_pos); + if (it == m_chunks.end()) { + neighbor_chunk_heightmap[i] = std::nullopt; + continue; + } + neighbor_chunk_heightmap[i] = it->second.get_heightmap(); + + } + chunks.gen_phase_four(neighbor_chunk_heightmap); + } + + for (auto& [pos, chunks] : m_chunks) { + chunks.gen_phase_five(); + chunks.gen_phase_six(); + } + + std::atomic sync{0}; + sync.store(1, std::memory_order_release); + sync.load(std::memory_order_acquire); + + std::vector pending_gen_data; + pending_gen_data.reserve(m_chunks.size()); + for (auto& [pos, chunk] : m_chunks) { + ChunkRenderData data; + data.chunk = &chunk; + for (int i = 0; i < 4; i++) { + auto it = m_chunks.find(pos + CHUNK_DIR[i]); + if (it != m_chunks.end()) { + data.neighbor_block[i] = &(it->second.get_chunk_blocks()); + } else { + data.neighbor_block[i] = nullptr; + } + } + pending_gen_data.emplace_back(std::move(data)); + } + std::for_each(std::execution::par, pending_gen_data.begin(), pending_gen_data.end(), [](ChunkRenderData& data){ + if(!data.chunk) { + return ; + } + data.chunk->gen_vertex_data(data.neighbor_block); + }); + for (auto& chunk_map : m_chunks) { + auto& [chunk_pos, chunk] = chunk_map; + chunk.upload_to_gpu(); + + } } @@ -215,6 +275,7 @@ void World::gen_chunks_internal() { Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size()); if (need_gen_chunks_pos.empty()) { + m_could_gen = true; return; } ChunkUpdateList new_chunks; @@ -229,8 +290,49 @@ void World::gen_chunks_internal() { std::array*, 4> neighbor_block; // build new chunk, but the neighbor in m_chunks also need to re-build + for (auto& [pos, chunk] : new_chunks) { - chunk.init_chunk(); + chunk.gen_phase_one(); + } + + std::array neighbor_chunks; + for (auto& [pos, chunks] : new_chunks) { + for (int i = 0; i < 4; i++) { + auto neighbor_pos = pos + CHUNK_DIR[i]; + auto it = new_chunks_neighbor.find(neighbor_pos); + if (it == new_chunks_neighbor.end()) { + neighbor_chunks[i] = nullptr; + continue; + } + neighbor_chunks[i] = it->second; + + } + chunks.gen_phase_two(neighbor_chunks); + } + + for (auto& [pos, chunks] : new_chunks) { + chunks.gen_phase_three(); + } + std::array, 4> neighbor_chunk_heightmap; + for (auto& [pos, chunks] : new_chunks) { + { + //std::lock_guard lk(m_chunks_mutex); + for (int i = 0; i < 4; i++) { + auto neighbor_pos = pos + CHUNK_DIR[i]; + auto it = new_chunks_neighbor.find(neighbor_pos); + if (it == new_chunks_neighbor.end()) { + neighbor_chunk_heightmap[i] = std::nullopt; + continue; + } + neighbor_chunk_heightmap[i] = it->second->get_heightmap(); + } + } + chunks.gen_phase_four(neighbor_chunk_heightmap); + } + + for (auto& [pos, chunks] : new_chunks) { + chunks.gen_phase_five(); + chunks.gen_phase_six(); } for (auto& [pos, chunk] : new_chunks) { @@ -371,6 +473,11 @@ void World::stop_gen_thread() { } void World::need_gen() { + if (!m_could_gen) { + Logger::warn("It is generating or consuming new chunks"); + return; + } + m_could_gen = false; { std::lock_guard lk(m_gen_player_pos_mutex); m_gen_player_pos = get_player("TestPlayer").get_player_pos(); @@ -509,9 +616,16 @@ void World::update(float delta_time) { // unified compute vertex data before rendering { std::lock_guard lk(m_chunks_mutex); + bool consumed = false; + for (auto& x : m_new_chunk) { m_chunks.insert_or_assign(x.first, std::move(x.second)); + consumed = true; } + if (consumed) { + m_could_gen = true; + } + m_render_snapshots.clear(); for (auto& [pos, chunk] : m_chunks) { if (chunk.is_dirty()) {