master uint/magnolia / source / client / world.c
  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}