15 Commits

Author SHA1 Message Date
35f0271fd4 fix: include shared_mutex header 2026-06-21 19:45:55 +08:00
4c4cc515a4 refactor(world): add thread pool start/stop and auto-detect threads
Extract thread pool starting and stopping into dedicated methods. Set default pool threads to 0 to enable automatic detection of available cores, and ensure thread pool is properly managed during world rebuild.
2026-06-21 18:39:40 +08:00
943013b83d feat(player): add configurable fly Y speed 2026-06-21 17:53:23 +08:00
47f13ca995 feat(world): add ChunkLoadStyle enum and rename chunk_pos to get_chunk_pos 2026-06-21 17:42:59 +08:00
4f6c5303ec feat(world): add thread pool size management and UI controls 2026-06-21 16:59:11 +08:00
34d9439466 feat(world): add dynamic thread pool resizing 2026-06-21 16:36:15 +08:00
2386d98217 refactor: remove unused uniforms and set cameraPos uniform outside lambda 2026-06-21 16:04:44 +08:00
790f4a5aa4 fix: add thread safety for cave and river path mutexes 2026-06-21 15:56:58 +08:00
9a7fe1bfe9 feat(gameplay): add temporary chunk flag to prevent path clearing 2026-06-21 15:00:50 +08:00
8929af888a fix(renderer): correct shader uniform name and remove unused uniform 2026-06-20 22:08:16 +08:00
a72b0dd677 feat(world): integrate thread pool and async chunk generation 2026-06-20 21:57:27 +08:00
4b617612e8 fix(gameplay): use gen_phase_one to get seed 2026-06-20 17:12:48 +08:00
5cfd663566 refactor(gameplay): move chunk generation phases into gen_chunk method
Consolidate multiple phase generation calls into a single gen_chunk() method on Chunk, which handles neighbor generation and ensures thread safety. Simplify World::gen_chunks_internal by using gen_chunk() instead of manual phase orchestration.
2026-06-20 17:11:05 +08:00
d69e1895d4 refactor(gameplay): remove dead code and simplify chunk neighbor context
Remove the large commented-out `init_chunks()` function, and eliminate the `affected_neighbor` tracking in `gen_chunks_internal()`. This simplifies the neighbor context building and removes unused vertex data regeneration for affected neighbors.
2026-06-20 16:38:36 +08:00
be17846c16 feat(renderer): add fog effect based on render distance 2026-06-20 15:49:00 +08:00
924 changed files with 5709 additions and 251569 deletions

View File

@@ -1,3 +1,4 @@
# .github/workflows/format-check.yml
name: Code Format Check
on: [push, pull_request]
@@ -8,22 +9,9 @@ permissions:
jobs:
formatting:
runs-on: ubuntu-latest
container: silkeh/clang:latest
steps:
- uses: actions/checkout@v4
- name: Install LLVM 22
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 22
sudo apt-get install -y clang-format-22
clang-format-22 --version
- name: Run clang-format
run: |
find src include \
\( -name '*.cpp' -o -name '*.hpp' \) \
-print0 | xargs -0 clang-format-22 --dry-run --Werror
find src include -name '*.cpp' -o -name '*.h' -print0 | xargs -0 clang-format --dry-run --Werror

View File

@@ -1,154 +0,0 @@
name: Release-Build
on:
workflow_dispatch:
inputs:
version:
description: Version
required: false
default: dev
push:
tags:
- "v*"
permissions:
contents: write
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: windows-2022
platform: windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Get Version
id: version
shell: bash
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME#v}"
else
VERSION="${{ github.event.inputs.version }}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Setup MSVC
if: matrix.platform == 'windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Bootstrap vcpkg
run: |
git clone https://github.com/microsoft/vcpkg.git
./vcpkg/bootstrap-vcpkg.bat
- name: Configure
if: matrix.platform == 'windows'
run: >
cmake
-B build
-G Ninja
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake
-DVCPKG_TARGET_TRIPLET=x64-windows-release
-DVCPKG_BUILD_TYPE=release
-DBUILD_TESTING=OFF
"-DCUBED_VERSION=${{ steps.version.outputs.version }}"
- name: Build
run: cmake --build build --config Release --target Cubed
- name: Copy assets
if: matrix.platform == 'windows'
shell: pwsh
run: |
if (Test-Path assets) {
Copy-Item -Path assets -Destination build/Cubed -Recurse
} else {
Write-Warning "assets folder not found, skipping copy"
}
- name: Create Zip
if: matrix.platform == 'windows'
shell: pwsh
run: |
Compress-Archive `
-Path build/Cubed/* `
-DestinationPath Cubed-${{ steps.version.outputs.version }}-windows-x64.zip
####################################################################
# Upload
####################################################################
- name: Upload Windows Artifacts
if: matrix.platform == 'windows'
uses: actions/upload-artifact@v4
with:
name: windows
path: |
*.zip
##########################################################################
# Release
##########################################################################
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Download Artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Generate SHA256
run: |
cd artifacts
find . -type f | while read file
do
sha256sum "$file"
done > SHA256SUMS
- name: Determine prerelease
id: prerelease
run: |
MAIN_BRANCH="${{ github.event.repository.default_branch }}"
git fetch origin "$MAIN_BRANCH" --depth=1 2>/dev/null || true
if git merge-base --is-ancestor "${{ github.sha }}" "origin/$MAIN_BRANCH"; then
echo "is_prerelease=false" >> $GITHUB_OUTPUT
echo "is_prerelease=false"
else
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "is_prerelease=true"
fi
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/**/*
artifacts/SHA256SUMS
generate_release_notes: true
draft: false
prerelease: ${{ steps.prerelease.outputs.is_prerelease }}

2
.gitignore vendored
View File

@@ -41,7 +41,5 @@ CMakeError.log
*~
.DS_Store
assets/config.toml
assets/server-config.toml
.venv/
pyout/
vcpkg_installed/

View File

@@ -1,9 +1,4 @@
cmake_minimum_required(VERSION 3.21)
if(APPLE)
message(FATAL_ERROR "This game does not support macOS")
endif()
cmake_minimum_required(VERSION 3.14...3.24)
project(Cubed LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
@@ -11,120 +6,167 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_CONFIGURATION_TYPES
AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()
if(NOT DEFINED CUBED_VERSION)
set(CUBED_VERSION "dev")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
if(MSVC)
add_compile_options(/utf-8)
endif()
find_package(OpenGL REQUIRED)
if (UNIX AND NOT APPLE)
find_package(Freetype REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(EGL REQUIRED egl)
pkg_check_modules(Wayland REQUIRED wayland-client wayland-egl)
find_package(glfw3 REQUIRED)
endif()
add_library(glad STATIC third_party/glad/src/glad.c)
target_include_directories(glad PUBLIC third_party/glad/include)
include(FetchContent)
if (WIN32)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE)
set(GLFW_STATIC ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(glfw)
FetchContent_Declare(
freetype
GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git
GIT_TAG VER-2-14-3
)
FetchContent_MakeAvailable(freetype)
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(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 1.0.3
)
FetchContent_MakeAvailable(glm)
FetchContent_Declare(
soil2
GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git
GIT_TAG 1.31
include(Dependencies)
add_subdirectory(third_party/glad)
)
FetchContent_MakeAvailable(soil2)
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG v3.4.0
)
FetchContent_MakeAvailable(tomlplusplus)
add_subdirectory(third_party/imgui)
add_subdirectory(third_party/dr_libs)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
add_executable(${PROJECT_NAME})
add_subdirectory(src)
file(GLOB_RECURSE PROTO_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto
add_executable(${PROJECT_NAME}
src/main.cpp
src/app.cpp
src/debug_collector.cpp
src/camera.cpp
src/config.cpp
src/dev_panel.cpp
src/gameplay/biome.cpp
src/gameplay/chunk.cpp
src/gameplay/chunk_generator.cpp
src/gameplay/player.cpp
src/gameplay/tree.cpp
src/gameplay/world.cpp
src/input.cpp
src/map_table.cpp
src/renderer.cpp
src/shader.cpp
src/texture_manager.cpp
src/tools/cubed_random.cpp
src/tools/math_tools.cpp
src/tools/shader_tools.cpp
src/tools/font.cpp
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
)
protobuf_generate(
TARGET ${PROJECT_NAME}
LANGUAGE cpp
PROTOS ${PROTO_FILES}
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
configure_file(
src/version.hpp.in
${CMAKE_BINARY_DIR}/generated/version.hpp
@ONLY
)
target_compile_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
-fno-omit-frame-pointer
-g
)
target_compile_options(${PROJECT_NAME}
PRIVATE
$<$<CONFIG:Debug>:-fno-omit-frame-pointer>
$<$<CONFIG:Debug>:-g>
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
target_link_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
)
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wextra>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
target_link_options(${PROJECT_NAME}
PRIVATE
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASIO_STANDALONE
ASIO_NO_DEPRECATED
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CXX_COMPILER_ID:MSVC>:
WIN32_LEAN_AND_MEAN
NOMINMAX
_CRT_SECURE_NO_WARNINGS
>
)
if(CMAKE_CONFIGURATION_TYPES)
# Visual Studio / Xcode multi-configuration generator
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASSETS_PATH="$<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/assets/>$<$<NOT:$<CONFIG:Debug>>:./assets/>"
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_MODE)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/"
)
else()
# Ninja / Makefiles single-configuration generator
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASSETS_PATH="${PROJECT_SOURCE_DIR}/assets/"
)
else()
target_compile_definitions(${PROJECT_NAME}
PRIVATE
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="./assets/"
)
endif()
endif()
target_include_directories(${PROJECT_NAME}
PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/third_party/asio/include
${CMAKE_BINARY_DIR}/generated
${PROJECT_BINARY_DIR}
${PROJECT_BINARY_DIR}/src
)
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME}
PRIVATE
@@ -136,28 +178,43 @@ target_link_libraries(${PROJECT_NAME}
Freetype::Freetype
tomlplusplus::tomlplusplus
imgui
dr_libs
tbb
protobuf::libprotobuf
absl::log
absl::check
absl::base
absl::strings
absl::flat_hash_map
zstd::zstd
OpenAL::OpenAL
harfbuzz::harfbuzz
$<$<PLATFORM_ID:Windows>:ws2_32>
)
if(WIN32)
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND_EXPAND_LISTS
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# target_link_libraries(${PROJECT_NAME} PRIVATE tbb)
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(${PROJECT_NAME}
PRIVATE
${EGL_LIBRARIES}
${Wayland_LIBRARIES}
)
target_include_directories(${PROJECT_NAME}
PRIVATE
${EGL_INCLUDE_DIRS}
${Wayland_INCLUDE_DIRS}
)
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()

