Compare commits

...

10 Commits

Author SHA1 Message Date
31bf337f6f perf(shadow): increase depth map resolution and refine PCF sampling 2026-06-17 15:23:38 +08:00
662f10047a feat(renderer): add shadow mapping with PCF soft shadows
Introduce shadow mapping using a dedicated depth framebuffer and shader. The block fragment shader now performs percentage-closer filtering (PCF) with Poisson disk sampling and random rotation for soft shadows. The vertex shader outputs light-space coordinates. A new depth shader pair handles rendering from the light's perspective, discarding transparent fragments. The renderer sets up the light projection based on the camera position and sun direction, and applies the shadow factor to diffuse lighting. Day/night cycle can now be toggled off in the world server thread.
2026-06-16 22:27:08 +08:00
943c6f1f46 fix(game_time): use unsigned tick type and enforce positive tick speed 2026-06-16 18:51:47 +08:00
7ede49da72 feat(renderer): make ambient strength adjustable via dev panel 2026-06-16 18:44:59 +08:00
a4f92e3659 feat(world): add day/night cycle with server tick system 2026-06-16 16:17:47 +08:00
f43ef64691 feat(rendering): add basic diffuse and ambient lighting to block rendering 2026-06-16 13:53:47 +08:00
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
zhenyan121
bac3df801b refactor: chunk render (#15)
* refactor(renderer): centralize VAO setup and rename init_underwater

* refactor(gameplay): replace VBO with VAO for vertex data

* fix(renderer): move depth test enable to world rendering

The `glEnable(GL_DEPTH_TEST)` call was incorrectly placed in the general `render()` function, affecting UI and text rendering. Moved it to the start of the world rendering loop to ensure depth testing is only active during 3D world pass.
2026-06-11 14:58:39 +08:00
zhenyan121
d0bc8d627f refactor: transparent render (#14)
* fix(renderer): defer uniform location retrieval and add view matrix in outline rendering

* refactor(gameplay): encapsulate per-type vertex data into VertexData struct

* feat(rendering): separate transparent blocks into discard and blend modes

* feat(renderer): implement order-independent transparency

* fix(shaders): reduce alpha discard threshold to 0.8
2026-06-11 12:21:19 +08:00
60 changed files with 1792 additions and 619 deletions

View File

@@ -58,6 +58,21 @@ if (WIN32)
if(TARGET freetype)
add_library(Freetype::Freetype ALIAS freetype)
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()
FetchContent_Declare(
@@ -83,7 +98,6 @@ FetchContent_MakeAvailable(tomlplusplus)
add_subdirectory(third_party/imgui)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
add_executable(${PROJECT_NAME}
@@ -123,6 +137,8 @@ add_executable(${PROJECT_NAME}
src/gameplay/river_worm.cpp
src/gameplay/river_path.cpp
src/block.cpp
src/gameplay/vertex_data.cpp
src/gameplay/builders/ocean_builder.cpp
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -162,10 +178,11 @@ target_link_libraries(${PROJECT_NAME}
Freetype::Freetype
tomlplusplus::tomlplusplus
imgui
tbb
)
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()
if (UNIX AND NOT APPLE)
@@ -185,3 +202,19 @@ if (UNIX AND NOT APPLE)
target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER})
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

@@ -1,7 +1,10 @@
id = 0
is_blend = false
is_cross_plane = false
is_discard = true
is_gas = true
is_liquid = false
is_passable = true
is_transitional = false
is_transparent = true
name = 'air'

View File

@@ -1,7 +1,10 @@
id = 2
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = true
is_transparent = false
name = 'dirt'

View File

@@ -1,7 +1,10 @@
id = 9
is_blend = false
is_cross_plane = true
is_discard = true
is_gas = false
is_liquid = false
is_passable = true
is_transitional = false
is_transparent = true
name = 'grass'

View File

@@ -1,7 +1,10 @@
id = 1
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = true
is_transparent = false
name = 'grass_block'

View File

@@ -1,7 +1,10 @@
id = 6
is_blend = false
is_cross_plane = false
is_discard = true
is_gas = false
is_liquid = false
is_passable = false
is_transitional = false
is_transparent = true
name = 'leaf'

View File

@@ -1,7 +1,10 @@
id = 5
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = false
is_transparent = false
name = 'log'

View File

@@ -1,7 +1,10 @@
id = 4
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = true
is_transparent = false
name = 'sand'

View File

@@ -1,7 +1,10 @@
id = 8
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = true
is_transparent = false
name = 'snowy_grass_block'

View File

@@ -1,7 +1,10 @@
id = 3
is_blend = false
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = false
is_passable = false
is_transitional = true
is_transparent = false
name = 'stone'

View File

@@ -5,3 +5,6 @@ is_gas = false
is_passable = false
is_cross_plane = false
is_transparent = false
is_discard = false
is_blend = false
is_transitional = false

View File

@@ -1,7 +1,10 @@
id = 7
is_blend = true
is_cross_plane = false
is_discard = false
is_gas = false
is_liquid = true
is_passable = true
is_transitional = false
is_transparent = true
name = 'water'

View File

@@ -0,0 +1,28 @@
#version 460
layout (location = 0) out vec4 accum;
layout (location = 1) out float reveal;
in vec2 tc;
flat in int tex_layer;
in float v_depth;
layout (binding = 0) uniform sampler2DArray samp;
float weight(float z, float a) {
float intermediate = 0.03 / (1e-5 + pow(z / 200.0, 4.0));
return a * clamp(intermediate, 1e-2, 3e2);
}
void main() {
vec4 color = texture(samp, vec3(tc, tex_layer));
float alpha = color.a;
if (alpha < 1e-4) discard;
float w = weight(v_depth, alpha);
accum = vec4(color.rgb * alpha * w, alpha * w);
reveal = alpha;
}

View File

@@ -0,0 +1,21 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
out vec2 tc;
flat out int tex_layer;
out float v_depth;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
void main(void) {
vec4 view_pos = mv_matrix * vec4(pos, 1.0);
gl_Position = proj_matrix * view_pos;
tc = texCoord;
tex_layer = int(layer);
v_depth = -view_pos.z;
}

View File

@@ -0,0 +1,19 @@
#version 460
uniform sampler2D u_accumTex;
uniform sampler2D u_revealTex;
in vec2 TexCoord;
out vec4 FragColor;
void main() {
vec4 a = texture(u_accumTex, TexCoord);
float r = texture(u_revealTex, TexCoord).r;
if (a.a < 1e-4) discard;
vec3 color = a.rgb / max(a.a, 1e-5);
float transmittance = r;
float opacity = 1.0 - transmittance;
FragColor = vec4(color * opacity, opacity);
}

View File

@@ -0,0 +1,11 @@
#version 460
layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 texCoord;
out vec2 TexCoord;
void main() {
gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
TexCoord = texCoord;
}

View File

@@ -1,15 +1,106 @@
#version 460
in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec4 FragPosLightSpace;
flat in int tex_layer;
out vec4 color;
layout (binding = 0) uniform sampler2D shadowMap;
layout (binding = 1) uniform sampler2DArray samp;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 sunlightDir;
const vec2 poissonDisk[8] = vec2[](
vec2( 0.1440, 0.7659), vec2(-0.5761, 0.4479),
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
);
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}
float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z < 0.0 || projCoords.z > 1.0) {
return 0.0;
}
float currentDepth = projCoords.z;
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
float shadow = 0.0;
float bias =
max(
0.0003,
0.001 * (1.0 - dot(norm, lightDir))
);
vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
float angle = random(seed) * 6.2831853;; // 2*PI
float s = sin(angle), c = cos(angle);
mat2 rot = mat2(c, -s, s, c);
float radius = 0.7;
const int samples = 8;
for (int i = 0; i < samples; ++i) {
vec2 offset = rot * poissonDisk[i] * radius * texelSize;
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
/*
for (int x = -1; x <= 1; ++x) {
for (int y = -1; y <= 1; ++y) {
vec2 offset = vec2(x, y) * texelSize;
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
}
shadow /= 9.0;
*/
// pcf off
//float pcfDepth =
//texture(shadowMap, projCoords.xy).r;
//shadow =
// currentDepth - bias > pcfDepth
// ? 1.0
// : 0.0;
return shadow;
}
layout (binding = 0) uniform sampler2DArray samp;
void main(void) {
color = texture(samp, vec3(tc, tex_layer));
if (color.a < 0.1) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
if (objectColor.a < 0.8) {
discard;
}
vec3 lightDir = normalize(-sunlightDir);
vec3 ambient = ambientStrength * sunlightColor;
vec3 norm = normalize(normal);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * sunlightColor;
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb, objectColor.a);
//color = varyingColor;
}

View File

@@ -3,8 +3,12 @@
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
layout (location = 3) in vec3 aNormal;
out vec2 tc;
out vec3 normal;
out vec3 vert_pos;
flat out int tex_layer;
out vec4 FragPosLightSpace;
mat4 buildRotateX(float rad);
mat4 buildRotateY(float rad);
@@ -13,13 +17,21 @@ mat4 buildTranslate(float x, float y, float z);
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
uniform mat4 lightSpaceMatrix;
void main(void) {
gl_Position = proj_matrix * mv_matrix * vec4(pos, 1.0);
vec4 viewPos = mv_matrix * vec4(pos, 1.0);
vert_pos = pos;
tc = texCoord;
tex_layer = int(layer);
normal = mat3(norm_matrix) * aNormal;
FragPosLightSpace = lightSpaceMatrix * vec4(pos, 1.0);
gl_Position = proj_matrix * viewPos;
}
mat4 buildTranslate(float x, float y, float z) {

View File

@@ -0,0 +1,12 @@
#version 460
in vec2 tc;
flat in int tex_layer;
layout (binding = 1) uniform sampler2DArray samp;
void main() {
vec4 texColor = texture(samp, vec3(tc, tex_layer));
if (texColor.a < 0.8)
discard;
//gl_FragDepth = gl_FragCoord.z;
}

View File

@@ -0,0 +1,12 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
uniform mat4 lightSpaceMatrix;
out vec2 tc;
flat out int tex_layer;
void main() {
tc = texCoord;
tex_layer = int(layer);
gl_Position = lightSpaceMatrix * vec4(pos, 1.0);
}

View File

@@ -2,8 +2,10 @@
out vec4 frag_color;
uniform vec3 color;
void main(void) {
frag_color = vec4(0.529, 0.808, 0.922, 1.0);
frag_color = vec4(color, 1.0);
}

View File

@@ -32,6 +32,7 @@ public:
const glm::vec3& get_camera_pos() const;
bool is_under_water() const;
glm::vec3 get_camera_front() const;
};
} // namespace Cubed

