Compare commits

2 Commits

Author SHA1 Message Date
zhenyan121
f4114c2699 refactor: world generation (#17)
* refactor: use TBB for concurrent hash maps and parallelize chunk processing

* fix: tbb link fail

* refactor(chunk): remove biome check for caves in rivers and oceans

* refactor(random): replace std distributions with custom implementations

Avoid overhead and platform-dependent behavior of `<random>` distributions by using direct engine operations and integer arithmetic. This ensures deterministic, cross-platform results and improves performance.

* refactor(generation): use chunk seed for cave and river paths

- Use per-chunk seed instead of global path_id for cave and river generation.
- Remove unused m_sum variables and m_path_id members.
- Clamp river yaw within 10 degrees of initial direction.
- Fix river radius interpolation (use t instead of 1-t).
- Lower sea level from 64 to 63.
2026-06-14 11:36:37 +08:00
zhenyan121
932463663f feat: ocean (#16)
* 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.

* fix(gameplay): re-enable border blending and protect water in cave gen

* refactor(generation): move ocean water build to later phase

* feat(block): add is_transitional property and refine border blending

* fix(block): set stone block as transitional

* fix(world): generate temporary chunks for surface blend neighbor data

* fix(gameplay): simplify block fill logic in blend_surface_blocks_borders

* refactor(tree): remove debug logging and unused include
2026-06-12 19:42:59 +08:00
41 changed files with 617 additions and 280 deletions

View File

@@ -58,6 +58,21 @@ if (WIN32)
if(TARGET freetype) if(TARGET freetype)
add_library(Freetype::Freetype ALIAS freetype) add_library(Freetype::Freetype ALIAS freetype)
endif() endif()
set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS ON)
FetchContent_Declare(
onetbb
GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git
GIT_TAG v2023.0.0
)
set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE)
FetchContent_MakeAvailable(onetbb)
set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED})
unset(_BUILD_SHARED_LIBS_SAVED)
endif() endif()
FetchContent_Declare( FetchContent_Declare(
@@ -83,7 +98,6 @@ FetchContent_MakeAvailable(tomlplusplus)
add_subdirectory(third_party/imgui) add_subdirectory(third_party/imgui)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
add_executable(${PROJECT_NAME} add_executable(${PROJECT_NAME}
@@ -124,6 +138,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")
@@ -163,10 +178,11 @@ target_link_libraries(${PROJECT_NAME}
Freetype::Freetype Freetype::Freetype
tomlplusplus::tomlplusplus tomlplusplus::tomlplusplus
imgui imgui
tbb
) )
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_link_libraries(${PROJECT_NAME} PRIVATE tbb) # target_link_libraries(${PROJECT_NAME} PRIVATE tbb)
endif() endif()
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
@@ -186,3 +202,19 @@ if (UNIX AND NOT APPLE)
target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER}) target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER})
endif() endif()
if (WIN32)
foreach(TBB_LIB IN ITEMS tbb tbbmalloc tbbmalloc_proxy)
if(TARGET ${TBB_LIB})
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${TBB_LIB}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying ${TBB_LIB}.dll"
)
else()
message(STATUS "Target ${TBB_LIB} not found, skipping copy")
endif()
endforeach()
endif()

View File

@@ -5,5 +5,6 @@ is_discard = true
is_gas = true is_gas = true
is_liquid = false is_liquid = false
is_passable = true is_passable = true
is_transitional = false
is_transparent = true is_transparent = true
name = 'air' name = 'air'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = true
is_transparent = false is_transparent = false
name = 'dirt' name = 'dirt'

View File

@@ -5,5 +5,6 @@ is_discard = true
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = true is_passable = true
is_transitional = false
is_transparent = true is_transparent = true
name = 'grass' name = 'grass'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = true
is_transparent = false is_transparent = false
name = 'grass_block' name = 'grass_block'

View File

@@ -5,5 +5,6 @@ is_discard = true
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = false
is_transparent = true is_transparent = true
name = 'leaf' name = 'leaf'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = false
is_transparent = false is_transparent = false
name = 'log' name = 'log'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = true
is_transparent = false is_transparent = false
name = 'sand' name = 'sand'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = true
is_transparent = false is_transparent = false
name = 'snowy_grass_block' name = 'snowy_grass_block'

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = false is_liquid = false
is_passable = false is_passable = false
is_transitional = true
is_transparent = false is_transparent = false
name = 'stone' name = 'stone'

View File

@@ -7,3 +7,4 @@ is_cross_plane = false
is_transparent = false is_transparent = false
is_discard = false is_discard = false
is_blend = false is_blend = false
is_transitional = false

View File

@@ -5,5 +5,6 @@ is_discard = false
is_gas = false is_gas = false
is_liquid = true is_liquid = true
is_passable = true is_passable = true
is_transitional = false
is_transparent = true is_transparent = true
name = 'water' name = 'water'

View File

