8 Commits

Author SHA1 Message Date
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
zhenyan121
5385876a8a feat: pbr (#20)
* feat: add pbr texture

* feat(rendering): add normal mapping support for blocks

* fix: normal map load

* feat(scripts): add batch nearest neighbor upscale script
2026-06-20 15:03:40 +08:00
zhenyan121
a8d2ddbacd feat: water effects (#19)
* feat(shaders): add lighting to block accumulation OIT shaders

* feat(renderer): add water rendering with OIT and new shaders

* feat(water): implement screen-space reflection and refraction for water with depth fade and perturbation

* refactor(shader): replace glUniform calls with generic set_loc and remove cached locations

* feat(water): add noise-based caustics and configurable fog density

* fix(water shader): update depth fade and remove underwater check

- Increase DEPTH_FADE_DISTANCE from 8 to 10
- Remove conditional so depth fade always applies
- Darken deepColor from (0, 0.08, 0.15) to (0, 0.015, 0.045)

* feat(underwater): add volume scattering and shadow mapping for light shafts
2026-06-20 09:04:09 +08:00
792 changed files with 183522 additions and 3098 deletions

View File

@@ -1,4 +1,3 @@
# .github/workflows/format-check.yml
name: Code Format Check name: Code Format Check
on: [push, pull_request] on: [push, pull_request]
@@ -9,9 +8,22 @@ permissions:
jobs: jobs:
formatting: formatting:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: silkeh/clang:latest
steps: steps:
- uses: actions/checkout@v4 - 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 - name: Run clang-format
run: | 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 }}

4
.gitignore vendored
View File

@@ -41,4 +41,6 @@ CMakeError.log
*~ *~
.DS_Store .DS_Store
assets/config.toml assets/config.toml
.venv/ .venv/
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) project(Cubed LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
@@ -6,167 +6,118 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_CONFIGURATION_TYPES
set(CMAKE_BUILD_TYPE Debug) AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()
if(NOT DEFINED CUBED_VERSION)
set(CUBED_VERSION "dev")
endif() endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set(CMAKE_LIBRARY_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) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
if(MSVC) list(APPEND CMAKE_MODULE_PATH
add_compile_options(/utf-8) "${CMAKE_CURRENT_SOURCE_DIR}/cmake"
endif() "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
find_package(OpenGL REQUIRED)
if (UNIX AND NOT APPLE)
find_package(Freetype REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(EGL REQUIRED egl)
pkg_check_modules(Wayland REQUIRED wayland-client wayland-egl)
find_package(glfw3 REQUIRED)
endif()
add_library(glad STATIC third_party/glad/src/glad.c)
target_include_directories(glad PUBLIC third_party/glad/include)
include(FetchContent)
if (WIN32)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE)
set(GLFW_STATIC ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(glfw)
FetchContent_Declare(
freetype
GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git
GIT_TAG VER-2-14-3
)
FetchContent_MakeAvailable(freetype)
if(TARGET freetype)
add_library(Freetype::Freetype ALIAS freetype)
endif()
set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS ON)
FetchContent_Declare(
onetbb
GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git
GIT_TAG v2023.0.0
)
set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE)
FetchContent_MakeAvailable(onetbb)
set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED})
unset(_BUILD_SHARED_LIBS_SAVED)
endif()
FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 1.0.3
) )
FetchContent_MakeAvailable(glm)
FetchContent_Declare( include(Dependencies)
soil2
GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git add_subdirectory(third_party/glad)
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)
add_subdirectory(third_party/imgui) add_subdirectory(third_party/imgui)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) add_executable(${PROJECT_NAME})
add_executable(${PROJECT_NAME} add_subdirectory(src)
src/main.cpp
src/app.cpp file(GLOB_RECURSE PROTO_FILES
src/debug_collector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto
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
) )
if(CMAKE_BUILD_TYPE STREQUAL "Debug") protobuf_generate(
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}") TARGET ${PROJECT_NAME}
LANGUAGE cpp
PROTOS ${PROTO_FILES}
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto
)
target_compile_options(${PROJECT_NAME} PRIVATE configure_file(
#-fsanitize=address src/version.hpp.in
#-fsanitize=thread ${CMAKE_BINARY_DIR}/generated/version.hpp
-fno-omit-frame-pointer @ONLY
-g )
)
target_link_options(${PROJECT_NAME} PRIVATE target_compile_options(${PROJECT_NAME}
#-fsanitize=address PRIVATE
#-fsanitize=thread $<$<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) $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CXX_COMPILER_ID:GNU,Clang>:-Wextra>
ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/" $<$<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() else()
target_compile_definitions(${PROJECT_NAME} PRIVATE # Ninja / Makefiles single-configuration generator
ASSETS_PATH="./assets/" 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() 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} target_link_libraries(${PROJECT_NAME}
PRIVATE PRIVATE
@@ -179,42 +130,24 @@ target_link_libraries(${PROJECT_NAME}
tomlplusplus::tomlplusplus tomlplusplus::tomlplusplus
imgui imgui
tbb 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") if(WIN32)
# target_link_libraries(${PROJECT_NAME} PRIVATE tbb) add_custom_command(
endif() TARGET ${PROJECT_NAME}
POST_BUILD
if (UNIX AND NOT APPLE) COMMAND ${CMAKE_COMMAND} -E copy_if_different
target_link_libraries(${PROJECT_NAME} $<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
PRIVATE $<TARGET_FILE_DIR:${PROJECT_NAME}>
${EGL_LIBRARIES} COMMAND_EXPAND_LISTS
${Wayland_LIBRARIES}
) )
target_include_directories(${PROJECT_NAME}
PRIVATE
${EGL_INCLUDE_DIRS}
${Wayland_INCLUDE_DIRS}
)
target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER})
endif()
if (WIN32)
foreach(TBB_LIB IN ITEMS tbb tbbmalloc tbbmalloc_proxy)
if(TARGET ${TBB_LIB})
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${TBB_LIB}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying ${TBB_LIB}.dll"
)
else()
message(STATUS "Target ${TBB_LIB} not found, skipping copy")
endif()
endforeach()
endif() endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -4,10 +4,21 @@ layout (location = 0) out vec4 accum;
layout (location = 1) out float reveal; layout (location = 1) out float reveal;
in vec2 tc; in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in float roughness;
flat in int tex_layer; flat in int tex_layer;
in float v_depth; in float v_depth;
layout (binding = 0) uniform sampler2DArray samp; layout (binding = 0) uniform sampler2DArray samp;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 ambientColor;
uniform vec3 sunlightDir;
uniform vec3 cameraPos;
uniform bool shader_on;
uniform float specularStrength;
float weight(float z, float a) { float weight(float z, float a) {
float intermediate = 0.03 / (1e-5 + pow(z / 200.0, 4.0)); float intermediate = 0.03 / (1e-5 + pow(z / 200.0, 4.0));
@@ -15,7 +26,71 @@ float weight(float z, float a) {
} }
void main() { void main() {
vec4 color = texture(samp, vec3(tc, tex_layer));
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
if (!shader_on) {
vec4 color = objectColor;
float alpha = color.a;
if (alpha < 1e-4) discard;
float w = weight(v_depth, alpha);
accum = vec4(color.rgb * alpha * w, alpha * w);
reveal = alpha;
return;
}
vec3 lightDir = normalize(-sunlightDir);
vec3 norm = normalize(normal);
vec3 V =
normalize(cameraPos - vert_pos);
vec3 H =
normalize(lightDir + V);
vec3 ambient = ambientStrength * ambientColor;
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * sunlightColor;
float r =
clamp(roughness, 0.0, 1.0);
float shininess =
mix(
512.0,
4.0,
r
);
float ks =
mix(
0.8,
0.02,
r
);
float spec = 0.0;
if(diff > 0.0)
{
spec =
ks *
pow(
max(dot(norm,H),0.0),
shininess
);
}
vec3 specular = spec * sunlightColor * specularStrength;
vec4 color = vec4((ambient + diffuse) * objectColor.rgb + specular, objectColor.a);
float alpha = color.a; float alpha = color.a;
if (alpha < 1e-4) discard; if (alpha < 1e-4) discard;

View File

@@ -3,19 +3,28 @@
layout (location = 0) in vec3 pos; layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord; layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer; layout (location = 2) in float layer;
layout (location = 3) in vec3 aNormal;
layout (location = 4) in float Roughness;
out vec2 tc; out vec2 tc;
out vec3 normal;
out vec3 vert_pos;
flat out int tex_layer; flat out int tex_layer;
out float roughness;
out float v_depth; out float v_depth;
uniform mat4 mv_matrix; uniform mat4 mv_matrix;
uniform mat4 proj_matrix; uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
void main(void) { void main(void) {
vec4 view_pos = mv_matrix * vec4(pos, 1.0); vec4 view_pos = mv_matrix * vec4(pos, 1.0);
gl_Position = proj_matrix * view_pos; vert_pos = pos;
roughness = Roughness;
tc = texCoord; tc = texCoord;
tex_layer = int(layer); tex_layer = int(layer);
v_depth = -view_pos.z; v_depth = -view_pos.z;
normal = mat3(norm_matrix) * aNormal;
gl_Position = proj_matrix * view_pos;
} }

View File

@@ -3,13 +3,15 @@
in vec2 tc; in vec2 tc;
in vec3 normal; in vec3 normal;
in vec3 vert_pos; in vec3 vert_pos;
in vec3 tangent;
in vec3 bitangent;
in vec4 FragPosLightSpace; in vec4 FragPosLightSpace;
in float roughness; in float roughness;
flat in int tex_layer; flat in int tex_layer;
out vec4 color; out vec4 color;
layout (binding = 0) uniform sampler2D shadowMap; layout (binding = 0) uniform sampler2D shadowMap;
layout (binding = 1) uniform sampler2DArray samp; layout (binding = 1) uniform sampler2DArray samp;
layout (binding = 2) uniform sampler2DArray normMap;
uniform float ambientStrength; uniform float ambientStrength;
uniform vec3 sunlightColor; uniform vec3 sunlightColor;
uniform vec3 ambientColor; uniform vec3 ambientColor;
@@ -21,6 +23,12 @@ uniform float specularStrength;
uniform float lightSizeUV; uniform float lightSizeUV;
uniform float minRadius; uniform float minRadius;
uniform float maxRadius; uniform float maxRadius;
uniform bool enablePBR;
uniform bool flipY;
uniform int renderDistance;
uniform vec3 skyColor;
const vec2 poissonDisk32[32] = vec2[]( const vec2 poissonDisk32[32] = vec2[](
vec2(-0.975402, -0.071138), vec2(-0.975402, -0.071138),
vec2(-0.920347, -0.411420), vec2(-0.920347, -0.411420),
@@ -259,6 +267,16 @@ float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
return shadow; return shadow;
} }
vec3 calcNewNormal() {
mat3 TBN = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec3 retrievedNormal = texture(normMap, vec3(tc, tex_layer)).xyz;
retrievedNormal = retrievedNormal * 2.0 - 1.0;
if (flipY) {
retrievedNormal.y = -retrievedNormal.y;
}
vec3 newNormal = TBN * retrievedNormal;
return normalize(newNormal);
}
void main(void) { void main(void) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer)); vec4 objectColor = texture(samp, vec3(tc, tex_layer));
@@ -273,8 +291,13 @@ void main(void) {
vec3 lightDir = normalize(-sunlightDir); vec3 lightDir = normalize(-sunlightDir);
vec3 norm;
vec3 norm = normalize(normal); if (enablePBR) {
norm = calcNewNormal();
} else {
norm = normalize(normal);
}
vec3 V = vec3 V =
normalize(cameraPos - vert_pos); normalize(cameraPos - vert_pos);
@@ -322,8 +345,19 @@ void main(void) {
vec3 specular = spec * sunlightColor * specularStrength; vec3 specular = spec * sunlightColor * specularStrength;
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir); float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb + (1.0-shadow) * specular, objectColor.a); // fog
float dist = length(cameraPos - vert_pos);
vec4 fogColor = vec4(skyColor, 1.0);
float fogStart = renderDistance * 16 * 0.9;
float fogEnd = renderDistance * 16;
//color = varyingColor; 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);
//color = vec4(calcNewNormal(), 1.0);
} }