Binary file not shown.

View File

@@ -1,90 +0,0 @@
The SIL Open Font License version 1.1 is copied below, and is also
available with a FAQ at http://scripts.sil.org/OFL.
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -1,34 +0,0 @@
{
"menu.settings": "Settings",
"menu.main.host_game": "Host Game",
"menu.main.join_game": "Join Game",
"menu.main.credits": "Credits",
"menu.main.quit": "Quit",
"menu.main.player_name": "Player: {name}",
"menu.main.cubed_version": "Cubed: {version}",
"menu.pause.pause_menu": "Pause Menu",
"menu.pause.back_to_game": "Back To Game",
"menu.pause.return_to_main_menu": "Return to Menu",
"button.done": "Done",
"button.return": "Return",
"settings.title": "Settings",
"settings.fov": "Fov: {fov}",
"settings.mouse_sensitivity": "Sensitivity: {sensitivity}",
"settings.distance": "Distance: {distance}",
"settings.music_volume": "Music: {music}",
"settings.sfx_volume": "SFX: {sfx}",
"settings.fullscreen": "fullscreen: {state}",
"settings.aniso": "Aniso: {aniso}",
"settings.vsync": "V-Sync: {state}",
"settings.language": "Language: {lang}",
"settings.restart_game_info": "You need to restart the game to apply this changes.",
"common.on": "on",
"common.off": "off",
"hostgame.create_a_new_world": "Create A New World",
"hostgame.world_seed": "World Seed",
"hostgame.port": "Port",
"hostgame.create_world": "Create World",
"joingame.join_a_world": "Join A World",
"joingame.server_ip": "Server Ip",
"joingame.join_world": "Join World"
}

View File

@@ -1,34 +0,0 @@
{
"menu.settings": "设置",
"menu.main.host_game": "创建游戏",
"menu.main.join_game": "加入游戏",
"menu.main.credits": "制作人员",
"menu.main.quit": "退出",
"menu.main.player_name": "玩家:{name}",
"menu.main.cubed_version": "Cubed{version}",
"menu.pause.pause_menu": "暂停菜单",
"menu.pause.back_to_game": "返回游戏",
"menu.pause.return_to_main_menu": "返回主菜单",
"button.done": "完成",
"button.return": "返回",
"settings.title": "设置",
"settings.fov": "视野:{fov}",
"settings.mouse_sensitivity": "灵敏度:{sensitivity}",
"settings.distance": "距离:{distance}",
"settings.music_volume": "音乐:{music}",
"settings.sfx_volume": "音效:{sfx}",
"settings.fullscreen": "全屏:{state}",
"settings.aniso": "各向异性过滤:{aniso}",
"settings.vsync": "垂直同步: {state}",
"settings.language": "语言: {lang}",
"settings.restart_game_info": "您需要重启游戏以应用这些更改。",
"common.on": "开",
"common.off": "关",
"hostgame.create_a_new_world": "创建新世界",
"hostgame.world_seed": "世界种子",
"hostgame.port": "端口",
"hostgame.create_world": "创建世界",
"joingame.join_a_world": "加入世界",
"joingame.server_ip": "服务器IP",
"joingame.join_world": "加入世界"
}

View File

@@ -29,8 +29,254 @@ uniform bool flipY;
uniform int renderDistance;
uniform vec3 skyColor;
#include "shadow.glsl"
#include "normal.glsl"
const vec2 poissonDisk32[32] = vec2[](
vec2(-0.975402, -0.071138),
vec2(-0.920347, -0.411420),
vec2(-0.883908, 0.217872),
vec2(-0.815442, -0.879125),
vec2(-0.775043, 0.543896),
vec2(-0.698126, -0.227570),
vec2(-0.682433, 0.801894),
vec2(-0.563905, 0.021517),
vec2(-0.443233, -0.975116),
vec2(-0.412231, 0.361307),
vec2(-0.264969, -0.418930),
vec2(-0.241888, 0.997065),
vec2(-0.094184, -0.929389),
vec2(-0.019101, 0.680997),
vec2( 0.143832, -0.141008),
vec2( 0.199841, 0.786414),
vec2( 0.344959, 0.293878),
vec2( 0.443233, -0.475115),
vec2( 0.537430, -0.473734),
vec2( 0.589349, 0.569135),
vec2( 0.674281, -0.178897),
vec2( 0.791975, 0.190902),
vec2( 0.815442, 0.879125),
vec2( 0.896420, -0.613392),
vec2( 0.945586, -0.768907),
vec2( 0.974844, 0.756484),
vec2(-0.814100, 0.914376),
vec2(-0.382775, 0.276768),
vec2(-0.915886, 0.457714),
vec2( 0.537800, 0.912200),
vec2(-0.620000, -0.650000),
vec2( 0.120000, -0.780000)
);
const vec2 poissonDisk16[16] = vec2[](
vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
vec2(-0.09418410, -0.92938870), vec2(0.34495938, 0.29387760),
vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
);
const vec2 poissonDisk8[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)
);
uniform int samples;
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}
float FindBlocker(vec2 uv,
float zReceiver,
vec2 texelSize,
float bias,
float lightSizeUV)
{
float avgDepth = 0.0;
int blockers = 0;
float searchRadius = lightSizeUV * 0.5;
for(int i = 0; i < samples; i++)
{
vec2 offset;
if (samples == 32) {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
} else if (samples == 16) {
offset =
poissonDisk16[i]
* searchRadius
* texelSize;
} else if (samples == 8) {
offset =
poissonDisk8[i]
* searchRadius
* texelSize;
} else {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
}
float depth =
texture(shadowMap, uv + offset).r;
if(depth < zReceiver - bias)
{
avgDepth += depth;
blockers++;
}
}
if(blockers == 0)
return -1.0;
return avgDepth / blockers;
}
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 =
clamp(
0.001 * (1.0 - dot(norm, lightDir)),
0.0003,
0.003
);
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;
float radius = mix(1.0, 4.0, currentDepth);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[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 if (shadowMode == 3) {
float avgBlockerDepth =
FindBlocker(
projCoords.xy,
currentDepth,
texelSize,
bias,
lightSizeUV
);
if(avgBlockerDepth < 0.0)
{
return 0.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 penumbraRatio = (currentDepth - avgBlockerDepth);
float radius = clamp(
penumbraRatio * lightSizeUV,
minRadius,
maxRadius
);
*/
float radius =
mix(
minRadius,
maxRadius,
smoothstep(
0.0,
0.05,
currentDepth - avgBlockerDepth
)
);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[i] * radius * texelSize;
}
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
} else {
float pcfDepth =
texture(shadowMap, projCoords.xy).r;
shadow =
currentDepth - bias > pcfDepth
? 1.0
: 0.0;
}
return shadow;
}
vec3 calcNewNormal() {
mat3 TBN = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec3 retrievedNormal = texture(normMap, vec3(tc, tex_layer)).xyz;
retrievedNormal = retrievedNormal * 2.0 - 1.0;
if (flipY) {
retrievedNormal.y = -retrievedNormal.y;
}
vec3 newNormal = TBN * retrievedNormal;
return normalize(newNormal);
}
void main(void) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));

View File

@@ -1,46 +0,0 @@
#include "noise.glsl"
vec3 computeSkyColor(vec3 dir) {
vec3 sund = normalize(sunDir);
float t =
clamp(
dir.y * 0.5 + 0.5,
0.0,
1.0
);
vec3 sky =
mix(
skyBottom,
skyTop,
pow(t, horizonSharpness)
);
// cloud
if (dir.y > 0.0) {
vec2 cloud_uv = dir.xz / (dir.y + 0.15) * 0.5 + vec2(time * 0.005, time * 0.002);
float cloud_density = fbm(cloud_uv * 2.0);
float safeLow = cloudThresholdLow;
float safeHigh = max(cloudThresholdHigh, cloudThresholdLow + 0.001);
cloud_density = smoothstep(safeLow,safeHigh, cloud_density);
float fade = smoothstep(0.0, 0.3, dir.y) * (1.0 - smoothstep(0.85, 1.0, dir.y));
cloud_density *= fade;
vec3 cloud_color = mix(skyBottom, vec3(1.0), cloudWhiteMix);
sky = mix(sky, cloud_color, cloud_density * 0.6);
}
float sunAmount = max(dot(dir, sund), 0.0);
//float glow = pow(sunAmount, 8.0) * 0.15;
float glow = pow(sunAmount, 8.0) * 0.15 + pow(sunAmount, 32.0) * 0.3;
sky += glow * sunColor;
return sky;
}

View File

@@ -1,7 +0,0 @@
#version 460
in vec2 tc;
void main() {
}

View File

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

View File

@@ -1,11 +0,0 @@
#version 460
in vec2 tc;
out vec4 color;
layout (binding = 0) uniform sampler2D samp;
void main(void) {
color = texture(samp, tc);
}

View File

@@ -1,14 +0,0 @@
#version 460
layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 texCoord;
out vec2 tc;
uniform mat4 model_matrix;
uniform mat4 proj_matrix;
void main(void) {
gl_Position = proj_matrix * model_matrix * vec4(pos, 0.0, 1.0);
tc = texCoord;
}

View File