@@ -6,7 +6,7 @@ namespace Cubed {
constexpr int WORLD_SIZE_Y = 256; constexpr int WORLD_SIZE_Y = 256;
constexpr int CHUNK_SIZE = 16; constexpr int CHUNK_SIZE = 16;
constexpr int SEA_LEVEL = 64; constexpr int SEA_LEVEL = 63;
constexpr int MAX_UI_NUM = 1; constexpr int MAX_UI_NUM = 1;
constexpr int MAX_BLOCK_STATUS = 1; constexpr int MAX_BLOCK_STATUS = 1;

View File

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

View File

@@ -50,13 +50,14 @@ struct BlockData {
bool is_discard = false; bool is_discard = false;
bool is_blend = false; bool is_blend = false;
bool is_transitional = false;
BlockData(BlockType b_id, std::string_view b_name, bool liquid, BlockData(BlockType b_id, std::string_view b_name, bool liquid,
bool passable, bool cross_plane, bool transparent, bool gas, bool passable, bool cross_plane, bool transparent, bool gas,
bool discard, bool blend) bool discard, bool blend, bool transitional)
: name(b_name), id(b_id), is_liquid(liquid), is_gas(gas), : name(b_name), id(b_id), is_liquid(liquid), is_gas(gas),
is_passable(passable), is_cross_plane(cross_plane), is_passable(passable), is_cross_plane(cross_plane),
is_transparent(transparent), is_discard(discard), is_blend(blend) {} is_transparent(transparent), is_discard(discard), is_blend(blend),
is_transitional(transitional) {}
}; };
class BlockManager { class BlockManager {
@@ -77,7 +78,7 @@ public:
static bool is_discard(BlockType id); static bool is_discard(BlockType id);
static bool is_blend(BlockType id); static bool is_blend(BlockType id);
static bool is_transitional(BlockType id);
static BlockType cross_plane_index(BlockType id); static BlockType cross_plane_index(BlockType id);
private: private:

View File

@@ -9,6 +9,7 @@ public:
virtual ChunkGenerator& get_chunk_generator() = 0; virtual ChunkGenerator& get_chunk_generator() = 0;
virtual void build_biome() = 0; virtual void build_biome() = 0;
virtual void build_vegetation() = 0; virtual void build_vegetation() = 0;
void ocean_water_build();
protected: protected:
void build_bottom(); void build_bottom();

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

@@ -1,10 +1,14 @@
#pragma once #pragma once
#include "Cubed/gameplay/cave_path.hpp" #include "Cubed/gameplay/cave_path.hpp"
#include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class CaveCarver { class CaveCarver {
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
public: public:
CaveCarver(); CaveCarver();
std::unordered_map<unsigned, CavePath>& paths(); CaveHashMap& paths();
void init(unsigned world_seed); void init(unsigned world_seed);
void reload(unsigned world_seed); void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed); void add_path(const glm::vec3& pos, unsigned chunk_seed);
@@ -15,9 +19,8 @@ public:
float& cave_probability(); float& cave_probability();
private: private:
std::unordered_map<unsigned, CavePath> m_paths; CaveHashMap m_paths;
unsigned m_seed = 0; unsigned m_seed = 0;
int m_sum = 0;
Random m_random; Random m_random;
float m_cave_probability = 0.035f; float m_cave_probability = 0.035f;
}; };

View File

@@ -5,12 +5,16 @@
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <unordered_set> #include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class CavePath { class CavePath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public: public:
CavePath(unsigned int world_seed, int path_id, const glm::vec3& start_pos); CavePath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const; const std::vector<PathPoint>& points() const;
void clear_chunk(const ChunkPos& pos); void clear_chunk(const ChunkPos& pos);
bool is_finished() const; bool is_finished() const;
@@ -34,7 +38,6 @@ private:
static inline int m_step_min = 10; static inline int m_step_min = 10;
static inline int m_step_max = 400; static inline int m_step_max = 400;
int m_path_id = 0;
unsigned int m_seed = 0; unsigned int m_seed = 0;
float m_yaw = 0.0f; float m_yaw = 0.0f;
float m_pitch = 0.0f; float m_pitch = 0.0f;
@@ -44,7 +47,7 @@ private:
Random m_random; Random m_random;
std::vector<PathPoint> m_points; std::vector<PathPoint> m_points;
std::unordered_set<ChunkPos, ChunkPos::Hash> m_pending_chunks; ChunkPosSet m_pending_chunks;
void collect_path_points(); void collect_path_points();
void precompute_chunk_coverage(); void precompute_chunk_coverage();
}; };

View File

@@ -14,6 +14,8 @@ class World;
// if want to use, do init_chunk(), gen_vertex_data() and // if want to use, do init_chunk(), gen_vertex_data() and
class Chunk { class Chunk {
private: private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
static constexpr int SIZE_X = CHUNK_SIZE; static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y; static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE; static constexpr int SIZE_Z = CHUNK_SIZE;
@@ -46,8 +48,7 @@ private:
BiomeConditions m_conditions; BiomeConditions m_conditions;
void clear_dirty(); void clear_dirty();
void gen_vertices( void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z, void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id); BlockType id);
@@ -97,8 +98,7 @@ public:
// 1 : (-1, 0) // 1 : (-1, 0)
// 2 : (0, 1) // 2 : (0, 1)
// 3 : (0, -1) // 3 : (0, -1)
void gen_vertex_data( void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void upload_to_gpu(); void upload_to_gpu();
GLuint get_normal_vao() const; GLuint get_normal_vao() const;

View File

@@ -4,6 +4,7 @@
#include "Cubed/gameplay/biome.hpp" #include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp" #include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/builders/biome_builder.hpp" #include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
#include <atomic> #include <atomic>
@@ -45,6 +46,7 @@ public:
Chunk& chunk(); Chunk& chunk();
Random& random(); Random& random();
const std::array<BiomeType, 8>& neighbor_biome() const; const std::array<BiomeType, 8>& neighbor_biome() const;
void ocean_build();
void generate_cave(); void generate_cave();
void generate_river(); void generate_river();
@@ -60,6 +62,9 @@ private:
unsigned m_chunk_seed = 0; unsigned m_chunk_seed = 0;
void make_biome_builder(); void make_biome_builder();
void
carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
std::function<void(int /*x*/, int /*y*/, int /*z*/)> on_hit);
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -16,7 +16,14 @@ struct ChunkPos {
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
} }
}; };
struct TBBHash {
std::size_t hash(const ChunkPos& p) const {
return ChunkPos::Hash{}(p);
}
bool equal(const ChunkPos& a, const ChunkPos& b) const {
return a == b;
}
};
ChunkPos operator+(const ChunkPos& pos) const { ChunkPos operator+(const ChunkPos& pos) const {
return ChunkPos{x + pos.x, z + pos.z}; return ChunkPos{x + pos.x, z + pos.z};
} }

View File

@@ -5,13 +5,16 @@
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <unordered_set> #include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class RiverPath { class RiverPath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public: public:
RiverPath(unsigned int world_seed, int path_id, const glm::vec3& start_pos); RiverPath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const; const std::vector<PathPoint>& points() const;
void clear_chunk(const ChunkPos& pos); void clear_chunk(const ChunkPos& pos);
bool is_finished() const; bool is_finished() const;
@@ -32,12 +35,12 @@ private:
static inline float m_radius_y_max = 8.0f; static inline float m_radius_y_max = 8.0f;
static inline float m_delta_angle_min = -3.0f; static inline float m_delta_angle_min = -3.0f;
static inline float m_delta_angle_max = 3.0f; static inline float m_delta_angle_max = 3.0f;
static inline int m_step_min = 150; static inline int m_step_min = 200;
static inline int m_step_max = 400; static inline int m_step_max = 400;
int m_path_id = 0;
unsigned int m_seed = 0; unsigned int m_seed = 0;
float m_yaw = 0.0f; float m_yaw = 0.0f;
float m_initial_yaw = 0.0f;
float m_pitch = 0.0f; float m_pitch = 0.0f;
int m_step = 0; int m_step = 0;
float m_step_len = 1.0f; float m_step_len = 1.0f;
@@ -45,7 +48,7 @@ private:
Random m_random; Random m_random;
std::vector<PathPoint> m_points; std::vector<PathPoint> m_points;
std::unordered_set<ChunkPos, ChunkPos::Hash> m_pending_chunks; ChunkPosSet m_pending_chunks;
void collect_path_points(); void collect_path_points();
void precompute_chunk_coverage(); void precompute_chunk_coverage();
}; };

