From: Elias Fleckenstein Date: Fri, 15 Apr 2022 21:45:01 +0000 (+0200) Subject: refactoring X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=f931fe22ecf29461e2fc3fced222c1ee0d083ff9;p=dragonblocks_alpha.git refactoring --- diff --git a/.gitignore b/.gitignore index ce6db77..4eb449c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,10 @@ CTestTestfile.cmake _deps # Binaries -Dragonblocks -DragonblocksServer -DragonblocksAlpha-*.zip -DragonblocksAlpha +dragonblocks +dragonblocks_server +dragonblocks_alpha-*.zip +dragonblocks_alpha # Data client.conf diff --git a/.gitmodules b/.gitmodules index ae8eb62..6ced1b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,15 +13,15 @@ [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 diff --git a/README.md b/README.md index 529bb0f..fad75bc 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ A PC with at least 4 CPU cores is recommended, but not necessarly required. - 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 diff --git a/deps/asprintf b/deps/asprintf new file mode 160000 index 0000000..84c7e7c --- /dev/null +++ b/deps/asprintf @@ -0,0 +1 @@ +Subproject commit 84c7e7cb1e0a6ea4102ca785cca31a3e66f34a48 diff --git a/deps/dragonnet b/deps/dragonnet index 164e1ed..c346a21 160000 --- a/deps/dragonnet +++ b/deps/dragonnet @@ -1 +1 @@ -Subproject commit 164e1ed7f93e1889c36b549a995e93fc0992c888 +Subproject commit c346a21deaf3aec0983d0e740d6c0b6799f076ef diff --git a/deps/dragonport b/deps/dragonport deleted file mode 160000 index d719d15..0000000 --- a/deps/dragonport +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d719d15af18415f17ba5ab9cde0718f2e38717ba diff --git a/deps/dragonstd b/deps/dragonstd index 0f30ec8..d439328 160000 --- a/deps/dragonstd +++ b/deps/dragonstd @@ -1 +1 @@ -Subproject commit 0f30ec889c7a70ab7f7b79836d1a34ddf8659a47 +Subproject commit d439328eed1446e6869a1fd23fe5c0a99350806d diff --git a/deps/dragontype b/deps/dragontype deleted file mode 160000 index 18ae77e..0000000 --- a/deps/dragontype +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 18ae77e35da46f97f3b06562a4a892ebdeeb9750 diff --git a/deps/protogen b/deps/protogen new file mode 160000 index 0000000..a7957f4 --- /dev/null +++ b/deps/protogen @@ -0,0 +1 @@ +Subproject commit a7957f4abdccb075693b01713492cd67b881d1ec diff --git a/models/player.txt b/models/player.txt new file mode 100644 index 0000000..0fe8485 --- /dev/null +++ b/models/player.txt @@ -0,0 +1,16 @@ +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 diff --git a/schematics/spawn_hut.gox b/schematics/spawn_hut.gox new file mode 100644 index 0000000..7387575 Binary files /dev/null and b/schematics/spawn_hut.gox differ diff --git a/schematics/spawn_hut.txt b/schematics/spawn_hut.txt new file mode 100644 index 0000000..5e33f2d --- /dev/null +++ b/schematics/spawn_hut.txt @@ -0,0 +1,429 @@ +# 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 diff --git a/shaders/3d/fragment.glsl b/shaders/3d/fragment.glsl deleted file mode 100755 index 58b06bd..0000000 --- a/shaders/3d/fragment.glsl +++ /dev/null @@ -1,20 +0,0 @@ -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; -} diff --git a/shaders/3d/vertex.glsl b/shaders/3d/vertex.glsl deleted file mode 100755 index 8d48d53..0000000 --- a/shaders/3d/vertex.glsl +++ /dev/null @@ -1,34 +0,0 @@ -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; -} diff --git a/shaders/terrain/fragment.glsl b/shaders/terrain/fragment.glsl new file mode 100755 index 0000000..533c91c --- /dev/null +++ b/shaders/terrain/fragment.glsl @@ -0,0 +1,20 @@ +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; +} diff --git a/shaders/terrain/vertex.glsl b/shaders/terrain/vertex.glsl new file mode 100755 index 0000000..7104497 --- /dev/null +++ b/shaders/terrain/vertex.glsl @@ -0,0 +1,34 @@ +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; +} diff --git a/singleplayer.sh b/singleplayer.sh index 120f4a2..b1961d0 100755 --- a/singleplayer.sh +++ b/singleplayer.sh @@ -1,2 +1,4 @@ -#! /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 $$ diff --git a/snapshot.sh b/snapshot.sh index 6d4363d..66b6882 100755 --- a/snapshot.sh +++ b/snapshot.sh @@ -1,4 +1,9 @@ -#! /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/ @@ -9,14 +14,10 @@ if ! (cmake -B . -S ../src -DCMAKE_BUILD_TYPE=Release -DRESSOURCE_PATH="\"\"" && 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 879bec1..0a9c496 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,10 +22,10 @@ find_package(Freetype REQUIRED) # 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 @@ -51,43 +51,47 @@ endif() # 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 @@ -97,53 +101,53 @@ add_executable(Dragonblocks 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 @@ -154,9 +158,9 @@ add_custom_command( 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) diff --git a/src/client/blockmesh.c b/src/client/blockmesh.c deleted file mode 100644 index 25274e2..0000000 --- a/src/client/blockmesh.c +++ /dev/null @@ -1,130 +0,0 @@ -#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); -} diff --git a/src/client/blockmesh.h b/src/client/blockmesh.h deleted file mode 100644 index f469a06..0000000 --- a/src/client/blockmesh.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef _BLOCKMESH_H_ -#define _BLOCKMESH_H_ - -#include "map.h" - -void blockmesh_make(MapBlock *block); - -#endif diff --git a/src/client/camera.c b/src/client/camera.c index 4cea5a3..adb2364 100644 --- a/src/client/camera.c +++ b/src/client/camera.c @@ -1,6 +1,6 @@ #include -#include "client/client.h" #include "client/camera.h" +#include "client/client.h" struct Camera camera; diff --git a/src/client/camera.h b/src/client/camera.h index bc7d57c..abe7d8f 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -6,8 +6,7 @@ #include #include "types.h" -extern struct Camera -{ +extern struct Camera { mat4x4 view; vec3 eye, front, right, up; struct { @@ -21,4 +20,4 @@ void camera_set_position(v3f32 pos); void camera_set_angle(f32 yaw, f32 pitch); void camera_on_resize(int width, int height); -#endif +#endif // _CAMERA_H_ diff --git a/src/client/client.c b/src/client/client.c index 63d3c59..0dd8626 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -1,115 +1,181 @@ +#define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include #include -#include #include +#include +#include #include -#include #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; } diff --git a/src/client/client.h b/src/client/client.h index 51ad358..28b89df 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -5,4 +5,4 @@ extern DragonnetPeer *client; -#endif +#endif // _CLIENT_H_ diff --git a/src/client/client_auth.c b/src/client/client_auth.c index d2db7e0..4782d32 100644 --- a/src/client/client_auth.c +++ b/src/client/client_auth.c @@ -6,48 +6,58 @@ #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); } diff --git a/src/client/client_auth.h b/src/client/client_auth.h index 4d30ca0..3d8d4e4 100644 --- a/src/client/client_auth.h +++ b/src/client/client_auth.h @@ -1,21 +1,22 @@ #ifndef _CLIENT_AUTH_H_ #define _CLIENT_AUTH_H_ -typedef enum -{ +#include + +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_ diff --git a/src/client/client_config.c b/src/client/client_config.c index 2dbe24b..be0d221 100644 --- a/src/client/client_config.c +++ b/src/client/client_config.c @@ -4,39 +4,46 @@ 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); +} diff --git a/src/client/client_config.h b/src/client/client_config.h index 4850e45..fea57d2 100644 --- a/src/client/client_config.h +++ b/src/client/client_config.h @@ -6,7 +6,7 @@ extern struct ClientConfig { unsigned int antialiasing; bool mipmap; - double render_distance; + double view_distance; bool vsync; unsigned int meshgen_threads; } client_config; diff --git a/src/client/client_entity.c b/src/client/client_entity.c new file mode 100644 index 0000000..2c34b68 --- /dev/null +++ b/src/client/client_entity.c @@ -0,0 +1,175 @@ +#include +#include +#include +#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); +} diff --git a/src/client/client_entity.h b/src/client/client_entity.h new file mode 100644 index 0000000..05651c4 --- /dev/null +++ b/src/client/client_entity.h @@ -0,0 +1,51 @@ +#ifndef _CLIENT_ENTITY_H_ +#define _CLIENT_ENTITY_H_ + +#include +#include +#include +#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_ diff --git a/src/client/client_map.c b/src/client/client_map.c deleted file mode 100644 index 43252dd..0000000 --- a/src/client/client_map.c +++ /dev/null @@ -1,253 +0,0 @@ -#include -#include -#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); -} diff --git a/src/client/client_map.h b/src/client/client_map.h deleted file mode 100644 index b49f199..0000000 --- a/src/client/client_map.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _CLIENT_MAP_H_ -#define _CLIENT_MAP_H_ - -#include -#include -#include -#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 diff --git a/src/client/client_node.c b/src/client/client_node.c index 18283eb..9700590 100644 --- a/src/client/client_node.c +++ b/src/client/client_node.c @@ -1,113 +1,83 @@ #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, }, @@ -118,16 +88,16 @@ ClientNodeDefinition client_node_definitions[NODE_UNLOADED] = { .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 { @@ -136,16 +106,16 @@ ClientNodeDefinition client_node_definitions[NODE_UNLOADED] = { .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 { @@ -154,42 +124,42 @@ ClientNodeDefinition client_node_definitions[NODE_UNLOADED] = { .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, }, @@ -197,10 +167,10 @@ ClientNodeDefinition client_node_definitions[NODE_UNLOADED] = { 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++) { diff --git a/src/client/client_node.h b/src/client/client_node.h index bb145f6..a922df4 100644 --- a/src/client/client_node.h +++ b/src/client/client_node.h @@ -1,32 +1,36 @@ #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_ diff --git a/src/client/client_player.c b/src/client/client_player.c index a5a69c1..08b9adb 100644 --- a/src/client/client_player.c +++ b/src/client/client_player.c @@ -1,100 +1,204 @@ #include -#include "environment.h" +#include #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() { @@ -119,96 +223,62 @@ 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); } diff --git a/src/client/client_player.h b/src/client/client_player.h index 0de19b9..f2ea0e4 100644 --- a/src/client/client_player.h +++ b/src/client/client_player.h @@ -2,29 +2,25 @@ #define _CLIENT_PLAYER_H_ #include -#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_ diff --git a/src/client/client_terrain.c b/src/client/client_terrain.c new file mode 100644 index 0000000..f60dbb1 --- /dev/null +++ b/src/client/client_terrain.c @@ -0,0 +1,304 @@ +#define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/src/client/client_terrain.h b/src/client/client_terrain.h new file mode 100644 index 0000000..f1e1355 --- /dev/null +++ b/src/client/client_terrain.h @@ -0,0 +1,34 @@ +#ifndef _CLIENT_TERRAIN_H_ +#define _CLIENT_TERRAIN_H_ + +#include +#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 diff --git a/src/client/cube.c b/src/client/cube.c index 7384076..874d14d 100644 --- a/src/client/cube.c +++ b/src/client/cube.c @@ -1,53 +1,53 @@ #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}}, }, }; diff --git a/src/client/cube.h b/src/client/cube.h index e467be9..4fae1fa 100644 --- a/src/client/cube.h +++ b/src/client/cube.h @@ -1,8 +1,15 @@ #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_ diff --git a/src/client/debug_menu.c b/src/client/debug_menu.c index 04b9256..8385707 100644 --- a/src/client/debug_menu.c +++ b/src/client/debug_menu.c @@ -1,62 +1,117 @@ -#include +#include #include #include +#include +#include +#include #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, @@ -64,124 +119,47 @@ void debug_menu_init() .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); } diff --git a/src/client/debug_menu.h b/src/client/debug_menu.h index fdba1d1..3b1b7de 100644 --- a/src/client/debug_menu.h +++ b/src/client/debug_menu.h @@ -1,31 +1,34 @@ #ifndef _DEBUG_MENU_H_ #define _DEBUG_MENU_H_ -#include +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_ diff --git a/src/client/facecache.c b/src/client/facecache.c index f08854d..bcb8ba4 100644 --- a/src/client/facecache.c +++ b/src/client/facecache.c @@ -1,31 +1,26 @@ -#include #include +#include #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) \ { \ @@ -33,13 +28,13 @@ static void face_cache_calculate(s32 size) *(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) @@ -47,44 +42,46 @@ static void face_cache_calculate(s32 size) 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; } diff --git a/src/client/facecache.h b/src/client/facecache.h index 847b739..7b62b9e 100644 --- a/src/client/facecache.h +++ b/src/client/facecache.h @@ -4,7 +4,7 @@ #include #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_ diff --git a/src/client/font.c b/src/client/font.c index 97c4177..15e432a 100644 --- a/src/client/font.c +++ b/src/client/font.c @@ -7,169 +7,138 @@ #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]); + } } diff --git a/src/client/font.h b/src/client/font.h index def7410..b7ba6bd 100644 --- a/src/client/font.h +++ b/src/client/font.h @@ -1,22 +1,24 @@ #ifndef _FONT_H_ #define _FONT_H_ +#include +#include #include #include #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_ diff --git a/src/client/frustum.c b/src/client/frustum.c index e05d649..f04ade9 100644 --- a/src/client/frustum.c +++ b/src/client/frustum.c @@ -1,7 +1,8 @@ -#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, @@ -11,52 +12,52 @@ typedef enum 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; } } } @@ -74,7 +75,7 @@ static bool outside_plane(Plane i, aabb3f32 box) 1.0f, }; - if (vec4_mul_inner(frustum.planes[i], plane) > 0.0) + if (vec4_mul_inner(planes[i], plane) > 0.0) return false; } } @@ -84,7 +85,7 @@ static bool outside_plane(Plane i, aabb3f32 box) } // 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)) @@ -95,12 +96,12 @@ bool frustum_is_visible(aabb3f32 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++) @@ -114,3 +115,8 @@ bool frustum_is_visible(aabb3f32 box) return true; } + +bool frustum_cull(aabb3f32 box) +{ + return !box_visible(box); +} diff --git a/src/client/frustum.h b/src/client/frustum.h index fa8eba5..a747265 100644 --- a/src/client/frustum.h +++ b/src/client/frustum.h @@ -5,7 +5,9 @@ #include #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 diff --git a/src/client/game.c b/src/client/game.c index 02216ac..99da578 100644 --- a/src/client/game.c +++ b/src/client/game.c @@ -1,42 +1,42 @@ -#include -#include +#define STB_IMAGE_WRITE_IMPLEMENTATION #include #include #include -#define STB_IMAGE_WRITE_IMPLEMENTATION #include +#include +#include #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}, }); } @@ -52,28 +52,33 @@ static void render(f64 dtime) 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; } @@ -83,9 +88,10 @@ static void game_loop() 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); @@ -94,116 +100,95 @@ static void game_loop() } } -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]; @@ -212,18 +197,12 @@ char *take_screenshot() // 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; -} diff --git a/src/client/game.h b/src/client/game.h index b6c04bd..af1d74c 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -1,8 +1,11 @@ #ifndef _GAME_H_ #define _GAME_H_ -bool game(); -char *take_screenshot(); -void game_on_resize(int width, int height); +#include -#endif +extern int game_fps; + +bool game(Flag *gfx_init); +char *game_take_screenshot(); + +#endif // _GAME_H_ diff --git a/src/client/gui.c b/src/client/gui.c index 11fa50a..29f6b91 100644 --- a/src/client/gui.c +++ b/src/client/gui.c @@ -1,288 +1,329 @@ -#include -#include -#include -#include #include #include +#include +#include +#include #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; @@ -294,19 +335,18 @@ GUIElement *gui_add(GUIElement *parent, GUIElementDefinition def) 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); @@ -314,120 +354,11 @@ void gui_set_text(GUIElement *element, char *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); } diff --git a/src/client/gui.h b/src/client/gui.h index 7fd1844..7f4c0d2 100644 --- a/src/client/gui.h +++ b/src/client/gui.h @@ -1,25 +1,22 @@ #ifndef _GUI_H_ #define _GUI_H_ -#include +#include #include -#include -#include +#include #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; @@ -34,8 +31,7 @@ typedef struct v4f32 bg_color; } GUIElementDefinition; -typedef struct GUIElement -{ +typedef struct GUIElement { GUIElementDefinition def; bool visible; v2f32 pos; @@ -44,17 +40,15 @@ typedef struct GUIElement 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_ diff --git a/src/client/input.c b/src/client/input.c index a862017..e9b154c 100644 --- a/src/client/input.c +++ b/src/client/input.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,57 +11,33 @@ #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; @@ -69,9 +46,9 @@ static bool move(int forward, int backward, vec3 dir) 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; } @@ -79,156 +56,174 @@ static bool move(int forward, int backward, vec3 dir) 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); } diff --git a/src/client/input.h b/src/client/input.h index fd27cf6..9d63151 100644 --- a/src/client/input.h +++ b/src/client/input.h @@ -3,8 +3,8 @@ #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_ diff --git a/src/client/mesh.c b/src/client/mesh.c index 5d42f8c..f2ded84 100644 --- a/src/client/mesh.c +++ b/src/client/mesh.c @@ -1,63 +1,59 @@ -#include #include +#include #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; } diff --git a/src/client/mesh.h b/src/client/mesh.h index eae283f..3597043 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -4,23 +4,29 @@ #include #include #include -#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_ diff --git a/src/client/model.c b/src/client/model.c new file mode 100644 index 0000000..1d163c7 --- /dev/null +++ b/src/client/model.c @@ -0,0 +1,542 @@ +#include +#include +#include +#include +#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); +} diff --git a/src/client/model.h b/src/client/model.h new file mode 100644 index 0000000..663df77 --- /dev/null +++ b/src/client/model.h @@ -0,0 +1,106 @@ +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include +#include +#include +#include +#include +#include +#include +#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_ diff --git a/src/client/object.c b/src/client/object.c deleted file mode 100644 index 247d985..0000000 --- a/src/client/object.c +++ /dev/null @@ -1,213 +0,0 @@ -#include -#include -#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); -} diff --git a/src/client/object.h b/src/client/object.h deleted file mode 100644 index a5152d2..0000000 --- a/src/client/object.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef _OBJECT_H_ -#define _OBJECT_H_ - -#include -#include -#include -#include -#include -#include -#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 diff --git a/src/client/scene.c b/src/client/scene.c deleted file mode 100644 index d2d42ca..0000000 --- a/src/client/scene.c +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include -#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; -} diff --git a/src/client/scene.h b/src/client/scene.h deleted file mode 100644 index 49a786d..0000000 --- a/src/client/scene.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _SCENE_H_ -#define _SCENE_H_ - -#include -#include -#include -#include -#include -#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 diff --git a/src/client/shader.c b/src/client/shader.c index 533ecc8..f4f050f 100644 --- a/src/client/shader.c +++ b/src/client/shader.c @@ -1,16 +1,16 @@ -#include #include -#include #include +#include +#include #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; } @@ -47,7 +47,8 @@ static GLuint compile_and_attach_shader(GLenum type, const char *path, const cha 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, @@ -67,10 +68,10 @@ static GLuint compile_and_attach_shader(GLenum type, const char *path, const cha 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; } @@ -84,17 +85,17 @@ bool shader_program_create(const char *path, GLuint *idptr, const char *definiti { 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; @@ -106,10 +107,10 @@ bool shader_program_create(const char *path, GLuint *idptr, const char *definiti 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; } diff --git a/src/client/shader.h b/src/client/shader.h index cfc1abb..eb62941 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -1,10 +1,10 @@ #ifndef _SHADER_H_ #define _SHADER_H_ -#include #include #include +#include bool shader_program_create(const char *path, GLuint *idptr, const char *definitions); -#endif +#endif // _SHADER_H_ diff --git a/src/client/sky.c b/src/client/sky.c index 2867298..45b7836 100644 --- a/src/client/sky.c +++ b/src/client/sky.c @@ -5,202 +5,136 @@ #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() { @@ -221,28 +155,32 @@ 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); diff --git a/src/client/sky.h b/src/client/sky.h index 3293931..027abe7 100644 --- a/src/client/sky.h +++ b/src/client/sky.h @@ -7,4 +7,4 @@ bool sky_init(); void sky_deinit(); void sky_render(); -#endif +#endif // _SKY_H_ diff --git a/src/client/terrain_gfx.c b/src/client/terrain_gfx.c new file mode 100644 index 0000000..38c7799 --- /dev/null +++ b/src/client/terrain_gfx.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#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); +} diff --git a/src/client/terrain_gfx.h b/src/client/terrain_gfx.h new file mode 100644 index 0000000..d41bce8 --- /dev/null +++ b/src/client/terrain_gfx.h @@ -0,0 +1,18 @@ +#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 diff --git a/src/client/texture.c b/src/client/texture.c index 1036a91..9f4f7ec 100644 --- a/src/client/texture.c +++ b/src/client/texture.c @@ -1,57 +1,83 @@ #define STB_IMAGE_IMPLEMENTATION #include #include -#include +#include #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", @@ -66,14 +92,15 @@ GLuint texture_create_cubemap(char *path) 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); } @@ -83,30 +110,26 @@ GLuint texture_create_cubemap(char *path) 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); } diff --git a/src/client/texture.h b/src/client/texture.h index 7737575..933fe72 100644 --- a/src/client/texture.h +++ b/src/client/texture.h @@ -4,15 +4,14 @@ #include #include -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_ diff --git a/src/client/vertex.c b/src/client/vertex.c deleted file mode 100644 index d203668..0000000 --- a/src/client/vertex.c +++ /dev/null @@ -1,15 +0,0 @@ -#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; - } -} diff --git a/src/client/vertex.h b/src/client/vertex.h deleted file mode 100644 index 361d5f6..0000000 --- a/src/client/vertex.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _VERTEX_H_ -#define _VERTEX_H_ - -#include -#include - -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 diff --git a/src/client/window.c b/src/client/window.c index b45b95b..63dc81f 100644 --- a/src/client/window.c +++ b/src/client/window.c @@ -6,36 +6,45 @@ #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; } } @@ -46,21 +55,21 @@ void window_enter_fullscreen() 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; } @@ -69,24 +78,29 @@ bool window_init(int width, int height) 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; } diff --git a/src/client/window.h b/src/client/window.h index 83b784f..0203510 100644 --- a/src/client/window.h +++ b/src/client/window.h @@ -3,19 +3,16 @@ #include -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_ diff --git a/src/color.c b/src/color.c new file mode 100644 index 0000000..66c5d84 --- /dev/null +++ b/src/color.c @@ -0,0 +1,40 @@ +#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; +} + diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..ebf8d73 --- /dev/null +++ b/src/color.h @@ -0,0 +1,8 @@ +#ifndef _COLOR_H_ +#define _COLOR_H_ + +#include "types.h" + +v3f32 hsl_to_rgb(v3f32 hsl); + +#endif // _COLOR_H_ diff --git a/src/config.c b/src/config.c index 00c50b5..8f200c8 100644 --- a/src/config.c +++ b/src/config.c @@ -8,14 +8,14 @@ void config_read(char *path, ConfigEntry *entries, size_t num_entries) { 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; @@ -25,38 +25,45 @@ void config_read(char *path, ConfigEntry *entries, size_t num_entries) 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; @@ -68,20 +75,35 @@ void config_read(char *path, ConfigEntry *entries, size_t num_entries) 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); + } + } +} diff --git a/src/config.h b/src/config.h index c69a5fd..c7eeab3 100644 --- a/src/config.h +++ b/src/config.h @@ -3,22 +3,21 @@ #include -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_ diff --git a/src/day.c b/src/day.c index 24b6b4b..e28e21b 100644 --- a/src/day.c +++ b/src/day.c @@ -1,7 +1,6 @@ #include #include #include "day.h" -#include "util.h" bool timelapse = false; static f64 time_of_day_offset; diff --git a/src/day.h b/src/day.h index b35e12e..75cf3e9 100644 --- a/src/day.h +++ b/src/day.h @@ -3,6 +3,7 @@ #include #include "types.h" + #define MINUTES_PER_HOUR 60 #define HOURS_PER_DAY 24 #define MINUTES_PER_DAY (HOURS_PER_DAY * MINUTES_PER_HOUR) @@ -17,4 +18,4 @@ void split_time_of_day(int *hours, int *minutes); extern bool timelapse; -#endif +#endif // _DAY_H_ diff --git a/src/debug.sh b/src/debug.sh index 98d38c4..7f80044 100755 --- a/src/debug.sh +++ b/src/debug.sh @@ -1,4 +1,5 @@ -#! /bin/bash +#!/bin/bash + if ! make -j$(nproc); then exit 1 fi @@ -29,13 +30,13 @@ set print thread-events off 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` diff --git a/src/debug_loop.sh b/src/debug_loop.sh index 199a3bd..3c6c460 100755 --- a/src/debug_loop.sh +++ b/src/debug_loop.sh @@ -1,8 +1,8 @@ -#! /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 diff --git a/src/debug_mapgen.sh b/src/debug_mapgen.sh deleted file mode 100755 index c216dca..0000000 --- a/src/debug_mapgen.sh +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/bash -rm -f map.sqlite -./debug.sh diff --git a/src/debug_terrain.sh b/src/debug_terrain.sh new file mode 100755 index 0000000..7da1b80 --- /dev/null +++ b/src/debug_terrain.sh @@ -0,0 +1,3 @@ +#!/bin/bash +rm -f *.sqlite +./debug.sh diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..f564cee --- /dev/null +++ b/src/entity.h @@ -0,0 +1,11 @@ +#ifndef _ENTITY_H_ +#define _ENTITY_H_ + +// ET phone home +typedef enum { + ENTITY_LOCALPLAYER, + ENTITY_PLAYER, + COUNT_ENTITY, +} EntityType; + +#endif // _ENTITY_H_ diff --git a/src/environment.c b/src/environment.c index 02db276..5341603 100644 --- a/src/environment.c +++ b/src/environment.c @@ -1,6 +1,5 @@ #include "environment.h" #include "perlin.h" -#include "util.h" f64 get_humidity(v3s32 pos) { diff --git a/src/environment.h b/src/environment.h index 1de120c..f9a3ef7 100644 --- a/src/environment.h +++ b/src/environment.h @@ -6,4 +6,4 @@ f64 get_humidity(v3s32 pos); f64 get_temperature(v3s32 pos); -#endif +#endif // _ENVIRONMENT_H_ diff --git a/src/interrupt.c b/src/interrupt.c index 2fb1d01..c11c3cd 100644 --- a/src/interrupt.c +++ b/src/interrupt.c @@ -3,18 +3,18 @@ #include #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); @@ -23,5 +23,5 @@ void interrupt_init() void interrupt_deinit() { - flag_delete(interrupt); + flag_dst(&interrupt); } diff --git a/src/interrupt.h b/src/interrupt.h index 489a264..9cf840b 100644 --- a/src/interrupt.h +++ b/src/interrupt.h @@ -3,9 +3,9 @@ #include -extern Flag *interrupt; - void interrupt_init(); void interrupt_deinit(); -#endif +extern Flag interrupt; + +#endif // _INTERRUPT_H_ diff --git a/src/map.c b/src/map.c deleted file mode 100644 index 8c75756..0000000 --- a/src/map.c +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include -#include -#include -#include -#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); -} diff --git a/src/map.h b/src/map.h deleted file mode 100644 index 9e61d0d..0000000 --- a/src/map.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef _MAP_H_ -#define _MAP_H_ - -#include -#include -#include -#include -#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 diff --git a/src/mktypes.sh b/src/mktypes.sh index e89025e..be1ef8f 100755 --- a/src/mktypes.sh +++ b/src/mktypes.sh @@ -1,3 +1,3 @@ -#! /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" diff --git a/src/node.c b/src/node.c index 5aeefea..727f81c 100644 --- a/src/node.c +++ b/src/node.c @@ -1,8 +1,6 @@ -#include "types.h" -#include "map.h" #include "node.h" -#include "util.h" -#include +#include "terrain.h" +#include "types.h" NodeDefinition node_definitions[NODE_UNLOADED] = { // unknown @@ -62,56 +60,56 @@ NodeDefinition node_definitions[NODE_UNLOADED] = { // 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 { diff --git a/src/node.h b/src/node.h index 2ed4edd..3e7e930 100644 --- a/src/node.h +++ b/src/node.h @@ -2,12 +2,12 @@ #define _NODE_H_ #include +#include #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, @@ -24,17 +24,16 @@ typedef enum 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; diff --git a/src/perlin.h b/src/perlin.h index 0213c68..15a63c9 100644 --- a/src/perlin.h +++ b/src/perlin.h @@ -4,8 +4,9 @@ #include #include "types.h" -typedef enum -{ +#define U32(x) (((u32) 1 << 31) + (x)) + +typedef enum { SO_NONE, SO_HEIGHT, SO_MOUNTAIN, @@ -35,4 +36,4 @@ typedef enum extern s32 seed; -#endif +#endif // _PERLIN_H_ diff --git a/src/physics.c b/src/physics.c new file mode 100644 index 0000000..8f117b4 --- /dev/null +++ b/src/physics.c @@ -0,0 +1,125 @@ +#include +#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); +} diff --git a/src/physics.h b/src/physics.h new file mode 100644 index 0000000..083cc09 --- /dev/null +++ b/src/physics.h @@ -0,0 +1,11 @@ +#ifndef _PHYSICS_H_ +#define _PHYSICS_H_ + +#include +#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_ diff --git a/src/server/biomes.c b/src/server/biomes.c index 44a17c3..22f88a6 100644 --- a/src/server/biomes.c +++ b/src/server/biomes.c @@ -1,14 +1,14 @@ #include #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) @@ -17,74 +17,71 @@ Biome get_biome(v2s32 pos, f64 *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); } @@ -92,13 +89,16 @@ static f64 get_ocean_level_factor(f64 factor, OceanLevel level) 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); @@ -130,17 +130,17 @@ static f64 distance(v2s32 a, v2s32 b) 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; @@ -149,61 +149,66 @@ static s32 calculate_ocean_floor(f64 factor, s32 height) 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 { @@ -218,9 +223,9 @@ Node ocean_get_node_at(v3s32 pos, s32 diff, void *row_data) 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 @@ -235,27 +240,27 @@ static bool boulder_touching_ground(v3s32 pos, s32 diff) 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, @@ -263,10 +268,10 @@ BiomeDef biomes[BIOME_COUNT] = { .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, @@ -275,10 +280,10 @@ BiomeDef biomes[BIOME_COUNT] = { .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, @@ -287,9 +292,9 @@ BiomeDef biomes[BIOME_COUNT] = { .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, }, }; diff --git a/src/server/biomes.h b/src/server/biomes.h index fb44049..3994100 100644 --- a/src/server/biomes.h +++ b/src/server/biomes.h @@ -1,35 +1,66 @@ #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_ diff --git a/src/server/database.c b/src/server/database.c index 3d9eb92..3d06edb 100644 --- a/src/server/database.c +++ b/src/server/database.c @@ -1,16 +1,15 @@ -#include #include +#include #include #include #include #include #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; @@ -23,24 +22,24 @@ static inline sqlite3_stmt *prepare_statement(sqlite3 *database, const char *sql 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); @@ -56,6 +55,15 @@ static inline void bind_v3f64(sqlite3_stmt *stmt, int idx, v3f64 pos) 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 @@ -66,20 +74,20 @@ bool database_init() 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; } @@ -105,68 +113,68 @@ bool database_init() 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); } @@ -174,10 +182,10 @@ void database_save_block(MapBlock *block) // 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; } @@ -189,7 +197,7 @@ bool database_load_meta(const char *key, s64 *value_ptr) 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; @@ -198,10 +206,10 @@ bool database_load_meta(const char *key, s64 *value_ptr) // 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; } @@ -209,18 +217,18 @@ void database_save_meta(const char *key, s64 value) 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; } @@ -229,49 +237,53 @@ bool database_load_player(char *name, v3f64 *pos_ptr) 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); } diff --git a/src/server/database.h b/src/server/database.h index 40923b7..754b6b9 100644 --- a/src/server/database.h +++ b/src/server/database.h @@ -2,17 +2,17 @@ #define _DATABASE_H_ #include -#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_ diff --git a/src/server/mapgen.c b/src/server/mapgen.c deleted file mode 100644 index 767c19e..0000000 --- a/src/server/mapgen.c +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include -#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]); - } -} diff --git a/src/server/mapgen.h b/src/server/mapgen.h deleted file mode 100644 index 8e73e72..0000000 --- a/src/server/mapgen.h +++ /dev/null @@ -1,10 +0,0 @@ -#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 diff --git a/src/server/schematic.c b/src/server/schematic.c new file mode 100644 index 0000000..5a754e8 --- /dev/null +++ b/src/server/schematic.c @@ -0,0 +1,94 @@ +#include +#include +#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); +} diff --git a/src/server/schematic.h b/src/server/schematic.h new file mode 100644 index 0000000..d4e1a2e --- /dev/null +++ b/src/server/schematic.h @@ -0,0 +1,27 @@ +#ifndef _SCHEMATIC_H_ +#define _SCHEMATIC_H_ + +#include +#include +#include +#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_ diff --git a/src/server/server.c b/src/server/server.c index 1a84a52..f4bfaa1 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -1,17 +1,19 @@ +#define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include #include #include -#include +#include #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); } @@ -22,71 +24,78 @@ static void on_ToServerAuth(DragonnetPeer *peer, ToServerAuth *pkt) } // 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(); diff --git a/src/server/server.h b/src/server/server.h index c2adddb..67de00b 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -5,4 +5,4 @@ DragonnetListener *server; -#endif +#endif // _SERVER_H_ diff --git a/src/server/server_config.c b/src/server/server_config.c index e4d146e..51a39c2 100644 --- a/src/server/server_config.c +++ b/src/server/server_config.c @@ -2,23 +2,56 @@ #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); +} diff --git a/src/server/server_config.h b/src/server/server_config.h index 4effe33..6355aa2 100644 --- a/src/server/server_config.h +++ b/src/server/server_config.h @@ -2,8 +2,16 @@ #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_ diff --git a/src/server/server_map.c b/src/server/server_map.c deleted file mode 100644 index 854afe1..0000000 --- a/src/server/server_map.c +++ /dev/null @@ -1,399 +0,0 @@ -#include -#include -#include -#include -#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); - } -} diff --git a/src/server/server_map.h b/src/server/server_map.h deleted file mode 100644 index 68f3a3e..0000000 --- a/src/server/server_map.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef _SERVER_MAP_H_ -#define _SERVER_MAP_H_ - -#include -#include -#include -#include -#include -#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 diff --git a/src/server/server_player.c b/src/server/server_player.c index e480196..1f464f9 100644 --- a/src/server/server_player.c +++ b/src/server/server_player.c @@ -1,242 +1,243 @@ +#include #include #include +#include #include -#include +#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); } /* diff --git a/src/server/server_player.h b/src/server/server_player.h index ad035fc..71d0916 100644 --- a/src/server/server_player.h +++ b/src/server/server_player.h @@ -1,23 +1,26 @@ #ifndef _SERVER_PLAYER_H_ #define _SERVER_PLAYER_H_ +#include +#include #include #include -#include #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(); @@ -26,14 +29,11 @@ void server_player_deinit(); 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_ diff --git a/src/server/server_terrain.c b/src/server/server_terrain.c new file mode 100644 index 0000000..a4aa91e --- /dev/null +++ b/src/server/server_terrain.c @@ -0,0 +1,428 @@ +#define _GNU_SOURCE // don't worry, GNU extensions are only used when available +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/src/server/server_terrain.h b/src/server/server_terrain.h new file mode 100644 index 0000000..88fa7a8 --- /dev/null +++ b/src/server/server_terrain.h @@ -0,0 +1,44 @@ +#ifndef _SERVER_TERRAIN_H_ +#define _SERVER_TERRAIN_H_ + +#include +#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_ diff --git a/src/server/terrain_gen.c b/src/server/terrain_gen.c new file mode 100644 index 0000000..5b0c1d1 --- /dev/null +++ b/src/server/terrain_gen.c @@ -0,0 +1,113 @@ +#include +#include +#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]); +} diff --git a/src/server/terrain_gen.h b/src/server/terrain_gen.h new file mode 100644 index 0000000..76bf813 --- /dev/null +++ b/src/server/terrain_gen.h @@ -0,0 +1,9 @@ +#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_ diff --git a/src/server/trees.c b/src/server/trees.c index 31dc6c7..4de2285 100644 --- a/src/server/trees.c +++ b/src/server/trees.c @@ -1,21 +1,19 @@ -#include #include #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); @@ -35,7 +33,7 @@ static void oak_tree_leaf(Voxelctx *ctx) static void oak_tree_top(Voxelctx *ctx) { - if (! voxelctx_is_alive(ctx)) + if (!voxelctx_is_alive(ctx)) return; voxelctx_push(ctx); @@ -58,7 +56,7 @@ static void oak_tree_top(Voxelctx *ctx) static void oak_tree_part(Voxelctx *ctx, f32 n) { - if (! voxelctx_is_alive(ctx)) + if (!voxelctx_is_alive(ctx)) return; voxelctx_push(ctx); @@ -83,9 +81,9 @@ static void oak_tree_part(Voxelctx *ctx, f32 n) 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); @@ -109,12 +107,12 @@ static void oak_tree(v3s32 pos, List *changed_blocks) // 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++) { @@ -130,24 +128,28 @@ static void pine_tree(v3s32 pos, List *changed_blocks) 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); @@ -160,9 +162,9 @@ static void palm_branch(Voxelctx *ctx) 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); diff --git a/src/server/trees.h b/src/server/trees.h index 3bc6763..8084c52 100644 --- a/src/server/trees.h +++ b/src/server/trees.h @@ -2,22 +2,34 @@ #define _TREES_H_ #include +#include #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_ diff --git a/src/server/voxelctx.c b/src/server/voxelctx.c index 6f5ee20..bea87fd 100644 --- a/src/server/voxelctx.c +++ b/src/server/voxelctx.c @@ -1,13 +1,13 @@ #include #include -#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; @@ -29,29 +29,24 @@ static VoxelctxState *create_state(VoxelctxState *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); } @@ -150,20 +145,13 @@ void voxelctx_s(Voxelctx *ctx, f32 value) 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) @@ -177,9 +165,9 @@ 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] = { @@ -230,23 +218,25 @@ void voxelctx_cube(Voxelctx *ctx, Node node, bool use_color) 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); } @@ -254,7 +244,7 @@ void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_color) /* void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_color) { - if (! voxelctx_is_alive(ctx)) + if (!voxelctx_is_alive(ctx)) return; return; @@ -269,7 +259,7 @@ void voxelctx_cylinder(Voxelctx *ctx, Node node, bool use_color) 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); } } } diff --git a/src/server/voxelctx.h b/src/server/voxelctx.h index 42618b3..2469be0 100644 --- a/src/server/voxelctx.h +++ b/src/server/voxelctx.h @@ -1,15 +1,14 @@ #ifndef _VOXELCTX_H_ #define _VOXELCTX_H_ -#define VOXELCTXSTATE(ctx) (*((VoxelctxState *) (ctx)->statestack.first->key)) +#define VOXELCTXSTATE(ctx) (*((VoxelctxState *) (ctx)->statestack.fst->dat)) -#include #include +#include +#include "server/server_terrain.h" #include "types.h" -#include "server/server_map.h" -typedef struct -{ +typedef struct { vec4 pos; vec3 scale; mat4x4 mat; @@ -17,16 +16,15 @@ typedef struct 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); @@ -45,8 +43,8 @@ void voxelctx_s(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_ diff --git a/src/terrain.c b/src/terrain.c new file mode 100644 index 0000000..5b6d48a --- /dev/null +++ b/src/terrain.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include +#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); +} diff --git a/src/terrain.h b/src/terrain.h new file mode 100644 index 0000000..111e175 --- /dev/null +++ b/src/terrain.h @@ -0,0 +1,72 @@ +#ifndef _TERRAIN_H_ +#define _TERRAIN_H_ + +#include +#include +#include +#include +#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 diff --git a/src/types.def b/src/types.def index 4a82d9c..9aa6aa7 100644 --- a/src/types.def +++ b/src/types.def @@ -1,23 +1,33 @@ -#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 @@ -26,25 +36,57 @@ pkt ToServerSetnode 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 diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 436e4d9..0000000 --- a/src/util.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -char *format_string(const char *format, ...) -{ - va_list args; - va_start(args, format); - char *ptr; - vasprintf(&ptr, format, args); - va_end(args); - return ptr; -} diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 64e7287..0000000 --- a/src/util.h +++ /dev/null @@ -1,11 +0,0 @@ -#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 diff --git a/textures/models/player/arm/back.png b/textures/models/player/arm/back.png new file mode 100644 index 0000000..6b98546 Binary files /dev/null and b/textures/models/player/arm/back.png differ diff --git a/textures/models/player/arm/bottom.png b/textures/models/player/arm/bottom.png new file mode 100644 index 0000000..ceaa6aa Binary files /dev/null and b/textures/models/player/arm/bottom.png differ diff --git a/textures/models/player/arm/front.png b/textures/models/player/arm/front.png new file mode 100644 index 0000000..f62ee88 Binary files /dev/null and b/textures/models/player/arm/front.png differ diff --git a/textures/models/player/arm/left.png b/textures/models/player/arm/left.png new file mode 100644 index 0000000..558391a Binary files /dev/null and b/textures/models/player/arm/left.png differ diff --git a/textures/models/player/arm/right.png b/textures/models/player/arm/right.png new file mode 100644 index 0000000..b1b7b9f Binary files /dev/null and b/textures/models/player/arm/right.png differ diff --git a/textures/models/player/arm/top.png b/textures/models/player/arm/top.png new file mode 100644 index 0000000..45567ca Binary files /dev/null and b/textures/models/player/arm/top.png differ diff --git a/textures/models/player/chest/back.png b/textures/models/player/chest/back.png new file mode 100644 index 0000000..0a0a862 Binary files /dev/null and b/textures/models/player/chest/back.png differ diff --git a/textures/models/player/chest/bottom.png b/textures/models/player/chest/bottom.png new file mode 100644 index 0000000..e51749a Binary files /dev/null and b/textures/models/player/chest/bottom.png differ diff --git a/textures/models/player/chest/front.png b/textures/models/player/chest/front.png new file mode 100644 index 0000000..6ec46e1 Binary files /dev/null and b/textures/models/player/chest/front.png differ diff --git a/textures/models/player/chest/left.png b/textures/models/player/chest/left.png new file mode 100644 index 0000000..c04fb71 Binary files /dev/null and b/textures/models/player/chest/left.png differ diff --git a/textures/models/player/chest/right.png b/textures/models/player/chest/right.png new file mode 100644 index 0000000..60329f4 Binary files /dev/null and b/textures/models/player/chest/right.png differ diff --git a/textures/models/player/chest/top.png b/textures/models/player/chest/top.png new file mode 100644 index 0000000..2711dd1 Binary files /dev/null and b/textures/models/player/chest/top.png differ diff --git a/textures/models/player/head/back.png b/textures/models/player/head/back.png new file mode 100644 index 0000000..6b98546 Binary files /dev/null and b/textures/models/player/head/back.png differ diff --git a/textures/models/player/head/bottom.png b/textures/models/player/head/bottom.png new file mode 100644 index 0000000..ceaa6aa Binary files /dev/null and b/textures/models/player/head/bottom.png differ diff --git a/textures/models/player/head/front.png b/textures/models/player/head/front.png new file mode 100644 index 0000000..f62ee88 Binary files /dev/null and b/textures/models/player/head/front.png differ diff --git a/textures/models/player/head/left.png b/textures/models/player/head/left.png new file mode 100644 index 0000000..558391a Binary files /dev/null and b/textures/models/player/head/left.png differ diff --git a/textures/models/player/head/right.png b/textures/models/player/head/right.png new file mode 100644 index 0000000..b1b7b9f Binary files /dev/null and b/textures/models/player/head/right.png differ diff --git a/textures/models/player/head/top.png b/textures/models/player/head/top.png new file mode 100644 index 0000000..45567ca Binary files /dev/null and b/textures/models/player/head/top.png differ diff --git a/textures/models/player/leg/back.png b/textures/models/player/leg/back.png new file mode 100644 index 0000000..6b98546 Binary files /dev/null and b/textures/models/player/leg/back.png differ diff --git a/textures/models/player/leg/bottom.png b/textures/models/player/leg/bottom.png new file mode 100644 index 0000000..ceaa6aa Binary files /dev/null and b/textures/models/player/leg/bottom.png differ diff --git a/textures/models/player/leg/front.png b/textures/models/player/leg/front.png new file mode 100644 index 0000000..f62ee88 Binary files /dev/null and b/textures/models/player/leg/front.png differ diff --git a/textures/models/player/leg/left.png b/textures/models/player/leg/left.png new file mode 100644 index 0000000..558391a Binary files /dev/null and b/textures/models/player/leg/left.png differ diff --git a/textures/models/player/leg/right.png b/textures/models/player/leg/right.png new file mode 100644 index 0000000..b1b7b9f Binary files /dev/null and b/textures/models/player/leg/right.png differ diff --git a/textures/models/player/leg/top.png b/textures/models/player/leg/top.png new file mode 100644 index 0000000..45567ca Binary files /dev/null and b/textures/models/player/leg/top.png differ diff --git a/textures/oak_wood.png b/textures/oak_wood.png index 55cb954..90af189 100644 Binary files a/textures/oak_wood.png and b/textures/oak_wood.png differ diff --git a/upload.sh b/upload.sh index c9a8e6c..fa554b9 100755 --- a/upload.sh +++ b/upload.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#!/bin/bash VERSION=`git tag --points-at HEAD` IS_RELEASE="1" if [[ $VERSION = "" ]]; then @@ -9,5 +9,5 @@ curl -f -i -X POST -H "Content-Type: multipart/form-data" \ -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