View File

@@ -6,7 +6,7 @@ namespace Cubed {
constexpr int WORLD_SIZE_Y = 256;
constexpr int CHUNK_SIZE = 16;
constexpr int SEA_LEVEL = 64;
constexpr int SEA_LEVEL = 63;
constexpr int MAX_UI_NUM = 1;
constexpr int MAX_BLOCK_STATUS = 1;
@@ -23,9 +23,9 @@ constexpr float DEFAULT_MAX_RUN_SPEED = 7.0f;
constexpr float DEFAULT_ACCELERATION = 10.0f;
constexpr float DEFAULT_DECELERATION = 15.0f;
constexpr float DEFAULT_G = 22.5f;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
constexpr int SIZE_X = CHUNK_SIZE;
constexpr int SIZE_Y = WORLD_SIZE_Y;
constexpr int SIZE_Z = CHUNK_SIZE;
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};

View File

@@ -44,8 +44,11 @@ private:
bool m_need_save_config = false;
bool m_gen_thread_running = true;
int m_theme = 0;
int m_pre_set_day_tick = 0;
int m_pre_set_tick_speed = 1;
void show_about_table_bar();
void show_biome_table_bar();
void show_time_table_bar();
void show_cave_table_bar();
void show_river_table_bar();
void show_settings_tab_item();

View File

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

View File

@@ -47,11 +47,17 @@ struct BlockData {
bool is_passable = false;
bool is_cross_plane = false;
bool is_transparent = false;
bool is_discard = false;
bool is_blend = false;
bool is_transitional = false;
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 transitional)
: name(b_name), id(b_id), is_liquid(liquid), is_gas(gas),
is_passable(passable), is_cross_plane(cross_plane),
is_transparent(transparent) {}
is_transparent(transparent), is_discard(discard), is_blend(blend),
is_transitional(transitional) {}
};
class BlockManager {
@@ -69,6 +75,10 @@ public:
static bool is_cross_plane(BlockType id);
static bool is_transparent(BlockType id);
static bool is_passable(BlockType id);
static bool is_discard(BlockType id);
static bool is_blend(BlockType id);
static bool is_transitional(BlockType id);
static BlockType cross_plane_index(BlockType id);
private:

View File

@@ -9,6 +9,7 @@ public:
virtual ChunkGenerator& get_chunk_generator() = 0;
virtual void build_biome() = 0;
virtual void build_vegetation() = 0;
void ocean_water_build();
protected:
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
#include "Cubed/gameplay/cave_path.hpp"
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CaveCarver {
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
public:
CaveCarver();
std::unordered_map<unsigned, CavePath>& paths();
CaveHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed);
@@ -15,9 +19,8 @@ public:
float& cave_probability();
private:
std::unordered_map<unsigned, CavePath> m_paths;
CaveHashMap m_paths;
unsigned m_seed = 0;
int m_sum = 0;
Random m_random;
float m_cave_probability = 0.035f;
};

View File

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

View File

@@ -4,7 +4,7 @@
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/primitive_data.hpp"
#include "Cubed/gameplay/vertex_data.hpp"
#include <atomic>
#include <mutex>
@@ -14,16 +14,15 @@ class World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
static constexpr int VERTEX_DATA_SUM = 4;
std::atomic<bool> m_dirty{false};
std::atomic<bool> m_need_upload{true};
std::atomic<bool> m_is_on_gen_vertex_data{false};
std::atomic<size_t> m_normal_vertices_sum = 0;
std::atomic<size_t> m_cross_vertices_sum = 0;
std::atomic<size_t> m_transparent_vertices_sum = 0;
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
@@ -34,12 +33,14 @@ private:
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
GLuint m_normal_vbo = 0;
GLuint m_cross_plane_vbo = 0;
GLuint m_transparent_normal_vbo = 0;
std::vector<Vertex> m_normal_vertices;
std::vector<Vertex> m_cross_plane_vertices;
std::vector<Vertex> m_transparent_normal_vertices;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
*/
std::vector<VertexData> m_vertex_data;
float frequency = 0.01f;
float height = 80;
unsigned m_seed = 0;
@@ -47,9 +48,9 @@ private:
BiomeConditions m_conditions;
void clear_dirty();
void gen_normal_vertices(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void gen_cross_plane_vertices();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id);
public:
Chunk(World& world, ChunkPos chunk_pos);
@@ -97,18 +98,20 @@ public:
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void gen_vertex_data(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block);
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
void upload_to_gpu();
GLuint get_normal_vbo() const;
GLuint get_normal_vao() const;
size_t get_normal_vertices_sum() const;
GLuint get_cross_vbo() const;
GLuint get_cross_vao() const;
size_t get_cross_vertices_sum() const;
GLuint get_transparent_vbo() const;
size_t get_transparent_vertices_sum() const;
GLuint get_normal_discard_vao() const;
size_t get_normal_discard_vertices_sum() const;
GLuint get_normal_blend_vao() const;
size_t get_normal_blend_vertices_sum() const;
bool is_dirty() const;
void mark_dirty();

View File

@@ -4,6 +4,7 @@
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <atomic>
@@ -45,6 +46,7 @@ public:
Chunk& chunk();
Random& random();
const std::array<BiomeType, 8>& neighbor_biome() const;
void ocean_build();
void generate_cave();
void generate_river();
@@ -60,6 +62,9 @@ private:
unsigned m_chunk_seed = 0;
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

View File

@@ -16,7 +16,14 @@ struct ChunkPos {
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 {
return ChunkPos{x + pos.x, z + pos.z};
}

View File

@@ -0,0 +1,9 @@
#pragma once
using TickType = unsigned long long;
constexpr int DEFAULT_PER_TICK_TIME = 50;
constexpr TickType DAY_TIME = 24000;
constexpr TickType PER_HOUR = 1000;

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Cubed/primitive_data.hpp"
#include <atomic>
#include <glad/glad.h>
#include <vector>
namespace Cubed {
class World;
struct VertexData {
std::vector<Vertex3D> m_vertices;
GLuint m_vbo = 0;
GLuint m_vao = 0;
std::atomic<std::size_t> m_sum{0};
World& m_world;
VertexData(World& world);
~VertexData();
VertexData(const VertexData&) = delete;
VertexData(VertexData&&) noexcept;
VertexData& operator=(const VertexData&) = delete;
VertexData& operator=(VertexData&&) noexcept;
void upload();
void update_sum();
};
} // namespace Cubed

View File

@@ -2,6 +2,7 @@
#include "Cubed/AABB.hpp"
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/river_worm.hpp"
#include <atomic>
@@ -15,12 +16,14 @@
namespace Cubed {
struct ChunkRenderSnapshot {
GLuint normal_vbo;
GLuint normal_vao;
size_t normal_vertices_count;
GLuint cross_vbo;
GLuint cross_vao;
size_t cross_vertices_count;
GLuint transparent_vbo;
size_t transparent_vertices_count;
GLuint normal_discard_vao;
size_t normal_discard_vertices_count;
GLuint normal_blend_vao;
size_t normal_blend_vertices_count;
glm::vec3 center;
glm::vec3 half_extents;
};
@@ -29,32 +32,49 @@ class Player;
class TextureManager;
class World {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
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 =
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
ChunkHashMap m_chunks;
std::unordered_map<std::size_t, Player> m_players;
std::vector<glm::vec4> m_planes;
std::thread m_gen_thread;
std::thread m_server_thread;
std::stop_source m_server_stop_source;
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
std::atomic<TickType> m_day_tick = 6000;
mutable std::mutex m_chunks_mutex;
std::mutex m_gen_signal_mutex;
std::mutex m_new_chunk_queue_mutex;
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
std::mutex m_gen_player_pos_mutex;
std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao;
std::condition_variable m_gen_cv;
std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_day_night_cycle{true};
std::atomic<int> m_rendering_distance{24};
std::atomic<float> m_chunk_gen_fraction{0.0f};
std::atomic<TickType> m_game_ticks{0};
std::vector<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk;
@@ -66,14 +86,16 @@ private:
void gen_chunks_internal();
void sync_player_pos(glm::vec3& player_pos);
void compute_required_chunks(ChunkPosSet& required_chunks,
ChunkHashMap& temp_neighbor);
void
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>&,
const ChunkPosSet&);
void
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks);
const ChunkPairVector& new_chunks);
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
ConstChunkMap&);
@@ -90,9 +112,6 @@ public:
Player& get_player(const std::string& name);
void init_world();
bool is_aabb_in_frustum(const glm::vec3& center,
const glm::vec3& half_extents);
int get_block(const glm::ivec3& block_pos) const;
bool is_solid(const glm::ivec3& block_pos) const;
bool can_pass_block(const glm::ivec3& block_pos) const;
@@ -100,15 +119,12 @@ public:
static ChunkPos chunk_pos(int world_x, int world_z);
void need_gen();
void render(const glm::mat4& mvp_matrix,
const TextureManager& texture_manager,
const glm::vec3& camera_pos);
void set_block(const glm::ivec3& pos, unsigned id);
void update(float delta_time);
void push_delete_vbo(GLuint vbo);
void push_delete_vao(GLuint vao);
void hot_reload();
void rebuild_world();
@@ -117,10 +133,22 @@ public:
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_gen_thread();
void start_server_thread();
void stop_gen_thread();
void stop_server_thread();
void serever_run(std::stop_token stoken);
CaveCarver& cave_carcer();
RiverWorm& river_worm();
std::vector<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& render_snapshots();
glm::vec3 sunlight_dir() const;
TickType game_tick() const;
TickType day_tick() const;
void day_tick(TickType tick);
int per_tick_time() const;
void per_tick_time(int ms);
};
} // namespace Cubed