View File

@@ -4,13 +4,15 @@
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <unordered_map> #include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class RiverWorm { class RiverWorm {
using RiverHashMap = tbb::concurrent_hash_map<unsigned, RiverPath>;
public: public:
RiverWorm(); RiverWorm();
std::unordered_map<unsigned, RiverPath>& paths(); RiverHashMap& paths();
void init(unsigned world_seed); void init(unsigned world_seed);
void reload(unsigned world_seed); void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed); void add_path(const glm::vec3& pos, unsigned chunk_seed);
@@ -21,9 +23,8 @@ public:
float& river_probability(); float& river_probability();
private: private:
std::unordered_map<unsigned, RiverPath> m_paths; RiverHashMap m_paths;
unsigned m_seed = 0; unsigned m_seed = 0;
int m_sum = 0;
Random m_random; Random m_random;
float m_probability = 0.01f; float m_probability = 0.01f;
}; };

View File

@@ -31,8 +31,10 @@ class Player;
class TextureManager; class TextureManager;
class World { class World {
private: private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>; using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
using ChunkUpdateList = std::vector<std::pair<ChunkPos, Chunk>>; using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
using ConstChunkMap = using ConstChunkMap =
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>; std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>; using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
@@ -70,14 +72,16 @@ private:
void gen_chunks_internal(); void gen_chunks_internal();
void sync_player_pos(glm::vec3& player_pos); void sync_player_pos(glm::vec3& player_pos);
void compute_required_chunks(ChunkPosSet& required_chunks, void
ChunkHashMap& temp_neighbor); compute_required_chunks(ChunkPosSet& required_chunks,
ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&, void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&); const ChunkPosSet&);
void void
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor, build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
ChunkPtrUpdateList& affected_neighbor, ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks); const ChunkPairVector& new_chunks);
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&, void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
ConstChunkMap&); ConstChunkMap&);

View File