View File

@@ -5,9 +5,11 @@ layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer; layout (location = 2) in float layer;
layout (location = 3) in vec3 aNormal; layout (location = 3) in vec3 aNormal;
layout (location = 4) in float Roughness; layout (location = 4) in float Roughness;
layout (location = 5) in vec3 aTangent;
out vec2 tc; out vec2 tc;
out vec3 normal; out vec3 normal;
out vec3 tangent;
out vec3 bitangent;
out vec3 vert_pos; out vec3 vert_pos;
flat out int tex_layer; flat out int tex_layer;
out vec4 FragPosLightSpace; out vec4 FragPosLightSpace;
@@ -20,6 +22,7 @@ mat4 buildTranslate(float x, float y, float z);
uniform mat4 mv_matrix; uniform mat4 mv_matrix;
uniform mat4 proj_matrix; uniform mat4 proj_matrix;
uniform mat4 model_matrix;
uniform mat4 norm_matrix; uniform mat4 norm_matrix;
uniform mat4 lightSpaceMatrix; uniform mat4 lightSpaceMatrix;
@@ -32,7 +35,9 @@ void main(void) {
tex_layer = int(layer); tex_layer = int(layer);
roughness = Roughness; roughness = Roughness;
normal = mat3(norm_matrix) * aNormal; normal = normalize(mat3(norm_matrix) * aNormal);
tangent = normalize(mat3(model_matrix) * aTangent);
bitangent = cross(normal, tangent);
FragPosLightSpace = lightSpaceMatrix * vec4(pos, 1.0); FragPosLightSpace = lightSpaceMatrix * vec4(pos, 1.0);
gl_Position = proj_matrix * viewPos; gl_Position = proj_matrix * viewPos;
} }

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

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,8 @@
#version 460
out vec4 color;
in vec2 tc;
void main() {
color = vec4(0.0, 0.0, 1.0, 1.0);
}

View File

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

View File

@@ -19,77 +19,12 @@ uniform float cloudThresholdHigh;
uniform float time; uniform float time;
#include "compute_sky_color.glsl"
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float amp = 0.5;
for (int i = 0; i < 5; i++) {
v += amp * noise(p);
p *= 2.0;
amp *= 0.5;
}
return v;
}
void main(void) { void main(void) {
vec3 sund = normalize(sunDir);
vec3 sky = computeSkyColor(dir);
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;
frag_color = vec4(sky, 1.0); frag_color = vec4(sky, 1.0);
//frag_color = vec4(vec3(sunAmount), 1.0);
//frag_color = vec4(t,0,0,1);
} }

View File