@@ -1,25 +0,0 @@
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float amp = 0.5;
for (int i = 0; i < 5; i++) {
v += amp * noise(p);
p *= 2.0;
amp *= 0.5;
}
return v;
}

View File

@@ -1,10 +0,0 @@
vec3 calcNewNormal() {
mat3 TBN = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec3 retrievedNormal = texture(normMap, vec3(tc, tex_layer)).xyz;
retrievedNormal = retrievedNormal * 2.0 - 1.0;
if (flipY) {
retrievedNormal.y = -retrievedNormal.y;
}
vec3 newNormal = TBN * retrievedNormal;
return normalize(newNormal);
}

View File

@@ -1,47 +0,0 @@
#version 460
in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec4 FragPosLightSpace;
out vec4 color;
layout (binding = 0) uniform sampler2D shadowMap;
layout (binding = 1) uniform sampler2D samp;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 ambientColor;
uniform vec3 sunlightDir;
uniform bool shader_on;
uniform int shadowMode;
uniform float lightSizeUV;
uniform float minRadius;
uniform float maxRadius;
#include "shadow.glsl"
void main() {
vec4 objectColor = texture(samp, tc);
if (!shader_on) {
color = objectColor;
return;
}
vec3 lightDir = normalize(-sunlightDir);
vec3 norm = normalize(normal);
vec3 ambient = ambientStrength * ambientColor;
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * sunlightColor;
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
//float shadow = 0.0;
//color = vec4(vec3(shadow),1);
//color = vec4(vec3(diff),1);
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb, objectColor.a);
}

View File

@@ -1,27 +0,0 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in vec3 aTangent;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
uniform mat4 lightSpaceMatrix;
uniform mat4 modelMatrix;
out vec4 FragPosLightSpace;
out vec3 normal;
out vec2 tc;
out vec3 vert_pos;
void main() {
vec4 worldPos = modelMatrix * vec4(pos, 1.0);
FragPosLightSpace = lightSpaceMatrix * worldPos;
vec4 viewPos = mv_matrix * vec4(pos, 1.0);
tc = texCoord;
vert_pos = pos;
normal = normalize(mat3(norm_matrix) * aNormal);
gl_Position = proj_matrix * viewPos;
}

View File

@@ -1,9 +0,0 @@
#version 460
out vec4 color;
uniform vec4 inColor;
void main(void) {
color = inColor;
}

View File

@@ -1,11 +0,0 @@
#version 460
layout (location = 0) in vec2 pos;
uniform mat4 model_matrix;
uniform mat4 proj_matrix;
void main(void) {
gl_Position = proj_matrix * model_matrix * vec4(pos, 0.0, 1.0);
}

View File

@@ -1,238 +0,0 @@
const vec2 poissonDisk32[32] = vec2[](
vec2(-0.975402, -0.071138),
vec2(-0.920347, -0.411420),
vec2(-0.883908, 0.217872),
vec2(-0.815442, -0.879125),
vec2(-0.775043, 0.543896),
vec2(-0.698126, -0.227570),
vec2(-0.682433, 0.801894),
vec2(-0.563905, 0.021517),
vec2(-0.443233, -0.975116),
vec2(-0.412231, 0.361307),
vec2(-0.264969, -0.418930),
vec2(-0.241888, 0.997065),
vec2(-0.094184, -0.929389),
vec2(-0.019101, 0.680997),
vec2( 0.143832, -0.141008),
vec2( 0.199841, 0.786414),
vec2( 0.344959, 0.293878),
vec2( 0.443233, -0.475115),
vec2( 0.537430, -0.473734),
vec2( 0.589349, 0.569135),
vec2( 0.674281, -0.178897),
vec2( 0.791975, 0.190902),
vec2( 0.815442, 0.879125),
vec2( 0.896420, -0.613392),
vec2( 0.945586, -0.768907),
vec2( 0.974844, 0.756484),
vec2(-0.814100, 0.914376),
vec2(-0.382775, 0.276768),
vec2(-0.915886, 0.457714),
vec2( 0.537800, 0.912200),
vec2(-0.620000, -0.650000),
vec2( 0.120000, -0.780000)
);
const vec2 poissonDisk16[16] = vec2[](
vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
vec2(-0.09418410, -0.92938870), vec2(0.34495938, 0.29387760),
vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
);
const vec2 poissonDisk8[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)
);
uniform int samples;
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}
float FindBlocker(vec2 uv,
float zReceiver,
vec2 texelSize,
float bias,
float lightSizeUV)
{
float avgDepth = 0.0;
int blockers = 0;
float searchRadius = lightSizeUV * 0.5;
for(int i = 0; i < samples; i++)
{
vec2 offset;
if (samples == 32) {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
} else if (samples == 16) {
offset =
poissonDisk16[i]
* searchRadius
* texelSize;
} else if (samples == 8) {
offset =
poissonDisk8[i]
* searchRadius
* texelSize;
} else {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
}
float depth =
texture(shadowMap, uv + offset).r;
if(depth < zReceiver - bias)
{
avgDepth += depth;
blockers++;
}
}
if(blockers == 0)
return -1.0;
return avgDepth / blockers;
}
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 =
clamp(
0.001 * (1.0 - dot(norm, lightDir)),
0.0003,
0.003
);
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;
float radius = mix(1.0, 4.0, currentDepth);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[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 if (shadowMode == 3) {
float avgBlockerDepth =
FindBlocker(
projCoords.xy,
currentDepth,
texelSize,
bias,
lightSizeUV
);
if(avgBlockerDepth < 0.0)
{
return 0.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 penumbraRatio = (currentDepth - avgBlockerDepth);
float radius = clamp(
penumbraRatio * lightSizeUV,
minRadius,
maxRadius
);
*/
float radius =
mix(
minRadius,
maxRadius,
smoothstep(
0.0,
0.05,
currentDepth - avgBlockerDepth
)
);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[i] * radius * texelSize;
}
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
} else {
float pcfDepth =
texture(shadowMap, projCoords.xy).r;
shadow =
currentDepth - bias > pcfDepth
? 1.0
: 0.0;
}
return shadow;
}

View File

@@ -19,12 +19,84 @@ uniform float cloudThresholdHigh;
uniform float time;
#include "compute_sky_color.glsl"
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float amp = 0.5;
for (int i = 0; i < 5; i++) {
v += amp * noise(p);
p *= 2.0;
amp *= 0.5;
}
return v;
}
vec3 computeSkyColor(vec3 dir) {
vec3 sund = normalize(sunDir);
float t =
clamp(
dir.y * 0.5 + 0.5,
0.0,
1.0
);
vec3 sky =
mix(
skyBottom,
skyTop,
pow(t, horizonSharpness)
);
// cloud
if (dir.y > 0.0) {
vec2 cloud_uv = dir.xz / (dir.y + 0.15) * 0.5 + vec2(time * 0.005, time * 0.002);
float cloud_density = fbm(cloud_uv * 2.0);
float safeLow = cloudThresholdLow;
float safeHigh = max(cloudThresholdHigh, cloudThresholdLow + 0.001);
cloud_density = smoothstep(safeLow,safeHigh, cloud_density);
float fade = smoothstep(0.0, 0.3, dir.y) * (1.0 - smoothstep(0.85, 1.0, dir.y));
cloud_density *= fade;
vec3 cloud_color = mix(skyBottom, vec3(1.0), cloudWhiteMix);
sky = mix(sky, cloud_color, cloud_density * 0.6);
}
float sunAmount = max(dot(dir, sund), 0.0);
//float glow = pow(sunAmount, 8.0) * 0.15;
float glow = pow(sunAmount, 8.0) * 0.15 + pow(sunAmount, 32.0) * 0.3;
sky += glow * sunColor;
return sky;
}
void main(void) {
vec3 sky = computeSkyColor(dir);
frag_color = vec4(sky, 1.0);
//frag_color = vec4(vec3(sunAmount), 1.0);
//frag_color = vec4(t,0,0,1);
}

View File

@@ -0,0 +1,12 @@
#version 460
in vec2 tc;
flat in int tex_layer;
out vec4 color;
layout (binding = 0) uniform sampler2DArray samp;
void main(void) {
color = texture(samp, vec3(tc, tex_layer));
}

View File

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

View File

@@ -19,7 +19,33 @@ uniform vec3 sunDir;
uniform vec3 sunColor;
uniform float waterDensity;
#include "noise.glsl"
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x),
f.y
);
}
float fbm(vec2 p) {
float value = 0.0;
float amp = 0.5;
float freq = 1.0;
for (int i = 0; i < 4; ++i) {
value += amp * noise(p * freq);
freq *= 2.0;
amp *= 0.5;
}
return value;
}
float getCausticValue(float x, float y, float z) {
float w = 8.0;

View File

@@ -42,15 +42,82 @@ uniform float refractStrength;
uniform bool enablePerturb;
uniform bool enableDepthFade;
#include "compute_sky_color.glsl"
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);
}
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float amp = 0.5;
for (int i = 0; i < 5; i++) {
v += amp * noise(p);
p *= 2.0;
amp *= 0.5;
}
return v;
}
vec3 computeSkyColor(vec3 dir) {
vec3 sund = normalize(sunDir);
float t =
clamp(
dir.y * 0.5 + 0.5,
0.0,
1.0
);
vec3 sky =
mix(
skyBottom,
skyTop,
pow(t, horizonSharpness)
);
// cloud
if (dir.y > 0.0) {
vec2 cloud_uv = dir.xz / (dir.y + 0.15) * 0.5 + vec2(time * 0.005, time * 0.002);
float cloud_density = fbm(cloud_uv * 2.0);
float safeLow = cloudThresholdLow;
float safeHigh = max(cloudThresholdHigh, cloudThresholdLow + 0.001);
cloud_density = smoothstep(safeLow,safeHigh, cloud_density);
float fade = smoothstep(0.0, 0.3, dir.y) * (1.0 - smoothstep(0.85, 1.0, dir.y));
cloud_density *= fade;
vec3 cloud_color = mix(skyBottom, vec3(1.0), cloudWhiteMix);
sky = mix(sky, cloud_color, cloud_density * 0.6);
}
float sunAmount = max(dot(dir, sund), 0.0);
//float glow = pow(sunAmount, 8.0) * 0.15;
float glow = pow(sunAmount, 8.0) * 0.15 + pow(sunAmount, 32.0) * 0.3;
sky += glow * sunColor;
return sky;
}
// Reconstruct eye-space coordinates from screen UV and depth buffer value
vec3 reconstructViewPos(vec2 uv, float depth) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

