8 Commits

Author SHA1 Message Date
zhenyan121
b1341d7da4 feat: sound (#28)
* build: add OpenAL dependency

* feat(deps): add dr_libs audio library

* feat(audio): integrate OpenAL audio engine with WAV/MP3/FLAC support

* feat: add bgm001

* feat(audio): add audio fade in/out and fix delta time naming

* fix(audio-source): improve state, volume, and const correctness

* feat(audio): add 3D audio playback with source pool and auto-loading

* feat: add ogg loader

* feat(audio): add footstep sounds and fix audio listener orientation

* feat(sound): add block place and break sounds

* feat(audio): add per-channel volume control and config reload

* fix(audio): set distance model, reduce max distance, fix sound pos

- Set OpenAL distance model to AL_INVERSE_DISTANCE_CLAMPED in engine.
- Reduced AL_MAX_DISTANCE from 100 to 48 for better falloff.
- Fixed block sound position to use world coordinates instead of local block coordinates.

* refactor(game_time): introduce Timer for time-based updates and rename existing to TickTimer

Add a new `Timer` class that operates in seconds (using `TimeType`) alongside the existing `TickTimer` which uses `TickType`. Rename the original `Timer` to `TickTimer` throughout the codebase. Update `ClientWorld` to maintain separate maps for tick-based and time-based timers, and replace the hardcoded footstep timer in `ClientPlayer` with a time-based callback timer. Also add `play_2d` to `AudioEngine` and load an ambient bird sound to demonstrate the new timer functionality.

* feat(gameplay): add ocean wave ambient sounds with random selection

Add four ocean wave FLAC sound files and play one randomly when player is within 10 blocks of sea level and in an ocean biome. Include a Random member initialized from ChunkGenerator seed for consistent randomization.

* feat(gameplay): detect underwater state and play transition/bubble sounds

- Add is_underwater() and set_underwater() to ClientPlayer.
- Change get_world() to return non-const reference for audio access.
- Update Camera::update_move_camera() to detect water block changes and trigger transition sound.
- Add repeating timer in ClientWorld to play random bubble ambient sounds while underwater.
- Include new ambient sound assets for water transitions and bubbles.
- Also add sand walk sound asset (unused in this commit).

* feat(assets): add and update block break/place/walk sounds

Add sounds for dirt, grass_block, leaf, log, sand, snowy_grass_block, stone.
Update existing leaf/walk, sand/place, sand/walk, and stone/place sounds.

* feat(gameplay): add walking sound for players and adjust underwater bubble interval

- Move walk/run sound interval constants from anonymous namespace to ClientPlayer header
- Add moving_time field to PlayerInfo to track continuous movement
- Implement play_walk_sound callback triggered by WALK and RUN gaits
- Increase underwater bubble timer from 1.0s to 1.5s

* feat(asset): add grass block break and place sound effects

* feat(renderer): add --no-debug flag to disable OpenGL debug output

* feat(audio): add day/night-based BGM switching

* feat(audio): add underwater low-pass filter effect

* feat(audio): add pitch control to audio sources

Implement set_pitch method that clamps pitch between 0.0 and 1.0 and applies it via AL_PITCH. Includes minor whitespace cleanup in audio_engine.cpp.

* feat(audio): implement reverb effect and integrate into underwater system

* asset(sound): update dirt and grass block break/place sounds

* feat(network): synchronize player water sound

* fix: include <numbers>

* fix(audio): play ambient water bubble sound in 3D at player position
2026-07-08 19:41:10 +08:00
zhenyan121
8a4081cf9c feat: player rendering (#27)
* refactor(player): extract player rendering into PlayerRenderer class

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


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

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

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

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

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

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

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

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

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

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

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

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

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

* feat: add camera collision and sphere collision for world

* fix(skin): update player001 texture

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

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

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

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

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

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

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

* feat(dev_panel): add Asio to about table

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* refactor(server): replace Chunk with ServerChunk

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

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

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

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

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

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

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

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

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

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

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

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

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

* build: add ZLIB dependency

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* feat: add player name configuration and rename config file

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

* build(cmake): modularize build configuration

* fix: build fail on windows

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

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

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

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

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

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

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

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

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

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

* fix: address unused parameter warnings and missing port assignment

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* feat(tools): add PriorityThreadPool with priority scheduling

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix(gameplay): use gen_phase_one to get seed

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

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

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

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

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

* feat(world): add dynamic thread pool resizing

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

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

* feat(player): add configurable fly Y speed

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

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

* fix: include shared_mutex header

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

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

View File

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

154
.github/workflows/release-build.yml vendored Normal file
View File

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

3
.gitignore vendored
View File

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

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.14...3.24)
cmake_minimum_required(VERSION 3.21)
project(Cubed LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
@@ -6,167 +6,120 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
if(NOT CMAKE_CONFIGURATION_TYPES
AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()
if(NOT DEFINED CUBED_VERSION)
set(CUBED_VERSION "dev")
endif()
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
if(MSVC)
add_compile_options(/utf-8)
endif()
find_package(OpenGL REQUIRED)
if (UNIX AND NOT APPLE)
find_package(Freetype REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(EGL REQUIRED egl)
pkg_check_modules(Wayland REQUIRED wayland-client wayland-egl)
find_package(glfw3 REQUIRED)
endif()
add_library(glad STATIC third_party/glad/src/glad.c)
target_include_directories(glad PUBLIC third_party/glad/include)
include(FetchContent)
if (WIN32)
FetchContent_Declare(
glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
set(GLFW_VULKAN_STATIC ON CACHE BOOL "" FORCE)
set(GLFW_STATIC ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(glfw)
FetchContent_Declare(
freetype
GIT_REPOSITORY https://gitlab.freedesktop.org/freetype/freetype.git
GIT_TAG VER-2-14-3
)
FetchContent_MakeAvailable(freetype)
if(TARGET freetype)
add_library(Freetype::Freetype ALIAS freetype)
endif()
set(_BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS ON)
FetchContent_Declare(
onetbb
GIT_REPOSITORY https://github.com/uxlfoundation/oneTBB.git
GIT_TAG v2023.0.0
)
set(BUILD_TESTING OFF CACHE BOOL "Build tests" FORCE)
set(TBB_TEST OFF CACHE BOOL "Build TBB tests" FORCE)
FetchContent_MakeAvailable(onetbb)
set(BUILD_SHARED_LIBS ${_BUILD_SHARED_LIBS_SAVED})
unset(_BUILD_SHARED_LIBS_SAVED)
endif()
FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG 1.0.3
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
)
FetchContent_MakeAvailable(glm)
FetchContent_Declare(
soil2
GIT_REPOSITORY https://github.com/SpartanJ/SOIL2.git
GIT_TAG 1.31
)
FetchContent_MakeAvailable(soil2)
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG v3.4.0
)
FetchContent_MakeAvailable(tomlplusplus)
include(Dependencies)
add_subdirectory(third_party/glad)
add_subdirectory(third_party/imgui)
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
add_subdirectory(third_party/dr_libs)
add_executable(${PROJECT_NAME}
src/main.cpp
src/app.cpp
src/debug_collector.cpp
src/camera.cpp
src/config.cpp
src/dev_panel.cpp
src/gameplay/biome.cpp
src/gameplay/chunk.cpp
src/gameplay/chunk_generator.cpp
src/gameplay/player.cpp
src/gameplay/tree.cpp
src/gameplay/world.cpp
src/input.cpp
src/map_table.cpp
src/renderer.cpp
src/shader.cpp
src/texture_manager.cpp
src/tools/cubed_random.cpp
src/tools/math_tools.cpp
src/tools/shader_tools.cpp
src/tools/font.cpp
src/tools/perlin_noise.cpp
src/ui/text.cpp
src/window.cpp
src/gameplay/builders/biome_builder.cpp
src/gameplay/builders/plain_builder.cpp
src/gameplay/builders/mountain_builder.cpp
src/gameplay/builders/river_builder.cpp
src/gameplay/builders/desert_builder.cpp
src/gameplay/builders/forest_builder.cpp
src/gameplay/cave_carver.cpp
src/gameplay/cave_path.cpp
src/gameplay/builders/snowy_plain_builder.cpp
src/gameplay/river_worm.cpp
src/gameplay/river_path.cpp
src/block.cpp
src/gameplay/vertex_data.cpp
src/gameplay/builders/ocean_builder.cpp
add_executable(${PROJECT_NAME})
add_subdirectory(src)
file(GLOB_RECURSE PROTO_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/*.proto
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
protobuf_generate(
TARGET ${PROJECT_NAME}
LANGUAGE cpp
PROTOS ${PROTO_FILES}
IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src/proto
)
target_compile_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
-fno-omit-frame-pointer
-g
)
configure_file(
src/version.hpp.in
${CMAKE_BINARY_DIR}/generated/version.hpp
@ONLY
)
target_link_options(${PROJECT_NAME} PRIVATE
#-fsanitize=address
#-fsanitize=thread
)
target_compile_options(${PROJECT_NAME}
PRIVATE
$<$<CONFIG:Debug>:-fno-omit-frame-pointer>
$<$<CONFIG:Debug>:-g>
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_MODE)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="${CMAKE_SOURCE_DIR}/assets/"
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wextra>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
target_link_options(${PROJECT_NAME}
PRIVATE
#$<$<CONFIG:Debug>:-fsanitize=address>
#$<$<CONFIG:Debug>:-fsanitize=thread>
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASIO_STANDALONE
ASIO_NO_DEPRECATED
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CXX_COMPILER_ID:MSVC>:
WIN32_LEAN_AND_MEAN
NOMINMAX
_CRT_SECURE_NO_WARNINGS
>
)
if(CMAKE_CONFIGURATION_TYPES)
# Visual Studio / Xcode multi-configuration generator
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASSETS_PATH="$<$<CONFIG:Debug>:${PROJECT_SOURCE_DIR}/assets/>$<$<NOT:$<CONFIG:Debug>>:./assets/>"
)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_PATH="./assets/"
)
# Ninja / Makefiles single-configuration generator
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASSETS_PATH="${PROJECT_SOURCE_DIR}/assets/"
)
else()
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ASSETS_PATH="./assets/"
)
endif()
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIR})
target_include_directories(${PROJECT_NAME}
PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/third_party/asio/include
${CMAKE_BINARY_DIR}/generated
${PROJECT_BINARY_DIR}
${PROJECT_BINARY_DIR}/src
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
@@ -178,43 +131,27 @@ target_link_libraries(${PROJECT_NAME}
Freetype::Freetype
tomlplusplus::tomlplusplus
imgui
dr_libs
tbb
protobuf::libprotobuf
absl::log
absl::check
absl::base
absl::strings
absl::flat_hash_map
zstd::zstd
OpenAL::OpenAL
$<$<PLATFORM_ID:Windows>:ws2_32>
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# target_link_libraries(${PROJECT_NAME} PRIVATE tbb)
endif()
if (UNIX AND NOT APPLE)
target_link_libraries(${PROJECT_NAME}
PRIVATE
${EGL_LIBRARIES}
${Wayland_LIBRARIES}
if(WIN32)
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMAND_EXPAND_LISTS
)
target_include_directories(${PROJECT_NAME}
PRIVATE
${EGL_INCLUDE_DIRS}
${Wayland_INCLUDE_DIRS}
)
target_compile_options(${PROJECT_NAME} PRIVATE ${EGL_CFLAGS_OTHER} ${Wayland_CFLAGS_OTHER})
endif()
if (WIN32)
foreach(TBB_LIB IN ITEMS tbb tbbmalloc tbbmalloc_proxy)
if(TARGET ${TBB_LIB})
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:${TBB_LIB}>
$<TARGET_FILE_DIR:${PROJECT_NAME}>
COMMENT "Copying ${TBB_LIB}.dll"
)
else()
message(STATUS "Target ${TBB_LIB} not found, skipping copy")
endif()
endforeach()
endif()

View File

@@ -25,254 +25,12 @@ uniform float minRadius;
uniform float maxRadius;
uniform bool enablePBR;
uniform bool flipY;
const vec2 poissonDisk32[32] = vec2[](
vec2(-0.975402, -0.071138),
vec2(-0.920347, -0.411420),
vec2(-0.883908, 0.217872),
vec2(-0.815442, -0.879125),
vec2(-0.775043, 0.543896),
vec2(-0.698126, -0.227570),
vec2(-0.682433, 0.801894),
vec2(-0.563905, 0.021517),
vec2(-0.443233, -0.975116),
vec2(-0.412231, 0.361307),
vec2(-0.264969, -0.418930),
vec2(-0.241888, 0.997065),
vec2(-0.094184, -0.929389),
vec2(-0.019101, 0.680997),
vec2( 0.143832, -0.141008),
vec2( 0.199841, 0.786414),
vec2( 0.344959, 0.293878),
vec2( 0.443233, -0.475115),
vec2( 0.537430, -0.473734),
vec2( 0.589349, 0.569135),
vec2( 0.674281, -0.178897),
vec2( 0.791975, 0.190902),
vec2( 0.815442, 0.879125),
vec2( 0.896420, -0.613392),
vec2( 0.945586, -0.768907),
vec2( 0.974844, 0.756484),
vec2(-0.814100, 0.914376),
vec2(-0.382775, 0.276768),
vec2(-0.915886, 0.457714),
vec2( 0.537800, 0.912200),
vec2(-0.620000, -0.650000),
vec2( 0.120000, -0.780000)
);
uniform int renderDistance;
uniform vec3 skyColor;
const vec2 poissonDisk16[16] = vec2[](
vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725),
vec2(-0.09418410, -0.92938870), vec2(0.34495938, 0.29387760),
vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464),
vec2(-0.38277543, 0.27676845), vec2(0.97484398, 0.75648379),
vec2(0.44323325, -0.97511554), vec2(0.53742981, -0.47373420),
vec2(-0.26496911, -0.41893023), vec2(0.79197514, 0.19090188),
vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590),
vec2(0.19984126, 0.78641367), vec2(0.14383161, -0.14100790)
);
const vec2 poissonDisk8[8] = vec2[](
vec2( 0.1440, 0.7659), vec2(-0.5761, 0.4479),
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
);
uniform int samples;
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}
float FindBlocker(vec2 uv,
float zReceiver,
vec2 texelSize,
float bias,
float lightSizeUV)
{
float avgDepth = 0.0;
int blockers = 0;
float searchRadius = lightSizeUV * 0.5;
for(int i = 0; i < samples; i++)
{
vec2 offset;
if (samples == 32) {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
} else if (samples == 16) {
offset =
poissonDisk16[i]
* searchRadius
* texelSize;
} else if (samples == 8) {
offset =
poissonDisk8[i]
* searchRadius
* texelSize;
} else {
offset =
poissonDisk32[i]
* searchRadius
* texelSize;
}
float depth =
texture(shadowMap, uv + offset).r;
if(depth < zReceiver - bias)
{
avgDepth += depth;
blockers++;
}
}
if(blockers == 0)
return -1.0;
return avgDepth / blockers;
}
float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z < 0.0 || projCoords.z > 1.0) {
return 0.0;
}
float currentDepth = projCoords.z;
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
float shadow = 0.0;
float bias =
clamp(
0.001 * (1.0 - dot(norm, lightDir)),
0.0003,
0.003
);
if (shadowMode == 0) {
vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
float angle = random(seed) * 6.2831853;; // 2*PI
float s = sin(angle), c = cos(angle);
mat2 rot = mat2(c, -s, s, c);
//float radius = 0.7;
float radius = mix(1.0, 4.0, currentDepth);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[i] * radius * texelSize;
}
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
} else if (shadowMode == 1) {
for (int x = -1; x <= 1; ++x) {
for (int y = -1; y <= 1; ++y) {
vec2 offset = vec2(x, y) * texelSize;
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
}
shadow /= 9.0;
} else if (shadowMode == 2) {
// pcf off
float pcfDepth =
texture(shadowMap, projCoords.xy).r;
shadow =
currentDepth - bias > pcfDepth
? 1.0
: 0.0;
} else if (shadowMode == 3) {
float avgBlockerDepth =
FindBlocker(
projCoords.xy,
currentDepth,
texelSize,
bias,
lightSizeUV
);
if(avgBlockerDepth < 0.0)
{
return 0.0;
}
vec3 seed = vert_pos * 37.0 + sin(vert_pos * 91.7) * 13.0;
float angle = random(seed) * 6.2831853;; // 2*PI
float s = sin(angle), c = cos(angle);
mat2 rot = mat2(c, -s, s, c);
/*
float penumbraRatio = (currentDepth - avgBlockerDepth);
float radius = clamp(
penumbraRatio * lightSizeUV,
minRadius,
maxRadius
);
*/
float radius =
mix(
minRadius,
maxRadius,
smoothstep(
0.0,
0.05,
currentDepth - avgBlockerDepth
)
);
for (int i = 0; i < samples; ++i) {
vec2 offset;
if (samples == 32) {
offset = rot * poissonDisk32[i] * radius * texelSize;
} else if (samples == 16) {
offset = rot * poissonDisk16[i] * radius * texelSize;
} else if (samples == 8) {
offset = rot * poissonDisk8[i] * radius * texelSize;
} else {
offset = rot * poissonDisk32[i] * radius * texelSize;
}
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
} else {
float pcfDepth =
texture(shadowMap, projCoords.xy).r;
shadow =
currentDepth - bias > pcfDepth
? 1.0
: 0.0;
}
return shadow;
}
vec3 calcNewNormal() {
mat3 TBN = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec3 retrievedNormal = texture(normMap, vec3(tc, tex_layer)).xyz;
retrievedNormal = retrievedNormal * 2.0 - 1.0;
if (flipY) {
retrievedNormal.y = -retrievedNormal.y;
}
vec3 newNormal = TBN * retrievedNormal;
return normalize(newNormal);
}
#include "shadow.glsl"
#include "normal.glsl"
void main(void) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
@@ -341,8 +99,17 @@ void main(void) {
vec3 specular = spec * sunlightColor * specularStrength;
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
// fog
float dist = length(cameraPos - vert_pos);
vec4 fogColor = vec4(skyColor, 1.0);
float fogStart = renderDistance * 16 * 0.9;
float fogEnd = renderDistance * 16;
float fogFactor = smoothstep(fogEnd, fogStart, dist);
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb + (1.0-shadow) * specular * objectColor.rgb, objectColor.a);
color = mix(fogColor, color, fogFactor);
//color = vec4(normal * 0.5 + 0.5, 1.0);
//color = vec4(tangent * 0.5 + 0.5, 1.0);;
//color = vec4(norm * 0.5 + 0.5, 1.0);

View File

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

View File

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

View File

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

25
assets/shaders/noise.glsl Normal file
View File

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

View File

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

View File

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

View File

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

238
assets/shaders/shadow.glsl Normal file
View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/sound/bgm/bgm001.mp3 Normal file

Binary file not shown.

BIN
assets/sound/bgm/bgm002.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

80
cmake/Dependencies.cmake Normal file
View File

@@ -0,0 +1,80 @@
include(FetchContent)
# System packages
find_package(OpenGL REQUIRED)
find_package(Protobuf REQUIRED)
find_package(absl REQUIRED)
find_package(zstd REQUIRED)
find_package(OpenAL 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,11 @@
#pragma once
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/network_server.hpp"
#include "Cubed/gameplay/server_world.hpp"
#define GLFW_INCLUDE_NONE
#include "Cubed/camera.hpp"
#include "Cubed/dev_panel.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/renderer.hpp"
#include "Cubed/texture_manager.hpp"
#include "Cubed/window.hpp"
@@ -10,6 +13,14 @@ namespace Cubed {
class App {
public:
struct Argument {
bool is_client = false;
int port = 25530;
std::string ip{"127.0.0.1"};
std::string player{"Unknown"};
bool debug_on = true;
};
App();
~App();
static void cursor_position_callback(GLFWwindow* window, double xpos,
@@ -28,7 +39,7 @@ public:
static int start_cubed_application(int argc, char** argv);
static unsigned int seed();
static float delte_time();
static float delta_time();
static float get_fps();
Camera& camera();
@@ -36,26 +47,36 @@ public:
Renderer& renderer();
TextureManager& texture_manager();
Window& window();
World& world();
ClientWorld& client_world();
ServerWorld& server_world();
const Argument& argument() const;
AudioEngine& audio();
private:
Camera m_camera;
TextureManager m_texture_manager;
World m_world;
NetworkServer m_server;
std::shared_ptr<NetworkClient> m_client;
ClientWorld m_client_world;
DevPanel m_dev_panel{*this};
Renderer m_renderer{m_camera, m_world, m_texture_manager, m_dev_panel};
Renderer m_renderer{m_camera, m_client_world, m_texture_manager,
m_dev_panel};
Window m_window{m_renderer};
AudioEngine m_audio;
inline static double last_time = glfwGetTime();
inline static double current_time = glfwGetTime();
inline static double delta_time = 0.0f;
inline static double dt = 0.0f;
inline static double fps_time_count = 0.0f;
inline static int frame_count = 0;
inline static int fps = 0;
void init();
Argument m_argument;
void init(int argc, char** argv);
void handle_argument(int argc, char** argv);
void handle_toml();
auto init_camera();
auto init_texture();
auto init_world();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,22 +8,32 @@
namespace Cubed {
class Player;
class ClientPlayer;
class Camera {
private:
enum class Perspective {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT,
};
bool m_firse_mouse = true;
Player* m_player;
ClientPlayer* m_player;
float m_last_mouse_x, m_last_mouse_y;
glm::vec3 m_camera_pos;
bool m_under_water = false;
Perspective m_perspective = Perspective::FIRST_PERSON;
glm::vec3 m_front;
glm::vec3 camera_collision(glm::vec3 start, glm::vec3 end,
float radius = 0.2f);
public:
Camera();
void update_move_camera();
void camera_init(Player* player);
void camera_init(ClientPlayer* player);
void hot_reload();
void reset_camera();
void update_cursor_position_camera(double xpos, double ypos);
@@ -33,6 +43,8 @@ public:
bool is_under_water() const;
glm::vec3 get_camera_front() const;
void change_perspective();
bool is_first_person() const;
};
} // namespace Cubed

View File

@@ -1,17 +1,9 @@
#pragma once
#include "Cubed/tools/cubed_assert.hpp"
#include <toml++/toml.hpp>
#include "Cubed/tools/toml.utils.hpp"
namespace Cubed {
template <typename T>
concept TomlValueType =
std::same_as<T, int> || std::same_as<T, bool> || std::same_as<T, double> ||
std::same_as<T, const char*> || std::same_as<T, toml::date> ||
std::same_as<T, toml::time> || std::same_as<T, toml::date_time> ||
std::same_as<T, std::string>;
class Config {
public:
Config();
@@ -24,7 +16,7 @@ public:
void load_or_create_config();
void save_to_file();
template <TomlValueType T> T get(std::string_view key) const {
template <TOML::TomlValueType T> T get(std::string_view key) const {
size_t cur = 0;
auto pos = key.find('.');
const toml::table* table = &m_tbl;
@@ -61,7 +53,7 @@ public:
}
}
template <typename T> void set(std::string_view key, T&& val) {
if constexpr (!TomlValueType<std::decay_t<T>>) {
if constexpr (!TOML::TomlValueType<std::decay_t<T>>) {
static_assert(false, "Type Not Support");
}
size_t cur = 0;

View File

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

View File

@@ -5,7 +5,7 @@
namespace Cubed {
class App;
class Player;
class ClientPlayer;
class DevPanel {
struct ConfigView {
float fov = 70.0f;
@@ -20,15 +20,14 @@ class DevPanel {
bool is_enable_aniso = false;
bool is_support_aniso = true;
bool is_reload = true;
float volume_music = 1.0f;
float volume_sfx = 1.0f;
};
struct PlayerProfile {
int game_mode = 0;
int gait = 0;
float pos[3] = {0.0f, 0.0f, 0.0f};
};
struct TextEditing {
bool perlin_seed = false;
};
public:
DevPanel(App& app);
@@ -38,9 +37,8 @@ public:
private:
App& m_app;
ConfigView m_config;
Player* m_player;
ClientPlayer* m_player;
PlayerProfile m_player_profile;
TextEditing m_text_editing;
bool m_need_save_config = false;
bool m_gen_thread_running = true;
int m_theme = 0;
@@ -48,13 +46,18 @@ private:
int m_pre_set_tick_speed = 1;
bool m_tick_frezze = false;
int m_samples_idx = 1;
int m_threads = 1;
int m_chunk_style = 0;
void show_about_table_bar();
void show_biome_table_bar();
void show_time_table_bar();
void show_cave_table_bar();
void show_river_table_bar();
void show_chunk_table_bar();
void show_settings_tab_item();
void show_world_tab_item();
void show_server_world_table_bar();
void show_client_world_table_bar();
void show_player_tab_item();
void show_items_tab_item();
void show_shader_tab_item();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,66 +1,42 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/vertex_data.hpp"
#include "world/chunk_data.pb.h"
#include <atomic>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <mutex>
namespace Cubed {
class World;
// if want to use, do init_chunk(), gen_vertex_data() and
class Chunk {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
static constexpr int VERTEX_DATA_SUM = 5;
std::atomic<bool> m_dirty{false};
std::atomic<bool> m_need_upload{true};
std::atomic<bool> m_is_on_gen_vertex_data{false};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
std::unique_ptr<ChunkGenerator> m_generator;
ChunkPos m_chunk_pos;
World& m_world;
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
4 - water
*/
std::vector<VertexData> m_vertex_data;
float frequency = 0.01f;
float height = 80;
unsigned m_seed = 0;
BiomeConditions m_conditions;
void clear_dirty();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id);
class ClientWorld;
struct ChunkRenderSnapshot {
GLuint normal_vao;
size_t normal_vertices_count;
GLuint cross_vao;
size_t cross_vertices_count;
GLuint normal_discard_vao;
size_t normal_discard_vertices_count;
GLuint normal_blend_vao;
size_t normal_blend_vertices_count;
GLuint water_vao;
size_t water_vertices_count;
glm::vec3 center;
glm::vec3 half_extents;
};
class ClientChunk {
public:
Chunk(World& world, ChunkPos chunk_pos);
~Chunk();
Chunk(const Chunk&) = delete;
Chunk& operator=(const Chunk&) = delete;
Chunk(Chunk&&) noexcept;
Chunk& operator=(Chunk&&) noexcept;
ClientChunk(ClientWorld& world);
~ClientChunk();
ClientChunk(const ClientChunk&) = delete;
ClientChunk& operator=(const ClientChunk&) = delete;
ClientChunk(ClientChunk&&) noexcept;
ClientChunk& operator=(ClientChunk&&) noexcept;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
@@ -73,33 +49,9 @@ public:
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& get_chunk_blocks() const;
HeightMapArray get_heightmap() const;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
// Init Chunk
// Determine biome from temperature and humidity noise
void gen_phase_one();
// Resolve biome adjacency conflicts with neighbor chunks
void gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks);
// Generate heightmap using biome-specific noise
void gen_phase_three();
// Blend heightmap with neighbors for smooth transitions
void gen_phase_four(
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& neighbor_biome);
// Generate terrain blocks from heightmap and biome
void gen_phase_five();
// Blend surface blocks at chunk borders with neighbors
void gen_phase_six(const std::array<std::optional<std::vector<BlockType>>,
4>& neighbor_block);
// Generate biome-specific vegetation/structures
void gen_phase_seven();
// void gen_vertex_data();
// 0 : (1, 0)
// 1 : (-1, 0)
// 2 : (0, 1)
// 3 : (0, -1)
void receive_chunk(const ChunkDataRsp& data);
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
// Can only be called on the render thread
void upload_to_gpu();
GLuint get_normal_vao() const;
@@ -124,15 +76,59 @@ public:
void need_upload();
void set_chunk_block(int index, unsigned id);
BlockType get_chunk_block(int index);
bool is_temp_chunk() const;
ChunkPos chunk_pos() const;
BiomeType biome() const;
void biome(BiomeType b);
HeightMapArray& heightmap();
std::vector<BlockType>& blocks();
World& world();
ClientWorld& world();
unsigned seed() const;
BiomeConditions& conditions();
};
const ChunkRenderSnapshot* get_render_snapshot() const;
private:
struct FaceKey {
BlockType block_id = 0;
int face = -1; // 0-5, used to index NORMALS/TANGENTS/TEX_COORDS
bool valid() const { return block_id != 0; }
bool operator==(const FaceKey& o) const {
return block_id == o.block_id && face == o.face;
}
bool operator!=(const FaceKey& o) const { return !(*this == o); }
};
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
static constexpr int BLOCK_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
static constexpr int VERTEX_DATA_SUM = 5;
std::atomic<bool> m_dirty{false};
std::atomic<bool> m_need_upload{true};
std::atomic<bool> m_is_on_gen_vertex_data{false};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
ChunkPos m_chunk_pos;
ClientWorld& m_world;
// the index is a array of block id
std::vector<BlockType> m_blocks;
/*
0 - normal
1 - cross_plane
2 - normal_discard
3 - transparent and blend
4 - water
*/
std::vector<VertexData> m_vertex_data;
ChunkRenderSnapshot m_render_snapshot;
unsigned m_seed = 0;
void clear_dirty();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id);
void emit_quad(int axis, int face_dir, int layer, int i, int j, int w,
int h, int u_axis, int v_axis, FaceKey key);
};
} // namespace Cubed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,110 +1,21 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/input.hpp"
#include <glm/glm.hpp>
#include <optional>
#include <string>
#include <stdexcept>
#include <utility>
namespace Cubed {
enum class Gait { STOP = 0, WALK = 1, RUN = 2 };
constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); }
enum class Gait { WALK = 0, RUN };
class World;
class Player {
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
float m_acceleration = DEFAULT_ACCELERATION;
float m_deceleration = DEFAULT_DECELERATION;
float m_g = DEFAULT_G;
constexpr static float MAX_SPACE_ON_TIME = 0.3f;
float m_yaw = 0.0f;
float m_pitch = 0.0f;
float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
bool can_up = true;
float space_on_time = 0.0f;
bool space_on = false;
bool is_fly = false;
float m_xz_speed = 0.0f;
unsigned m_place_block = 1;
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// player is tow block tall, the pos is the lower pos
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
ChunkPos m_player_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
glm::vec3 m_size{0.6f, 1.8f, 0.6f};
Gait m_gait = Gait::WALK;
MoveState m_move_state{};
GameMode m_game_mode = CREATIVE;
std::optional<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
World& m_world;
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
void check_player_chunk_transition();
void update_direction();
void update_lookup_block();
void update_move(float delta_time);
void update_x_move();
void update_y_move();
void update_z_move();
public:
Player(World& world, const std::string& name);
~Player();
AABB get_aabb() const;
const glm::vec3& get_front() const;
const Gait& get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const;
const glm::vec3& get_player_pos() const;
const MoveState& get_move_state() const;
void change_mode(GameMode mode);
void hot_reload();
void set_player_pos(const glm::vec3& pos);
void set_place_block(unsigned id);
void update(float delta_time);
void update_front_vec(float offset_x, float offset_y);
void update_player_move_state(int key, int action);
void update_scroll(double yoffset);
float& max_walk_speed();
float& max_run_speed();
float& max_speed();
float& acceleration();
float& deceleration();
float& g();
unsigned place_block() const;
Gait& gait();
GameMode& game_mode();
const World& get_world() const;
};
inline Gait get_gait_from_id(int id) {
switch (id) {
case std::to_underlying(Gait::STOP):
return Gait::STOP;
case std::to_underlying(Gait::WALK):
return Gait::WALK;
case std::to_underlying(Gait::RUN):
return Gait::RUN;
default:
throw std::runtime_error("Unknown Gait");
}
}
} // namespace Cubed

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include <array>
#include <atomic>
#include <optional>
#include <tuple>
namespace Cubed {
class ServerWorld;
class ServerChunk {
public:
ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
bool temp_chunk = false);
ServerChunk(const ServerChunk&) = delete;
ServerChunk(ServerChunk&&) noexcept;
ServerChunk& operator=(const ServerChunk&) = delete;
ServerChunk& operator=(ServerChunk&&) noexcept;
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
int chunk_x, int chunk_z);
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
void set_chunk_block(int index, unsigned id);
// ensure thread safe!
void gen_chunk();
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& get_chunk_blocks() const;
HeightMapArray get_heightmap() const;
bool is_temp_chunk() const;
ChunkPos chunk_pos() const;
BiomeType biome() const;
void biome(BiomeType b);
HeightMapArray& heightmap();
std::vector<BlockType>& blocks();
ServerWorld& world();
unsigned seed() const;
BiomeConditions& conditions();
bool& has_cave();
const OptionalBlockVectorArray& get_neightbor_blocks() const;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
private:
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
std::atomic<bool> m_gening{false};
std::atomic<bool> m_temp_chunk{false};
bool m_has_cave{false};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
ChunkPos m_chunk_pos;
ServerWorld& m_world;
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
OptionalBlockVectorArray m_neightbor_blocks;
float frequency = 0.01f;
float height = 80;
unsigned m_seed = 0;
BiomeConditions m_conditions;
std::unique_ptr<ChunkGenerator> m_generator;
// Init Chunk
// Determine biome from temperature and humidity noise
void gen_phase_one();
// Generate heightmap using biome-specific noise
void gen_phase_two();
// Generate terrain blocks from heightmap and biome
void gen_phase_three();
// Blend surface blocks at chunk borders with neighbors
void gen_phase_four(const std::array<std::optional<std::vector<BlockType>>,
4>& neighbor_block);
// Generate biome-specific vegetation/structures
void gen_phase_five();
};
} // namespace Cubed

View File

@@ -0,0 +1,66 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/player.hpp"
#include <absl/container/flat_hash_set.h>
#include <atomic>
#include <glm/glm.hpp>
#include <memory>
#include <shared_mutex>
#include <string>
#include <string_view>
namespace Cubed {
class ServerWorld;
class Session;
class ServerPlayer {
public:
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
ServerPlayer(const ServerPlayer&) = delete;
ServerPlayer(ServerPlayer&&) = delete;
ServerPlayer& operator=(const ServerPlayer&) = delete;
ServerPlayer& operator=(ServerPlayer&&) = delete;
ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& m_world, std::shared_ptr<Session> session,
TickType gametick);
const glm::vec3& get_pos() const;
const std::string& get_name() const;
const std::string& get_uuid() const;
std::shared_ptr<Session> get_session() const;
void update_pos(float x, float y, float z);
void update_sync_gametick(TickType gametick);
bool is_disconnect(TickType current_gametick) const;
int task_id() const;
void task_id(int id);
bool has_player(ChunkPos pos) const;
void update_chunk_set(const ChunkPosSet& set);
const ChunkPosSet& get_chunk_pos_set() const;
ChunkPosSet& get_chunk_pos_set();
void set_yaw(float yaw);
void set_pitch(float pitch);
float yaw() const;
float pitch() const;
Gait gait() const;
void set_gait(Gait gait);
private:
static constexpr TickType TIMEOUT = 200;
std::string m_name;
std::string m_uuid;
glm::vec3 m_pos{0.0f};
ServerWorld& m_world;
ChunkPos m_last_chunk_pos{0, 0};
std::atomic<std::shared_ptr<Session>> m_session;
std::atomic<TickType> m_last_gametick{0};
std::atomic<int> m_chunk_task_id{0};
std::atomic<float> m_yaw{0.0f};
std::atomic<float> m_pitch{0.0f};
std::atomic<Gait> m_gait;
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
};
} // namespace Cubed

View File

@@ -0,0 +1,189 @@
#pragma once
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/packet.hpp" // IWYU pragma: keep
#include "Cubed/gameplay/river_worm.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/gameplay/server_player.hpp"
#include "Cubed/tools/priority_thread_pool.hpp"
#include "Cubed/tools/recent_queue.hpp"
#include "Cubed/tools/thread_pool.hpp"
#include "world/block_change.pb.h"
#include <absl/container/flat_hash_set.h>
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Cubed {
class Session;
class ServerWorld {
public:
enum class ThreadPoolKind { NET, GEN };
ServerWorld();
~ServerWorld();
void stop();
void handle_player_exit(const std::string& uuid);
void init_world();
void need_gen(std::string uuid);
void update();
void hot_reload();
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_gen_thread();
void start_server_thread();
void stop_gen_thread();
void stop_server_thread();
void stop_thread_pool();
void start_thread_pool();
void serever_run(std::stop_token stoken);
CaveCarver& cave_carcer();
RiverWorm& river_worm();
TickType game_tick() const;
TickType day_tick() const;
void day_tick(TickType tick);
int per_tick_time() const;
void per_tick_time(int ms);
bool is_tick_running() const;
void tick_running(bool run);
int gen_pool_threads() const;
int max_threads() const;
void change_pool_threads(ThreadPoolKind kind, int threads);
int chunk_load_style() const;
void set_chunk_load_style(int id);
bool set_block(const glm::ivec3& block_pos, unsigned id);
void sync_player_pos(const C2S_PlayerInfo& rsp);
void sync_player_water_sound(const PlayerWaterSound& rsp);
void handle_player_login(const std::string& player_name,
std::shared_ptr<Session> session);
glm::vec3 get_player_pos(const std::string& uuid) const;
void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos);
void handle_block_change(const BlockChangeReq& req);
int chunk_size() const;
template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
std::forward_as_tuple(std::string(id)),
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
}
private:
enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE };
struct ChunkEntity {
ChunkState state;
std::shared_ptr<ServerChunk> chunk;
uint32_t ref_count = 0;
};
enum class ChunkLoadStyle { RANDOM, CENTER };
struct PendingRequest {
std::string uuid;
int task_id;
ChunkPos pos;
};
struct PendingChunk {
ChunkPos pos;
std::unique_ptr<ServerChunk> chunk;
};
using ChunkHashMap =
tbb::concurrent_hash_map<ChunkPos, ChunkEntity, ChunkPos::TBBHash>;
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
using NewChunkVector = std::vector<PendingChunk>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
using chunk_acc = ChunkHashMap::accessor;
using chunk_caac = ChunkHashMap::const_accessor;
using uuid_acc = PlayerUUIDMap::accessor;
using uuid_cacc = PlayerUUIDMap::const_accessor;
// key = uuid
PlayerHashMap m_players;
ChunkHashMap m_chunks;
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
std::jthread m_gen_thread;
std::jthread m_server_thread;
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_init{false};
std::atomic<bool> m_stopped{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_gen_pool_threads{0};
std::atomic<int> m_net_pool_threads{0};
std::atomic<int> m_max_threads{1};
std::atomic<size_t> m_player_sum{0};
std::atomic<TickType> m_game_ticks{0};
std::atomic<TickType> m_day_tick{6000};
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
mutable std::shared_mutex m_player_mutex;
std::mutex m_need_gen_queue_mutex;
std::condition_variable_any m_gen_cv;
RecentQueue<std::string> m_need_gen_queue;
std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool;
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
PlayerUUIDMap m_uuid_to_name;
tbb::concurrent_unordered_map<std::string, TickTimer> m_timers;
tbb::concurrent_queue<PendingRequest> m_waiting_chunk_requests;
tbb::concurrent_queue<std::unique_ptr<ServerChunk>> m_finished_queue;
void init_chunks();
void gen_chunks_internal(const std::string& uuid);
void compute_required_chunks(ChunkPosSet& required_chunks,
const std::optional<std::string>& uuid);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&);
void submit_new_chunks(const std::string& uuid, NewChunkVector& new_chunks);
// void wait_all_chunk_tasks();
void update_ref_count(const ChunkPosSet& old, const ChunkPosSet& now);
void send_time();
void send_chunk(int task_id, const std::string& uuid, ChunkPos pos);
int
change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool,
int threads);
int change_pool_threads(
std::atomic<std::shared_ptr<PriorityThreadPool>>& thread_pool,
int threads);
void send_server_stop();
};
} // namespace Cubed

View File

@@ -0,0 +1,60 @@
#pragma once
#include "Cubed/gameplay/packet.hpp"
#include <asio.hpp>
#include <memory>
#include <queue>
#include <string>
namespace Cubed {
using asio::ip::tcp;
class ServerWorld;
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket, ServerWorld& server_world,
asio::io_context& io);
~Session();
void start();
void send(Packet packet, int priority = 10);
void close();
const std::string& uuid() const;
private:
struct Task {
int priority = 10;
std::uint64_t sequence = 0;
Packet packet;
Task(int p, std::uint64_t seq, Packet pac)
: priority(p), sequence(seq), packet(std::move(pac)) {}
};
struct TaskCompare {
bool operator()(const Task& a, const Task& b) const {
if (a.priority != b.priority) {
return a.priority > b.priority;
}
return a.sequence > b.sequence;
}
};
static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024;
tcp::socket m_socket;
std::vector<char> m_read_buffer;
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
asio::strand<asio::io_context::executor_type> m_strand;
std::string m_uuid;
ServerWorld& m_server_world;
std::atomic<bool> m_closed{false};
std::atomic_uint64_t m_sequence{0};
asio::awaitable<void> read_loop();
void do_write();
};
} // namespace Cubed

View File

@@ -4,13 +4,13 @@
namespace Cubed {
class Chunk;
class ServerChunk;
struct TreeStructNode {
glm::ivec3 offset{0, 0, 0};
unsigned id = 0;
};
bool build_tree(Chunk& chunk, const glm::ivec3& pos);
bool build_tree(ServerChunk& chunk, const glm::ivec3& pos);
} // namespace Cubed

View File

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

View File

@@ -1,159 +0,0 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/river_worm.hpp"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <thread>
#include <unordered_map>
#include <unordered_set>
namespace Cubed {
struct ChunkRenderSnapshot {
GLuint normal_vao;
size_t normal_vertices_count;
GLuint cross_vao;
size_t cross_vertices_count;
GLuint normal_discard_vao;
size_t normal_discard_vertices_count;
GLuint normal_blend_vao;
size_t normal_blend_vertices_count;
GLuint water_vao;
size_t water_vertices_count;
glm::vec3 center;
glm::vec3 half_extents;
};
class Player;
class TextureManager;
class World {
private:
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
using ConstChunkMap =
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
ChunkHashMap m_chunks;
std::unordered_map<std::size_t, Player> m_players;
std::vector<glm::vec4> m_planes;
std::thread m_gen_thread;
std::thread m_server_thread;
std::stop_source m_server_stop_source;
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
std::atomic<TickType> m_day_tick = 6000;
mutable std::mutex m_chunks_mutex;
std::mutex m_gen_signal_mutex;
std::mutex m_new_chunk_queue_mutex;
std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex;
std::mutex m_gen_player_pos_mutex;
std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao;
std::condition_variable m_gen_cv;
std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_rendering_distance{24};
std::atomic<float> m_chunk_gen_fraction{0.0f};
std::atomic<TickType> m_game_ticks{0};
std::vector<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk_queue;
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
void init_chunks();
void gen_chunks_internal();
void sync_player_pos(glm::vec3& player_pos);
void
compute_required_chunks(ChunkPosSet& required_chunks,
ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&);
void
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
ChunkPtrUpdateList& affected_neighbor,
const ChunkPairVector& new_chunks);
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
ConstChunkMap&);
public:
World();
~World();
bool can_move(const AABB& player_box) const;
// const BlockRenderData& get_block_render_data(int x, int y ,int z);
const std::optional<LookBlock>&
get_look_block_pos(const std::string& name) const;
const Chunk* get_chunk(const ChunkPos& pos) const;
Player& get_player(const std::string& name);
void init_world();
int get_block(const glm::ivec3& block_pos) const;
bool is_solid(const glm::ivec3& block_pos) const;
bool can_pass_block(const glm::ivec3& block_pos) const;
BlockType get_block_tpye(const glm::ivec3& block_pos) const;
static ChunkPos chunk_pos(int world_x, int world_z);
void need_gen();
void set_block(const glm::ivec3& pos, unsigned id);
void update(float delta_time);
void push_delete_vbo(GLuint vbo);
void push_delete_vao(GLuint vao);
void hot_reload();
void rebuild_world();
float chunk_gen_fraction() const;
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_gen_thread();
void start_server_thread();
void stop_gen_thread();
void stop_server_thread();
void serever_run(std::stop_token stoken);
CaveCarver& cave_carcer();
RiverWorm& river_worm();
std::vector<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& render_snapshots();
glm::vec3 sunlight_dir() const;
TickType game_tick() const;
TickType day_tick() const;
void day_tick(TickType tick);
int per_tick_time() const;
void per_tick_time(int ms);
bool is_tick_running() const;
void tick_running(bool run);
};
} // namespace Cubed

