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 void 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
228 shader_prog = shader_program_create(RESSOURCE_PATH "shaders/3d/terrain", shader_def);
231 loc_VP = glGetUniformLocation(shader_prog, "VP"); GL_DEBUG
233 GLint texture_indices[texture_units];
234 for (GLint i = 0; i < texture_units; i++)
235 texture_indices[i] = i;
237 glProgramUniform1iv(shader_prog, glGetUniformLocation(shader_prog, "textures"), texture_units, texture_indices); GL_DEBUG
239 model_shader.prog = shader_prog;
240 model_shader.loc_transform = glGetUniformLocation(shader_prog, "model"); GL_DEBUG
242 light_shader.prog = shader_prog;
243 light_shader_locate(&light_shader);
246 void terrain_gfx_deinit()
248 glDeleteProgram(shader_prog); GL_DEBUG
251 void terrain_gfx_update()
253 glProgramUniformMatrix4fv(shader_prog, loc_VP, 1, GL_FALSE, frustum[0]); GL_DEBUG
254 light_shader_update(&light_shader);
257 void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
260 TerrainChunkMeta *meta = chunk->extra;
263 pthread_mutex_lock(&meta->mtx_model);
265 // giving 10 arguments to a function is slow and unmaintainable, use pointer to struct instead
266 ChunkRenderData data = {
269 .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
271 .batch = model_batch_create(
272 &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
273 .batch_blend = model_batch_create(
274 &model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
278 .remake_needed = false,
281 // animate if old animation hasn't finished (or this is the first model)
283 data.animate = meta->model->callbacks.step ? true : false;
285 data.animate = !meta->has_model;
287 // obtain own data lock
288 assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
290 // clear dependencies, they are repopulated by calls to grab_neighbor
291 for (int i = 0; i < 6; i++)
292 meta->depends[i] = false;
296 // obtain changed state
297 pthread_rwlock_rdlock(&meta->lock_state);
298 data.abort = meta->state < CHUNK_STATE_CLEAN;
299 pthread_rwlock_unlock(&meta->lock_state);
301 // abort if chunk has been changed
302 // just "break" won't work, the CHUNK_ITERATE macro is a nested loop
306 // put vertex data into batches
307 render_node(&data, (v3s32) {x, y, z});
309 // abort if failed to grab a neighbor
315 // release grabbed data locks
316 for (int i = 0; i < 6; i++)
318 pthread_rwlock_unlock(&meta->neighbors[i]->lock);
320 // release own data lock
321 pthread_rwlock_unlock(&chunk->lock);
323 // only create model if we didn't abort
324 Model *model = data.abort ? NULL : create_chunk_model(&data);
326 // make sure to free batch mem if it wasn't fed into model
328 model_batch_free(data.batch);
329 model_batch_free(data.batch_blend);
332 // abort if chunk changed
334 pthread_mutex_unlock(&meta->mtx_model);
341 // copy animation callback
342 model->callbacks.step = meta->model->callbacks.step;
344 model->root->scale = meta->model->root->scale;
345 model_node_transform(model->root);
348 // if old model wasn't drawn in this frame yet, new model will be drawn instead
349 meta->model->replace = model;
350 meta->model->flags.delete = 1;
352 // model will be drawn in next frame
353 model_scene_add(model);
354 model_node_transform(model->root);
359 meta->has_model = true;
362 pthread_mutex_unlock(&meta->mtx_model);