View File

@@ -1,72 +0,0 @@
include(FetchContent)
# System packages
find_package(OpenGL REQUIRED)
find_package(Protobuf REQUIRED)
find_package(absl REQUIRED)
find_package(zstd REQUIRED)
find_package(OpenAL REQUIRED)
find_package(harfbuzz REQUIRED)
find_package(Freetype REQUIRED)
if (UNIX AND NOT APPLE)
find_package(glfw3 REQUIRED)
endif()
# Third-party libraries
FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 1.0.3
)
FetchContent_MakeAvailable(glm)
FetchContent_Declare(
soil2
GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git
GIT_TAG 1.31
)
FetchContent_MakeAvailable(soil2)
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG v3.4.0
)
FetchContent_MakeAvailable(tomlplusplus)
if (WIN32)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(glfw)
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()

View File

@@ -1,22 +0,0 @@
find_path(ZSTD_INCLUDE_DIRS
NAMES zstd.h
HINTS ${zstd_ROOT_DIR}/include)
find_library(ZSTD_LIBRARIES
NAMES zstd
HINTS ${zstd_ROOT_DIR}/lib)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARIES ZSTD_INCLUDE_DIRS)
mark_as_advanced(
ZSTD_LIBRARIES
ZSTD_INCLUDE_DIRS)
if(ZSTD_FOUND AND NOT (TARGET zstd::zstd))
add_library (zstd::zstd UNKNOWN IMPORTED)
set_target_properties(zstd::zstd
PROPERTIES
IMPORTED_LOCATION ${ZSTD_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIRS})
endif()

View File

@@ -1,11 +1,9 @@
#pragma once
#include "Cubed/audio/audio_engine.hpp"
#define GLFW_INCLUDE_NONE
#include "Cubed/argument.hpp"
#include "Cubed/config.hpp"
#include "Cubed/camera.hpp"
#include "Cubed/dev_panel.hpp"
#include "Cubed/render/renderer.hpp"
#include "Cubed/scene/scene_manager.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/renderer.hpp"
#include "Cubed/texture_manager.hpp"
#include "Cubed/window.hpp"
namespace Cubed {
@@ -23,8 +21,6 @@ public:
static void window_focus_callback(GLFWwindow* window, int focused);
static void window_reshape_callback(GLFWwindow* window, int new_width,
int new_height);
static void framebuffer_size_callback(GLFWwindow* window, int new_width,
int new_height);
static void mouse_scroll_callback(GLFWwindow* window, double xoffset,
double yoffset);
static void cursor_enter_callback(GLFWwindow* window, int entered);
@@ -32,43 +28,34 @@ public:
static int start_cubed_application(int argc, char** argv);
static unsigned int seed();
static float delta_time();
static float delte_time();
static float get_fps();
Camera& camera();
DevPanel& dev_panel();
Renderer& renderer();
TextureManager& texture_manager();
Window& window();
Config& config();
const Argument& argument() const;
AudioEngine& audio();
const char* get_clipboard_text();
World& world();
private:
Config m_game_config;
Camera m_camera;
TextureManager m_texture_manager;
World m_world;
DevPanel m_dev_panel{*this};
Renderer m_renderer{m_camera, m_world, m_texture_manager, m_dev_panel};
AudioEngine m_audio;
Renderer m_renderer;
Window m_window;
SceneManager m_scene_manager;
Window m_window{m_renderer};
inline static double last_time = glfwGetTime();
inline static double current_time = glfwGetTime();
inline static double dt = 0.0f;
inline static double delta_time = 0.0f;
inline static double fps_time_count = 0.0f;
inline static int frame_count = 0;
inline static int fps = 0;
Argument m_argument;
void init(int argc, char** argv);
void handle_argument(int argc, char** argv);
void handle_toml();
void init();
auto init_camera();
auto init_texture();
auto init_world();
@@ -76,8 +63,6 @@ private:
void render();
void run();
void update();
void dispatch_event(const Event& e);
};
} // namespace Cubed

View File

@@ -1,11 +0,0 @@
#pragma once
#include <string>
namespace Cubed {
struct Argument {
bool is_client = false;
int port = 25530;
std::string ip{"127.0.0.1"};
std::string player{"Unknown"};
bool debug_on = true;
};
} // namespace Cubed

View File

@@ -1,24 +0,0 @@
#pragma once
#include "Cubed/audio/audio_data.hpp"
#include <AL/al.h>
namespace Cubed {
class AudioBuffer {
public:
AudioBuffer(const AudioData& data);
AudioBuffer(const AudioBuffer&) = delete;
AudioBuffer(AudioBuffer&&) = delete;
AudioBuffer& operator=(const AudioBuffer&) = delete;
AudioBuffer& operator=(AudioBuffer&&) = delete;
~AudioBuffer();
ALuint buffer() const;
float duration() const;
uint32_t channels() const;
private:
ALuint m_buffer = 0;
float m_duration = 0.0f;
uint32_t m_channels = 0;
void set_data(const AudioData& data);
};
} // namespace Cubed

View File

@@ -1,10 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Cubed {
struct AudioData {
std::vector<int16_t> pcm;
uint32_t channels;
uint32_t sample_rate;
};
} // namespace Cubed

View File

@@ -1,19 +0,0 @@
#pragma once
#include <AL/al.h>
namespace Cubed {
class AudioEffect {
public:
AudioEffect();
AudioEffect(const AudioEffect&) = delete;
AudioEffect(AudioEffect&&) = delete;
AudioEffect& operator=(const AudioEffect&) = default;
AudioEffect& operator=(AudioEffect&&) = delete;
~AudioEffect();
void set_reverb(float decay_time, float reverb_gain, float gain_hf,
float density = 1.0f, float diffusion = 1.0f);
ALuint effect() const;
private:
ALuint m_effect = 0;
};
} // namespace Cubed

View File

@@ -1,18 +0,0 @@
#pragma once
#include "Cubed/audio/audio_effect.hpp"
namespace Cubed {
class AudioEffectSlot {
public:
AudioEffectSlot();
AudioEffectSlot(const AudioEffectSlot&) = delete;
AudioEffectSlot(AudioEffectSlot&&) = delete;
AudioEffectSlot& operator=(const AudioEffectSlot&) = delete;
AudioEffectSlot& operator=(AudioEffectSlot&&) = delete;
~AudioEffectSlot();
void set_effect(const AudioEffect& effect);
ALuint slot() const;
private:
ALuint m_slot = 0;
};
} // namespace Cubed

View File

@@ -1,63 +0,0 @@
#pragma once
#include "Cubed/audio/audio_fade.hpp"
#include "Cubed/audio/audio_source.hpp"
#include "Cubed/audio/sound_manager.hpp"
#include "Cubed/audio/source_pool.hpp"
#include "Cubed/config.hpp"
#include <AL/al.h>
#include <AL/alc.h>
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <unordered_map>
namespace Cubed {
class ClientWorld;
class AudioEngine {
public:
AudioEngine(Config& config);
AudioEngine(const AudioEngine&) = delete;
AudioEngine(AudioEngine&&) = delete;
AudioEngine& operator=(const AudioEngine&) = delete;
AudioEngine& operator=(AudioEngine&&) = delete;
~AudioEngine();
void init();
void play_bgm();
void stop_bgm();
void change_bgm(const std::string& sound);
void play_3d(const std::string& sound, const glm::vec3& pos,
bool check = false);
void play_2d(const std::string& sound, bool check = false);
void update_listener(const glm::vec3& pos, const glm::vec3& forward,
const glm::vec3& up);
void update();
void reload_config();
void underwater_change(bool underwater);
float& bgm_target_volume();
private:
using FadeMap = std::unordered_map<std::string, AudioFade>;
bool m_init{false};
ALCdevice* device{nullptr};
ALCcontext* context{nullptr};
glm::vec3 listener_pos;
std::unique_ptr<AudioSource> m_bgm;
FadeMap m_fade_map;
SoundManager m_sounds;
Config& m_config;
std::shared_ptr<SourcePool> m_pool;
bool m_efx_supported = false;
bool m_underwater = false;
float m_music_volume = 1.0f;
float m_sfx_volume = 1.0f;
std::unique_ptr<AudioFilter> m_low_pass_filter;
std::unique_ptr<AudioEffect> m_underwater_effect;
std::unique_ptr<AudioEffectSlot> m_underwater_slot;
};
} // namespace Cubed

View File

@@ -1,15 +0,0 @@
#pragma once
#include "Cubed/tools/log.hpp"
#include <AL/al.h>
namespace Cubed {
inline void check_al_error(
std::source_location loaction = std::source_location::current()) {
ALenum error = alGetError();
if (error != AL_NO_ERROR) {
Logger::error("File {} Line {} Function {} OpenAL Error {} ",
loaction.file_name(), loaction.line(),
loaction.function_name(), alGetString(error));
}
}
} // namespace Cubed

View File

@@ -1,19 +0,0 @@
#pragma once
#include "Cubed/audio/audio_source.hpp"
namespace Cubed {
class AudioFade {
public:
AudioFade(AudioSource* source, float fade_in = 0.0f, float fade_out = 0.0f);
~AudioFade();
void update();
void reset();
private:
AudioSource* m_source = nullptr;
float m_in_duration = 0.0f;
float m_out_duration = 0.0f;
float m_start_gain = 0.0f;
bool m_fade_in = true;
bool m_active = true;
};
} // namespace Cubed

View File