View File

@@ -0,0 +1,32 @@
#pragma once
#include "Cubed/shader.hpp"
#include <array>
#include <glad/glad.h>
namespace Cubed {
class Renderer;
class PlayerRenderer {
public:
static constexpr int BODY_PART_NUM = 6;
PlayerRenderer(Renderer& renderer);
~PlayerRenderer();
void init();
void render(const Shader& shader);
void shadow_render(const Shader& shader, glm::mat4& light_matrix);
private:
struct PlayerVertex {
float x = 0.0f, y = 0.0f, z = 0.0f;
float s = 0.0f, t = 0.0f;
float nx = 0.0f, ny = 0.0f, nz = 0.0f;
float tx = 0.0f, ty = 0.0f, tz = 0.0f;
};
Renderer& m_renderer;
std::array<GLuint, BODY_PART_NUM> m_vao;
std::array<GLuint, BODY_PART_NUM> m_vbo;
bool m_inited{false};
std::array<std::vector<PlayerVertex>, BODY_PART_NUM> m_vertices;
};
} // namespace Cubed

View File

@@ -63,12 +63,12 @@ constexpr float TEX_COORDS[6][6][2] = {
{0.0f, 0.0f}, // top front
{0.0f, 1.0f}}, // bottom front
// ===== back (z = -1) =====
{{1.0f, 1.0f}, // bottom left
{0.0f, 1.0f}, // bottom right
{0.0f, 0.0f}, // top right
{0.0f, 0.0f}, // top right
{1.0f, 0.0f}, // top left
{1.0f, 1.0f}}, // bottom left
{{0.0f, 1.0f}, // bottom left
{0.0f, 0.0f}, // top left
{1.0f, 0.0f}, // top right
{1.0f, 0.0f}, // top right
{1.0f, 1.0f}, // bottom right
{0.0f, 1.0f}}, // bottom left
// ===== left (x = -1) =====
{{1.0f, 1.0f}, // bottom back
{0.0f, 1.0f}, // bottom front
@@ -152,12 +152,12 @@ constexpr float TANGENTS[6][6][3] = {
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f}},
// ===== back (z = -1) =====
{{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f}},
{{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f}},
// ===== left (x = -1) =====
{{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
@@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = {
{-0.7071f, 0.0f, 0.7071f}}};
#pragma endregion
#pragma region Player
constexpr float VERTICES_PLAYER[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f}, // bottom left
{0.0f, 2.0f, 1.0f}, // top left
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 0.0f, 1.0f}, // bottom right
{0.0f, 0.0f, 1.0f}}, // bottom left
// ===== right (x = +1) =====
{{1.0f, 0.0f, 1.0f}, // bottom front
{1.0f, 0.0f, 0.0f}, // bottom back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 1.0f}, // top front
{1.0f, 0.0f, 1.0f}}, // bottom front
// ===== back (z = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom left
{1.0f, 0.0f, 0.0f}, // bottom right
{1.0f, 2.0f, 0.0f}, // top right
{1.0f, 2.0f, 0.0f}, // top right
{0.0f, 2.0f, 0.0f}, // top left
{0.0f, 0.0f, 0.0f}}, // bottom left
// ===== left (x = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom back
{0.0f, 0.0f, 1.0f}, // bottom front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 0.0f}, // top back
{0.0f, 0.0f, 0.0f}}, // bottom back
// ===== top (y = +2) =====
{{0.0f, 2.0f, 0.0f}, // back left
{1.0f, 2.0f, 0.0f}, // back right
{1.0f, 2.0f, 1.0f}, // front right
{1.0f, 2.0f, 1.0f}, // front right
{0.0f, 2.0f, 1.0f}, // front left
{0.0f, 2.0f, 0.0f}}, // back left
// ===== bottom (y = -1) =====
{{0.0f, 0.0f, 1.0f}, // front left
{1.0f, 0.0f, 1.0f}, // front right
{1.0f, 0.0f, 0.0f}, // back right
{1.0f, 0.0f, 0.0f}, // back right
{0.0f, 0.0f, 0.0f}, // back left
{0.0f, 0.0f, 1.0f}} // front left
};
#pragma endregion
// [-1, 1]
constexpr float QUAD_VERTICES[] = {
// postion // texcoorlds

View File

@@ -1,6 +1,7 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/player_renderer.hpp"
#include "Cubed/primitive_data.hpp"
#include "Cubed/shader.hpp"
#include "Cubed/ui/text.hpp"
@@ -11,17 +12,17 @@ namespace Cubed {
class Camera;
class TextureManager;
class World;
class ClientWorld;
class DevPanel;
class Renderer {
public:
constexpr static int NUM_VAO = 7;
Renderer(const Camera& camera, World& world,
Renderer(const Camera& camera, ClientWorld& world,
const TextureManager& texture_manager, DevPanel& dev_panel);
~Renderer();
void hot_reload();
void init();
void init(bool debug_on);
const Shader& get_shader(const std::string& name) const;
void render();
void update(float delta_time);
@@ -50,6 +51,14 @@ public:
float& underwater_fog_density();
float& water_density();
const Camera& camera() const;
const ClientWorld& world() const;
ClientWorld& world();
const glm::mat4& proj_mat() const;
const TextureManager& texture_mamger() const;
float delta_time() const;
private:
struct ParallelLight {
glm::vec3 sundir; // direction from sun to vertex
@@ -91,14 +100,17 @@ private:
const Camera& m_camera;
DevPanel& m_dev_panel;
const TextureManager& m_texture_manager;
World& m_world;
ClientWorld& m_world;
PlayerRenderer m_player_renderer;
bool m_discard_tranparent = true;
bool m_shader_on = true;
bool m_water_perturb = true;
bool m_water_depth_fade = true;
bool m_pbr = true;
bool m_flip_y = false;
bool m_init = false;
int m_shadow_mode = 0;
int m_light_cull_face = 0;
float m_aspect = 0.0f;
@@ -119,7 +131,7 @@ private:
GLuint m_outline_indices_vbo = 0;
GLuint m_outline_vbo = 0;
GLuint m_ui_vbo = 0;
GLuint m_player_vbo = 0;
GLuint m_fbo = 0;
GLuint m_screen_texture = 0;
GLuint m_screen_depth_texture = 0;
@@ -171,7 +183,6 @@ private:
2 - outline vao
3 - ui vao
4 - text vao
*/
std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui;
@@ -186,6 +197,7 @@ private:
void render_text();
void render_ui();
void render_world();
void render_player();
void render_underwater();
void render_dev_panel();

View File

@@ -8,13 +8,16 @@ namespace Cubed {
class TextureManager {
private:
bool m_need_reload = false;
bool m_init = false;
GLuint m_block_status_array = 0;
GLuint m_texture_array = 0;
GLuint m_cross_plane_array = 0;
GLuint m_ui_array = 0;
GLuint m_pbr_texture_array = 0;
GLuint m_normal_texture_array = 0;
GLfloat m_max_aniso = 0.0f;
GLuint m_skin = 0;
int m_aniso = 1;
std::vector<GLuint> m_item_textures;
@@ -29,6 +32,8 @@ private:
void init_block();
void init_ui();
void init_block_status();
void init_skin();
void hot_reload();
public:
TextureManager();
@@ -41,9 +46,10 @@ public:
GLuint get_ui_array() const;
GLuint get_pbr_texture() const;
const std::vector<GLuint>& item_textures() const;
GLuint get_skin() const;
// Must call after MapTable::init_map() and glfwMakeContextCurrent(window);
void init_texture();
void hot_reload();
void need_reload();
void update();
int max_aniso() const;

View File

@@ -0,0 +1,35 @@
#pragma once
#include <format>
#include <span>
#include <stdexcept>
#include <string_view>
namespace Cubed {
class ArgParser {
public:
ArgParser(int argc, char** argv) : m_args(argv, argc) {};
ArgParser(std::span<char*> args) : m_args(args) {}
bool has_next() const { return m_index < m_args.size(); }
std::string_view next() {
if (!has_next()) {
throw std::runtime_error("No more arguments");
}
return m_args[m_index++];
}
std::string_view require_next(std::string_view option) {
if (!has_next()) {
throw std::runtime_error(
std::format("{} requires an argument", option));
}
return next();
}
private:
std::span<char*> m_args;
size_t m_index = 1;
};
} // namespace Cubed

View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <format>
#include <span>
#include <stdexcept>
#include <vector>
#include <zstd.h>
namespace Cubed {
constexpr int DEFAULT_ZSTD_LEVEL = 3;
inline std::vector<uint8_t> compress_data(std::span<const uint8_t> data) {
size_t max_size = ZSTD_compressBound(data.size());
std::vector<uint8_t> compressed_data(max_size);
size_t compressed_bytes =
ZSTD_compress(compressed_data.data(), max_size, data.data(),
data.size(), DEFAULT_ZSTD_LEVEL);
if (ZSTD_isError(compressed_bytes)) {
throw std::runtime_error(std::format(
"Compress Fail {}", ZSTD_getErrorName(compressed_bytes)));
}
compressed_data.resize(compressed_bytes);
return compressed_data;
}
inline std::vector<uint8_t> decompress_data(std::span<const uint8_t> data,
uint32_t original_size) {
std::vector<uint8_t> decompressed_data(original_size);
size_t decompressed_bytes = ZSTD_decompress(
decompressed_data.data(), original_size, data.data(), data.size());
if (ZSTD_isError(decompressed_bytes)) {
throw std::runtime_error(std::format(
"Decompress Fail {}", ZSTD_getErrorName(decompressed_bytes)));
}
if (decompressed_bytes != original_size) {
throw std::runtime_error("Unexpected decompressed size");
}
return decompressed_data;
}
} // namespace Cubed

View File

@@ -9,7 +9,7 @@
namespace Cubed {
namespace Logger {
enum class Level { TRACE, DEBUG, INFO, ERROR, WARN };
enum class Level { L_TRACE, L_DEBUG, L_INFO, L_ERROR, L_WARN };
template <typename... Args>
inline void info(std::format_string<Args...> fmt, Args&&... args) {
@@ -53,7 +53,7 @@ inline void log(Level level, std::source_location loc,
std::chrono::system_clock::now());
std::string msg = std::vformat(fmt.get(), std::make_format_args(args...));
switch (level) {
case Logger::Level::TRACE:
case Logger::Level::L_TRACE:
std::osyncstream(std::cout)
<< "\033[1;34m"
<< std::format("[TRACE][{:%Y-%m-%d %H:%M:%S}]", now_time) << "["
@@ -61,20 +61,20 @@ inline void log(Level level, std::source_location loc,
<< "[" << loc.function_name() << "]" << msg << "\033[0m"
<< "\n";
break;
case Logger::Level::DEBUG:
case Logger::Level::L_DEBUG:
std::osyncstream(std::cout)
<< "\033[1;34m"
<< std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg
<< "\033[0m"
<< "\n";
break;
case Logger::Level::INFO:
case Logger::Level::L_INFO:
info(fmt, std::forward<Args>(args)...);
break;
case Logger::Level::WARN:
case Logger::Level::L_WARN:
warn(fmt, std::forward<Args>(args)...);
break;
case Logger::Level::ERROR:
case Logger::Level::L_ERROR:
error(fmt, std::forward<Args>(args)...);
break;
}

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