@@ -7,7 +7,17 @@ namespace HASH {
inline std::size_t str(std::string_view value) { inline std::size_t str(std::string_view value) {
return std::hash<std::string_view>{}(value); return std::hash<std::string_view>{}(value);
} }
inline uint32_t mix_hash(int32_t a, int32_t b, uint32_t fixed_seed) { inline uint32_t combine_32(uint32_t seed, uint32_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
inline uint32_t chunk_seed_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
uint32_t seed =
combine_32(combine_32(fixed_seed, (uint32_t)a), (uint32_t)b);
return seed;
}
/*
inline uint32_t chunk_seed_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
uint32_t h = fixed_seed; uint32_t h = fixed_seed;
h ^= (uint32_t)a * 0xcc9e2d51u; h ^= (uint32_t)a * 0xcc9e2d51u;
@@ -27,10 +37,8 @@ inline uint32_t mix_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
return h; 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 HASH
} // namespace Cubed } // namespace Cubed

View File

@@ -11,7 +11,7 @@ void extract_frustum_planes(const glm::mat4& mvp_matrix,
float smootherstep(float edge0, float edge1, float x); float smootherstep(float edge0, float edge1, float x);
bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents, bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
const std::vector<glm::vec4>& planes); const std::vector<glm::vec4>& planes);
float deterministic_random(int x, int z, uint64_t seed);
} // namespace Math } // namespace Math
} // namespace Cubed } // namespace Cubed

View File

@@ -104,7 +104,13 @@ bool BlockManager::is_blend(BlockType id) {
} }
return m_datas[id].is_blend; return m_datas[id].is_blend;
} }
bool BlockManager::is_transitional(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_transitional;
}
return m_datas[id].is_transitional;
}
void BlockManager::init() { void BlockManager::init() {
fs::path data_path{block_data_dir}; fs::path data_path{block_data_dir};
@@ -142,9 +148,10 @@ void BlockManager::init() {
auto is_gas = safe_get_value(block, "is_gas", false); auto is_gas = safe_get_value(block, "is_gas", false);
auto is_discard = safe_get_value(block, "is_discard", false); auto is_discard = safe_get_value(block, "is_discard", false);
auto is_blend = safe_get_value(block, "is_blend", false); auto is_blend = safe_get_value(block, "is_blend", false);
auto is_transitional = safe_get_value(block, "is_transitional", false);
m_datas.emplace_back(*id, *name, *is_liquid, *is_passable, m_datas.emplace_back(*id, *name, *is_liquid, *is_passable,
*is_cross_plane, *is_transparent, *is_gas, *is_cross_plane, *is_transparent, *is_gas,
*is_discard, *is_blend); *is_discard, *is_blend, *is_transitional);
} }
std::sort( std::sort(
m_datas.begin(), m_datas.end(), m_datas.begin(), m_datas.end(),

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

@@ -0,0 +1,34 @@
#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;
}
}
}
}
void OceanBuilder::build_vegetation() {}
ChunkGenerator& OceanBuilder::get_chunk_generator() {
return m_chunk_generator;
};
} // namespace Cubed

View File

@@ -5,11 +5,10 @@
namespace Cubed { namespace Cubed {
CaveCarver::CaveCarver() {} CaveCarver::CaveCarver() {}
std::unordered_map<unsigned, CavePath>& CaveCarver::paths() { return m_paths; } CaveCarver::CaveHashMap& CaveCarver::paths() { return m_paths; }
void CaveCarver::init(unsigned world_seed) { void CaveCarver::init(unsigned world_seed) {
m_seed = world_seed; m_seed = world_seed;
m_sum = 0;
m_random.init(m_seed); m_random.init(m_seed);
} }
@@ -20,16 +19,18 @@ void CaveCarver::reload(unsigned world_seed) {
} }
void CaveCarver::add_path(const glm::vec3& pos, unsigned chunk_seed) { void CaveCarver::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, CavePath{m_seed, m_sum, pos}); m_paths.emplace(chunk_seed, CavePath{chunk_seed, m_seed, pos});
m_sum++;
} }
void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos, void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) { unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed); {
if (it != m_paths.end()) { CaveHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return; return;
} }
}
Random random{chunk_seed}; Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_cave_probability))) { if (random.random_bool(static_cast<double>(m_cave_probability))) {
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE; const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
@@ -47,10 +48,17 @@ void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
} }
void CaveCarver::cleanup_finished_caves() { void CaveCarver::cleanup_finished_caves() {
std::erase_if(m_paths, std::vector<unsigned int> finished_keys;
[](const auto& kv) { return kv.second.is_finished(); }); 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::cave_sum() const { return m_sum; } int CaveCarver::cave_sum() const { return m_paths.size(); }
float& CaveCarver::cave_probability() { return m_cave_probability; } float& CaveCarver::cave_probability() { return m_cave_probability; }
} // namespace Cubed } // namespace Cubed

View File

@@ -6,10 +6,9 @@
#include <algorithm> #include <algorithm>
namespace Cubed { namespace Cubed {
CavePath::CavePath(unsigned int world_seed, int path_id, CavePath::CavePath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos) { const glm::vec3& start_pos) {
m_path_id = path_id; m_seed = HASH::combine_32(chunk_seed, world_seed);
m_seed = HASH::combine_32(world_seed, path_id);
m_random.init(m_seed); m_random.init(m_seed);
m_yaw = m_random.random_float(0.0f, 360.0f); m_yaw = m_random.random_float(0.0f, 360.0f);
m_pitch = m_random.random_float(-10.0f, 10.0f); m_pitch = m_random.random_float(-10.0f, 10.0f);
@@ -75,7 +74,8 @@ void CavePath::precompute_chunk_coverage() {
for (int cx = min_cx; cx <= max_cx; ++cx) for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz) for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert({cx, cz}); m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
} }
} }

View File

@@ -103,8 +103,7 @@ int Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z); return Chunk::index(pos.x, pos.y, pos.z);
} }
void Chunk::gen_vertex_data( void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
if (m_is_on_gen_vertex_data) { if (m_is_on_gen_vertex_data) {
return; return;
} }
@@ -199,9 +198,8 @@ void Chunk::gen_phase_six(
Logger::error("ChunkGenerator is Nullptr"); Logger::error("ChunkGenerator is Nullptr");
return; return;
} }
// This must be fully completed before any other operations can proceed!
m_generator->blend_surface_blocks_borders(neighbor_block); m_generator->blend_surface_blocks_borders(neighbor_block);
m_generator->generate_cave();
m_generator->generate_river();
} }
void Chunk::gen_phase_seven() { void Chunk::gen_phase_seven() {
@@ -209,6 +207,10 @@ void Chunk::gen_phase_seven() {
Logger::error("ChunkGenerator is Nullptr"); Logger::error("ChunkGenerator is Nullptr");
return; return;
} }
m_generator->ocean_build();
m_generator->generate_river();
m_generator->generate_cave();
m_generator->generate_vegetation(); m_generator->generate_vegetation();
mark_dirty(); mark_dirty();
m_generator = nullptr; m_generator = nullptr;
@@ -262,8 +264,7 @@ unsigned Chunk::seed() const {
BiomeConditions& Chunk::conditions() { return m_conditions; } BiomeConditions& Chunk::conditions() { return m_conditions; }
void Chunk::gen_vertices( void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1}, static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}}; {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
@@ -296,8 +297,9 @@ void Chunk::gen_vertices(
World::chunk_pos(world_nx, world_nz); World::chunk_pos(world_nx, world_nz);
auto is_culled = auto is_culled =
[&](const std::vector<BlockType>* chunk_blocks) { [&](const std::optional<std::vector<BlockType>>&
if (chunk_blocks == nullptr) { chunk_blocks) {
if (chunk_blocks == std::nullopt) {
return true; return true;
} }
int x, y, z; int x, y, z;

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"
@@ -17,11 +18,11 @@ namespace Cubed {
using enum BiomeType; using enum BiomeType;
constexpr int BLEND_RADIUS = 12; constexpr int BLEND_RADIUS = 8;
ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) { ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) {
ASSERT_MSG(is_init, "ChunksGenerator is not init"); ASSERT_MSG(is_init, "ChunksGenerator is not init");
ChunkPos pos = m_chunk.get_chunk_pos(); ChunkPos pos = m_chunk.get_chunk_pos();
unsigned seed = HASH::mix_hash(pos.x, pos.z, m_generator_seed); unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed);
m_random.init(seed); m_random.init(seed);
m_chunk_seed = seed; m_chunk_seed = seed;
} }
@@ -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,
@@ -451,9 +477,11 @@ void ChunkGenerator::blend_surface_blocks_borders(
for (int y = WORLD_HEIGHT - 1; y >= 0; --y) { for (int y = WORLD_HEIGHT - 1; y >= 0; --y) {
int idx = Chunk::index(nx, y, int idx = Chunk::index(nx, y,
nz); // linear index: y * area + z * size + x nz); // linear index: y * area + z * size + x
if (idx >= 0 && idx < static_cast<int>(blocks.size()) && if (idx >= 0 && idx < static_cast<int>(blocks.size())) {
blocks[idx] != 0) { BlockType neighbor_type = blocks[idx];
return blocks[idx]; if (BlockManager::is_transitional(neighbor_type)) {
return neighbor_type;
}
} }
} }
return 0; // fallback, should not happen for valid chunks return 0; // fallback, should not happen for valid chunks
@@ -473,8 +501,8 @@ void ChunkGenerator::blend_surface_blocks_borders(
// Weight map: type -> total weight // Weight map: type -> total weight
std::unordered_map<BlockType, float> weights; std::unordered_map<BlockType, float> weights;
weights[type_self] = 1.0f; // self weight float self_weight = 1.0f;
weights[type_self] = self_weight;
// --- Right neighbor (index 0) --- // --- Right neighbor (index 0) ---
if (neighbor_block[0] && x >= CHUNK_SIZE - BLEND_RADIUS) { if (neighbor_block[0] && x >= CHUNK_SIZE - BLEND_RADIUS) {
int dist = (CHUNK_SIZE - 1) - x; int dist = (CHUNK_SIZE - 1) - x;
@@ -523,47 +551,50 @@ void ChunkGenerator::blend_surface_blocks_borders(
} }
} }
if (weights.empty()) {
continue;
}
// Find type with maximum total weight // Find type with maximum total weight
BlockType final_type = type_self; BlockType final_type = type_self;
float max_weight = weights[type_self]; /*float max_weight = weights[type_self];
for (const auto& [type, w] : weights) { for (const auto& [type, w] : weights) {
if (w > max_weight) { if (w > max_weight) {
max_weight = w; max_weight = w;
final_type = type; final_type = type;
} }
}*/
float sum = 0.0f;
for (auto& kv : weights) {
sum += kv.second;
}
float rnd = m_random.random_float(0.0f, 1.0f);
float accum = 0.0f;
for (auto [t, w] : weights) {
accum += w / sum;
if (rnd < accum) {
final_type = t;
break;
}
} }
if (final_type == 0) { if (!BlockManager::is_transitional(final_type)) {
return; continue;
} }
// Update the top block if the type changed // Update the top block if the type changed
if (final_type != type_self) { if (final_type != type_self) {
// top block // top block
if (final_type == 7 && top_y > SEA_LEVEL) { BlockType new_surface = final_type;
if (type_self == 7) { m_blocks[Chunk::index(x, top_y, z)] = new_surface;
m_blocks[Chunk::index(x, top_y, z)] = 0;
} else {
m_blocks[Chunk::index(x, top_y, z)] = type_self;
}
} else {
m_blocks[Chunk::index(x, top_y, z)] = final_type;
}
// 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) {
fill_type = 4;
}
for (int y = top_y - 5; y < top_y; y++) {
if (fill_type == 7 && y > SEA_LEVEL) {
m_blocks[Chunk::index(x, y, z)] = 0;
} else { } else {
m_blocks[Chunk::index(x, y, z)] = fill_type; fill_type = final_type;
} }
for (int y = std::max(0, top_y - 5); y < top_y; y++) {
m_blocks[Chunk::index(x, y, z)] = fill_type;
} }
} }
} }
@@ -600,30 +631,40 @@ 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;
} }
} }
void ChunkGenerator::generate_cave() { void ChunkGenerator::ocean_build() { m_biome_builder->ocean_water_build(); }
auto& cave_carver = m_chunk.world().cave_carcer();
auto& paths = cave_carver.paths(); void ChunkGenerator::carve_worm(
const auto& chunk_pos = m_chunk.chunk_pos(); const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
auto& blocks = m_chunk.blocks(); 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_X = chunk_pos.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = chunk_pos.z * 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_X = CHUNK_MIN_X + SIZE_X - 1;
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 (const auto& point : points) {
for (const auto& point : path.points()) {
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;
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 min_x = static_cast<int>(std::floor(center.x - rad_xz));
int max_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 min_z = static_cast<int>(std::floor(center.z - rad_xz));
@@ -638,25 +679,61 @@ void ChunkGenerator::generate_cave() {
min_y = std::max(min_y, CHUNK_MIN_Y); min_y = std::max(min_y, CHUNK_MIN_Y);
max_y = std::min(max_y, CHUNK_MAX_Y); max_y = std::min(max_y, CHUNK_MAX_Y);
for (int wx = min_x; wx <= max_x; ++wx) { glm::vec3 right_raw =
int x = wx - CHUNK_MIN_X; glm::cross(point.tangent, glm::vec3(0.0f, 1.0f, 0.0f));
for (int wz = min_z; wz <= max_z; ++wz) { if (glm::dot(right_raw, right_raw) < 1e-6f)
int z = wz - CHUNK_MIN_Z; 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) { for (int wy = min_y; wy <= max_y; ++wy) {
int y = wy; if (wy == 0)
glm::vec3 pos(static_cast<float>(wx),
static_cast<float>(wy),
static_cast<float>(wz));
if (point.contains(pos)) {
if (y == 0) {
continue; continue;
} float dy = static_cast<float>(wy) - point.pos.y;
blocks[Chunk::index(x, y, z)] = 0;
} 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();
for (auto& [id, path] : paths) {
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
if (blocks[idx] == 7)
return;
if (y < WORLD_SIZE_Y - 1 && blocks[Chunk::index(x, y + 1, z)] == 7)
return;
blocks[idx] = 0;
});
path.clear_chunk(chunk_pos); path.clear_chunk(chunk_pos);
} }
} }
@@ -667,63 +744,27 @@ void ChunkGenerator::generate_river() {
auto& paths = river_worm.paths(); auto& paths = river_worm.paths();
const auto& chunk_pos = m_chunk.chunk_pos(); const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks(); 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;
bool is_river = false; bool is_river = false;
for (auto& [id, path] : paths) { for (auto& [id, path] : paths) {
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;
} }
const glm::vec3& center = point.pos; carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
float rad_xz = point.rad_xz; int idx = Chunk::index(x, y, z);
float rad_y = point.rad_y;
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);
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<float>(wx),
static_cast<float>(wy),
static_cast<float>(wz));
if (point.contains(pos)) {
if (y > SEA_LEVEL) { if (y > SEA_LEVEL) {
blocks[Chunk::index(x, y, z)] = 0; blocks[idx] = 0;
continue; return;
} }
is_river = true; is_river = true;
if (blocks[Chunk::index(x, y, z)] == 0) { if (blocks[idx] == 0) {
continue; return;
}
blocks[Chunk::index(x, y, z)] = 7;
}
}
}
}
} }
blocks[idx] = 7;
});
path.clear_chunk(chunk_pos); path.clear_chunk(chunk_pos);
} }
if (is_river) { if (is_river) {

View File

@@ -5,12 +5,15 @@
#include <algorithm> #include <algorithm>
namespace Cubed { namespace Cubed {
RiverPath::RiverPath(unsigned int world_seed, int path_id, RiverPath::RiverPath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos) { const glm::vec3& start_pos) {
m_path_id = path_id;
m_seed = HASH::combine_32(world_seed, path_id); m_seed = HASH::combine_32(chunk_seed, world_seed);
m_random.init(m_seed); m_random.init(m_seed);
m_yaw = m_random.random_float(0.0f, 360.0f); m_yaw = m_random.random_float(0.0f, 360.0f);
m_initial_yaw = m_yaw;
m_pitch = 0.0f; m_pitch = 0.0f;
m_start_path_point.pos = start_pos; m_start_path_point.pos = start_pos;
m_start_path_point.rad_xz = m_start_path_point.rad_xz =
@@ -41,14 +44,15 @@ void RiverPath::collect_path_points() {
float t = Math::smootherstep(0, m_step - 1, i); float t = Math::smootherstep(0, m_step - 1, i);
float drad_xz = m_start_path_point.rad_xz * (1.0f - t); float drad_xz = m_start_path_point.rad_xz * t;
float drad_y = m_start_path_point.rad_y * (1.0f - t); float drad_y = m_start_path_point.rad_y * t;
drad_xz = std::max(drad_xz, 4.0f); drad_xz = std::max(drad_xz, 4.0f);
drad_y = std::max(drad_y, 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, m_points.emplace_back(m_points[i].pos + glm::vec3{dx, dy, dz}, drad_xz,
drad_y); drad_y);
m_yaw += m_random.random_float(m_delta_angle_min, m_delta_angle_max); m_yaw += m_random.random_float(m_delta_angle_min, m_delta_angle_max);
m_yaw = std::clamp(m_yaw, m_initial_yaw - 10.0f, m_initial_yaw + 10.0f);
} }
auto n = m_points.size(); auto n = m_points.size();
if (n >= 2) { if (n >= 2) {
@@ -72,7 +76,8 @@ void RiverPath::precompute_chunk_coverage() {
for (int cx = min_cx; cx <= max_cx; ++cx) for (int cx = min_cx; cx <= max_cx; ++cx)
for (int cz = min_cz; cz <= max_cz; ++cz) for (int cz = min_cz; cz <= max_cz; ++cz)
m_pending_chunks.insert({cx, cz}); m_pending_chunks.insert(
std::make_pair(ChunkPos{cx, cz}, false));
} }
} }

View File

@@ -5,11 +5,11 @@
namespace Cubed { namespace Cubed {
RiverWorm::RiverWorm() {} RiverWorm::RiverWorm() {}
std::unordered_map<unsigned, RiverPath>& RiverWorm::paths() { return m_paths; } RiverWorm::RiverHashMap& RiverWorm::paths() { return m_paths; }
void RiverWorm::init(unsigned world_seed) { void RiverWorm::init(unsigned world_seed) {
m_seed = world_seed; m_seed = world_seed;
m_sum = 0;
m_random.init(m_seed); m_random.init(m_seed);
} }
@@ -20,16 +20,17 @@ void RiverWorm::reload(unsigned world_seed) {
} }
void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) { void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, RiverPath{m_seed, m_sum, pos}); m_paths.emplace(chunk_seed, RiverPath{chunk_seed, m_seed, pos});
m_sum++;
} }
void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos, void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) { unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed); {
if (it != m_paths.end()) { RiverHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return; return;
} }
}
Random random{chunk_seed}; Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_probability))) { if (random.random_bool(static_cast<double>(m_probability))) {
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE; const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
@@ -44,10 +45,17 @@ void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
} }
void RiverWorm::cleanup_finished_rivers() { void RiverWorm::cleanup_finished_rivers() {
std::erase_if(m_paths, std::vector<unsigned> finished_keys;
[](const auto& kv) { return kv.second.is_finished(); }); 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::river_sum() const { return m_sum; } int RiverWorm::river_sum() const { return m_paths.size(); }
float& RiverWorm::river_probability() { return m_probability; } float& RiverWorm::river_probability() { return m_probability; }
} // namespace Cubed } // namespace Cubed

View File

@@ -1,7 +1,6 @@
#include "Cubed/gameplay/tree.hpp" #include "Cubed/gameplay/tree.hpp"
#include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/chunk.hpp"
#include "Cubed/tools/log.hpp"
#include <array> #include <array>
@@ -32,7 +31,6 @@ bool build_tree(Chunk& chunk, const glm::ivec3& pos) {
auto& block = chunk.get_chunk_blocks(); auto& block = chunk.get_chunk_blocks();
if (block[Chunk::index(pos)] != 1) { if (block[Chunk::index(pos)] != 1) {
Logger::info("Root is not Grass Block");
return false; return false;
} }
for (const auto& d : TREE) { for (const auto& d : TREE) {

View File

@@ -5,6 +5,8 @@
#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/cubed_hash.hpp"
#include <execution>
namespace Cubed { namespace Cubed {
struct ChunkRenderData { struct ChunkRenderData {
@@ -89,6 +91,7 @@ void World::init_world() {
void World::init_chunks() { void World::init_chunks() {
hot_reload(); hot_reload();
while (!m_chunk_gen_finished) { while (!m_chunk_gen_finished) {
// Logger::info("World Spawn: {:.2f}%", m_chunk_gen_fraction.load());
std::this_thread::sleep_for(std::chrono::microseconds(200)); std::this_thread::sleep_for(std::chrono::microseconds(200));
} }
} }
@@ -319,8 +322,10 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.0f; m_chunk_gen_fraction = 0.0f;
m_chunk_gen_finished = false; m_chunk_gen_finished = false;
ChunkPosSet required_chunks; ChunkPosSet required_chunks;
ChunkHashMap temp_neighbor; ChunkPairVector temp_neighbor;
compute_required_chunks(required_chunks, temp_neighbor); std::vector<ChunkPos> need_gen_temp_chunks_pos;
compute_required_chunks(required_chunks, temp_neighbor,
need_gen_temp_chunks_pos);
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!"); ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
@@ -338,11 +343,14 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.1f; m_chunk_gen_fraction = 0.1f;
ChunkUpdateList new_chunks; ChunkPairVector new_chunks;
ChunkHashMap new_temp_chunks;
for (auto& pos : need_gen_chunks_pos) { for (auto& pos : need_gen_chunks_pos) {
new_chunks.push_back({pos, Chunk(*this, pos)}); new_chunks.push_back({pos, Chunk(*this, pos)});
} }
for (auto& pos : need_gen_temp_chunks_pos) {
new_temp_chunks.emplace(pos, Chunk(*this, pos));
}
ConstChunkMap new_chunks_neighbor; ConstChunkMap new_chunks_neighbor;
// affected neighbor // affected neighbor
ChunkPtrUpdateList affected_neighbor; ChunkPtrUpdateList affected_neighbor;
@@ -352,17 +360,28 @@ void World::gen_chunks_internal() {
// build new chunk, but the neighbor in m_chunks also need to re-build // build new chunk, but the neighbor in m_chunks also need to re-build
for (auto& [pos, chunk] : new_chunks) { std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[this](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one(); chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed()); m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed()); m_river_worm.try_to_add_path(pos, chunk.seed());
} });
std::for_each(new_temp_chunks.begin(), new_temp_chunks.end(),
[](std::pair<const ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one();
});
// precompute path to ensure the continuity of the path // precompute path to ensure the continuity of the path
for (auto& [pos, chunk] : temp_neighbor) { std::for_each(std::execution::par, temp_neighbor.begin(),
temp_neighbor.end(),
[this](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_one(); chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed()); m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed()); m_river_worm.try_to_add_path(pos, chunk.seed());
} });
m_chunk_gen_fraction = 0.2f; m_chunk_gen_fraction = 0.2f;
@@ -400,15 +419,19 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.3f; m_chunk_gen_fraction = 0.3f;
for (auto& [pos, chunks] : new_chunks) { std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[](std::pair<ChunkPos, Chunk>& pair) {
auto& [pos, chunks] = pair;
chunks.gen_phase_three(); chunks.gen_phase_three();
});
for (auto& [pos, chunk] : new_temp_chunks) {
chunk.gen_phase_three();
} }
// for (auto& [pos, chunks] : temp_neighbor) { // for (auto& [pos, chunks] : temp_neighbor) {
// chunks.gen_phase_three(); // chunks.gen_phase_three();
// } // }
m_chunk_gen_fraction = 0.4f;
/* /*
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
for (auto& [pos, chunks] : temp_neighbor) { for (auto& [pos, chunks] : temp_neighbor) {
@@ -452,68 +475,106 @@ void World::gen_chunks_internal() {
} }
} }
*/ */
m_chunk_gen_fraction = 0.4f;
m_chunk_gen_fraction = 0.5f;
for (auto& [pos, chunks] : new_chunks) { for (auto& [pos, chunks] : new_chunks) {
chunks.gen_phase_five(); chunks.gen_phase_five();
} }
m_chunk_gen_fraction = 0.45f;
for (auto& [pos, chunk] : new_temp_chunks) {
chunk.gen_phase_five();
}
m_chunk_gen_fraction = 0.5f;
/* /*
for (auto& [pos, chunks] : temp_neighbor) { for (auto& [pos, chunks] : temp_neighbor) {
chunks.gen_phase_five(); chunks.gen_phase_five();
} }
*/ */
std::array<std::optional<std::vector<BlockType>>, 4> neighbor_blocks_data; std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
for (auto& [pos, chunks] : new_chunks) { new_chunks_surface_blend_data(new_chunks.size());
for (size_t idx = 0; idx < new_chunks.size(); idx++) {
auto& [pos, chunk] = new_chunks[idx];
new_chunks_surface_blend_data[idx].first = &chunk;
{ {
// std::lock_guard lk(m_chunks_mutex); // std::lock_guard lk(m_chunks_mutex);
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
auto neighbor_pos = pos + CHUNK_DIR[i]; auto neighbor_pos = pos + CHUNK_DIR[i];
auto it = new_chunks_neighbor.find(neighbor_pos); auto it = new_chunks_neighbor.find(neighbor_pos);
if (it == new_chunks_neighbor.end()) { if (it == new_chunks_neighbor.end()) {
neighbor_blocks_data[i] = std::nullopt; auto it = new_temp_chunks.find(neighbor_pos);
if (it == new_temp_chunks.end()) {
new_chunks_surface_blend_data[idx].second[i] =
std::nullopt;
Logger::warn(
"Can't find neighbor for chunk surface blend");
continue; continue;
} }
neighbor_blocks_data[i] = it->second->get_chunk_blocks(); new_chunks_surface_blend_data[idx].second[i] =
it->second.get_chunk_blocks();
continue;
}
new_chunks_surface_blend_data[idx].second[i] =
it->second->get_chunk_blocks();
} }
} }
chunks.gen_phase_six(neighbor_blocks_data);
} }
for (auto& [pos, chunks] : new_chunks) { std::for_each(
chunks.gen_phase_seven(); std::execution::par, new_chunks_surface_blend_data.begin(),
} new_chunks_surface_blend_data.end(),
[](std::pair<Chunk*, OptionalBlockVectorArray>& new_chunk_data) {
auto& [chunk, neighbor_data] = new_chunk_data;
chunk->gen_phase_six(neighbor_data);
});
m_chunk_gen_fraction = 0.55f;
std::for_each(std::execution::par, new_chunks.begin(), new_chunks.end(),
[](std::pair<ChunkPos, Chunk>& new_chunk) {
auto& [pos, chunk] = new_chunk;
chunk.gen_phase_seven();
});
m_chunk_gen_fraction = 0.6f; m_chunk_gen_fraction = 0.6f;
std::array<const std::vector<BlockType>*, 4> neighbor_block; std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
for (auto& [pos, chunk] : new_chunks) { new_chunk_vertices_data(new_chunks.size());
for (size_t idx = 0; idx < new_chunks.size(); idx++) {
auto& [pos, chunk] = new_chunks[idx];
new_chunk_vertices_data[idx].first = &chunk;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]); auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
if (it != new_chunks_neighbor.end()) { if (it != new_chunks_neighbor.end()) {
neighbor_block[i] = &(it->second->get_chunk_blocks()); new_chunk_vertices_data[idx].second[i] =
(it->second->get_chunk_blocks());
} else { } else {
neighbor_block[i] = nullptr; new_chunk_vertices_data[idx].second[i] = std::nullopt;
} }
} }
chunk.gen_vertex_data(neighbor_block);
} }
std::for_each(
std::execution::par, new_chunk_vertices_data.begin(),
new_chunk_vertices_data.end(),
[](std::pair<Chunk*, OptionalBlockVectorArray>& new_chunk_data) {
auto& [chunk, neighbor_data] = new_chunk_data;
chunk->gen_vertex_data(neighbor_data);
});
m_chunk_gen_fraction = 0.7f; m_chunk_gen_fraction = 0.7f;
build_neighbor_context_for_affected_neighbors(affected_neighbor, build_neighbor_context_for_affected_neighbors(affected_neighbor,
new_chunks_neighbor); new_chunks_neighbor);
m_chunk_gen_fraction = 0.8f; m_chunk_gen_fraction = 0.8f;
OptionalBlockVectorArray neighbor_block;
for (auto& [pos, chunk] : affected_neighbor) { for (auto& [pos, chunk] : affected_neighbor) {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]); auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
if (it != new_chunks_neighbor.end()) { if (it != new_chunks_neighbor.end()) {
neighbor_block[i] = &(it->second->get_chunk_blocks()); neighbor_block[i] = (it->second->get_chunk_blocks());
} else { } else {
neighbor_block[i] = nullptr; neighbor_block[i] = std::nullopt;
} }
} }
chunk->gen_vertex_data(neighbor_block); chunk->gen_vertex_data(neighbor_block);
@@ -539,32 +600,53 @@ void World::sync_player_pos(glm::vec3& player_pos) {
player_pos = m_gen_player_pos; player_pos = m_gen_player_pos;
} }
void World::compute_required_chunks(ChunkPosSet& required_chunks, void World::compute_required_chunks(
ChunkHashMap& temp_neighbor) { ChunkPosSet& required_chunks, ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos) {
glm::vec3 player_pos; glm::vec3 player_pos;
sync_player_pos(player_pos); sync_player_pos(player_pos);
int x = std::floor(player_pos.x); int x = std::floor(player_pos.x);
int z = std::floor(player_pos.z); int z = std::floor(player_pos.z);
auto [chunk_x, chunk_z] = chunk_pos(x, z); auto [chunk_x, chunk_z] = chunk_pos(x, z);
int radius = m_rendering_distance;
int r2 = radius * radius;
required_chunks.reserve(radius * radius);
required_chunks.reserve(m_rendering_distance * m_rendering_distance); for (int dx = -radius; dx <= radius; ++dx) {
int half = m_rendering_distance / 2; for (int dz = -radius; dz <= radius; ++dz) {
for (int u = chunk_x - half; u <= chunk_x + half; ++u) { if (dx * dx + dz * dz <= r2) {
for (int v = chunk_z - half; v <= chunk_z + half; ++v) { required_chunks.emplace(chunk_x + dx, chunk_z + dz);
required_chunks.emplace(u, v); }
}
}
int new_radius = radius + 1;
int new_r2 = new_radius * new_radius;
for (int dx = -new_radius; dx <= new_radius; ++dx) {
for (int dz = -new_radius; dz <= new_radius; ++dz) {
if (dx * dx + dz * dz <= new_r2) {
int nx = chunk_x + dx;
int nz = chunk_z + dz;
auto it = required_chunks.find({nx, nz});
if (it == required_chunks.end()) {
need_gen_temp_chunks_pos.push_back({nx, nz});
}
}
} }
} }
int max_path_len = std::max(CavePath::step_max(), RiverPath::step_max()); int max_path_len = std::max(CavePath::step_max(), RiverPath::step_max());
half = std::ceil(static_cast<float>(max_path_len) / CHUNK_SIZE) * 2; radius = max_path_len / 2;
for (int u = chunk_x - half; u <= chunk_x + half; ++u) { r2 = radius * radius;
for (int v = chunk_z - half; v <= chunk_z + half; ++v) { for (int dx = -radius; dx <= radius; ++dx) {
ChunkPos pos{u, v}; for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
ChunkPos pos{chunk_x + dx, chunk_z + dz};
auto it = required_chunks.find(pos); auto it = required_chunks.find(pos);
if (it != required_chunks.end()) { if (it != required_chunks.end()) {
continue; continue;
} }
temp_neighbor.emplace(pos, Chunk(*this, pos)); temp_neighbor.emplace_back(pos, Chunk(*this, pos));
}
} }
} }
} }
@@ -591,7 +673,7 @@ void World::sync_and_collect_missing_chunks(
void World::build_neighbor_context_for_new_chunks( void World::build_neighbor_context_for_new_chunks(
ConstChunkMap& new_chunks_neighbor, ChunkPtrUpdateList& affected_neighbor, ConstChunkMap& new_chunks_neighbor, ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks) { const ChunkPairVector& new_chunks) {
{ {
std::lock_guard lk(m_chunks_mutex); std::lock_guard lk(m_chunks_mutex);
for (auto& [pos, chunk] : new_chunks) { for (auto& [pos, chunk] : new_chunks) {
@@ -836,13 +918,13 @@ void World::update(float delta_time) {
for (auto& [pos, chunk] : m_chunks) { for (auto& [pos, chunk] : m_chunks) {
if (chunk.is_dirty()) { if (chunk.is_dirty()) {
// the curial fator influence // the curial fator influence
std::array<const std::vector<BlockType>*, 4> neighbor_block; OptionalBlockVectorArray neighbor_block;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
auto it = m_chunks.find(pos + CHUNK_DIR[i]); auto it = m_chunks.find(pos + CHUNK_DIR[i]);
if (it != m_chunks.end()) { if (it != m_chunks.end()) {
neighbor_block[i] = &(it->second.get_chunk_blocks()); neighbor_block[i] = (it->second.get_chunk_blocks());
} else { } else {
neighbor_block[i] = nullptr; neighbor_block[i] = std::nullopt;
} }
} }
chunk.gen_vertex_data(neighbor_block); chunk.gen_vertex_data(neighbor_block);

View File

@@ -5,8 +5,15 @@ namespace Cubed {
Random::Random() {} Random::Random() {}
Random::Random(unsigned seed) { init(seed); } Random::Random(unsigned seed) { init(seed); }
bool Random::random_bool(double probability) { bool Random::random_bool(double probability) {
std::bernoulli_distribution dist(probability); if (probability <= 0.0)
return dist(m_engine); return false;
if (probability >= 1.0)
return true;
const double MAX_VAL = 4294967295.0;
unsigned threshold = static_cast<unsigned>(probability * MAX_VAL);
return m_engine() <= threshold;
} }
std::mt19937& Random::engine() { return m_engine; } std::mt19937& Random::engine() { return m_engine; }
@@ -18,12 +25,23 @@ void Random::init(unsigned seed) {
m_engine.seed(seed); m_engine.seed(seed);
} }
int Random::random_int(int min, int max) { int Random::random_int(int min, int max) {
std::uniform_int_distribution<int> dist(min, max); unsigned range = static_cast<unsigned>(max - min) + 1;
return dist(m_engine);
const unsigned LIMIT =
(std::numeric_limits<unsigned>::max() / range) * range;
unsigned r;
do {
r = m_engine();
} while (r >= LIMIT);
return min + static_cast<int>(r % range);
} }
float Random::random_float(float min, float max) { float Random::random_float(float min, float max) {
std::uniform_real_distribution<float> dist(min, max);
return dist(m_engine);
}
unsigned r = m_engine() >> 8;
float t = static_cast<float>(r) * (1.0f / 16777216.0f);
float result = min + t * (max - min);
return result;
}
} // namespace Cubed } // namespace Cubed

View File

@@ -59,7 +59,12 @@ bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
} }
return true; return true;
} }
float deterministic_random(int x, int z, uint64_t seed) {
uint64_t h = seed;
h = h * 6364136223846793005ULL + (uint64_t)x;
h = h * 6364136223846793005ULL + (uint64_t)z;
return (float)(h >> 40) / (float)(1 << 24);
}
} // namespace Math } // namespace Math
} // namespace Cubed } // namespace Cubed