Compare commits

1 Commits

Author SHA1 Message Date
623f991fcf feat(gameplay): add Ocean biome with water generation and heightmap adjustments
- Introduce Ocean biome enum, builder, and detection logic.
- Add ocean water building to all existing biomes and modify heightmap thresholds for low mountainous areas.
- Skip cave and river generation in Ocean (and River) biomes; avoid carving water blocks.
- Comment out border blending call and update block fill logic.
2026-06-11 21:58:48 +08:00
14 changed files with 146 additions and 16 deletions

View File

@@ -124,6 +124,7 @@ add_executable(${PROJECT_NAME}
src/gameplay/river_path.cpp src/gameplay/river_path.cpp
src/block.cpp src/block.cpp
src/gameplay/vertex_data.cpp src/gameplay/vertex_data.cpp
src/gameplay/builders/ocean_builder.cpp
) )
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")

View File

@@ -15,6 +15,7 @@ enum class BiomeType {
MOUNTAIN, MOUNTAIN,
RIVER, RIVER,
SNOWY_PLAIN, SNOWY_PLAIN,
OCEAN,
NONE NONE
}; };

View File

@@ -13,5 +13,6 @@ public:
protected: protected:
void build_bottom(); void build_bottom();
void place_grass(); void place_grass();
void ocean_water_build();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -0,0 +1,23 @@
#pragma once
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class OceanBuilder : public BiomeBuilder {
public:
OceanBuilder(ChunkGenerator& chunk_generator);
void build_biome() override;
ChunkGenerator& get_chunk_generator() override;
void build_vegetation() override;
private:
ChunkGenerator& m_chunk_generator;
void build_blocks();
};
} // namespace Cubed

View File

@@ -62,6 +62,10 @@ std::string get_biome_str(BiomeType biome) {
break; break;
case SNOWY_PLAIN: case SNOWY_PLAIN:
str = "Snowy Plain"; str = "Snowy Plain";
break;
case OCEAN:
str = "Ocean";
break;
case NONE: case NONE:
str = "Unknown"; str = "Unknown";
break; break;
@@ -190,6 +194,9 @@ BiomeType determine_biome(const BiomeConditions& conditions) {
if (conditions.mountainous > 0.75) { if (conditions.mountainous > 0.75) {
return MOUNTAIN; return MOUNTAIN;
} }
if (conditions.mountainous < 0.25) {
return OCEAN;
}
auto temp = conditions.temp; auto temp = conditions.temp;
auto humid = conditions.humid; auto humid = conditions.humid;
if (temp < 0.5) { if (temp < 0.5) {

View File

@@ -39,4 +39,23 @@ void BiomeBuilder::place_grass() {
} }
} }
} }
void BiomeBuilder::ocean_water_build() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
auto& blocks = chunk.blocks();
const auto& heightmap = chunk.get_heightmap();
for (int x = 0; x < SIZE_X; ++x) {
for (int z = 0; z < SIZE_Z; ++z) {
int height = heightmap[x][z];
if (height <= SEA_LEVEL) {
for (int y = height; y <= SEA_LEVEL; y++) {
blocks[Chunk::index(x, y, z)] = 7;
}
}
}
}
}
} // namespace Cubed } // namespace Cubed

View File

@@ -27,6 +27,7 @@ void DesertBuilder::build_blocks() {
} }
} }
} }
ocean_water_build();
} }
void DesertBuilder::build_vegetation() {} void DesertBuilder::build_vegetation() {}

View File

@@ -32,6 +32,7 @@ void ForestBuilder::build_blocks() {
m_blocks[Chunk::index(x, height, z)] = 1; m_blocks[Chunk::index(x, height, z)] = 1;
} }
} }
ocean_water_build();
} }
void ForestBuilder::build_vegetation() { void ForestBuilder::build_vegetation() {

View File

@@ -23,6 +23,7 @@ void MountainBuilder::build_blocks() {
} }
} }
} }
ocean_water_build();
} }
void MountainBuilder::build_vegetation() {} void MountainBuilder::build_vegetation() {}

View File

