mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
Compare commits
7 Commits
35f0271fd4
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a4081cf9c | ||
|
|
0f75144b2f | ||
|
|
30f843ba6b | ||
|
|
d5a12869e6 | ||
|
|
97993b72fe | ||
|
|
7ecdab08fc | ||
|
|
7ffc349eb3 |
18
.github/workflows/format-check.yml
vendored
18
.github/workflows/format-check.yml
vendored
@@ -1,4 +1,3 @@
|
||||
# .github/workflows/format-check.yml
|
||||
name: Code Format Check
|
||||
|
||||
on: [push, pull_request]
|
||||
@@ -9,9 +8,22 @@ 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 '*.h' -print0 | xargs -0 clang-format --dry-run --Werror
|
||||
find src include \
|
||||
\( -name '*.cpp' -o -name '*.hpp' \) \
|
||||
-print0 | xargs -0 clang-format-22 --dry-run --Werror
|
||||
154
.github/workflows/release-build.yml
vendored
Normal file
154
.github/workflows/release-build.yml
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
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 }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,4 +42,5 @@ CMakeError.log
|
||||
.DS_Store
|
||||
assets/config.toml
|
||||
.venv/
|
||||
pyout/
|
||||
pyout/
|
||||
vcpkg_installed/
|
||||
287
CMakeLists.txt
287
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.14...3.24)
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(Cubed LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
@@ -6,167 +6,118 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
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")
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
|
||||
)
|
||||
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)
|
||||
|
||||
include(Dependencies)
|
||||
|
||||
add_subdirectory(third_party/glad)
|
||||
|
||||
add_subdirectory(third_party/imgui)
|
||||
|
||||
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
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
|
||||
add_subdirectory(src)
|
||||
|
||||
file(GLOB_RECURSE PROTO_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
|
||||
protobuf_generate(
|
||||
TARGET ${PROJECT_NAME}
|
||||
LANGUAGE cpp
|
||||
PROTOS ${PROTO_FILES}
|
||||
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
#-fsanitize=address
|
||||
#-fsanitize=thread
|
||||
-fno-omit-frame-pointer
|
||||
-g
|
||||
)
|
||||
configure_file(
|
||||
src/version.hpp.in
|
||||
${CMAKE_BINARY_DIR}/generated/version.hpp
|
||||
@ONLY
|
||||
)
|
||||
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
#-fsanitize=address
|
||||
#-fsanitize=thread
|
||||
)
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
$<$<CONFIG:Debug>:-fno-omit-frame-pointer>
|
||||
$<$<CONFIG:Debug>:-g>
|
||||
#$<$<CONFIG:Debug>:-fsanitize=address>
|
||||
#$<$<CONFIG:Debug>:-fsanitize=thread>
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_MODE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/"
|
||||
$<$<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/>"
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
ASSETS_PATH="./assets/"
|
||||
)
|
||||
# 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
|
||||
ASSETS_PATH="./assets/"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIR})
|
||||
|
||||
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_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
@@ -179,42 +130,24 @@ target_link_libraries(${PROJECT_NAME}
|
||||
tomlplusplus::tomlplusplus
|
||||
imgui
|
||||
tbb
|
||||
protobuf::libprotobuf
|
||||
absl::log
|
||||
absl::check
|
||||
absl::base
|
||||
absl::strings
|
||||
absl::flat_hash_map
|
||||
zstd::zstd
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
|
||||
)
|
||||
|
||||
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}
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
@@ -25,254 +25,12 @@ uniform float minRadius;
|
||||
uniform float maxRadius;
|
||||
uniform bool enablePBR;
|
||||
uniform bool flipY;
|
||||
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)
|
||||
);
|
||||
uniform int renderDistance;
|
||||
uniform vec3 skyColor;
|
||||
|
||||
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);
|
||||
}
|
||||
#include "shadow.glsl"
|
||||
#include "normal.glsl"
|
||||
|
||||
void main(void) {
|
||||
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
|
||||
@@ -341,8 +99,17 @@ void main(void) {
|
||||
vec3 specular = spec * sunlightColor * specularStrength;
|
||||
|
||||
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
|
||||
|
||||
|
||||
// fog
|
||||
float dist = length(cameraPos - vert_pos);
|
||||
vec4 fogColor = vec4(skyColor, 1.0);
|
||||
float fogStart = renderDistance * 16 * 0.9;
|
||||
float fogEnd = renderDistance * 16;
|
||||
|
||||
float fogFactor = smoothstep(fogEnd, fogStart, dist);
|
||||
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb + (1.0-shadow) * specular * objectColor.rgb, objectColor.a);
|
||||
|
||||
color = mix(fogColor, color, fogFactor);
|
||||
//color = vec4(normal * 0.5 + 0.5, 1.0);
|
||||
//color = vec4(tangent * 0.5 + 0.5, 1.0);;
|
||||
//color = vec4(norm * 0.5 + 0.5, 1.0);
|
||||
|
||||
46
assets/shaders/compute_sky_color.glsl
Normal file
46
assets/shaders/compute_sky_color.glsl
Normal file
@@ -0,0 +1,46 @@
|
||||
#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;
|
||||
}
|
||||
7
assets/shaders/depth_player_fragment_shader.glsl
Normal file
7
assets/shaders/depth_player_fragment_shader.glsl
Normal file
@@ -0,0 +1,7 @@
|
||||
#version 460
|
||||
|
||||
in vec2 tc;
|
||||
|
||||
void main() {
|
||||
|
||||
}
|
||||
15
assets/shaders/depth_player_shader.glsl
Normal file
15
assets/shaders/depth_player_shader.glsl
Normal file
@@ -0,0 +1,15 @@
|
||||
#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);
|
||||
}
|
||||
25
assets/shaders/noise.glsl
Normal file
25
assets/shaders/noise.glsl
Normal file
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
}
|
||||
10
assets/shaders/normal.glsl
Normal file
10
assets/shaders/normal.glsl
Normal file
@@ -0,0 +1,10 @@
|
||||
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);
|
||||
}
|
||||
47
assets/shaders/player_f_shader.glsl
Normal file
47
assets/shaders/player_f_shader.glsl
Normal file
@@ -0,0 +1,47 @@
|
||||
#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);
|
||||
}
|
||||
27
assets/shaders/player_v_shader.glsl
Normal file
27
assets/shaders/player_v_shader.glsl
Normal file
@@ -0,0 +1,27 @@
|
||||
#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;
|
||||
}
|
||||
238
assets/shaders/shadow.glsl
Normal file
238
assets/shaders/shadow.glsl
Normal file
@@ -0,0 +1,238 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -19,84 +19,12 @@ uniform float cloudThresholdHigh;
|
||||
|
||||
uniform float time;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
#include "compute_sky_color.glsl"
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -19,33 +19,7 @@ uniform vec3 sunDir;
|
||||
uniform vec3 sunColor;
|
||||
uniform float waterDensity;
|
||||
|
||||
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;
|
||||
}
|
||||
#include "noise.glsl"
|
||||
|
||||
float getCausticValue(float x, float y, float z) {
|
||||
float w = 8.0;
|
||||
|
||||
@@ -22,7 +22,6 @@ uniform float ambientStrength;
|
||||
uniform vec3 sunlightColor;
|
||||
uniform vec3 ambientColor;
|
||||
uniform vec3 sunlightDir;
|
||||
uniform vec3 cameraPos;
|
||||
uniform bool shader_on;
|
||||
uniform float specularStrength;
|
||||
|
||||
@@ -43,82 +42,15 @@ 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) {
|
||||
|
||||
BIN
assets/texture/skin/player001.png
Normal file
BIN
assets/texture/skin/player001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 535 B |
79
cmake/Dependencies.cmake
Normal file
79
cmake/Dependencies.cmake
Normal file
@@ -0,0 +1,79 @@
|
||||
include(FetchContent)
|
||||
|
||||
# System packages
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_package(absl REQUIRED)
|
||||
find_package(zstd REQUIRED)
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Freetype REQUIRED)
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
22
cmake/modules/Findzstd.cmake
Normal file
22
cmake/modules/Findzstd.cmake
Normal file
@@ -0,0 +1,22 @@
|
||||
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()
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/gameplay/network_server.hpp"
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "Cubed/camera.hpp"
|
||||
#include "Cubed/dev_panel.hpp"
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/renderer.hpp"
|
||||
#include "Cubed/texture_manager.hpp"
|
||||
#include "Cubed/window.hpp"
|
||||
@@ -10,6 +12,13 @@ namespace Cubed {
|
||||
|
||||
class App {
|
||||
public:
|
||||
struct Argument {
|
||||
bool is_client = false;
|
||||
int port = 25530;
|
||||
std::string ip{"127.0.0.1"};
|
||||
std::string player{"Unknown"};
|
||||
};
|
||||
|
||||
App();
|
||||
~App();
|
||||
static void cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
@@ -36,14 +45,20 @@ public:
|
||||
Renderer& renderer();
|
||||
TextureManager& texture_manager();
|
||||
Window& window();
|
||||
World& world();
|
||||
ClientWorld& client_world();
|
||||
ServerWorld& server_world();
|
||||
const Argument& argument() const;
|
||||
|
||||
private:
|
||||
Camera m_camera;
|
||||
TextureManager m_texture_manager;
|
||||
World m_world;
|
||||
NetworkServer m_server;
|
||||
std::shared_ptr<NetworkClient> m_client;
|
||||
ClientWorld m_client_world;
|
||||
|
||||
DevPanel m_dev_panel{*this};
|
||||
Renderer m_renderer{m_camera, m_world, m_texture_manager, m_dev_panel};
|
||||
Renderer m_renderer{m_camera, m_client_world, m_texture_manager,
|
||||
m_dev_panel};
|
||||
|
||||
Window m_window{m_renderer};
|
||||
|
||||
@@ -53,9 +68,10 @@ private:
|
||||
inline static double fps_time_count = 0.0f;
|
||||
inline static int frame_count = 0;
|
||||
inline static int fps = 0;
|
||||
|
||||
void init();
|
||||
|
||||
Argument m_argument;
|
||||
void init(int argc, char** argv);
|
||||
void handle_argument(int argc, char** argv);
|
||||
void handle_toml();
|
||||
auto init_camera();
|
||||
auto init_texture();
|
||||
auto init_world();
|
||||
|
||||
@@ -8,22 +8,32 @@
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
class Player;
|
||||
class ClientPlayer;
|
||||
|
||||
class Camera {
|
||||
private:
|
||||
enum class Perspective {
|
||||
FIRST_PERSON,
|
||||
THIRD_PERSON_BACK,
|
||||
THIRD_PERSON_FRONT,
|
||||
};
|
||||
|
||||
bool m_firse_mouse = true;
|
||||
Player* m_player;
|
||||
ClientPlayer* 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);
|
||||
|
||||
public:
|
||||
Camera();
|
||||
|
||||
void update_move_camera();
|
||||
|
||||
void camera_init(Player* player);
|
||||
void camera_init(ClientPlayer* player);
|
||||
void hot_reload();
|
||||
void reset_camera();
|
||||
void update_cursor_position_camera(double xpos, double ypos);
|
||||
@@ -33,6 +43,8 @@ public:
|
||||
|
||||
bool is_under_water() const;
|
||||
glm::vec3 get_camera_front() const;
|
||||
void change_perspective();
|
||||
bool is_first_person() const;
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include "Cubed/tools/toml.utils.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:
|
||||
Config();
|
||||
@@ -24,7 +16,7 @@ public:
|
||||
void load_or_create_config();
|
||||
void save_to_file();
|
||||
|
||||
template <TomlValueType T> T get(std::string_view key) const {
|
||||
template <TOML::TomlValueType T> T get(std::string_view key) const {
|
||||
size_t cur = 0;
|
||||
auto pos = key.find('.');
|
||||
const toml::table* table = &m_tbl;
|
||||
@@ -61,7 +53,7 @@ public:
|
||||
}
|
||||
}
|
||||
template <typename T> void set(std::string_view key, T&& val) {
|
||||
if constexpr (!TomlValueType<std::decay_t<T>>) {
|
||||
if constexpr (!TOML::TomlValueType<std::decay_t<T>>) {
|
||||
static_assert(false, "Type Not Support");
|
||||
}
|
||||
size_t cur = 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
|
||||
#include <array>
|
||||
namespace Cubed {
|
||||
@@ -26,9 +25,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 ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
|
||||
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
|
||||
constexpr float DEFAULT_CAVE_PROBABILITY = 0.035f;
|
||||
|
||||
using HeightMapArray = std::array<std::array<int, CHUNK_SIZE>, CHUNK_SIZE>;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace Cubed {
|
||||
|
||||
class App;
|
||||
class Player;
|
||||
class ClientPlayer;
|
||||
class DevPanel {
|
||||
struct ConfigView {
|
||||
float fov = 70.0f;
|
||||
@@ -26,9 +26,6 @@ class DevPanel {
|
||||
int gait = 0;
|
||||
float pos[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
struct TextEditing {
|
||||
bool perlin_seed = false;
|
||||
};
|
||||
|
||||
public:
|
||||
DevPanel(App& app);
|
||||
@@ -38,9 +35,8 @@ public:
|
||||
private:
|
||||
App& m_app;
|
||||
ConfigView m_config;
|
||||
Player* m_player;
|
||||
ClientPlayer* 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;
|
||||
@@ -48,13 +44,18 @@ private:
|
||||
int m_pre_set_tick_speed = 1;
|
||||
bool m_tick_frezze = false;
|
||||
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();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Cubed {
|
||||
@@ -77,4 +80,32 @@ 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
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
#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;
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/cave_path.hpp"
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/path.hpp"
|
||||
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
|
||||
namespace Cubed {
|
||||
class CaveCarver {
|
||||
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
|
||||
|
||||
public:
|
||||
CaveCarver();
|
||||
CaveHashMap& paths();
|
||||
|
||||
void init(unsigned world_seed);
|
||||
void reload(unsigned world_seed);
|
||||
void add_path(const glm::vec3& pos, unsigned chunk_seed);
|
||||
void try_to_add_path(const ChunkPos& pos, unsigned chunk_seed);
|
||||
void cleanup_finished_caves();
|
||||
|
||||
int cave_sum() const;
|
||||
float& cave_probability();
|
||||
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;
|
||||
|
||||
private:
|
||||
CaveHashMap m_paths;
|
||||
unsigned m_seed = 0;
|
||||
Random m_random;
|
||||
float m_cave_probability = 0.035f;
|
||||
std::atomic<unsigned> m_world_seed{0};
|
||||
std::atomic<float> m_cave_probability{DEFAULT_CAVE_PROBABILITY};
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/path_point.hpp"
|
||||
#include "Cubed/tools/cubed_random.hpp"
|
||||
|
||||
@@ -9,15 +8,11 @@
|
||||
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();
|
||||
@@ -27,6 +22,7 @@ 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;
|
||||
@@ -37,18 +33,16 @@ 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
|
||||
@@ -4,7 +4,6 @@
|
||||
#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>
|
||||
@@ -12,11 +11,11 @@
|
||||
#include <optional>
|
||||
namespace Cubed {
|
||||
|
||||
class Chunk;
|
||||
class ServerChunk;
|
||||
|
||||
class ChunkGenerator {
|
||||
public:
|
||||
ChunkGenerator(Chunk& chunk);
|
||||
ChunkGenerator(ServerChunk& chunk);
|
||||
|
||||
static void init();
|
||||
static void reload();
|
||||
@@ -27,7 +26,7 @@ public:
|
||||
void assign_chunk_biome();
|
||||
// Adjust Biome
|
||||
void resolve_biome_adjacency_conflict(
|
||||
const std::array<const Chunk*, 8>& adj_chunks);
|
||||
const std::array<const ServerChunk*, 8>& adj_chunks);
|
||||
// Generate Heightmap
|
||||
void generate_heightmap();
|
||||
// Adjust Height
|
||||
@@ -43,7 +42,7 @@ public:
|
||||
// Generate Structure
|
||||
void generate_vegetation();
|
||||
BiomeType get_biome_at(float world_x, float world_z);
|
||||
Chunk& chunk();
|
||||
ServerChunk& chunk();
|
||||
Random& random();
|
||||
const std::array<BiomeType, 8>& neighbor_biome() const;
|
||||
void ocean_build();
|
||||
@@ -54,7 +53,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};
|
||||
Chunk& m_chunk;
|
||||
ServerChunk& m_chunk;
|
||||
Random m_random;
|
||||
std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr};
|
||||
bool is_cur_chunk_ins = false;
|
||||
@@ -62,9 +61,6 @@ 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
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Cubed {
|
||||
@@ -34,5 +36,29 @@ 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
|
||||
@@ -1,66 +1,42 @@
|
||||
#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 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<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);
|
||||
|
||||
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:
|
||||
Chunk(World& world, ChunkPos chunk_pos);
|
||||
~Chunk();
|
||||
Chunk(const Chunk&) = delete;
|
||||
Chunk& operator=(const Chunk&) = delete;
|
||||
Chunk(Chunk&&) noexcept;
|
||||
Chunk& operator=(Chunk&&) noexcept;
|
||||
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);
|
||||
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z);
|
||||
@@ -73,33 +49,9 @@ public:
|
||||
BiomeType get_biome() const;
|
||||
ChunkPos get_chunk_pos() const;
|
||||
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||
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 receive_chunk(const ChunkDataRsp& data);
|
||||
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;
|
||||
@@ -124,15 +76,58 @@ public:
|
||||
void need_upload();
|
||||
|
||||
void set_chunk_block(int index, unsigned id);
|
||||
|
||||
bool is_temp_chunk() const;
|
||||
ChunkPos chunk_pos() const;
|
||||
BiomeType biome() const;
|
||||
void biome(BiomeType b);
|
||||
HeightMapArray& heightmap();
|
||||
std::vector<BlockType>& blocks();
|
||||
World& world();
|
||||
ClientWorld& world();
|
||||
unsigned seed() const;
|
||||
BiomeConditions& conditions();
|
||||
};
|
||||
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);
|
||||
};
|
||||
} // namespace Cubed
|
||||
139
include/Cubed/gameplay/client_player.hpp
Normal file
139
include/Cubed/gameplay/client_player.hpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#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/player.hpp"
|
||||
#include "Cubed/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:
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
ClientPlayer(ClientWorld& world);
|
||||
~ClientPlayer();
|
||||
|
||||
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 hot_reload();
|
||||
void set_player_pos(const glm::vec3& pos);
|
||||
void set_place_block(unsigned id);
|
||||
void update(float delta_time);
|
||||
void update_front_vec(float offset_x, float offset_y);
|
||||
void update_player_move_state(int key, int action);
|
||||
void update_scroll(double yoffset);
|
||||
|
||||
float& max_walk_speed();
|
||||
float& max_run_speed();
|
||||
float& max_speed();
|
||||
float& acceleration();
|
||||
float& deceleration();
|
||||
float& g();
|
||||
float& fly_y_speed();
|
||||
|
||||
unsigned place_block() const;
|
||||
|
||||
void set_gait(Gait gait);
|
||||
GameMode& game_mode();
|
||||
|
||||
const ClientWorld& get_world() const;
|
||||
|
||||
void set_uuid(std::string_view uuid);
|
||||
std::string get_uuid() const;
|
||||
const std::string& get_name() const;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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{};
|
||||
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};
|
||||
|
||||
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();
|
||||
Gait compute_gait() const;
|
||||
};
|
||||
} // namespace Cubed
|
||||
153
include/Cubed/gameplay/client_world.hpp
Normal file
153
include/Cubed/gameplay/client_world.hpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
#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/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;
|
||||
};
|
||||
|
||||
struct PlayerRenderData {
|
||||
std::string name;
|
||||
std::string uuid;
|
||||
glm::vec3 render_pos;
|
||||
float yaw;
|
||||
float pitch;
|
||||
Gait gait;
|
||||
float angle;
|
||||
};
|
||||
|
||||
class ClientWorld {
|
||||
public:
|
||||
ClientWorld();
|
||||
~ClientWorld();
|
||||
void init(std::string_view player_name,
|
||||
std::shared_ptr<NetworkClient> client);
|
||||
void update(float delta_time);
|
||||
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(GLuint vbo);
|
||||
void push_delete_vao(GLuint 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);
|
||||
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 hot_reload();
|
||||
void request_chunk();
|
||||
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);
|
||||
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::string(id)),
|
||||
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
|
||||
}
|
||||
|
||||
private:
|
||||
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;
|
||||
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;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
std::jthread m_client_thread;
|
||||
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_mutex;
|
||||
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;
|
||||
|
||||
std::vector<GLuint> m_pending_delete_vbo;
|
||||
std::vector<GLuint> m_pending_delete_vao;
|
||||
|
||||
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, 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;
|
||||
|
||||
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
|
||||
@@ -1,10 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
// Prevent unsigned underflow issues in subtraction
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
namespace Cubed {
|
||||
using TickType = long long;
|
||||
|
||||
constexpr int DEFAULT_PER_TICK_TIME = 50;
|
||||
|
||||
constexpr TickType DAY_TIME = 24000;
|
||||
|
||||
constexpr TickType PER_HOUR = 1000;
|
||||
constexpr TickType PER_HOUR = 1000;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
template <typename Fn>
|
||||
Timer(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;
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
65
include/Cubed/gameplay/network_client.hpp
Normal file
65
include/Cubed/gameplay/network_client.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#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;
|
||||
|
||||
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};
|
||||
// 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();
|
||||
};
|
||||
} // namespace Cubed
|
||||
33
include/Cubed/gameplay/network_server.hpp
Normal file
33
include/Cubed/gameplay/network_server.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <thread>
|
||||
namespace Cubed {
|
||||
|
||||
class NetworkServer {
|
||||
public:
|
||||
NetworkServer(int port = 25530);
|
||||
~NetworkServer();
|
||||
void stop();
|
||||
|
||||
// Run in another thread after initialization is complete
|
||||
void start_server(int port = 25530);
|
||||
|
||||
int port() const;
|
||||
ServerWorld& server_world();
|
||||
|
||||
private:
|
||||
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
|
||||
216
include/Cubed/gameplay/packet.hpp
Normal file
216
include/Cubed/gameplay/packet.hpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#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,
|
||||
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 <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
|
||||
8
include/Cubed/gameplay/path.hpp
Normal file
8
include/Cubed/gameplay/path.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
struct PathOrigin {
|
||||
bool exists;
|
||||
glm::vec3 pos;
|
||||
unsigned seed;
|
||||
};
|
||||
@@ -1,110 +1,21 @@
|
||||
#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/input.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
namespace Cubed {
|
||||
enum class Gait { STOP = 0, WALK = 1, RUN = 2 };
|
||||
constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); }
|
||||
|
||||
enum class Gait { WALK = 0, RUN };
|
||||
|
||||
class World;
|
||||
|
||||
class Player {
|
||||
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;
|
||||
|
||||
float m_yaw = 0.0f;
|
||||
float m_pitch = 0.0f;
|
||||
|
||||
float m_sensitivity = 0.15f;
|
||||
|
||||
float m_max_speed = m_max_walk_speed;
|
||||
float m_y_speed = 0.0f;
|
||||
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;
|
||||
|
||||
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_player_chunk_pos{0, 0};
|
||||
|
||||
glm::vec3 m_front{0, 0, -1};
|
||||
glm::vec3 m_right{0, 0, 0};
|
||||
glm::vec3 m_size{0.6f, 1.8f, 0.6f};
|
||||
|
||||
Gait m_gait = Gait::WALK;
|
||||
MoveState m_move_state{};
|
||||
GameMode m_game_mode = CREATIVE;
|
||||
std::optional<LookBlock> m_look_block = std::nullopt;
|
||||
std::string m_name{};
|
||||
World& m_world;
|
||||
|
||||
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
|
||||
glm::ivec3& block_pos, glm::vec3& normal,
|
||||
float distance = 4.0f);
|
||||
|
||||
void check_player_chunk_transition();
|
||||
void update_direction();
|
||||
void update_lookup_block();
|
||||
void update_move(float delta_time);
|
||||
void update_x_move();
|
||||
void update_y_move();
|
||||
void update_z_move();
|
||||
|
||||
public:
|
||||
Player(World& world, const std::string& name);
|
||||
~Player();
|
||||
AABB get_aabb() const;
|
||||
const glm::vec3& get_front() const;
|
||||
const Gait& get_gait() const;
|
||||
const std::optional<LookBlock>& get_look_block_pos() const;
|
||||
const glm::vec3& get_player_pos() const;
|
||||
const MoveState& get_move_state() const;
|
||||
|
||||
void change_mode(GameMode mode);
|
||||
void hot_reload();
|
||||
void set_player_pos(const glm::vec3& pos);
|
||||
void set_place_block(unsigned id);
|
||||
void update(float delta_time);
|
||||
void update_front_vec(float offset_x, float offset_y);
|
||||
void update_player_move_state(int key, int action);
|
||||
void update_scroll(double yoffset);
|
||||
|
||||
float& max_walk_speed();
|
||||
float& max_run_speed();
|
||||
float& max_speed();
|
||||
float& acceleration();
|
||||
float& deceleration();
|
||||
float& g();
|
||||
|
||||
unsigned place_block() const;
|
||||
|
||||
Gait& gait();
|
||||
GameMode& game_mode();
|
||||
const World& get_world() const;
|
||||
};
|
||||
inline Gait get_gait_from_id(int id) {
|
||||
switch (id) {
|
||||
case std::to_underlying(Gait::STOP):
|
||||
return Gait::STOP;
|
||||
case std::to_underlying(Gait::WALK):
|
||||
return Gait::WALK;
|
||||
case std::to_underlying(Gait::RUN):
|
||||
return Gait::RUN;
|
||||
default:
|
||||
throw std::runtime_error("Unknown Gait");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/path_point.hpp"
|
||||
#include "Cubed/tools/cubed_random.hpp"
|
||||
|
||||
@@ -9,15 +8,11 @@
|
||||
|
||||
namespace Cubed {
|
||||
class RiverPath {
|
||||
using ChunkPosSet =
|
||||
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
|
||||
|
||||
public:
|
||||
RiverPath(unsigned int chunk_seed, unsigned world_seed,
|
||||
const glm::vec3& start_pos);
|
||||
const std::vector<PathPoint>& points() const;
|
||||
void clear_chunk(const ChunkPos& pos);
|
||||
bool is_finished() const;
|
||||
|
||||
static float& radius_xz_min();
|
||||
static float& radius_xz_max();
|
||||
@@ -27,6 +22,7 @@ public:
|
||||
static float& delta_angle_max();
|
||||
static int& step_min();
|
||||
static int& step_max();
|
||||
static float step_len();
|
||||
|
||||
private:
|
||||
static inline float m_radius_xz_min = 5.0f;
|
||||
@@ -37,19 +33,16 @@ private:
|
||||
static inline float m_delta_angle_max = 3.0f;
|
||||
static inline int m_step_min = 200;
|
||||
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_initial_yaw = 0.0f;
|
||||
float m_pitch = 0.0f;
|
||||
int m_step = 0;
|
||||
float m_step_len = 1.0f;
|
||||
PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f};
|
||||
Random m_random;
|
||||
|
||||
std::vector<PathPoint> m_points;
|
||||
ChunkPosSet m_pending_chunks;
|
||||
void collect_path_points();
|
||||
void precompute_chunk_coverage();
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/river.path.hpp"
|
||||
#include "Cubed/tools/cubed_random.hpp"
|
||||
#include "Cubed/gameplay/path.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
namespace Cubed {
|
||||
|
||||
class RiverWorm {
|
||||
using RiverHashMap = tbb::concurrent_hash_map<unsigned, RiverPath>;
|
||||
|
||||
public:
|
||||
RiverWorm();
|
||||
RiverHashMap& paths();
|
||||
~RiverWorm();
|
||||
|
||||
void init(unsigned world_seed);
|
||||
void reload(unsigned world_seed);
|
||||
void add_path(const glm::vec3& pos, unsigned chunk_seed);
|
||||
void try_to_add_path(const ChunkPos& pos, unsigned chunk_seed);
|
||||
void cleanup_finished_rivers();
|
||||
|
||||
int river_sum() const;
|
||||
float& river_probability();
|
||||
PathOrigin get_origin(const ChunkPos& origin_chunk) const;
|
||||
int search_radius() const;
|
||||
unsigned world_seed() const;
|
||||
|
||||
float river_probability() const;
|
||||
bool has_origin_fast(const ChunkPos& pos) const;
|
||||
|
||||
private:
|
||||
RiverHashMap m_paths;
|
||||
unsigned m_seed = 0;
|
||||
Random m_random;
|
||||
float m_probability = 0.01f;
|
||||
std::atomic<unsigned> m_world_seed{0};
|
||||
std::atomic<float> m_probability{0.01f};
|
||||
};
|
||||
|
||||
}; // namespace Cubed
|
||||
97
include/Cubed/gameplay/server_chunk.hpp
Normal file
97
include/Cubed/gameplay/server_chunk.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#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 <array>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
namespace Cubed {
|
||||
class ServerWorld;
|
||||
class ServerChunk {
|
||||
public:
|
||||
ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
|
||||
bool temp_chunk = false);
|
||||
ServerChunk(const ServerChunk&) = delete;
|
||||
ServerChunk(ServerChunk&&) noexcept;
|
||||
ServerChunk& operator=(const ServerChunk&) = delete;
|
||||
ServerChunk& operator=(ServerChunk&&) 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);
|
||||
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos);
|
||||
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
|
||||
int chunk_x, int chunk_z);
|
||||
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos);
|
||||
|
||||
void set_chunk_block(int index, unsigned id);
|
||||
// ensure thread safe!
|
||||
void gen_chunk();
|
||||
|
||||
BiomeType get_biome() const;
|
||||
ChunkPos get_chunk_pos() const;
|
||||
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||
HeightMapArray get_heightmap() const;
|
||||
bool is_temp_chunk() const;
|
||||
ChunkPos chunk_pos() const;
|
||||
BiomeType biome() const;
|
||||
void biome(BiomeType b);
|
||||
HeightMapArray& heightmap();
|
||||
std::vector<BlockType>& blocks();
|
||||
ServerWorld& world();
|
||||
unsigned seed() const;
|
||||
BiomeConditions& conditions();
|
||||
bool& has_cave();
|
||||
const OptionalBlockVectorArray& get_neightbor_blocks() const;
|
||||
static int index(int x, int y, int z);
|
||||
static int index(const glm::vec3& pos);
|
||||
|
||||
private:
|
||||
static constexpr int SIZE_X = CHUNK_SIZE;
|
||||
static constexpr int SIZE_Y = WORLD_SIZE_Y;
|
||||
static constexpr int SIZE_Z = CHUNK_SIZE;
|
||||
|
||||
std::atomic<bool> m_gening{false};
|
||||
std::atomic<bool> m_temp_chunk{false};
|
||||
|
||||
bool m_has_cave{false};
|
||||
|
||||
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
|
||||
|
||||
ChunkPos m_chunk_pos;
|
||||
ServerWorld& m_world;
|
||||
HeightMapArray m_heightmap;
|
||||
// the index is a array of block id
|
||||
std::vector<BlockType> m_blocks;
|
||||
OptionalBlockVectorArray m_neightbor_blocks;
|
||||
float frequency = 0.01f;
|
||||
float height = 80;
|
||||
unsigned m_seed = 0;
|
||||
|
||||
BiomeConditions m_conditions;
|
||||
|
||||
std::unique_ptr<ChunkGenerator> m_generator;
|
||||
|
||||
// Init Chunk
|
||||
// Determine biome from temperature and humidity noise
|
||||
void gen_phase_one();
|
||||
|
||||
// Generate heightmap using biome-specific noise
|
||||
void gen_phase_two();
|
||||
|
||||
// Generate terrain blocks from heightmap and biome
|
||||
void gen_phase_three();
|
||||
// Blend surface blocks at chunk borders with neighbors
|
||||
void gen_phase_four(const std::array<std::optional<std::vector<BlockType>>,
|
||||
4>& neighbor_block);
|
||||
// Generate biome-specific vegetation/structures
|
||||
void gen_phase_five();
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
66
include/Cubed/gameplay/server_player.hpp
Normal file
66
include/Cubed/gameplay/server_player.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <atomic>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
namespace Cubed {
|
||||
class ServerWorld;
|
||||
class Session;
|
||||
class ServerPlayer {
|
||||
|
||||
public:
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
ServerPlayer(const ServerPlayer&) = delete;
|
||||
ServerPlayer(ServerPlayer&&) = delete;
|
||||
ServerPlayer& operator=(const ServerPlayer&) = delete;
|
||||
ServerPlayer& operator=(ServerPlayer&&) = delete;
|
||||
ServerPlayer(std::string_view name, std::string_view uuid,
|
||||
ServerWorld& m_world, std::shared_ptr<Session> session,
|
||||
TickType gametick);
|
||||
|
||||
const glm::vec3& get_pos() const;
|
||||
const std::string& get_name() const;
|
||||
const std::string& get_uuid() const;
|
||||
std::shared_ptr<Session> get_session() const;
|
||||
void update_pos(float x, float y, float z);
|
||||
void update_sync_gametick(TickType gametick);
|
||||
bool is_disconnect(TickType current_gametick) const;
|
||||
int task_id() const;
|
||||
void task_id(int id);
|
||||
bool has_player(ChunkPos pos) const;
|
||||
void update_chunk_set(const ChunkPosSet& set);
|
||||
const ChunkPosSet& get_chunk_pos_set() const;
|
||||
ChunkPosSet& get_chunk_pos_set();
|
||||
|
||||
void set_yaw(float yaw);
|
||||
void set_pitch(float pitch);
|
||||
float yaw() const;
|
||||
float pitch() const;
|
||||
|
||||
Gait gait() const;
|
||||
void set_gait(Gait gait);
|
||||
|
||||
private:
|
||||
static constexpr TickType TIMEOUT = 200;
|
||||
std::string m_name;
|
||||
std::string m_uuid;
|
||||
glm::vec3 m_pos{0.0f};
|
||||
ServerWorld& m_world;
|
||||
ChunkPos m_last_chunk_pos{0, 0};
|
||||
std::atomic<std::shared_ptr<Session>> m_session;
|
||||
std::atomic<TickType> m_last_gametick{0};
|
||||
std::atomic<int> m_chunk_task_id{0};
|
||||
std::atomic<float> m_yaw{0.0f};
|
||||
std::atomic<float> m_pitch{0.0f};
|
||||
std::atomic<Gait> m_gait;
|
||||
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||
ChunkPosSet m_player_chunk_pos_set;
|
||||
};
|
||||
} // namespace Cubed
|
||||
188
include/Cubed/gameplay/server_world.hpp
Normal file
188
include/Cubed/gameplay/server_world.hpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/packet.hpp" // IWYU pragma: keep
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
#include "Cubed/tools/priority_thread_pool.hpp"
|
||||
#include "Cubed/tools/recent_queue.hpp"
|
||||
#include "Cubed/tools/thread_pool.hpp"
|
||||
#include "world/block_change.pb.h"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <shared_mutex>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class Session;
|
||||
class ServerWorld {
|
||||
public:
|
||||
enum class ThreadPoolKind { NET, GEN };
|
||||
ServerWorld();
|
||||
~ServerWorld();
|
||||
void stop();
|
||||
void handle_player_exit(const std::string& uuid);
|
||||
void init_world();
|
||||
void need_gen(std::string uuid);
|
||||
void update();
|
||||
void hot_reload();
|
||||
|
||||
int rendering_distance() const;
|
||||
void rendering_distance(int rendering_distance);
|
||||
void start_gen_thread();
|
||||
void start_server_thread();
|
||||
|
||||
void stop_gen_thread();
|
||||
void stop_server_thread();
|
||||
|
||||
void stop_thread_pool();
|
||||
void start_thread_pool();
|
||||
|
||||
void serever_run(std::stop_token stoken);
|
||||
|
||||
CaveCarver& cave_carcer();
|
||||
RiverWorm& river_worm();
|
||||
|
||||
TickType game_tick() const;
|
||||
TickType day_tick() const;
|
||||
|
||||
void day_tick(TickType tick);
|
||||
|
||||
int per_tick_time() const;
|
||||
void per_tick_time(int ms);
|
||||
bool is_tick_running() const;
|
||||
void tick_running(bool run);
|
||||
|
||||
int gen_pool_threads() const;
|
||||
int max_threads() const;
|
||||
|
||||
void change_pool_threads(ThreadPoolKind kind, int threads);
|
||||
|
||||
int chunk_load_style() const;
|
||||
void set_chunk_load_style(int id);
|
||||
|
||||
bool set_block(const glm::ivec3& block_pos, unsigned id);
|
||||
|
||||
void sync_player_pos(const C2S_PlayerInfo& rsp);
|
||||
void handle_player_login(const std::string& player_name,
|
||||
std::shared_ptr<Session> session);
|
||||
glm::vec3 get_player_pos(const std::string& uuid) const;
|
||||
|
||||
void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos);
|
||||
void handle_block_change(const BlockChangeReq& req);
|
||||
|
||||
int chunk_size() const;
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::string(id)),
|
||||
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE };
|
||||
struct ChunkEntity {
|
||||
ChunkState state;
|
||||
std::shared_ptr<ServerChunk> chunk;
|
||||
uint32_t ref_count = 0;
|
||||
};
|
||||
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
struct PendingRequest {
|
||||
std::string uuid;
|
||||
int task_id;
|
||||
ChunkPos pos;
|
||||
};
|
||||
struct PendingChunk {
|
||||
ChunkPos pos;
|
||||
std::unique_ptr<ServerChunk> chunk;
|
||||
};
|
||||
|
||||
using ChunkHashMap =
|
||||
tbb::concurrent_hash_map<ChunkPos, ChunkEntity, ChunkPos::TBBHash>;
|
||||
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
|
||||
using NewChunkVector = std::vector<PendingChunk>;
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
|
||||
|
||||
using chunk_acc = ChunkHashMap::accessor;
|
||||
using chunk_caac = ChunkHashMap::const_accessor;
|
||||
|
||||
using uuid_acc = PlayerUUIDMap::accessor;
|
||||
using uuid_cacc = PlayerUUIDMap::const_accessor;
|
||||
// key = uuid
|
||||
PlayerHashMap m_players;
|
||||
ChunkHashMap m_chunks;
|
||||
|
||||
CaveCarver m_cave_carcer;
|
||||
RiverWorm m_river_worm;
|
||||
|
||||
std::jthread m_gen_thread;
|
||||
std::jthread m_server_thread;
|
||||
|
||||
std::atomic<bool> m_chunk_gen_finished{false};
|
||||
std::atomic<bool> m_could_gen{true};
|
||||
std::atomic<bool> m_gen_running{false};
|
||||
std::atomic<bool> m_need_gen_chunk{false};
|
||||
std::atomic<bool> m_init{false};
|
||||
std::atomic<bool> m_stopped{false};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
std::atomic<int> m_gen_pool_threads{0};
|
||||
std::atomic<int> m_net_pool_threads{0};
|
||||
std::atomic<int> m_max_threads{1};
|
||||
std::atomic<size_t> m_player_sum{0};
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
std::atomic<TickType> m_day_tick{6000};
|
||||
std::atomic<bool> m_tick_running{true};
|
||||
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
|
||||
|
||||
mutable std::shared_mutex m_player_mutex;
|
||||
std::mutex m_need_gen_queue_mutex;
|
||||
std::condition_variable_any m_gen_cv;
|
||||
|
||||
RecentQueue<std::string> m_need_gen_queue;
|
||||
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool;
|
||||
std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool;
|
||||
|
||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
|
||||
|
||||
PlayerUUIDMap m_uuid_to_name;
|
||||
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
tbb::concurrent_queue<PendingRequest> m_waiting_chunk_requests;
|
||||
tbb::concurrent_queue<std::unique_ptr<ServerChunk>> m_finished_queue;
|
||||
|
||||
void init_chunks();
|
||||
|
||||
void gen_chunks_internal(const std::string& uuid);
|
||||
|
||||
void compute_required_chunks(ChunkPosSet& required_chunks,
|
||||
const std::optional<std::string>& uuid);
|
||||
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
||||
const ChunkPosSet&);
|
||||
void submit_new_chunks(const std::string& uuid, NewChunkVector& new_chunks);
|
||||
// void wait_all_chunk_tasks();
|
||||
|
||||
void update_ref_count(const ChunkPosSet& old, const ChunkPosSet& now);
|
||||
|
||||
void send_time();
|
||||
|
||||
void send_chunk(int task_id, const std::string& uuid, ChunkPos pos);
|
||||
|
||||
int
|
||||
change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool,
|
||||
int threads);
|
||||
int change_pool_threads(
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>>& thread_pool,
|
||||
int threads);
|
||||
void send_server_stop();
|
||||
};
|
||||
} // namespace Cubed
|
||||
60
include/Cubed/gameplay/session.hpp
Normal file
60
include/Cubed/gameplay/session.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
namespace Cubed {
|
||||
|
||||
using asio::ip::tcp;
|
||||
class ServerWorld;
|
||||
class Session : public std::enable_shared_from_this<Session> {
|
||||
|
||||
public:
|
||||
Session(tcp::socket socket, ServerWorld& server_world,
|
||||
asio::io_context& io);
|
||||
~Session();
|
||||
void start();
|
||||
void send(Packet packet, int priority = 10);
|
||||
|
||||
void close();
|
||||
const std::string& uuid() const;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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::string m_uuid;
|
||||
ServerWorld& m_server_world;
|
||||
std::atomic<bool> m_closed{false};
|
||||
|
||||
std::atomic_uint64_t m_sequence{0};
|
||||
|
||||
asio::awaitable<void> read_loop();
|
||||
|
||||
void do_write();
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
class Chunk;
|
||||
class ServerChunk;
|
||||
|
||||
struct TreeStructNode {
|
||||
glm::ivec3 offset{0, 0, 0};
|
||||
unsigned id = 0;
|
||||
};
|
||||
|
||||
bool build_tree(Chunk& chunk, const glm::ivec3& pos);
|
||||
bool build_tree(ServerChunk& chunk, const glm::ivec3& pos);
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#include <glad/glad.h>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class World;
|
||||
class ClientWorld;
|
||||
struct VertexData {
|
||||
std::vector<Vertex3D> m_vertices;
|
||||
GLuint m_vbo = 0;
|
||||
GLuint m_vao = 0;
|
||||
std::atomic<std::size_t> m_sum{0};
|
||||
World& m_world;
|
||||
VertexData(World& world);
|
||||
ClientWorld& m_world;
|
||||
VertexData(ClientWorld& world);
|
||||
~VertexData();
|
||||
VertexData(const VertexData&) = delete;
|
||||
VertexData(VertexData&&) noexcept;
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
#pragma once
|
||||
#include "Cubed/AABB.hpp"
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
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 Player;
|
||||
class TextureManager;
|
||||
class World {
|
||||
private:
|
||||
using OptionalBlockVectorArray =
|
||||
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
|
||||
using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
|
||||
using ConstChunkMap =
|
||||
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
|
||||
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
|
||||
|
||||
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
|
||||
ChunkHashMap m_chunks;
|
||||
std::unordered_map<std::size_t, Player> m_players;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
|
||||
std::thread m_gen_thread;
|
||||
std::thread m_server_thread;
|
||||
|
||||
std::stop_source m_server_stop_source;
|
||||
|
||||
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
|
||||
|
||||
std::atomic<TickType> m_day_tick = 6000;
|
||||
|
||||
mutable std::mutex m_chunks_mutex;
|
||||
std::mutex m_gen_signal_mutex;
|
||||
std::mutex m_new_chunk_queue_mutex;
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_mutex;
|
||||
std::mutex m_gen_player_pos_mutex;
|
||||
std::vector<GLuint> m_pending_delete_vbo;
|
||||
std::vector<GLuint> m_pending_delete_vao;
|
||||
std::condition_variable m_gen_cv;
|
||||
std::atomic<bool> m_gen_running{false};
|
||||
std::atomic<bool> m_need_gen_chunk{false};
|
||||
std::atomic<bool> m_is_rebuilding{false};
|
||||
std::atomic<bool> m_chunk_gen_finished{false};
|
||||
std::atomic<bool> m_could_gen{true};
|
||||
std::atomic<bool> m_tick_running{true};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
std::atomic<float> m_chunk_gen_fraction{0.0f};
|
||||
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
|
||||
std::vector<ChunkPos> m_dirty_queue;
|
||||
std::vector<ChunkRenderSnapshot> m_render_snapshots;
|
||||
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk;
|
||||
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk_queue;
|
||||
|
||||
CaveCarver m_cave_carcer;
|
||||
RiverWorm m_river_worm;
|
||||
void init_chunks();
|
||||
|
||||
void gen_chunks_internal();
|
||||
void sync_player_pos(glm::vec3& player_pos);
|
||||
void
|
||||
compute_required_chunks(ChunkPosSet& required_chunks,
|
||||
ChunkPairVector& temp_neighbor,
|
||||
std::vector<ChunkPos>& need_gen_temp_chunks_pos);
|
||||
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
||||
const ChunkPosSet&);
|
||||
void
|
||||
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
|
||||
ChunkPtrUpdateList& affected_neighbor,
|
||||
const ChunkPairVector& new_chunks);
|
||||
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
|
||||
ConstChunkMap&);
|
||||
|
||||
public:
|
||||
World();
|
||||
~World();
|
||||
|
||||
bool can_move(const AABB& player_box) const;
|
||||
// const BlockRenderData& get_block_render_data(int x, int y ,int z);
|
||||
|
||||
const std::optional<LookBlock>&
|
||||
get_look_block_pos(const std::string& name) const;
|
||||
const Chunk* get_chunk(const ChunkPos& pos) const;
|
||||
|
||||
Player& get_player(const std::string& name);
|
||||
void init_world();
|
||||
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;
|
||||
static ChunkPos chunk_pos(int world_x, int world_z);
|
||||
|
||||
void need_gen();
|
||||
|
||||
void set_block(const glm::ivec3& pos, unsigned id);
|
||||
void update(float delta_time);
|
||||
|
||||
void push_delete_vbo(GLuint vbo);
|
||||
void push_delete_vao(GLuint vao);
|
||||
void hot_reload();
|
||||
|
||||
void rebuild_world();
|
||||
|
||||
float chunk_gen_fraction() const;
|
||||
int rendering_distance() const;
|
||||
void rendering_distance(int rendering_distance);
|
||||
void start_gen_thread();
|
||||
void start_server_thread();
|
||||
void stop_gen_thread();
|
||||
void stop_server_thread();
|
||||
void serever_run(std::stop_token stoken);
|
||||
|
||||
CaveCarver& cave_carcer();
|
||||
RiverWorm& river_worm();
|
||||
std::vector<glm::vec4>& planes();
|
||||
std::vector<ChunkRenderSnapshot>& render_snapshots();
|
||||
|
||||
glm::vec3 sunlight_dir() const;
|
||||
TickType game_tick() const;
|
||||
TickType day_tick() const;
|
||||
void day_tick(TickType tick);
|
||||
int per_tick_time() const;
|
||||
void per_tick_time(int ms);
|
||||
|
||||
bool is_tick_running() const;
|
||||
void tick_running(bool run);
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
32
include/Cubed/player_renderer.hpp
Normal file
32
include/Cubed/player_renderer.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "Cubed/shader.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace Cubed {
|
||||
class Renderer;
|
||||
class PlayerRenderer {
|
||||
public:
|
||||
static constexpr int BODY_PART_NUM = 6;
|
||||
PlayerRenderer(Renderer& renderer);
|
||||
~PlayerRenderer();
|
||||
void init();
|
||||
void render(const Shader& shader);
|
||||
void shadow_render(const Shader& shader, glm::mat4& light_matrix);
|
||||
|
||||
private:
|
||||
struct PlayerVertex {
|
||||
|
||||
float x = 0.0f, y = 0.0f, z = 0.0f;
|
||||
float s = 0.0f, t = 0.0f;
|
||||
float nx = 0.0f, ny = 0.0f, nz = 0.0f;
|
||||
float tx = 0.0f, ty = 0.0f, tz = 0.0f;
|
||||
};
|
||||
Renderer& m_renderer;
|
||||
std::array<GLuint, BODY_PART_NUM> m_vao;
|
||||
std::array<GLuint, BODY_PART_NUM> m_vbo;
|
||||
bool m_inited{false};
|
||||
std::array<std::vector<PlayerVertex>, BODY_PART_NUM> m_vertices;
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -63,12 +63,12 @@ constexpr float TEX_COORDS[6][6][2] = {
|
||||
{0.0f, 0.0f}, // top front
|
||||
{0.0f, 1.0f}}, // bottom front
|
||||
// ===== back (z = -1) =====
|
||||
{{1.0f, 1.0f}, // bottom left
|
||||
{0.0f, 1.0f}, // bottom right
|
||||
{0.0f, 0.0f}, // top right
|
||||
{0.0f, 0.0f}, // top right
|
||||
{1.0f, 0.0f}, // top left
|
||||
{1.0f, 1.0f}}, // bottom left
|
||||
{{0.0f, 1.0f}, // bottom left
|
||||
{0.0f, 0.0f}, // top left
|
||||
{1.0f, 0.0f}, // top right
|
||||
{1.0f, 0.0f}, // top right
|
||||
{1.0f, 1.0f}, // bottom right
|
||||
{0.0f, 1.0f}}, // bottom left
|
||||
// ===== left (x = -1) =====
|
||||
{{1.0f, 1.0f}, // bottom back
|
||||
{0.0f, 1.0f}, // bottom front
|
||||
@@ -152,12 +152,12 @@ constexpr float TANGENTS[6][6][3] = {
|
||||
{0.0f, 0.0f, -1.0f},
|
||||
{0.0f, 0.0f, -1.0f}},
|
||||
// ===== back (z = -1) =====
|
||||
{{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f}},
|
||||
{{1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f}},
|
||||
// ===== left (x = -1) =====
|
||||
{{0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
@@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = {
|
||||
{-0.7071f, 0.0f, 0.7071f}}};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Player
|
||||
|
||||
constexpr float VERTICES_PLAYER[6][6][3] = {
|
||||
// ===== front (z = +1) =====
|
||||
{{0.0f, 0.0f, 1.0f}, // bottom left
|
||||
{0.0f, 2.0f, 1.0f}, // top left
|
||||
{1.0f, 2.0f, 1.0f}, // top right
|
||||
{1.0f, 2.0f, 1.0f}, // top right
|
||||
{1.0f, 0.0f, 1.0f}, // bottom right
|
||||
{0.0f, 0.0f, 1.0f}}, // bottom left
|
||||
// ===== right (x = +1) =====
|
||||
{{1.0f, 0.0f, 1.0f}, // bottom front
|
||||
{1.0f, 0.0f, 0.0f}, // bottom back
|
||||
{1.0f, 2.0f, 0.0f}, // top back
|
||||
{1.0f, 2.0f, 0.0f}, // top back
|
||||
{1.0f, 2.0f, 1.0f}, // top front
|
||||
{1.0f, 0.0f, 1.0f}}, // bottom front
|
||||
// ===== back (z = -1) =====
|
||||
{{0.0f, 0.0f, 0.0f}, // bottom left
|
||||
{1.0f, 0.0f, 0.0f}, // bottom right
|
||||
{1.0f, 2.0f, 0.0f}, // top right
|
||||
{1.0f, 2.0f, 0.0f}, // top right
|
||||
{0.0f, 2.0f, 0.0f}, // top left
|
||||
{0.0f, 0.0f, 0.0f}}, // bottom left
|
||||
// ===== left (x = -1) =====
|
||||
{{0.0f, 0.0f, 0.0f}, // bottom back
|
||||
{0.0f, 0.0f, 1.0f}, // bottom front
|
||||
{0.0f, 2.0f, 1.0f}, // top front
|
||||
{0.0f, 2.0f, 1.0f}, // top front
|
||||
{0.0f, 2.0f, 0.0f}, // top back
|
||||
{0.0f, 0.0f, 0.0f}}, // bottom back
|
||||
// ===== top (y = +2) =====
|
||||
{{0.0f, 2.0f, 0.0f}, // back left
|
||||
{1.0f, 2.0f, 0.0f}, // back right
|
||||
{1.0f, 2.0f, 1.0f}, // front right
|
||||
{1.0f, 2.0f, 1.0f}, // front right
|
||||
{0.0f, 2.0f, 1.0f}, // front left
|
||||
{0.0f, 2.0f, 0.0f}}, // back left
|
||||
// ===== bottom (y = -1) =====
|
||||
{{0.0f, 0.0f, 1.0f}, // front left
|
||||
{1.0f, 0.0f, 1.0f}, // front right
|
||||
{1.0f, 0.0f, 0.0f}, // back right
|
||||
{1.0f, 0.0f, 0.0f}, // back right
|
||||
{0.0f, 0.0f, 0.0f}, // back left
|
||||
{0.0f, 0.0f, 1.0f}} // front left
|
||||
};
|
||||
#pragma endregion
|
||||
|
||||
// [-1, 1]
|
||||
constexpr float QUAD_VERTICES[] = {
|
||||
// postion // texcoorlds
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/player_renderer.hpp"
|
||||
#include "Cubed/primitive_data.hpp"
|
||||
#include "Cubed/shader.hpp"
|
||||
#include "Cubed/ui/text.hpp"
|
||||
@@ -11,13 +12,13 @@ namespace Cubed {
|
||||
|
||||
class Camera;
|
||||
class TextureManager;
|
||||
class World;
|
||||
class ClientWorld;
|
||||
class DevPanel;
|
||||
class Renderer {
|
||||
public:
|
||||
constexpr static int NUM_VAO = 7;
|
||||
|
||||
Renderer(const Camera& camera, World& world,
|
||||
Renderer(const Camera& camera, ClientWorld& world,
|
||||
const TextureManager& texture_manager, DevPanel& dev_panel);
|
||||
~Renderer();
|
||||
void hot_reload();
|
||||
@@ -50,6 +51,14 @@ public:
|
||||
float& underwater_fog_density();
|
||||
float& water_density();
|
||||
|
||||
const Camera& camera() const;
|
||||
const ClientWorld& world() const;
|
||||
ClientWorld& world();
|
||||
const glm::mat4& proj_mat() const;
|
||||
const TextureManager& texture_mamger() const;
|
||||
|
||||
float delta_time() const;
|
||||
|
||||
private:
|
||||
struct ParallelLight {
|
||||
glm::vec3 sundir; // direction from sun to vertex
|
||||
@@ -91,14 +100,17 @@ private:
|
||||
const Camera& m_camera;
|
||||
DevPanel& m_dev_panel;
|
||||
const TextureManager& m_texture_manager;
|
||||
World& m_world;
|
||||
|
||||
ClientWorld& m_world;
|
||||
PlayerRenderer m_player_renderer;
|
||||
bool m_discard_tranparent = true;
|
||||
bool m_shader_on = true;
|
||||
bool m_water_perturb = true;
|
||||
bool m_water_depth_fade = true;
|
||||
bool m_pbr = true;
|
||||
bool m_flip_y = false;
|
||||
|
||||
bool m_init = false;
|
||||
|
||||
int m_shadow_mode = 0;
|
||||
int m_light_cull_face = 0;
|
||||
float m_aspect = 0.0f;
|
||||
@@ -119,7 +131,7 @@ private:
|
||||
GLuint m_outline_indices_vbo = 0;
|
||||
GLuint m_outline_vbo = 0;
|
||||
GLuint m_ui_vbo = 0;
|
||||
|
||||
GLuint m_player_vbo = 0;
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_screen_texture = 0;
|
||||
GLuint m_screen_depth_texture = 0;
|
||||
@@ -171,7 +183,6 @@ private:
|
||||
2 - outline vao
|
||||
3 - ui vao
|
||||
4 - text vao
|
||||
|
||||
*/
|
||||
std::vector<GLuint> m_vao;
|
||||
std::vector<Vertex2D> m_ui;
|
||||
@@ -186,6 +197,7 @@ private:
|
||||
void render_text();
|
||||
void render_ui();
|
||||
void render_world();
|
||||
void render_player();
|
||||
void render_underwater();
|
||||
void render_dev_panel();
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ namespace Cubed {
|
||||
class TextureManager {
|
||||
private:
|
||||
bool m_need_reload = false;
|
||||
bool m_init = false;
|
||||
GLuint m_block_status_array = 0;
|
||||
GLuint m_texture_array = 0;
|
||||
GLuint m_cross_plane_array = 0;
|
||||
GLuint m_ui_array = 0;
|
||||
GLuint m_pbr_texture_array = 0;
|
||||
GLuint m_normal_texture_array = 0;
|
||||
GLfloat m_max_aniso = 0.0f;
|
||||
|
||||
GLuint m_skin = 0;
|
||||
|
||||
int m_aniso = 1;
|
||||
|
||||
std::vector<GLuint> m_item_textures;
|
||||
@@ -29,6 +32,8 @@ private:
|
||||
void init_block();
|
||||
void init_ui();
|
||||
void init_block_status();
|
||||
void init_skin();
|
||||
void hot_reload();
|
||||
|
||||
public:
|
||||
TextureManager();
|
||||
@@ -41,9 +46,10 @@ public:
|
||||
GLuint get_ui_array() const;
|
||||
GLuint get_pbr_texture() const;
|
||||
const std::vector<GLuint>& item_textures() const;
|
||||
GLuint get_skin() const;
|
||||
// Must call after MapTable::init_map() and glfwMakeContextCurrent(window);
|
||||
void init_texture();
|
||||
void hot_reload();
|
||||
|
||||
void need_reload();
|
||||
void update();
|
||||
int max_aniso() const;
|
||||
|
||||
35
include/Cubed/tools/arg_parser.hpp
Normal file
35
include/Cubed/tools/arg_parser.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace Cubed {
|
||||
class ArgParser {
|
||||
public:
|
||||
ArgParser(int argc, char** argv) : m_args(argv, argc) {};
|
||||
ArgParser(std::span<char*> args) : m_args(args) {}
|
||||
|
||||
bool has_next() const { return m_index < m_args.size(); }
|
||||
|
||||
std::string_view next() {
|
||||
if (!has_next()) {
|
||||
throw std::runtime_error("No more arguments");
|
||||
}
|
||||
return m_args[m_index++];
|
||||
}
|
||||
|
||||
std::string_view require_next(std::string_view option) {
|
||||
if (!has_next()) {
|
||||
throw std::runtime_error(
|
||||
std::format("{} requires an argument", option));
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<char*> m_args;
|
||||
size_t m_index = 1;
|
||||
};
|
||||
} // namespace Cubed
|
||||
40
include/Cubed/tools/compression.hpp
Normal file
40
include/Cubed/tools/compression.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <zstd.h>
|
||||
namespace Cubed {
|
||||
constexpr int DEFAULT_ZSTD_LEVEL = 3;
|
||||
inline std::vector<uint8_t> compress_data(std::span<const uint8_t> data) {
|
||||
size_t max_size = ZSTD_compressBound(data.size());
|
||||
std::vector<uint8_t> compressed_data(max_size);
|
||||
size_t compressed_bytes =
|
||||
ZSTD_compress(compressed_data.data(), max_size, data.data(),
|
||||
data.size(), DEFAULT_ZSTD_LEVEL);
|
||||
if (ZSTD_isError(compressed_bytes)) {
|
||||
throw std::runtime_error(std::format(
|
||||
"Compress Fail {}", ZSTD_getErrorName(compressed_bytes)));
|
||||
}
|
||||
compressed_data.resize(compressed_bytes);
|
||||
return compressed_data;
|
||||
}
|
||||
|
||||
inline std::vector<uint8_t> decompress_data(std::span<const uint8_t> data,
|
||||
uint32_t original_size) {
|
||||
std::vector<uint8_t> decompressed_data(original_size);
|
||||
size_t decompressed_bytes = ZSTD_decompress(
|
||||
decompressed_data.data(), original_size, data.data(), data.size());
|
||||
if (ZSTD_isError(decompressed_bytes)) {
|
||||
throw std::runtime_error(std::format(
|
||||
"Decompress Fail {}", ZSTD_getErrorName(decompressed_bytes)));
|
||||
}
|
||||
if (decompressed_bytes != original_size) {
|
||||
throw std::runtime_error("Unexpected decompressed size");
|
||||
}
|
||||
|
||||
return decompressed_data;
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace Cubed {
|
||||
|
||||
namespace Logger {
|
||||
enum class Level { TRACE, DEBUG, INFO, ERROR, WARN };
|
||||
enum class Level { L_TRACE, L_DEBUG, L_INFO, L_ERROR, L_WARN };
|
||||
|
||||
template <typename... Args>
|
||||
inline void info(std::format_string<Args...> fmt, Args&&... args) {
|
||||
@@ -53,7 +53,7 @@ inline void log(Level level, std::source_location loc,
|
||||
std::chrono::system_clock::now());
|
||||
std::string msg = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||
switch (level) {
|
||||
case Logger::Level::TRACE:
|
||||
case Logger::Level::L_TRACE:
|
||||
std::osyncstream(std::cout)
|
||||
<< "\033[1;34m"
|
||||
<< std::format("[TRACE][{:%Y-%m-%d %H:%M:%S}]", now_time) << "["
|
||||
@@ -61,20 +61,20 @@ inline void log(Level level, std::source_location loc,
|
||||
<< "[" << loc.function_name() << "]" << msg << "\033[0m"
|
||||
<< "\n";
|
||||
break;
|
||||
case Logger::Level::DEBUG:
|
||||
case Logger::Level::L_DEBUG:
|
||||
std::osyncstream(std::cout)
|
||||
<< "\033[1;34m"
|
||||
<< std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg
|
||||
<< "\033[0m"
|
||||
<< "\n";
|
||||
break;
|
||||
case Logger::Level::INFO:
|
||||
case Logger::Level::L_INFO:
|
||||
info(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
case Logger::Level::WARN:
|
||||
case Logger::Level::L_WARN:
|
||||
warn(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
case Logger::Level::ERROR:
|
||||
case Logger::Level::L_ERROR:
|
||||
error(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,108 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
namespace Cubed {
|
||||
|
||||
namespace Math {
|
||||
|
||||
void extract_frustum_planes(const glm::mat4& mvp_matrix,
|
||||
std::vector<glm::vec4>& planes);
|
||||
inline void extract_frustum_planes(const glm::mat4& mvp_matrix,
|
||||
std::vector<glm::vec4>& planes) {
|
||||
if (planes.size() != 6) {
|
||||
planes.resize(6);
|
||||
}
|
||||
|
||||
float smootherstep(float edge0, float edge1, float x);
|
||||
bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
|
||||
const std::vector<glm::vec4>& planes);
|
||||
float deterministic_random(int x, int z, uint64_t seed);
|
||||
glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t);
|
||||
const float* m = glm::value_ptr(mvp_matrix);
|
||||
|
||||
// left plane
|
||||
planes[0] =
|
||||
glm::vec4(m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12]);
|
||||
// right plane
|
||||
planes[1] =
|
||||
glm::vec4(m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12]);
|
||||
// bottom plane
|
||||
planes[2] =
|
||||
glm::vec4(m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13]);
|
||||
// top plane
|
||||
planes[3] =
|
||||
glm::vec4(m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13]);
|
||||
// near plane
|
||||
planes[4] =
|
||||
glm::vec4(m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14]);
|
||||
// far plane
|
||||
planes[5] =
|
||||
glm::vec4(m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14]);
|
||||
|
||||
for (auto& p : planes) {
|
||||
p = glm::normalize(p);
|
||||
}
|
||||
}
|
||||
|
||||
inline float smootherstep(float edge0, float edge1, float x) {
|
||||
|
||||
x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
|
||||
|
||||
return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f);
|
||||
}
|
||||
|
||||
inline bool is_aabb_in_frustum(const glm::vec3& center,
|
||||
const glm::vec3& half_extents,
|
||||
const std::vector<glm::vec4>& planes) {
|
||||
for (const auto& plane : planes) {
|
||||
// distance
|
||||
float d = glm::dot(glm::vec3(plane), center) + plane.w;
|
||||
float r = half_extents.x * std::abs(plane.x) +
|
||||
half_extents.y * std::abs(plane.y) +
|
||||
half_extents.z * std::abs(plane.z);
|
||||
if (d + r < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
inline float deterministic_random(int x, int z, uint64_t seed) {
|
||||
uint64_t h = seed;
|
||||
h = h * 6364136223846793005ULL + (uint64_t)x;
|
||||
h = h * 6364136223846793005ULL + (uint64_t)z;
|
||||
return (float)(h >> 40) / (float)(1 << 24);
|
||||
}
|
||||
|
||||
inline glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t) {
|
||||
|
||||
float cos_theta = glm::clamp(glm::dot(from, to), -1.0f, 1.0f);
|
||||
|
||||
if (cos_theta > 0.9995f) {
|
||||
return glm::normalize(glm::mix(from, to, t));
|
||||
}
|
||||
|
||||
if (cos_theta < -0.9995f) {
|
||||
|
||||
glm::vec3 axis = (std::fabs(from.x) < 0.9f)
|
||||
? glm::vec3(1.0f, 0.0f, 0.0f)
|
||||
: glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 ortho = glm::normalize(glm::cross(from, axis));
|
||||
|
||||
float angle = glm::pi<float>() * t;
|
||||
|
||||
glm::vec3 rotated =
|
||||
from * std::cos(angle) + glm::cross(ortho, from) * std::sin(angle);
|
||||
|
||||
return glm::normalize(rotated);
|
||||
}
|
||||
|
||||
float theta = std::acos(cos_theta);
|
||||
float sin_theta = std::sin(theta);
|
||||
|
||||
float a = std::sin((1.0f - t) * theta) / sin_theta;
|
||||
float b = std::sin(t * theta) / sin_theta;
|
||||
|
||||
return glm::normalize(a * from + b * to);
|
||||
}
|
||||
|
||||
inline float distance2(const glm::vec3& a, const glm::vec3& b) {
|
||||
glm::vec3 diff = a - b;
|
||||
return glm::dot(diff, diff);
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
|
||||
|
||||
151
include/Cubed/tools/priority_thread_pool.hpp
Normal file
151
include/Cubed/tools/priority_thread_pool.hpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class PriorityThreadPool {
|
||||
private:
|
||||
struct Task {
|
||||
int priority = 10;
|
||||
std::uint64_t sequence;
|
||||
std::function<void()> task;
|
||||
Task(int p, std::uint64_t seq, std::function<void()> t)
|
||||
: priority(p), sequence(seq), task(std::move(t)) {}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::jthread> m_workers;
|
||||
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_tasks;
|
||||
std::mutex m_mtx;
|
||||
std::condition_variable_any m_cv;
|
||||
std::atomic<bool> m_stopping{false};
|
||||
std::atomic<size_t> m_thread_sum{0};
|
||||
std::atomic_uint64_t m_sequence{0};
|
||||
|
||||
public:
|
||||
PriorityThreadPool(const PriorityThreadPool&) = delete;
|
||||
PriorityThreadPool(PriorityThreadPool&&) = delete;
|
||||
PriorityThreadPool& operator=(const PriorityThreadPool&) = delete;
|
||||
PriorityThreadPool& operator=(PriorityThreadPool&&) = delete;
|
||||
explicit PriorityThreadPool(size_t thread_sum) : m_thread_sum(thread_sum) {
|
||||
for (size_t i = 0; i < thread_sum; i++) {
|
||||
m_workers.emplace_back([this](std::stop_token stoken) {
|
||||
while (true) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_cv.wait(lock, stoken,
|
||||
[this] { return !m_tasks.empty(); });
|
||||
if (stoken.stop_requested() && m_tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
task = std::move(m_tasks.top().task);
|
||||
m_tasks.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
~PriorityThreadPool() { stop(); }
|
||||
template <typename F> auto enqueue(int priority, F&& f) {
|
||||
|
||||
using R = std::invoke_result_t<F>;
|
||||
|
||||
auto task =
|
||||
std::make_shared<std::packaged_task<R()>>(std::forward<F>(f));
|
||||
auto fut = task->get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_mtx);
|
||||
if (m_stopping)
|
||||
throw std::runtime_error("thread pool stopped");
|
||||
m_tasks.emplace(priority, m_sequence++, [task] { (*task)(); });
|
||||
}
|
||||
m_cv.notify_one();
|
||||
return fut;
|
||||
}
|
||||
|
||||
template <typename F> auto enqueue(F&& f) {
|
||||
return enqueue(10, std::forward<F>(f));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (m_stopping.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& w : m_workers) {
|
||||
w.request_stop();
|
||||
}
|
||||
|
||||
m_cv.notify_all();
|
||||
|
||||
for (auto& w : m_workers) {
|
||||
if (w.joinable()) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t thread_sum() const { return m_thread_sum.load(); }
|
||||
};
|
||||
|
||||
template <std::random_access_iterator Iter, typename F>
|
||||
void parallel_do(PriorityThreadPool& pool, Iter first, Iter last,
|
||||
size_t max_threads, F&& f) {
|
||||
max_threads = std::max<size_t>(1, max_threads);
|
||||
max_threads = std::min(max_threads, pool.thread_sum());
|
||||
std::decay_t<F> fn(std::forward<F>(f));
|
||||
size_t length = std::distance(first, last);
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr size_t MIN_PER_THREAD = 25;
|
||||
size_t num_blocks =
|
||||
std::min(max_threads, (length + MIN_PER_THREAD - 1) / MIN_PER_THREAD);
|
||||
num_blocks = std::max<size_t>(1, num_blocks);
|
||||
size_t block_size = (length + num_blocks - 1) / num_blocks;
|
||||
|
||||
std::vector<std::future<void>> futures;
|
||||
futures.reserve(num_blocks - 1);
|
||||
Iter block_start = first;
|
||||
for (size_t i = 0; i < num_blocks - 1; ++i) {
|
||||
Iter block_end = block_start;
|
||||
auto remain = std::distance(block_start, last);
|
||||
std::advance(block_end, std::min<size_t>(block_size, remain));
|
||||
|
||||
futures.emplace_back(pool.enqueue([block_start, block_end, &fn]() {
|
||||
for (auto it = block_start; it != block_end; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
}));
|
||||
|
||||
block_start = block_end;
|
||||
}
|
||||
for (auto it = block_start; it != last; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
|
||||
for (auto& fut : futures) {
|
||||
fut.get();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
55
include/Cubed/tools/recent_queue.hpp
Normal file
55
include/Cubed/tools/recent_queue.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
namespace Cubed {
|
||||
template <typename T> class RecentQueue {
|
||||
private:
|
||||
std::list<T> m_list;
|
||||
std::unordered_map<T, typename std::list<T>::iterator> m_map;
|
||||
|
||||
public:
|
||||
void enqueue(T key) {
|
||||
auto it = m_map.find(key);
|
||||
if (it != m_map.end()) {
|
||||
m_list.splice(m_list.end(), m_list, it->second);
|
||||
return;
|
||||
}
|
||||
m_list.emplace_back(std::move(key));
|
||||
auto iter = std::prev(m_list.end());
|
||||
m_map.emplace(*iter, iter);
|
||||
}
|
||||
|
||||
void pop() {
|
||||
if (m_list.empty()) {
|
||||
return;
|
||||
}
|
||||
m_map.erase(m_list.front());
|
||||
m_list.pop_front();
|
||||
}
|
||||
void clear() {
|
||||
m_list.clear();
|
||||
m_map.clear();
|
||||
}
|
||||
|
||||
const T& front() const {
|
||||
assert(!empty());
|
||||
return m_list.front();
|
||||
}
|
||||
const T& back() const {
|
||||
assert(!empty());
|
||||
return m_list.back();
|
||||
}
|
||||
[[nodiscard]]
|
||||
bool empty() const {
|
||||
return m_list.empty();
|
||||
}
|
||||
[[nodiscard]]
|
||||
size_t size() const {
|
||||
return m_list.size();
|
||||
}
|
||||
[[nodiscard]]
|
||||
bool contains(const T& key) const {
|
||||
return m_map.find(key) != m_map.end();
|
||||
}
|
||||
};
|
||||
} // namespace Cubed
|
||||
125
include/Cubed/tools/thread_pool.hpp
Normal file
125
include/Cubed/tools/thread_pool.hpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class ThreadPool {
|
||||
private:
|
||||
std::vector<std::jthread> m_workers;
|
||||
std::queue<std::function<void()>> m_tasks;
|
||||
std::mutex m_mtx;
|
||||
std::condition_variable_any m_cv;
|
||||
std::atomic<bool> m_stopping{false};
|
||||
std::atomic<size_t> m_thread_sum{0};
|
||||
|
||||
public:
|
||||
ThreadPool(const ThreadPool&) = delete;
|
||||
ThreadPool(ThreadPool&&) = delete;
|
||||
ThreadPool& operator=(const ThreadPool&) = delete;
|
||||
ThreadPool& operator=(ThreadPool&&) = delete;
|
||||
explicit ThreadPool(size_t thread_sum) : m_thread_sum(thread_sum) {
|
||||
for (size_t i = 0; i < thread_sum; i++) {
|
||||
m_workers.emplace_back([this](std::stop_token stoken) {
|
||||
while (true) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_cv.wait(lock, stoken,
|
||||
[this] { return !m_tasks.empty(); });
|
||||
if (stoken.stop_requested() && m_tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
task = std::move(m_tasks.front());
|
||||
m_tasks.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
~ThreadPool() { stop(); }
|
||||
template <typename F> auto enqueue(F&& f) {
|
||||
|
||||
using R = std::invoke_result_t<F>;
|
||||
|
||||
auto task =
|
||||
std::make_shared<std::packaged_task<R()>>(std::forward<F>(f));
|
||||
auto fut = task->get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_mtx);
|
||||
if (m_stopping)
|
||||
throw std::runtime_error("thread pool stopped");
|
||||
m_tasks.emplace([task] { (*task)(); });
|
||||
}
|
||||
m_cv.notify_one();
|
||||
return fut;
|
||||
}
|
||||
void stop() {
|
||||
if (m_stopping.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
for (auto& w : m_workers) {
|
||||
w.request_stop();
|
||||
}
|
||||
|
||||
m_cv.notify_all();
|
||||
|
||||
for (auto& w : m_workers) {
|
||||
if (w.joinable()) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t thread_sum() const { return m_thread_sum.load(); }
|
||||
};
|
||||
|
||||
template <std::random_access_iterator Iter, typename F>
|
||||
void parallel_do(ThreadPool& pool, Iter first, Iter last, size_t max_threads,
|
||||
F&& f) {
|
||||
max_threads = std::max<size_t>(1, max_threads);
|
||||
max_threads = std::min(max_threads, pool.thread_sum());
|
||||
std::decay_t<F> fn(std::forward<F>(f));
|
||||
size_t length = std::distance(first, last);
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr size_t MIN_PER_THREAD = 25;
|
||||
size_t num_blocks =
|
||||
std::min(max_threads, (length + MIN_PER_THREAD - 1) / MIN_PER_THREAD);
|
||||
num_blocks = std::max<size_t>(1, num_blocks);
|
||||
size_t block_size = (length + num_blocks - 1) / num_blocks;
|
||||
|
||||
std::vector<std::future<void>> futures;
|
||||
futures.reserve(num_blocks - 1);
|
||||
Iter block_start = first;
|
||||
for (size_t i = 0; i < num_blocks - 1; ++i) {
|
||||
Iter block_end = block_start;
|
||||
auto remain = std::distance(block_start, last);
|
||||
std::advance(block_end, std::min<size_t>(block_size, remain));
|
||||
|
||||
futures.emplace_back(pool.enqueue([block_start, block_end, &fn]() {
|
||||
for (auto it = block_start; it != block_end; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
}));
|
||||
|
||||
block_start = block_end;
|
||||
}
|
||||
for (auto it = block_start; it != last; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
|
||||
for (auto& fut : futures) {
|
||||
fut.get();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
33
include/Cubed/tools/toml.utils.hpp
Normal file
33
include/Cubed/tools/toml.utils.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
namespace Cubed {
|
||||
namespace TOML {
|
||||
|
||||
template <typename T>
|
||||
concept TomlValueType =
|
||||
std::same_as<std::decay_t<T>, int> || std::same_as<std::decay_t<T>, bool> ||
|
||||
std::same_as<std::decay_t<T>, double> ||
|
||||
std::same_as<std::decay_t<T>, char> ||
|
||||
std::same_as<std::decay_t<T>, toml::date> ||
|
||||
std::same_as<std::decay_t<T>, toml::time> ||
|
||||
std::same_as<std::decay_t<T>, toml::date_time> ||
|
||||
std::same_as<std::decay_t<T>, std::string>;
|
||||
|
||||
template <TomlValueType T>
|
||||
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
|
||||
const T& default_value) {
|
||||
auto value = table[key].value<T>();
|
||||
if (value == std::nullopt) {
|
||||
Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}", key,
|
||||
default_value);
|
||||
value = default_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace TOML
|
||||
|
||||
} // namespace Cubed
|
||||
42
include/Cubed/tools/uuid.hpp
Normal file
42
include/Cubed/tools/uuid.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
namespace Cubed {
|
||||
inline std::string generate_uuid() {
|
||||
|
||||
static std::mt19937_64 rng(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() ^
|
||||
(std::random_device{}()));
|
||||
std::uniform_int_distribution<uint64_t> dist(0, UINT64_MAX);
|
||||
|
||||
uint64_t a = dist(rng);
|
||||
uint64_t b = dist(rng);
|
||||
|
||||
std::array<uint8_t, 16> bytes{};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
bytes[i] = (a >> (56 - 8 * i)) & 0xFF;
|
||||
bytes[8 + i] = (b >> (56 - 8 * i)) & 0xFF;
|
||||
}
|
||||
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
||||
ss << '-';
|
||||
}
|
||||
ss << std::setw(2) << static_cast<int>(bytes[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace Cubed
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
private:
|
||||
bool m_mouse_enable = false;
|
||||
bool m_imgui_init = false;
|
||||
float m_aspect;
|
||||
GLFWwindow* m_window;
|
||||
int m_width;
|
||||
|
||||
@@ -4,4 +4,5 @@ leak:libpangocairo
|
||||
leak:libdecor-gtk.so
|
||||
leak:libgtk-3.so
|
||||
leak:libwayland-client.so
|
||||
leak:libglfw.so
|
||||
leak:libglfw.so
|
||||
leak:libEGL_nvidia.so
|
||||
47
src/CMakeLists.txt
Normal file
47
src/CMakeLists.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
main.cpp
|
||||
app.cpp
|
||||
debug_collector.cpp
|
||||
camera.cpp
|
||||
config.cpp
|
||||
dev_panel.cpp
|
||||
gameplay/biome.cpp
|
||||
gameplay/chunk_generator.cpp
|
||||
gameplay/tree.cpp
|
||||
input.cpp
|
||||
map_table.cpp
|
||||
renderer.cpp
|
||||
shader.cpp
|
||||
texture_manager.cpp
|
||||
tools/cubed_random.cpp
|
||||
tools/shader_tools.cpp
|
||||
tools/font.cpp
|
||||
tools/perlin_noise.cpp
|
||||
ui/text.cpp
|
||||
window.cpp
|
||||
gameplay/builders/biome_builder.cpp
|
||||
gameplay/builders/plain_builder.cpp
|
||||
gameplay/builders/mountain_builder.cpp
|
||||
gameplay/builders/river_builder.cpp
|
||||
gameplay/builders/desert_builder.cpp
|
||||
gameplay/builders/forest_builder.cpp
|
||||
gameplay/cave_carver.cpp
|
||||
gameplay/cave_path.cpp
|
||||
gameplay/builders/snowy_plain_builder.cpp
|
||||
gameplay/river_worm.cpp
|
||||
gameplay/river_path.cpp
|
||||
gameplay/block.cpp
|
||||
gameplay/vertex_data.cpp
|
||||
gameplay/builders/ocean_builder.cpp
|
||||
gameplay/network_server.cpp
|
||||
gameplay/server_world.cpp
|
||||
gameplay/client_world.cpp
|
||||
gameplay/server_chunk.cpp
|
||||
gameplay/client_chunk.cpp
|
||||
gameplay/server_player.cpp
|
||||
gameplay/client_player.cpp
|
||||
gameplay/session.cpp
|
||||
gameplay/network_client.cpp
|
||||
player_renderer.cpp
|
||||
)
|
||||
133
src/app.cpp
133
src/app.cpp
@@ -2,19 +2,23 @@
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/debug_collector.hpp"
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
#include "Cubed/tools/arg_parser.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/system_info.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <imgui_impl_glfw.h>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
App::App() {}
|
||||
|
||||
App::~App() {}
|
||||
App::~App() {
|
||||
if (m_client) {
|
||||
m_client->stop();
|
||||
}
|
||||
}
|
||||
void App::cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
double ypos) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@@ -30,9 +34,13 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
app->m_camera.update_cursor_position_camera(xpos, ypos);
|
||||
}
|
||||
}
|
||||
void App::init() {
|
||||
void App::init(int argc, char** argv) {
|
||||
handle_toml();
|
||||
handle_argument(argc, argv);
|
||||
|
||||
m_window.init();
|
||||
m_window.imgui_init();
|
||||
|
||||
Logger::info("Window Init Success");
|
||||
|
||||
glfwSetWindowUserPointer(m_window.get_glfw_window(), this);
|
||||
@@ -50,6 +58,7 @@ void App::init() {
|
||||
glfwSetCursorEnterCallback(m_window.get_glfw_window(),
|
||||
cursor_enter_callback);
|
||||
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
|
||||
|
||||
ChunkGenerator::init();
|
||||
BlockManager::init();
|
||||
m_renderer.init();
|
||||
@@ -58,13 +67,94 @@ void App::init() {
|
||||
// MapTable::init_map();
|
||||
m_texture_manager.init_texture();
|
||||
Logger::info("Texture Load Success");
|
||||
m_world.init_world();
|
||||
if (!m_argument.is_client) {
|
||||
m_server.start_server(m_argument.port);
|
||||
}
|
||||
|
||||
m_client = std::make_shared<NetworkClient>(m_client_world);
|
||||
|
||||
m_client->start(m_argument.ip, m_argument.port);
|
||||
// init will send packet
|
||||
m_client_world.init(m_argument.player, m_client);
|
||||
|
||||
Logger::info("World Init Success");
|
||||
|
||||
m_camera.camera_init(&m_world.get_player("TestPlayer"));
|
||||
m_camera.camera_init(&m_client_world.get_player());
|
||||
m_dev_panel.init();
|
||||
}
|
||||
|
||||
void App::handle_argument(int argc, char** argv) {
|
||||
|
||||
static const std::unordered_map<std::string_view,
|
||||
std::function<void(ArgParser&)>>
|
||||
HANDLERS{
|
||||
|
||||
{"--client", [&](ArgParser&) { m_argument.is_client = true; }},
|
||||
|
||||
{"--host", [&](ArgParser&) { m_argument.is_client = false; }},
|
||||
|
||||
{"-p",
|
||||
[&](ArgParser& p) {
|
||||
auto arg = p.require_next("-p");
|
||||
|
||||
auto r = std::from_chars(arg.data(), arg.data() + arg.size(),
|
||||
m_argument.port);
|
||||
|
||||
if (r.ec != std::errc{} || r.ptr != arg.data() + arg.size()) {
|
||||
throw std::runtime_error(
|
||||
std::format("Invalid port: {}", arg));
|
||||
}
|
||||
|
||||
if (m_argument.port > 65535) {
|
||||
throw std::runtime_error(
|
||||
std::format("Port {} out of range", m_argument.port));
|
||||
}
|
||||
}},
|
||||
|
||||
{"--ip",
|
||||
[&](ArgParser& p) {
|
||||
auto arg = p.require_next("--ip");
|
||||
m_argument.ip = arg;
|
||||
}},
|
||||
{"--player",
|
||||
[&](ArgParser& p) {
|
||||
auto arg = p.require_next("--player");
|
||||
m_argument.player = arg;
|
||||
}},
|
||||
{"-V",
|
||||
[&](ArgParser) {
|
||||
std::cout << CUBED_VERSION << "\n";
|
||||
exit(EXIT_SUCCESS);
|
||||
}}
|
||||
|
||||
};
|
||||
ArgParser parser(argc, argv);
|
||||
|
||||
while (parser.has_next()) {
|
||||
auto arg = parser.next();
|
||||
if (auto it = HANDLERS.find(arg); it != HANDLERS.end()) {
|
||||
it->second(parser);
|
||||
} else {
|
||||
Logger::warn("Unknown argument: {}", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::handle_toml() {
|
||||
toml::table server;
|
||||
try {
|
||||
server = toml::parse_file("server.toml");
|
||||
} catch (const toml::parse_error& e) {
|
||||
// Logger::warn("Ip toml parse error {}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
m_argument.ip =
|
||||
*TOML::safe_get_value(server, "ip", std::string("127.0.01"));
|
||||
m_argument.port = *TOML::safe_get_value(server, "port", 25530);
|
||||
m_argument.is_client = *TOML::safe_get_value(server, "client", false);
|
||||
}
|
||||
|
||||
void App::key_callback(GLFWwindow* window, int key, int scancode, int action,
|
||||
int mods) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@@ -109,9 +199,14 @@ void App::key_callback(GLFWwindow* window, int key, int scancode, int action,
|
||||
app->m_camera.reset_camera();
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_F5:
|
||||
if (action == GLFW_PRESS) {
|
||||
app->m_camera.change_perspective();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
app->m_world.get_player("TestPlayer").update_player_move_state(key, action);
|
||||
app->m_client_world.get_player().update_player_move_state(key, action);
|
||||
}
|
||||
|
||||
void App::mouse_button_callback(GLFWwindow* window, int button, int action,
|
||||
@@ -162,8 +257,7 @@ void App::window_focus_callback(GLFWwindow* window, int focused) {
|
||||
}
|
||||
}
|
||||
|
||||
void App::window_reshape_callback(GLFWwindow* window, int new_width,
|
||||
int new_height) {
|
||||
void App::window_reshape_callback(GLFWwindow* window, int, int) {
|
||||
|
||||
App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
|
||||
ASSERT_MSG(app, "nullptr");
|
||||
@@ -180,7 +274,7 @@ void App::mouse_scroll_callback(GLFWwindow* window, double xoffset,
|
||||
ImGui_ImplGlfw_ScrollCallback(window, xoffset, yoffset);
|
||||
return;
|
||||
}
|
||||
auto& player = app->m_world.get_player("TestPlayer");
|
||||
auto& player = app->m_client_world.get_player();
|
||||
player.update_scroll(yoffset);
|
||||
}
|
||||
|
||||
@@ -218,10 +312,16 @@ void App::run() {
|
||||
|
||||
last_time = glfwGetTime();
|
||||
while (!glfwWindowShouldClose(m_window.get_glfw_window())) {
|
||||
|
||||
if (m_client_world.is_receive_exit()) {
|
||||
break;
|
||||
}
|
||||
update();
|
||||
render();
|
||||
}
|
||||
m_client_world.request_exit();
|
||||
if (!m_argument.is_client) {
|
||||
m_server.server_world().stop();
|
||||
}
|
||||
}
|
||||
static Gait player_gait = Gait::WALK;
|
||||
void App::update() {
|
||||
@@ -244,9 +344,9 @@ void App::update() {
|
||||
std::format("RSS: {}mb", Tools::get_current_rss() / (1024 * 1024)));
|
||||
}
|
||||
m_texture_manager.update();
|
||||
m_world.update(delta_time);
|
||||
m_client_world.update(delta_time);
|
||||
m_camera.update_move_camera();
|
||||
const auto& player = m_world.get_player("TestPlayer");
|
||||
const auto& player = m_client_world.get_player();
|
||||
if (player_gait != player.get_gait()) {
|
||||
player_gait = player.get_gait();
|
||||
float fov = static_cast<float>(Config::get().get<double>("player.fov"));
|
||||
@@ -265,7 +365,7 @@ int App::start_cubed_application(int argc, char** argv) {
|
||||
App app;
|
||||
|
||||
try {
|
||||
app.init();
|
||||
app.init(argc, argv);
|
||||
Logger::info("Game Init Finish Start Run...");
|
||||
app.run();
|
||||
|
||||
@@ -288,6 +388,7 @@ DevPanel& App::dev_panel() { return m_dev_panel; }
|
||||
Renderer& App::renderer() { return m_renderer; }
|
||||
TextureManager& App::texture_manager() { return m_texture_manager; }
|
||||
Window& App::window() { return m_window; }
|
||||
World& App::world() { return m_world; }
|
||||
|
||||
ClientWorld& App::client_world() { return m_client_world; }
|
||||
ServerWorld& App::server_world() { return m_server.server_world(); }
|
||||
const App::Argument& App::argument() const { return m_argument; }
|
||||
} // namespace Cubed
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "Cubed/camera.hpp"
|
||||
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/gameplay/client_player.hpp"
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr float DISTANCE = 4.0f;
|
||||
|
||||
} // namespace
|
||||
namespace Cubed {
|
||||
|
||||
Camera::Camera() {}
|
||||
@@ -12,7 +16,28 @@ void Camera::update_move_camera() {
|
||||
ASSERT_MSG(m_player, "nullptr");
|
||||
auto pos = m_player->get_player_pos();
|
||||
// pos.y need to add 1.6f to center
|
||||
m_camera_pos = glm::vec3(pos.x, pos.y + 1.6f, pos.z);
|
||||
static constexpr float PLAYER_EYE_OFFSET = 1.6f;
|
||||
glm::vec3 eye = glm::vec3(pos.x, pos.y + PLAYER_EYE_OFFSET, pos.z);
|
||||
auto forward = m_player->get_front();
|
||||
m_front = forward;
|
||||
switch (m_perspective) {
|
||||
case Perspective::FIRST_PERSON:
|
||||
m_camera_pos = eye;
|
||||
break;
|
||||
case Perspective::THIRD_PERSON_BACK: {
|
||||
constexpr float CAMERA_RADIUS = 0.2f;
|
||||
|
||||
glm::vec3 target = eye - forward * DISTANCE;
|
||||
m_camera_pos = camera_collision(eye, target, CAMERA_RADIUS);
|
||||
} break;
|
||||
case Perspective::THIRD_PERSON_FRONT: {
|
||||
m_front = -forward;
|
||||
constexpr float CAMERA_RADIUS = 0.2f;
|
||||
|
||||
glm::vec3 target = eye + forward * DISTANCE;
|
||||
m_camera_pos = camera_collision(eye, target, CAMERA_RADIUS);
|
||||
} break;
|
||||
}
|
||||
glm::ivec3 block_pos = glm::floor(m_camera_pos);
|
||||
auto& world = m_player->get_world();
|
||||
if (world.get_block_tpye(block_pos) == 7) {
|
||||
@@ -22,7 +47,7 @@ void Camera::update_move_camera() {
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::camera_init(Player* player) {
|
||||
void Camera::camera_init(ClientPlayer* player) {
|
||||
m_player = player;
|
||||
update_move_camera();
|
||||
reset_camera();
|
||||
@@ -52,7 +77,7 @@ void Camera::update_cursor_position_camera(double xpos, double ypos) {
|
||||
|
||||
const glm::mat4 Camera::get_camera_lookat() const {
|
||||
ASSERT_MSG(m_player, "nullptr");
|
||||
return glm::lookAt(m_camera_pos, m_camera_pos + m_player->get_front(),
|
||||
return glm::lookAt(m_camera_pos, m_camera_pos + m_front,
|
||||
glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
|
||||
@@ -60,6 +85,44 @@ const glm::vec3& Camera::get_camera_pos() const { return m_camera_pos; }
|
||||
|
||||
bool Camera::is_under_water() const { return m_under_water; }
|
||||
|
||||
glm::vec3 Camera::get_camera_front() const { return m_player->get_front(); }
|
||||
glm::vec3 Camera::get_camera_front() const { return m_front; }
|
||||
|
||||
void Camera::change_perspective() {
|
||||
switch (m_perspective) {
|
||||
case Perspective::FIRST_PERSON:
|
||||
m_perspective = Perspective::THIRD_PERSON_BACK;
|
||||
break;
|
||||
case Perspective::THIRD_PERSON_BACK:
|
||||
m_perspective = Perspective::THIRD_PERSON_FRONT;
|
||||
break;
|
||||
case Perspective::THIRD_PERSON_FRONT:
|
||||
m_perspective = Perspective::FIRST_PERSON;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool Camera::is_first_person() const {
|
||||
return m_perspective == Perspective::FIRST_PERSON;
|
||||
}
|
||||
|
||||
glm::vec3 Camera::camera_collision(glm::vec3 start, glm::vec3 end,
|
||||
float radius) {
|
||||
constexpr float STEP = 0.05f;
|
||||
|
||||
glm::vec3 last = start;
|
||||
|
||||
glm::vec3 dir = glm::normalize(end - start);
|
||||
float len = glm::length(end - start);
|
||||
|
||||
for (float t = 0.0f; t <= len; t += STEP) {
|
||||
glm::vec3 p = start + dir * t;
|
||||
|
||||
if (m_player->get_world().sphere_collide_world(p, radius))
|
||||
return last;
|
||||
|
||||
last = p;
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -24,7 +24,6 @@ toml::table& Config::table() { return m_tbl; }
|
||||
|
||||
void Config::create_config() {
|
||||
static constexpr auto SOURCE = R"(
|
||||
version = "0.0.1"
|
||||
|
||||
[window]
|
||||
width = 800
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "Cubed/debug_collector.hpp"
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
#include "Cubed/tools/system_info.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
@@ -24,10 +24,16 @@ void DebugCollector::init_text() {
|
||||
Text opengl_version_text("opengl_version");
|
||||
Text biome_text("biome");
|
||||
Text speed_text("speed");
|
||||
std::string version{"Version: " CUBED_VERSION};
|
||||
#ifdef DEBUG_MODE
|
||||
version.append("-debug");
|
||||
#else
|
||||
version.append("-release");
|
||||
#endif
|
||||
version_text.position(0.0f, 100.0f)
|
||||
.scale(0.8f)
|
||||
.color(Color::WHITE)
|
||||
.text("Version: " + Config::get().get<std::string>("version"));
|
||||
.text(version);
|
||||
fps_text.position(0.0f, 50.0f).text("FPS: 0");
|
||||
player_pos_text.position(0.0f, 150.0f)
|
||||
.scale(0.8f)
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
#include "Cubed/app.hpp"
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
#include "Cubed/gameplay/cave_path.hpp"
|
||||
#include "Cubed/gameplay/client_player.hpp"
|
||||
#include "Cubed/gameplay/river.path.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -14,7 +16,7 @@ namespace Cubed {
|
||||
static constexpr const char* THEMES[] = {"Dark", "Light"};
|
||||
static constexpr const char* GAITS[] = {"Walk", "Run"};
|
||||
static constexpr const char* GAME_MODES[] = {"Creative", "Spectator"};
|
||||
static char perlin_noise_input_buffer[64];
|
||||
static constexpr const char* CHUNK_LOAD_STYLE[] = {"Random", "Center"};
|
||||
|
||||
constexpr float TEMP_MIN = 0.0f;
|
||||
constexpr float TEMP_MAX = 1.0f;
|
||||
@@ -33,8 +35,8 @@ constexpr int AMPLITUDE_MAX = 80;
|
||||
constexpr float TREE_FREQ_MIM = 0.001f;
|
||||
constexpr float TREE_FREQ_MAX = 0.3f;
|
||||
|
||||
constexpr float PATH_PROBABILITY_MIN = 0.005f;
|
||||
constexpr float PATH_PROBABILITY_MAX = 0.1f;
|
||||
// constexpr float PATH_PROBABILITY_MIN = 0.005f;
|
||||
// constexpr float PATH_PROBABILITY_MAX = 0.1f;
|
||||
constexpr float RADIUS_XZ_MIN = 1.0f;
|
||||
constexpr float RADIUS_XZ_MAX = 50.0f;
|
||||
constexpr float RADIUS_Y_MIN = 1.0f;
|
||||
@@ -44,20 +46,10 @@ constexpr float DELTA_ANGLE_MAX = 30.0f;
|
||||
constexpr int PATH_STEP_MIN = 1;
|
||||
constexpr int PATH_STEP_MAX = 1000;
|
||||
|
||||
static int filter_unsigned(ImGuiInputTextCallbackData* data) {
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) {
|
||||
char c = data->EventChar;
|
||||
if (c < '0' || c > '9') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
DevPanel::DevPanel(App& app) : m_app(app) {}
|
||||
|
||||
void DevPanel::init() {
|
||||
m_player = &m_app.world().get_player("TestPlayer");
|
||||
m_player = &m_app.client_world().get_player();
|
||||
update_config_view();
|
||||
update_player_profile();
|
||||
}
|
||||
@@ -109,6 +101,9 @@ void DevPanel::show_about_table_bar() {
|
||||
ImGui::Text("toml++");
|
||||
ImGui::Text("Dear ImGui");
|
||||
ImGui::Text("Tbb");
|
||||
ImGui::Text("Asio");
|
||||
ImGui::Text("protobuf");
|
||||
ImGui::Text("zstd");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Special Thanks");
|
||||
ImGui::Text("TANGERIME");
|
||||
@@ -266,7 +261,7 @@ void DevPanel::show_biome_table_bar() {
|
||||
}
|
||||
|
||||
void DevPanel::show_time_table_bar() {
|
||||
World& world = m_app.world();
|
||||
ServerWorld& world = m_app.server_world();
|
||||
ImGui::Text("Game Tick %llu", world.game_tick());
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Day Tick %llu", world.day_tick());
|
||||
@@ -291,11 +286,11 @@ void DevPanel::show_time_table_bar() {
|
||||
}
|
||||
|
||||
void DevPanel::show_cave_table_bar() {
|
||||
auto& cave_carcer = m_app.world().cave_carcer();
|
||||
// auto& cave_carcer = m_app.world().cave_carcer();
|
||||
|
||||
ImGui::Text("Total Cave Sum %d", cave_carcer.cave_sum());
|
||||
ImGui::SliderFloat("Cave Probability", &cave_carcer.cave_probability(),
|
||||
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
|
||||
// ImGui::Text("Total Cave Sum %d", cave_carcer.cave_sum());
|
||||
// ImGui::SliderFloat("Cave Probability", &cave_carcer.cave_probability(),
|
||||
// PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
|
||||
ImGui::SliderFloat("Radius XZ Min", &CavePath::radius_xz_min(),
|
||||
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
|
||||
ImGui::SliderFloat("Radius XZ Max", &CavePath::radius_xz_max(),
|
||||
@@ -315,11 +310,11 @@ void DevPanel::show_cave_table_bar() {
|
||||
}
|
||||
|
||||
void DevPanel::show_river_table_bar() {
|
||||
auto& river_wrom = m_app.world().river_worm();
|
||||
// auto& river_wrom = m_app.world().river_worm();
|
||||
|
||||
ImGui::Text("Total River Sum %d", river_wrom.river_sum());
|
||||
ImGui::SliderFloat("River Probability", &river_wrom.river_probability(),
|
||||
PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
|
||||
// ImGui::Text("Total River Sum %d", river_wrom.river_sum());
|
||||
// ImGui::SliderFloat("River Probability", &river_wrom.river_probability(),
|
||||
// PATH_PROBABILITY_MIN, PATH_PROBABILITY_MAX);
|
||||
ImGui::SliderFloat("Radius XZ Min##river", &RiverPath::radius_xz_min(),
|
||||
RADIUS_XZ_MIN, RADIUS_XZ_MAX);
|
||||
ImGui::SliderFloat("Radius XZ Max##river", &RiverPath::radius_xz_max(),
|
||||
@@ -338,6 +333,20 @@ void DevPanel::show_river_table_bar() {
|
||||
PATH_STEP_MAX);
|
||||
}
|
||||
|
||||
void DevPanel::show_chunk_table_bar() {
|
||||
/*
|
||||
auto& world = m_app.client_world();
|
||||
auto& player = world.get_player();
|
||||
|
||||
ImGui::Text("Chunk X: %d Z: %d Info", info.pos.x, info.pos.z);
|
||||
ImGui::Text("Seed: %u", info.seed);
|
||||
ImGui::Text("%s", ("Biome " + get_biome_str(info.biome)).c_str());
|
||||
ImGui::Text("First Random %u", info.first_random);
|
||||
ImGui::Text("%s",
|
||||
std::format("Has Cave Start {}", info.has_cave_start).c_str());
|
||||
ImGui::Text("%s", std::format("Has Cave {}", info.has_cave).c_str());*/
|
||||
}
|
||||
|
||||
void DevPanel::show_settings_tab_item() {
|
||||
if (ImGui::BeginTabItem("settings")) {
|
||||
if (ImGui::SliderFloat("FOV", &m_config.fov, 1.0f, 140.0f)) {
|
||||
@@ -368,7 +377,7 @@ void DevPanel::show_settings_tab_item() {
|
||||
128)) {
|
||||
Config::get().set("world.rendering_distance",
|
||||
m_config.rendering_distance);
|
||||
m_app.world().hot_reload();
|
||||
m_app.client_world().hot_reload();
|
||||
}
|
||||
if (ImGui::Checkbox("Fullscreen", &m_config.fullscreen)) {
|
||||
Config::get().set("window.fullscreen", m_config.fullscreen);
|
||||
@@ -414,7 +423,7 @@ void DevPanel::show_settings_tab_item() {
|
||||
}
|
||||
if (ImGui::Button("ReloadTexture")) {
|
||||
Config::get().set("texture.aniso", m_config.aniso);
|
||||
m_app.texture_manager().hot_reload();
|
||||
m_app.texture_manager().need_reload();
|
||||
m_config.is_reload = true;
|
||||
}
|
||||
if (!m_config.is_reload) {
|
||||
@@ -439,80 +448,116 @@ void DevPanel::show_settings_tab_item() {
|
||||
|
||||
void DevPanel::show_world_tab_item() {
|
||||
if (ImGui::BeginTabItem("world")) {
|
||||
if (m_text_editing.perlin_seed) {
|
||||
if (ImGui::InputText("ChunkGenerator Seed",
|
||||
perlin_noise_input_buffer,
|
||||
sizeof(perlin_noise_input_buffer),
|
||||
ImGuiInputTextFlags_CallbackCharFilter |
|
||||
ImGuiInputTextFlags_EnterReturnsTrue,
|
||||
filter_unsigned)) {
|
||||
ChunkGenerator::seed(static_cast<unsigned int>(
|
||||
std::strtoul(perlin_noise_input_buffer, nullptr, 10)));
|
||||
m_text_editing.perlin_seed = false;
|
||||
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
|
||||
m_app.world().rebuild_world();
|
||||
|
||||
if (ImGui::BeginTabBar("World Kind")) {
|
||||
if (!m_app.argument().is_client) {
|
||||
if (ImGui::BeginTabItem("ServerWorld")) {
|
||||
show_server_world_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!m_text_editing.perlin_seed) {
|
||||
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_text_editing.perlin_seed = true;
|
||||
}
|
||||
}
|
||||
static int rendering_distance = m_app.world().rendering_distance();
|
||||
if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) {
|
||||
m_app.world().rendering_distance(rendering_distance);
|
||||
}
|
||||
if (ImGui::Button("Rebuild World")) {
|
||||
m_app.world().rebuild_world();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Request Chunk Build")) {
|
||||
m_app.world().need_gen();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Spawn Point")) {
|
||||
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
||||
if (m_gen_thread_running) {
|
||||
m_app.world().start_gen_thread();
|
||||
} else {
|
||||
m_app.world().stop_gen_thread();
|
||||
}
|
||||
}
|
||||
ImGui::Text("Chunk Build Progress\n");
|
||||
ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
|
||||
if (ImGui::BeginTabBar("World Settings")) {
|
||||
if (ImGui::BeginTabItem("Time")) {
|
||||
show_time_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Cave")) {
|
||||
show_cave_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("River")) {
|
||||
show_river_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Biome")) {
|
||||
show_biome_table_bar();
|
||||
if (ImGui::BeginTabItem("Client World")) {
|
||||
show_client_world_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
// ImGui::Text("Chunk Build Progress\n");
|
||||
// ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
|
||||
// show_chunk_table_bar();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
void DevPanel::show_server_world_table_bar() {
|
||||
|
||||
ImGui::Text("ChunkGenerator Seed: %u", ChunkGenerator::seed());
|
||||
|
||||
ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d",
|
||||
m_app.server_world().gen_pool_threads(),
|
||||
m_app.server_world().max_threads(), RESERVED_THREADS);
|
||||
ImGui::SliderInt("Set Pool Threads", &m_threads, 1,
|
||||
m_app.server_world().max_threads());
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Set")) {
|
||||
m_app.server_world().change_pool_threads(
|
||||
ServerWorld::ThreadPoolKind::GEN, m_threads);
|
||||
}
|
||||
if (m_threads > m_app.server_world().max_threads() - RESERVED_THREADS) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1.0f, 1.0f, 0.0f, 1.0f),
|
||||
"Waring: When the threads in the thread pool exceed \n(maximum "
|
||||
"threads minus reserved threads), \nit may cause stuttering.");
|
||||
}
|
||||
|
||||
m_chunk_style = m_app.server_world().chunk_load_style();
|
||||
if (ImGui::Combo("ChunkLoadStyle", &m_chunk_style, CHUNK_LOAD_STYLE,
|
||||
IM_ARRAYSIZE(CHUNK_LOAD_STYLE))) {
|
||||
m_app.server_world().set_chunk_load_style(m_chunk_style);
|
||||
}
|
||||
|
||||
if (ImGui::Button("Request Chunk Build")) {
|
||||
m_app.server_world().need_gen(m_player->get_uuid());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
|
||||
if (m_gen_thread_running) {
|
||||
m_app.server_world().start_gen_thread();
|
||||
} else {
|
||||
m_app.server_world().stop_gen_thread();
|
||||
}
|
||||
}
|
||||
ImGui::Text("Server Chunk Size %d", m_app.server_world().chunk_size());
|
||||
|
||||
if (ImGui::BeginTabBar("World Settings")) {
|
||||
if (ImGui::BeginTabItem("Time")) {
|
||||
show_time_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
/*
|
||||
if (ImGui::BeginTabItem("Cave")) {
|
||||
show_cave_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("River")) {
|
||||
show_river_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}*/
|
||||
if (ImGui::BeginTabItem("Biome")) {
|
||||
show_biome_table_bar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
void DevPanel::show_client_world_table_bar() {
|
||||
|
||||
static int rendering_distance = m_app.client_world().rendering_distance();
|
||||
if (ImGui::SliderInt("Render Distance", &rendering_distance, 2, 128)) {
|
||||
m_app.client_world().rendering_distance(rendering_distance);
|
||||
// Config::get().set("world.rendering_distance", rendering_distance);
|
||||
}
|
||||
if (ImGui::Button("Rebuild World")) {
|
||||
m_app.client_world().rebuild_world();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Spawn Point")) {
|
||||
m_player->set_player_pos({0.0f, 255.0f, 0.0f});
|
||||
}
|
||||
ImGui::Text("Chunk Task Id %d", m_app.client_world().get_chunk_task_id());
|
||||
ImGui::Text("Client World Chunk %d", m_app.client_world().chunk_size());
|
||||
}
|
||||
|
||||
void DevPanel::show_player_tab_item() {
|
||||
if (!m_player) {
|
||||
Logger::error("Player is Nullptr");
|
||||
return;
|
||||
}
|
||||
if (ImGui::BeginTabItem("player")) {
|
||||
ImGui::Text("Player %s", m_player->get_name().c_str());
|
||||
if (ImGui::Combo("GameMode", &m_player_profile.game_mode, GAME_MODES,
|
||||
IM_ARRAYSIZE(GAME_MODES))) {
|
||||
if (m_player_profile.game_mode == 0) {
|
||||
@@ -527,9 +572,9 @@ void DevPanel::show_player_tab_item() {
|
||||
if (ImGui::Combo("Gait", &m_player_profile.gait, GAITS,
|
||||
IM_ARRAYSIZE(GAITS))) {
|
||||
if (m_player_profile.gait == 0) {
|
||||
m_player->gait() = Gait::WALK;
|
||||
m_player->set_gait(Gait::WALK);
|
||||
} else if (m_player_profile.gait == 1) {
|
||||
m_player->gait() = Gait::RUN;
|
||||
m_player->set_gait(Gait::RUN);
|
||||
} else {
|
||||
ASSERT_MSG(false, "Unknown Gait");
|
||||
}
|
||||
@@ -542,6 +587,8 @@ void DevPanel::show_player_tab_item() {
|
||||
m_player_profile.pos[1],
|
||||
m_player_profile.pos[2]});
|
||||
}
|
||||
ImGui::SliderFloat("Fly Y Speed", &m_player->fly_y_speed(), 0.0f,
|
||||
100.0f);
|
||||
ImGui::SliderFloat("Acceleration", &m_player->acceleration(), 1.0f,
|
||||
200.0f);
|
||||
ImGui::SliderFloat("Deceleration", &m_player->deceleration(), 1.0f,
|
||||
@@ -566,11 +613,11 @@ void DevPanel::show_player_tab_item() {
|
||||
m_player->deceleration() = DEFAULT_DECELERATION;
|
||||
m_player->g() = DEFAULT_G;
|
||||
m_player->change_mode(GameMode::CREATIVE);
|
||||
m_player->gait() = Gait::WALK;
|
||||
m_player->set_gait(Gait::WALK);
|
||||
m_player_profile.game_mode = 0;
|
||||
m_player_profile.gait = 0;
|
||||
}
|
||||
if (m_player->gait() == Gait::WALK) {
|
||||
if (m_player->get_gait() == Gait::WALK) {
|
||||
m_player_profile.gait = 0;
|
||||
} else {
|
||||
m_player_profile.gait = 1;
|
||||
@@ -623,8 +670,9 @@ void DevPanel::show_shader_tab_item() {
|
||||
ImGui::Checkbox("Flip Y", &m_app.renderer().flip_y());
|
||||
if (ImGui::SliderFloat("AmbientStrength",
|
||||
&m_app.renderer().ambient_strength(), 0.0f,
|
||||
0.35f))
|
||||
;
|
||||
0.35f)) {
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("SpecularStrength",
|
||||
&m_app.renderer().specular_strength(), 0.0f, 2.0f);
|
||||
ImGui::Checkbox("Discard Transparent",
|
||||
@@ -705,7 +753,7 @@ void DevPanel::update_player_profile() {
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
m_player_profile.gait = std::to_underlying(m_player->gait());
|
||||
m_player_profile.gait = std::to_underlying(m_player->get_gait());
|
||||
m_player_profile.game_mode = std::to_underlying(m_player->game_mode());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/toml.utils.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
using namespace Cubed::TOML;
|
||||
namespace {
|
||||
std::string block_data_dir = ASSETS_PATH + "data/block"s;
|
||||
|
||||
template <Cubed::TomlValueType T>
|
||||
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
|
||||
const T& default_value) {
|
||||
auto value = table[key].value<T>();
|
||||
if (value == std::nullopt) {
|
||||
Cubed::Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}",
|
||||
key, default_value);
|
||||
value = default_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Cubed {
|
||||
@@ -1,39 +1,39 @@
|
||||
#include "Cubed/gameplay/builders/biome_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
void BiomeBuilder::build_bottom() {
|
||||
ChunkGenerator& chunk_generator = get_chunk_generator();
|
||||
Chunk& chunk = chunk_generator.chunk();
|
||||
ServerChunk& chunk = chunk_generator.chunk();
|
||||
auto& m_blocks = chunk.blocks();
|
||||
for (int x = 0; x < CHUNK_SIZE; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void BiomeBuilder::place_grass() {
|
||||
ChunkGenerator& chunk_generator = get_chunk_generator();
|
||||
Chunk& chunk = chunk_generator.chunk();
|
||||
ServerChunk& chunk = chunk_generator.chunk();
|
||||
auto& blocks = chunk.blocks();
|
||||
const auto& heightmap = chunk.get_heightmap();
|
||||
auto& random = chunk_generator.random();
|
||||
for (int x = 0; x < SIZE_X; ++x) {
|
||||
for (int z = 0; z < SIZE_Z; ++z) {
|
||||
int y = heightmap[x][z];
|
||||
BlockType top_id = blocks[Chunk::index(x, y, z)];
|
||||
BlockType top_id = blocks[ServerChunk::index(x, y, z)];
|
||||
if (top_id != 1) {
|
||||
continue;
|
||||
}
|
||||
if (blocks[Chunk::index(x, y + 1, z)] != 0) {
|
||||
if (blocks[ServerChunk::index(x, y + 1, z)] != 0) {
|
||||
continue;
|
||||
}
|
||||
if (random.random_bool(0.2)) {
|
||||
if (y + 1 < SIZE_Y) {
|
||||
blocks[Chunk::index(x, y + 1, z)] = 9;
|
||||
blocks[ServerChunk::index(x, y + 1, z)] = 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ void BiomeBuilder::place_grass() {
|
||||
|
||||
void BiomeBuilder::ocean_water_build() {
|
||||
ChunkGenerator& chunk_generator = get_chunk_generator();
|
||||
Chunk& chunk = chunk_generator.chunk();
|
||||
ServerChunk& chunk = chunk_generator.chunk();
|
||||
auto& blocks = chunk.blocks();
|
||||
const auto& heightmap = chunk.get_heightmap();
|
||||
|
||||
@@ -51,7 +51,7 @@ void BiomeBuilder::ocean_water_build() {
|
||||
int height = heightmap[x][z];
|
||||
if (height <= SEA_LEVEL) {
|
||||
for (int y = height; y <= SEA_LEVEL; y++) {
|
||||
blocks[Chunk::index(x, y, z)] = 7;
|
||||
blocks[ServerChunk::index(x, y, z)] = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/desert_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
DesertBuilder::DesertBuilder(ChunkGenerator& chunk_generator)
|
||||
: m_chunk_generator(chunk_generator) {}
|
||||
@@ -19,11 +19,11 @@ void DesertBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y < height - 5; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
|
||||
for (int y = height - 5; y <= height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 4;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/forest_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/tree.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -24,12 +24,12 @@ void ForestBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y < height - 5; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
for (int y = height - 5; y < height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 2;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 2;
|
||||
}
|
||||
m_blocks[Chunk::index(x, height, z)] = 1;
|
||||
m_blocks[ServerChunk::index(x, height, z)] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/mountain_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
MountainBuilder::MountainBuilder(ChunkGenerator& chunk_generator)
|
||||
: m_chunk_generator(chunk_generator) {}
|
||||
@@ -19,7 +19,7 @@ void MountainBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y <= height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/ocean_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
OceanBuilder::OceanBuilder(ChunkGenerator& chunk_generator)
|
||||
: m_chunk_generator(chunk_generator) {}
|
||||
@@ -19,7 +19,7 @@ void OceanBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y <= height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/plain_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
PlainBuilder::PlainBuilder(ChunkGenerator& chunk_generator)
|
||||
: m_chunk_generator(chunk_generator) {}
|
||||
@@ -19,12 +19,12 @@ void PlainBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y < height - 5; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
for (int y = height - 5; y < height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 2;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 2;
|
||||
}
|
||||
m_blocks[Chunk::index(x, height, z)] = 1;
|
||||
m_blocks[ServerChunk::index(x, height, z)] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
namespace Cubed {
|
||||
SnowyPlainBuilder::SnowyPlainBuilder(ChunkGenerator& chunk_generator)
|
||||
: m_chunk_generator(chunk_generator) {}
|
||||
@@ -19,12 +19,12 @@ void SnowyPlainBuilder::build_blocks() {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
int height = static_cast<int>(m_heightmap[x][z]);
|
||||
for (int y = 5; y < height - 5; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 3;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 3;
|
||||
}
|
||||
for (int y = height - 5; y < height; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = 2;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = 2;
|
||||
}
|
||||
m_blocks[Chunk::index(x, height, z)] = 8;
|
||||
m_blocks[ServerChunk::index(x, height, z)] = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,55 @@
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
|
||||
#include "Cubed/gameplay/cave_path.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
#include "Cubed/tools/cubed_random.hpp"
|
||||
namespace Cubed {
|
||||
CaveCarver::CaveCarver() {}
|
||||
|
||||
CaveCarver::CaveHashMap& CaveCarver::paths() { return m_paths; }
|
||||
|
||||
void CaveCarver::init(unsigned world_seed) {
|
||||
m_seed = world_seed;
|
||||
m_random.init(m_seed);
|
||||
}
|
||||
void CaveCarver::init(unsigned world_seed) { m_world_seed = world_seed; }
|
||||
|
||||
void CaveCarver::reload(unsigned world_seed) {
|
||||
m_seed = world_seed;
|
||||
m_paths.clear();
|
||||
|
||||
m_world_seed = world_seed;
|
||||
|
||||
init(world_seed);
|
||||
}
|
||||
|
||||
void CaveCarver::add_path(const glm::vec3& pos, unsigned chunk_seed) {
|
||||
m_paths.emplace(chunk_seed, CavePath{chunk_seed, m_seed, pos});
|
||||
bool CaveCarver::has_origin_fast(const ChunkPos& pos) const {
|
||||
unsigned h = HASH::combine_32(HASH::combine_32(pos.x, pos.z), m_world_seed);
|
||||
|
||||
return (h & 0xFFFF) < static_cast<unsigned>(m_cave_probability * 0xFFFF);
|
||||
}
|
||||
|
||||
void CaveCarver::try_to_add_path(const ChunkPos& chunk_pos,
|
||||
unsigned chunk_seed) {
|
||||
{
|
||||
CaveHashMap::const_accessor acc;
|
||||
if (m_paths.find(acc, chunk_seed)) {
|
||||
return;
|
||||
}
|
||||
PathOrigin CaveCarver::get_origin(const ChunkPos& origin_chunk) const {
|
||||
// Quickly check if there is an origin point without constructing Random
|
||||
if (!has_origin_fast(origin_chunk)) {
|
||||
return {false, {}, 0};
|
||||
}
|
||||
|
||||
unsigned chunk_seed =
|
||||
HASH::chunk_seed_hash(origin_chunk.x, origin_chunk.z, m_world_seed);
|
||||
Random random{chunk_seed};
|
||||
if (random.random_bool(static_cast<double>(m_cave_probability))) {
|
||||
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
const int CHUNK_MIN_Y = 0;
|
||||
const int CHUNK_MAX_Y = SIZE_Y - 1;
|
||||
int max_y = std::min(CHUNK_MAX_Y, 40);
|
||||
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
|
||||
int y = random.random_int(CHUNK_MIN_Y + 1, max_y);
|
||||
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
|
||||
add_path(glm::vec3{x, y, z}, chunk_seed);
|
||||
}
|
||||
const int CHUNK_MIN_X = origin_chunk.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = origin_chunk.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
const int CHUNK_MIN_Y = 0;
|
||||
const int CHUNK_MAX_Y = SIZE_Y - 1;
|
||||
int max_y = std::min(CHUNK_MAX_Y, 40);
|
||||
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
|
||||
int y = random.random_int(CHUNK_MIN_Y + 1, max_y);
|
||||
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
|
||||
return {true, {x, y, z}, chunk_seed};
|
||||
}
|
||||
|
||||
void CaveCarver::cleanup_finished_caves() {
|
||||
std::vector<unsigned int> finished_keys;
|
||||
for (const auto& pair : m_paths) {
|
||||
if (pair.second.is_finished()) {
|
||||
finished_keys.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
for (const auto& key : finished_keys) {
|
||||
m_paths.erase(key);
|
||||
}
|
||||
int CaveCarver::search_radius() const {
|
||||
float max_displacement =
|
||||
3.0f * std::sqrt(static_cast<float>(CavePath::step_max())) *
|
||||
CavePath::step_len();
|
||||
return static_cast<int>(
|
||||
std::ceil((max_displacement + CavePath::radius_xz_max()) / CHUNK_SIZE));
|
||||
}
|
||||
|
||||
int CaveCarver::cave_sum() const { return m_paths.size(); }
|
||||
float& CaveCarver::cave_probability() { return m_cave_probability; }
|
||||
unsigned CaveCarver::world_seed() const { return m_world_seed; }
|
||||
float CaveCarver::cave_probability() const { return m_cave_probability; }
|
||||
} // namespace Cubed
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "Cubed/gameplay/cave_path.hpp"
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
|
||||
@@ -21,15 +20,12 @@ CavePath::CavePath(unsigned int chunk_seed, unsigned world_seed,
|
||||
m_points.reserve(m_step + 1);
|
||||
m_points.push_back(m_start_path_point);
|
||||
collect_path_points();
|
||||
precompute_chunk_coverage();
|
||||
}
|
||||
|
||||
void CavePath::collect_path_points() {
|
||||
|
||||
for (int i = 0; i < m_step; i++) {
|
||||
|
||||
m_yaw = std::fmod(m_yaw, 360.0f);
|
||||
if (m_yaw < 0.0f)
|
||||
m_yaw += 360.0f;
|
||||
m_pitch = std::clamp(m_pitch, -90.0f, 90.0f);
|
||||
|
||||
float dx = std::cos(glm::radians(m_pitch)) *
|
||||
@@ -58,30 +54,7 @@ void CavePath::collect_path_points() {
|
||||
}
|
||||
}
|
||||
|
||||
void CavePath::precompute_chunk_coverage() {
|
||||
for (const auto& point : m_points) {
|
||||
float rad = point.rad_xz;
|
||||
const glm::vec3& center = point.pos;
|
||||
|
||||
int min_cx =
|
||||
static_cast<int>(std::floor((center.x - rad) / CHUNK_SIZE));
|
||||
int max_cx =
|
||||
static_cast<int>(std::floor((center.x + rad) / CHUNK_SIZE));
|
||||
int min_cz =
|
||||
static_cast<int>(std::floor((center.z - rad) / CHUNK_SIZE));
|
||||
int max_cz =
|
||||
static_cast<int>(std::floor((center.z + rad) / CHUNK_SIZE));
|
||||
|
||||
for (int cx = min_cx; cx <= max_cx; ++cx)
|
||||
for (int cz = min_cz; cz <= max_cz; ++cz)
|
||||
m_pending_chunks.insert(
|
||||
std::make_pair(ChunkPos{cx, cz}, false));
|
||||
}
|
||||
}
|
||||
|
||||
void CavePath::clear_chunk(const ChunkPos& pos) { m_pending_chunks.erase(pos); }
|
||||
const std::vector<PathPoint>& CavePath::points() const { return m_points; }
|
||||
bool CavePath::is_finished() const { return m_pending_chunks.empty(); }
|
||||
|
||||
float& CavePath::radius_xz_min() { return m_radius_xz_min; }
|
||||
float& CavePath::radius_xz_max() { return m_radius_xz_max; }
|
||||
@@ -91,5 +64,5 @@ float& CavePath::delta_angle_min() { return m_delta_angle_min; }
|
||||
float& CavePath::delta_angle_max() { return m_delta_angle_max; }
|
||||
int& CavePath::step_min() { return m_step_min; }
|
||||
int& CavePath::step_max() { return m_step_max; }
|
||||
|
||||
int CavePath::step_len() { return m_step_len; }
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -1,460 +0,0 @@
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
Chunk::Chunk(World& world, ChunkPos chunk_pos)
|
||||
: m_chunk_pos(chunk_pos), m_world(world) {
|
||||
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
|
||||
m_vertex_data.emplace_back(m_world);
|
||||
}
|
||||
}
|
||||
|
||||
Chunk::~Chunk() {}
|
||||
|
||||
Chunk::Chunk(Chunk&& other) noexcept
|
||||
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
|
||||
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
|
||||
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
|
||||
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
|
||||
m_blocks(std::move(other.m_blocks)),
|
||||
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed),
|
||||
m_conditions(other.m_conditions) {}
|
||||
|
||||
Chunk& Chunk::operator=(Chunk&& other) noexcept {
|
||||
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
||||
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
|
||||
// static_cast<const void*>(&other));
|
||||
|
||||
m_chunk_pos = std::move(other.m_chunk_pos);
|
||||
m_heightmap = std::move(other.m_heightmap);
|
||||
m_blocks = std::move(other.m_blocks);
|
||||
m_dirty = other.is_dirty();
|
||||
m_vertex_data = std::move(other.m_vertex_data);
|
||||
m_biome = other.m_biome.load();
|
||||
m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load();
|
||||
m_need_upload = other.m_need_upload.load();
|
||||
m_seed = other.m_seed;
|
||||
m_conditions = other.m_conditions;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::tuple<int, int, int> Chunk::world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z) {
|
||||
int x, y, z;
|
||||
y = world_y;
|
||||
x = world_x - chunk_x * CHUNK_SIZE;
|
||||
z = world_z - chunk_z * CHUNK_SIZE;
|
||||
return {x, y, z};
|
||||
}
|
||||
|
||||
std::tuple<int, int, int> Chunk::world_to_block(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos) {
|
||||
return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
|
||||
std::tuple<int, int, int> Chunk::block_to_world(int x, int y, int z,
|
||||
int chunk_x, int chunk_z) {
|
||||
int world_x = x + chunk_x * CHUNK_SIZE;
|
||||
int world_z = z + chunk_z * CHUNK_SIZE;
|
||||
int world_y = y;
|
||||
return {world_x, world_y, world_z};
|
||||
}
|
||||
std::tuple<int, int, int> Chunk::block_to_world(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos) {
|
||||
return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
|
||||
BiomeType Chunk::get_biome() const { return m_biome.load(); }
|
||||
|
||||
ChunkPos Chunk::get_chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
const std::vector<BlockType>& Chunk::get_chunk_blocks() const {
|
||||
return m_blocks;
|
||||
}
|
||||
|
||||
HeightMapArray Chunk::get_heightmap() const {
|
||||
// Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x,
|
||||
// m_chunk_pos.z, static_cast<const void*>(this));
|
||||
return m_heightmap;
|
||||
}
|
||||
|
||||
int Chunk::index(int x, int y, int z) {
|
||||
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE));
|
||||
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
|
||||
(x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >=
|
||||
CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) {
|
||||
Logger::error("block pos x {} y {} z {} range error", x, y, z);
|
||||
ASSERT(0);
|
||||
}
|
||||
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
|
||||
}
|
||||
|
||||
int Chunk::index(const glm::vec3& pos) {
|
||||
return Chunk::index(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
|
||||
if (m_is_on_gen_vertex_data) {
|
||||
return;
|
||||
}
|
||||
m_is_on_gen_vertex_data = true;
|
||||
std::lock_guard lk(m_vertexs_data_mutex);
|
||||
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.m_vertices.clear();
|
||||
}
|
||||
|
||||
gen_vertices(neighbor_block);
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.update_sum();
|
||||
}
|
||||
m_need_upload = true;
|
||||
m_is_on_gen_vertex_data = false;
|
||||
}
|
||||
|
||||
GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
|
||||
|
||||
size_t Chunk::get_normal_vertices_sum() const {
|
||||
if (m_vertex_data[0].m_sum == 0) {
|
||||
Logger::warn("m_normal_vertices_sum is 0");
|
||||
}
|
||||
return m_vertex_data[0].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint Chunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
|
||||
size_t Chunk::get_cross_vertices_sum() const {
|
||||
return m_vertex_data[1].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint Chunk::get_normal_discard_vao() const { return m_vertex_data[2].m_vao; }
|
||||
size_t Chunk::get_normal_discard_vertices_sum() const {
|
||||
return m_vertex_data[2].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint Chunk::get_normal_blend_vao() const { return m_vertex_data[3].m_vao; }
|
||||
size_t Chunk::get_normal_blend_vertices_sum() const {
|
||||
return m_vertex_data[3].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint Chunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
|
||||
size_t Chunk::get_water_vertices_sum() const {
|
||||
return m_vertex_data[4].m_sum.load();
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_one() {
|
||||
m_generator = std::make_unique<ChunkGenerator>(*this);
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->assign_chunk_biome();
|
||||
m_seed = m_generator->chunk_seed();
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks) {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
// m_generator->resolve_biome_adjacency_conflict(adj_chunks);
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_three() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->generate_heightmap();
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_four(
|
||||
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
|
||||
const std::array<BiomeType, 8>& neighbor_biome) {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
// m_generator->blend_heightmap_boundaries(neighbor_heightmap,
|
||||
// neighbor_biome);
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_five() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->generate_terrain_blocks();
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_six(
|
||||
const std::array<std::optional<std::vector<BlockType>>, 4>&
|
||||
neighbor_block) {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
// This must be fully completed before any other operations can proceed!
|
||||
m_generator->blend_surface_blocks_borders(neighbor_block);
|
||||
}
|
||||
|
||||
void Chunk::gen_phase_seven() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->ocean_build();
|
||||
m_generator->generate_river();
|
||||
m_generator->generate_cave();
|
||||
|
||||
m_generator->generate_vegetation();
|
||||
mark_dirty();
|
||||
m_generator = nullptr;
|
||||
}
|
||||
|
||||
void Chunk::upload_to_gpu() {
|
||||
|
||||
ASSERT(is_need_upload());
|
||||
|
||||
std::lock_guard lk(m_vertexs_data_mutex);
|
||||
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.upload();
|
||||
}
|
||||
|
||||
// after fininshed it, can use
|
||||
clear_dirty();
|
||||
m_need_upload = false;
|
||||
}
|
||||
|
||||
bool Chunk::is_dirty() const { return m_dirty.load(); }
|
||||
|
||||
void Chunk::mark_dirty() { m_dirty = true; }
|
||||
|
||||
void Chunk::clear_dirty() { m_dirty = false; }
|
||||
|
||||
bool Chunk::is_need_upload() const { return m_need_upload.load(); }
|
||||
|
||||
void Chunk::need_upload() { m_need_upload = true; }
|
||||
|
||||
void Chunk::set_chunk_block(int index, unsigned id) {
|
||||
m_blocks[index] = id;
|
||||
mark_dirty();
|
||||
}
|
||||
|
||||
ChunkPos Chunk::chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
BiomeType Chunk::biome() const { return m_biome; }
|
||||
|
||||
void Chunk::biome(BiomeType b) { m_biome = b; }
|
||||
|
||||
HeightMapArray& Chunk::heightmap() { return m_heightmap; }
|
||||
std::vector<BlockType>& Chunk::blocks() { return m_blocks; }
|
||||
World& Chunk::world() { return m_world; }
|
||||
unsigned Chunk::seed() const {
|
||||
if (m_seed == 0) {
|
||||
Logger::warn("Seed Not Generator");
|
||||
}
|
||||
return m_seed;
|
||||
}
|
||||
|
||||
BiomeConditions& Chunk::conditions() { return m_conditions; }
|
||||
|
||||
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
|
||||
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
|
||||
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
|
||||
|
||||
for (int x = 0; x < SIZE_X; x++) {
|
||||
for (int y = 0; y < SIZE_Y; y++) {
|
||||
for (int z = 0; z < SIZE_Z; z++) {
|
||||
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
|
||||
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
|
||||
int world_y = y;
|
||||
int cur_id = m_blocks[index(x, y, z)];
|
||||
// air
|
||||
if (cur_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int face = 0; face < 6; face++) {
|
||||
int nx = x + DIR[face].x;
|
||||
int ny = y + DIR[face].y;
|
||||
int nz = z + DIR[face].z;
|
||||
bool neighbor_culled = false;
|
||||
|
||||
if (nx < 0 || nx >= SIZE_X || ny < 0 || ny >= SIZE_Y ||
|
||||
nz < 0 || nz >= SIZE_Z) {
|
||||
|
||||
int world_nx = world_x + DIR[face].x;
|
||||
int world_ny = world_y + DIR[face].y;
|
||||
int world_nz = world_z + DIR[face].z;
|
||||
|
||||
auto [neighbor_x, neighbor_z] =
|
||||
World::chunk_pos(world_nx, world_nz);
|
||||
|
||||
auto is_culled =
|
||||
[&](const std::optional<std::vector<BlockType>>&
|
||||
chunk_blocks) {
|
||||
if (chunk_blocks == std::nullopt) {
|
||||
return true;
|
||||
}
|
||||
int x, y, z;
|
||||
y = world_ny;
|
||||
x = world_nx - neighbor_x * CHUNK_SIZE;
|
||||
z = world_nz - neighbor_z * CHUNK_SIZE;
|
||||
if (x < 0 || y < 0 || z < 0 ||
|
||||
x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = Chunk::index(x, y, z);
|
||||
// not init
|
||||
if (static_cast<size_t>(idx) >=
|
||||
chunk_blocks->size()) {
|
||||
// Logger::warn("not init");
|
||||
return true;
|
||||
}
|
||||
auto id = (*chunk_blocks)[idx];
|
||||
// transparent
|
||||
if (BlockManager::is_transparent(id)) {
|
||||
if (id == cur_id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if (m_chunk_pos.x + 1 == neighbor_x) {
|
||||
neighbor_culled = is_culled(neighbor_block[0]);
|
||||
} else if (m_chunk_pos.x - 1 == neighbor_x) {
|
||||
neighbor_culled = is_culled(neighbor_block[1]);
|
||||
} else if (m_chunk_pos.z + 1 == neighbor_z) {
|
||||
neighbor_culled = is_culled(neighbor_block[2]);
|
||||
} else if (m_chunk_pos.z - 1 == neighbor_z) {
|
||||
neighbor_culled = is_culled(neighbor_block[3]);
|
||||
}
|
||||
// neighbor_cull = m_world.is_block(glm::ivec3(world_x,
|
||||
// world_y, world_z) + DIR[face]);
|
||||
} else {
|
||||
auto neighbor_id = m_blocks[index(nx, ny, nz)];
|
||||
// transparent block
|
||||
if (!BlockManager::is_transparent(neighbor_id)) {
|
||||
neighbor_culled = true;
|
||||
} else {
|
||||
if (neighbor_id == cur_id) {
|
||||
neighbor_culled = true;
|
||||
} else {
|
||||
neighbor_culled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (neighbor_culled) {
|
||||
continue;
|
||||
}
|
||||
if (BlockManager::is_cross_plane(cur_id)) {
|
||||
gen_cross_plane_vertices(world_x, world_y, world_z,
|
||||
cur_id);
|
||||
}
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Vertex3D vex = {
|
||||
VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
|
||||
VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
|
||||
VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
|
||||
TEX_COORDS[face][i][0],
|
||||
TEX_COORDS[face][i][1],
|
||||
|
||||
static_cast<float>(cur_id * 6 + face),
|
||||
|
||||
NORMALS[face][i][0],
|
||||
NORMALS[face][i][1],
|
||||
NORMALS[face][i][2],
|
||||
BlockManager::roughness(cur_id),
|
||||
TANGENTS[face][i][0],
|
||||
TANGENTS[face][i][1],
|
||||
TANGENTS[face][i][2]
|
||||
|
||||
};
|
||||
if (BlockManager::is_transparent(cur_id)) {
|
||||
if (BlockManager::is_discard(cur_id) &&
|
||||
BlockManager::is_blend(cur_id)) {
|
||||
Logger::warn(
|
||||
"Block id {} is both discard and blend is "
|
||||
"must only one can true !!!",
|
||||
cur_id);
|
||||
}
|
||||
if (BlockManager::is_discard(cur_id)) {
|
||||
m_vertex_data[2].m_vertices.emplace_back(vex);
|
||||
} else if (BlockManager::is_blend(cur_id)) {
|
||||
if (cur_id == 7) {
|
||||
m_vertex_data[4].m_vertices.emplace_back(
|
||||
vex);
|
||||
} else {
|
||||
m_vertex_data[3].m_vertices.emplace_back(
|
||||
vex);
|
||||
}
|
||||
|
||||
} else {
|
||||
Logger::warn("Id {} is transparent but not "
|
||||
"discard or blend",
|
||||
cur_id);
|
||||
m_vertex_data[3].m_vertices.emplace_back(vex);
|
||||
}
|
||||
|
||||
} else {
|
||||
m_vertex_data[0].m_vertices.emplace_back(vex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
|
||||
BlockType id) {
|
||||
|
||||
if (!BlockManager::is_cross_plane(id)) {
|
||||
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
|
||||
world_y, world_z, id);
|
||||
return;
|
||||
}
|
||||
for (int face = 0; face < 2; face++) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Vertex3D vex = {
|
||||
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
|
||||
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
|
||||
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
|
||||
CROSS_TEX_COORDS[face][i][0],
|
||||
CROSS_TEX_COORDS[face][i][1],
|
||||
static_cast<float>(BlockManager::cross_plane_index(id)),
|
||||
CROSS_NORMALS[face][i][0],
|
||||
CROSS_NORMALS[face][i][1],
|
||||
CROSS_NORMALS[face][i][2],
|
||||
BlockManager::roughness(id),
|
||||
CROSS_TANGENTS[face][i][0],
|
||||
CROSS_TANGENTS[face][i][1],
|
||||
CROSS_TANGENTS[face][i][2]
|
||||
|
||||
};
|
||||
m_vertex_data[1].m_vertices.emplace_back(vex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -7,19 +7,99 @@
|
||||
#include "Cubed/gameplay/builders/plain_builder.hpp"
|
||||
#include "Cubed/gameplay/builders/river_builder.hpp"
|
||||
#include "Cubed/gameplay/builders/snowy_plain_builder.hpp"
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/cave_path.hpp"
|
||||
#include "Cubed/gameplay/river.path.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/gameplay/tree.hpp"
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
#include "Cubed/tools/perlin_noise.hpp"
|
||||
namespace Cubed {
|
||||
|
||||
namespace {
|
||||
template <typename F>
|
||||
void carve_worm(const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
|
||||
F&& on_hit) {
|
||||
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
const int CHUNK_MIN_Y = 0;
|
||||
const int CHUNK_MAX_Y = SIZE_Y - 1;
|
||||
for (const auto& point : points) {
|
||||
|
||||
const glm::vec3& center = point.pos;
|
||||
float rad_xz = point.rad_xz;
|
||||
float rad_y = point.rad_y;
|
||||
|
||||
if (center.x + rad_xz < CHUNK_MIN_X ||
|
||||
center.x - rad_xz > CHUNK_MAX_X ||
|
||||
center.z + rad_xz < CHUNK_MIN_Z ||
|
||||
center.z - rad_xz > CHUNK_MAX_Z || center.y + rad_y < CHUNK_MIN_Y ||
|
||||
center.y - rad_y > CHUNK_MAX_Y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int min_x = static_cast<int>(std::floor(center.x - rad_xz));
|
||||
int max_x = static_cast<int>(std::floor(center.x + rad_xz));
|
||||
int min_z = static_cast<int>(std::floor(center.z - rad_xz));
|
||||
int max_z = static_cast<int>(std::floor(center.z + rad_xz));
|
||||
int min_y = static_cast<int>(std::floor(center.y - rad_y));
|
||||
int max_y = static_cast<int>(std::floor(center.y + rad_y));
|
||||
|
||||
min_x = std::max(min_x, CHUNK_MIN_X);
|
||||
max_x = std::min(max_x, CHUNK_MAX_X);
|
||||
min_z = std::max(min_z, CHUNK_MIN_Z);
|
||||
max_z = std::min(max_z, CHUNK_MAX_Z);
|
||||
min_y = std::max(min_y, CHUNK_MIN_Y);
|
||||
max_y = std::min(max_y, CHUNK_MAX_Y);
|
||||
|
||||
glm::vec3 right_raw =
|
||||
glm::cross(point.tangent, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
if (glm::dot(right_raw, right_raw) < 1e-6f)
|
||||
right_raw = glm::cross(point.tangent, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 right = glm::normalize(right_raw);
|
||||
glm::vec3 up = glm::normalize(glm::cross(point.tangent, right));
|
||||
|
||||
float inv_a2 = 1.0f / (point.rad_xz * point.rad_xz);
|
||||
float inv_b2 = 1.0f / (point.rad_y * point.rad_y);
|
||||
|
||||
for (int wy = min_y; wy <= max_y; ++wy) {
|
||||
if (wy == 0)
|
||||
continue;
|
||||
float dy = static_cast<float>(wy) - point.pos.y;
|
||||
|
||||
float vy_contrib = dy * up.y;
|
||||
float vy2 = vy_contrib * vy_contrib * inv_b2;
|
||||
if (vy2 >= 1.0f)
|
||||
continue;
|
||||
|
||||
for (int wx = min_x; wx <= max_x; ++wx) {
|
||||
float dx = static_cast<float>(wx) - point.pos.x;
|
||||
for (int wz = min_z; wz <= max_z; ++wz) {
|
||||
float dz = static_cast<float>(wz) - point.pos.z;
|
||||
glm::vec3 to_point(dx, dy, dz);
|
||||
|
||||
float h = glm::dot(to_point, right);
|
||||
float v = glm::dot(to_point, up);
|
||||
|
||||
if (h * h * inv_a2 + v * v * inv_b2 > 1.0f)
|
||||
continue;
|
||||
int x = wx - CHUNK_MIN_X;
|
||||
on_hit(x, wy, wz - CHUNK_MIN_Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using enum BiomeType;
|
||||
|
||||
constexpr int BLEND_RADIUS = 8;
|
||||
ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) {
|
||||
ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) {
|
||||
ASSERT_MSG(is_init, "ChunksGenerator is not init");
|
||||
ChunkPos pos = m_chunk.get_chunk_pos();
|
||||
unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed);
|
||||
@@ -77,7 +157,7 @@ void ChunkGenerator::assign_chunk_biome() {
|
||||
}
|
||||
|
||||
void ChunkGenerator::resolve_biome_adjacency_conflict(
|
||||
const std::array<const Chunk*, 8>& adj_chunks) {
|
||||
const std::array<const ServerChunk*, 8>& adj_chunks) {
|
||||
auto m_biome = m_chunk.biome();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
auto& chunk = adj_chunks[i];
|
||||
@@ -475,7 +555,8 @@ void ChunkGenerator::blend_surface_blocks_borders(
|
||||
int nx, int nz) -> BlockType {
|
||||
// Search from topmost y downwards for the first non-zero block
|
||||
for (int y = WORLD_HEIGHT - 1; y >= 0; --y) {
|
||||
int idx = Chunk::index(nx, y,
|
||||
int idx =
|
||||
ServerChunk::index(nx, y,
|
||||
nz); // linear index: y * area + z * size + x
|
||||
if (idx >= 0 && idx < static_cast<int>(blocks.size())) {
|
||||
BlockType neighbor_type = blocks[idx];
|
||||
@@ -494,7 +575,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
|
||||
BlockType type_self = 0;
|
||||
int top_y = -1;
|
||||
top_y = m_heightmap[x][z];
|
||||
type_self = m_blocks[Chunk::index(x, top_y, z)];
|
||||
type_self = m_blocks[ServerChunk::index(x, top_y, z)];
|
||||
|
||||
if (top_y == -1)
|
||||
continue; // no block? skip
|
||||
@@ -585,7 +666,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
|
||||
if (final_type != type_self) {
|
||||
// top block
|
||||
BlockType new_surface = final_type;
|
||||
m_blocks[Chunk::index(x, top_y, z)] = new_surface;
|
||||
m_blocks[ServerChunk::index(x, top_y, z)] = new_surface;
|
||||
// bottom block
|
||||
unsigned fill_type = 2;
|
||||
if (final_type == 1 || final_type == 8) {
|
||||
@@ -594,7 +675,7 @@ void ChunkGenerator::blend_surface_blocks_borders(
|
||||
fill_type = final_type;
|
||||
}
|
||||
for (int y = std::max(0, top_y - 5); y < top_y; y++) {
|
||||
m_blocks[Chunk::index(x, y, z)] = fill_type;
|
||||
m_blocks[ServerChunk::index(x, y, z)] = fill_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -642,137 +723,84 @@ void ChunkGenerator::make_biome_builder() {
|
||||
|
||||
void ChunkGenerator::ocean_build() { m_biome_builder->ocean_water_build(); }
|
||||
|
||||
void ChunkGenerator::carve_worm(
|
||||
const std::vector<PathPoint>& points, const ChunkPos& chunk_pos,
|
||||
std::function<void(int /*x*/, int /*y*/, int /*z*/)> on_hit) {
|
||||
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
const int CHUNK_MIN_Y = 0;
|
||||
const int CHUNK_MAX_Y = SIZE_Y - 1;
|
||||
for (const auto& point : points) {
|
||||
|
||||
const glm::vec3& center = point.pos;
|
||||
float rad_xz = point.rad_xz;
|
||||
float rad_y = point.rad_y;
|
||||
|
||||
if (center.x + rad_xz < CHUNK_MIN_X ||
|
||||
center.x - rad_xz > CHUNK_MAX_X ||
|
||||
center.z + rad_xz < CHUNK_MIN_Z ||
|
||||
center.z - rad_xz > CHUNK_MAX_Z || center.y + rad_y < CHUNK_MIN_Y ||
|
||||
center.y - rad_y > CHUNK_MAX_Y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int min_x = static_cast<int>(std::floor(center.x - rad_xz));
|
||||
int max_x = static_cast<int>(std::floor(center.x + rad_xz));
|
||||
int min_z = static_cast<int>(std::floor(center.z - rad_xz));
|
||||
int max_z = static_cast<int>(std::floor(center.z + rad_xz));
|
||||
int min_y = static_cast<int>(std::floor(center.y - rad_y));
|
||||
int max_y = static_cast<int>(std::floor(center.y + rad_y));
|
||||
|
||||
min_x = std::max(min_x, CHUNK_MIN_X);
|
||||
max_x = std::min(max_x, CHUNK_MAX_X);
|
||||
min_z = std::max(min_z, CHUNK_MIN_Z);
|
||||
max_z = std::min(max_z, CHUNK_MAX_Z);
|
||||
min_y = std::max(min_y, CHUNK_MIN_Y);
|
||||
max_y = std::min(max_y, CHUNK_MAX_Y);
|
||||
|
||||
glm::vec3 right_raw =
|
||||
glm::cross(point.tangent, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
if (glm::dot(right_raw, right_raw) < 1e-6f)
|
||||
right_raw = glm::cross(point.tangent, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 right = glm::normalize(right_raw);
|
||||
glm::vec3 up = glm::normalize(glm::cross(point.tangent, right));
|
||||
|
||||
float inv_a2 = 1.0f / (point.rad_xz * point.rad_xz);
|
||||
float inv_b2 = 1.0f / (point.rad_y * point.rad_y);
|
||||
|
||||
for (int wy = min_y; wy <= max_y; ++wy) {
|
||||
if (wy == 0)
|
||||
continue;
|
||||
float dy = static_cast<float>(wy) - point.pos.y;
|
||||
|
||||
float vy_contrib = dy * up.y;
|
||||
float vy2 = vy_contrib * vy_contrib * inv_b2;
|
||||
if (vy2 >= 1.0f)
|
||||
continue;
|
||||
|
||||
for (int wx = min_x; wx <= max_x; ++wx) {
|
||||
float dx = static_cast<float>(wx) - point.pos.x;
|
||||
for (int wz = min_z; wz <= max_z; ++wz) {
|
||||
float dz = static_cast<float>(wz) - point.pos.z;
|
||||
glm::vec3 to_point(dx, dy, dz);
|
||||
|
||||
float h = glm::dot(to_point, right);
|
||||
float v = glm::dot(to_point, up);
|
||||
|
||||
if (h * h * inv_a2 + v * v * inv_b2 > 1.0f)
|
||||
continue;
|
||||
int x = wx - CHUNK_MIN_X;
|
||||
on_hit(x, wy, wz - CHUNK_MIN_Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkGenerator::generate_cave() {
|
||||
auto& cave_carver = m_chunk.world().cave_carcer();
|
||||
auto& paths = cave_carver.paths();
|
||||
const auto& chunk_pos = m_chunk.chunk_pos();
|
||||
auto& blocks = m_chunk.blocks();
|
||||
auto& carver = m_chunk.world().cave_carcer();
|
||||
|
||||
for (auto& [id, path] : paths) {
|
||||
int search_r = carver.search_radius();
|
||||
for (int dx = -search_r; dx <= search_r; dx++) {
|
||||
for (int dz = -search_r; dz <= search_r; dz++) {
|
||||
ChunkPos origin_pos{chunk_pos.x + dx, chunk_pos.z + dz};
|
||||
auto origin = carver.get_origin(origin_pos);
|
||||
if (!origin.exists)
|
||||
continue;
|
||||
|
||||
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
|
||||
int idx = Chunk::index(x, y, z);
|
||||
if (blocks[idx] == 7)
|
||||
return;
|
||||
if (y < WORLD_SIZE_Y - 1 && blocks[Chunk::index(x, y + 1, z)] == 7)
|
||||
return;
|
||||
blocks[idx] = 0;
|
||||
});
|
||||
path.clear_chunk(chunk_pos);
|
||||
// Deterministically reconstruct this path (lightweight: only
|
||||
// compute points, no storage).
|
||||
CavePath path{origin.seed, carver.world_seed(), origin.pos};
|
||||
|
||||
carve_worm(path.points(), chunk_pos,
|
||||
[&](int x, int y, int z) -> void {
|
||||
int idx = ServerChunk::index(x, y, z);
|
||||
m_chunk.has_cave() = true;
|
||||
if (blocks[idx] == 7)
|
||||
return;
|
||||
if (y < WORLD_SIZE_Y - 1 &&
|
||||
blocks[ServerChunk::index(x, y + 1, z)] == 7)
|
||||
return;
|
||||
blocks[idx] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChunkGenerator::generate_river() {
|
||||
if ((m_chunk.biome() == BiomeType::DESERT) ||
|
||||
(m_chunk.biome() == BiomeType::OCEAN)) {
|
||||
|
||||
return;
|
||||
}
|
||||
auto& river_worm = m_chunk.world().river_worm();
|
||||
auto& paths = river_worm.paths();
|
||||
|
||||
const auto& chunk_pos = m_chunk.chunk_pos();
|
||||
auto& blocks = m_chunk.blocks();
|
||||
|
||||
bool is_river = false;
|
||||
int search_r = river_worm.search_radius();
|
||||
|
||||
for (auto& [id, path] : paths) {
|
||||
if ((m_chunk.biome() == BiomeType::DESERT) ||
|
||||
(m_chunk.biome() == BiomeType::OCEAN)) {
|
||||
path.clear_chunk(chunk_pos);
|
||||
continue;
|
||||
for (int dx = -search_r; dx <= search_r; dx++) {
|
||||
for (int dz = -search_r; dz <= search_r; dz++) {
|
||||
ChunkPos origin_pos{chunk_pos.x + dx, chunk_pos.z + dz};
|
||||
auto origin = river_worm.get_origin(origin_pos);
|
||||
if (!origin.exists)
|
||||
continue;
|
||||
|
||||
// Deterministically reconstruct this path (lightweight: only
|
||||
// compute points, no storage).
|
||||
RiverPath path{origin.seed, river_worm.world_seed(), origin.pos};
|
||||
|
||||
carve_worm(path.points(), chunk_pos,
|
||||
[&](int x, int y, int z) -> void {
|
||||
int idx = ServerChunk::index(x, y, z);
|
||||
if (y > SEA_LEVEL) {
|
||||
blocks[idx] = 0;
|
||||
return;
|
||||
}
|
||||
is_river = true;
|
||||
if (blocks[idx] == 0) {
|
||||
return;
|
||||
}
|
||||
blocks[idx] = 7;
|
||||
});
|
||||
}
|
||||
carve_worm(path.points(), chunk_pos, [&](int x, int y, int z) -> void {
|
||||
int idx = Chunk::index(x, y, z);
|
||||
if (y > SEA_LEVEL) {
|
||||
blocks[idx] = 0;
|
||||
return;
|
||||
}
|
||||
is_river = true;
|
||||
if (blocks[idx] == 0) {
|
||||
return;
|
||||
}
|
||||
blocks[idx] = 7;
|
||||
});
|
||||
path.clear_chunk(chunk_pos);
|
||||
}
|
||||
|
||||
if (is_river) {
|
||||
m_chunk.biome(RIVER);
|
||||
}
|
||||
}
|
||||
|
||||
Chunk& ChunkGenerator::chunk() { return m_chunk; }
|
||||
ServerChunk& ChunkGenerator::chunk() { return m_chunk; }
|
||||
|
||||
Random& ChunkGenerator::random() { return m_random; }
|
||||
const std::array<BiomeType, 8>& ChunkGenerator::neighbor_biome() const {
|
||||
|
||||
573
src/gameplay/client_chunk.cpp
Normal file
573
src/gameplay/client_chunk.cpp
Normal file
@@ -0,0 +1,573 @@
|
||||
#include "Cubed/gameplay/client_chunk.hpp"
|
||||
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
using OptionalBlockVectorArray =
|
||||
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||
namespace {
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Face direction mapping
|
||||
// Original DIR[6]: {+Z,+X,-Z,-X,+Y,-Y} => face index 0-5
|
||||
// Axis × direction => face:
|
||||
// axis=2(Z) dir=+1 => face 0 (+Z)
|
||||
// axis=0(X) dir=+1 => face 1 (+X)
|
||||
// axis=2(Z) dir=-1 => face 2 (-Z)
|
||||
// axis=0(X) dir=-1 => face 3 (-X)
|
||||
// axis=1(Y) dir=+1 => face 4 (+Y)
|
||||
// axis=1(Y) dir=-1 => face 5 (-Y)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
inline int axis_dir_to_face(int axis, int dir) {
|
||||
// axis: 0=X 1=Y 2=Z
|
||||
// dir: +1 or -1
|
||||
static const int TABLE[3][2] = {
|
||||
{3, 1}, // X: dir=-1->face3(-X), dir=+1->face1(+X)
|
||||
{5, 4}, // Y: dir=-1->face5(-Y), dir=+1->face4(+Y)
|
||||
{2, 0}, // Z: dir=-1->face2(-Z), dir=+1->face0(+Z)
|
||||
};
|
||||
return TABLE[axis][dir > 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
inline BlockType
|
||||
get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
|
||||
const std::vector<BlockType>& blocks,
|
||||
const OptionalBlockVectorArray& neighbor_block) {
|
||||
if (lx >= 0 && lx < CHUNK_SIZE && ly >= 0 && ly < WORLD_SIZE_Y && lz >= 0 &&
|
||||
lz < CHUNK_SIZE) {
|
||||
return blocks[ClientChunk::index(lx, ly, lz)];
|
||||
}
|
||||
|
||||
// Out of bounds: check neighbors
|
||||
int world_x = lx + chunk_pos.x * CHUNK_SIZE;
|
||||
int world_z = lz + chunk_pos.z * CHUNK_SIZE;
|
||||
|
||||
auto [nb_cx, nb_cz] = get_chunk_pos(world_x, world_z);
|
||||
|
||||
const std::optional<std::vector<BlockType>>* nb = nullptr;
|
||||
if (nb_cx == chunk_pos.x + 1)
|
||||
nb = &neighbor_block[0];
|
||||
else if (nb_cx == chunk_pos.x - 1)
|
||||
nb = &neighbor_block[1];
|
||||
else if (nb_cz == chunk_pos.z + 1)
|
||||
nb = &neighbor_block[2];
|
||||
else if (nb_cz == chunk_pos.z - 1)
|
||||
nb = &neighbor_block[3];
|
||||
|
||||
if (!nb || !nb->has_value())
|
||||
return 0; // Neighbor does not exist, treat as opaque
|
||||
|
||||
int nbx = world_x - nb_cx * CHUNK_SIZE;
|
||||
int nby = ly;
|
||||
int nbz = world_z - nb_cz * CHUNK_SIZE;
|
||||
|
||||
if (nbx < 0 || nby < 0 || nbz < 0 || nbx >= CHUNK_SIZE ||
|
||||
nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE)
|
||||
return 0;
|
||||
|
||||
int idx = ClientChunk::index(nbx, nby, nbz);
|
||||
if (static_cast<size_t>(idx) >= (*nb)->size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (**nb)[idx];
|
||||
}
|
||||
// Determine whether the face from cur_id looking towards neighbor_id should be
|
||||
// culled (does not need to be rendered)
|
||||
inline bool is_face_culled(BlockType cur_id, BlockType neighbor_id) {
|
||||
if (!BlockManager::is_transparent(neighbor_id))
|
||||
return true; // Neighbor is opaque, blocking
|
||||
// Neighbor transparency: same block type culls each other (e.g., water
|
||||
// adjacent to water does not render internal faces)
|
||||
if (neighbor_id == cur_id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline int choose_buf(BlockType id) {
|
||||
if (!BlockManager::is_transparent(id))
|
||||
return 0;
|
||||
if (BlockManager::is_discard(id))
|
||||
return 2;
|
||||
if (BlockManager::is_blend(id)) {
|
||||
return (id == 7) ? 4 : 3; // water=4, other blend=3
|
||||
}
|
||||
return 3; // fallback
|
||||
}
|
||||
|
||||
} // namespace
|
||||
ClientChunk::ClientChunk(ClientWorld& world) : m_world(world) {
|
||||
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
|
||||
m_vertex_data.emplace_back(m_world);
|
||||
}
|
||||
}
|
||||
ClientChunk::~ClientChunk() {}
|
||||
|
||||
ClientChunk::ClientChunk(ClientChunk&& other) noexcept
|
||||
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
|
||||
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
|
||||
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
|
||||
m_world(other.m_world), m_blocks(std::move(other.m_blocks)),
|
||||
m_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed) {}
|
||||
|
||||
ClientChunk& ClientChunk::operator=(ClientChunk&& other) noexcept {
|
||||
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
||||
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
|
||||
// static_cast<const void*>(&other));
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
m_chunk_pos = std::move(other.m_chunk_pos);
|
||||
m_blocks = std::move(other.m_blocks);
|
||||
m_dirty = other.is_dirty();
|
||||
m_vertex_data = std::move(other.m_vertex_data);
|
||||
m_biome = other.m_biome.load();
|
||||
m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load();
|
||||
m_need_upload = other.m_need_upload.load();
|
||||
m_seed = other.m_seed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int ClientChunk::index(int x, int y, int z) {
|
||||
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE));
|
||||
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
|
||||
(x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >=
|
||||
CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) {
|
||||
Logger::error("block pos x {} y {} z {} range error", x, y, z);
|
||||
ASSERT(0);
|
||||
}
|
||||
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
|
||||
}
|
||||
|
||||
int ClientChunk::index(const glm::vec3& pos) {
|
||||
return ClientChunk::index(pos.x, pos.y, pos.z);
|
||||
}
|
||||
std::tuple<int, int, int> ClientChunk::world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z) {
|
||||
int x, y, z;
|
||||
y = world_y;
|
||||
x = world_x - chunk_x * CHUNK_SIZE;
|
||||
z = world_z - chunk_z * CHUNK_SIZE;
|
||||
return {x, y, z};
|
||||
}
|
||||
|
||||
std::tuple<int, int, int>
|
||||
ClientChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
|
||||
return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
|
||||
std::tuple<int, int, int>
|
||||
ClientChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) {
|
||||
int world_x = x + chunk_x * CHUNK_SIZE;
|
||||
int world_z = z + chunk_z * CHUNK_SIZE;
|
||||
int world_y = y;
|
||||
return {world_x, world_y, world_z};
|
||||
}
|
||||
std::tuple<int, int, int>
|
||||
ClientChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
|
||||
return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
BiomeType ClientChunk::get_biome() const { return m_biome.load(); }
|
||||
|
||||
ChunkPos ClientChunk::get_chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
const std::vector<BlockType>& ClientChunk::get_chunk_blocks() const {
|
||||
return m_blocks;
|
||||
}
|
||||
|
||||
void ClientChunk::gen_vertex_data(
|
||||
const OptionalBlockVectorArray& neighbor_block) {
|
||||
if (m_is_on_gen_vertex_data.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard lk(m_vertexs_data_mutex);
|
||||
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.m_vertices.clear();
|
||||
}
|
||||
|
||||
gen_vertices(neighbor_block);
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.update_sum();
|
||||
}
|
||||
m_need_upload = true;
|
||||
m_is_on_gen_vertex_data = false;
|
||||
}
|
||||
|
||||
GLuint ClientChunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
|
||||
|
||||
size_t ClientChunk::get_normal_vertices_sum() const {
|
||||
if (m_vertex_data[0].m_sum == 0) {
|
||||
Logger::warn("m_normal_vertices_sum is 0");
|
||||
}
|
||||
return m_vertex_data[0].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint ClientChunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
|
||||
size_t ClientChunk::get_cross_vertices_sum() const {
|
||||
return m_vertex_data[1].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint ClientChunk::get_normal_discard_vao() const {
|
||||
return m_vertex_data[2].m_vao;
|
||||
}
|
||||
size_t ClientChunk::get_normal_discard_vertices_sum() const {
|
||||
return m_vertex_data[2].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint ClientChunk::get_normal_blend_vao() const {
|
||||
return m_vertex_data[3].m_vao;
|
||||
}
|
||||
size_t ClientChunk::get_normal_blend_vertices_sum() const {
|
||||
return m_vertex_data[3].m_sum.load();
|
||||
}
|
||||
|
||||
GLuint ClientChunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
|
||||
size_t ClientChunk::get_water_vertices_sum() const {
|
||||
return m_vertex_data[4].m_sum.load();
|
||||
}
|
||||
|
||||
void ClientChunk::upload_to_gpu() {
|
||||
|
||||
if (!is_need_upload()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lk(m_vertexs_data_mutex);
|
||||
|
||||
for (auto& data : m_vertex_data) {
|
||||
data.upload();
|
||||
}
|
||||
|
||||
// after fininshed it, can use
|
||||
clear_dirty();
|
||||
|
||||
m_render_snapshot = {
|
||||
get_normal_vao(),
|
||||
get_normal_vertices_sum(),
|
||||
get_cross_vao(),
|
||||
get_cross_vertices_sum(),
|
||||
get_normal_discard_vao(),
|
||||
get_normal_discard_vertices_sum(),
|
||||
get_normal_blend_vao(),
|
||||
get_normal_blend_vertices_sum(),
|
||||
get_water_vao(),
|
||||
get_water_vertices_sum(),
|
||||
glm::vec3(static_cast<float>(m_chunk_pos.x * CHUNK_SIZE) +
|
||||
static_cast<float>(CHUNK_SIZE / 2),
|
||||
static_cast<float>(WORLD_SIZE_Y / 2),
|
||||
static_cast<float>(m_chunk_pos.z * CHUNK_SIZE) +
|
||||
static_cast<float>(CHUNK_SIZE / 2)),
|
||||
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
|
||||
static_cast<float>(WORLD_SIZE_Y / 2),
|
||||
static_cast<float>(CHUNK_SIZE / 2))};
|
||||
|
||||
m_need_upload = false;
|
||||
}
|
||||
|
||||
bool ClientChunk::is_dirty() const { return m_dirty.load(); }
|
||||
|
||||
void ClientChunk::mark_dirty() { m_dirty = true; }
|
||||
|
||||
void ClientChunk::clear_dirty() { m_dirty = false; }
|
||||
|
||||
bool ClientChunk::is_need_upload() const { return m_need_upload.load(); }
|
||||
|
||||
void ClientChunk::need_upload() { m_need_upload = true; }
|
||||
|
||||
void ClientChunk::set_chunk_block(int index, unsigned id) {
|
||||
m_blocks[index] = id;
|
||||
}
|
||||
|
||||
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
BiomeType ClientChunk::biome() const { return m_biome; }
|
||||
|
||||
void ClientChunk::biome(BiomeType b) { m_biome = b; }
|
||||
|
||||
std::vector<BlockType>& ClientChunk::blocks() { return m_blocks; }
|
||||
ClientWorld& ClientChunk::world() { return m_world; }
|
||||
unsigned ClientChunk::seed() const {
|
||||
if (m_seed == 0) {
|
||||
Logger::warn("Seed Not Generator");
|
||||
}
|
||||
return m_seed;
|
||||
}
|
||||
|
||||
const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const {
|
||||
return &m_render_snapshot;
|
||||
}
|
||||
|
||||
void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
|
||||
|
||||
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256
|
||||
// Axis order: axis 0=X, 1=Y, 2=Z
|
||||
// Two slice dimensions of each axis
|
||||
const int DIMS[3] = {CHUNK_SIZE, WORLD_SIZE_Y, CHUNK_SIZE};
|
||||
|
||||
// Maximum mask size: max(16*256, 16*16) = 4096
|
||||
static thread_local FaceKey mask[CHUNK_SIZE * WORLD_SIZE_Y];
|
||||
static thread_local bool visited[CHUNK_SIZE * WORLD_SIZE_Y];
|
||||
|
||||
for (int axis = 0; axis < 3; axis++) {
|
||||
int u_axis = (axis + 1) % 3; // horizontal
|
||||
int v_axis = (axis + 2) % 3; // vertical
|
||||
|
||||
int u = DIMS[u_axis];
|
||||
int v = DIMS[v_axis];
|
||||
int d = DIMS[axis]; // Depth along the normal axis
|
||||
|
||||
for (int face_dir : {1, -1}) {
|
||||
int face_idx = axis_dir_to_face(axis, face_dir);
|
||||
|
||||
for (int layer = 0; layer < d; layer++) {
|
||||
|
||||
// ── 1. Build mask ──────────────────────────────────────────
|
||||
for (int vi = 0; vi < v; vi++) {
|
||||
for (int ui = 0; ui < u; ui++) {
|
||||
// Current cell local coordinates
|
||||
int lpos[3];
|
||||
lpos[axis] = layer;
|
||||
lpos[u_axis] = ui;
|
||||
lpos[v_axis] = vi;
|
||||
|
||||
// Neighbor (offset one cell along the normal direction)
|
||||
int npos[3];
|
||||
npos[axis] = layer + face_dir;
|
||||
npos[u_axis] = ui;
|
||||
npos[v_axis] = vi;
|
||||
|
||||
BlockType cur_id = get_block_safe(
|
||||
lpos[0], lpos[1], lpos[2], m_chunk_pos, m_blocks,
|
||||
neighbor_block);
|
||||
|
||||
// Air / cross plane are not involved in greedy meshing
|
||||
if (cur_id == 0 ||
|
||||
BlockManager::is_cross_plane(cur_id)) {
|
||||
mask[vi * u + ui] = {};
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockType nb_id = get_block_safe(
|
||||
npos[0], npos[1], npos[2], m_chunk_pos, m_blocks,
|
||||
neighbor_block);
|
||||
|
||||
if (is_face_culled(cur_id, nb_id)) {
|
||||
mask[vi * u + ui] = {};
|
||||
} else {
|
||||
mask[vi * u + ui] = {cur_id, face_idx};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. Greedy Merge ──────────────────────────────────────
|
||||
std::fill(visited, visited + u * v, false);
|
||||
|
||||
for (int vi = 0; vi < v; vi++) {
|
||||
for (int ui = 0; ui < u; ui++) {
|
||||
if (visited[vi * u + ui])
|
||||
continue;
|
||||
FaceKey cur = mask[vi * u + ui];
|
||||
if (!cur.valid())
|
||||
continue;
|
||||
|
||||
// Extend width in the u direction
|
||||
int w = 1;
|
||||
while (ui + w < u && !visited[vi * u + (ui + w)] &&
|
||||
mask[vi * u + (ui + w)] == cur) {
|
||||
w++;
|
||||
}
|
||||
|
||||
// Extend height in the v direction
|
||||
int h = 1;
|
||||
bool can_expand = true;
|
||||
while (vi + h < v && can_expand) {
|
||||
for (int k = 0; k < w; k++) {
|
||||
int idx = (vi + h) * u + (ui + k);
|
||||
if (visited[idx] || mask[idx] != cur) {
|
||||
can_expand = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (can_expand)
|
||||
h++;
|
||||
}
|
||||
|
||||
// mark visited
|
||||
for (int dv = 0; dv < h; dv++)
|
||||
for (int du = 0; du < w; du++)
|
||||
visited[(vi + dv) * u + (ui + du)] = true;
|
||||
|
||||
// output quad
|
||||
emit_quad(axis, face_dir, layer, ui, vi, w, h, u_axis,
|
||||
v_axis, cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < CHUNK_SIZE; x++) {
|
||||
for (int y = 0; y < WORLD_SIZE_Y; y++) {
|
||||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||||
BlockType id = m_blocks[index(x, y, z)];
|
||||
if (id != 0 && BlockManager::is_cross_plane(id)) {
|
||||
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
|
||||
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
|
||||
gen_cross_plane_vertices(world_x, y, world_z, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void ClientChunk::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) {
|
||||
float axis_val = (float)(layer + (face_dir > 0 ? 1 : 0));
|
||||
float wx_base = (float)(m_chunk_pos.x * CHUNK_SIZE);
|
||||
float wz_base = (float)(m_chunk_pos.z * CHUNK_SIZE);
|
||||
|
||||
// Offsets of the four corners along the u_axis/v_axis
|
||||
int su[4] = {0, w, w, 0};
|
||||
int sv[4] = {0, 0, h, h};
|
||||
|
||||
// Each face's UV: directly read from the four corners of TEX_COORDS, then
|
||||
// scaled by w/h TEX_COORDS vertex order: 0=BL, 1=TL, 2=TR, 3=TR, 4=BR, 5=BL
|
||||
// (two triangles) Four unique corners correspond to indices: BL=0, TL=1,
|
||||
// TR=2, BR=4 Extract the UVs of the four corners from TEX_COORDS (unique
|
||||
// corners after removing duplicate vertices) Vertices 0,1,2,4 correspond to
|
||||
// BL, TL, TR, BR
|
||||
float u0 = TEX_COORDS[key.face][0][0]; // BL.u
|
||||
float v0 = TEX_COORDS[key.face][0][1]; // BL.v
|
||||
float u1 = TEX_COORDS[key.face][4][0]; // BR.u
|
||||
float v1 = TEX_COORDS[key.face][4][1]; // BR.v
|
||||
float u3 = TEX_COORDS[key.face][1][0]; // TL.u
|
||||
float v3 = TEX_COORDS[key.face][1][1]; // TL.v
|
||||
|
||||
float du_u = u1 - u0; // Change in u when su increases (per block)
|
||||
float dv_u = v1 - v0;
|
||||
float du_v = u3 - u0; // Change in u when sv increases
|
||||
float dv_v = v3 - v0;
|
||||
|
||||
float uvs[4][2] = {
|
||||
{u0, v0}, // (0, 0 )
|
||||
{u0 + du_u * (float)w, v0 + dv_u * (float)w}, // (w, 0 )
|
||||
{u0 + du_u * (float)w + du_v * (float)h,
|
||||
v0 + dv_u * (float)w + dv_v * (float)h}, // (w, h )
|
||||
{u0 + du_v * (float)h, v0 + dv_v * (float)h}, // (0, h )
|
||||
};
|
||||
|
||||
int tri[6] = {0, 1, 2, 0, 2, 3};
|
||||
|
||||
float pos[4][3];
|
||||
for (int c = 0; c < 4; c++) {
|
||||
pos[c][axis] = axis_val;
|
||||
pos[c][u_axis] = (float)(i + su[c]);
|
||||
pos[c][v_axis] = (float)(j + sv[c]);
|
||||
pos[c][0] += wx_base;
|
||||
pos[c][2] += wz_base;
|
||||
}
|
||||
|
||||
float layer_id = (float)(key.block_id * 6 + key.face);
|
||||
float roughness = BlockManager::roughness(key.block_id);
|
||||
int buf = choose_buf(key.block_id);
|
||||
|
||||
for (int vi = 0; vi < 6; vi++) {
|
||||
int c = tri[vi];
|
||||
Vertex3D vex = {
|
||||
pos[c][0],
|
||||
pos[c][1],
|
||||
pos[c][2],
|
||||
uvs[c][0],
|
||||
uvs[c][1],
|
||||
layer_id,
|
||||
NORMALS[key.face][0][0],
|
||||
NORMALS[key.face][0][1],
|
||||
NORMALS[key.face][0][2],
|
||||
roughness,
|
||||
TANGENTS[key.face][0][0],
|
||||
TANGENTS[key.face][0][1],
|
||||
TANGENTS[key.face][0][2],
|
||||
};
|
||||
m_vertex_data[buf].m_vertices.emplace_back(vex);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientChunk::gen_cross_plane_vertices(int world_x, int world_y,
|
||||
int world_z, BlockType id) {
|
||||
|
||||
if (!BlockManager::is_cross_plane(id)) {
|
||||
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
|
||||
world_y, world_z, id);
|
||||
return;
|
||||
}
|
||||
for (int face = 0; face < 2; face++) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
Vertex3D vex = {
|
||||
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
|
||||
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
|
||||
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
|
||||
CROSS_TEX_COORDS[face][i][0],
|
||||
CROSS_TEX_COORDS[face][i][1],
|
||||
static_cast<float>(BlockManager::cross_plane_index(id)),
|
||||
CROSS_NORMALS[face][i][0],
|
||||
CROSS_NORMALS[face][i][1],
|
||||
CROSS_NORMALS[face][i][2],
|
||||
BlockManager::roughness(id),
|
||||
CROSS_TANGENTS[face][i][0],
|
||||
CROSS_TANGENTS[face][i][1],
|
||||
CROSS_TANGENTS[face][i][2]
|
||||
|
||||
};
|
||||
m_vertex_data[1].m_vertices.emplace_back(vex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientChunk::receive_chunk(const ChunkDataRsp& data) {
|
||||
OptionalBlockVectorArray neighbor;
|
||||
|
||||
m_chunk_pos.x = data.pos().x();
|
||||
m_chunk_pos.z = data.pos().z();
|
||||
m_seed = data.chunk_seed();
|
||||
m_biome = get_biome_from_id(data.biome_type());
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
neighbor[i] = std::nullopt;
|
||||
}
|
||||
|
||||
if (data.chunk_blocks_size() != BLOCK_SIZE) {
|
||||
Logger::error("Bad Chunk, size {}", data.chunk_blocks_size());
|
||||
return;
|
||||
}
|
||||
m_blocks.reserve(BLOCK_SIZE);
|
||||
|
||||
for (const auto& b : data.chunk_blocks()) {
|
||||
m_blocks.push_back(static_cast<BlockType>(b));
|
||||
}
|
||||
// temp neighbor block data
|
||||
auto load_neighbor = [&](int idx, const auto& blocks) {
|
||||
if (blocks.size() != BLOCK_SIZE)
|
||||
return;
|
||||
|
||||
neighbor[idx].emplace();
|
||||
neighbor[idx]->reserve(BLOCK_SIZE);
|
||||
|
||||
for (auto b : blocks) {
|
||||
neighbor[idx]->push_back(static_cast<BlockType>(b));
|
||||
}
|
||||
};
|
||||
|
||||
load_neighbor(0, data.neighbor_blocks_1());
|
||||
load_neighbor(1, data.neighbor_blocks_2());
|
||||
load_neighbor(2, data.neighbor_blocks_3());
|
||||
load_neighbor(3, data.neighbor_blocks_4());
|
||||
|
||||
gen_vertex_data(neighbor);
|
||||
mark_dirty();
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -1,48 +1,41 @@
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
#include "Cubed/gameplay/client_player.hpp"
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/debug_collector.hpp"
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
ClientPlayer::ClientPlayer(ClientWorld& world) : m_world(world) {}
|
||||
ClientPlayer::~ClientPlayer() {}
|
||||
|
||||
Player::Player(World& world, const std::string& name)
|
||||
: m_name(name), m_world(world) {
|
||||
hot_reload();
|
||||
}
|
||||
Player::~Player() {}
|
||||
AABB ClientPlayer::get_aabb(const glm::vec3& pos) {
|
||||
float half_width = M_SIZE.x / 2.0f;
|
||||
float half_depth = M_SIZE.z / 2.0f;
|
||||
|
||||
AABB Player::get_aabb() const {
|
||||
float half_width = m_size.x / 2.0f;
|
||||
float half_depth = m_size.z / 2.0f;
|
||||
glm::vec3 min{pos.x - half_width, pos.y, pos.z - half_depth};
|
||||
|
||||
glm::vec3 min{m_player_pos.x - half_width, m_player_pos.y,
|
||||
m_player_pos.z - half_depth};
|
||||
|
||||
glm::vec3 max{m_player_pos.x + half_width, m_player_pos.y + m_size.y,
|
||||
m_player_pos.z + half_depth};
|
||||
glm::vec3 max{pos.x + half_width, pos.y + M_SIZE.y, pos.z + half_depth};
|
||||
|
||||
return AABB{min, max};
|
||||
}
|
||||
const glm::vec3& ClientPlayer::get_front() const { return m_front; }
|
||||
|
||||
const glm::vec3& Player::get_front() const { return m_front; }
|
||||
Gait ClientPlayer::get_gait() const { return m_gait.load(); }
|
||||
|
||||
const Gait& Player::get_gait() const { return m_gait; }
|
||||
|
||||
const std::optional<LookBlock>& Player::get_look_block_pos() const {
|
||||
const std::optional<LookBlock>& ClientPlayer::get_look_block_pos() const {
|
||||
return m_look_block;
|
||||
}
|
||||
glm::vec3 ClientPlayer::get_player_pos() const {
|
||||
|
||||
const glm::vec3& Player::get_player_pos() const { return m_player_pos; }
|
||||
std::shared_lock lock(m_player_pos_mutex);
|
||||
return m_player_pos;
|
||||
}
|
||||
|
||||
const MoveState& Player::get_move_state() const { return m_move_state; }
|
||||
const MoveState& ClientPlayer::get_move_state() const { return m_move_state; }
|
||||
|
||||
bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front,
|
||||
glm::ivec3& block_pos, glm::vec3& normal,
|
||||
float distance) {
|
||||
bool ClientPlayer::ray_cast(const glm::vec3& start, const glm::vec3& front,
|
||||
glm::ivec3& block_pos, glm::vec3& normal,
|
||||
float distance) {
|
||||
glm::vec3 dir = glm::normalize(front);
|
||||
// float step = 0.1f;
|
||||
glm::ivec3 cur = glm::floor(start);
|
||||
@@ -113,34 +106,30 @@ bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front,
|
||||
return false;
|
||||
}
|
||||
|
||||
void Player::change_mode(GameMode mode) {
|
||||
void ClientPlayer::change_mode(GameMode mode) {
|
||||
m_game_mode = mode;
|
||||
Logger::info("Change GameMode to {}", to_str(mode));
|
||||
if (mode == CREATIVE) {
|
||||
is_fly = false;
|
||||
m_gait = Gait::WALK;
|
||||
m_max_speed = m_max_walk_speed;
|
||||
} else if (mode == SPECTATOR) {
|
||||
is_fly = true;
|
||||
m_gait = Gait::RUN;
|
||||
m_max_speed = m_max_run_speed;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::hot_reload() {
|
||||
void ClientPlayer::hot_reload() {
|
||||
auto& config = Config::get();
|
||||
m_sensitivity =
|
||||
static_cast<float>(config.get<double>("player.mouse_sensitivity"));
|
||||
}
|
||||
void ClientPlayer::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
|
||||
|
||||
void Player::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
|
||||
|
||||
void Player::set_place_block(unsigned id) { m_place_block = id; }
|
||||
|
||||
void Player::update(float delta_time) {
|
||||
void ClientPlayer::set_place_block(unsigned id) { m_place_block = id; }
|
||||
|
||||
void ClientPlayer::update(float delta_time) {
|
||||
m_gait = compute_gait();
|
||||
update_move(delta_time);
|
||||
update_lookup_block();
|
||||
check_player_chunk_transition();
|
||||
|
||||
DebugCollector::get().report("player_pos",
|
||||
std::format("x: {:.2f} y: {:.2f} z: {:.2f}",
|
||||
@@ -150,42 +139,47 @@ void Player::update(float delta_time) {
|
||||
DebugCollector::get().report("speed",
|
||||
std::format("Speed: {:.2} m/s", m_xz_speed));
|
||||
}
|
||||
|
||||
void Player::update_player_move_state(int key, int action) {
|
||||
void ClientPlayer::update_player_move_state(int key, int action) {
|
||||
switch (key) {
|
||||
case GLFW_KEY_W:
|
||||
if (action == GLFW_PRESS) {
|
||||
m_move_state.forward = true;
|
||||
m_moving = true;
|
||||
}
|
||||
if (action == GLFW_RELEASE) {
|
||||
m_move_state.forward = false;
|
||||
if (m_game_mode != SPECTATOR) {
|
||||
m_gait = Gait::WALK;
|
||||
}
|
||||
m_moving = false;
|
||||
m_sprinting = false;
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_S:
|
||||
if (action == GLFW_PRESS) {
|
||||
m_move_state.back = true;
|
||||
m_moving = true;
|
||||
}
|
||||
if (action == GLFW_RELEASE) {
|
||||
m_move_state.back = false;
|
||||
m_moving = false;
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_A:
|
||||
if (action == GLFW_PRESS) {
|
||||
m_move_state.left = true;
|
||||
m_moving = true;
|
||||
}
|
||||
if (action == GLFW_RELEASE) {
|
||||
m_move_state.left = false;
|
||||
m_moving = false;
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_D:
|
||||
if (action == GLFW_PRESS) {
|
||||
m_move_state.right = true;
|
||||
m_moving = true;
|
||||
}
|
||||
if (action == GLFW_RELEASE) {
|
||||
m_move_state.right = false;
|
||||
m_moving = false;
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_SPACE:
|
||||
@@ -216,7 +210,7 @@ void Player::update_player_move_state(int key, int action) {
|
||||
break;
|
||||
case GLFW_KEY_LEFT_CONTROL:
|
||||
if (action == GLFW_PRESS) {
|
||||
m_gait = Gait::RUN;
|
||||
m_sprinting = true;
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_F4:
|
||||
@@ -231,37 +225,24 @@ void Player::update_player_move_state(int key, int action) {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_front_vec(float offset_x, float offset_y) {
|
||||
void ClientPlayer::update_front_vec(float offset_x, float offset_y) {
|
||||
m_yaw += offset_x * m_sensitivity;
|
||||
m_pitch += offset_y * m_sensitivity;
|
||||
|
||||
m_yaw = std::fmod(m_yaw, 360.0);
|
||||
// m_yaw = std::fmod(m_yaw.load(), 360.0);
|
||||
|
||||
m_pitch = std::clamp(m_pitch, -89.0f, 89.0f);
|
||||
m_pitch = std::clamp(m_pitch.load(), -89.0f, 89.0f);
|
||||
|
||||
m_front.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
|
||||
m_front.y = sin(glm::radians(m_pitch));
|
||||
m_front.z = -cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
|
||||
m_front.x =
|
||||
sin(glm::radians(m_yaw.load())) * cos(glm::radians(m_pitch.load()));
|
||||
m_front.y = sin(glm::radians(m_pitch.load()));
|
||||
m_front.z =
|
||||
-cos(glm::radians(m_yaw.load())) * cos(glm::radians(m_pitch.load()));
|
||||
|
||||
m_front = glm::normalize(m_front);
|
||||
}
|
||||
|
||||
void Player::check_player_chunk_transition() {
|
||||
ChunkPos cur_pos = m_world.chunk_pos(m_player_pos.x, m_player_pos.z);
|
||||
if (cur_pos != m_player_chunk_pos) {
|
||||
m_world.need_gen();
|
||||
m_player_chunk_pos = cur_pos;
|
||||
auto chunk = m_world.get_chunk(cur_pos);
|
||||
if (chunk == nullptr) {
|
||||
DebugCollector::get().report("biome", "Biome: Unknown");
|
||||
} else {
|
||||
DebugCollector::get().report(
|
||||
"biome", "Biome: " + get_biome_str(chunk->get_biome()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_direction() {
|
||||
void ClientPlayer::update_direction() {
|
||||
m_right = glm::normalize(glm::cross(m_front, glm::vec3(0.0f, 1.0f, 0.0f)));
|
||||
|
||||
glm::vec3 move_dir_front = glm::vec3(0.0f);
|
||||
@@ -286,7 +267,7 @@ void Player::update_direction() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_lookup_block() {
|
||||
void ClientPlayer::update_lookup_block() {
|
||||
// calculate the block that is looked
|
||||
glm::ivec3 block_pos;
|
||||
glm::vec3 block_normal;
|
||||
@@ -301,25 +282,17 @@ void Player::update_lookup_block() {
|
||||
if (m_look_block != std::nullopt) {
|
||||
if (Input::get_input_state().mouse_state.left) {
|
||||
if (m_world.is_solid(m_look_block->pos)) {
|
||||
m_world.set_block(m_look_block->pos, 0);
|
||||
m_world.report_block_change(m_look_block->pos, 0);
|
||||
}
|
||||
Input::get_input_state().mouse_state.left = false;
|
||||
}
|
||||
if (Input::get_input_state().mouse_state.right) {
|
||||
glm::ivec3 near_pos = m_look_block->pos + m_look_block->normal;
|
||||
if (!m_world.is_solid(near_pos)) {
|
||||
auto x = near_pos.x;
|
||||
auto y = near_pos.y;
|
||||
auto z = near_pos.z;
|
||||
AABB block_box = {glm::vec3{static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(z)},
|
||||
glm::vec3{static_cast<float>(x + 1),
|
||||
static_cast<float>(y + 1),
|
||||
static_cast<float>(z + 1)}};
|
||||
AABB player_box = get_aabb();
|
||||
AABB block_box = ClientWorld::get_block_aabb(near_pos);
|
||||
AABB player_box = get_aabb(get_player_pos());
|
||||
if (!player_box.intersects(block_box)) {
|
||||
m_world.set_block(near_pos, m_place_block);
|
||||
m_world.report_block_change(near_pos, m_place_block);
|
||||
}
|
||||
}
|
||||
Input::get_input_state().mouse_state.right = false;
|
||||
@@ -327,18 +300,27 @@ void Player::update_lookup_block() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_move(float delta_time) {
|
||||
void ClientPlayer::update_move(float delta_time) {
|
||||
// if frame rate less than 1 frame per second, don't update
|
||||
if (delta_time > 1.0f) {
|
||||
return;
|
||||
}
|
||||
if (m_xz_speed < 0.01f) {
|
||||
m_sprinting = false;
|
||||
}
|
||||
// ensure the thread safe
|
||||
glm::vec3 player_pos;
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_player_pos_mutex);
|
||||
player_pos = m_player_pos;
|
||||
}
|
||||
|
||||
if (m_game_mode != SPECTATOR) {
|
||||
if (m_gait == Gait::RUN) {
|
||||
m_max_speed = m_max_run_speed;
|
||||
}
|
||||
if (m_gait == Gait::WALK) {
|
||||
m_max_speed = m_max_walk_speed;
|
||||
}
|
||||
m_max_speed =
|
||||
(m_gait == Gait::RUN) ? m_max_run_speed : m_max_walk_speed;
|
||||
} else {
|
||||
m_max_speed = m_max_run_speed;
|
||||
}
|
||||
|
||||
if (space_on) {
|
||||
@@ -372,11 +354,11 @@ void Player::update_move(float delta_time) {
|
||||
|
||||
if (is_fly) {
|
||||
if (m_move_state.up) {
|
||||
m_y_speed = 7.5f;
|
||||
m_y_speed = m_fly_y_speed;
|
||||
}
|
||||
|
||||
if (m_move_state.down) {
|
||||
m_y_speed = -7.5f;
|
||||
m_y_speed = -m_fly_y_speed;
|
||||
}
|
||||
|
||||
if (!m_move_state.down && !m_move_state.up) {
|
||||
@@ -393,24 +375,30 @@ void Player::update_move(float delta_time) {
|
||||
|
||||
move_distance.y = m_y_speed * delta_time;
|
||||
// y
|
||||
update_y_move();
|
||||
update_y_move(player_pos);
|
||||
// x
|
||||
update_x_move();
|
||||
update_x_move(player_pos);
|
||||
|
||||
update_z_move();
|
||||
update_z_move(player_pos);
|
||||
|
||||
if (m_player_pos.y < -15.0f) {
|
||||
if (player_pos.y < -15.0f) {
|
||||
Logger::warn("y is tow low");
|
||||
m_player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
|
||||
player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_player_pos_mutex);
|
||||
m_player_pos = player_pos;
|
||||
}
|
||||
update_player_chunk();
|
||||
}
|
||||
|
||||
void Player::update_x_move() {
|
||||
m_player_pos.x += move_distance.x;
|
||||
void ClientPlayer::update_x_move(glm::vec3& player_pos) {
|
||||
player_pos.x += move_distance.x;
|
||||
if (m_game_mode == SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
AABB player_box = get_aabb();
|
||||
AABB player_box = get_aabb(player_pos);
|
||||
int minx = std::floor(player_box.min.x);
|
||||
int maxx = std::floor(player_box.max.x);
|
||||
int miny = std::floor(player_box.min.y);
|
||||
@@ -421,16 +409,12 @@ void Player::update_x_move() {
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
|
||||
AABB block_box = {glm::vec3{static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(z)},
|
||||
glm::vec3{static_cast<float>(x + 1),
|
||||
static_cast<float>(y + 1),
|
||||
static_cast<float>(z + 1)}};
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!m_world.can_pass_block(block_pos)) {
|
||||
AABB block_box = ClientWorld::get_block_aabb(block_pos);
|
||||
if (player_box.intersects(block_box)) {
|
||||
m_gait = Gait::WALK;
|
||||
m_player_pos.x -= move_distance.x;
|
||||
m_sprinting = false;
|
||||
player_pos.x -= move_distance.x;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -439,12 +423,12 @@ void Player::update_x_move() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_y_move() {
|
||||
m_player_pos.y += move_distance.y;
|
||||
void ClientPlayer::update_y_move(glm::vec3& player_pos) {
|
||||
player_pos.y += move_distance.y;
|
||||
if (m_game_mode == SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
AABB player_box = get_aabb();
|
||||
AABB player_box = get_aabb(player_pos);
|
||||
int minx = std::floor(player_box.min.x);
|
||||
int maxx = std::floor(player_box.max.x);
|
||||
int miny = std::floor(player_box.min.y);
|
||||
@@ -455,15 +439,11 @@ void Player::update_y_move() {
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
|
||||
AABB block_box = {glm::vec3{static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(z)},
|
||||
glm::vec3{static_cast<float>(x + 1),
|
||||
static_cast<float>(y + 1),
|
||||
static_cast<float>(z + 1)}};
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!m_world.can_pass_block(block_pos)) {
|
||||
AABB block_box = ClientWorld::get_block_aabb(block_pos);
|
||||
if (player_box.intersects(block_box)) {
|
||||
m_player_pos.y -= move_distance.y;
|
||||
player_pos.y -= move_distance.y;
|
||||
m_y_speed = 0.0f;
|
||||
if (move_distance.y < 0) {
|
||||
can_up = true;
|
||||
@@ -477,12 +457,12 @@ void Player::update_y_move() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_z_move() {
|
||||
m_player_pos.z += move_distance.z;
|
||||
void ClientPlayer::update_z_move(glm::vec3& player_pos) {
|
||||
player_pos.z += move_distance.z;
|
||||
if (m_game_mode == SPECTATOR) {
|
||||
return;
|
||||
}
|
||||
AABB player_box = get_aabb();
|
||||
AABB player_box = get_aabb(player_pos);
|
||||
int minx = std::floor(player_box.min.x);
|
||||
int maxx = std::floor(player_box.max.x);
|
||||
int miny = std::floor(player_box.min.y);
|
||||
@@ -493,16 +473,12 @@ void Player::update_z_move() {
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
|
||||
AABB block_box = {glm::vec3{static_cast<float>(x),
|
||||
static_cast<float>(y),
|
||||
static_cast<float>(z)},
|
||||
glm::vec3{static_cast<float>(x + 1),
|
||||
static_cast<float>(y + 1),
|
||||
static_cast<float>(z + 1)}};
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!m_world.can_pass_block(block_pos)) {
|
||||
AABB block_box = ClientWorld::get_block_aabb(block_pos);
|
||||
if (player_box.intersects(block_box)) {
|
||||
m_gait = Gait::WALK;
|
||||
m_player_pos.z -= move_distance.z;
|
||||
m_sprinting = false;
|
||||
player_pos.z -= move_distance.z;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -511,7 +487,33 @@ void Player::update_z_move() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::update_scroll(double yoffset) {
|
||||
void ClientPlayer::update_player_chunk() {
|
||||
float x, z;
|
||||
{
|
||||
std::shared_lock lock(m_player_pos_mutex);
|
||||
x = m_player_pos.x;
|
||||
z = m_player_pos.z;
|
||||
}
|
||||
ChunkPos chunk_pos = get_chunk_pos(x, z);
|
||||
float dist = distance2(chunk_pos, m_last_chunk_pos);
|
||||
if (dist > 2) {
|
||||
Logger::info("Player request new chunk");
|
||||
m_world.request_chunk();
|
||||
m_last_chunk_pos = chunk_pos;
|
||||
}
|
||||
}
|
||||
|
||||
Gait ClientPlayer::compute_gait() const {
|
||||
if (m_xz_speed < 0.01f)
|
||||
return Gait::STOP;
|
||||
|
||||
if (m_sprinting)
|
||||
return Gait::RUN;
|
||||
|
||||
return Gait::WALK;
|
||||
}
|
||||
|
||||
void ClientPlayer::update_scroll(double yoffset) {
|
||||
if (m_game_mode == SPECTATOR) {
|
||||
if (yoffset > 0) {
|
||||
if (m_max_speed < 500.0f) {
|
||||
@@ -538,14 +540,48 @@ void Player::update_scroll(double yoffset) {
|
||||
}
|
||||
}
|
||||
|
||||
float& Player::max_walk_speed() { return m_max_walk_speed; }
|
||||
float& Player::max_run_speed() { return m_max_run_speed; }
|
||||
float& Player::max_speed() { return m_max_speed; }
|
||||
float& Player::acceleration() { return m_acceleration; }
|
||||
float& Player::deceleration() { return m_deceleration; }
|
||||
float& Player::g() { return m_g; }
|
||||
unsigned Player::place_block() const { return m_place_block; };
|
||||
Gait& Player::gait() { return m_gait; }
|
||||
GameMode& Player::game_mode() { return m_game_mode; }
|
||||
const World& Player::get_world() const { return m_world; }
|
||||
} // namespace Cubed
|
||||
void ClientPlayer::update_chunk_set(const ChunkPosSet& set) {
|
||||
std::lock_guard lock(m_chunk_pos_mutex);
|
||||
m_player_chunk_pos_set.clear();
|
||||
m_player_chunk_pos_set.insert(set.begin(), set.end());
|
||||
}
|
||||
|
||||
const ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() const {
|
||||
std::shared_lock lock(m_chunk_pos_mutex);
|
||||
return m_player_chunk_pos_set;
|
||||
}
|
||||
|
||||
ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() {
|
||||
std::lock_guard lock(m_chunk_pos_mutex);
|
||||
return m_player_chunk_pos_set;
|
||||
}
|
||||
|
||||
float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; }
|
||||
float& ClientPlayer::max_run_speed() { return m_max_run_speed; }
|
||||
float& ClientPlayer::max_speed() { return m_max_speed; }
|
||||
float& ClientPlayer::acceleration() { return m_acceleration; }
|
||||
float& ClientPlayer::deceleration() { return m_deceleration; }
|
||||
float& ClientPlayer::g() { return m_g; }
|
||||
float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; }
|
||||
unsigned ClientPlayer::place_block() const { return m_place_block; };
|
||||
void ClientPlayer::set_gait(Gait gait) { m_gait = gait; }
|
||||
GameMode& ClientPlayer::game_mode() { return m_game_mode; }
|
||||
const ClientWorld& ClientPlayer::get_world() const { return m_world; }
|
||||
|
||||
void ClientPlayer::set_uuid(std::string_view uuid) {
|
||||
std::lock_guard lock(m_uuid_mutex);
|
||||
m_uuid = uuid;
|
||||
}
|
||||
std::string ClientPlayer::get_uuid() const {
|
||||
|
||||
std::shared_lock lock(m_uuid_mutex);
|
||||
return m_uuid;
|
||||
}
|
||||
const std::string& ClientPlayer::get_name() const { return m_name; }
|
||||
void ClientPlayer::init(std::string_view name) { m_name = name; }
|
||||
|
||||
float ClientPlayer::yaw() const { return m_yaw; }
|
||||
float ClientPlayer::pitch() const { return m_pitch; }
|
||||
float& ClientPlayer::angle() { return m_angle; }
|
||||
float& ClientPlayer::walk_time() { return m_walk_time; }
|
||||
} // namespace Cubed
|
||||
828
src/gameplay/client_world.cpp
Normal file
828
src/gameplay/client_world.cpp
Normal file
@@ -0,0 +1,828 @@
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
|
||||
#include <absl/container/inlined_vector.h>
|
||||
#include <numbers>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace google::protobuf;
|
||||
namespace Cubed {
|
||||
|
||||
namespace {
|
||||
struct ChunkRenderData {
|
||||
std::array<const std::vector<BlockType>*, 4> neighbor_block;
|
||||
ClientChunk* chunk;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
ClientWorld::ClientWorld() : m_player(*this) {}
|
||||
|
||||
ClientWorld::~ClientWorld() {
|
||||
stop_client_thread();
|
||||
stop_thread_pool();
|
||||
|
||||
m_chunks.clear();
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_delete_vbo_mutex);
|
||||
for (auto x : m_pending_delete_vbo) {
|
||||
glDeleteBuffers(1, &x);
|
||||
}
|
||||
m_pending_delete_vbo.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard lk(m_delete_vao_mutex);
|
||||
for (auto x : m_pending_delete_vao) {
|
||||
glDeleteVertexArrays(1, &x);
|
||||
}
|
||||
m_pending_delete_vao.clear();
|
||||
}
|
||||
m_timers.clear();
|
||||
}
|
||||
|
||||
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
|
||||
|
||||
return m_player.get_look_block_pos();
|
||||
}
|
||||
|
||||
ClientPlayer& ClientWorld::get_player() { return m_player; }
|
||||
const ClientPlayer& ClientWorld::get_player() const { return m_player; }
|
||||
int ClientWorld::get_block(const glm::ivec3& block_pos) const {
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
|
||||
chunk_cacc cacc;
|
||||
|
||||
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
|
||||
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
return chunk_blocks[ClientChunk::index(x, y, z)];
|
||||
}
|
||||
bool ClientWorld::is_solid(const glm::ivec3& block_pos) const {
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
|
||||
chunk_cacc cacc;
|
||||
|
||||
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
|
||||
return false;
|
||||
}
|
||||
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
|
||||
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return false;
|
||||
}
|
||||
auto id = chunk_blocks[ClientChunk::index(x, y, z)];
|
||||
if (BlockManager::is_gas(id) || BlockManager::is_liquid(id)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool ClientWorld::can_pass_block(const glm::ivec3& block_pos) const {
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
|
||||
chunk_cacc cacc;
|
||||
|
||||
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
|
||||
return true;
|
||||
}
|
||||
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
|
||||
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return true;
|
||||
}
|
||||
auto id = chunk_blocks[ClientChunk::index(x, y, z)];
|
||||
return BlockManager::is_passable(id);
|
||||
}
|
||||
|
||||
void ClientWorld::rebuild_world() {
|
||||
if (m_is_rebuilding.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
stop_client_thread();
|
||||
stop_thread_pool();
|
||||
|
||||
m_chunks.clear();
|
||||
|
||||
m_pending_upload_queue.clear();
|
||||
|
||||
start_thread_pool();
|
||||
start_client_thread(m_player.get_uuid());
|
||||
request_chunk();
|
||||
m_is_rebuilding = false;
|
||||
}
|
||||
|
||||
BlockType ClientWorld::get_block_tpye(const glm::ivec3& block_pos) const {
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
|
||||
chunk_cacc cacc;
|
||||
;
|
||||
|
||||
if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) {
|
||||
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
|
||||
// block_pos.z);
|
||||
return 0;
|
||||
}
|
||||
const auto& chunk_blocks = cacc->second->get_chunk_blocks();
|
||||
auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z});
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
|
||||
// block_pos.z);
|
||||
return 0;
|
||||
}
|
||||
return chunk_blocks[ClientChunk::index(x, y, z)];
|
||||
}
|
||||
void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
|
||||
int world_x, world_y, world_z;
|
||||
world_x = block_pos.x;
|
||||
world_y = block_pos.y;
|
||||
world_z = block_pos.z;
|
||||
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
|
||||
ChunkPos pos{chunk_x, chunk_z};
|
||||
{
|
||||
chunk_acc acc;
|
||||
|
||||
if (!m_chunks.find(acc, pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z,
|
||||
chunk_x, chunk_z);
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
acc->second->set_chunk_block(ClientChunk::index(x, y, z), id);
|
||||
acc->second->mark_dirty();
|
||||
}
|
||||
|
||||
auto pool = m_thread_pool.load();
|
||||
|
||||
pool->enqueue(0, [this, pos]() {
|
||||
std::shared_ptr<ClientChunk> chunk;
|
||||
|
||||
{
|
||||
chunk_acc acc;
|
||||
if (m_chunks.find(acc, pos)) {
|
||||
chunk = acc->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (!chunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
OptionalBlockVectorArray neighbor_block;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
chunk_cacc cacc;
|
||||
if (m_chunks.find(cacc, pos + CHUNK_DIR[i])) {
|
||||
neighbor_block[i] = (cacc->second->get_chunk_blocks());
|
||||
} else {
|
||||
neighbor_block[i] = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
chunk->gen_vertex_data(neighbor_block);
|
||||
m_dirty_chunk_queue.emplace(pos);
|
||||
});
|
||||
|
||||
static const glm::ivec3 NEIGHBOR_DIRS[] = {
|
||||
{1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}};
|
||||
static constexpr int NPOS_SUM = sizeof(NEIGHBOR_DIRS);
|
||||
|
||||
absl::InlinedVector<ChunkPos, NPOS_SUM> nposes;
|
||||
|
||||
for (const auto& dir : NEIGHBOR_DIRS) {
|
||||
glm::ivec3 neighbor = block_pos + dir;
|
||||
|
||||
auto [cx, cz] = get_chunk_pos(neighbor.x, neighbor.z);
|
||||
{
|
||||
chunk_acc acc;
|
||||
if (m_chunks.find(acc, {cx, cz})) {
|
||||
if (acc->second->is_dirty()) {
|
||||
continue;
|
||||
}
|
||||
nposes.emplace_back(acc->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& npos : nposes) {
|
||||
pool->enqueue(0, [this, npos]() {
|
||||
std::shared_ptr<ClientChunk> chunk;
|
||||
|
||||
{
|
||||
chunk_acc acc;
|
||||
if (m_chunks.find(acc, npos)) {
|
||||
chunk = acc->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (!chunk) {
|
||||
return;
|
||||
}
|
||||
|
||||
OptionalBlockVectorArray neighbor_block;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
chunk_cacc cacc;
|
||||
if (m_chunks.find(cacc, npos + CHUNK_DIR[i])) {
|
||||
neighbor_block[i] = (cacc->second->get_chunk_blocks());
|
||||
} else {
|
||||
neighbor_block[i] = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
chunk->gen_vertex_data(neighbor_block);
|
||||
|
||||
m_dirty_chunk_queue.emplace(npos);
|
||||
});
|
||||
}
|
||||
}
|
||||
void ClientWorld::push_delete_vbo(GLuint vbo) {
|
||||
std::lock_guard lk(m_delete_vbo_mutex);
|
||||
m_pending_delete_vbo.push_back(vbo);
|
||||
}
|
||||
void ClientWorld::push_delete_vao(GLuint vao) {
|
||||
std::lock_guard lk(m_delete_vao_mutex);
|
||||
m_pending_delete_vao.push_back(vao);
|
||||
}
|
||||
|
||||
void ClientWorld::report_block_change(const glm::ivec3& pos,
|
||||
unsigned id) const {
|
||||
if (id != 0) {
|
||||
AABB block_box = get_block_aabb(pos);
|
||||
std::shared_lock lock(m_player_info_mutex);
|
||||
|
||||
for (auto& [uuid, player] : m_player_info) {
|
||||
AABB box = ClientPlayer::get_aabb(player.target_pos);
|
||||
if (box.intersects(block_box)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Arena arena;
|
||||
auto* req = Arena::Create<BlockChangeReq>(&arena);
|
||||
req->set_uuid(m_player.get_uuid());
|
||||
req->set_block(id);
|
||||
auto* p = req->mutable_pos();
|
||||
p->set_x(pos.x);
|
||||
p->set_y(pos.y);
|
||||
p->set_z(pos.z);
|
||||
m_client->send(make_packet(*req), 0);
|
||||
}
|
||||
|
||||
void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) {
|
||||
glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
|
||||
set_block(pos, rsp.block());
|
||||
}
|
||||
|
||||
void ClientWorld::receive_time(const UpdateTime& rsp) {
|
||||
m_game_ticks = rsp.game_tick();
|
||||
m_day_tick = rsp.day_tick();
|
||||
}
|
||||
|
||||
void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) {
|
||||
auto pitch = rsp.pitch();
|
||||
auto yaw = rsp.yaw();
|
||||
{
|
||||
std::lock_guard lock(m_player_info_mutex);
|
||||
glm::vec3 pos{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
|
||||
auto it = m_player_info.find(rsp.uuid());
|
||||
if (it == m_player_info.end()) {
|
||||
m_player_info.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(rsp.uuid()),
|
||||
std::forward_as_tuple(rsp.name(), rsp.uuid(), pos, pos, yaw,
|
||||
yaw, pitch, pitch,
|
||||
get_gait_from_id(rsp.gait())));
|
||||
} else {
|
||||
it->second.target_pos = pos;
|
||||
it->second.yaw = yaw;
|
||||
it->second.pitch = pitch;
|
||||
it->second.gait = get_gait_from_id(rsp.gait());
|
||||
}
|
||||
// Logger::info("Player {} pos Update", rsp.name());
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
|
||||
if (rsp.server_stop()) {
|
||||
m_receive_exit = true;
|
||||
return;
|
||||
}
|
||||
if (rsp.uuid() == m_player.get_uuid()) {
|
||||
m_receive_exit = true;
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(m_player_info_mutex);
|
||||
int sum = m_player_info.erase(rsp.uuid());
|
||||
if (sum == 0) {
|
||||
Logger::warn("Player {} not find", rsp.uuid());
|
||||
} else {
|
||||
Logger::info("Player {} erase", rsp.uuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWorld::init(std::string_view player_name,
|
||||
std::shared_ptr<NetworkClient> client) {
|
||||
m_player.init(player_name);
|
||||
m_client = client;
|
||||
// timer
|
||||
register_timer("player_pos", 1, [this]() { report_player_info(); });
|
||||
LoginReq req;
|
||||
req.set_name(m_player.get_name());
|
||||
while (!client->is_connected()) {
|
||||
if (client->is_connect_error()) {
|
||||
throw std::runtime_error("Can't connect to the server");
|
||||
}
|
||||
std::this_thread::sleep_for(milliseconds(200));
|
||||
}
|
||||
start_thread_pool();
|
||||
// request login
|
||||
Logger::info("Send Login Request");
|
||||
m_client->send(make_packet(req), 0);
|
||||
}
|
||||
|
||||
void ClientWorld::start_client_thread(std::string_view uuid) {
|
||||
if (m_game_running) {
|
||||
Logger::error("Game Already Running");
|
||||
return;
|
||||
}
|
||||
// response
|
||||
m_player.set_uuid(uuid);
|
||||
m_client_thread = std::jthread([this](std::stop_token token) {
|
||||
m_game_running = true;
|
||||
client_run(token);
|
||||
});
|
||||
|
||||
// Wait for 20 ticks, after the server's central chunk is generated, then
|
||||
// request chunks
|
||||
|
||||
std::this_thread::sleep_for(milliseconds(20 * DEFAULT_PER_TICK_TIME));
|
||||
|
||||
request_chunk();
|
||||
}
|
||||
|
||||
void ClientWorld::stop_client_thread() {
|
||||
m_client_thread.request_stop();
|
||||
if (m_client_thread.joinable()) {
|
||||
m_client_thread.join();
|
||||
}
|
||||
m_game_running = false;
|
||||
}
|
||||
void ClientWorld::start_thread_pool() {
|
||||
int max_threads = std::thread::hardware_concurrency();
|
||||
int threads = std::min<size_t>(max_threads, 4);
|
||||
change_pool_threads(threads);
|
||||
}
|
||||
void ClientWorld::stop_thread_pool() {
|
||||
auto pool_ptr = m_thread_pool.load();
|
||||
if (pool_ptr) {
|
||||
pool_ptr->stop();
|
||||
}
|
||||
m_thread_pool.store(nullptr);
|
||||
Logger::info("Thread Pool Stopped");
|
||||
}
|
||||
|
||||
void ClientWorld::change_pool_threads(int threads) {
|
||||
int m_max_threads = std::thread::hardware_concurrency();
|
||||
if (m_max_threads < 1) {
|
||||
Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4");
|
||||
m_max_threads = 1;
|
||||
}
|
||||
int used_thread = std::clamp(threads, 1, m_max_threads);
|
||||
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
|
||||
m_thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread));
|
||||
}
|
||||
|
||||
void ClientWorld::hot_reload() {
|
||||
auto& config = Config::get();
|
||||
int dist = config.get<int>("world.rendering_distance");
|
||||
Logger::info("Get Config Randering dist {}", dist);
|
||||
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
|
||||
request_chunk();
|
||||
}
|
||||
|
||||
void ClientWorld::client_run(std::stop_token stoken) {
|
||||
Logger::info("Client Thread Started");
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME);
|
||||
|
||||
auto next = Clock::now();
|
||||
while (!stoken.stop_requested()) {
|
||||
next += TICK;
|
||||
for (auto& x : m_timers) {
|
||||
x.second.update();
|
||||
}
|
||||
std::this_thread::sleep_until(next);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWorld::report_player_info() {
|
||||
if (!m_client) {
|
||||
return;
|
||||
}
|
||||
Arena arena;
|
||||
auto* info = Arena::Create<C2S_PlayerInfo>(&arena);
|
||||
info->set_uuid(m_player.get_uuid());
|
||||
glm::vec3 player_pos = m_player.get_player_pos();
|
||||
auto* v3 = info->mutable_pos();
|
||||
v3->set_x(player_pos.x);
|
||||
v3->set_y(player_pos.y);
|
||||
v3->set_z(player_pos.z);
|
||||
info->set_yaw(m_player.yaw());
|
||||
info->set_pitch(m_player.pitch());
|
||||
info->set_gait(get_gait_id(m_player.get_gait()));
|
||||
|
||||
m_client->send(make_packet(*info), 0);
|
||||
}
|
||||
|
||||
void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) {
|
||||
|
||||
// Elements in the old set that are not contained in now are not needed by
|
||||
// the current player.
|
||||
|
||||
for (auto& pos : old) {
|
||||
if (!now.contains(pos)) {
|
||||
|
||||
chunk_acc acc;
|
||||
if (!m_chunks.find(acc, pos)) {
|
||||
Logger::warn("Update Ref Count Error, can't Find old pos "
|
||||
"in m_chunks");
|
||||
continue;
|
||||
}
|
||||
|
||||
m_chunks.erase(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWorld::request_chunk() {
|
||||
if (m_requesting_chunk.exchange(true)) {
|
||||
Logger::warn("It is requesting new chunk!");
|
||||
return;
|
||||
}
|
||||
ChunkPosSet required_chunks;
|
||||
|
||||
glm::vec3 player_pos = m_player.get_player_pos();
|
||||
|
||||
int x = std::floor(player_pos.x);
|
||||
int z = std::floor(player_pos.z);
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(x, z);
|
||||
int radius = m_rendering_distance;
|
||||
Logger::info("Client Chunk Radius {}", radius);
|
||||
int r2 = radius * radius;
|
||||
required_chunks.reserve(radius * radius);
|
||||
|
||||
for (int dx = -radius; dx <= radius; ++dx) {
|
||||
for (int dz = -radius; dz <= radius; ++dz) {
|
||||
if (dx * dx + dz * dz <= r2) {
|
||||
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChunkPosSet old = std::move(m_player.get_chunk_pos_set());
|
||||
m_player.update_chunk_set(required_chunks);
|
||||
|
||||
ChunkPosVector need_send_pos;
|
||||
|
||||
for (auto pos : required_chunks) {
|
||||
chunk_cacc cacc;
|
||||
if (!m_chunks.find(cacc, pos)) {
|
||||
need_send_pos.emplace_back(pos);
|
||||
}
|
||||
}
|
||||
|
||||
update_chunk(old, required_chunks);
|
||||
|
||||
if (need_send_pos.empty()) {
|
||||
m_requesting_chunk = false;
|
||||
return;
|
||||
}
|
||||
using enum ChunkLoadStyle;
|
||||
switch (m_chunk_load_style) {
|
||||
case RANDOM:
|
||||
|
||||
break;
|
||||
case CENTER: {
|
||||
|
||||
glm::vec3 player_pos = m_player.get_player_pos();
|
||||
ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z);
|
||||
auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) {
|
||||
float dx = player_chunk_pos.x - chunk_pos.x;
|
||||
float dz = player_chunk_pos.z - chunk_pos.z;
|
||||
return dx * dx + dz * dz;
|
||||
};
|
||||
|
||||
std::sort(need_send_pos.begin(), need_send_pos.end(),
|
||||
[&dist2](const auto& a, const auto& b) {
|
||||
return dist2(a) < dist2(b);
|
||||
});
|
||||
}
|
||||
}
|
||||
auto uuid = m_player.get_uuid();
|
||||
Arena arena;
|
||||
++m_chunk_task_id;
|
||||
auto* req = Arena::Create<ChunkDataReq>(&arena);
|
||||
for (const auto& pos : need_send_pos) {
|
||||
req->set_task_id(m_chunk_task_id.load());
|
||||
req->set_uuid(uuid);
|
||||
auto* p = req->mutable_pos();
|
||||
p->set_x(pos.x);
|
||||
p->set_z(pos.z);
|
||||
m_client->send(make_packet(*req));
|
||||
}
|
||||
Logger::info("Send Chunk Request Success");
|
||||
m_requesting_chunk = false;
|
||||
}
|
||||
|
||||
void ClientWorld::receive_chunk(std::vector<uint8_t> raw_data,
|
||||
PacketHeader header) {
|
||||
|
||||
// vertex data will genrator in client thread pool instead of net thread;
|
||||
auto pool = m_thread_pool.load();
|
||||
if (!pool) {
|
||||
Logger::error("Client Thread Pool is nullptr");
|
||||
return;
|
||||
}
|
||||
pool->enqueue(
|
||||
[this, raw_data = std::move(raw_data), header = std::move(header)]() {
|
||||
Arena arena;
|
||||
auto* data = Arena::Create<ChunkDataRsp>(&arena);
|
||||
if (!decode_packet(*data, raw_data, header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->task_id() < m_chunk_task_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
chunk_cacc cacc;
|
||||
ChunkPos pos{data->pos().x(), data->pos().z()};
|
||||
if (m_chunks.find(cacc, pos)) {
|
||||
Logger::warn("Chunk {} {} has already in client world",
|
||||
pos.x, pos.z);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ClientChunk> chunk =
|
||||
std::make_unique<ClientChunk>(*this);
|
||||
chunk->receive_chunk(*data);
|
||||
|
||||
m_pending_upload_queue.emplace(std::move(chunk));
|
||||
});
|
||||
}
|
||||
bool ClientWorld::is_receive_exit() { return m_receive_exit; }
|
||||
|
||||
int ClientWorld::chunk_size() const { return m_chunks.size(); }
|
||||
|
||||
AABB ClientWorld::get_block_aabb(const glm::ivec3& pos) {
|
||||
auto x = pos.x;
|
||||
auto y = pos.y;
|
||||
auto z = pos.z;
|
||||
return {glm::vec3{static_cast<float>(x), static_cast<float>(y),
|
||||
static_cast<float>(z)},
|
||||
glm::vec3{static_cast<float>(x + 1), static_cast<float>(y + 1),
|
||||
static_cast<float>(z + 1)}};
|
||||
}
|
||||
|
||||
void ClientWorld::request_exit() {
|
||||
if (m_receive_exit) {
|
||||
return;
|
||||
}
|
||||
Arena arena;
|
||||
auto* req = Arena::Create<LogoutReq>(&arena);
|
||||
req->set_uuid(m_player.get_uuid());
|
||||
m_client->send(make_packet(*req));
|
||||
int cnt = 0;
|
||||
while (!m_receive_exit) {
|
||||
std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME));
|
||||
++cnt;
|
||||
if (cnt >= WORLD_EXIT_TIMEOUT) {
|
||||
Logger::warn("Can't Receive Server Exit Sign");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWorld::update(float delta_time) {
|
||||
m_player.update(delta_time);
|
||||
{
|
||||
std::lock_guard lk(m_delete_vbo_mutex);
|
||||
for (auto x : m_pending_delete_vbo) {
|
||||
glDeleteBuffers(1, &x);
|
||||
}
|
||||
m_pending_delete_vbo.clear();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_delete_vao_mutex);
|
||||
for (auto x : m_pending_delete_vao) {
|
||||
glDeleteVertexArrays(1, &x);
|
||||
}
|
||||
m_pending_delete_vao.clear();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ClientChunk>> new_chunks;
|
||||
{
|
||||
std::unique_ptr<ClientChunk> chunk;
|
||||
int sum = 0;
|
||||
while (m_pending_upload_queue.try_pop(chunk)) {
|
||||
new_chunks.emplace_back(std::move(chunk));
|
||||
++sum;
|
||||
if (sum >= MAX_UPLOAD_CHUNK_SUM) {
|
||||
break; // Limit the maximum number of uploads per frame to
|
||||
// improve frame rate performance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& c : new_chunks) {
|
||||
c->upload_to_gpu();
|
||||
}
|
||||
|
||||
for (auto& c : new_chunks) {
|
||||
m_chunks.emplace(c->get_chunk_pos(), std::move(c));
|
||||
}
|
||||
m_render_snapshots.clear();
|
||||
|
||||
ChunkPos pos;
|
||||
|
||||
while (m_dirty_chunk_queue.try_pop(pos)) {
|
||||
std::shared_ptr<ClientChunk> chunk;
|
||||
{
|
||||
chunk_acc acc;
|
||||
if (m_chunks.find(acc, pos)) {
|
||||
chunk = acc->second;
|
||||
}
|
||||
}
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk->upload_to_gpu();
|
||||
}
|
||||
|
||||
auto chunk_pos_set = m_player.get_chunk_pos_set();
|
||||
|
||||
for (auto& pos : chunk_pos_set) {
|
||||
std::shared_ptr<ClientChunk> chunk;
|
||||
{
|
||||
chunk_acc acc;
|
||||
if (m_chunks.find(acc, pos)) {
|
||||
chunk = acc->second;
|
||||
}
|
||||
}
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_render_snapshots.push_back(chunk->get_render_snapshot());
|
||||
}
|
||||
|
||||
m_render_player_data.clear();
|
||||
{
|
||||
std::lock_guard lock(m_player_info_mutex);
|
||||
for (auto& [uuid, player] : m_player_info) {
|
||||
player.render_pos =
|
||||
glm::mix(player.render_pos, player.target_pos, 0.15f);
|
||||
if (Math::distance2(player.render_pos, m_player.get_player_pos()) >
|
||||
m_rendering_distance * CHUNK_SIZE * m_rendering_distance *
|
||||
CHUNK_SIZE) {
|
||||
continue;
|
||||
}
|
||||
player.render_yaw = glm::mix(player.render_yaw, player.yaw, 0.15);
|
||||
player.render_pitch =
|
||||
glm::mix(player.render_pitch, player.pitch, 0.15);
|
||||
|
||||
if (player.gait == Gait::WALK || player.gait == Gait::RUN) {
|
||||
|
||||
player.walk_time += delta_time;
|
||||
|
||||
float speed = player.gait == Gait::RUN ? 14.0f : 8.0f;
|
||||
float amp = player.gait == Gait::RUN ? 50.0f : 35.0f;
|
||||
// float amp = 90.0f;
|
||||
player.angle =
|
||||
glm::sin(player.walk_time * speed) * glm::radians(amp);
|
||||
} else if (player.gait == Gait::STOP) {
|
||||
float t = glm::clamp(delta_time * 10.0f, 0.0f, 1.0f);
|
||||
player.angle = glm::mix(player.angle, 0.0f, t);
|
||||
}
|
||||
|
||||
m_render_player_data.emplace_back(
|
||||
player.name, player.uuid, player.render_pos, player.render_yaw,
|
||||
player.render_pitch, player.gait, player.angle);
|
||||
}
|
||||
{
|
||||
auto gait = m_player.get_gait();
|
||||
auto& walk_time = m_player.walk_time();
|
||||
auto& angle = m_player.angle();
|
||||
if (gait == Gait::WALK || gait == Gait::RUN) {
|
||||
|
||||
walk_time += delta_time;
|
||||
|
||||
float speed = gait == Gait::RUN ? 14.0f : 8.0f;
|
||||
float amp = gait == Gait::RUN ? 50.0f : 35.0f;
|
||||
// float amp = 90.0f;
|
||||
angle = glm::sin(walk_time * speed) * glm::radians(amp);
|
||||
} else if (gait == Gait::STOP) {
|
||||
float t = glm::clamp(delta_time * 10.0f, 0.0f, 1.0f);
|
||||
angle = glm::mix(angle, 0.0f, t);
|
||||
}
|
||||
|
||||
m_render_player_data.emplace_back(
|
||||
m_player.get_name(), m_player.get_uuid(),
|
||||
m_player.get_player_pos(), m_player.yaw(), m_player.pitch(),
|
||||
m_player.get_gait(), m_player.angle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 ClientWorld::sunlight_dir() const {
|
||||
float altitude = sin((m_day_tick - 6 * PER_HOUR) /
|
||||
static_cast<float>(DAY_TIME / 2) * std::numbers::pi) *
|
||||
90.0f;
|
||||
|
||||
float t = static_cast<float>(m_day_tick) / DAY_TIME;
|
||||
float azimuth = 90.0f - 360.0f * (t - 0.25f);
|
||||
|
||||
float alt = glm::radians(altitude);
|
||||
float az = glm::radians(azimuth);
|
||||
glm::vec3 dir;
|
||||
dir.x = cos(alt) * sin(az);
|
||||
dir.y = sin(alt);
|
||||
dir.z = cos(alt) * cos(az);
|
||||
|
||||
return glm::normalize(-dir);
|
||||
}
|
||||
|
||||
bool ClientWorld::sphere_collide_world(glm::vec3 center, float radius) const {
|
||||
glm::ivec3 min = glm::floor(center - glm::vec3(radius));
|
||||
glm::ivec3 max = glm::floor(center + glm::vec3(radius));
|
||||
|
||||
for (int x = min.x; x <= max.x; ++x) {
|
||||
for (int y = min.y; y <= max.y; ++y) {
|
||||
for (int z = min.z; z <= max.z; ++z) {
|
||||
if (!is_solid({x, y, z}))
|
||||
continue;
|
||||
|
||||
glm::vec3 closest;
|
||||
closest.x = glm::clamp(center.x, float(x), float(x + 1));
|
||||
closest.y = glm::clamp(center.y, float(y), float(y + 1));
|
||||
closest.z = glm::clamp(center.z, float(z), float(z + 1));
|
||||
|
||||
glm::vec3 d = center - closest;
|
||||
|
||||
if (glm::dot(d, d) < radius * radius)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int ClientWorld::rendering_distance() const {
|
||||
return m_rendering_distance.load();
|
||||
}
|
||||
|
||||
void ClientWorld::rendering_distance(int rendering_distance) {
|
||||
m_rendering_distance = rendering_distance;
|
||||
Logger::info("Set Rendering dist {} , the value is {}", rendering_distance,
|
||||
m_rendering_distance.load());
|
||||
request_chunk();
|
||||
}
|
||||
|
||||
int ClientWorld::get_chunk_task_id() const { return m_chunk_task_id.load(); }
|
||||
|
||||
const std::vector<const ChunkRenderSnapshot*>&
|
||||
ClientWorld::render_snapshots() const {
|
||||
return m_render_snapshots;
|
||||
};
|
||||
const std::vector<PlayerRenderData>& ClientWorld::render_player_data() const {
|
||||
return m_render_player_data;
|
||||
}
|
||||
std::vector<PlayerRenderData>& ClientWorld::render_player_data() {
|
||||
return m_render_player_data;
|
||||
}
|
||||
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
|
||||
} // namespace Cubed
|
||||
205
src/gameplay/network_client.cpp
Normal file
205
src/gameplay/network_client.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "Cubed/gameplay/network_client.hpp"
|
||||
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace google::protobuf;
|
||||
namespace Cubed {
|
||||
NetworkClient::NetworkClient(ClientWorld& world)
|
||||
: m_socket(m_io), m_strand(asio::make_strand(m_io)), m_world(world) {}
|
||||
|
||||
NetworkClient::~NetworkClient() { close(); }
|
||||
|
||||
void NetworkClient::start(std::string ip, int port) {
|
||||
if (m_net_thread.joinable()) {
|
||||
return;
|
||||
}
|
||||
m_net_thread = std::thread([self = shared_from_this(), ip, port]() {
|
||||
asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached);
|
||||
self->m_io.run();
|
||||
});
|
||||
Logger::info("NetworkClient Started");
|
||||
}
|
||||
|
||||
bool NetworkClient::is_connected() const { return m_connected.load(); }
|
||||
bool NetworkClient::is_connect_error() const { return m_connect_error.load(); }
|
||||
asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
|
||||
Logger::info("Connect Begin");
|
||||
try {
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
tcp::resolver resolver(ex);
|
||||
auto eps = co_await resolver.async_resolve(ip, std::to_string(port),
|
||||
asio::use_awaitable);
|
||||
Logger::info("Resolve Success");
|
||||
co_await async_connect(m_socket, eps, asio::use_awaitable);
|
||||
Logger::info("Connect Success, Server ip {} port {}", ip, port);
|
||||
asio::co_spawn(m_strand, read_loop(), asio::detached);
|
||||
Logger::info("NetworkClient Read Loop Started");
|
||||
m_connected = true;
|
||||
co_return;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Client Error {}", e.what());
|
||||
m_connect_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> NetworkClient::read_loop() {
|
||||
try {
|
||||
while (true) {
|
||||
std::array<uint8_t, HEADER_LEN> header_buffer;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
|
||||
asio::use_awaitable);
|
||||
auto header = decode_packet_header(header_buffer);
|
||||
uint32_t total_len = HEADER_LEN + header.compressed_size;
|
||||
|
||||
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
|
||||
|
||||
throw std::runtime_error("invalid packet length");
|
||||
}
|
||||
// maybe move, don't use it after switch!
|
||||
std::vector<uint8_t> body_data(header.compressed_size);
|
||||
if (header.compressed_size > 0) {
|
||||
co_await asio::async_read(m_socket, asio::buffer(body_data),
|
||||
asio::use_awaitable);
|
||||
}
|
||||
|
||||
using std::to_underlying;
|
||||
Arena arena;
|
||||
switch (header.cmd) {
|
||||
case std::to_underlying(PacketEnum::LOGIN_RSP): {
|
||||
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
||||
Logger::info("Client: Receive Login rsp");
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
if (rsp->success()) {
|
||||
m_world.start_client_thread(rsp->uuid());
|
||||
} else {
|
||||
Logger::error("Connected Server Fail");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::CHUNK_DATA_RSP): {
|
||||
// Logger::info("Client: Receive Chunk Data rsp, size {}mb",
|
||||
// body_data.size() / 1024.0f / 1024);
|
||||
m_world.receive_chunk(std::move(body_data), header);
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): {
|
||||
auto* rsp = Arena::Create<BlockChangeRsp>(&arena);
|
||||
Logger::info("Client: Receive Block Change rsp");
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
m_world.receive_block_change(*rsp);
|
||||
}
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::UPDATE_TIME): {
|
||||
auto* rsp = Arena::Create<UpdateTime>(&arena);
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
m_world.receive_time(*rsp);
|
||||
}
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::PLAYER_INFO_RSP): {
|
||||
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
m_world.receive_remote_player(*rsp);
|
||||
}
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::LOGOUT_RSP): {
|
||||
auto* rsp = Arena::Create<LogoutRsp>(&arena);
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
m_world.receive_player_logout(*rsp);
|
||||
}
|
||||
} break;
|
||||
case std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS): {
|
||||
auto* rsp = Arena::Create<S2C_ClearAllChunks>(&arena);
|
||||
if (decode_packet(*rsp, body_data, header)) {
|
||||
if (rsp->clear()) {
|
||||
Logger::info("Client Clear All Chunk");
|
||||
m_world.rebuild_world();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} catch (const asio::system_error& e) {
|
||||
auto ec = e.code();
|
||||
|
||||
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
|
||||
|
||||
Logger::info("Client disconnected");
|
||||
} else {
|
||||
Logger::warn("Asio Error {}", e.what());
|
||||
}
|
||||
|
||||
close();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Session Error {}", e.what());
|
||||
close();
|
||||
} catch (...) {
|
||||
Logger::error("Unknow Error");
|
||||
close();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
void NetworkClient::send(Packet packet, int priority) {
|
||||
if (m_closed.load()) {
|
||||
return;
|
||||
}
|
||||
asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet),
|
||||
priority]() mutable {
|
||||
bool idle = self->m_write_queue.empty();
|
||||
self->m_write_queue.emplace(priority, self->m_sequence++,
|
||||
std::move(packet));
|
||||
if (idle) {
|
||||
self->do_write();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkClient::do_write() {
|
||||
if (m_closed.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto self = shared_from_this();
|
||||
auto packet = std::move(m_write_queue.top().packet);
|
||||
asio::async_write(
|
||||
m_socket, asio::buffer(*packet),
|
||||
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
|
||||
if (ec) {
|
||||
Logger::warn("Write Ec {}", ec.message());
|
||||
self->close();
|
||||
return;
|
||||
}
|
||||
self->m_write_queue.pop();
|
||||
if (!self->m_write_queue.empty()) {
|
||||
self->do_write();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void NetworkClient::close() {
|
||||
if (m_closed.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
m_socket.shutdown(tcp::socket::shutdown_both, ec);
|
||||
|
||||
m_socket.close(ec);
|
||||
Logger::info("NetworkClient Closed");
|
||||
m_connected = false;
|
||||
m_io.stop();
|
||||
}
|
||||
|
||||
void NetworkClient::stop() {
|
||||
close();
|
||||
|
||||
if (m_net_thread.joinable()) {
|
||||
m_net_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
94
src/gameplay/network_server.cpp
Normal file
94
src/gameplay/network_server.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "Cubed/gameplay/network_server.hpp"
|
||||
|
||||
#include "Cubed/tools/log.hpp"
|
||||
using asio::ip::tcp;
|
||||
namespace Cubed {
|
||||
|
||||
NetworkServer::NetworkServer(int port) : m_port(port) {}
|
||||
|
||||
NetworkServer::~NetworkServer() { stop(); }
|
||||
|
||||
void NetworkServer::stop() {
|
||||
if (!m_started) {
|
||||
return;
|
||||
}
|
||||
if (m_stopped.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_io.stop();
|
||||
|
||||
std::vector<std::shared_ptr<Session>> sessions;
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_session_mutex);
|
||||
|
||||
for (auto& [id, s] : m_session) {
|
||||
sessions.push_back(s);
|
||||
}
|
||||
|
||||
m_session.clear();
|
||||
}
|
||||
|
||||
for (auto& s : sessions) {
|
||||
s->close();
|
||||
}
|
||||
|
||||
if (m_net_thread.joinable()) {
|
||||
Logger::info("Server join thread={}, current={}", m_net_thread.get_id(),
|
||||
std::this_thread::get_id());
|
||||
m_net_thread.join();
|
||||
}
|
||||
Logger::info("Server Net Thread Stopped!");
|
||||
}
|
||||
|
||||
asio::awaitable<void> NetworkServer::listen() {
|
||||
|
||||
try {
|
||||
tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port));
|
||||
while (!m_stopped) {
|
||||
tcp::socket socket =
|
||||
co_await acceptor.async_accept(asio::use_awaitable);
|
||||
|
||||
std::shared_ptr<Session> s =
|
||||
std::make_shared<Session>(std::move(socket), m_world, m_io);
|
||||
{
|
||||
std::lock_guard lock(m_session_mutex);
|
||||
m_session.emplace(s->uuid(), s);
|
||||
}
|
||||
s->start();
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
if (!m_stopped) {
|
||||
Logger::error("accept error {}", e.what());
|
||||
}
|
||||
} catch (...) {
|
||||
if (!m_stopped) {
|
||||
Logger::error("Network Server: Unknown Error");
|
||||
}
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
void NetworkServer::net_run() {
|
||||
if (m_net_thread.joinable()) {
|
||||
return;
|
||||
}
|
||||
m_net_thread = std::thread([this]() {
|
||||
asio::co_spawn(m_io, listen(), asio::detached);
|
||||
m_io.run();
|
||||
});
|
||||
Logger::info("Server Started!");
|
||||
}
|
||||
|
||||
void NetworkServer::start_server(int port) {
|
||||
m_port = port;
|
||||
m_world.init_world();
|
||||
net_run();
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
int NetworkServer::port() const { return m_port; }
|
||||
ServerWorld& NetworkServer::server_world() { return m_world; }
|
||||
} // namespace Cubed
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/gameplay/river.path.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
@@ -24,16 +23,11 @@ RiverPath::RiverPath(unsigned int chunk_seed, unsigned world_seed,
|
||||
m_points.reserve(m_step + 1);
|
||||
m_points.push_back(m_start_path_point);
|
||||
collect_path_points();
|
||||
precompute_chunk_coverage();
|
||||
}
|
||||
|
||||
void RiverPath::collect_path_points() {
|
||||
for (int i = 0; i < m_step; i++) {
|
||||
|
||||
m_yaw = std::fmod(m_yaw, 360.0f);
|
||||
if (m_yaw < 0.0f)
|
||||
m_yaw += 360.0f;
|
||||
|
||||
float dx = std::cos(glm::radians(m_pitch)) *
|
||||
std::sin(glm::radians(m_yaw)) * m_step_len;
|
||||
float dy = std::sin(glm::radians(m_pitch)) * m_step_len;
|
||||
@@ -60,33 +54,7 @@ void RiverPath::collect_path_points() {
|
||||
}
|
||||
}
|
||||
|
||||
void RiverPath::precompute_chunk_coverage() {
|
||||
for (const auto& point : m_points) {
|
||||
float rad = point.rad_xz;
|
||||
const glm::vec3& center = point.pos;
|
||||
|
||||
int min_cx =
|
||||
static_cast<int>(std::floor((center.x - rad) / CHUNK_SIZE));
|
||||
int max_cx =
|
||||
static_cast<int>(std::floor((center.x + rad) / CHUNK_SIZE));
|
||||
int min_cz =
|
||||
static_cast<int>(std::floor((center.z - rad) / CHUNK_SIZE));
|
||||
int max_cz =
|
||||
static_cast<int>(std::floor((center.z + rad) / CHUNK_SIZE));
|
||||
|
||||
for (int cx = min_cx; cx <= max_cx; ++cx)
|
||||
for (int cz = min_cz; cz <= max_cz; ++cz)
|
||||
m_pending_chunks.insert(
|
||||
std::make_pair(ChunkPos{cx, cz}, false));
|
||||
}
|
||||
}
|
||||
|
||||
void RiverPath::clear_chunk(const ChunkPos& pos) {
|
||||
m_pending_chunks.erase(pos);
|
||||
}
|
||||
const std::vector<PathPoint>& RiverPath::points() const { return m_points; }
|
||||
bool RiverPath::is_finished() const { return m_pending_chunks.empty(); }
|
||||
|
||||
float& RiverPath::radius_xz_min() { return m_radius_xz_min; }
|
||||
float& RiverPath::radius_xz_max() { return m_radius_xz_max; }
|
||||
float& RiverPath::radius_y_min() { return m_radius_y_min; }
|
||||
@@ -95,4 +63,5 @@ float& RiverPath::delta_angle_min() { return m_delta_angle_min; }
|
||||
float& RiverPath::delta_angle_max() { return m_delta_angle_max; }
|
||||
int& RiverPath::step_min() { return m_step_min; }
|
||||
int& RiverPath::step_max() { return m_step_max; }
|
||||
float RiverPath::step_len() { return m_step_len; }
|
||||
} // namespace Cubed
|
||||
@@ -1,61 +1,55 @@
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
|
||||
#include "Cubed/gameplay/river.path.hpp"
|
||||
#include "Cubed/tools/cubed_hash.hpp"
|
||||
namespace Cubed {
|
||||
RiverWorm::RiverWorm() {}
|
||||
RiverWorm::~RiverWorm() {}
|
||||
|
||||
RiverWorm::RiverHashMap& RiverWorm::paths() { return m_paths; }
|
||||
|
||||
void RiverWorm::init(unsigned world_seed) {
|
||||
m_seed = world_seed;
|
||||
|
||||
m_random.init(m_seed);
|
||||
}
|
||||
void RiverWorm::init(unsigned world_seed) { m_world_seed = world_seed; }
|
||||
|
||||
void RiverWorm::reload(unsigned world_seed) {
|
||||
m_seed = world_seed;
|
||||
m_paths.clear();
|
||||
|
||||
m_world_seed = world_seed;
|
||||
|
||||
init(world_seed);
|
||||
}
|
||||
|
||||
void RiverWorm::add_path(const glm::vec3& pos, unsigned chunk_seed) {
|
||||
m_paths.emplace(chunk_seed, RiverPath{chunk_seed, m_seed, pos});
|
||||
bool RiverWorm::has_origin_fast(const ChunkPos& pos) const {
|
||||
unsigned h = HASH::combine_32(HASH::combine_32(pos.x, pos.z), m_world_seed);
|
||||
|
||||
return (h & 0xFFFF) < static_cast<unsigned>(m_probability * 0xFFFF);
|
||||
}
|
||||
|
||||
void RiverWorm::try_to_add_path(const ChunkPos& chunk_pos,
|
||||
unsigned chunk_seed) {
|
||||
{
|
||||
RiverHashMap::const_accessor acc;
|
||||
if (m_paths.find(acc, chunk_seed)) {
|
||||
return;
|
||||
}
|
||||
PathOrigin RiverWorm::get_origin(const ChunkPos& origin_chunk) const {
|
||||
// Quickly check if there is an origin point without constructing Random
|
||||
if (!has_origin_fast(origin_chunk)) {
|
||||
return {false, {}, 0};
|
||||
}
|
||||
|
||||
unsigned chunk_seed =
|
||||
HASH::chunk_seed_hash(origin_chunk.x, origin_chunk.z, m_world_seed);
|
||||
Random random{chunk_seed};
|
||||
if (random.random_bool(static_cast<double>(m_probability))) {
|
||||
const int CHUNK_MIN_X = chunk_pos.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = chunk_pos.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
|
||||
int y = SEA_LEVEL + 2;
|
||||
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
|
||||
add_path(glm::vec3{x, y, z}, chunk_seed);
|
||||
}
|
||||
|
||||
const int CHUNK_MIN_X = origin_chunk.x * CHUNK_SIZE;
|
||||
const int CHUNK_MIN_Z = origin_chunk.z * CHUNK_SIZE;
|
||||
const int CHUNK_MAX_X = CHUNK_MIN_X + SIZE_X - 1;
|
||||
const int CHUNK_MAX_Z = CHUNK_MIN_Z + SIZE_Z - 1;
|
||||
int x = random.random_int(CHUNK_MIN_X, CHUNK_MAX_X);
|
||||
int y = SEA_LEVEL + 2;
|
||||
int z = random.random_int(CHUNK_MIN_Z, CHUNK_MAX_Z);
|
||||
return {true, {x, y, z}, chunk_seed};
|
||||
}
|
||||
|
||||
void RiverWorm::cleanup_finished_rivers() {
|
||||
std::vector<unsigned> finished_keys;
|
||||
for (const auto& pair : m_paths) {
|
||||
if (pair.second.is_finished()) {
|
||||
finished_keys.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
for (const auto& key : finished_keys) {
|
||||
m_paths.erase(key);
|
||||
}
|
||||
int RiverWorm::search_radius() const {
|
||||
float max_displacement =
|
||||
3.0f * std::sqrt(static_cast<float>(RiverPath::step_max())) *
|
||||
RiverPath::step_len();
|
||||
return static_cast<int>(std::ceil(
|
||||
(max_displacement + RiverPath::radius_xz_max()) / CHUNK_SIZE));
|
||||
}
|
||||
unsigned RiverWorm::world_seed() const { return m_world_seed; }
|
||||
float RiverWorm::river_probability() const { return m_probability; }
|
||||
|
||||
int RiverWorm::river_sum() const { return m_paths.size(); }
|
||||
float& RiverWorm::river_probability() { return m_probability; }
|
||||
} // namespace Cubed
|
||||
211
src/gameplay/server_chunk.cpp
Normal file
211
src/gameplay/server_chunk.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
ServerChunk::ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
|
||||
bool temp_chunk)
|
||||
: m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {}
|
||||
|
||||
ServerChunk::ServerChunk(ServerChunk&& other) noexcept
|
||||
: m_gening(other.m_gening.load()), m_has_cave(other.m_has_cave),
|
||||
m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)),
|
||||
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
|
||||
m_blocks(std::move(other.m_blocks)),
|
||||
m_neightbor_blocks(std::move(other.m_neightbor_blocks)),
|
||||
m_seed(other.m_seed), m_conditions(other.m_conditions) {
|
||||
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
|
||||
}
|
||||
|
||||
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
|
||||
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
|
||||
// other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z,
|
||||
// static_cast<const void*>(&other));
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
|
||||
m_chunk_pos = std::move(other.m_chunk_pos);
|
||||
m_heightmap = std::move(other.m_heightmap);
|
||||
m_blocks = std::move(other.m_blocks);
|
||||
m_biome = other.m_biome.load();
|
||||
m_seed = other.m_seed;
|
||||
m_conditions = other.m_conditions;
|
||||
m_neightbor_blocks = std::move(other.m_neightbor_blocks);
|
||||
m_has_cave = other.m_has_cave;
|
||||
m_gening = other.m_gening.load();
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::tuple<int, int, int> ServerChunk::world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z) {
|
||||
int x, y, z;
|
||||
y = world_y;
|
||||
x = world_x - chunk_x * CHUNK_SIZE;
|
||||
z = world_z - chunk_z * CHUNK_SIZE;
|
||||
return {x, y, z};
|
||||
}
|
||||
|
||||
std::tuple<int, int, int>
|
||||
ServerChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
|
||||
return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
|
||||
std::tuple<int, int, int>
|
||||
ServerChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) {
|
||||
int world_x = x + chunk_x * CHUNK_SIZE;
|
||||
int world_z = z + chunk_z * CHUNK_SIZE;
|
||||
int world_y = y;
|
||||
return {world_x, world_y, world_z};
|
||||
}
|
||||
std::tuple<int, int, int>
|
||||
ServerChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) {
|
||||
return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x,
|
||||
chunk_pos.z);
|
||||
}
|
||||
|
||||
BiomeType ServerChunk::get_biome() const { return m_biome.load(); }
|
||||
|
||||
ChunkPos ServerChunk::get_chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
const std::vector<BlockType>& ServerChunk::get_chunk_blocks() const {
|
||||
return m_blocks;
|
||||
}
|
||||
|
||||
HeightMapArray ServerChunk::get_heightmap() const {
|
||||
// Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x,
|
||||
// m_chunk_pos.z, static_cast<const void*>(this));
|
||||
return m_heightmap;
|
||||
}
|
||||
|
||||
int ServerChunk::index(int x, int y, int z) {
|
||||
ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE));
|
||||
if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 ||
|
||||
(x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >=
|
||||
CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) {
|
||||
Logger::error("block pos x {} y {} z {} range error", x, y, z);
|
||||
ASSERT(0);
|
||||
}
|
||||
return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z;
|
||||
}
|
||||
|
||||
int ServerChunk::index(const glm::vec3& pos) {
|
||||
return ServerChunk::index(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
void ServerChunk::gen_phase_one() {
|
||||
m_generator = std::make_unique<ChunkGenerator>(*this);
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->assign_chunk_biome();
|
||||
m_seed = m_generator->chunk_seed();
|
||||
}
|
||||
void ServerChunk::gen_phase_two() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->generate_heightmap();
|
||||
}
|
||||
void ServerChunk::gen_phase_three() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->generate_terrain_blocks();
|
||||
}
|
||||
|
||||
void ServerChunk::gen_phase_four(
|
||||
const std::array<std::optional<std::vector<BlockType>>, 4>&
|
||||
neighbor_block) {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
// This must be fully completed before any other operations can proceed!
|
||||
m_generator->blend_surface_blocks_borders(neighbor_block);
|
||||
}
|
||||
|
||||
void ServerChunk::gen_phase_five() {
|
||||
if (!m_generator) {
|
||||
Logger::error("ChunkGenerator is Nullptr");
|
||||
return;
|
||||
}
|
||||
m_generator->ocean_build();
|
||||
m_generator->generate_river();
|
||||
m_generator->generate_cave();
|
||||
|
||||
m_generator->generate_vegetation();
|
||||
m_generator = nullptr;
|
||||
}
|
||||
|
||||
void ServerChunk::gen_chunk() {
|
||||
if (m_gening.exchange(true))
|
||||
return;
|
||||
m_gening = true;
|
||||
ASSERT_MSG(m_blocks.empty(),
|
||||
"Blocks isn't Empty, chunk already generated!");
|
||||
if (m_blocks.size() != 0) {
|
||||
Logger::warn(
|
||||
"Request Generator Chunk {} {} ,but the Blocks size is Not 0",
|
||||
m_chunk_pos.x, m_chunk_pos.z);
|
||||
return;
|
||||
}
|
||||
std::vector<ServerChunk> neighbor;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
neighbor.emplace_back(m_world, m_chunk_pos + CHUNK_DIR[i], true);
|
||||
}
|
||||
for (auto& chunk : neighbor) {
|
||||
chunk.gen_phase_one();
|
||||
chunk.gen_phase_two();
|
||||
chunk.gen_phase_three();
|
||||
chunk.gen_phase_five();
|
||||
}
|
||||
gen_phase_one();
|
||||
gen_phase_two();
|
||||
gen_phase_three();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
m_neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
|
||||
}
|
||||
gen_phase_four(m_neightbor_blocks);
|
||||
gen_phase_five();
|
||||
m_gening = false;
|
||||
}
|
||||
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
|
||||
|
||||
bool ServerChunk::is_temp_chunk() const { return m_temp_chunk.load(); }
|
||||
|
||||
bool& ServerChunk::has_cave() { return m_has_cave; }
|
||||
|
||||
const OptionalBlockVectorArray& ServerChunk::get_neightbor_blocks() const {
|
||||
return m_neightbor_blocks;
|
||||
}
|
||||
|
||||
void ServerChunk::set_chunk_block(int index, unsigned id) {
|
||||
m_blocks[index] = id;
|
||||
}
|
||||
ChunkPos ServerChunk::chunk_pos() const { return m_chunk_pos; }
|
||||
|
||||
BiomeType ServerChunk::biome() const { return m_biome; }
|
||||
|
||||
void ServerChunk::biome(BiomeType b) { m_biome = b; }
|
||||
|
||||
HeightMapArray& ServerChunk::heightmap() { return m_heightmap; }
|
||||
std::vector<BlockType>& ServerChunk::blocks() { return m_blocks; }
|
||||
ServerWorld& ServerChunk::world() { return m_world; }
|
||||
unsigned ServerChunk::seed() const {
|
||||
if (m_seed == 0) {
|
||||
Logger::warn("Seed Not Generator");
|
||||
}
|
||||
return m_seed;
|
||||
}
|
||||
|
||||
BiomeConditions& ServerChunk::conditions() { return m_conditions; }
|
||||
|
||||
} // namespace Cubed
|
||||
62
src/gameplay/server_player.cpp
Normal file
62
src/gameplay/server_player.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
namespace Cubed {
|
||||
ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid,
|
||||
ServerWorld& world, std::shared_ptr<Session> session,
|
||||
TickType gametick)
|
||||
: m_name(name), m_uuid(uuid), m_world(world), m_session(session),
|
||||
m_last_gametick(gametick) {}
|
||||
const glm::vec3& ServerPlayer::get_pos() const { return m_pos; }
|
||||
const std::string& ServerPlayer::get_name() const { return m_name; }
|
||||
const std::string& ServerPlayer::get_uuid() const { return m_uuid; }
|
||||
std::shared_ptr<Session> ServerPlayer::get_session() const { return m_session; }
|
||||
void ServerPlayer::update_pos(float x, float y, float z) {
|
||||
m_pos = glm::vec3{x, y, z};
|
||||
ChunkPos chunk_pos = get_chunk_pos(x, z);
|
||||
float dist = distance2(chunk_pos, m_last_chunk_pos);
|
||||
if (dist > 2) {
|
||||
m_world.need_gen(m_uuid);
|
||||
m_last_chunk_pos = chunk_pos;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerPlayer::update_sync_gametick(TickType gametick) {
|
||||
m_last_gametick = gametick;
|
||||
}
|
||||
bool ServerPlayer::is_disconnect(TickType current_gametick) const {
|
||||
if (current_gametick - m_last_gametick > TIMEOUT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int ServerPlayer::task_id() const { return m_chunk_task_id.load(); }
|
||||
void ServerPlayer::task_id(int id) { m_chunk_task_id = id; }
|
||||
|
||||
bool ServerPlayer::has_player(ChunkPos pos) const {
|
||||
std::shared_lock lock(m_chunk_pos_mutex);
|
||||
return m_player_chunk_pos_set.find(pos) != m_player_chunk_pos_set.end();
|
||||
}
|
||||
void ServerPlayer::update_chunk_set(const ChunkPosSet& set) {
|
||||
std::lock_guard lock(m_chunk_pos_mutex);
|
||||
m_player_chunk_pos_set.clear();
|
||||
m_player_chunk_pos_set.insert(set.begin(), set.end());
|
||||
}
|
||||
|
||||
const ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() const {
|
||||
std::shared_lock lock(m_chunk_pos_mutex);
|
||||
return m_player_chunk_pos_set;
|
||||
}
|
||||
|
||||
ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() {
|
||||
std::lock_guard lock(m_chunk_pos_mutex);
|
||||
return m_player_chunk_pos_set;
|
||||
}
|
||||
void ServerPlayer::set_yaw(float yaw) { m_yaw = yaw; }
|
||||
void ServerPlayer::set_pitch(float pitch) { m_pitch = pitch; }
|
||||
float ServerPlayer::yaw() const { return m_yaw.load(); }
|
||||
float ServerPlayer::pitch() const { return m_pitch.load(); }
|
||||
Gait ServerPlayer::gait() const { return m_gait; }
|
||||
void ServerPlayer::set_gait(Gait gait) { m_gait = gait; }
|
||||
} // namespace Cubed
|
||||
844
src/gameplay/server_world.cpp
Normal file
844
src/gameplay/server_world.cpp
Normal file
@@ -0,0 +1,844 @@
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/uuid.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
using namespace std::chrono;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace google::protobuf;
|
||||
|
||||
namespace Cubed {
|
||||
ServerWorld::ServerWorld() {}
|
||||
|
||||
ServerWorld::~ServerWorld() { stop(); }
|
||||
|
||||
void ServerWorld::stop() {
|
||||
if (!m_init) {
|
||||
return;
|
||||
}
|
||||
if (m_stopped.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
send_server_stop();
|
||||
stop_gen_thread();
|
||||
stop_server_thread();
|
||||
// wait_all_chunk_tasks();
|
||||
stop_thread_pool();
|
||||
|
||||
m_finished_queue.clear();
|
||||
m_chunks.clear();
|
||||
}
|
||||
|
||||
void ServerWorld::update_ref_count(const ChunkPosSet& old,
|
||||
const ChunkPosSet& now) {
|
||||
|
||||
// Elements in the old set that are not contained in now are not needed by
|
||||
// the current player.
|
||||
|
||||
for (auto& pos : old) {
|
||||
if (!now.contains(pos)) {
|
||||
|
||||
chunk_acc acc;
|
||||
if (!m_chunks.find(acc, pos)) {
|
||||
Logger::warn("Update Ref Count Error, can't Find old pos "
|
||||
"in m_chunks");
|
||||
continue;
|
||||
}
|
||||
if (acc->second.ref_count == 0) {
|
||||
Logger::error("Chunk {} {} error, ref count is 0", pos.x,
|
||||
pos.z);
|
||||
m_chunks.erase(acc);
|
||||
continue;
|
||||
}
|
||||
if (--acc->second.ref_count == 0) {
|
||||
m_chunks.erase(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pos : now) {
|
||||
|
||||
chunk_acc acc;
|
||||
if (!m_chunks.find(acc, pos)) {
|
||||
Logger::warn(
|
||||
"Update Ref Count Error, can't Find now pos in m_chunks");
|
||||
continue;
|
||||
}
|
||||
if (!old.contains(pos)) {
|
||||
++acc->second.ref_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::send_time() {
|
||||
Arena arena;
|
||||
auto* rsp = Arena::Create<UpdateTime>(&arena);
|
||||
|
||||
rsp->set_day_tick(m_day_tick);
|
||||
rsp->set_game_tick(m_game_ticks);
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [uuid, player] : m_players) {
|
||||
player.get_session()->send(make_packet(*rsp), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::send_chunk(int task_id, const std::string& uuid,
|
||||
ChunkPos pos) {
|
||||
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
return;
|
||||
}
|
||||
if (task_id < it->second.task_id()) {
|
||||
// Old chunk requests are simply discarded
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Arena arean;
|
||||
ChunkDataRsp* rsp = Arena::Create<ChunkDataRsp>(&arean);
|
||||
auto* rsq_pos = rsp->mutable_pos();
|
||||
rsq_pos->set_x(pos.x);
|
||||
rsq_pos->set_z(pos.z);
|
||||
{
|
||||
chunk_caac cacc;
|
||||
if (!m_chunks.find(cacc, pos)) {
|
||||
// No chunk found and not generating
|
||||
Logger::error("Chunk {} {} neither pending nor ready", pos.x,
|
||||
pos.z);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cacc->second.state == ChunkState::GENERATING) {
|
||||
|
||||
m_waiting_chunk_requests.emplace(uuid, task_id, pos);
|
||||
return;
|
||||
}
|
||||
if (cacc->second.state != ChunkState::READY) {
|
||||
Logger::error("Chunk {} {} is invaild", pos.x, pos.z);
|
||||
return;
|
||||
}
|
||||
|
||||
rsp->set_chunk_seed(cacc->second.chunk->seed());
|
||||
rsp->set_biome_type(std::to_underlying(cacc->second.chunk->biome()));
|
||||
auto* blocks = rsp->mutable_chunk_blocks();
|
||||
auto& chunk_blocks = cacc->second.chunk->get_chunk_blocks();
|
||||
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
|
||||
auto& neighbor_blocks = cacc->second.chunk->get_neightbor_blocks();
|
||||
|
||||
auto assign = [](auto* nb,
|
||||
const std::optional<std::vector<BlockType>>& blocks) {
|
||||
if (!blocks) {
|
||||
return;
|
||||
}
|
||||
if (!nb) {
|
||||
return;
|
||||
}
|
||||
nb->Assign(blocks->begin(), blocks->end());
|
||||
};
|
||||
auto* nb1 = rsp->mutable_neighbor_blocks_1();
|
||||
auto* nb2 = rsp->mutable_neighbor_blocks_2();
|
||||
auto* nb3 = rsp->mutable_neighbor_blocks_3();
|
||||
auto* nb4 = rsp->mutable_neighbor_blocks_4();
|
||||
assign(nb1, neighbor_blocks[0]);
|
||||
assign(nb2, neighbor_blocks[1]);
|
||||
assign(nb3, neighbor_blocks[2]);
|
||||
assign(nb4, neighbor_blocks[3]);
|
||||
}
|
||||
std::shared_ptr<Session> s;
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it != m_players.end()) {
|
||||
s = it->second.get_session();
|
||||
it->second.update_sync_gametick(m_game_ticks);
|
||||
}
|
||||
}
|
||||
if (!s) {
|
||||
Logger::error("Player {} session not exist", uuid);
|
||||
return;
|
||||
}
|
||||
rsp->set_task_id(task_id);
|
||||
s->send(make_packet(*rsp));
|
||||
}
|
||||
|
||||
void ServerWorld::init_world() {
|
||||
|
||||
register_timer("player disconnect", 5, [this]() {
|
||||
std::vector<std::string> disconnect;
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [uuid, player] : m_players) {
|
||||
if (player.is_disconnect(m_game_ticks)) {
|
||||
disconnect.emplace_back(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& uuid : disconnect) {
|
||||
handle_player_exit(uuid);
|
||||
}
|
||||
});
|
||||
// Periodically process pending players
|
||||
register_timer("player chunk send", 1, [this]() {
|
||||
PendingRequest request;
|
||||
if (m_waiting_chunk_requests.try_pop(request)) {
|
||||
handle_chunk_req(request.task_id, request.uuid, request.pos);
|
||||
}
|
||||
});
|
||||
|
||||
m_cave_carcer.init(ChunkGenerator::seed());
|
||||
m_river_worm.init(ChunkGenerator::seed());
|
||||
// m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
|
||||
start_thread_pool();
|
||||
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
|
||||
start_gen_thread();
|
||||
init_chunks();
|
||||
auto t2 = std::chrono::system_clock::now();
|
||||
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
|
||||
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
|
||||
|
||||
start_server_thread();
|
||||
m_init = true;
|
||||
}
|
||||
|
||||
void ServerWorld::init_chunks() { hot_reload(); }
|
||||
|
||||
void ServerWorld::gen_chunks_internal(const std::string& uuid) {
|
||||
// Logger::info("gen_chunks_internal");
|
||||
m_chunk_gen_finished = false;
|
||||
|
||||
ChunkPosSet required_chunks_set;
|
||||
compute_required_chunks(required_chunks_set, uuid);
|
||||
std::vector<ChunkPos> need_gen_chunks_pos;
|
||||
|
||||
ChunkPosSet old_set;
|
||||
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks_set);
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
return;
|
||||
}
|
||||
old_set = std::move(it->second.get_chunk_pos_set());
|
||||
it->second.update_chunk_set(required_chunks_set);
|
||||
}
|
||||
|
||||
update_ref_count(old_set, required_chunks_set);
|
||||
ASSERT_MSG(!required_chunks_set.empty(), "required chunks is empty!!");
|
||||
|
||||
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
|
||||
|
||||
if (need_gen_chunks_pos.empty()) {
|
||||
m_could_gen = true;
|
||||
|
||||
return;
|
||||
}
|
||||
NewChunkVector new_chunks;
|
||||
|
||||
// Create new chunk
|
||||
|
||||
for (auto& pos : need_gen_chunks_pos) {
|
||||
new_chunks.emplace_back(
|
||||
pos, std::make_unique<ServerChunk>(ServerChunk(*this, pos)));
|
||||
}
|
||||
|
||||
submit_new_chunks(uuid, new_chunks);
|
||||
m_chunk_gen_finished = true;
|
||||
}
|
||||
|
||||
void ServerWorld::compute_required_chunks(
|
||||
ChunkPosSet& required_chunks, const std::optional<std::string>& uuid) {
|
||||
glm::vec3 player_pos;
|
||||
if (uuid == std::nullopt) {
|
||||
player_pos = glm::vec3{0.0f};
|
||||
} else {
|
||||
player_pos = get_player_pos(uuid.value());
|
||||
}
|
||||
int x = std::floor(player_pos.x);
|
||||
int z = std::floor(player_pos.z);
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(x, z);
|
||||
int radius = m_rendering_distance;
|
||||
int r2 = radius * radius;
|
||||
required_chunks.reserve(radius * radius);
|
||||
|
||||
for (int dx = -radius; dx <= radius; ++dx) {
|
||||
for (int dz = -radius; dz <= radius; ++dz) {
|
||||
if (dx * dx + dz * dz <= r2) {
|
||||
required_chunks.emplace(chunk_x + dx, chunk_z + dz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::sync_and_collect_missing_chunks(
|
||||
std::vector<ChunkPos>& need_gen_chunks_pos,
|
||||
const ChunkPosSet& required_chunks) {
|
||||
|
||||
for (auto pos : required_chunks) {
|
||||
chunk_acc acc;
|
||||
if (m_chunks.insert(acc, pos)) {
|
||||
need_gen_chunks_pos.push_back(pos);
|
||||
acc->second = ChunkEntity{ChunkState::GENERATING, nullptr, 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::submit_new_chunks(const std::string& uuid,
|
||||
NewChunkVector& new_chunks) {
|
||||
using enum ChunkLoadStyle;
|
||||
auto pool_ptr = m_gen_thread_pool.load();
|
||||
if (!pool_ptr) {
|
||||
return;
|
||||
}
|
||||
switch (m_chunk_load_style) {
|
||||
case RANDOM:
|
||||
// Enqueue directly in random order
|
||||
for (auto& task : new_chunks) {
|
||||
|
||||
pool_ptr->enqueue([&task, this]() {
|
||||
std::unique_ptr<ServerChunk> chunk{std::move(task.chunk)};
|
||||
chunk->gen_chunk();
|
||||
m_finished_queue.push(std::move(chunk));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case CENTER: {
|
||||
std::vector<std::pair<ChunkPos, PendingChunk*>> tasks;
|
||||
for (auto& task : new_chunks) {
|
||||
|
||||
tasks.emplace_back(task.pos, &task);
|
||||
}
|
||||
glm::vec3 player_pos = get_player_pos(uuid);
|
||||
ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z);
|
||||
auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) {
|
||||
float dx = player_chunk_pos.x - chunk_pos.x;
|
||||
float dz = player_chunk_pos.z - chunk_pos.z;
|
||||
return dx * dx + dz * dz;
|
||||
};
|
||||
|
||||
std::sort(tasks.begin(), tasks.end(),
|
||||
[&dist2](const auto& a, const auto& b) {
|
||||
return dist2(a.first) < dist2(b.first);
|
||||
});
|
||||
|
||||
const int CHUNKS_PER_PRIORITY = m_gen_pool_threads;
|
||||
|
||||
for (size_t i = 0; i < tasks.size(); ++i) {
|
||||
int priority = 10 + static_cast<int>(i / CHUNKS_PER_PRIORITY);
|
||||
auto* task = tasks[i].second;
|
||||
pool_ptr->enqueue(priority,
|
||||
[this, chunk = std::move(task->chunk)]() mutable {
|
||||
chunk->gen_chunk();
|
||||
m_finished_queue.push(std::move(chunk));
|
||||
});
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::start_gen_thread() {
|
||||
m_gen_running = true;
|
||||
Logger::info("Gen Thread Started");
|
||||
m_gen_thread = std::jthread([this](std::stop_token token) {
|
||||
while (!token.stop_requested()) {
|
||||
std::unique_lock<std::mutex> lk(m_need_gen_queue_mutex);
|
||||
|
||||
m_gen_cv.wait(lk, token, [this]() {
|
||||
return m_need_gen_chunk.load() || !m_gen_running ||
|
||||
!m_need_gen_queue.empty();
|
||||
});
|
||||
if (!m_gen_running) {
|
||||
break;
|
||||
}
|
||||
if (token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
m_need_gen_chunk = false;
|
||||
std::string uuid;
|
||||
if (!m_need_gen_queue.empty()) {
|
||||
uuid = m_need_gen_queue.front();
|
||||
m_need_gen_queue.pop();
|
||||
}
|
||||
lk.unlock();
|
||||
gen_chunks_internal(uuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ServerWorld::start_server_thread() {
|
||||
m_server_thread =
|
||||
std::jthread([this](std::stop_token token) { serever_run(token); });
|
||||
}
|
||||
|
||||
void ServerWorld::start_thread_pool() {
|
||||
int max_thread = std::thread::hardware_concurrency();
|
||||
if (m_gen_pool_threads == 0) {
|
||||
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool,
|
||||
max_thread - RESERVED_THREADS);
|
||||
} else {
|
||||
m_gen_pool_threads =
|
||||
change_pool_threads(m_gen_thread_pool, m_gen_pool_threads);
|
||||
}
|
||||
|
||||
if (m_net_pool_threads == 0) {
|
||||
m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4);
|
||||
} else {
|
||||
m_net_pool_threads =
|
||||
change_pool_threads(m_net_thread_pool, m_net_pool_threads);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::stop_gen_thread() {
|
||||
m_gen_running = false;
|
||||
m_gen_cv.notify_all();
|
||||
m_gen_thread.request_stop();
|
||||
if (m_gen_thread.joinable()) {
|
||||
m_gen_thread.join();
|
||||
}
|
||||
Logger::info("Gen Thread Stopped");
|
||||
}
|
||||
|
||||
void ServerWorld::stop_server_thread() {
|
||||
m_server_thread.request_stop();
|
||||
if (m_server_thread.joinable()) {
|
||||
m_server_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::stop_thread_pool() {
|
||||
auto pool_ptr = m_gen_thread_pool.load();
|
||||
if (pool_ptr) {
|
||||
pool_ptr->stop();
|
||||
}
|
||||
m_gen_thread_pool.store(nullptr);
|
||||
Logger::info("Gen Thread Pool Stopped");
|
||||
|
||||
auto p = m_net_thread_pool.load();
|
||||
if (p) {
|
||||
p->stop();
|
||||
}
|
||||
m_net_thread_pool.store(nullptr);
|
||||
Logger::info("Net Thread Pool Stopped");
|
||||
}
|
||||
|
||||
void ServerWorld::serever_run(std::stop_token stoken) {
|
||||
Logger::info("Server Thread Started!");
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME);
|
||||
|
||||
auto next = Clock::now();
|
||||
while (!stoken.stop_requested()) {
|
||||
next += TICK;
|
||||
if (m_tick_running) {
|
||||
++m_game_ticks;
|
||||
m_day_tick = (m_day_tick + 1) % DAY_TIME;
|
||||
}
|
||||
update();
|
||||
std::this_thread::sleep_until(next);
|
||||
}
|
||||
Logger::info("Server Thread Stopped!");
|
||||
}
|
||||
|
||||
void ServerWorld::need_gen(std::string uuid) {
|
||||
|
||||
// if (!m_could_gen) {
|
||||
// Logger::warn("It is generating or consuming new chunks");
|
||||
// return;
|
||||
// }
|
||||
|
||||
m_could_gen = false;
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_need_gen_queue_mutex);
|
||||
m_need_gen_queue.enqueue(std::move(uuid));
|
||||
}
|
||||
|
||||
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
|
||||
|
||||
m_need_gen_chunk = true;
|
||||
|
||||
m_gen_cv.notify_one();
|
||||
}
|
||||
|
||||
bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
|
||||
|
||||
int world_x, world_y, world_z;
|
||||
world_x = block_pos.x;
|
||||
world_y = block_pos.y;
|
||||
world_z = block_pos.z;
|
||||
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
|
||||
chunk_acc acc;
|
||||
|
||||
if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) {
|
||||
return false;
|
||||
}
|
||||
if (acc->second.state != ChunkState::READY) {
|
||||
return false;
|
||||
}
|
||||
auto [x, y, z] = ServerChunk::world_to_block(world_x, world_y, world_z,
|
||||
chunk_x, chunk_z);
|
||||
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
|
||||
z >= CHUNK_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
acc->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServerWorld::hot_reload() {
|
||||
auto& config = Config::get();
|
||||
int dist = config.get<int>("world.rendering_distance");
|
||||
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
|
||||
}
|
||||
|
||||
void ServerWorld::update() {
|
||||
// poll_finished_chunks();
|
||||
{
|
||||
bool consumed = false;
|
||||
std::unique_ptr<ServerChunk> chunk;
|
||||
while (m_finished_queue.try_pop(chunk)) {
|
||||
if (!chunk) {
|
||||
Logger::error("Finished Queue has nullptr Chunk");
|
||||
return;
|
||||
}
|
||||
chunk_acc acc;
|
||||
auto pos = chunk->get_chunk_pos();
|
||||
if (!m_chunks.find(acc, pos)) {
|
||||
Logger::error(
|
||||
"New Chunk {} {} not Find, don't move to m_chunks", pos.x,
|
||||
pos.z);
|
||||
continue;
|
||||
}
|
||||
acc->second.chunk = std::move(chunk);
|
||||
acc->second.state = ChunkState::READY;
|
||||
consumed = true;
|
||||
}
|
||||
if (consumed) {
|
||||
m_could_gen = true;
|
||||
}
|
||||
}
|
||||
|
||||
send_time();
|
||||
for (auto& [id, timer] : m_timers) {
|
||||
timer.update();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::sync_player_pos(const C2S_PlayerInfo& prsp) {
|
||||
std::string name;
|
||||
auto x = prsp.pos().x();
|
||||
auto y = prsp.pos().y();
|
||||
auto z = prsp.pos().z();
|
||||
auto uuid = prsp.uuid();
|
||||
auto yaw = prsp.yaw();
|
||||
auto pitch = prsp.pitch();
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
Logger::warn("Player {} is not in this Server", uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
it->second.update_pos(x, y, z);
|
||||
it->second.update_sync_gametick(m_game_ticks);
|
||||
it->second.set_pitch(pitch);
|
||||
it->second.set_yaw(yaw);
|
||||
it->second.set_gait(get_gait_from_id(prsp.gait()));
|
||||
|
||||
name = it->second.get_name();
|
||||
}
|
||||
ChunkPos pos = get_chunk_pos(x, z);
|
||||
// update other player pos;
|
||||
std::vector<std::shared_ptr<Session>> other;
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [o_uuid, player] : m_players) {
|
||||
if (o_uuid == uuid) {
|
||||
continue;
|
||||
}
|
||||
if (player.has_player(pos)) {
|
||||
other.emplace_back(player.get_session());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& session : other) {
|
||||
if (!session) {
|
||||
continue;
|
||||
}
|
||||
Arena arena;
|
||||
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
|
||||
rsp->set_uuid(uuid);
|
||||
rsp->set_name(name);
|
||||
auto* pos = rsp->mutable_pos();
|
||||
pos->set_x(x);
|
||||
pos->set_y(y);
|
||||
pos->set_z(z);
|
||||
rsp->set_yaw(yaw);
|
||||
rsp->set_pitch(pitch);
|
||||
rsp->set_gait(prsp.gait());
|
||||
session->send(make_packet(*rsp), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::handle_player_login(const std::string& name,
|
||||
std::shared_ptr<Session> session) {
|
||||
std::string uuid = generate_uuid();
|
||||
Logger::info("Player {} (uuid {}) join the world", name, uuid);
|
||||
bool sucess = true;
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto [_, inserted] = m_players.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
|
||||
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
|
||||
if (!inserted) {
|
||||
Logger::error("Player insert Fail");
|
||||
}
|
||||
sucess = inserted;
|
||||
}
|
||||
|
||||
Arena arena;
|
||||
if (!sucess) {
|
||||
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
||||
rsp->set_success(false);
|
||||
session->send(make_packet(*rsp), 0);
|
||||
return;
|
||||
}
|
||||
++m_player_sum;
|
||||
m_uuid_to_name.emplace(uuid, name);
|
||||
// Pre-insert into new_chunks to ensure correct addition to waiting_player
|
||||
/*ChunkPosSet required_chunks;
|
||||
compute_required_chunks(required_chunks, uuid);
|
||||
std::vector<ChunkPos> need_gen_chunks_pos;
|
||||
|
||||
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_new_chunk_mutex);
|
||||
for (auto& pos : need_gen_chunks_pos) {
|
||||
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
|
||||
}
|
||||
}
|
||||
*/
|
||||
need_gen(uuid);
|
||||
|
||||
auto* rsp = Arena::Create<LoginRsp>(&arena);
|
||||
rsp->set_success(true);
|
||||
rsp->set_uuid(uuid);
|
||||
session->send(make_packet(*rsp), 0);
|
||||
}
|
||||
|
||||
void ServerWorld::handle_player_exit(const std::string& uuid) {
|
||||
std::shared_ptr<Session> exit_session;
|
||||
ChunkPosSet old_set;
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it != m_players.end()) {
|
||||
Logger::info("Player {} Exit the Server", it->second.get_name());
|
||||
exit_session = it->second.get_session();
|
||||
old_set = std::move(it->second.get_chunk_pos_set());
|
||||
m_players.erase(it);
|
||||
} else {
|
||||
Logger::error("Player {} isn't in Server", uuid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_uuid_to_name.erase(uuid);
|
||||
--m_player_sum;
|
||||
update_ref_count(old_set, {});
|
||||
|
||||
Arena arena;
|
||||
auto* rsp = Arena::Create<LogoutRsp>(&arena);
|
||||
rsp->set_uuid(uuid);
|
||||
rsp->set_server_stop(false);
|
||||
exit_session->send(make_packet(*rsp), 0);
|
||||
|
||||
std::vector<std::shared_ptr<Session>> sessions;
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [uuid, player] : m_players) {
|
||||
sessions.emplace_back(player.get_session());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& s : sessions) {
|
||||
if (s) {
|
||||
s->send(make_packet(*rsp), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const {
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
Logger::error("Can't find player uuid {}", uuid);
|
||||
return glm::vec3{0.0f};
|
||||
}
|
||||
return it->second.get_pos();
|
||||
}
|
||||
|
||||
void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid,
|
||||
ChunkPos pos) {
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
return;
|
||||
}
|
||||
if (it->second.task_id() < task_id) {
|
||||
// task_id is an atomic variable, can be operated on directly
|
||||
it->second.task_id(task_id);
|
||||
}
|
||||
}
|
||||
auto pool = m_net_thread_pool.load();
|
||||
pool->enqueue(
|
||||
[task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); });
|
||||
}
|
||||
|
||||
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
|
||||
float x = std::floor(req.pos().x());
|
||||
float y = std::floor(req.pos().y());
|
||||
float z = std::floor(req.pos().z());
|
||||
if (!set_block(glm::ivec3(x, y, z), req.block())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Arena arena;
|
||||
BlockChangeRsp* rsp = Arena::Create<BlockChangeRsp>(&arena);
|
||||
auto* pos = rsp->mutable_pos();
|
||||
pos->set_x(x);
|
||||
pos->set_y(y);
|
||||
pos->set_z(z);
|
||||
rsp->set_block(req.block());
|
||||
std::vector<std::shared_ptr<Session>> sessions;
|
||||
auto chunk_pos = get_chunk_pos(x, z);
|
||||
{
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [uuid, player] : m_players) {
|
||||
if (player.has_player(chunk_pos)) {
|
||||
auto session = player.get_session();
|
||||
sessions.emplace_back(std::move(session));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& x : sessions) {
|
||||
if (x) {
|
||||
x->send(make_packet(*rsp), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ServerWorld::rendering_distance() const {
|
||||
return m_rendering_distance.load();
|
||||
}
|
||||
|
||||
void ServerWorld::rendering_distance(int rendering_distance) {
|
||||
m_rendering_distance = rendering_distance;
|
||||
}
|
||||
|
||||
CaveCarver& ServerWorld::cave_carcer() { return m_cave_carcer; }
|
||||
RiverWorm& ServerWorld::river_worm() { return m_river_worm; }
|
||||
|
||||
TickType ServerWorld::game_tick() const { return m_game_ticks.load(); }
|
||||
TickType ServerWorld::day_tick() const { return m_day_tick.load(); }
|
||||
void ServerWorld::day_tick(TickType tick) {
|
||||
tick %= DAY_TIME;
|
||||
m_day_tick = tick;
|
||||
}
|
||||
int ServerWorld::per_tick_time() const { return m_per_tick_time.load(); }
|
||||
void ServerWorld::per_tick_time(int ms) { m_per_tick_time = ms; }
|
||||
|
||||
bool ServerWorld::is_tick_running() const { return m_tick_running.load(); }
|
||||
void ServerWorld::tick_running(bool run) { m_tick_running = run; }
|
||||
int ServerWorld::gen_pool_threads() const { return m_gen_pool_threads.load(); }
|
||||
int ServerWorld::max_threads() const { return m_max_threads.load(); }
|
||||
|
||||
void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) {
|
||||
switch (kind) {
|
||||
case ThreadPoolKind::NET:
|
||||
m_net_pool_threads = change_pool_threads(m_net_thread_pool, threads);
|
||||
break;
|
||||
case ThreadPoolKind::GEN:
|
||||
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, threads);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int ServerWorld::change_pool_threads(
|
||||
std::atomic<std::shared_ptr<ThreadPool>>& thread_pool, int threads) {
|
||||
m_max_threads = std::thread::hardware_concurrency();
|
||||
if (m_max_threads < 1) {
|
||||
Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4");
|
||||
m_max_threads = 1;
|
||||
}
|
||||
int used_thread = std::clamp(threads, 1, m_max_threads.load());
|
||||
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
|
||||
thread_pool.store(std::make_shared<ThreadPool>(used_thread));
|
||||
return used_thread;
|
||||
}
|
||||
|
||||
int ServerWorld::change_pool_threads(
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>>& thread_pool,
|
||||
int threads) {
|
||||
m_max_threads = std::thread::hardware_concurrency();
|
||||
if (m_max_threads < 1) {
|
||||
Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4");
|
||||
m_max_threads = 1;
|
||||
}
|
||||
int used_thread = std::clamp(threads, 1, m_max_threads.load());
|
||||
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
|
||||
thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread));
|
||||
return used_thread;
|
||||
}
|
||||
|
||||
void ServerWorld::send_server_stop() {
|
||||
Arena arena;
|
||||
auto* rsp = Arena::Create<LogoutRsp>(&arena);
|
||||
rsp->set_server_stop(true);
|
||||
std::shared_lock lock(m_player_mutex);
|
||||
for (auto& [uuid, player] : m_players) {
|
||||
player.get_session()->send(make_packet(*rsp), 0);
|
||||
}
|
||||
Logger::info("Send Server Mesaage Success");
|
||||
}
|
||||
|
||||
int ServerWorld::chunk_load_style() const {
|
||||
return std::to_underlying(m_chunk_load_style.load());
|
||||
}
|
||||
void ServerWorld::set_chunk_load_style(int id) {
|
||||
using enum ChunkLoadStyle;
|
||||
|
||||
switch (id) {
|
||||
case std::to_underlying(RANDOM):
|
||||
m_chunk_load_style = RANDOM;
|
||||
return;
|
||||
case std::to_underlying(CENTER):
|
||||
m_chunk_load_style = CENTER;
|
||||
return;
|
||||
}
|
||||
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
|
||||
}
|
||||
|
||||
int ServerWorld::chunk_size() const { return m_chunks.size(); }
|
||||
|
||||
} // namespace Cubed
|
||||
148
src/gameplay/session.cpp
Normal file
148
src/gameplay/session.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/uuid.hpp"
|
||||
using asio::ip::tcp;
|
||||
using namespace google::protobuf;
|
||||
namespace Cubed {
|
||||
Session::Session(tcp::socket socket, ServerWorld& server_world,
|
||||
asio::io_context& io)
|
||||
: m_socket(std::move(socket)), m_strand(asio::make_strand(io)),
|
||||
m_uuid(generate_uuid()), m_server_world(server_world) {}
|
||||
|
||||
Session::~Session() {}
|
||||
|
||||
void Session::start() {
|
||||
auto self = shared_from_this();
|
||||
asio::co_spawn(
|
||||
m_strand,
|
||||
[self]() -> asio::awaitable<void> { co_await self->read_loop(); },
|
||||
asio::detached);
|
||||
}
|
||||
|
||||
void Session::send(std::shared_ptr<std::vector<uint8_t>> packet, int priority) {
|
||||
asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet),
|
||||
priority]() mutable {
|
||||
bool idle = self->m_write_queue.empty();
|
||||
self->m_write_queue.emplace(priority, self->m_sequence++,
|
||||
std::move(packet));
|
||||
if (idle) {
|
||||
self->do_write();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const std::string& Session::uuid() const { return m_uuid; }
|
||||
|
||||
asio::awaitable<void> Session::read_loop() {
|
||||
try {
|
||||
while (true) {
|
||||
std::array<uint8_t, HEADER_LEN> header_buffer;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
|
||||
asio::use_awaitable);
|
||||
|
||||
auto header = decode_packet_header(header_buffer);
|
||||
uint32_t total_len = HEADER_LEN + header.compressed_size;
|
||||
|
||||
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
|
||||
|
||||
throw std::runtime_error("invalid packet length");
|
||||
}
|
||||
std::vector<uint8_t> body_data(header.compressed_size);
|
||||
if (header.compressed_size > 0) {
|
||||
co_await asio::async_read(m_socket, asio::buffer(body_data),
|
||||
asio::use_awaitable);
|
||||
}
|
||||
auto cmd_id = header.cmd;
|
||||
Arena arena;
|
||||
if (cmd_id == std::to_underlying(PacketEnum::LOGIN_REQ)) {
|
||||
auto* req = Arena::Create<LoginReq>(&arena);
|
||||
Logger::info("Session: Receive Login req");
|
||||
if (decode_packet(*req, body_data, header)) {
|
||||
m_server_world.handle_player_login(req->name(),
|
||||
shared_from_this());
|
||||
}
|
||||
}
|
||||
if (cmd_id == std::to_underlying(PacketEnum::C2S_PLAYER_INFO)) {
|
||||
auto* pos = Arena::Create<C2S_PlayerInfo>(&arena);
|
||||
if (decode_packet(*pos, body_data, header)) {
|
||||
m_server_world.sync_player_pos(*pos);
|
||||
}
|
||||
}
|
||||
if (cmd_id == std::to_underlying(PacketEnum::CHUNK_DATA_REQ)) {
|
||||
auto* req = Arena::Create<ChunkDataReq>(&arena);
|
||||
// Logger::info("Session: Receive Chunk Data req");
|
||||
if (decode_packet(*req, body_data, header)) {
|
||||
m_server_world.handle_chunk_req(
|
||||
req->task_id(), req->uuid(),
|
||||
ChunkPos(req->pos().x(), req->pos().z()));
|
||||
}
|
||||
}
|
||||
if (cmd_id == std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ)) {
|
||||
auto* req = Arena::Create<BlockChangeReq>(&arena);
|
||||
Logger::info("Session: Receive Block Change req");
|
||||
if (decode_packet(*req, body_data, header)) {
|
||||
m_server_world.handle_block_change(*req);
|
||||
}
|
||||
}
|
||||
if (cmd_id == std::to_underlying(PacketEnum::LOGOUT_REQ)) {
|
||||
auto* req = Arena::Create<LogoutReq>(&arena);
|
||||
if (decode_packet(*req, body_data, header)) {
|
||||
m_server_world.handle_player_exit(req->uuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const asio::system_error& e) {
|
||||
auto ec = e.code();
|
||||
|
||||
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
|
||||
|
||||
Logger::info("Client disconnected");
|
||||
} else {
|
||||
Logger::warn("Asio Error {}", e.what());
|
||||
}
|
||||
|
||||
close();
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Session Error {}", e.what());
|
||||
close();
|
||||
} catch (...) {
|
||||
Logger::error("Unknow Error");
|
||||
close();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
void Session::do_write() {
|
||||
|
||||
auto self = shared_from_this();
|
||||
auto packet = std::move(m_write_queue.top().packet);
|
||||
asio::async_write(
|
||||
m_socket, asio::buffer(*packet),
|
||||
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
|
||||
if (ec) {
|
||||
Logger::warn("Write Ec {}", ec.message());
|
||||
self->close();
|
||||
return;
|
||||
}
|
||||
self->m_write_queue.pop();
|
||||
if (!self->m_write_queue.empty()) {
|
||||
self->do_write();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void Session::close() {
|
||||
if (m_closed.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
m_socket.shutdown(tcp::socket::shutdown_both, ec);
|
||||
|
||||
m_socket.close(ec);
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "Cubed/gameplay/tree.hpp"
|
||||
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
@@ -27,10 +27,10 @@ static constexpr std::array<TreeStructNode, 62> TREE{{
|
||||
{{-1, 3, -2}, 6}, {{-2, 3, -1}, 6},
|
||||
}};
|
||||
|
||||
bool build_tree(Chunk& chunk, const glm::ivec3& pos) {
|
||||
bool build_tree(ServerChunk& chunk, const glm::ivec3& pos) {
|
||||
auto& block = chunk.get_chunk_blocks();
|
||||
|
||||
if (block[Chunk::index(pos)] != 1) {
|
||||
if (block[ServerChunk::index(pos)] != 1) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& d : TREE) {
|
||||
@@ -42,13 +42,13 @@ bool build_tree(Chunk& chunk, const glm::ivec3& pos) {
|
||||
z >= CHUNK_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (block[Chunk::index(tree_node)] != 0) {
|
||||
if (block[ServerChunk::index(tree_node)] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& d : TREE) {
|
||||
auto tree_node = pos + d.offset;
|
||||
chunk.set_chunk_block(Chunk::index(tree_node), d.id);
|
||||
chunk.set_chunk_block(ServerChunk::index(tree_node), d.id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "Cubed/gameplay/vertex_data.hpp"
|
||||
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
VertexData::VertexData(World& world) : m_world(world) {}
|
||||
VertexData::VertexData(ClientWorld& world) : m_world(world) {}
|
||||
VertexData::~VertexData() {
|
||||
if (m_vbo != 0) {
|
||||
m_world.push_delete_vbo(m_vbo);
|
||||
@@ -63,6 +63,9 @@ void VertexData::upload() {
|
||||
glEnableVertexAttribArray(5);
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Release memory
|
||||
m_vertices.clear();
|
||||
}
|
||||
void VertexData::update_sum() { m_sum = m_vertices.size(); }
|
||||
} // namespace Cubed
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,14 @@
|
||||
#include "Cubed/app.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
extern "C" {
|
||||
__declspec(dllexport) unsigned long NvOptimusEnablement = 1;
|
||||
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
static_assert(sizeof(int) == sizeof(int32_t));
|
||||
|
||||
375
src/player_renderer.cpp
Normal file
375
src/player_renderer.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
#include "Cubed/player_renderer.hpp"
|
||||
|
||||
#include "Cubed/camera.hpp"
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/primitive_data.hpp"
|
||||
#include "Cubed/renderer.hpp"
|
||||
#include "Cubed/texture_manager.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace {
|
||||
struct Cuboid {
|
||||
glm::vec3 offset;
|
||||
glm::vec3 size;
|
||||
};
|
||||
|
||||
constexpr Cuboid PLAYER_MODEL[] = {
|
||||
// Head
|
||||
{{0.25f, 1.50f, 0.25f}, {0.50f, 0.50f, 0.50f}},
|
||||
|
||||
// Body
|
||||
{{0.25f, 0.75f, 0.375f}, {0.50f, 0.75f, 0.25f}},
|
||||
|
||||
// Left Arm
|
||||
{{0.00f, 0.75f, 0.375f}, {0.25f, 0.75f, 0.25f}},
|
||||
|
||||
// Right Arm
|
||||
{{0.75f, 0.75f, 0.375f}, {0.25f, 0.75f, 0.25f}},
|
||||
|
||||
// Left Leg
|
||||
{{0.25f, 0.00f, 0.375f}, {0.25f, 0.75f, 0.25f}},
|
||||
|
||||
// Right Leg
|
||||
{{0.50f, 0.00f, 0.375f}, {0.25f, 0.75f, 0.25f}},
|
||||
};
|
||||
|
||||
struct UVRect {
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
};
|
||||
using FaceUV = std::array<UVRect, 6>;
|
||||
constexpr FaceUV HEAD_UV = {{
|
||||
{0, 0, 8, 8}, // front
|
||||
{8, 0, 8, 8}, // right
|
||||
{16, 0, 8, 8}, // back
|
||||
{24, 0, 8, 8}, // left
|
||||
{32, 0, 8, 8}, // top
|
||||
{40, 0, 8, 8}, // bottom
|
||||
}};
|
||||
|
||||
constexpr FaceUV BODY_UV = {{
|
||||
{0, 8, 8, 12},
|
||||
{8, 8, 4, 12},
|
||||
{12, 8, 8, 12},
|
||||
{20, 8, 4, 12},
|
||||
{24, 8, 8, 4},
|
||||
{32, 8, 8, 4},
|
||||
}};
|
||||
|
||||
constexpr FaceUV LEFT_ARM_UV = {{
|
||||
{0, 20, 4, 12},
|
||||
{4, 20, 4, 12},
|
||||
{8, 20, 4, 12},
|
||||
{12, 20, 4, 12},
|
||||
{16, 20, 4, 4},
|
||||
{20, 20, 4, 4},
|
||||
}};
|
||||
|
||||
constexpr FaceUV RIGHT_ARM_UV = {{
|
||||
{24, 20, 4, 12},
|
||||
{28, 20, 4, 12},
|
||||
{32, 20, 4, 12},
|
||||
{36, 20, 4, 12},
|
||||
{40, 20, 4, 4},
|
||||
{44, 20, 4, 4},
|
||||
}};
|
||||
|
||||
constexpr FaceUV LEFT_LEG_UV = {{
|
||||
{0, 32, 4, 12}, // front
|
||||
{4, 32, 4, 12}, // right
|
||||
{8, 32, 4, 12}, // back
|
||||
{12, 32, 4, 12}, // left
|
||||
{16, 32, 4, 4}, // top
|
||||
{20, 32, 4, 4}, // bottom
|
||||
}};
|
||||
|
||||
constexpr FaceUV RIGHT_LEG_UV = {{
|
||||
{24, 32, 4, 12}, // front
|
||||
{28, 32, 4, 12}, // right
|
||||
{32, 32, 4, 12}, // back
|
||||
{36, 32, 4, 12}, // left
|
||||
{40, 32, 4, 4}, // top
|
||||
{44, 32, 4, 4}, // bottom
|
||||
}};
|
||||
|
||||
constexpr std::array<std::array<UVRect, 6>,
|
||||
Cubed::PlayerRenderer::BODY_PART_NUM>
|
||||
PLAYER_TEX = {{{HEAD_UV},
|
||||
{BODY_UV},
|
||||
{LEFT_ARM_UV},
|
||||
{RIGHT_ARM_UV},
|
||||
{LEFT_LEG_UV},
|
||||
{RIGHT_LEG_UV}}};
|
||||
|
||||
constexpr glm::vec3 HEAD_PIVOT{0.5, 1.5, 0.5};
|
||||
constexpr glm::vec3 LEFT_ARM_PIVOT{0.125f, 1.50f, 0.50f};
|
||||
constexpr glm::vec3 RIGHT_ARM_PIVOT{0.875, 1.50, 0.50};
|
||||
constexpr glm::vec3 LEFT_LEG_PIVOT{0.375, 0.75, 0.50};
|
||||
constexpr glm::vec3 RIGHT_LEG_PIVOT{0.625, 0.75, 0.50};
|
||||
|
||||
} // namespace
|
||||
namespace Cubed {
|
||||
PlayerRenderer::PlayerRenderer(Renderer& renderer) : m_renderer(renderer) {}
|
||||
|
||||
PlayerRenderer::~PlayerRenderer() {
|
||||
if (!m_inited) {
|
||||
return;
|
||||
}
|
||||
glDeleteBuffers(BODY_PART_NUM, m_vbo.data());
|
||||
glDeleteVertexArrays(BODY_PART_NUM, m_vao.data());
|
||||
}
|
||||
|
||||
void PlayerRenderer::init() {
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
const auto& part = PLAYER_MODEL[i];
|
||||
|
||||
for (int face = 0; face < 6; ++face) {
|
||||
const auto& rect = PLAYER_TEX[i][face];
|
||||
for (int vex = 0; vex < 6; ++vex) {
|
||||
glm::vec3 p{VERTICES_POS[face][vex][0],
|
||||
VERTICES_POS[face][vex][1],
|
||||
VERTICES_POS[face][vex][2]};
|
||||
float su = TEX_COORDS[face][vex][0];
|
||||
float sv = TEX_COORDS[face][vex][1];
|
||||
|
||||
if (face == 2) {
|
||||
float t = su;
|
||||
su = sv;
|
||||
sv = 1.0f - t;
|
||||
}
|
||||
float u = rect.x + su * rect.w;
|
||||
float v = rect.y + sv * rect.h;
|
||||
|
||||
u /= 64.0f;
|
||||
v /= 64.0f;
|
||||
|
||||
p *= part.size;
|
||||
p += part.offset;
|
||||
|
||||
m_vertices[i].emplace_back(
|
||||
p.x, p.y, p.z, u, v, NORMALS[face][vex][0],
|
||||
NORMALS[face][vex][1], NORMALS[face][vex][2],
|
||||
TANGENTS[face][vex][0], TANGENTS[face][vex][1],
|
||||
TANGENTS[face][vex][2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
glGenVertexArrays(BODY_PART_NUM, m_vao.data());
|
||||
glGenBuffers(BODY_PART_NUM, m_vbo.data());
|
||||
for (int i = 0; i < BODY_PART_NUM; i++) {
|
||||
glBindVertexArray(m_vao[i]);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo[i]);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
m_vertices[i].size() * sizeof(PlayerVertex),
|
||||
m_vertices[i].data(), GL_STATIC_DRAW);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(PlayerVertex),
|
||||
(void*)0);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(PlayerVertex),
|
||||
(void*)offsetof(PlayerVertex, s));
|
||||
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(PlayerVertex),
|
||||
(void*)offsetof(PlayerVertex, nx));
|
||||
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(PlayerVertex),
|
||||
(void*)offsetof(PlayerVertex, tx));
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
glEnableVertexAttribArray(3);
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
m_inited = true;
|
||||
}
|
||||
|
||||
void PlayerRenderer::render(const Shader& shader) {
|
||||
if (!m_inited) {
|
||||
Logger::error("Player Renderer isn't init");
|
||||
return;
|
||||
}
|
||||
auto& m_camera = m_renderer.camera();
|
||||
auto& m_world = m_renderer.world();
|
||||
auto& m_player = m_world.get_player();
|
||||
glm::mat4 m_v_mat = m_camera.get_camera_lookat();
|
||||
glm::mat4 m_p_mat = m_renderer.proj_mat();
|
||||
|
||||
auto& players = m_world.render_player_data();
|
||||
shader.set_loc("proj_matrix", m_p_mat);
|
||||
|
||||
for (auto& player : players) {
|
||||
|
||||
if (player.uuid == m_player.get_uuid()) {
|
||||
if (m_camera.is_first_person()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4 model(1.0f);
|
||||
|
||||
model = glm::translate(model, player.render_pos);
|
||||
|
||||
// model = glm::translate(model, glm::vec3(0.5f, 0.0f, 0.5f));
|
||||
// glm::rotate(..., +yaw, Y) follows the OpenGL right‑handed coordinate
|
||||
// system, where a positive angle means counter‑clockwise rotation.
|
||||
// Therefore the model must use -yaw to align with the direction of
|
||||
// m_front.
|
||||
model = glm::rotate(model, glm::radians(-player.yaw + 180.0f),
|
||||
glm::vec3(0, 1, 0));
|
||||
model = glm::translate(model, glm::vec3(-0.5f, 0.0f, -0.5f));
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, m_renderer.texture_mamger().get_skin());
|
||||
|
||||
auto make_rotated = [&](glm::vec3 pivot, float angle) {
|
||||
glm::mat4 mat = model;
|
||||
mat = glm::translate(mat, pivot);
|
||||
mat = glm::rotate(mat, angle, glm::vec3(1, 0, 0));
|
||||
mat = glm::translate(mat, -pivot);
|
||||
return mat;
|
||||
};
|
||||
|
||||
for (int i = 0; i < BODY_PART_NUM; i++) {
|
||||
switch (i) {
|
||||
case 0: {
|
||||
glm::mat4 head_model = model;
|
||||
head_model = glm::translate(head_model, HEAD_PIVOT);
|
||||
head_model =
|
||||
glm::rotate(head_model, glm::radians(-player.pitch),
|
||||
glm::vec3(1, 0, 0));
|
||||
head_model = glm::translate(head_model, -HEAD_PIVOT);
|
||||
glm::mat4 head_mv = m_v_mat * head_model;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(head_mv)));
|
||||
shader.set_loc("modelMatrix", head_model);
|
||||
shader.set_loc("mv_matrix", head_mv);
|
||||
} break;
|
||||
case 1: {
|
||||
glm::mat4 mv_mat = m_v_mat * model;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(mv_mat)));
|
||||
shader.set_loc("modelMatrix", model);
|
||||
shader.set_loc("mv_matrix", mv_mat);
|
||||
} break;
|
||||
case 2: { // left arm
|
||||
glm::mat4 model_mat =
|
||||
make_rotated(LEFT_ARM_PIVOT, player.angle);
|
||||
glm::mat4 mv_mat = m_v_mat * model_mat;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(mv_mat)));
|
||||
shader.set_loc("modelMatrix", model_mat);
|
||||
shader.set_loc("mv_matrix", mv_mat);
|
||||
} break;
|
||||
case 3: { // right arm
|
||||
glm::mat4 model_mat =
|
||||
make_rotated(RIGHT_ARM_PIVOT, -player.angle);
|
||||
glm::mat4 mv_mat = m_v_mat * model_mat;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(mv_mat)));
|
||||
shader.set_loc("modelMatrix", model_mat);
|
||||
shader.set_loc("mv_matrix", mv_mat);
|
||||
} break;
|
||||
case 4: { // left leg
|
||||
glm::mat4 model_mat =
|
||||
make_rotated(LEFT_LEG_PIVOT, -player.angle);
|
||||
glm::mat4 mv_mat = m_v_mat * model_mat;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(mv_mat)));
|
||||
shader.set_loc("modelMatrix", model_mat);
|
||||
shader.set_loc("mv_matrix", mv_mat);
|
||||
} break;
|
||||
case 5: { // right leg
|
||||
glm::mat4 model_mat =
|
||||
make_rotated(RIGHT_LEG_PIVOT, player.angle);
|
||||
|
||||
glm::mat4 mv_mat = m_v_mat * model_mat;
|
||||
shader.set_loc("norm_matrix",
|
||||
glm::transpose(glm::inverse(mv_mat)));
|
||||
shader.set_loc("modelMatrix", model_mat);
|
||||
shader.set_loc("mv_matrix", mv_mat);
|
||||
} break;
|
||||
}
|
||||
|
||||
glBindVertexArray(m_vao[i]);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_vertices[i].size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerRenderer::shadow_render(const Shader& shader,
|
||||
glm::mat4& light_matrix) {
|
||||
if (!m_inited) {
|
||||
Logger::error("Player Renderer isn't init");
|
||||
return;
|
||||
}
|
||||
shader.use();
|
||||
shader.set_loc("lightSpaceMatrix", light_matrix);
|
||||
auto& m_world = m_renderer.world();
|
||||
auto& players = m_world.render_player_data();
|
||||
|
||||
for (auto& player : players) {
|
||||
glm::mat4 model(1.0f);
|
||||
|
||||
model = glm::translate(model, player.render_pos);
|
||||
|
||||
// model = glm::translate(model, glm::vec3(0.5f, 0.0f, 0.5f));
|
||||
|
||||
model = glm::rotate(model, glm::radians(-player.yaw + 180.0f),
|
||||
glm::vec3(0, 1, 0));
|
||||
model = glm::translate(model, glm::vec3(-0.5f, 0.0f, -0.5f));
|
||||
|
||||
auto make_rotated = [&](glm::vec3 pivot, float angle) {
|
||||
glm::mat4 mat = model;
|
||||
mat = glm::translate(mat, pivot);
|
||||
mat = glm::rotate(mat, angle, glm::vec3(1, 0, 0));
|
||||
mat = glm::translate(mat, -pivot);
|
||||
return mat;
|
||||
};
|
||||
|
||||
for (int i = 0; i < BODY_PART_NUM; i++) {
|
||||
switch (i) {
|
||||
case 0: {
|
||||
glm::mat4 head_model = model;
|
||||
head_model = glm::translate(head_model, HEAD_PIVOT);
|
||||
head_model =
|
||||
glm::rotate(head_model, glm::radians(-player.pitch),
|
||||
glm::vec3(1, 0, 0));
|
||||
head_model = glm::translate(head_model, -HEAD_PIVOT);
|
||||
shader.set_loc("modelMatrix", head_model);
|
||||
} break;
|
||||
case 1: {
|
||||
shader.set_loc("modelMatrix", model);
|
||||
} break;
|
||||
case 2: { // left arm
|
||||
shader.set_loc("modelMatrix",
|
||||
make_rotated(LEFT_ARM_PIVOT, player.angle));
|
||||
} break;
|
||||
case 3: { // right arm
|
||||
shader.set_loc("modelMatrix",
|
||||
make_rotated(RIGHT_ARM_PIVOT, -player.angle));
|
||||
} break;
|
||||
case 4: { // left leg
|
||||
shader.set_loc("modelMatrix",
|
||||
|
||||
make_rotated(LEFT_LEG_PIVOT, -player.angle));
|
||||
} break;
|
||||
case 5: { // right leg
|
||||
shader.set_loc("modelMatrix",
|
||||
|
||||
make_rotated(RIGHT_LEG_PIVOT, player.angle));
|
||||
} break;
|
||||
}
|
||||
glBindVertexArray(m_vao[i]);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_vertices[i].size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
20
src/proto/auth/auth.proto
Normal file
20
src/proto/auth/auth.proto
Normal file
@@ -0,0 +1,20 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message LoginReq {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message LoginRsp {
|
||||
bool success = 1;
|
||||
string uuid = 2;
|
||||
}
|
||||
|
||||
message LogoutReq {
|
||||
string uuid = 1;
|
||||
}
|
||||
|
||||
message LogoutRsp {
|
||||
string uuid = 1;
|
||||
bool server_stop = 2;
|
||||
}
|
||||
|
||||
6
src/proto/common/chunk_pos.proto
Normal file
6
src/proto/common/chunk_pos.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message ChunkPosNet {
|
||||
int32 x = 1;
|
||||
int32 z = 2;
|
||||
}
|
||||
6
src/proto/common/error.proto
Normal file
6
src/proto/common/error.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Error {
|
||||
int32 code = 1;
|
||||
string mes = 2;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user