View File

@@ -91,6 +91,51 @@ constexpr float TEX_COORDS[6][6][2] = {
{0.0f, 1.0f}, // back left
{0.0f, 0.0f}} // front left
};
constexpr float NORMALS[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f}},
// ===== right (x = +1) =====
{{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f}},
// ===== back (z = -1) =====
{{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f}},
// ===== left (x = -1) =====
{{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f}},
// ===== top (y = +1) =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}},
// ===== bottom (y = -1) =====
{{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f}}};
#pragma endregion
constexpr float CUBE_VER[24] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0,
@@ -148,6 +193,24 @@ constexpr float CROSS_TEX_COORDS[2][6][2] = {
{1.0f, 1.0f}, // bottom right
{0.0f, 1.0f}}, // bottom left
};
constexpr float CROSS_NORMALS[2][6][3] = {
// ===== Plane 1: upward =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}},
// ===== Plane 2: upward =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}}};
#pragma endregion
constexpr float QUAD_VERTICES[] = {
@@ -156,10 +219,11 @@ constexpr float QUAD_VERTICES[] = {
-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};
struct Vertex {
struct Vertex3D {
float x = 0.0f, y = 0.0f, z = 0.0f;
float s = 0.0f, t = 0.0f;
float layer = 0.0f;
float nx = 0.0f, ny = 0.0f, nz = 0.0f;
};
struct Vertex2D {

View File

@@ -15,7 +15,7 @@ class World;
class DevPanel;
class Renderer {
public:
constexpr static int NUM_VAO = 6;
constexpr static int NUM_VAO = 7;
Renderer(const Camera& camera, World& world,
const TextureManager& texture_manager, DevPanel& dev_panel);
@@ -28,8 +28,20 @@ public:
void update_fov(float fov);
void update_proj_matrix(float aspect, float width, float height);
void updata_framebuffer(int width, int height);
float& ambient_strength();
private:
static constexpr glm::vec3 SUNLIGHT_COLOR{1.0f, 1.0f, 1.0f};
static constexpr glm::vec3 SUN_COLOR{1.00f, 0.95f, 0.80f};
static constexpr glm::vec3 MOON_COLOR{0.75f, 0.80f, 1.00f};
static constexpr glm::vec3 SKY_COLOR{0.529, 0.808, 0.922};
static constexpr float FAR_PLANE = 1000.0f;
static constexpr float NEAR_PLANE = 0.1f;
static constexpr float SUN_SIZE = 50.0f;
static constexpr float MOON_SIZE = 50.0f;
static constexpr float DEPTH_MAP_SIZE = 4096.0f;
float m_ambient_strength = 0.1f;
const Camera& m_camera;
DevPanel& m_dev_panel;
const TextureManager& m_texture_manager;
@@ -40,7 +52,10 @@ private:
float m_delta_time = 0.0f;
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat;
float m_width = 0.0f;
float m_height = 0.0f;
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat, m_norm_mat;
GLuint m_mv_loc = 0;
GLuint m_proj_loc = 0;
@@ -55,15 +70,32 @@ private:
GLuint m_screen_texture = 0;
GLuint m_depth_render_buffer = 0;
GLuint m_oit_fbo = 0;
GLuint m_accum_texture = 0;
GLuint m_reveal_texture = 0;
GLuint m_oit_depth_render_buffer = 0;
GLuint m_depth_map_fbo = 0;
GLuint m_depth_map_texture = 0;
GLuint m_quad_vbo = 0;
glm::mat4 m_ui_proj;
glm::mat4 m_ui_m_matrix;
std::unordered_map<std::size_t, Shader> m_shaders;
/*
0 - quad vao
1 - sky vao
2 - outline vao
3 - ui vao
4 - text vao
*/
std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui;
void init_underwater();
void init_quad();
void init_text();
void render_outline();

View File

@@ -7,7 +7,17 @@ namespace HASH {
inline std::size_t str(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;
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;
}
inline uint32_t combine_32(uint32_t seed, uint32_t v) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
*/
} // namespace HASH
} // namespace Cubed

View File

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

View File

@@ -90,6 +90,27 @@ bool BlockManager::is_passable(BlockType id) {
return m_datas[id].is_passable;
}
bool BlockManager::is_discard(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_discard;
}
return m_datas[id].is_discard;
}
bool BlockManager::is_blend(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].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() {
fs::path data_path{block_data_dir};
@@ -125,8 +146,12 @@ void BlockManager::init() {
auto is_cross_plane = safe_get_value(block, "is_cross_plane", false);
auto is_transparent = safe_get_value(block, "is_transparent", false);
auto is_gas = safe_get_value(block, "is_gas", false);
auto is_discard = safe_get_value(block, "is_discard", 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,
*is_cross_plane, *is_transparent, *is_gas);
*is_cross_plane, *is_transparent, *is_gas,
*is_discard, *is_blend, *is_transitional);
}
std::sort(
m_datas.begin(), m_datas.end(),

View File

@@ -60,4 +60,6 @@ const glm::vec3& Camera::get_camera_pos() const { return m_camera_pos; }
bool Camera::is_under_water() const { return m_under_water; }
glm::vec3 Camera::get_camera_front() const { return m_player->get_front(); }
} // namespace Cubed

View File

@@ -263,6 +263,25 @@ void DevPanel::show_biome_table_bar() {
}
}
void DevPanel::show_time_table_bar() {
World& world = m_app.world();
ImGui::Text("Game Tick %llu", world.game_tick());
ImGui::Text("Day Tick %llu", world.day_tick());
if (ImGui::SliderInt("SetDayTick", &m_pre_set_day_tick, 0, DAY_TIME)) {
}
ImGui::SameLine();
if (ImGui::Button("Set##DayTick")) {
world.day_tick(static_cast<TickType>(m_pre_set_day_tick));
}
ImGui::Text("MSPT %d", world.per_tick_time());
if (ImGui::SliderInt("SetMSPT", &m_pre_set_tick_speed, 1, 200)) {
}
ImGui::SameLine();
if (ImGui::Button("Set##MSPT")) {
world.per_tick_time(m_pre_set_tick_speed);
}
}
void DevPanel::show_cave_table_bar() {
auto& cave_carcer = m_app.world().cave_carcer();
@@ -336,6 +355,10 @@ void DevPanel::show_settings_tab_item() {
static_cast<double>(m_config.mouse_sensitivity));
m_player->hot_reload();
}
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))
;
if (ImGui::SliderInt("Distance", &m_config.rendering_distance, 2,
128)) {
Config::get().set("world.rendering_distance",
@@ -457,6 +480,10 @@ void DevPanel::show_world_tab_item() {
ImGui::Text("Chunk Build Progress\n");
ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
show_time_table_bar();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Cave")) {
show_cave_table_bar();
ImGui::EndTabItem();

View File

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

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 {
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) {
m_seed = world_seed;
m_sum = 0;
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) {
m_paths.emplace(chunk_seed, CavePath{m_seed, m_sum, pos});
m_sum++;
m_paths.emplace(chunk_seed, CavePath{chunk_seed, m_seed, pos});
}
void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed);
if (it != m_paths.end()) {
return;
{
CaveHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
}
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_cave_probability))) {
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() {
std::erase_if(m_paths,
[](const auto& kv) { return kv.second.is_finished(); });
std::vector<unsigned int> finished_keys;
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; }
} // namespace Cubed

View File