@@ -1,35 +1,111 @@
#version 460 #version 460
in vec2 TexCoord; in vec2 TexCoord;
in vec3 rayDir;
out vec4 FragColor; out vec4 FragColor;
uniform sampler2D u_sceneTexture; layout (binding = 0) uniform sampler2D u_sceneTexture;
layout (binding = 1) uniform sampler2D u_depthTexture;
layout (binding = 2) uniform sampler2D u_shadowMap;
uniform mat4 u_lightSpaceMatrix;
uniform float u_time; uniform float u_time;
uniform bool u_underwater; uniform bool u_underwater;
uniform vec3 u_waterColor; uniform vec3 u_waterColor;
uniform float u_fogDensity; uniform float u_fogDensity;
uniform mat4 InverseViewProjection;
uniform vec3 cameraPos;
uniform vec3 sunDir;
uniform vec3 sunColor;
uniform float waterDensity;
#include "noise.glsl"
float getCausticValue(float x, float y, float z) {
float w = 8.0;
float strength = 4.0;
vec2 coord = vec2(x, y) * w + z * 0.5;
float n = fbm(coord);
float caustic = pow(n, 3.0) * strength;
return caustic;
}
float getShadow(vec3 worldPos) {
vec4 posLightSpace = u_lightSpaceMatrix * vec4(worldPos, 1.0);
vec3 projCoords = posLightSpace.xyz / posLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.z > 1.0) return 1.0;
float closestDepth = texture(u_shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
return currentDepth - 0.005 > closestDepth ? 0.0 : 1.0;
}
void main() { void main() {
vec4 original = texture(u_sceneTexture, TexCoord); vec4 original = texture(u_sceneTexture, TexCoord);
if (!u_underwater) { if (!u_underwater) {
FragColor = original; FragColor = original;
return; return;
} }
float rawDepth = texture(u_depthTexture, TexCoord).r;
vec3 ndc = vec3(TexCoord * 2.0 - 1.0, rawDepth * 2.0 - 1.0);
vec4 worldPosH = InverseViewProjection * vec4(ndc, 1.0);
vec3 worldPos = worldPosH.xyz / worldPosH.w;
float sceneDistance = distance(cameraPos, worldPos);
vec2 distoredUV = TexCoord; vec2 distoredUV = TexCoord;
float strength = 0.003; float strength = 0.003;
distoredUV.x += sin(TexCoord.y * 15.0 + u_time * 5.0) * strength; distoredUV.x += sin(TexCoord.y * 15.0 + u_time * 5.0) * strength;
distoredUV.y += cos(TexCoord.x * 15.0 + u_time * 4.3) * strength; distoredUV.y += cos(TexCoord.x * 15.0 + u_time * 4.3) * strength;
distoredUV = clamp(distoredUV, 0.001, 0.999); distoredUV = clamp(distoredUV, 0.001, 0.999);
vec4 distorted = texture(u_sceneTexture, distoredUV); vec4 distorted = texture(u_sceneTexture, distoredUV);
float rawCaustic = getCausticValue(TexCoord.x, TexCoord.y, u_time * 0.3);
float caustic = 0.5 + rawCaustic * 0.5;
vec3 causticLight = vec3(caustic * 0.9, caustic, caustic * 0.7);
float caustic = 0.9 + 0.1 * sin(TexCoord.x * 20.0 + u_time) * cos(TexCoord.y * 20.0 + u_time * 1.2);
vec3 causticLight = vec3(caustic, caustic * 0.95, caustic * 0.9);
//vec3 causticLight = vec3(1.0);
float fogFactor = clamp(1.0 - (TexCoord.y * u_fogDensity * 10.0), 0.0, 1.0); float fogFactor = clamp(1.0 - (TexCoord.y * u_fogDensity * 10.0), 0.0, 1.0);
vec3 mixed = mix(u_waterColor, distorted.rgb * causticLight, fogFactor); vec3 mixed = mix(u_waterColor, distorted.rgb * causticLight, fogFactor);
// Volume Scattering
vec3 rayOrigin = cameraPos;
vec3 rayDirection = normalize(rayDir);
FragColor = vec4(mixed, 1.0); float maxDist = 10.0; // Maximum visible distance
float stepSize = 0.4;
float phasePower = 8.0; // Beam Sharpness
vec3 scattering = vec3(0.0);
float transmittance = 1.0;
float cosTheta = dot(rayDirection, sunDir);
float phase = pow(max(cosTheta, 0.0), phasePower);
for (float t = 0.0; t < maxDist; t += stepSize) {
if (t > sceneDistance) break;
vec3 pos = rayOrigin + rayDirection * t;
float shadow = getShadow(pos);
float density = waterDensity;
vec3 absorption = vec3(0.3, 0.08, 0.02); // Absorption coefficient per meter
vec3 sunLightAtPos = sunColor * exp(-absorption * t);
scattering += density * phase * sunLightAtPos * transmittance * stepSize * shadow;
float extinction = waterDensity * 1.5; // Extinction coefficient, tunable
transmittance *= exp(-extinction * stepSize);
if (transmittance < 0.01) break;
}
FragColor.rgb = mixed + scattering;
FragColor.a = 1.0;
} }

View File

@@ -3,9 +3,18 @@
layout (location = 0) in vec2 pos; layout (location = 0) in vec2 pos;
layout (location = 1) in vec2 texCoord; layout (location = 1) in vec2 texCoord;
out vec2 TexCoord; uniform mat4 InverseViewProjection;
uniform vec3 cameraPos;
out vec2 TexCoord;
out vec3 rayDir;
void main() { void main() {
gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
TexCoord = texCoord; TexCoord = texCoord;
vec4 clipPos = vec4(pos, 1.0, 1.0);
vec4 worldPosH = InverseViewProjection * clipPos;
vec3 worldPos = worldPosH.xyz / worldPosH.w;
vec3 RayDir = worldPos - cameraPos;
rayDir = normalize(RayDir);
gl_Position = clipPos;
} }

View File

@@ -0,0 +1,248 @@
#version 460
layout (location = 0) out vec4 accum;
layout (location = 1) out float reveal;
in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec3 world_pos;
in float roughness;
flat in int tex_layer;
in float v_depth;
layout (binding = 0) uniform sampler2DArray samp;
uniform sampler2D sceneColorTex; // binding 1
uniform sampler2D sceneDepthTex; // binding 2
uniform mat4 proj_matrix;
uniform mat4 inv_proj_matrix;
uniform mat4 inv_view_matrix;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 ambientColor;
uniform vec3 sunlightDir;
uniform bool shader_on;
uniform float specularStrength;
uniform vec3 skyTop;
uniform vec3 skyBottom;
uniform vec3 sunColor;
uniform float horizonSharpness;
uniform float cloudWhiteMix;
uniform float cloudThresholdLow;
uniform float cloudThresholdHigh;
uniform float time;
uniform vec3 sunDir; // world!
uniform bool underwater;
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);
}
// Reconstruct eye-space coordinates from screen UV and depth buffer value
vec3 reconstructViewPos(vec2 uv, float depth) {
vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 view = inv_proj_matrix * clip;
return view.xyz / view.w;
}
// Screen-space ray marching, origin/dir are in eye space
bool traceSSR(vec3 origin, vec3 dir, out vec2 hitUV) {
const int COARSE_STEPS = 32;
const float COARSE_STEP = 0.6;
const int REFINE_STEPS = 8;
const float THICKNESS = 0.3;
vec3 pos = origin;
vec3 prevPos = origin;
bool foundCoarse = false;
for (int i = 0; i < COARSE_STEPS; ++i) {
prevPos = pos;
pos += dir * COARSE_STEP;
vec4 clip = proj_matrix * vec4(pos, 1.0);
if (clip.w <= 0.0) return false;
vec3 ndc = clip.xyz / clip.w;
if (abs(ndc.x) > 1.0 || abs(ndc.y) > 1.0) return false;
vec2 uv = ndc.xy * 0.5 + 0.5;
float sceneDepth = texture(sceneDepthTex, uv).r;
vec3 scenePos = reconstructViewPos(uv, sceneDepth);
if (pos.z - scenePos.z < 0.0) {
foundCoarse = true;
break;
}
}
if (!foundCoarse) return false;
vec3 lo = prevPos;
vec3 hi = pos;
for (int i = 0; i < REFINE_STEPS; ++i) {
vec3 mid = (lo + hi) * 0.5;
vec4 clip = proj_matrix * vec4(mid, 1.0);
vec3 ndc = clip.xyz / clip.w;
vec2 uv = ndc.xy * 0.5 + 0.5;
float sceneDepth = texture(sceneDepthTex, uv).r;
vec3 scenePos = reconstructViewPos(uv, sceneDepth);
if (mid.z - scenePos.z < 0.0) {
hi = mid;
} else {
lo = mid;
}
}
vec4 finalClip = proj_matrix * vec4(hi, 1.0);
vec3 finalNdc = finalClip.xyz / finalClip.w;
vec2 finalUV = finalNdc.xy * 0.5 + 0.5;
float finalDepth = texture(sceneDepthTex, finalUV).r;
vec3 finalScenePos = reconstructViewPos(finalUV, finalDepth);
if (abs(hi.z - finalScenePos.z) > THICKNESS) return false;
hitUV = finalUV;
return true;
}
void main() {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
if (!shader_on) {
vec4 color = objectColor;
float alpha = color.a;
if (alpha < 1e-4) discard;
float w = weight(v_depth, alpha);
accum = vec4(color.rgb * alpha * w, alpha * w);
reveal = alpha;
return;
}
// Normal perturbation
vec3 N;
if (enablePerturb) {
const float wave_speed_scale = 40.0;
vec2 waveUV1 = world_pos.xz * 8.0 + vec2(time * 0.05 * wave_speed_scale, time * 0.03 * wave_speed_scale);
vec2 waveUV2 = world_pos.xz * 13.0 + vec2(-time * 0.04 * wave_speed_scale, time * 0.06 * wave_speed_scale);
float wave1 = noise(waveUV1);
float wave2 = noise(waveUV2);
vec2 normalPerturb = vec2(wave1 - 0.5, wave2 - 0.5) * 0.1;
N = normalize(normal + vec3(normalPerturb.x, 0.0, normalPerturb.y));
} else {
N = normalize(normal);
}
vec3 V = normalize(-vert_pos);
vec3 reflectColor;
vec3 refractColor;
float fresnel;
vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(sceneDepthTex, 0));
// water depth
float sceneDepthRaw = texture(sceneDepthTex, screenUV).r;
vec3 sceneViewPos = reconstructViewPos(screenUV, sceneDepthRaw);
float waterDepth = vert_pos.z - sceneViewPos.z;
waterDepth = max(waterDepth, 0.0);
const float DEPTH_FADE_DISTANCE = 10;
float depthFactor = clamp(waterDepth / DEPTH_FADE_DISTANCE, 0.0, 1.0);
if (underwater) {
reflectColor = vec3(0.0);
refractColor = objectColor.rgb;
fresnel = 0.0;
} else {
vec3 R = reflect(-V, N);
vec3 origin = vert_pos + N * 0.05;
vec2 hitUV;
if (traceSSR(origin, R, hitUV)) {
reflectColor = texture(sceneColorTex, hitUV).rgb;
} else {
vec3 worldR = mat3(inv_view_matrix) * R;
reflectColor = computeSkyColor(worldR);
}
float effectiveRefractStrength = refractStrength * (1.0 - depthFactor * 0.5);
vec2 refractOffset = N.xz * effectiveRefractStrength;
vec2 refractUV = clamp(screenUV + refractOffset, vec2(0.001), vec2(0.999));
refractColor = texture(sceneColorTex, refractUV).rgb;
fresnel = pow(1.0 - max(dot(N, V), 0.0), 5.0);
fresnel = mix(0.02, 1.0, fresnel);
}
vec3 lightDir = normalize(-sunlightDir);
vec3 H = normalize(lightDir + V);
vec3 ambient = ambientStrength * ambientColor;
float diff = max(dot(N, lightDir), 0.0);
vec3 diffuse = diff * sunlightColor;
float r = clamp(roughness, 0.0, 1.0);
float shininess = mix(512.0, 4.0, r);
float ks = mix(0.8, 0.02, r);
float spec = 0.0;
if (diff > 0.0) {
spec = ks * pow(max(dot(N, H), 0.0), shininess);
}
vec3 specular = spec * sunlightColor * specularStrength;
vec3 tintedRefract;
if (enableDepthFade) {
vec3 shallowColor = vec3(0.4, 0.75, 0.7);
vec3 deepColor = vec3(0.0, 0.015, 0.045);
vec3 waterTint = mix(shallowColor, deepColor, depthFactor);
tintedRefract = mix(refractColor, refractColor * waterTint, depthFactor);
tintedRefract = mix(tintedRefract, objectColor.rgb, 0.4); // Forcefully blend 40% texture color, independent of depth
} else {
tintedRefract = mix(refractColor, objectColor.rgb, 0.4);
}
//vec3 baseWaterColor = (ambient + diffuse) * objectColor.rgb + specular;
vec3 baseWaterColor = (ambient + diffuse) * tintedRefract + specular;
vec3 finalColor = mix(baseWaterColor, reflectColor, fresnel);
float alpha = objectColor.a;
if (alpha < 1e-4) discard;
float w = weight(v_depth, alpha);
accum = vec4(finalColor * alpha * w, alpha * w);
reveal = alpha;
return;
}

