1#include <stdlib.h>
2#include <string.h>
3
4#include "types.h"
5#include "util.h"
6#include "client/world.h"
7
8#define MC_CHUNK_SECTION_BLOCK_BYTES 8192
9
10static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z);
11static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z);
12static ChunkSection* world_chunk_section_find(ClientWorld* world, i32 x, i32 z, i32 section);
13static i8 world_chunk_section_mesh_create(ClientWorld* world, Chunk* chunk, i32 section);
14static i8 world_chunk_remesh(ClientWorld* world, Chunk* chunk, u16 section_mask);
15
16static const i32 neighbour_offsets[6][3] = { { 0, 0, -1 }, { 0, 0, 1 }, { -1, 0, 0 },
17 { 1, 0, 0 }, { 0, -1, 0 }, { 0, 1, 0 } };
18
19/* find chunk in world by coordinates
20 returns NULL if not found */
21static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z)
22{
23 i32 i;
24
25 for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
26 if (!world->chunks[i].used)
27 continue;
28
29 if (world->chunks[i].x == x && world->chunks[i].z == z)
30 return &world->chunks[i];
31 }
32
33 return NULL;
34}
35
36/* get chunk from world by coordinates
37 returns NULL if not found or no free chunk available */
38static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z)
39{
40 Chunk* chunk;
41 i32 i;
42
43 chunk = world_chunk_find(world, x, z);
44 if (chunk)
45 return chunk;
46
47 for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++) {
48 if (world->chunks[i].used)
49 continue;
50
51 /* unused chunk: init and return */
52 chunk = &world->chunks[i];
53 memset(chunk, 0, sizeof(*chunk));
54 chunk->used = 1;
55 chunk->x = x;
56 chunk->z = z;
57 return chunk;
58 }
59
60 mg_log(LOG_ERR, "World chunk storage is full");
61 return NULL;
62}
63
64/* find chunk section in world by coordinates
65 returns NULL if not found */
66static ChunkSection* world_chunk_section_find(ClientWorld* world, i32 x, i32 z, i32 section)
67{
68 Chunk* chunk;
69
70 if (section < 0 || section >= CHUNK_SECTION_COUNT)
71 return NULL;
72
73 chunk = world_chunk_find(world, x, z);
74 if (!chunk || !(chunk->section_mask & (1U << section)))
75 return NULL;
76
77 return chunk->sections[section];
78}
79
80/* rebuild chunk section mesh with face culling
81 returns 1 on success, 0 on failure */
82static i8 world_chunk_section_mesh_create(ClientWorld* world, Chunk* chunk, i32 section)
83{
84 const ChunkSection* neighbours[6];
85 i32 face;
86
87 for (face = 0; face < 6; face++) {
88 neighbours[face] = world_chunk_section_find(world, chunk->x + neighbour_offsets[face][0],
89 chunk->z + neighbour_offsets[face][2],
90 section + neighbour_offsets[face][1]);
91 }
92
93 if (!chunk->sections[section])
94 return 0;
95
96 return chunk_section_mesh_create(chunk->sections[section], neighbours);
97}
98
99/* rebuild chunk mesh and neighbouring chunk meshes for sections in section_mask
100 returns 1 on success, 0 on failure */
101static i8 world_chunk_remesh(ClientWorld* world, Chunk* chunk, u16 section_mask)
102{
103 Chunk* nbr;
104 i32 face;
105 i32 section;
106 i32 adj_section;
107
108 for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
109 if (!(section_mask & (1U << section)))
110 continue;
111
112 if ((chunk->section_mask & (1U << section)) && !world_chunk_section_mesh_create(world, chunk, section))
113 return 0;
114
115 for (face = 0; face < 6; face++) {
116 adj_section = section + neighbour_offsets[face][1];
117 nbr = world_chunk_find(world, chunk->x + neighbour_offsets[face][0],
118 chunk->z + neighbour_offsets[face][2]);
119
120 if (!nbr || adj_section < 0 || adj_section >= CHUNK_SECTION_COUNT ||
121 !(nbr->section_mask & (1U << adj_section)))
122 continue;
123
124 if (!world_chunk_section_mesh_create(world, nbr, adj_section))
125 return 0;
126 }
127 }
128
129 return 1;
130}
131
132void world_init(ClientWorld* world)
133{
134 if (!world)
135 return;
136
137 memset(world, 0, sizeof(*world));
138}
139
140void world_destroy(ClientWorld* world)
141{
142 i32 i;
143
144 if (!world)
145 return;
146
147 for (i = 0; i < CLIENT_WORLD_MAX_CHUNKS; i++)
148 chunk_remove(&world->chunks[i]);
149}
150
151void world_remove_chunk(ClientWorld* world, i32 x, i32 z)
152{
153 Chunk* chunk;
154 u16 section_mask;
155
156 if (!world)
157 return;
158
159 chunk = world_chunk_find(world, x, z);
160 if (chunk) {
161 section_mask = chunk->section_mask;
162 chunk_remove(chunk);
163 chunk->x = x;
164 chunk->z = z;
165 world_chunk_remesh(world, chunk, section_mask);
166 }
167}
168
169i8 world_mesh_create(ClientWorld* world)
170{
171 Chunk* chunk;
172 i32 chunk_index;
173 i32 section;
174
175 if (!world)
176 return 0;
177
178 for (chunk_index = 0; chunk_index < CLIENT_WORLD_MAX_CHUNKS; chunk_index++) {
179 chunk = &world->chunks[chunk_index];
180 if (!chunk->used)
181 continue;
182
183 for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
184 if (!(chunk->section_mask & (1U << section)))
185 continue;
186
187 if (!world_chunk_section_mesh_create(world, chunk, section))
188 return 0;
189 }
190 }
191
192 return 1;
193}
194
195i8 world_update_chunk(ClientWorld* world, const MCChunkUpdate* update, const u8* data)
196{
197 Chunk* chunk;
198 ChunkSection* chunk_section;
199 i32 section;
200 i32 data_section;
201 u16 remesh_mask;
202
203 if (!world || !update || !data)
204 return 0;
205
206 chunk = world_chunk_get(world, update->x, update->z);
207 if (!chunk)
208 return 1;
209
210 remesh_mask = update->mask;
211 if (update->replace) {
212 remesh_mask |= chunk->section_mask;
213 chunk_clear(chunk);
214 }
215
216 /* decode each section in chunk */
217 data_section = 0;
218 for (section = 0; section < CHUNK_SECTION_COUNT; section++) {
219 /* skip sections not in update mask */
220 if (!(update->mask & (1U << section)))
221 continue;
222
223 chunk_section = chunk->sections[section];
224 if (!chunk_section) {
225 chunk_section = calloc(1, sizeof(*chunk_section));
226 if (!chunk_section) {
227 mg_log(LOG_ERR, "Failed to allocate chunk section");
228 return 0;
229 }
230 chunk->sections[section] = chunk_section;
231 }
232
233 /* decode section data into mesh */
234 if (!chunk_section_decode(chunk_section,
235 data + (size_t)data_section * MC_CHUNK_SECTION_BLOCK_BYTES,
236 MC_CHUNK_SECTION_BLOCK_BYTES)) {
237 mg_log(LOG_ERR, "Failed to create chunk mesh");
238 return 0;
239 }
240
241 /* mark section present in chunk */
242 chunk->section_mask |= (u16)(1U << section);
243 data_section++;
244 }
245
246 return world_chunk_remesh(world, chunk, remesh_mask);
247}