@@ -1,19 +0,0 @@
#pragma once
#include <AL/al.h>
namespace Cubed {
class AudioFilter {
public:
AudioFilter();
AudioFilter(const AudioFilter&) = delete;
AudioFilter(AudioFilter&&) = delete;
AudioFilter& operator=(const AudioFilter&) = delete;
AudioFilter& operator=(AudioFilter&&) = delete;
~AudioFilter();
void set_lowpass(float gain, float gain_hf);
ALuint filter() const;
private:
ALuint m_filter = 0;
};
} // namespace Cubed

View File

@@ -1,17 +0,0 @@
#pragma once
#include "Cubed/audio/audio_data.hpp"
#include <filesystem>
namespace Cubed {
class AudioLoader {
public:
static AudioData load(const std::filesystem::path& path);
private:
static AudioData load_wav(const std::filesystem::path& path);
static AudioData load_mp3(const std::filesystem::path& path);
static AudioData load_flac(const std::filesystem::path& path);
static AudioData load_ogg(const std::filesystem::path& path);
};
} // namespace Cubed

View File

@@ -1,50 +0,0 @@
#pragma once
#include "Cubed/audio/audio_buffer.hpp"
#include "Cubed/audio/audio_effect_slot.hpp"
#include "Cubed/audio/audio_filter.hpp"
#include <AL/al.h>
#include <glm/glm.hpp>
namespace Cubed {
enum class AudioState { INITIAL, PLAYING, PAUSED, STOPPED };
class AudioSource {
public:
AudioSource(float volume = 1.0f);
~AudioSource();
void set_buffer_2d(const AudioBuffer& buffer);
void set_buffer_3d(const AudioBuffer& buffer, const glm::vec3& vec3);
void set_loop(bool on = true);
void set_volume(float volume);
void set_pitch(float pitch);
void play();
void play_2d(const AudioBuffer& buffer);
void play_3d(const AudioBuffer& buffer, const glm::vec3& pos);
void stop();
void pause();
float duration() const;
float current_time() const;
float target_volume() const;
float& target_volume();
void set_target_volume(float volume);
AudioState state() const;
void mark_in_use();
bool in_use() const;
void reset();
void set_filter(const AudioFilter& filter);
void clear_filter();
void set_effect_slot(const AudioEffectSlot& slot);
void clear_effect_slot();
private:
ALuint m_source = 0;
float m_target_volume = 1.0f;
float m_duration = 0.0f;
bool m_using;
};
} // namespace Cubed

View File

@@ -1,19 +0,0 @@
#pragma once
#include "Cubed/audio/audio_buffer.hpp"
#include <string>
#include <unordered_map>
namespace Cubed {
class SoundManager {
public:
SoundManager();
~SoundManager();
const AudioBuffer& load(const std::string& name);
const AudioBuffer& get_buffer(const std::string& name);
void init();
void clear();
private:
std::unordered_map<std::string, AudioBuffer> m_buffers;
};
} // namespace Cubed

View File

@@ -1,23 +0,0 @@
#pragma once
#include "Cubed/audio/audio_source.hpp"
namespace Cubed {
class SourcePool {
private:
std::vector<AudioSource> m_sources;
public:
explicit SourcePool(size_t size);
~SourcePool();
SourcePool(const SourcePool&) = delete;
SourcePool(SourcePool&&) = delete;
SourcePool& operator=(const SourcePool&) = delete;
SourcePool& operator=(SourcePool&&) = delete;
void update();
AudioSource* acquire();
std::vector<AudioSource>& sources();
};
} // namespace Cubed

View File

@@ -1,6 +1,5 @@
#pragma once
#include "Cubed/input/event.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
@@ -9,35 +8,22 @@
namespace Cubed {
class ClientPlayer;
class Player;
class Camera {
private:
enum class Perspective {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT,
};
bool m_firse_mouse = true;
ClientPlayer* m_player;
Player* m_player;
float m_last_mouse_x, m_last_mouse_y;
glm::vec3 m_camera_pos;
bool m_under_water = false;
Perspective m_perspective = Perspective::FIRST_PERSON;
glm::vec3 m_front;
glm::vec3 camera_collision(glm::vec3 start, glm::vec3 end,
float radius = 0.2f);
bool handle_key_event(const KeyEvent& e);
bool handle_mouse_move_event(const MouseMoveEvent& e);
public:
Camera();
void update_move_camera();
void camera_init(ClientPlayer* player);
void camera_init(Player* player);
void hot_reload();
void reset_camera();
void update_cursor_position_camera(double xpos, double ypos);
@@ -47,9 +33,6 @@ public:
bool is_under_water() const;
glm::vec3 get_camera_front() const;
void change_perspective();
bool is_first_person() const;
bool handle_event(const Event& e);
};
} // namespace Cubed

View File

