From: Elias Fleckenstein Date: Fri, 22 Apr 2022 09:24:03 +0000 (+0200) Subject: Trees break if they have no connection to ground X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=f4c001204190dd4e1d3fd591431002bc795b823b;p=dragonblocks_alpha.git Trees break if they have no connection to ground --- diff --git a/deps/dragonnet b/deps/dragonnet index 431e3bd..9ab2148 160000 --- a/deps/dragonnet +++ b/deps/dragonnet @@ -1 +1 @@ -Subproject commit 431e3bd29073d9c10ab3a853116860ba83ed1c5e +Subproject commit 9ab2148e1b8d03dab816bbba6209794663b31576 diff --git a/deps/dragonstd b/deps/dragonstd index 8878e82..a3a5043 160000 --- a/deps/dragonstd +++ b/deps/dragonstd @@ -1 +1 @@ -Subproject commit 8878e826fa3e7b1231e652fc13d11c7a61629d13 +Subproject commit a3a50433ffe0674291d4c5e9d2247cca7f691faa diff --git a/models/player.txt b/models/player.txt index e67a0e9..ec997b8 100644 --- a/models/player.txt +++ b/models/player.txt @@ -3,13 +3,13 @@ name neck pos 0 1.35 0 name head pos 0 0.225 0 scale 0.45 0.45 0.45 cube head name eyes pos 0 0 +0.5 name chest pos 0 1.0125 0 scale 0.48 0.675 0.225 cube chest -name arm_left pos -0.36 1.35 0 scale -1 1 1 clockwise +name arm_left pos +0.36 1.35 0 pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube arm name hand pos 0 -0.585 0 scale 0.0625 0.0625 0.0625 rot 90 0 0 -name arm_right pos +0.36 1.35 0 +name arm_right pos -0.36 1.35 0 scale -1 1 1 clockwise pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube arm name hand pos 0 -0.585 0 scale 0.0625 0.0625 0.0625 rot 90 0 0 -name leg_left pos -0.12 0.675 0 scale -1 1 1 clockwise +name leg_left pos +0.12 0.675 0 pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube leg -name leg_right pos +0.12 0.675 0 +name leg_right pos -0.12 0.675 0 scale -1 1 1 clockwise pos 0 -0.3375 0 scale 0.24 0.675 0.225 cube leg diff --git a/singleplayer.sh b/singleplayer.sh index b1961d0..0daa513 100755 --- a/singleplayer.sh +++ b/singleplayer.sh @@ -1,4 +1,4 @@ #!/bin/bash ./dragonblocks_server "[::1]:4000" & echo "singleplayer" | ./dragonblocks "[::1]:4000" -pkill -P $$ +pkill -P $$ -9 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba84911..bb61231 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -78,6 +78,7 @@ set(COMMON_SOURCES config.c day.c environment.c + facedir.c interrupt.c item.c node.c @@ -103,7 +104,6 @@ add_executable(dragonblocks client/cube.c client/debug_menu.c client/facecache.c - client/facedir.c client/font.c client/frustum.c client/game.c @@ -144,10 +144,12 @@ add_executable(dragonblocks_server server/server.c server/server_config.c server/server_item.c + server/server_node.c server/server_player.c server/server_terrain.c server/terrain_gen.c - server/trees.c + server/tree.c + server/tree_physics.c server/voxel_depth_search.c server/voxel_procedural.c ) @@ -159,7 +161,7 @@ target_link_libraries(dragonblocks_server # Version add_custom_target(version - COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake + COMMAND ${CMAKE_COMMAND} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/version.cmake WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) diff --git a/src/client/client.c b/src/client/client.c index 40f58dc..a4e811d 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -8,6 +8,7 @@ #include "client/client.h" #include "client/client_auth.h" #include "client/client_inventory.h" +#include "client/client_node.h" #include "client/client_player.h" #include "client/client_terrain.h" #include "client/debug_menu.h" @@ -89,7 +90,7 @@ static void on_ToClientChunk(__attribute__((unused)) DragonnetPeer *peer, ToClie { TerrainChunk *chunk = terrain_get_chunk(client_terrain, pkt->pos, true); - terrain_deserialize_chunk(chunk, pkt->data); + terrain_deserialize_chunk(client_terrain, chunk, pkt->data, &client_node_deserialize); ((TerrainChunkMeta *) chunk->extra)->empty = (pkt->data.siz == 0); client_terrain_chunk_received(chunk); } diff --git a/src/client/client_auth.c b/src/client/client_auth.c index 4782d32..c0fa81a 100644 --- a/src/client/client_auth.c +++ b/src/client/client_auth.c @@ -8,6 +8,7 @@ struct ClientAuth client_auth; +#include static void auth_loop() { while (!interrupt.set) switch (client_auth.state) { @@ -15,8 +16,11 @@ static void auth_loop() if (client_auth.name) linenoiseFree(client_auth.name); + /* if (!(client_auth.name = linenoise("Enter name: "))) return; + */ + client_auth.name = strdup("singleplayer"); printf("[access] authenticating as %s...\n", client_auth.name); client_auth.state = AUTH_WAIT; diff --git a/src/client/client_node.c b/src/client/client_node.c index 5285c53..1142a85 100644 --- a/src/client/client_node.c +++ b/src/client/client_node.c @@ -1,3 +1,4 @@ +#include #include "client/client.h" #include "client/client_node.h" #include "color.h" @@ -38,7 +39,7 @@ static void render_color(NodeArgsRender *args) args->vertex.color = ((ColorData *) args->node->data)->color; } -ClientNodeDef client_node_def[NODE_UNLOADED] = { +ClientNodeDef client_node_def[COUNT_NODE] = { // unknown { .tiles = TILES_SIMPLE(RESSOURCE_PATH "textures/unknown.png"), @@ -215,7 +216,7 @@ ClientNodeDef client_node_def[NODE_UNLOADED] = { void client_node_init() { - for (NodeType node = 0; node < NODE_UNLOADED; node++) { + for (NodeType node = 0; node < COUNT_NODE; node++) { ClientNodeDef *def = &client_node_def[node]; if (def->visibility != VISIBILITY_NONE) { @@ -235,3 +236,27 @@ void client_node_init() } } } + +void client_node_delete(TerrainNode *node) +{ + switch (node->type) { + NODES_TREE + free(node->data); + break; + + default: + break; + } +} + +void client_node_deserialize(TerrainNode *node, Blob buffer) +{ + switch (node->type) { + NODES_TREE + ColorData_read(&buffer, node->data = malloc(sizeof(ColorData))); + break; + + default: + break; + } +} diff --git a/src/client/client_node.h b/src/client/client_node.h index 6f23c87..8068eca 100644 --- a/src/client/client_node.h +++ b/src/client/client_node.h @@ -36,4 +36,7 @@ typedef struct { extern ClientNodeDef client_node_def[]; void client_node_init(); +void client_node_delete(TerrainNode *node); +void client_node_deserialize(TerrainNode *node, Blob buffer); + #endif // _CLIENT_NODE_H_ diff --git a/src/client/client_player.c b/src/client/client_player.c index a92b4c1..ffdc6b2 100644 --- a/src/client/client_player.c +++ b/src/client/client_player.c @@ -144,7 +144,7 @@ static void local_on_add(ClientEntity *entity) if (player_entity) { fprintf(stderr, "[error] attempt to re-add localplayer entity\n"); - exit(EXIT_FAILURE); + abort(); } on_add(entity); diff --git a/src/client/client_terrain.c b/src/client/client_terrain.c index 2e33b87..9dde109 100644 --- a/src/client/client_terrain.c +++ b/src/client/client_terrain.c @@ -6,13 +6,14 @@ #include #include #include "client/client.h" -#include "client/facedir.h" #include "client/facecache.h" #include "client/client_config.h" +#include "client/client_node.h" #include "client/client_player.h" #include "client/client_terrain.h" #include "client/debug_menu.h" #include "client/terrain_gfx.h" +#include "facedir.h" #define MAX_REQUESTS 4 @@ -77,7 +78,7 @@ static void sync_step() return; } - v3s32 center = terrain_node_to_chunk_pos((v3s32) {player_pos.x, player_pos.y, player_pos.z}, NULL); + v3s32 center = terrain_chunkp((v3s32) {player_pos.x, player_pos.y, player_pos.z}); u64 last_tick = tick++; @@ -196,11 +197,10 @@ static bool on_get_chunk(TerrainChunk *chunk, bool create) 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; + 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.delete_node = &client_node_delete; cancel = false; queue_ini(&meshgen_tasks); diff --git a/src/client/facedir.c b/src/client/facedir.c deleted file mode 100644 index 4eaf32c..0000000 --- a/src/client/facedir.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "client/facedir.h" - -v3s32 facedir[6] = { - {+0, +0, -1}, - {+0, +0, +1}, - {-1, +0, +0}, - {+1, +0, +0}, - {+0, -1, +0}, - {+0, +1, +0}, -}; diff --git a/src/client/facedir.h b/src/client/facedir.h deleted file mode 100644 index b676f0e..0000000 --- a/src/client/facedir.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef _FACEDIR_H_ -#define _FACEDIR_H_ - -#include "types.h" - -extern v3s32 facedir[]; - -#endif // _FACEDIR_H_ diff --git a/src/client/raycast.c b/src/client/raycast.c index d836bde..99cffdc 100644 --- a/src/client/raycast.c +++ b/src/client/raycast.c @@ -11,7 +11,7 @@ bool raycast(v3f64 pos, v3f64 dir, f64 len, v3s32 *node_pos, NodeType *node) *node = terrain_get_node(client_terrain, *node_pos = (v3s32) {floor(pos.x + 0.5), floor(pos.y + 0.5), floor(pos.z + 0.5)}).type; - if (*node == NODE_UNLOADED) + if (*node == COUNT_NODE) return false; if (client_node_def[*node].pointable) diff --git a/src/client/terrain_gfx.c b/src/client/terrain_gfx.c index 0883d63..bfe8757 100644 --- a/src/client/terrain_gfx.c +++ b/src/client/terrain_gfx.c @@ -6,12 +6,12 @@ #include "client/client_node.h" #include "client/client_terrain.h" #include "client/cube.h" -#include "client/facedir.h" #include "client/frustum.h" #include "client/gl_debug.h" #include "client/light.h" #include "client/shader.h" #include "client/terrain_gfx.h" +#include "facedir.h" typedef struct { bool visible; @@ -60,7 +60,7 @@ static inline bool show_face(NodeType self, NodeType nbr) return nbr != self; case VISIBILITY_SOLID: - return nbr != NODE_UNLOADED && client_node_def[nbr].visibility != VISIBILITY_SOLID; + return nbr != COUNT_NODE && client_node_def[nbr].visibility != VISIBILITY_SOLID; default: // impossible break; @@ -99,7 +99,7 @@ static inline void render_node(ChunkRenderData *data, v3s32 offset) data->tried_nbrs[args.f] = true; } - NodeType nbr_node = NODE_UNLOADED; + NodeType nbr_node = COUNT_NODE; if (nbr_chunk) nbr_node = nbr_chunk->data [(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE] diff --git a/src/client/texture.c b/src/client/texture.c index 525b2aa..54063fe 100644 --- a/src/client/texture.c +++ b/src/client/texture.c @@ -64,7 +64,7 @@ Texture *texture_load(char *path, bool mipmap) &texture->width, &texture->height, &texture->channels, 0); if (!data) { fprintf(stderr, "[error] failed to load texture %s\n", path); - exit(EXIT_FAILURE); + abort(); } texture_upload(texture, data, GL_RGBA, mipmap); @@ -123,7 +123,7 @@ Texture *texture_load_cubemap(char *path, bool bilinear_filter) if (!(faces[i].data = stbi_load(filename, &faces[i].width, &faces[i].height, &faces[i].channels, 0))) { fprintf(stderr, "[error] failed to load texture %s\n", filename); - exit(EXIT_FAILURE); + abort(); } size = least_common_multiple(size, faces[i].width); diff --git a/src/debug.sh b/src/debug.sh index 5dc2bac..0dcf103 100755 --- a/src/debug.sh +++ b/src/debug.sh @@ -1,6 +1,6 @@ #!/bin/bash -if ! make -j$(nproc); then +if ! make -j$(nproc) dragonblocks_server; then exit 1 fi @@ -19,10 +19,10 @@ define hook-stop quit end end -break gl_error " echo "$COMMON +break gl_error run \"[::1]:4000\" < $DEBUG_DIR/name " > $DEBUG_DIR/client_script diff --git a/src/facedir.c b/src/facedir.c new file mode 100644 index 0000000..e1125a9 --- /dev/null +++ b/src/facedir.c @@ -0,0 +1,10 @@ +#include "facedir.h" + +v3s32 facedir[6] = { + {+0, +0, -1}, + {+0, +0, +1}, + {-1, +0, +0}, + {+1, +0, +0}, + {+0, -1, +0}, + {+0, +1, +0}, +}; diff --git a/src/facedir.h b/src/facedir.h new file mode 100644 index 0000000..b676f0e --- /dev/null +++ b/src/facedir.h @@ -0,0 +1,8 @@ +#ifndef _FACEDIR_H_ +#define _FACEDIR_H_ + +#include "types.h" + +extern v3s32 facedir[]; + +#endif // _FACEDIR_H_ diff --git a/src/node.c b/src/node.c index 54b6294..e79352b 100644 --- a/src/node.c +++ b/src/node.c @@ -3,147 +3,85 @@ #include "terrain.h" #include "types.h" -NodeDef node_def[NODE_UNLOADED] = { +NodeDef node_def[COUNT_NODE] = { // unknown { .solid = true, - .data_size = 0, .dig_class = DIG_NONE, - .callbacks = {NULL}, }, // air { .solid = false, - .data_size = 0, .dig_class = DIG_NONE, - .callbacks = {NULL}, }, // grass { .solid = true, - .data_size = 0, .dig_class = DIG_DIRT, - .callbacks = {NULL}, }, // dirt { .solid = true, - .data_size = 0, .dig_class = DIG_DIRT, - .callbacks = {NULL}, }, // stone { .solid = true, - .data_size = 0, .dig_class = DIG_STONE, - .callbacks = {NULL}, }, // snow { .solid = true, - .data_size = 0, .dig_class = DIG_DIRT, - .callbacks = {NULL}, }, // oak wood { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_WOOD, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // oak leaves { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_LEAVES, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // pine wood { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_WOOD, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // pine leaves { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_LEAVES, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // palm wood { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_WOOD, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // palm leaves { .solid = true, - .data_size = sizeof(ColorData), .dig_class = DIG_LEAVES, - .callbacks = { - .create = NULL, - .delete = NULL, - .serialize = (void *) &ColorData_write, - .deserialize = (void *) &ColorData_read, - }, }, // sand { .solid = true, - .data_size = 0, .dig_class = DIG_DIRT, - .callbacks = {NULL}, }, // water { .solid = false, - .data_size = 0, .dig_class = DIG_NONE, - .callbacks = {NULL}, }, // lava { .solid = false, - .data_size = 0, .dig_class = DIG_NONE, - .callbacks = {NULL}, }, // vulcanostone { .solid = true, - .data_size = 0, .dig_class = DIG_STONE, - .callbacks = {NULL}, }, }; diff --git a/src/node.h b/src/node.h index cdb1a3b..ef21713 100644 --- a/src/node.h +++ b/src/node.h @@ -5,6 +5,8 @@ #include #include "types.h" +#define NODES_TREE case NODE_OAK_WOOD: case NODE_OAK_LEAVES: case NODE_PINE_WOOD: case NODE_PINE_LEAVES: case NODE_PALM_WOOD: case NODE_PALM_LEAVES: + typedef enum { NODE_UNKNOWN, // Used for unknown nodes received from server (caused by outdated clients) NODE_AIR, @@ -22,21 +24,14 @@ typedef enum { NODE_WATER, NODE_LAVA, NODE_VULCANO_STONE, - NODE_UNLOADED, // Used for nodes in unloaded chunks + COUNT_NODE, // Used for nodes in unloaded chunks } NodeType; struct TerrainNode; typedef struct { bool solid; - size_t data_size; unsigned long dig_class; - struct { - void (*create)(struct TerrainNode *node); - void (*delete)(struct TerrainNode *node); - void (*serialize)(Blob *buffer, void *data); - void (*deserialize)(Blob *buffer, void *data); - } callbacks; } NodeDef; extern NodeDef node_def[]; diff --git a/src/physics.c b/src/physics.c index 4074d8b..5fab114 100644 --- a/src/physics.c +++ b/src/physics.c @@ -20,7 +20,7 @@ static aabb3s32 round_box(aabb3f64 box) 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_def[node].solid; + return node == COUNT_NODE || node_def[node].solid; } bool physics_ground(Terrain *terrain, bool collide, aabb3f32 box, v3f64 *pos, v3f64 *vel) diff --git a/src/server/biomes.c b/src/server/biomes.c index da21675..e9253dc 100644 --- a/src/server/biomes.c +++ b/src/server/biomes.c @@ -232,14 +232,16 @@ static bool is_boulder(s32 diff, v3s32 pos) smooth3d(U32(pos.x) / 16.0, U32(pos.y) / 12.0, U32(pos.z) / 16.0, 0, seed + OFFSET_BOULDER) > 0.8; } -static DepthSearchNodeType boulder_get_node_type(v3s32 pos) +static void boulder_search_callback(DepthSearchNode *node) { - s32 diff = pos.y - terrain_gen_get_base_height((v2s32) {pos.x, pos.z}); + s32 diff = node->pos.y - terrain_gen_get_base_height((v2s32) {node->pos.x, node->pos.z}); if (diff <= 0) - return DEPTH_SEARCH_TARGET; - - return is_boulder(diff, pos) ? DEPTH_SEARCH_PATH : DEPTH_SEARCH_BLOCK; + node->type = DEPTH_SEARCH_TARGET; + else if (is_boulder(diff, node->pos)) + node->type = DEPTH_SEARCH_PATH; + else + node->type = DEPTH_SEARCH_BLOCK; } static NodeType generate_hills(BiomeArgsGenerate *args) @@ -247,7 +249,7 @@ static NodeType generate_hills(BiomeArgsGenerate *args) HillsChunkData *chunk_data = args->chunk_data; if (is_boulder(args->diff, args->pos) && (args->diff <= 0 || voxel_depth_search(args->pos, - &boulder_get_node_type, + (void *) &boulder_search_callback, NULL, &chunk_data->boulder_success[args->offset.x][args->offset.y][args->offset.z], &chunk_data->boulder_visit))) return NODE_STONE; diff --git a/src/server/database.c b/src/server/database.c index 3d06edb..aa029c9 100644 --- a/src/server/database.c +++ b/src/server/database.c @@ -6,6 +6,7 @@ #include #include "day.h" #include "server/database.h" +#include "server/server_node.h" #include "server/server_terrain.h" #include "perlin.h" @@ -138,12 +139,13 @@ bool database_load_chunk(TerrainChunk *chunk) TerrainChunkMeta *meta = chunk->extra; 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); + Blob data = {sqlite3_column_bytes(stmt, 1), (void *) sqlite3_column_blob(stmt, 1)}; + Blob tgsb = {sqlite3_column_bytes(stmt, 2), (void *) sqlite3_column_blob(stmt, 2)}; - if (!terrain_deserialize_chunk(chunk, meta->data)) { + TerrainGenStageBuffer_read(&tgsb, &meta->tgsb); + if (!terrain_deserialize_chunk(server_terrain, chunk, data, &server_node_deserialize)) { fprintf(stderr, "[error] failed deserializing chunk at (%d, %d, %d)\n", chunk->pos.x, chunk->pos.y, chunk->pos.z); - exit(EXIT_FAILURE); + abort(); } } else if (rc != SQLITE_DONE) { print_chunk_error(chunk, "loading"); @@ -163,8 +165,7 @@ void database_save_chunk(TerrainChunk *chunk) TerrainChunkMeta *meta = chunk->extra; - Blob data = {0, NULL}; - Blob_write(&data, &meta->data); + Blob data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize); Blob tgsb = {0, NULL}; TerrainGenStageBuffer_write(&tgsb, &meta->tgsb); diff --git a/src/server/schematic.c b/src/server/schematic.c index 0f18ebb..9cd9270 100644 --- a/src/server/schematic.c +++ b/src/server/schematic.c @@ -1,6 +1,7 @@ #include #include #include "server/schematic.h" +#include "server/server_node.h" #include "terrain.h" void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings) @@ -26,7 +27,6 @@ void schematic_load(List *schematic, const char *path, SchematicMapping *mapping continue; SchematicNode *node = malloc(sizeof *node); - node->data = (Blob) {0, NULL}; v3s32 color; if (sscanf(line, "%d %d %d %2x%2x%2x", @@ -52,14 +52,12 @@ void schematic_load(List *schematic, const char *path, SchematicMapping *mapping continue; } - node->type = mapping->type; - - if (mapping->use_color) - ColorData_write(&node->data, &(ColorData) {{ + node->node = mapping->use_color + ? server_node_create_color(mapping->type, (v3f32) { (f32) color.x / 0xFF, (f32) color.y / 0xFF, (f32) color.z / 0xFF, - }}); + }) : server_node_create(mapping->type); list_apd(schematic, node); } @@ -77,14 +75,14 @@ void schematic_place(List *schematic, v3s32 pos, TerrainGenStage tgs, List *chan server_terrain_gen_node( v3s32_add(pos, node->pos), - terrain_node_create(node->type, node->data), + server_node_copy(node->node), tgs, changed_chunks); } } static void delete_schematic_node(SchematicNode *node) { - Blob_free(&node->data); + server_node_delete(&node->node); free(node); } diff --git a/src/server/schematic.h b/src/server/schematic.h index d4e1a2e..213645e 100644 --- a/src/server/schematic.h +++ b/src/server/schematic.h @@ -16,8 +16,7 @@ typedef struct { typedef struct { v3s32 pos; - NodeType type; - Blob data; + TerrainNode node; } SchematicNode; void schematic_load(List *schematic, const char *path, SchematicMapping *mappings, size_t num_mappings); diff --git a/src/server/server_item.c b/src/server/server_item.c index e411d7e..a806591 100644 --- a/src/server/server_item.c +++ b/src/server/server_item.c @@ -1,23 +1,38 @@ #include "node.h" #include "server/server_item.h" +#include "server/server_node.h" #include "server/server_terrain.h" +#include "server/tree_physics.h" static void use_dig(__attribute__((unused)) ServerPlayer *player, ItemStack *stack, bool pointed, v3s32 pos) { if (!pointed) return; - NodeType node = terrain_get_node(server_terrain, pos).type; - - if (node == NODE_UNLOADED) + v3s32 off; + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &off, false); + if (!chunk) return; + TerrainChunkMeta *meta = chunk->extra; + terrain_lock_chunk(chunk); + + TerrainNode *node = &chunk->data[off.x][off.y][off.z]; - if (!(node_def[node].dig_class & item_def[stack->type].dig_class)) + if (!(node_def[node->type].dig_class & item_def[stack->type].dig_class)) { + pthread_mutex_unlock(&chunk->mtx); return; + } + + *node = server_node_create(NODE_AIR); + meta->tgsb.raw.nodes[off.x][off.y][off.z] = STAGE_PLAYER; + + pthread_mutex_unlock(&chunk->mtx); + + server_terrain_lock_and_send_chunk(chunk); - terrain_set_node(server_terrain, pos, - terrain_node_create(NODE_AIR, (Blob) {0, NULL}), - false, NULL); + // destroy trees if they have no connection to ground + // todo: run in seperate thread to not block client connection + tree_physics_check(pos); } ServerItemDef server_item_def[COUNT_ITEM] = { diff --git a/src/server/server_node.c b/src/server/server_node.c new file mode 100644 index 0000000..9918275 --- /dev/null +++ b/src/server/server_node.c @@ -0,0 +1,90 @@ +#include +#include "server/server_node.h" + +TerrainNode server_node_create(NodeType type) +{ + switch (type) { + NODES_TREE + return server_node_create_tree(type, (TreeData) {{0.5f, 0.5f, 0.5f}, 0, {0, 0, 0}}); + + default: + return (TerrainNode) {type, NULL}; + } +} + +TerrainNode server_node_create_color(NodeType type, v3f32 color) +{ + switch (type) { + NODES_TREE + return server_node_create_tree(type, (TreeData) {color, 0, {0, 0, 0}}); + + default: + return server_node_create(type); + } +} + +TerrainNode server_node_create_tree(NodeType type, TreeData data) +{ + TerrainNode node = {type, malloc(sizeof data)}; + *((TreeData *) node.data) = data; + return node; +} + +TerrainNode server_node_copy(TerrainNode node) +{ + switch (node.type) { + NODES_TREE + return server_node_create_tree(node.type, *((TreeData *) node.data)); + + default: + return server_node_create(node.type); + } +} + +void server_node_delete(TerrainNode *node) +{ + switch (node->type) { + NODES_TREE + free(node->data); + break; + + default: + break; + } +} + +void server_node_deserialize(TerrainNode *node, Blob buffer) +{ + switch (node->type) { + NODES_TREE + TreeData_read(&buffer, node->data = malloc(sizeof(TreeData))); + break; + + default: + break; + } +} + +void server_node_serialize(TerrainNode *node, Blob *buffer) +{ + switch (node->type) { + NODES_TREE + TreeData_write(buffer, node->data); + break; + + default: + break; + } +} + +void server_node_serialize_client(TerrainNode *node, Blob *buffer) +{ + switch (node->type) { + NODES_TREE + ColorData_write(buffer, &(ColorData) {((TreeData *) node->data)->color}); + break; + + default: + break; + } +} diff --git a/src/server/server_node.h b/src/server/server_node.h new file mode 100644 index 0000000..045ffcc --- /dev/null +++ b/src/server/server_node.h @@ -0,0 +1,16 @@ +#ifndef _SERVER_NODE_H_ +#define _SERVER_NODE_H_ + +#include "terrain.h" +#include "types.h" + +TerrainNode server_node_create(NodeType type); +TerrainNode server_node_create_color(NodeType type, v3f32 color); +TerrainNode server_node_create_tree(NodeType type, TreeData data); +TerrainNode server_node_copy(TerrainNode node); +void server_node_delete(TerrainNode *node); +void server_node_deserialize(TerrainNode *node, Blob buffer); +void server_node_serialize(TerrainNode *node, Blob *buffer); +void server_node_serialize_client(TerrainNode *node, Blob *buffer); + +#endif diff --git a/src/server/server_terrain.c b/src/server/server_terrain.c index 71dce28..61ce30e 100644 --- a/src/server/server_terrain.c +++ b/src/server/server_terrain.c @@ -12,6 +12,7 @@ #include "server/database.h" #include "server/schematic.h" #include "server/server_config.h" +#include "server/server_node.h" #include "server/server_terrain.h" #include "server/terrain_gen.h" #include "terrain.h" @@ -32,7 +33,7 @@ static pthread_mutex_t mtx_num_gen_chunks; // lock to protect the above 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); + v3s32 ppos = terrain_chunkp((v3s32) {player->pos.x, player->pos.y, player->pos.z}); pthread_rwlock_unlock(&player->lock_pos); return abs(ppos.x - cpos.x) <= (s32) dist @@ -41,7 +42,7 @@ static bool within_load_distance(ServerPlayer *player, v3s32 cpos, u32 dist) } // send a chunk to a client and reset chunk request -static void send_chunk(ServerPlayer *player, TerrainChunk *chunk) +static void send_chunk_to_client(ServerPlayer *player, TerrainChunk *chunk) { if (!within_load_distance(player, chunk->pos, server_config.load_distance)) return; @@ -55,34 +56,6 @@ static void send_chunk(ServerPlayer *player, TerrainChunk *chunk) 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(&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() { @@ -100,11 +73,11 @@ static void terrain_gen_step() terrain_gen_chunk(chunk, &changed_chunks); - pthread_mutex_lock(&chunk->mtx); + pthread_mutex_lock(&meta->mtx); meta->state = CHUNK_READY; - pthread_mutex_unlock(&chunk->mtx); + pthread_mutex_unlock(&meta->mtx); - list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL); + server_terrain_lock_and_send_chunks(&changed_chunks); pthread_mutex_lock(&mtx_num_gen_chunks); num_gen_chunks--; @@ -123,7 +96,7 @@ static void *terrain_gen_thread() terrain_gen_step(); return NULL; -} + } // enqueue chunk static void generate_chunk(TerrainChunk *chunk) @@ -136,25 +109,26 @@ static void generate_chunk(TerrainChunk *chunk) 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); + pthread_mutex_init(&meta->mtx, NULL); - if (!database_load_chunk(chunk)) { + if (database_load_chunk(chunk)) { + meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client); + } else { meta->state = CHUNK_CREATED; meta->data = (Blob) {0, NULL}; CHUNK_ITERATE { - chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL}); + chunk->data[x][y][z] = server_node_create(NODE_AIR); meta->tgsb.raw.nodes[x][y][z] = STAGE_VOID; } } @@ -165,6 +139,7 @@ static void on_create_chunk(TerrainChunk *chunk) static void on_delete_chunk(TerrainChunk *chunk) { TerrainChunkMeta *meta = chunk->extra; + pthread_mutex_destroy(&meta->mtx); Blob_free(&meta->data); free(meta); @@ -174,42 +149,16 @@ static void on_delete_chunk(TerrainChunk *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); - + if (create) return true; - } - return false; -} + TerrainChunkMeta *meta = chunk->extra; + pthread_mutex_lock(&meta->mtx); -// 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); + bool ret = meta->state == CHUNK_READY; + + pthread_mutex_unlock(&meta->mtx); + return ret; } // generate a hut for new players to spawn in @@ -248,8 +197,10 @@ static void generate_spawn_hut() {+4, +1}, }; - Blob wood_color = {0, NULL}; - ColorData_write(&wood_color, &(ColorData) {{(f32) 0x7d / 0xff, (f32) 0x54 / 0xff, (f32) 0x35 / 0xff}}); + v3f32 wood_color = { + (f32) 0x7d / 0xff, + (f32) 0x54 / 0xff, + (f32) 0x35 / 0xff}; for (int i = 0; i < 6; i++) { for (s32 y = spawn_height - 1;; y--) { @@ -266,17 +217,14 @@ static void generate_spawn_hut() if (node_def[node].solid) break; - server_terrain_gen_node(pos, - terrain_node_create(node == NODE_LAVA - ? NODE_VULCANO_STONE - : NODE_OAK_WOOD, - wood_color), + server_terrain_gen_node(pos, node == NODE_LAVA + ? server_node_create(NODE_VULCANO_STONE) + : server_node_create_color(NODE_OAK_WOOD, wood_color), STAGE_PLAYER, &changed_chunks); } } - Blob_free(&wood_color); - list_clr(&changed_chunks, &iterator_send_chunk_to_near, NULL, NULL); + server_terrain_lock_and_send_chunks(&changed_chunks); } // public functions @@ -288,8 +236,7 @@ void server_terrain_init() 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; + server_terrain->callbacks.delete_node = &server_node_delete; cancel = false; queue_ini(&terrain_gen_tasks); @@ -322,10 +269,9 @@ 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; + + pthread_mutex_lock(&meta->mtx); switch (meta->state) { case CHUNK_CREATED: generate_chunk(chunk); @@ -335,10 +281,11 @@ void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos) break; case CHUNK_READY: - send_chunk(player, chunk); + send_chunk_to_client(player, chunk); + break; }; - pthread_mutex_unlock(&chunk->mtx); + pthread_mutex_unlock(&meta->mtx); } } @@ -374,11 +321,12 @@ void server_terrain_prepare_spawn() return; TerrainChunk *chunk = terrain_get_chunk(server_terrain, (v3s32) {x, y, z}, true); + TerrainChunkMeta *meta = chunk->extra; - pthread_mutex_lock(&chunk->mtx); - if (((TerrainChunkMeta *) chunk->extra)->state == CHUNK_CREATED) + pthread_mutex_lock(&meta->mtx); + if (meta->state == CHUNK_CREATED) generate_chunk(chunk); - pthread_mutex_unlock(&chunk->mtx); + pthread_mutex_unlock(&meta->mtx); update_percentage(); } @@ -411,14 +359,31 @@ void server_terrain_prepare_spawn() } } -void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage tgs, List *changed_chunks) +void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks) { - TerrainSetNodeArg arg = { - .tgs = tgs, - .changed_chunks = changed_chunks, - }; + v3s32 offset; + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, true); + TerrainChunkMeta *meta = chunk->extra; - terrain_set_node(server_terrain, pos, node, true, &arg); + terrain_lock_chunk(chunk); + + u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z]; + + if (new_tgs < *tgs) { + pthread_mutex_unlock(&chunk->mtx); + server_node_delete(&node); + return; + } + + *tgs = new_tgs; + chunk->data[offset.x][offset.y][offset.z] = node; + + if (changed_chunks) + list_add(changed_chunks, chunk, chunk, &cmp_ref, NULL); + else + server_terrain_send_chunk(chunk); + + pthread_mutex_unlock(&chunk->mtx); } s32 server_terrain_spawn_height() @@ -426,3 +391,40 @@ s32 server_terrain_spawn_height() // wow, so useful! return spawn_height; } + +// send chunk to near clients +// meta mutex has to be locked +void server_terrain_send_chunk(TerrainChunk *chunk) +{ + TerrainChunkMeta *meta = chunk->extra; + + if (meta->state == CHUNK_GENERATING) + return; + + terrain_lock_chunk(chunk); + + Blob_free(&meta->data); + meta->data = terrain_serialize_chunk(server_terrain, chunk, &server_node_serialize_client); + database_save_chunk(chunk); + + pthread_mutex_unlock(&chunk->mtx); + + if (meta->state == CHUNK_CREATED) + return; + + server_player_iterate(&send_chunk_to_client, chunk); +} + +void server_terrain_lock_and_send_chunk(TerrainChunk *chunk) +{ + TerrainChunkMeta *meta = chunk->extra; + + pthread_mutex_lock(&meta->mtx); + server_terrain_send_chunk(chunk); + pthread_mutex_unlock(&meta->mtx); +} + +void server_terrain_lock_and_send_chunks(List *changed_chunks) +{ + list_clr(changed_chunks, &server_terrain_lock_and_send_chunk, NULL, NULL); +} diff --git a/src/server/server_terrain.h b/src/server/server_terrain.h index 88fa7a8..62c0a11 100644 --- a/src/server/server_terrain.h +++ b/src/server/server_terrain.h @@ -1,6 +1,7 @@ #ifndef _SERVER_TERRAIN_H_ #define _SERVER_TERRAIN_H_ +#include #include #include "server/server_player.h" #include "terrain.h" @@ -15,8 +16,7 @@ typedef enum { 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_TREES, // trees replace terrain STAGE_PLAYER, // player-placed nodes or things placed after terrain generation } TerrainGenStage; @@ -26,19 +26,56 @@ typedef struct { } TerrainSetNodeArg; typedef struct { + pthread_mutex_t mtx; // UwU please hit me senpai 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 +/* + Locking conventions: + - chunk mutex protects chunk->data and meta->tgsb + - meta mutex protects everything else in meta + - if both meta and chunk mutex are locked, meta must be locked first + - you may not lock multiple meta mutexes at once + - if multiple chunk mutexes are being locked at once, EDEADLK must be handled + - when locking a single chunk mtx, check return value and crash on failure (terrain_lock_chunk()) -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 + After changing the data in a chunk: + 1. release chunk mtx + 2. + - if meta mutex is currently locked: use server_terrain_send_chunk + - if meta mutex is not locked: use server_terrain_lock_and_send_chunk + + If an operation affects multiple nodes (potentially in multiple chunks): + - create a list changed_chunks + - do job as normal, release individual chunk mutexes immediately after modifying their data + - use server_terrain_lock_and_send_chunks to clear the list + + Note: Unless changed_chunks is given to server_terrain_gen_node, it sends chunks automatically +*/ + +// terrain object, data is stored here +extern Terrain *server_terrain; + +// called on server startup +void server_terrain_init(); +// called on server shutdown +void server_terrain_deinit(); +// handle chunk request from client (thread safe) +void server_terrain_requested_chunk(ServerPlayer *player, v3s32 pos); +// prepare spawn region +void server_terrain_prepare_spawn(); +// set node with terraingen stage +void server_terrain_gen_node(v3s32 pos, TerrainNode node, TerrainGenStage new_tgs, List *changed_chunks); +// get the spawn height because idk +s32 server_terrain_spawn_height(); +// when bit chunkus changes +void server_terrain_send_chunk(TerrainChunk *chunk); +// lock and send +void server_terrain_lock_and_send_chunk(TerrainChunk *chunk); +// lock and send multiple chunks at once +void server_terrain_lock_and_send_chunks(List *list); #endif // _SERVER_TERRAIN_H_ diff --git a/src/server/terrain_gen.c b/src/server/terrain_gen.c index 39e64b3..b5ed209 100644 --- a/src/server/terrain_gen.c +++ b/src/server/terrain_gen.c @@ -3,9 +3,10 @@ #include "environment.h" #include "perlin.h" #include "server/biomes.h" +#include "server/server_node.h" #include "server/server_terrain.h" #include "server/terrain_gen.h" -#include "server/trees.h" +#include "server/tree.h" s32 terrain_gen_get_base_height(v2s32 pos) { @@ -105,9 +106,9 @@ void terrain_gen_chunk(TerrainChunk *chunk, List *changed_chunks) } } - pthread_mutex_lock(&chunk->mtx); + terrain_lock_chunk(chunk); if (meta->tgsb.raw.nodes[x][y][z] <= STAGE_TERRAIN) { - chunk->data[x][y][z] = terrain_node_create(node, (Blob) {0, NULL}); + chunk->data[x][y][z] = server_node_create(node); meta->tgsb.raw.nodes[x][y][z] = STAGE_TERRAIN; } pthread_mutex_unlock(&chunk->mtx); diff --git a/src/server/tree.c b/src/server/tree.c new file mode 100644 index 0000000..effe1dc --- /dev/null +++ b/src/server/tree.c @@ -0,0 +1,254 @@ +#include +#include "server/biomes.h" +#include "server/server_node.h" +#include "server/server_terrain.h" +#include "server/tree.h" +#include "server/voxel_procedural.h" + +typedef struct { + NodeType type; + v3s32 root; +} ProceduralTreeArg; + +static TerrainNode create_tree_node(__attribute__((unused)) v3s32 pos, v3f32 color, ProceduralTreeArg *arg) +{ + return server_node_create_tree(arg->type, (TreeData) { + .color = color, + .has_root = 1, + .root = arg->root, + }); +} + +// oak + +static bool oak_condition(TreeArgsCondition *args) +{ + return args->biome == BIOME_HILLS; +} + +static void oak_tree_leaf(VoxelProcedural *proc, v3s32 root) +{ + if (!voxel_procedural_is_alive(proc)) + return; + + voxel_procedural_push(proc); + voxel_procedural_cube(proc, (void *) &create_tree_node, + &(ProceduralTreeArg) {NODE_OAK_LEAVES, root}); + voxel_procedural_pop(proc); + + voxel_procedural_push(proc); + voxel_procedural_x(proc, 0.5f); + voxel_procedural_sx(proc, 0.9f); + voxel_procedural_sy(proc, 0.9f); + voxel_procedural_sz(proc, 0.8f); + voxel_procedural_ry(proc, 25.0f); + voxel_procedural_x(proc, 0.4f); + oak_tree_leaf(proc, root); + voxel_procedural_pop(proc); +} + +static void oak_tree_top(VoxelProcedural *proc, v3s32 root) +{ + if (!voxel_procedural_is_alive(proc)) + return; + + voxel_procedural_push(proc); + for (int i = 0; i < 8; i++) { + voxel_procedural_rz(proc, 360.0f / 8.0f); + voxel_procedural_push(proc); + voxel_procedural_life(proc, 8); + voxel_procedural_sy(proc, 2.0f); + voxel_procedural_z(proc, voxel_procedural_random(proc, 0.0f, 5.0f)); + voxel_procedural_s(proc, 5.0f); + voxel_procedural_light(proc, -0.4f); + voxel_procedural_sat(proc, 0.5f); + voxel_procedural_hue(proc, voxel_procedural_random(proc, 60.0f, 20.0f)); + voxel_procedural_ry(proc, -45.0f); + oak_tree_leaf(proc, root); + voxel_procedural_pop(proc); + } + voxel_procedural_pop(proc); +} + +static void oak_tree_part(VoxelProcedural *proc, v3s32 root, f32 n) +{ + if (!voxel_procedural_is_alive(proc)) + return; + + voxel_procedural_push(proc); + for (int i = 1; i <= n; i++) { + voxel_procedural_z(proc, 1.0f); + voxel_procedural_rz(proc, voxel_procedural_random(proc, 30.0f, 10.0f)); + voxel_procedural_rx(proc, voxel_procedural_random(proc, 0.0f, 10.0f)); + + voxel_procedural_push(proc); + voxel_procedural_s(proc, 4.0f); + voxel_procedural_x(proc, 0.1f); + voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.1f)); + voxel_procedural_cylinder(proc, (void *) &create_tree_node, + &(ProceduralTreeArg) {NODE_OAK_WOOD, root}); + voxel_procedural_pop(proc); + + if (i == (int) (n - 2.0f)) { + voxel_procedural_push(proc); + oak_tree_top(proc, root); + voxel_procedural_pop(proc); + } + } + voxel_procedural_pop(proc); +} + +static void oak_tree(v3s32 root, List *changed_chunks) +{ + VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, root); + + voxel_procedural_hue(proc, 40.0f); + voxel_procedural_light(proc, -0.5f); + voxel_procedural_sat(proc, 0.5f); + + f32 n = voxel_procedural_random(proc, 40.0f, 10.0f); + + voxel_procedural_push(proc); + for (int i = 1; i <= 3; i++) { + voxel_procedural_rz(proc, voxel_procedural_random(proc, 120.0f, 45.0f)); + voxel_procedural_push(proc); + voxel_procedural_y(proc, 0.5f); + voxel_procedural_light(proc, voxel_procedural_random(proc, -0.3f, 0.05f)); + oak_tree_part(proc, root, n); + voxel_procedural_pop(proc); + } + voxel_procedural_pop(proc); + + voxel_procedural_delete(proc); +} + +// pine + +static bool pine_condition(TreeArgsCondition *args) +{ + return args->biome == BIOME_MOUNTAIN; +} + +static void pine_tree(v3s32 pos, List *changed_chunks) +{ + s32 tree_top = (noise2d(pos.x, pos.z, 0, seed + OFFSET_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++) { + f64 branch_length = noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + OFFSET_PINETREE_BRANCH) * 3.0; + + v3s32 dirs[4] = { + {+0, +0, +1}, + {+1, +0, +0}, + {+0, +0, -1}, + {-1, +0, +0}, + }; + + s32 dir = (noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + OFFSET_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])) + server_terrain_gen_node(branch_pos, server_node_create(NODE_PINE_WOOD), + STAGE_TREES, changed_chunks); + + server_terrain_gen_node(tree_pos, server_node_create(NODE_PINE_WOOD), + STAGE_TREES, changed_chunks); + } +} + +// palm + +static bool palm_condition(TreeArgsCondition *args) +{ + 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(VoxelProcedural *proc, v3s32 root) +{ + if (!voxel_procedural_is_alive(proc)) + return; + + voxel_procedural_cube(proc, (void *) &create_tree_node, + &(ProceduralTreeArg) {NODE_PALM_LEAVES, root}); + + voxel_procedural_push(proc); + voxel_procedural_z(proc, 0.5f); + voxel_procedural_s(proc, 0.8f); + voxel_procedural_rx(proc, voxel_procedural_random(proc, 20.0f, 4.0f)); + voxel_procedural_z(proc, 0.5f); + palm_branch(proc, root); + voxel_procedural_pop(proc); +} + +static void palm_tree(v3s32 root, List *changed_chunks) +{ + VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, (v3s32) {root.x, root.y - 1, root.z}); + + f32 s = voxel_procedural_random(proc, 8.0f, 2.0f); + + voxel_procedural_push(proc); + for (int i = 1; i <= s; i++) { + voxel_procedural_z(proc, 1.0f); + voxel_procedural_push(proc); + voxel_procedural_s(proc, 1.0f); + voxel_procedural_light(proc, voxel_procedural_random(proc, -0.8f, 0.1f)); + voxel_procedural_sat(proc, 0.5f); + voxel_procedural_cube(proc, (void *) &create_tree_node, + &(ProceduralTreeArg) {NODE_PALM_WOOD, root}); + voxel_procedural_pop(proc); + } + voxel_procedural_pop(proc); + + voxel_procedural_z(proc, s); + voxel_procedural_sat(proc, 1.0f), + voxel_procedural_light(proc, -0.5f); + voxel_procedural_hue(proc, voxel_procedural_random(proc, 50.0f, 30.0f)); + + voxel_procedural_push(proc); + for (int i = 0; i < 6; i++) { + voxel_procedural_rz(proc, 360.0f / 6.0f); + voxel_procedural_rz(proc, voxel_procedural_random(proc, 0.0f, 10.0f)); + voxel_procedural_push(proc); + voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.3f)); + voxel_procedural_rx(proc, 90.0f); + voxel_procedural_s(proc, 2.0f); + palm_branch(proc, root); + voxel_procedural_pop(proc); + } + voxel_procedural_pop(proc); + + voxel_procedural_delete(proc); +} + +TreeDef tree_def[NUM_TREES] = { + // oak + { + .spread = 64.0f, + .probability = 0.0005f, + .area_probability = 0.3f, + .offset = OFFSET_OAKTREE, + .area_offset = OFFSET_OAKTREE_AREA, + .condition = &oak_condition, + .generate = &oak_tree, + }, + // pine + { + .spread = 256.0f, + .probability = 0.01f, + .area_probability = 0.1f, + .offset = OFFSET_PINETREE, + .area_offset = OFFSET_PINETREE_AREA, + .condition = &pine_condition, + .generate = &pine_tree, + }, + // palm + { + .spread = 16.0f, + .probability = 0.005f, + .area_probability = 0.5f, + .offset = OFFSET_PALMTREE, + .area_offset = OFFSET_PALMTREE_AREA, + .condition = &palm_condition, + .generate = &palm_tree, + }, +}; + diff --git a/src/server/tree.h b/src/server/tree.h new file mode 100644 index 0000000..2eb08aa --- /dev/null +++ b/src/server/tree.h @@ -0,0 +1,35 @@ +#ifndef _TREE_H_ +#define _TREE_H_ + +#include +#include +#include "perlin.h" +#include "terrain.h" +#include "types.h" + +#define NUM_TREES 3 + +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)(TreeArgsCondition *args); + void (*generate)(v3s32 pos, List *changed_chunks); +} TreeDef; + +extern TreeDef tree_def[]; + +#endif // _TREE_H_ diff --git a/src/server/tree_physics.c b/src/server/tree_physics.c new file mode 100644 index 0000000..03c6726 --- /dev/null +++ b/src/server/tree_physics.c @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "facedir.h" +#include "server/server_node.h" +#include "server/server_terrain.h" +#include "server/tree_physics.h" +#include "server/voxel_depth_search.h" + +typedef struct { + v3s32 root; + bool deadlock; + Tree chunks; +} CheckTreeArg; + +typedef struct { + TerrainChunk *chunk; + TerrainNode *node; + u32 *tgs; +} CheckTreeSearchNodeMeta; + +static inline bool is_tree_with_root(TerrainNode *node) +{ + switch (node->type) { + NODES_TREE + return ((TreeData *) node->data)->has_root; + + default: + return false; + } +} + +static int cmp_chunk(const TerrainChunk *chunk, const v3s32 *pos) +{ + return v3s32_cmp(&chunk->pos, pos); +} + +static void unlock_chunk(TerrainChunk *chunk) +{ + pthread_mutex_unlock(&chunk->mtx); +} + +static void init_search_node(DepthSearchNode *search_node, CheckTreeArg *arg) +{ + // first, get chunk position and offset + v3s32 chunkp = terrain_chunkp(search_node->pos); + + // check for chunk in cache + TerrainChunk *chunk = tree_get(&arg->chunks, &chunkp, &cmp_chunk, NULL); + + // if not found in cache, get it from server_terrain and lock it + if (!chunk) { + chunk = terrain_get_chunk(server_terrain, chunkp, false); + + // check if chunk is unloaded + if (!chunk) { + // if chunk is unloaded, don't remove the tree, it might have a connection to ground + search_node->type = DEPTH_SEARCH_TARGET; + + // done + return; + } + + // try to obtain the chunk mutex + int lock_err = pthread_mutex_lock(&chunk->mtx); + + // a deadlock might occur because of the order the chunks are locked + if (lock_err == EDEADLK) { + // notify caller deadlock has occured + arg->deadlock = true; + + // finish search directly + search_node->type = DEPTH_SEARCH_TARGET; + + // done + return; + } else if (lock_err != 0) { + // a different error has occured while trying to obtain the lock + // this should never happen + + // print error message + fprintf(stderr, "[error] failed to lock terrain chunk mutex\n"); + + // exit program + abort(); + } + + // insert chunk into cache + tree_add(&arg->chunks, &chunk->pos, chunk, &cmp_chunk, NULL); + } + + // get node offset + v3s32 offset = terrain_offset(search_node->pos); + + // type coersion for easier access + TerrainChunkMeta *meta = chunk->extra; + + // pointer to node and generation stage + TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z]; + u32 *tgs = &meta->tgsb.raw.nodes[offset.x][offset.y][offset.z]; + + // type coersion for easier access + TreeData *data = node->data; + + // have we found terrain? + if (*tgs == STAGE_TERRAIN && node->type != NODE_AIR) { + // if we've reached the target, set search node type accordingly + search_node->type = DEPTH_SEARCH_TARGET; + } else if (is_tree_with_root(node) && v3s32_equals(arg->root, data->root)) { + // if node is part of our tree, continue search + search_node->type = DEPTH_SEARCH_PATH; + + // allocate meta storage + CheckTreeSearchNodeMeta *search_meta = search_node->extra = malloc(sizeof *search_meta); + + // store chunk, node and stage pointer for later + search_meta->chunk = chunk; + search_meta->node = node; + search_meta->tgs = tgs; + } else { + // otherwise, this is a roadblock + search_node->type = DEPTH_SEARCH_BLOCK; + } +} + +static void free_search_node(DepthSearchNode *node) +{ + if (node->extra) + free(node->extra); + + free(node); +} + +static void destroy_search_node(DepthSearchNode *node, List *changed_chunks) +{ + if (node->type == DEPTH_SEARCH_PATH && !(*node->success)) { + // this is a tree/leaves node without connection to ground + + CheckTreeSearchNodeMeta *meta = node->extra; + + // overwrite node and generation stage + *meta->node = server_node_create(NODE_AIR); + *meta->tgs = STAGE_PLAYER; + + // flag chunk as changed + list_add(changed_chunks, meta->chunk, meta->chunk, &cmp_ref, NULL); + } + + free_search_node(node); +} + +/* + Check whether all positions (that are part of the same tree) still are connected to the ground. + Destroy any tree parts without ground connection. + + The advantage of grouping them together is that they can use the same search cache. +*/ +static bool check_tree(v3s32 root, Array *positions, Array *chunks) +{ + CheckTreeArg arg; + // inform depth search callbacks about root of tree (to only match nodes that belong to it) + arg.root = root; + // output parameter to prevent deadlocks + arg.deadlock = false; + // cache chunks, to accelerate lookup and prevent locking them twice + tree_ini(&arg.chunks); + + // add the chunks the starting points are in to the chunk cache + for (size_t i = 0; i < chunks->siz; i++) { + TerrainChunk *chunk = ((TerrainChunk **) chunks->ptr)[i]; + tree_add(&arg.chunks, &chunk->pos, chunk, &cmp_chunk, NULL); + } + + // nodes that have been visited + // serves as search cache and contains all tree nodes, to remove them if no ground found + Tree visit; + tree_ini(&visit); + + // success means ground has been found + + // true if ground has been found for all positions + bool success_all = true; + // individual buffer for each start position (required by depth search algo) + bool success_buf[positions->siz]; + + // iterate over start positions + for (size_t i = 0; i < positions->siz; i++) { + success_buf[i] = false; + + // call depth search algorithm to collect positions and find ground + if (!voxel_depth_search(((v3s32 *) positions->ptr)[i], (void *) &init_search_node, &arg, + &success_buf[i], &visit)) + success_all = false; + + // immediately stop if there was a deadlock + if (arg.deadlock) + break; + } + + if (success_all || arg.deadlock) { + // ground has been found for all parts (or a deadlock was detected) + + // if ground has been found for all, there is no need to pass more complex callback + tree_clr(&visit, &free_search_node, NULL, NULL, 0); + + // unlock grabbed chunks + tree_clr(&arg.chunks, &unlock_chunk, NULL, NULL, 0); + + // return false if there was a deadlock - caller will reinitiate search + return !arg.deadlock; + } + + // keep track of changed chunks + List changed_chunks; + list_ini(&changed_chunks); + + // some or all positions have no connection to ground, pass callback to destroy nodes without + tree_clr(&visit, &destroy_search_node, &changed_chunks, NULL, 0); + + // now, unlock all the chunks (before sending some of them) + tree_clr(&arg.chunks, &unlock_chunk, NULL, NULL, 0); + + // send changed chunks + server_terrain_lock_and_send_chunks(&changed_chunks); + + // done + return true; +} + +/* + To be called after a node has been removed. + For all neigbors that are part of a tree, check whether the tree now still has connection to + the ground, destroy tree (partly) otherwise. + + - select neighbor nodes that are leaves or wood + - in every iteration, select only the nodes that belong to the same tree (have the same root) + - in every iteration, keep the mutexes of the chunks the selected nodes belong to locked + - skip nodes that don't match the currently selected root, process them in a later iteration +*/ +void tree_physics_check(v3s32 center) +{ + // remember directions that have been processed + bool dirs[6] = {false}; + + bool skipped; + do { + skipped = false; + + // the first node that has a root will initialize these variables + bool selected_root = false; + v3s32 root; + + // remember selected positions and their associated locked chunks + Array positions, chunks; + array_ini(&positions, sizeof(v3s32), 5); + array_ini(&chunks, sizeof(TerrainChunk *), 5); + + // remember indices of positions selected in this iteration + bool selected[6] = {false}; + + for (int i = 0; i < 6; i++) { + // we already processed this direction + if (dirs[i]) + continue; + + // this may change back to false if we skip + dirs[i] = true; + + // facedir contains offsets to neighbor nodes + v3s32 pos = v3s32_add(center, facedir[i]); + + // get chunk + v3s32 offset; + TerrainChunk *chunk = terrain_get_chunk_nodep(server_terrain, pos, &offset, false); + if (!chunk) + continue; + + // check if chunk is already locked + bool locked_before = array_idx(&chunks, &chunk) != -1; + + // lock if not locked + if (!locked_before) + terrain_lock_chunk(chunk); + + // now that chunk is locked, actually get node + TerrainNode *node = &chunk->data[offset.x][offset.y][offset.z]; + + // check whether we're dealing with a tree node that has a root + if (is_tree_with_root(node)) { + // type coersion for easier access + TreeData *data = node->data; + + // select root and initialize variables + if (!selected_root) { + selected_root = true; + root = data->root; + } + + // check whether root matches + if (v3s32_equals(root, data->root)) { + // remember position + array_apd(&positions, &pos); + + // remember chunk - unless it's already on the list + if (!locked_before) + array_apd(&chunks, &chunk); + + // remember index was selected + selected[i] = true; + + // don't run rest of loop body: don't unlock chunk mutex + continue; + } else { + // doesn't match selected root: mark as skipped + skipped = true; + dirs[i] = false; + } + } + + // only unlock if it wasn't locked before + if (!locked_before) + pthread_mutex_unlock(&chunk->mtx); + } + + if (selected_root) { + // run depth search + if (!check_tree(root, &positions, &chunks)) { + // a return value of false means a deadlock occured (should be very rare) + printf("[verbose] tree_physics detected deadlock (this not an issue, but should not happen frequently)\n"); + + // sleep for 50ms to hopefully resolve the conflict + nanosleep(&(struct timespec) {0, 50e6}, NULL); + + // invalidate faces that were selected in this iteration + for (int i = 0; i < 6; i++) + if (selected[i]) + dirs[i] = false; + } + + // free memory + array_clr(&positions); + array_clr(&chunks); + } + + // repeat until all directions have been processed + } while (skipped); +} diff --git a/src/server/tree_physics.h b/src/server/tree_physics.h new file mode 100644 index 0000000..bee088a --- /dev/null +++ b/src/server/tree_physics.h @@ -0,0 +1,8 @@ +#ifndef _TREE_PHYSICS_H_ +#define _TREE_PHYSICS_H_ + +#include "types.h" + +void tree_physics_check(v3s32 pos); + +#endif // _TREE_PHYSICS_H_ diff --git a/src/server/trees.c b/src/server/trees.c deleted file mode 100644 index d5c04d6..0000000 --- a/src/server/trees.c +++ /dev/null @@ -1,236 +0,0 @@ -#include -#include "server/biomes.h" -#include "server/server_terrain.h" -#include "server/trees.h" -#include "server/voxel_procedural.h" - -// oak - -static bool oak_condition(TreeArgsCondition *args) -{ - return args->biome == BIOME_HILLS; -} - -static void oak_tree_leaf(VoxelProcedural *proc) -{ - if (!voxel_procedural_is_alive(proc)) - return; - - voxel_procedural_push(proc); - voxel_procedural_cube(proc, NODE_OAK_LEAVES, true); - voxel_procedural_pop(proc); - - voxel_procedural_push(proc); - voxel_procedural_x(proc, 0.5f); - voxel_procedural_sx(proc, 0.9f); - voxel_procedural_sy(proc, 0.9f); - voxel_procedural_sz(proc, 0.8f); - voxel_procedural_ry(proc, 25.0f); - voxel_procedural_x(proc, 0.4f); - oak_tree_leaf(proc); - voxel_procedural_pop(proc); -} - -static void oak_tree_top(VoxelProcedural *proc) -{ - if (!voxel_procedural_is_alive(proc)) - return; - - voxel_procedural_push(proc); - for (int i = 0; i < 8; i++) { - voxel_procedural_rz(proc, 360.0f / 8.0f); - voxel_procedural_push(proc); - voxel_procedural_life(proc, 8); - voxel_procedural_sy(proc, 2.0f); - voxel_procedural_z(proc, voxel_procedural_random(proc, 0.0f, 5.0f)); - voxel_procedural_s(proc, 5.0f); - voxel_procedural_light(proc, -0.4f); - voxel_procedural_sat(proc, 0.5f); - voxel_procedural_hue(proc, voxel_procedural_random(proc, 60.0f, 20.0f)); - voxel_procedural_ry(proc, -45.0f); - oak_tree_leaf(proc); - voxel_procedural_pop(proc); - } - voxel_procedural_pop(proc); -} - -static void oak_tree_part(VoxelProcedural *proc, f32 n) -{ - if (!voxel_procedural_is_alive(proc)) - return; - - voxel_procedural_push(proc); - for (int i = 1; i <= n; i++) { - voxel_procedural_z(proc, 1.0f); - voxel_procedural_rz(proc, voxel_procedural_random(proc, 30.0f, 10.0f)); - voxel_procedural_rx(proc, voxel_procedural_random(proc, 0.0f, 10.0f)); - - voxel_procedural_push(proc); - voxel_procedural_s(proc, 4.0f); - voxel_procedural_x(proc, 0.1f); - voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.1f)); - voxel_procedural_cylinder(proc, NODE_OAK_WOOD, true); - voxel_procedural_pop(proc); - - if (i == (int) (n - 2.0f)) { - voxel_procedural_push(proc); - oak_tree_top(proc); - voxel_procedural_pop(proc); - } - } - voxel_procedural_pop(proc); -} - -static void oak_tree(v3s32 pos, List *changed_chunks) -{ - VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, pos); - - voxel_procedural_hue(proc, 40.0f); - voxel_procedural_light(proc, -0.5f); - voxel_procedural_sat(proc, 0.5f); - - f32 n = voxel_procedural_random(proc, 40.0f, 10.0f); - - voxel_procedural_push(proc); - for (int i = 1; i <= 3; i++) { - voxel_procedural_rz(proc, voxel_procedural_random(proc, 120.0f, 45.0f)); - voxel_procedural_push(proc); - voxel_procedural_y(proc, 0.5f); - voxel_procedural_light(proc, voxel_procedural_random(proc, -0.3f, 0.05f)); - oak_tree_part(proc, n); - voxel_procedural_pop(proc); - } - voxel_procedural_pop(proc); - - voxel_procedural_delete(proc); -} - -// pine - -static bool pine_condition(TreeArgsCondition *args) -{ - return args->biome == BIOME_MOUNTAIN; -} - -static void pine_tree(v3s32 pos, List *changed_chunks) -{ - s32 tree_top = (noise2d(pos.x, pos.z, 0, seed + OFFSET_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++) { - f64 branch_length = noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + OFFSET_PINETREE_BRANCH) * 3.0; - - v3s32 dirs[4] = { - {+0, +0, +1}, - {+1, +0, +0}, - {+0, +0, -1}, - {-1, +0, +0}, - }; - - s32 dir = (noise3d(tree_pos.x, tree_pos.y, tree_pos.z, 0, seed + OFFSET_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])) - server_terrain_gen_node(branch_pos, - terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}), - STAGE_TREES, changed_chunks); - - server_terrain_gen_node(tree_pos, - terrain_node_create(NODE_PINE_WOOD, (Blob) {0, NULL}), - STAGE_TREES, changed_chunks); - } -} - -// palm - -static bool palm_condition(TreeArgsCondition *args) -{ - 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(VoxelProcedural *proc) -{ - if (!voxel_procedural_is_alive(proc)) - return; - - voxel_procedural_cube(proc, NODE_PALM_LEAVES, true); - voxel_procedural_push(proc); - voxel_procedural_z(proc, 0.5f); - voxel_procedural_s(proc, 0.8f); - voxel_procedural_rx(proc, voxel_procedural_random(proc, 20.0f, 4.0f)); - voxel_procedural_z(proc, 0.5f); - palm_branch(proc); - voxel_procedural_pop(proc); -} - -static void palm_tree(v3s32 pos, List *changed_chunks) -{ - VoxelProcedural *proc = voxel_procedural_create(changed_chunks, STAGE_TREES, (v3s32) {pos.x, pos.y - 1, pos.z}); - - f32 s = voxel_procedural_random(proc, 8.0f, 2.0f); - - voxel_procedural_push(proc); - for (int i = 1; i <= s; i++) { - voxel_procedural_z(proc, 1.0f); - voxel_procedural_push(proc); - voxel_procedural_s(proc, 1.0f); - voxel_procedural_light(proc, voxel_procedural_random(proc, -0.8f, 0.1f)); - voxel_procedural_sat(proc, 0.5f); - voxel_procedural_cube(proc, NODE_PALM_WOOD, true); - voxel_procedural_pop(proc); - } - voxel_procedural_pop(proc); - - voxel_procedural_z(proc, s); - voxel_procedural_sat(proc, 1.0f), - voxel_procedural_light(proc, -0.5f); - voxel_procedural_hue(proc, voxel_procedural_random(proc, 50.0f, 30.0f)); - - voxel_procedural_push(proc); - for (int i = 0; i < 6; i++) { - voxel_procedural_rz(proc, 360.0f / 6.0f); - voxel_procedural_rz(proc, voxel_procedural_random(proc, 0.0f, 10.0f)); - voxel_procedural_push(proc); - voxel_procedural_light(proc, voxel_procedural_random(proc, 0.0f, 0.3f)); - voxel_procedural_rx(proc, 90.0f); - voxel_procedural_s(proc, 2.0f); - palm_branch(proc); - voxel_procedural_pop(proc); - } - voxel_procedural_pop(proc); - - voxel_procedural_delete(proc); -} - -TreeDef tree_def[NUM_TREES] = { - // oak - { - .spread = 64.0f, - .probability = 0.0005f, - .area_probability = 0.3f, - .offset = OFFSET_OAKTREE, - .area_offset = OFFSET_OAKTREE_AREA, - .condition = &oak_condition, - .generate = &oak_tree, - }, - // pine - { - .spread = 256.0f, - .probability = 0.01f, - .area_probability = 0.1f, - .offset = OFFSET_PINETREE, - .area_offset = OFFSET_PINETREE_AREA, - .condition = &pine_condition, - .generate = &pine_tree, - }, - // palm - { - .spread = 16.0f, - .probability = 0.005f, - .area_probability = 0.5f, - .offset = OFFSET_PALMTREE, - .area_offset = OFFSET_PALMTREE_AREA, - .condition = &palm_condition, - .generate = &palm_tree, - }, -}; - diff --git a/src/server/trees.h b/src/server/trees.h deleted file mode 100644 index e4ee7ef..0000000 --- a/src/server/trees.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _TREES_H_ -#define _TREES_H_ - -#include -#include -#include "perlin.h" -#include "terrain.h" -#include "types.h" - -#define NUM_TREES 3 - -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)(TreeArgsCondition *args); - void (*generate)(v3s32 pos, List *changed_chunks); -} TreeDef; - -extern TreeDef tree_def[]; - -#endif // _TREES_H_ diff --git a/src/server/voxel_depth_search.c b/src/server/voxel_depth_search.c index 722aeda..edf9fb5 100644 --- a/src/server/voxel_depth_search.c +++ b/src/server/voxel_depth_search.c @@ -15,7 +15,7 @@ static int cmp_depth_search_node(const DepthSearchNode *node, const v3s32 *pos) return v3s32_cmp(&node->pos, pos); } -bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), bool *success, Tree *visit) +bool voxel_depth_search(v3s32 pos, void (*callback)(DepthSearchNode *node, void *arg), void *arg, bool *success, Tree *visit) { TreeNode **tree_node = tree_nfd(visit, &pos, &cmp_depth_search_node); if (*tree_node) @@ -23,14 +23,15 @@ bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), b DepthSearchNode *node = malloc(sizeof *node); tree_nmk(visit, tree_node, node); - node->type = get_type(pos); node->pos = pos; + node->extra = NULL; + callback(node, arg); if ((*(node->success = success) = (node->type == DEPTH_SEARCH_TARGET))) return true; if (node->type == DEPTH_SEARCH_PATH) for (int i = 0; i < 6; i++) - if (voxel_depth_search(v3s32_add(pos, dirs[i]), get_type, success, visit)) + if (voxel_depth_search(v3s32_add(pos, dirs[i]), callback, arg, success, visit)) return true; return false; diff --git a/src/server/voxel_depth_search.h b/src/server/voxel_depth_search.h index 2712707..b9163a0 100644 --- a/src/server/voxel_depth_search.h +++ b/src/server/voxel_depth_search.h @@ -8,15 +8,16 @@ typedef enum { DEPTH_SEARCH_TARGET, // goal has been reached DEPTH_SEARCH_PATH, // can used this as path - DEPTH_SEARCH_BLOCK // cannot use this as paths + DEPTH_SEARCH_BLOCK // cannot use this as path } DepthSearchNodeType; typedef struct { v3s32 pos; DepthSearchNodeType type; bool *success; + void *extra; } DepthSearchNode; -bool voxel_depth_search(v3s32 pos, DepthSearchNodeType (*get_type)(v3s32 pos), bool *success, Tree *visit); +bool voxel_depth_search(v3s32 pos, void (*callback)(DepthSearchNode *node, void *arg), void *arg, bool *success, Tree *visit); #endif // _VOXEL_DEPTH_SEARCH_ diff --git a/src/server/voxel_procedural.c b/src/server/voxel_procedural.c index ff61ea8..2eb9bf8 100644 --- a/src/server/voxel_procedural.c +++ b/src/server/voxel_procedural.c @@ -171,11 +171,16 @@ bool voxel_procedural_is_alive(VoxelProcedural *proc) VOXEL_PROCEDURAL_STATE(proc).scale[2] >= 1.0f; } -void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color) +void voxel_procedural_cube(VoxelProcedural *proc, VoxelProceduralNode func, void *arg) { if (!voxel_procedural_is_alive(proc)) return; + v3f32 color = hsl_to_rgb((v3f32) { + VOXEL_PROCEDURAL_STATE(proc).h / 360.0, + VOXEL_PROCEDURAL_STATE(proc).s, + VOXEL_PROCEDURAL_STATE(proc).l}); + vec4 base_corners[8] = { {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, @@ -221,30 +226,16 @@ void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color) v[i] = floor(VOXEL_PROCEDURAL_STATE(proc).pos[i] + f + 0.5f); } - Blob buffer = {0, NULL}; - - if (use_color) - ColorData_write(&buffer, &(ColorData) {hsl_to_rgb((v3f32) { - VOXEL_PROCEDURAL_STATE(proc).h / 360.0, - VOXEL_PROCEDURAL_STATE(proc).s, - VOXEL_PROCEDURAL_STATE(proc).l, - })}); - - server_terrain_gen_node( - v3s32_add(proc->pos, (v3s32) {v[0], v[2], v[1]}), - terrain_node_create(node, buffer), - proc->tgs, - proc->changed_chunks - ); - - Blob_free(&buffer); + v3s32 pos = v3s32_add(proc->pos, (v3s32) {v[0], v[2], v[1]}); + server_terrain_gen_node(pos, func(pos, color, arg), + proc->tgs, proc->changed_chunks); } } -void voxel_procedural_cylinder(VoxelProcedural *proc, NodeType node, bool use_color) +void voxel_procedural_cylinder(VoxelProcedural *proc, VoxelProceduralNode func, void *arg) { - voxel_procedural_cube(proc, node, use_color); + voxel_procedural_cube(proc, func, arg); } /* diff --git a/src/server/voxel_procedural.h b/src/server/voxel_procedural.h index c953add..6de0f1e 100644 --- a/src/server/voxel_procedural.h +++ b/src/server/voxel_procedural.h @@ -24,6 +24,8 @@ typedef struct { List state; } VoxelProcedural; +typedef TerrainNode (*VoxelProceduralNode)(v3s32 pos, v3f32 color, void *arg); + VoxelProcedural *voxel_procedural_create(List *changed_chunks, TerrainGenStage tgs, v3s32 pos); void voxel_procedural_delete(VoxelProcedural *proc); void voxel_procedural_hue(VoxelProcedural *proc, f32 value); @@ -43,8 +45,8 @@ void voxel_procedural_s(VoxelProcedural *proc, f32 value); void voxel_procedural_pop(VoxelProcedural *proc); void voxel_procedural_push(VoxelProcedural *proc); bool voxel_procedural_is_alive(VoxelProcedural *proc); -void voxel_procedural_cube(VoxelProcedural *proc, NodeType node, bool use_color); -void voxel_procedural_cylinder(VoxelProcedural *proc, NodeType node, bool use_color); +void voxel_procedural_cube(VoxelProcedural *proc, VoxelProceduralNode func, void *arg); +void voxel_procedural_cylinder(VoxelProcedural *proc, VoxelProceduralNode func, void *arg); f32 voxel_procedural_random(VoxelProcedural *proc, f32 base, f32 vary); #endif // _VOXEL_PROCEDURAL_H_ diff --git a/src/terrain.c b/src/terrain.c index 3897b44..9238b8e 100644 --- a/src/terrain.c +++ b/src/terrain.c @@ -1,16 +1,49 @@ #include #include +#include #include #include #include #include "terrain.h" +typedef struct { + v2s32 pos; + Tree chunks; + pthread_rwlock_t lock; +} TerrainSector; + +static TerrainChunk *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_ERRORCHECK); + pthread_mutex_init(&chunk->mtx, &attr); + + CHUNK_ITERATE + chunk->data[x][y][z] = (TerrainNode) {NODE_UNKNOWN, NULL}; + + return chunk; +} + +static void free_chunk(Terrain *terrain, TerrainChunk *chunk) +{ + if (terrain->callbacks.delete_node) CHUNK_ITERATE + terrain->callbacks.delete_node(&chunk->data[x][y][z]); + + pthread_mutex_destroy(&chunk->mtx); + free(chunk); +} + static void delete_chunk(TerrainChunk *chunk, Terrain *terrain) { if (terrain->callbacks.delete_chunk) terrain->callbacks.delete_chunk(chunk); - terrain_free_chunk(chunk); + free_chunk(terrain, chunk); } static void delete_sector(TerrainSector *sector, Terrain *terrain) @@ -20,25 +53,7 @@ static void delete_sector(TerrainSector *sector, Terrain *terrain) 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, &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) +static TerrainSector *get_sector(Terrain *terrain, v2s32 pos, bool create) { if (create) pthread_rwlock_wrlock(&terrain->lock); @@ -64,6 +79,24 @@ TerrainSector *terrain_get_sector(Terrain *terrain, v2s32 pos, bool create) return 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, &delete_sector, terrain, NULL, 0); + pthread_rwlock_destroy(&terrain->lock); + pthread_rwlock_destroy(&terrain->cache_lock); + free(terrain); +} + TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) { TerrainChunk *cache = NULL; @@ -75,7 +108,7 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) if (cache && v3s32_equals(cache->pos, pos)) return cache; - TerrainSector *sector = terrain_get_sector(terrain, (v2s32) {pos.x, pos.z}, create); + TerrainSector *sector = get_sector(terrain, (v2s32) {pos.x, pos.z}, create); if (!sector) return NULL; @@ -90,18 +123,15 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) 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)); + tree_nmk(§or->chunks, loc, chunk = allocate_chunk(pos)); if (terrain->callbacks.create_chunk) terrain->callbacks.create_chunk(chunk); @@ -112,33 +142,16 @@ TerrainChunk *terrain_get_chunk(Terrain *terrain, v3s32 pos, bool create) return chunk; } -TerrainChunk *terrain_allocate_chunk(v3s32 pos) +TerrainChunk *terrain_get_chunk_nodep(Terrain *terrain, v3s32 nodep, v3s32 *offset, bool create) { - 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}); - + TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_chunkp(nodep), create); + if (!chunk) + return NULL; + *offset = terrain_offset(nodep); 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) +Blob terrain_serialize_chunk(__attribute__((unused)) Terrain *terrain, TerrainChunk *chunk, void (*callback)(TerrainNode *node, Blob *buffer)) { bool empty = true; @@ -152,118 +165,95 @@ Blob terrain_serialize_chunk(TerrainChunk *chunk) if (empty) return (Blob) {0, NULL}; - SerializedTerrainChunk chunk_data; + SerializedTerrainChunk serialized_chunk; 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, - }, - }; + SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z]; - NodeDef *def = &node_def[node->type]; + serialized->type = node->type; + serialized->data = (Blob) {0, NULL}; - if (def->callbacks.serialize) - def->callbacks.serialize(&node_data->data, node->data); + if (callback) + callback(node, &serialized->data); } Blob buffer = {0, NULL}; - SerializedTerrainChunk_write(&buffer, &chunk_data); - SerializedTerrainChunk_free(&chunk_data); - + SerializedTerrainChunk_write(&buffer, &serialized_chunk); + SerializedTerrainChunk_free(&serialized_chunk); return buffer; } -bool terrain_deserialize_chunk(TerrainChunk *chunk, Blob buffer) +bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffer, void (*callback)(TerrainNode *node, Blob buffer)) { if (buffer.siz == 0) { - CHUNK_ITERATE - chunk->data[x][y][z] = terrain_node_create(NODE_AIR, (Blob) {0, NULL}); + CHUNK_ITERATE { + if (terrain->callbacks.delete_node) + terrain->callbacks.delete_node(&chunk->data[x][y][z]); + chunk->data[x][y][z] = (TerrainNode) {NODE_AIR, 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); + SerializedTerrainChunk serialized_chunk = {0}; + bool success = SerializedTerrainChunk_read(&buffer, &serialized_chunk); - 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); + if (success) CHUNK_ITERATE { + if (terrain->callbacks.delete_node) + terrain->callbacks.delete_node(&chunk->data[x][y][z]); - SerializedTerrainChunk_free(&chunk_data); - return success; -} + TerrainNode *node = &chunk->data[x][y][z]; + SerializedTerrainNode *serialized = &serialized_chunk.raw.nodes[x][y][z]; -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)}; -} + node->type = serialized->type; -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]; + if (callback) + callback(node, serialized->data); + } + + SerializedTerrainChunk_free(&serialized_chunk); + return success; } -void terrain_set_node(Terrain *terrain, v3s32 pos, TerrainNode node, bool create, void *arg) +void terrain_lock_chunk(TerrainChunk *chunk) { - v3u8 offset; - TerrainChunk *chunk = terrain_get_chunk(terrain, terrain_node_to_chunk_pos(pos, &offset), create); - - if (!chunk) + if (pthread_mutex_lock(&chunk->mtx) == 0) 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); + fprintf(stderr, "[error] failed to lock terrain chunk mutex\n"); + abort(); } -TerrainNode terrain_node_create(NodeType type, Blob buffer) +TerrainNode terrain_get_node(Terrain *terrain, v3s32 pos) { - if (type >= NODE_UNLOADED) - type = NODE_UNKNOWN; - - NodeDef *def = &node_def[type]; - - TerrainNode node; - node.type = type; - node.data = def->data_size ? malloc(def->data_size) : NULL; - - if (def->callbacks.create) - def->callbacks.create(&node); + v3s32 offset; + TerrainChunk *chunk = terrain_get_chunk_nodep(terrain, pos, &offset, false); + if (!chunk) + return (TerrainNode) {COUNT_NODE, NULL}; - if (def->callbacks.deserialize) - def->callbacks.deserialize(&buffer, node.data); + terrain_lock_chunk(chunk); + TerrainNode node = chunk->data[offset.x][offset.y][offset.z]; + pthread_mutex_unlock(&chunk->mtx); return node; } -void terrain_node_delete(TerrainNode node) +v3s32 terrain_chunkp(v3s32 pos) { - NodeDef *def = &node_def[node.type]; - - if (def->callbacks.delete) - def->callbacks.delete(&node); + return (v3s32) { + floor((double) pos.x / (double) CHUNK_SIZE), + floor((double) pos.y / (double) CHUNK_SIZE), + floor((double) pos.z / (double) CHUNK_SIZE)}; +} - if (node.data) - free(node.data); +v3s32 terrain_offset(v3s32 pos) +{ + return (v3s32) { + (u32) pos.x % CHUNK_SIZE, + (u32) pos.y % CHUNK_SIZE, + (u32) pos.z % CHUNK_SIZE}; } diff --git a/src/terrain.h b/src/terrain.h index 44098c7..54869e4 100644 --- a/src/terrain.h +++ b/src/terrain.h @@ -18,23 +18,14 @@ typedef struct TerrainNode { void *data; } TerrainNode; -typedef TerrainNode TerrainChunkData[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; - typedef struct { s32 level; v3s32 pos; - TerrainChunkData data; + TerrainNode data[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; 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; @@ -44,29 +35,24 @@ typedef 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); + void (*delete_node)(TerrainNode *node); } 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_get_chunk_nodep(Terrain *terrain, v3s32 node_pos, v3s32 *offset, 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); +Blob terrain_serialize_chunk(Terrain *terrain, TerrainChunk *chunk, void (*callback)(TerrainNode *node, Blob *buffer)); +bool terrain_deserialize_chunk(Terrain *terrain, TerrainChunk *chunk, Blob buffer, void (*callback)(TerrainNode *node, Blob buffer)); 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); +void terrain_lock_chunk(TerrainChunk *chunk); + +v3s32 terrain_chunkp(v3s32 pos); +v3s32 terrain_offset(v3s32 pos); #endif diff --git a/src/types.def b/src/types.def index 6d18723..1cd4a31 100644 --- a/src/types.def +++ b/src/types.def @@ -3,6 +3,11 @@ ColorData v3f32 color +TreeData + v3f32 color + u8 has_root + v3s32 root + SerializedTerrainNode u32 type Blob data