@@ -6,10 +6,9 @@
#include <algorithm>
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) {
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_yaw = m_random.random_float(0.0f, 360.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 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

@@ -9,65 +9,36 @@
namespace Cubed {
Chunk::Chunk(World& world, ChunkPos chunk_pos)
: m_chunk_pos(chunk_pos), m_world(world) {}
Chunk::~Chunk() {
if (m_normal_vbo != 0) {
m_world.push_delete_vbo(m_normal_vbo);
}
if (m_cross_plane_vbo != 0) {
m_world.push_delete_vbo(m_cross_plane_vbo);
}
if (m_transparent_normal_vbo != 0) {
m_world.push_delete_vbo(m_transparent_normal_vbo);
: m_chunk_pos(chunk_pos), m_world(world) {
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
m_vertex_data.emplace_back(m_world);
}
}
Chunk::~Chunk() {}
Chunk::Chunk(Chunk&& other) noexcept
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
m_normal_vertices_sum(other.m_normal_vertices_sum.load()),
m_cross_vertices_sum(other.m_cross_vertices_sum.load()),
m_transparent_vertices_sum(other.m_transparent_vertices_sum.load()),
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_normal_vbo(other.m_normal_vbo),
m_cross_plane_vbo(other.m_cross_plane_vbo),
m_transparent_normal_vbo(other.m_transparent_normal_vbo),
m_normal_vertices(std::move(other.m_normal_vertices)),
m_cross_plane_vertices(std::move(other.m_cross_plane_vertices)),
m_transparent_normal_vertices(
std::move(other.m_transparent_normal_vertices)),
m_seed(other.m_seed), m_conditions(other.m_conditions) {
other.m_normal_vbo = 0;
other.m_cross_plane_vbo = 0;
other.m_transparent_normal_vbo = 0;
}
m_blocks(std::move(other.m_blocks)),
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed),
m_conditions(other.m_conditions) {}
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<const void*>(&other));
m_normal_vbo = other.m_normal_vbo;
other.m_normal_vbo = 0;
m_cross_plane_vbo = other.m_cross_plane_vbo;
m_transparent_normal_vbo = other.m_transparent_normal_vbo;
other.m_transparent_normal_vbo = 0;
other.m_cross_plane_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_normal_vertices = std::move(other.m_normal_vertices);
m_cross_plane_vertices = std::move(other.m_cross_plane_vertices);
m_transparent_normal_vertices =
std::move(other.m_transparent_normal_vertices);
m_vertex_data = std::move(other.m_vertex_data);
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_normal_vertices_sum = other.m_normal_vertices_sum.load();
m_cross_vertices_sum = other.m_cross_vertices_sum.load();
m_transparent_vertices_sum = other.m_transparent_vertices_sum.load();
m_seed = other.m_seed;
m_conditions = other.m_conditions;
return *this;
@@ -132,36 +103,47 @@ int Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z);
}
void Chunk::gen_vertex_data(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data) {
return;
}
m_is_on_gen_vertex_data = true;
std::lock_guard lk(m_vertexs_data_mutex);
gen_normal_vertices(neighbor_block);
gen_cross_plane_vertices();
for (auto& data : m_vertex_data) {
data.m_vertices.clear();
}
gen_vertices(neighbor_block);
for (auto& data : m_vertex_data) {
data.update_sum();
}
m_need_upload = true;
m_is_on_gen_vertex_data = false;
}
GLuint Chunk::get_normal_vbo() const { return m_normal_vbo; }
GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
size_t Chunk::get_normal_vertices_sum() const {
if (m_normal_vertices_sum == 0) {
if (m_vertex_data[0].m_sum == 0) {
Logger::warn("m_normal_vertices_sum is 0");
}
return m_normal_vertices_sum.load();
return m_vertex_data[0].m_sum.load();
}
GLuint Chunk::get_cross_vbo() const { return m_cross_plane_vbo; }
GLuint Chunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
size_t Chunk::get_cross_vertices_sum() const {
return m_cross_vertices_sum.load();
return m_vertex_data[1].m_sum.load();
}
GLuint Chunk::get_transparent_vbo() const { return m_transparent_normal_vbo; }
size_t Chunk::get_transparent_vertices_sum() const {
return m_transparent_vertices_sum.load();
GLuint Chunk::get_normal_discard_vao() const { return m_vertex_data[2].m_vao; }
size_t Chunk::get_normal_discard_vertices_sum() const {
return m_vertex_data[2].m_sum.load();
}
GLuint Chunk::get_normal_blend_vao() const { return m_vertex_data[3].m_vao; }
size_t Chunk::get_normal_blend_vertices_sum() const {
return m_vertex_data[3].m_sum.load();
}
void Chunk::gen_phase_one() {
@@ -216,9 +198,8 @@ void Chunk::gen_phase_six(
Logger::error("ChunkGenerator is Nullptr");
return;
}
// This must be fully completed before any other operations can proceed!
m_generator->blend_surface_blocks_borders(neighbor_block);
m_generator->generate_cave();
m_generator->generate_river();
}
void Chunk::gen_phase_seven() {
@@ -226,6 +207,10 @@ void Chunk::gen_phase_seven() {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->ocean_build();
m_generator->generate_river();
m_generator->generate_cave();
m_generator->generate_vegetation();
mark_dirty();
m_generator = nullptr;
@@ -235,33 +220,12 @@ void Chunk::upload_to_gpu() {
ASSERT(is_need_upload());
if (m_normal_vbo == 0) {
glGenBuffers(1, &m_normal_vbo);
std::lock_guard lk(m_vertexs_data_mutex);
for (auto& data : m_vertex_data) {
data.upload();
}
std::lock_guard lk(m_vertexs_data_mutex);
glBindBuffer(GL_ARRAY_BUFFER, m_normal_vbo);
glBufferData(GL_ARRAY_BUFFER, m_normal_vertices.size() * sizeof(Vertex),
m_normal_vertices.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
if (m_cross_plane_vertices.size() != 0) {
if (m_cross_plane_vbo == 0) {
glGenBuffers(1, &m_cross_plane_vbo);
}
glBindBuffer(GL_ARRAY_BUFFER, m_cross_plane_vbo);
glBufferData(GL_ARRAY_BUFFER,
m_cross_plane_vertices.size() * sizeof(Vertex),
m_cross_plane_vertices.data(), GL_DYNAMIC_DRAW);
}
if (m_transparent_normal_vertices.size() != 0) {
if (m_transparent_normal_vbo == 0) {
glGenBuffers(1, &m_transparent_normal_vbo);
}
glBindBuffer(GL_ARRAY_BUFFER, m_transparent_normal_vbo);
glBufferData(GL_ARRAY_BUFFER,
m_transparent_normal_vertices.size() * sizeof(Vertex),
m_transparent_normal_vertices.data(), GL_DYNAMIC_DRAW);
}
// after fininshed it, can use
clear_dirty();
m_need_upload = false;
@@ -300,10 +264,7 @@ unsigned Chunk::seed() const {
BiomeConditions& Chunk::conditions() { return m_conditions; }
void Chunk::gen_normal_vertices(
const std::array<const std::vector<BlockType>*, 4>& neighbor_block) {
m_normal_vertices.clear();
m_transparent_normal_vertices.clear();
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
@@ -336,8 +297,9 @@ void Chunk::gen_normal_vertices(
World::chunk_pos(world_nx, world_nz);
auto is_culled =
[&](const std::vector<BlockType>* chunk_blocks) {
if (chunk_blocks == nullptr) {
[&](const std::optional<std::vector<BlockType>>&
chunk_blocks) {
if (chunk_blocks == std::nullopt) {
return true;
}
int x, y, z;
@@ -399,66 +361,80 @@ void Chunk::gen_normal_vertices(
if (neighbor_culled) {
continue;
}
if (BlockManager::is_cross_plane(cur_id)) {
gen_cross_plane_vertices(world_x, world_y, world_z,
cur_id);
}
for (int i = 0; i < 6; i++) {
Vertex vex = {
Vertex3D vex = {
VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
TEX_COORDS[face][i][0],
TEX_COORDS[face][i][1],
static_cast<float>(cur_id * 6 + face)
static_cast<float>(cur_id * 6 + face),
NORMALS[face][i][0],
NORMALS[face][i][1],
NORMALS[face][i][2]
};
if (BlockManager::is_transparent(cur_id)) {
m_transparent_normal_vertices.emplace_back(vex);
if (BlockManager::is_discard(cur_id) &&
BlockManager::is_blend(cur_id)) {
Logger::warn(
"Block id {} is both discard and blend is "
"must only one can true !!!",
cur_id);
}
if (BlockManager::is_discard(cur_id)) {
m_vertex_data[2].m_vertices.emplace_back(vex);
} else if (BlockManager::is_blend(cur_id)) {
m_vertex_data[3].m_vertices.emplace_back(vex);
} else {
Logger::warn("Id {} is transparent but not "
"discard or blend",
cur_id);
m_vertex_data[3].m_vertices.emplace_back(vex);
}
} else {
m_normal_vertices.emplace_back(vex);
m_vertex_data[0].m_vertices.emplace_back(vex);
}
}
}
}
}
}
m_normal_vertices_sum = m_normal_vertices.size();
m_transparent_vertices_sum = m_transparent_normal_vertices.size();
}
void Chunk::gen_cross_plane_vertices() {
void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id) {
m_cross_plane_vertices.clear();
if (!BlockManager::is_cross_plane(id)) {
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
world_y, world_z, id);
return;
}
for (int face = 0; face < 2; face++) {
for (int i = 0; i < 6; i++) {
Vertex3D vex = {
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
CROSS_TEX_COORDS[face][i][0],
CROSS_TEX_COORDS[face][i][1],
static_cast<float>(BlockManager::cross_plane_index(id)),
CROSS_NORMALS[face][i][0],
CROSS_NORMALS[face][i][1],
CROSS_NORMALS[face][i][2]
for (int x = 0; x < SIZE_X; x++) {
for (int y = 0; y < SIZE_Y; y++) {
for (int z = 0; z < SIZE_Z; z++) {
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
int world_y = y;
int id = m_blocks[index(x, y, z)];
if (!BlockManager::is_cross_plane(id)) {
continue;
}
for (int face = 0; face < 2; face++) {
for (int i = 0; i < 6; i++) {
Vertex vex = {CROSS_VERTICES_POS[face][i][0] +
(float)world_x * 1.0f,
CROSS_VERTICES_POS[face][i][1] +
(float)world_y * 1.0f,
CROSS_VERTICES_POS[face][i][2] +
(float)world_z * 1.0f,
CROSS_TEX_COORDS[face][i][0],
CROSS_TEX_COORDS[face][i][1],
static_cast<float>(
BlockManager::cross_plane_index(id))
};
m_cross_plane_vertices.emplace_back(vex);
}
}
}
};
m_vertex_data[1].m_vertices.emplace_back(vex);
}
}
m_cross_vertices_sum = m_cross_plane_vertices.size();
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
} // namespace Cubed

View File

@@ -3,6 +3,7 @@
#include "Cubed/gameplay/builders/desert_builder.hpp"
#include "Cubed/gameplay/builders/forest_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/river_builder.hpp"
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
@@ -17,11 +18,11 @@ namespace Cubed {
using enum BiomeType;
constexpr int BLEND_RADIUS = 12;
constexpr int BLEND_RADIUS = 8;
ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) {
ASSERT_MSG(is_init, "ChunksGenerator is not init");
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_chunk_seed = seed;
}
@@ -166,18 +167,43 @@ void ChunkGenerator::generate_heightmap() {
amplitude = std::lerp(10, 40, t);
*/
float t;
if (mountainous >= 0.7f) {
t = Math::smootherstep(0.7f, 0.75, mountainous);
base_y = std::lerp(70, 88, t);
amplitude = std::lerp(28, 48, t);
} else if (mountainous >= 0.65f) {
t = Math::smootherstep(0.65f, 0.7f, mountainous);
base_y = std::lerp(66, 70, t);
if (mountainous >= 0.95f) {
t = Math::smootherstep(0.95f, 1.0f, mountainous);
base_y = std::lerp(130, 140, t);
amplitude = std::lerp(38, 48, t);
} else if (mountainous >= 0.85f) {
t = Math::smootherstep(0.85f, 0.95f, mountainous);
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);
} 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 {
t = Math::smootherstep(0.55, 0.65, mountainous);
base_y = std::lerp(58, 66, t);
amplitude = std::lerp(8, 18, t);
t = Math::smootherstep(0.0f, 0.25f, mountainous);
base_y = std::lerp(35, 44, t);
amplitude = std::lerp(3, 6, t);
}
heightmap[x][z] =
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) {
int idx = Chunk::index(nx, y,
nz); // linear index: y * area + z * size + x
if (idx >= 0 && idx < static_cast<int>(blocks.size()) &&
blocks[idx] != 0) {
return blocks[idx];
if (idx >= 0 && idx < static_cast<int>(blocks.size())) {
BlockType neighbor_type = blocks[idx];
if (BlockManager::is_transitional(neighbor_type)) {
return neighbor_type;
}
}
}
return 0; // fallback, should not happen for valid chunks
@@ -473,8 +501,8 @@ void ChunkGenerator::blend_surface_blocks_borders(
// Weight map: type -> total weight
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) ---
if (neighbor_block[0] && x >= CHUNK_SIZE - BLEND_RADIUS) {
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
BlockType final_type = type_self;
float max_weight = weights[type_self];
/*float max_weight = weights[type_self];
for (const auto& [type, w] : weights) {
if (w > max_weight) {
max_weight = w;
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) {
return;
if (!BlockManager::is_transitional(final_type)) {
continue;
}
// Update the top block if the type changed
if (final_type != type_self) {
// top block
if (final_type == 7 && top_y > SEA_LEVEL) {
if (type_self == 7) {
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;
}
BlockType new_surface = final_type;
m_blocks[Chunk::index(x, top_y, z)] = new_surface;
// bottom block
unsigned fill_type = 2;
if (final_type == 1) {
if (final_type == 1 || final_type == 8) {
fill_type = 2;
} else if (final_type == 4) {
fill_type = 4;
} else {
fill_type = final_type;
}
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 {
m_blocks[Chunk::index(x, y, z)] = fill_type;
}
for (int y = std::max(0, top_y - 5); y < top_y; y++) {
m_blocks[Chunk::index(x, y, z)] = fill_type;
}
}
}
@@ -600,63 +631,109 @@ void ChunkGenerator::make_biome_builder() {
case SNOWY_PLAIN:
m_biome_builder = std::make_unique<SnowyPlainBuilder>(*this);
break;
case OCEAN:
m_biome_builder = std::make_unique<OceanBuilder>(*this);
break;
case NONE:
m_biome_builder = nullptr;
break;
}
}
void ChunkGenerator::ocean_build() { m_biome_builder->ocean_water_build(); }
void ChunkGenerator::carve_worm(
const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
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_Z = chunk_pos.z * CHUNK_SIZE;
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1;
for (const auto& point : points) {
const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz;
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 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);
glm::vec3 right_raw =
glm::cross(point.tangent, glm::vec3(0.0f, 1.0f, 0.0f));
if (glm::dot(right_raw, right_raw) < 1e-6f)
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) {
if (wy == 0)
continue;
float dy = static_cast<float>(wy) - point.pos.y;
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();
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1;
for (auto& [id, path] : paths) {
for (const auto& point : path.points()) {
const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz;
float rad_y = point.rad_y;
int min_x = static_cast<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 == 0) {
continue;
}
blocks[Chunk::index(x, y, z)] = 0;
}
}
}
}
}
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);
}
}
@@ -667,63 +744,27 @@ void ChunkGenerator::generate_river() {
auto& paths = river_worm.paths();
const auto& chunk_pos = m_chunk.chunk_pos();
auto& blocks = m_chunk.blocks();
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
const int CHUNK_MIN_Y = 0;
const int CHUNK_MAX_Y = SIZE_Y - 1;
bool is_river = false;
for (auto& [id, path] : paths) {
for (const auto& point : path.points()) {
if (m_chunk.biome() == BiomeType::DESERT) {
path.clear_chunk(chunk_pos);
continue;
}
const glm::vec3& center = point.pos;
float rad_xz = point.rad_xz;
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) {
blocks[Chunk::index(x, y, z)] = 0;
continue;
}
is_river = true;
if (blocks[Chunk::index(x, y, z)] == 0) {
continue;
}
blocks[Chunk::index(x, y, z)] = 7;
}
}
}
}
if ((m_chunk.biome() == BiomeType::DESERT) ||
(m_chunk.biome() == BiomeType::OCEAN)) {
path.clear_chunk(chunk_pos);
continue;
}
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
int idx = Chunk::index(x, y, z);
if (y > SEA_LEVEL) {
blocks[idx] = 0;
return;
}
is_river = true;
if (blocks[idx] == 0) {
return;
}
blocks[idx] = 7;
});
path.clear_chunk(chunk_pos);
}
if (is_river) {

View File

@@ -5,12 +5,15 @@
#include <algorithm>
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) {
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_yaw = m_random.random_float(0.0f, 360.0f);
m_initial_yaw = m_yaw;
m_pitch = 0.0f;
m_start_path_point.pos = start_pos;
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 drad_xz = m_start_path_point.rad_xz * (1.0f - t);
float drad_y = m_start_path_point.rad_y * (1.0f - t);
float drad_xz = m_start_path_point.rad_xz * t;
float drad_y = m_start_path_point.rad_y * t;
drad_xz = std::max(drad_xz, 4.0f);
drad_y = std::max(drad_y, 4.0f);
m_points.emplace_back(m_points[i].pos + glm::vec3{dx, dy, dz}, drad_xz,
drad_y);
m_yaw += m_random.random_float(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();
if (n >= 2) {
@@ -72,7 +76,8 @@ void RiverPath::precompute_chunk_coverage() {
for (int cx = min_cx; cx <= max_cx; ++cx)
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 {
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) {
m_seed = world_seed;
m_sum = 0;
m_random.init(m_seed);
}
@@ -20,15 +20,16 @@ void RiverWorm::reload(unsigned world_seed) {
}
void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) {
m_paths.emplace(chunk_seed, RiverPath{m_seed, m_sum, pos});
m_sum++;
m_paths.emplace(chunk_seed, RiverPath{chunk_seed, m_seed, pos});
}
void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
unsigned chunk_seed) {
auto it = m_paths.find(chunk_seed);
if (it != m_paths.end()) {
return;
{
RiverHashMap::const_accessor acc;
if (m_paths.find(acc, chunk_seed)) {
return;
}
}
Random random{chunk_seed};
if (random.random_bool(static_cast<double>(m_probability))) {
@@ -44,10 +45,17 @@ void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
}
void RiverWorm::cleanup_finished_rivers() {
std::erase_if(m_paths,
[](const auto& kv) { return kv.second.is_finished(); });
std::vector<unsigned> finished_keys;
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; }
} // namespace Cubed

View File

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

View File

@@ -0,0 +1,63 @@
#include "Cubed/gameplay/vertex_data.hpp"
#include "Cubed/gameplay/world.hpp"
namespace Cubed {
VertexData::VertexData(World& world) : m_world(world) {}
VertexData::~VertexData() {
if (m_vbo != 0) {
m_world.push_delete_vbo(m_vbo);
}
if (m_vao != 0) {
m_world.push_delete_vao(m_vao);
}
}
VertexData::VertexData(VertexData&& o) noexcept
: m_vertices(std::move(o.m_vertices)), m_vbo(o.m_vbo), m_vao(o.m_vao),
m_sum(o.m_sum.load()), m_world(o.m_world) {
o.m_vbo = 0;
o.m_sum = 0;
o.m_vao = 0;
}
VertexData& VertexData::operator=(VertexData&& o) noexcept {
m_vbo = o.m_vbo;
o.m_vbo = 0;
m_sum = o.m_sum.load();
o.m_sum = 0;
m_vertices = std::move(o.m_vertices);
m_vao = o.m_vao;
o.m_vao = 0;
return *this;
}
void VertexData::upload() {
if (m_vertices.size() == 0) {
return;
}
if (m_vao == 0) {
glGenVertexArrays(1, &m_vao);
}
if (m_vbo == 0) {
glGenBuffers(1, &m_vbo);
}
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex3D),
m_vertices.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, layer));
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, nx));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void VertexData::update_sum() { m_sum = m_vertices.size(); }
} // namespace Cubed

View File

@@ -1,12 +1,13 @@
#include "Cubed/gameplay/world.hpp"
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/texture_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/math_tools.hpp"
#include <execution>
using namespace std::chrono;
namespace Cubed {
@@ -19,6 +20,7 @@ World::World() {}
World::~World() {
stop_gen_thread();
stop_server_thread();
m_chunks.clear();
{
std::lock_guard lk(m_delete_vbo_mutex);
@@ -27,6 +29,13 @@ World::~World() {
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
}
bool World::can_move(const AABB& player_box) const { return true; }
@@ -80,11 +89,14 @@ void World::init_world() {
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
start_server_thread();
Logger::info("TestPlayer Create Finish");
}
void World::init_chunks() {
hot_reload();
while (!m_chunk_gen_finished) {
// Logger::info("World Spawn: {:.2f}%", m_chunk_gen_fraction.load());
std::this_thread::sleep_for(std::chrono::microseconds(200));
}
}
@@ -291,115 +303,6 @@ void World::init_chunks() {
}
}
*/
void World::render(const glm::mat4& mvp_matrix,
const TextureManager& texture_manager,
const glm::vec3& camera_pos) {
Math::extract_frustum_planes(mvp_matrix, m_planes);
int rendered_sum = 0;
for (const auto& snapshot : m_render_snapshots) {
if (is_aabb_in_frustum(snapshot.center, snapshot.half_extents)) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
texture_manager.get_texture_array());
glBindBuffer(GL_ARRAY_BUFFER, snapshot.normal_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, layer));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
glBindBuffer(GL_ARRAY_BUFFER, 0);
rendered_sum++;
}
}
glDepthMask(GL_FALSE);
struct SortableSnapshot {
const ChunkRenderSnapshot* snapshot;
float distance;
};
std::vector<SortableSnapshot> cross_list;
std::vector<SortableSnapshot> transparent_list;
for (const auto& snapshot : m_render_snapshots) {
if (!is_aabb_in_frustum(snapshot.center, snapshot.half_extents)) {
continue;
}
float dist = glm::distance(camera_pos, snapshot.center);
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
cross_list.push_back({&snapshot, dist});
}
}
if (snapshot.transparent_vertices_count != 0) {
transparent_list.push_back({&snapshot, dist});
}
}
std::sort(transparent_list.begin(), transparent_list.end(),
[](const SortableSnapshot& a, const SortableSnapshot& b) {
return a.distance > b.distance;
});
std::sort(cross_list.begin(), cross_list.end(),
[](const SortableSnapshot& a, const SortableSnapshot& b) {
return a.distance > b.distance;
});
for (const auto& item : cross_list) {
const auto& snapshot = *item.snapshot;
glBindTexture(GL_TEXTURE_2D_ARRAY,
texture_manager.get_cross_plane_array());
glBindBuffer(GL_ARRAY_BUFFER, snapshot.cross_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, layer));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
for (const auto& item : transparent_list) {
const auto& snapshot = *item.snapshot;
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_manager.get_texture_array());
glBindBuffer(GL_ARRAY_BUFFER, snapshot.transparent_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, layer));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawArrays(GL_TRIANGLES, 0, snapshot.transparent_vertices_count);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
glDepthMask(GL_TRUE);
DebugCollector::get().report(
"rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum));
}
ChunkPos World::chunk_pos(int world_x, int world_z) {
int chunk_x, chunk_z;
@@ -424,8 +327,10 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.0f;
m_chunk_gen_finished = false;
ChunkPosSet required_chunks;
ChunkHashMap temp_neighbor;
compute_required_chunks(required_chunks, temp_neighbor);
ChunkPairVector 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!!");
@@ -443,11 +348,14 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.1f;
ChunkUpdateList new_chunks;
ChunkPairVector new_chunks;
ChunkHashMap new_temp_chunks;
for (auto& pos : need_gen_chunks_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;
// affected neighbor
ChunkPtrUpdateList affected_neighbor;
@@ -457,17 +365,28 @@ void World::gen_chunks_internal() {
// build new chunk, but the neighbor in m_chunks also need to re-build
for (auto& [pos, chunk] : new_chunks) {
chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed());
}
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();
m_cave_carcer.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
for (auto& [pos, chunk] : temp_neighbor) {
chunk.gen_phase_one();
m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed());
}
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();
m_cave_carcer.try_to_add_path(pos, chunk.seed());
m_river_worm.try_to_add_path(pos, chunk.seed());
});
m_chunk_gen_fraction = 0.2f;
@@ -505,15 +424,19 @@ void World::gen_chunks_internal() {
m_chunk_gen_fraction = 0.3f;
for (auto& [pos, chunks] : new_chunks) {
chunks.gen_phase_three();
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();
});
for (auto& [pos, chunk] : new_temp_chunks) {
chunk.gen_phase_three();
}
// for (auto& [pos, chunks] : temp_neighbor) {
// chunks.gen_phase_three();
// }
m_chunk_gen_fraction = 0.4f;
/*
for (int i = 0; i < 4; i++) {
for (auto& [pos, chunks] : temp_neighbor) {
@@ -557,68 +480,106 @@ void World::gen_chunks_internal() {
}
}
*/
m_chunk_gen_fraction = 0.4f;
m_chunk_gen_fraction = 0.5f;
for (auto& [pos, chunks] : new_chunks) {
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) {
chunks.gen_phase_five();
}
*/
std::array<std::optional<std::vector<BlockType>>, 4> neighbor_blocks_data;
for (auto& [pos, chunks] : new_chunks) {
std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
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);
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_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;
}
new_chunks_surface_blend_data[idx].second[i] =
it->second.get_chunk_blocks();
continue;
}
neighbor_blocks_data[i] = it->second->get_chunk_blocks();
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) {
chunks.gen_phase_seven();
}
std::for_each(
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;
std::array<const std::vector<BlockType>*, 4> neighbor_block;
for (auto& [pos, chunk] : new_chunks) {
std::vector<std::pair<Chunk*, OptionalBlockVectorArray>>
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++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
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 {
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;
build_neighbor_context_for_affected_neighbors(affected_neighbor,
new_chunks_neighbor);
m_chunk_gen_fraction = 0.8f;
OptionalBlockVectorArray neighbor_block;
for (auto& [pos, chunk] : affected_neighbor) {
for (int i = 0; i < 4; i++) {
auto it = new_chunks_neighbor.find(pos + CHUNK_DIR[i]);
if (it != new_chunks_neighbor.end()) {
neighbor_block[i] = &(it->second->get_chunk_blocks());
neighbor_block[i] = (it->second->get_chunk_blocks());
} else {
neighbor_block[i] = nullptr;
neighbor_block[i] = std::nullopt;
}
}
chunk->gen_vertex_data(neighbor_block);
@@ -644,32 +605,53 @@ void World::sync_player_pos(glm::vec3& player_pos) {
player_pos = m_gen_player_pos;
}
void World::compute_required_chunks(ChunkPosSet& required_chunks,
ChunkHashMap& temp_neighbor) {
void World::compute_required_chunks(
ChunkPosSet& required_chunks, ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos) {
glm::vec3 player_pos;
sync_player_pos(player_pos);
int x = std::floor(player_pos.x);
int z = std::floor(player_pos.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);
int half = m_rendering_distance / 2;
for (int u = chunk_x - half; u <= chunk_x + half; ++u) {
for (int v = chunk_z - half; v <= chunk_z + half; ++v) {
required_chunks.emplace(u, v);
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz <= r2) {
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
}
}
}
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());
half = std::ceil(static_cast<float>(max_path_len) / CHUNK_SIZE) * 2;
for (int u = chunk_x - half; u <= chunk_x + half; ++u) {
for (int v = chunk_z - half; v <= chunk_z + half; ++v) {
ChunkPos pos{u, v};
auto it = required_chunks.find(pos);
if (it != required_chunks.end()) {
continue;
radius = max_path_len / 2;
r2 = radius * radius;
for (int dx = -radius; dx <= radius; ++dx) {
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);
if (it != required_chunks.end()) {
continue;
}
temp_neighbor.emplace_back(pos, Chunk(*this, pos));
}
temp_neighbor.emplace(pos, Chunk(*this, pos));
}
}
}
@@ -696,7 +678,7 @@ void World::sync_and_collect_missing_chunks(
void World::build_neighbor_context_for_new_chunks(
ConstChunkMap& new_chunks_neighbor, ChunkPtrUpdateList& affected_neighbor,
const ChunkUpdateList& new_chunks) {
const ChunkPairVector& new_chunks) {
{
std::lock_guard lk(m_chunks_mutex);
for (auto& [pos, chunk] : new_chunks) {
@@ -750,6 +732,11 @@ void World::start_gen_thread() {
});
}
void World::start_server_thread() {
m_server_thread = std::thread(
[this]() { serever_run(m_server_stop_source.get_token()); });
}
void World::stop_gen_thread() {
m_gen_running = false;
m_gen_cv.notify_all();
@@ -759,6 +746,25 @@ void World::stop_gen_thread() {
Logger::info("Gen Thread Stopped");
}
void World::stop_server_thread() {
m_server_stop_source.request_stop();
if (m_server_thread.joinable()) {
m_server_thread.join();
}
}
void World::serever_run(std::stop_token stoken) {
Logger::info("Server Thread Started!");
while (!stoken.stop_requested()) {
std::this_thread::sleep_for(milliseconds(m_per_tick_time));
if (m_day_night_cycle) {
++m_game_ticks;
m_day_tick = (++m_day_tick) % DAY_TIME;
}
}
Logger::info("Server Thread Stopped!");
}
void World::need_gen() {
if (!m_could_gen) {
Logger::warn("It is generating or consuming new chunks");
@@ -774,21 +780,6 @@ void World::need_gen() {
m_gen_cv.notify_one();
}
bool World::is_aabb_in_frustum(const glm::vec3& center,
const glm::vec3& half_extents) {
for (const auto& plane : m_planes) {
// distance
float d = glm::dot(glm::vec3(plane), center) + plane.w;
float r = half_extents.x * std::abs(plane.x) +
half_extents.y * std::abs(plane.y) +
half_extents.z * std::abs(plane.z);
if (d + r < 0) {
return false;
}
}
return true;
}
int World::get_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = chunk_pos(block_pos.x, block_pos.z);
std::lock_guard lk(m_chunks_mutex);
@@ -917,6 +908,15 @@ void World::update(float delta_time) {
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
{
std::scoped_lock lk(m_chunks_mutex, m_new_chunk_queue_mutex);
m_new_chunk.clear();
@@ -947,13 +947,13 @@ void World::update(float delta_time) {
for (auto& [pos, chunk] : m_chunks) {
if (chunk.is_dirty()) {
// the curial fator influence
std::array<const std::vector<BlockType>*, 4> neighbor_block;
OptionalBlockVectorArray neighbor_block;
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());
neighbor_block[i] = (it->second.get_chunk_blocks());
} else {
neighbor_block[i] = nullptr;
neighbor_block[i] = std::nullopt;
}
}
chunk.gen_vertex_data(neighbor_block);
@@ -964,10 +964,12 @@ void World::update(float delta_time) {
chunk.upload_to_gpu();
}
m_render_snapshots.push_back(
{chunk.get_normal_vbo(), chunk.get_normal_vertices_sum(),
chunk.get_cross_vbo(), chunk.get_cross_vertices_sum(),
chunk.get_transparent_vbo(),
chunk.get_transparent_vertices_sum(),
{chunk.get_normal_vao(), chunk.get_normal_vertices_sum(),
chunk.get_cross_vao(), chunk.get_cross_vertices_sum(),
chunk.get_normal_discard_vao(),
chunk.get_normal_discard_vertices_sum(),
chunk.get_normal_blend_vao(),
chunk.get_normal_blend_vertices_sum(),
glm::vec3(static_cast<float>(pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
@@ -986,6 +988,11 @@ void World::push_delete_vbo(GLuint vbo) {
m_pending_delete_vbo.push_back(vbo);
}
void World::push_delete_vao(GLuint vao) {
std::lock_guard lk(m_delete_vao_mutex);
m_pending_delete_vao.push_back(vao);
}
void World::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("world.rendering_distance");
@@ -1024,4 +1031,51 @@ void World::rendering_distance(int rendering_distance) {
CaveCarver& World::cave_carcer() { return m_cave_carcer; }
RiverWorm& World::river_worm() { return m_river_worm; }
std::vector<glm::vec4>& World::planes() { return m_planes; }
std::vector<ChunkRenderSnapshot>& World::render_snapshots() {
return m_render_snapshots;
};
/*
glm::vec3 World::sunlight_dir() const {
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = glm::radians(90.0f - t * 360.0f);
float altitude =
glm::half_pi<float>() * sin((t - 0.25f) * glm::two_pi<float>());
glm::vec3 dir{cos(altitude) * cos(azimuth), sin(altitude),
cos(altitude) * sin(azimuth)};
return glm::normalize(dir);
}
*/
glm::vec3 World::sunlight_dir() const {
float altitude = sin((m_day_tick - 6 * PER_HOUR) /
static_cast<float>(DAY_TIME / 2) * std::numbers::pi) *
90.0f;
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = 90.0f - 360.0f * (t - 0.25f);
float alt = glm::radians(altitude);
float az = glm::radians(azimuth);
glm::vec3 dir;
dir.x = cos(alt) * sin(az);
dir.y = sin(alt);
dir.z = cos(alt) * cos(az);
return glm::normalize(-dir);
}
TickType World::game_tick() const { return m_game_ticks.load(); }
TickType World::day_tick() const { return m_day_tick.load(); }
void World::day_tick(TickType tick) {
tick %= DAY_TIME;
m_day_tick = tick;
}
int World::per_tick_time() const { return m_per_tick_time.load(); }
void World::per_tick_time(int ms) { m_per_tick_time = ms; }
} // namespace Cubed

View File

@@ -12,6 +12,7 @@
#include "Cubed/tools/cubed_hash.hpp"
#include "Cubed/tools/font.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/math_tools.hpp"
#include "Cubed/tools/shader_tools.hpp"
#include <GLFW/glfw3.h>
@@ -36,6 +37,14 @@ Renderer::~Renderer() {
glDeleteFramebuffers(1, &m_fbo);
glDeleteTextures(1, &m_screen_texture);
glDeleteRenderbuffers(1, &m_depth_render_buffer);
glDeleteFramebuffers(1, &m_oit_fbo);
glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture);
glDeleteRenderbuffers(1, &m_oit_depth_render_buffer);
glDeleteFramebuffers(1, &m_depth_map_fbo);
glDeleteTextures(1, &m_depth_map_texture);
}
void Renderer::hot_reload() {
@@ -52,7 +61,7 @@ void Renderer::init() {
Logger::info("Renderer: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
Shader world_shader{"world", "shaders/block_v_shader.glsl",
Shader world_shader{"normal_block", "shaders/block_v_shader.glsl",
"shaders/block_f_shader.glsl"};
Shader outline_shader{"outline", "shaders/outline_v_shader.glsl",
"shaders/outline_f_shader.glsl"};
@@ -65,7 +74,13 @@ void Renderer::init() {
Shader under_water_shader{"under_water",
"shaders/under_water_v_shader.glsl",
"shaders/under_water_f_shader.glsl"};
Shader accum_shader{"accum", "shaders/block_accumulation_v_shader.glsl",
"shaders/block_accumulation_f_shader.glsl"};
Shader composite_block_shader{"composite",
"shaders/block_composite_v_shader.glsl",
"shaders/block_composite_f_shader.glsl"};
Shader depth_shader{"depth_shader", "shaders/depth_shader.glsl",
"shaders/depth_fragment_shader.glsl"};
m_shaders.insert({world_shader.hash(), std::move(world_shader)});
m_shaders.insert({outline_shader.hash(), std::move(outline_shader)});
m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)});
@@ -73,7 +88,10 @@ void Renderer::init() {
m_shaders.insert({text_shdaer.hash(), std::move(text_shdaer)});
m_shaders.insert(
{under_water_shader.hash(), std::move(under_water_shader)});
m_shaders.insert({accum_shader.hash(), std::move(accum_shader)});
m_shaders.insert(
{composite_block_shader.hash(), std::move(composite_block_shader)});
m_shaders.insert({depth_shader.hash(), std::move(depth_shader)});
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
@@ -94,20 +112,28 @@ void Renderer::init() {
m_vao.resize(NUM_VAO);
glGenVertexArrays(NUM_VAO, m_vao.data());
glBindVertexArray(0);
glGenBuffers(1, &m_outline_vbo);
glBindVertexArray(m_vao[2]);
glGenBuffers(1, &m_outline_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_outline_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(CUBE_VER), CUBE_VER, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glGenBuffers(1, &m_outline_indices_vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_outline_indices_vbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(OUTLINE_CUBE_INDICES),
OUTLINE_CUBE_INDICES, GL_STATIC_DRAW);
glBindVertexArray(m_vao[1]);
glGenBuffers(1, &m_sky_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_sky_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_POS), VERTICES_POS,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindVertexArray(m_vao[3]);
glGenBuffers(1, &m_ui_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_ui_vbo);
@@ -120,13 +146,23 @@ void Renderer::init() {
glBufferData(GL_ARRAY_BUFFER, m_ui.size() * sizeof(Vertex2D), m_ui.data(),
GL_STATIC_DRAW);
glGenBuffers(1, &m_text_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_text_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
init_underwater();
glBindBuffer(GL_ARRAY_BUFFER, m_ui_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
(void*)offsetof(Vertex2D, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
(void*)offsetof(Vertex2D, layer));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
init_quad();
init_text();
hot_reload();
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
const Shader& Renderer::get_shader(const std::string& name) const {
@@ -135,46 +171,52 @@ const Shader& Renderer::get_shader(const std::string& name) const {
return it->second;
}
void Renderer::init_underwater() {
void Renderer::init_quad() {
glBindVertexArray(m_vao[0]);
glGenBuffers(1, &m_quad_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(QUAD_VERTICES), QUAD_VERTICES,
GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void*)(2 * sizeof(float)));
}
void Renderer::init_text() {
glBindVertexArray(m_vao[4]);
glGenBuffers(1, &m_text_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_text_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
const auto& shader = get_shader("text");
Text::set_loc(shader);
DebugCollector::get().init_text();
}
void Renderer::render() {
glDisable(GL_FRAMEBUFFER_SRGB);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(m_vao[0]);
render_sky();
glBindVertexArray(m_vao[1]);
render_world();
glBindVertexArray(m_vao[2]);
render_outline();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(m_vao[3]);
render_underwater();
glEnable(GL_DEPTH_TEST);
glBindVertexArray(m_vao[4]);
glDisable(GL_FRAMEBUFFER_SRGB);
render_ui();
glBindVertexArray(m_vao[5]);
render_text();
glBindVertexArray(0);
render_dev_panel();
}
@@ -182,23 +224,24 @@ void Renderer::render_outline() {
const auto& shader = get_shader("outline");
shader.use();
m_mv_loc = shader.loc("mv_matrix");
m_proj_loc = shader.loc("proj_matrix");
const auto& block_pos = m_world.get_look_block_pos("TestPlayer");
if (block_pos != std::nullopt) {
m_mv_loc = shader.loc("mv_matrix");
m_proj_loc = shader.loc("proj_matrix");
m_m_mat =
glm::translate(glm::mat4(1.0f), glm::vec3(block_pos.value().pos));
m_v_mat = m_camera.get_camera_lookat();
m_mv_mat = m_v_mat * m_m_mat;
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glBindBuffer(GL_ARRAY_BUFFER, m_outline_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindVertexArray(m_vao[2]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_outline_indices_vbo);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glLineWidth(4.0f);
@@ -221,22 +264,59 @@ void Renderer::render_sky() {
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glBindBuffer(GL_ARRAY_BUFFER, m_sky_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(SKY_COLOR));
glBindVertexArray(m_vao[1]);
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
// draw sun and moon
glDepthMask(GL_FALSE);
glBindVertexArray(m_vao[0]);
// draw sum
glm::vec3 sun_pos = m_camera.get_camera_pos() +
normalize(-m_world.sunlight_dir()) * (FAR_PLANE * 0.9f);
glm::vec3 sun_view_pos = glm::vec3(m_v_mat * glm::vec4(sun_pos, 1.0f));
m_mv_mat = glm::translate(glm::mat4(1.0f), sun_view_pos) *
glm::scale(glm::mat4(1.0f), glm::vec3(SUN_SIZE)) *
glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0.0f));
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(SUN_COLOR));
glDrawArrays(GL_TRIANGLES, 0, 6);
glm::vec3 moon_pos = m_camera.get_camera_pos() +
normalize(m_world.sunlight_dir()) * (FAR_PLANE * 0.9f);
glm::vec3 moon_view_pos = glm::vec3(m_v_mat * glm::vec4(moon_pos, 1.0f));
m_mv_mat = glm::translate(glm::mat4(1.0f), moon_view_pos) *
glm::scale(glm::mat4(1.0f), glm::vec3(MOON_SIZE)) *
glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0.0f));
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(MOON_COLOR));
glDrawArrays(GL_TRIANGLES, 0, 6);
glDepthMask(GL_TRUE);
}
void Renderer::render_text() {
glBindVertexArray(m_vao[4]);
const auto& shader = get_shader("text");
shader.use();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
m_proj_loc = shader.loc("projection");
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_ui_proj));
@@ -254,30 +334,21 @@ void Renderer::render_ui() {
shader.use();
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_mv_loc = shader.loc("m_matrix");
m_proj_loc = shader.loc("proj_matrix");
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_ui_m_matrix));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_ui_proj));
glBindBuffer(GL_ARRAY_BUFFER, m_ui_vbo);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
(void*)offsetof(Vertex2D, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex2D),
(void*)offsetof(Vertex2D, layer));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindVertexArray(m_vao[3]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_ui_array());
glDrawArrays(GL_TRIANGLES, 0, 6);
Tools::check_opengl_error();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
}
@@ -285,13 +356,8 @@ void Renderer::render_ui() {
void Renderer::render_underwater() {
const auto& shader = get_shader("under_water");
shader.use();
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void*)(2 * sizeof(float)));
glBindVertexArray(m_vao[0]);
glUniform1i(shader.loc("u_sceneTexture"), 0);
glUniform1f(shader.loc("u_time"), glfwGetTime());
@@ -315,7 +381,8 @@ void Renderer::update_fov(float fov) {
void Renderer::update_proj_matrix(float aspect, float width, float height) {
m_aspect = aspect;
m_p_mat = glm::perspective(glm::radians(m_fov), aspect, 0.1f, 1000.0f);
m_p_mat =
glm::perspective(glm::radians(m_fov), aspect, NEAR_PLANE, FAR_PLANE);
m_ui_proj = glm::ortho(0.0f, width, height, 0.0f, -1.0f, 1.0f);
// scale and then translate
m_ui_m_matrix =
@@ -330,6 +397,9 @@ void Renderer::updata_framebuffer(int width, int height) {
if (m_fbo == 0) {
glGenFramebuffers(1, &m_fbo);
}
if (m_oit_fbo == 0) {
glGenFramebuffers(1, &m_oit_fbo);
}
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glDeleteTextures(1, &m_screen_texture);
@@ -357,23 +427,312 @@ void Renderer::updata_framebuffer(int width, int height) {
Logger::info("Frame Buffer Complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, m_oit_fbo);
glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture);
glDeleteRenderbuffers(1, &m_oit_depth_render_buffer);
glGenTextures(1, &m_accum_texture);
glBindTexture(GL_TEXTURE_2D, m_accum_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA,
GL_HALF_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
m_accum_texture, 0);
glGenTextures(1, &m_reveal_texture);
glBindTexture(GL_TEXTURE_2D, m_reveal_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, width, height, 0, GL_RED,
GL_HALF_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
m_reveal_texture, 0);
glGenRenderbuffers(1, &m_oit_depth_render_buffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_oit_depth_render_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, m_oit_depth_render_buffer);
GLenum draw_buffer[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, draw_buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
Logger::error("FBO incomplete after resize!");
} else {
Logger::info("Frame Buffer Complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// depth map fbo
if (m_depth_map_fbo == 0) {
glGenFramebuffers(1, &m_depth_map_fbo);
}
glDeleteTextures(1, &m_depth_map_texture);
glGenTextures(1, &m_depth_map_texture);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, DEPTH_MAP_SIZE,
DEPTH_MAP_SIZE, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float border_color[] = {1.0f, 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
// Manually compare shadows
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
// GL_COMPARE_REF_TO_TEXTURE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glBindFramebuffer(GL_FRAMEBUFFER, m_depth_map_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
m_depth_map_texture, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
Logger::error("FBO incomplete after resize!");
} else {
Logger::info("Frame Buffer Complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
m_width = width;
m_height = height;
}
void Renderer::render_world() {
const auto& shader = get_shader("world");
shader.use();
// shader map
const auto& depth_shader = get_shader("depth_shader");
depth_shader.use();
glm::vec3 cam_pos = m_camera.get_camera_pos();
glm::vec3 cam_fwd = m_camera.get_camera_front();
float half_extent = 128.0f;
glm::vec3 center = cam_pos + cam_fwd * (half_extent * 0.5f);
glm::vec3 sundir = glm::normalize(m_world.sunlight_dir());
glm::vec3 up =
fabs(sundir.y) > 0.99f ? glm::vec3(0, 0, 1) : glm::vec3(0, 1, 0);
glm::mat4 light_basis = glm::lookAt(glm::vec3(0.0f), sundir, up);
float texels_per_unit = DEPTH_MAP_SIZE / (half_extent * 2.0f);
glm::vec3 ls_center = glm::vec3(light_basis * glm::vec4(center, 1.0f));
ls_center.x = std::round(ls_center.x * texels_per_unit) / texels_per_unit;
ls_center.y = std::round(ls_center.y * texels_per_unit) / texels_per_unit;
glm::vec3 snapped_center =
glm::vec3(glm::inverse(light_basis) * glm::vec4(ls_center, 1.0f));
float distance = half_extent * 1.5f;
float near_plane = 1.0f;
float far_plane = distance + half_extent * 2.0f;
glm::vec3 light_pos = snapped_center - sundir * distance;
glm::mat4 light_view = glm::lookAt(light_pos, snapped_center, up);
glm::mat4 light_projection =
glm::ortho(-half_extent, half_extent, -half_extent, half_extent,
near_plane, far_plane);
glm::mat4 light_space_matrix = light_projection * light_view;
glUniformMatrix4fv(depth_shader.loc("lightSpaceMatrix"), 1, GL_FALSE,
glm::value_ptr(light_space_matrix));
glViewport(0, 0, DEPTH_MAP_SIZE, DEPTH_MAP_SIZE);
glCullFace(GL_FRONT);
glBindFramebuffer(GL_FRAMEBUFFER, m_depth_map_fbo);
glClear(GL_DEPTH_BUFFER_BIT);
auto& m_render_snapshots = m_world.render_snapshots();
auto& camera_pos = m_camera.get_camera_pos();
glActiveTexture(GL_TEXTURE1);
glEnable(GL_DEPTH_TEST);
for (const auto& snapshot : m_render_snapshots) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
}
// cross_plane and discard
for (const auto& snapshot : m_render_snapshots) {
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.cross_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
}
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glCullFace(GL_BACK);
glViewport(0, 0, m_width, m_height);
const auto& normal_block_shader = get_shader("normal_block");
normal_block_shader.use();
m_mv_loc = normal_block_shader.loc("mv_matrix");
m_proj_loc = normal_block_shader.loc("proj_matrix");
m_mv_loc = shader.loc("mv_matrix");
m_proj_loc = shader.loc("proj_matrix");
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glActiveTexture(GL_TEXTURE1);
m_m_mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
m_v_mat = m_camera.get_camera_lookat();
m_mv_mat = m_v_mat * m_m_mat;
m_norm_mat = glm::transpose(glm::inverse(m_mv_mat));
glm::vec3 light_dir_view =
glm::normalize(glm::mat3(m_v_mat) * m_world.sunlight_dir());
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniformMatrix4fv(normal_block_shader.loc("norm_matrix"), 1, GL_FALSE,
glm::value_ptr(m_norm_mat));
glUniformMatrix4fv(normal_block_shader.loc("lightSpaceMatrix"), 1, GL_FALSE,
glm::value_ptr(light_space_matrix));
glUniform1f(normal_block_shader.loc("ambientStrength"), m_ambient_strength);
glUniform3fv(normal_block_shader.loc("sunlightColor"), 1,
glm::value_ptr(SUNLIGHT_COLOR));
glUniform3fv(normal_block_shader.loc("sunlightDir"), 1,
glm::value_ptr(light_dir_view));
m_mvp_mat = m_p_mat * m_mv_mat;
m_world.render(m_mvp_mat, m_texture_manager, m_camera.get_camera_pos());
auto& m_planes = m_world.planes();
Math::extract_frustum_planes(m_mvp_mat, m_planes);
int rendered_sum = 0;
glEnable(GL_DEPTH_TEST);
for (const auto& snapshot : m_render_snapshots) {
if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
rendered_sum++;
}
}
// cross_plane and discard
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
continue;
}
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_cross_plane_array());
glBindVertexArray(snapshot.cross_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
}
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
}
}
// copy depth buffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_oit_fbo);
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height,
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_oit_fbo);
// pass one accumulate
auto& accum_shader = get_shader("accum");
accum_shader.use();
GLint mv_loc = accum_shader.loc("mv_matrix");
GLint proj_loc = accum_shader.loc("proj_matrix");
glUniformMatrix4fv(mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glBindFramebuffer(GL_FRAMEBUFFER, m_oit_fbo);
glClearBufferfv(GL_COLOR, 0, glm::value_ptr(glm::vec4(0.0f)));
float one = 1.0f;
glClearBufferfv(GL_COLOR, 1, &one);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunci(0, GL_ONE, GL_ONE);
glBlendFunci(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
glActiveTexture(GL_TEXTURE0);
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
continue;
}
if (snapshot.normal_blend_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_blend_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_blend_vertices_count);
}
}
auto& composite_shader = get_shader("composite");
glDisable(GL_BLEND);
composite_shader.use();
glUniform1i(composite_shader.loc("u_accumTex"), 0);
glUniform1i(composite_shader.loc("u_revealTex"), 1);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArray(m_vao[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_accum_texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_reveal_texture);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
DebugCollector::get().report(
"rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum));
}
void Renderer::render_dev_panel() {
@@ -382,4 +741,6 @@ void Renderer::render_dev_panel() {
glEnable(GL_DEPTH_TEST);
}
float& Renderer::ambient_strength() { return m_ambient_strength; }
} // namespace Cubed

View File

@@ -5,8 +5,15 @@ namespace Cubed {
Random::Random() {}
Random::Random(unsigned seed) { init(seed); }
bool Random::random_bool(double probability) {
std::bernoulli_distribution dist(probability);
return dist(m_engine);
if (probability <= 0.0)
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; }
@@ -18,12 +25,23 @@ void Random::init(unsigned seed) {
m_engine.seed(seed);
}
int Random::random_int(int min, int max) {
std::uniform_int_distribution<int> dist(min, max);
return dist(m_engine);
unsigned range = static_cast<unsigned>(max - min) + 1;
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) {
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

View File

@@ -45,6 +45,26 @@ float smootherstep(float edge0, float edge1, float x) {
return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f);
}
bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
const std::vector<glm::vec4>& planes) {
for (const auto& plane : planes) {
// distance
float d = glm::dot(glm::vec3(plane), center) + plane.w;
float r = half_extents.x * std::abs(plane.x) +
half_extents.y * std::abs(plane.y) +
half_extents.z * std::abs(plane.z);
if (d + r < 0) {
return false;
}
}
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 Cubed