Compare commits

..

27 Commits

Author SHA1 Message Date
c00f05aafd feat(renderer): smooth shadow sun direction transitions using quantized directions and slerp 2026-06-17 22:09:50 +08:00
dc1ef70231 fix(texture): set texture wrap mode to clamp to edge 2026-06-17 21:13:23 +08:00
e224110452 feat(renderer): add runtime shader and shadow mode controls
Introduce user-controllable shader on/off, shadow mode (rotated Poisson disk, 3x3 grid, or off), light cull face, and discard transparent in depth pass. Expose all settings in new dev panel "shader" tab. Move ambient strength slider to the new tab.
2026-06-17 20:16:20 +08:00
74b0aebc9f chore(world): add missing <numbers> include 2026-06-17 19:16:02 +08:00
be425a705c feat(dev-panel): add tick freeze toggle and fix TickType sign
Add checkbox to freeze tick advancement in dev panel.
Change TickType from unsigned long long to signed long long to prevent underflow.
2026-06-17 19:15:16 +08:00
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
zhenyan121
2906106597 feat: water rendering (#13)
* refactor: update water texture

* feat(gameplay): implement transparent block rendering with depth sorting

* fix(world): use camera pos for distance calculations, make water passable

* refactor(player): use ivec3 for block pass check

* feat(camera): add underwater detection

* feat(renderer): add underwater effect with framebuffer post-processing

* feat(block): add gas property and refactor solid block check

* fix(assets): set leaf block transparency to false

* fix(block): set leaf as transparent
2026-05-30 15:11:40 +08:00
zhenyan121
a0139dd315 fix: msvc build fail (#12) 2026-05-28 21:55:44 +08:00
zhenyan121
5901ab7cd9 feat: grass (#11)
* feat: add grass texture and update grass_block texture

* feat: add block data

* feat: add blocks_tool

* feat: add sync info and change function in blocks_tools

* feat: add check and new function

* refactor: make block texture loading data-driven

* feat: add rendering for grass

* feat: passable grass

* feat: random grass place

* fix: memory leak in TextureManager::load_cross_plane_texture
2026-05-28 21:34:36 +08:00
zhenyan121
bbf8b4e969 refactor: river (#10)
* fix: correct snowy grass block texture

* refactor: river generation

* fix: water placement error due to interpolation

* perf: improve river naturalness

* feat: add river tab item

* fix: path truncation
2026-05-23 14:29:41 +08:00
zhenyan121
a54e87dbc6 refactor: terrain generation (#9)
* feat: add BlockType

* refactor: use fBM for heightmap generation

* feat: improve mountain realism

* refactor: adjust mountain spawn probability

* feat: add biome boundary blending

* refactor: remove resolve_biome_adjacency_conflict function

* feat: add snowy plain

* perf: speed up world generation

* refactor: lower overall terrain height
2026-05-23 10:33:52 +08:00
zhenyan121
1a26474a05 feat: add cave (#8)
* feat: add cave generate

* fix: incorrect blocks on cave surface

* fix: non-deterministic cave generator

* refactor: move all chunk generation to dedicated generation thread

* refactor: remove inital cave

* feat: add cave parameter adjustment

* refactor: adjust cave probability
2026-05-09 20:13:55 +08:00
zhenyan121
d986e03f9c fix: windows build fail (#7) 2026-05-03 19:45:14 +08:00
zhenyan121
9d200f31be refactor: chunk interpolate (#6)
* refactor: rewrite blend_heightmap_boundaries

* refactor: init_world

* fix: unnatural biome boundary transition
2026-05-03 16:02:01 +08:00
zhenyan121
a02bfad639 refactor: biome build (#5)
* refactor: rename Biome to BiomeType

* feat: add biome builder
2026-05-02 13:40:46 +08:00
zhenyan121
a63dfa7f47 feat: add river biome (#4)
* feat: add river biome

* fix: duplicate mountain terrain generation

* fix: safe_int_to_biome not include river
2026-05-01 19:18:46 +08:00
zhenyan121
d4d761b2aa Potential fix for code scanning alert no. 1: Workflow does not contain permissions (#3)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-30 12:08:12 +08:00
zhenyan121
315c60e4a6 feat: block switching (#2)
* feat: add item tab item in dev panel

* feat: add block switching via mouse wheel
2026-04-30 11:53:35 +08:00
124 changed files with 5445 additions and 863 deletions

View File

@@ -3,6 +3,9 @@ name: Code Format Check
on: [push, pull_request]
permissions:
contents: read
jobs:
formatting:
runs-on: ubuntu-latest

1
.gitignore vendored
View File

@@ -41,3 +41,4 @@ CMakeError.log
*~
.DS_Store
assets/config.toml
.venv/

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

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}
@@ -111,20 +125,34 @@ add_executable(${PROJECT_NAME}
src/tools/perlin_noise.cpp
src/ui/text.cpp
src/window.cpp
src/gameplay/builders/biome_builder.cpp
src/gameplay/builders/plain_builder.cpp
src/gameplay/builders/mountain_builder.cpp
src/gameplay/builders/river_builder.cpp
src/gameplay/builders/desert_builder.cpp
src/gameplay/builders/forest_builder.cpp
src/gameplay/cave_carver.cpp
src/gameplay/cave_path.cpp
src/gameplay/builders/snowy_plain_builder.cpp
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")
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
target_compile_options(${PROJECT_NAME} PRIVATE
-fsanitize=address
#-fsanitize=address
#-fsanitize=thread
-fno-omit-frame-pointer
-g
)
target_link_options(${PROJECT_NAME} PRIVATE
-fsanitize=address
#-fsanitize=address
#-fsanitize=thread
)
@@ -150,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)
@@ -173,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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +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

@@ -0,0 +1,10 @@
name = "template"
id = 0
is_liquid = false
is_gas = false
is_passable = false
is_cross_plane = false
is_transparent = false
is_discard = false
is_blend = false
is_transitional = false

View File

@@ -0,0 +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,125 @@
#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;
uniform bool shader_on;
uniform int shadowMode;
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))
);
if (shadowMode == 0) {
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);
} else if (shadowMode == 1) {
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;
} else if (shadowMode == 2) {
// pcf off
float pcfDepth =
texture(shadowMap, projCoords.xy).r;
shadow =
currentDepth - bias > pcfDepth
? 1.0
: 0.0;
} else {
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.5) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
if (objectColor.a < 0.8) {
discard;
}
if (!shader_on) {
color = objectColor;
return;
}
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,16 @@
#version 460
in vec2 tc;
flat in int tex_layer;
layout (binding = 1) uniform sampler2DArray samp;
uniform bool is_discard_tranparent;
void main() {
if (is_discard_tranparent) {
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

@@ -0,0 +1,35 @@
#version 460
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D u_sceneTexture;
uniform float u_time;
uniform bool u_underwater;
uniform vec3 u_waterColor;
uniform float u_fogDensity;
void main() {
vec4 original = texture(u_sceneTexture, TexCoord);
if (!u_underwater) {
FragColor = original;
return;
}
vec2 distoredUV = TexCoord;
float strength = 0.003;
distoredUV.x += sin(TexCoord.y * 15.0 + u_time * 5.0) * strength;
distoredUV.y += cos(TexCoord.x * 15.0 + u_time * 4.3) * strength;
distoredUV = clamp(distoredUV, 0.001, 0.999);
vec4 distorted = texture(u_sceneTexture, distoredUV);
float caustic = 0.9 + 0.1 * sin(TexCoord.x * 20.0 + u_time) * cos(TexCoord.y * 20.0 + u_time * 1.2);
vec3 causticLight = vec3(caustic, caustic * 0.95, caustic * 0.9);
//vec3 causticLight = vec3(1.0);
float fogFactor = clamp(1.0 - (TexCoord.y * u_fogDensity * 10.0), 0.0, 1.0);
vec3 mixed = mix(u_waterColor, distorted.rgb * causticLight, fogFactor);
FragColor = vec4(mixed, 1.0);
}

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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

View File

@@ -16,6 +16,7 @@ private:
Player* m_player;
float m_last_mouse_x, m_last_mouse_y;
glm::vec3 m_camera_pos;
bool m_under_water = false;
public:
Camera();
@@ -29,6 +30,9 @@ public:
const glm::mat4 get_camera_lookat() const;
const glm::vec3& get_camera_pos() const;
bool is_under_water() const;
glm::vec3 get_camera_front() const;
};
} // namespace Cubed

View File

@@ -1,11 +1,13 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include <array>
namespace Cubed {
constexpr int WORLD_SIZE_Y = 256;
constexpr int CHUCK_SIZE = 16;
constexpr int CHUNK_SIZE = 16;
constexpr int SEA_LEVEL = 63;
constexpr int MAX_BLOCK_NUM = 7;
constexpr int MAX_UI_NUM = 1;
constexpr int MAX_BLOCK_STATUS = 1;
constexpr int MAX_BIOME_SUM = 4;
@@ -14,14 +16,20 @@ constexpr int MAX_CHARACTER = 128;
constexpr int PRE_LOAD_DISTANCE = 24;
constexpr int MAX_DISTANCE = 128;
constexpr int CROSS_PLANE_DISTANCE = 8;
constexpr float DEFAULT_FOV = 70.0f;
constexpr float DEFAULT_MAX_WALK_SPEED = 4.5f;
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;
constexpr int SIZE_X = CHUNK_SIZE;
constexpr int SIZE_Y = WORLD_SIZE_Y;
constexpr int SIZE_Z = CHUNK_SIZE;
using HeightMapArray = std::array<std::array<float, CHUCK_SIZE>, CHUCK_SIZE>;
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
using HeightMapArray = std::array<std::array<int, CHUNK_SIZE>, CHUNK_SIZE>;
} // namespace Cubed

View File

@@ -42,12 +42,21 @@ private:
PlayerProfile m_player_profile;
TextEditing m_text_editing;
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;
bool m_tick_frezze = false;
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();
void show_world_tab_item();
void show_player_tab_item();
void show_items_tab_item();
void show_shader_tab_item();
void update_config_view();
void update_player_profile();

View File

@@ -5,14 +5,25 @@
namespace Cubed {
constexpr float BIOME_NOISE_FREQUENCY = 0.03f;
constexpr float BIOME_NOISE_FREQUENCY = 0.06f;
constexpr float HEIGHTMAP_NOISE_FREQUENCY = 0.001f;
constexpr float MOUNTAINOUS_NOISE_FREQUENCY = 0.003f;
enum class BiomeType {
PLAIN = 0,
FOREST,
DESERT,
MOUNTAIN,
RIVER,
SNOWY_PLAIN,
OCEAN,
NONE
};
constexpr float PLAIN_FREQ = 0.4f;
constexpr float FOREST_FREQ = 1.2f;
constexpr float DESERT_FREQ = 1.2f;
constexpr float MOUNTAIN_FREQ = 2.0f;
enum class Biome { PLAIN = 0, FOREST, DESERT, MOUNTAIN, NONE };
struct BiomeConditions {
float temp = 0.0f;
float humid = 0.0f;
float mountainous = 0.0f;
};
struct BiomeHeightRange {
int base_y;
@@ -20,19 +31,19 @@ struct BiomeHeightRange {
};
struct BiomeNonAdjacent {
Biome first;
std::vector<Biome> second;
Biome replace;
BiomeType first;
std::vector<BiomeType> second;
BiomeType replace;
};
static inline const std::vector<BiomeNonAdjacent> NON_ADJACENT{
{{Biome::PLAIN, {Biome::NONE}, Biome::PLAIN},
{Biome::FOREST, {Biome::DESERT}, Biome::PLAIN},
{Biome::DESERT, {Biome::MOUNTAIN, Biome::FOREST}, Biome::PLAIN},
{Biome::MOUNTAIN, {Biome::DESERT}, Biome::PLAIN}}};
{{BiomeType::PLAIN, {BiomeType::DESERT}, BiomeType::RIVER},
{BiomeType::FOREST, {BiomeType::DESERT}, BiomeType::RIVER},
{BiomeType::DESERT, {BiomeType::FOREST}, BiomeType::RIVER},
{BiomeType::MOUNTAIN, {BiomeType::NONE}, BiomeType::RIVER}}};
struct BaseBiomeParams {
Biome biome;
BiomeType biome;
std::pair<float, float> temp;
std::pair<float, float> humid;
std::array<float, 3> frequencies;
@@ -49,17 +60,21 @@ struct DesertParams : public BaseBiomeParams {};
struct MountainParams : public BaseBiomeParams {};
std::string get_biome_str(Biome biome);
Biome get_biome_from_noise(float temp, float humid);
std::array<float, 3> get_noise_frequencies_for_biome(Biome biome);
BiomeHeightRange get_biome_height_range(Biome biome);
Biome safe_int_to_biome(int x);
struct RiverParams : public BaseBiomeParams {};
std::string get_biome_str(BiomeType biome);
// std::array<float, 3> get_noise_frequencies_for_biome(BiomeType biome);
// BiomeHeightRange get_biome_height_range(BiomeType biome);
BiomeType safe_int_to_biome(int x);
int get_interpolated_height(float world_x, float world_z, float temp,
float humid);
BiomeType determine_biome(const BiomeConditions& conditions);
PlainParams& plain_params();
ForestParams& forest_params();
DesertParams& desert_params();
MountainParams& mountain_params();
RiverParams& river_params();
} // namespace Cubed

View File

@@ -1,8 +1,5 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include <array>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <string>
@@ -10,6 +7,8 @@
namespace Cubed {
using BlockType = uint8_t;
struct BlockTexture {
std::string name;
unsigned id;
@@ -38,15 +37,55 @@ struct LookBlock {
glm::ivec3 normal;
};
constexpr std::array<std::string_view, MAX_BLOCK_NUM> BLOCK_REISTER{
"air", "grass_block", "dirt", "stone", "sand", "log", "leaf"};
struct BlockData {
std::string name;
BlockType id = 0;
const std::array<bool, MAX_BLOCK_NUM> TRANSPARENT_MAP{
true, false, false, false, false, false, true};
bool is_liquid = false;
bool is_gas = false;
inline bool is_in_transparent_map(unsigned id) {
ASSERT_MSG(id < MAX_BLOCK_NUM, "ID is invaild");
return TRANSPARENT_MAP[id];
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 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_discard(discard), is_blend(blend),
is_transitional(transitional) {}
};
class BlockManager {
public:
static const std::vector<BlockData>& datas();
static void init();
static unsigned sums();
static unsigned cross_plane_sum();
static const std::string& name_form_id(BlockType id);
static bool is_gas(BlockType id);
static bool is_liquid(BlockType id);
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:
static void set_up_cross_plane_map();
static inline std::vector<BlockData> m_datas;
static inline bool is_init = false;
static inline std::unordered_map<BlockType, BlockType> m_cross_plane_map;
};
} // namespace Cubed

View File

@@ -0,0 +1,18 @@
#pragma once
namespace Cubed {
class ChunkGenerator;
class BiomeBuilder {
public:
BiomeBuilder() = default;
virtual ~BiomeBuilder() = default;
virtual ChunkGenerator& get_chunk_generator() = 0;
virtual void build_biome() = 0;
virtual void build_vegetation() = 0;
void ocean_water_build();
protected:
void build_bottom();
void place_grass();
};
} // namespace Cubed

View File

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class DesertBuilder : public BiomeBuilder {
public:
DesertBuilder(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

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class ForestBuilder : public BiomeBuilder {
public:
ForestBuilder(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

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class MountainBuilder : public BiomeBuilder {
public:
MountainBuilder(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

@@ -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

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class PlainBuilder : public BiomeBuilder {
public:
PlainBuilder(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

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class RiverBuilder : public BiomeBuilder {
public:
RiverBuilder(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

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/gameplay/builders/biome_builder.hpp"
namespace Cubed {
class ChunkGenerator;
class SnowyPlainBuilder : public BiomeBuilder {
public:
SnowyPlainBuilder(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

@@ -0,0 +1,27 @@
#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();
CaveHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed);
void try_to_add_path(const ChunkPos& pos, unsigned chunk_seed);
void cleanup_finished_caves();
int cave_sum() const;
float& cave_probability();
private:
CaveHashMap m_paths;
unsigned m_seed = 0;
Random m_random;
float m_cave_probability = 0.035f;
};
} // namespace Cubed

View File

@@ -0,0 +1,54 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CavePath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public:
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;
static float& radius_xz_min();
static float& radius_xz_max();
static float& radius_y_min();
static float& radius_y_max();
static float& delta_angle_min();
static float& delta_angle_max();
static int& step_min();
static int& step_max();
private:
static inline float m_radius_xz_min = 5.0f;
static inline float m_radius_xz_max = 15.0f;
static inline float m_radius_y_min = 4.0f;
static inline float m_radius_y_max = 10.0f;
static inline float m_delta_angle_min = -5.0f;
static inline float m_delta_angle_max = 5.0f;
static inline int m_step_min = 10;
static inline int m_step_max = 400;
unsigned int m_seed = 0;
float m_yaw = 0.0f;
float m_pitch = 0.0f;
int m_step = 0;
float m_step_len = 1.0f;
PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f};
Random m_random;
std::vector<PathPoint> m_points;
ChunkPosSet m_pending_chunks;
void collect_path_points();
void precompute_chunk_coverage();
};
} // namespace Cubed

View File

@@ -4,27 +4,26 @@
#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 <cstdint>
#include <mutex>
namespace Cubed {
class World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
static constexpr int SIZE_X = CHUCK_SIZE;
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 = CHUCK_SIZE;
using HeightMapArray = std::array<std::array<float, SIZE_Z>, SIZE_X>;
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_vertex_sum = 0;
std::atomic<Biome> m_biome = Biome::PLAIN;
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
std::unique_ptr<ChunkGenerator> m_generator;
@@ -33,14 +32,25 @@ private:
World& m_world;
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<uint8_t> m_blocks;
GLuint m_vbo = 0;
std::vector<Vertex> m_vertexs_data;
std::vector<BlockType> m_blocks;
/*
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;
BiomeConditions m_conditions;
void clear_dirty();
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);
@@ -50,27 +60,37 @@ public:
Chunk(Chunk&&) noexcept;
Chunk& operator=(Chunk&&) noexcept;
Biome get_biome() const;
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
int chunk_x, int chunk_z);
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<uint8_t>& get_chunk_blocks() const;
const std::vector<BlockType>& get_chunk_blocks() const;
HeightMapArray get_heightmap() const;
static int get_index(int x, int y, int z);
static int get_index(const glm::vec3& pos);
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
// Init Chunk
// Determine biome from temperature and humidity noise
void gen_phase_one();
// Resolve biome adjacency conflicts with neighbor chunks
void gen_phase_two(const std::array<const Chunk*, 4>& adj_chunks);
void gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks);
// Generate heightmap using biome-specific noise
void gen_phase_three();
// Blend heightmap with neighbors for smooth transitions
void gen_phase_four(
const std::array<std::optional<HeightMapArray>, 4>& neighbor_heightmap);
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& neighbor_biome);
// Generate terrain blocks from heightmap and biome
void gen_phase_five();
// Blend surface blocks at chunk borders with neighbors
void gen_phase_six(const std::array<std::optional<std::vector<uint8_t>>, 4>&
neighbor_block);
void gen_phase_six(const std::array<std::optional<std::vector<BlockType>>,
4>& neighbor_block);
// Generate biome-specific vegetation/structures
void gen_phase_seven();
// void gen_vertex_data();
@@ -78,12 +98,20 @@ public:
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void gen_vertex_data(
const std::array<const std::vector<uint8_t>*, 4>& neighbor_block);
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
void upload_to_gpu();
GLuint get_vbo() const;
size_t get_vertex_sum() const;
GLuint get_normal_vao() const;
size_t get_normal_vertices_sum() const;
GLuint get_cross_vao() const;
size_t get_cross_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();
@@ -94,10 +122,13 @@ public:
void set_chunk_block(int index, unsigned id);
ChunkPos chunk_pos() const;
Biome biome() const;
void biome(Biome b);
BiomeType biome() const;
void biome(BiomeType b);
HeightMapArray& heightmap();
std::vector<uint8_t>& blocks();
std::vector<BlockType>& blocks();
World& world();
unsigned seed() const;
BiomeConditions& conditions();
};
} // namespace Cubed

View File

@@ -1,21 +1,20 @@
#pragma once
#include "Cubed/constants.hpp"
#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>
#include <memory>
#include <optional>
namespace Cubed {
class Chunk;
class ChunkGenerator {
static constexpr int SIZE_X = CHUCK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUCK_SIZE;
using HeightMapArray =
std::array<std::array<float, CHUCK_SIZE>, CHUCK_SIZE>;
public:
ChunkGenerator(Chunk& chunk);
@@ -23,25 +22,33 @@ public:
static void reload();
static const unsigned& seed();
static void seed(unsigned s);
unsigned chunk_seed() const;
// Generate Biome
void assign_chunk_biome();
// Adjust Biome
void resolve_biome_adjacency_conflict(
const std::array<const Chunk*, 4>& adj_chunks);
const std::array<const Chunk*, 8>& adj_chunks);
// Generate Heightmap
void generate_heightmap();
// Adjust Height
void blend_heightmap_boundaries(
const std::array<std::optional<HeightMapArray>, 4>& neighbor_heightmap);
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& neighbor_biome);
// Generate Block
void generate_terrain_blocks();
// Adjust Block;
void blend_surface_blocks_borders(
const std::array<std::optional<std::vector<uint8_t>>, 4>&
const std::array<std::optional<std::vector<BlockType>>, 4>&
neighbor_block);
// Generate Structure
void generate_vegetation();
BiomeType get_biome_at(float world_x, float world_z);
Chunk& chunk();
Random& random();
const std::array<BiomeType, 8>& neighbor_biome() const;
void ocean_build();
void generate_cave();
void generate_river();
private:
static inline std::atomic<bool> is_init{false};
@@ -49,6 +56,15 @@ private:
static inline std::atomic<bool> is_seed_change{false};
Chunk& m_chunk;
Random m_random;
std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr};
bool is_cur_chunk_ins = false;
std::array<BiomeType, 8> m_neighbor_biome;
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,10 @@
#pragma once
// Prevent unsigned underflow issues in subtraction
using TickType = long long;
constexpr int DEFAULT_PER_TICK_TIME = 50;
constexpr TickType DAY_TIME = 24000;
constexpr TickType PER_HOUR = 1000;

View File

@@ -0,0 +1,36 @@
#pragma once
#include <glm/glm.hpp>
struct PathPoint {
glm::vec3 pos;
glm::vec3 tangent{0.0f, 0.0f, 1.0f};
float rad_xz;
float rad_y;
PathPoint(const glm::vec3& p, float rx, float ry)
: pos(p), rad_xz(rx), rad_y(ry) {}
bool contains(const glm::vec3& other_pos) const {
glm::vec3 to_point = other_pos - pos;
glm::vec3 world_up(0.0f, 1.0f, 0.0f);
glm::vec3 right = glm::normalize(glm::cross(tangent, world_up));
if (glm::length(right) < 0.001f) {
glm::vec3 alt_up(1.0f, 0.0f, 0.0f);
right = glm::normalize(glm::cross(tangent, alt_up));
}
glm::vec3 up = glm::normalize(glm::cross(right, tangent));
float horizontal_dist = glm::dot(to_point, right);
float vertical_dist = glm::dot(to_point, up);
float a = rad_xz;
float b = rad_y;
if (a <= 0.0f || b <= 0.0f)
return false;
float check = (horizontal_dist * horizontal_dist) / (a * a) +
(vertical_dist * vertical_dist) / (b * b);
return check <= 1.0f;
}
};

View File

@@ -42,6 +42,8 @@ private:
float m_xz_speed = 0.0f;
unsigned m_place_block = 1;
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// player is tow block tall, the pos is the lower pos
@@ -85,6 +87,7 @@ public:
void change_mode(GameMode mode);
void hot_reload();
void set_player_pos(const glm::vec3& pos);
void set_place_block(unsigned id);
void update(float delta_time);
void update_front_vec(float offset_x, float offset_y);
void update_player_move_state(int key, int action);
@@ -97,8 +100,11 @@ public:
float& deceleration();
float& g();
unsigned place_block() const;
Gait& gait();
GameMode& game_mode();
const World& get_world() const;
};
} // namespace Cubed

View File

@@ -0,0 +1,55 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class RiverPath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public:
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;
static float& radius_xz_min();
static float& radius_xz_max();
static float& radius_y_min();
static float& radius_y_max();
static float& delta_angle_min();
static float& delta_angle_max();
static int& step_min();
static int& step_max();
private:
static inline float m_radius_xz_min = 5.0f;
static inline float m_radius_xz_max = 10.0f;
static inline float m_radius_y_min = 4.0f;
static inline float m_radius_y_max = 8.0f;
static inline float m_delta_angle_min = -3.0f;
static inline float m_delta_angle_max = 3.0f;
static inline int m_step_min = 200;
static inline int m_step_max = 400;
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;
PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f};
Random m_random;
std::vector<PathPoint> m_points;
ChunkPosSet m_pending_chunks;
void collect_path_points();
void precompute_chunk_coverage();
};
} // namespace Cubed

View File

@@ -0,0 +1,32 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/river.path.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class RiverWorm {
using RiverHashMap = tbb::concurrent_hash_map<unsigned, RiverPath>;
public:
RiverWorm();
RiverHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed);
void try_to_add_path(const ChunkPos& pos, unsigned chunk_seed);
void cleanup_finished_rivers();
int river_sum() const;
float& river_probability();
private:
RiverHashMap m_paths;
unsigned m_seed = 0;
Random m_random;
float m_probability = 0.01f;
};
}; // namespace Cubed

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

@@ -1,6 +1,9 @@
#pragma once
#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>
#include <condition_variable>
@@ -13,61 +16,88 @@
namespace Cubed {
struct ChunkRenderSnapshot {
GLuint vbo;
size_t vertex_count;
GLuint normal_vao;
size_t normal_vertices_count;
GLuint cross_vao;
size_t cross_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;
};
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};
std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash> m_chunks;
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_tick_running{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;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk_queue;
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
void init_chunks();
void gen_chunks_internal();
void sync_player_pos(glm::vec3& player_pos);
void compute_required_chunks(ChunkPosSet& required_chunks);
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&);
void start_gen_thread();
void stop_gen_thread();
public:
World();
@@ -82,22 +112,19 @@ 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_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;
BlockType get_block_tpye(const glm::ivec3& block_pos) const;
static ChunkPos chunk_pos(int world_x, int world_z);
void need_gen();
void render(const glm::mat4& mvp_matrix);
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();
@@ -105,6 +132,26 @@ public:
float chunk_gen_fraction() const;
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);
bool is_tick_running() const;
void tick_running(bool run);
};
} // namespace Cubed

View File

@@ -1,18 +1,25 @@
#pragma once
#include <string>
#include <unordered_map>
// #include <string>
// #include <unordered_map>
// #include <vector>
namespace Cubed {
class MapTable {
private:
static std::unordered_map<unsigned, std::string> id_to_name_map;
static std::unordered_map<size_t, unsigned> name_to_id_map;
/*
static inline std::unordered_map<unsigned, std::string> id_to_name_map;
static inline std::unordered_map<size_t, unsigned> name_to_id_map;
static inline std::vector<std::string> item_id_to_name;
*/
public:
// please using reference
static const std::string& get_name_from_id(unsigned id);
/*
static std::string_view get_name_from_id(unsigned id);
static unsigned get_id_from_name(const std::string& name);
static std::string_view item_name(unsigned id);
static const std::vector<std::string>& item_map();
*/
static void init_map();
};

View File

@@ -1,7 +1,7 @@
#pragma once
namespace Cubed {
#pragma region NORMAL_BLOCK
constexpr float VERTICES_POS[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f}, // bottom left
@@ -92,6 +92,51 @@ constexpr float TEX_COORDS[6][6][2] = {
{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,
0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0};
@@ -112,11 +157,73 @@ constexpr float SQUARE_TEXTURE_POS[6][2] = {
{0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f},
{1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f},
};
#pragma region CROSS_PLANE
constexpr float CROSS_VERTICES_POS[2][6][3] = {
// ===== Plane 1: bottom-front-left to top-back-right =====
{{0.0f, 0.0f, 0.0f}, // bottom front left
{0.0f, 1.0f, 0.0f}, // top front left
{1.0f, 1.0f, 1.0f}, // top back right
{1.0f, 1.0f, 1.0f}, // top back right
{1.0f, 0.0f, 1.0f}, // bottom back right
{0.0f, 0.0f, 0.0f}}, // bottom front left
struct Vertex {
// ===== Plane 2: bottom-front-right to top-back-left =====
{{1.0f, 0.0f, 0.0f}, // bottom front right
{1.0f, 1.0f, 0.0f}, // top front right
{0.0f, 1.0f, 1.0f}, // top back left
{0.0f, 1.0f, 1.0f}, // top back left
{0.0f, 0.0f, 1.0f}, // bottom back left
{1.0f, 0.0f, 0.0f}}, // bottom front right
};
constexpr float CROSS_TEX_COORDS[2][6][2] = {
// ===== Plane 1: bottom-front-left to top-back-right =====
{{0.0f, 1.0f}, // bottom left
{0.0f, 0.0f}, // top left
{1.0f, 0.0f}, // top right
{1.0f, 0.0f}, // top right
{1.0f, 1.0f}, // bottom right
{0.0f, 1.0f}}, // bottom left
// ===== Plane 2: bottom-front-right to top-back-left =====
{{0.0f, 1.0f}, // bottom left
{0.0f, 0.0f}, // top left
{1.0f, 0.0f}, // top right
{1.0f, 0.0f}, // top right
{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[] = {
// postion // texcoorlds
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f,
-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 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 = 5;
constexpr static int NUM_VAO = 7;
Renderer(const Camera& camera, World& world,
const TextureManager& texture_manager, DevPanel& dev_panel);
@@ -24,34 +24,94 @@ public:
void init();
const Shader& get_shader(const std::string& name) const;
void render();
void update(float delta_time);
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();
bool& discard_transparent();
bool& shader_on();
int& shadow_mode();
int& light_cull_face();
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;
static constexpr float ANGLE_STEP_DEG = 0.5f;
float m_ambient_strength = 0.1f;
const Camera& m_camera;
DevPanel& m_dev_panel;
const TextureManager& m_texture_manager;
World& m_world;
bool m_discard_tranparent = true;
bool m_shader_on = true;
int m_shadow_mode = 0;
int m_light_cull_face = 0;
float m_aspect = 0.0f;
float m_fov = DEFAULT_FOV;
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat;
GLuint m_mv_loc;
GLuint m_proj_loc;
float m_delta_time = 0.0f;
GLuint m_sky_vbo;
GLuint m_text_vbo;
GLuint m_outline_indices_vbo;
GLuint m_outline_vbo;
GLuint m_ui_vbo;
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;
GLuint m_sky_vbo = 0;
GLuint m_text_vbo = 0;
GLuint m_outline_indices_vbo = 0;
GLuint m_outline_vbo = 0;
GLuint m_ui_vbo = 0;
GLuint m_fbo = 0;
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;
glm::vec3 m_blend_from_sundir;
glm::vec3 m_blend_to_sundir;
float m_blend_t = 1.0f;
bool m_blend_initialized = false;
static constexpr float BLEND_DURATION = 0.15f;
/*
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_quad();
void init_text();
void render_outline();
@@ -59,7 +119,13 @@ private:
void render_text();
void render_ui();
void render_world();
void render_underwater();
void render_dev_panel();
glm::vec3 quantize_sun_direction(const glm::vec3& sundir,
float angle_step_deg) const;
glm::vec3 get_smoothed_shadow_sundir(const glm::vec3& raw_shadow_sundir,
float dt);
};
} // namespace Cubed

View File

@@ -1,7 +1,7 @@
#pragma once
#include <glad/glad.h>
#include <string>
#include <unordered_map>
namespace Cubed {
class Shader {
@@ -26,6 +26,7 @@ private:
GLuint m_program = 0;
std::size_t m_hash = 0;
std::string m_name = "-1";
mutable std::unordered_map<std::string, GLint> m_uniform_cache;
};
} // namespace Cubed

View File

@@ -8,14 +8,24 @@ namespace Cubed {
class TextureManager {
private:
bool m_need_reload = false;
GLuint m_block_status_array;
GLuint m_texture_array;
GLuint m_ui_array;
GLuint m_block_status_array = 0;
GLuint m_texture_array = 0;
GLuint m_cross_plane_array = 0;
GLuint m_ui_array = 0;
GLfloat m_max_aniso = 0.0f;
int m_aniso = 1;
std::vector<GLuint> m_item_textures;
void load_block_status(unsigned status_id);
void load_block_texture(unsigned block_id);
void load_block_item_texture(unsigned id);
void load_cross_plane_texture(unsigned id);
void load_ui_texture(unsigned id);
void init_item();
void init_block();
void init_ui();
void init_block_status();
public:
TextureManager();
@@ -24,7 +34,9 @@ public:
void delet_texture();
GLuint get_block_status_array() const;
GLuint get_texture_array() const;
GLuint get_cross_plane_array() const;
GLuint get_ui_array() const;
const std::vector<GLuint>& item_textures() const;
// Must call after MapTable::init_map() and glfwMakeContextCurrent(window);
void init_texture();
void hot_reload();

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,6 +37,8 @@ inline uint32_t mix_hash(int32_t a, int32_t b, uint32_t fixed_seed) {
return h;
}
*/
} // namespace HASH
} // namespace Cubed

View File

@@ -5,12 +5,14 @@ namespace Cubed {
class Random {
public:
Random();
Random(unsigned seed);
bool random_bool(double probability);
std::mt19937& engine();
unsigned seed();
void init(unsigned seed);
int random_int(int min, int max);
float random_float(float min, float max);
private:
unsigned int m_seed = 0;

View File

@@ -4,8 +4,16 @@
namespace Cubed {
namespace Math {
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);
glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t);
} // namespace Math
} // namespace Cubed

View File

@@ -4,7 +4,7 @@
namespace Cubed {
class PerlinNoise {
class PerlinNoise3D {
public:
static void init(unsigned seed);
static float noise(float x, float y, float z);
@@ -18,4 +18,19 @@ private:
static float grad(int hash, float x, float y, float z);
};
class PerlinNoise2D {
public:
static void init(unsigned seed);
static float noise(float x, float y);
static void reload(unsigned seed);
private:
static inline std::atomic<bool> is_init = false;
static inline std::vector<int> p;
static float fade(float t);
static float lerp(float t, float a, float b);
static float grad(int hash, float x, float y);
};
} // namespace Cubed

View File

@@ -6,8 +6,10 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <psapi.h>
// clang-format off
#include <windows.h>
#include <psapi.h>
// clang-format on
typedef LONG(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
#elif defined(__linux__)
#include <fstream>

15
pyproject.toml Normal file
View File

@@ -0,0 +1,15 @@
[project]
name = "cubed"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"loguru>=0.7.3",
"pytomlpp>=1.1.0",
]
[dependency-groups]
dev = [
"ruff>=0.15.14",
]

403
scripts/blocks_tool.py Normal file
View File

@@ -0,0 +1,403 @@
import argparse
import copy
import sys
from functools import singledispatch
from pathlib import Path
from pprint import pprint
from typing import Any
import pytomlpp
from loguru import logger
VERSION = "0.0.1"
DATA_PATH = "assets/data/block"
TEXTURE_PATH = "assets/texture/block"
work_path = Path(__file__).parent.parent
data_path = work_path / DATA_PATH
texture_path = work_path / TEXTURE_PATH
def collect_blocks() -> list[dict[str, Any]]:
blocks: list[dict[str, Any]] = []
for block in data_path.rglob("*.toml"):
if not block.is_file():
continue
if block.name == "template.toml":
continue
blocks.append(pytomlpp.loads(block.read_text(encoding="utf-8")))
blocks.sort(key=lambda x: x["id"])
return blocks
def save_data(blocks: list[dict[str, Any]]):
for block in blocks:
block_path: Path = data_path / (block["name"] + ".toml")
if not block_path.is_file():
logger.warning(
f"Block: {block_path} is not Exists and Will Create A New One"
)
block_path.write_text(pytomlpp.dumps(block))
def sync_template_value():
blocks = collect_blocks()
template_path = data_path / "template.toml"
if not template_path.is_file():
logger.error("Template.toml is not Exists!")
return
template_block = pytomlpp.loads(template_path.read_text(encoding="utf-8"))
add_count = 0
for key, value in template_block.items():
for block in blocks:
if key not in block:
block[key] = value
add_count += 1
save_data(blocks)
logger.info(f"Synced {add_count} template fields to blocks")
@singledispatch
def show_data_info(arg: Any):
logger.error("No Match show_data_info")
@show_data_info.register(type(None))
def _(arg: None):
blocks = collect_blocks()
print("Please Input Block Name or Id, Input exit or e to Exit")
while True:
input_str = input("Name or Id: ")
try:
id = int(input_str)
if id >= len(blocks) or id < 0:
print(f"Id: {id} Not Find, Input e or exit to Exit")
continue
pprint(blocks[id])
except ValueError:
if input_str.lower() == "exit" or input_str.lower() == "e":
break
find = False
for block in blocks:
if block["name"] == input_str:
pprint(block)
find = True
break
if not find:
print(f"Name: {input_str} Not Find, Input e or exit to Exit")
@show_data_info.register(int)
def _(id: int):
blocks = collect_blocks()
if id >= len(blocks) or id < 0:
logger.error(f"ID: {id} is Not Invaild!")
return
pprint(blocks[id])
@show_data_info.register(str)
def _(name: str):
blocks = collect_blocks()
find = False
for block in blocks:
if block["name"] == name:
pprint(block)
find = True
break
if not find:
logger.error(f"Block Name: {name} Not Find")
def change_key(block: dict[str, Any], key: str, value: str):
if type(block[key]) is str:
block[key] = value
elif type(block[key]) is int:
try:
v = int(value)
block[key] = v
except ValueError:
logger.error("The Value Is Not A Int")
return False
elif type(block[key]) is bool:
if value.lower() == "true" or value.lower() == "t":
block[key] = True
elif value.lower() == "false" or value.lower() == "f":
block[key] = False
else:
logger.error("The Value Is Not A Bool")
return False
elif type(block[key]) is float:
try:
v = float(value)
block[key] = v
except ValueError:
logger.error("The Value Is Not A Float")
return False
else:
logger.error("Unkown Key Type")
return False
return True
def handle_change(block: dict[str, Any]) -> dict[str, Any]:
print("Please Input Block Key, Input exit or e to Exit")
while True:
key = input("Key: ")
if key.lower() == "exit" or key.lower() == "e":
break
if key not in block:
logger.error("The Key Is Not Exists!")
continue
value = input("Value: ")
old_name = block[key]
if change_key(block, key, value):
print("Change Success")
if key == "name":
old_path: Path = data_path / (old_name + ".toml")
try:
old_path.unlink()
except FileNotFoundError:
logger.warning(
f"Name Change But Old File {old_name}.toml is Not Exists!"
)
else:
print("Change Fail")
pprint(block)
return block
@singledispatch
def change_data(arg: Any):
logger.error("Not Match change")
@change_data.register(int)
def _(id: int):
blocks = collect_blocks()
if id >= len(blocks) or id < 0:
logger.error(f"ID: {id} is Invaild!")
return
pprint(blocks[id])
blocks[id] = handle_change(blocks[id])
save_data(blocks)
@change_data.register(str)
def _(name: str):
blocks = collect_blocks()
find = False
for i, block in enumerate(blocks):
if block["name"] == name:
pprint(block)
blocks[i] = handle_change(block)
save_data(blocks)
find = True
break
if not find:
logger.error(f"Block Name: {name} Not Find")
@change_data.register(type(None))
def _(arg: None):
blocks = collect_blocks()
print("Please Input Block Name or Id, Input exit or e to Exit")
while True:
input_str = input("Name or Id: ")
try:
id = int(input_str)
if id >= len(blocks) or id < 0:
print(f"Id: {id} Not Find, Input e or exit to Exit")
continue
pprint(blocks[id])
blocks[id] = handle_change(blocks[id])
save_data(blocks)
except ValueError:
if input_str.lower() == "exit" or input_str.lower() == "e":
break
find = False
for i, block in enumerate(blocks):
if block["name"] == input_str:
pprint(block)
blocks[i] = handle_change(block)
save_data(blocks)
find = True
break
if not find:
print(f"Name: {input_str} Not Find, Input e or exit to Exit")
def show_data_list():
blocks = collect_blocks()
for block in blocks:
print(f"id: {block['id']} name: {block['name']}")
def check_path():
logger.info(f"Work Path {work_path.resolve()}")
logger.info(f"Script Dir {sys.path[0]}")
data_exists = True
if not data_path.exists():
logger.error(f"Blocks Data Path {data_path} not Exists!")
data_exists = False
else:
logger.info(f"Blocks Data Path {data_path}")
texture_exists = True
if not texture_path.exists():
logger.error(f"Blocks Texture Path {texture_path} not Exists!")
texture_exists = False
else:
logger.info(f"Blocks Texture Path {texture_path}")
return data_exists and texture_exists
def check_integrity():
find_error = False
errors = 0
if check_path():
blocks = collect_blocks()
template_path = data_path / "template.toml"
if not template_path.is_file():
logger.error("Template.toml is not Exists!")
find_error = True
errors += 1
return
template_block = pytomlpp.loads(template_path.read_text(encoding="utf-8"))
n = len(blocks)
for i in range(n):
if "id" not in blocks[i]:
logger.error(f"Id: {i} not Exists!")
find_error = True
errors += 1
continue
if blocks[i]["id"] != i:
logger.error(
f"Id Error, Block {blocks[i].get('name', 'Unknow')} Id Should Be {i} Instead of {blocks[i]['id']}"
)
find_error = True
errors += 1
for key, value in template_block.items():
if key not in blocks[i]:
logger.error(
f"Key Error, Block {blocks[i].get('name', 'Unknow')} Key {key} not Exists!"
)
find_error = True
errors += 1
continue
if type(blocks[i][key]) is not type(value):
logger.error(
f"Value Type Error, Block {blocks[i].get(key, 'Unknow')} The Type Should Be {type(value)}, Instead of {type(blocks[i][key])}"
)
find_error = True
errors += 1
if find_error:
logger.error(f"Find {errors} Errors")
else:
logger.info("No Error")
def add_new_block():
blocks = collect_blocks()
template_path = data_path / "template.toml"
if not template_path.is_file():
logger.error("Template.toml is not Exists, Can't Create A New Block!")
return
template_block = pytomlpp.loads(template_path.read_text(encoding="utf-8"))
new_block = copy.deepcopy(template_block)
num = len(blocks)
logger.info(f"New Block Id is {num}")
new_block["id"] = num
for key in template_block:
if key == "id":
continue
nvalue = input(f"Input {key}: ")
if not change_key(new_block, key, nvalue):
logger.error(f"Add Key {key} Value {nvalue} Fail")
return
new_block_path: Path = data_path / (new_block["name"] + ".toml")
new_block_path.write_text(pytomlpp.dumps(new_block))
logger.info("Successfully Add New Block!")
pprint(new_block)
def handle_args(args: argparse.Namespace):
if args.version:
print(f"Blocks Tools: {VERSION}")
print(f"Python: {sys.version}")
if args.path:
check_path()
if args.list:
show_data_list()
if args.sync:
sync_template_value()
if args.info:
if args.info == "EMPTY":
show_data_info(None)
else:
try:
id = int(args.info)
show_data_info(id)
except ValueError:
show_data_info(args.info)
if args.change:
if args.change == "EMPTY":
change_data(None)
else:
try:
id = int(args.change)
change_data(id)
except ValueError:
change_data(args.change)
if args.check:
check_integrity()
if args.new:
add_new_block()
def init_parser(parser: argparse.ArgumentParser):
parser.add_argument(
"-v", "--version", action="store_true", help="Show Blocks Tools Version"
)
parser.add_argument(
"--path", action="store_true", help="Check Blcoks Data and Texture Path"
)
parser.add_argument("-l", "--list", action="store_true", help="Show Blocks List")
parser.add_argument(
"-s",
"--sync",
action="store_true",
help="Sync Template.toml Value to Other Toml, Only New Value Will Add",
)
parser.add_argument(
"-i",
"--info",
nargs="?",
const="EMPTY",
help="Show Block Data, If Provide Id Will Print the Corresponding Blcok Data, You Can Input Id or Name",
)
parser.add_argument(
"-c", "--change", nargs="?", const="EMPTY", help="Change Block Data"
)
parser.add_argument(
"-C", "--check", action="store_true", help="Check The Block Data Integrity"
)
parser.add_argument("-n", "--new", action="store_true", help="Add A New Block")
def main():
parser = argparse.ArgumentParser(description="Block Manage Tool")
init_parser(parser)
if len(sys.argv) == 1:
parser.print_help()
exit(0)
args = parser.parse_args()
handle_args(args)
if __name__ == "__main__":
main()

View File

@@ -51,7 +51,7 @@ void App::init() {
cursor_enter_callback);
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
ChunkGenerator::init();
BlockManager::init();
m_renderer.init();
Logger::info("Renderer Init Success");
m_window.update_viewport();
@@ -257,6 +257,7 @@ void App::update() {
m_renderer.update_fov(fov + 5.0f);
}
}
m_renderer.update(delta_time);
}
int App::start_cubed_application(int argc, char** argv) {

185
src/block.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "Cubed/gameplay/block.hpp"
#include "Cubed/config.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <filesystem>
#include <toml++/toml.hpp>
namespace fs = std::filesystem;
using namespace std::string_literals;
namespace {
std::string block_data_dir = ASSETS_PATH + "data/block"s;
template <Cubed::TomlValueType T>
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
const T& default_value) {
auto value = table[key].value<T>();
if (value == std::nullopt) {
Cubed::Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}",
key, default_value);
value = default_value;
}
return value;
}
} // namespace
namespace Cubed {
const std::vector<BlockData>& BlockManager::datas() {
ASSERT(is_init);
return m_datas;
}
unsigned BlockManager::sums() {
ASSERT(is_init);
return m_datas.size();
}
unsigned BlockManager::cross_plane_sum() {
ASSERT(is_init);
return m_cross_plane_map.size();
}
const std::string& BlockManager::name_form_id(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].name;
}
return m_datas[id].name;
}
bool BlockManager::is_gas(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_gas;
}
return m_datas[id].is_gas;
}
bool BlockManager::is_liquid(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_liquid;
}
return m_datas[id].is_liquid;
}
bool BlockManager::is_cross_plane(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_cross_plane;
}
return m_datas[id].is_cross_plane;
}
bool BlockManager::is_transparent(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_transparent;
}
return m_datas[id].is_transparent;
}
bool BlockManager::is_passable(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_passable;
}
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};
for (auto entry : fs::recursive_directory_iterator(data_path)) {
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().filename() == "template.toml") {
continue;
}
toml::table block;
try {
block = toml::parse_file(entry.path().string());
} catch (const toml::parse_error& err) {
Logger::error("Load Block Data {} Fail, Parser Error {}",
entry.path().string(), err.what());
ASSERT(false);
}
auto id = block["id"].value<int>();
if (id == std::nullopt) {
Logger::error("Very Serious Error, Block Id Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto name = block["name"].value<std::string>();
if (name == std::nullopt) {
Logger::error("Very Serious Error, Block Name Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto is_liquid = safe_get_value(block, "is_liquid", false);
auto is_passable = safe_get_value(block, "is_passable", false);
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_discard, *is_blend, *is_transitional);
}
std::sort(
m_datas.begin(), m_datas.end(),
[](const BlockData& a, const BlockData& b) { return a.id < b.id; });
set_up_cross_plane_map();
is_init = true;
}
BlockType BlockManager::cross_plane_index(BlockType id) {
auto it = m_cross_plane_map.find(id);
if (it == m_cross_plane_map.end()) {
Logger::error("Can't Find Cross Plane Id {}", id);
ASSERT(false);
throw std::out_of_range{"Can't Find Cross Plane Id" +
std::to_string(id)};
}
return it->second;
}
void BlockManager::set_up_cross_plane_map() {
unsigned cur_id = 0;
for (const auto& data : m_datas) {
if (data.is_cross_plane) {
m_cross_plane_map[data.id] = cur_id;
cur_id++;
}
}
}
} // namespace Cubed

View File

@@ -1,6 +1,7 @@
#include "Cubed/camera.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
namespace Cubed {
@@ -12,6 +13,13 @@ void Camera::update_move_camera() {
auto pos = m_player->get_player_pos();
// pos.y need to add 1.6f to center
m_camera_pos = glm::vec3(pos.x, pos.y + 1.6f, pos.z);
glm::ivec3 block_pos = glm::floor(m_camera_pos);
auto& world = m_player->get_world();
if (world.get_block_tpye(block_pos) == 7) {
m_under_water = true;
} else {
m_under_water = false;
}
}
void Camera::camera_init(Player* player) {
@@ -50,4 +58,8 @@ const glm::mat4 Camera::get_camera_lookat() const {
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

@@ -33,6 +33,17 @@ constexpr int AMPLITUDE_MAX = 80;
constexpr float TREE_FREQ_MIM = 0.001f;
constexpr float TREE_FREQ_MAX = 0.3f;
constexpr float PATH_PROBABILITY_MIN = 0.005f;
constexpr float PATH_PROBABILITY_MAX = 0.1f;
constexpr float RADIUS_XZ_MIN = 1.0f;
constexpr float RADIUS_XZ_MAX = 50.0f;
constexpr float RADIUS_Y_MIN = 1.0f;
constexpr float RADIUS_Y_MAX = 50.0f;
constexpr float DELTA_ANGLE_MIN = -30.0f;
constexpr float DELTA_ANGLE_MAX = 30.0f;
constexpr int PATH_STEP_MIN = 1;
constexpr int PATH_STEP_MAX = 1000;
static int filter_unsigned(ImGuiInputTextCallbackData* data) {
if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) {
char c = data->EventChar;
@@ -72,7 +83,10 @@ void DevPanel::render() {
show_settings_tab_item();
show_world_tab_item();
show_player_tab_item();
show_items_tab_item();
show_shader_tab_item();
show_about_table_bar();
ImGui::EndTabBar();
}
ImGui::End();
@@ -94,6 +108,7 @@ void DevPanel::show_about_table_bar() {
ImGui::Text("FreeType");
ImGui::Text("toml++");
ImGui::Text("Dear ImGui");
ImGui::Text("Tbb");
ImGui::Separator();
ImGui::Text("Special Thanks");
ImGui::Text("TANGERIME");
@@ -105,7 +120,7 @@ void DevPanel::show_about_table_bar() {
}
void DevPanel::show_biome_table_bar() {
ImGui::Text("Biome");
if (ImGui::BeginTabBar("Biome")) {
if (ImGui::BeginTabItem("Plain")) {
ImGui::SliderFloat("MinTemp##plain", &plain_params().temp.first,
@@ -220,10 +235,109 @@ void DevPanel::show_biome_table_bar() {
AMPLITUDE_MIN, AMPLITUDE_MAX);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("River")) {
ImGui::SliderFloat("MinTemp##river", &river_params().temp.first,
TEMP_MIN, TEMP_MAX);
ImGui::SliderFloat("MaxTemp##river", &river_params().temp.second,
TEMP_MIN, TEMP_MAX);
ImGui::SliderFloat("MinHumid##river", &river_params().humid.first,
HUMID_MIN, HUMID_MAX);
ImGui::SliderFloat("MaxHumid##river", &river_params().humid.second,
HUMID_MIN, HUMID_MAX);
ImGui::SliderFloat("Freq One##river",
&river_params().frequencies[0], FREQ1_MIN,
FREQ1_MAX);
ImGui::SliderFloat("Freq Two##river",
&river_params().frequencies[1], FREQ2_MIN,
FREQ2_MAX);
ImGui::SliderFloat("Freq Three##river",
&river_params().frequencies[2], FREQ3_MIN,
FREQ3_MAX);
ImGui::SliderInt("Base Y##river",
&river_params().height_range.base_y,
HEIGHT_BASE_MIN, HEIGHT_BASE_MAX);
ImGui::SliderInt("Amplitude##river",
&river_params().height_range.amplitude,
AMPLITUDE_MIN, AMPLITUDE_MAX);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void DevPanel::show_time_table_bar() {
World& world = m_app.world();
ImGui::Text("Game Tick %llu", world.game_tick());
ImGui::SameLine();
ImGui::Text("Day Tick %llu", world.day_tick());
m_tick_frezze = !world.is_tick_running();
ImGui::SameLine();
if (ImGui::Checkbox("Tick Frezze", &m_tick_frezze)) {
world.tick_running(!m_tick_frezze);
}
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();
ImGui::Text("Total Cave Sum %d", cave_carcer.cave_sum());
ImGui::SliderFloat("Cave Probability", &cave_carcer.cave_probability(),
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
ImGui::SliderFloat("Radius XZ Min", &CavePath::radius_xz_min(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius XZ Max", &CavePath::radius_xz_max(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius Y Min", &CavePath::radius_y_min(), RADIUS_Y_MIN,
RADIUS_Y_MAX);
ImGui::SliderFloat("Radius Y Max", &CavePath::radius_y_max(), RADIUS_Y_MIN,
RADIUS_Y_MAX);
ImGui::SliderFloat("Delta Angle Min", &CavePath::delta_angle_min(),
DELTA_ANGLE_MIN, 0.0f);
ImGui::SliderFloat("Delta Angle Max", &CavePath::delta_angle_max(), 0.0f,
DELTA_ANGLE_MAX);
ImGui::SliderInt("Step Min", &CavePath::step_min(), PATH_STEP_MIN,
PATH_STEP_MAX);
ImGui::SliderInt("Step Max", &CavePath::step_max(), PATH_STEP_MIN,
PATH_STEP_MAX);
}
void DevPanel::show_river_table_bar() {
auto& river_wrom = m_app.world().river_worm();
ImGui::Text("Total River Sum %d", river_wrom.river_sum());
ImGui::SliderFloat("River Probability", &river_wrom.river_probability(),
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
ImGui::SliderFloat("Radius XZ Min##river", &RiverPath::radius_xz_min(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius XZ Max##river", &RiverPath::radius_xz_max(),
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
ImGui::SliderFloat("Radius Y Min##river", &RiverPath::radius_y_min(),
RADIUS_Y_MIN, RADIUS_Y_MAX);
ImGui::SliderFloat("Radius Y Max##river", &RiverPath::radius_y_max(),
RADIUS_Y_MIN, RADIUS_Y_MAX);
ImGui::SliderFloat("Delta Angle Min##river", &RiverPath::delta_angle_min(),
DELTA_ANGLE_MIN, 0.0f);
ImGui::SliderFloat("Delta Angle Max##river", &RiverPath::delta_angle_max(),
0.0f, DELTA_ANGLE_MAX);
ImGui::SliderInt("Step Min##river", &RiverPath::step_min(), PATH_STEP_MIN,
PATH_STEP_MAX);
ImGui::SliderInt("Step Max##river", &RiverPath::step_max(), PATH_STEP_MIN,
PATH_STEP_MAX);
}
void DevPanel::show_settings_tab_item() {
if (ImGui::BeginTabItem("settings")) {
if (ImGui::SliderFloat("FOV", &m_config.fov, 1.0f, 140.0f)) {
@@ -249,6 +363,7 @@ void DevPanel::show_settings_tab_item() {
static_cast<double>(m_config.mouse_sensitivity));
m_player->hot_reload();
}
if (ImGui::SliderInt("Distance", &m_config.rendering_distance, 2,
128)) {
Config::get().set("world.rendering_distance",
@@ -325,7 +440,8 @@ void DevPanel::show_settings_tab_item() {
void DevPanel::show_world_tab_item() {
if (ImGui::BeginTabItem("world")) {
if (m_text_editing.perlin_seed) {
if (ImGui::InputText("Perlin Noise Seed", perlin_noise_input_buffer,
if (ImGui::InputText("ChunkGenerator Seed",
perlin_noise_input_buffer,
sizeof(perlin_noise_input_buffer),
ImGuiInputTextFlags_CallbackCharFilter |
ImGuiInputTextFlags_EnterReturnsTrue,
@@ -358,11 +474,37 @@ void DevPanel::show_world_tab_item() {
if (ImGui::Button("Spawn Point")) {
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
}
ImGui::SameLine();
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
if (m_gen_thread_running) {
m_app.world().start_gen_thread();
} else {
m_app.world().stop_gen_thread();
}
}
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();
}
if (ImGui::BeginTabItem("River")) {
show_river_table_bar();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Biome")) {
show_biome_table_bar();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::EndTabItem();
}
}
void DevPanel::show_player_tab_item() {
@@ -437,6 +579,57 @@ void DevPanel::show_player_tab_item() {
ImGui::EndTabItem();
}
}
void DevPanel::show_items_tab_item() {
auto& textures = m_app.texture_manager().item_textures();
// auto& names = MapTable::item_map();
if (ImGui::BeginTabItem("item")) {
ImGui::Text("Place Block ");
ImGui::SameLine();
ImGui::Image(static_cast<ImTextureID>(static_cast<intptr_t>(
textures[m_player->place_block()])),
ImVec2{48, 48});
for (size_t i = 1; i < textures.size(); i++) {
if (ImGui::ImageButton(("##item" + std::to_string(i)).c_str(),
static_cast<ImTextureID>(
static_cast<intptr_t>(textures[i])),
ImVec2{48, 48})) {
m_player->set_place_block(i);
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("%s", BlockManager::name_form_id(i).c_str());
ImGui::EndTooltip();
}
if (i % 10 != 0) {
ImGui::SameLine();
}
}
ImGui::EndTabItem();
}
}
void DevPanel::show_shader_tab_item() {
static const char* shader_mode[] = {"Rotated Poisson Disk PCF",
"3x3 Square Grid PCF", "PCF off"};
static const char* cull_face_mode[] = {"Front", "Back"};
if (ImGui::BeginTabItem("shader")) {
ImGui::Checkbox("Shader", &m_app.renderer().shader_on());
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))
;
ImGui::Checkbox("Discard Transparent",
&m_app.renderer().discard_transparent());
ImGui::Combo("ShaderMode", &m_app.renderer().shadow_mode(), shader_mode,
IM_ARRAYSIZE(shader_mode));
ImGui::Combo("LightCullFaceMode", &m_app.renderer().light_cull_face(),
cull_face_mode, IM_ARRAYSIZE(cull_face_mode));
ImGui::EndTabItem();
}
}
void DevPanel::update_config_view() {
auto config = Config::get();
m_config.fov =

View File

@@ -1,44 +1,49 @@
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/perlin_noise.hpp"
#include <cmath>
#include <unordered_map>
namespace Cubed {
static PlainParams plain{{Biome::PLAIN,
using enum BiomeType;
static PlainParams plain{{BiomeType::PLAIN,
{0.0f, 0.5f},
{0.0f, 0.5f},
{0.003f, 0.010f, 0.020f},
{62, 8}}};
static ForestParams forest{{Biome::FOREST,
static ForestParams forest{{BiomeType::FOREST,
{0.5f, 1.0f},
{0.5f, 1.0f},
{0.004f, 0.010f, 0.020f},
{62, 12}},
{62, 8}},
0.1f
};
static DesertParams desert{{Biome::DESERT,
static DesertParams desert{{BiomeType::DESERT,
{0.5f, 1.0f},
{0.0f, 0.5f},
{0.003f, 0.010f, 0.020f},
{61, 12}}};
static MountainParams mountain{{Biome::MOUNTAIN,
static MountainParams mountain{{BiomeType::MOUNTAIN,
{0.0f, 0.5f},
{0.5f, 1.0f},
{0.006f, 0.014f, 0.010f},
{70, 70}}};
std::string get_biome_str(Biome biome) {
static RiverParams river{{BiomeType::RIVER,
{-0.1f, -0.1f},
{-0.1f, -0.1f},
{0.003f, 0.010f, 0.020f},
{50, 6}}};
std::string get_biome_str(BiomeType biome) {
std::string str;
using enum Biome;
using enum BiomeType;
switch (biome) {
case PLAIN:
str = "Plain";
@@ -52,6 +57,15 @@ std::string get_biome_str(Biome biome) {
case MOUNTAIN:
str = "Mountain";
break;
case RIVER:
str = "River";
break;
case SNOWY_PLAIN:
str = "Snowy Plain";
break;
case OCEAN:
str = "Ocean";
break;
case NONE:
str = "Unknown";
break;
@@ -77,29 +91,9 @@ Biome get_biome_from_noise(float temp, float humid) {
return Biome::FOREST;
}
*/
Biome get_biome_from_noise(float temp, float humid) {
using enum Biome;
if (plain.temp.first <= temp && temp < plain.temp.second &&
plain.humid.first <= humid && humid < plain.humid.second) {
return PLAIN;
}
if (forest.temp.first <= temp && temp < forest.temp.second &&
forest.humid.first <= humid && humid < forest.humid.second) {
return FOREST;
}
if (desert.temp.first <= temp && temp < desert.temp.second &&
desert.humid.first <= humid && humid < desert.humid.second) {
return DESERT;
}
if (mountain.temp.first <= temp && temp <= mountain.temp.second &&
mountain.humid.first <= humid && humid <= mountain.humid.second) {
return MOUNTAIN;
}
Logger::warn("Invail Temp {} or Humid {}", temp, humid);
return PLAIN;
}
std::array<float, 3> get_noise_frequencies_for_biome(Biome biome) {
using enum Biome;
/*
std::array<float, 3> get_noise_frequencies_for_biome(BiomeType biome) {
using enum BiomeType;
switch (biome) {
case PLAIN:
return plain.frequencies;
@@ -109,6 +103,8 @@ std::array<float, 3> get_noise_frequencies_for_biome(Biome biome) {
return desert.frequencies;
case MOUNTAIN:
return mountain.frequencies;
case RIVER:
return river.frequencies;
case NONE:
ASSERT_MSG(false, "Chunk Biome is None");
throw std::invalid_argument{"Chunk Biome is None"};
@@ -116,9 +112,10 @@ std::array<float, 3> get_noise_frequencies_for_biome(Biome biome) {
Logger::warn("Unknown Biome");
return {0.003f, 0.015f, 0.06f};
}
BiomeHeightRange get_biome_height_range(Biome biome) {
using enum Biome;
*/
/*
BiomeHeightRange get_biome_height_range(BiomeType biome) {
using enum BiomeType;
switch (biome) {
case PLAIN:
return plain.height_range;
@@ -128,6 +125,8 @@ BiomeHeightRange get_biome_height_range(Biome biome) {
return desert.height_range;
case MOUNTAIN:
return mountain.height_range;
case RIVER:
return river.height_range;
case NONE:
ASSERT_MSG(false, "Chunk Biome is None");
throw std::invalid_argument{"Chunk Biome is None"};
@@ -135,17 +134,17 @@ BiomeHeightRange get_biome_height_range(Biome biome) {
Logger::warn("Unknown Biome");
return {62, 4};
}
Biome safe_int_to_biome(int x) {
using enum Biome;
static const std::unordered_map<int, Biome> INT_TO_BIOME_MAP{
{0, PLAIN}, {1, FOREST}, {2, DESERT}, {3, MOUNTAIN}};
*/
BiomeType safe_int_to_biome(int x) {
using enum BiomeType;
static const std::unordered_map<int, BiomeType> INT_TO_BIOME_MAP{
{0, PLAIN}, {1, FOREST}, {2, DESERT}, {3, MOUNTAIN}, {4, RIVER}};
auto it = INT_TO_BIOME_MAP.find(x);
ASSERT_MSG(it != INT_TO_BIOME_MAP.end(), ":Can't Find");
return it->second;
}
/*
int get_interpolated_height(float world_x, float world_z, float temp,
float humid) {
@@ -173,7 +172,7 @@ int get_interpolated_height(float world_x, float world_z, float temp,
w_desert /= total;
w_forest /= total;
auto sample_height = [&](Biome b) -> float {
auto sample_height = [&](BiomeType b) -> float {
auto range = get_biome_height_range(b);
auto [f1, f2, f3] = get_noise_frequencies_for_biome(b);
float n = 1.00f * PerlinNoise::noise(world_x * f1, 0.5f, world_z * f1) +
@@ -183,16 +182,43 @@ int get_interpolated_height(float world_x, float world_z, float temp,
return range.base_y + n * range.amplitude;
};
float h = w_mountain * sample_height(Biome::MOUNTAIN) +
w_plain * sample_height(Biome::PLAIN) +
w_desert * sample_height(Biome::DESERT) +
w_forest * sample_height(Biome::FOREST);
float h = w_mountain * sample_height(BiomeType::MOUNTAIN) +
w_plain * sample_height(BiomeType::PLAIN) +
w_desert * sample_height(BiomeType::DESERT) +
w_forest * sample_height(BiomeType::FOREST);
return static_cast<int>(h);
}
*/
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) {
if (humid < 0.5) {
return SNOWY_PLAIN;
} else {
return PLAIN;
}
} else {
if (humid < 0.5) {
return DESERT;
} else {
return FOREST;
}
}
return PLAIN;
}
PlainParams& plain_params() { return plain; }
ForestParams& forest_params() { return forest; }
DesertParams& desert_params() { return desert; }
MountainParams& mountain_params() { return mountain; }
RiverParams& river_params() { return river; }
} // namespace Cubed

View File

@@ -0,0 +1,61 @@
#include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
namespace Cubed {
void BiomeBuilder::build_bottom() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
auto& m_blocks = chunk.blocks();
for (int x = 0; x < CHUNK_SIZE; x++) {
for (int y = 0; y < 5; y++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
m_blocks[Chunk::index(x, y, z)] = 3;
}
}
}
}
void BiomeBuilder::place_grass() {
ChunkGenerator& chunk_generator = get_chunk_generator();
Chunk& chunk = chunk_generator.chunk();
auto& blocks = chunk.blocks();
const auto& heightmap = chunk.get_heightmap();
auto& random = chunk_generator.random();
for (int x = 0; x < SIZE_X; ++x) {
for (int z = 0; z < SIZE_Z; ++z) {
int y = heightmap[x][z];
BlockType top_id = blocks[Chunk::index(x, y, z)];
if (top_id != 1) {
continue;
}
if (blocks[Chunk::index(x, y + 1, z)] != 0) {
continue;
}
if (random.random_bool(0.2)) {
if (y + 1 < SIZE_Y) {
blocks[Chunk::index(x, y + 1, z)] = 9;
}
}
}
}
}
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,38 @@
#include "Cubed/gameplay/builders/desert_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
namespace Cubed {
DesertBuilder::DesertBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
void DesertBuilder::build_biome() {
BiomeBuilder::build_bottom();
build_blocks();
};
void DesertBuilder::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 - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y <= height; y++) {
m_blocks[Chunk::index(x, y, z)] = 4;
}
}
}
}
void DesertBuilder::build_vegetation() {}
ChunkGenerator& DesertBuilder::get_chunk_generator() {
return m_chunk_generator;
};
} // namespace Cubed

View File

@@ -0,0 +1,63 @@
#include "Cubed/gameplay/builders/forest_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/tree.hpp"
#include <algorithm>
#include <numeric>
namespace Cubed {
ForestBuilder::ForestBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
void ForestBuilder::build_biome() {
BiomeBuilder::build_bottom();
build_blocks();
};
void ForestBuilder::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 - 5; y++) {
m_blocks[Chunk::index(x, y, z)] = 3;
}
for (int y = height - 5; y < height; y++) {
m_blocks[Chunk::index(x, y, z)] = 2;
}
m_blocks[Chunk::index(x, height, z)] = 1;
}
}
}
void ForestBuilder::build_vegetation() {
auto& m_chunk = m_chunk_generator.chunk();
auto& m_heightmap = m_chunk.heightmap();
auto& m_random = m_chunk_generator.random();
std::array<int, SIZE_X> x_arr;
std::iota(x_arr.begin(), x_arr.end(), 0);
std::shuffle(x_arr.begin(), x_arr.end(), m_random.engine());
std::array<int, SIZE_Z> z_arr;
std::iota(z_arr.begin(), z_arr.end(), 0);
std::shuffle(z_arr.begin(), z_arr.end(), m_random.engine());
for (auto x : x_arr) {
for (auto z : z_arr) {
int y = static_cast<int>(m_heightmap[x][z]);
if (m_random.random_bool(forest_params().tree_frequency) &&
y >= SEA_LEVEL) {
build_tree(m_chunk, {x, y, z});
}
}
}
place_grass();
}
ChunkGenerator& ForestBuilder::get_chunk_generator() {
return m_chunk_generator;
};
} // namespace Cubed

View File

@@ -0,0 +1,34 @@
#include "Cubed/gameplay/builders/mountain_builder.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
namespace Cubed {
MountainBuilder::MountainBuilder(ChunkGenerator& chunk_generator)
: m_chunk_generator(chunk_generator) {}
void MountainBuilder::build_biome() {
BiomeBuilder::build_bottom();
build_blocks();
};
void MountainBuilder::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 MountainBuilder::build_vegetation() {}
ChunkGenerator& MountainBuilder::get_chunk_generator() {
return m_chunk_generator;
};
} // namespace Cubed

Some files were not shown because too many files have changed in this diff Show More