]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/client/terrain_gfx.c
Cull liquid faces next to unloaded nodes
[dragonblocks_alpha.git] / src / client / terrain_gfx.c
1 #include <asprintf/asprintf.h>
2 #include <linmath.h/linmath.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include "client/client_config.h"
6 #include "client/client_node.h"
7 #include "client/client_terrain.h"
8 #include "client/cube.h"
9 #include "client/frustum.h"
10 #include "client/gl_debug.h"
11 #include "client/light.h"
12 #include "client/shader.h"
13 #include "client/terrain_gfx.h"
14
15 typedef struct {
16         bool visible;
17         bool transparent;
18         ModelBatch *batch;
19         ModelBatch *batch_transparent;
20         TerrainChunk *chunk;
21         v3s32 chunkp;
22         TerrainChunk *nbrs[6];
23         bool tried_nbrs[6];
24         bool cull_edges;
25 } ChunkRenderData;
26
27 static VertexLayout terrain_vertex_layout = {
28         .attributes = (VertexAttribute []) {
29                 {GL_FLOAT, 3, sizeof(v3f32)}, // position
30                 {GL_FLOAT, 3, sizeof(v3f32)}, // normal
31                 {GL_FLOAT, 2, sizeof(v2f32)}, // textureCoordinates
32                 {GL_FLOAT, 1, sizeof(f32  )}, // textureIndex
33                 {GL_FLOAT, 3, sizeof(v3f32)}, // color
34         },
35         .count = 5,
36         .size = sizeof(TerrainVertex),
37 };
38
39 static v3s32 face_dir[6] = {
40         {+0, +0, -1},
41         {+0, +0, +1},
42         {-1, +0, +0},
43         {+1, +0, +0},
44         {+0, -1, +0},
45         {+0, +1, +0},
46 };
47
48 static v3f32 center_offset = {
49         CHUNK_SIZE * 0.5f + 0.5f,
50         CHUNK_SIZE * 0.5f + 0.5f,
51         CHUNK_SIZE * 0.5f + 0.5f,
52 };
53
54 static GLuint shader_prog;
55 static GLint loc_VP;
56
57 static LightShader light_shader;
58 static ModelShader model_shader;
59
60 static inline bool cull_face(NodeType self, NodeType nbr)
61 {
62         switch (client_node_definitions[self].visibility) {
63                 case VISIBILITY_CLIP:
64                         return false;
65
66                 case VISIBILITY_BLEND:
67                         return nbr == NODE_UNLOADED
68                                 || nbr == self;
69
70                 case VISIBILITY_SOLID:
71                         return nbr == NODE_UNLOADED
72                                 || client_node_definitions[nbr].visibility == VISIBILITY_SOLID;
73
74                 default: // impossible
75                         break;
76         }
77
78         // impossible
79         return false;
80 }
81
82 static inline void render_node(ChunkRenderData *data, v3s32 offset)
83 {
84         NodeArgsRender args;
85
86         args.node = &data->chunk->data[offset.x][offset.y][offset.z];
87
88         ClientNodeDefinition *def = &client_node_definitions[args.node->type];
89         if (def->visibility == VISIBILITY_NONE)
90                 return;
91
92         v3f32 vertex_offset = v3f32_sub(v3s32_to_f32(offset), center_offset);
93         if (def->render)
94                 args.pos = v3s32_add(offset, data->chunkp);
95
96         for (args.f = 0; args.f < 6; args.f++) {
97                 v3s32 nbr_offset = v3s32_add(offset, face_dir[args.f]);
98
99                 TerrainChunk *nbr_chunk;
100
101                 if (nbr_offset.z >= 0 && nbr_offset.z < CHUNK_SIZE &&
102                         nbr_offset.x >= 0 && nbr_offset.x < CHUNK_SIZE &&
103                         nbr_offset.y >= 0 && nbr_offset.y < CHUNK_SIZE) {
104                         nbr_chunk = data->chunk;
105                 } else if (!(nbr_chunk = data->nbrs[args.f]) && !data->tried_nbrs[args.f]) {
106                         nbr_chunk = data->nbrs[args.f] = terrain_get_chunk(client_terrain,
107                                 v3s32_add(data->chunk->pos, face_dir[args.f]), false);
108                         data->tried_nbrs[args.f] = true;
109                 }
110
111                 NodeType nbr_node = NODE_UNLOADED;
112                 if (nbr_chunk)
113                         nbr_node = nbr_chunk->data
114                                 [(nbr_offset.x + CHUNK_SIZE) % CHUNK_SIZE]
115                                 [(nbr_offset.y + CHUNK_SIZE) % CHUNK_SIZE]
116                                 [(nbr_offset.z + CHUNK_SIZE) % CHUNK_SIZE].type;
117
118                 if (cull_face(args.node->type, nbr_node)) {
119                         // exception to culling rules: don't cull solid edge nodes, unless cull_edges
120                         if (data->cull_edges || nbr_chunk == data->chunk || def->visibility != VISIBILITY_SOLID)
121                                 continue;
122                 } else {
123                         data->visible = true;
124                 }
125
126                 ModelBatch *batch = data->batch;
127
128                 if (def->visibility == VISIBILITY_BLEND) {
129                         data->transparent = true;
130                         batch = data->batch_transparent;
131                 }
132
133                 for (args.v = 0; args.v < 6; args.v++) {
134                         args.vertex.cube = cube_vertices[args.f][args.v];
135                         args.vertex.cube.position = v3f32_add(args.vertex.cube.position, vertex_offset);
136                         args.vertex.color = (v3f32) {1.0f, 1.0f, 1.0f};
137
138                         if (def->render)
139                                 def->render(&args);
140
141                         model_batch_add_vertex(batch, def->tiles.textures[args.f]->txo, &args.vertex);
142                 }
143         }
144 }
145
146 static void animate_chunk_model(Model *model, f64 dtime)
147 {
148         if ((model->root->scale.x += dtime * 2.0f) > 1.0f) {
149                 model->root->scale.x = 1.0f;
150                 client_terrain_meshgen_task(model->extra);
151         }
152
153         model->root->scale.z
154                 = model->root->scale.y
155                 = model->root->scale.x;
156
157         model_node_transform(model->root);
158 }
159
160 static Model *create_chunk_model(TerrainChunk *chunk, bool animate)
161 {
162         ChunkRenderData data = {
163                 .visible = false,
164                 .transparent = false,
165                 .batch = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
166                 .batch_transparent = model_batch_create(&model_shader, &terrain_vertex_layout, offsetof(TerrainVertex, textureIndex)),
167                 .chunk = chunk,
168                 .chunkp = v3s32_scale(chunk->pos, CHUNK_SIZE),
169                 .nbrs = {NULL},
170                 .tried_nbrs = {false},
171                 .cull_edges = !animate,
172         };
173
174         CHUNK_ITERATE
175                 render_node(&data, (v3s32) {x, y, z});
176
177         if (!data.batch->textures.siz && !data.batch_transparent->textures.siz) {
178                 model_batch_free(data.batch);
179                 model_batch_free(data.batch_transparent);
180                 return NULL;
181         }
182
183         Model *model = model_create();
184         model->extra = chunk;
185         model->box = (aabb3f32) {
186                 v3f32_sub((v3f32) {-1.0f, -1.0f, -1.0f}, center_offset),
187                 v3f32_add((v3f32) {+1.0f, +1.0f, +1.0f}, center_offset)};
188         model->callbacks.step = animate ? &animate_chunk_model : NULL;
189         model->callbacks.delete = &model_free_meshes;
190         model->flags.frustum_culling = 1;
191         model->flags.transparent = data.transparent;
192
193         model->root->visible = data.visible;
194         model->root->pos = v3f32_add(v3s32_to_f32(data.chunkp), center_offset);
195         model->root->scale = (v3f32) {0.0f, 0.0f, 0.0f};
196
197         if (data.visible) {
198                 model_node_add_batch(model->root, data.batch);
199                 model_node_add_batch(model->root, data.batch_transparent);
200         } else {
201                 model_batch_free(data.batch);
202                 model_batch_free(data.batch_transparent);
203         }
204
205         return model;
206 }
207
208 bool terrain_gfx_init()
209 {
210         GLint texture_units;
211         glGetIntegerv(GL_MAX_TEXTURE_UNITS, &texture_units); GL_DEBUG
212
213         char *shader_defs;
214         asprintf(&shader_defs,
215                 "#define MAX_TEXTURE_UNITS %d\n"
216                 "#define VIEW_DISTANCE %lf\n",
217                 texture_units,
218                 client_config.view_distance
219         );
220
221         if (!shader_program_create(RESSOURCE_PATH "shaders/3d/terrain", &shader_prog, shader_defs)) {
222                 fprintf(stderr, "[error] failed to create terrain shader program\n");
223                 return false;
224         }
225
226         free(shader_defs);
227
228         loc_VP = glGetUniformLocation(shader_prog, "VP"); GL_DEBUG
229
230         GLint texture_indices[texture_units];
231         for (GLint i = 0; i < texture_units; i++)
232                 texture_indices[i] = i;
233
234         glProgramUniform1iv(shader_prog, glGetUniformLocation(shader_prog, "textures"), texture_units, texture_indices); GL_DEBUG
235
236         model_shader.prog = shader_prog;
237         model_shader.loc_transform = glGetUniformLocation(shader_prog, "model"); GL_DEBUG
238
239         light_shader.prog = shader_prog;
240         light_shader_locate(&light_shader);
241
242         return true;
243 }
244
245 void terrain_gfx_deinit()
246 {
247         glDeleteProgram(shader_prog); GL_DEBUG
248 }
249
250 void terrain_gfx_update()
251 {
252         glProgramUniformMatrix4fv(shader_prog, loc_VP, 1, GL_FALSE, frustum[0]); GL_DEBUG
253         light_shader_update(&light_shader);
254 }
255
256 void terrain_gfx_make_chunk_model(TerrainChunk *chunk)
257 {
258         TerrainChunkMeta *meta = chunk->extra;
259
260         bool animate = true;
261
262         pthread_mutex_lock(&chunk->mtx);
263         if (meta->model && meta->model->root->scale.x == 1.0f)
264                 animate = false;
265         pthread_mutex_unlock(&chunk->mtx);
266
267         Model *model = create_chunk_model(chunk, animate);
268
269         pthread_mutex_lock(&chunk->mtx);
270
271         if (meta->model) {
272                 if (model) {
273                         model->callbacks.step = meta->model->callbacks.step;
274                         model->root->scale = meta->model->root->scale;
275                         model_node_transform(model->root);
276                 }
277
278                 meta->model->replace = model;
279                 meta->model->flags.delete = 1;
280         } else if (model) {
281                 model_scene_add(model);
282         }
283
284         meta->model = model;
285         pthread_mutex_unlock(&chunk->mtx);
286 }