@@ -1,74 +1,107 @@
#pragma once
#include "Cubed/tools/toml.utils.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include <toml++/toml.hpp>
namespace Cubed {
template <typename T>
concept TomlValueType =
std::same_as<T, int> || std::same_as<T, bool> || std::same_as<T, double> ||
std::same_as<T, const char*> || std::same_as<T, toml::date> ||
std::same_as<T, toml::time> || std::same_as<T, toml::date_time> ||
std::same_as<T, std::string>;
class Config {
public:
explicit Config(std::string_view path);
Config(const Config&) = delete;
Config(Config&&) = delete;
Config& operator=(const Config&) = delete;
Config& operator=(Config&&) = delete;
Config();
~Config();
static Config& get();
toml::table& table();
void load_config();
void load_or_create_config();
void save_to_file();
template <TOML::TomlValueType T>
T get(std::string_view key, T default_value) {
if (auto* node = find_node(m_tbl, key)) {
if (auto value = node->value<T>())
return *value;
template <TomlValueType T> T get(std::string_view key) const {
size_t cur = 0;
auto pos = key.find('.');
const toml::table* table = &m_tbl;
while (pos != std::string_view::npos) {
std::string_view s = key.substr(cur, pos - cur);
if (s.empty()) {
Logger::error("Empty key/table name in path '{}'", key);
ASSERT(false);
std::abort();
}
set(key, default_value);
return default_value;
}
template <TOML::TomlValueType T> void set(std::string_view key, T&& value) {
auto pos = key.rfind('.');
toml::table* table;
std::string_view name;
if (pos == std::string_view::npos) {
table = &m_tbl;
name = key;
cur = pos + 1;
pos = key.find('.', cur);
if (auto* next = (*table)[s].as_table()) {
table = next;
} else {
table = find_or_create_table(key.substr(0, pos));
name = key.substr(pos + 1);
Logger::error("Can't find table {}", s);
ASSERT(false);
std::abort();
}
// Insert node at the last level's table
table->insert_or_assign(name, std::forward<T>(value));
}
std::string_view n_key = key.substr(cur);
if (n_key.empty()) {
Logger::error("Trailing dot in path '{}'", key);
ASSERT(false);
std::abort();
}
auto opt = (*table)[n_key].value<T>();
if (opt) {
return *opt;
} else {
Logger::error("Can't find key {}", n_key);
ASSERT(false);
std::abort();
}
}
template <typename T> void set(std::string_view key, T&& val) {
if constexpr (!TomlValueType<std::decay_t<T>>) {
static_assert(false, "Type Not Support");
}
size_t cur = 0;
auto pos = key.find('.');
toml::table* table = &m_tbl;
while (pos != std::string_view::npos) {
std::string_view s = key.substr(cur, pos - cur);
if (s.empty()) {
Logger::error("Empty key/table name in path '{}'", key);
ASSERT(false);
std::abort();
}
cur = pos + 1;
pos = key.find('.', cur);
if (auto* next = (*table)[s].as_table()) {
table = next;
} else {
auto [it, inserted] = table->insert_or_assign(s, toml::table{});
table = it->second.as_table();
}
}
std::string_view n_key = key.substr(cur);
if (n_key.empty()) {
Logger::error("Trailing dot in path '{}'", key);
ASSERT(false);
std::abort();
}
table->insert_or_assign(n_key, std::forward<T>(val));
}
template <typename T> void set_and_save(std::string_view key, T&& val) {
set(key, std::forward(val));
save_to_file();
}
toml::node_view<toml::node> val_view(std::string_view key);
private:
toml::table m_tbl;
const std::string CONGIF_PATH;
const toml::node* find_node(const toml::table& root,
std::string_view path) const;
// Follow the path to find the last-level toml::table, creating it if it
// does not exist
toml::table* find_or_create_table(std::string_view path);
constexpr static inline std::string_view CONGIF_PATH =
ASSETS_PATH "config.toml";
void create_config();
};
template <>
inline float Config::get(std::string_view key, float default_value) {
return static_cast<float>(
Config::get<double>(key, static_cast<double>(default_value)));
}
template <> inline void Config::set(std::string_view key, float&& value) {
Config::set<double>(key, static_cast<double>(value));
}
} // namespace Cubed

View File

@@ -1,4 +1,5 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include <array>
namespace Cubed {
@@ -25,9 +26,9 @@ 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;
constexpr int RESERVED_THREADS = 5;
constexpr float DEFAULT_CAVE_PROBABILITY = 0.035f;
constexpr int RESERVED_THREADS = 3;
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>;

View File

@@ -1,8 +1,6 @@
#pragma once
#include "Cubed/ui/column_layout.hpp"
#include "Cubed/ui/label.hpp"
#include "Cubed/ui/widget.hpp"
#include "Cubed/ui/text.hpp"
#include <unordered_map>
@@ -13,14 +11,14 @@ public:
static DebugCollector& get();
DebugCollector();
void report(const std::string& name, std::string_view content);
void init(int width, int height);
Widget& get_widget();
bool handle_event(const Event& e);
std::unordered_map<std::size_t, Text>& all_texts();
Text& text(std::string_view name);
void report(std::string_view name, std::string_view content);
void init_text();
private:
ColumnLayout m_widget;
std::unordered_map<std::string, Label*> m_component;
std::unordered_map<std::size_t, Text> m_texts;
};
} // namespace Cubed

View File

@@ -1,14 +1,11 @@
#pragma once
#include "Cubed/config.hpp"
#include <toml++/toml.hpp>
namespace Cubed {
class WorldScene;
class ClientPlayer;
class App;
class Player;
class DevPanel {
struct ConfigView {
float fov = 70.0f;
@@ -23,27 +20,27 @@ class DevPanel {
bool is_enable_aniso = false;
bool is_support_aniso = true;
bool is_reload = true;
float volume_music = 1.0f;
float volume_sfx = 1.0f;
};
struct PlayerProfile {
int game_mode = 0;
int gait = 0;
float pos[3] = {0.0f, 0.0f, 0.0f};
};
struct TextEditing {
bool perlin_seed = false;
};
public:
DevPanel(WorldScene& app);
DevPanel(App& app);
void init();
void render();
private:
App& m_app;
WorldScene& m_world_scene;
Config& m_config;
ConfigView m_config_view;
ClientPlayer* m_player;
ConfigView m_config;
Player* m_player;
PlayerProfile m_player_profile;
TextEditing m_text_editing;
bool m_need_save_config = false;
bool m_gen_thread_running = true;
int m_theme = 0;
@@ -53,15 +50,13 @@ private:
int m_samples_idx = 1;
int m_threads = 1;
int m_chunk_style = 0;
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_chunk_table_bar();
void show_settings_tab_item();
void show_world_tab_item();
void show_server_world_table_bar();
void show_client_world_table_bar();
void show_player_tab_item();
void show_items_tab_item();
void show_shader_tab_item();

View File

@@ -1,9 +1,6 @@
#pragma once
#include "Cubed/tools/cubed_assert.hpp"
#include <array>
#include <string>
#include <utility>
#include <vector>
namespace Cubed {
@@ -80,32 +77,4 @@ DesertParams& desert_params();
MountainParams& mountain_params();
RiverParams& river_params();
inline BiomeType get_biome_from_id(int id) {
using enum BiomeType;
auto to = std::to_underlying<BiomeType>;
if (id == to(PLAIN)) {
return PLAIN;
}
if (id == to(FOREST)) {
return FOREST;
}
if (id == to(DESERT)) {
return DESERT;
}
if (id == to(MOUNTAIN)) {
return MOUNTAIN;
}
if (id == to(RIVER)) {
return RIVER;
}
if (id == to(SNOWY_PLAIN)) {
return SNOWY_PLAIN;
}
if (id == to(OCEAN)) {
return OCEAN;
}
ASSERT_MSG(false, "Unknown Biome Id");
throw std::invalid_argument("Unknown Biome Id");
}
} // namespace Cubed

View File

@@ -2,15 +2,12 @@
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <optional>
#include <string>
#include <vector>
namespace Cubed {
using BlockType = uint8_t;
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
struct BlockTexture {
std::string name;

View File

@@ -1,26 +1,31 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path.hpp"
#include "Cubed/gameplay/cave_path.hpp"
#include <shared_mutex>
#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);
bool has_origin_fast(const ChunkPos& pos) const;
float cave_probability() const;
PathOrigin get_origin(const ChunkPos& origin_chunk) const;
int search_radius() const;
unsigned world_seed() const;
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();
std::shared_mutex& path_mutex();
private:
std::atomic<unsigned> m_world_seed{0};
std::atomic<float> m_cave_probability{DEFAULT_CAVE_PROBABILITY};
CaveHashMap m_paths;
unsigned m_seed = 0;
Random m_random;
float m_cave_probability = 0.035f;
std::shared_mutex m_path_mutex;
};
} // namespace Cubed

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
@@ -8,11 +9,15 @@
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();
@@ -22,7 +27,6 @@ public:
static float& delta_angle_max();
static int& step_min();
static int& step_max();
static int step_len();
private:
static inline float m_radius_xz_min = 5.0f;
@@ -33,16 +37,18 @@ private:
static inline float m_delta_angle_max = 5.0f;
static inline int m_step_min = 10;
static inline int m_step_max = 400;
static inline float m_step_len = 4.0f;
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

@@ -1,42 +1,69 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/vertex_data.hpp"
#include "world/chunk_data.pb.h"
#include <atomic>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <mutex>
namespace Cubed {
class ClientWorld;
struct ChunkRenderSnapshot {
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;
GLuint water_vao;
size_t water_vertices_count;
glm::vec3 center;
glm::vec3 half_extents;
};
class ClientChunk {
public:
ClientChunk(ClientWorld& world);
~ClientChunk();
ClientChunk(const ClientChunk&) = delete;
ClientChunk& operator=(const ClientChunk&) = delete;
ClientChunk(ClientChunk&&) noexcept;
ClientChunk& operator=(ClientChunk&&) noexcept;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
class World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
static constexpr int VERTEX_DATA_SUM = 5;
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<bool> m_gening{false};
std::atomic<bool> m_temp_chunk{false};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
std::unique_ptr<ChunkGenerator> m_generator;
ChunkPos m_chunk_pos;
World& m_world;
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
4 - water
*/
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, bool temp_chunk = false);
~Chunk();
Chunk(const Chunk&) = delete;
Chunk& operator=(const Chunk&) = delete;
Chunk(Chunk&&) noexcept;
Chunk& operator=(Chunk&&) noexcept;
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
@@ -49,9 +76,33 @@ public:
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& get_chunk_blocks() const;
void receive_chunk(const ChunkDataRsp& data);
HeightMapArray get_heightmap() const;
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*, 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>, 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<BlockType>>,
4>& neighbor_block);
// Generate biome-specific vegetation/structures
void gen_phase_seven();
// void gen_vertex_data();
// 0 : (1, 0)
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
// Can only be called on the render thread
void upload_to_gpu();
GLuint get_normal_vao() const;
@@ -76,59 +127,17 @@ public:
void need_upload();
void set_chunk_block(int index, unsigned id);
BlockType get_chunk_block(int index);
// ensure thread safe!
void gen_chunk();
bool is_temp_chunk() const;
ChunkPos chunk_pos() const;
BiomeType biome() const;
void biome(BiomeType b);
HeightMapArray& heightmap();
std::vector<BlockType>& blocks();
ClientWorld& world();
World& world();
unsigned seed() const;
const ChunkRenderSnapshot* get_render_snapshot() const;
private:
struct FaceKey {
BlockType block_id = 0;
int face = -1; // 0-5, used to index NORMALS/TANGENTS/TEX_COORDS
bool valid() const { return block_id != 0; }
bool operator==(const FaceKey& o) const {
return block_id == o.block_id && face == o.face;
}
bool operator!=(const FaceKey& o) const { return !(*this == o); }
};
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
static constexpr int BLOCK_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
static constexpr int VERTEX_DATA_SUM = 5;
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<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
ChunkPos m_chunk_pos;
ClientWorld& m_world;
// the index is a array of block id
std::vector<BlockType> m_blocks;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
4 - water
*/
std::vector<VertexData> m_vertex_data;
ChunkRenderSnapshot m_render_snapshot;
unsigned m_seed = 0;
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);
void emit_quad(int axis, int face_dir, int layer, int i, int j, int w,
int h, int u_axis, int v_axis, FaceKey key);
BiomeConditions& conditions();
};
} // namespace Cubed

View File

@@ -4,6 +4,7 @@
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <atomic>
@@ -11,14 +12,13 @@
#include <optional>
namespace Cubed {
class ServerChunk;
class Chunk;
class ChunkGenerator {
public:
ChunkGenerator(ServerChunk& chunk);
ChunkGenerator(Chunk& chunk);
static void init();
static void init(unsigned seed);
static void reload();
static const unsigned& seed();
static void seed(unsigned s);
@@ -27,7 +27,7 @@ public:
void assign_chunk_biome();
// Adjust Biome
void resolve_biome_adjacency_conflict(
const std::array<const ServerChunk*, 8>& adj_chunks);
const std::array<const Chunk*, 8>& adj_chunks);
// Generate Heightmap
void generate_heightmap();
// Adjust Height
@@ -43,7 +43,7 @@ public:
// Generate Structure
void generate_vegetation();
BiomeType get_biome_at(float world_x, float world_z);
ServerChunk& chunk();
Chunk& chunk();
Random& random();
const std::array<BiomeType, 8>& neighbor_biome() const;
void ocean_build();
@@ -54,7 +54,7 @@ private:
static inline std::atomic<bool> is_init{false};
static inline unsigned m_generator_seed{0};
static inline std::atomic<bool> is_seed_change{false};
ServerChunk& m_chunk;
Chunk& m_chunk;
Random m_random;
std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr};
bool is_cur_chunk_ins = false;
@@ -62,6 +62,9 @@ private:
unsigned m_chunk_seed = 0;
void make_biome_builder();
void
carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
std::function<void(int /*x*/, int /*y*/, int /*z*/)> on_hit);
};
} // namespace Cubed

View File

@@ -1,7 +1,5 @@
#pragma once
#include "Cubed/constants.hpp"
#include <functional>
namespace Cubed {
@@ -36,29 +34,5 @@ struct ChunkPos {
return *this;
};
};
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
inline ChunkPos get_chunk_pos(int world_x, int world_z) {
int chunk_x, chunk_z;
if (world_x < 0) {
chunk_x = (world_x + 1) / CHUNK_SIZE - 1;
}
if (world_x >= 0) {
chunk_x = world_x / CHUNK_SIZE;
}
if (world_z < 0) {
chunk_z = (world_z + 1) / CHUNK_SIZE - 1;
}
if (world_z >= 0) {
chunk_z = world_z / CHUNK_SIZE;
}
return {chunk_x, chunk_z};
}
inline float distance2(const ChunkPos& a, const ChunkPos& b) {
float dx = static_cast<float>(a.x) - b.x;
float dz = static_cast<float>(a.z) - b.z;
return dx * dx + dz * dz;
}
} // namespace Cubed

View File