View File

@@ -0,0 +1,32 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
layout (location = 3) in vec3 aNormal;
layout (location = 4) in float Roughness;
out vec2 tc;
out vec3 normal;
out vec3 vert_pos;
out vec3 world_pos;
flat out int tex_layer;
out float roughness;
out float v_depth;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
void main(void) {
vec4 view_pos = mv_matrix * vec4(pos, 1.0);
world_pos = pos;
vert_pos = view_pos.xyz;
normal = mat3(norm_matrix) * aNormal;
roughness = Roughness;
tc = texCoord;
tex_layer = int(layer);
v_depth = -view_pos.z;
gl_Position = proj_matrix * view_pos;
}

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 #pragma once
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/network_server.hpp"
#include "Cubed/gameplay/server_world.hpp"
#define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_NONE
#include "Cubed/camera.hpp" #include "Cubed/camera.hpp"
#include "Cubed/dev_panel.hpp" #include "Cubed/dev_panel.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/renderer.hpp" #include "Cubed/renderer.hpp"
#include "Cubed/texture_manager.hpp" #include "Cubed/texture_manager.hpp"
#include "Cubed/window.hpp" #include "Cubed/window.hpp"
@@ -10,6 +12,13 @@ namespace Cubed {
class App { class App {
public: public:
struct Argument {
bool is_client = false;
int port = 25530;
std::string ip{"127.0.0.1"};
std::string player{"Unknown"};
};
App(); App();
~App(); ~App();
static void cursor_position_callback(GLFWwindow* window, double xpos, static void cursor_position_callback(GLFWwindow* window, double xpos,
@@ -36,14 +45,20 @@ public:
Renderer& renderer(); Renderer& renderer();
TextureManager& texture_manager(); TextureManager& texture_manager();
Window& window(); Window& window();
World& world(); ClientWorld& client_world();
ServerWorld& server_world();
const Argument& argument() const;
private: private:
Camera m_camera; Camera m_camera;
TextureManager m_texture_manager; 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}; 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}; Window m_window{m_renderer};
@@ -53,9 +68,10 @@ private:
inline static double fps_time_count = 0.0f; inline static double fps_time_count = 0.0f;
inline static int frame_count = 0; inline static int frame_count = 0;
inline static int fps = 0; inline static int fps = 0;
Argument m_argument;
void init(); void init(int argc, char** argv);
void handle_argument(int argc, char** argv);
void handle_toml();
auto init_camera(); auto init_camera();
auto init_texture(); auto init_texture();
auto init_world(); auto init_world();

View File

@@ -8,12 +8,12 @@
namespace Cubed { namespace Cubed {
class Player; class ClientPlayer;
class Camera { class Camera {
private: private:
bool m_firse_mouse = true; bool m_firse_mouse = true;
Player* m_player; ClientPlayer* m_player;
float m_last_mouse_x, m_last_mouse_y; float m_last_mouse_x, m_last_mouse_y;
glm::vec3 m_camera_pos; glm::vec3 m_camera_pos;
bool m_under_water = false; bool m_under_water = false;
@@ -23,7 +23,7 @@ public:
void update_move_camera(); void update_move_camera();
void camera_init(Player* player); void camera_init(ClientPlayer* player);
void hot_reload(); void hot_reload();
void reset_camera(); void reset_camera();
void update_cursor_position_camera(double xpos, double ypos); void update_cursor_position_camera(double xpos, double ypos);

View File

@@ -1,17 +1,9 @@
#pragma once #pragma once
#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/toml.utils.hpp"
#include <toml++/toml.hpp>
namespace Cubed { 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 { class Config {
public: public:
Config(); Config();
@@ -24,7 +16,7 @@ public:
void load_or_create_config(); void load_or_create_config();
void save_to_file(); 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; size_t cur = 0;
auto pos = key.find('.'); auto pos = key.find('.');
const toml::table* table = &m_tbl; const toml::table* table = &m_tbl;
@@ -61,7 +53,7 @@ public:
} }
} }
template <typename T> void set(std::string_view key, T&& val) { 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"); static_assert(false, "Type Not Support");
} }
size_t cur = 0; size_t cur = 0;

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include <array> #include <array>
namespace Cubed { namespace Cubed {
@@ -26,9 +25,9 @@ constexpr float DEFAULT_G = 22.5f;
constexpr int SIZE_X = CHUNK_SIZE; constexpr int SIZE_X = CHUNK_SIZE;
constexpr int SIZE_Y = WORLD_SIZE_Y; constexpr int SIZE_Y = WORLD_SIZE_Y;
constexpr int SIZE_Z = CHUNK_SIZE; constexpr int SIZE_Z = CHUNK_SIZE;
constexpr int RESERVED_THREADS = 5;
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, constexpr float DEFAULT_CAVE_PROBABILITY = 0.035f;
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
using HeightMapArray = std::array<std::array<int, CHUNK_SIZE>, CHUNK_SIZE>; using HeightMapArray = std::array<std::array<int, CHUNK_SIZE>, CHUNK_SIZE>;

View File

@@ -5,7 +5,7 @@
namespace Cubed { namespace Cubed {
class App; class App;
class Player; class ClientPlayer;
class DevPanel { class DevPanel {
struct ConfigView { struct ConfigView {
float fov = 70.0f; float fov = 70.0f;
@@ -26,9 +26,6 @@ class DevPanel {
int gait = 0; int gait = 0;
float pos[3] = {0.0f, 0.0f, 0.0f}; float pos[3] = {0.0f, 0.0f, 0.0f};
}; };
struct TextEditing {
bool perlin_seed = false;
};
public: public:
DevPanel(App& app); DevPanel(App& app);
@@ -38,9 +35,8 @@ public:
private: private:
App& m_app; App& m_app;
ConfigView m_config; ConfigView m_config;
Player* m_player; ClientPlayer* m_player;
PlayerProfile m_player_profile; PlayerProfile m_player_profile;
TextEditing m_text_editing;
bool m_need_save_config = false; bool m_need_save_config = false;
bool m_gen_thread_running = true; bool m_gen_thread_running = true;
int m_theme = 0; int m_theme = 0;
@@ -48,13 +44,18 @@ private:
int m_pre_set_tick_speed = 1; int m_pre_set_tick_speed = 1;
bool m_tick_frezze = false; bool m_tick_frezze = false;
int m_samples_idx = 1; int m_samples_idx = 1;
int m_threads = 1;
int m_chunk_style = 0;
void show_about_table_bar(); void show_about_table_bar();
void show_biome_table_bar(); void show_biome_table_bar();
void show_time_table_bar(); void show_time_table_bar();
void show_cave_table_bar(); void show_cave_table_bar();
void show_river_table_bar(); void show_river_table_bar();
void show_chunk_table_bar();
void show_settings_tab_item(); void show_settings_tab_item();
void show_world_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_player_tab_item();
void show_items_tab_item(); void show_items_tab_item();
void show_shader_tab_item(); void show_shader_tab_item();

View File

@@ -1,6 +1,9 @@
#pragma once #pragma once
#include "Cubed/tools/cubed_assert.hpp"
#include <array> #include <array>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
namespace Cubed { namespace Cubed {
@@ -77,4 +80,32 @@ DesertParams& desert_params();
MountainParams& mountain_params(); MountainParams& mountain_params();
RiverParams& river_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 } // namespace Cubed

View File

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

View File

@@ -1,27 +1,26 @@
#pragma once #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> #include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class CaveCarver { class CaveCarver {
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
public: public:
CaveCarver(); CaveCarver();
CaveHashMap& paths();
void init(unsigned world_seed); void init(unsigned world_seed);
void reload(unsigned world_seed); void reload(unsigned world_seed);
void add_path(const glm::vec3& pos, unsigned chunk_seed); bool has_origin_fast(const ChunkPos& pos) const;
void try_to_add_path(const ChunkPos& pos, unsigned chunk_seed); float cave_probability() const;
void cleanup_finished_caves(); PathOrigin get_origin(const ChunkPos& origin_chunk) const;
int search_radius() const;
int cave_sum() const; unsigned world_seed() const;
float& cave_probability();
private: private:
CaveHashMap m_paths; std::atomic<unsigned> m_world_seed{0};
unsigned m_seed = 0; std::atomic<float> m_cave_probability{DEFAULT_CAVE_PROBABILITY};
Random m_random;
float m_cave_probability = 0.035f;
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path_point.hpp" #include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
@@ -9,15 +8,11 @@
namespace Cubed { namespace Cubed {
class CavePath { class CavePath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public: public:
CavePath(unsigned int chunk_seed, unsigned world_seed, CavePath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos); const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const; 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_min();
static float& radius_xz_max(); static float& radius_xz_max();
@@ -27,6 +22,7 @@ public:
static float& delta_angle_max(); static float& delta_angle_max();
static int& step_min(); static int& step_min();
static int& step_max(); static int& step_max();
static int step_len();
private: private:
static inline float m_radius_xz_min = 5.0f; 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 float m_delta_angle_max = 5.0f;
static inline int m_step_min = 10; static inline int m_step_min = 10;
static inline int m_step_max = 400; static inline int m_step_max = 400;
static inline float m_step_len = 4.0f;
unsigned int m_seed = 0; unsigned int m_seed = 0;
float m_yaw = 0.0f; float m_yaw = 0.0f;
float m_pitch = 0.0f; float m_pitch = 0.0f;
int m_step = 0; 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}; PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f};
Random m_random; Random m_random;
std::vector<PathPoint> m_points; std::vector<PathPoint> m_points;
ChunkPosSet m_pending_chunks;
void collect_path_points(); void collect_path_points();
void precompute_chunk_coverage();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -4,7 +4,6 @@
#include "Cubed/gameplay/biome.hpp" #include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp" #include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/builders/biome_builder.hpp" #include "Cubed/gameplay/builders/biome_builder.hpp"
#include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
#include <atomic> #include <atomic>
@@ -12,11 +11,11 @@
#include <optional> #include <optional>
namespace Cubed { namespace Cubed {
class Chunk; class ServerChunk;
class ChunkGenerator { class ChunkGenerator {
public: public:
ChunkGenerator(Chunk& chunk); ChunkGenerator(ServerChunk& chunk);
static void init(); static void init();
static void reload(); static void reload();
@@ -27,7 +26,7 @@ public:
void assign_chunk_biome(); void assign_chunk_biome();
// Adjust Biome // Adjust Biome
void resolve_biome_adjacency_conflict( void resolve_biome_adjacency_conflict(
const std::array<const Chunk*, 8>& adj_chunks); const std::array<const ServerChunk*, 8>& adj_chunks);
// Generate Heightmap // Generate Heightmap
void generate_heightmap(); void generate_heightmap();
// Adjust Height // Adjust Height
@@ -43,7 +42,7 @@ public:
// Generate Structure // Generate Structure
void generate_vegetation(); void generate_vegetation();
BiomeType get_biome_at(float world_x, float world_z); BiomeType get_biome_at(float world_x, float world_z);
Chunk& chunk(); ServerChunk& chunk();
Random& random(); Random& random();
const std::array<BiomeType, 8>& neighbor_biome() const; const std::array<BiomeType, 8>& neighbor_biome() const;
void ocean_build(); void ocean_build();
@@ -54,7 +53,7 @@ private:
static inline std::atomic<bool> is_init{false}; static inline std::atomic<bool> is_init{false};
static inline unsigned m_generator_seed{0}; static inline unsigned m_generator_seed{0};
static inline std::atomic<bool> is_seed_change{false}; static inline std::atomic<bool> is_seed_change{false};
Chunk& m_chunk; ServerChunk& m_chunk;
Random m_random; Random m_random;
std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr}; std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr};
bool is_cur_chunk_ins = false; bool is_cur_chunk_ins = false;
@@ -62,9 +61,6 @@ private:
unsigned m_chunk_seed = 0; unsigned m_chunk_seed = 0;
void make_biome_builder(); 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 } // namespace Cubed

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "Cubed/constants.hpp"
#include <functional> #include <functional>
namespace Cubed { namespace Cubed {
@@ -34,5 +36,29 @@ struct ChunkPos {
return *this; 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 } // namespace Cubed

View File

@@ -1,65 +1,42 @@
#pragma once #pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp" #include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp" #include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/vertex_data.hpp" #include "Cubed/gameplay/vertex_data.hpp"
#include "world/chunk_data.pb.h"
#include <atomic> #include <atomic>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <mutex> #include <mutex>
namespace Cubed { namespace Cubed {
class ClientWorld;
class World; struct ChunkRenderSnapshot {
// if want to use, do init_chunk(), gen_vertex_data() and GLuint normal_vao;
class Chunk { size_t normal_vertices_count;
private: GLuint cross_vao;
using OptionalBlockVectorArray = size_t cross_vertices_count;
std::array<std::optional<std::vector<BlockType>>, 4>; GLuint normal_discard_vao;
static constexpr int SIZE_X = CHUNK_SIZE; size_t normal_discard_vertices_count;
static constexpr int SIZE_Y = WORLD_SIZE_Y; GLuint normal_blend_vao;
static constexpr int SIZE_Z = CHUNK_SIZE; size_t normal_blend_vertices_count;
static constexpr int VERTEX_DATA_SUM = 4; GLuint water_vao;
std::atomic<bool> m_dirty{false}; size_t water_vertices_count;
std::atomic<bool> m_need_upload{true}; glm::vec3 center;
std::atomic<bool> m_is_on_gen_vertex_data{false}; glm::vec3 half_extents;
std::atomic<BiomeType> m_biome = BiomeType::PLAIN; };
std::mutex m_vertexs_data_mutex; class ClientChunk {
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
*/
std::vector<VertexData> m_vertex_data;
float frequency = 0.01f;
float height = 80;
unsigned m_seed = 0;
BiomeConditions m_conditions;
void clear_dirty();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id);
public: public:
Chunk(World& world, ChunkPos chunk_pos); ClientChunk(ClientWorld& world);
~Chunk(); ~ClientChunk();
Chunk(const Chunk&) = delete; ClientChunk(const ClientChunk&) = delete;
Chunk& operator=(const Chunk&) = delete; ClientChunk& operator=(const ClientChunk&) = delete;
Chunk(Chunk&&) noexcept; ClientChunk(ClientChunk&&) noexcept;
Chunk& operator=(Chunk&&) 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, static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x, int world_z, int chunk_x,
int chunk_z); int chunk_z);
@@ -72,33 +49,9 @@ public:
BiomeType get_biome() const; BiomeType get_biome() const;
ChunkPos get_chunk_pos() const; ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& get_chunk_blocks() const; const std::vector<BlockType>& get_chunk_blocks() const;
HeightMapArray get_heightmap() const; void receive_chunk(const ChunkDataRsp& data);
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
// Init Chunk
// Determine biome from temperature and humidity noise
void gen_phase_one();
// Resolve biome adjacency conflicts with neighbor chunks
void gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks);
// Generate heightmap using biome-specific noise
void gen_phase_three();
// Blend heightmap with neighbors for smooth transitions
void gen_phase_four(
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& neighbor_biome);
// Generate terrain blocks from heightmap and biome
void gen_phase_five();
// Blend surface blocks at chunk borders with neighbors
void gen_phase_six(const std::array<std::optional<std::vector<BlockType>>,
4>& neighbor_block);
// Generate biome-specific vegetation/structures
void gen_phase_seven();
// void gen_vertex_data();
// 0 : (1, 0)
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block); void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
// Can only be called on the render thread
void upload_to_gpu(); void upload_to_gpu();
GLuint get_normal_vao() const; GLuint get_normal_vao() const;
@@ -113,6 +66,9 @@ public:
GLuint get_normal_blend_vao() const; GLuint get_normal_blend_vao() const;
size_t get_normal_blend_vertices_sum() const; size_t get_normal_blend_vertices_sum() const;
GLuint get_water_vao() const;
size_t get_water_vertices_sum() const;
bool is_dirty() const; bool is_dirty() const;
void mark_dirty(); void mark_dirty();
@@ -120,15 +76,58 @@ public:
void need_upload(); void need_upload();
void set_chunk_block(int index, unsigned id); void set_chunk_block(int index, unsigned id);
bool is_temp_chunk() const;
ChunkPos chunk_pos() const; ChunkPos chunk_pos() const;
BiomeType biome() const; BiomeType biome() const;
void biome(BiomeType b); void biome(BiomeType b);
HeightMapArray& heightmap();
std::vector<BlockType>& blocks(); std::vector<BlockType>& blocks();
World& world(); ClientWorld& world();
unsigned seed() const; 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 } // namespace Cubed

View File

@@ -6,82 +6,29 @@
#include "Cubed/gameplay/game_mode.hpp" #include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/input.hpp" #include "Cubed/input.hpp"
#include <absl/container/flat_hash_set.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <optional> #include <optional>
#include <string> #include <shared_mutex>
namespace Cubed { namespace Cubed {
enum class Gait { WALK = 0, RUN }; enum class Gait { WALK = 0, RUN };
class ClientWorld;
class World; class ClientPlayer {
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: public:
Player(World& world, const std::string& name); using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
~Player(); ClientPlayer(ClientWorld& world);
AABB get_aabb() const; ~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; const glm::vec3& get_front() const;
const Gait& get_gait() const; const Gait& get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const; const std::optional<LookBlock>& get_look_block_pos() const;
const glm::vec3& get_player_pos() const; // thread safe
glm::vec3 get_player_pos() const;
const MoveState& get_move_state() const; const MoveState& get_move_state() const;
void change_mode(GameMode mode); void change_mode(GameMode mode);
@@ -99,12 +46,80 @@ public:
float& acceleration(); float& acceleration();
float& deceleration(); float& deceleration();
float& g(); float& g();
float& fly_y_speed();
unsigned place_block() const; unsigned place_block() const;
Gait& gait(); Gait& gait();
GameMode& game_mode(); GameMode& game_mode();
const World& get_world() const;
};
const ClientWorld& get_world() const;
void set_uuid(std::string_view uuid);
const std::string& get_uuid() const;
const std::string& get_name() const;
void init(std::string_view name);
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;
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;
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};
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{};
std::string m_uuid;
ClientWorld& m_world;
mutable std::shared_mutex m_player_pos_mutex;
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
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();
};
} // namespace Cubed } // namespace Cubed

View File

@@ -0,0 +1,138 @@
#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 RemotePlayerInfo {
std::string name;
glm::vec3 render_pos;
glm::vec3 target_pos;
};
struct RemotePlayerRenderData {
std::string name;
glm::vec3 render_pos;
};
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();
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<RemotePlayerRenderData>& render_player_data() const;
glm::vec3 sunlight_dir() 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, RemotePlayerInfo>;
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_other_players;
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_other_players_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<RemotePlayerRenderData> 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 set_player_pos();
void report_player_pos();
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 #pragma once
// Prevent unsigned underflow issues in subtraction // Prevent unsigned underflow issues in subtraction
#include "Cubed/tools/cubed_assert.hpp"
#include <functional>
#include <utility>
namespace Cubed {
using TickType = long long; using TickType = long long;
constexpr int DEFAULT_PER_TICK_TIME = 50; constexpr int DEFAULT_PER_TICK_TIME = 50;
constexpr TickType DAY_TIME = 24000; 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,
PLAYER_POS = 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<PlayerPos>() {
return std::to_underlying(PacketEnum::PLAYER_POS);
}
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,6 +1,5 @@
#pragma once #pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/path_point.hpp" #include "Cubed/gameplay/path_point.hpp"
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
@@ -9,15 +8,11 @@
namespace Cubed { namespace Cubed {
class RiverPath { class RiverPath {
using ChunkPosSet =
tbb::concurrent_hash_map<ChunkPos, bool, ChunkPos::TBBHash>;
public: public:
RiverPath(unsigned int chunk_seed, unsigned world_seed, RiverPath(unsigned int chunk_seed, unsigned world_seed,
const glm::vec3& start_pos); const glm::vec3& start_pos);
const std::vector<PathPoint>& points() const; 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_min();
static float& radius_xz_max(); static float& radius_xz_max();
@@ -27,6 +22,7 @@ public:
static float& delta_angle_max(); static float& delta_angle_max();
static int& step_min(); static int& step_min();
static int& step_max(); static int& step_max();
static float step_len();
private: private:
static inline float m_radius_xz_min = 5.0f; 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 float m_delta_angle_max = 3.0f;
static inline int m_step_min = 200; static inline int m_step_min = 200;
static inline int m_step_max = 400; static inline int m_step_max = 400;
static inline float m_step_len = 4.0f;
unsigned int m_seed = 0; unsigned int m_seed = 0;
float m_yaw = 0.0f; float m_yaw = 0.0f;
float m_initial_yaw = 0.0f; float m_initial_yaw = 0.0f;
float m_pitch = 0.0f; float m_pitch = 0.0f;
int m_step = 0; 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}; PathPoint m_start_path_point{{0.0f, 0.0f, 0.0f}, 0.0f, 0.0f};
Random m_random; Random m_random;
std::vector<PathPoint> m_points; std::vector<PathPoint> m_points;
ChunkPosSet m_pending_chunks;
void collect_path_points(); void collect_path_points();
void precompute_chunk_coverage();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -1,32 +1,30 @@
#pragma once #pragma once
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/river.path.hpp" #include "Cubed/gameplay/path.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <tbb/concurrent_hash_map.h> #include <tbb/concurrent_hash_map.h>
namespace Cubed { namespace Cubed {
class RiverWorm { class RiverWorm {
using RiverHashMap = tbb::concurrent_hash_map<unsigned, RiverPath>;
public: public:
RiverWorm(); RiverWorm();
RiverHashMap& paths(); ~RiverWorm();
void init(unsigned world_seed); void init(unsigned world_seed);
void reload(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; PathOrigin get_origin(const ChunkPos& origin_chunk) const;
float& river_probability(); int search_radius() const;
unsigned world_seed() const;
float river_probability() const;
bool has_origin_fast(const ChunkPos& pos) const;
private: private:
RiverHashMap m_paths; std::atomic<unsigned> m_world_seed{0};
unsigned m_seed = 0; std::atomic<float> m_probability{0.01f};
Random m_random;
float m_probability = 0.01f;
}; };
}; // namespace Cubed }; // 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,54 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.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();
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::shared_ptr<Session> m_session;
std::atomic<TickType> m_last_gametick{0};
std::atomic<int> m_chunk_task_id{0};
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
};
} // namespace Cubed

View File

@@ -0,0 +1,187 @@
#pragma once
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp"
#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 std::string& uuid, float x, float y, float z);
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<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 { namespace Cubed {
class Chunk; class ServerChunk;
struct TreeStructNode { struct TreeStructNode {
glm::ivec3 offset{0, 0, 0}; glm::ivec3 offset{0, 0, 0};
unsigned id = 0; unsigned id = 0;
}; };
bool build_tree(Chunk& chunk, const glm::ivec3& pos); bool build_tree(ServerChunk& chunk, const glm::ivec3& pos);
} // namespace Cubed } // namespace Cubed

View File

@@ -5,14 +5,14 @@
#include <glad/glad.h> #include <glad/glad.h>
#include <vector> #include <vector>
namespace Cubed { namespace Cubed {
class World; class ClientWorld;
struct VertexData { struct VertexData {
std::vector<Vertex3D> m_vertices; std::vector<Vertex3D> m_vertices;
GLuint m_vbo = 0; GLuint m_vbo = 0;
GLuint m_vao = 0; GLuint m_vao = 0;
std::atomic<std::size_t> m_sum{0}; std::atomic<std::size_t> m_sum{0};
World& m_world; ClientWorld& m_world;
VertexData(World& world); VertexData(ClientWorld& world);
~VertexData(); ~VertexData();
VertexData(const VertexData&) = delete; VertexData(const VertexData&) = delete;
VertexData(VertexData&&) noexcept; VertexData(VertexData&&) noexcept;

View File

@@ -1,157 +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;
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

@@ -63,12 +63,12 @@ constexpr float TEX_COORDS[6][6][2] = {
{0.0f, 0.0f}, // top front {0.0f, 0.0f}, // top front
{0.0f, 1.0f}}, // bottom front {0.0f, 1.0f}}, // bottom front
// ===== back (z = -1) ===== // ===== back (z = -1) =====
{{1.0f, 1.0f}, // bottom left {{0.0f, 1.0f}, // bottom left
{0.0f, 1.0f}, // bottom right {0.0f, 0.0f}, // top left
{0.0f, 0.0f}, // top right {1.0f, 0.0f}, // top right
{0.0f, 0.0f}, // top right {1.0f, 0.0f}, // top right
{1.0f, 0.0f}, // top left {1.0f, 1.0f}, // bottom right
{1.0f, 1.0f}}, // bottom left {0.0f, 1.0f}}, // bottom left
// ===== left (x = -1) ===== // ===== left (x = -1) =====
{{1.0f, 1.0f}, // bottom back {{1.0f, 1.0f}, // bottom back
{0.0f, 1.0f}, // bottom front {0.0f, 1.0f}, // bottom front
@@ -136,6 +136,50 @@ constexpr float NORMALS[6][6][3] = {
{0.0f, -1.0f, 0.0f}, {0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f}}}; {0.0f, -1.0f, 0.0f}}};
constexpr float TANGENTS[6][6][3] = {
// ===== front (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}},
// ===== right (x = +1) =====
{{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f}},
// ===== 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}},
// ===== left (x = -1) =====
{{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f}},
// ===== top (y = +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}},
// ===== bottom (y = -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}}};
#pragma endregion #pragma endregion
constexpr float CUBE_VER[24] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, constexpr float CUBE_VER[24] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0,
@@ -211,8 +255,73 @@ constexpr float CROSS_NORMALS[2][6][3] = {
{0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}}}; {0.0f, 1.0f, 0.0f}}};
constexpr float CROSS_TANGENTS[2][6][3] = {
// ===== Plane 1 =====
{{0.7071f, 0.0f, 0.7071f},
{0.7071f, 0.0f, 0.7071f},
{0.7071f, 0.0f, 0.7071f},
{0.7071f, 0.0f, 0.7071f},
{0.7071f, 0.0f, 0.7071f},
{0.7071f, 0.0f, 0.7071f}},
// ===== Plane 2 =====
{{-0.7071f, 0.0f, 0.7071f},
{-0.7071f, 0.0f, 0.7071f},
{-0.7071f, 0.0f, 0.7071f},
{-0.7071f, 0.0f, 0.7071f},
{-0.7071f, 0.0f, 0.7071f},
{-0.7071f, 0.0f, 0.7071f}}};
#pragma endregion #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[] = { constexpr float QUAD_VERTICES[] = {
// postion // texcoorlds // postion // texcoorlds
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f,
@@ -225,6 +334,7 @@ struct Vertex3D {
float layer = 0.0f; float layer = 0.0f;
float nx = 0.0f, ny = 0.0f, nz = 0.0f; float nx = 0.0f, ny = 0.0f, nz = 0.0f;
float roughness = 1.0f; float roughness = 1.0f;
float tx = 0.0f, ty = 0.0f, tz = 0.0f;
}; };
struct Vertex2D { struct Vertex2D {

View File

@@ -11,13 +11,13 @@ namespace Cubed {
class Camera; class Camera;
class TextureManager; class TextureManager;
class World; class ClientWorld;
class DevPanel; class DevPanel;
class Renderer { class Renderer {
public: public:
constexpr static int NUM_VAO = 7; 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); const TextureManager& texture_manager, DevPanel& dev_panel);
~Renderer(); ~Renderer();
void hot_reload(); void hot_reload();
@@ -32,6 +32,10 @@ public:
bool& discard_transparent(); bool& discard_transparent();
bool& shader_on(); bool& shader_on();
bool& water_perturb();
bool& water_depth_fade();
bool& pbr();
bool& flip_y();
int& shadow_mode(); int& shadow_mode();
int& light_cull_face(); int& light_cull_face();
int& light_size_uv(); int& light_size_uv();
@@ -42,10 +46,13 @@ public:
float& cloud_speed(); float& cloud_speed();
float& cloud_threshold_low(); float& cloud_threshold_low();
float& cloud_threshold_high(); float& cloud_threshold_high();
float& refract_strength();
float& underwater_fog_density();
float& water_density();
private: private:
struct ParallelLight { struct ParallelLight {
glm::vec3 sundir; glm::vec3 sundir; // direction from sun to vertex
glm::vec3 lightdir; glm::vec3 lightdir;
float sun_height = 0.0f; float sun_height = 0.0f;
float day_light = 0.0f; float day_light = 0.0f;
@@ -53,6 +60,15 @@ private:
glm::vec3 sun_color; glm::vec3 sun_color;
glm::vec3 directional_light_color; glm::vec3 directional_light_color;
glm::vec3 finnal_ambient_color; glm::vec3 finnal_ambient_color;
glm::mat4 light_space_matrix;
};
struct SkyUniform {
glm::vec3 sky_top;
glm::vec3 sky_bottom;
glm::vec3 sun_dir_view;
float horizon_sharpness;
float cloud_white_mix;
}; };
static constexpr glm::vec3 SUN_COLOR{1.00f, 0.95f, 0.80f}; static constexpr glm::vec3 SUN_COLOR{1.00f, 0.95f, 0.80f};
@@ -75,10 +91,17 @@ private:
const Camera& m_camera; const Camera& m_camera;
DevPanel& m_dev_panel; DevPanel& m_dev_panel;
const TextureManager& m_texture_manager; const TextureManager& m_texture_manager;
World& m_world; ClientWorld& m_world;
bool m_discard_tranparent = true; bool m_discard_tranparent = true;
bool m_shader_on = 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_shadow_mode = 0;
int m_light_cull_face = 0; int m_light_cull_face = 0;
float m_aspect = 0.0f; float m_aspect = 0.0f;
@@ -94,23 +117,20 @@ private:
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat, m_norm_mat; glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat, m_norm_mat;
GLuint m_mv_loc = 0;
GLuint m_proj_loc = 0;
GLuint m_sky_vbo = 0; GLuint m_sky_vbo = 0;
GLuint m_text_vbo = 0; GLuint m_text_vbo = 0;
GLuint m_outline_indices_vbo = 0; GLuint m_outline_indices_vbo = 0;
GLuint m_outline_vbo = 0; GLuint m_outline_vbo = 0;
GLuint m_ui_vbo = 0; GLuint m_ui_vbo = 0;
GLuint m_player_vbo = 0;
GLuint m_fbo = 0; GLuint m_fbo = 0;
GLuint m_screen_texture = 0; GLuint m_screen_texture = 0;
GLuint m_depth_render_buffer = 0; GLuint m_screen_depth_texture = 0;
GLuint m_oit_fbo = 0; GLuint m_oit_fbo = 0;
GLuint m_accum_texture = 0; GLuint m_accum_texture = 0;
GLuint m_reveal_texture = 0; GLuint m_reveal_texture = 0;
GLuint m_oit_depth_render_buffer = 0; GLuint m_oit_depth_texture = 0;
GLuint m_depth_map_fbo = 0; GLuint m_depth_map_fbo = 0;
GLuint m_depth_map_texture = 0; GLuint m_depth_map_texture = 0;
@@ -140,14 +160,21 @@ private:
float m_cloud_threshold_low = 0.5f; float m_cloud_threshold_low = 0.5f;
float m_cloud_threshold_high = 0.75f; float m_cloud_threshold_high = 0.75f;
float m_refract_strength = 0.03f;
float m_underwater_fog_density = 0.08f;
float m_water_density = 0.12f;
ParallelLight m_parallel_light; ParallelLight m_parallel_light;
SkyUniform m_sky_uniform;
/* /*
0 - quad vao 0 - quad vao
1 - sky vao 1 - sky vao
2 - outline vao 2 - outline vao
3 - ui vao 3 - ui vao
4 - text vao 4 - text vao
5 - player vao
*/ */
std::vector<GLuint> m_vao; std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui; std::vector<Vertex2D> m_ui;
@@ -162,6 +189,7 @@ private:
void render_text(); void render_text();
void render_ui(); void render_ui();
void render_world(); void render_world();
void render_player();
void render_underwater(); void render_underwater();
void render_dev_panel(); void render_dev_panel();

View File

@@ -1,4 +1,7 @@
#pragma once #pragma once
#include "glm/ext/vector_float3.hpp"
#include "glm/gtc/type_ptr.hpp"
#include <glad/glad.h> #include <glad/glad.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -21,6 +24,26 @@ public:
GLuint loc(const std::string& loc) const; GLuint loc(const std::string& loc) const;
const std::string& name() const; const std::string& name() const;
void use() const; void use() const;
template <typename> struct always_false : std::false_type {}; // NOLINT
template <typename T>
void set_loc(const std::string& location, T&& value) const {
using std::is_same_v;
using dT = std::decay_t<T>;
if constexpr (is_same_v<dT, int> || is_same_v<dT, bool>) {
glUniform1i(loc(location), value);
} else if constexpr (is_same_v<dT, float>) {
glUniform1f(loc(location), value);
} else if constexpr (is_same_v<dT, double>) {
glUniform1f(loc(location), static_cast<float>(value));
} else if constexpr (is_same_v<dT, glm::vec3>) {
glUniform3fv(loc(location), 1, glm::value_ptr(value));
} else if constexpr (is_same_v<dT, glm::mat4>) {
glUniformMatrix4fv(loc(location), 1, GL_FALSE,
glm::value_ptr(value));
} else {
static_assert(always_false<dT>::value, "Unknown Type");
}
};
private: private:
GLuint m_program = 0; GLuint m_program = 0;

View File

@@ -8,11 +8,14 @@ namespace Cubed {
class TextureManager { class TextureManager {
private: private:
bool m_need_reload = false; bool m_need_reload = false;
bool m_init = false;
GLuint m_block_status_array = 0; GLuint m_block_status_array = 0;
GLuint m_texture_array = 0; GLuint m_texture_array = 0;
GLuint m_cross_plane_array = 0; GLuint m_cross_plane_array = 0;
GLuint m_ui_array = 0; GLuint m_ui_array = 0;
GLuint m_normal_texture_array = 0;
GLfloat m_max_aniso = 0.0f; GLfloat m_max_aniso = 0.0f;
int m_aniso = 1; int m_aniso = 1;
std::vector<GLuint> m_item_textures; std::vector<GLuint> m_item_textures;
@@ -22,10 +25,12 @@ private:
void load_block_item_texture(unsigned id); void load_block_item_texture(unsigned id);
void load_cross_plane_texture(unsigned id); void load_cross_plane_texture(unsigned id);
void load_ui_texture(unsigned id); void load_ui_texture(unsigned id);
void load_pbr_texture(unsigned id);
void init_item(); void init_item();
void init_block(); void init_block();
void init_ui(); void init_ui();
void init_block_status(); void init_block_status();
void hot_reload();
public: public:
TextureManager(); TextureManager();
@@ -36,10 +41,11 @@ public:
GLuint get_texture_array() const; GLuint get_texture_array() const;
GLuint get_cross_plane_array() const; GLuint get_cross_plane_array() const;
GLuint get_ui_array() const; GLuint get_ui_array() const;
GLuint get_pbr_texture() const;
const std::vector<GLuint>& item_textures() const; const std::vector<GLuint>& item_textures() const;
// Must call after MapTable::init_map() and glfwMakeContextCurrent(window); // Must call after MapTable::init_map() and glfwMakeContextCurrent(window);
void init_texture(); void init_texture();
void hot_reload();
void need_reload(); void need_reload();
void update(); void update();
int max_aniso() const; 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 Cubed {
namespace Logger { 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> template <typename... Args>
inline void info(std::format_string<Args...> fmt, Args&&... 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::chrono::system_clock::now());
std::string msg = std::vformat(fmt.get(), std::make_format_args(args...)); std::string msg = std::vformat(fmt.get(), std::make_format_args(args...));
switch (level) { switch (level) {
case Logger::Level::TRACE: case Logger::Level::L_TRACE:
std::osyncstream(std::cout) std::osyncstream(std::cout)
<< "\033[1;34m" << "\033[1;34m"
<< std::format("[TRACE][{:%Y-%m-%d %H:%M:%S}]", now_time) << "[" << 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" << "[" << loc.function_name() << "]" << msg << "\033[0m"
<< "\n"; << "\n";
break; break;
case Logger::Level::DEBUG: case Logger::Level::L_DEBUG:
std::osyncstream(std::cout) std::osyncstream(std::cout)
<< "\033[1;34m" << "\033[1;34m"
<< std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg << std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg
<< "\033[0m" << "\033[0m"
<< "\n"; << "\n";
break; break;
case Logger::Level::INFO: case Logger::Level::L_INFO:
info(fmt, std::forward<Args>(args)...); info(fmt, std::forward<Args>(args)...);
break; break;
case Logger::Level::WARN: case Logger::Level::L_WARN:
warn(fmt, std::forward<Args>(args)...); warn(fmt, std::forward<Args>(args)...);
break; break;
case Logger::Level::ERROR: case Logger::Level::L_ERROR:
error(fmt, std::forward<Args>(args)...); error(fmt, std::forward<Args>(args)...);
break; break;
} }

View File

@@ -1,18 +1,108 @@
#pragma once #pragma once
#include <algorithm>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
namespace Cubed { namespace Cubed {
namespace Math { namespace Math {
void extract_frustum_planes(const glm::mat4& mvp_matrix, inline void extract_frustum_planes(const glm::mat4& mvp_matrix,
std::vector<glm::vec4>& planes); std::vector<glm::vec4>& planes) {
if (planes.size() != 6) {
planes.resize(6);
}
float smootherstep(float edge0, float edge1, float x); const float* m = glm::value_ptr(mvp_matrix);
bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
const std::vector<glm::vec4>& planes); // left plane
float deterministic_random(int x, int z, uint64_t seed); planes[0] =
glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t); 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 } // 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

@@ -14,7 +14,8 @@ void print_program_info(int prog);
bool check_opengl_error(); bool check_opengl_error();
std::string read_shader_source(const std::string& file_path); std::string read_shader_source(const std::string& file_path);
void delete_image_data(unsigned char* data); void delete_image_data(unsigned char* data);
unsigned char* load_image_data(const std::string& tex_image_path); unsigned char* load_image_data(const std::string& tex_image_path,
bool check_exist = true);
} // namespace Tools } // namespace Tools

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: private:
bool m_mouse_enable = false; bool m_mouse_enable = false;
bool m_imgui_init = false;
float m_aspect; float m_aspect;
GLFWwindow* m_window; GLFWwindow* m_window;
int m_width; int m_width;

View File

@@ -4,4 +4,5 @@ leak:libpangocairo
leak:libdecor-gtk.so leak:libdecor-gtk.so
leak:libgtk-3.so leak:libgtk-3.so
leak:libwayland-client.so leak:libwayland-client.so
leak:libglfw.so leak:libglfw.so
leak:libEGL_nvidia.so

View File

@@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">=3.14" requires-python = ">=3.14"
dependencies = [ dependencies = [
"loguru>=0.7.3", "loguru>=0.7.3",
"pillow>=12.2.0",
"pytomlpp>=1.1.0", "pytomlpp>=1.1.0",
] ]

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