master uint/magnolia / source / world / block.c
  1#include <math.h>
  2#include <stdio.h>
  3#include <stdlib.h>
  4#include <string.h>
  5
  6#include "json.h"
  7#include "util.h"
  8#include "world/block.h"
  9
 10#define NAME_SIZE 128
 11#define PACK_SIZE 256
 12#define PATH_SIZE 512
 13#define VARIABLE_COUNT 16
 14#define MODEL_FACE_COUNT 64
 15#define BLOCK_STATE_COUNT 4096
 16
 17typedef struct {
 18	char name[NAME_SIZE];
 19	char value[NAME_SIZE];
 20} TextureVariable;
 21
 22typedef struct {
 23	MGVertex vertices[4];
 24	char     texture[NAME_SIZE];
 25	i32      cullface;
 26} ModelFace;
 27
 28typedef struct {
 29	TextureVariable variables[VARIABLE_COUNT];
 30	i32             variable_count;
 31	ModelFace       faces[MODEL_FACE_COUNT];
 32	i32             face_count;
 33} BlockModel;
 34
 35typedef struct {
 36	BlockFace* faces;
 37	i32        face_count;
 38	i8         loaded;
 39	i8         occludes;
 40} LoadedBlock;
 41
 42static const char* face_names[BLOCK_FACE_COUNT] = { "north", "south", "west", "east", "down", "up" };
 43
 44/*its a cube... what more do you want*/
 45/* clang-format off */
 46static const MGVertex face_vertices[BLOCK_FACE_COUNT][4] = {
 47	{ { 0, 0, 0, 0, 1, 0xffffffff }, { 0, 1, 0, 0, 0, 0xffffffff },
 48	  { 1, 1, 0, 1, 0, 0xffffffff }, { 1, 0, 0, 1, 1, 0xffffffff } },
 49	{ { 1, 0, 1, 0, 1, 0xffffffff }, { 1, 1, 1, 0, 0, 0xffffffff },
 50	  { 0, 1, 1, 1, 0, 0xffffffff }, { 0, 0, 1, 1, 1, 0xffffffff } },
 51	{ { 0, 0, 1, 0, 1, 0xffffffff }, { 0, 1, 1, 0, 0, 0xffffffff },
 52	  { 0, 1, 0, 1, 0, 0xffffffff }, { 0, 0, 0, 1, 1, 0xffffffff } },
 53	{ { 1, 0, 0, 0, 1, 0xffffffff }, { 1, 1, 0, 0, 0, 0xffffffff },
 54	  { 1, 1, 1, 1, 0, 0xffffffff }, { 1, 0, 1, 1, 1, 0xffffffff } },
 55	{ { 0, 0, 1, 0, 1, 0xffffffff }, { 0, 0, 0, 0, 0, 0xffffffff },
 56	  { 1, 0, 0, 1, 0, 0xffffffff }, { 1, 0, 1, 1, 1, 0xffffffff } },
 57	{ { 0, 1, 0, 0, 1, 0xffffffff }, { 0, 1, 1, 0, 0, 0xffffffff },
 58	  { 1, 1, 1, 1, 0, 0xffffffff }, { 1, 1, 0, 1, 1, 0xffffffff } }
 59};
 60/* clang-format on */
 61
 62static MGTexture   textures[BLOCK_TEXTURE_COUNT];
 63static char        texture_names[BLOCK_TEXTURE_COUNT][NAME_SIZE];
 64static LoadedBlock blocks[BLOCK_STATE_COUNT];
 65static char        pack_path[PACK_SIZE];
 66static i32         texture_count;
 67static cJSON*      block_registry;
 68
 69static void        block_load(BlockType block);
 70static i8          face_covers_cube(const ModelFace* face);
 71static float       json_number(cJSON* array, i32 index);
 72static cJSON*      json_load(const char* path);
 73static void        model_cube(BlockModel* model, const char* texture);
 74static void        model_face(BlockModel* model, cJSON* from, cJSON* to, cJSON* faces, i32 face);
 75static i8          model_load(const char* name, BlockModel* model, i32 depth);
 76static const char* model_resolve(const BlockModel* model, const char* name);
 77static void        model_rotate(BlockModel* model, i32 first, cJSON* rotation);
 78static void        model_texture(BlockModel* model, const char* name, const char* value);
 79static i32         texture_load(const char* name);
 80
 81/* load block model and textures */
 82static void block_load(BlockType block)
 83{
 84	LoadedBlock* loaded;
 85	BlockModel   model;
 86	cJSON*       entry;
 87	cJSON*       item;
 88	const char*  name;
 89	const char*  fallback;
 90	i32          face;
 91	i32          mask;
 92	char         path[PATH_SIZE];
 93
 94	loaded = &blocks[block];
 95	loaded->loaded = 1;
 96	entry = cJSON_GetArrayItem(block_registry, block >> 4);
 97	item = cJSON_GetObjectItemCaseSensitive(entry, "blockState");
 98	name = cJSON_IsString(item) ? item->valuestring : NULL;
 99	item = cJSON_GetObjectItemCaseSensitive(entry, "texture");
100	fallback = cJSON_IsString(item) ? item->valuestring : NULL;
101	memset(&model, 0, sizeof(model));
102	if (name) {
103		snprintf(path, sizeof(path), "%s/blockstates/%s.json", pack_path, name);
104		item = json_load(path);
105		if (item) {
106			entry = cJSON_GetObjectItemCaseSensitive(item, "variants");
107			if (cJSON_GetArraySize(entry) > (block & 15))
108				entry = cJSON_GetArrayItem(entry, block & 15);
109			else
110				entry = entry ? entry->child : NULL;
111			if (cJSON_IsArray(entry))
112				entry = cJSON_GetArrayItem(entry, 0);
113			entry = cJSON_GetObjectItemCaseSensitive(entry, "model");
114			if (cJSON_IsString(entry))
115				model_load(entry->valuestring, &model, 0);
116			cJSON_Delete(item);
117		}
118	}
119	if (!model.face_count && fallback)
120		model_cube(&model, fallback);
121	if (!model.face_count)
122		return;
123	loaded->faces = malloc(model.face_count * sizeof(*loaded->faces));
124	if (!loaded->faces)
125		return;
126	loaded->face_count = model.face_count;
127	mask = 0;
128	for (face = 0; face < model.face_count; face++) {
129		name = model_resolve(&model, model.faces[face].texture);
130		if (!name)
131			name = fallback;
132		loaded->faces[face].texture = name ? texture_load(name) : -1;
133		loaded->faces[face].cullface = model.faces[face].cullface;
134		memcpy(loaded->faces[face].vertices, model.faces[face].vertices, sizeof(model.faces[face].vertices));
135		if (model.faces[face].cullface >= 0 && face_covers_cube(&model.faces[face]))
136			mask |= 1 << model.faces[face].cullface;
137	}
138	loaded->occludes = mask == (1 << BLOCK_FACE_COUNT) - 1;
139}
140
141/* check whether face covers cube side
142   returns 1 if face covers side, 0 otherwise */
143static i8 face_covers_cube(const ModelFace* face)
144{
145	/* only faces spanning their entire boundary plane can occlude neighbours */
146	static const i32 face_axes[BLOCK_FACE_COUNT] = { 2, 2, 0, 0, 1, 1 };
147	float            low[3] = { 1, 1, 1 };
148	float            high[3] = { 0, 0, 0 };
149	float            value[3];
150	i32              vertex;
151	i32              axis;
152	i32              plane;
153
154	for (vertex = 0; vertex < 4; vertex++) {
155		value[0] = face->vertices[vertex].x;
156		value[1] = face->vertices[vertex].y;
157		value[2] = face->vertices[vertex].z;
158		for (axis = 0; axis < 3; axis++) {
159			if (value[axis] < low[axis])
160				low[axis] = value[axis];
161			if (value[axis] > high[axis])
162				high[axis] = value[axis];
163		}
164	}
165	plane = face_axes[face->cullface];
166	for (axis = 0; axis < 3; axis++) {
167		if (axis != plane && (low[axis] != 0 || high[axis] != 1))
168			return 0;
169	}
170	return face->cullface & 1 ? high[plane] == 1 : low[plane] == 0;
171}
172
173/* load JSON file
174   returns parsed JSON, or NULL on failure */
175static cJSON* json_load(const char* path)
176{
177	FILE*  file;
178	char*  data;
179	long   size;
180	cJSON* json;
181
182	file = fopen(path, "rb");
183	if (!file)
184		return NULL;
185	fseek(file, 0, SEEK_END);
186	size = ftell(file);
187	rewind(file);
188	data = malloc((size_t)size + 1);
189	if (!data || fread(data, 1, (size_t)size, file) != (size_t)size) {
190		free(data);
191		fclose(file);
192		return NULL;
193	}
194	data[size] = '\0';
195	fclose(file);
196	json = cJSON_Parse(data);
197	free(data);
198	return json;
199}
200
201/* read normalized JSON number
202   returns number, or 0 on failure */
203static float json_number(cJSON* array, i32 index)
204{
205	cJSON* item = cJSON_GetArrayItem(array, index);
206	return cJSON_IsNumber(item) ? (float)item->valuedouble / 16.0f : 0.0f;
207}
208
209/* create cube model from texture */
210static void model_cube(BlockModel* model, const char* texture)
211{
212	i32 face;
213
214	for (face = 0; face < BLOCK_FACE_COUNT; face++) {
215		model->faces[face].cullface = face;
216		memcpy(model->faces[face].vertices, face_vertices[face], sizeof(face_vertices[face]));
217		snprintf(model->faces[face].texture, NAME_SIZE, "%s", texture);
218	}
219	model->face_count = BLOCK_FACE_COUNT;
220}
221
222/* convert JSON face element into vertices */
223static void model_face(BlockModel* model, cJSON* from, cJSON* to, cJSON* faces, i32 face)
224{
225	ModelFace* out;
226	cJSON*     item;
227	cJSON*     uv;
228	float      low[3];
229	float      high[3];
230	i32        vertex;
231	i32        axis;
232
233	item = cJSON_GetObjectItemCaseSensitive(faces, face_names[face]);
234	if (!cJSON_IsObject(item) || model->face_count == MODEL_FACE_COUNT)
235		return;
236	out = &model->faces[model->face_count++];
237	memset(out, 0, sizeof(*out));
238	out->cullface = -1;
239	for (axis = 0; axis < 3; axis++) {
240		low[axis] = json_number(from, axis);
241		high[axis] = json_number(to, axis);
242	}
243	for (vertex = 0; vertex < 4; vertex++) {
244		out->vertices[vertex] = face_vertices[face][vertex];
245		out->vertices[vertex].x = out->vertices[vertex].x ? high[0] : low[0];
246		out->vertices[vertex].y = out->vertices[vertex].y ? high[1] : low[1];
247		out->vertices[vertex].z = out->vertices[vertex].z ? high[2] : low[2];
248	}
249	uv = cJSON_GetObjectItemCaseSensitive(item, "uv");
250	if (cJSON_IsArray(uv) && cJSON_GetArraySize(uv) == 4) {
251		out->vertices[0].u = out->vertices[1].u = json_number(uv, 0);
252		out->vertices[2].u = out->vertices[3].u = json_number(uv, 2);
253		out->vertices[0].v = out->vertices[3].v = json_number(uv, 3);
254		out->vertices[1].v = out->vertices[2].v = json_number(uv, 1);
255	}
256	uv = cJSON_GetObjectItemCaseSensitive(item, "texture");
257	if (cJSON_IsString(uv))
258		snprintf(out->texture, NAME_SIZE, "%s", uv->valuestring);
259	uv = cJSON_GetObjectItemCaseSensitive(item, "tintindex");
260	if (cJSON_IsNumber(uv)) {
261		/* TODO for now we just tint everything the same green */
262		for (vertex = 0; vertex < 4; vertex++)
263			out->vertices[vertex].col = 0xff59bd91;
264	}
265	uv = cJSON_GetObjectItemCaseSensitive(item, "cullface");
266	if (cJSON_IsString(uv)) {
267		for (axis = 0; axis < BLOCK_FACE_COUNT; axis++) {
268			if (strcmp(uv->valuestring, face_names[axis]) == 0)
269				out->cullface = axis;
270		}
271	}
272}
273
274/* load inherited model data and apply overrides
275   returns 1 on success, 0 on failure */
276static i8 model_load(const char* name, BlockModel* model, i32 depth)
277{
278	char   path[PATH_SIZE];
279	cJSON* json;
280	cJSON* item;
281	cJSON* element;
282	cJSON* from;
283	cJSON* to;
284	cJSON* faces;
285	i32    face;
286	i32    first;
287
288	if (depth == 16)
289		return 0;
290	if (strchr(name, ':'))
291		name = strchr(name, ':') + 1;
292	snprintf(path, sizeof(path), "%s/models/%s%s.json", pack_path, strchr(name, '/') ? "" : "block/", name);
293	json = json_load(path);
294	if (!json)
295		return 0;
296	/* load the parent first... */
297	item = cJSON_GetObjectItemCaseSensitive(json, "parent");
298	if (cJSON_IsString(item) && !model_load(item->valuestring, model, depth + 1)) {
299		cJSON_Delete(json);
300		return 0;
301	}
302	item = cJSON_GetObjectItemCaseSensitive(json, "textures");
303	cJSON_ArrayForEach(faces, item)
304	{
305		if (cJSON_IsString(faces))
306			model_texture(model, faces->string, faces->valuestring);
307	}
308	item = cJSON_GetObjectItemCaseSensitive(json, "elements");
309	if (cJSON_IsArray(item)) {
310		/* ... then let the child override*/
311		model->face_count = 0;
312		/* get the from and to values and then turn them into verticies*/
313		/*TODO we dont rotate stairs*/
314		cJSON_ArrayForEach(element, item)
315		{
316			first = model->face_count;
317			from = cJSON_GetObjectItemCaseSensitive(element, "from");
318			to = cJSON_GetObjectItemCaseSensitive(element, "to");
319			faces = cJSON_GetObjectItemCaseSensitive(element, "faces");
320			for (face = 0; face < BLOCK_FACE_COUNT; face++)
321				model_face(model, from, to, faces, face);
322			model_rotate(model, first, cJSON_GetObjectItemCaseSensitive(element, "rotation"));
323		}
324	}
325	cJSON_Delete(json);
326	return 1;
327}
328
329/* resolve model texture variable
330   returns resolved texture name, or NULL on failure */
331static const char* model_resolve(const BlockModel* model, const char* name)
332{
333	i32 depth;
334	i32 i;
335
336	for (depth = 0; name && name[0] == '#' && depth < VARIABLE_COUNT; depth++) {
337		for (i = 0; i < model->variable_count; i++) {
338			if (strcmp(model->variables[i].name, name + 1) == 0)
339				break;
340		}
341		name = i == model->variable_count ? NULL : model->variables[i].value;
342	}
343	return name;
344}
345
346/* rotate model faces from JSON rotation */
347static void model_rotate(BlockModel* model, i32 first, cJSON* rotation)
348{
349	cJSON*      origin;
350	cJSON*      item;
351	const char* axis;
352	float       center[3];
353	float       value[3];
354	float       angle;
355	float       sine;
356	float       cosine;
357	float       a;
358	float       b;
359	i32         face;
360	i32         vertex;
361
362	if (!cJSON_IsObject(rotation))
363		return;
364	origin = cJSON_GetObjectItemCaseSensitive(rotation, "origin");
365	item = cJSON_GetObjectItemCaseSensitive(rotation, "axis");
366	axis = cJSON_IsString(item) ? item->valuestring : NULL;
367	item = cJSON_GetObjectItemCaseSensitive(rotation, "angle");
368	if (!axis || !cJSON_IsNumber(item))
369		return;
370	center[0] = json_number(origin, 0);
371	center[1] = json_number(origin, 1);
372	center[2] = json_number(origin, 2);
373	angle = (float)(item->valuedouble * 3.14159265358979323846 / 180.0);
374	sine = sinf(angle);
375	cosine = cosf(angle);
376	for (face = first; face < model->face_count; face++) {
377		for (vertex = 0; vertex < 4; vertex++) {
378			value[0] = model->faces[face].vertices[vertex].x - center[0];
379			value[1] = model->faces[face].vertices[vertex].y - center[1];
380			value[2] = model->faces[face].vertices[vertex].z - center[2];
381			if (axis[0] == 'x') {
382				a = value[1];
383				b = value[2];
384				value[1] = a * cosine - b * sine;
385				value[2] = a * sine + b * cosine;
386			}
387			else if (axis[0] == 'y') {
388				a = value[2];
389				b = value[0];
390				value[2] = a * cosine - b * sine;
391				value[0] = a * sine + b * cosine;
392			}
393			else {
394				a = value[0];
395				b = value[1];
396				value[0] = a * cosine - b * sine;
397				value[1] = a * sine + b * cosine;
398			}
399			model->faces[face].vertices[vertex].x = value[0] + center[0];
400			model->faces[face].vertices[vertex].y = value[1] + center[1];
401			model->faces[face].vertices[vertex].z = value[2] + center[2];
402		}
403	}
404}
405
406/* add or override model texture */
407static void model_texture(BlockModel* model, const char* name, const char* value)
408{
409	i32 i;
410
411	for (i = 0; i < model->variable_count; i++) {
412		if (strcmp(model->variables[i].name, name) == 0)
413			break;
414	}
415	if (i == VARIABLE_COUNT)
416		return;
417	if (i == model->variable_count)
418		model->variable_count++;
419	snprintf(model->variables[i].name, NAME_SIZE, "%s", name);
420	snprintf(model->variables[i].value, NAME_SIZE, "%s", value);
421}
422
423/* load texture by name
424   returns texture index, or -1 on failure */
425static i32 texture_load(const char* name)
426{
427	char        path[PATH_SIZE];
428	const char* colon;
429	i32         index;
430
431	colon = strchr(name, ':');
432	if (colon)
433		name = colon + 1;
434	for (index = 0; index < texture_count; index++) {
435		if (strcmp(texture_names[index], name) == 0)
436			return index;
437	}
438	if (texture_count == BLOCK_TEXTURE_COUNT)
439		return -1;
440	snprintf(path, sizeof(path), "%s/%s.png", pack_path, name);
441	textures[texture_count] = renderer_texture_load(path);
442	if (textures[texture_count] == MGTEXTURE_INVALID)
443		return -1;
444	snprintf(texture_names[texture_count], NAME_SIZE, "%s", name);
445	return texture_count++;
446}
447
448i8 block_textures_create(const char* path)
449{
450	block_textures_destroy();
451	snprintf(pack_path, sizeof(pack_path), "%s", path);
452	block_registry = json_load("assets/resourcepacks/blocks.json");
453	return cJSON_IsArray(block_registry);
454}
455
456void block_textures_destroy(void)
457{
458	i32 index;
459
460	for (index = 0; index < BLOCK_STATE_COUNT; index++)
461		free(blocks[index].faces);
462	for (index = 0; index < texture_count; index++)
463		renderer_texture_destroy(textures[index]);
464	cJSON_Delete(block_registry);
465	memset(blocks, 0, sizeof(blocks));
466	memset(textures, 0, sizeof(textures));
467	memset(texture_names, 0, sizeof(texture_names));
468	block_registry = NULL;
469	pack_path[0] = '\0';
470	texture_count = 0;
471}
472
473const BlockFace* block_model_faces(BlockType block, i32* count)
474{
475	if (!blocks[block].loaded)
476		block_load(block);
477	*count = blocks[block].face_count;
478	return blocks[block].faces;
479}
480
481i8 block_model_occludes(BlockType block)
482{
483	i32 count;
484
485	block_model_faces(block, &count);
486	return blocks[block].occludes;
487}
488
489MGTexture block_texture_get(i32 index)
490{
491	return index >= 0 && index < texture_count ? textures[index] : MGTEXTURE_INVALID;
492}