_deps
# Binaries
-Dragonblocks
-DragonblocksServer
-DragonblocksAlpha-*.zip
-DragonblocksAlpha
+dragonblocks
+dragonblocks_server
+dragonblocks_alpha-*.zip
+dragonblocks_alpha
# Data
client.conf
[submodule "deps/endian.h"]
path = deps/endian.h
url = https://github.com/dragonblocks/endian.h
-[submodule "deps/dragonport"]
- path = deps/dragonport
- url = https://github.com/dragonblocks/dragonport
+[submodule "deps/asprintf"]
+ path = deps/asprintf
+ url = https://github.com/dragonblocks/asprintf
[submodule "deps/dragonnet"]
path = deps/dragonnet
url = https://github.com/dragonblocks/dragonnet
[submodule "deps/linenoise"]
path = deps/linenoise
url = https://github.com/antirez/linenoise
-[submodule "deps/dragontype"]
- path = deps/dragontype
- url = https://github.com/dragonblocks/dragontype
+[submodule "deps/protogen"]
+ path = deps/protogen
+ url = https://github.com/dragonblocks/protogen
- Saving map, player positions and other data to a SQLite3 database
- Multithreaded map generation, mesh generation and networking
- Handlers for SIGINT und SIGTERM (just Ctrl+C to shut down the server)
+- Log levels: error, warning, access, action, info, verbose
## Usage
--- /dev/null
+Subproject commit 84c7e7cb1e0a6ea4102ca785cca31a3e66f34a48
-Subproject commit 164e1ed7f93e1889c36b549a995e93fc0992c888
+Subproject commit c346a21deaf3aec0983d0e740d6c0b6799f076ef
+++ /dev/null
-Subproject commit d719d15af18415f17ba5ab9cde0718f2e38717ba
-Subproject commit 0f30ec889c7a70ab7f7b79836d1a34ddf8659a47
+Subproject commit d439328eed1446e6869a1fd23fe5c0a99350806d
+++ /dev/null
-Subproject commit 18ae77e35da46f97f3b06562a4a892ebdeeb9750
--- /dev/null
+Subproject commit a7957f4abdccb075693b01713492cd67b881d1ec
--- /dev/null
+name player scale 1 1.8 1
+ name head pos 0 0.875 0 scale 0.45 0.25 0.45 cube head
+ name body scale 0.48 0.75 0.36
+ name upper pos 0 1.0 0 scale 1 0.5 1
+ name chest pos 0 -0.5 0 cube chest
+ name shoulders scale 0.5 1 1
+ name left pos -1.5 0 0
+ name arm pos 0 -0.5 0 cube arm
+ name right pos +1.5 0 0
+ name arm pos 0 -0.5 0 cube arm
+ name lower pos 0 0.5 0 scale 1 0.5 1
+ name hips scale 0.5 1 1
+ name left pos -0.5 0 0
+ name leg pos 0 -0.5 0 cube leg
+ name right pos +0.5 0 0
+ name leg pos 0 -0.5 0 cube leg
--- /dev/null
+# Goxel 0.10.6
+# One line per voxel
+# X Y Z RRGGBB
+0 -3 0 7d5435
+1 -3 0 7d5435
+2 -3 0 7d5435
+3 -3 0 7d5435
+4 -3 0 7d5435
+0 -2 0 7d5435
+1 -2 0 7d5435
+2 -2 0 7d5435
+3 -2 0 7d5435
+4 -2 0 7d5435
+0 -1 0 7d5435
+1 -1 0 7d5435
+2 -1 0 7d5435
+3 -1 0 7d5435
+4 -1 0 7d5435
+0 -3 1 7d5435
+1 -3 1 7d5435
+2 -3 1 7d5435
+3 -3 1 7d5435
+4 -3 1 7d5435
+4 -2 1 7d5435
+4 -1 1 7d5435
+0 -3 2 7d5435
+3 -3 2 7d5435
+4 -3 2 7d5435
+4 -2 2 7d5435
+4 -1 2 7d5435
+0 -3 3 7d5435
+3 -3 3 7d5435
+4 -3 3 7d5435
+4 -2 3 7d5435
+4 -1 3 7d5435
+0 -3 4 7d5435
+1 -3 4 7d5435
+2 -3 4 7d5435
+3 -3 4 7d5435
+4 -3 4 7d5435
+4 -2 4 7d5435
+4 -1 4 7d5435
+0 -4 5 503728
+1 -4 5 503728
+2 -4 5 503728
+3 -4 5 503728
+4 -4 5 503728
+5 -4 5 503728
+0 -3 5 503728
+1 -3 5 503728
+2 -3 5 503728
+3 -3 5 503728
+4 -3 5 503728
+0 -2 5 503728
+1 -2 5 503728
+2 -2 5 503728
+3 -2 5 503728
+4 -2 5 7d5435
+0 -1 5 503728
+1 -1 5 503728
+2 -1 5 503728
+3 -1 5 503728
+4 -1 5 7d5435
+0 -3 6 503728
+1 -3 6 503728
+2 -3 6 503728
+3 -3 6 503728
+4 -3 6 503728
+5 -3 6 503728
+0 -2 6 503728
+1 -2 6 503728
+2 -2 6 503728
+3 -2 6 503728
+4 -2 6 503728
+4 -1 6 7d5435
+0 -2 7 503728
+1 -2 7 503728
+2 -2 7 503728
+3 -2 7 503728
+4 -2 7 503728
+5 -2 7 503728
+0 -1 7 503728
+1 -1 7 503728
+2 -1 7 503728
+3 -1 7 503728
+4 -1 7 503728
+0 -1 8 503728
+1 -1 8 503728
+2 -1 8 503728
+3 -1 8 503728
+4 -1 8 503728
+5 -1 8 503728
+-5 -3 0 7d5435
+-4 -3 0 7d5435
+-3 -3 0 7d5435
+-2 -3 0 7d5435
+-1 -3 0 7d5435
+-5 -2 0 7d5435
+-4 -2 0 7d5435
+-3 -2 0 7d5435
+-2 -2 0 7d5435
+-1 -2 0 7d5435
+-5 -1 0 7d5435
+-4 -1 0 7d5435
+-3 -1 0 7d5435
+-2 -1 0 7d5435
+-1 -1 0 7d5435
+-5 -3 1 7d5435
+-4 -3 1 7d5435
+-3 -3 1 7d5435
+-2 -3 1 7d5435
+-1 -3 1 7d5435
+-5 -2 1 7d5435
+-5 -1 1 7d5435
+-5 -3 2 7d5435
+-4 -3 2 7d5435
+-1 -3 2 7d5435
+-5 -3 3 7d5435
+-4 -3 3 7d5435
+-1 -3 3 7d5435
+-5 -3 4 7d5435
+-4 -3 4 7d5435
+-3 -3 4 7d5435
+-2 -3 4 7d5435
+-1 -3 4 7d5435
+-5 -2 4 7d5435
+-5 -1 4 7d5435
+-6 -4 5 503728
+-5 -4 5 503728
+-4 -4 5 503728
+-3 -4 5 503728
+-2 -4 5 503728
+-1 -4 5 503728
+-5 -3 5 503728
+-4 -3 5 503728
+-3 -3 5 503728
+-2 -3 5 503728
+-1 -3 5 503728
+-5 -2 5 7d5435
+-4 -2 5 503728
+-3 -2 5 503728
+-2 -2 5 503728
+-1 -2 5 503728
+-5 -1 5 7d5435
+-4 -1 5 503728
+-3 -1 5 503728
+-2 -1 5 503728
+-1 -1 5 503728
+-6 -3 6 503728
+-5 -3 6 503728
+-4 -3 6 503728
+-3 -3 6 503728
+-2 -3 6 503728
+-1 -3 6 503728
+-5 -2 6 503728
+-4 -2 6 503728
+-3 -2 6 503728
+-2 -2 6 503728
+-1 -2 6 503728
+-5 -1 6 7d5435
+-6 -2 7 503728
+-5 -2 7 503728
+-4 -2 7 503728
+-3 -2 7 503728
+-2 -2 7 503728
+-1 -2 7 503728
+-5 -1 7 503728
+-4 -1 7 503728
+-3 -1 7 503728
+-2 -1 7 503728
+-1 -1 7 503728
+-6 -1 8 503728
+-5 -1 8 503728
+-4 -1 8 503728
+-3 -1 8 503728
+-2 -1 8 503728
+-1 -1 8 503728
+0 0 0 7d5435
+1 0 0 7d5435
+2 0 0 7d5435
+3 0 0 7d5435
+4 0 0 7d5435
+0 1 0 7d5435
+1 1 0 7d5435
+2 1 0 7d5435
+3 1 0 7d5435
+4 1 0 7d5435
+0 2 0 7d5435
+1 2 0 7d5435
+2 2 0 7d5435
+3 2 0 7d5435
+4 2 0 7d5435
+0 3 0 7d5435
+1 3 0 7d5435
+2 3 0 7d5435
+3 3 0 7d5435
+4 3 0 7d5435
+0 4 0 7d5435
+1 4 0 7d5435
+2 4 0 7d5435
+3 4 0 7d5435
+4 4 0 7d5435
+4 2 1 7d5435
+4 3 1 7d5435
+0 4 1 7d5435
+1 4 1 7d5435
+2 4 1 7d5435
+3 4 1 7d5435
+4 4 1 7d5435
+4 2 2 7d5435
+4 3 2 7d5435
+1 4 2 7d5435
+4 4 2 7d5435
+4 2 3 7d5435
+4 3 3 7d5435
+1 4 3 7d5435
+4 4 3 7d5435
+4 0 4 7d5435
+4 1 4 7d5435
+4 2 4 7d5435
+4 3 4 7d5435
+0 4 4 7d5435
+1 4 4 7d5435
+2 4 4 7d5435
+3 4 4 7d5435
+4 4 4 7d5435
+0 0 5 503728
+1 0 5 503728
+2 0 5 503728
+3 0 5 503728
+4 0 5 7d5435
+0 1 5 503728
+1 1 5 503728
+2 1 5 503728
+3 1 5 503728
+4 1 5 7d5435
+0 2 5 503728
+1 2 5 503728
+2 2 5 503728
+3 2 5 503728
+4 2 5 7d5435
+0 3 5 503728
+1 3 5 503728
+2 3 5 503728
+3 3 5 503728
+4 3 5 7d5435
+0 4 5 503728
+1 4 5 503728
+2 4 5 503728
+3 4 5 503728
+4 4 5 503728
+0 5 5 503728
+1 5 5 503728
+2 5 5 503728
+3 5 5 503728
+4 5 5 503728
+5 5 5 503728
+4 0 6 7d5435
+4 1 6 7d5435
+4 2 6 7d5435
+0 3 6 503728
+1 3 6 503728
+2 3 6 503728
+3 3 6 503728
+4 3 6 503728
+0 4 6 503728
+1 4 6 503728
+2 4 6 503728
+3 4 6 503728
+4 4 6 503728
+5 4 6 503728
+4 0 7 503728
+4 1 7 503728
+0 2 7 503728
+1 2 7 503728
+2 2 7 503728
+3 2 7 503728
+4 2 7 503728
+0 3 7 503728
+1 3 7 503728
+2 3 7 503728
+3 3 7 503728
+4 3 7 503728
+5 3 7 503728
+0 0 8 503728
+1 0 8 503728
+2 0 8 503728
+3 0 8 503728
+4 0 8 503728
+5 0 8 503728
+0 1 8 503728
+1 1 8 503728
+2 1 8 503728
+3 1 8 503728
+4 1 8 503728
+5 1 8 503728
+0 2 8 503728
+1 2 8 503728
+2 2 8 503728
+3 2 8 503728
+4 2 8 503728
+5 2 8 503728
+-5 0 0 7d5435
+-4 0 0 7d5435
+-3 0 0 7d5435
+-2 0 0 7d5435
+-1 0 0 7d5435
+-5 1 0 7d5435
+-4 1 0 7d5435
+-3 1 0 7d5435
+-2 1 0 7d5435
+-1 1 0 7d5435
+-5 2 0 7d5435
+-4 2 0 7d5435
+-3 2 0 7d5435
+-2 2 0 7d5435
+-1 2 0 7d5435
+-5 3 0 7d5435
+-4 3 0 7d5435
+-3 3 0 7d5435
+-2 3 0 7d5435
+-1 3 0 7d5435
+-5 4 0 7d5435
+-4 4 0 7d5435
+-3 4 0 7d5435
+-2 4 0 7d5435
+-1 4 0 7d5435
+-5 0 1 7d5435
+-5 1 1 7d5435
+-5 2 1 7d5435
+-5 3 1 7d5435
+-5 4 1 7d5435
+-4 4 1 7d5435
+-3 4 1 7d5435
+-2 4 1 7d5435
+-1 4 1 7d5435
+-5 1 2 7d5435
+-5 2 2 7d5435
+-5 4 2 7d5435
+-3 4 2 7d5435
+-5 1 3 7d5435
+-5 2 3 7d5435
+-5 4 3 7d5435
+-3 4 3 7d5435
+-5 0 4 7d5435
+-5 1 4 7d5435
+-5 2 4 7d5435
+-5 3 4 7d5435
+-5 4 4 7d5435
+-4 4 4 7d5435
+-3 4 4 7d5435
+-2 4 4 7d5435
+-1 4 4 7d5435
+-5 0 5 7d5435
+-4 0 5 503728
+-3 0 5 503728
+-2 0 5 503728
+-1 0 5 503728
+-5 1 5 7d5435
+-4 1 5 503728
+-3 1 5 503728
+-2 1 5 503728
+-1 1 5 503728
+-5 2 5 7d5435
+-4 2 5 503728
+-3 2 5 503728
+-2 2 5 503728
+-1 2 5 503728
+-5 3 5 7d5435
+-4 3 5 503728
+-3 3 5 503728
+-2 3 5 503728
+-1 3 5 503728
+-5 4 5 503728
+-4 4 5 503728
+-3 4 5 503728
+-2 4 5 503728
+-1 4 5 503728
+-6 5 5 503728
+-5 5 5 503728
+-4 5 5 503728
+-3 5 5 503728
+-2 5 5 503728
+-1 5 5 503728
+-5 0 6 7d5435
+-5 1 6 7d5435
+-5 2 6 7d5435
+-5 3 6 503728
+-4 3 6 503728
+-3 3 6 503728
+-2 3 6 503728
+-1 3 6 503728
+-6 4 6 503728
+-5 4 6 503728
+-4 4 6 503728
+-3 4 6 503728
+-2 4 6 503728
+-1 4 6 503728
+-5 0 7 503728
+-5 1 7 503728
+-5 2 7 503728
+-4 2 7 503728
+-3 2 7 503728
+-2 2 7 503728
+-1 2 7 503728
+-6 3 7 503728
+-5 3 7 503728
+-4 3 7 503728
+-3 3 7 503728
+-2 3 7 503728
+-1 3 7 503728
+-6 0 8 503728
+-5 0 8 503728
+-4 0 8 503728
+-3 0 8 503728
+-2 0 8 503728
+-1 0 8 503728
+-6 1 8 503728
+-5 1 8 503728
+-4 1 8 503728
+-3 1 8 503728
+-2 1 8 503728
+-1 1 8 503728
+-6 2 8 503728
+-5 2 8 503728
+-4 2 8 503728
+-3 2 8 503728
+-2 2 8 503728
+-1 2 8 503728
+++ /dev/null
-in vec3 fragmentPosition;
-in vec3 fragmentNormal;
-in float fragmentTextureIndex;
-in vec2 fragmentTextureCoords;
-in vec3 fragmentColor;
-
-out vec4 outColor;
-
-uniform vec3 fogColor;
-uniform vec3 cameraPos;
-uniform sampler2D textures[MAX_TEXTURE_UNITS];
-
-void main()
-{
- outColor = texture(textures[int(fragmentTextureIndex + 0.5)], fragmentTextureCoords) * vec4(fragmentColor, 1.0);
- outColor.rgb = mix(outColor.rgb, fogColor, clamp(length(fragmentPosition - cameraPos) / RENDER_DISTANCE, 0.0, 1.0));
-
- if (outColor.a == 0.0)
- discard;
-}
+++ /dev/null
-layout(location = 0) in vec3 vertexPosition;
-layout(location = 1) in vec3 vertexNormal;
-layout(location = 2) in float vertexTextureIndex;
-layout(location = 3) in vec2 vertexTextureCoords;
-layout(location = 4) in vec3 vertexColor;
-
-out vec3 fragmentPosition;
-out vec3 fragmentNormal;
-out float fragmentTextureIndex;
-out vec2 fragmentTextureCoords;
-out vec3 fragmentColor;
-
-uniform mat4 model;
-uniform mat4 VP;
-uniform float daylight;
-uniform float ambientLight;
-uniform vec3 lightDir;
-
-void main()
-{
- vec4 worldSpace = model * vec4(vertexPosition, 1.0);
- gl_Position = VP * worldSpace;
-
- fragmentPosition = worldSpace.xyz;
- fragmentNormal = vertexNormal;
- fragmentTextureIndex = vertexTextureIndex;
- fragmentTextureCoords = vertexTextureCoords;
- fragmentColor = vertexColor;
-
- float diffuseLight = 0.3 * daylight * clamp(dot(normalize(fragmentNormal), normalize(lightDir)), 0.0, 1.0);
- float light = ambientLight + diffuseLight;
-
- fragmentColor *= light;
-}
--- /dev/null
+in vec3 fragmentPosition;
+in vec3 fragmentNormal;
+in vec2 fragmentTextureCoords;
+in float fragmentTextureIndex;
+in vec3 fragmentColor;
+
+out vec4 outColor;
+
+uniform vec3 fogColor;
+uniform vec3 cameraPos;
+uniform sampler2D textures[MAX_TEXTURE_UNITS];
+
+void main()
+{
+ outColor = texture(textures[int(fragmentTextureIndex + 0.5)], fragmentTextureCoords) * vec4(fragmentColor, 1.0);
+ outColor.rgb = mix(outColor.rgb, fogColor, clamp(length(fragmentPosition - cameraPos) / VIEW_DISTANCE, 0.0, 1.0));
+
+ if (outColor.a == 0.0)
+ discard;
+}
--- /dev/null
+layout(location = 0) in vec3 vertexPosition;
+layout(location = 1) in vec3 vertexNormal;
+layout(location = 2) in vec2 vertexTextureCoords;
+layout(location = 3) in float vertexTextureIndex;
+layout(location = 4) in vec3 vertexColor;
+
+out vec3 fragmentPosition;
+out vec3 fragmentNormal;
+out vec2 fragmentTextureCoords;
+out float fragmentTextureIndex;
+out vec3 fragmentColor;
+
+uniform mat4 model;
+uniform mat4 VP;
+uniform float daylight;
+uniform float ambientLight;
+uniform vec3 lightDir;
+
+void main()
+{
+ vec4 worldSpace = model * vec4(vertexPosition, 1.0);
+ gl_Position = VP * worldSpace;
+
+ fragmentPosition = worldSpace.xyz;
+ fragmentNormal = vertexNormal;
+ fragmentTextureCoords = vertexTextureCoords;
+ fragmentTextureIndex = vertexTextureIndex;
+ fragmentColor = vertexColor;
+
+ float diffuseLight = 0.3 * daylight * clamp(dot(normalize(fragmentNormal), normalize(lightDir)), 0.0, 1.0);
+ float light = ambientLight + diffuseLight;
+
+ fragmentColor *= light;
+}
-#! /bin/bash
-./DragonblocksServer "[::1]:4000" & echo "singleplayer" | ./Dragonblocks "[::1]:4000"; killall DragonblocksServer
+#!/bin/bash
+./dragonblocks_server "[::1]:4000" &
+echo "singleplayer" | ./dragonblocks "[::1]:4000"
+pkill -P $$
-#! /bin/bash
+#!/bin/bash
+VERSION=`git tag --points-at HEAD`
+if [[ $VERSION = "" ]]; then
+ VERSION=`git rev-parse --short HEAD`
+fi
+DIR=dragonblocks_alpha-$VERSION
mkdir .build
cp -r * .build/
cd .build/
rm -rf .build
exit 1
fi
-cp Dragonblocks DragonblocksServer ..
+cp dragonblocks dragonblocks_server ..
cd ..
-rm -rf .git* deps src build BUILDING.md snapshot.sh upload.sh DragonblocksAlpha-*.zip DragonblocksAlpha screenshot-*.png
+rm -rf .git* deps src build BUILDING.md snapshot.sh upload.sh dragonblocks_alpha-* screenshot-*.png
cd ..
-mv .build DragonblocksAlpha
-VERSION=`git tag --points-at HEAD`
-if [[ $VERSION = "" ]]; then
- VERSION=`git rev-parse --short HEAD`
-fi
-zip -r DragonblocksAlpha-$VERSION.zip DragonblocksAlpha/*
-rm -rf DragonblocksAlpha
+mv .build $DIR
+zip -r $DIR.zip $DIR/*
+rm -rf $DIR
# Options
-add_compile_definitions("RESSOURCE_PATH=\"${RESSOURCE_PATH}\"")
add_compile_definitions("USE_DRAGONNET")
+add_compile_definitions("RESSOURCE_PATH=\"${RESSOURCE_PATH}\"")
-add_compile_options(-Wall -Wextra -Werror -fmax-errors=4)
+add_compile_options(-Wall -Wextra -Werror -Wno-address-of-packed-member -fmax-errors=4)
link_libraries(
pthread
# Common sources
set(COMMON_SOURCES
+ "${DEPS_DIR}/asprintf/asprintf.c"
"${DEPS_DIR}/dragonnet/addr.c"
"${DEPS_DIR}/dragonnet/listen.c"
"${DEPS_DIR}/dragonnet/peer.c"
"${DEPS_DIR}/dragonnet/recv.c"
"${DEPS_DIR}/dragonnet/recv_thread.c"
"${DEPS_DIR}/dragonnet/send.c"
- "${DEPS_DIR}/dragonport/asprintf.c"
"${DEPS_DIR}/dragonstd/array.c"
- "${DEPS_DIR}/dragonstd/bintree.c"
"${DEPS_DIR}/dragonstd/flag.c"
"${DEPS_DIR}/dragonstd/list.c"
+ "${DEPS_DIR}/dragonstd/map.c"
"${DEPS_DIR}/dragonstd/queue.c"
+ "${DEPS_DIR}/dragonstd/refcount.c"
+ "${DEPS_DIR}/dragonstd/tree.c"
+ "${DEPS_DIR}/dragonstd/bits/compare.c"
"${DEPS_DIR}/linenoise/linenoise.c"
"${DEPS_DIR}/perlin/perlin.c"
+ color.c
config.c
day.c
environment.c
interrupt.c
- map.c
node.c
perlin.c
+ physics.c
+ terrain.c
types.c
- util.c
)
# Client
-add_executable(Dragonblocks
+add_executable(dragonblocks
${COMMON_SOURCES}
- client/blockmesh.c
client/camera.c
client/client.c
client/client_auth.c
client/client_config.c
- client/client_map.c
+ client/client_entity.c
client/client_node.c
client/client_player.c
+ client/client_terrain.c
client/cube.c
client/debug_menu.c
client/facecache.c
client/gui.c
client/input.c
client/mesh.c
- client/object.c
- client/scene.c
+ client/model.c
client/shader.c
client/sky.c
+ client/terrain_gfx.c
client/texture.c
- client/vertex.c
client/window.c
)
-target_link_libraries(Dragonblocks
+target_link_libraries(dragonblocks
${OPENGL_LIBRARIES}
${GLEW_LIBRARIES}
glfw
${FREETYPE_LIBRARIES}
)
-target_include_directories(Dragonblocks PUBLIC
+target_include_directories(dragonblocks PUBLIC
${FREETYPE_INCLUDE_DIRS}
)
# Server
-add_executable(DragonblocksServer
+add_executable(dragonblocks_server
${COMMON_SOURCES}
server/biomes.c
server/database.c
- server/mapgen.c
+ server/schematic.c
server/server.c
server/server_config.c
- server/server_map.c
server/server_player.c
+ server/server_terrain.c
+ server/terrain_gen.c
server/trees.c
server/voxelctx.c
)
-target_link_libraries(DragonblocksServer
+target_link_libraries(dragonblocks_server
sqlite3
)
# Version
-add_custom_target(Version
+add_custom_target(version
COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${CMAKE_SOURCE_DIR} -P ${CMAKE_SOURCE_DIR}/version.cmake
)
-add_dependencies(Dragonblocks Version)
-add_dependencies(DragonblocksServer Version)
+add_dependencies(dragonblocks version)
+add_dependencies(dragonblocks_server version)
# Types
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
-add_custom_target(DragonnetTypes
+add_custom_target(types
DEPENDS "${CMAKE_SOURCE_DIR}/types.c" "${CMAKE_SOURCE_DIR}/types.h"
)
-add_dependencies(Dragonblocks DragonnetTypes)
-add_dependencies(DragonblocksServer DragonnetTypes)
+add_dependencies(dragonblocks types)
+add_dependencies(dragonblocks_server types)
+++ /dev/null
-#include "client/blockmesh.h"
-#include "client/client_map.h"
-#include "client/client_node.h"
-#include "client/cube.h"
-
-static v3s8 fdir[6] = {
- {+0, +0, -1},
- {+0, +0, +1},
- {-1, +0, +0},
- {+1, +0, +0},
- {+0, -1, +0},
- {+0, +1, +0},
-};
-
-static s32 half_block_size = MAPBLOCK_SIZE / 2;
-
-static void make_vertices(Object *object, MapBlock *block, bool hide_edges)
-{
- object->visible = false;
- v3s32 node_bp = {
- block->pos.x * MAPBLOCK_SIZE,
- block->pos.y * MAPBLOCK_SIZE,
- block->pos.z * MAPBLOCK_SIZE
- };
-
- ITERATE_MAPBLOCK {
- MapNode *node = &block->data[x][y][z];
- ClientNodeDefinition *def = &client_node_definitions[node->type];
-
- if (def->visibility != NV_NONE) {
- v3f32 offset = {
- x - half_block_size - 0.5,
- y - half_block_size - 0.5,
- z - half_block_size - 0.5
- };
-
- for (int f = 0; f < 6; f++) {
- v3s8 npos = {
- x + fdir[f].x,
- y + fdir[f].y,
- z + fdir[f].z,
- };
-
- bool direct_neighbor = npos.x >= 0 && npos.x < MAPBLOCK_SIZE
- && npos.y >= 0 && npos.y < MAPBLOCK_SIZE
- && npos.z >= 0 && npos.z < MAPBLOCK_SIZE;
-
- MapNode neighbor = direct_neighbor
- ? block->data[npos.x][npos.y][npos.z]
- : map_get_node(client_map.map, (v3s32) {npos.x + node_bp.x, npos.y + node_bp.y, npos.z + node_bp.z});
-
- bool transparency_edge = def->visibility != NV_BLEND || neighbor.type != node->type;
-
- bool render_node = def->visibility == NV_CLIP || (neighbor.type != NODE_UNLOADED
- && client_node_definitions[neighbor.type].visibility != NV_SOLID
- && transparency_edge);
-
- object->visible = object->visible || render_node;
-
- if (! hide_edges && ! direct_neighbor)
- render_node = transparency_edge;
-
- if (render_node) {
- object->transparent = object->transparent || def->visibility == NV_BLEND;
- object_set_texture(object, def->tiles.textures[f]);
-
- for (int v = 0; v < 6; v++) {
- Vertex3D vertex = cube_vertices[f][v];
- vertex.position.x += offset.x;
- vertex.position.y += offset.y;
- vertex.position.z += offset.z;
-
- if (def->render)
- def->render((v3s32) {x + node_bp.x, y + node_bp.y, z + node_bp.z}, node, &vertex, f, v);
-
- object_add_vertex(object, &vertex);
- }
- }
- }
- }
- }
-}
-
-static void animate_mapblock_mesh(Object *obj, f64 dtime)
-{
- obj->scale.x += dtime * 2.0;
-
- if (obj->scale.x > 1.0f) {
- obj->scale.x = 1.0f;
- client_map_schedule_update_block_mesh(obj->extra);
- }
-
- obj->scale.z = obj->scale.y = obj->scale.x;
-
- object_transform(obj);
-}
-
-void blockmesh_make(MapBlock *block)
-{
- MapBlockExtraData *extra = block->extra;
-
- Object *obj = object_create();
-
- obj->pos = (v3f32) {block->pos.x * MAPBLOCK_SIZE + half_block_size + 0.5f, block->pos.y * MAPBLOCK_SIZE + half_block_size + 0.5f, block->pos.z * MAPBLOCK_SIZE + half_block_size + 0.5f};
- obj->scale = (v3f32) {0.1f, 0.1f, 0.1f};
- obj->frustum_culling = true;
- obj->box = (aabb3f32) {{-half_block_size - 1.0f, -half_block_size - 1.0f, -half_block_size - 1.0f}, {half_block_size + 1.0f, half_block_size + 1.0f, half_block_size + 1.0f}};
- obj->on_render = (obj->scale.x == 1.0f) ? NULL : &animate_mapblock_mesh;
- obj->extra = block;
-
- make_vertices(obj, block, obj->scale.x == 1.0f);
-
- if (! object_add_to_scene(obj)) {
- object_delete(obj);
- obj = NULL;
- }
-
- pthread_mutex_lock(&block->mtx);
- if (extra->obj) {
- if (obj) {
- obj->scale = extra->obj->scale;
- object_transform(obj);
- }
-
- extra->obj->remove = true;
- }
-
- extra->obj = obj;
- pthread_mutex_unlock(&block->mtx);
-}
+++ /dev/null
-#ifndef _BLOCKMESH_H_
-#define _BLOCKMESH_H_
-
-#include "map.h"
-
-void blockmesh_make(MapBlock *block);
-
-#endif
#include <math.h>
-#include "client/client.h"
#include "client/camera.h"
+#include "client/client.h"
struct Camera camera;
#include <linmath.h/linmath.h>
#include "types.h"
-extern struct Camera
-{
+extern struct Camera {
mat4x4 view;
vec3 eye, front, right, up;
struct {
void camera_set_angle(f32 yaw, f32 pitch);
void camera_on_resize(int width, int height);
-#endif
+#endif // _CAMERA_H_
+#define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <dragonstd/flag.h>
#include <stdio.h>
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
#include <unistd.h>
-#include <dragonstd/flag.h>
#include "client/client.h"
#include "client/client_auth.h"
-#include "client/client_map.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
+#include "client/debug_menu.h"
#include "client/game.h"
#include "client/input.h"
#include "day.h"
#include "interrupt.h"
#include "perlin.h"
#include "types.h"
-#include "util.h"
DragonnetPeer *client;
-static Flag *finish;
-static bool on_recv(unused DragonnetPeer *peer, DragonnetTypeId type, unused void *pkt)
+static Flag finish;
+static Flag gfx_init;
+
+static bool on_recv(__attribute__((unused)) DragonnetPeer *peer, DragonnetTypeId type, __attribute__((unused)) void *pkt)
{
- return (client_auth.state == AUTH_WAIT) == (type == DRAGONNET_TYPE_ToClientAuth);
+ bool allowed = false;
+ pthread_mutex_lock(&client_auth.mtx);
+
+ // this code exists to stop malicious or malfunctioning packets
+ switch (client_auth.state) {
+ // the server shouldn't send anything during auth preparation, drop it
+ case AUTH_INIT:
+ allowed = false;
+ break;
+
+ // only the auth packet is allowed before auth is finished
+ case AUTH_WAIT:
+ allowed = type == DRAGONNET_TYPE_ToClientAuth;
+ break;
+
+ // don't process auth packets when auth is already finished
+ case AUTH_SUCCESS:
+ allowed = type != DRAGONNET_TYPE_ToClientAuth;
+ break;
+ }
+
+ /*
+ It is important that the auth state does not change to until the packet is
+ processed.
+
+ However, the only state change done by other threads is AUTH_INIT -> AUTH_WAIT,
+ which is not problematic since packets that are received during AUTH_INIT
+ are not processed, they are always dropped.
+
+ Therefore the mutex can be unlocked at this point.
+ */
+ pthread_mutex_unlock(&client_auth.mtx);
+ return allowed;
}
-static void on_disconnect(unused DragonnetPeer *peer)
+static void on_disconnect(__attribute__((unused)) DragonnetPeer *peer)
{
- flag_set(interrupt);
- flag_wait(finish);
+ flag_set(&interrupt);
+ // don't free the connection before all other client components have shut down
+ flag_slp(&finish);
}
-static void on_ToClientAuth(unused DragonnetPeer *peer, ToClientAuth *pkt)
+static void on_ToClientAuth(__attribute__((unused)) DragonnetPeer *peer, ToClientAuth *pkt)
{
+ pthread_mutex_lock(&client_auth.mtx);
if (pkt->success) {
client_auth.state = AUTH_SUCCESS;
- printf("Authenticated successfully\n");
+ printf("[access] authenticated successfully\n");
} else {
client_auth.state = AUTH_INIT;
- printf("Authentication failed, please try again\n");
+ printf("[access] authentication failed, please try again\n");
}
+ pthread_cond_signal(&client_auth.cv);
+ pthread_mutex_unlock(&client_auth.mtx);
+
+ // yield the connection until the game is fully initialized
+ if (pkt->success)
+ flag_slp(&gfx_init);
}
-static void on_ToClientBlock(unused DragonnetPeer *peer, ToClientBlock *pkt)
+static void on_ToClientChunk(__attribute__((unused)) DragonnetPeer *peer, ToClientChunk *pkt)
{
- MapBlock *block = map_get_block(client_map.map, pkt->pos, true);
+ TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, true);
- map_deserialize_block(block, pkt->data);
- ((MapBlockExtraData *) block->extra)->all_air = (pkt->data.siz == 0);
- client_map_block_received(block);
+ terrain_deserialize_chunk(chunk, pkt->data);
+ ((TerrainChunkMeta *) chunk->extra)->empty = (pkt->data.siz == 0);
+ client_terrain_chunk_received(chunk);
}
-static void on_ToClientInfo(unused DragonnetPeer *peer, ToClientInfo *pkt)
+static void on_ToClientInfo(__attribute__((unused)) DragonnetPeer *peer, ToClientInfo *pkt)
{
- client_map_set_simulation_distance(pkt->simulation_distance);
+ client_terrain_set_load_distance(pkt->load_distance);
seed = pkt->seed;
}
-static void on_ToClientPos(unused DragonnetPeer *peer, ToClientPos *pkt)
+static void on_ToClientTimeOfDay(__attribute__((unused)) DragonnetPeer *peer, ToClientTimeOfDay *pkt)
{
- client_player_set_position(pkt->pos);
+ set_time_of_day(pkt->time_of_day);
}
-static void on_ToClientTimeOfDay(unused DragonnetPeer *peer, ToClientTimeOfDay *pkt)
+static void on_ToClientMovement(__attribute__((unused)) DragonnetPeer *peer, ToClientMovement *pkt)
{
- set_time_of_day(pkt->time_of_day);
+ pthread_rwlock_wrlock(&client_player.lock_movement);
+ client_player.movement = *pkt;
+ pthread_rwlock_unlock(&client_player.lock_movement);
+
+ debug_menu_changed(ENTRY_FLIGHT);
+ debug_menu_changed(ENTRY_COLLISION);
}
int main(int argc, char **argv)
{
+#ifdef __GLIBC__ // check whether bloat is enabled
+ pthread_setname_np(pthread_self(), "main");
+#endif // __GLIBC__
+
if (argc < 2) {
- fprintf(stderr, "Missing address\n");
+ fprintf(stderr, "[error] missing address\n");
return EXIT_FAILURE;
}
- if (! (client = dragonnet_connect(argv[1]))) {
- fprintf(stderr, "Failed to connect to server\n");
+ if (!(client = dragonnet_connect(argv[1]))) {
+ fprintf(stderr, "[error] failed to connect to server\n");
return EXIT_FAILURE;
}
- client->on_disconnect = &on_disconnect;
- client->on_recv = &on_recv;
- client->on_recv_type[DRAGONNET_TYPE_ToClientAuth ] = (void *) &on_ToClientAuth;
- client->on_recv_type[DRAGONNET_TYPE_ToClientBlock ] = (void *) &on_ToClientBlock;
- client->on_recv_type[DRAGONNET_TYPE_ToClientInfo ] = (void *) &on_ToClientInfo;
- client->on_recv_type[DRAGONNET_TYPE_ToClientPos ] = (void *) &on_ToClientPos;
- client->on_recv_type[DRAGONNET_TYPE_ToClientTimeOfDay] = (void *) &on_ToClientTimeOfDay;
+ char *address = dragonnet_addr_str(client->raddr);
+ printf("[access] connected to %s\n", address);
+ free(address);
- finish = flag_create();
+ client->on_disconnect = &on_disconnect;
+ client->on_recv = (void *) &on_recv;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientAuth ] = (void *) &on_ToClientAuth;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientChunk ] = (void *) &on_ToClientChunk;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientInfo ] = (void *) &on_ToClientInfo;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientTimeOfDay ] = (void *) &on_ToClientTimeOfDay;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientMovement ] = (void *) &on_ToClientMovement;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityAdd ] = (void *) &client_entity_add;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityRemove ] = (void *) &client_entity_remove;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityUpdatePosRot ] = (void *) &client_entity_update_pos_rot;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityUpdateBoxEye ] = (void *) &client_entity_update_box_eye;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityUpdateNametag] = (void *) &client_entity_update_nametag;
+
+ flag_ini(&finish);
+ flag_ini(&gfx_init);
interrupt_init();
- client_map_init();
+ client_terrain_init();
client_player_init();
+ client_entity_init();
dragonnet_peer_run(client);
- if (! client_auth_init())
+ if (!client_auth_init())
return EXIT_FAILURE;
- if (! game())
+ if (!game(&gfx_init))
return EXIT_FAILURE;
dragonnet_peer_shutdown(client);
client_auth_deinit();
+ client_entity_deinit();
client_player_deinit();
- client_map_deinit();
+ client_terrain_deinit();
interrupt_deinit();
pthread_t recv_thread = client->recv_thread;
- flag_set(finish);
+ flag_set(&finish);
pthread_join(recv_thread, NULL);
- flag_delete(finish);
+ flag_dst(&finish);
+ flag_dst(&gfx_init);
return EXIT_SUCCESS;
}
extern DragonnetPeer *client;
-#endif
+#endif // _CLIENT_H_
#include "interrupt.h"
#include "types.h"
-volatile struct ClientAuth client_auth;
+struct ClientAuth client_auth;
-static bool name_prompt()
+static void auth_loop()
{
- if (! (client_auth.name = linenoise("Enter name: ")))
- return false;
+ while (!interrupt.set) switch (client_auth.state) {
+ case AUTH_INIT:
+ if (client_auth.name)
+ linenoiseFree(client_auth.name);
- printf("Authenticating as %s...\n", client_auth.name);
- client_auth.state = AUTH_WAIT;
+ if (!(client_auth.name = linenoise("Enter name: ")))
+ return;
- dragonnet_peer_send_ToServerAuth(client, &(ToServerAuth) {
- .name = client_auth.name,
- });
+ printf("[access] authenticating as %s...\n", client_auth.name);
+ client_auth.state = AUTH_WAIT;
- return true;
+ dragonnet_peer_send_ToServerAuth(client, &(ToServerAuth) {
+ .name = client_auth.name,
+ });
+
+ __attribute__((fallthrough));
+
+ case AUTH_WAIT:
+ pthread_cond_wait(&client_auth.cv, &client_auth.mtx);
+ break;
+
+ case AUTH_SUCCESS:
+ return;
+ }
}
bool client_auth_init()
{
+ client_auth.name = NULL;
+ pthread_cond_init(&client_auth.cv, NULL);
+ pthread_mutex_init(&client_auth.mtx, NULL);
+
+ pthread_mutex_lock(&client_auth.mtx);
client_auth.state = AUTH_INIT;
+ flag_sub(&interrupt, &client_auth.cv); // make sure Ctrl+C will work during AUTH_WAIT
- while (! interrupt->done) {
- switch (client_auth.state) {
- case AUTH_INIT:
- if (name_prompt())
- break;
- else
- return false;
-
- case AUTH_WAIT:
- sched_yield();
- break;
-
- case AUTH_SUCCESS:
- return true;
- }
- }
+ auth_loop();
+
+ flag_uns(&interrupt, &client_auth.cv);
+ bool success = client_auth.state == AUTH_SUCCESS;
+ pthread_mutex_unlock(&client_auth.mtx);
- return false;
+ return success;
}
void client_auth_deinit()
{
+ pthread_cond_destroy(&client_auth.cv);
+ pthread_mutex_destroy(&client_auth.mtx);
linenoiseFree(client_auth.name);
}
#ifndef _CLIENT_AUTH_H_
#define _CLIENT_AUTH_H_
-typedef enum
-{
+#include <pthread.h>
+
+typedef enum {
AUTH_INIT,
AUTH_WAIT,
AUTH_SUCCESS,
} ClientAuthState;
-extern volatile struct ClientAuth
-{
+extern struct ClientAuth {
char *name;
ClientAuthState state;
+ pthread_cond_t cv;
+ pthread_mutex_t mtx;
} client_auth;
bool client_auth_init();
-void client_auth_assert_state(ClientAuthState state, const char *pkt);
void client_auth_deinit();
-#endif
+#endif // _CLIENT_AUTH_H_
struct ClientConfig client_config = {
.antialiasing = 4,
.mipmap = true,
- .render_distance = 255.0,
+ .view_distance = 255.0,
.vsync = true,
.meshgen_threads = 4,
};
+#define NUM_CONFIG_ENTRIES 5
+static ConfigEntry config_entries[NUM_CONFIG_ENTRIES] = {
+ {
+ .type = CONFIG_UINT,
+ .key = "antialiasing",
+ .value = &client_config.antialiasing,
+ },
+ {
+ .type = CONFIG_BOOL,
+ .key = "mipmap",
+ .value = &client_config.mipmap,
+ },
+ {
+ .type = CONFIG_FLOAT,
+ .key = "view_distance",
+ .value = &client_config.view_distance,
+ },
+ {
+ .type = CONFIG_BOOL,
+ .key = "vsync",
+ .value = &client_config.vsync,
+ },
+ {
+ .type = CONFIG_UINT,
+ .key = "meshgen_threads",
+ .value = &client_config.meshgen_threads,
+ },
+};
+
__attribute__((constructor)) static void client_config_init()
{
- config_read("client.conf", (ConfigEntry[]) {
- {
- .type = CT_UINT,
- .key = "antialiasing",
- .value = &client_config.antialiasing,
- },
- {
- .type = CT_BOOL,
- .key = "mipmap",
- .value = &client_config.mipmap,
- },
- {
- .type = CT_FLOAT,
- .key = "render_distance",
- .value = &client_config.render_distance,
- },
- {
- .type = CT_BOOL,
- .key = "vsync",
- .value = &client_config.vsync,
- },
- {
- .type = CT_UINT,
- .key = "meshgen_threads",
- .value = &client_config.meshgen_threads,
- },
- }, 5);
+ config_read("client.conf", config_entries, NUM_CONFIG_ENTRIES);
}
+__attribute__((destructor)) static void client_config_deinit()
+{
+ config_free(config_entries, NUM_CONFIG_ENTRIES);
+}
extern struct ClientConfig {
unsigned int antialiasing;
bool mipmap;
- double render_distance;
+ double view_distance;
bool vsync;
unsigned int meshgen_threads;
} client_config;
--- /dev/null
+#include <dragonstd/map.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "client/client_entity.h"
+#include "client/client_player.h"
+
+ClientEntityType client_entity_types[COUNT_ENTITY];
+
+static Map entities;
+
+// any thread
+// called when adding, getting or removing an entity from the map
+static int cmp_entity(const Refcount *entity, const u64 *id)
+{
+ return u64_cmp(&((ClientEntity *) entity->obj)->data.id, id);
+}
+
+// recv thread
+// called when server sent removal of entity
+static void entity_drop(ClientEntity *entity)
+{
+ if (entity->type->remove)
+ entity->type->remove(entity);
+
+ refcount_drp(&entity->rc);
+}
+
+// any thread
+// called when all refs have been dropped
+static void entity_delete(ClientEntity *entity)
+{
+ if (entity->type->free)
+ entity->type->free(entity);
+
+ refcount_dst(&entity->rc);
+
+ if (entity->data.nametag)
+ free(entity->data.nametag);
+
+ pthread_rwlock_init(&entity->lock_pos_rot, NULL);
+ pthread_rwlock_init(&entity->lock_box_eye, NULL);
+ pthread_rwlock_init(&entity->lock_nametag, NULL);
+
+ free(entity);
+}
+
+// main thread
+// called on startup
+void client_entity_init()
+{
+ map_ini(&entities);
+}
+
+// main thead
+// called on shutdown
+void client_entity_deinit()
+{
+ // forget all entities
+ map_cnl(&entities, &refcount_drp, NULL, NULL, 0);
+}
+
+ClientEntity *client_entity_grab(u64 id)
+{
+ return id ? map_get(&entities, &id, &cmp_entity, &refcount_grb) : NULL;
+}
+
+void client_entity_transform(ClientEntity *entity)
+{
+ if (!entity->model)
+ return;
+
+ entity->model->root->pos = (v3f32) {entity->data.pos.x, entity->data.pos.y, entity->data.pos.z};
+ entity->model->root->rot = (v3f32) {entity->data.rot.x, entity->data.rot.y, entity->data.rot.z};
+
+ if (entity->type->transform)
+ entity->type->transform(entity);
+
+ model_node_transform(entity->model->root);
+}
+
+void client_entity_add(__attribute__((unused)) DragonnetPeer *peer, ToClientEntityAdd *pkt)
+{
+ if (pkt->type >= COUNT_ENTITY)
+ return;
+
+ ClientEntity *entity = malloc(sizeof *entity);
+
+ entity->data = pkt->data;
+ entity->type = &client_entity_types[pkt->type];
+ refcount_ini(&entity->rc, entity, &entity_delete);
+
+ pkt->data.nametag = NULL;
+
+ entity->model = NULL;
+
+ pthread_rwlock_init(&entity->lock_pos_rot, NULL);
+ pthread_rwlock_init(&entity->lock_box_eye, NULL);
+ pthread_rwlock_init(&entity->lock_nametag, NULL);
+
+ if (entity->type->add)
+ entity->type->add(entity);
+
+ if (!map_add(&entities, &entity->data.id, &entity->rc, &cmp_entity, &refcount_inc))
+ fprintf(stderr, "[warning] failed to add entity %lu\n", entity->data.id);
+
+ refcount_drp(&entity->rc);
+}
+
+void client_entity_remove(__attribute__((unused)) DragonnetPeer *peer, ToClientEntityRemove *pkt)
+{
+ map_del(&entities, &pkt->id, &cmp_entity, &entity_drop, NULL, &refcount_obj);
+}
+
+void client_entity_update_pos_rot(__attribute__((unused)) DragonnetPeer *peer, ToClientEntityUpdatePosRot *pkt)
+{
+ ClientEntity *entity = client_entity_grab(pkt->id);
+
+ if (!entity)
+ return;
+
+ pthread_rwlock_wrlock(&entity->lock_pos_rot);
+
+ entity->data.pos = pkt->pos;
+ entity->data.rot = pkt->rot;
+
+ if (entity->type->update_pos_rot)
+ entity->type->update_pos_rot(entity);
+
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+
+ refcount_drp(&entity->rc);
+}
+
+void client_entity_update_box_eye(__attribute__((unused)) DragonnetPeer *peer, ToClientEntityUpdateBoxEye *pkt)
+{
+ ClientEntity *entity = client_entity_grab(pkt->id);
+
+ if (!entity)
+ return;
+
+ pthread_rwlock_wrlock(&entity->lock_box_eye);
+
+ entity->data.box = pkt->box;
+ entity->data.eye = pkt->eye;
+
+ if (entity->type->update_box_eye)
+ entity->type->update_box_eye(entity);
+
+ pthread_rwlock_unlock(&entity->lock_box_eye);
+
+ refcount_drp(&entity->rc);
+}
+
+void client_entity_update_nametag(__attribute__((unused)) DragonnetPeer *peer, ToClientEntityUpdateNametag *pkt)
+{
+ ClientEntity *entity = client_entity_grab(pkt->id);
+
+ if (!entity)
+ return;
+
+ pthread_rwlock_wrlock(&entity->lock_nametag);
+
+ if (entity->data.nametag)
+ free(entity->data.nametag);
+
+ entity->data.nametag = pkt->nametag;
+ pkt->nametag = NULL;
+
+ if (entity->type->update_nametag)
+ entity->type->update_nametag(entity);
+
+ pthread_rwlock_unlock(&entity->lock_nametag);
+
+ refcount_drp(&entity->rc);
+}
--- /dev/null
+#ifndef _CLIENT_ENTITY_H_
+#define _CLIENT_ENTITY_H_
+
+#include <dragonnet/peer.h>
+#include <dragonstd/refcount.h>
+#include <pthread.h>
+#include "client/model.h"
+#include "entity.h"
+#include "types.h"
+
+typedef struct {
+ EntityData data;
+ struct ClientEntityType *type;
+ Refcount rc;
+
+ Model *model;
+
+ pthread_rwlock_t lock_pos_rot;
+ pthread_rwlock_t lock_box_eye;
+ pthread_rwlock_t lock_nametag;
+} ClientEntity;
+
+typedef struct ClientEntityType {
+ void (*add )(ClientEntity *entity); // called when server sent addition of entity
+ void (*remove)(ClientEntity *entity); // called when server sent removal of entity
+ void (*free )(ClientEntity *entity); // called when entity is garbage collected
+
+ void (*update_pos_rot)(ClientEntity *entity);
+ void (*update_box_eye)(ClientEntity *entity);
+ void (*update_nametag)(ClientEntity *entity);
+
+ void (*transform)(ClientEntity *entity);
+} ClientEntityType;
+
+void client_entity_init();
+void client_entity_deinit();
+
+ClientEntity *client_entity_grab(u64 id);
+void client_entity_drop(ClientEntity *entity);
+
+void client_entity_transform(ClientEntity *entity);
+
+void client_entity_add(DragonnetPeer *peer, ToClientEntityAdd *pkt);
+void client_entity_remove(DragonnetPeer *peer, ToClientEntityRemove *pkt);
+void client_entity_update_pos_rot(DragonnetPeer *peer, ToClientEntityUpdatePosRot *pkt);
+void client_entity_update_box_eye(DragonnetPeer *peer, ToClientEntityUpdateBoxEye *pkt);
+void client_entity_update_nametag(DragonnetPeer *peer, ToClientEntityUpdateNametag *pkt);
+
+extern ClientEntityType client_entity_types[];
+
+#endif // _CLIENT_ENTITY_H_
+++ /dev/null
-#include <stdio.h>
-#include <stdlib.h>
-#include "client/blockmesh.h"
-#include "client/facecache.h"
-#include "client/client_config.h"
-#include "client/client_map.h"
-#include "client/client_player.h"
-#include "client/debug_menu.h"
-#include "util.h"
-#define MAX_BLOCK_REQUESTS 4
-
-struct ClientMap client_map;
-
-// meshgen functions
-
-// dequeue callback to thread-safely update
-static void set_dequeued(MapBlock *block)
-{
- pthread_mutex_lock(&block->mtx);
- ((MapBlockExtraData *) block->extra)->queue = false;
- pthread_mutex_unlock(&block->mtx);
-}
-
-// mesh generator step
-static void meshgen_step()
-{
- MapBlock *block = queue_dequeue_callback(client_map.queue, (void *) &set_dequeued);
-
- if (block)
- blockmesh_make(block);
-}
-
-// pthread start routine for meshgen thread
-static void *meshgen_thread(unused void *arg)
-{
- while (! client_map.cancel)
- meshgen_step();
-
- return NULL;
-}
-
-// sync functions
-
-// send block request command to server
-static void request_position(v3s32 pos)
-{
- dragonnet_peer_send_ToServerRequestBlock(client, &(ToServerRequestBlock) {
- .pos = pos
- });
-}
-
-// mapblock synchronisation step
-static void sync_step()
-{
- static u64 tick = 0;
- static v3s32 *old_requested_positions = NULL;
- static size_t old_requested_positions_count = 0;
-
- u64 last_tick = tick++;
-
- v3f64 player_pos = client_player_get_position();
- v3s32 center = map_node_to_block_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
-
- v3s32 *requested_positions = malloc(sizeof(v3s32) * MAX_BLOCK_REQUESTS);
- size_t requested_positions_count = 0;
-
- for (size_t i = 0; i < client_map.blocks_count; i++) {
- v3s32 pos = facecache_face(i, ¢er);
- MapBlock *block = map_get_block(client_map.map, pos, false);
-
- if (block) {
- pthread_mutex_lock(&block->mtx);
- MapBlockExtraData *extra = block->extra;
-
- switch (extra->state) {
- case MBS_READY:
- if (extra->last_synced < last_tick)
- request_position(pos);
- fallthrough;
-
- case MBS_FRESH:
- extra->state = MBS_READY;
- extra->last_synced = tick;
- break;
-
- case MBS_RECIEVING:
- break;
- }
- pthread_mutex_unlock(&block->mtx);
- } else if (requested_positions_count < MAX_BLOCK_REQUESTS) {
- bool should_request = true;
-
- for (size_t i = 0; i < old_requested_positions_count; i++) {
- if (v3s32_equals(old_requested_positions[i], pos)) {
- should_request = false;
- break;
- }
- }
-
- if (should_request)
- request_position(pos);
-
- requested_positions[requested_positions_count++] = pos;
- }
- }
-
- if (old_requested_positions)
- free(old_requested_positions);
-
- old_requested_positions = requested_positions;
- old_requested_positions_count = requested_positions_count;
-}
-
-// pthread start routine for sync thread
-static void *sync_thread(unused void *arg)
-{
- while (! client_map.cancel)
- sync_step();
-
- return NULL;
-}
-
-// map callbacks
-// note: all these functions require the block mutex to be locked, which is always the case when a map callback is invoked
-
-// callback for initializing a newly created block
-// allocate and initialize extra data
-static void on_create_block(MapBlock *block)
-{
- MapBlockExtraData *extra = block->extra = malloc(sizeof(MapBlockExtraData));
-
- extra->state = MBS_RECIEVING;
- extra->queue = false;
- extra->last_synced = 0;
- extra->obj = NULL;
- extra->all_air = false;
-}
-
-// callback for deleting a block
-// free extra data
-static void on_delete_block(MapBlock *block)
-{
- free(block->extra);
-}
-
-// callback for determining whether a block should be returned by map_get_block
-// hold back blocks that have not been fully read from server yet when the create flag is set to true
-static bool on_get_block(MapBlock *block, bool create)
-{
- return create || ((MapBlockExtraData *) block->extra)->state > MBS_RECIEVING;
-}
-
-// public functions
-
-// ClientMap singleton constructor
-void client_map_init()
-{
- client_map.map = map_create((MapCallbacks) {
- .create_block = &on_create_block,
- .delete_block = &on_delete_block,
- .get_block = &on_get_block,
- .set_node = NULL,
- .after_set_node = NULL,
- });
- client_map.queue = queue_create();
- client_map.cancel = false;
- client_map.sync_thread = 0;
- client_map_set_simulation_distance(10);
-
- client_map.meshgen_threads = malloc(sizeof *client_map.meshgen_threads * client_config.meshgen_threads);
- for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
- client_map.meshgen_threads[i] = 0; // why
-}
-
-// ClientMap singleton destructor
-void client_map_deinit()
-{
- queue_delete(client_map.queue);
- map_delete(client_map.map);
-}
-
-// start meshgen and sync threads
-void client_map_start()
-{
- for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
- pthread_create(&client_map.meshgen_threads[i], NULL, &meshgen_thread, NULL);
-
- pthread_create(&client_map.sync_thread, NULL, &sync_thread, NULL);
-}
-
-// stop meshgen and sync threads
-void client_map_stop()
-{
- client_map.cancel = true;
- queue_cancel(client_map.queue);
-
- for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
- if (client_map.meshgen_threads[i])
- pthread_join(client_map.meshgen_threads[i], NULL);
- free(client_map.meshgen_threads);
-
- if (client_map.sync_thread)
- pthread_join(client_map.sync_thread, NULL);
-}
-
-// update simulation distance
-void client_map_set_simulation_distance(u32 simulation_distance)
-{
- client_map.simulation_distance = simulation_distance;
- client_map.blocks_count = facecache_count(simulation_distance);
-}
-
-// called when a block was actually recieved from server
-void client_map_block_received(MapBlock *block)
-{
- pthread_mutex_lock(&block->mtx);
- MapBlockExtraData *extra = block->extra;
- if (extra->state == MBS_RECIEVING)
- extra->state = MBS_FRESH;
- pthread_mutex_unlock(&block->mtx);
-
- client_map_schedule_update_block_mesh(block);
-
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 1, block->pos.y + 0, block->pos.z + 0}, false));
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 1, block->pos.z + 0}, false));
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x + 0, block->pos.y + 0, block->pos.z + 1}, false));
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 1, block->pos.y - 0, block->pos.z - 0}, false));
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 1, block->pos.z - 0}, false));
- client_map_schedule_update_block_mesh(map_get_block(client_map.map, (v3s32) {block->pos.x - 0, block->pos.y - 0, block->pos.z - 1}, false));
-}
-
-// enqueue block to mesh update queue
-void client_map_schedule_update_block_mesh(MapBlock *block)
-{
- if (! block)
- return;
-
- pthread_mutex_lock(&block->mtx);
- MapBlockExtraData *extra = block->extra;
-
- if (! extra->queue) {
- if (extra->all_air) {
- if (extra->obj) {
- extra->obj->remove = true;
- extra->obj = NULL;
- }
- } else {
- extra->queue = true;
- queue_enqueue(client_map.queue, block);
- }
- }
- pthread_mutex_unlock(&block->mtx);
-}
+++ /dev/null
-#ifndef _CLIENT_MAP_H_
-#define _CLIENT_MAP_H_
-
-#include <stdbool.h>
-#include <pthread.h>
-#include <dragonstd/queue.h>
-#include "map.h"
-#include "client/object.h"
-
-typedef enum
-{
- MBS_RECIEVING, // currently deserializing
- MBS_FRESH, // first deserialisation finished, not processed by sync thread yet
- MBS_READY, // ready to use and processed by sync thread
-} MapBlockState;
-
-typedef struct
-{
- MapBlockState state; // keep track of the deserialisation and sync processing state
- bool queue; // whether the block is in meshgen queue
- u64 last_synced; // keep track of when a block was synced the last time (used to detect when a block got out of and then back into range)
- Object *obj; // mesh object, generated by blockmesh file
- bool all_air; // no thoughts brain empty
-} MapBlockExtraData;
-
-extern struct ClientMap
-{
- Map *map; // map object
- Queue *queue; // MapBlock * queue (thread safe)
- atomic_bool cancel; // used to notify meshgen and sync thread about quit
- pthread_t *meshgen_threads; // consumer threads for meshgen queue
- pthread_t sync_thread; // this thread requests new / changed blocks from server
- u32 simulation_distance; // simulation distance sent by server
- size_t blocks_count; // cached number of facecache positions to process every sync step (matches simulation distance)
-} client_map;
-
-void client_map_init(); // ClientMap singleton constructor
-void client_map_deinit(); // ClientMap singleton destructor
-void client_map_set_simulation_distance(u32 simulation_distance); // update simulation distance
-void client_map_start(); // start meshgen and sync threads
-void client_map_stop(); // stop meshgen and sync threads
-void client_map_block_received(MapBlock *block); // called when a block was actually recieved from server
-void client_map_schedule_update_block_mesh(MapBlock *block); // enqueue block to mesh update queue
-
-#endif
#include "client/client.h"
#include "client/client_node.h"
+#include "color.h"
#include "environment.h"
#include "node.h"
#include "perlin.h"
-#include "util.h"
+
#define TILES_SIMPLE(path) {.paths = {path, NULL, NULL, NULL, NULL, NULL}, .indices = {0, 0, 0, 0, 0, 0}, .textures = {NULL}}
#define TILES_NONE {.paths = {NULL}, .indices = {0}, .textures = {NULL}}
-static f32 hue_to_rgb(f32 p, f32 q, f32 t)
-{
- if (t < 0.0f)
- t += 1.0f;
-
- if (t > 1.0f)
- t -= 1.0f;
-
- if (t < 1.0f / 6.0f)
- return p + (q - p) * 6.0f * t;
-
- if (t < 1.0f / 2.0f)
- return q;
-
- if (t < 2.0f / 3.0f)
- return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
-
- return p;
-}
-
-static Vertex3DColor hsl_to_rgb(v3f32 hsl)
+static void render_grass(NodeArgsRender *args)
{
- Vertex3DColor rgb;
-
- if (hsl.y == 0.0f) {
- rgb = (Vertex3DColor) {hsl.z, hsl.z, hsl.z};
- } else {
- f32 q = hsl.z < 0.5f ? hsl.z * (1.0f + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y;
- f32 p = 2.0f * hsl.z - q;
-
- rgb.r = hue_to_rgb(p, q, hsl.x + 1.0f / 3.0f);
- rgb.g = hue_to_rgb(p, q, hsl.x);
- rgb.b = hue_to_rgb(p, q, hsl.x - 1.0f / 3.0f);
- }
-
- return rgb;
-}
-
-static void render_grass(v3s32 pos, unused MapNode *node, Vertex3D *vertex, unused int f, unused int v)
-{
- f32 hum_min, hum_max, temp_max;
- hum_min = 0.13f;
- hum_max = 0.33f;
- temp_max = 0.45f;
-
- f32 temp_f = f64_clamp(0.3f - get_temperature(pos), 0.0f, 0.3f) / 0.3f;
-
- vertex->color = hsl_to_rgb((v3f32) {(get_humidity(pos) * (hum_max - hum_min) + hum_min) * (1.0f - temp_f) + temp_max * temp_f, 1.0f, 0.5f});
+ args->vertex.color = hsl_to_rgb((v3f32) {f32_mix(
+ // hue values between .13 and .33 depending on humidity
+ f32_mix(
+ 0.13f,
+ 0.33f,
+ get_humidity(args->pos)
+ ),
+ // move towards .45 while temperature is between .3 and .0
+ 0.45f,
+ f32_clamp(
+ 0.3f - get_temperature(args->pos),
+ 0.0f,
+ 0.3f
+ ) / 0.3f
+ ), 1.0f, 0.5f});
}
-static void render_stone(v3s32 pos, unused MapNode *node, Vertex3D *vertex, unused int f, unused int v)
+static void render_stone(NodeArgsRender *args)
{
- vertex->textureCoordinates.s += noise2d(pos.x, pos.z, 0, seed + SO_TEXTURE_OFFSET_S);
- vertex->textureCoordinates.t += noise2d(pos.x, pos.z, 0, seed + SO_TEXTURE_OFFSET_T);
+ args->vertex.cube.textureCoordinates.x += noise2d(args->pos.x, args->pos.z, 0, seed + SO_TEXTURE_OFFSET_S);
+ args->vertex.cube.textureCoordinates.y += noise2d(args->pos.x, args->pos.z, 0, seed + SO_TEXTURE_OFFSET_T);
}
-static void render_hsl(unused v3s32 pos, MapNode *node, Vertex3D *vertex, unused int f, unused int v)
+static void render_color(NodeArgsRender *args)
{
- vertex->color = hsl_to_rgb(((HSLData *) node->data)->color);
+ args->vertex.color = ((ColorData *) args->node->data)->color;
}
ClientNodeDefinition client_node_definitions[NODE_UNLOADED] = {
// unknown
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/unknown.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = NULL,
},
// air
{
.tiles = TILES_NONE,
- .visibility = NV_NONE,
+ .visibility = VISIBILITY_NONE,
.mipmap = true,
.render = NULL,
},
// grass
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/grass.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = &render_grass,
},
// dirt
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/dirt.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = NULL,
},
// stone
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/stone.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = &render_stone,
},
// snow
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/snow.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = NULL,
},
.indices = {0, 0, 0, 0, 1, 1},
.textures = {NULL},
},
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// oak leaves
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/oak_leaves.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// pine wood
{
.indices = {0, 0, 0, 0, 1, 1},
.textures = {NULL},
},
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// pine leaves
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/pine_leaves.png"),
- .visibility = NV_CLIP,
+ .visibility = VISIBILITY_CLIP,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// palm wood
{
.indices = {0, 0, 0, 0, 1, 1},
.textures = {NULL},
},
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// palm leaves
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/palm_leaves.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
- .render = &render_hsl,
+ .render = &render_color,
},
// sand
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/sand.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = NULL,
},
// water
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/water.png"),
- .visibility = NV_BLEND,
+ .visibility = VISIBILITY_BLEND,
.mipmap = true,
.render = NULL,
},
// lava
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/lava.png"),
- .visibility = NV_BLEND,
+ .visibility = VISIBILITY_BLEND,
.mipmap = true,
.render = NULL,
},
// vulcano_stone
{
.tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/vulcano_stone.png"),
- .visibility = NV_SOLID,
+ .visibility = VISIBILITY_SOLID,
.mipmap = true,
.render = NULL,
},
void client_node_init()
{
- for (Node node = NODE_UNKNOWN; node < NODE_UNLOADED; node++) {
+ for (NodeType node = NODE_UNKNOWN; node < NODE_UNLOADED; node++) {
ClientNodeDefinition *def = &client_node_definitions[node];
- if (def->visibility != NV_NONE) {
+ if (def->visibility != VISIBILITY_NONE) {
Texture *textures[6];
for (int i = 0; i < 6; i++) {
#ifndef _CLIENT_NODE_H_
#define _CLIENT_NODE_H_
-#include "client/object.h"
+#include "client/terrain_gfx.h"
#include "client/texture.h"
-#include "map.h"
+#include "terrain.h"
-typedef enum
-{
- NV_NONE,
- NV_CLIP,
- NV_BLEND,
- NV_SOLID,
+typedef enum {
+ VISIBILITY_NONE,
+ VISIBILITY_CLIP,
+ VISIBILITY_BLEND,
+ VISIBILITY_SOLID,
} NodeVisibility;
-typedef struct
-{
- struct
- {
+typedef struct {
+ v3s32 pos;
+ TerrainNode *node;
+ TerrainVertex vertex;
+ unsigned int f, v;
+} NodeArgsRender;
+
+typedef struct {
+ struct {
char *paths[6]; // input
int indices[6]; // input
Texture *textures[6]; // output
} tiles;
NodeVisibility visibility;
bool mipmap;
- void (*render)(v3s32 pos, MapNode *node, Vertex3D *vertex, int f, int v);
+ void (*render)(NodeArgsRender *args);
} ClientNodeDefinition;
extern ClientNodeDefinition client_node_definitions[];
void client_node_init();
-#endif
+#endif // _CLIENT_NODE_H_
#include <stdio.h>
-#include "environment.h"
+#include <stdlib.h>
#include "client/camera.h"
#include "client/client.h"
-#include "client/client_map.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
#include "client/cube.h"
#include "client/debug_menu.h"
#include "client/texture.h"
+#include "environment.h"
+#include "physics.h"
struct ClientPlayer client_player;
-// to be called whenever the player position changes
-// rwlock has to be read or write locked
+static ClientEntity *player_entity;
+static pthread_rwlock_t lock_player_entity;
+
+// updat epos/rot box/eye functions
+
+static void update_eye_pos_camera()
+{
+ v3f64 pos = player_entity->data.pos;
+ v3f32 eye = player_entity->data.eye;
+
+ camera_set_position((v3f32) {pos.x + eye.x, pos.y + eye.y, pos.z + eye.z});
+}
+
static void update_pos()
{
- camera_set_position((v3f32) {client_player.pos.x, client_player.pos.y + client_player.eye_height, client_player.pos.z});
- dragonnet_peer_send_ToServerPos(client, &(ToServerPos) {
- .pos = client_player.pos,
- });
+ pthread_rwlock_rdlock(&player_entity->lock_box_eye);
+ update_eye_pos_camera();
+ pthread_rwlock_unlock(&player_entity->lock_box_eye);
- client_player.obj->pos = (v3f32) {client_player.pos.x, client_player.pos.y, client_player.pos.z};
- object_transform(client_player.obj);
+ debug_menu_changed(ENTRY_POS);
+ debug_menu_changed(ENTRY_HUMIDITY);
+ debug_menu_changed(ENTRY_TEMPERATURE);
+}
- debug_menu_update_pos();
- debug_menu_update_humidity();
- debug_menu_update_temperature();
+static void update_rot()
+{
+ camera_set_angle(player_entity->data.rot.x, player_entity->data.rot.y);
+ debug_menu_changed(ENTRY_YAW);
+ debug_menu_changed(ENTRY_PITCH);
}
-// get absolute player bounding box
-// rwlock has to be read- or write locked
-static aabb3f64 get_box()
+static void update_transform()
{
- return (aabb3f64) {
- {client_player.box.min.x + client_player.pos.x, client_player.box.min.y + client_player.pos.y, client_player.box.min.z + client_player.pos.z},
- {client_player.box.max.x + client_player.pos.x, client_player.box.max.y + client_player.pos.y, client_player.box.max.z + client_player.pos.z},
- };
+ client_entity_transform(player_entity);
}
-// get absolute integer box that contains all nodes a float bounding box touches
-static aabb3s32 round_box(aabb3f64 box)
+static void send_pos_rot()
{
- return (aabb3s32) {
- {floor(box.min.x + 0.5), floor(box.min.y + 0.5), floor(box.min.z + 0.5)},
- {ceil(box.max.x - 0.5), ceil(box.max.y - 0.5), ceil(box.max.z - 0.5)},
- };
+ dragonnet_peer_send_ToServerPosRot(client, &(ToServerPosRot) {
+ .pos = player_entity->data.pos,
+ .rot = player_entity->data.rot,
+ });
+
+ update_transform();
}
-// return true if node at x, y, z is solid (or unloaded)
-static bool is_solid(s32 x, s32 y, s32 z)
+static void recv_pos_rot()
{
- Node node = map_get_node(client_map.map, (v3s32) {x, y, z}).type;
- return node == NODE_UNLOADED || node_definitions[node].solid;
+ update_pos();
+ update_rot();
+
+ update_transform();
}
-// determine if player can jump currently (must be standing on a solid block)
-// rwlock has to be read- or write locked
-static bool can_jump()
+// entity callbacks
+
+static void on_add(ClientEntity *entity)
{
- if (client_player.velocity.y != 0.0)
- return false;
+ pthread_rwlock_wrlock(&lock_player_entity);
- aabb3f64 fbox = get_box();
- fbox.min.y -= 0.5;
+ if (player_entity) {
+ fprintf(stderr, "[error] attempt to re-add localplayer entity\n");
+ exit(EXIT_FAILURE);
+ } else {
+ player_entity = refcount_grb(&entity->rc);
+ recv_pos_rot();
- aabb3s32 box = round_box(fbox);
+ entity->type->update_nametag(entity);
+ }
- if (fbox.min.y - (f64) box.min.y > 0.01)
- return false;
+ pthread_rwlock_unlock(&lock_player_entity);
+}
- for (s32 x = box.min.x; x <= box.max.x; x++)
- for (s32 z = box.min.z; z <= box.max.z; z++)
- if (is_solid(x, box.min.y, z))
- return true;
+static void on_remove(ClientEntity *entity)
+{
+ pthread_rwlock_wrlock(&lock_player_entity);
+ refcount_drp(&entity->rc);
+ player_entity = NULL;
+ pthread_rwlock_unlock(&lock_player_entity);
+}
+
+static void on_update_pos_rot(__attribute__((unused)) ClientEntity *entity)
+{
+ recv_pos_rot();
+}
- return false;
+static void on_update_box_eye(__attribute__((unused)) ClientEntity *entity)
+{
+ pthread_rwlock_rdlock(&lock_player_entity);
+ update_eye_pos_camera();
+ pthread_rwlock_unlock(&lock_player_entity);
+}
+
+static void on_update_nametag(ClientEntity *entity)
+{
+ if (entity->data.nametag) {
+ free(entity->data.nametag);
+ entity->data.nametag = NULL;
+ }
}
-// ClientPlayer singleton constructor
+static void on_transform(ClientEntity *entity)
+{
+ entity->model->root->rot.y = entity->model->root->rot.z = 0.0f;
+}
+
+// called on startup
void client_player_init()
{
- client_player.pos = (v3f64) {0.0, 0.0, 0.0};
- client_player.velocity = (v3f64) {0.0, 0.0, 0.0};
- client_player.box = (aabb3f64) {{-0.3, 0.0, -0.3}, {0.3, 1.75, 0.3}};
- client_player.yaw = client_player.pitch = 0.0f;
- client_player.eye_height = 1.5;
- client_player.fly = false;
- client_player.collision = true;
- pthread_rwlock_init(&client_player.rwlock, NULL);
+ client_player.movement = (ToClientMovement) {
+ .flight = false,
+ .collision = true,
+ .speed = 0.0f,
+ .jump = 0.0f,
+ .gravity = 0.0f,
+ };
+
+ client_entity_types[ENTITY_LOCALPLAYER] = (ClientEntityType) {
+ .add = &on_add,
+ .remove = &on_remove,
+ .free = NULL,
+ .update_pos_rot = &on_update_pos_rot,
+ .update_box_eye = &on_update_box_eye,
+ .update_nametag = &on_update_nametag,
+ .transform = &on_transform,
+ };
+
+ client_entity_types[ENTITY_PLAYER] = (ClientEntityType) {
+ .add = NULL,
+ .remove = NULL,
+ .free = NULL,
+ .update_pos_rot = NULL,
+ .update_box_eye = NULL,
+ .update_nametag = NULL,
+ .transform = &on_transform,
+ };
+
+ pthread_rwlock_init(&client_player.lock_movement, NULL);
+
+ player_entity = NULL;
+ pthread_rwlock_init(&lock_player_entity, NULL);
}
-// ClientPlayer singleton destructor
+// called on shutdown
void client_player_deinit()
{
- pthread_rwlock_destroy(&client_player.rwlock);
+ pthread_rwlock_destroy(&client_player.lock_movement);
+ pthread_rwlock_destroy(&lock_player_entity);
+}
+
+ClientEntity *client_player_entity()
+{
+ ClientEntity *entity = NULL;
+
+ pthread_rwlock_rdlock(&lock_player_entity);
+ if (player_entity)
+ entity = refcount_grb(&player_entity->rc);
+ pthread_rwlock_unlock(&lock_player_entity);
+
+ return entity;
+}
+
+void client_player_update_pos(ClientEntity *entity)
+{
+ pthread_rwlock_rdlock(&lock_player_entity);
+
+ if (entity == player_entity) {
+ update_pos();
+ send_pos_rot();
+ }
+
+ pthread_rwlock_unlock(&lock_player_entity);
+}
+
+void client_player_update_rot(ClientEntity *entity)
+{
+ pthread_rwlock_rdlock(&lock_player_entity);
+
+ if (entity == player_entity) {
+ update_rot();
+ send_pos_rot();
+ }
+
+ pthread_rwlock_unlock(&lock_player_entity);
}
+/*
// create mesh object and info hud
void client_player_add_to_scene()
{
debug_menu_update_yaw();
debug_menu_update_pitch();
}
+*/
// jump if possible
void client_player_jump()
{
- pthread_rwlock_wrlock(&client_player.rwlock);
- if (can_jump())
- client_player.velocity.y += 10.0;
- pthread_rwlock_unlock(&client_player.rwlock);
-}
-
-// get position (thread-safe)
-v3f64 client_player_get_position()
-{
- v3f64 pos;
-
- pthread_rwlock_rdlock(&client_player.rwlock);
- pos = client_player.pos;
- pthread_rwlock_unlock(&client_player.rwlock);
-
- return pos;
-}
-
-// set position (thread-safe)
-void client_player_set_position(v3f64 pos)
-{
- pthread_rwlock_rdlock(&client_player.rwlock);
- client_player.pos = pos;
- pthread_rwlock_unlock(&client_player.rwlock);
+ ClientEntity *entity = client_player_entity();
+ if (!entity)
+ return;
+
+ pthread_rwlock_rdlock(&entity->lock_pos_rot);
+ pthread_rwlock_rdlock(&entity->lock_box_eye);
+
+ if (physics_ground(
+ client_terrain,
+ client_player.movement.collision,
+ entity->data.box,
+ &entity->data.pos,
+ &client_player.velocity
+ ))
+ client_player.velocity.y += client_player.movement.jump;
+
+ pthread_rwlock_unlock(&entity->lock_box_eye);
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+
+ refcount_drp(&entity->rc);
}
// to be called every frame
void client_player_tick(f64 dtime)
{
- pthread_rwlock_wrlock(&client_player.rwlock);
-
- v3f64 old_pos = client_player.pos;
- v3f64 old_velocity = client_player.velocity;
-
- if (! client_player.fly)
- client_player.velocity.y -= 32.0 * dtime;
-
-#define GETS(vec, comp) *(s32 *) ((char *) &vec + offsetof(v3s32, comp))
-#define GETF(vec, comp) *(f64 *) ((char *) &vec + offsetof(v3f64, comp))
-#define PHYSICS(a, b, c) { \
- f64 v = (GETF(client_player.velocity, a) + GETF(old_velocity, a)) / 2.0f; \
- if (v == 0.0) \
- goto a ## _physics_done; \
- aabb3s32 box = round_box(get_box()); \
- v3f64 old_pos = client_player.pos; \
- GETF(client_player.pos, a) += v * dtime; \
- if (! client_player.collision) \
- goto a ## _physics_done; \
- s32 dir; \
- f64 offset; \
- if (v > 0.0) { \
- dir = +1; \
- offset = GETF(client_player.box.max, a); \
- GETS(box.min, a) = ceil(GETF(old_pos, a) + offset + 0.5); \
- GETS(box.max, a) = floor(GETF(client_player.pos, a) + offset + 0.5); \
- } else { \
- dir = -1; \
- offset = GETF(client_player.box.min, a); \
- GETS(box.min, a) = floor(GETF(old_pos, a) + offset - 0.5); \
- GETS(box.max, a) = ceil(GETF(client_player.pos, a) + offset - 0.5); \
- } \
- GETS(box.max, a) += dir; \
- for (s32 a = GETS(box.min, a); a != GETS(box.max, a); a += dir) { \
- for (s32 b = GETS(box.min, b); b <= GETS(box.max, b); b++) { \
- for (s32 c = GETS(box.min, c); c <= GETS(box.max, c); c++) { \
- if (is_solid(x, y, z)) { \
- GETF(client_player.pos, a) = (f64) a - offset - 0.5 * (f64) dir; \
- GETF(client_player.velocity, a) = 0.0; \
- goto a ## _physics_done; \
- } \
- } \
- } \
- } \
- a ## _physics_done: (void) 0; \
- }
-
- PHYSICS(x, y, z)
- PHYSICS(y, x, z)
- PHYSICS(z, x, y)
-
-#undef GETS
-#undef GETF
-#undef PHYSICS
-
- if (! v3f64_equals(old_pos, client_player.pos))
- update_pos();
-
- pthread_rwlock_unlock(&client_player.rwlock);
+ ClientEntity *entity = client_player_entity();
+ if (!entity)
+ return;
+
+ pthread_rwlock_rdlock(&client_player.lock_movement);
+ pthread_rwlock_wrlock(&entity->lock_pos_rot);
+ pthread_rwlock_rdlock(&entity->lock_box_eye);
+
+ if (physics_step(
+ client_terrain,
+ client_player.movement.collision,
+ entity->data.box,
+ &entity->data.pos,
+ &client_player.velocity,
+ &(v3f64) {
+ 0.0,
+ client_player.movement.flight ? 0.0 : -client_player.movement.gravity,
+ 0.0,
+ },
+ dtime
+ ))
+ client_player_update_pos(entity);
+
+ pthread_rwlock_unlock(&entity->lock_box_eye);
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+ pthread_rwlock_unlock(&client_player.lock_movement);
+
+ refcount_drp(&entity->rc);
}
#define _CLIENT_PLAYER_H_
#include <pthread.h>
-#include "client/client.h"
-#include "client/object.h"
+#include "client/client_entity.h"
#include "types.h"
-extern struct ClientPlayer
-{
- v3f64 pos; // feet position
- v3f64 velocity; // current velocity
- aabb3f64 box; // axis-aligned bounding box (used for collision), with 0, 0, 0 being the feet position
- f32 yaw, pitch; // look direction
- f64 eye_height; // eye height above feet
- pthread_rwlock_t rwlock; // used to protect the above properties
- bool fly; // can the player fly?
- bool collision; // should the player collide with the floor?
- Object *obj; // 3D mesh object (currently always invisible), not thread safe
+extern struct ClientPlayer {
+ v3f64 velocity; // velocity is changed and read from the same thread, no lock needed
+ ToClientMovement movement;
+ pthread_rwlock_t lock_movement;
} client_player;
-void client_player_init(); // ClientPlayer singleton constructor
-void client_player_deinit(); // ClientPlayer singleton destructor
-void client_player_add_to_scene(); // create mesh object
-void client_player_jump(); // jump if possible
-v3f64 client_player_get_position(); // get position (thread-safe)
-void client_player_set_position(v3f64 pos); // set position (thread-safe)
-void client_player_tick(f64 dtime); // to be called every frame
+void client_player_init(); // called on startup
+void client_player_deinit(); // called on shutdown
-#endif
+ClientEntity *client_player_entity(); // grab and return client entity
+
+void client_player_jump(); // jump if possible
+
+void client_player_update_pos(ClientEntity *entity); // entity needs to be the client entity
+void client_player_update_rot(ClientEntity *entity); // entity needs to be the client entity
+
+void client_player_tick(f64 dtime); // to be called every frame
+
+#endif // _CLIENT_PLAYER_H_
--- /dev/null
+#define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <dragonstd/queue.h>
+#include <sched.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "client/client.h"
+#include "client/facecache.h"
+#include "client/client_config.h"
+#include "client/client_player.h"
+#include "client/client_terrain.h"
+#include "client/debug_menu.h"
+#include "client/terrain_gfx.h"
+
+#define MAX_REQUESTS 4
+
+Terrain *client_terrain;
+
+static atomic_bool cancel; // used to notify meshgen and sync thread about quit
+static Queue meshgen_tasks; // TerrainCHunk * queue (thread safe)
+static pthread_t *meshgen_threads; // consumer threads for meshgen queue
+static pthread_t sync_thread; // this thread requests new / changed chunks from server
+static u32 load_distance; // load distance sent by server
+static size_t load_chunks; // cached number of facecache positions to process every sync step (matches load distance)
+
+// meshgen functions
+
+// dequeue callback to update queue state in a thread safe manner
+static TerrainChunk *set_dequeued(TerrainChunk *chunk)
+{
+ pthread_mutex_lock(&chunk->mtx);
+ ((TerrainChunkMeta *) chunk->extra)->queue = false;
+ pthread_mutex_unlock(&chunk->mtx);
+
+ return chunk;
+}
+
+// mesh generator step
+static void meshgen_step()
+{
+ TerrainChunk *chunk = queue_deq(&meshgen_tasks, (void *) &set_dequeued);
+
+ if (chunk)
+ terrain_gfx_make_chunk_model(chunk);
+}
+
+// sync functions
+
+// send chunk request command to server
+static void request_chunk(v3s32 pos)
+{
+ dragonnet_peer_send_ToServerRequestChunk(client, &(ToServerRequestChunk) {
+ .pos = pos
+ });
+}
+
+// terrain synchronisation step
+static void sync_step()
+{
+ static u64 tick = 0;
+ static v3s32 *old_requests = NULL;
+ static size_t old_num_requests = 0;
+
+ v3f64 player_pos;
+ ClientEntity *entity = client_player_entity();
+
+ if (entity) {
+ pthread_rwlock_rdlock(&entity->lock_pos_rot);
+ player_pos = entity->data.pos;
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+
+ refcount_drp(&entity->rc);
+ } else {
+ sched_yield();
+ return;
+ }
+
+ v3s32 center = terrain_node_to_chunk_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL);
+
+ u64 last_tick = tick++;
+
+ v3s32 *requests = malloc(MAX_REQUESTS * sizeof *requests);
+ size_t num_requests = 0;
+
+ for (size_t i = 0; i < load_chunks; i++) {
+ v3s32 pos = v3s32_add(facecache_get(i), center);
+ TerrainChunk *chunk = terrain_get_chunk(client_terrain, pos, false);
+
+ if (chunk) {
+ pthread_mutex_lock(&chunk->mtx);
+
+ TerrainChunkMeta *meta = chunk->extra;
+ switch (meta->state) {
+ case CHUNK_READY:
+ // re-request chunks that got back into range
+ if (meta->sync < last_tick)
+ request_chunk(pos);
+ __attribute__((fallthrough));
+
+ case CHUNK_FRESH:
+ meta->state = CHUNK_READY;
+ meta->sync = tick;
+ break;
+
+ case CHUNK_RECIEVING:
+ break;
+ }
+
+ pthread_mutex_unlock(&chunk->mtx);
+ } else if (num_requests < MAX_REQUESTS) {
+ // avoid duplicate requests
+ bool requested = false;
+
+ for (size_t i = 0; i < old_num_requests; i++) {
+ if (v3s32_equals(old_requests[i], pos)) {
+ requested = true;
+ break;
+ }
+ }
+
+ if (!requested)
+ request_chunk(pos);
+
+ requests[num_requests++] = pos;
+ }
+ }
+
+ if (old_requests)
+ free(old_requests);
+
+ old_requests = requests;
+ old_num_requests = num_requests;
+}
+
+// pthread routine for meshgen and sync thread
+
+static struct LoopThread {
+ const char *name;
+ void (*step)();
+} loop_threads[2] = {
+ {"meshgen", &meshgen_step},
+ { "sync", &sync_step},
+};
+
+static void *loop_routine(struct LoopThread *thread)
+{
+#ifdef __GLIBC__ // check whether bloat is enabled
+ pthread_setname_np(pthread_self(), thread->name);
+#endif // __GLIBC__
+
+ // warning: extremely advanced logic
+ while (!cancel)
+ thread->step();
+
+ return NULL;
+}
+
+// terrain callbacks
+// note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
+
+// callback for initializing a newly created chunk
+// allocate and initialize meta data
+static void on_create_chunk(TerrainChunk *chunk)
+{
+ TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
+
+ meta->state = CHUNK_RECIEVING;
+ meta->queue = false;
+ meta->sync = 0;
+ meta->model = NULL;
+ meta->empty = false;
+}
+
+// callback for deleting a chunk
+// free meta data
+static void on_delete_chunk(TerrainChunk *chunk)
+{
+ free(chunk->extra);
+}
+
+// callback for determining whether a chunk should be returned by terrain_get_chunk
+// hold back chunks that have not been fully read from server yet when the create flag is not set
+static bool on_get_chunk(TerrainChunk *chunk, bool create)
+{
+ return create || ((TerrainChunkMeta *) chunk->extra)->state > CHUNK_RECIEVING;
+}
+
+// public functions
+
+// called on startup
+void client_terrain_init()
+{
+ client_terrain = terrain_create();
+ client_terrain->callbacks.create_chunk = &on_create_chunk;
+ client_terrain->callbacks.delete_chunk = &on_delete_chunk;
+ client_terrain->callbacks.get_chunk = &on_get_chunk;
+ client_terrain->callbacks.set_node = NULL;
+ client_terrain->callbacks.after_set_node = NULL;
+
+ cancel = false;
+ queue_ini(&meshgen_tasks);
+
+ client_terrain_set_load_distance(10); // some initial fuck idk just in case server is stupid
+
+ sync_thread = 0;
+ meshgen_threads = malloc(sizeof *meshgen_threads * client_config.meshgen_threads);
+ for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
+ meshgen_threads[i] = 0; // but why???
+}
+
+// called on shutdown
+void client_terrain_deinit()
+{
+ queue_clr(&meshgen_tasks, NULL, NULL, NULL);
+ terrain_delete(client_terrain);
+}
+
+// start meshgen and sync threads
+void client_terrain_start()
+{
+ for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
+ pthread_create(&meshgen_threads[i], NULL, (void *) &loop_routine, &loop_threads[0]);
+
+ pthread_create(&sync_thread, NULL, (void *) &loop_routine, &loop_threads[1]);
+}
+
+// stop meshgen and sync threads
+void client_terrain_stop()
+{
+ cancel = true;
+ queue_cnl(&meshgen_tasks);
+
+ for (unsigned int i = 0; i < client_config.meshgen_threads; i++)
+ if (meshgen_threads[i])
+ pthread_join(meshgen_threads[i], NULL);
+ free(meshgen_threads);
+
+ if (sync_thread)
+ pthread_join(sync_thread, NULL);
+}
+
+// update load distance
+void client_terrain_set_load_distance(u32 dist)
+{
+ load_distance = dist;
+ load_chunks = facecache_count(load_distance);
+ debug_menu_changed(ENTRY_LOAD_DISTANCE);
+}
+
+// return load distance
+u32 client_terrain_get_load_distance()
+{
+ return load_distance;
+}
+
+// called when a chunk was recieved from server
+void client_terrain_chunk_received(TerrainChunk *chunk)
+{
+ pthread_mutex_lock(&chunk->mtx);
+ TerrainChunkMeta *extra = chunk->extra;
+ if (extra->state == CHUNK_RECIEVING)
+ extra->state = CHUNK_FRESH;
+ pthread_mutex_unlock(&chunk->mtx);
+
+ client_terrain_meshgen_task(chunk);
+
+ v3s32 neighbors[6] = {
+ {+1, 0, 0},
+ { 0, +1, 0},
+ { 0, 0, +1},
+ {-1, 0, 0},
+ { 0, -1, 0},
+ { 0, 0, -1},
+ };
+
+ for (int i = 0; i < 6; i++)
+ client_terrain_meshgen_task(terrain_get_chunk(client_terrain,
+ v3s32_add(chunk->pos, neighbors[i]), false));
+}
+
+// enqueue chunk to mesh update queue
+void client_terrain_meshgen_task(TerrainChunk *chunk)
+{
+ if (!chunk)
+ return;
+
+ pthread_mutex_lock(&chunk->mtx);
+
+ TerrainChunkMeta *meta = chunk->extra;
+ if (!meta->queue) {
+ if (meta->empty) {
+ if (meta->model) {
+ meta->model->flags.delete = 1;
+ meta->model = NULL;
+ }
+ } else {
+ meta->queue = true;
+ queue_enq(&meshgen_tasks, chunk);
+ }
+ }
+
+ pthread_mutex_unlock(&chunk->mtx);
+}
--- /dev/null
+#ifndef _CLIENT_TERRAIN_H_
+#define _CLIENT_TERRAIN_H_
+
+#include <stdbool.h>
+#include "client/model.h"
+#include "terrain.h"
+#include "types.h"
+
+typedef enum {
+ CHUNK_RECIEVING, // currently deserializing
+ CHUNK_FRESH, // first deserialisation finished, not processed by sync thread yet
+ CHUNK_READY, // ready to use and processed by sync thread
+} TerrainChunkState;
+
+typedef struct {
+ TerrainChunkState state; // keep track of the deserialisation and sync processing state
+ bool queue; // whether the chunk is in meshgen queue
+ u64 sync; // keep track of when a chunk was synced the last time (used to detect when a chunk got out of and then back into load distance)
+ Model *model; // generated by terrain_gfx
+ bool empty; // whether the chunk is all air
+} TerrainChunkMeta;
+
+extern Terrain *client_terrain;
+
+void client_terrain_init(); // called on startup
+void client_terrain_deinit(); // called on shutdown
+void client_terrain_set_load_distance(u32 dist); // update load distance
+u32 client_terrain_get_load_distance(); // return load distance
+void client_terrain_start(); // start meshgen and sync threads
+void client_terrain_stop(); // stop meshgen and sync threads
+void client_terrain_chunk_received(TerrainChunk *chunk); // called when a chunk was recieved from server
+void client_terrain_meshgen_task(TerrainChunk *chunk); // enqueue chunk to mesh update queue
+
+#endif
#include "client/cube.h"
-Vertex3D cube_vertices[6][6] = {
+CubeVertex cube_vertices[6][6] = {
{
- {{-0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
+ {{-0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, {+0.0, +0.0}},
+ {{+0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, {+1.0, +1.0}},
+ {{+0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, {+1.0, +0.0}},
+ {{+0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, {+1.0, +1.0}},
+ {{-0.5, -0.5, -0.5}, {+0.0, +0.0, -1.0}, {+0.0, +0.0}},
+ {{-0.5, +0.5, -0.5}, {+0.0, +0.0, -1.0}, {+0.0, +1.0}},
},
{
- {{-0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
+ {{-0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, {+0.0, +0.0}},
+ {{+0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, {+1.0, +0.0}},
+ {{+0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, {+1.0, +1.0}},
+ {{+0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, {+1.0, +1.0}},
+ {{-0.5, +0.5, +0.5}, {+0.0, +0.0, +1.0}, {+0.0, +1.0}},
+ {{-0.5, -0.5, +0.5}, {+0.0, +0.0, +1.0}, {+0.0, +0.0}},
},
{
- {{-0.5, +0.5, +0.5}, {-1.0, +0.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, -0.5}, {-1.0, +0.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, -0.5}, {-1.0, +0.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, -0.5}, {-1.0, +0.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, +0.5}, {-1.0, +0.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, +0.5}, {-1.0, +0.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
+ {{-0.5, +0.5, +0.5}, {-1.0, +0.0, +0.0}, {+1.0, +1.0}},
+ {{-0.5, +0.5, -0.5}, {-1.0, +0.0, +0.0}, {+0.0, +1.0}},
+ {{-0.5, -0.5, -0.5}, {-1.0, +0.0, +0.0}, {+0.0, +0.0}},
+ {{-0.5, -0.5, -0.5}, {-1.0, +0.0, +0.0}, {+0.0, +0.0}},
+ {{-0.5, -0.5, +0.5}, {-1.0, +0.0, +0.0}, {+1.0, +0.0}},
+ {{-0.5, +0.5, +0.5}, {-1.0, +0.0, +0.0}, {+1.0, +1.0}},
},
{
- {{+0.5, +0.5, +0.5}, {+1.0, +0.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, -0.5}, {+1.0, +0.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, -0.5}, {+1.0, +0.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, -0.5}, {+1.0, +0.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, +0.5}, {+1.0, +0.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, +0.5}, {+1.0, +0.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
+ {{+0.5, +0.5, +0.5}, {+1.0, +0.0, +0.0}, {+1.0, +1.0}},
+ {{+0.5, -0.5, -0.5}, {+1.0, +0.0, +0.0}, {+0.0, +0.0}},
+ {{+0.5, +0.5, -0.5}, {+1.0, +0.0, +0.0}, {+0.0, +1.0}},
+ {{+0.5, -0.5, -0.5}, {+1.0, +0.0, +0.0}, {+0.0, +0.0}},
+ {{+0.5, +0.5, +0.5}, {+1.0, +0.0, +0.0}, {+1.0, +1.0}},
+ {{+0.5, -0.5, +0.5}, {+1.0, +0.0, +0.0}, {+1.0, +0.0}},
},
{
- {{-0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
+ {{-0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, {+0.0, +1.0}},
+ {{+0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, {+1.0, +1.0}},
+ {{+0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, {+1.0, +0.0}},
+ {{+0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, {+1.0, +0.0}},
+ {{-0.5, -0.5, +0.5}, {+0.0, -1.0, +0.0}, {+0.0, +0.0}},
+ {{-0.5, -0.5, -0.5}, {+0.0, -1.0, +0.0}, {+0.0, +1.0}},
},
{
- {{-0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, +0.0, {+1.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{+0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, +0.0, {+1.0, +0.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, +0.0, {+0.0, +1.0}, {+1.0, +1.0, +1.0}},
- {{-0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, +0.0, {+0.0, +0.0}, {+1.0, +1.0, +1.0}},
+ {{-0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, {+0.0, +1.0}},
+ {{+0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, {+1.0, +0.0}},
+ {{+0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, {+1.0, +1.0}},
+ {{+0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, {+1.0, +0.0}},
+ {{-0.5, +0.5, -0.5}, {+0.0, +1.0, +0.0}, {+0.0, +1.0}},
+ {{-0.5, +0.5, +0.5}, {+0.0, +1.0, +0.0}, {+0.0, +0.0}},
},
};
#ifndef _CUBE_H_
#define _CUBE_H_
-#include "client/object.h"
+#include "client/model.h"
+#include "types.h"
-extern Vertex3D cube_vertices[6][6];
+typedef struct {
+ v3f32 position;
+ v3f32 normal;
+ v2f32 textureCoordinates;
+} __attribute__((packed)) CubeVertex;
-#endif
+extern CubeVertex cube_vertices[6][6];
+
+#endif // _CUBE_H_
-#include <stdio.h>
+#include <asprintf/asprintf.h>
#include <GL/glew.h>
#include <GL/gl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <pthread.h>
#include "client/client_config.h"
-#include "client/client_map.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
#include "client/debug_menu.h"
+#include "client/game.h"
#include "client/gui.h"
#include "client/window.h"
#include "day.h"
#include "environment.h"
#include "perlin.h"
-#include "util.h"
#include "version.h"
-typedef enum
-{
- DME_VERSION,
- DME_FPS,
- DME_POS,
- DME_YAW,
- DME_PITCH,
- DME_TIME,
- DME_DAYLIGHT,
- DME_SUN_ANGLE,
- DME_HUMIDITY,
- DME_TEMPERATURE,
- DME_SEED,
- DME_FLIGHT,
- DME_COLLISION,
- DME_TIMELAPSE,
- DME_FULLSCREEN,
- DME_OPENGL,
- DME_GPU,
- DME_ANTIALIASING,
- DME_MIPMAP,
- DME_RENDER_DISTANCE,
- DME_SIMULATION_DISTANCE,
- DME_COUNT,
-} DebugMenuEntry;
-
-static GUIElement *gui_elements[DME_COUNT] = {NULL};
+static GUIElement *gui_elements[COUNT_ENTRY] = {NULL};
+static bool changed_elements[COUNT_ENTRY] = {false};
+static pthread_mutex_t changed_elements_mtx = PTHREAD_MUTEX_INITIALIZER;
static bool debug_menu_enabled = true;
-static DebugMenuEntry last_always_visible = DME_POS;
+static DebugMenuEntry last_always_visible = ENTRY_POS;
+
+static char *get_entry_text(DebugMenuEntry entry)
+{
+ bool flight = false;
+ bool collision = false;
+ int hours = 0;
+ int minutes = 0;
+ v3f64 pos = {0.0f, 0.0f, 0.0f};
+ v3f32 rot = {0.0f, 0.0f, 0.0f};
+
+ switch (entry) {
+ case ENTRY_POS:
+ case ENTRY_YAW:
+ case ENTRY_PITCH:
+ case ENTRY_HUMIDITY:
+ case ENTRY_TEMPERATURE: {
+ ClientEntity *entity = client_player_entity();
+ if (!entity)
+ return strdup("");
+
+ pthread_rwlock_rdlock(&entity->lock_pos_rot);
+ pos = entity->data.pos;
+ rot = entity->data.rot;
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+ refcount_drp(&entity->rc);
+ break;
+ }
+
+ case ENTRY_FLIGHT:
+ case ENTRY_COLLISION:
+ pthread_rwlock_rdlock(&client_player.lock_movement);
+ flight = client_player.movement.flight;
+ collision = client_player.movement.collision;
+ pthread_rwlock_unlock(&client_player.lock_movement);
+ break;
+
+ case ENTRY_ANTIALIASING:
+ if (!client_config.antialiasing)
+ return strdup("antialiasing: disabled");
+ break;
+
+ case ENTRY_TIME:
+ split_time_of_day(&hours, &minutes);
+ break;
+
+ default:
+ break;
+ }
+
+ char *str;
+ switch (entry) {
+ case ENTRY_VERSION: asprintf(&str, "Dragonblocks Alpha %s", VERSION ); break;
+ case ENTRY_FPS: asprintf(&str, "%d FPS", game_fps ); break;
+ case ENTRY_POS: asprintf(&str, "(%.1f %.1f %.1f)", pos.x, pos.y, pos.z ); break;
+ case ENTRY_YAW: asprintf(&str, "yaw = %.1f", rot.x / M_PI * 180.0 ); break;
+ case ENTRY_PITCH: asprintf(&str, "pitch = %.1f", rot.y / M_PI * 180.0 ); break;
+ case ENTRY_TIME: asprintf(&str, "%02d:%02d", hours, minutes ); break;
+ case ENTRY_DAYLIGHT: asprintf(&str, "daylight = %.2f", get_daylight() ); break;
+ case ENTRY_SUN_ANGLE: asprintf(&str, "sun angle = %.1f", fmod(get_sun_angle() / M_PI * 180.0, 360.0) ); break;
+ case ENTRY_HUMIDITY: asprintf(&str, "humidity = %.2f", get_humidity((v3s32) {pos.x, pos.y, pos.z}) ); break;
+ case ENTRY_TEMPERATURE: asprintf(&str, "temperature = %.2f", get_temperature((v3s32) {pos.x, pos.y, pos.z})); break;
+ case ENTRY_SEED: asprintf(&str, "seed = %d", seed ); break;
+ case ENTRY_FLIGHT: asprintf(&str, "flight: %s", flight ? "enabled" : "disabled" ); break;
+ case ENTRY_COLLISION: asprintf(&str, "collision: %s", collision ? "enabled" : "disabled" ); break;
+ case ENTRY_TIMELAPSE: asprintf(&str, "timelapse: %s", timelapse ? "enabled" : "disabled" ); break;
+ case ENTRY_FULLSCREEN: asprintf(&str, "fullscreen: %s", window.fullscreen ? "enabled" : "disabled" ); break;
+ case ENTRY_OPENGL: asprintf(&str, "OpenGL %s", glGetString(GL_VERSION) ); break;
+ case ENTRY_GPU: asprintf(&str, "%s", glGetString(GL_RENDERER) ); break;
+ case ENTRY_ANTIALIASING: asprintf(&str, "antialiasing: %u samples", client_config.antialiasing ); break;
+ case ENTRY_MIPMAP: asprintf(&str, "mipmap: %s", client_config.mipmap ? "enabled" : "disabled" ); break;
+ case ENTRY_VIEW_DISTANCE: asprintf(&str, "view distance: %.1lf", client_config.view_distance ); break;
+ case ENTRY_LOAD_DISTANCE: asprintf(&str, "load distance: %u", client_terrain_get_load_distance() ); break;
+ default: break;
+ }
+ return str;
+}
void debug_menu_init()
{
s32 offset = -16;
- for (DebugMenuEntry i = 0; i < DME_COUNT; i++) {
- gui_elements[i] = gui_add(&gui_root, (GUIElementDefinition) {
+ for (DebugMenuEntry i = 0; i < COUNT_ENTRY; i++) {
+ gui_elements[i] = gui_add(NULL, (GUIElementDefinition) {
.pos = {0.0f, 0.0f},
.z_index = 0.1f,
.offset = {2, offset += 18},
.margin = {2, 2},
.align = {0.0f, 0.0f},
.scale = {1.0f, 1.0f},
- .scale_type = GST_TEXT,
+ .scale_type = SCALE_TEXT,
.affect_parent_scale = false,
.text = strdup(""),
.image = NULL,
.bg_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f},
});
}
+
+ debug_menu_toggle();
+
+ debug_menu_changed(ENTRY_VERSION);
+ debug_menu_changed(ENTRY_SEED);
+ debug_menu_changed(ENTRY_TIMELAPSE);
+ debug_menu_changed(ENTRY_FULLSCREEN);
+ debug_menu_changed(ENTRY_OPENGL);
+ debug_menu_changed(ENTRY_GPU);
+ debug_menu_changed(ENTRY_ANTIALIASING);
+ debug_menu_changed(ENTRY_MIPMAP);
+ debug_menu_changed(ENTRY_VIEW_DISTANCE);
}
void debug_menu_toggle()
{
- debug_menu_enabled = ! debug_menu_enabled;
+ debug_menu_enabled = !debug_menu_enabled;
- for (DebugMenuEntry i = 0; i < DME_COUNT; i++) {
+ for (DebugMenuEntry i = 0; i < COUNT_ENTRY; i++) {
gui_elements[i]->visible = debug_menu_enabled || i <= last_always_visible;
gui_elements[i]->def.bg_color.w = debug_menu_enabled ? 0.5f : 0.0f;
}
}
-void debug_menu_update_version()
-{
- gui_set_text(gui_elements[DME_VERSION], format_string("Dragonblocks Alpha %s", VERSION));
-}
-
-void debug_menu_update_fps(int fps)
-{
- gui_set_text(gui_elements[DME_FPS], format_string("%d FPS", fps));
-}
-
-void debug_menu_update_pos()
-{
- gui_set_text(gui_elements[DME_POS], format_string("(%.1f %.1f %.1f)", client_player.pos.x, client_player.pos.y, client_player.pos.z));
-}
-
-void debug_menu_update_yaw()
-{
- gui_set_text(gui_elements[DME_YAW], format_string("yaw = %.1f", client_player.yaw / M_PI * 180.0));
-}
-
-void debug_menu_update_pitch()
-{
- gui_set_text(gui_elements[DME_PITCH], format_string("pitch = %.1f", client_player.pitch / M_PI * 180.0));
-}
-
-void debug_menu_update_time()
-{
- int hours, minutes;
- split_time_of_day(&hours, &minutes);
- gui_set_text(gui_elements[DME_TIME], format_string("%02d:%02d", hours, minutes));
-}
-
-void debug_menu_update_daylight()
-{
- gui_set_text(gui_elements[DME_DAYLIGHT], format_string("daylight = %.2f", get_daylight()));
-}
-
-void debug_menu_update_sun_angle()
-{
- gui_set_text(gui_elements[DME_SUN_ANGLE], format_string("sun angle = %.1f", fmod(get_sun_angle() / M_PI * 180.0, 360.0)));
-}
-
-void debug_menu_update_humidity()
-{
- gui_set_text(gui_elements[DME_HUMIDITY], format_string("humidity = %.2f", get_humidity((v3s32) {client_player.pos.x, client_player.pos.y, client_player.pos.z})));
-}
-
-void debug_menu_update_temperature()
-{
- gui_set_text(gui_elements[DME_TEMPERATURE], format_string("temperature = %.2f", get_temperature((v3s32) {client_player.pos.x, client_player.pos.y, client_player.pos.z})));
-}
-
-void debug_menu_update_seed()
-{
- gui_set_text(gui_elements[DME_SEED], format_string("seed = %d", seed));
-}
-
-void debug_menu_update_flight()
-{
- gui_set_text(gui_elements[DME_FLIGHT], format_string("flight: %s", client_player.fly ? "enabled" : "disabled"));
-}
-
-void debug_menu_update_collision()
-{
- gui_set_text(gui_elements[DME_COLLISION], format_string("collision: %s", client_player.collision ? "enabled" : "disabled"));
-}
-
-void debug_menu_update_timelapse()
+void debug_menu_update()
{
- gui_set_text(gui_elements[DME_TIMELAPSE], format_string("timelapse: %s", timelapse ? "enabled" : "disabled"));
-}
-
-void debug_menu_update_fullscreen()
-{
- gui_set_text(gui_elements[DME_FULLSCREEN], format_string("fullscreen: %s", window.fullscreen ? "enabled" : "disabled"));
-}
-
-void debug_menu_update_opengl()
-{
- gui_set_text(gui_elements[DME_OPENGL], format_string("OpenGL %s", glGetString(GL_VERSION)));
-}
-
-void debug_menu_update_gpu()
-{
- gui_set_text(gui_elements[DME_GPU], format_string("%s", glGetString(GL_RENDERER)));
-}
-
-void debug_menu_update_antialiasing()
-{
- gui_set_text(gui_elements[DME_ANTIALIASING], client_config.antialiasing > 1
- ? format_string("antialiasing: %u samples", client_config.antialiasing)
- : format_string("antialiasing: disabled")
- );
-}
+ bool changed_elements_cpy[COUNT_ENTRY];
-void debug_menu_update_mipmap()
-{
- gui_set_text(gui_elements[DME_MIPMAP], format_string("mipmap: %s", client_config.mipmap ? "enabled" : "disabled"));
-}
+ pthread_mutex_lock(&changed_elements_mtx);
+ memcpy(changed_elements_cpy, changed_elements, COUNT_ENTRY * sizeof(bool));
+ memset(changed_elements, 0, COUNT_ENTRY * sizeof(bool));
+ pthread_mutex_unlock(&changed_elements_mtx);
-void debug_menu_update_render_distance()
-{
- gui_set_text(gui_elements[DME_RENDER_DISTANCE], format_string("render distance: %.1lf", client_config.render_distance));
+ for (DebugMenuEntry i = 0; i < COUNT_ENTRY; i++)
+ if (changed_elements_cpy[i])
+ gui_text(gui_elements[i], get_entry_text(i));
}
-void debug_menu_update_simulation_distance()
+void debug_menu_changed(DebugMenuEntry entry)
{
- gui_set_text(gui_elements[DME_SIMULATION_DISTANCE], format_string("simulation distance: %u", client_map.simulation_distance));
+ pthread_mutex_lock(&changed_elements_mtx);
+ changed_elements[entry] = true;
+ pthread_mutex_unlock(&changed_elements_mtx);
}
#ifndef _DEBUG_MENU_H_
#define _DEBUG_MENU_H_
-#include <stdbool.h>
+typedef enum {
+ ENTRY_VERSION,
+ ENTRY_FPS,
+ ENTRY_POS,
+ ENTRY_YAW,
+ ENTRY_PITCH,
+ ENTRY_TIME,
+ ENTRY_DAYLIGHT,
+ ENTRY_SUN_ANGLE,
+ ENTRY_HUMIDITY,
+ ENTRY_TEMPERATURE,
+ ENTRY_SEED,
+ ENTRY_FLIGHT,
+ ENTRY_COLLISION,
+ ENTRY_TIMELAPSE,
+ ENTRY_FULLSCREEN,
+ ENTRY_OPENGL,
+ ENTRY_GPU,
+ ENTRY_ANTIALIASING,
+ ENTRY_MIPMAP,
+ ENTRY_VIEW_DISTANCE,
+ ENTRY_LOAD_DISTANCE,
+ COUNT_ENTRY,
+} DebugMenuEntry;
void debug_menu_init();
void debug_menu_toggle();
+void debug_menu_changed(DebugMenuEntry entry);
+void debug_menu_update();
-void debug_menu_update_version();
-void debug_menu_update_fps(int fps);
-void debug_menu_update_pos();
-void debug_menu_update_yaw();
-void debug_menu_update_pitch();
-void debug_menu_update_time();
-void debug_menu_update_daylight();
-void debug_menu_update_sun_angle();
-void debug_menu_update_humidity();
-void debug_menu_update_temperature();
-void debug_menu_update_seed();
-void debug_menu_update_flight();
-void debug_menu_update_collision();
-void debug_menu_update_timelapse();
-void debug_menu_update_fullscreen();
-void debug_menu_update_opengl();
-void debug_menu_update_gpu();
-void debug_menu_update_antialiasing();
-void debug_menu_update_mipmap();
-void debug_menu_update_render_distance();
-void debug_menu_update_simulation_distance();
-
-#endif
+#endif // _DEBUG_MENU_H_
-#include <stdlib.h>
#include <dragonstd/array.h>
+#include <stdlib.h>
#include "client/facecache.h"
-static struct
-{
- Array positions;
- u32 size;
- pthread_mutex_t mtx;
-} facecache;
+static Array positions;
+static u32 radius;
+static pthread_mutex_t mtx;
-__attribute((constructor)) static void face_cache_init()
+__attribute__((constructor)) static void facecache_init()
{
- facecache.size = 0;
- facecache.positions = array_create(sizeof(v3s32));
- v3s32 pos = {0, 0, 0};
- array_append(&facecache.positions, &pos);
- pthread_mutex_init(&facecache.mtx, NULL);
+ array_ini(&positions, sizeof(v3s32), 1000);
+ array_apd(&positions, &(v3s32) {0, 0, 0});
+ pthread_mutex_init(&mtx, NULL);
+ radius = 0;
}
-__attribute((destructor)) void face_cache_deinit()
+__attribute__((destructor)) static void facecache_deinit()
{
- if (facecache.positions.ptr)
- free(facecache.positions.ptr);
- pthread_mutex_destroy(&facecache.mtx);
+ array_clr(&positions);
+ pthread_mutex_destroy(&mtx);
}
-static void face_cache_calculate(s32 size)
+static inline void facecache_calculate(s32 radius)
{
#define ADDPOS(a, b, c, va, vb, vc) \
{ \
*(s32 *) ((char *) &pos + offsetof(v3s32, a)) = va; \
*(s32 *) ((char *) &pos + offsetof(v3s32, b)) = vb; \
*(s32 *) ((char *) &pos + offsetof(v3s32, c)) = vc; \
- array_append(&facecache.positions, &pos); \
+ array_apd(&positions, &pos); \
}
#define SQUARES(a, b, c) \
- for (s32 va = -size + 1; va < size; va++) { \
- for (s32 vb = -size + 1; vb < size; vb++) { \
- ADDPOS(a, b, c, va, vb, size) \
- ADDPOS(a, b, c, va, vb, -size) \
+ for (s32 va = -radius + 1; va < radius; va++) { \
+ for (s32 vb = -radius + 1; vb < radius; vb++) { \
+ ADDPOS(a, b, c, va, vb, radius) \
+ ADDPOS(a, b, c, va, vb, -radius) \
} \
}
SQUARES(x, z, y)
SQUARES(z, y, x)
#undef SQUARES
#define EDGES(a, b, c) \
- for (s32 va = -size + 1; va < size; va++) { \
- ADDPOS(a, b, c, va, size, size) \
- ADDPOS(a, b, c, va, size, -size) \
- ADDPOS(a, b, c, va, -size, size) \
- ADDPOS(a, b, c, va, -size, -size) \
+ for (s32 va = -radius + 1; va < radius; va++) { \
+ ADDPOS(a, b, c, va, radius, radius) \
+ ADDPOS(a, b, c, va, radius, -radius) \
+ ADDPOS(a, b, c, va, -radius, radius) \
+ ADDPOS(a, b, c, va, -radius, -radius) \
}
EDGES(x, y, z)
EDGES(z, x, y)
EDGES(y, x, z)
#undef EDGES
- ADDPOS(x, y, z, size, size, size)
- ADDPOS(x, y, z, size, size, -size)
- ADDPOS(x, y, z, size, -size, size)
- ADDPOS(x, y, z, size, -size, -size)
- ADDPOS(x, y, z, -size, size, size)
- ADDPOS(x, y, z, -size, size, -size)
- ADDPOS(x, y, z, -size, -size, size)
- ADDPOS(x, y, z, -size, -size, -size)
+ ADDPOS(x, y, z, radius, radius, radius)
+ ADDPOS(x, y, z, radius, radius, -radius)
+ ADDPOS(x, y, z, radius, -radius, radius)
+ ADDPOS(x, y, z, radius, -radius, -radius)
+ ADDPOS(x, y, z, -radius, radius, radius)
+ ADDPOS(x, y, z, -radius, radius, -radius)
+ ADDPOS(x, y, z, -radius, -radius, radius)
+ ADDPOS(x, y, z, -radius, -radius, -radius)
#undef ADDPOS
}
-v3s32 facecache_face(size_t i, v3s32 *base)
+v3s32 facecache_get(size_t i)
{
- pthread_mutex_lock(&facecache.mtx);
- while (facecache.positions.siz <= i)
- face_cache_calculate(++facecache.size);
- v3s32 pos = ((v3s32 *) facecache.positions.ptr)[i];
- pthread_mutex_unlock(&facecache.mtx);
- if (base) {
- pos.x += base->x;
- pos.y += base->y;
- pos.z += base->z;
+ pthread_mutex_lock(&mtx);
+
+ if (positions.cap <= i) {
+ positions.cap = i;
+ array_rlc(&positions);
}
+
+ while (positions.siz <= i)
+ facecache_calculate(++radius);
+
+ v3s32 pos = ((v3s32 *) positions.ptr)[i];
+ pthread_mutex_unlock(&mtx);
return pos;
}
-size_t facecache_count(u32 size)
+size_t facecache_count(u32 radius)
{
- size_t len = 1 + size * 2;
+ size_t len = 1 + radius * 2;
return len * len * len;
}
#include <pthread.h>
#include "types.h"
-v3s32 facecache_face(size_t i, v3s32 *base);
+v3s32 facecache_get(size_t i);
size_t facecache_count(u32 size);
-#endif
+#endif // _FACECACHE_H_
#define NUM_CHARS 128
-typedef struct
-{
- Texture *texture;
+typedef struct {
+ Texture texture;
v2s32 bearing;
u32 advance;
} Character;
-static struct
-{
- FT_Library library;
- FT_Face face;
- Character chars[NUM_CHARS];
- GLfloat height;
-} font;
-
-typedef struct
-{
- GLfloat x, y;
-} __attribute__((packed)) VertexFontPosition;
-
-typedef struct
-{
- GLfloat s, t;
-} __attribute__((packed)) VertexFontTextureCoordinates;
-
-typedef struct
-{
- VertexFontPosition position;
- VertexFontTextureCoordinates textureCoordinates;
-} __attribute__((packed)) VertexFont;
-
-static VertexAttribute vertex_attributes[2] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexFontPosition),
- },
- // textureCoordinates
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexFontTextureCoordinates),
+static FT_Library font_library;
+static FT_Face font_face;
+static Character font_chars[NUM_CHARS];
+static GLfloat font_height;
+
+typedef struct {
+ v2f32 position;
+ v2f32 textureCoordinates;
+} __attribute__((packed)) FontVertex;
+static VertexLayout font_vertex_layout = {
+ .attributes = (VertexAttribute[]) {
+ {GL_FLOAT, 2, sizeof(v2f32)}, // position
+ {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
},
-};
-
-static VertexLayout vertex_layout = {
- .attributes = vertex_attributes,
.count = 2,
- .size = sizeof(VertexFont),
+ .size = sizeof(FontVertex),
};
bool font_init()
{
- if (FT_Init_FreeType(&font.library)) {
- fprintf(stderr, "Failed to initialize Freetype\n");
+ if (FT_Init_FreeType(&font_library)) {
+ fprintf(stderr, "[error] failed to initialize Freetype\n");
return false;
}
- if (FT_New_Face(font.library, RESSOURCE_PATH "fonts/Minecraftia.ttf", 0, &font.face)) {
- fprintf(stderr, "Failed to load Minecraftia.ttf\n");
+ if (FT_New_Face(font_library, RESSOURCE_PATH "fonts/Minecraftia.ttf", 0, &font_face)) {
+ fprintf(stderr, "[error] failed to load Minecraftia.ttf\n");
return false;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- FT_Set_Pixel_Sizes(font.face, 0, 16);
+ FT_Set_Pixel_Sizes(font_face, 0, 16);
for (unsigned char c = 0; c < NUM_CHARS; c++) {
- if (FT_Load_Char(font.face, c, FT_LOAD_RENDER)) {
- fprintf(stderr, "Failed to load glyph %c\n", c);
-
- font.chars[c] = (Character) {
- .texture = NULL,
- .bearing = {0, 0},
- .advance = 0,
- };
- } else {
- font.chars[c] = (Character) {
- .texture = texture_create(font.face->glyph->bitmap.buffer, font.face->glyph->bitmap.width, font.face->glyph->bitmap.rows, GL_RED, false),
- .bearing = {font.face->glyph->bitmap_left, font.face->glyph->bitmap_top},
- .advance = font.face->glyph->advance.x,
- };
+ if (FT_Load_Char(font_face, c, FT_LOAD_RENDER)) {
+ fprintf(stderr, "[warning] failed to load glyph %c\n", c);
+ font_chars[c].texture.txo = 0;
+ continue;
}
+
+ font_chars[c].texture.width = font_face->glyph->bitmap.width;
+ font_chars[c].texture.height = font_face->glyph->bitmap.rows;
+ texture_upload(&font_chars[c].texture, font_face->glyph->bitmap.buffer, GL_RED, false);
+
+ font_chars[c].bearing = (v2s32) {font_face->glyph->bitmap_left, font_face->glyph->bitmap_top};
+ font_chars[c].advance = font_face->glyph->advance.x;
}
- font.height = font.chars['|'].texture->height;
+ font_height = font_chars['|'].texture.height;
- FT_Done_Face(font.face);
- FT_Done_FreeType(font.library);
+ FT_Done_Face(font_face);
+ FT_Done_FreeType(font_library);
return true;
}
void font_deinit()
{
- for (unsigned char c = 0; c < NUM_CHARS; c++) {
- if (font.chars[c].texture)
- texture_delete(font.chars[c].texture);
- }
+ for (unsigned char c = 0; c < NUM_CHARS; c++)
+ texture_destroy(&font_chars[c].texture);
}
Font *font_create(const char *text)
{
- Font *fnt = malloc(sizeof *fnt);
+ Font *font = malloc(sizeof *font);
- size_t len = strlen(text);
-
- fnt->meshes = malloc(sizeof(Mesh *) * len);
- fnt->meshes_count = len;
+ font->count = strlen(text);
+ font->meshes = malloc(font->count * sizeof *font->meshes);
+ font->textures = malloc(font->count * sizeof *font->textures);
GLfloat offset = 0.0f;
- for (size_t i = 0; i < len; i++) {
+ for (size_t i = 0; i < font->count; i++) {
unsigned char c = text[i];
- if (c >= NUM_CHARS || ! font.chars[c].texture)
+ if (c >= NUM_CHARS || !font_chars[c].texture.txo)
c = '?';
- Character *ch = &font.chars[c];
+ Character *ch = &font_chars[c];
- GLfloat width = ch->texture->width;
- GLfloat height = ch->texture->height;
+ GLfloat width = ch->texture.width;
+ GLfloat height = ch->texture.height;
GLfloat x = ch->bearing.x + offset;
- GLfloat y = font.height - ch->bearing.y;
-
- VertexFont vertices[6] = {
- {{x, y }, {0.0f, 0.0f}},
- {{x, y + height}, {0.0f, 1.0f}},
- {{x + width, y + height}, {1.0f, 1.0f}},
- {{x, y }, {0.0f, 0.0f}},
- {{x + width, y + height}, {1.0f, 1.0f}},
- {{x + width, y }, {1.0f, 0.0f}},
- };
-
- Mesh *mesh = fnt->meshes[i] = mesh_create();
- mesh->textures = &ch->texture->id;
- mesh->textures_count = 1;
- mesh->free_textures = false;
- mesh->vertices = vertices;
- mesh->vertices_count = 6;
- mesh->free_vertices = false;
- mesh->layout = &vertex_layout;
- mesh_configure(mesh);
+ GLfloat y = font_height - ch->bearing.y;
+
+ // this is art
+ // selling this as NFT starting price is 10 BTC
+ font->meshes[i].data = (FontVertex[]) {
+ {{x, y }, {0.0f, 0.0f}},
+ {{x, y + height}, {0.0f, 1.0f}},
+ {{x + width, y + height}, {1.0f, 1.0f}},
+ {{x, y }, {0.0f, 0.0f}},
+ {{x + width, y + height}, {1.0f, 1.0f}},
+ {{x + width, y }, {1.0f, 0.0f}},
+ };
+ font->meshes[i].count = 6;
+ font->meshes[i].layout = &font_vertex_layout;
+ font->meshes[i].vao = font->meshes[i].vbo = 0;
+ font->meshes[i].free_data = false;
+ mesh_upload(&font->meshes[i]);
+
+ font->textures[i] = ch->texture.txo;
offset += ch->advance >> 6;
}
- fnt->size = (v2f32) {offset, font.height};
+ font->size = (v2f32) {offset, font_height};
- return fnt;
+ return font;
}
-void font_delete(Font *fnt)
+void font_delete(Font *font)
{
- for (size_t i = 0; i < fnt->meshes_count; i++)
- mesh_delete(fnt->meshes[i]);
+ for (size_t i = 0; i < font->count; i++)
+ mesh_destroy(&font->meshes[i]);
- free(fnt);
+ free(font->meshes);
+ free(font->textures);
+ free(font);
}
-void font_render(Font *fnt)
+void font_render(Font *font)
{
- for (size_t i = 0; i < fnt->meshes_count; i++)
- mesh_render(fnt->meshes[i]);
+ for (size_t i = 0; i < font->count; i++) {
+ glBindTextureUnit(0, font->textures[i]);
+ mesh_render(&font->meshes[i]);
+ }
}
#ifndef _FONT_H_
#define _FONT_H_
+#include <GL/glew.h>
+#include <GL/gl.h>
#include <stdbool.h>
#include <stddef.h>
#include "client/mesh.h"
#include "types.h"
-typedef struct
-{
+typedef struct {
v2f32 size;
- Mesh **meshes;
- size_t meshes_count;
+ size_t count;
+ Mesh *meshes;
+ GLuint *textures;
} Font;
bool font_init();
void font_deinit();
Font *font_create(const char *text);
-void font_delete(Font *fnt);
-void font_render(Font *fnt);
+void font_delete(Font *font);
+void font_render(Font *font);
-#endif
+#endif // _FONT_H_
-#include "frustum.h"
+#include "client/camera.h"
+#include "client/frustum.h"
+#include "client/window.h"
-typedef enum
-{
+typedef enum {
PLANE_LEFT,
PLANE_RIGHT,
PLANE_BOTTOM,
PLANE_COUNT,
} Plane;
-static struct
-{
- vec3 points[8];
- vec4 planes[PLANE_COUNT];
- int cross_indices[PLANE_COUNT][PLANE_COUNT];
-} frustum;
+mat4x4 frustum;
-__attribute__((constructor)) static void init_frustum()
+static vec3 points[8];
+static vec4 planes[PLANE_COUNT];
+static int cross_indices[PLANE_COUNT][PLANE_COUNT];
+
+__attribute__((constructor)) static void frustum_init()
{
for (Plane a = 0; a < PLANE_COUNT; a++)
for (Plane b = 0; b < PLANE_COUNT; b++)
- frustum.cross_indices[a][b] = a * (9 - a) / 2 + b - 1;
+ cross_indices[a][b] = a * (9 - a) / 2 + b - 1;
}
-void frustum_update(mat4x4 view_proj)
+void frustum_update()
{
- mat4x4 m;
+ mat4x4_mul(frustum, window.projection, camera.view);
- mat4x4_transpose(m, view_proj);
+ mat4x4 m;
+ mat4x4_transpose(m, frustum);
- vec4_add(frustum.planes[PLANE_LEFT], m[3], m[0]);
- vec4_sub(frustum.planes[PLANE_RIGHT], m[3], m[0]);
- vec4_add(frustum.planes[PLANE_BOTTOM], m[3], m[1]);
- vec4_sub(frustum.planes[PLANE_TOP], m[3], m[1]);
- vec4_add(frustum.planes[PLANE_NEAR], m[3], m[2]);
- vec4_sub(frustum.planes[PLANE_FAR], m[3], m[2]);
+ vec4_add(planes[PLANE_LEFT], m[3], m[0]);
+ vec4_sub(planes[PLANE_RIGHT], m[3], m[0]);
+ vec4_add(planes[PLANE_BOTTOM], m[3], m[1]);
+ vec4_sub(planes[PLANE_TOP], m[3], m[1]);
+ vec4_add(planes[PLANE_NEAR], m[3], m[2]);
+ vec4_sub(planes[PLANE_FAR], m[3], m[2]);
int i = 0;
vec3 crosses[PLANE_COUNT * (PLANE_COUNT - 1) / 2];
for (Plane a = 0; a < PLANE_COUNT; a++)
for (Plane b = a + 1; b < PLANE_COUNT; b++)
- vec3_mul_cross(crosses[i++], frustum.planes[a], frustum.planes[b]);
+ vec3_mul_cross(crosses[i++], planes[a], planes[b]);
int j = 0;
for (Plane c = PLANE_NEAR; c <= PLANE_FAR; c++) {
for (Plane a = PLANE_LEFT; a <= PLANE_RIGHT; a++) {
for (Plane b = PLANE_BOTTOM; b <= PLANE_TOP; b++) {
- float d = -1.0f / vec3_mul_inner(frustum.planes[a], crosses[frustum.cross_indices[b][c]]);
- vec3 w = {frustum.planes[a][3], frustum.planes[b][3], frustum.planes[c][3]};
- float *res = frustum.points[j++];
+ float d = -1.0f / vec3_mul_inner(planes[a], crosses[cross_indices[b][c]]);
+ vec3 w = {planes[a][3], planes[b][3], planes[c][3]};
+ float *res = points[j++];
- vec3 res_1_cross = {-crosses[frustum.cross_indices[a][c]][0], -crosses[frustum.cross_indices[a][c]][1], -crosses[frustum.cross_indices[a][c]][2]};
+ vec3 res_1_cross = {-crosses[cross_indices[a][c]][0], -crosses[cross_indices[a][c]][1], -crosses[cross_indices[a][c]][2]};
- res[0] = vec3_mul_inner(crosses[frustum.cross_indices[b][c]], w) * d;
+ res[0] = vec3_mul_inner(crosses[cross_indices[b][c]], w) * d;
res[1] = vec3_mul_inner(res_1_cross, w) * d;
- res[2] = vec3_mul_inner(crosses[frustum.cross_indices[a][b]], w) * d;
+ res[2] = vec3_mul_inner(crosses[cross_indices[a][b]], w) * d;
}
}
}
1.0f,
};
- if (vec4_mul_inner(frustum.planes[i], plane) > 0.0)
+ if (vec4_mul_inner(planes[i], plane) > 0.0)
return false;
}
}
}
// http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm
-bool frustum_is_visible(aabb3f32 box)
+static bool box_visible(aabb3f32 box)
{
for (Plane i = 0; i < PLANE_COUNT; i++) {
if (outside_plane(i, box))
for (Plane i = 0; i < PLANE_COUNT; i++) {
int outside[6] = {
- frustum.points[i][0] > box.max.x,
- frustum.points[i][0] < box.min.x,
- frustum.points[i][1] > box.max.y,
- frustum.points[i][1] < box.min.y,
- frustum.points[i][2] > box.max.z,
- frustum.points[i][2] < box.min.z,
+ points[i][0] > box.max.x,
+ points[i][0] < box.min.x,
+ points[i][1] > box.max.y,
+ points[i][1] < box.min.y,
+ points[i][2] > box.max.z,
+ points[i][2] < box.min.z,
};
for (int i = 0; i < 6; i++)
return true;
}
+
+bool frustum_cull(aabb3f32 box)
+{
+ return !box_visible(box);
+}
#include <linmath.h/linmath.h>
#include "types.h"
-void frustum_update(mat4x4 view_proj);
-bool frustum_is_visible(aabb3f32 box);
+extern mat4x4 frustum;
+
+void frustum_update();
+bool frustum_cull(aabb3f32 box);
#endif
-#include <stdio.h>
-#include <unistd.h>
+#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <GL/glew.h>
#include <GL/gl.h>
#include <GLFW/glfw3.h>
-#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image_write.h>
+#include <stdio.h>
+#include <unistd.h>
#include "client/camera.h"
#include "client/client.h"
-#include "client/client_map.h"
#include "client/client_node.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
#include "client/debug_menu.h"
#include "client/font.h"
+#include "client/frustum.h"
#include "client/gui.h"
#include "client/input.h"
-#include "client/scene.h"
#include "client/sky.h"
#include "client/window.h"
#include "day.h"
#include "interrupt.h"
-int window_width, window_height;
+int game_fps = 0;
static void crosshair_init()
{
- gui_add(&gui_root, (GUIElementDefinition) {
+ gui_add(NULL, (GUIElementDefinition) {
.pos = {0.5f, 0.5f},
.z_index = 0.0f,
.offset = {0, 0},
.margin = {0, 0},
.align = {0.5f, 0.5f},
.scale = {1.0f, 1.0f},
- .scale_type = GST_IMAGE,
+ .scale_type = SCALE_IMAGE,
.affect_parent_scale = false,
.text = NULL,
.image = texture_load(RESSOURCE_PATH "textures/crosshair.png", false),
- .text_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f},
- .bg_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f},
+ .text_color = {0.0f, 0.0f, 0.0f, 0.0f},
+ .bg_color = {0.0f, 0.0f, 0.0f, 0.0f},
});
}
glAlphaFunc(GL_GREATER, 0.1f);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ frustum_update();
+ terrain_gfx_update();
+
sky_render();
- scene_render(dtime);
+ model_scene_render(dtime);
gui_render();
}
static void game_loop()
{
f64 fps_update_timer = 1.0f;
- int frames = 0;
+ unsigned int frames = 0;
struct timespec ts, ts_old;
clock_gettime(CLOCK_REALTIME, &ts_old);
- while (! glfwWindowShouldClose(window.handle) && ! interrupt->done) {
+ while (!glfwWindowShouldClose(window.handle) && !interrupt.set) {
clock_gettime(CLOCK_REALTIME, &ts);
f64 dtime = (f64) (ts.tv_sec - ts_old.tv_sec) + (f64) (ts.tv_nsec - ts_old.tv_nsec) / 1.0e9;
ts_old = ts;
if ((fps_update_timer -= dtime) <= 0.0) {
- debug_menu_update_fps(frames);
+ debug_menu_changed(ENTRY_FPS);
+ game_fps = frames;
fps_update_timer += 1.0;
frames = 0;
}
input_tick(dtime);
client_player_tick(dtime);
- debug_menu_update_time();
- debug_menu_update_daylight();
- debug_menu_update_sun_angle();
+ debug_menu_changed(ENTRY_TIME);
+ debug_menu_changed(ENTRY_DAYLIGHT);
+ debug_menu_changed(ENTRY_SUN_ANGLE);
+ debug_menu_update();
render(dtime);
}
}
-bool game()
+bool game(Flag *gfx_init)
{
- window_width = 1250;
- window_height = 750;
-
- if (! window_init(window_width, window_height))
+ if (!window_init())
return false;
- if (! font_init())
+ if (!font_init())
return false;
- if (! scene_init())
- return false;
+ model_init();
- scene_on_resize(window_width, window_height);
+ if (!sky_init())
+ return false;
- if (! sky_init())
+ if (!terrain_gfx_init())
return false;
client_node_init();
- client_map_start();
+ client_terrain_start();
camera_set_position((v3f32) {0.0f, 0.0f, 0.0f});
camera_set_angle(0.0f, 0.0f);
- if (! gui_init())
+ if (!gui_init())
return false;
- gui_on_resize(window_width, window_height);
-
debug_menu_init();
- debug_menu_toggle();
- debug_menu_update_fps(0);
- debug_menu_update_version();
- debug_menu_update_seed();
- debug_menu_update_flight();
- debug_menu_update_collision();
- debug_menu_update_timelapse();
- debug_menu_update_fullscreen();
- debug_menu_update_opengl();
- debug_menu_update_gpu();
- debug_menu_update_antialiasing();
- debug_menu_update_mipmap();
- debug_menu_update_render_distance();
- debug_menu_update_simulation_distance();
-
crosshair_init();
-
input_init();
- client_player_add_to_scene();
-
+ flag_set(gfx_init);
game_loop();
- client_map_stop();
+ client_terrain_stop();
font_deinit();
gui_deinit();
- scene_deinit();
+ model_deinit();
sky_deinit();
+ terrain_gfx_deinit();
return true;
}
-char *take_screenshot()
+char *game_take_screenshot()
{
// renderbuffer for depth & stencil buffer
- GLuint RBO;
- glGenRenderbuffers(1, &RBO);
- glBindRenderbuffer(GL_RENDERBUFFER, RBO);
- glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_DEPTH24_STENCIL8, window_width, window_height);
+ GLuint rbo;
+ glGenRenderbuffers(1, &rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbo);
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_DEPTH24_STENCIL8, window.width, window.height);
// 2 textures, one with AA, one without
- GLuint textures[2];
- glGenTextures(2, textures);
+ GLuint txos[2];
+ glGenTextures(2, txos);
- glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textures[0]);
- glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_RGB, window_width, window_height, GL_TRUE);
+ glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, txos[0]);
+ glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_RGB, window.width, window.height, GL_TRUE);
- glBindTexture(GL_TEXTURE_2D, textures[1]);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, window_width, window_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(GL_TEXTURE_2D, txos[1]);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, window.width, window.height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
// 2 framebuffers, one with AA, one without
- GLuint FBOs[2];
- glGenFramebuffers(2, FBOs);
+ GLuint fbos[2];
+ glGenFramebuffers(2, fbos);
- glBindFramebuffer(GL_FRAMEBUFFER, FBOs[0]);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textures[0], 0);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, RBO);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, txos[0], 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
- glBindFramebuffer(GL_FRAMEBUFFER, FBOs[1]);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[1], 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbos[1]);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, txos[1], 0);
// render scene
- glBindFramebuffer(GL_FRAMEBUFFER, FBOs[0]);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
render(0.0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// blit AA-buffer into no-AA buffer
- glBindFramebuffer(GL_READ_FRAMEBUFFER, FBOs[0]);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, FBOs[1]);
- glBlitFramebuffer(0, 0, window_width, window_height, 0, 0, window_width, window_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
+ glBlitFramebuffer(0, 0, window.width, window.height, 0, 0, window.width, window.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// read data
- GLubyte data[window_width * window_height * 3];
- glBindFramebuffer(GL_FRAMEBUFFER, FBOs[1]);
+ GLubyte data[window.width * window.height * 3];
+ glBindFramebuffer(GL_FRAMEBUFFER, fbos[1]);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
- glReadPixels(0, 0, window_width, window_height, GL_RGB, GL_UNSIGNED_BYTE, data);
+ glReadPixels(0, 0, window.width, window.height, GL_RGB, GL_UNSIGNED_BYTE, data);
// create filename
char filename[BUFSIZ];
// save screenshot
stbi_flip_vertically_on_write(true);
- stbi_write_png(filename, window_width, window_height, 3, data, window_width * 3);
+ stbi_write_png(filename, window.width, window.height, 3, data, window.width * 3);
// delete buffers
- glDeleteRenderbuffers(1, &RBO);
- glDeleteTextures(2, textures);
- glDeleteFramebuffers(2, FBOs);
+ glDeleteRenderbuffers(1, &rbo);
+ glDeleteTextures(2, txos);
+ glDeleteFramebuffers(2, fbos);
return strdup(filename);
}
-
-void game_on_resize(int width, int height)
-{
- window_width = width;
- window_height = height;
-}
#ifndef _GAME_H_
#define _GAME_H_
-bool game();
-char *take_screenshot();
-void game_on_resize(int width, int height);
+#include <dragonstd/flag.h>
-#endif
+extern int game_fps;
+
+bool game(Flag *gfx_init);
+char *game_take_screenshot();
+
+#endif // _GAME_H_
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
#include <GL/glew.h>
#include <GL/gl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include "client/client.h"
#include "client/cube.h"
#include "client/gui.h"
#include "client/mesh.h"
#include "client/shader.h"
-#include "client/vertex.h"
-#include "util.h"
+#include "client/window.h"
+
+static GUIElement root_element;
+
+static GLuint background_prog;
+static GLint background_loc_model;
+static GLint background_loc_projection;
+static GLint background_loc_color;
+typedef struct {
+ v2f32 position;
+} __attribute__((packed)) BackgroundVertex;
+static Mesh background_mesh = {
+ .layout = &(VertexLayout) {
+ .attributes = (VertexAttribute[]) {
+ {GL_FLOAT, 2, sizeof(v2f32)}, // position
+ },
+ .count = 1,
+ .size = sizeof(BackgroundVertex),
+ },
+ .vao = 0,
+ .vbo = 0,
+ .data = (BackgroundVertex[]) {
+ {{0.0, 0.0}},
+ {{1.0, 0.0}},
+ {{1.0, 1.0}},
+ {{1.0, 1.0}},
+ {{0.0, 1.0}},
+ {{0.0, 0.0}},
+ },
+ .count = 6,
+ .free_data = false,
+};
-static struct
-{
- List elements;
+static GLuint image_prog;
+static GLint image_loc_model;
+static GLint image_loc_projection;
+typedef struct {
+ v2f32 position;
+ v2f32 textureCoordinates;
+} __attribute__((packed)) ImageVertex;
+static Mesh image_mesh = {
+ .layout = &(VertexLayout) {
+ .attributes = (VertexAttribute[]) {
+ {GL_FLOAT, 2, sizeof(v2f32)}, // position
+ {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
+ },
+ .count = 2,
+ .size = sizeof(ImageVertex),
+ },
+ .vao = 0,
+ .vbo = 0,
+ .data = (ImageVertex[]) {
+ {{0.0, 0.0}, {0.0, 0.0}},
+ {{1.0, 0.0}, {1.0, 0.0}},
+ {{1.0, 1.0}, {1.0, 1.0}},
+ {{1.0, 1.0}, {1.0, 1.0}},
+ {{0.0, 1.0}, {0.0, 1.0}},
+ {{0.0, 0.0}, {0.0, 0.0}},
+ },
+ .count = 6,
+ .free_data = false,
+};
- GLuint background_prog;
- GLint background_loc_model;
- GLint background_loc_projection;
- GLint background_loc_color;
- Mesh *background_mesh;
+static GLuint font_prog;
+static GLint font_loc_model;
+static GLint font_loc_projection;
+static GLint font_loc_color;
+// font meshes are initialized in font.c
- GLuint image_prog;
- GLint image_loc_model;
- GLint image_loc_projection;
- Mesh *image_mesh;
+static mat4x4 projection;
- GLuint font_prog;
- GLint font_loc_model;
- GLint font_loc_projection;
- GLint font_loc_color;
+// element functions
- mat4x4 projection;
-} gui;
+static void delete_element(GUIElement *element);
+static void render_element(GUIElement *element);
+static void scale_element(GUIElement *element);
-GUIElement gui_root;
+static int cmp_element(const GUIElement *ea, const GUIElement *eb)
+{
+ return f32_cmp(&ea->def.z_index, &eb->def.z_index);
+}
-typedef struct
+static void delete_elements(Array *elements)
{
- GLfloat x, y;
-} __attribute__((packed)) VertexBackgroundPosition;
+ for (size_t i = 0; i < elements->siz; i++)
+ delete_element(((GUIElement **) elements->ptr)[i]);
+ array_clr(elements);
+}
-typedef struct
+static void delete_element(GUIElement *element)
{
- VertexBackgroundPosition position;
-} __attribute__((packed)) VertexBackground;
-
-static VertexAttribute background_vertex_attributes[1] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexBackgroundPosition),
- },
-};
+ delete_elements(&element->children);
-static VertexLayout background_vertex_layout = {
- .attributes = background_vertex_attributes,
- .count = 1,
- .size = sizeof(VertexBackground),
-};
+ if (element->def.text)
+ free(element->def.text);
-static VertexBackground background_vertices[6] = {
- {{0.0, 0.0}},
- {{1.0, 0.0}},
- {{1.0, 1.0}},
- {{1.0, 1.0}},
- {{0.0, 1.0}},
- {{0.0, 0.0}},
-};
+ if (element->text)
+ font_delete(element->text);
-typedef struct
+ free(element);
+}
+
+static void render_elements(Array *elements)
{
- GLfloat x, y;
-} __attribute__((packed)) VertexImagePosition;
+ for (size_t i = 0; i < elements->siz; i++)
+ render_element(((GUIElement **) elements->ptr)[i]);
+}
-typedef struct
+static void render_element(GUIElement *element)
{
- GLfloat s, t;
-} __attribute__((packed)) VertexImageTextureCoordinates;
+ if (element->visible) {
+ if (element->def.bg_color.w > 0.0f) {
+ glUseProgram(background_prog);
+ glUniformMatrix4fv(background_loc_model, 1, GL_FALSE, element->transform[0]);
+ glUniform4f(background_loc_color, element->def.bg_color.x, element->def.bg_color.y, element->def.bg_color.z, element->def.bg_color.w);
+ mesh_render(&background_mesh);
+ }
+
+ if (element->def.image) {
+ glUseProgram(image_prog);
+ glUniformMatrix4fv(image_loc_model, 1, GL_FALSE, element->transform[0]);
+ glBindTextureUnit(0, element->def.image->txo);
+ mesh_render(&image_mesh);
+ }
-typedef struct
+ if (element->text && element->def.text_color.w > 0.0f) {
+ glUseProgram(font_prog);
+ glUniformMatrix4fv(font_loc_model, 1, GL_FALSE, element->text_transform[0]);
+ glUniform4f(font_loc_color, element->def.text_color.x, element->def.text_color.y, element->def.text_color.z, element->def.text_color.w);
+ font_render(element->text);
+ }
+
+ render_elements(&element->children);
+ }
+}
+
+static void scale_elements(Array *elements, int mask, v3f32 *max)
{
- VertexImagePosition position;
- VertexImageTextureCoordinates textureCoordinates;
-} __attribute__((packed)) VertexImage;
-
-static VertexAttribute image_vertex_attributes[2] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexImagePosition),
- },
- // textureCoordinates
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexImageTextureCoordinates),
- },
-};
+ for (size_t i = 0; i < elements->siz; i++) {
+ GUIElement *element = ((GUIElement **) elements->ptr)[i];
-static VertexLayout image_vertex_layout = {
- .attributes = image_vertex_attributes,
- .count = 2,
- .size = sizeof(VertexImage),
-};
+ if ((1 << element->def.affect_parent_scale) & mask) {
+ scale_element(element);
-static VertexImage image_vertices[6] = {
- {{0.0, 0.0}, {0.0, 0.0}},
- {{1.0, 0.0}, {1.0, 0.0}},
- {{1.0, 1.0}, {1.0, 1.0}},
- {{1.0, 1.0}, {1.0, 1.0}},
- {{0.0, 1.0}, {0.0, 1.0}},
- {{0.0, 0.0}, {0.0, 0.0}},
-};
+ if (max) {
+ if (element->scale.x > max->x)
+ max->x = element->scale.x;
-static int bintree_compare_f32(void *v1, void *v2, unused Bintree *tree)
+ if (element->scale.y > max->y)
+ max->y = element->scale.y;
+ }
+ }
+ }
+}
+
+static void scale_element(GUIElement *element)
+{
+ element->scale = (v2f32) {
+ element->def.scale.x,
+ element->def.scale.y,
+ };
+
+ switch (element->def.scale_type) {
+ case SCALE_IMAGE:
+ element->scale.x *= element->def.image->width;
+ element->scale.y *= element->def.image->height;
+ break;
+
+ case SCALE_TEXT:
+ element->scale.x *= element->text->size.x;
+ element->scale.y *= element->text->size.y;
+ break;
+
+ case SCALE_PARENT:
+ element->scale.x *= element->parent->scale.x;
+ element->scale.y *= element->parent->scale.y;
+ break;
+
+ case SCALE_CHILDREN: {
+ v3f32 scale = {0.0f, 0.0f, 0.0f};
+ scale_elements(&element->children, 1 << true, &scale);
+
+ element->scale.x *= scale.x;
+ element->scale.y *= scale.y;
+
+ scale_elements(&element->children, 1 << false, NULL);
+ break;
+ }
+
+ case SCALE_NONE:
+ break;
+ }
+
+ if (element->def.scale_type != SCALE_CHILDREN)
+ scale_elements(&element->children, (1 << true) | (1 << false), NULL);
+}
+
+static void transform_element(GUIElement *element)
{
- f32 diff = (*(f32 *) v1) - (*(f32 *) v2);
- return CMPBOUNDS(diff);
+ element->pos = (v2f32) {
+ floor(element->parent->pos.x + element->def.offset.x + element->def.pos.x * element->parent->scale.x - element->def.align.x * element->scale.x),
+ floor(element->parent->pos.y + element->def.offset.y + element->def.pos.y * element->parent->scale.y - element->def.align.y * element->scale.y),
+ };
+
+ mat4x4_translate(element->transform, element->pos.x - element->def.margin.x, element->pos.y - element->def.margin.y, 0.0f);
+ mat4x4_translate(element->text_transform, element->pos.x, element->pos.y, 0.0f);
+ mat4x4_scale_aniso(element->transform, element->transform, element->scale.x + element->def.margin.x * 2.0f, element->scale.y + element->def.margin.y * 2.0f, 1.0f);
+
+ for (size_t i = 0; i < element->children.siz; i++)
+ transform_element(((GUIElement **) element->children.ptr)[i]);
}
+// public functions
+
bool gui_init()
{
// initialize background pipeline
- if (! shader_program_create(RESSOURCE_PATH "shaders/gui/background", &gui.background_prog, NULL)) {
- fprintf(stderr, "Failed to create GUI background shader program\n");
+ if (!shader_program_create(RESSOURCE_PATH "shaders/gui/background", &background_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create GUI background shader program\n");
return false;
}
- gui.background_loc_model = glGetUniformLocation(gui.background_prog, "model");
- gui.background_loc_projection = glGetUniformLocation(gui.background_prog, "projection");
- gui.background_loc_color = glGetUniformLocation(gui.background_prog, "color");
-
- gui.background_mesh = mesh_create();
- gui.background_mesh->textures = NULL;
- gui.background_mesh->textures_count = 0;
- gui.background_mesh->free_textures = false;
- gui.background_mesh->vertices = background_vertices;
- gui.background_mesh->vertices_count = 6;
- gui.background_mesh->free_vertices = false;
- gui.background_mesh->layout = &background_vertex_layout;
+ background_loc_model = glGetUniformLocation(background_prog, "model");
+ background_loc_projection = glGetUniformLocation(background_prog, "projection");
+ background_loc_color = glGetUniformLocation(background_prog, "color");
// initialize image pipeline
- if (! shader_program_create(RESSOURCE_PATH "shaders/gui/image", &gui.image_prog, NULL)) {
- fprintf(stderr, "Failed to create GUI image shader program\n");
+ if (!shader_program_create(RESSOURCE_PATH "shaders/gui/image", &image_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create GUI image shader program\n");
return false;
}
- gui.image_loc_model = glGetUniformLocation(gui.image_prog, "model");
- gui.image_loc_projection = glGetUniformLocation(gui.image_prog, "projection");
-
- gui.image_mesh = mesh_create();
- gui.image_mesh->textures = NULL;
- gui.image_mesh->textures_count = 1;
- gui.image_mesh->free_textures = false;
- gui.image_mesh->vertices = image_vertices;
- gui.image_mesh->vertices_count = 6;
- gui.image_mesh->free_vertices = false;
- gui.image_mesh->layout = &image_vertex_layout;
+ image_loc_model = glGetUniformLocation(image_prog, "model");
+ image_loc_projection = glGetUniformLocation(image_prog, "projection");
// initialize font pipeline
- if (! shader_program_create(RESSOURCE_PATH "shaders/gui/font", &gui.font_prog, NULL)) {
- fprintf(stderr, "Failed to create GUI font shader program\n");
+ if (!shader_program_create(RESSOURCE_PATH "shaders/gui/font", &font_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create GUI font shader program\n");
return false;
}
- gui.font_loc_model = glGetUniformLocation(gui.font_prog, "model");
- gui.font_loc_projection = glGetUniformLocation(gui.font_prog, "projection");
- gui.font_loc_color = glGetUniformLocation(gui.font_prog, "color");
-
- // font meshes are initialized in font.c
+ font_loc_model = glGetUniformLocation(font_prog, "model");
+ font_loc_projection = glGetUniformLocation(font_prog, "projection");
+ font_loc_color = glGetUniformLocation(font_prog, "color");
// initialize GUI root element
- gui_root.def.pos = (v2f32) {0.0f, 0.0f};
- gui_root.def.z_index = 0.0f;
- gui_root.def.offset = (v2s32) {0, 0};
- gui_root.def.align = (v2f32) {0.0f, 0.0f};
- gui_root.def.scale = (v2f32) {0.0f, 0.0f};
- gui_root.def.scale_type = GST_NONE;
- gui_root.def.affect_parent_scale = false;
- gui_root.def.text = NULL;
- gui_root.def.image = NULL;
- gui_root.def.text_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
- gui_root.def.bg_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
- gui_root.visible = true;
- gui_root.pos = (v2f32) {0.0f, 0.0f};
- gui_root.scale = (v2f32) {0.0f, 0.0f};
- gui_root.text = NULL;
- gui_root.parent = &gui_root;
- gui_root.children = bintree_create(sizeof(f32), &bintree_compare_f32);
+ root_element.def.pos = (v2f32) {0.0f, 0.0f};
+ root_element.def.z_index = 0.0f;
+ root_element.def.offset = (v2s32) {0, 0};
+ root_element.def.align = (v2f32) {0.0f, 0.0f};
+ root_element.def.scale = (v2f32) {0.0f, 0.0f};
+ root_element.def.scale_type = SCALE_NONE;
+ root_element.def.affect_parent_scale = false;
+ root_element.def.text = NULL;
+ root_element.def.image = NULL;
+ root_element.def.text_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
+ root_element.def.bg_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
+ root_element.visible = true;
+ root_element.pos = (v2f32) {0.0f, 0.0f};
+ root_element.scale = (v2f32) {0.0f, 0.0f};
+ root_element.text = NULL;
+ root_element.parent = &root_element;
+ array_ini(&root_element.children, sizeof(GUIElement *), 0);
+
+ gui_update_projection();
return true;
}
-static void free_element(BintreeNode *node, unused void *arg)
-{
- GUIElement *element = node->value;
-
- bintree_clear(&element->children, &free_element, NULL);
-
- if (element->def.text)
- free(element->def.text);
-
- if (element->text)
- font_delete(element->text);
-
- free(element);
-}
-
void gui_deinit()
{
- glDeleteProgram(gui.background_prog);
- mesh_delete(gui.background_mesh);
+ glDeleteProgram(background_prog);
+ mesh_destroy(&background_mesh);
- glDeleteProgram(gui.image_prog);
- mesh_delete(gui.image_mesh);
+ glDeleteProgram(image_prog);
+ mesh_destroy(&image_mesh);
- glDeleteProgram(gui.font_prog);
+ glDeleteProgram(font_prog);
- bintree_clear(&gui_root.children, &free_element, NULL);
+ delete_elements(&root_element.children);
}
-void gui_on_resize(int width, int height)
+void gui_update_projection()
{
- mat4x4_ortho(gui.projection, 0, width, height, 0, -1.0f, 1.0f);
- glProgramUniformMatrix4fv(gui.background_prog, gui.background_loc_projection, 1, GL_FALSE, gui.projection[0]);
- glProgramUniformMatrix4fv(gui.image_prog, gui.image_loc_projection, 1, GL_FALSE, gui.projection[0]);
- glProgramUniformMatrix4fv(gui.font_prog, gui.font_loc_projection, 1, GL_FALSE, gui.projection[0]);
+ mat4x4_ortho(projection, 0, window.width, window.height, 0, -1.0f, 1.0f);
+ glProgramUniformMatrix4fv(background_prog, background_loc_projection, 1, GL_FALSE, projection[0]);
+ glProgramUniformMatrix4fv(image_prog, image_loc_projection, 1, GL_FALSE, projection[0]);
+ glProgramUniformMatrix4fv(font_prog, font_loc_projection, 1, GL_FALSE, projection[0]);
- gui_root.def.scale.x = width;
- gui_root.def.scale.y = height;
+ root_element.def.scale.x = window.width;
+ root_element.def.scale.y = window.height;
- gui_update_transform(&gui_root);
-}
-
-static void render_element(BintreeNode *node, unused void *arg)
-{
- GUIElement *element = node->value;
-
- if (element->visible) {
- if (element->def.bg_color.w > 0.0f) {
- glUseProgram(gui.background_prog);
- glUniformMatrix4fv(gui.background_loc_model, 1, GL_FALSE, element->transform[0]);
- glUniform4f(gui.background_loc_color, element->def.bg_color.x, element->def.bg_color.y, element->def.bg_color.z, element->def.bg_color.w);
- mesh_render(gui.background_mesh);
- }
-
- if (element->def.image) {
- glUseProgram(gui.image_prog);
- glUniformMatrix4fv(gui.image_loc_model, 1, GL_FALSE, element->transform[0]);
- gui.image_mesh->textures = &element->def.image->id;
- mesh_render(gui.image_mesh);
- }
-
- if (element->text && element->def.text_color.w > 0.0f) {
- glUseProgram(gui.font_prog);
- glUniformMatrix4fv(gui.font_loc_model, 1, GL_FALSE, element->text_transform[0]);
- glUniform4f(gui.font_loc_color, element->def.text_color.x, element->def.text_color.y, element->def.text_color.z, element->def.text_color.w);
- font_render(element->text);
- }
-
- bintree_traverse(&element->children, BTT_INORDER, &render_element, NULL);
- }
+ gui_transform(&root_element);
}
void gui_render()
{
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
- bintree_traverse(&gui_root.children, BTT_INORDER, &render_element, NULL);
+
+ render_elements(&root_element.children);
+
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
GUIElement *gui_add(GUIElement *parent, GUIElementDefinition def)
{
- GUIElement *element = malloc(sizeof(GUIElement));
+ if (parent == NULL)
+ parent = &root_element;
+
+ GUIElement *element = malloc(sizeof *element);
element->def = def;
element->visible = true;
element->parent = parent;
element->text = NULL;
}
- bintree_insert(&parent->children, &element->def.z_index, element);
-
- element->children = bintree_create(sizeof(f32), &bintree_compare_f32);
+ array_ins(&parent->children, &element, (void *) &cmp_element);
+ array_ini(&element->children, sizeof(GUIElement), 0);
if (element->def.affect_parent_scale)
- gui_update_transform(parent);
+ gui_transform(parent);
else
- gui_update_transform(element);
+ gui_transform(element);
return element;
}
-void gui_set_text(GUIElement *element, char *text)
+void gui_text(GUIElement *element, char *text)
{
if (element->def.text)
free(element->def.text);
element->def.text = text;
font_delete(element->text);
element->text = font_create(text);
- gui_update_transform(element);
-}
-
-// transform code
-
-typedef struct
-{
- List left_nodes;
- v2f32 result;
-} PrecalculateChildrenScaleData;
-
-static void precalculate_children_scale(BintreeNode *node, void *arg);
-static void bintree_calculate_element_scale(BintreeNode *node, void *arg);
-static void list_calculate_element_scale(void *key, void *value, void *arg);
-static void bintree_calculate_element_transform(BintreeNode *node, unused void *arg);
-
-static void calculate_element_scale(GUIElement *element)
-{
- element->scale = (v2f32) {
- element->def.scale.x,
- element->def.scale.y,
- };
-
- bool traversed_children = false;
-
- switch (element->def.scale_type) {
- case GST_IMAGE:
- element->scale.x *= element->def.image->width;
- element->scale.y *= element->def.image->height;
- break;
-
- case GST_TEXT:
- element->scale.x *= element->text->size.x;
- element->scale.y *= element->text->size.y;
- break;
-
- case GST_PARENT:
- element->scale.x *= element->parent->scale.x;
- element->scale.y *= element->parent->scale.y;
- break;
-
- case GST_CHILDREN: {
- PrecalculateChildrenScaleData pdata = {
- .left_nodes = list_create(NULL),
- .result = {0.0f, 0.0f},
- };
-
- bintree_traverse(&element->children, BTT_INORDER, &precalculate_children_scale, &pdata);
-
- element->scale.x *= pdata.result.x;
- element->scale.y *= pdata.result.y;
-
- list_clear_func(&pdata.left_nodes, &list_calculate_element_scale, NULL);
- traversed_children = true;
- } break;
-
- case GST_NONE:
- break;
- }
-
- if (! traversed_children)
- bintree_traverse(&element->children, BTT_INORDER, &bintree_calculate_element_scale, NULL);
-}
-
-static void precalculate_children_scale(BintreeNode *node, void *arg)
-{
- GUIElement *element = node->value;
- PrecalculateChildrenScaleData *pdata = arg;
-
- if (element->def.affect_parent_scale) {
- assert(element->def.scale_type != GST_PARENT);
- calculate_element_scale(element);
-
- if (element->scale.x > pdata->result.x)
- pdata->result.x = element->scale.x;
-
- if (element->scale.y > pdata->result.y)
- pdata->result.y = element->scale.y;
- } else {
- list_put(&pdata->left_nodes, element, NULL);
- }
-}
-
-static void bintree_calculate_element_scale(BintreeNode *node, unused void *arg)
-{
- calculate_element_scale(node->value);
-}
-
-static void list_calculate_element_scale(void *key, unused void *value, unused void *arg)
-{
- calculate_element_scale(key);
-}
-
-static void calculate_element_transform(GUIElement *element)
-{
- element->pos = (v2f32) {
- floor(element->parent->pos.x + element->def.offset.x + element->def.pos.x * element->parent->scale.x - element->def.align.x * element->scale.x),
- floor(element->parent->pos.y + element->def.offset.y + element->def.pos.y * element->parent->scale.y - element->def.align.y * element->scale.y),
- };
-
- mat4x4_translate(element->transform, element->pos.x - element->def.margin.x, element->pos.y - element->def.margin.y, 0.0f);
- mat4x4_translate(element->text_transform, element->pos.x, element->pos.y, 0.0f);
- mat4x4_scale_aniso(element->transform, element->transform, element->scale.x + element->def.margin.x * 2.0f, element->scale.y + element->def.margin.y * 2.0f, 1.0f);
-
- bintree_traverse(&element->children, BTT_INORDER, &bintree_calculate_element_transform, NULL);
-}
-
-static void bintree_calculate_element_transform(BintreeNode *node, unused void *arg)
-{
- calculate_element_transform(node->value);
+ gui_transform(element);
}
-void gui_update_transform(GUIElement *element)
+void gui_transform(GUIElement *element)
{
- calculate_element_scale(element);
- calculate_element_transform(element);
+ scale_element(element);
+ transform_element(element);
}
#ifndef _GUI_H_
#define _GUI_H_
-#include <stdbool.h>
+#include <dragonstd/array.h>
#include <linmath.h/linmath.h>
-#include <dragonstd/bintree.h>
-#include <dragonstd/list.h>
+#include <stdbool.h>
#include "client/font.h"
#include "client/texture.h"
#include "types.h"
-typedef enum
-{
- GST_IMAGE,
- GST_TEXT,
- GST_PARENT,
- GST_CHILDREN,
- GST_NONE,
+typedef enum {
+ SCALE_IMAGE,
+ SCALE_TEXT,
+ SCALE_PARENT,
+ SCALE_CHILDREN,
+ SCALE_NONE,
} GUIScaleType;
-typedef struct
-{
+typedef struct {
v2f32 pos;
f32 z_index;
v2s32 offset;
v4f32 bg_color;
} GUIElementDefinition;
-typedef struct GUIElement
-{
+typedef struct GUIElement {
GUIElementDefinition def;
bool visible;
v2f32 pos;
mat4x4 text_transform;
Font *text;
struct GUIElement *parent;
- Bintree children;
+ Array children;
} GUIElement;
bool gui_init();
void gui_deinit();
-void gui_on_resize(int width, int height);
+void gui_update_projection();
void gui_render();
GUIElement *gui_add(GUIElement *parent, GUIElementDefinition def);
-void gui_set_text(GUIElement *element, char *text);
-void gui_update_transform(GUIElement *element);
-
-extern GUIElement gui_root;
+void gui_text(GUIElement *element, char *text);
+void gui_transform(GUIElement *element);
-#endif
+#endif // _GUI_H_
+#include <asprintf/asprintf.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "client/input.h"
#include "client/window.h"
#include "day.h"
-#include "util.h"
-typedef struct
-{
+typedef struct {
int key;
- bool was_pressed;
- bool fired;
+ bool state;
} KeyListener;
-static struct
-{
- GUIElement *pause_menu;
- GUIElement *status_message;
- bool paused;
- KeyListener pause_listener;
- KeyListener fullscreen_listener;
- KeyListener fly_listener;
- KeyListener collision_listener;
- KeyListener timelapse_listener;
- KeyListener debug_menu_listener;
- KeyListener screenshot_listener;
-} input;
-
-void input_on_cursor_pos(double current_x, double current_y)
-{
- if (input.paused)
- return;
-
- static double last_x, last_y = 0.0;
+static bool paused = false;
- double delta_x = current_x - last_x;
- double delta_y = current_y - last_y;
- last_x = current_x;
- last_y = current_y;
+static GUIElement *pause_menu;
+static GUIElement *status_message;
- client_player.yaw += (f32) delta_x * M_PI / 180.0f / 8.0f;
- client_player.pitch -= (f32) delta_y * M_PI / 180.0f / 8.0f;
+static KeyListener listener_pause = {GLFW_KEY_ESCAPE, false};
+static KeyListener listener_fullscreen = {GLFW_KEY_F11, false};
+static KeyListener listener_fly = {GLFW_KEY_F, false};
+static KeyListener listener_collision = {GLFW_KEY_C, false};
+static KeyListener listener_timelapse = {GLFW_KEY_T, false};
+static KeyListener listener_debug_menu = {GLFW_KEY_F3, false};
+static KeyListener listener_screenshot = {GLFW_KEY_F2, false};
- client_player.yaw = fmod(client_player.yaw + M_PI * 2.0f, M_PI * 2.0f);
- client_player.pitch = f32_clamp(client_player.pitch, -M_PI / 2.0f + 0.01f, M_PI / 2.0f - 0.01f);
-
- camera_set_angle(client_player.yaw, client_player.pitch);
-
- debug_menu_update_yaw();
- debug_menu_update_pitch();
-}
+static double cursor_last_x = 0.0;
+static double cursor_last_y = 0.0;
+// movement mutex needs to be locked
static bool move(int forward, int backward, vec3 dir)
{
- f64 sign;
- f64 speed = client_player.fly ? 25.0f : 4.317f;
+ // 25.0f; 4.317f
+ f32 sign;
if (glfwGetKey(window.handle, forward) == GLFW_PRESS)
sign = +1.0f;
else
return false;
- client_player.velocity.x += dir[0] * speed * sign;
- client_player.velocity.y += dir[1] * speed * sign;
- client_player.velocity.z += dir[2] * speed * sign;
+ client_player.velocity.x += dir[0] * client_player.movement.speed * sign;
+ client_player.velocity.y += dir[1] * client_player.movement.speed * sign;
+ client_player.velocity.z += dir[2] * client_player.movement.speed * sign;
return true;
}
static void enter_game()
{
glfwSetInputMode(window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
- input.pause_menu->visible = false;
+ pause_menu->visible = false;
}
-static void do_key_listener(KeyListener *listener)
+static bool key_listener(KeyListener *listener)
{
- bool is_pressed = glfwGetKey(window.handle, listener->key) == GLFW_PRESS;
- listener->fired = listener->was_pressed && ! is_pressed;
- listener->was_pressed = is_pressed;
+ bool was = listener->state;
+ return !(listener->state = (glfwGetKey(window.handle, listener->key) == GLFW_PRESS)) && was;
}
-static KeyListener create_key_listener(int key)
+static void set_status_message(char *message)
{
- return (KeyListener) {
- .key = key,
- .was_pressed = false,
- .fired = false,
- };
+ gui_text(status_message, message);
+ status_message->def.text_color.w = 1.01f;
}
-static void set_status_message(char *message)
+void input_init()
{
- gui_set_text(input.status_message, message);
- input.status_message->def.text_color.w = 1.01f;
+ pause_menu = gui_add(NULL, (GUIElementDefinition) {
+ .pos = {0.0f, 0.0f},
+ .z_index = 0.5f,
+ .offset = {0, 0},
+ .margin = {0, 0},
+ .align = {0.0f, 0.0f},
+ .scale = {1.0f, 1.0f},
+ .scale_type = SCALE_PARENT,
+ .affect_parent_scale = false,
+ .text = NULL,
+ .image = NULL,
+ .text_color = {0.0f, 0.0f, 0.0f, 0.0f},
+ .bg_color = {0.0f, 0.0f, 0.0f, 0.4f},
+ });
+
+ status_message = gui_add(NULL, (GUIElementDefinition) {
+ .pos = {0.5f, 0.25f},
+ .z_index = 0.1f,
+ .offset = {0, 0},
+ .margin = {0, 0},
+ .align = {0.5f, 0.5f},
+ .scale = {1.0f, 1.0f},
+ .scale_type = SCALE_TEXT,
+ .affect_parent_scale = false,
+ .text = strdup(""),
+ .image = NULL,
+ .text_color = {1.0f, 0.91f, 0.13f, 0.0f},
+ .bg_color = {0.0f, 0.0f, 0.0f, 0.0f},
+ });
+
+ glfwSetInputMode(window.handle, GLFW_STICKY_KEYS, GL_TRUE);
+ enter_game();
}
void input_tick(f64 dtime)
{
- if (input.status_message->def.text_color.w > 1.0f)
- input.status_message->def.text_color.w = 1.0f;
- else if (input.status_message->def.text_color.w > 0.0f)
- input.status_message->def.text_color.w -= dtime * 1.0f;
+ if (status_message->def.text_color.w > 1.0f)
+ status_message->def.text_color.w = 1.0f;
+ else if (status_message->def.text_color.w > 0.0f)
+ status_message->def.text_color.w -= dtime * 1.0f;
- do_key_listener(&input.pause_listener);
- do_key_listener(&input.fullscreen_listener);
+ if (key_listener(&listener_pause)) {
+ paused = !paused;
- if (input.pause_listener.fired) {
- input.paused = ! input.paused;
-
- if (input.paused) {
+ if (paused) {
glfwSetInputMode(window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
- input.pause_menu->visible = true;
+ pause_menu->visible = true;
} else {
enter_game();
}
}
- if (input.fullscreen_listener.fired) {
+ if (key_listener(&listener_fullscreen)) {
if (window.fullscreen)
window_exit_fullscreen();
else
window_enter_fullscreen();
}
- if (! input.paused) {
- do_key_listener(&input.fly_listener);
- do_key_listener(&input.collision_listener);
- do_key_listener(&input.timelapse_listener);
- do_key_listener(&input.debug_menu_listener);
- do_key_listener(&input.screenshot_listener);
-
- if (input.fly_listener.fired) {
- client_player.fly = ! client_player.fly;
- debug_menu_update_flight();
- set_status_message(format_string("Flight %s", client_player.fly ? "Enabled" : "Disabled"));
+ if (!paused) {
+ if (key_listener(&listener_fly)) {
+ pthread_rwlock_wrlock(&client_player.lock_movement);
+ client_player.movement.flight = !client_player.movement.flight;
+
+ char *msg;
+ asprintf(&msg, "Flight %s", client_player.movement.flight ? "Enabled" : "Disabled");
+ set_status_message(msg);
+ debug_menu_changed(ENTRY_FLIGHT);
+
+ pthread_rwlock_unlock(&client_player.lock_movement);
}
- if (input.collision_listener.fired) {
- client_player.collision = ! client_player.collision;
- debug_menu_update_collision();
- set_status_message(format_string("Collision %s", client_player.collision ? "Enabled" : "Disabled"));
+ if (key_listener(&listener_collision)) {
+ pthread_rwlock_wrlock(&client_player.lock_movement);
+ client_player.movement.collision = !client_player.movement.collision;
+
+ char *msg;
+ asprintf(&msg, "Collision %s", client_player.movement.collision ? "Enabled" : "Disabled");
+ set_status_message(msg);
+ debug_menu_changed(ENTRY_COLLISION);
+
+ pthread_rwlock_unlock(&client_player.lock_movement);
}
- if (input.timelapse_listener.fired) {
+ if (key_listener(&listener_timelapse)) {
f64 current_time = get_time_of_day();
- timelapse = ! timelapse;
+ timelapse = !timelapse;
set_time_of_day(current_time);
- debug_menu_update_timelapse();
- set_status_message(format_string("Timelapse %s", timelapse ? "Enabled" : "Disabled"));
+
+ char *msg;
+ asprintf(&msg, "Timelapse %s", timelapse ? "Enabled" : "Disabled");
+ set_status_message(msg);
+ debug_menu_changed(ENTRY_TIMELAPSE);
}
- if (input.debug_menu_listener.fired)
+ if (key_listener(&listener_debug_menu))
debug_menu_toggle();
- if (input.screenshot_listener.fired) {
- char *screenshot_filename = take_screenshot();
- set_status_message(format_string("Screenshot saved to %s", screenshot_filename));
+ if (key_listener(&listener_screenshot)) {
+ char *screenshot_filename = game_take_screenshot();
+ char *msg;
+ asprintf(&msg, "Screenshot saved to %s", screenshot_filename);
+ set_status_message(msg);
free(screenshot_filename);
}
}
+ pthread_rwlock_rdlock(&client_player.lock_movement);
+
client_player.velocity.x = 0.0f;
client_player.velocity.z = 0.0f;
- if (client_player.fly)
+ if (client_player.movement.flight)
client_player.velocity.y = 0.0f;
- if (! input.paused) {
+ if (!paused) {
move(GLFW_KEY_W, GLFW_KEY_S, camera.movement_dirs.front);
move(GLFW_KEY_D, GLFW_KEY_A, camera.movement_dirs.right);
- if (client_player.fly)
+ if (client_player.movement.flight)
move(GLFW_KEY_SPACE, GLFW_KEY_LEFT_SHIFT, camera.movement_dirs.up);
else if (glfwGetKey(window.handle, GLFW_KEY_SPACE) == GLFW_PRESS)
client_player_jump();
}
+
+ pthread_rwlock_unlock(&client_player.lock_movement);
}
-void input_init()
+void input_cursor(double current_x, double current_y)
{
- input.paused = false;
+ if (paused)
+ return;
- input.pause_listener = create_key_listener(GLFW_KEY_ESCAPE);
- input.fullscreen_listener = create_key_listener(GLFW_KEY_F11);
- input.fly_listener = create_key_listener(GLFW_KEY_F);
- input.collision_listener = create_key_listener(GLFW_KEY_C);
- input.timelapse_listener = create_key_listener(GLFW_KEY_T);
- input.debug_menu_listener = create_key_listener(GLFW_KEY_F3);
- input.screenshot_listener = create_key_listener(GLFW_KEY_F2);
+ double delta_x = current_x - cursor_last_x;
+ double delta_y = current_y - cursor_last_y;
+ cursor_last_x = current_x;
+ cursor_last_y = current_y;
- input.pause_menu = gui_add(&gui_root, (GUIElementDefinition) {
- .pos = {0.0f, 0.0f},
- .z_index = 0.5f,
- .offset = {0, 0},
- .margin = {0, 0},
- .align = {0.0f, 0.0f},
- .scale = {1.0f, 1.0f},
- .scale_type = GST_PARENT,
- .affect_parent_scale = false,
- .text = NULL,
- .image = NULL,
- .text_color = {0.0f, 0.0f, 0.0f, 0.0f},
- .bg_color = {0.0f, 0.0f, 0.0f, 0.4f},
- });
+ ClientEntity *entity = client_player_entity();
+ if (!entity)
+ return;
- input.status_message = gui_add(&gui_root, (GUIElementDefinition) {
- .pos = {0.5f, 0.25f},
- .z_index = 0.1f,
- .offset = {0, 0},
- .margin = {0, 0},
- .align = {0.5f, 0.5f},
- .scale = {1.0f, 1.0f},
- .scale_type = GST_TEXT,
- .affect_parent_scale = false,
- .text = strdup(""),
- .image = NULL,
- .text_color = {1.0f, 0.91f, 0.13f, 0.0f},
- .bg_color = {0.0f, 0.0f, 0.0f, 0.0f},
- });
+ pthread_rwlock_wrlock(&entity->lock_pos_rot);
- glfwSetInputMode(window.handle, GLFW_STICKY_KEYS, GL_TRUE);
+ entity->data.rot.x += (f32) delta_x * M_PI / 180.0f / 8.0f;
+ entity->data.rot.y -= (f32) delta_y * M_PI / 180.0f / 8.0f;
- enter_game();
+ entity->data.rot.x = fmod(entity->data.rot.x + M_PI * 2.0f, M_PI * 2.0f);
+ entity->data.rot.y = f32_clamp(entity->data.rot.y, -M_PI / 2.0f + 0.01f, M_PI / 2.0f - 0.01f);
+
+ client_player_update_rot(entity);
+ pthread_rwlock_unlock(&entity->lock_pos_rot);
+ refcount_drp(&entity->rc);
}
#include "types.h"
-void input_tick(f64 dtime);
void input_init();
-void input_on_cursor_pos(double current_x, double current_y);
+void input_tick(f64 dtime);
+void input_cursor(double current_x, double current_y);
-#endif
+#endif // _INPUT_H_
-#include <stdlib.h>
#include <stddef.h>
+#include <stdlib.h>
#include "client/mesh.h"
-Mesh *mesh_create()
-{
- Mesh *mesh = malloc(sizeof(Mesh));
- mesh->VAO = mesh->VBO = 0;
- mesh->free_textures = false;
- mesh->free_vertices = false;
-
- return mesh;
-}
-
-void mesh_delete(Mesh *mesh)
+// upload data to GPU (only done once)
+void mesh_upload(Mesh *mesh)
{
- if (mesh->textures && mesh->free_textures)
- free(mesh->textures);
-
- if (mesh->vertices && mesh->free_vertices)
- free(mesh->vertices);
-
- if (mesh->VAO)
- glDeleteVertexArrays(1, &mesh->VAO);
+ glGenVertexArrays(1, &mesh->vao);
+ glGenBuffers(1, &mesh->vbo);
- if (mesh->VBO)
- glDeleteBuffers(1, &mesh->VAO);
+ glBindVertexArray(mesh->vao);
+ glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo);
- free(mesh);
-}
-
-void mesh_configure(Mesh *mesh)
-{
- glGenVertexArrays(1, &mesh->VAO);
- glGenBuffers(1, &mesh->VBO);
+ glBufferData(GL_ARRAY_BUFFER, mesh->count * mesh->layout->size,
+ mesh->data, GL_STATIC_DRAW);
- glBindVertexArray(mesh->VAO);
- glBindBuffer(GL_ARRAY_BUFFER, mesh->VBO);
+ size_t offset = 0;
+ for (GLuint i = 0; i < mesh->layout->count; i++) {
+ VertexAttribute *attrib = &mesh->layout->attributes[i];
- glBufferData(GL_ARRAY_BUFFER, mesh->vertices_count * mesh->layout->size, mesh->vertices, GL_STATIC_DRAW);
+ glVertexAttribPointer(i, attrib->length, attrib->type, GL_FALSE,
+ mesh->layout->size, (GLvoid *) offset);
+ glEnableVertexAttribArray(i);
- vertex_layout_configure(mesh->layout);
+ offset += attrib->size;
+ }
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
- if (mesh->free_vertices)
- free(mesh->vertices);
+ if (mesh->free_data)
+ free(mesh->data);
- mesh->vertices = NULL;
+ mesh->data = NULL;
}
void mesh_render(Mesh *mesh)
{
- if (mesh->vertices)
- mesh_configure(mesh);
+ if (mesh->data)
+ mesh_upload(mesh);
+
+ // render
+ glBindVertexArray(mesh->vao);
+ glDrawArrays(GL_TRIANGLES, 0, mesh->count);
+}
+
+void mesh_destroy(Mesh *mesh)
+{
+ if (mesh->data && mesh->free_data)
+ free(mesh->data);
+
+ if (mesh->vao)
+ glDeleteVertexArrays(1, &mesh->vao);
- for (GLuint i = 0; i < mesh->textures_count; i++)
- glBindTextureUnit(i, mesh->textures[i]);
+ if (mesh->vbo)
+ glDeleteBuffers(1, &mesh->vbo);
- glBindVertexArray(mesh->VAO);
- glDrawArrays(GL_TRIANGLES, 0, mesh->vertices_count);
+ mesh->vao = mesh->vbo = 0;
}
#include <GL/glew.h>
#include <GL/gl.h>
#include <stdbool.h>
-#include "client/vertex.h"
-typedef struct
-{
- GLuint VAO, VBO;
- GLuint *textures;
- GLuint textures_count;
- bool free_textures;
- GLvoid *vertices;
- GLuint vertices_count;
- bool free_vertices;
+typedef struct {
+ GLenum type;
+ GLsizei length;
+ GLsizei size;
+} VertexAttribute;
+
+typedef struct {
+ VertexAttribute *attributes;
+ GLuint count;
+ GLsizei size;
+} VertexLayout;
+
+typedef struct {
VertexLayout *layout;
+ GLuint vao, vbo;
+ GLvoid *data;
+ GLuint count;
+ bool free_data;
} Mesh;
-Mesh *mesh_create();
-void mesh_delete(Mesh *mesh);
-void mesh_configure(Mesh *mesh);
+void mesh_upload(Mesh *mesh);
void mesh_render(Mesh *mesh);
+void mesh_destroy(Mesh *mesh);
-#endif
+#endif // _MESH_H_
--- /dev/null
+#include <dragonstd/tree.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "client/camera.h"
+#include "client/client_config.h"
+#include "client/frustum.h"
+#include "client/model.h"
+
+typedef struct {
+ GLuint texture;
+ Array vertices;
+} ModelBatchTexture;
+
+static List scene;
+static pthread_rwlock_t lock_scene;
+static GLint units;
+
+// fixme: blending issues still occur
+static int cmp_batch_texture(const ModelBatchTexture *ta, const ModelBatchTexture *tb)
+{
+ return
+ ta->vertices.siz > tb->vertices.siz ? -1 :
+ ta->vertices.siz < tb->vertices.siz ? +1 :
+ 0;
+}
+
+static int cmp_node(const ModelNode *node, const char *str)
+{
+ if (str == node->name)
+ return 0;
+
+ if (!node->name)
+ return -1;
+
+ if (!str)
+ return +1;
+
+ return strcmp(node->name, str);
+}
+
+static void transform_node(ModelNode *node)
+{
+ if (node->parent)
+ mat4x4_mul(node->abs, node->parent->abs, node->rel);
+ else
+ mat4x4_dup(node->abs, node->rel);
+
+ list_itr(&node->children, &transform_node, NULL, NULL);
+}
+
+static void render_node(ModelNode *node)
+{
+ if (!node->visible)
+ return;
+
+ for (size_t i = 0; i < node->meshes.siz; i++) {
+ ModelMesh *mesh = &((ModelMesh *) node->meshes.ptr)[i];
+
+ glUseProgram(mesh->shader->prog);
+ glUniformMatrix4fv(mesh->shader->loc_transform, 1, GL_FALSE, node->abs[0]);
+
+ // bind textures
+ for (GLuint i = 0; i < mesh->num_textures; i++)
+ glBindTextureUnit(i, mesh->textures[i]);
+
+ mesh_render(mesh->mesh);
+ }
+
+ list_itr(&node->children, (void *) &render_node, NULL, NULL);
+}
+
+static void free_node_meshes(ModelNode *node)
+{
+ for (size_t i = 0; i < node->meshes.siz; i++) {
+ ModelMesh *mesh = &((ModelMesh *) node->meshes.ptr)[i];
+
+ mesh_destroy(mesh->mesh);
+ free(mesh->mesh);
+ }
+
+ list_clr(&node->children, (void *) &free_node_meshes, NULL, NULL);
+}
+
+static void delete_node(ModelNode *node)
+{
+ for (size_t i = 0; i < node->meshes.siz; i++) {
+ ModelMesh *mesh = &((ModelMesh *) node->meshes.ptr)[i];
+
+ if (mesh->textures)
+ free(mesh->textures);
+ }
+ list_clr(&node->children, (void *) &delete_node, NULL, NULL);
+ array_clr(&node->meshes);
+
+ free(node);
+}
+
+static void init_node(ModelNode *node, ModelNode *parent)
+{
+ if ((node->parent = parent))
+ list_apd(&parent->children, node);
+
+ list_ini(&node->children);
+}
+
+static void clone_mesh(ModelMesh *mesh)
+{
+ GLuint *old_textures = mesh->textures;
+ memcpy(mesh->textures = malloc(mesh->num_textures * sizeof *mesh->textures),
+ old_textures, mesh->num_textures * sizeof *mesh->textures);
+}
+
+static ModelNode *clone_node(ModelNode *original, ModelNode *parent)
+{
+ ModelNode *node = malloc(sizeof *node);
+ *node = *original;
+ init_node(node, parent);
+
+ array_cln(&node->meshes, &original->meshes);
+ for (size_t i = 0; i < node->meshes.siz; i++)
+ clone_mesh(&((ModelMesh *) node->meshes.ptr)[i]);
+
+ list_itr(&original->children, (void *) &clone_node, parent, NULL);
+ return node;
+}
+
+static int cmp_model(const Model *model, const f32 *distance)
+{
+ return f32_cmp(&model->distance, distance);
+}
+
+static void render_model(Model *model)
+{
+ if (model->flags.wireframe)
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+
+ render_node(model->root);
+
+ if (model->flags.wireframe)
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+}
+
+// step model help im stuck
+static void model_step(Model *model, Tree *transparent, f64 dtime)
+{
+ if (client_config.view_distance < (model->distance = sqrt(
+ pow(model->root->pos.x - camera.eye[0], 2) +
+ pow(model->root->pos.y - camera.eye[1], 2) +
+ pow(model->root->pos.z - camera.eye[2], 2))))
+ return;
+
+ if (model->callbacks.step)
+ model->callbacks.step(model, dtime);
+
+ if (!model->root->visible)
+ return;
+
+ if (model->flags.frustum_culling && frustum_cull((aabb3f32) {
+ v3f32_add(model->box.min, model->root->pos),
+ v3f32_add(model->box.max, model->root->pos)}))
+ return;
+
+ // fixme: if there are multiple objects with the exact same distance, only one is rendered
+ if (model->flags.transparent)
+ tree_add(transparent, &model->distance, model, &cmp_model, NULL);
+ else
+ render_model(model);
+}
+
+// init
+void model_init()
+{
+ list_ini(&scene);
+ pthread_rwlock_init(&lock_scene, NULL);
+ glGetIntegerv(GL_MAX_TEXTURE_UNITS, &units);
+}
+
+// ded
+void model_deinit()
+{
+ list_clr(&scene, &model_delete, NULL, NULL);
+ pthread_rwlock_destroy(&lock_scene);
+}
+
+// Model functions
+
+Model *model_create()
+{
+ Model *model = malloc(sizeof *model);
+ model->root = model_node_create(NULL);
+ model->extra = NULL;
+
+ model->callbacks.step = NULL;
+ model->callbacks.delete = NULL;
+
+ model->flags.delete =
+ model->flags.wireframe =
+ model->flags.frustum_culling =
+ model->flags.transparent = 0;
+
+ return model;
+}
+
+Model *model_load(const char *path, const char *textures_path, Mesh *cube, ModelShader *shader)
+{
+ Model *model = model_create();
+
+ Array stack;
+ array_ini(&stack, sizeof(ModelNode *), 5);
+ array_apd(&stack, &model->root);
+
+ FILE *file = fopen(path, "r");
+ if (!file) {
+ fprintf(stderr, "[warning] failed to open model %s\n", path);
+ return NULL;
+ }
+
+ char *line = NULL;
+ size_t siz = 0;
+ ssize_t length;
+ int count = 0;
+
+ while ((length = getline(&line, &siz, file)) > 0) {
+ count++;
+
+ if (*line == '#')
+ continue;
+
+ char *cursor = line - 1;
+
+ size_t tabs = 0;
+ while (*++cursor == '\t')
+ tabs++;
+
+ if (tabs >= stack.siz) {
+ fprintf(stderr, "[warning] invalid indent in model %s in line %d\n", path, count);
+ continue;
+ }
+
+ ModelNode *node = model_node_create(((ModelNode **) stack.ptr)[tabs]);
+
+ int n;
+ char key[length + 1];
+ while (sscanf(cursor, "%s %n", key, &n) == 1) {
+ cursor += n;
+
+ if (strcmp(key, "name") == 0) {
+ char name[length + 1];
+
+ if (sscanf(cursor, "%s %n", name, &n) == 1) {
+ cursor += n;
+
+ if (node->name)
+ free(node->name);
+ node->name = strdup(name);
+ } else {
+ fprintf(stderr, "[warning] invalid value for name in model %s in line %d\n", path, count);
+ }
+ } else if (strcmp(key, "pos") == 0) {
+ if (sscanf(cursor, "%f %f %f %n", &node->pos.x, &node->pos.y, &node->pos.z, &n) == 3)
+ cursor += n;
+ else
+ fprintf(stderr, "[warning] invalid value for pos in model %s in line %d\n", path, count);
+ } else if (strcmp(key, "rot") == 0) {
+ if (sscanf(cursor, "%f %f %f %n", &node->rot.x, &node->rot.y, &node->rot.z, &n) == 3)
+ cursor += n;
+ else
+ fprintf(stderr, "[warning] invalid value for rot in model %s in line %d\n", path, count);
+ } else if (strcmp(key, "scale") == 0) {
+ if (sscanf(cursor, "%f %f %f %n", &node->scale.x, &node->scale.y, &node->scale.z, &n) == 3)
+ cursor += n;
+ else
+ fprintf(stderr, "[warning] invalid value for scale in model %s in line %d\n", path, count);
+ } else if (strcmp(key, "angle") == 0) {
+ if (sscanf(cursor, "%f%n", &node->angle, &n) == 1)
+ cursor += n;
+ else
+ fprintf(stderr, "[warning] invalid value for angle in model %s in line %d\n", path, count);
+ } else if (strcmp(key, "cube") == 0) {
+ char texture[length + 1];
+
+ if (sscanf(cursor, "%s %n", texture, &n) == 1) {
+ cursor += n;
+
+ char filepath[strlen(textures_path) + 1 + strlen(texture) + 1];
+ sprintf(filepath, "%s/%s", textures_path, texture);
+ Texture *texture = texture_load_cubemap(filepath);
+
+ model_node_add_mesh(node, &(ModelMesh) {
+ .mesh = cube,
+ .textures = &texture->txo,
+ .num_textures = 1,
+ .shader = shader,
+ });
+ } else {
+ fprintf(stderr, "[warning] invalid value for cube in model %s in line %d\n", path, count);
+ }
+ } else {
+ fprintf(stderr, "[warning] invalid key '%s' in model %s in line %d\n", key, path, count);
+ }
+ }
+
+ model_node_transform(node);
+
+ stack.siz = tabs + 1;
+ array_apd(&stack, &node);
+ }
+
+ if (line)
+ free(line);
+
+ fclose(file);
+ array_clr(&stack);
+
+ return model;
+}
+
+Model *model_clone(Model *original)
+{
+ Model *model = malloc(sizeof *model);
+ *model = *original;
+ model->root = clone_node(original->root, NULL);
+ return model;
+}
+
+void model_delete(Model *model)
+{
+ if (model->callbacks.delete)
+ model->callbacks.delete(model);
+
+ delete_node(model->root);
+ free(model);
+}
+
+void model_free_meshes(Model *model)
+{
+ free_node_meshes(model->root);
+}
+
+void model_get_bones(Model *model, ModelBoneMapping *mappings, size_t num_mappings)
+{
+ char *name, *cursor, *saveptr, *tok;
+
+ for (size_t i = 0; i < num_mappings; i++) {
+ name = cursor = strdup(mappings[i].name);
+
+ ModelNode *node = model->root;
+ while ((tok = strtok_r(cursor, ".", &saveptr))) {
+ node = list_get(&node->children, tok, (void *) &cmp_node, NULL);
+ cursor = NULL;
+ }
+
+ if (node)
+ *mappings[i].node = node;
+ else
+ fprintf(stderr, "[warning] no such bone: %s\n", name);
+
+ free(name);
+ }
+}
+
+// ModelNode functions
+
+ModelNode *model_node_create(ModelNode *parent)
+{
+ ModelNode *node = malloc(sizeof *node);
+ node->name = NULL;
+ node->visible = true;
+ node->pos = (v3f32) {0.0f, 0.0f, 0.0f};
+ node->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+ node->scale = (v3f32) {1.0f, 1.0f, 1.0f};
+ node->angle = 0.0f;
+ array_ini(&node->meshes, sizeof(ModelMesh), 0);
+ init_node(node, parent);
+ return node;
+}
+
+void model_node_transform(ModelNode *node)
+{
+ mat4x4_translate(node->rel,
+ node->pos.x,
+ node->pos.y,
+ node->pos.z);
+
+ mat4x4_rotate(node->rel, node->rel,
+ node->rot.x,
+ node->rot.y,
+ node->rot.z,
+ node->angle);
+
+ mat4x4_scale_aniso(node->rel, node->rel,
+ node->scale.x,
+ node->scale.y,
+ node->scale.z);
+
+ transform_node(node);
+}
+
+void model_node_add_mesh(ModelNode *node, const ModelMesh *mesh)
+{
+ array_apd(&node->meshes, mesh);
+ clone_mesh(&((ModelMesh *) node->meshes.ptr)[node->meshes.siz - 1]);
+}
+
+void model_node_add_batch(ModelNode *node, ModelBatch *batch)
+{
+ if (!batch->textures.siz) {
+ free(batch);
+ return;
+ }
+
+ array_srt(&batch->textures, &cmp_batch_texture);
+ ModelBatchTexture *textures = batch->textures.ptr;
+
+ size_t num_meshes = ceil((double) batch->textures.siz / (double) units);
+ array_grw(&node->meshes, num_meshes);
+ ModelMesh *meshes = node->meshes.ptr + node->meshes.siz - num_meshes;
+
+ for (size_t m = 0; m < num_meshes; m++) {
+ ModelMesh *mesh = &meshes[m];
+
+ mesh->mesh = malloc(sizeof *mesh->mesh);
+ mesh->mesh->layout = batch->layout;
+ mesh->mesh->vao = mesh->mesh->vbo = 0;
+ mesh->mesh->free_data = true;
+
+ mesh->textures = malloc(sizeof *mesh->textures * (mesh->num_textures =
+ ceil((double) (batch->textures.siz - m) / (double) num_meshes)));
+
+ mesh->shader = batch->shader;
+
+ mesh->mesh->count = 0;
+ for (size_t t = 0; t < mesh->num_textures; t++) {
+ ModelBatchTexture *texture = &textures[m + t * num_meshes];
+ mesh->mesh->count += texture->vertices.siz;
+ mesh->textures[t] = texture->texture;
+
+ for (size_t v = 0; v < texture->vertices.siz; v++)
+ *((f32 *) (texture->vertices.ptr + v * texture->vertices.mbs
+ + batch->off_tex_idx)) = t;
+ }
+
+ ModelBatchTexture *first = &textures[m];
+ first->vertices.cap = mesh->mesh->count;
+ first->vertices.ext = 0;
+ array_rlc(&first->vertices);
+
+ mesh->mesh->data = first->vertices.ptr;
+
+ for (size_t t = 1; t < mesh->num_textures; t++) {
+ ModelBatchTexture *texture = &textures[m + t * num_meshes];
+ memcpy(first->vertices.ptr + first->vertices.siz * first->vertices.mbs,
+ texture->vertices.ptr, texture->vertices.siz * texture->vertices.mbs);
+ first->vertices.siz += texture->vertices.siz;
+
+ array_clr(&texture->vertices);
+ }
+ }
+
+ array_clr(&batch->textures);
+ free(batch);
+}
+
+// ModelBatch functions
+
+ModelBatch *model_batch_create(ModelShader *shader, VertexLayout *layout, size_t off_tex_idx)
+{
+ ModelBatch *batch = malloc(sizeof *batch);
+ batch->shader = shader;
+ batch->layout = layout;
+ batch->off_tex_idx = off_tex_idx;
+ array_ini(&batch->textures, sizeof(ModelBatchTexture), 5);
+ return batch;
+}
+
+void model_batch_free(ModelBatch *batch)
+{
+ for (size_t i = 0; i < batch->textures.siz; i++)
+ array_clr(&((ModelBatchTexture *) batch->textures.ptr)[i].vertices);
+
+ array_clr(&batch->textures);
+ free(batch);
+}
+
+void model_batch_add_vertex(ModelBatch *batch, GLuint texture, const void *vertex)
+{
+ ModelBatchTexture *batch_texture = NULL;
+
+ for (size_t i = 0; i <= batch->textures.siz; i++) {
+ if (i == batch->textures.siz) {
+ ModelBatchTexture new;
+ new.texture = texture;
+ array_ini(&new.vertices, batch->layout->size, 10000);
+ array_apd(&batch->textures, &new);
+ }
+
+ if ((batch_texture = &((ModelBatchTexture *) batch->textures.ptr)[i])->texture == texture)
+ break;
+ }
+
+ array_apd(&batch_texture->vertices, vertex);
+}
+
+// scene functions
+
+void model_scene_add(Model *model)
+{
+ pthread_rwlock_wrlock(&lock_scene);
+ list_apd(&scene, model);
+ pthread_rwlock_unlock(&lock_scene);
+}
+
+void model_scene_render(f64 dtime)
+{
+ Tree transparent;
+ tree_ini(&transparent);
+
+ pthread_rwlock_rdlock(&lock_scene);
+ for (ListNode **node = &scene.fst; *node != NULL;) {
+ Model *model = (*node)->dat;
+
+ pthread_rwlock_unlock(&lock_scene);
+ if (model->flags.delete) {
+ model_delete(model);
+
+ pthread_rwlock_wrlock(&lock_scene);
+ list_nrm(&scene, node);
+ pthread_rwlock_unlock(&lock_scene);
+
+ pthread_rwlock_rdlock(&lock_scene);
+ } else {
+ model_step(model, &transparent, dtime);
+
+ pthread_rwlock_rdlock(&lock_scene);
+ node = &(*node)->nxt;
+ }
+ }
+ pthread_rwlock_unlock(&lock_scene);
+
+ tree_clr(&transparent, &render_model, NULL, NULL, TRAVERSION_INORDER);
+}
--- /dev/null
+#ifndef _MODEL_H_
+#define _MODEL_H_
+
+#include <dragonstd/array.h>
+#include <dragonstd/list.h>
+#include <GL/glew.h>
+#include <GL/gl.h>
+#include <linmath.h/linmath.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "client/mesh.h"
+#include "client/texture.h"
+#include "types.h"
+
+typedef struct {
+ GLuint prog;
+ GLint loc_transform;
+} ModelShader;
+
+typedef struct {
+ ModelShader *shader;
+ VertexLayout *layout;
+ size_t off_tex_idx;
+ Array textures;
+} ModelBatch;
+
+typedef struct ModelNode {
+ char *name;
+ bool visible;
+ v3f32 pos, rot, scale;
+ f32 angle;
+ mat4x4 abs, rel;
+ Array meshes;
+ struct ModelNode *parent;
+ List children;
+} ModelNode;
+
+typedef struct {
+ Mesh *mesh;
+ GLuint *textures;
+ GLuint num_textures;
+ ModelShader *shader;
+} ModelMesh;
+
+typedef struct {
+ char *name;
+ ModelNode **node;
+} ModelBoneMapping;
+
+typedef struct Model {
+ ModelNode *root;
+ void *extra;
+ aabb3f32 box;
+ f32 distance;
+ struct {
+ void (*step)(struct Model *model, f64 dtime);
+ void (*delete)(struct Model *model);
+ } callbacks;
+ struct {
+ unsigned int delete: 1;
+ unsigned int wireframe: 1;
+ unsigned int frustum_culling: 1;
+ unsigned int transparent: 1;
+ } flags;
+} Model;
+
+// initialize
+void model_init();
+// ded
+void model_deinit();
+
+// create empty model
+Model *model_create();
+// load model from file
+Model *model_load(const char *path, const char *textures_path, Mesh *cube, ModelShader *shader);
+// clone existing model
+Model *model_clone(Model *model);
+// delete model
+void model_delete(Model *model);
+// use this as delete callback to free all meshes associated with the model on delete
+void model_free_meshes(Model *model);
+// get bone locations
+void model_get_bones(Model *model, ModelBoneMapping *mappings, size_t num_mappings);
+
+// add a new mode
+ModelNode *model_node_create(ModelNode *parent);
+// recalculate transform matrices
+void model_node_transform(ModelNode *node);
+// add a mesh to model node (will be copied)
+void model_node_add_mesh(ModelNode *node, const ModelMesh *mesh);
+// add and delete batch (may add multiple meshes depending on available texture units)
+void model_node_add_batch(ModelNode *node, ModelBatch *batch);
+
+// create batch
+ModelBatch *model_batch_create(ModelShader *shader, VertexLayout *layout, size_t off_tex_idx);
+// delete batch
+void model_batch_free(ModelBatch *batch);
+// add a vertex to batch
+void model_batch_add_vertex(ModelBatch *batch, GLuint texture, const void *vertex);
+
+// add model to scene
+void model_scene_add(Model *model);
+// render scene
+void model_scene_render(f64 dtime);
+
+#endif // _MODEL_H_
+++ /dev/null
-#include <stdio.h>
-#include <stdlib.h>
-#include "client/frustum.h"
-#include "client/object.h"
-#include "client/scene.h"
-#define OBJECT_VERTEX_ATTRIBUTES 5
-
-static VertexAttribute vertex_attributes[OBJECT_VERTEX_ATTRIBUTES] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 3,
- .size = sizeof(Vertex3DPosition),
- },
- // normal
- {
- .type = GL_FLOAT,
- .length = 3,
- .size = sizeof(Vertex3DNormal),
- },
- // textureIndex
- {
- .type = GL_FLOAT,
- .length = 1,
- .size = sizeof(Vertex3DTextureIndex),
- },
- // textureCoordinates
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(Vertex3DTextureCoordinates),
-
- },
- // color
- {
- .type = GL_FLOAT,
- .length = 3,
- .size = sizeof(Vertex3DColor),
- },
-};
-
-static VertexLayout vertex_layout = {
- .attributes = vertex_attributes,
- .count = OBJECT_VERTEX_ATTRIBUTES,
- .size = sizeof(Vertex3D),
-};
-
-Object *object_create()
-{
- Object *obj = malloc(sizeof(Object));
- obj->pos = (v3f32) {0.0f, 0.0f, 0.0f};
- obj->rot = (v3f32) {0.0f, 0.0f, 0.0f};
- obj->scale = (v3f32) {1.0f, 1.0f, 1.0f};
- obj->angle = 0.0f;
- obj->remove = false;
- obj->meshes = NULL;
- obj->meshes_count = 0;
- obj->visible = true;
- obj->wireframe = false;
- obj->frustum_culling = false;
- obj->transparent = false;
- obj->box = (aabb3f32) {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}};
- obj->current_face = NULL;
- obj->faces = array_create(sizeof(ObjectFace));
- obj->on_render = NULL;
- obj->extra = NULL;
-
- return obj;
-}
-
-void object_delete(Object *obj)
-{
- for (size_t i = 0; i < obj->meshes_count; i++)
- mesh_delete(obj->meshes[i]);
-
- free(obj);
-}
-
-void object_set_texture(Object *obj, Texture *texture)
-{
- if (obj->current_face && obj->current_face->texture == texture->id)
- return;
-
- ObjectFace face = {
- .texture = texture->id,
- .vertices = array_create(sizeof(Vertex3D)),
- };
-
- array_append(&obj->faces, &face);
- obj->current_face = &((ObjectFace *) obj->faces.ptr)[obj->faces.siz - 1];
-}
-
-void object_add_vertex(Object *obj, Vertex3D *vertex)
-{
- array_append(&obj->current_face->vertices, vertex);
-}
-
-static int qsort_compare_faces(const void *f1, const void *f2)
-{
- return ((ObjectFace *) f1)->texture - ((ObjectFace *) f2)->texture;
-}
-
-static void add_mesh(Array *meshes, Array *vertices, Array *textures)
-{
- if (vertices->siz > 0) {
- Mesh *mesh = mesh_create();
- mesh->vertices = vertices->ptr;
- mesh->vertices_count = vertices->siz;
- mesh->free_vertices = true;
- mesh->free_textures = true;
- mesh->layout = &vertex_layout;
- size_t textures_count;
- array_copy(textures, (void *) &mesh->textures, &textures_count);
- mesh->textures_count = textures_count;
-
- free(textures->ptr);
-
- array_append(meshes, &mesh);
- }
-
- *vertices = array_create(sizeof(Vertex3D));
- *textures = array_create(sizeof(GLuint));
-}
-
-bool object_add_to_scene(Object *obj)
-{
- if (obj->faces.siz == 0)
- return false;
-
- object_transform(obj);
-
- qsort(obj->faces.ptr, obj->faces.siz, sizeof(ObjectFace), &qsort_compare_faces);
-
- GLuint max_texture_units = scene_get_max_texture_units();
-
- Array meshes = array_create(sizeof(Mesh *));
-
- Array vertices = array_create(sizeof(Vertex3D));
- Array textures = array_create(sizeof(GLuint));
-
- GLuint texture_id = 0;
- GLuint texture_index = max_texture_units;
-
- for (size_t f = 0; f < obj->faces.siz; f++) {
- ObjectFace *face = &((ObjectFace *) obj->faces.ptr)[f];
-
- if (face->texture != texture_id) {
- if (++texture_index >= max_texture_units) {
- add_mesh(&meshes, &vertices, &textures);
- texture_index = 0;
- }
-
- texture_id = face->texture;
- array_append(&textures, &texture_id);
- }
-
- for (size_t v = 0; v < face->vertices.siz; v++) {
- Vertex3D *vertex = &((Vertex3D *) face->vertices.ptr)[v];
- vertex->textureIndex = texture_index;
- array_append(&vertices, vertex);
- }
-
- free(face->vertices.ptr);
- }
- add_mesh(&meshes, &vertices, &textures);
- free(obj->faces.ptr);
-
- array_copy(&meshes, (void *) &obj->meshes, &obj->meshes_count);
- free(meshes.ptr);
-
- scene_add_object(obj);
-
- return true;
-}
-
-void object_transform(Object *obj)
-{
- mat4x4_translate(obj->transform, obj->pos.x, obj->pos.y, obj->pos.z);
- mat4x4_rotate(obj->transform, obj->transform, obj->rot.x, obj->rot.y, obj->rot.z, obj->angle);
- mat4x4_scale_aniso(obj->transform, obj->transform, obj->scale.x, obj->scale.y, obj->scale.z);
-}
-
-bool object_before_render(Object *obj, f64 dtime)
-{
- if (obj->on_render)
- obj->on_render(obj, dtime);
-
- if (! obj->visible)
- return false;
-
- if (obj->frustum_culling) {
- aabb3f32 box = {v3f32_add(obj->box.min, obj->pos), v3f32_add(obj->box.max, obj->pos)};
-
- if (! frustum_is_visible(box))
- return false;
- }
-
- return true;
-}
-
-void object_render(Object *obj)
-{
- glUniformMatrix4fv(scene.loc_model, 1, GL_FALSE, obj->transform[0]);
-
- if (obj->wireframe)
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-
- for (size_t i = 0; i < obj->meshes_count; i++)
- mesh_render(obj->meshes[i]);
-
- if (obj->wireframe)
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-}
+++ /dev/null
-#ifndef _OBJECT_H_
-#define _OBJECT_H_
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <GL/glew.h>
-#include <GL/gl.h>
-#include <linmath.h/linmath.h>
-#include <dragonstd/array.h>
-#include "client/mesh.h"
-#include "client/texture.h"
-#include "client/vertex.h"
-#include "types.h"
-
-typedef struct {
- GLfloat x, y, z;
-} __attribute__((packed)) Vertex3DPosition;
-
-typedef struct {
- GLfloat x, y, z;
-} __attribute__((packed)) Vertex3DNormal;
-
-typedef GLfloat Vertex3DTextureIndex;
-
-typedef struct {
- GLfloat s, t;
-} __attribute__((packed)) Vertex3DTextureCoordinates;
-
-typedef struct {
- GLfloat r, g, b;
-} __attribute__((packed)) Vertex3DColor;
-
-typedef struct
-{
- Vertex3DPosition position;
- Vertex3DNormal normal;
- Vertex3DTextureIndex textureIndex;
- Vertex3DTextureCoordinates textureCoordinates;
- Vertex3DColor color;
-} __attribute__((packed)) Vertex3D;
-
-typedef struct
-{
- GLuint texture;
- Array vertices;
-} ObjectFace;
-
-typedef struct Object
-{
- v3f32 pos, rot, scale;
- f32 angle;
- bool remove;
- Mesh **meshes;
- size_t meshes_count;
- mat4x4 transform;
- bool visible;
- bool wireframe;
- bool frustum_culling;
- bool transparent;
- aabb3f32 box;
- ObjectFace *current_face;
- Array faces;
- void (*on_render)(struct Object *obj, f64 dtime);
- void *extra;
-} Object;
-
-Object *object_create();
-void object_delete(Object *obj);
-void object_set_texture(Object *obj, Texture *texture);
-void object_add_vertex(Object *obj, Vertex3D *vertex);
-bool object_add_to_scene(Object *obj);
-void object_transform(Object *obj);
-bool object_before_render(Object *obj, f64 dtime);
-void object_render(Object *obj);
-
-#endif
+++ /dev/null
-#include <stdlib.h>
-#include <stdio.h>
-#include "client/camera.h"
-#include "client/client.h"
-#include "client/client_config.h"
-#include "client/frustum.h"
-#include "client/scene.h"
-#include "client/shader.h"
-#include "day.h"
-#include "util.h"
-
-struct Scene scene;
-
-static int bintree_compare_f32(void *v1, void *v2, unused Bintree *tree)
-{
- f32 diff = (*(f32 *) v2) - (*(f32 *) v1);
- return CMPBOUNDS(diff);
-}
-
-bool scene_init()
-{
- scene.objects = list_create(NULL);
- scene.transparent_objects = bintree_create(sizeof(f32), &bintree_compare_f32);
- pthread_mutex_init(&scene.mtx, NULL);
-
- glGetIntegerv(GL_MAX_TEXTURE_UNITS, &scene.max_texture_units);
-
- char *shader_defs = format_string(
- "#define MAX_TEXTURE_UNITS %d\n"
- "#define RENDER_DISTANCE %lf\n",
- scene.max_texture_units,
- client_config.render_distance
- );
-
- if (! shader_program_create(RESSOURCE_PATH "shaders/3d", &scene.prog, shader_defs)) {
- fprintf(stderr, "Failed to create 3D shader program\n");
- return false;
- }
-
- free(shader_defs);
-
- scene.loc_model = glGetUniformLocation(scene.prog, "model");
- scene.loc_VP = glGetUniformLocation(scene.prog, "VP");
- scene.loc_daylight = glGetUniformLocation(scene.prog, "daylight");
- scene.loc_fogColor = glGetUniformLocation(scene.prog, "fogColor");
- scene.loc_ambientLight = glGetUniformLocation(scene.prog, "ambientLight");
- scene.loc_lightDir = glGetUniformLocation(scene.prog, "lightDir");
- scene.loc_cameraPos = glGetUniformLocation(scene.prog, "cameraPos");
-
- GLint texture_indices[scene.max_texture_units];
- for (GLint i = 0; i < scene.max_texture_units; i++)
- texture_indices[i] = i;
-
- glProgramUniform1iv(scene.prog, glGetUniformLocation(scene.prog, "textures"), scene.max_texture_units, texture_indices);
-
- scene.fov = 86.1f;
-
- return true;
-}
-
-static void list_delete_object(void *key, unused void *value, unused void *arg)
-{
- object_delete(key);
-}
-
-void scene_deinit()
-{
- list_clear_func(&scene.objects, &list_delete_object, NULL);
- pthread_mutex_destroy(&scene.mtx);
- glDeleteProgram(scene.prog);
-}
-
-void scene_add_object(Object *obj)
-{
- pthread_mutex_lock(&scene.mtx);
- list_put(&scene.objects, obj, NULL);
- pthread_mutex_unlock(&scene.mtx);
-}
-
-static void bintree_render_object(BintreeNode *node, unused void *arg)
-{
- object_render(node->value);
-}
-
-void scene_render(f64 dtime)
-{
- mat4x4_mul(scene.VP, scene.projection, camera.view);
-
- vec4 base_sunlight_dir = {0.0f, 0.0f, -1.0f, 1.0f};
- vec4 sunlight_dir;
- mat4x4 sunlight_mat;
- mat4x4_identity(sunlight_mat);
-
- mat4x4_rotate(sunlight_mat, sunlight_mat, 1.0f, 0.0f, 0.0f, get_sun_angle() + M_PI / 2.0f);
- mat4x4_mul_vec4(sunlight_dir, sunlight_mat, base_sunlight_dir);
-
- frustum_update(scene.VP);
-
- f32 daylight = get_daylight();
- f32 ambient_light = f32_mix(0.3f, 0.7f, daylight);
- v3f32 fog_color = v3f32_mix((v3f32) {0x03, 0x0A, 0x1A}, (v3f32) {0x87, 0xCE, 0xEB}, daylight);
-
- glUseProgram(scene.prog);
- glUniformMatrix4fv(scene.loc_VP, 1, GL_FALSE, scene.VP[0]);
- glUniform3f(scene.loc_lightDir, sunlight_dir[0], sunlight_dir[1], sunlight_dir[2]);
- glUniform3f(scene.loc_cameraPos, camera.eye[0], camera.eye[1], camera.eye[2]);
- glUniform1f(scene.loc_daylight, daylight);
- glUniform3f(scene.loc_fogColor, fog_color.x / 0xFF * ambient_light, fog_color.y / 0xFF * ambient_light, fog_color.z / 0xFF * ambient_light);
- glUniform1f(scene.loc_ambientLight, ambient_light);
-
- for (ListPair **pairptr = &scene.objects.first; *pairptr != NULL; ) {
- ListPair *pair = *pairptr;
- Object *obj = pair->key;
- if (obj->remove) {
- pthread_mutex_lock(&scene.mtx);
- *pairptr = pair->next;
- pthread_mutex_unlock(&scene.mtx);
- free(pair);
- object_delete(obj);
- } else {
- f32 distance = sqrt(pow(obj->pos.x - camera.eye[0], 2) + pow(obj->pos.y - camera.eye[1], 2) + pow(obj->pos.z - camera.eye[2], 2));
- if (distance < client_config.render_distance && object_before_render(obj, dtime)) {
- if (obj->transparent)
- bintree_insert(&scene.transparent_objects, &distance, obj);
- else
- object_render(obj);
- }
- pairptr = &pair->next;
- }
- }
-
- bintree_traverse(&scene.transparent_objects, BTT_INORDER, &bintree_render_object, NULL);
- bintree_clear(&scene.transparent_objects, NULL, NULL);
-}
-
-void scene_on_resize(int width, int height)
-{
- mat4x4_perspective(scene.projection, scene.fov / 180.0f * M_PI, (float) width / (float) height, 0.01f, client_config.render_distance + 28.0f);
-}
-
-GLuint scene_get_max_texture_units()
-{
- return scene.max_texture_units;
-}
+++ /dev/null
-#ifndef _SCENE_H_
-#define _SCENE_H_
-
-#include <stdbool.h>
-#include <pthread.h>
-#include <linmath.h/linmath.h>
-#include <dragonstd/bintree.h>
-#include <dragonstd/list.h>
-#include "types.h"
-#include "client/object.h"
-
-extern struct Scene
-{
- List objects;
- Bintree transparent_objects;
- pthread_mutex_t mtx;
- GLuint prog;
- GLint loc_model;
- GLint loc_VP;
- GLint loc_daylight;
- GLint loc_fogColor;
- GLint loc_ambientLight;
- GLint loc_lightDir;
- GLint loc_cameraPos;
- GLint max_texture_units;
- mat4x4 VP;
- mat4x4 projection;
- f32 fov;
- f32 render_distance;
-} scene;
-
-bool scene_init();
-void scene_deinit();
-void scene_add_object(Object *obj);
-void scene_render(f64 dtime);
-void scene_on_resize(int width, int height);
-GLuint scene_get_max_texture_units();
-void scene_get_view_proj(mat4x4 target);
-
-#endif
-#include <string.h>
#include <stdio.h>
-#include <unistd.h>
#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
#include "client/shader.h"
-static GLuint compile_and_attach_shader(GLenum type, const char *path, const char *name, GLuint program, const char *definitions)
+static GLuint compile_shader(GLenum type, const char *path, const char *name, GLuint program, const char *definitions)
{
char full_path[strlen(path) + 1 + strlen(name) + 1 + 4 + 1];
sprintf(full_path, "%s/%s.glsl", path, name);
FILE *file = fopen(full_path, "r");
- if (! file) {
+ if (!file) {
perror("fopen");
return 0;
}
GLuint id = glCreateShader(type);
- const char *version = "#version 420 core\n";
+ // Minimum OpenGL version is 4.2.0 (idk some shader feature from that version is required)
+ const char *version = "#version 420 core\n"; // 420 blaze it
const char *code_list[3] = {
version,
GLint success;
glGetShaderiv(id, GL_COMPILE_STATUS, &success);
- if (! success) {
+ if (!success) {
char errbuf[BUFSIZ];
glGetShaderInfoLog(id, BUFSIZ, NULL, errbuf);
- fprintf(stderr, "Failed to compile %s shader: %s", name, errbuf);
+ fprintf(stderr, "[error] failed to compile %s shader: %s", name, errbuf);
glDeleteShader(id);
return 0;
}
{
GLuint id = glCreateProgram();
- if (! definitions)
+ if (!definitions)
definitions = "";
GLuint vert, frag;
- if (! (vert = compile_and_attach_shader(GL_VERTEX_SHADER, path, "vertex", id, definitions))) {
+ if (!(vert = compile_shader(GL_VERTEX_SHADER, path, "vertex", id, definitions))) {
glDeleteProgram(id);
return false;
}
- if (! (frag = compile_and_attach_shader(GL_FRAGMENT_SHADER, path, "fragment", id, definitions))) {
+ if (!(frag = compile_shader(GL_FRAGMENT_SHADER, path, "fragment", id, definitions))) {
glDeleteShader(vert);
glDeleteProgram(id);
return false;
GLint success;
glGetProgramiv(id, GL_LINK_STATUS, &success);
- if (! success) {
+ if (!success) {
char errbuf[BUFSIZ];
glGetProgramInfoLog(id, BUFSIZ, NULL, errbuf);
- fprintf(stderr, "Failed to link shader program: %s\n", errbuf);
+ fprintf(stderr, "[error] failed to link shader program: %s\n", errbuf);
glDeleteProgram(id);
return false;
}
#ifndef _SHADER_H_
#define _SHADER_H_
-#include <stdbool.h>
#include <GL/glew.h>
#include <GL/gl.h>
+#include <stdbool.h>
bool shader_program_create(const char *path, GLuint *idptr, const char *definitions);
-#endif
+#endif // _SHADER_H_
#include "client/client.h"
#include "client/cube.h"
#include "client/mesh.h"
-#include "client/scene.h"
#include "client/shader.h"
#include "client/sky.h"
#include "client/texture.h"
+#include "client/window.h"
#include "day.h"
-static struct
-{
- GLuint skybox_prog;
- GLint skybox_loc_VP;
- GLint skybox_loc_daylight;
- GLuint skybox_textures[2];
- Mesh *skybox_mesh;
- GLuint sun_prog;
- GLint sun_loc_MVP;
- Texture *sun_texture;
- Mesh *sun_mesh;
- GLuint clouds_prog;
- GLint clouds_loc_VP;
- GLint clouds_loc_daylight;
- Mesh *clouds_mesh;
-} sky;
-
-typedef struct
-{
- GLfloat x, y, z;
-} __attribute__((packed)) VertexSunPosition;
-
-typedef struct
-{
- GLfloat s, t;
-} __attribute__((packed)) VertexSunTextureCoordinates;
-
-typedef struct
-{
- VertexSunPosition position;
- VertexSunTextureCoordinates textureCoordinates;
-} __attribute__((packed)) VertexSun;
-
-static VertexAttribute sun_vertex_attributes[2] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 3,
- .size = sizeof(VertexSunPosition),
+static GLuint sun_prog;
+static GLint sun_loc_MVP;
+static GLuint sun_texture;
+typedef struct {
+ v3f32 position;
+ v2f32 textureCoordinates;
+} __attribute__((packed)) SunVertex;
+static Mesh sun_mesh = {
+ .layout = &(VertexLayout) {
+ .attributes = (VertexAttribute[]) {
+ {GL_FLOAT, 3, sizeof(v3f32)}, // position
+ {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
+ },
+ .count = 2,
+ .size = sizeof(SunVertex),
},
- // textureCoordinates
- {
- .type = GL_FLOAT,
- .length = 2,
- .size = sizeof(VertexSunTextureCoordinates),
+ .vao = 0,
+ .vbo = 0,
+ .data = (SunVertex[]) {
+ {{-0.5, -0.5, +0.0}, {+0.0, +0.0}},
+ {{+0.5, -0.5, +0.0}, {+1.0, +0.0}},
+ {{+0.5, +0.5, +0.0}, {+1.0, +1.0}},
+ {{+0.5, +0.5, +0.0}, {+1.0, +1.0}},
+ {{-0.5, +0.5, +0.0}, {+0.0, +1.0}},
+ {{-0.5, -0.5, +0.0}, {+0.0, +0.0}},
},
+ .count = 6,
+ .free_data = false,
};
-static VertexLayout sun_vertex_layout = {
- .attributes = sun_vertex_attributes,
- .count = 2,
- .size = sizeof(VertexSun),
-};
-
-static VertexSun sun_vertices[6] = {
- {{-0.5, -0.5, +0.0}, {+0.0, +0.0}},
- {{+0.5, -0.5, +0.0}, {+1.0, +0.0}},
- {{+0.5, +0.5, +0.0}, {+1.0, +1.0}},
- {{+0.5, +0.5, +0.0}, {+1.0, +1.0}},
- {{-0.5, +0.5, +0.0}, {+0.0, +1.0}},
- {{-0.5, -0.5, +0.0}, {+0.0, +0.0}},
-};
-
-typedef struct
-{
- GLfloat x, y, z;
-} __attribute__((packed)) VertexSkyboxPosition;
-
-typedef struct
-{
- VertexSkyboxPosition position;
-} __attribute__((packed)) VertexSkybox;
-
-static VertexAttribute skybox_vertex_attributes[2] = {
- // position
- {
- .type = GL_FLOAT,
- .length = 3,
- .size = sizeof(VertexSkyboxPosition),
+static GLuint skybox_prog;
+static GLint skybox_loc_VP;
+static GLint skybox_loc_daylight;
+static GLuint skybox_texture_day;
+static GLuint skybox_texture_night;
+typedef struct {
+ v3f32 position;
+} __attribute__((packed)) SkyboxVertex;
+static Mesh skybox_mesh = {
+ .layout = &(VertexLayout) {
+ .attributes = (VertexAttribute[]) {
+ {GL_FLOAT, 3, sizeof(v3f32)}, // position
+ },
+ .count = 1,
+ .size = sizeof(SkyboxVertex),
},
+ .vao = 0,
+ .vbo = 0,
+ .data = NULL,
+ .count = 36,
+ .free_data = false,
};
-static VertexLayout skybox_vertex_layout = {
- .attributes = skybox_vertex_attributes,
- .count = 1,
- .size = sizeof(VertexSkybox),
-};
-
-static VertexSkybox skybox_vertices[6][6];
-static VertexSkybox clouds_vertices[6][6];
+static GLuint clouds_prog;
+static GLint clouds_loc_VP;
+static GLint clouds_loc_daylight;
+static Mesh clouds_mesh;
bool sky_init()
{
- // skybox
-
- if (! shader_program_create(RESSOURCE_PATH "shaders/sky/skybox", &sky.skybox_prog, NULL)) {
- fprintf(stderr, "Failed to create skybox shader program\n");
- return false;
- }
-
- sky.skybox_loc_VP = glGetUniformLocation(sky.skybox_prog, "VP");
- sky.skybox_loc_daylight = glGetUniformLocation(sky.skybox_prog, "daylight");
-
- sky.skybox_textures[0] = texture_create_cubemap(RESSOURCE_PATH "textures/skybox/day");
- sky.skybox_textures[1] = texture_create_cubemap(RESSOURCE_PATH "textures/skybox/night");
-
- GLint texture_indices[2];
- for (GLint i = 0; i < 2; i++)
- texture_indices[i] = i;
-
- glProgramUniform1iv(sky.skybox_prog, glGetUniformLocation(sky.skybox_prog, "textures"), 2, texture_indices);
-
+ clouds_mesh = skybox_mesh;
+ SkyboxVertex skybox_vertices[6][6], clouds_vertices[6][6];
for (int f = 0; f < 6; f++) {
for (int v = 0; v < 6; v++) {
- skybox_vertices[f][v].position.x = cube_vertices[f][v].position.x;
- skybox_vertices[f][v].position.y = cube_vertices[f][v].position.y;
- skybox_vertices[f][v].position.z = cube_vertices[f][v].position.z;
+ skybox_vertices[f][v].position =
+ clouds_vertices[f][v].position =
+ cube_vertices[f][v].position;
+
+ clouds_vertices[f][v].position.y = fmax(clouds_vertices[f][v].position.y, 0.0);
}
}
- sky.skybox_mesh = mesh_create();
- sky.skybox_mesh->textures = sky.skybox_textures;
- sky.skybox_mesh->textures_count = 2;
- sky.skybox_mesh->free_textures = false;
- sky.skybox_mesh->vertices = skybox_vertices;
- sky.skybox_mesh->vertices_count = 36;
- sky.skybox_mesh->free_vertices = false;
- sky.skybox_mesh->layout = &skybox_vertex_layout;
-
- // sun
+ // skybox
- if (! shader_program_create(RESSOURCE_PATH "shaders/sky/sun", &sky.sun_prog, NULL)) {
- fprintf(stderr, "Failed to create sun shader program\n");
+ if (!shader_program_create(RESSOURCE_PATH "shaders/sky/skybox", &skybox_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create skybox shader program\n");
return false;
}
- sky.sun_loc_MVP = glGetUniformLocation(sky.sun_prog, "MVP");
-
- sky.sun_texture = texture_load(RESSOURCE_PATH "textures/sun.png", false);
+ glProgramUniform1iv(skybox_prog, glGetUniformLocation(skybox_prog, "textures"), 2, (GLint[]) {0, 1});
- sky.sun_mesh = mesh_create();
- sky.sun_mesh->textures = &sky.sun_texture->id;
- sky.sun_mesh->textures_count = 1;
- sky.sun_mesh->free_textures = false;
- sky.sun_mesh->vertices = sun_vertices;
- sky.sun_mesh->vertices_count = 6;
- sky.sun_mesh->free_vertices = false;
- sky.sun_mesh->layout = &sun_vertex_layout;
+ skybox_loc_VP = glGetUniformLocation(skybox_prog, "VP");
+ skybox_loc_daylight = glGetUniformLocation(skybox_prog, "daylight");
+ skybox_texture_day = texture_load_cubemap(RESSOURCE_PATH "textures/skybox/day")->txo;
+ skybox_texture_night = texture_load_cubemap(RESSOURCE_PATH "textures/skybox/night")->txo;
+ skybox_mesh.data = skybox_vertices;
+ mesh_upload(&skybox_mesh);
- // clouds
+ // sun
- if (! shader_program_create(RESSOURCE_PATH "shaders/sky/clouds", &sky.clouds_prog, NULL)) {
- fprintf(stderr, "Failed to create clouds shader program\n");
+ if (!shader_program_create(RESSOURCE_PATH "shaders/sky/sun", &sun_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create sun shader program\n");
return false;
}
- sky.clouds_loc_VP = glGetUniformLocation(sky.clouds_prog, "VP");
- sky.clouds_loc_daylight = glGetUniformLocation(sky.clouds_prog, "daylight");
+ sun_loc_MVP = glGetUniformLocation(sun_prog, "MVP");
+ sun_texture = texture_load(RESSOURCE_PATH "textures/sun.png", false)->txo;
- for (int f = 0; f < 6; f++) {
- for (int v = 0; v < 6; v++) {
- clouds_vertices[f][v].position.x = cube_vertices[f][v].position.x;
- clouds_vertices[f][v].position.y = fmax(cube_vertices[f][v].position.y, 0.0);
- clouds_vertices[f][v].position.z = cube_vertices[f][v].position.z;
- }
+ // clouds
+
+ if (!shader_program_create(RESSOURCE_PATH "shaders/sky/clouds", &clouds_prog, NULL)) {
+ fprintf(stderr, "[error] failed to create clouds shader program\n");
+ return false;
}
- sky.clouds_mesh = mesh_create();
- sky.clouds_mesh->textures = sky.skybox_textures;
- sky.clouds_mesh->textures_count = 1;
- sky.clouds_mesh->free_textures = false;
- sky.clouds_mesh->vertices = clouds_vertices;
- sky.clouds_mesh->vertices_count = 36;
- sky.clouds_mesh->free_vertices = false;
- sky.clouds_mesh->layout = &skybox_vertex_layout;
+ clouds_loc_VP = glGetUniformLocation(clouds_prog, "VP");
+ clouds_loc_daylight = glGetUniformLocation(clouds_prog, "daylight");
+ clouds_mesh.data = clouds_vertices;
+ mesh_upload(&clouds_mesh);
return true;
}
void sky_deinit()
{
- glDeleteProgram(sky.skybox_prog);
- glDeleteTextures(1, &sky.skybox_textures[0]);
- glDeleteTextures(1, &sky.skybox_textures[1]);
- mesh_delete(sky.skybox_mesh);
+ glDeleteProgram(sun_prog);
+ mesh_destroy(&sun_mesh);
- glDeleteProgram(sky.sun_prog);
- mesh_delete(sky.sun_mesh);
-}
+ glDeleteProgram(skybox_prog);
+ mesh_destroy(&skybox_mesh);
+ glDeleteProgram(clouds_prog);
+ mesh_destroy(&clouds_mesh);
+}
void sky_render()
{
view[3][1] = 0.0f;
view[3][2] = 0.0f;
- mat4x4 VP;
- mat4x4_mul(VP, scene.projection, view);
+ mat4x4 vp;
+ mat4x4_mul(vp, window.projection, view);
- mat4x4 MVP;
- mat4x4_mul(MVP, VP, model);
+ mat4x4 mvp;
+ mat4x4_mul(mvp, vp, model);
glDisable(GL_CULL_FACE);
glDepthFunc(GL_LEQUAL);
- glUseProgram(sky.skybox_prog);
- glUniformMatrix4fv(sky.skybox_loc_VP, 1, GL_FALSE, VP[0]);
- glUniform1f(sky.skybox_loc_daylight, daylight);
- mesh_render(sky.skybox_mesh);
-
- glUseProgram(sky.sun_prog);
- glUniformMatrix4fv(sky.sun_loc_MVP, 1, GL_FALSE, MVP[0]);
- mesh_render(sky.sun_mesh);
-
- glUseProgram(sky.clouds_prog);
- glUniformMatrix4fv(sky.clouds_loc_VP, 1, GL_FALSE, VP[0]);
- glUniform1f(sky.clouds_loc_daylight, daylight);
- mesh_render(sky.clouds_mesh);
+ glUseProgram(skybox_prog);
+ glUniformMatrix4fv(skybox_loc_VP, 1, GL_FALSE, vp[0]);
+ glUniform1f(skybox_loc_daylight, daylight);
+ glBindTextureUnit(0, skybox_texture_day);
+ glBindTextureUnit(1, skybox_texture_night);
+ mesh_render(&skybox_mesh);
+
+ glUseProgram(sun_prog);
+ glUniformMatrix4fv(sun_loc_MVP, 1, GL_FALSE, mvp[0]);
+ glBindTextureUnit(0, sun_texture);
+ mesh_render(&sun_mesh);
+
+ glUseProgram(clouds_prog);
+ glUniformMatrix4fv(clouds_loc_VP, 1, GL_FALSE, vp[0]);
+ glUniform1f(clouds_loc_daylight, daylight);
+ glBindTextureUnit(0, skybox_texture_day);
+ mesh_render(&clouds_mesh);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
void sky_deinit();
void sky_render();
-#endif
+#endif // _SKY_H_
--- /dev/null
+#include <asprintf/asprintf.h>
+#include <linmath.h/linmath.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "client/camera.h"
+#include "client/client_config.h"
+#include "client/client_node.h"
+#include "client/client_terrain.h"
+#include "client/cube.h"
+#include "client/frustum.h"
+#include "client/shader.h"
+#include "client/terrain_gfx.h"
+#include "day.h"
+
+typedef struct {
+ bool visible;
+ bool transparent;
+ ModelBatch *batch;
+ TerrainChunk *chunk;
+ v3s32 chunkp;
+ TerrainChunk *nbrs[6];
+ bool tried_nbrs[6];
+ bool cull_edges;
+} ChunkRenderData;
+
+static VertexLayout terrain_vertex_layout = {
+ .attributes = (VertexAttribute []) {
+ {GL_FLOAT, 3, sizeof(v3f32)}, // position
+ {GL_FLOAT, 3, sizeof(v3f32)}, // normal
+ {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
+ {GL_FLOAT, 1, sizeof(f32 )}, // textureIndex
+ {GL_FLOAT, 3, sizeof(v3f32)}, // color
+ },
+ .count = 5,
+ .size = sizeof(TerrainVertex),
+};
+
+static v3s32 face_dir[6] = {
+ {+0, +0, -1},
+ {+0, +0, +1},
+ {-1, +0, +0},
+ {+1, +0, +0},
+ {+0, -1, +0},
+ {+0, +1, +0},
+};
+
+static v3f32 center_offset = {
+ CHUNK_SIZE * 0.5f + 0.5f,
+ CHUNK_SIZE * 0.5f + 0.5f,
+ CHUNK_SIZE * 0.5f + 0.5f,
+};
+
+static GLuint shader_prog;
+static GLint loc_model;
+static GLint loc_VP;
+static GLint loc_daylight;
+static GLint loc_fogColor;
+static GLint loc_ambientLight;
+static GLint loc_lightDir;
+static GLint loc_cameraPos;
+
+static ModelShader model_shader;
+
+static inline bool cull_face(NodeType self, NodeType nbr)
+{
+ switch (client_node_definitions[self].visibility) {
+ case VISIBILITY_CLIP:
+ return false;
+
+ case VISIBILITY_BLEND:
+ return self == nbr;
+
+ case VISIBILITY_SOLID:
+ return nbr == NODE_UNLOADED
+ || client_node_definitions[nbr].visibility == VISIBILITY_SOLID;
+
+ default: // impossible
+ break;
+ }
+
+ // impossible
+ return false;
+}
+
+static inline void render_node(ChunkRenderData *data, v3s32 offset)
+{
+ NodeArgsRender args;
+
+ args.node = &data->chunk->data[offset.x][offset.y][offset.z];
+
+ ClientNodeDefinition *def = &client_node_definitions[args.node->type];
+ if (def->visibility == VISIBILITY_NONE)
+ return;
+
+ v3f32 vertex_offset = v3f32_sub(v3s32_to_f32(offset), center_offset);
+ if (def->render)
+ args.pos = v3s32_add(offset, data->chunkp);
+
+ for (args.f = 0; args.f < 6; args.f++) {
+ v3s32 nbr_offset = v3s32_add(offset, face_dir[args.f]);
+
+ TerrainChunk *nbr_chunk;
+
+ if (nbr_offset.z >= 0 && nbr_offset.z < CHUNK_SIZE &&
+ nbr_offset.x >= 0 && nbr_offset.x < CHUNK_SIZE &&
+ nbr_offset.y >= 0 && nbr_offset.y < CHUNK_SIZE) {
+ nbr_chunk = data->chunk;
+ } else if (!(nbr_chunk = data->nbrs[args.f]) && !data->tried_nbrs[args.f]) {
+ nbr_chunk = data->nbrs[args.f] = terrain_get_chunk(client_terrain,
+ v3s32_add(data->chunk->pos, face_dir[args.f]), false);
+ data->tried_nbrs[args.f] = true;
+ }
+
+ NodeType nbr_node = NODE_UNLOADED;
+ if (nbr_chunk)
+ nbr_node = nbr_chunk->data
+ [(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE]
+ [(nbr_offset.y + CHUNK_SIZE) % CHUNK_SIZE]
+ [(nbr_offset.z + CHUNK_SIZE) % CHUNK_SIZE].type;
+
+ if (cull_face(args.node->type, nbr_node)) {
+ // exception to culling rules: don't cull solid edge nodes, unless cull_edges
+ if (data->cull_edges || nbr_chunk == data->chunk || def->visibility != VISIBILITY_SOLID)
+ continue;
+ } else {
+ data->visible = true;
+ }
+
+ if (def->visibility == VISIBILITY_BLEND)
+ data->transparent = true;
+
+ for (args.v = 0; args.v < 6; args.v++) {
+ args.vertex.cube = cube_vertices[args.f][args.v];
+ args.vertex.cube.position = v3f32_add(args.vertex.cube.position, vertex_offset);
+ args.vertex.color = (v3f32) {1.0f, 1.0f, 1.0f};
+
+ if (def->render)
+ def->render(&args);
+
+ model_batch_add_vertex(data->batch, def->tiles.textures[args.f]->txo, &args.vertex);
+ }
+ }
+}
+
+static void animate_chunk_model(Model *model, f64 dtime)
+{
+ if ((model->root->scale.x += dtime * 2.0f) > 1.0f) {
+ model->root->scale.x = 1.0f;
+ client_terrain_meshgen_task(model->extra);
+ }
+
+ model->root->scale.z
+ = model->root->scale.y
+ = model->root->scale.x;
+
+ model_node_transform(model->root);
+}
+
+static Model *create_chunk_model(TerrainChunk *chunk, bool animate)
+{
+ ChunkRenderData data = {
+ .visible = false,
+ .transparent = false,
+ .batch = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
+ .chunk = chunk,
+ .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
+ .nbrs = {NULL},
+ .tried_nbrs = {false},
+ .cull_edges = !animate,
+ };
+
+ CHUNK_ITERATE
+ render_node(&data, (v3s32) {x, y, z});
+
+ if (!data.batch->textures.siz) {
+ model_batch_free(data.batch);
+ return NULL;
+ }
+
+ Model *model = model_create();
+ model->extra = chunk;
+ model->box = (aabb3f32) {
+ v3f32_sub((v3f32) {-1.0f, -1.0f, -1.0f}, center_offset),
+ v3f32_add((v3f32) {+1.0f, +1.0f, +1.0f}, center_offset)};
+ model->callbacks.step = animate ? &animate_chunk_model : NULL;
+ model->callbacks.delete = &model_free_meshes;
+ model->flags.frustum_culling = 1;
+ model->flags.transparent = data.transparent;
+
+ model->root->visible = data.visible;
+ model->root->pos = v3f32_add(v3s32_to_f32(data.chunkp), center_offset);
+ model->root->scale = (v3f32) {0.0f, 0.0f, 0.0f};
+
+ if (data.visible)
+ model_node_add_batch(model->root, data.batch);
+ else
+ model_batch_free(data.batch);
+
+ return model;
+}
+
+bool terrain_gfx_init()
+{
+ GLint texture_units;
+ glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_units);
+
+ char *shader_defs;
+ asprintf(&shader_defs,
+ "#define MAX_TEXTURE_UNITS %d\n"
+ "#define VIEW_DISTANCE %lf\n",
+ texture_units,
+ client_config.view_distance
+ );
+
+ if (!shader_program_create(RESSOURCE_PATH "shaders/terrain", &shader_prog, shader_defs)) {
+ fprintf(stderr, "[error] failed to create terrain shader program\n");
+ return false;
+ }
+
+ free(shader_defs);
+
+ loc_model = glGetUniformLocation(shader_prog, "model");
+ loc_VP = glGetUniformLocation(shader_prog, "VP");
+ loc_daylight = glGetUniformLocation(shader_prog, "daylight");
+ loc_fogColor = glGetUniformLocation(shader_prog, "fogColor");
+ loc_ambientLight = glGetUniformLocation(shader_prog, "ambientLight");
+ loc_lightDir = glGetUniformLocation(shader_prog, "lightDir");
+ loc_cameraPos = glGetUniformLocation(shader_prog, "cameraPos");
+
+ GLint texture_indices[texture_units];
+ for (GLint i = 0; i < texture_units; i++)
+ texture_indices[i] = i;
+
+ glProgramUniform1iv(shader_prog, glGetUniformLocation(shader_prog, "textures"), texture_units, texture_indices);
+
+ model_shader.prog = shader_prog;
+ model_shader.loc_transform = loc_model;
+
+ return true;
+}
+
+void terrain_gfx_deinit()
+{
+ glDeleteProgram(shader_prog);
+}
+
+void terrain_gfx_update()
+{
+ vec4 base_sunlight_dir = {0.0f, 0.0f, -1.0f, 1.0f};
+ vec4 sunlight_dir;
+ mat4x4 sunlight_mat;
+ mat4x4_identity(sunlight_mat);
+
+ mat4x4_rotate(sunlight_mat, sunlight_mat, 1.0f, 0.0f, 0.0f, get_sun_angle() + M_PI / 2.0f);
+ mat4x4_mul_vec4(sunlight_dir, sunlight_mat, base_sunlight_dir);
+
+ f32 daylight = get_daylight();
+ f32 ambient_light = f32_mix(0.3f, 0.7f, daylight);
+ v3f32 fog_color = v3f32_mix((v3f32) {0x03, 0x0A, 0x1A}, (v3f32) {0x87, 0xCE, 0xEB}, daylight);
+
+ glProgramUniformMatrix4fv(shader_prog, loc_VP, 1, GL_FALSE, frustum[0]);
+ glProgramUniform3f(shader_prog, loc_lightDir, sunlight_dir[0], sunlight_dir[1], sunlight_dir[2]);
+ glProgramUniform3f(shader_prog, loc_cameraPos, camera.eye[0], camera.eye[1], camera.eye[2]);
+ glProgramUniform1f(shader_prog, loc_daylight, daylight);
+ glProgramUniform3f(shader_prog, loc_fogColor, fog_color.x / 0xFF * ambient_light, fog_color.y / 0xFF * ambient_light, fog_color.z / 0xFF * ambient_light);
+ glProgramUniform1f(shader_prog, loc_ambientLight, ambient_light);
+}
+
+void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
+{
+ TerrainChunkMeta *meta = chunk->extra;
+
+ bool animate = true;
+
+ pthread_mutex_lock(&chunk->mtx);
+ if (meta->model && meta->model->root->scale.x == 1.0f)
+ animate = false;
+ pthread_mutex_unlock(&chunk->mtx);
+
+ Model *model = create_chunk_model(chunk, animate);
+
+ pthread_mutex_lock(&chunk->mtx);
+
+ if (meta->model) {
+ if (model) {
+ model->callbacks.step = meta->model->callbacks.step;
+ model->root->scale = meta->model->root->scale;
+ model_node_transform(model->root);
+ }
+
+ meta->model->flags.delete = 1;
+ }
+
+ meta->model = model;
+
+ if (model)
+ model_scene_add(model);
+
+ pthread_mutex_unlock(&chunk->mtx);
+}
--- /dev/null
+#ifndef _TERRAIN_GFX_H_
+#define _TERRAIN_GFX_H_
+
+#include "client/cube.h"
+#include "terrain.h"
+
+typedef struct {
+ CubeVertex cube;
+ f32 textureIndex;
+ v3f32 color;
+} __attribute__((packed)) TerrainVertex;
+
+bool terrain_gfx_init();
+void terrain_gfx_deinit();
+void terrain_gfx_update();
+void terrain_gfx_make_chunk_model(TerrainChunk *chunk);
+
+#endif
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#include <stdbool.h>
-#include <dragonstd/list.h>
+#include <dragonstd/tree.h>
#include "client/client_config.h"
#include "client/texture.h"
-#include "util.h"
-static List textures;
+static Tree textures;
-__attribute((constructor(101))) static void textures_init()
+typedef struct {
+ char *path;
+ Texture texture;
+} TextureLookup;
+
+static int cmp_texture(TextureLookup *texture, char *path)
{
- textures = list_create(&list_compare_string);
+ return strcmp(texture->path, path);
}
-static void list_delete_texture(unused void *key, void *value, unused void *arg)
+static bool lookup_texture(char *path, Texture **texture)
{
- texture_delete(value);
+ TreeNode **node = tree_nfd(&textures, path, &cmp_texture);
+
+ if (*node) {
+ *texture = &((TextureLookup *) &(*node)->dat)->texture;
+ return true;
+ }
+
+ TextureLookup *lookup = malloc(sizeof *lookup);
+ lookup->path = strdup(path);
+ *texture = &lookup->texture;
+
+ tree_nmk(&textures, node, lookup);
+ return false;
}
-__attribute((destructor)) static void textures_deinit()
+static void delete_texture(TextureLookup *lookup)
{
- list_clear_func(&textures, &list_delete_texture, NULL);
+ free(lookup->path);
+ texture_destroy(&lookup->texture);
+ free(lookup);
}
-Texture *texture_create(unsigned char *data, int width, int height, GLenum format, bool mipmap)
+__attribute__((constructor(101))) static void textures_init()
{
- Texture *texture = malloc(sizeof(Texture));
- texture->width = width;
- texture->height = height;
-
- glGenTextures(1, &texture->id);
-
- glBindTexture(GL_TEXTURE_2D, texture->id);
+ tree_ini(&textures);
+}
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmap && client_config.mipmap) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+__attribute__((destructor)) static void textures_deinit()
+{
+ tree_clr(&textures, &delete_texture, NULL, NULL, 0);
+}
- glTexImage2D(GL_TEXTURE_2D, 0, format, texture->width, texture->height, 0, format, GL_UNSIGNED_BYTE, data);
- glGenerateMipmap(GL_TEXTURE_2D);
+Texture *texture_load(char *path, bool mipmap)
+{
+ Texture *texture;
+ if (lookup_texture(path, &texture))
+ return texture;
+
+ unsigned char *data = stbi_load(path,
+ &texture->width, &texture->height, &texture->channels, 0);
+ if (!data) {
+ fprintf(stderr, "[error] failed to load texture %s\n", path);
+ exit(EXIT_FAILURE);
+ }
- glBindTexture(GL_TEXTURE_2D, 0);
+ texture_upload(texture, data, GL_RGBA, mipmap);
+ stbi_image_free(data);
return texture;
}
-GLuint texture_create_cubemap(char *path)
+Texture *texture_load_cubemap(char *path)
{
- GLuint id;
- glGenTextures(1, &id);
+ Texture *texture;
+ if (lookup_texture(path, &texture))
+ return texture;
- glBindTexture(GL_TEXTURE_CUBE_MAP, id);
+ glGenTextures(1, &texture->txo);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, texture->txo);
const char *directions[6] = {
"right",
char filename[strlen(path) + 1 + strlen(directions[i]) + 1 + 3 + 1];
sprintf(filename, "%s/%s.png", path, directions[i]);
- int width, height, channels;
- unsigned char *data = stbi_load(filename, &width, &height, &channels, 0);
- if (! data) {
- fprintf(stderr, "Failed to load texture %s\n", filename);
- return 0;
+ unsigned char *data = stbi_load(filename,
+ &texture->width, &texture->height, &texture->channels, 0);
+ if (!data) {
+ fprintf(stderr, "[error] failed to load texture %s\n", filename);
+ exit(EXIT_FAILURE);
}
- glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA,
+ texture->width, texture->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
- return id;
+ return texture;
}
-void texture_delete(Texture *texture)
+void texture_upload(Texture *texture, unsigned char *data, GLenum format, bool mipmap)
{
- glDeleteTextures(1, &texture->id);
- free(texture);
-}
-
-Texture *texture_load(char *path, bool mipmap)
-{
- int width, height, channels;
-
- unsigned char *data = stbi_load(path, &width, &height, &channels, 0);
- if (! data) {
- fprintf(stderr, "Failed to load texture %s\n", path);
- return NULL;
- }
-
- Texture *texture = texture_create(data, width, height, GL_RGBA, mipmap);
+ glGenTextures(1, &texture->txo);
+ glBindTexture(GL_TEXTURE_2D, texture->txo);
- stbi_image_free(data);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmap && client_config.mipmap)
+ ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- list_put(&textures, texture, NULL);
+ glTexImage2D(GL_TEXTURE_2D, 0, format,
+ texture->width, texture->height, 0, format, GL_UNSIGNED_BYTE, data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+}
- return texture;
+void texture_destroy(Texture *texture)
+{
+ glDeleteTextures(1, &texture->txo);
}
#include <GL/glew.h>
#include <GL/gl.h>
-typedef struct
-{
- GLuint id;
- int width, height;
+typedef struct {
+ GLuint txo;
+ int width, height, channels;
} Texture;
-Texture *texture_create(unsigned char *data, int width, int height, GLenum format, bool mipmap);
-GLuint texture_create_cubemap(char *path);
-void texture_delete(Texture *texture);
Texture *texture_load(char *path, bool mipmap);
+Texture *texture_load_cubemap(char *path);
+void texture_upload(Texture *texture, unsigned char *data, GLenum format, bool mipmap);
+void texture_destroy(Texture *texture);
-#endif
+#endif // _TEXTURE_H_
+++ /dev/null
-#include "client/vertex.h"
-
-void vertex_layout_configure(VertexLayout *layout)
-{
- size_t offset = 0;
-
- for (GLuint i = 0; i < layout->count; i++) {
- VertexAttribute *attrib = &layout->attributes[i];
-
- glVertexAttribPointer(i, attrib->length, attrib->type, GL_FALSE, layout->size, (GLvoid *) offset);
- glEnableVertexAttribArray(i);
-
- offset += attrib->size;
- }
-}
+++ /dev/null
-#ifndef _VERTEX_H_
-#define _VERTEX_H_
-
-#include <GL/glew.h>
-#include <GL/gl.h>
-
-typedef struct
-{
- GLenum type;
- GLsizei length;
- GLsizei size;
-} VertexAttribute;
-
-typedef struct
-{
- VertexAttribute *attributes;
- GLuint count;
- GLsizei size;
-} VertexLayout;
-
-void vertex_layout_configure(VertexLayout *layout);
-
-#endif
#include "client/game.h"
#include "client/gui.h"
#include "client/input.h"
-#include "client/scene.h"
#include "client/window.h"
-#include "util.h"
struct Window window;
-static void framebuffer_size_callback(unused GLFWwindow *handle, int width, int height)
+static int small_x, small_y, small_width, small_height;
+
+static void update_projection()
+{
+ mat4x4_perspective(window.projection,
+ window.fov / 180.0f * M_PI,
+ (float) window.width / (float) window.height,
+ 0.01f, client_config.view_distance + 28.0f);
+}
+
+static void framebuffer_size_callback(__attribute__((unused)) GLFWwindow *handle, int width, int height)
{
glViewport(0, 0, width, height);
+ window.width = width;
+ window.height = height;
- if (! window.fullscreen) {
- window.small_bounds.width = width;
- window.small_bounds.height = height;
+ if (!window.fullscreen) {
+ small_width = width;
+ small_height = height;
}
- scene_on_resize(width, height);
- gui_on_resize(width, height);
- game_on_resize(width, height);
+ update_projection();
+ gui_update_projection();
}
-static void cursor_pos_callback(unused GLFWwindow *handle, double current_x, double current_y)
+static void cursor_pos_callback(__attribute__((unused)) GLFWwindow *handle, double x, double y)
{
- input_on_cursor_pos(current_x, current_y);
+ input_cursor(x, y);
}
-static void window_pos_callback(unused GLFWwindow *handle, int x, int y)
+static void window_pos_callback(__attribute__((unused)) GLFWwindow *handle, int x, int y)
{
- if (! window.fullscreen) {
- window.small_bounds.x = x;
- window.small_bounds.y = y;
+ if (!window.fullscreen) {
+ small_x = x;
+ small_y = y;
}
}
const GLFWvidmode *vidmode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(window.handle, monitor, 0, 0, vidmode->width, vidmode->height, 0);
- debug_menu_update_fullscreen();
+ debug_menu_changed(ENTRY_FULLSCREEN);
}
void window_exit_fullscreen()
{
window.fullscreen = false;
- glfwSetWindowMonitor(window.handle, NULL, window.small_bounds.x, window.small_bounds.y, window.small_bounds.width, window.small_bounds.height, 0);
+ glfwSetWindowMonitor(window.handle, NULL, small_x, small_y, small_width, small_height, 0);
- debug_menu_update_fullscreen();
+ debug_menu_changed(ENTRY_FULLSCREEN);
}
-bool window_init(int width, int height)
+bool window_init()
{
- if(! glfwInit()) {
- fprintf(stderr, "Failed to initialize GLFW\n");
+ if(!glfwInit()) {
+ fprintf(stderr, "[error] failed to initialize GLFW\n");
return false;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
- window.handle = glfwCreateWindow(width, height, "Dragonblocks", NULL, NULL);
+ window.width = 1250;
+ window.height = 750;
+ window.handle = glfwCreateWindow(window.width, window.height, "Dragonblocks", NULL, NULL);
+ window.fullscreen = false;
+ window.fov = 86.1f;
+ update_projection();
- window.small_bounds.width = width;
- window.small_bounds.height = height;
+ small_width = window.width;
+ small_height = window.height;
- if (! window.handle) {
- fprintf(stderr, "Failed to create window\n");
+ if (!window.handle) {
+ fprintf(stderr, "[error] failed to create window\n");
glfwTerminate();
return false;
}
glfwMakeContextCurrent(window.handle);
- if (! client_config.vsync)
+ if (!client_config.vsync)
glfwSwapInterval(0);
if (glewInit() != GLEW_OK) {
- fprintf(stderr, "Failed to initialize GLEW\n");
+ fprintf(stderr, "[error] failed to initialize GLEW\n");
return false;
}
#include <GLFW/glfw3.h>
-extern struct Window
-{
+extern struct Window {
+ int width, height;
GLFWwindow *handle;
bool fullscreen;
- struct
- {
- int x, y;
- int width, height;
- } small_bounds;
+ f32 fov;
+ mat4x4 projection;
} window;
-bool window_init(int width, int height);
+bool window_init();
void window_enter_fullscreen();
void window_exit_fullscreen();
-#endif
+#endif // _WINDOW_H_
--- /dev/null
+#include "color.h"
+
+static f32 hue_to_rgb(f32 p, f32 q, f32 t)
+{
+ if (t < 0.0f)
+ t += 1.0f;
+
+ if (t > 1.0f)
+ t -= 1.0f;
+
+ if (t < 1.0f / 6.0f)
+ return p + (q - p) * 6.0f * t;
+
+ if (t < 1.0f / 2.0f)
+ return q;
+
+ if (t < 2.0f / 3.0f)
+ return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
+
+ return p;
+}
+
+v3f32 hsl_to_rgb(v3f32 hsl)
+{
+ v3f32 rgb;
+
+ if (hsl.y == 0.0f) {
+ rgb = (v3f32) {hsl.z, hsl.z, hsl.z};
+ } else {
+ f32 q = hsl.z < 0.5f ? hsl.z * (1.0f + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y;
+ f32 p = 2.0f * hsl.z - q;
+
+ rgb.x = hue_to_rgb(p, q, hsl.x + 1.0f / 3.0f);
+ rgb.y = hue_to_rgb(p, q, hsl.x);
+ rgb.z = hue_to_rgb(p, q, hsl.x - 1.0f / 3.0f);
+ }
+
+ return rgb;
+}
+
--- /dev/null
+#ifndef _COLOR_H_
+#define _COLOR_H_
+
+#include "types.h"
+
+v3f32 hsl_to_rgb(v3f32 hsl);
+
+#endif // _COLOR_H_
{
FILE *f = fopen(path, "r");
- if (! f)
+ if (!f)
return;
- printf("Reading config from %s\n", path);
+ printf("[info] reading config from %s\n", path);
- while (! feof(f)) {
+ while (!feof(f)) {
char key[BUFSIZ];
- if (! fscanf(f, "%s", key))
+ if (!fscanf(f, "%s", key))
break;
bool found = false;
bool valid = false;
switch (entry->type) {
- case CT_STRING: {
+ case CONFIG_STRING: {
char value[BUFSIZ];
- if (! fscanf(f, "%s", value))
+ if (!fscanf(f, "%s", value))
break;
valid = true;
- *(char **) entry->value = strdup(value);
- } break;
- case CT_INT:
+ char **entry_value = entry->value;
+
+ if (*entry_value)
+ free(*entry_value);
+ *entry_value = strdup(value);
+
+ break;
+ }
+
+ case CONFIG_INT:
if (fscanf(f, "%d", (int *) entry->value))
valid = true;
break;
- case CT_UINT:
+ case CONFIG_UINT:
if (fscanf(f, "%u", (unsigned int *) entry->value))
valid = true;
break;
- case CT_FLOAT:
+ case CONFIG_FLOAT:
if (fscanf(f, "%lf", (double *) entry->value))
valid = true;
break;
- case CT_BOOL: {
+ case CONFIG_BOOL: {
char value[BUFSIZ];
- if (! fscanf(f, "%s", value))
+ if (!fscanf(f, "%s", value))
break;
valid = true;
else
valid = false;
- } break;
+ break;
+ }
}
- if (! valid)
- fprintf(stderr, "Invalid value for setting %s in %s\n", key, path);
+ if (!valid)
+ fprintf(stderr, "[warning] invalid value for setting %s in %s\n", key, path);
found = true;
break;
}
}
- if (! found)
- fprintf(stderr, "Unknown setting %s in %s\n", key, path);
+ if (!found)
+ fprintf(stderr, "[warning] unknown setting %s in %s\n", key, path);
}
fclose(f);
}
+
+void config_free(ConfigEntry *entries, size_t num_entries)
+{
+ for (size_t i = 0; i < num_entries; i++) {
+ ConfigEntry *entry = &entries[i];
+
+ if (entry->type == CONFIG_STRING) {
+ char **entry_value = entry->value;
+
+ if (*entry_value)
+ free(*entry_value);
+ }
+ }
+}
#include <stddef.h>
-typedef enum
-{
- CT_STRING,
- CT_INT,
- CT_UINT,
- CT_FLOAT,
- CT_BOOL,
+typedef enum {
+ CONFIG_STRING,
+ CONFIG_INT,
+ CONFIG_UINT,
+ CONFIG_FLOAT,
+ CONFIG_BOOL,
} ConfigType;
-typedef struct
-{
+typedef struct {
ConfigType type;
char *key;
void *value;
} ConfigEntry;
void config_read(char *path, ConfigEntry *entries, size_t num_entries);
+void config_free(ConfigEntry *entries, size_t num_entires);
-#endif
+#endif // _CONFIG_H_
#include <math.h>
#include <time.h>
#include "day.h"
-#include "util.h"
bool timelapse = false;
static f64 time_of_day_offset;
#include <stdbool.h>
#include "types.h"
+
#define MINUTES_PER_HOUR 60
#define HOURS_PER_DAY 24
#define MINUTES_PER_DAY (HOURS_PER_DAY * MINUTES_PER_HOUR)
extern bool timelapse;
-#endif
+#endif // _DAY_H_
-#! /bin/bash
+#!/bin/bash
+
if ! make -j$(nproc); then
exit 1
fi
run \"[::1]:4000\"
" > $DEBUG_DIR/server_script
-kitty --detach -e bash -c "
+alacritty -e bash -c "
echo \$\$ > $DEBUG_DIR/server_pid
- exec gdb --command $DEBUG_DIR/server_script ./DragonblocksServer
-"
+ exec gdb --command $DEBUG_DIR/server_script ./dragonblocks_server
+" &
sleep 0.5
-gdb --command $DEBUG_DIR/client_script ./Dragonblocks
+gdb --command $DEBUG_DIR/client_script ./dragonblocks
kill `cat $DEBUG_DIR/server_pid`
-#! /bin/bash
+#!/bin/bash
COMMAND="./debug.sh"
-if [[ $1 == "mapgen" ]]; then
- COMMAND="./debug_mapgen.sh"
+if [[ $1 == "terrain" ]]; then
+ COMMAND="./debug_terrain.sh"
fi
while true; do
+++ /dev/null
-#! /bin/bash
-rm -f map.sqlite
-./debug.sh
--- /dev/null
+#!/bin/bash
+rm -f *.sqlite
+./debug.sh
--- /dev/null
+#ifndef _ENTITY_H_
+#define _ENTITY_H_
+
+// ET phone home
+typedef enum {
+ ENTITY_LOCALPLAYER,
+ ENTITY_PLAYER,
+ COUNT_ENTITY,
+} EntityType;
+
+#endif // _ENTITY_H_
#include "environment.h"
#include "perlin.h"
-#include "util.h"
f64 get_humidity(v3s32 pos)
{
f64 get_humidity(v3s32 pos);
f64 get_temperature(v3s32 pos);
-#endif
+#endif // _ENVIRONMENT_H_
#include <string.h>
#include "interrupt.h"
-Flag *interrupt;
+Flag interrupt;
static struct sigaction sa = {0};
static void interrupt_handler(int sig)
{
fprintf(stderr, "%s\n", strsignal(sig));
- flag_set(interrupt);
+ flag_set(&interrupt);
}
void interrupt_init()
{
- interrupt = flag_create();
+ flag_ini(&interrupt);
sa.sa_handler = &interrupt_handler;
sigaction(SIGINT, &sa, NULL);
void interrupt_deinit()
{
- flag_delete(interrupt);
+ flag_dst(&interrupt);
}
#include <dragonstd/flag.h>
-extern Flag *interrupt;
-
void interrupt_init();
void interrupt_deinit();
-#endif
+extern Flag interrupt;
+
+#endif // _INTERRUPT_H_
+++ /dev/null
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <math.h>
-#include <string.h>
-#include "map.h"
-#include "util.h"
-
-Map *map_create(MapCallbacks callbacks)
-{
- Map *map = malloc(sizeof(Map));
- pthread_rwlock_init(&map->rwlck, NULL);
- pthread_rwlock_init(&map->cached_rwlck, NULL);
- map->sectors = bintree_create(sizeof(v2s32), NULL);
- map->cached = NULL;
- map->callbacks = callbacks;
- return map;
-}
-
-static void free_block(BintreeNode *node, void *arg)
-{
- Map *map = arg;
-
- if (map->callbacks.delete_block)
- map->callbacks.delete_block(node->value);
-
- map_free_block(node->value);
-}
-
-static void free_sector(BintreeNode *node, void *arg)
-{
- MapSector *sector = node->value;
-
- bintree_clear(§or->blocks, &free_block, arg);
- pthread_rwlock_destroy(§or->rwlck);
- free(sector);
-}
-
-void map_delete(Map *map)
-{
- pthread_rwlock_destroy(&map->rwlck);
- pthread_rwlock_destroy(&map->cached_rwlck);
- bintree_clear(&map->sectors, &free_sector, map);
- free(map);
-}
-
-MapSector *map_get_sector(Map *map, v2s32 pos, bool create)
-{
- if (create)
- pthread_rwlock_wrlock(&map->rwlck);
- else
- pthread_rwlock_rdlock(&map->rwlck);
-
- BintreeNode **nodeptr = bintree_search(&map->sectors, &pos);
-
- MapSector *sector = NULL;
-
- if (*nodeptr) {
- sector = (*nodeptr)->value;
- } else if (create) {
- sector = malloc(sizeof(MapSector));
- pthread_rwlock_init(§or->rwlck, NULL);
- sector->pos = pos;
- sector->blocks = bintree_create(sizeof(s32), NULL);
-
- bintree_add_node(&map->sectors, nodeptr, &pos, sector);
- }
-
- pthread_rwlock_unlock(&map->rwlck);
-
- return sector;
-}
-
-MapBlock *map_get_block(Map *map, v3s32 pos, bool create)
-{
- MapBlock *cached = NULL;
-
- pthread_rwlock_rdlock(&map->cached_rwlck);
- cached = map->cached;
- pthread_rwlock_unlock(&map->cached_rwlck);
-
- if (cached && v3s32_equals(cached->pos, pos))
- return cached;
-
- MapSector *sector = map_get_sector(map, (v2s32) {pos.x, pos.z}, create);
- if (! sector)
- return NULL;
-
- if (create)
- pthread_rwlock_wrlock(§or->rwlck);
- else
- pthread_rwlock_rdlock(§or->rwlck);
-
- BintreeNode **nodeptr = bintree_search(§or->blocks, &pos.y);
-
- MapBlock *block = NULL;
-
- if (*nodeptr) {
- block = (*nodeptr)->value;
-
- pthread_mutex_lock(&block->mtx);
- if (map->callbacks.get_block && ! map->callbacks.get_block(block, create)) {
- pthread_mutex_unlock(&block->mtx);
- block = NULL;
- } else {
- pthread_mutex_unlock(&block->mtx);
- pthread_rwlock_wrlock(&map->cached_rwlck);
- map->cached = block;
- pthread_rwlock_unlock(&map->cached_rwlck);
- }
- } else if (create) {
- bintree_add_node(§or->blocks, nodeptr, &pos.y, block = map_allocate_block(pos));
-
- if (map->callbacks.create_block)
- map->callbacks.create_block(block);
- }
-
- pthread_rwlock_unlock(§or->rwlck);
-
- return block;
-}
-
-MapBlock *map_allocate_block(v3s32 pos)
-{
- MapBlock *block = malloc(sizeof(MapBlock));
- block->pos = pos;
- block->extra = NULL;
- pthread_mutexattr_t attr;
- pthread_mutexattr_init(&attr);
- pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- pthread_mutex_init(&block->mtx, &attr);
-
- ITERATE_MAPBLOCK block->data[x][y][z] = map_node_create(NODE_UNKNOWN, (Blob) {0, NULL});
-
- return block;
-}
-
-void map_free_block(MapBlock *block)
-{
- ITERATE_MAPBLOCK map_node_delete(block->data[x][y][z]);
-
- pthread_mutex_destroy(&block->mtx);
- free(block);
-}
-
-Blob map_serialize_block(MapBlock *block)
-{
- bool all_air = true;
-
- ITERATE_MAPBLOCK {
- if (block->data[x][y][z].type != NODE_AIR) {
- all_air = false;
- break;
- }
- }
-
- if (all_air)
- return (Blob) {0, NULL};
-
- SerializedMapBlock block_data;
-
- ITERATE_MAPBLOCK {
- MapNode *node = &block->data[x][y][z];
- SerializedMapNode *node_data = &block_data.raw.nodes[x][y][z];
-
- *node_data = (SerializedMapNode) {
- .type = node->type,
- .data = {
- .siz = 0,
- .data = NULL,
- },
- };
-
- NodeDefinition *def = &node_definitions[node->type];
-
- if (def->serialize)
- def->serialize(&node_data->data, node->data);
- }
-
- Blob buffer = {0, NULL};
- SerializedMapBlock_write(&buffer, &block_data);
- SerializedMapBlock_free(&block_data);
-
- return buffer;
-}
-
-bool map_deserialize_block(MapBlock *block, Blob buffer)
-{
- if (buffer.siz == 0) {
- ITERATE_MAPBLOCK
- block->data[x][y][z] = map_node_create(NODE_AIR, (Blob) {0, NULL});
-
- return true;
- }
-
- // it's important to copy Blobs that have been malloc'd before reading from them
- // because reading from a Blob modifies its data and size pointer,
- // but does not free anything
- SerializedMapBlock block_data = {0};
- bool success = SerializedMapBlock_read(&buffer, &block_data);
-
- if (success) ITERATE_MAPBLOCK
- block->data[x][y][z] = map_node_create(block_data.raw.nodes[x][y][z].type, block_data.raw.nodes[x][y][z].data);
-
- SerializedMapBlock_free(&block_data);
- return success;
-}
-
-v3s32 map_node_to_block_pos(v3s32 pos, v3u8 *offset)
-{
- if (offset)
- *offset = (v3u8) {(u32) pos.x % MAPBLOCK_SIZE, (u32) pos.y % MAPBLOCK_SIZE, (u32) pos.z % MAPBLOCK_SIZE};
- return (v3s32) {floor((double) pos.x / (double) MAPBLOCK_SIZE), floor((double) pos.y / (double) MAPBLOCK_SIZE), floor((double) pos.z / (double) MAPBLOCK_SIZE)};
-}
-
-MapNode map_get_node(Map *map, v3s32 pos)
-{
- v3u8 offset;
- v3s32 blockpos = map_node_to_block_pos(pos, &offset);
- MapBlock *block = map_get_block(map, blockpos, false);
- if (! block)
- return map_node_create(NODE_UNLOADED, (Blob) {0, NULL});
- return block->data[offset.x][offset.y][offset.z];
-}
-
-void map_set_node(Map *map, v3s32 pos, MapNode node, bool create, void *arg)
-{
- v3u8 offset;
- MapBlock *block = map_get_block(map, map_node_to_block_pos(pos, &offset), create);
- if (block) {
- pthread_mutex_lock(&block->mtx);
- if (! map->callbacks.set_node || map->callbacks.set_node(block, offset, &node, arg)) {
- block->data[offset.x][offset.y][offset.z] = node;
- if (map->callbacks.after_set_node)
- map->callbacks.after_set_node(block, offset, arg);
- } else {
- map_node_delete(node);
- }
- pthread_mutex_unlock(&block->mtx);
- }
-}
-
-MapNode map_node_create(Node type, Blob buffer)
-{
- if (type >= NODE_UNLOADED)
- type = NODE_UNKNOWN;
-
- NodeDefinition *def = &node_definitions[type];
-
- MapNode node;
- node.type = type;
- node.data = def->data_size ? malloc(def->data_size) : NULL;
-
- if (def->create)
- def->create(&node);
-
- if (def->deserialize)
- def->deserialize(&buffer, node.data);
-
- return node;
-}
-
-void map_node_delete(MapNode node)
-{
- NodeDefinition *def = &node_definitions[node.type];
-
- if (def->delete)
- def->delete(&node);
-
- if (node.data)
- free(node.data);
-}
+++ /dev/null
-#ifndef _MAP_H_
-#define _MAP_H_
-
-#include <stdbool.h>
-#include <pthread.h>
-#include <dragonstd/bintree.h>
-#include <dragonstd/list.h>
-#include "types.h"
-#include "node.h"
-
-#define ITERATE_MAPBLOCK for (u8 x = 0; x < MAPBLOCK_SIZE; x++) for (u8 y = 0; y < MAPBLOCK_SIZE; y++) for (u8 z = 0; z < MAPBLOCK_SIZE; z++)
-
-typedef struct MapNode
-{
- Node type;
- void *data;
-} MapNode;
-
-typedef MapNode MapBlockData[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE];
-
-typedef struct
-{
- MapBlockData data;
- v3s32 pos;
- pthread_mutex_t mtx;
- void *extra;
-} MapBlock;
-
-typedef struct
-{
- pthread_rwlock_t rwlck;
- Bintree blocks;
- v2s32 pos;
-} MapSector;
-
-typedef struct
-{
- void (*create_block)(MapBlock *block);
- void (*delete_block)(MapBlock *block);
- bool (*get_block)(MapBlock *block, bool create);
- bool (*set_node) (MapBlock *block, v3u8 offset, MapNode *node, void *arg);
- void (*after_set_node)(MapBlock *block, v3u8 offset, void *arg);
-} MapCallbacks;
-
-typedef struct
-{
- pthread_rwlock_t rwlck;
- Bintree sectors;
- pthread_rwlock_t cached_rwlck;
- MapBlock *cached;
- MapCallbacks callbacks;
-} Map;
-
-Map *map_create(MapCallbacks callbacks);
-void map_delete(Map *map);
-
-MapSector *map_get_sector(Map *map, v2s32 pos, bool create);
-MapBlock *map_get_block(Map *map, v3s32 pos, bool create);
-
-MapBlock *map_allocate_block(v3s32 pos);
-void map_free_block(MapBlock *block);
-
-Blob map_serialize_block(MapBlock *block);
-bool map_deserialize_block(MapBlock *block, Blob buffer);
-
-v3s32 map_node_to_block_pos(v3s32 pos, v3u8 *offset);
-
-MapNode map_get_node(Map *map, v3s32 pos);
-void map_set_node(Map *map, v3s32 pos, MapNode node, bool create, void *arg);
-
-MapNode map_node_create(Node type, Blob buffer);
-void map_node_delete(MapNode node);
-
-#endif
-#! /bin/sh
-LUA_PATH="../deps/dragontype/?.lua;../deps/dragontype/?/init.lua" "../deps/dragontype/typegen.lua"
+#!/bin/sh
+LUA_PATH="../deps/protogen/?.lua;../deps/protogen/?/init.lua" "../deps/protogen/protogen.lua"
-#include "types.h"
-#include "map.h"
#include "node.h"
-#include "util.h"
-#include <stdio.h>
+#include "terrain.h"
+#include "types.h"
NodeDefinition node_definitions[NODE_UNLOADED] = {
// unknown
// oak wood
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// oak leaves
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// pine wood
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// pine leaves
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// palm wood
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// palm leaves
{
.solid = true,
- .data_size = sizeof(HSLData),
+ .data_size = sizeof(ColorData),
.create = NULL,
.delete = NULL,
- .serialize = (void *) &HSLData_write,
- .deserialize = (void *) &HSLData_read,
+ .serialize = (void *) &ColorData_write,
+ .deserialize = (void *) &ColorData_read,
},
// sand
{
#define _NODE_H_
#include <stdbool.h>
+#include <stddef.h>
#include "types.h"
#define NODE_DEFINITION(type) ((type) < NODE_UNLOADED ? &node_definitions[NODE_UNKNOWN] : &node_definitions[(type)]);
-typedef enum
-{
+typedef enum {
NODE_UNKNOWN, // Used for unknown nodes received from server (caused by outdated clients)
NODE_AIR,
NODE_GRASS,
NODE_WATER,
NODE_LAVA,
NODE_VULCANO_STONE,
- NODE_UNLOADED, // Used for nodes in unloaded blocks
-} Node;
+ NODE_UNLOADED, // Used for nodes in unloaded chunks
+} NodeType;
-struct MapNode;
+struct TerrainNode;
-typedef struct
-{
+typedef struct {
bool solid;
size_t data_size;
- void (*create)(struct MapNode *node);
- void (*delete)(struct MapNode *node);
+ void (*create)(struct TerrainNode *node);
+ void (*delete)(struct TerrainNode *node);
void (*serialize)(Blob *buffer, void *data);
void (*deserialize)(Blob *buffer, void *data);
} NodeDefinition;
#include <perlin/perlin.h>
#include "types.h"
-typedef enum
-{
+#define U32(x) (((u32) 1 << 31) + (x))
+
+typedef enum {
SO_NONE,
SO_HEIGHT,
SO_MOUNTAIN,
extern s32 seed;
-#endif
+#endif // _PERLIN_H_
--- /dev/null
+#include <math.h>
+#include "physics.h"
+
+static aabb3f64 move_box(aabb3f32 box, v3f64 pos)
+{
+ return (aabb3f64) {
+ {pos.x + box.min.x, pos.y + box.min.y, pos.z + box.min.z},
+ {pos.x + box.max.x, pos.y + box.max.y, pos.z + box.max.z},
+ };
+}
+
+static aabb3s32 round_box(aabb3f64 box)
+{
+ return (aabb3s32) {
+ {floor(box.min.x + 0.5), floor(box.min.y + 0.5), floor(box.min.z + 0.5)},
+ { ceil(box.max.x - 0.5), ceil(box.max.y - 0.5), ceil(box.max.z - 0.5)},
+ };
+}
+
+static bool is_solid(Terrain *terrain, s32 x, s32 y, s32 z)
+{
+ NodeType node = terrain_get_node(terrain, (v3s32) {x, y, z}).type;
+ return node == NODE_UNLOADED || node_definitions[node].solid;
+}
+
+bool physics_ground(Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel)
+{
+ if (!collide)
+ return false;
+
+ if (vel->y != 0.0)
+ return false;
+
+ aabb3f64 mbox = move_box(box, *pos);
+ mbox.min.y -= 0.5;
+
+ aabb3s32 rbox = round_box(mbox);
+
+ if (mbox.min.y - (f64) rbox.min.y > 0.01)
+ return false;
+
+ for (s32 x = rbox.min.x; x <= rbox.max.x; x++)
+ for (s32 z = rbox.min.z; z <= rbox.max.z; z++)
+ if (is_solid(terrain, x, rbox.min.y, z))
+ return true;
+
+ return false;
+}
+
+bool physics_step(Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel, v3f64 *acc, f64 t)
+{
+ v3f64 old_pos = *pos;
+
+ f64 *x = &pos->x;
+ f64 *v = &vel->x;
+ f64 *a = &acc->x;
+
+ f32 *min = &box.min.x;
+ f32 *max = &box.max.x;
+
+ static u8 idx[3][3] = {
+ {0, 1, 2},
+ {1, 0, 2},
+ {2, 0, 1},
+ };
+
+ for (u8 i = 0; i < 3; i++) {
+ f64 v_old = v[i];
+ v[i] += a[i] * t;
+ f64 v_cur = (v[i] + v_old) / 2.0;
+ if (v_cur == 0.0)
+ continue;
+
+ f64 x_old = x[i];
+ x[i] += v_cur * t;
+ if (!collide)
+ continue;
+
+ aabb3s32 box_rnd = round_box(move_box(box, *pos));
+
+ s32 dir;
+ f32 off;
+ s32 *min_rnd = &box_rnd.min.x;
+ s32 *max_rnd = &box_rnd.max.x;
+
+ if (v[i] > 0.0) {
+ dir = +1;
+ off = max[i];
+
+ min_rnd[i] = ceil(x_old + off + 0.5);
+ max_rnd[i] = floor(x[i] + off + 0.5);
+ } else {
+ dir = -1;
+ off = min[i];
+
+ min_rnd[i] = floor(x_old + off - 0.5);
+ max_rnd[i] = ceil(x[i] + off - 0.5);
+ }
+
+ max_rnd[i] += dir;
+
+ u8 i_a = idx[i][0]; // = i
+ u8 i_b = idx[i][1];
+ u8 i_c = idx[i][2];
+
+ for (s32 a = min_rnd[i_a]; a != max_rnd[i_a]; a += dir)
+ for (s32 b = min_rnd[i_b]; b <= max_rnd[i_b]; b++)
+ for (s32 c = min_rnd[i_c]; c <= max_rnd[i_c]; c++) {
+ s32 p[3];
+ p[i_a] = a;
+ p[i_b] = b;
+ p[i_c] = c;
+
+ if (is_solid(terrain, p[0], p[1], p[2])) {
+ x[i] = (f64) a - off - 0.5 * (f64) dir;
+ v[i] = 0.0;
+ goto done;
+ }
+ }
+
+ done: continue;
+ }
+
+ return !v3f64_equals(*pos, old_pos);
+}
--- /dev/null
+#ifndef _PHYSICS_H_
+#define _PHYSICS_H_
+
+#include <stdbool.h>
+#include "terrain.h"
+#include "types.h"
+
+bool physics_ground(Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel);
+bool physics_step (Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel, v3f64 *acc, f64 t);
+
+#endif // _PHYSICS_H_
#include <math.h>
#include "server/biomes.h"
-#include "server/mapgen.h"
-#include "server/server_map.h"
-#include "util.h"
+#include "server/server_terrain.h"
+#include "server/terrain_gen.h"
Biome get_biome(v2s32 pos, f64 *factor)
{
- for (Biome i = 0; i < BIOME_COUNT; i++) {
+ for (Biome i = 0; i < COUNT_BIOME; i++) {
BiomeDef *def = &biomes[i];
- f64 f = def->probability == 1.0 ? 1.0 : (smooth2d(U32(pos.x) / def->threshold, U32(pos.y) / def->threshold, 0, seed + def->offset) * 0.5 - 0.5 + def->probability) / def->probability;
+ f64 f = def->probability == 1.0 ? 1.0
+ : (smooth2d(U32(pos.x) / def->threshold, U32(pos.y) / def->threshold, 0, seed + def->offset) * 0.5 - 0.5 + def->probability) / def->probability;
if (f > 0.0) {
if (factor)
}
}
- return BIOME_COUNT;
+ return COUNT_BIOME;
}
// mountain biome
-static s32 height_mountain(v2s32 pos, f64 factor, f32 height, unused void *row_data, unused void *block_data)
+static s32 height_mountain(BiomeArgsHeight *args)
{
- return pow((height + 96) * pow(((smooth2d(U32(pos.x) / 48.0, U32(pos.y) / 48.0, 0, seed + SO_MOUNTAIN_HEIGHT) + 1.0) * 256.0 + 128.0), factor), 1.0 / (factor + 1.0)) - 96;
+ return pow((args->height + 96) * pow(((smooth2d(U32(args->pos.x) / 48.0, U32(args->pos.y) / 48.0, 0, seed + SO_MOUNTAIN_HEIGHT) + 1.0) * 256.0 + 128.0), args->factor), 1.0 / (args->factor + 1.0)) - 96;
}
-static Node generate_mountain(unused v3s32 pos, s32 diff, unused f64 humidity, unused f64 temperature, unused f64 factor, unused MapBlock *block, unused List *changed_blocks, unused void *row_data, unused void *block_data)
+static NodeType generate_mountain(BiomeArgsGenerate *args)
{
- return diff <= 0 ? NODE_STONE : NODE_AIR;
+ return args->diff <= 0 ? NODE_STONE : NODE_AIR;
}
// ocean biome
-typedef enum
-{
- OL_BEACH_EDGE,
- OL_BEACH,
- OL_OCEAN,
- OL_DEEP_OCEAN,
- OL_COUNT
+typedef enum {
+ OCEAN_EDGE,
+ OCEAN_BEACH,
+ OCEAN_MAIN,
+ OCEAN_DEEP,
+ COUNT_OCEAN
} OceanLevel;
-static f64 ocean_level_start[OL_COUNT] = {
+static f64 ocean_level_start[COUNT_OCEAN] = {
0.0,
0.1,
0.2,
0.5,
};
-typedef struct
-{
+typedef struct {
bool has_vulcano;
v2s32 vulcano_pos;
-} OceanBlockData;
+} OceanChunkData;
-typedef struct
-{
+typedef struct {
bool vulcano;
bool vulcano_crater;
s32 vulcano_height;
s32 vulcano_crater_top;
- Node vulcano_stone;
+ NodeType vulcano_stone;
} OceanRowData;
static const f64 vulcano_radius = 256.0;
-static const f64 vulcano_block_offset = vulcano_radius * 2.0 / MAPBLOCK_SIZE;
+static const f64 vulcano_chunk_offset = vulcano_radius * 2.0 / CHUNK_SIZE;
static OceanLevel get_ocean_level(f64 factor)
{
- if (factor >= ocean_level_start[OL_DEEP_OCEAN])
- return OL_DEEP_OCEAN;
- else if (factor >= ocean_level_start[OL_OCEAN])
- return OL_OCEAN;
- else if (factor >= ocean_level_start[OL_BEACH])
- return OL_BEACH;
-
- return OL_BEACH_EDGE;
+ if (factor >= ocean_level_start[OCEAN_DEEP])
+ return OCEAN_DEEP;
+ else if (factor >= ocean_level_start[OCEAN_MAIN])
+ return OCEAN_MAIN;
+ else if (factor >= ocean_level_start[OCEAN_BEACH])
+ return OCEAN_BEACH;
+
+ return OCEAN_EDGE;
}
static f64 get_ocean_level_factor(f64 factor, OceanLevel level)
{
f64 start, end;
start = ocean_level_start[level];
- end = ++level == OL_COUNT ? 1.0 : ocean_level_start[level];
+ end = ++level == COUNT_OCEAN ? 1.0 : ocean_level_start[level];
return (factor - start) / (end - start);
}
static bool is_vulcano(v2s32 pos)
{
f64 factor;
- return noise2d(pos.x, pos.y, 0, seed + SO_VULCANO) > 0.0 && get_biome((v2s32) {pos.x * MAPBLOCK_SIZE, pos.y * MAPBLOCK_SIZE}, &factor) == BIOME_OCEAN && get_ocean_level(factor) == OL_DEEP_OCEAN;
+
+ return noise2d(pos.x, pos.y, 0, seed + SO_VULCANO) > 0.0
+ && get_biome((v2s32) {pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE}, &factor) == BIOME_OCEAN
+ && get_ocean_level(factor) == OCEAN_DEEP;
}
static bool find_near_vulcano(v2s32 pos, v2s32 *result)
{
- f64 x = pos.x / vulcano_block_offset;
- f64 z = pos.y / vulcano_block_offset;
+ f64 x = pos.x / vulcano_chunk_offset;
+ f64 z = pos.y / vulcano_chunk_offset;
s32 lx, lz;
lx = floor(x);
static s32 calculate_ocean_floor(f64 factor, s32 height)
{
switch (get_ocean_level(factor)) {
- case OL_BEACH_EDGE:
- return f64_mix(height + 1, 0, pow(get_ocean_level_factor(factor, OL_BEACH_EDGE), 0.8));
+ case OCEAN_EDGE:
+ return f64_mix(height + 1, 0, pow(get_ocean_level_factor(factor, OCEAN_EDGE), 0.8));
- case OL_BEACH:
+ case OCEAN_BEACH:
return 0;
- case OL_OCEAN:
- return f64_mix(0, -10, pow(get_ocean_level_factor(factor, OL_OCEAN), 0.5));
+ case OCEAN_MAIN:
+ return f64_mix(0, -10, pow(get_ocean_level_factor(factor, OCEAN_MAIN), 0.5));
- case OL_DEEP_OCEAN:
- return f64_mix(-10, -50, pow(get_ocean_level_factor(factor, OL_DEEP_OCEAN), 0.5));
+ case OCEAN_DEEP:
+ return f64_mix(-10, -50, pow(get_ocean_level_factor(factor, OCEAN_DEEP), 0.5));
default:
break;
return height;
}
-static void preprocess_block_ocean(MapBlock *block, unused List *changed_blocks, void *block_data)
+static void chunk_ocean(BiomeArgsChunk *args)
{
- OceanBlockData *data = block_data;
-
+ OceanChunkData *chunk_data = args->chunk_data;
v2s32 vulcano_pos;
- if ((data->has_vulcano = find_near_vulcano((v2s32) {block->pos.x, block->pos.z}, &vulcano_pos)))
- data->vulcano_pos = (v2s32) {vulcano_pos.x * MAPBLOCK_SIZE, vulcano_pos.y * MAPBLOCK_SIZE};
+
+ if ((chunk_data->has_vulcano = find_near_vulcano((v2s32) {args->chunk->pos.x, args->chunk->pos.z}, &vulcano_pos)))
+ chunk_data->vulcano_pos = (v2s32) {vulcano_pos.x * CHUNK_SIZE, vulcano_pos.y * CHUNK_SIZE};
}
-static void preprocess_row_ocean(v2s32 pos, unused f64 factor, void *row_data, void *block_data)
+static void row_ocean(BiomeArgsRow *args)
{
- OceanRowData *rdata = row_data;
- OceanBlockData *bdata = block_data;
- rdata->vulcano = false;
+ OceanChunkData *chunk_data = args->chunk_data;
+ OceanRowData *row_data = args->row_data;
- if (bdata->has_vulcano) {
- f64 dist = distance(pos, bdata->vulcano_pos);
+ row_data->vulcano = false;
+
+ if (chunk_data->has_vulcano) {
+ f64 dist = distance(args->pos, chunk_data->vulcano_pos);
if (dist < vulcano_radius) {
f64 crater_factor = pow(asin(1.0 - dist / vulcano_radius), 2.0);
- f64 vulcano_height = (pnoise2d(U32(pos.x) / 100.0, U32(pos.y) / 100.0, 0.2, 2, seed + SO_VULCANO_HEIGHT) * 0.5 + 0.5) * 128.0 * crater_factor + 1.0 - 30.0;
+ f64 vulcano_height = (pnoise2d(U32(args->pos.x) / 100.0, U32(args->pos.y) / 100.0, 0.2, 2, seed + SO_VULCANO_HEIGHT) * 0.5 + 0.5) * 128.0 * crater_factor + 1.0 - 30.0;
bool is_crater = vulcano_height > 0;
- if (! is_crater)
+ if (!is_crater)
vulcano_height = f64_min(vulcano_height + 5.0, 0.0);
if (vulcano_height < 0)
vulcano_height *= 2.0;
- rdata->vulcano = true;
- rdata->vulcano_crater = is_crater;
- rdata->vulcano_height = floor(vulcano_height + 0.5);
- rdata->vulcano_crater_top = 50 + floor((pnoise2d(U32(pos.x) / 3.0, U32(pos.y) / 3.0, 0.0, 1, seed + SO_VULCANO_CRATER_TOP) * 0.5 + 0.5) * 3.0 + 0.5);
- rdata->vulcano_stone = is_crater ? ((pnoise2d(U32(pos.x) / 16.0, U32(pos.y) / 16.0, 0.85, 3, seed + SO_VULCANO_STONE) * 0.5 + 0.5) * crater_factor > 0.4 ? NODE_VULCANO_STONE : NODE_STONE) : NODE_SAND;
+ row_data->vulcano = true;
+ row_data->vulcano_crater = is_crater;
+ row_data->vulcano_height = floor(vulcano_height + 0.5);
+ row_data->vulcano_crater_top = 50 + floor((pnoise2d(U32(args->pos.x) / 3.0, U32(args->pos.y) / 3.0, 0.0, 1, seed + SO_VULCANO_CRATER_TOP) * 0.5 + 0.5) * 3.0 + 0.5);
+ row_data->vulcano_stone = is_crater
+ ? ((pnoise2d(U32(args->pos.x) / 16.0, U32(args->pos.y) / 16.0, 0.85, 3, seed + SO_VULCANO_STONE) * 0.5 + 0.5) * crater_factor > 0.4
+ ? NODE_VULCANO_STONE
+ : NODE_STONE)
+ : NODE_SAND;
}
}
}
-static s32 height_ocean(unused v2s32 pos, f64 factor, f32 height, void *row_data, unused void *block_data)
+static s32 height_ocean(BiomeArgsHeight *args)
{
- OceanRowData *rdata = row_data;
- s32 ocean_floor = calculate_ocean_floor(factor, height);
+ OceanRowData *row_data = args->row_data;
- return rdata->vulcano ? f64_max(ocean_floor, rdata->vulcano_height) : ocean_floor;
+ s32 ocean_floor = calculate_ocean_floor(args->factor, args->height);
+ return row_data->vulcano ? f64_max(ocean_floor, row_data->vulcano_height) : ocean_floor;
}
-Node ocean_get_node_at(v3s32 pos, s32 diff, void *row_data)
+NodeType ocean_get_node_at(v3s32 pos, s32 diff, void *_row_data)
{
- OceanRowData *rdata = row_data;
+ OceanRowData *row_data = _row_data;
- if (rdata->vulcano && rdata->vulcano_crater) {
+ if (row_data->vulcano && row_data->vulcano_crater) {
if (diff <= -5)
return pos.y <= 45 ? NODE_LAVA : NODE_AIR;
else if (diff <= 0)
- return pos.y <= rdata->vulcano_crater_top ? rdata->vulcano_stone : NODE_AIR;
+ return pos.y <= row_data->vulcano_crater_top ? row_data->vulcano_stone : NODE_AIR;
else
return NODE_AIR;
} else {
return NODE_AIR;
}
-static Node generate_ocean(v3s32 pos, s32 diff, unused f64 humidity, unused f64 temperature, unused f64 factor, unused MapBlock *block, unused List *changed_blocks, void *row_data, unused void *block_data)
+static NodeType generate_ocean(BiomeArgsGenerate *args)
{
- return ocean_get_node_at(pos, diff, row_data);
+ return ocean_get_node_at(args->pos, args->diff, args->row_data);
}
// hills biome
return true;
}
-static s32 height_hills(unused v2s32 pos, unused f64 factor, f32 height, unused void *row_data, unused void *block_data)
+static s32 height_hills(BiomeArgsHeight *args)
{
- return height;
+ return args->height;
}
-static Node generate_hills(v3s32 pos, s32 diff, unused f64 humidity, unused f64 temperature, unused f64 factor, unused MapBlock *block, unused List *changed_blocks, unused void *row_data, unused void *block_data)
+static NodeType generate_hills(BiomeArgsGenerate *args)
{
- if (boulder_touching_ground(pos, diff))
+ if (boulder_touching_ground(args->pos, args->diff))
return NODE_STONE;
- if (diff <= -5)
+ if (args->diff <= -5)
return NODE_STONE;
- else if (diff <= -1)
+ else if (args->diff <= -1)
return NODE_DIRT;
- else if (diff <= 0)
+ else if (args->diff <= 0)
return NODE_GRASS;
return NODE_AIR;
}
-BiomeDef biomes[BIOME_COUNT] = {
+BiomeDef biomes[COUNT_BIOME] = {
{
.probability = 0.2,
.offset = SO_MOUNTAIN,
.snow = true,
.height = &height_mountain,
.generate = &generate_mountain,
- .block_data_size = 0,
- .preprocess_block = NULL,
+ .chunk_data_size = 0,
+ .chunk = NULL,
.row_data_size = 0,
- .preprocess_row = NULL,
+ .row = NULL,
},
{
.probability = 0.2,
.snow = false,
.height = &height_ocean,
.generate = &generate_ocean,
- .block_data_size = sizeof(OceanBlockData),
- .preprocess_block = &preprocess_block_ocean,
+ .chunk_data_size = sizeof(OceanChunkData),
+ .chunk = &chunk_ocean,
.row_data_size = sizeof(OceanRowData),
- .preprocess_row = &preprocess_row_ocean,
+ .row = &row_ocean,
},
{
.probability = 1.0,
.snow = true,
.height = &height_hills,
.generate = &generate_hills,
- .block_data_size = 0,
- .preprocess_block = NULL,
+ .chunk_data_size = 0,
+ .chunk = NULL,
.row_data_size = 0,
- .preprocess_row = NULL,
+ .row = NULL,
},
};
#ifndef _BIOMES_H_
#define _BIOMES_H_
-#include "map.h"
#include "perlin.h"
+#include "terrain.h"
#include "types.h"
-typedef enum
-{
+typedef enum {
BIOME_MOUNTAIN,
BIOME_OCEAN,
BIOME_HILLS,
- BIOME_COUNT,
+ COUNT_BIOME,
} Biome;
-typedef struct
-{
+typedef struct {
+ TerrainChunk *chunk;
+ List *changed_chunks;
+ void *chunk_data;
+} BiomeArgsChunk;
+
+typedef struct {
+ v2s32 pos;
+ f64 factor;
+ void *row_data;
+ void *chunk_data;
+} BiomeArgsRow;
+
+typedef struct {
+ v2s32 pos;
+ f64 factor;
+ f32 height;
+ void *row_data;
+ void *chunk_data;
+} BiomeArgsHeight;
+
+typedef struct {
+ v3s32 pos;
+ s32 diff;
+ f64 humidity;
+ f64 temperature;
+ f64 factor;
+ TerrainChunk *chunk;
+ List *changed_chunks;
+ void *row_data;
+ void *chunk_data;
+} BiomeArgsGenerate;
+
+typedef struct {
f64 probability;
SeedOffset offset;
f64 threshold;
bool snow;
- s32 (*height)(v2s32 pos, f64 factor, f32 height, void *row_data, void *block_data);
- Node (*generate)(v3s32 pos, s32 diff, f64 humidity, f64 temperature, f64 factor, MapBlock *block, List *changed_blocks, void *row_data, void *block_data);
- size_t block_data_size;
- void (*preprocess_block)(MapBlock *block, List *changed_blocks, void *block_data);
+ s32 (*height)(BiomeArgsHeight *args);
+ NodeType (*generate)(BiomeArgsGenerate *args);
+ size_t chunk_data_size;
+ void (*chunk)(BiomeArgsChunk *args);
size_t row_data_size;
- void (*preprocess_row)(v2s32 pos, f64 factor, void *row_data, void *block_data);
+ void (*row)(BiomeArgsRow *args);
} BiomeDef;
extern BiomeDef biomes[];
Biome get_biome(v2s32 pos, f64 *factor);
-Node ocean_get_node_at(v3s32 pos, s32 diff, void *row_data);
+NodeType ocean_get_node_at(v3s32 pos, s32 diff, void *_row_data);
-#endif
+#endif // _BIOMES_H_
-#include <stdio.h>
#include <endian.h/endian.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <time.h>
#include "day.h"
#include "server/database.h"
-#include "server/server_map.h"
+#include "server/server_terrain.h"
#include "perlin.h"
-#include "util.h"
-static sqlite3 *map_database;
+static sqlite3 *terrain_database;
static sqlite3 *meta_database;
static sqlite3 *players_database;
return sqlite3_prepare_v2(database, sql, -1, &stmt, NULL) == SQLITE_OK ? stmt : NULL;
}
-// print SQLite3 error message for failed block SQL statement
-static inline void print_block_error(MapBlock *block, const char *action)
+// print SQLite3 error message for failed chunk SQL statement
+static inline void print_chunk_error(TerrainChunk *chunk, const char *action)
{
- fprintf(stderr, "Database error with %s block at (%d, %d, %d): %s\n", action, block->pos.x, block->pos.y, block->pos.z, sqlite3_errmsg(map_database));
+ fprintf(stderr, "[warning] failed %s chunk at (%d, %d, %d): %s\n", action, chunk->pos.x, chunk->pos.y, chunk->pos.z, sqlite3_errmsg(terrain_database));
}
-// prepare a SQLite3 block statement and bind the position
-static sqlite3_stmt *prepare_block_statement(MapBlock *block, const char *action, const char *sql)
+// prepare a SQLite3 chunk statement and bind the position
+static sqlite3_stmt *prepare_chunk_statement(TerrainChunk *chunk, const char *action, const char *sql)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(terrain_database, sql);
- if (! (stmt = prepare_statement(map_database, sql))) {
- print_block_error(block, action);
+ if (!stmt) {
+ print_chunk_error(chunk, action);
return NULL;
}
Blob buffer = {0, NULL};
- v3s32_write(&buffer, &block->pos);
+ v3s32_write(&buffer, &chunk->pos);
sqlite3_bind_blob(stmt, 1, buffer.data, buffer.siz, &free);
sqlite3_bind_blob(stmt, idx, buffer.data, buffer.siz, &free);
}
+// bind v3f32 to sqlite3 statement
+static inline void bind_v3f32(sqlite3_stmt *stmt, int idx, v3f32 pos)
+{
+ Blob buffer = {0, NULL};
+ v3f32_write(&buffer, &pos);
+
+ sqlite3_bind_blob(stmt, idx, buffer.data, buffer.siz, &free);
+}
+
// public functions
// open and initialize SQLite3 databases
const char *path;
const char *init;
} databases[3] = {
- {&map_database, "map.sqlite", "CREATE TABLE IF NOT EXISTS map (pos BLOB PRIMARY KEY, generated INTEGER, data BLOB, mgsb BLOB);"},
+ {&terrain_database, "terrain.sqlite", "CREATE TABLE IF NOT EXISTS terrain (pos BLOB PRIMARY KEY, generated INTEGER, data BLOB, tgsb BLOB);"},
{&meta_database, "meta.sqlite", "CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value INTEGER );"},
- {&players_database, "players.sqlite", "CREATE TABLE IF NOT EXISTS players (name TEXT PRIMARY KEY, pos BLOB );"},
+ {&players_database, "players.sqlite", "CREATE TABLE IF NOT EXISTS players (name TEXT PRIMARY KEY, pos BLOB, rot BLOB );"},
};
for (int i = 0; i < 3; i++) {
if (sqlite3_open_v2(databases[i].path, databases[i].handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) != SQLITE_OK) {
- fprintf(stderr, "Failed to open %s: %s\n", databases[i].path, sqlite3_errmsg(*databases[i].handle));
+ fprintf(stderr, "[error] failed to open %s: %s\n", databases[i].path, sqlite3_errmsg(*databases[i].handle));
return false;
}
char *err;
if (sqlite3_exec(*databases[i].handle, databases[i].init, NULL, NULL, &err) != SQLITE_OK) {
- fprintf(stderr, "Failed to initialize %s: %s\n", databases[i].path, err);
+ fprintf(stderr, "[error] failed initializing %s: %s\n", databases[i].path, err);
sqlite3_free(err);
return false;
}
return true;
}
-// close database
+// close databases
void database_deinit()
{
database_save_meta("time_of_day", (s64) get_time_of_day());
- sqlite3_close(map_database);
+ sqlite3_close(terrain_database);
sqlite3_close(meta_database);
sqlite3_close(players_database);
}
-// load a block from map database (initializes state, mgs buffer and data), returns false on failure
-bool database_load_block(MapBlock *block)
+// load a chunk from terrain database (initializes state, tgs buffer and data), returns false on failure
+bool database_load_chunk(TerrainChunk *chunk)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_chunk_statement(chunk, "loading", "SELECT generated, data, tgsb FROM terrain WHERE pos=?");
- if (! (stmt = prepare_block_statement(block, "loading", "SELECT generated, data, mgsb FROM map WHERE pos=?")))
+ if (!stmt)
return false;
int rc = sqlite3_step(stmt);
bool found = rc == SQLITE_ROW;
if (found) {
- MapBlockExtraData *extra = block->extra;
+ TerrainChunkMeta *meta = chunk->extra;
- extra->state = sqlite3_column_int(stmt, 0) ? MBS_READY : MBS_CREATED;
- Blob_read( &(Blob) {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}, &extra->data);
- MapgenStageBuffer_read(&(Blob) {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)}, &extra->mgsb);
+ meta->state = sqlite3_column_int(stmt, 0) ? CHUNK_READY : CHUNK_CREATED;
+ Blob_read( &(Blob) {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}, &meta->data);
+ TerrainGenStageBuffer_read(&(Blob) {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)}, &meta->tgsb);
- if (! map_deserialize_block(block, extra->data)) {
- fprintf(stderr, "Failed to load block at (%d, %d, %d)\n", block->pos.x, block->pos.y, block->pos.z);
+ if (!terrain_deserialize_chunk(chunk, meta->data)) {
+ fprintf(stderr, "[error] failed deserializing chunk at (%d, %d, %d)\n", chunk->pos.x, chunk->pos.y, chunk->pos.z);
exit(EXIT_FAILURE);
}
} else if (rc != SQLITE_DONE) {
- print_block_error(block, "loading");
+ print_chunk_error(chunk, "loading");
}
sqlite3_finalize(stmt);
return found;
}
-// save a block to database
-void database_save_block(MapBlock *block)
+// save a chunk to terrain database
+void database_save_chunk(TerrainChunk *chunk)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_chunk_statement(chunk, "saving", "REPLACE INTO terrain (pos, generated, data, tgsb) VALUES(?1, ?2, ?3, ?4)");
- if (! (stmt = prepare_block_statement(block, "saving", "REPLACE INTO map (pos, generated, data, mgsb) VALUES(?1, ?2, ?3, ?4)")))
+ if (!stmt)
return;
- MapBlockExtraData *extra = block->extra;
+ TerrainChunkMeta *meta = chunk->extra;
Blob data = {0, NULL};
- Blob_write(&data, &extra->data);
+ Blob_write(&data, &meta->data);
- Blob mgsb = {0, NULL};
- MapgenStageBuffer_write(&mgsb, &extra->mgsb);
+ Blob tgsb = {0, NULL};
+ TerrainGenStageBuffer_write(&tgsb, &meta->tgsb);
- sqlite3_bind_int(stmt, 2, extra->state > MBS_CREATED);
+ sqlite3_bind_int(stmt, 2, meta->state > CHUNK_CREATED);
sqlite3_bind_blob(stmt, 3, data.data, data.siz, &free);
- sqlite3_bind_blob(stmt, 4, mgsb.data, mgsb.siz, &free);
+ sqlite3_bind_blob(stmt, 4, tgsb.data, tgsb.siz, &free);
if (sqlite3_step(stmt) != SQLITE_DONE)
- print_block_error(block, "saving");
+ print_chunk_error(chunk, "saving");
sqlite3_finalize(stmt);
}
// load a meta entry
bool database_load_meta(const char *key, s64 *value_ptr)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(meta_database, "SELECT value FROM meta WHERE key=?");
- if (! (stmt = prepare_statement(meta_database, "SELECT value FROM meta WHERE key=?"))) {
- fprintf(stderr, "Database error with loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
+ if (!stmt) {
+ fprintf(stderr, "[warning] failed loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
return false;
}
if (found)
*value_ptr = sqlite3_column_int64(stmt, 0);
else if (rc != SQLITE_DONE)
- fprintf(stderr, "Database error with loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
+ fprintf(stderr, "[warning] failed loading meta %s: %s\n", key, sqlite3_errmsg(meta_database));
sqlite3_finalize(stmt);
return found;
// save / update a meta entry
void database_save_meta(const char *key, s64 value)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(meta_database, "REPLACE INTO meta (key, value) VALUES(?1, ?2)");
- if (! (stmt = prepare_statement(meta_database, "REPLACE INTO meta (key, value) VALUES(?1, ?2)"))) {
- fprintf(stderr, "Database error with saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
+ if (!stmt) {
+ fprintf(stderr, "[warning] failed saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
return;
}
sqlite3_bind_int64(stmt, 2, value);
if (sqlite3_step(stmt) != SQLITE_DONE)
- fprintf(stderr, "Database error with saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
+ fprintf(stderr, "[warning] failed saving meta %s: %s\n", key, sqlite3_errmsg(meta_database));
sqlite3_finalize(stmt);
}
// load player data from database
-bool database_load_player(char *name, v3f64 *pos_ptr)
+bool database_load_player(char *name, v3f64 *pos, v3f32 *rot)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(players_database, "SELECT pos, rot FROM players WHERE name=?");
- if (! (stmt = prepare_statement(players_database, "SELECT pos FROM players WHERE name=?"))) {
- fprintf(stderr, "Database error with loading player %s: %s\n", name, sqlite3_errmsg(players_database));
+ if (!stmt) {
+ fprintf(stderr, "[warning] failed loading player %s: %s\n", name, sqlite3_errmsg(players_database));
return false;
}
int rc = sqlite3_step(stmt);
bool found = rc == SQLITE_ROW;
- if (found)
- v3f64_read(&(Blob) {sqlite3_column_bytes(stmt, 0), (void *) sqlite3_column_blob(stmt, 0)}, pos_ptr);
- else if (rc != SQLITE_DONE)
- fprintf(stderr, "Database error with loading player %s: %s\n", name, sqlite3_errmsg(players_database));
+ if (found) {
+ v3f64_read(&(Blob) {sqlite3_column_bytes(stmt, 0), (void *) sqlite3_column_blob(stmt, 0)}, pos);
+ v3f32_read(&(Blob) {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}, rot);
+ } else if (rc != SQLITE_DONE) {
+ fprintf(stderr, "[warning] failed loading player %s: %s\n", name, sqlite3_errmsg(players_database));
+ }
sqlite3_finalize(stmt);
return found;
}
// insert new player into database
-void database_create_player(char *name, v3f64 pos)
+void database_create_player(char *name, v3f64 pos, v3f32 rot)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(players_database, "INSERT INTO players (name, pos, rot) VALUES(?1, ?2, ?3)");
- if (! (stmt = prepare_statement(players_database, "INSERT INTO players (name, pos) VALUES(?1, ?2)"))) {
- fprintf(stderr, "Database error with creating player %s: %s\n", name, sqlite3_errmsg(players_database));
+ if (!stmt) {
+ fprintf(stderr, "[warning] failed creating player %s: %s\n", name, sqlite3_errmsg(players_database));
return;
}
sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_TRANSIENT);
bind_v3f64(stmt, 2, pos);
+ bind_v3f32(stmt, 3, rot);
if (sqlite3_step(stmt) != SQLITE_DONE)
- fprintf(stderr, "Database error with creating player %s: %s\n", name, sqlite3_errmsg(players_database));
+ fprintf(stderr, "[warning] failed creating player %s: %s\n", name, sqlite3_errmsg(players_database));
sqlite3_finalize(stmt);
}
// update player position
-void database_update_player_pos(char *name, v3f64 pos)
+void database_update_player_pos_rot(char *name, v3f64 pos, v3f32 rot)
{
- sqlite3_stmt *stmt;
+ sqlite3_stmt *stmt = prepare_statement(players_database, "UPDATE players SET pos=?1, rot=?2 WHERE name=?3");
- if (! (stmt = prepare_statement(players_database, "UPDATE players SET pos=?1 WHERE name=?2"))) {
- fprintf(stderr, "Database error with updating position of player %s: %s\n", name, sqlite3_errmsg(players_database));
+ if (!stmt) {
+ fprintf(stderr, "[warning] failed updating player %s: %s\n", name, sqlite3_errmsg(players_database));
return;
}
bind_v3f64(stmt, 1, pos);
- sqlite3_bind_text(stmt, 2, name, strlen(name), SQLITE_TRANSIENT);
+ bind_v3f32(stmt, 2, rot);
+ sqlite3_bind_text(stmt, 3, name, strlen(name), SQLITE_TRANSIENT);
if (sqlite3_step(stmt) != SQLITE_DONE)
- fprintf(stderr, "Database error with updating player %s position: %s\n", name, sqlite3_errmsg(players_database));
+ fprintf(stderr, "[warning] failed updating player %s: %s\n", name, sqlite3_errmsg(players_database));
sqlite3_finalize(stmt);
}
#define _DATABASE_H_
#include <stdbool.h>
-#include "map.h"
+#include "terrain.h"
#include "types.h"
-bool database_init(); // open and initialize world SQLite3 database
-void database_deinit(); // close database
-bool database_load_block(MapBlock *block); // load a block from map database (initializes state, mgs buffer and data), returns false on failure
-void database_save_block(MapBlock *block); // save a block to database
-bool database_load_meta(const char *key, s64 *value_ptr); // load a meta entry
-void database_save_meta(const char *key, s64 value); // save / update a meta entry
-bool database_load_player(char *name, v3f64 *pos_ptr); // load player data from database
-void database_create_player(char *name, v3f64 pos); // insert new player into database
-void database_update_player_pos(char *name, v3f64 pos); // update player position
+bool database_init(); // open and initialize SQLite3 databases
+void database_deinit(); // close databases
+bool database_load_chunk(TerrainChunk *chunk); // load a chunk from terrain database (initializes state, tgs buffer and data), returns false on failure
+void database_save_chunk(TerrainChunk *chunk); // save a chunk to terrain database
+bool database_load_meta(const char *key, s64 *value_ptr); // load a meta entry
+void database_save_meta(const char *key, s64 value); // save / update a meta entry
+bool database_load_player(char *name, v3f64 *pos, v3f32 *rot); // load player data from database
+void database_create_player(char *name, v3f64 pos, v3f32 rot); // insert new player into database
+void database_update_player_pos_rot(char *name, v3f64 pos, v3f32 rot); // update player position
-#endif
+#endif // _DATABASE_H_
+++ /dev/null
-#include <math.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include "environment.h"
-#include "perlin.h"
-#include "server/biomes.h"
-#include "server/mapgen.h"
-#include "server/server_map.h"
-#include "server/trees.h"
-#include "util.h"
-
-void mapgen_set_node(v3s32 pos, MapNode node, MapgenStage mgs, List *changed_blocks)
-{
- MapgenSetNodeArg arg = {
- .mgs = mgs,
- .changed_blocks = changed_blocks,
- };
-
- map_set_node(server_map.map, pos, node, true, &arg);
-}
-
-// generate a block (does not manage block state or threading)
-void mapgen_generate_block(MapBlock *block, List *changed_blocks)
-{
- MapBlockExtraData *extra = block->extra;
-
- v3s32 block_node_pos = {block->pos.x * MAPBLOCK_SIZE, block->pos.y * MAPBLOCK_SIZE, block->pos.z * MAPBLOCK_SIZE};
-
- char *block_data[BIOME_COUNT] = {NULL};
- bool preprocessed_block[BIOME_COUNT] = {false};
-
- for (u8 x = 0; x < MAPBLOCK_SIZE; x++) {
- s32 pos_x = block_node_pos.x + x;
-
- for (u8 z = 0; z < MAPBLOCK_SIZE; z++) {
- v2s32 pos_horizontal = {pos_x, block_node_pos.z + z};
-
- s32 default_height = (pnoise2d(U32(pos_horizontal.x) / 32.0, U32(pos_horizontal.y) / 32.0, 0.45, 5, seed + SO_HEIGHT) * 16.0)
- * (pnoise2d(U32(pos_horizontal.x) / 256.0, U32(pos_horizontal.y) / 256.0, 0.45, 5, seed + SO_HILLYNESS) * 0.5 + 0.5)
- + 32.0;
-
- f64 factor;
- Biome biome = get_biome(pos_horizontal, &factor);
- BiomeDef *biome_def = &biomes[biome];
-
- if (biome_def->block_data_size > 0 && ! block_data[biome])
- block_data[biome] = malloc(biome_def->block_data_size);
-
- if (biome_def->preprocess_block && ! preprocessed_block[biome]) {
- biome_def->preprocess_block(block, changed_blocks, block_data[biome]);
- preprocessed_block[biome] = true;
- }
-
- char row_data[biome_def->row_data_size];
-
- if (biome_def->preprocess_row)
- biome_def->preprocess_row(pos_horizontal, factor, row_data, block_data[biome]);
-
- s32 height = biome_def->height(pos_horizontal, factor, default_height, row_data, block_data[biome]);
-
- for (u8 y = 0; y < MAPBLOCK_SIZE; y++) {
- v3s32 pos = {pos_horizontal.x, block_node_pos.y + y, pos_horizontal.y};
-
- f64 humidity = get_humidity(pos);
- f64 temperature = get_temperature(pos);
-
- s32 diff = pos.y - height;
-
- Node node = biome_def->generate(pos, diff, humidity, temperature, factor, block, changed_blocks, row_data, block_data[biome]);
-
- if (biome_def->snow && diff <= 1 && temperature < 0.0 && node == NODE_AIR)
- node = NODE_SNOW;
-
- if (diff == 1) {
- for (int i = 0; i < NUM_TREES; i++) {
- TreeDef *def = &tree_definitions[i];
-
- if (def->condition(pos, humidity, temperature, biome, factor, block, row_data, block_data)
- && noise2d(pos.x, pos.z, 0, seed + def->offset) * 0.5 + 0.5 < def->probability
- && smooth2d(U32(pos.x) / def->spread, U32(pos.z) / def->spread, 0, seed + def->area_offset) * 0.5 + 0.5 < def->area_probability) {
- def->generate(pos, changed_blocks);
- break;
- }
- }
- }
-
- pthread_mutex_lock(&block->mtx);
- if (extra->mgsb.raw.nodes[x][y][z] <= MGS_TERRAIN) {
- block->data[x][y][z] = map_node_create(node, (Blob) {0, NULL});
- extra->mgsb.raw.nodes[x][y][z] = MGS_TERRAIN;
- }
- pthread_mutex_unlock(&block->mtx);
- }
- }
- }
-
- for (Biome i = 0; i < BIOME_COUNT; i++) {
- if (block_data[i])
- free(block_data[i]);
- }
-}
+++ /dev/null
-#ifndef _MAPGEN_H_
-#define _MAPGEN_H_
-
-#include "map.h"
-#include "server/server_map.h"
-
-void mapgen_set_node(v3s32 pos, MapNode node, MapgenStage mgs, List *changed_blocks);
-void mapgen_generate_block(MapBlock *block, List *changed_blocks); // generate a block (does not manage block state or threading)
-
-#endif
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include "server/schematic.h"
+#include "terrain.h"
+
+void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings)
+{
+ list_ini(schematic);
+
+ FILE *file = fopen(path, "r");
+ if (!file) {
+ fprintf(stderr, "[warning] failed to open schematic %s\n", path);
+ return;
+ }
+
+ char *line = NULL;
+ size_t siz = 0;
+ ssize_t length;
+ int count = 0;
+
+ // getline is POSIX 2008, so we can use it
+ while ((length = getline(&line, &siz, file)) > 0) {
+ count++;
+
+ if (*line == '#')
+ continue;
+
+ SchematicNode *node = malloc(sizeof *node);
+ node->data = (Blob) {0, NULL};
+
+ v3s32 color;
+ if (sscanf(line, "%d %d %d %2x%2x%2x",
+ &node->pos.x, &node->pos.z, &node->pos.y,
+ &color.x, &color.y, &color.z) != 6) {
+ fprintf(stderr, "[warning] syntax error in schematic %s in line %d: %s\n",
+ path, count, line);
+ free(node);
+ continue;
+ }
+
+ SchematicMapping *mapping = NULL;
+ for (size_t i = 0; i < num_mappings; i++)
+ if (v3s32_equals(color, mappings[i].color)) {
+ mapping = &mappings[i];
+ break;
+ }
+
+ if (!mapping) {
+ fprintf(stderr, "[warning] color not mapped to node in schematic %s in line %d: %02x%02x%02x\n",
+ path, count, color.x, color.y, color.z);
+ free(node);
+ continue;
+ }
+
+ node->type = mapping->type;
+
+ if (mapping->use_color)
+ ColorData_write(&node->data, &(ColorData) {{
+ (f32) color.x / 0xFF,
+ (f32) color.y / 0xFF,
+ (f32) color.z / 0xFF,
+ }});
+
+ list_apd(schematic, node);
+ }
+
+ if (line)
+ free(line);
+
+ fclose(file);
+}
+
+void schematic_place(List *schematic, v3s32 pos, TerrainGenStage tgs, List *changed_chunks)
+{
+ LIST_ITERATE(schematic, list_node) {
+ SchematicNode *node = list_node->dat;
+
+ server_terrain_gen_node(
+ v3s32_add(pos, node->pos),
+ terrain_node_create(node->type, node->data),
+ tgs, changed_chunks);
+ }
+}
+
+static void delete_schematic_node(SchematicNode *node)
+{
+ Blob_free(&node->data);
+ free(node);
+}
+
+void schematic_delete(List *schematic)
+{
+ list_clr(schematic, (void *) &delete_schematic_node, NULL, NULL);
+}
--- /dev/null
+#ifndef _SCHEMATIC_H_
+#define _SCHEMATIC_H_
+
+#include <dragonstd/list.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "node.h"
+#include "server/server_terrain.h"
+#include "types.h"
+
+typedef struct {
+ v3s32 color;
+ NodeType type;
+ bool use_color;
+} SchematicMapping;
+
+typedef struct {
+ v3s32 pos;
+ NodeType type;
+ Blob data;
+} SchematicNode;
+
+void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings);
+void schematic_place(List *schematic, v3s32 pos, TerrainGenStage tgs, List *changed_chunks);
+void schematic_delete(List *schematic);
+
+#endif // _SCHEMATIC_H_
+#define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <dragonnet/addr.h>
#include <stdio.h>
#include <stdlib.h>
-#include <dragonnet/addr.h>
+#include <pthread.h>
#include "interrupt.h"
#include "server/database.h"
#include "server/server.h"
-#include "server/server_map.h"
#include "server/server_player.h"
-#include "util.h"
+#include "server/server_terrain.h"
DragonnetListener *server;
-static bool on_recv(DragonnetPeer *peer, DragonnetTypeId type, unused void *pkt)
+static bool on_recv(DragonnetPeer *peer, DragonnetTypeId type, __attribute__((unused)) void *pkt)
{
+ // this is recv thread, so we don't need lock_auth
return ((ServerPlayer *) peer->extra)->auth != (type == DRAGONNET_TYPE_ToServerAuth);
}
}
// set a node on the map
-static void on_ToServerSetnode(unused DragonnetPeer *peer, ToServerSetnode *pkt)
+static void on_ToServerSetnode(__attribute__((unused)) DragonnetPeer *peer, ToServerSetnode *pkt)
{
- map_set_node(server_map.map, pkt->pos, map_node_create(pkt->node, (Blob) {0, NULL}), false, NULL);
+ terrain_set_node(server_terrain, pkt->pos,
+ terrain_node_create(pkt->node, (Blob) {0, NULL}),
+ false, NULL);
}
// update player's position
-static void on_ToServerPos(DragonnetPeer *peer, ToServerPos *pkt)
+static void on_ToServerPosRot(DragonnetPeer *peer, ToServerPosRot *pkt)
{
ServerPlayer *player = peer->extra;
- pthread_rwlock_wrlock(&player->pos_lock);
+ pthread_rwlock_wrlock(&player->lock_pos);
player->pos = pkt->pos;
- database_update_player_pos(player->name, player->pos);
- pthread_rwlock_unlock(&player->pos_lock);
+ player->rot = pkt->rot;
+
+ // this is recv thread, no lock_auth needed
+ database_update_player_pos_rot(player->name, player->pos, player->rot);
+ pthread_rwlock_unlock(&player->lock_pos);
}
-// tell server map manager client requested the block
-static void on_ToServerRequestBlock(DragonnetPeer *peer, ToServerRequestBlock *pkt)
+// tell server map manager client requested the chunk
+static void on_ToServerRequestChunk(DragonnetPeer *peer, ToServerRequestChunk *pkt)
{
- server_map_requested_block(peer->extra, pkt->pos);
+ server_terrain_requested_chunk(peer->extra, pkt->pos);
}
// server entry point
int main(int argc, char **argv)
{
+#ifdef __GLIBC__ // check whether bloat is enabled
+ pthread_setname_np(pthread_self(), "main");
+#endif // __GLIBC__
+
if (argc < 2) {
- fprintf(stderr, "Missing address\n");
+ fprintf(stderr, "[error] missing address\n");
return EXIT_FAILURE;
}
- if (! (server = dragonnet_listener_new(argv[1]))) {
- fprintf(stderr, "Failed to listen to connections\n");
+ if (!(server = dragonnet_listener_new(argv[1]))) {
+ fprintf(stderr, "[error] failed to listen to connections\n");
return EXIT_FAILURE;
}
char *address = dragonnet_addr_str(server->laddr);
- printf("Listening on %s\n", address);
+ printf("[info] listening on %s\n", address);
free(address);
server->on_connect = &server_player_add;
server->on_disconnect = &server_player_remove;
server->on_recv = &on_recv;
- server->on_recv_type[DRAGONNET_TYPE_ToServerAuth] = (void *) &on_ToServerAuth;
- server->on_recv_type[DRAGONNET_TYPE_ToServerSetnode] = (void *) &on_ToServerSetnode;
- server->on_recv_type[DRAGONNET_TYPE_ToServerPos] = (void *) &on_ToServerPos;
- server->on_recv_type[DRAGONNET_TYPE_ToServerRequestBlock] = (void *) &on_ToServerRequestBlock;
+ server->on_recv_type[DRAGONNET_TYPE_ToServerAuth ] = (void *) &on_ToServerAuth;
+ server->on_recv_type[DRAGONNET_TYPE_ToServerSetnode ] = (void *) &on_ToServerSetnode;
+ server->on_recv_type[DRAGONNET_TYPE_ToServerPosRot ] = (void *) &on_ToServerPosRot;
+ server->on_recv_type[DRAGONNET_TYPE_ToServerRequestChunk] = (void *) &on_ToServerRequestChunk;
interrupt_init();
-
- if (! database_init())
+ if (!database_init())
return EXIT_FAILURE;
-
- server_map_init();
+ server_terrain_init();
server_player_init();
- server_map_prepare_spawn();
+ server_terrain_prepare_spawn();
dragonnet_listener_run(server);
- flag_wait(interrupt);
+ flag_slp(&interrupt);
- printf("Shutting down\n");
+ printf("[info] shutting down\n");
dragonnet_listener_close(server);
server_player_deinit();
- server_map_deinit();
+ server_terrain_deinit();
database_deinit();
interrupt_deinit();
DragonnetListener *server;
-#endif
+#endif // _SERVER_H_
#include "server/server_config.h"
struct ServerConfig server_config = {
- .simulation_distance = 10,
- .mapgen_threads = 4,
+ .load_distance = 10,
+ .terrain_gen_threads = 4,
+ .movement = {
+ .speed_normal = 4.317,
+ .speed_flight = 25.0,
+ .gravity = 32.0,
+ .jump = 8.944,
+ }
+};
+
+#define NUM_CONFIG_ENTRIES 6
+static ConfigEntry config_entries[NUM_CONFIG_ENTRIES] = {
+ {
+ .type = CONFIG_UINT,
+ .key = "load_distance",
+ .value = &server_config.load_distance,
+ },
+ {
+ .type = CONFIG_UINT,
+ .key = "terrain_gen_threads",
+ .value = &server_config.terrain_gen_threads,
+ },
+ {
+ .type = CONFIG_FLOAT,
+ .key = "movement.speed_normal",
+ .value = &server_config.movement.speed_normal,
+ },
+ {
+ .type = CONFIG_FLOAT,
+ .key = "movement.speed_flight",
+ .value = &server_config.movement.speed_flight,
+ },
+ {
+ .type = CONFIG_FLOAT,
+ .key = "movement.gravity",
+ .value = &server_config.movement.gravity,
+ },
+ {
+ .type = CONFIG_FLOAT,
+ .key = "movement.jump",
+ .value = &server_config.movement.jump,
+ },
};
__attribute__((constructor)) static void server_config_init()
{
- config_read("server.conf", (ConfigEntry[]) {
- {
- .type = CT_UINT,
- .key = "simulation_distance",
- .value = &server_config.simulation_distance,
- },
- {
- .type = CT_UINT,
- .key = "mapgen_threads",
- .value = &server_config.mapgen_threads,
- },
- }, 2);
+ config_read("server.conf", config_entries, NUM_CONFIG_ENTRIES);
}
+__attribute__((destructor)) static void server_config_deinit()
+{
+ config_free(config_entries, NUM_CONFIG_ENTRIES);
+}
#define _SERVER_CONFIG_H_
extern struct ServerConfig {
- unsigned int simulation_distance;
- unsigned int mapgen_threads;
+ unsigned int load_distance;
+ unsigned int terrain_gen_threads;
+ struct {
+ double speed_normal;
+ double speed_flight;
+ double gravity;
+ double jump;
+
+ // allow_op, allow_all, force_on, force_off
+ } movement;
} server_config;
-#endif
+#endif // _SERVER_CONFIG_H_
+++ /dev/null
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdio.h>
-#include "interrupt.h"
-#include "map.h"
-#include "server/database.h"
-#include "server/mapgen.h"
-#include "server/server_config.h"
-#include "server/server_map.h"
-#include "util.h"
-
-// this file is too long
-struct ServerMap server_map;
-
-// utility functions
-
-// return true if a player is close enough to a block to access it
-static bool within_simulation_distance(ServerPlayer *player, v3s32 blkp, u32 dist)
-{
- pthread_rwlock_rdlock(&player->pos_lock);
- v3s32 ppos = map_node_to_block_pos((v3s32) {player->pos.x, player->pos.y, player->pos.z}, NULL);
- pthread_rwlock_unlock(&player->pos_lock);
-
- return abs(ppos.x - blkp.x) <= (s32) dist
- && abs(ppos.y - blkp.y) <= (s32) dist
- && abs(ppos.z - blkp.z) <= (s32) dist;
-}
-
-// send a block to a client and reset block request
-static void send_block(ServerPlayer *player, MapBlock *block)
-{
- if (! within_simulation_distance(player, block->pos, server_config.simulation_distance))
- return;
-
- dragonnet_peer_send_ToClientBlock(player->peer, &(ToClientBlock) {
- .pos = block->pos,
- .data = ((MapBlockExtraData *) block->extra)->data,
- });
-}
-
-// send block to near clients
-// block mutex has to be locked
-static void send_block_to_near(MapBlock *block)
-{
- MapBlockExtraData *extra = block->extra;
-
- if (extra->state == MBS_GENERATING)
- return;
-
- Blob_free(&extra->data);
- extra->data = map_serialize_block(block);
-
- database_save_block(block);
-
- if (extra->state == MBS_CREATED)
- return;
-
- server_player_iterate((void *) &send_block, block);
-}
-
-// list_clear_func callback for sending changed blocks to near clients
-static void list_send_block(void *key, unused void *value, unused void *arg)
-{
- MapBlock *block = key;
-
- pthread_mutex_lock(&block->mtx);
- send_block_to_near(block);
- pthread_mutex_unlock(&block->mtx);
-}
-
-// me when the
-static void mapgen_step()
-{
- MapBlock *block = queue_dequeue(server_map.mapgen_tasks);
-
- if (! block)
- return;
-
- MapBlockExtraData *extra = block->extra;
-
- List changed_blocks = list_create(NULL);
- list_put(&changed_blocks, block, NULL);
-
- mapgen_generate_block(block, &changed_blocks);
-
- pthread_mutex_lock(&block->mtx);
- extra->state = MBS_READY;
- pthread_mutex_unlock(&block->mtx);
-
- list_clear_func(&changed_blocks, &list_send_block, NULL);
-
- pthread_mutex_lock(&server_map.num_blocks_mtx);
- server_map.num_blocks--;
- pthread_mutex_unlock(&server_map.num_blocks_mtx);
-}
-
-// there was a time when i wrote actually useful comments lol
-static void *mapgen_thread(unused void *arg)
-{
- while (! server_map.cancel)
- mapgen_step();
-
- return NULL;
-}
-
-// enqueue block
-static void generate_block(MapBlock *block)
-{
- if (server_map.cancel)
- return;
-
- pthread_mutex_lock(&server_map.num_blocks_mtx);
- server_map.num_blocks++;
- pthread_mutex_unlock(&server_map.num_blocks_mtx);
-
- MapBlockExtraData *extra = block->extra;
- extra->state = MBS_GENERATING;
- queue_enqueue(server_map.mapgen_tasks, block);
-}
-
-// map callbacks
-// note: all these functions require the block mutex to be locked, which is always the case when a map callback is invoked
-
-// callback for initializing a newly created block
-// load block from database or initialize state, mgstage buffer and data
-static void on_create_block(MapBlock *block)
-{
- MapBlockExtraData *extra = block->extra = malloc(sizeof(MapBlockExtraData));
-
- if (! database_load_block(block)) {
- extra->state = MBS_CREATED;
- extra->data = (Blob) {0, NULL};
-
- ITERATE_MAPBLOCK {
- block->data[x][y][z] = map_node_create(NODE_AIR, (Blob) {0, NULL});
- extra->mgsb.raw.nodes[x][y][z] = MGS_VOID;
- }
- }
-}
-
-// callback for deleting a block
-// free extra data
-static void on_delete_block(MapBlock *block)
-{
- MapBlockExtraData *extra = block->extra;
-
- Blob_free(&extra->data);
- free(extra);
-}
-
-// callback for determining whether a block should be returned by map_get_block
-// hold back blocks that are not fully generated except when the create flag is set to true
-static bool on_get_block(MapBlock *block, bool create)
-{
- MapBlockExtraData *extra = block->extra;
-
- if (extra->state < MBS_READY && ! create)
- return false;
-
- return true;
-}
-
-// callback for deciding whether a set_node call succeeds or not
-// reject set_node calls that try to override nodes placed by later mapgen stages, else update mgs buffer - also make sure block is inserted into changed blocks list
-static bool on_set_node(MapBlock *block, v3u8 offset, unused MapNode *node, void *arg)
-{
- MapgenSetNodeArg *msn_arg = arg;
-
- MapgenStage mgs;
-
- if (msn_arg)
- mgs = msn_arg->mgs;
- else
- mgs = MGS_PLAYER;
-
- MapgenStage *old_mgs = &((MapBlockExtraData *) block->extra)->mgsb.raw.nodes[offset.x][offset.y][offset.z];
-
- if (mgs >= *old_mgs) {
- *old_mgs = mgs;
-
- if (msn_arg)
- list_put(msn_arg->changed_blocks, block, NULL);
-
- return true;
- }
-
- return false;
-}
-
-// callback for when a block changes
-// send block to near clients if not part of map generation
-static void on_after_set_node(MapBlock *block, unused v3u8 offset, void *arg)
-{
- if (! arg)
- send_block_to_near(block);
-}
-
-// generate a hut for new players to spawn in
-static void generate_spawn_hut()
-{
- Blob wood_color = {0, NULL};
- HSLData_write(&wood_color, &(HSLData) {{0.11f, 1.0f, 0.29f}});
-
- List changed_blocks = list_create(NULL);
-
- for (s32 x = -4; x <= +4; x++) {
- for (s32 y = 0; y <= 3; y++) {
- for (s32 z = -3; z <= +2; z++) {
- mapgen_set_node((v3s32) {x, server_map.spawn_height + y, z}, map_node_create(NODE_AIR, (Blob) {0, NULL}), MGS_PLAYER, &changed_blocks);
- }
- }
- }
-
- for (s32 x = -5; x <= +5; x++) {
- for (s32 z = -4; z <= +3; z++) {
- mapgen_set_node((v3s32) {x, server_map.spawn_height - 1, z}, map_node_create(NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- mapgen_set_node((v3s32) {x, server_map.spawn_height + 4, z}, map_node_create(NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- }
- }
-
- for (s32 y = 0; y <= 3; y++) {
- for (s32 x = -5; x <= +5; x++) {
- mapgen_set_node((v3s32) {x, server_map.spawn_height + y, -4}, map_node_create(((y == 1 || y == 2) && ((x >= -3 && x <= -1) || (x >= +1 && x <= +2))) ? NODE_AIR : NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- mapgen_set_node((v3s32) {x, server_map.spawn_height + y, +3}, map_node_create(((y == 1 || y == 2) && ((x >= -3 && x <= -2) || (x >= +1 && x <= +3))) ? NODE_AIR : NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- }
- }
-
- for (s32 y = 0; y <= 3; y++) {
- for (s32 z = -3; z <= +2; z++) {
- mapgen_set_node((v3s32) {-5, server_map.spawn_height + y, z}, map_node_create(NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- mapgen_set_node((v3s32) {+5, server_map.spawn_height + y, z}, map_node_create(((y != 3) && (z == -1 || z == +0)) ? NODE_AIR : NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- }
- }
-
- v2s32 posts[6] = {
- {-4, -3},
- {-4, +2},
- {+4, -3},
- {+4, +2},
- {+5, -1},
- {+5, +0},
- };
-
- for (int i = 0; i < 6; i++) {
- for (s32 y = server_map.spawn_height - 2;; y--) {
- v3s32 pos = {posts[i].x, y, posts[i].y};
- Node node = map_get_node(server_map.map, pos).type;
-
- if (i >= 4) {
- if (node != NODE_AIR)
- break;
-
- pos.y++;
- }
-
- if (node_definitions[node].solid)
- break;
-
- mapgen_set_node(pos, map_node_create(node == NODE_LAVA ? NODE_VULCANO_STONE : NODE_OAK_WOOD, wood_color), MGS_PLAYER, &changed_blocks);
- }
- }
-
- list_clear_func(&changed_blocks, &list_send_block, NULL);
-}
-
-// public functions
-
-// ServerMap singleton constructor
-void server_map_init()
-{
- server_map.map = map_create((MapCallbacks) {
- .create_block = &on_create_block,
- .delete_block = &on_delete_block,
- .get_block = &on_get_block,
- .set_node = &on_set_node,
- .after_set_node = &on_after_set_node,
- });
-
- server_map.cancel = false;
- server_map.mapgen_tasks = queue_create();
- server_map.mapgen_threads = malloc(sizeof *server_map.mapgen_threads * server_config.mapgen_threads);
- server_map.num_blocks = 0;
- pthread_mutex_init(&server_map.num_blocks_mtx, NULL);
-
- for (unsigned int i = 0; i < server_config.mapgen_threads; i++)
- pthread_create(&server_map.mapgen_threads[i], NULL, &mapgen_thread, NULL);
-}
-
-// ServerMap singleton destructor
-void server_map_deinit()
-{
- queue_finish(server_map.mapgen_tasks);
- server_map.cancel = true;
- queue_cancel(server_map.mapgen_tasks);
-
- for (unsigned int i = 0; i < server_config.mapgen_threads; i++)
- pthread_join(server_map.mapgen_threads[i], NULL);
- free(server_map.mapgen_threads);
-
- pthread_mutex_destroy(&server_map.num_blocks_mtx);
- queue_delete(server_map.mapgen_tasks);
- map_delete(server_map.map);
-}
-
-// handle block request from client (thread safe)
-void server_map_requested_block(ServerPlayer *player, v3s32 pos)
-{
- if (within_simulation_distance(player, pos, server_config.simulation_distance)) {
- MapBlock *block = map_get_block(server_map.map, pos, true);
-
- pthread_mutex_lock(&block->mtx);
-
- MapBlockExtraData *extra = block->extra;
- switch (extra->state) {
- case MBS_CREATED:
- generate_block(block);
- break;
-
- case MBS_GENERATING:
- break;
-
- case MBS_READY:
- send_block(player, block);
- };
-
- pthread_mutex_unlock(&block->mtx);
- }
-}
-
-static void update_percentage()
-{
- static s32 total = 3 * 3 * 21;
- static s32 done = -1;
- static s32 last_percentage = -1;
-
- if (done < total)
- done++;
-
- pthread_mutex_lock(&server_map.num_blocks_mtx);
- s32 percentage = 100.0 * (done - server_map.num_blocks) / total;
- pthread_mutex_unlock(&server_map.num_blocks_mtx);
-
- if (percentage > last_percentage) {
- last_percentage = percentage;
- printf("Preparing spawn... %d%%\n", percentage);
- }
-
-}
-
-// prepare spawn region
-void server_map_prepare_spawn()
-{
- update_percentage();
-
- for (s32 x = -1; x <= (s32) 1; x++) {
- for (s32 y = -10; y <= (s32) 10; y++) {
- for (s32 z = -1; z <= (s32) 1; z++) {
- if (interrupt->done)
- return;
-
- MapBlock *block = map_get_block(server_map.map, (v3s32) {x, y, z}, true);
-
- pthread_mutex_lock(&block->mtx);
- if (((MapBlockExtraData *) block->extra)->state == MBS_CREATED)
- generate_block(block);
- pthread_mutex_unlock(&block->mtx);
-
- update_percentage();
- }
- }
- }
-
- while (true) {
- pthread_mutex_lock(&server_map.num_blocks_mtx);
- bool done = (server_map.num_blocks == 0);
- pthread_mutex_unlock(&server_map.num_blocks_mtx);
-
- if (done)
- break;
-
- update_percentage();
- sched_yield();
- }
-
- s64 saved_spawn_height;
- if (database_load_meta("spawn_height", &saved_spawn_height)) {
- server_map.spawn_height = saved_spawn_height;
- } else {
- s32 spawn_height = -1;
-
- while (map_get_node(server_map.map, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR)
- ;
-
- server_map.spawn_height = spawn_height + 5;
- generate_spawn_hut();
- database_save_meta("spawn_height", server_map.spawn_height);
- }
-}
+++ /dev/null
-#ifndef _SERVER_MAP_H_
-#define _SERVER_MAP_H_
-
-#include <stdatomic.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <pthread.h>
-#include <dragonstd/queue.h>
-#include "map.h"
-#include "server/server_player.h"
-#include "types.h"
-
-typedef enum
-{
- MBS_CREATED, // block exists but was not yet generated
- MBS_GENERATING, // currently generating in a seperate thread
- MBS_READY, // generation finished
-} MapBlockState;
-
-typedef enum
-{
- MGS_VOID, // initial air, can be overridden by anything
- MGS_TERRAIN, // basic terrain, can be overridden by anything except the void
- MGS_BOULDERS, // boulders, replace terrain
- MGS_TREES, // trees replace boulders
- MGS_PLAYER, // player-placed nodes or things placed after map generation
-} MapgenStage;
-
-typedef struct {
- MapgenStage mgs;
- List *changed_blocks;
-} MapgenSetNodeArg;
-
-typedef struct
-{
- Blob data; // the big cum
- MapBlockState state; // generation state of the block
- pthread_t mapgen_thread; // thread that is generating block
- MapgenStageBuffer mgsb; // buffer to make sure mapgen only overrides things it should
-} MapBlockExtraData;
-
-extern struct ServerMap {
- atomic_bool cancel; // remove the smooth
- Map *map; // map object, data is stored here
- Queue *mapgen_tasks; // this is terry the fat shark
- pthread_t *mapgen_threads; // thread pool
- s32 spawn_height; // elevation to spawn players at
- unsigned int num_blocks; // number of enqueued / generating blocks
- pthread_mutex_t num_blocks_mtx; // lock to protect the above
-} server_map; // ServerMap singleton
-
-void server_map_init(); // ServerMap singleton constructor
-void server_map_deinit(); // ServerMap singleton destructor
-void server_map_requested_block(ServerPlayer *player, v3s32 pos); // handle block request from client (thread safe)
-void server_map_prepare_spawn(); // prepare spawn region
-
-#endif
+#include <dragonstd/map.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <pthread.h>
-#include <dragonstd/list.h>
+#include "day.h"
+#include "entity.h"
+#include "perlin.h"
#include "server/database.h"
#include "server/server_config.h"
-#include "server/server_map.h"
#include "server/server_player.h"
-#include "perlin.h"
-#include "day.h"
-#include "util.h"
-
-static bool shutting_down = false;
-static pthread_rwlock_t shutting_down_lock;
+#include "server/server_terrain.h"
-static List players;
-static pthread_rwlock_t players_lock;
+static Map players;
+static Map players_named;
-static List names;
-static pthread_rwlock_t names_lock;
+// main thread
+// called on server shutdown
+static void player_drop(ServerPlayer *player)
+{
+ pthread_rwlock_rdlock(&player->lock_peer);
+ pthread_t recv_thread = player->peer ? player->peer->recv_thread : 0;
+ pthread_rwlock_unlock(&player->lock_peer);
-static u64 next_id = 1;
+ server_player_disconnect(player);
+ if (recv_thread)
+ pthread_join(recv_thread, NULL);
-static bool list_compare_u64(u64 *p1, u64 *p2)
-{
- return *p1 == *p2;
+ refcount_drp(&player->rc); // map no longer has a reference to player
}
-static bool get_lock(pthread_rwlock_t *lock, bool write)
+// any thread
+// called when all refs have been dropped
+static void player_delete(ServerPlayer *player)
{
- pthread_rwlock_rdlock(&shutting_down_lock);
- if (shutting_down) {
- pthread_rwlock_unlock(&shutting_down_lock);
- return false;
- }
+ refcount_dst(&player->rc);
- if (write)
- pthread_rwlock_wrlock(lock);
- else
- pthread_rwlock_rdlock(lock);
+ pthread_rwlock_destroy(&player->lock_peer);
- pthread_rwlock_unlock(&shutting_down_lock);
- return true;
+ free(player->name);
+ pthread_rwlock_destroy(&player->lock_auth);
+
+ pthread_rwlock_destroy(&player->lock_pos);
+
+ free(player);
}
-void server_player_init()
+// recv thread
+// called when auth was successful
+static void player_spawn(ServerPlayer *player)
{
- pthread_rwlock_init(&shutting_down_lock, NULL);
+ // lock_pos has already been wrlocked by caller
+ if (!database_load_player(player->name, &player->pos, &player->rot)) {
+ player->pos = (v3f64) {0.0, server_terrain_spawn_height() + 0.5, 0.0};
+ player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+ database_create_player(player->name, player->pos, player->rot);
+ }
- players = list_create((void *) &list_compare_u64);
- pthread_rwlock_init(&players_lock, NULL);
+ // since this is recv thread, we don't need lock_peer
+ dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
+ .seed = seed,
+ .load_distance = server_config.load_distance,
+ });
+ dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
+ .time_of_day = get_time_of_day(),
+ });
+ dragonnet_peer_send_ToClientMovement(player->peer, &(ToClientMovement) {
+ .flight = false,
+ .collision = true,
+ .speed = server_config.movement.speed_normal,
+ .gravity = server_config.movement.gravity,
+ .jump = server_config.movement.jump,
+ });
+ dragonnet_peer_send_ToClientEntityAdd(player->peer, &(ToClientEntityAdd) {
+ .type = ENTITY_LOCALPLAYER,
+ .data = {
+ .id = player->id,
+ .pos = player->pos,
+ .rot = player->rot,
+ .box = {{-0.3f, 0.0f, -0.3f}, {0.3f, 1.75f, 0.3f}},
+ .eye = {0.0f, 1.75f, 0.0f},
+ .nametag = NULL,
+ },
+ });
+}
- names = list_create(&list_compare_string);
- pthread_rwlock_init(&names_lock, NULL);
+// any thread
+// called when adding, getting or removing a player from the map
+static int cmp_player_id(const Refcount *player, const u64 *id)
+{
+ return u64_cmp(&((ServerPlayer *) player->obj)->id, id);
}
-// list_clear_func callback used on server shutdown to disconnect all clients properly
-static void list_disconnect_player(void *key, unused void *value, unused void *arg)
+// any thread
+// called when adding, getting or removing a player from the players_named Map
+static int cmp_player_name(const Refcount *player, const char *name)
{
- ServerPlayer *player = key;
+ // names of players in players_named Map don't change, no lock_auth needed
+ return strcmp(((ServerPlayer *) player->obj)->name, name);
+}
- pthread_t recv_thread = player->peer->recv_thread;
- server_player_disconnect(player);
- pthread_join(recv_thread, NULL);
+// main thread
+// called on server startup
+void server_player_init()
+{
+ map_ini(&players);
+ map_ini(&players_named);
}
+// main thread
+// called on server shutdown
void server_player_deinit()
{
- pthread_rwlock_wrlock(&shutting_down_lock);
- shutting_down = true;
-
- pthread_rwlock_wrlock(&players_lock);
- pthread_rwlock_wrlock(&names_lock);
- pthread_rwlock_unlock(&shutting_down_lock);
-
- list_clear_func(&players, &list_disconnect_player, NULL);
- list_clear(&names);
-
- pthread_rwlock_destroy(&players_lock);
- pthread_rwlock_destroy(&names_lock);
- pthread_rwlock_destroy(&shutting_down_lock);
+ // just forget about name -> player mapping
+ map_cnl(&players_named, &refcount_drp, NULL, NULL, 0);
+ // disconnect players and forget about them
+ map_cnl(&players, &player_drop, NULL, &refcount_obj, 0);
}
+// accept thread
+// called on new connection
void server_player_add(DragonnetPeer *peer)
{
ServerPlayer *player = malloc(sizeof *player);
- player->id = next_id++;
+ // ID is allocated later in this function
+ player->id = 0;
+ refcount_ini(&player->rc, player, &player_delete);
+
player->peer = peer;
- pthread_rwlock_init(&player->ref, NULL);
+ pthread_rwlock_init(&player->lock_peer, NULL);
+
player->auth = false;
+ // use address as name until auth is done
player->name = dragonnet_addr_str(peer->raddr);
- pthread_rwlock_init(&player->auth_lock, NULL);
- player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
- pthread_rwlock_init(&player->pos_lock, NULL);
+ pthread_rwlock_init(&player->lock_auth, NULL);
- printf("Connected %s\n", player->name);
+ player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
+ player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+ pthread_rwlock_init(&player->lock_pos, NULL);
- // accept thread is joined before shutdown, we are guaranteed to obtain the lock
- pthread_rwlock_wrlock(&players_lock);
+ printf("[access] connected %s\n", player->name);
+ peer->extra = refcount_grb(&player->rc);
- list_put(&players, &player->id, player);
- peer->extra = player;
+ // keep the search tree somewhat balanced by using random IDs
+ // duplicate IDs are very unlikely, but it doesn't hurt to check
+ // make sure to avoid 0 since it's not a valid ID
+ while (!player->id || !map_add(&players, &player->id, &player->rc, &cmp_player_id, &refcount_inc))
+ player->id = random();
- pthread_rwlock_unlock(&players_lock);
+ // player has been grabbed by Map and peer
+ refcount_drp(&player->rc);
}
+// recv thread
+// called on connection close
void server_player_remove(DragonnetPeer *peer)
{
ServerPlayer *player = peer->extra;
-
- // only (this) recv thread will modify the auth or name fields, no rdlocks needed
-
- if (get_lock(&players_lock, true)) {
- list_delete(&players, &player->id);
- pthread_rwlock_unlock(&players_lock);
-
- printf("Disconnected %s\n", player->name);
- }
-
- if (player->auth && get_lock(&names_lock, true)) {
- list_delete(&names, player->name);
- pthread_rwlock_unlock(&names_lock);
- }
-
- pthread_rwlock_wrlock(&player->ref);
-
- free(player->name);
-
- pthread_rwlock_destroy(&player->ref);
- pthread_rwlock_destroy(&player->auth_lock);
- pthread_rwlock_destroy(&player->pos_lock);
-
- free(player);
-}
-
-u64 server_player_find(char *name)
-{
- if (! get_lock(&names_lock, false))
- return 0;
-
- u64 *id = list_get(&names, name);
- return id ? *id : 0;
+ peer->extra = NULL; // technically not necessary, but just in case
+
+ // peer will be deleted - forget about it!
+ pthread_rwlock_wrlock(&player->lock_peer);
+ player->peer = NULL;
+ pthread_rwlock_unlock(&player->lock_peer);
+
+ // only (this) recv thread will modify the auth or name fields, no lock_auth needed
+ // map_del returns false if it was canceled
+ // (we don't want disconnect messages for every player on server shutdown)
+ if (map_del(&players, &player->id, &cmp_player_id, &refcount_drp, NULL, NULL))
+ printf("[access] disconnected %s\n", player->name);
+ if (player->auth)
+ map_del(&players_named, player->name, &cmp_player_name, &refcount_drp, NULL, NULL);
+
+ // peer no longer has a reference to player
+ refcount_drp(&player->rc);
}
+// any thread
ServerPlayer *server_player_grab(u64 id)
{
- if (! id)
- return NULL;
-
- if (! get_lock(&players_lock, false))
- return NULL;
-
- ServerPlayer *player = list_get(&players, &id);
- if (player)
- pthread_rwlock_rdlock(&player->ref);
-
- pthread_rwlock_unlock(&players_lock);
-
- return player;
+ // 0 is an invalid ID
+ return id ? map_get(&players, &id, &cmp_player_id, &refcount_grb) : NULL;
}
-void server_player_drop(ServerPlayer *player)
+// any thread
+ServerPlayer *server_player_grab_named(char *name)
{
- pthread_rwlock_unlock(&player->ref);
+ // NULL is an invalid name
+ return name ? map_get(&players, name, &cmp_player_name, &refcount_grb) : NULL;
}
+// recv thread
+// called on recv auth packet
bool server_player_auth(ServerPlayer *player, char *name)
{
- if (! get_lock(&names_lock, true))
- return false;
+ pthread_rwlock_wrlock(&player->lock_auth);
+ pthread_rwlock_wrlock(&player->lock_pos);
+
+ // temporary change name, save old name to either free or reset it if auth fails
+ char *old_name = player->name;
+ player->name = name;
- pthread_rwlock_wrlock(&player->auth_lock);
- pthread_rwlock_wrlock(&player->pos_lock);
+ bool success = map_add(&players_named, player->name, &player->rc, &cmp_player_name, &refcount_inc);
- bool success = list_put(&names, name, &player->id);
+ printf("[access] authentication %s: %s -> %s\n", success ? "success" : "failure", old_name, player->name);
+ // since this is recv thread, we don't need lock_peer
dragonnet_peer_send_ToClientAuth(player->peer, &(ToClientAuth) {
.success = success,
});
if (success) {
- printf("Authentication %s: %s -> %s\n", success ? "success" : "failure", player->name, name);
-
- free(player->name);
- player->name = name;
+ free(old_name);
player->auth = true;
-
- if (! database_load_player(player->name, &player->pos)) {
- player->pos = (v3f64) {0.0, server_map.spawn_height + 0.5, 0.0};
- database_create_player(player->name, player->pos);
- }
-
- dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
- .seed = seed,
- .simulation_distance = server_config.simulation_distance,
- });
-
- dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
- .time_of_day = get_time_of_day(),
- });
-
- server_player_send_pos(player);
+ // load player from database and send some initial info
+ player_spawn(player);
+ } else {
+ player->name = old_name;
}
- pthread_rwlock_unlock(&player->pos_lock);
- pthread_rwlock_unlock(&player->auth_lock);
- pthread_rwlock_unlock(&names_lock);
-
+ pthread_rwlock_unlock(&player->lock_pos);
+ pthread_rwlock_unlock(&player->lock_auth);
return success;
}
+// any thread
void server_player_disconnect(ServerPlayer *player)
{
- dragonnet_peer_shutdown(player->peer);
+ pthread_rwlock_rdlock(&player->lock_peer);
+ // the recv thread will call server_player_remove when the connection was shut down
+ if (player->peer)
+ dragonnet_peer_shutdown(player->peer);
+ pthread_rwlock_unlock(&player->lock_peer);
}
-void server_player_send_pos(ServerPlayer *player)
+// any thread
+void server_player_iterate(void *func, void *arg)
{
- dragonnet_peer_send_ToClientPos(player->peer, & (ToClientPos) {
- .pos = player->pos,
- });
-}
-
-void server_player_iterate(void (cb)(ServerPlayer *, void *), void *arg)
-{
- if (! get_lock(&players_lock, false))
- return;
-
- ITERATE_LIST(&players, pair) {
- ServerPlayer *player = pair->value;
-
- pthread_rwlock_rdlock(&player->auth_lock);
- if (player->auth)
- cb(player, arg);
- pthread_rwlock_unlock(&player->auth_lock);
- }
-
- pthread_rwlock_unlock(&players_lock);
+ map_trv(&players_named, func, arg, &refcount_obj, TRAVERSION_INORDER);
}
/*
#ifndef _SERVER_PLAYER_H_
#define _SERVER_PLAYER_H_
+#include <dragonnet/peer.h>
+#include <dragonstd/refcount.h>
#include <pthread.h>
#include <stdbool.h>
-#include <dragonnet/peer.h>
#include "types.h"
-typedef struct
-{
- u64 id; // unique identifier
- DragonnetPeer *peer;
- pthread_rwlock_t ref; // programming socks make you 100% cuter
+typedef struct {
+ u64 id; // unique identifier
+ Refcount rc; // delete yourself if no one cares about you
- bool auth;
- char *name; // player name
- pthread_rwlock_t auth_lock; // why
+ DragonnetPeer *peer; // not to be confused with beer
+ pthread_rwlock_t lock_peer; // programming socks make you 100% cuter
- v3f64 pos; // player position
- pthread_rwlock_t pos_lock; // i want to commit die
+ bool auth; // YES OR NO I DEMAND AN ANSWER
+ char *name; // player name
+ pthread_rwlock_t lock_auth; // poggers based cringe
+
+ v3f64 pos; // player position
+ v3f32 rot; // you wont guess what this is
+ pthread_rwlock_t lock_pos; // git commit crime
} ServerPlayer;
void server_player_init();
void server_player_add(DragonnetPeer *peer);
void server_player_remove(DragonnetPeer *peer);
-u64 server_player_find(char *name);
-
ServerPlayer *server_player_grab(u64 id);
-void server_player_drop(ServerPlayer *player);
+ServerPlayer *server_player_grab_named(char *name);
bool server_player_auth(ServerPlayer *player, char *name);
void server_player_disconnect(ServerPlayer *player);
-void server_player_send_pos(ServerPlayer *player);
-void server_player_iterate(void (cb)(ServerPlayer *, void *), void *arg);
+void server_player_iterate(void *func, void *arg);
-#endif
+#endif // _SERVER_PLAYER_H_
--- /dev/null
+#define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <dragonstd/queue.h>
+#include <features.h>
+#include <stdatomic.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "interrupt.h"
+#include "server/database.h"
+#include "server/schematic.h"
+#include "server/server_config.h"
+#include "server/server_terrain.h"
+#include "server/terrain_gen.h"
+#include "terrain.h"
+
+// this file is too long
+Terrain *server_terrain;
+
+static atomic_bool cancel; // remove the smooth
+static Queue terrain_gen_tasks; // this is terry the fat shark
+static pthread_t *terrain_gen_threads; // thread pool
+static s32 spawn_height; // elevation to spawn players at
+static unsigned int num_gen_chunks; // number of enqueued / generating chunks
+static pthread_mutex_t mtx_num_gen_chunks; // lock to protect the above
+
+// utility functions
+
+// return true if a player is close enough to a chunk to access it
+static bool within_load_distance(ServerPlayer *player, v3s32 cpos, u32 dist)
+{
+ pthread_rwlock_rdlock(&player->lock_pos);
+ v3s32 ppos = terrain_node_to_chunk_pos((v3s32) {player->pos.x, player->pos.y, player->pos.z}, NULL);
+ pthread_rwlock_unlock(&player->lock_pos);
+
+ return abs(ppos.x - cpos.x) <= (s32) dist
+ && abs(ppos.y - cpos.y) <= (s32) dist
+ && abs(ppos.z - cpos.z) <= (s32) dist;
+}
+
+// send a chunk to a client and reset chunk request
+static void send_chunk(ServerPlayer *player, TerrainChunk *chunk)
+{
+ if (!within_load_distance(player, chunk->pos, server_config.load_distance))
+ return;
+
+ pthread_rwlock_rdlock(&player->lock_peer);
+ if (player->peer)
+ dragonnet_peer_send_ToClientChunk(player->peer, &(ToClientChunk) {
+ .pos = chunk->pos,
+ .data = ((TerrainChunkMeta *) chunk->extra)->data,
+ });
+ pthread_rwlock_unlock(&player->lock_peer);
+}
+
+// send chunk to near clients
+// chunk mutex has to be locked
+static void send_chunk_to_near(TerrainChunk *chunk)
+{
+ TerrainChunkMeta *meta = chunk->extra;
+
+ if (meta->state == CHUNK_GENERATING)
+ return;
+
+ Blob_free(&meta->data);
+ meta->data = terrain_serialize_chunk(chunk);
+
+ database_save_chunk(chunk);
+
+ if (meta->state == CHUNK_CREATED)
+ return;
+
+ server_player_iterate((void *) &send_chunk, chunk);
+}
+
+// Iterator for sending changed chunks to near clients
+static void iterator_send_chunk_to_near(TerrainChunk *chunk)
+{
+ pthread_mutex_lock(&chunk->mtx);
+ send_chunk_to_near(chunk);
+ pthread_mutex_unlock(&chunk->mtx);
+}
+
+// me when the
+static void terrain_gen_step()
+{
+ // big chunkus
+ TerrainChunk *chunk = queue_deq(&terrain_gen_tasks, NULL);
+
+ if (!chunk)
+ return;
+
+ TerrainChunkMeta *meta = chunk->extra;
+
+ List changed_chunks;
+ list_ini(&changed_chunks);
+ list_apd(&changed_chunks, chunk);
+
+ terrain_gen_chunk(chunk, &changed_chunks);
+
+ pthread_mutex_lock(&chunk->mtx);
+ meta->state = CHUNK_READY;
+ pthread_mutex_unlock(&chunk->mtx);
+
+ list_clr(&changed_chunks, (void *) &iterator_send_chunk_to_near, NULL, NULL);
+
+ pthread_mutex_lock(&mtx_num_gen_chunks);
+ num_gen_chunks--;
+ pthread_mutex_unlock(&mtx_num_gen_chunks);
+}
+
+// there was a time when i wrote actually useful comments lol
+static void *terrain_gen_thread()
+{
+#ifdef __GLIBC__ // check whether bloat is enabled
+ pthread_setname_np(pthread_self(), "terrain_gen");
+#endif // __GLIBC__
+
+ // extremely advanced logic
+ while (!cancel)
+ terrain_gen_step();
+
+ return NULL;
+}
+
+// enqueue chunk
+static void generate_chunk(TerrainChunk *chunk)
+{
+ if (cancel)
+ return;
+
+ pthread_mutex_lock(&mtx_num_gen_chunks);
+ num_gen_chunks++;
+ pthread_mutex_unlock(&mtx_num_gen_chunks);
+
+ TerrainChunkMeta *meta = chunk->extra;
+ meta->state = CHUNK_GENERATING;
+ queue_enq(&terrain_gen_tasks, chunk);
+}
+
+// terrain callbacks
+// note: all these functions require the chunk mutex to be locked, which is always the case when a terrain callback is invoked
+
+// callback for initializing a newly created chunk
+// load chunk from database or initialize state, tgstage buffer and data
+static void on_create_chunk(TerrainChunk *chunk)
+{
+ TerrainChunkMeta *meta = chunk->extra = malloc(sizeof *meta);
+
+ if (!database_load_chunk(chunk)) {
+ meta->state = CHUNK_CREATED;
+ meta->data = (Blob) {0, NULL};
+
+ CHUNK_ITERATE {
+ chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL});
+ meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID;
+ }
+ }
+}
+
+// callback for deleting a chunk
+// free meta data
+static void on_delete_chunk(TerrainChunk *chunk)
+{
+ TerrainChunkMeta *meta = chunk->extra;
+
+ Blob_free(&meta->data);
+ free(meta);
+}
+
+// callback for determining whether a chunk should be returned by terrain_get_chunk
+// hold back chunks that are not fully generated except when the create flag is set to true
+static bool on_get_chunk(TerrainChunk *chunk, bool create)
+{
+ TerrainChunkMeta *meta = chunk->extra;
+
+ if (meta->state < CHUNK_READY && !create)
+ return false;
+
+ return true;
+}
+
+// callback for deciding whether a set_node call succeeds or not
+// reject set_node calls that try to override nodes placed by later terraingen stages, else update tgs buffer - also make sure chunk is inserted into changed_chunks list
+static bool on_set_node(TerrainChunk *chunk, v3u8 offset, __attribute__((unused)) TerrainNode *node, void *_arg)
+{
+ TerrainSetNodeArg *arg = _arg;
+
+ TerrainGenStage new_tgs = arg ? arg->tgs : STAGE_PLAYER;
+ TerrainGenStage *tgs = &((TerrainChunkMeta *) chunk->extra)->
+ tgsb.raw.nodes[offset.x][offset.y][offset.z];
+
+ if (new_tgs >= *tgs) {
+ *tgs = new_tgs;
+
+ if (arg)
+ list_add(arg->changed_chunks, chunk, chunk, &cmp_ref, NULL);
+
+ return true;
+ }
+
+ return false;
+}
+
+// callback for when chunk content changes
+// send chunk to near clients if not part of terrain generation
+static void on_after_set_node(TerrainChunk *chunk, __attribute__((unused)) v3u8 offset, void *arg)
+{
+ if (!arg)
+ send_chunk_to_near(chunk);
+}
+
+// generate a hut for new players to spawn in
+static void generate_spawn_hut()
+{
+ List changed_chunks;
+ list_ini(&changed_chunks);
+
+ List spawn_hut;
+ schematic_load(&spawn_hut, RESSOURCE_PATH "schematics/spawn_hut.txt", (SchematicMapping[]) {
+ {
+ .color = {0x7d, 0x54, 0x35},
+ .type = NODE_OAK_WOOD,
+ .use_color = true,
+ },
+ {
+ .color = {0x50, 0x37, 0x28},
+ .type = NODE_OAK_WOOD,
+ .use_color = true,
+ },
+ }, 2);
+
+ schematic_place(&spawn_hut, (v3s32) {0, spawn_height, 0},
+ STAGE_PLAYER, &changed_chunks);
+
+ schematic_delete(&spawn_hut);
+
+ // dynamic part of spawn hut - cannot be generated by a schematic
+
+ v2s32 posts[6] = {
+ {-4, -2},
+ {-4, +3},
+ {+3, -2},
+ {+3, +3},
+ {+4, +0},
+ {+4, +1},
+ };
+
+ Blob wood_color = {0, NULL};
+ ColorData_write(&wood_color, &(ColorData) {{(f32) 0x7d / 0xff, (f32) 0x54 / 0xff, (f32) 0x35 / 0xff}});
+
+ for (int i = 0; i < 6; i++) {
+ for (s32 y = spawn_height - 1;; y--) {
+ v3s32 pos = {posts[i].x, y, posts[i].y};
+ NodeType node = terrain_get_node(server_terrain, pos).type;
+
+ if (i >= 4) {
+ if (node != NODE_AIR)
+ break;
+
+ pos.y++;
+ }
+
+ if (node_definitions[node].solid)
+ break;
+
+ server_terrain_gen_node(pos,
+ terrain_node_create(node == NODE_LAVA
+ ? NODE_VULCANO_STONE
+ : NODE_OAK_WOOD,
+ wood_color),
+ STAGE_PLAYER, &changed_chunks);
+ }
+ }
+
+ Blob_free(&wood_color);
+ list_clr(&changed_chunks, (void *) iterator_send_chunk_to_near, NULL, NULL);
+}
+
+// public functions
+
+// called on server startup
+void server_terrain_init()
+{
+ server_terrain = terrain_create();
+ server_terrain->callbacks.create_chunk = &on_create_chunk;
+ server_terrain->callbacks.delete_chunk = &on_delete_chunk;
+ server_terrain->callbacks.get_chunk = &on_get_chunk;
+ server_terrain->callbacks.set_node = &on_set_node;
+ server_terrain->callbacks.after_set_node = &on_after_set_node;
+
+ cancel = false;
+ queue_ini(&terrain_gen_tasks);
+ terrain_gen_threads = malloc(sizeof *terrain_gen_threads * server_config.terrain_gen_threads);
+ num_gen_chunks = 0;
+ pthread_mutex_init(&mtx_num_gen_chunks, NULL);
+
+ for (unsigned int i = 0; i < server_config.terrain_gen_threads; i++)
+ pthread_create(&terrain_gen_threads[i], NULL, (void *) &terrain_gen_thread, NULL);
+}
+
+// called on server shutdown
+void server_terrain_deinit()
+{
+ queue_fin(&terrain_gen_tasks);
+ cancel = true;
+ queue_cnl(&terrain_gen_tasks);
+
+ for (unsigned int i = 0; i < server_config.terrain_gen_threads; i++)
+ pthread_join(terrain_gen_threads[i], NULL);
+ free(terrain_gen_threads);
+
+ pthread_mutex_destroy(&mtx_num_gen_chunks);
+ queue_dst(&terrain_gen_tasks);
+ terrain_delete(server_terrain);
+}
+
+// handle chunk request from client (thread safe)
+void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos)
+{
+ if (within_load_distance(player, pos, server_config.load_distance)) {
+ TerrainChunk *chunk = terrain_get_chunk(server_terrain, pos, true);
+
+ pthread_mutex_lock(&chunk->mtx);
+
+ TerrainChunkMeta *meta = chunk->extra;
+ switch (meta->state) {
+ case CHUNK_CREATED:
+ generate_chunk(chunk);
+ break;
+
+ case CHUNK_GENERATING:
+ break;
+
+ case CHUNK_READY:
+ send_chunk(player, chunk);
+ };
+
+ pthread_mutex_unlock(&chunk->mtx);
+ }
+}
+
+static void update_percentage()
+{
+ static s32 total = 3 * 3 * 21;
+ static s32 done = -1;
+ static s32 last_percentage = -1;
+
+ if (done < total)
+ done++;
+
+ pthread_mutex_lock(&mtx_num_gen_chunks);
+ s32 percentage = 100.0 * (done - num_gen_chunks) / total;
+ pthread_mutex_unlock(&mtx_num_gen_chunks);
+
+ if (percentage > last_percentage) {
+ last_percentage = percentage;
+ printf("[verbose] preparing spawn... %d%%\n", percentage);
+ }
+
+}
+
+// prepare spawn region
+void server_terrain_prepare_spawn()
+{
+ update_percentage();
+
+ for (s32 x = -1; x <= (s32) 1; x++) {
+ for (s32 y = -10; y <= (s32) 10; y++) {
+ for (s32 z = -1; z <= (s32) 1; z++) {
+ if (interrupt.set)
+ return;
+
+ TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, true);
+
+ pthread_mutex_lock(&chunk->mtx);
+ if (((TerrainChunkMeta *) chunk->extra)->state == CHUNK_CREATED)
+ generate_chunk(chunk);
+ pthread_mutex_unlock(&chunk->mtx);
+
+ update_percentage();
+ }
+ }
+ }
+
+ for (;;) {
+ update_percentage();
+
+ pthread_mutex_lock(&mtx_num_gen_chunks);
+ bool done = (num_gen_chunks == 0);
+ pthread_mutex_unlock(&mtx_num_gen_chunks);
+
+ if (done)
+ break;
+
+ sched_yield();
+ }
+
+ s64 saved_spawn_height;
+ if (database_load_meta("spawn_height", &saved_spawn_height)) {
+ spawn_height = saved_spawn_height;
+ } else {
+ spawn_height = -1;
+ while (terrain_get_node(server_terrain, (v3s32) {0, ++spawn_height, 0}).type != NODE_AIR);
+ spawn_height += 5;
+
+ database_save_meta("spawn_height", spawn_height);
+ generate_spawn_hut();
+ }
+}
+
+void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks)
+{
+ TerrainSetNodeArg arg = {
+ .tgs = tgs,
+ .changed_chunks = changed_chunks,
+ };
+
+ terrain_set_node(server_terrain, pos, node, true, &arg);
+}
+
+s32 server_terrain_spawn_height()
+{
+ // wow, so useful!
+ return spawn_height;
+}
--- /dev/null
+#ifndef _SERVER_TERRAIN_H_
+#define _SERVER_TERRAIN_H_
+
+#include <pthread.h>
+#include "server/server_player.h"
+#include "terrain.h"
+#include "types.h"
+
+typedef enum {
+ CHUNK_CREATED, // chunk exists but was not yet generated
+ CHUNK_GENERATING, // currently generating in a seperate thread
+ CHUNK_READY, // generation finished
+} TerrainChunkState;
+
+typedef enum {
+ STAGE_VOID, // initial air, can be overridden by anything
+ STAGE_TERRAIN, // basic terrain, can be overridden by anything except the void
+ STAGE_BOULDERS, // boulders, replace terrain
+ STAGE_TREES, // trees replace boulders
+ STAGE_PLAYER, // player-placed nodes or things placed after terrain generation
+} TerrainGenStage;
+
+typedef struct {
+ TerrainGenStage tgs;
+ List *changed_chunks;
+} TerrainSetNodeArg;
+
+typedef struct {
+ Blob data; // the big cum
+ TerrainChunkState state; // generation state of the chunk
+ pthread_t gen_thread; // thread that is generating chunk
+ TerrainGenStageBuffer tgsb; // buffer to make sure terraingen only overrides things it should
+} TerrainChunkMeta; // OMG META VERSE WEB 3.0 VIRTUAL REALITY
+
+extern Terrain *server_terrain; // terrain object, data is stored here
+
+void server_terrain_init(); // called on server startup
+void server_terrain_deinit(); // called on server shutdown
+void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos); // handle chunk request from client (thread safe)
+void server_terrain_prepare_spawn(); // prepare spawn region
+void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks); // set node with terraingen stage
+s32 server_terrain_spawn_height(); // get the spawn height because idk
+
+#endif // _SERVER_TERRAIN_H_
--- /dev/null
+#include <math.h>
+#include <stdlib.h>
+#include "environment.h"
+#include "perlin.h"
+#include "server/biomes.h"
+#include "server/server_terrain.h"
+#include "server/terrain_gen.h"
+#include "server/trees.h"
+
+// generate a chunk (does not manage chunk state or threading)
+void terrain_gen_chunk(TerrainChunk *chunk, List *changed_chunks)
+{
+ TerrainChunkMeta *meta = chunk->extra;
+
+ BiomeArgsChunk chunk_args;
+ BiomeArgsRow row_args;
+ BiomeArgsHeight height_args;
+ BiomeArgsGenerate generate_args;
+ TreeArgsCondition condition_args;
+
+ chunk_args.chunk = condition_args.chunk = chunk;
+ chunk_args.changed_chunks = generate_args.changed_chunks = changed_chunks;
+
+ v3s32 chunkp = {
+ chunk->pos.x * CHUNK_SIZE,
+ chunk->pos.y * CHUNK_SIZE,
+ chunk->pos.z * CHUNK_SIZE,
+ };
+
+ unsigned char *chunk_data[COUNT_BIOME] = {NULL};
+ bool chunk_called[COUNT_BIOME] = {false};
+
+ for (u8 x = 0; x < CHUNK_SIZE; x++) {
+ s32 pos_x = chunkp.x + x;
+
+ for (u8 z = 0; z < CHUNK_SIZE; z++) {
+ row_args.pos = height_args.pos = (v2s32) {pos_x, chunkp.z + z};
+
+ condition_args.biome = get_biome(row_args.pos, &condition_args.factor);
+ BiomeDef *biome_def = &biomes[condition_args.biome];
+
+ height_args.factor = generate_args.factor = row_args.factor
+ = condition_args.factor;
+
+ if (biome_def->chunk_data_size && !chunk_data[condition_args.biome])
+ chunk_data[condition_args.biome] = malloc(biome_def->chunk_data_size);
+
+ chunk_args.chunk_data = row_args.chunk_data = height_args.chunk_data =
+ generate_args.chunk_data = condition_args.chunk_data =
+ chunk_data[condition_args.biome];
+
+ if (biome_def->chunk && !chunk_called[condition_args.biome]) {
+ biome_def->chunk(&chunk_args);
+ chunk_called[condition_args.biome] = true;
+ }
+
+ unsigned char row_data[biome_def->row_data_size];
+ row_args.row_data = height_args.row_data = generate_args.row_data =
+ condition_args.row_data = row_data;
+
+ if (biome_def->row)
+ biome_def->row(&row_args);
+
+ height_args.height = 1.0
+ * (pnoise2d(U32(height_args.pos.x) / 32.0, U32(height_args.pos.y) / 32.0, 0.45, 5, seed + SO_HEIGHT) * 16.0 + 0.0)
+ * (pnoise2d(U32(height_args.pos.x) / 256.0, U32(height_args.pos.y) / 256.0, 0.45, 5, seed + SO_HILLYNESS) * 0.5 + 0.5)
+ + 32.0;
+
+ s32 height = biome_def->height(&height_args);
+
+ for (u8 y = 0; y < CHUNK_SIZE; y++) {
+ generate_args.pos = condition_args.pos = (v3s32)
+ {row_args.pos.x, chunkp.y + y, row_args.pos.y};
+ generate_args.diff = generate_args.pos.y - height;
+
+ generate_args.humidity = condition_args.humidity =
+ get_humidity(generate_args.pos);
+ generate_args.temperature = condition_args.temperature =
+ get_temperature(generate_args.pos);
+
+ NodeType node = biome_def->generate(&generate_args);
+
+ if (biome_def->snow
+ && generate_args.diff <= 1
+ && generate_args.temperature < 0.0
+ && node == NODE_AIR)
+ node = NODE_SNOW;
+
+ if (generate_args.diff == 1) for (int i = 0; i < NUM_TREES; i++) {
+ TreeDef *def = &tree_definitions[i];
+
+ if (def->condition(&condition_args)
+ && noise2d(condition_args.pos.x, condition_args.pos.z, 0, seed + def->offset) * 0.5 + 0.5 < def->probability
+ && smooth2d(U32(condition_args.pos.x) / def->spread, U32(condition_args.pos.z) / def->spread, 0, seed + def->area_offset) * 0.5 + 0.5 < def->area_probability) {
+ def->generate(condition_args.pos, changed_chunks);
+ break;
+ }
+ }
+
+ pthread_mutex_lock(&chunk->mtx);
+ if (meta->tgsb.raw.nodes[x][y][z] <= STAGE_TERRAIN) {
+ chunk->data[x][y][z] = terrain_node_create(node, (Blob) {0, NULL});
+ meta->tgsb.raw.nodes[x][y][z] = STAGE_TERRAIN;
+ }
+ pthread_mutex_unlock(&chunk->mtx);
+ }
+ }
+ }
+
+ for (Biome i = 0; i < COUNT_BIOME; i++)
+ if (chunk_data[i])
+ free(chunk_data[i]);
+}
--- /dev/null
+#ifndef _TERRAIN_GEN_H_
+#define _TERRAIN_GEN_H_
+
+#include "server/server_terrain.h"
+#include "terrain.h"
+
+void terrain_gen_chunk(TerrainChunk *chunk, List *changed_chunks); // generate a chunk (does not manage chunk state or threading)
+
+#endif // _TERRAIN_GEN_H_
-#include <stdio.h>
#include <stdlib.h>
#include "server/biomes.h"
-#include "server/mapgen.h"
+#include "server/server_terrain.h"
#include "server/trees.h"
#include "server/voxelctx.h"
-#include "util.h"
// oak
-static bool oak_condition(unused v3s32 pos, unused f64 humidity, unused f64 temperature, Biome biome, unused f64 factor, unused MapBlock *block, unused void *row_data, unused void *block_data)
+static bool oak_condition(TreeArgsCondition *args)
{
- return biome == BIOME_HILLS;
+ return args->biome == BIOME_HILLS;
}
static void oak_tree_leaf(Voxelctx *ctx)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
voxelctx_push(ctx);
static void oak_tree_top(Voxelctx *ctx)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
voxelctx_push(ctx);
static void oak_tree_part(Voxelctx *ctx, f32 n)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
voxelctx_push(ctx);
voxelctx_pop(ctx);
}
-static void oak_tree(v3s32 pos, List *changed_blocks)
+static void oak_tree(v3s32 pos, List *changed_chunks)
{
- Voxelctx *ctx = voxelctx_create(changed_blocks, MGS_TREES, pos);
+ Voxelctx *ctx = voxelctx_create(changed_chunks, STAGE_TREES, pos);
voxelctx_hue(ctx, 40.0f);
voxelctx_light(ctx, -0.5f);
// pine
-static bool pine_condition(unused v3s32 pos, unused f64 humidity, unused f64 temperature, Biome biome, unused f64 factor, unused MapBlock *block, unused void *row_data, unused void *block_data)
+static bool pine_condition(TreeArgsCondition *args)
{
- return biome == BIOME_MOUNTAIN;
+ return args->biome == BIOME_MOUNTAIN;
}
-static void pine_tree(v3s32 pos, List *changed_blocks)
+static void pine_tree(v3s32 pos, List *changed_chunks)
{
s32 tree_top = (noise2d(pos.x, pos.z, 0, seed + SO_PINETREE_HEIGHT) * 0.5 + 0.5) * (35.0 - 20.0) + 20.0 + pos.y;
for (v3s32 tree_pos = pos; tree_pos.y < tree_top; tree_pos.y++) {
s32 dir = (noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + SO_PINETREE_BRANCH_DIR) * 0.5 + 0.5) * 4.0;
for (v3s32 branch_pos = tree_pos; branch_length > 0; branch_length--, branch_pos = v3s32_add(branch_pos, dirs[dir]))
- mapgen_set_node(branch_pos, map_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}), MGS_TREES, changed_blocks);
+ server_terrain_gen_node(branch_pos,
+ terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}),
+ STAGE_TREES, changed_chunks);
- mapgen_set_node(tree_pos, map_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}), MGS_TREES, changed_blocks);
+ server_terrain_gen_node(tree_pos,
+ terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}),
+ STAGE_TREES, changed_chunks);
}
}
// palm
-static bool palm_condition(v3s32 pos, unused f64 humidity, unused f64 temperature, Biome biome, unused f64 factor, unused MapBlock *block, void *row_data, unused void *block_data)
+static bool palm_condition(TreeArgsCondition *args)
{
- return biome == BIOME_OCEAN
- && ocean_get_node_at((v3s32) {pos.x, pos.y - 0, pos.z}, 1, row_data) == NODE_AIR
- && ocean_get_node_at((v3s32) {pos.x, pos.y - 1, pos.z}, 0, row_data) == NODE_SAND;
+ return args->biome == BIOME_OCEAN
+ && ocean_get_node_at((v3s32) {args->pos.x, args->pos.y - 0, args->pos.z}, 1, args->row_data) == NODE_AIR
+ && ocean_get_node_at((v3s32) {args->pos.x, args->pos.y - 1, args->pos.z}, 0, args->row_data) == NODE_SAND;
}
static void palm_branch(Voxelctx *ctx)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
voxelctx_cube(ctx, NODE_PALM_LEAVES, true);
voxelctx_pop(ctx);
}
-static void palm_tree(v3s32 pos, List *changed_blocks)
+static void palm_tree(v3s32 pos, List *changed_chunks)
{
- Voxelctx *ctx = voxelctx_create(changed_blocks, MGS_TREES, (v3s32) {pos.x, pos.y - 1, pos.z});
+ Voxelctx *ctx = voxelctx_create(changed_chunks, STAGE_TREES, (v3s32) {pos.x, pos.y - 1, pos.z});
f32 s = voxelctx_random(ctx, 8.0f, 2.0f);
#define _TREES_H_
#include <dragonstd/list.h>
+#include <stdbool.h>
#include "perlin.h"
+#include "terrain.h"
#include "types.h"
#define NUM_TREES 3
-typedef struct
-{
+typedef struct {
+ v3s32 pos;
+ f64 humidity;
+ f64 temperature;
+ Biome biome;
+ f64 factor;
+ TerrainChunk *chunk;
+ void *row_data;
+ void *chunk_data;
+} TreeArgsCondition;
+
+typedef struct {
f32 spread;
f32 probability;
f32 area_probability;
SeedOffset offset;
SeedOffset area_offset;
- bool (*condition)(v3s32 pos, f64 humidity, f64 temperature, Biome biome, f64 factor, MapBlock *block, void *row_data, void *block_data);
- void (*generate)(v3s32 pos, List *changed_blocks);
+ bool (*condition)(TreeArgsCondition *args);
+ void (*generate)(v3s32 pos, List *changed_chunks);
} TreeDef;
extern TreeDef tree_definitions[];
-#endif
+#endif // _TREES_H_
#include <stdlib.h>
#include <pthread.h>
-#include "server/mapgen.h"
-#include "server/voxelctx.h"
+#include "color.h"
#include "perlin.h"
-#include "util.h"
+#include "server/terrain_gen.h"
+#include "server/voxelctx.h"
static VoxelctxState *create_state(VoxelctxState *old)
{
- VoxelctxState *state = malloc(sizeof(VoxelctxState));
+ VoxelctxState *state = malloc(sizeof *state);
if (old) {
*state = *old;
return state;
}
-Voxelctx *voxelctx_create(List *changed_blocks, MapgenStage mgs, v3s32 pos)
+Voxelctx *voxelctx_create(List *changed_chunks, TerrainGenStage tgs, v3s32 pos)
{
Voxelctx *ctx = malloc(sizeof(Voxelctx));
- ctx->changed_blocks = changed_blocks;
- ctx->mgs = mgs;
+ ctx->changed_chunks = changed_chunks;
+ ctx->tgs = tgs;
ctx->pos = pos;
- ctx->statestack = list_create(NULL);
+ list_ini(&ctx->statestack);
ctx->random = 0;
- list_put(&ctx->statestack, create_state(NULL), NULL);
+ list_apd(&ctx->statestack, create_state(NULL));
return ctx;
}
-static void list_delete_state(void *key, unused void *value, unused void *arg)
-{
- free(key);
-}
-
void voxelctx_delete(Voxelctx *ctx)
{
- list_clear_func(&ctx->statestack, &list_delete_state, NULL);
+ list_clr(&ctx->statestack, (void *) &free, NULL, NULL);
free(ctx);
}
void voxelctx_pop(Voxelctx *ctx)
{
- ListPair *next = ctx->statestack.first->next;
- free(ctx->statestack.first->key);
- free(ctx->statestack.first);
- ctx->statestack.first = next;
+ free(ctx->statestack.fst->dat);
+ list_nrm(&ctx->statestack, &ctx->statestack.fst);
}
void voxelctx_push(Voxelctx *ctx)
{
- ListPair *pair = malloc(sizeof(ListPair));
- pair->key = create_state(&VOXELCTXSTATE(ctx));
- pair->value = NULL;
- pair->next = ctx->statestack.first;
-
- ctx->statestack.first = pair;
+ list_ppd(&ctx->statestack, create_state(&VOXELCTXSTATE(ctx)));
}
bool voxelctx_is_alive(Voxelctx *ctx)
return VOXELCTXSTATE(ctx).scale[0] >= 1.0f && VOXELCTXSTATE(ctx).scale[1] >= 1.0f && VOXELCTXSTATE(ctx).scale[2] >= 1.0f;
}
-void voxelctx_cube(Voxelctx *ctx, Node node, bool use_color)
+void voxelctx_cube(Voxelctx *ctx, NodeType node, bool use_color)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
vec4 base_corners[8] = {
Blob buffer = {0, NULL};
if (use_color)
- HSLData_write(&buffer, &(HSLData) {{
+ ColorData_write(&buffer, &(ColorData) {hsl_to_rgb((v3f32) {
VOXELCTXSTATE(ctx).h / 360.0,
VOXELCTXSTATE(ctx).s,
VOXELCTXSTATE(ctx).l,
- }});
+ })});
- mapgen_set_node(
+ server_terrain_gen_node(
v3s32_add(ctx->pos, (v3s32) {v[0], v[2], v[1]}),
- map_node_create(node, buffer),
- ctx->mgs,
- ctx->changed_blocks
+ terrain_node_create(node, buffer),
+ ctx->tgs,
+ ctx->changed_chunks
);
+
+ Blob_free(&buffer);
}
}
-void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_color)
+void voxelctx_cylinder(Voxelctx *ctx, NodeType node, bool use_color)
{
voxelctx_cube(ctx, node, use_color);
}
/*
void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_color)
{
- if (! voxelctx_is_alive(ctx))
+ if (!voxelctx_is_alive(ctx))
return;
return;
ctx->pos.x + round(VOXELCTXSTATE(ctx).pos[0] + x),
ctx->pos.y + round(VOXELCTXSTATE(ctx).pos[2] + z),
ctx->pos.z + round(VOXELCTXSTATE(ctx).pos[1] + y),
- }, CREATE_NODE, ctx->mgs, ctx->changed_blocks);
+ }, CREATE_NODE, ctx->tgs, ctx->changed_chunks);
}
}
}
#ifndef _VOXELCTX_H_
#define _VOXELCTX_H_
-#define VOXELCTXSTATE(ctx) (*((VoxelctxState *) (ctx)->statestack.first->key))
+#define VOXELCTXSTATE(ctx) (*((VoxelctxState *) (ctx)->statestack.fst->dat))
-#include <linmath.h/linmath.h>
#include <dragonstd/list.h>
+#include <linmath.h/linmath.h>
+#include "server/server_terrain.h"
#include "types.h"
-#include "server/server_map.h"
-typedef struct
-{
+typedef struct {
vec4 pos;
vec3 scale;
mat4x4 mat;
s32 life;
} VoxelctxState;
-typedef struct
-{
+typedef struct {
v3s32 pos;
- List *changed_blocks;
- MapgenStage mgs;
+ List *changed_chunks;
+ TerrainGenStage tgs;
List statestack;
s32 random;
} Voxelctx;
-Voxelctx *voxelctx_create(List *changed_blocks, MapgenStage mgs, v3s32 pos);
+Voxelctx *voxelctx_create(List *changed_chunks, TerrainGenStage tgs, v3s32 pos);
void voxelctx_delete(Voxelctx *ctx);
void voxelctx_hue(Voxelctx *ctx, f32 value);
void voxelctx_sat(Voxelctx *ctx, f32 value);
void voxelctx_pop(Voxelctx *ctx);
void voxelctx_push(Voxelctx *ctx);
bool voxelctx_is_alive(Voxelctx *ctx);
-void voxelctx_cube(Voxelctx *ctx, Node node, bool use_hsl);
-void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_hsl);
+void voxelctx_cube(Voxelctx *ctx, NodeType node, bool use_color);
+void voxelctx_cylinder(Voxelctx *ctx, NodeType node, bool use_color);
f32 voxelctx_random(Voxelctx *ctx, f32 base, f32 vary);
-#endif
+#endif // _VOXELCTX_H_
--- /dev/null
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "terrain.h"
+
+static void delete_chunk(TerrainChunk *chunk, Terrain *terrain)
+{
+ if (terrain->callbacks.delete_chunk)
+ terrain->callbacks.delete_chunk(chunk);
+
+ terrain_free_chunk(chunk);
+}
+
+static void delete_sector(TerrainSector *sector, Terrain *terrain)
+{
+ tree_clr(§or->chunks, (void *) &delete_chunk, terrain, NULL, 0);
+ pthread_rwlock_destroy(§or->lock);
+ free(sector);
+}
+
+Terrain *terrain_create()
+{
+ Terrain *terrain = malloc(sizeof *terrain);
+ tree_ini(&terrain->sectors);
+ pthread_rwlock_init(&terrain->lock, NULL);
+ terrain->cache = NULL;
+ pthread_rwlock_init(&terrain->cache_lock, NULL);
+ return terrain;
+}
+
+void terrain_delete(Terrain *terrain)
+{
+ tree_clr(&terrain->sectors, (void *) &delete_sector, terrain, NULL, 0);
+ pthread_rwlock_destroy(&terrain->lock);
+ pthread_rwlock_destroy(&terrain->cache_lock);
+ free(terrain);
+}
+
+TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create)
+{
+ if (create)
+ pthread_rwlock_wrlock(&terrain->lock);
+ else
+ pthread_rwlock_rdlock(&terrain->lock);
+
+ TreeNode **loc = tree_nfd(&terrain->sectors, &pos, &v2s32_cmp);
+ TerrainSector *sector = NULL;
+
+ if (*loc) {
+ sector = (*loc)->dat;
+ } else if (create) {
+ sector = malloc(sizeof *sector);
+ sector->pos = pos;
+ tree_ini(§or->chunks);
+ pthread_rwlock_init(§or->lock, NULL);
+
+ tree_nmk(&terrain->sectors, loc, sector);
+ }
+
+ pthread_rwlock_unlock(&terrain->lock);
+
+ return sector;
+}
+
+TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create)
+{
+ TerrainChunk *cache = NULL;
+
+ pthread_rwlock_rdlock(&terrain->cache_lock);
+ cache = terrain->cache;
+ pthread_rwlock_unlock(&terrain->cache_lock);
+
+ if (cache && v3s32_equals(cache->pos, pos))
+ return cache;
+
+ TerrainSector *sector = terrain_get_sector(terrain, (v2s32) {pos.x, pos.z}, create);
+ if (!sector)
+ return NULL;
+
+ if (create)
+ pthread_rwlock_wrlock(§or->lock);
+ else
+ pthread_rwlock_rdlock(§or->lock);
+
+ TreeNode **loc = tree_nfd(§or->chunks, &pos.y, &s32_cmp);
+ TerrainChunk *chunk = NULL;
+
+ if (*loc) {
+ chunk = (*loc)->dat;
+
+ pthread_mutex_lock(&chunk->mtx);
+ if (terrain->callbacks.get_chunk && !terrain->callbacks.get_chunk(chunk, create)) {
+ pthread_mutex_unlock(&chunk->mtx);
+ chunk = NULL;
+ } else {
+ pthread_mutex_unlock(&chunk->mtx);
+ pthread_rwlock_wrlock(&terrain->cache_lock);
+ terrain->cache = chunk;
+ pthread_rwlock_unlock(&terrain->cache_lock);
+ }
+ } else if (create) {
+ tree_nmk(§or->chunks, loc, chunk = terrain_allocate_chunk(pos));
+
+ if (terrain->callbacks.create_chunk)
+ terrain->callbacks.create_chunk(chunk);
+ }
+
+ pthread_rwlock_unlock(§or->lock);
+
+ return chunk;
+}
+
+TerrainChunk *terrain_allocate_chunk(v3s32 pos)
+{
+ TerrainChunk *chunk = malloc(sizeof * chunk);
+ chunk->level = pos.y;
+ chunk->pos = pos;
+ chunk->extra = NULL;
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&chunk->mtx, &attr);
+
+ CHUNK_ITERATE
+ chunk->data[x][y][z] = terrain_node_create(NODE_UNKNOWN, (Blob) {0, NULL});
+
+ return chunk;
+}
+
+void terrain_free_chunk(TerrainChunk *chunk)
+{
+ CHUNK_ITERATE
+ terrain_node_delete(chunk->data[x][y][z]);
+
+ pthread_mutex_destroy(&chunk->mtx);
+ free(chunk);
+}
+
+Blob terrain_serialize_chunk(TerrainChunk *chunk)
+{
+ bool empty = true;
+
+ CHUNK_ITERATE {
+ if (chunk->data[x][y][z].type != NODE_AIR) {
+ empty = false;
+ break;
+ }
+ }
+
+ if (empty)
+ return (Blob) {0, NULL};
+
+ SerializedTerrainChunk chunk_data;
+
+ CHUNK_ITERATE {
+ TerrainNode *node = &chunk->data[x][y][z];
+ SerializedTerrainNode *node_data = &chunk_data.raw.nodes[x][y][z];
+
+ *node_data = (SerializedTerrainNode) {
+ .type = node->type,
+ .data = {
+ .siz = 0,
+ .data = NULL,
+ },
+ };
+
+ NodeDefinition *def = &node_definitions[node->type];
+
+ if (def->serialize)
+ def->serialize(&node_data->data, node->data);
+ }
+
+ Blob buffer = {0, NULL};
+ SerializedTerrainChunk_write(&buffer, &chunk_data);
+ SerializedTerrainChunk_free(&chunk_data);
+
+ return buffer;
+}
+
+bool terrain_deserialize_chunk(TerrainChunk *chunk, Blob buffer)
+{
+ if (buffer.siz == 0) {
+ CHUNK_ITERATE
+ chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL});
+
+ return true;
+ }
+
+ // it's important to copy Blobs that have been malloc'd before reading from them
+ // because reading from a Blob modifies its data and size pointer,
+ // but does not free anything
+ SerializedTerrainChunk chunk_data = {0};
+ bool success = SerializedTerrainChunk_read(&buffer, &chunk_data);
+
+ if (success) CHUNK_ITERATE
+ chunk->data[x][y][z] = terrain_node_create(chunk_data.raw.nodes[x][y][z].type, chunk_data.raw.nodes[x][y][z].data);
+
+ SerializedTerrainChunk_free(&chunk_data);
+ return success;
+}
+
+v3s32 terrain_node_to_chunk_pos(v3s32 pos, v3u8 *offset)
+{
+ if (offset)
+ *offset = (v3u8) {(u32) pos.x % CHUNK_SIZE, (u32) pos.y % CHUNK_SIZE, (u32) pos.z % CHUNK_SIZE};
+ return (v3s32) {floor((double) pos.x / (double) CHUNK_SIZE), floor((double) pos.y / (double) CHUNK_SIZE), floor((double) pos.z / (double) CHUNK_SIZE)};
+}
+
+TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos)
+{
+ v3u8 offset;
+ v3s32 chunkpos = terrain_node_to_chunk_pos(pos, &offset);
+ TerrainChunk *chunk = terrain_get_chunk(terrain, chunkpos, false);
+ if (!chunk)
+ return terrain_node_create(NODE_UNLOADED, (Blob) {0, NULL});
+ return chunk->data[offset.x][offset.y][offset.z];
+}
+
+void terrain_set_node(Terrain *terrain, v3s32 pos, TerrainNode node, bool create, void *arg)
+{
+ v3u8 offset;
+ TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_node_to_chunk_pos(pos, &offset), create);
+
+ if (!chunk)
+ return;
+
+ pthread_mutex_lock(&chunk->mtx);
+ if (!terrain->callbacks.set_node || terrain->callbacks.set_node(chunk, offset, &node, arg)) {
+ chunk->data[offset.x][offset.y][offset.z] = node;
+ if (terrain->callbacks.after_set_node)
+ terrain->callbacks.after_set_node(chunk, offset, arg);
+ } else {
+ terrain_node_delete(node);
+ }
+ pthread_mutex_unlock(&chunk->mtx);
+}
+
+TerrainNode terrain_node_create(NodeType type, Blob buffer)
+{
+ if (type >= NODE_UNLOADED)
+ type = NODE_UNKNOWN;
+
+ NodeDefinition *def = &node_definitions[type];
+
+ TerrainNode node;
+ node.type = type;
+ node.data = def->data_size ? malloc(def->data_size) : NULL;
+
+ if (def->create)
+ def->create(&node);
+
+ if (def->deserialize)
+ def->deserialize(&buffer, node.data);
+
+ return node;
+}
+
+void terrain_node_delete(TerrainNode node)
+{
+ NodeDefinition *def = &node_definitions[node.type];
+
+ if (def->delete)
+ def->delete(&node);
+
+ if (node.data)
+ free(node.data);
+}
--- /dev/null
+#ifndef _TERRAIN_H_
+#define _TERRAIN_H_
+
+#include <dragonstd/list.h>
+#include <dragonstd/tree.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include "node.h"
+#include "types.h"
+
+#define CHUNK_ITERATE \
+ for (u8 x = 0; x < CHUNK_SIZE; x++) \
+ for (u8 y = 0; y < CHUNK_SIZE; y++) \
+ for (u8 z = 0; z < CHUNK_SIZE; z++)
+
+typedef struct TerrainNode {
+ NodeType type;
+ void *data;
+} TerrainNode;
+
+typedef TerrainNode TerrainChunkData[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
+
+typedef struct {
+ s32 level;
+ v3s32 pos;
+ TerrainChunkData data;
+ void *extra;
+ pthread_mutex_t mtx;
+} TerrainChunk;
+
+typedef struct
+{
+ v2s32 pos;
+ Tree chunks;
+ pthread_rwlock_t lock;
+} TerrainSector;
+
+typedef struct {
+ Tree sectors;
+ pthread_rwlock_t lock;
+ TerrainChunk *cache;
+ pthread_rwlock_t cache_lock;
+ struct {
+ void (*create_chunk)(TerrainChunk *chunk);
+ void (*delete_chunk)(TerrainChunk *chunk);
+ bool (*get_chunk)(TerrainChunk *chunk, bool create);
+ bool (*set_node) (TerrainChunk *chunk, v3u8 offset, TerrainNode *node, void *arg);
+ void (*after_set_node)(TerrainChunk *chunk, v3u8 offset, void *arg);
+ } callbacks;
+} Terrain;
+
+Terrain *terrain_create();
+void terrain_delete(Terrain *terrain);
+
+TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create);
+TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create);
+
+TerrainChunk *terrain_allocate_chunk(v3s32 pos);
+void terrain_free_chunk(TerrainChunk *chunk);
+
+Blob terrain_serialize_chunk(TerrainChunk *chunk);
+bool terrain_deserialize_chunk(TerrainChunk *chunk, Blob buffer);
+
+v3s32 terrain_node_to_chunk_pos(v3s32 pos, v3u8 *offset);
+
+TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos);
+void terrain_set_node(Terrain *terrain, v3s32 pos, TerrainNode node, bool create, void *arg);
+
+TerrainNode terrain_node_create(NodeType type, Blob buffer);
+void terrain_node_delete(TerrainNode node);
+
+#endif
-#define MAPBLOCK_SIZE 16
+#define CHUNK_SIZE 16
-HSLData
+ColorData
v3f32 color
-SerializedMapNode
+SerializedTerrainNode
u32 type
Blob data
-SerializedMapBlockRaw
- SerializedMapNode[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE] nodes
+SerializedTerrainChunkRaw
+ SerializedTerrainNode[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE] nodes
-SerializedMapBlock
- compressed SerializedMapBlockRaw raw
+SerializedTerrainChunk
+ compressed SerializedTerrainChunkRaw raw
-MapgenStageBufferRaw
- u32[MAPBLOCK_SIZE][MAPBLOCK_SIZE][MAPBLOCK_SIZE] nodes
+TerrainGenStageBufferRaw
+ u32[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE] nodes
-MapgenStageBuffer
- compressed MapgenStageBufferRaw raw
+TerrainGenStageBuffer
+ compressed TerrainGenStageBufferRaw raw
+
+EntityData
+ u64 id
+ v3f64 pos
+ v3f32 rot
+ aabb3f32 box
+ v3f32 eye
+ String nametag
+
+; server packets
pkt ToServerAuth
String name
v3s32 pos
u32 node
-pkt ToServerPos
+pkt ToServerPosRot
v3f64 pos
+ v3f32 rot
-pkt ToServerRequestBlock
+pkt ToServerRequestChunk
v3s32 pos
+pkt ToServerRequestMovement
+ u8 flight
+ u8 collision
+
+; client packets
+
pkt ToClientAuth
u8 success
-pkt ToClientBlock
+pkt ToClientChunk
v3s32 pos
Blob data
pkt ToClientInfo
- u32 simulation_distance
+ u32 load_distance
s32 seed
-pkt ToClientPos
- v3f64 pos
-
pkt ToClientTimeOfDay
u64 time_of_day
+
+pkt ToClientMovement
+ u8 flight
+ u8 collision
+ f32 speed
+ f32 jump
+ f32 gravity
+
+pkt ToClientEntityAdd
+ u32 type
+ EntityData data
+
+pkt ToClientEntityRemove
+ u64 id
+
+pkt ToClientEntityUpdatePosRot
+ u64 id
+ v3f64 pos
+ v3f32 rot
+
+pkt ToClientEntityUpdateBoxEye
+ u64 id
+ aabb3f32 box
+ v3f32 eye
+
+pkt ToClientEntityUpdateNametag
+ u64 id
+ String nametag
+++ /dev/null
-#include <stdarg.h>
-#include <dragonport/asprintf.h>
-
-char *format_string(const char *format, ...)
-{
- va_list args;
- va_start(args, format);
- char *ptr;
- vasprintf(&ptr, format, args);
- va_end(args);
- return ptr;
-}
+++ /dev/null
-#ifndef _UTIL_H_
-#define _UTIL_H_
-
-#define CMPBOUNDS(x) ((x) == 0 ? 0 : (x) > 0 ? 1 : -1) // resolves to 1 if x > 0, 0 if x == 0 and -1 if x < 0
-#define fallthrough __attribute__ ((fallthrough)) // prevent compiler warning about implicit fallthrough with style
-#define unused __attribute__ ((unused))
-#define U32(x) (((u32) 1 << 31) + (x))
-
-char *format_string(const char *format, ...);
-
-#endif
-#! /bin/bash
+#!/bin/bash
VERSION=`git tag --points-at HEAD`
IS_RELEASE="1"
if [[ $VERSION = "" ]]; then
-F "secret=$SECRET" \
-F "name=$VERSION" \
-F "is_release=$IS_RELEASE" \
- -F "build=@DragonblocksAlpha-$VERSION.zip" \
+ -F "build=@dragonblocks_alpha-$VERSION.zip" \
https://elidragon.tk/dragonblocks_alpha/upload.php