From a63dfa7f477e98c1946578bf3a56020b4d4f4d4f Mon Sep 17 00:00:00 2001 From: zhenyan121 <104683324+zhenyan121@users.noreply.github.com> Date: Fri, 1 May 2026 19:18:46 +0800 Subject: [PATCH] feat: add river biome (#4) * feat: add river biome * fix: duplicate mountain terrain generation * fix: safe_int_to_biome not include river --- assets/texture/block/water/back.png | Bin 0 -> 262 bytes assets/texture/block/water/base.png | Bin 0 -> 262 bytes assets/texture/block/water/front.png | Bin 0 -> 262 bytes assets/texture/block/water/left.png | Bin 0 -> 262 bytes assets/texture/block/water/right.png | Bin 0 -> 262 bytes assets/texture/block/water/top.png | Bin 0 -> 262 bytes assets/texture/item/block/water.png | Bin 0 -> 874 bytes include/Cubed/constants.hpp | 3 +- include/Cubed/gameplay/biome.hpp | 14 ++-- include/Cubed/gameplay/block.hpp | 2 +- include/Cubed/gameplay/chunk_generator.hpp | 4 + src/dev_panel.cpp | 26 +++++++ src/gameplay/biome.cpp | 19 ++++- src/gameplay/chunk_generator.cpp | 82 ++++++++++++++++++--- 14 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 assets/texture/block/water/back.png create mode 100644 assets/texture/block/water/base.png create mode 100644 assets/texture/block/water/front.png create mode 100644 assets/texture/block/water/left.png create mode 100644 assets/texture/block/water/right.png create mode 100644 assets/texture/block/water/top.png create mode 100644 assets/texture/item/block/water.png diff --git a/assets/texture/block/water/back.png b/assets/texture/block/water/back.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/block/water/base.png b/assets/texture/block/water/base.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/block/water/front.png b/assets/texture/block/water/front.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/block/water/left.png b/assets/texture/block/water/left.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/block/water/right.png b/assets/texture/block/water/right.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/block/water/top.png b/assets/texture/block/water/top.png new file mode 100644 index 0000000000000000000000000000000000000000..ddec4463e14d87c1c23048bcda6d2913aeea9510 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4tTmahFJ6_ zmvqT@HSx+oGq;hdD+vCNnlO4qmq$=pyaj%rRc({vo{{OiB>4^yzUwDpMzeqgX z^e#Sy^#)VGH9xMSqCF=!|NrkUrXafEftSvYBY{CmM(p89j^&b@rbll0DSy{Mr0}4< zh9NV5v;1`V#9pJAiyX{78}4+x5lRvk?-Jjzl(l5JGLyhUF$U=)^QJkL9Qg$F3xlVt KpUXO@geCy7{AAYv literal 0 HcmV?d00001 diff --git a/assets/texture/item/block/water.png b/assets/texture/item/block/water.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb8b80542d03d835a64dea6226e774f3d462061 GIT binary patch literal 874 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPR!=VeS?7NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lrT$; z@J#ddWzYh$C4krih#BED6QdLZ$V89`2ctBc4RQ~|go^ijfD~uGn_Dr64+giiww(hq zI14-?fr8vZAk28_ZrvZCpm~XFM2TBwPAY?bMt*LpepN3}d45s0eo{_q zUP@|Fa%oa(ivF`XtDa8W`*cFD9$1*+hh;6$2nmo8E{P?n3`PbSq~XCpQ6-=tFG$cCC{~on z0HHGezkdmo;08$qC#UA6GJwgaim}sx!oncoAh25z*610S+XbI~1PrUGo-U3d7J|LO z)n^t;k>7J=D#i958R|93% zx)-T4{P)-W-c=oz;@!Q-V`AMstv7Ez-P?AH|C|1X#j(zF;!4(N>K%FXQ2!J|+S)rY zb8{XXW^9&nuY75DzNPq=6{GH5KL@K*otkH#E(n|O`txIf>5;XSRx@@bocZ`r?%29r zkEO2t=H~VLC7ij{T5;_h^^7Z@Dt#W3@MiHJxB8+Eu1v@3F$v(XJ>%jBd zl353uA8M?2U9%uyjVyDd58JM$u6NoVr=9rPg$f*%t)JSK*{52Z%bM}NWn0Nj!C!k> zSy?u;uCcqUkv(r+?-Ksn!(DaxjSq6?OfE|7yR+QO 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}}}; + {{Biome::PLAIN, {Biome::DESERT}, Biome::RIVER}, + {Biome::FOREST, {Biome::DESERT}, Biome::RIVER}, + {Biome::DESERT, {Biome::MOUNTAIN, Biome::FOREST}, Biome::RIVER}, + {Biome::MOUNTAIN, {Biome::DESERT, Biome::FOREST}, Biome::RIVER}}}; struct BaseBiomeParams { Biome biome; @@ -49,6 +49,8 @@ struct DesertParams : public BaseBiomeParams {}; struct MountainParams : public BaseBiomeParams {}; +struct RiverParams : public BaseBiomeParams {}; + 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); @@ -61,5 +63,5 @@ PlainParams& plain_params(); ForestParams& forest_params(); DesertParams& desert_params(); MountainParams& mountain_params(); - +RiverParams& river_params(); } // namespace Cubed diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index e369c51..86c5846 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -39,7 +39,7 @@ struct LookBlock { }; constexpr std::array BLOCK_REISTER{ - "air", "grass_block", "dirt", "stone", "sand", "log", "leaf"}; + "air", "grass_block", "dirt", "stone", "sand", "log", "leaf", "water"}; const std::array TRANSPARENT_MAP{ true, false, false, false, false, false, true}; diff --git a/include/Cubed/gameplay/chunk_generator.hpp b/include/Cubed/gameplay/chunk_generator.hpp index 6fc7de5..feb2528 100644 --- a/include/Cubed/gameplay/chunk_generator.hpp +++ b/include/Cubed/gameplay/chunk_generator.hpp @@ -1,6 +1,7 @@ #pragma once #include "Cubed/constants.hpp" +#include "Cubed/gameplay/biome.hpp" #include "Cubed/tools/cubed_random.hpp" #include @@ -49,6 +50,9 @@ private: static inline std::atomic is_seed_change{false}; Chunk& m_chunk; Random m_random; + std::array neighbor_biome{Biome::NONE, Biome::NONE, Biome::NONE, + Biome::NONE}; + bool is_neighbor_river = false; }; } // namespace Cubed \ No newline at end of file diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index 65f2cfd..44d4d41 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -223,6 +223,32 @@ void DevPanel::show_biome_table_bar() { AMPLITUDE_MIN, AMPLITUDE_MAX); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("River")) { + ImGui::SliderFloat("MinTemp##river", &river_params().temp.first, + TEMP_MIN, TEMP_MAX); + ImGui::SliderFloat("MaxTemp##river", &river_params().temp.second, + TEMP_MIN, TEMP_MAX); + ImGui::SliderFloat("MinHumid##river", &river_params().humid.first, + HUMID_MIN, HUMID_MAX); + ImGui::SliderFloat("MaxHumid##river", &river_params().humid.second, + HUMID_MIN, HUMID_MAX); + ImGui::SliderFloat("Freq One##river", + &river_params().frequencies[0], FREQ1_MIN, + FREQ1_MAX); + ImGui::SliderFloat("Freq Two##river", + &river_params().frequencies[1], FREQ2_MIN, + FREQ2_MAX); + ImGui::SliderFloat("Freq Three##river", + &river_params().frequencies[2], FREQ3_MIN, + FREQ3_MAX); + ImGui::SliderInt("Base Y##river", + &river_params().height_range.base_y, + HEIGHT_BASE_MIN, HEIGHT_BASE_MAX); + ImGui::SliderInt("Amplitude##river", + &river_params().height_range.amplitude, + AMPLITUDE_MIN, AMPLITUDE_MAX); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } } diff --git a/src/gameplay/biome.cpp b/src/gameplay/biome.cpp index e91e490..b48447c 100644 --- a/src/gameplay/biome.cpp +++ b/src/gameplay/biome.cpp @@ -19,7 +19,7 @@ static ForestParams forest{{Biome::FOREST, {0.5f, 1.0f}, {0.5f, 1.0f}, {0.004f, 0.010f, 0.020f}, - {62, 12}}, + {62, 8}}, 0.1f }; @@ -36,6 +36,12 @@ static MountainParams mountain{{Biome::MOUNTAIN, {0.006f, 0.014f, 0.010f}, {70, 70}}}; +static RiverParams river{{Biome::RIVER, + {-0.1f, -0.1f}, + {-0.1f, -0.1f}, + {0.003f, 0.010f, 0.020f}, + {50, 6}}}; + std::string get_biome_str(Biome biome) { std::string str; using enum Biome; @@ -52,6 +58,9 @@ std::string get_biome_str(Biome biome) { case MOUNTAIN: str = "Mountain"; break; + case RIVER: + str = "River"; + break; case NONE: str = "Unknown"; break; @@ -109,6 +118,8 @@ std::array get_noise_frequencies_for_biome(Biome biome) { return desert.frequencies; case MOUNTAIN: return mountain.frequencies; + case RIVER: + return river.frequencies; case NONE: ASSERT_MSG(false, "Chunk Biome is None"); throw std::invalid_argument{"Chunk Biome is None"}; @@ -128,6 +139,8 @@ BiomeHeightRange get_biome_height_range(Biome biome) { return desert.height_range; case MOUNTAIN: return mountain.height_range; + case RIVER: + return river.height_range; case NONE: ASSERT_MSG(false, "Chunk Biome is None"); throw std::invalid_argument{"Chunk Biome is None"}; @@ -139,7 +152,7 @@ BiomeHeightRange get_biome_height_range(Biome biome) { Biome safe_int_to_biome(int x) { using enum Biome; static const std::unordered_map INT_TO_BIOME_MAP{ - {0, PLAIN}, {1, FOREST}, {2, DESERT}, {3, MOUNTAIN}}; + {0, PLAIN}, {1, FOREST}, {2, DESERT}, {3, MOUNTAIN}, {4, RIVER}}; auto it = INT_TO_BIOME_MAP.find(x); ASSERT_MSG(it != INT_TO_BIOME_MAP.end(), ":Can't Find"); @@ -194,5 +207,5 @@ PlainParams& plain_params() { return plain; } ForestParams& forest_params() { return forest; } DesertParams& desert_params() { return desert; } MountainParams& mountain_params() { return mountain; } - +RiverParams& river_params() { return river; } } // namespace Cubed diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index 2d0d160..10f3536 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -7,7 +7,7 @@ #include namespace Cubed { - +constexpr int BLEND_RADIUS = 12; ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) { ASSERT_MSG(is_init, "ChunksGenerator is not init"); ChunkPos pos = m_chunk.get_chunk_pos(); @@ -52,11 +52,16 @@ void ChunkGenerator::assign_chunk_biome() { void ChunkGenerator::resolve_biome_adjacency_conflict( const std::array& adj_chunks) { auto m_biome = m_chunk.biome(); - for (auto& chunk : adj_chunks) { + for (int i = 0; i < 4; i++) { + auto& chunk = adj_chunks[i]; if (chunk == nullptr) { continue; } Biome biome = chunk->get_biome(); + neighbor_biome[i] = biome; + if (biome == Biome::RIVER) { + is_neighbor_river = true; + } for (const auto& non : NON_ADJACENT) { if (m_biome != non.first) { continue; @@ -104,9 +109,8 @@ void ChunkGenerator::generate_heightmap() { void ChunkGenerator::blend_heightmap_boundaries( const std::array, 4>& neighbor_heightmap) { auto& m_heightmap = m_chunk.heightmap(); - + auto m_biome = m_chunk.biome(); // Width of interpolation influence (in number of cells) - constexpr int BLEND_RADIUS = 12; for (int x = 0; x < SIZE_X; x++) { for (int z = 0; z < SIZE_Z; z++) { @@ -116,7 +120,8 @@ void ChunkGenerator::blend_heightmap_boundaries( // --- Right neighbor neighbor[0]: (1, 0) --- // Blend when x is close to SIZE_X-1 - if (neighbor_heightmap[0] != std::nullopt) { + if (neighbor_heightmap[0] != std::nullopt && + neighbor_biome[0] != m_biome) { int dist = (SIZE_X - 1) - x; // distance from right border if (dist < BLEND_RADIUS) { // Neighbor's boundary row is its x=0 column @@ -133,7 +138,8 @@ void ChunkGenerator::blend_heightmap_boundaries( } // --- Left neighbor neighbor[1]: (-1, 0) --- - if (neighbor_heightmap[1] != std::nullopt) { + if (neighbor_heightmap[1] != std::nullopt && + neighbor_biome[1] != m_biome) { int dist = x; // distance from left border if (dist < BLEND_RADIUS) { float neighbor_h = static_cast( @@ -146,7 +152,8 @@ void ChunkGenerator::blend_heightmap_boundaries( } // --- Front neighbor neighbor[2]: (0, 1) --- - if (neighbor_heightmap[2] != std::nullopt) { + if (neighbor_heightmap[2] != std::nullopt && + neighbor_biome[2] != m_biome) { int dist = (SIZE_Z - 1) - z; if (dist < BLEND_RADIUS) { float neighbor_h = @@ -159,7 +166,8 @@ void ChunkGenerator::blend_heightmap_boundaries( } // --- Back neighbor neighbor[3]: (0, -1) --- - if (neighbor_heightmap[3] != std::nullopt) { + if (neighbor_heightmap[3] != std::nullopt && + neighbor_biome[3] != m_biome) { int dist = z; if (dist < BLEND_RADIUS) { float neighbor_h = static_cast( @@ -213,6 +221,17 @@ void ChunkGenerator::generate_terrain_blocks() { for (int y = height - 5; y <= height; y++) { m_blocks[Chunk::get_index(x, y, z)] = 4; } + } else if (m_biome == Biome::RIVER) { + for (int y = height - 5; y <= height - 1; y++) { + m_blocks[Chunk::get_index(x, y, z)] = 2; + } + for (int y = height; y <= height; y++) { + if (y >= SEA_LEVEL - 1) { + m_blocks[Chunk::get_index(x, y, z)] = 1; + } else { + m_blocks[Chunk::get_index(x, y, z)] = 2; + } + } } else { for (int y = height - 5; y <= height - 1; y++) { m_blocks[Chunk::get_index(x, y, z)] = 2; @@ -230,7 +249,6 @@ void ChunkGenerator::blend_surface_blocks_borders( auto& m_blocks = m_chunk.blocks(); auto& m_heightmap = m_chunk.heightmap(); - constexpr int BLEND_RADIUS = 12; constexpr int WORLD_HEIGHT = WORLD_SIZE_Y; // Helper lambda: get top block type from a neighbor's block data at (nx, @@ -325,7 +343,19 @@ void ChunkGenerator::blend_surface_blocks_borders( // Update the top block if the type changed if (final_type != type_self) { + // top block + if (m_chunk.biome() == Biome::RIVER && final_type == 1) { + final_type = 2; + } + if (is_neighbor_river && final_type == 1) { + if (top_y < SEA_LEVEL) { + final_type = 2; + } else { + final_type = 1; + } + } m_blocks[Chunk::get_index(x, top_y, z)] = final_type; + // bottom block unsigned fill_type = 2; if (final_type == 1) { fill_type = 2; @@ -342,6 +372,7 @@ void ChunkGenerator::blend_surface_blocks_borders( void ChunkGenerator::generate_vegetation() { auto m_biome = m_chunk.biome(); + auto& m_blocks = m_chunk.blocks(); auto& m_heightmap = m_chunk.heightmap(); if (m_biome == Biome::FOREST) { std::array x_arr; @@ -352,9 +383,36 @@ void ChunkGenerator::generate_vegetation() { std::shuffle(z_arr.begin(), z_arr.end(), m_random.engine()); for (auto x : x_arr) { for (auto z : z_arr) { - if (m_random.random_bool(forest_params().tree_frequency)) { - build_tree(m_chunk, - {x, static_cast(m_heightmap[x][z]), z}); + int y = static_cast(m_heightmap[x][z]); + if (m_random.random_bool(forest_params().tree_frequency) && + y >= SEA_LEVEL) { + build_tree(m_chunk, {x, y, z}); + } + } + } + } + if (m_biome == Biome::RIVER) { + for (int x = 0; x < SIZE_X; x++) { + for (int z = 0; z < SIZE_Z; z++) { + int height = static_cast(m_heightmap[x][z]); + if (height >= SEA_LEVEL) { + continue; + } + for (int y = height + 1; y < SEA_LEVEL; y++) { + m_blocks[Chunk::get_index(x, y, z)] = 7; + } + } + } + } + if (is_neighbor_river) { + for (int x = 0; x < SIZE_X; x++) { + for (int z = 0; z < SIZE_Z; z++) { + int height = static_cast(m_heightmap[x][z]); + if (height >= SEA_LEVEL) { + continue; + } + for (int y = height + 1; y < SEA_LEVEL; y++) { + m_blocks[Chunk::get_index(x, y, z)] = 7; } } }