@@ -0,0 +1,35 @@
#include "Cubed/gameplay/builders/ocean_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
namespace Cubed {
OceanBuilder::OceanBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
void OceanBuilder::build_biome() {
BiomeBuilder::build_bottom();
build_blocks();
};
void OceanBuilder::build_blocks() {
auto& m_chunk = m_chunk_generator.chunk();
auto& m_blocks = m_chunk.blocks();
auto& m_heightmap = m_chunk.heightmap();
for (int x = 0; x < CHUNK_SIZE; x++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
int height = static_cast<int>(m_heightmap[x][z]);
for (int y = 5; y <= height; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
}
}
}
ocean_water_build();
}
void OceanBuilder::build_vegetation() {}
ChunkGenerator& OceanBuilder::get_chunk_generator() {
return m_chunk_generator;
};
} // namespace Cubed

View File

@@ -27,6 +27,7 @@ void PlainBuilder::build_blocks() {
m_blocks[Chunk::index(x, height, z)] = 1; m_blocks[Chunk::index(x, height, z)] = 1;
} }
} }
ocean_water_build();
} }
void PlainBuilder::build_vegetation() { place_grass(); } void PlainBuilder::build_vegetation() { place_grass(); }

View File

@@ -27,6 +27,7 @@ void SnowyPlainBuilder::build_blocks() {
m_blocks[Chunk::index(x, height, z)] = 8; m_blocks[Chunk::index(x, height, z)] = 8;
} }
} }
ocean_water_build();
} }
void SnowyPlainBuilder::build_vegetation() {} void SnowyPlainBuilder::build_vegetation() {}

View File

@@ -199,7 +199,7 @@ void Chunk::gen_phase_six(
Logger::error("ChunkGenerator is Nullptr"); Logger::error("ChunkGenerator is Nullptr");
return; return;
} }
m_generator->blend_surface_blocks_borders(neighbor_block); // m_generator->blend_surface_blocks_borders(neighbor_block);
m_generator->generate_cave(); m_generator->generate_cave();
m_generator->generate_river(); m_generator->generate_river();
} }

View File