@@ -1,161 +0,0 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/input/input.hpp"
#include <absl/container/flat_hash_set.h>
#include <glm/glm.hpp>
#include <optional>
#include <shared_mutex>
namespace Cubed {
class ClientWorld;
class ClientPlayer {
public:
static constexpr float WALK_SOUND_INTERVAL = 0.45f;
static constexpr float RUN_SOUND_INTERVAL = 0.3f;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
ClientPlayer(ClientWorld& world);
~ClientPlayer();
bool handle_mouse_button_event(const MouseButtonEvent& e);
bool handle_key_event(const KeyEvent& e);
bool handle_mouse_wheel_event(const MouseWheelEvent& e);
void update_front_vec(float offset_x, float offset_y);
bool update_player_move_state(Key key, KeyAction action);
bool update_scroll(float yoffset);
void update_chunk_set(const ChunkPosSet& set);
const ChunkPosSet& get_chunk_pos_set() const;
ChunkPosSet get_chunk_pos_set();
static AABB get_aabb(const glm::vec3& pos);
const glm::vec3& get_front() const;
Gait get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const;
// thread safe
glm::vec3 get_player_pos() const;
const MoveState& get_move_state() const;
void change_mode(GameMode mode);
void reload_config();
void set_player_pos(const glm::vec3& pos);
void set_place_block(unsigned id);
void update(float delta_time);
float& max_walk_speed();
float& max_run_speed();
float& max_speed();
float& acceleration();
float& deceleration();
float& g();
float& fly_y_speed();
unsigned get_current_block() const;
void set_gait(Gait gait);
GameMode& game_mode();
ClientWorld& get_world();
void set_uuid(std::string_view uuid);
std::string get_uuid() const;
const std::string& get_name() const;
void reset_key_status();
void init(std::string_view name);
float yaw() const;
float pitch() const;
float& angle();
float& walk_time();
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
bool is_underwater() const;
void set_underwater(bool u);
void place_block(float dt);
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
float m_acceleration = DEFAULT_ACCELERATION;
float m_deceleration = DEFAULT_DECELERATION;
float m_g = DEFAULT_G;
constexpr static float MAX_SPACE_ON_TIME = 0.3f;
constexpr static float PLACE_BLOCK_INTERVAL = 0.2f;
float m_place_time = PLACE_BLOCK_INTERVAL;
std::atomic<float> m_yaw = 0.0f;
std::atomic<float> m_pitch = 0.0f;
float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
float m_fly_y_speed = 7.5f;
bool can_up = true;
float space_on_time = 0.0f;
bool space_on = false;
bool is_fly = false;
float m_xz_speed = 0.0f;
unsigned m_place_block = 1;
bool m_moving = false;
bool m_sprinting = false;
bool m_underwater = false;
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
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
ChunkPos m_last_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
static constexpr glm::vec3 M_SIZE{0.6f, 1.8f, 0.6f};
std::atomic<Gait> m_gait = Gait::STOP;
MoveState m_move_state{};
MouseState m_mouse_state{};
GameMode m_game_mode = CREATIVE;
std::optional<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
mutable std::shared_mutex m_uuid_mutex;
std::string m_uuid;
ClientWorld& m_world;
float m_angle{0.0f};
float m_walk_time{0.0f};
std::unordered_map<std::string, Timer> m_timers;
mutable std::shared_mutex m_player_pos_mutex;
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
void update_direction();
void update_lookup_block();
void update_move(float delta_time);
void update_x_move(glm::vec3& player_pos);
void update_y_move(glm::vec3& player_pos);
void update_z_move(glm::vec3& player_pos);
void update_player_chunk();
void play_walk_sound(float dt);
Gait compute_gait() const;
};
} // namespace Cubed

View File

@@ -1,180 +0,0 @@
#pragma once
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/config.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/client_chunk.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/network_client.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include "Cubed/tools/priority_thread_pool.hpp"
#include <absl/container/flat_hash_set.h>
#include <deque>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
namespace Cubed {
struct PlayerInfo {
std::string name;
std::string uuid;
glm::vec3 render_pos;
glm::vec3 target_pos;
float render_yaw;
float yaw;
float render_pitch;
float pitch;
Gait gait;
float angle = 0.0f;
float walk_time = 0.0f;
float moving_time = 0.0f;
};
struct PlayerRenderData {
std::string name;
std::string uuid;
glm::vec3 render_pos;
float yaw;
float pitch;
Gait gait;
float angle;
};
class WorldScene;
class ClientWorld {
public:
ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene);
~ClientWorld();
void init(std::string_view player_name,
std::shared_ptr<NetworkClient> client);
void update(float delta_time);
bool handle_event(const Event& e);
const std::optional<LookBlock>& get_look_block_pos() const;
ClientPlayer& get_player();
const ClientPlayer& get_player() const;
int get_block(const glm::ivec3& block_pos) const;
bool is_solid(const glm::ivec3& block_pos) const;
bool can_pass_block(const glm::ivec3& block_pos) const;
BlockType get_block_tpye(const glm::ivec3& block_pos) const;
void rebuild_world();
void push_delete_vbo(std::unique_ptr<VertexBuffer>& vbo);
void push_delete_vao(std::unique_ptr<VertexArray>& vao);
// void hot_reload();
// void rebuild_world();
void report_block_change(const glm::ivec3& pos, unsigned id) const;
void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp);
void receive_remote_player(const PlayerInfoRsp& rsp);
void receive_player_logout(const LogoutRsp& rsp);
void receive_player_water_sound(const PlayerWaterSound& rsp);
void send_player_water_sound(bool underwater, const glm::vec3& pos);
int rendering_distance() const;
void rendering_distance(int rendering_distance);
int get_chunk_task_id() const;
void start_client_thread(std::string_view uuid);
void stop_client_thread();
void start_thread_pool();
void stop_thread_pool();
void change_pool_threads(int threads);
void reload_config(bool chunk_build = true);
void request_chunk();
void reset_key_status();
std::vector<glm::vec4>& planes();
const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
const std::vector<PlayerRenderData>& render_player_data() const;
std::vector<PlayerRenderData>& render_player_data();
glm::vec3 sunlight_dir() const;
bool sphere_collide_world(glm::vec3 center, float radius) const;
void receive_chunk(std::vector<uint8_t> data, PacketHeader header);
void request_exit();
bool is_receive_exit();
int chunk_size() const;
static AABB get_block_aabb(const glm::ivec3& pos);
AudioEngine& get_audio();
Config& get_config();
WorldScene& world_scene();
template <typename Fn>
void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) {
m_ticktimers.emplace(
std::piecewise_construct, std::forward_as_tuple(std::string(id)),
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
}
private:
std::atomic<bool> m_is_pending_delete_queue_free{false};
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
std::vector<std::unique_ptr<VertexBuffer>> m_pending_delete_vbo;
std::vector<std::unique_ptr<VertexArray>> m_pending_delete_vao;
enum class ChunkLoadStyle { RANDOM, CENTER };
using ChunkHashMap =
tbb::concurrent_hash_map<ChunkPos, std::shared_ptr<ClientChunk>,
ChunkPos::TBBHash>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap = std::unordered_map<std::string, PlayerInfo>;
using chunk_acc = ChunkHashMap::accessor;
using chunk_cacc = ChunkHashMap::const_accessor;
struct PendingSound {
std::string sound;
glm::vec3 sound_pos;
};
static constexpr int WORLD_EXIT_TIMEOUT = 200;
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
ClientPlayer m_player;
OtherPlayerHashMap m_player_info;
ChunkHashMap m_chunks;
AudioEngine& m_audio;
Config& m_config;
WorldScene& m_world_scene;
std::vector<glm::vec4> m_planes;
std::jthread m_client_thread;
mutable std::shared_mutex m_player_info_mutex;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
tbb::concurrent_queue<PendingSound> m_pending_sound;
std::deque<ChunkPos> m_dirty_queue;
std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
std::vector<PlayerRenderData> m_render_player_data;
tbb::concurrent_unordered_map<std::string, TickTimer> m_ticktimers;
std::unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_game_running{false};
std::atomic<bool> m_receive_exit{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<TickType> m_game_ticks{0};
std::atomic<TickType> m_day_tick{6000};
std::atomic<bool> m_requesting_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<int> m_chunk_task_id{0};
std::shared_ptr<NetworkClient> m_client;
ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER};
std::atomic<std::shared_ptr<PriorityThreadPool>> m_thread_pool;
Random m_random;
void client_run(std::stop_token token);
void report_player_info();
void set_block(const glm::ivec3& pos, unsigned id);
void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now);
};
} // namespace Cubed

View File

@@ -1,75 +1,10 @@
#pragma once
// Prevent unsigned underflow issues in subtraction
#include "Cubed/tools/cubed_assert.hpp"
#include <functional>
#include <utility>
namespace Cubed {
using TickType = long long;
using TimeType = float;
constexpr int DEFAULT_PER_TICK_TIME = 50;
constexpr TickType DAY_TIME = 24000;
constexpr TickType PER_HOUR = 1000;
class TickTimer {
public:
template <typename Fn>
TickTimer(TickType threshold, Fn&& f)
: m_fn(std::forward<Fn>(f)), m_threshold(threshold) {
ASSERT_MSG(threshold > 0, "Threshold Must Rreater Than 0");
}
bool update() {
if (++m_current >= m_threshold) {
m_current = 0;
m_fn();
return true;
}
return false;
}
void reset() { m_current = 0; }
private:
std::function<void()> m_fn;
TickType m_threshold;
TickType m_current = 0;
};
class Timer {
public:
template <typename Fn>
Timer(TimeType threshold, Fn&& f)
: m_fn(std::forward<Fn>(f)), m_threshold(threshold) {
ASSERT_MSG(threshold > 0, "Threshold Must Rreater Than 0");
}
bool update(TimeType dt) {
m_current += dt;
bool triggered = false;
while (m_current >= m_threshold) {
m_current -= m_threshold;
m_fn();
triggered = true;
}
return triggered;
}
void reset() { m_current = 0.0f; }
void set_threshold(TimeType threshold) {
ASSERT_MSG(threshold > 0, "Threshold must be greater than 0");
m_threshold = threshold;
}
private:
std::function<void()> m_fn;
TimeType m_threshold;
TimeType m_current = 0;
};
} // namespace Cubed

