-Subproject commit 431e3bd29073d9c10ab3a853116860ba83ed1c5e
+Subproject commit 9ab2148e1b8d03dab816bbba6209794663b31576
-Subproject commit 8878e826fa3e7b1231e652fc13d11c7a61629d13
+Subproject commit a3a50433ffe0674291d4c5e9d2247cca7f691faa
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
#!/bin/bash
./dragonblocks_server "[::1]:4000" &
echo "singleplayer" | ./dragonblocks "[::1]:4000"
-pkill -P $$
+pkill -P $$ -9
config.c
day.c
environment.c
+ facedir.c
interrupt.c
item.c
node.c
client/cube.c
client/debug_menu.c
client/facecache.c
- client/facedir.c
client/font.c
client/frustum.c
client/game.c
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
)
# 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}"
)
#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"
{
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);
}
struct ClientAuth client_auth;
+#include <string.h>
static void auth_loop()
{
while (!interrupt.set) switch (client_auth.state) {
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;
+#include <stdlib.h>
#include "client/client.h"
#include "client/client_node.h"
#include "color.h"
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"),
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) {
}
}
}
+
+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;
+ }
+}
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_
if (player_entity) {
fprintf(stderr, "[error] attempt to re-add localplayer entity\n");
- exit(EXIT_FAILURE);
+ abort();
}
on_add(entity);
#include <stdlib.h>
#include <pthread.h>
#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
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++;
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);
+++ /dev/null
-#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},
-};
+++ /dev/null
-#ifndef _FACEDIR_H_
-#define _FACEDIR_H_
-
-#include "types.h"
-
-extern v3s32 facedir[];
-
-#endif // _FACEDIR_H_
*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)
#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;
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;
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]
&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);
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);
#!/bin/bash
-if ! make -j$(nproc); then
+if ! make -j$(nproc) dragonblocks_server; then
exit 1
fi
quit
end
end
-break gl_error
"
echo "$COMMON
+break gl_error
run \"[::1]:4000\" < $DEBUG_DIR/name
" > $DEBUG_DIR/client_script
--- /dev/null
+#include "facedir.h"
+
+v3s32 facedir[6] = {
+ {+0, +0, -1},
+ {+0, +0, +1},
+ {-1, +0, +0},
+ {+1, +0, +0},
+ {+0, -1, +0},
+ {+0, +1, +0},
+};
--- /dev/null
+#ifndef _FACEDIR_H_
+#define _FACEDIR_H_
+
+#include "types.h"
+
+extern v3s32 facedir[];
+
+#endif // _FACEDIR_H_
#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},
},
};
#include <stddef.h>
#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,
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[];
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)
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)
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;
#include <time.h>
#include "day.h"
#include "server/database.h"
+#include "server/server_node.h"
#include "server/server_terrain.h"
#include "perlin.h"
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");
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);
#include <stdio.h>
#include <stdlib.h>
#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)
continue;
SchematicNode *node = malloc(sizeof *node);
- node->data = (Blob) {0, NULL};
v3s32 color;
if (sscanf(line, "%d %d %d %2x%2x%2x",
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);
}
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);
}
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);
#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] = {
--- /dev/null
+#include <stdlib.h>
+#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;
+ }
+}
--- /dev/null
+#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
#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"
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
}
// 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;
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()
{
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--;
terrain_gen_step();
return NULL;
-}
+ }
// enqueue chunk
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;
}
}
static void on_delete_chunk(TerrainChunk *chunk)
{
TerrainChunkMeta *meta = chunk->extra;
+ pthread_mutex_destroy(&meta->mtx);
Blob_free(&meta->data);
free(meta);
// 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
{+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--) {
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
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);
{
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);
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);
}
}
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();
}
}
}
-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()
// 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);
+}
#ifndef _SERVER_TERRAIN_H_
#define _SERVER_TERRAIN_H_
+#include <dragonstd/list.h>
#include <pthread.h>
#include "server/server_player.h"
#include "terrain.h"
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;
} 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_
#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)
{
}
}
- 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);
--- /dev/null
+#include <stdlib.h>
+#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,
+ },
+};
+
--- /dev/null
+#ifndef _TREE_H_
+#define _TREE_H_
+
+#include <dragonstd/list.h>
+#include <stdbool.h>
+#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_
--- /dev/null
+#include <dragonstd/array.h>
+#include <dragonstd/list.h>
+#include <dragonstd/tree.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#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);
+}
--- /dev/null
+#ifndef _TREE_PHYSICS_H_
+#define _TREE_PHYSICS_H_
+
+#include "types.h"
+
+void tree_physics_check(v3s32 pos);
+
+#endif // _TREE_PHYSICS_H_
+++ /dev/null
-#include <stdlib.h>
-#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,
- },
-};
-
+++ /dev/null
-#ifndef _TREES_H_
-#define _TREES_H_
-
-#include <dragonstd/list.h>
-#include <stdbool.h>
-#include "perlin.h"
-#include "terrain.h"
-#include "types.h"
-
-#define NUM_TREES 3
-
-typedef struct {
- 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_
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)
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;
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_
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},
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);
}
/*
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);
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_
#include <math.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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)
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);
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;
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;
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);
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;
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};
}
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;
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
ColorData
v3f32 color
+TreeData
+ v3f32 color
+ u8 has_root
+ v3s32 root
+
SerializedTerrainNode
u32 type
Blob data