@@ -3,6 +3,7 @@
#include "Cubed/gameplay/builders/desert_builder.hpp" #include "Cubed/gameplay/builders/desert_builder.hpp"
#include "Cubed/gameplay/builders/forest_builder.hpp" #include "Cubed/gameplay/builders/forest_builder.hpp"
#include "Cubed/gameplay/builders/mountain_builder.hpp" #include "Cubed/gameplay/builders/mountain_builder.hpp"
#include "Cubed/gameplay/builders/ocean_builder.hpp"
#include "Cubed/gameplay/builders/plain_builder.hpp" #include "Cubed/gameplay/builders/plain_builder.hpp"
#include "Cubed/gameplay/builders/river_builder.hpp" #include "Cubed/gameplay/builders/river_builder.hpp"
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp" #include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
@@ -166,18 +167,43 @@ void ChunkGenerator::generate_heightmap() {
amplitude = std::lerp(10, 40, t); amplitude = std::lerp(10, 40, t);
*/ */
float t; float t;
if (mountainous >= 0.7f) { if (mountainous >= 0.95f) {
t = Math::smootherstep(0.7f, 0.75, mountainous); t = Math::smootherstep(0.95f, 1.0f, mountainous);
base_y = std::lerp(70, 88, t); base_y = std::lerp(130, 140, t);
amplitude = std::lerp(28, 48, t); amplitude = std::lerp(38, 48, t);
} else if (mountainous >= 0.65f) { } else if (mountainous >= 0.85f) {
t = Math::smootherstep(0.65f, 0.7f, mountainous); t = Math::smootherstep(0.85f, 0.95f, mountainous);
base_y = std::lerp(66, 70, t); base_y = std::lerp(100, 130, t);
amplitude = std::lerp(28, 38, t);
} else if (mountainous >= 0.8) {
t = Math::smootherstep(0.8f, 0.85f, mountainous);
base_y = std::lerp(85, 100, t);
amplitude = std::lerp(18, 28, t); amplitude = std::lerp(18, 28, t);
} else if (mountainous >= 0.75f) {
t = Math::smootherstep(0.75f, 0.8f, mountainous);
base_y = std::lerp(70, 85, t);
amplitude = std::lerp(6, 18, t);
} else if (mountainous >= 0.7) {
t = Math::smootherstep(0.7f, 0.75f, mountainous);
base_y = std::lerp(66, 70, t);
amplitude = std::lerp(6, 6, t);
} else if (mountainous >= 0.45f) {
t = Math::smootherstep(0.45f, 0.7f, mountainous);
base_y = std::lerp(64, 66, t);
amplitude = std::lerp(6, 6, t);
} else if (mountainous >= 0.3f) {
t = Math::smootherstep(0.3f, 0.45f, mountainous);
base_y = std::lerp(60, 64, t);
amplitude = std::lerp(6, 6, t);
} else if (mountainous >= 0.25f) {
t = Math::smootherstep(0.25f, 0.3f, mountainous);
base_y = std::lerp(44, 60, t);
amplitude = std::lerp(6, 6, t);
} else { } else {
t = Math::smootherstep(0.55, 0.65, mountainous); t = Math::smootherstep(0.0f, 0.25f, mountainous);
base_y = std::lerp(58, 66, t); base_y = std::lerp(35, 44, t);
amplitude = std::lerp(8, 18, t); amplitude = std::lerp(3, 6, t);
} }
heightmap[x][z] = heightmap[x][z] =
base_y + fbm_height(world_x, world_z, octaves, lacunarity, gain, base_y + fbm_height(world_x, world_z, octaves, lacunarity, gain,
@@ -553,10 +579,10 @@ void ChunkGenerator::blend_surface_blocks_borders(
// bottom block // bottom block
unsigned fill_type = 2; unsigned fill_type = 2;
if (final_type == 1) { if (final_type == 1 || final_type == 8) {
fill_type = 2; fill_type = 2;
} else if (final_type == 4) { } else {
fill_type = 4; fill_type = final_type;
} }
for (int y = top_y - 5; y < top_y; y++) { for (int y = top_y - 5; y < top_y; y++) {
if (fill_type == 7 && y > SEA_LEVEL) { if (fill_type == 7 && y > SEA_LEVEL) {
@@ -600,6 +626,9 @@ void ChunkGenerator::make_biome_builder() {
case SNOWY_PLAIN: case SNOWY_PLAIN:
m_biome_builder = std::make_unique<SnowyPlainBuilder>(*this); m_biome_builder = std::make_unique<SnowyPlainBuilder>(*this);
break; break;
case OCEAN:
m_biome_builder = std::make_unique<OceanBuilder>(*this);
break;
case NONE: case NONE:
m_biome_builder = nullptr; m_biome_builder = nullptr;
break; break;
@@ -617,9 +646,14 @@ void ChunkGenerator::generate_cave() {
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1; const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0; const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1; const int CHUNK_MAX_Y = SIZE_Y - 1;
for (auto& [id, path] : paths) { for (auto& [id, path] : paths) {
for (const auto& point : path.points()) { 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; const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz; float rad_xz = point.rad_xz;
float rad_y = point.rad_y; float rad_y = point.rad_y;
@@ -651,6 +685,9 @@ void ChunkGenerator::generate_cave() {
if (y == 0) { if (y == 0) {
continue; continue;
} }
if (blocks[Chunk::index(x, y, z)] == 7) {
continue;
}
blocks[Chunk::index(x, y, z)] = 0; blocks[Chunk::index(x, y, z)] = 0;
} }
} }
@@ -678,7 +715,8 @@ void ChunkGenerator::generate_river() {
for (auto& [id, path] : paths) { for (auto& [id, path] : paths) {
for (const auto& point : path.points()) { for (const auto& point : path.points()) {
if (m_chunk.biome() == BiomeType::DESERT) { if ((m_chunk.biome() == BiomeType::DESERT) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
path.clear_chunk(chunk_pos); path.clear_chunk(chunk_pos);
continue; continue;
} }