View File

@@ -1,70 +0,0 @@
#pragma once
#include "Cubed/gameplay/packet.hpp"
#include <asio.hpp>
#include <queue>
#include <string>
#include <thread>
namespace Cubed {
using asio::ip::tcp;
class ClientWorld;
class NetworkClient : public std::enable_shared_from_this<NetworkClient> {
public:
NetworkClient(ClientWorld& world);
~NetworkClient();
void close();
void stop();
void send(Packet packet, int priority = 10);
void start(std::string ip, int port = 25530);
bool is_connected() const;
bool is_connect_error() const;
std::string get_error_string() const;
void clear_error();
private:
struct Task {
int priority = 10;
std::uint64_t sequence = 0;
Packet packet;
Task(int p, std::uint64_t seq, Packet pac)
: priority(p), sequence(seq), packet(std::move(pac)) {}
};
struct TaskCompare {
bool operator()(const Task& a, const Task& b) const {
if (a.priority != b.priority) {
return a.priority > b.priority;
}
return a.sequence > b.sequence;
}
};
asio::io_context m_io;
std::thread m_net_thread;
static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024;
tcp::socket m_socket;
std::vector<char> m_read_buffer;
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
asio::strand<asio::io_context::executor_type> m_strand;
std::atomic<bool> m_closed{false};
std::atomic<bool> m_connected{false};
std::atomic<bool> m_connect_error{false};
mutable std::mutex m_error_string_mutex;
std::string m_error_string;
// ClientWorld is managed by App
ClientWorld& m_world;
std::atomic_uint64_t m_sequence{0};
asio::awaitable<void> connect(std::string ip, int port);
asio::awaitable<void> read_loop();
void do_write();
void set_error(std::string_view error);
};
} // namespace Cubed

View File

@@ -1,36 +0,0 @@
#pragma once
#include "Cubed/config.hpp"
#include "Cubed/gameplay/server_world.hpp"
#include "Cubed/gameplay/session.hpp"
#include <asio.hpp>
#include <thread>
namespace Cubed {
class NetworkServer {
public:
explicit NetworkServer();
~NetworkServer();
void stop();
// Run in another thread after initialization is complete
void start_server(int port);
void start_server();
int port() const;
ServerWorld& server_world();
private:
Config m_config;
asio::io_context m_io;
std::thread m_net_thread;
int m_port = 25530;
std::atomic<bool> m_stopped{false};
std::atomic<bool> m_started{false};
ServerWorld m_world;
std::mutex m_session_mutex;
std::unordered_map<std::string, std::shared_ptr<Session>> m_session;
asio::awaitable<void> listen();
void net_run();
};
} // namespace Cubed

View File

@@ -1,220 +0,0 @@
#pragma once
#include "Cubed/tools/compression.hpp"
#include "packet.pb.h" // IWYU pragma: keep
#include <concepts>
#include <cstdint>
#include <cstring>
#include <memory>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
#include <span>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace Cubed {
constexpr size_t HEADER_LEN =
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t);
constexpr size_t PACKET_COMPRESSION_THRESHOLD = 100;
using Packet = std::shared_ptr<std::vector<uint8_t>>;
enum class CompressType : uint16_t {
NONE = 0,
ZSTD = 1,
};
inline CompressType get_compress_type(uint16_t id) {
using enum CompressType;
switch (id) {
case std::to_underlying(NONE):
return NONE;
case std::to_underlying(ZSTD):
return ZSTD;
}
throw std::runtime_error(std::format("Unknown CompressType {}", id));
}
struct PacketHeader {
uint16_t cmd{};
CompressType compress_type{}; // 0=none 1=zlib
uint32_t uncompressed_size{};
uint32_t compressed_size{};
};
enum class PacketEnum : uint16_t {
LOGIN_REQ = 1001,
LOGIN_RSP = 1002,
LOGOUT_REQ = 1003,
LOGOUT_RSP = 1004,
PLAYER_INFO = 2001,
C2S_PLAYER_INFO = 2002,
PLAYER_INFO_RSP = 2003,
PLAYER_WATER_SOUND = 2004,
CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003,
BLOCK_CHANGE_RSP = 3004,
S2C_CLEAR_ALL_CHUNKS = 3005,
UPDATE_TIME = 3006,
PING = 9001,
PONG = 9002
};
template <typename> struct always_false : std::false_type {}; // NOLINT
template <typename T> constexpr uint16_t get_packet_id() {
static_assert(always_false<T>::value, "Unknown Type");
return 0;
}
template <> constexpr uint16_t get_packet_id<LoginReq>() {
return std::to_underlying(PacketEnum::LOGIN_REQ);
}
template <> constexpr uint16_t get_packet_id<LoginRsp>() {
return std::to_underlying(PacketEnum::LOGIN_RSP);
}
template <> constexpr uint16_t get_packet_id<LogoutReq>() {
return std::to_underlying(PacketEnum::LOGOUT_REQ);
}
template <> constexpr uint16_t get_packet_id<LogoutRsp>() {
return std::to_underlying(PacketEnum::LOGOUT_RSP);
}
template <> constexpr uint16_t get_packet_id<PlayerInfo>() {
return std::to_underlying(PacketEnum::PLAYER_INFO);
}
template <> constexpr uint16_t get_packet_id<C2S_PlayerInfo>() {
return std::to_underlying(PacketEnum::C2S_PLAYER_INFO);
}
template <> constexpr uint16_t get_packet_id<PlayerInfoRsp>() {
return std::to_underlying(PacketEnum::PLAYER_INFO_RSP);
}
template <> constexpr uint16_t get_packet_id<ChunkDataReq>() {
return std::to_underlying(PacketEnum::CHUNK_DATA_REQ);
}
template <> constexpr uint16_t get_packet_id<ChunkDataRsp>() {
return std::to_underlying(PacketEnum::CHUNK_DATA_RSP);
}
template <> constexpr uint16_t get_packet_id<BlockChangeReq>() {
return std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ);
}
template <> constexpr uint16_t get_packet_id<BlockChangeRsp>() {
return std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP);
}
template <> constexpr uint16_t get_packet_id<S2C_ClearAllChunks>() {
return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS);
}
template <> constexpr uint16_t get_packet_id<UpdateTime>() {
return std::to_underlying(PacketEnum::UPDATE_TIME);
}
template <> constexpr uint16_t get_packet_id<Ping>() {
return std::to_underlying(PacketEnum::PING);
}
template <> constexpr uint16_t get_packet_id<Pong>() {
return std::to_underlying(PacketEnum::PONG);
}
template <> constexpr uint16_t get_packet_id<PlayerWaterSound>() {
return std::to_underlying(PacketEnum::PLAYER_WATER_SOUND);
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>
Packet make_packet(const T& msg) {
PacketHeader header{};
header.cmd = get_packet_id<T>();
uint32_t raw_size = static_cast<uint32_t>(msg.ByteSizeLong());
std::vector<uint8_t> raw(raw_size);
if (!msg.SerializeToArray(raw.data(), raw_size)) {
return {};
}
std::vector<uint8_t> payload;
if (raw_size >= PACKET_COMPRESSION_THRESHOLD) {
std::vector<uint8_t> compressed = compress_data(raw);
if (compressed.size() < raw.size()) {
payload = std::move(compressed);
header.compress_type = CompressType::ZSTD;
} else {
payload = std::move(raw);
header.compress_type = CompressType::NONE;
}
} else {
payload = std::move(raw);
header.compress_type = CompressType::NONE;
}
header.uncompressed_size = raw_size;
header.compressed_size = static_cast<uint32_t>(payload.size());
auto packet =
std::make_shared<std::vector<uint8_t>>(HEADER_LEN + payload.size());
uint16_t cmd_net = htons(header.cmd);
uint16_t compress_type_net =
htons(std::to_underlying(header.compress_type));
uint32_t uncompressed_size_net = htonl(header.uncompressed_size);
uint32_t compressed_size_net = htonl(header.compressed_size);
std::memcpy(packet->data(), &cmd_net, sizeof(cmd_net));
std::memcpy(packet->data() + 2, &compress_type_net,
sizeof(compress_type_net));
std::memcpy(packet->data() + 4, &uncompressed_size_net,
sizeof(uncompressed_size_net));
std::memcpy(packet->data() + 8, &compressed_size_net,
sizeof(compressed_size_net));
std::memcpy(packet->data() + HEADER_LEN, payload.data(), payload.size());
return packet;
}
inline PacketHeader decode_packet_header(std::span<const uint8_t> header) {
if (header.size() < HEADER_LEN)
throw std::runtime_error("Invalid header");
uint16_t cmd_net;
uint16_t compress_type_net;
uint32_t uncompressed_size_net;
uint32_t compressed_size_net;
std::memcpy(&cmd_net, header.data(), sizeof(cmd_net));
std::memcpy(&compress_type_net, header.data() + 2,
sizeof(compress_type_net));
std::memcpy(&uncompressed_size_net, header.data() + 4,
sizeof(uncompressed_size_net));
std::memcpy(&compressed_size_net, header.data() + 8,
sizeof(compressed_size_net));
return {ntohs(cmd_net), get_compress_type(ntohs(compress_type_net)),
ntohl(uncompressed_size_net), ntohl(compressed_size_net)};
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>
bool decode_packet(T& message, std::span<const uint8_t> data,
const PacketHeader& header) {
if (data.size() != header.compressed_size) {
return false;
}
if (header.compress_type == CompressType::NONE &&
header.uncompressed_size != header.compressed_size) {
return false;
}
switch (header.compress_type) {
case CompressType::NONE: {
return message.ParseFromArray(
data.data(), static_cast<int>(header.uncompressed_size));
}
case CompressType::ZSTD: {
auto raw = decompress_data(data, header.uncompressed_size);
return message.ParseFromArray(raw.data(), static_cast<int>(raw.size()));
}
default:
return false;
}
}
} // namespace Cubed

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