7 Commits

Author SHA1 Message Date
zhenyan121
8a4081cf9c feat: player rendering (#27)
* refactor(player): extract player rendering into PlayerRenderer class

Move player rendering code from Renderer to new PlayerRenderer class.
Add normal and tangent vertex attributes for improved per-pixel lighting.


* feat(player-renderer): add shadow rendering and include local player data

* feat(gameplay): add player yaw/pitch synchronization and rendering

* refactor(player_renderer): split rendering into per-body-part VAOs and VBOs

Move `PlayerVertex` struct to header and replace single VAO/VBO with arrays of 6 parts. Implement head pitch rotation in `render()` and `shadow_render()` by applying a separate model matrix for the head (index 0). Remove old `vao()` and `sum()` accessors. Adjust model translation for consistency.

* feat(player): add texture mapping to player model using skin

* fix(player-renderer): correct player rendering rotation and texture coordinates

* feat(gameplay): add player gait system with limb animation

* fix(player): update leg UVs and remove yaw wrap

Update left and right leg UV coordinates to align with updated player texture. Remove yaw modulo operation to allow unrestricted yaw rotation beyond 360 degrees.

* feat(camera): add third-person perspective and walk animation

- Introduce Perspective enum (FIRST_PERSON, THIRD_PERSON_BACK, THIRD_PERSON_FRONT) and m_front member for camera direction.
- Handle F5 key to cycle perspectives.
- Add angle and walk_time to ClientPlayer for walking animation.
- Update player rendering to skip local player only in first person and fix rotation sign.
- Remove redundant player info reporting; update player data with animation angle.

* fix(camera): replace magic number with constant and fix missing break

* fix(client-player): stop sprinting on forward key release

* feat: add camera collision and sphere collision for world

* fix(skin): update player001 texture

* refactor(gameplay): improve thread safety and remove debug logging

* refactor(shaders): extract shadow and normal logic into include files and add lighting to player shaders

* chore(shader): remove unused uniforms cameraPos and specularStrength

* fix: inlcude <array> header
2026-07-05 17:18:40 +08:00
zhenyan121
0f75144b2f fix: release build (#26)
* ci(release): use local vcpkg bootstrap and custom release triplet

* refactor(triplets): set arch and linkage explicitly in x64-windows-release

* ci(release-build): remove unused vcpkg overlay triplets

* ci(release-build): add VCPKG_BUILD_TYPE and disable testing
2026-07-02 16:06:46 +08:00
zhenyan121
30f843ba6b feature: multiplayer (#25)
* build: add asio library

* feat(dev_panel): add Asio to about table

* refactor(gameplay): split Chunk into server/client variants and add networking

* feat(gameplay): add Session class and UUID generation utility

* feat(network): integrate protobuf for player sync and session management

Add Protobuf dependency, define proto messages for player requests, positions, and chunk data. Refactor Session to use strand and async write. Implement player join/exit and position sync in ServerWorld.

* refactor(proto): restructure protobuf definitions and update build system

* feat(protocol): add packet serialization and login handling

Introduce packet header and ID mapping for protobuf messages.
Refactor session and server_world to use new packet wrapper.
Fix missing semicolons in proto files.

* refactor(packet): serialize directly into packet buffer with ByteSizeLong

* feat(world): add player-based chunk loading and UUID support

Restructure world generation to trigger chunk loading based on player
movement. Replace player name with UUID for identification. Implement
chunk request/response protocol. Improve thread management for gen
thread.

* feat(server): add block change packet handling and increase reserved threads

* refactor(server): enhance thread safety and session management

* fix(session): catch std::exception in read_loop and log error

* fix(network-server): stop accept loop on shutdown flag

* fix(gameplay): correct unknown error log condition and missing chunk pos update

* feat(client): add client-side chunk, player, world and network classes

* feat(client-chunk): add greedy meshing, receive_chunk, and biome field

* feat(client_player): add name constructor param, remove chunk transition

* feat(gameplay): integrate network client and thread-safe player pos

* feat(gameplay): add block change sync and coordinate utils

* refactor(server): replace Chunk with ServerChunk

* refactor(gameplay): split world into client and server

Remove monolithic World, Player, Chunk classes. Introduce ClientWorld, ServerWorld, ClientPlayer, and related networked components. Add Abseil dependency for logging and checks. Rename old files to pre_remove_* and update all includes and references accordingly.

* feat(gameplay): add time sync and network improvements

* fix: correct neighbor block indexing and add time update support

* feat(gameplay): optimize chunk request with player chunk tracking

Add `update_player_chunk()` method to `ClientPlayer` that triggers a chunk request when the player moves more than 2 chunks away from the last recorded chunk. Introduce an atomic `m_requesting_chunk` flag in `ClientWorld` to prevent concurrent requests. Rename `m_player_chunk_pos` to `m_last_chunk_pos` for clarity.

* refactor(client_world): split pending queue into data and upload queues

* feat(client): add center-based chunk loading priority

* feat(app): add client/server CLI arguments and init guards

* feat(multiplayer): add rendering of other players as colored cubes

* feat(gameplay): add thread pool for asynchronous chunk data processing

* feat(gameplay): add player logout and disconnect handling

* fix(dev_panel): add protobuf to about table libraries

* build: add ZLIB dependency

* refactor(packet): replace zlib with zstd compression and restructure header

Increase header length to 12 bytes, add CompressType and PacketHeader struct, and implement decode_packet_header. Update CMake to find zstd and link against it, adding Findzstd module.

* refactor(network): extract packet header decoding into function

* feat(packet): implement zstd compression and refactor packet decoding

* perf(gameplay): optimize protobuf message allocation with Arena and limit thread pool size

Switch to Arena allocation for protobuf messages to reduce heap allocations. Pass ChunkDataRsp by value to enable move semantics. Cap thread pool size to hardware concurrency with max 4 threads.

* refactor(gameplay): use steady clock and sleep_until for tick loop timing

* refactor(gameplay): rename remote player types and add interpolation

* refactor(server_world): use thread pool for chunk request handling

* refactor(packet): replace if-constexpr chain with explicit template specializations

Refactor `get_packet_id()` by removing the if-constexpr chain and instead
providing explicit specializations for each packet type. This simplifies the
primary template (which now only contains a static assertion) and improves
compile-time dispatch clarity. The `always_false` helper is no longer needed
in the primary template.

* feat(gameplay): add packet to clear all chunks on server rebuild

Add new packet S2C_ClearAllChunks (ID 3005) to notify clients when the server rebuilds its world. Clients respond by clearing their chunk cache and requesting fresh chunks. Shift UPDATE_TIME packet ID to 3006 to accommodate.

* feat(dev_panel): split world tab; add chunk request logging and fix flag reset

* feat(tools): add RecentQueue to replace std::deque in server_world

* feat(gameplay): add task ID to chunk requests to discard stale responses

* refactor(block): move block source to gameplay directory

* feat(toml): add shared TOML utilities and handle ip.toml

* feat: add player name configuration and rename config file

- Add --player command-line argument and 'player' field in Arguments struct
- Rename ip.toml to server.toml and corresponding internal variable to 'server'
- Fix block.cpp source path in CMakeLists.txt

* build(cmake): modularize build configuration

* fix: build fail on windows

* refactor(renderer): rename Logger level enum value DEBUG to L_DEBUG

* refactor(world): rework chunk state machine and player chunk tracking

Introduce ChunkState enum and ChunkEntity struct to manage chunk lifecycle. Store chunks as shared_ptr to avoid move operations during generation. Add clear_unused_chunks to remove chunks not referenced by any player. Implement deferred chunk request queue for safe processing after generation completes. Update player chunk set during required chunk computation. Improve thread safety with mutexes on chunk and player maps. Fix m_gening flag not reset after generation and add assertions for correctness. Change need_gen to require a player UUID, removing std::optional. Add chunk_size query method for debugging.

* fix(server): ensure chunk is ready before sending or setting block

* refactor(server_world): separate gen and net thread pools

Introduce a second thread pool for network operations and a `ThreadPoolKind` enum to distinguish between gen and net pools. Rename `pool_threads()` to `gen_pool_threads()`, add `change_pool_threads()` overload that accepts the pool kind, and update the dev panel to use the gen pool. Adjust logging and initialization to handle both pools.

* feat(client_world): implement server exit acknowledgment with timeout

* feat(gameplay): implement server stop and client exit handling

Add server_stop flag to LogoutRsp protocol. Modify client_world to check for server stop or own logout to set exit flag. Add ServerWorld::stop() to broadcast stop and cleanly shut down. Refactor chunk ownership to unique_ptr. Remove name parameter from get_look_block_pos.

* fix(app): correct port validation and remove redundant TOML helper

* fix: address unused parameter warnings and missing port assignment

* refactor(gameplay): remove old pre_remove chunk, player, and world files

* refactor(server): replace chunk set with flat_hash_set and add ref count

- Replace std::unordered_set with absl::flat_hash_set for chunk position sets.
- Add ref_count field to ChunkEntity and implement update_ref_count().
- Remove clear_unused_chunks() and use ref counting for chunk lifetime.
- Add get_chunk_pos_set() accessors to ServerPlayer.

* fix(client-world): delay chunk request to wait for server central chunk generation

* refactor(gameplay): migrate chunk storage to tbb::concurrent_hash_map

* refactor(server_world): replace future-based chunk generation with queue

Remove `std::future` per-chunk tracking and poll_finished_chunks().
Instead, generation tasks push completed chunks to a concurrent queue
consumed during update. This eliminates wait_all_chunk_tasks() and
simplifies synchronization.

* refactor(client_world): use concurrent data structures for chunk management

Replace std::shared_mutex and std::unordered_set with tbb::concurrent_hash_map for chunk storage, tbb::concurrent_queue for pending uploads, and absl::flat_hash_set for player chunk positions. This improves thread safety and reduces locking overhead. Refactor receive_chunk and request_chunk to work with the new concurrent model.

* build(windows): add Nvidia and AMD GPU high performance exports

* fix(ci): update header file pattern in clang-format check

* style(clang-format): comment out AlignPPAndNotPP option

* chore: enable AlignPPAndNotPP in clang-format and pin clang version in CI

* ci(workflow): update format check to use LLVM 22

* ci(format-check): install clang-format-22

* perf(world): cull distant players from updates and rendering

Add distance2 utility function. On client, skip rendering other players if distance squared exceeds rendering distance. On server, only send player position and block change updates to players in relevant chunks.

* refactor(math_tools): convert free functions to inline in header

* refactor(dev_panel): remove seed editing and server rebuild world, move client rebuild

- Remove text editing for perlin seed and filter function
- Remove ServerWorld::rebuild_world() method and its atomic flag
- Move 'Rebuild World' button to client world tab
- Remove unused perlin_noise_input_buffer

* fix(server_world): floor block position coordinates in handle_block_change

* refactor(gameplay): pass new_chunks vector as parameter instead of member variable

* perf(client): improve chunk rendering performance with snapshots and threaded generation

* feat(tools): add PriorityThreadPool with priority scheduling

Implement PriorityThreadPool supporting task priorities and FIFO ordering for same priority.
Update ClientWorld to use the new pool with explicit priority for chunk operations.
Fix ThreadPool stop logic with atomic exchange and remove unnecessary lambda capture.

* feat(networking): add priority and sequence ordering to packet send queues

* feat(server-world): use priority thread pool for chunk generation

Introduce PriorityThreadPool to process chunk generation tasks with priorities based on their distance from the player. Closer chunks receive higher priority, improving responsiveness.

* refactor(client): extract AABB creation and add collision check on block place

Make ClientPlayer::get_aabb and ClientWorld::get_block_aabb static.
Change player size to static constexpr M_SIZE.
Use shared_mutex for concurrent reads of other players.
Check for collision with other players before placing a block.

* feat(build): add build-time version system via CMake configure_file

* ci(release): add release build workflow for windows
2026-07-02 14:51:34 +08:00
zhenyan121
d5a12869e6 feat: include for glsl (#24)
* refactor(gameplay): remove unused includes and members, clear vertex data after upload

* feat(shader-tools): add recursive #include support for shader sources

* refactor(shaders): extract noise and sky color functions into shared GLSL includes
2026-06-23 10:27:17 +08:00
zhenyan121
97993b72fe perf:greedy meshing (#23)
* feat(gameplay): implement greedy meshing for chunk generation

Replace the per-face vertex generation with a greedy meshing algorithm that merges adjacent faces of the same block type into larger quads. Introduce `FaceKey` struct and helper functions (`axis_dir_to_face`, `get_block_safe`, `is_face_culled`, `choose_buf`) to support the new algorithm. Comment out the old `gen_vertices` implementation.

In texture management, rename `m_pbr_texture_array` to `m_normal_texture_array` to reflect its actual usage, and set texture wrap mode to `GL_REPEAT` for both the block and normal texture arrays.

* fix(primitive_data): correct back face texture coordinates and tangents

* fix(texture-manager): correct texture deletion and refactor reload

Make hot_reload private and add public need_reload method. Update UI to call need_reload instead of hot_reload. Fix incorrect use of glDeleteBuffers for normal texture array by using glDeleteTextures.
2026-06-22 19:48:25 +08:00
zhenyan121
7ecdab08fc refactor: cave and river (#22)
* refactor(chunk): add ChunkInfo and switch to shared_mutex for chunk access

Introduced ChunkInfo struct to expose chunk metadata (position, seed, biome). Replaced std::mutex with std::shared_mutex for chunk map to allow concurrent read access. Added World::get_chunk_info() method. Temporarily disabled cave/river cleanup and debug biome reporting.

* refactor(cave,river,chunk): use ChunkPos as key for paths and track cave existence

* refactor(gameplay): use deterministic origin-based cave and river generation

Compute cave and river paths on-the-fly per chunk from a deterministic origin instead of storing them globally. Remove concurrent hash map storage, shared mutexes, and related cleanup methods. This simplifies concurrency and reduces memory overhead.

* refactor(world): remove unused chunk generation progress tracking
2026-06-22 16:43:22 +08:00
zhenyan121
7ffc349eb3 refactor: chunk load (#21)
* feat(renderer): add fog effect based on render distance

* refactor(gameplay): remove dead code and simplify chunk neighbor context

Remove the large commented-out `init_chunks()` function, and eliminate the `affected_neighbor` tracking in `gen_chunks_internal()`. This simplifies the neighbor context building and removes unused vertex data regeneration for affected neighbors.

* refactor(gameplay): move chunk generation phases into gen_chunk method

Consolidate multiple phase generation calls into a single gen_chunk() method on Chunk, which handles neighbor generation and ensures thread safety. Simplify World::gen_chunks_internal by using gen_chunk() instead of manual phase orchestration.

* fix(gameplay): use gen_phase_one to get seed

* feat(world): integrate thread pool and async chunk generation

* fix(renderer): correct shader uniform name and remove unused uniform

* feat(gameplay): add temporary chunk flag to prevent path clearing

* fix: add thread safety for cave and river path mutexes

* refactor: remove unused uniforms and set cameraPos uniform outside lambda

* feat(world): add dynamic thread pool resizing

* feat(world): add thread pool size management and UI controls

* feat(world): add ChunkLoadStyle enum and rename chunk_pos to get_chunk_pos

* feat(player): add configurable fly Y speed

* refactor(world): add thread pool start/stop and auto-detect threads

Extract thread pool starting and stopping into dedicated methods. Set default pool threads to 0 to enable automatic detection of available cores, and ensure thread pool is properly managed during world rebuild.

* fix: include shared_mutex header

* fix(gameplay): simplify set_chunk_load_style switch and fix fallthrough bug

* fix(world): add missing include for <utility>
2026-06-22 13:34:45 +08:00
754 changed files with 183547 additions and 3384 deletions

View File

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

@@ -42,4 +42,5 @@ CMakeError.log
.DS_Store
assets/config.toml
.venv/
pyout/
pyout/
vcpkg_installed/

View File

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

View File

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

79
cmake/Dependencies.cmake Normal file
View 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()

View 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()

View File

@@ -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();

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View File

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

View 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

View 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

View 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

View File

@@ -0,0 +1,8 @@
#pragma once
#include <glm/glm.hpp>
struct PathOrigin {
bool exists;
glm::vec3 pos;
unsigned seed;
};

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

View File

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

View File

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

View File

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

View 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

View File

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

View File

@@ -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();

View File

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

View 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

View 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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View 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

View 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

View 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

View File

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

View File

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

View 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

View 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

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

View File

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

View File

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

View File

@@ -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
View 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 righthanded coordinate
// system, where a positive angle means counterclockwise 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
View 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;
}

View File

@@ -0,0 +1,6 @@
syntax = "proto3";
message ChunkPosNet {
int32 x = 1;
int32 z = 2;
}

View 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