]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/client/terrain_gfx.c
ff6ec70a71f6e591f4c61046efe8cb8ba4347c20
[dragonblocks_alpha.git] / src / client / terrain_gfx.c
1 #include <assert.h>
2 #include <asprintf/asprintf.h>
3 #include <linmath.h/linmath.h>
4 #include <stdio.h>
5 #include <stdlib.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"
15 #include "facedir.h"
16
17 typedef struct {
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
28 } ChunkRenderData;
29
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
37         },
38         .count = 5,
39         .size = sizeof(TerrainVertex),
40 };
41
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,
46 };
47
48 static GLuint shader_prog;
49 static GLint loc_VP;
50
51 static LightShader light_shader;
52 static ModelShader model_shader;
53
54 static void grab_neighbor(ChunkRenderData *data, int i)
55 {
56         // return if we've already subscribed/grabbed the lock
57         if (data->meta->depends[i])
58                 return;
59
60         // we are now subscribed to state changes from the neighbor
61         data->meta->depends[i] = true;
62
63         TerrainChunk *neighbor = data->meta->neighbors[i];
64         TerrainChunkMeta *neighbor_meta = neighbor->extra;
65
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);
71         else
72                 // if state is bad, set flag to abort
73                 data->abort = true;
74         pthread_rwlock_unlock(&neighbor_meta->lock_state);
75 }
76
77 static inline bool show_face(ChunkRenderData *data, NodeArgsRender *args, v3s32 offset)
78 {
79         NodeVisibility visibility = client_node_def[args->node->type].visibility;
80
81         // always render clip nodes
82         if (visibility == VISIBILITY_CLIP)
83                 return data->visible = true;
84
85         // calculate offset of neighbor node from current chunk
86         v3s32 nbr_offset = v3s32_add(offset, facedir[args->f]);
87
88         // if offset is outside bounds, we'll have to select a neighbor chunk
89         bool nbr_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;
93
94         NodeType nbr_node = NODE_UNKNOWN;
95
96         if (nbr_chunk) {
97                 // grab neighbor chunk data lock
98                 grab_neighbor(data, args->f);
99
100                 // if grabbing failed, return true so caller immediately takes notice of abort
101                 if (data->abort)
102                         return true;
103
104                 nbr_offset = terrain_offset(nbr_offset);
105
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;
109         } else {
110                 // select node from current chunk
111                 nbr_node = data->chunk->data[nbr_offset.x][nbr_offset.y][nbr_offset.z].type;
112         }
113
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;
122
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;
127         }
128
129         return false;
130 }
131
132 static inline void render_node(ChunkRenderData *data, v3s32 offset)
133 {
134         NodeArgsRender args;
135
136         args.node = &data->chunk->data[offset.x][offset.y][offset.z];
137
138         ClientNodeDef *def = &client_node_def[args.node->type];
139
140         if (def->visibility == VISIBILITY_NONE)
141                 return;
142
143         v3f32 vertex_offset = v3f32_sub(v3s32_to_f32(offset), center_offset);
144         if (def->render)
145                 args.pos = v3s32_add(offset, data->chunkp);
146
147         for (args.f = 0; args.f < 6; args.f++) {
148                 if (!show_face(data, &args, offset))
149                         continue;
150
151                 if (data->abort)
152                         return;
153
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};
159
160                         if (def->render)
161                                 def->render(&args);
162
163                         model_batch_add_vertex(batch, def->tiles.textures[args.f]->txo, &args.vertex);
164                 }
165         }
166 }
167
168 static void animate_chunk_model(Model *model, f64 dtime)
169 {
170         bool finished = (model->root->scale.x += dtime * 2.0f) > 1.0f;
171         if (finished)
172                 model->root->scale.x = 1.0f;
173
174         model->root->scale.z
175                 = model->root->scale.y
176                 = model->root->scale.x;
177
178         model_node_transform(model->root);
179
180         if (finished) {
181                 model->callbacks.step = NULL;
182
183                 if (model->extra)
184                         client_terrain_meshgen_task(model->extra, false);
185         }
186 }
187
188 static Model *create_chunk_model(ChunkRenderData *data)
189 {
190         if (!data->visible || (!data->batch->textures.siz && !data->batch_blend->textures.siz))
191                 return NULL;
192
193         Model *model = model_create();
194
195         if (data->remake_needed)
196                 model->extra = data->chunk;
197
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)};
201
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;
206
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};
209
210         model_node_add_batch(model->root, data->batch);
211         model_node_add_batch(model->root, data->batch_blend);
212
213         return model;
214 }
215
216 bool terrain_gfx_init()
217 {
218         GLint texture_units;
219         glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_units); GL_DEBUG
220
221         char *shader_def;
222         asprintf(&shader_def,
223                 "#define MAX_TEXTURE_UNITS %d\n"
224                 "#define VIEW_DISTANCE %lf\n",
225                 texture_units,
226                 client_config.view_distance
227         );
228
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");
231                 return false;
232         }
233
234         free(shader_def);
235
236         loc_VP = glGetUniformLocation(shader_prog, "VP"); GL_DEBUG
237
238         GLint texture_indices[texture_units];
239         for (GLint i = 0; i < texture_units; i++)
240                 texture_indices[i] = i;
241
242         glProgramUniform1iv(shader_prog, glGetUniformLocation(shader_prog, "textures"), texture_units, texture_indices); GL_DEBUG
243
244         model_shader.prog = shader_prog;
245         model_shader.loc_transform = glGetUniformLocation(shader_prog, "model"); GL_DEBUG
246
247         light_shader.prog = shader_prog;
248         light_shader_locate(&light_shader);
249
250         return true;
251 }
252
253 void terrain_gfx_deinit()
254 {
255         glDeleteProgram(shader_prog); GL_DEBUG
256 }
257
258 void terrain_gfx_update()
259 {
260         glProgramUniformMatrix4fv(shader_prog, loc_VP, 1, GL_FALSE, frustum[0]); GL_DEBUG
261         light_shader_update(&light_shader);
262 }
263
264 void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
265 {
266         // type coersion
267         TerrainChunkMeta *meta = chunk->extra;
268
269         // lock model mutex
270         pthread_mutex_lock(&meta->mtx_model);
271
272         // giving 10 arguments to a function is slow and unmaintainable, use pointer to struct instead
273         ChunkRenderData data = {
274                 .chunk = chunk,
275                 .meta = meta,
276                 .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
277                 .animate = false,
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)),
282                 .abort = false,
283                 .grabbed = {false},
284                 .visible = false,
285                 .remake_needed = false,
286         };
287
288         //  animate if old animation hasn't finished (or this is the first model)
289         if (meta->model)
290                 data.animate = meta->model->callbacks.step ? true : false;
291         else
292                 data.animate = !meta->has_model;
293
294         // obtain own data lock
295         assert(pthread_rwlock_rdlock(&chunk->lock) == 0);
296
297         // clear dependencies, they are repopulated by calls to grab_neighbor
298         for (int i = 0; i < 6; i++)
299                 meta->depends[i] = false;
300
301         // render nodes
302         CHUNK_ITERATE {
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);
307
308                 // abort if chunk has been changed
309                 // just "break" won't work, the CHUNK_ITERATE macro is a nested loop
310                 if (data.abort)
311                         goto abort;
312
313                 // put vertex data into batches
314                 render_node(&data, (v3s32) {x, y, z});
315
316                 // abort if failed to grab a neighbor
317                 if (data.abort)
318                         goto abort;
319         }
320         abort:
321
322         // release grabbed data locks
323         for (int i = 0; i < 6; i++)
324                 if (data.grabbed[i])
325                         pthread_rwlock_unlock(&meta->neighbors[i]->lock);
326
327         // release own data lock
328         pthread_rwlock_unlock(&chunk->lock);
329
330         // only create model if we didn't abort
331         Model *model = data.abort ? NULL : create_chunk_model(&data);
332
333         // make sure to free batch mem if it wasn't fed into model
334         if (!model) {
335                 model_batch_free(data.batch);
336                 model_batch_free(data.batch_blend);
337         }
338
339         // abort if chunk changed
340         if (data.abort) {
341                 pthread_mutex_unlock(&meta->mtx_model);
342                 return;
343         }
344
345         // replace old model
346         if (meta->model) {
347                 if (model) {
348                         // copy animation callback
349                         model->callbacks.step = meta->model->callbacks.step;
350                         // copy scale
351                         model->root->scale = meta->model->root->scale;
352                         model_node_transform(model->root);
353                 }
354
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;
358         } else if (model) {
359                 // model will be drawn in next frame
360                 model_scene_add(model);
361                 model_node_transform(model->root);
362         }
363
364         // update pointers
365         meta->model = model;
366         meta->has_model = true;
367
368         // bye bye
369         pthread_mutex_unlock(&meta->mtx_model);
370 }