2 #include <asprintf/asprintf.h>
3 #include <linmath.h/linmath.h>
6 #include "client/client_config.h"
7 #include "client/client_node.h"
8 #include "client/client_terrain.h"
9 #include "client/cube.h"
10 #include "client/frustum.h"
11 #include "client/gl_debug.h"
12 #include "client/light.h"
13 #include "client/shader.h"
14 #include "client/terrain_gfx.h"
18 TerrainChunk *chunk; // input: chunk pointer
19 TerrainChunkMeta *meta; // input: coersed chunk metadata pointer
20 v3s32 chunkp; // input: position of chunk
21 bool animate; // input: disable edge culling
22 ModelBatch *batch; // main output: vertex data
23 ModelBatch *batch_blend; // main output: vertex data for transparent textures
24 bool abort; // output: state changes have occured that invalidate generated output
25 bool grabbed[6]; // output: neighbors that have been grabbed
26 bool visible; // output: edge culled model would be visible
27 bool remake_needed; // output: edge culled model would be different from non-culled
30 static VertexLayout terrain_vertex_layout = {
31 .attributes = (VertexAttribute []) {
32 {GL_FLOAT, 3, sizeof(v3f32)}, // position
33 {GL_FLOAT, 3, sizeof(v3f32)}, // normal
34 {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
35 {GL_FLOAT, 1, sizeof(f32 )}, // textureIndex
36 {GL_FLOAT, 3, sizeof(v3f32)}, // color
39 .size = sizeof(TerrainVertex),
42 static v3f32 center_offset = {
43 CHUNK_SIZE * 0.5f + 0.5f,
44 CHUNK_SIZE * 0.5f + 0.5f,
45 CHUNK_SIZE * 0.5f + 0.5f,
48 static GLuint shader_prog;
51 static LightShader light_shader;
52 static ModelShader model_shader;
54 static void grab_neighbor(ChunkRenderData *data, int i)
56 // return if we've already subscribed/grabbed the lock
57 if (data->meta->depends[i])
60 // we are now subscribed to state changes from the neighbor
61 data->meta->depends[i] = true;
63 TerrainChunk *neighbor = data->meta->neighbors[i];
64 TerrainChunkMeta *neighbor_meta = neighbor->extra;
66 pthread_rwlock_rdlock(&neighbor_meta->lock_state);
67 // check neighbor in case it was already in a bad state before we subscribed
68 if ((data->grabbed[i] = neighbor_meta->state > CHUNK_STATE_RECV))
69 // if state is good, actually grab the data lock in read mode
70 assert(pthread_rwlock_rdlock(&neighbor->lock) == 0);
72 // if state is bad, set flag to abort
74 pthread_rwlock_unlock(&neighbor_meta->lock_state);
77 static inline bool show_face(ChunkRenderData *data, NodeArgsRender *args, v3s32 offset)
79 NodeVisibility visibility = client_node_def[args->node->type].visibility;
81 // always render clip nodes
82 if (visibility == VISIBILITY_CLIP)
83 return data->visible = true;
85 // calculate offset of neighbor node from current chunk
86 v3s32 nbr_offset = v3s32_add(offset, facedir[args->f]);
88 // if offset is outside bounds, we'll have to select a neighbor chunk
90 nbr_offset.x < 0 || nbr_offset.x >= CHUNK_SIZE ||
91 nbr_offset.y < 0 || nbr_offset.y >= CHUNK_SIZE ||
92 nbr_offset.z < 0 || nbr_offset.z >= CHUNK_SIZE;
94 NodeType nbr_node = NODE_UNKNOWN;
97 // grab neighbor chunk data lock
98 grab_neighbor(data, args->f);
100 // if grabbing failed, return true so caller immediately takes notice of abort
104 nbr_offset = terrain_offset(nbr_offset);
106 // select node from neighbor chunk
107 nbr_node = data->meta->neighbors[args->f]->data
108 [nbr_offset.x][nbr_offset.y][nbr_offset.z].type;
110 // select node from current chunk
111 nbr_node = data->chunk->data[nbr_offset.x][nbr_offset.y][nbr_offset.z].type;
114 if (visibility == VISIBILITY_BLEND) {
115 // liquid nodes only render faces towards 'outsiders'
116 if (nbr_node != args->node->type)
117 return data->visible = true;
118 } else { // visibility == VISIBILITY_SOLID
119 // faces between two solid nodes are culled
120 if (client_node_def[nbr_node].visibility != VISIBILITY_SOLID)
121 return data->visible = true;
123 // if the chunk needs to be animated, dont cull faces to nodes in other chunks
124 // but remember to rebuild the chunk model after the animation has finished
125 if (nbr_chunk && data->animate)
126 return data->remake_needed = true;
132 static inline void render_node(ChunkRenderData *data, v3s32 offset)
136 args.node = &data->chunk->data[offset.x][offset.y][offset.z];
138 ClientNodeDef *def = &client_node_def[args.node->type];
140 if (def->visibility == VISIBILITY_NONE)
143 v3f32 vertex_offset = v3f32_sub(v3s32_to_f32(offset), center_offset);
145 args.pos = v3s32_add(offset, data->chunkp);
147 for (args.f = 0; args.f < 6; args.f++) {
148 if (!show_face(data, &args, offset))
154 ModelBatch *batch = def->visibility == VISIBILITY_BLEND ? data->batch_blend : data->batch;
155 for (args.v = 0; args.v < 6; args.v++) {
156 args.vertex.cube = cube_vertices[args.f][args.v];
157 args.vertex.cube.position = v3f32_add(args.vertex.cube.position, vertex_offset);
158 args.vertex.color = (v3f32) {1.0f, 1.0f, 1.0f};
163 model_batch_add_vertex(batch, def->tiles.textures[args.f]->txo, &args.vertex);
168 static void animate_chunk_model(Model *model, f64 dtime)
170 bool finished = (model->root->scale.x += dtime * 2.0f) > 1.0f;
172 model->root->scale.x = 1.0f;
175 = model->root->scale.y
176 = model->root->scale.x;
178 model_node_transform(model->root);
181 model->callbacks.step = NULL;
184 client_terrain_meshgen_task(model->extra, false);
188 static Model *create_chunk_model(ChunkRenderData *data)
190 if (!data->visible || (!data->batch->textures.siz && !data->batch_blend->textures.siz))
193 Model *model = model_create();
195 if (data->remake_needed)
196 model->extra = data->chunk;
198 model->box = (aabb3f32) {
199 v3f32_sub((v3f32) {-1.0f, -1.0f, -1.0f}, center_offset),
200 v3f32_add((v3f32) {+1.0f, +1.0f, +1.0f}, center_offset)};
202 model->callbacks.step = data->animate ? &animate_chunk_model : NULL;
203 model->callbacks.delete = &model_free_meshes;
204 model->flags.frustum_culling = 1;
205 model->flags.transparent = data->batch_blend->textures.siz > 0;
207 model->root->pos = v3f32_add(v3s32_to_f32(data->chunkp), center_offset);
208 model->root->scale = data->animate ? (v3f32) {0.0f, 0.0f, 0.0f} : (v3f32) {1.0f, 1.0f, 1.0f};
210 model_node_add_batch(model->root, data->batch);
211 model_node_add_batch(model->root, data->batch_blend);
216 bool terrain_gfx_init()
219 glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_units); GL_DEBUG
222 asprintf(&shader_def,
223 "#define MAX_TEXTURE_UNITS %d\n"
224 "#define VIEW_DISTANCE %lf\n",
226 client_config.view_distance
229 if (!shader_program_create(RESSOURCE_PATH "shaders/3d/terrain", &shader_prog, shader_def)) {
230 fprintf(stderr, "[error] failed to create terrain shader program\n");
236 loc_VP = glGetUniformLocation(shader_prog, "VP"); GL_DEBUG
238 GLint texture_indices[texture_units];
239 for (GLint i = 0; i < texture_units; i++)
240 texture_indices[i] = i;
242 glProgramUniform1iv(shader_prog, glGetUniformLocation(shader_prog, "textures"), texture_units, texture_indices); GL_DEBUG
244 model_shader.prog = shader_prog;
245 model_shader.loc_transform = glGetUniformLocation(shader_prog, "model"); GL_DEBUG
247 light_shader.prog = shader_prog;
248 light_shader_locate(&light_shader);
253 void terrain_gfx_deinit()
255 glDeleteProgram(shader_prog); GL_DEBUG
258 void terrain_gfx_update()
260 glProgramUniformMatrix4fv(shader_prog, loc_VP, 1, GL_FALSE, frustum[0]); GL_DEBUG
261 light_shader_update(&light_shader);
264 void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
267 TerrainChunkMeta *meta = chunk->extra;
270 pthread_mutex_lock(&meta->mtx_model);
272 // giving 10 arguments to a function is slow and unmaintainable, use pointer to struct instead
273 ChunkRenderData data = {
276 .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
278 .batch = model_batch_create(
279 &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
280 .batch_blend = model_batch_create(
281 &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
285 .remake_needed = false,
288 // animate if old animation hasn't finished (or this is the first model)
290 data.animate = meta->model->callbacks.step ? true : false;
292 data.animate = !meta->has_model;
294 // obtain own data lock
295 assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
297 // clear dependencies, they are repopulated by calls to grab_neighbor
298 for (int i = 0; i < 6; i++)
299 meta->depends[i] = false;
303 // obtain changed state
304 pthread_rwlock_rdlock(&meta->lock_state);
305 data.abort = meta->state < CHUNK_STATE_CLEAN;
306 pthread_rwlock_unlock(&meta->lock_state);
308 // abort if chunk has been changed
309 // just "break" won't work, the CHUNK_ITERATE macro is a nested loop
313 // put vertex data into batches
314 render_node(&data, (v3s32) {x, y, z});
316 // abort if failed to grab a neighbor
322 // release grabbed data locks
323 for (int i = 0; i < 6; i++)
325 pthread_rwlock_unlock(&meta->neighbors[i]->lock);
327 // release own data lock
328 pthread_rwlock_unlock(&chunk->lock);
330 // only create model if we didn't abort
331 Model *model = data.abort ? NULL : create_chunk_model(&data);
333 // make sure to free batch mem if it wasn't fed into model
335 model_batch_free(data.batch);
336 model_batch_free(data.batch_blend);
339 // abort if chunk changed
341 pthread_mutex_unlock(&meta->mtx_model);
348 // copy animation callback
349 model->callbacks.step = meta->model->callbacks.step;
351 model->root->scale = meta->model->root->scale;
352 model_node_transform(model->root);
355 // if old model wasn't drawn in this frame yet, new model will be drawn instead
356 meta->model->replace = model;
357 meta->model->flags.delete = 1;
359 // model will be drawn in next frame
360 model_scene_add(model);
361 model_node_transform(model->root);
366 meta->has_model = true;
369 pthread_mutex_unlock(&meta->mtx_model);