From 1fcb3c0317e694a72ed4b6ed6601839cbf674809 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Sep 2018 10:59:05 +0200 Subject: [PATCH] Started working on IQM/glTF loaders --- src/external/cgltf.h | 2023 ++++++++++++++++++++++++++++++++++++++++++ src/models.c | 87 +- src/raylib.h | 1 + 3 files changed, 2104 insertions(+), 7 deletions(-) create mode 100644 src/external/cgltf.h diff --git a/src/external/cgltf.h b/src/external/cgltf.h new file mode 100644 index 000000000..ed04d545c --- /dev/null +++ b/src/external/cgltf.h @@ -0,0 +1,2023 @@ +#ifndef CGLTF_H_INCLUDED__ +#define CGLTF_H_INCLUDED__ + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef unsigned long cgltf_size; +typedef float cgltf_float; +typedef int cgltf_bool; + +typedef enum cgltf_file_type +{ + cgltf_file_type_invalid, + cgltf_file_type_gltf, + cgltf_file_type_glb, +} cgltf_file_type; + +typedef struct cgltf_options +{ + cgltf_file_type type; + cgltf_size json_token_count; /* 0 == auto */ + void* (*memory_alloc)(void* user, cgltf_size size); + void (*memory_free) (void* user, void* ptr); + void* memory_user_data; +} cgltf_options; + +typedef enum cgltf_result +{ + cgltf_result_success, + cgltf_result_data_too_short, + cgltf_result_unknown_format, + cgltf_result_invalid_json, + cgltf_result_invalid_options, +} cgltf_result; + +typedef enum cgltf_buffer_view_type +{ + cgltf_buffer_view_type_invalid, + cgltf_buffer_view_type_indices, + cgltf_buffer_view_type_vertices, +} cgltf_buffer_view_type; + +typedef enum cgltf_attribute_type +{ + cgltf_attribute_type_invalid, + cgltf_attribute_type_position, + cgltf_attribute_type_normal, + cgltf_attribute_type_tangent, + cgltf_attribute_type_texcoord_0, + cgltf_attribute_type_texcoord_1, + cgltf_attribute_type_color_0, + cgltf_attribute_type_joints_0, + cgltf_attribute_type_weights_0, +} cgltf_attribute_type; + +typedef enum cgltf_component_type +{ + cgltf_component_type_invalid, + cgltf_component_type_rgb_32f, + cgltf_component_type_rgba_32f, + cgltf_component_type_rg_32f, + cgltf_component_type_rg_8, + cgltf_component_type_rg_16, + cgltf_component_type_rgba_8, + cgltf_component_type_rgba_16, + cgltf_component_type_r_8, + cgltf_component_type_r_8u, + cgltf_component_type_r_16, + cgltf_component_type_r_16u, + cgltf_component_type_r_32u, + cgltf_component_type_r_32f, +} cgltf_component_type; + +typedef enum cgltf_type +{ + cgltf_type_invalid, + cgltf_type_scalar, + cgltf_type_vec2, + cgltf_type_vec3, + cgltf_type_vec4, + cgltf_type_mat2, + cgltf_type_mat3, + cgltf_type_mat4, +} cgltf_type; + +typedef enum cgltf_primitive_type +{ + cgltf_type_points, + cgltf_type_lines, + cgltf_type_line_loop, + cgltf_type_line_strip, + cgltf_type_triangles, + cgltf_type_triangle_strip, + cgltf_type_triangle_fan, +} cgltf_primitive_type; + +typedef struct cgltf_buffer +{ + cgltf_size size; + char* uri; +} cgltf_buffer; + +typedef struct cgltf_buffer_view +{ + cgltf_buffer* buffer; + cgltf_size offset; + cgltf_size size; + cgltf_size stride; /* 0 == automatically determined by accessor */ + cgltf_buffer_view_type type; +} cgltf_buffer_view; + +typedef struct cgltf_accessor +{ + cgltf_component_type component_type; + cgltf_type type; + cgltf_size offset; + cgltf_size count; + cgltf_size stride; + cgltf_buffer_view* buffer_view; +} cgltf_accessor; + +typedef struct cgltf_attribute +{ + cgltf_attribute_type name; + cgltf_accessor* data; +} cgltf_attribute; + + +typedef struct cgltf_rgba +{ + cgltf_float r; + cgltf_float g; + cgltf_float b; + cgltf_float a; +} cgltf_rgba; + +typedef struct cgltf_image +{ + char* uri; + cgltf_buffer_view* buffer_view; + char* mime_type; +} cgltf_image; + +typedef struct cgltf_sampler +{ + cgltf_float mag_filter; + cgltf_float min_filter; + cgltf_float wrap_s; + cgltf_float wrap_t; +} cgltf_sampler; + +typedef struct cgltf_texture +{ + cgltf_image* image; + cgltf_sampler* sampler; +} cgltf_texture; + +typedef struct cgltf_texture_view +{ + cgltf_texture* texture; + cgltf_size texcoord; + cgltf_float scale; +} cgltf_texture_view; + +typedef struct cgltf_pbr +{ + cgltf_texture_view base_color_texture; + cgltf_texture_view metallic_roughness_texture; + + cgltf_rgba base_color; + cgltf_float metallic_factor; + cgltf_float roughness_factor; +} cgltf_pbr; + +typedef struct cgltf_material +{ + char* name; + cgltf_pbr pbr; + cgltf_rgba emissive_color; + cgltf_texture_view normal_texture; + cgltf_texture_view emissive_texture; + cgltf_texture_view occlusion_texture; + cgltf_bool double_sided; +} cgltf_material; + +typedef struct cgltf_primitive { + cgltf_primitive_type type; + cgltf_accessor* indices; + cgltf_material* material; + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_primitive; + +typedef struct cgltf_mesh { + char* name; + cgltf_primitive* primitives; + cgltf_size primitives_count; +} cgltf_mesh; + +typedef struct cgltf_data +{ + unsigned version; + cgltf_file_type file_type; + + cgltf_mesh* meshes; + cgltf_size meshes_count; + + cgltf_material* materials; + cgltf_size materials_count; + + cgltf_accessor* accessors; + cgltf_size accessors_count; + + cgltf_buffer_view* buffer_views; + cgltf_size buffer_views_count; + + cgltf_buffer* buffers; + cgltf_size buffers_count; + + cgltf_image* images; + cgltf_size images_count; + + cgltf_texture* textures; + cgltf_size textures_count; + + cgltf_sampler* samplers; + cgltf_size samplers_count; + + const void* bin; + cgltf_size bin_size; + + void (*memory_free) (void* user, void* ptr); + void* memory_user_data; +} cgltf_data; + +cgltf_result cgltf_parse( + const cgltf_options* options, + const void* data, + cgltf_size size, + cgltf_data* out_data); + +void cgltf_free(cgltf_data* data); + +#endif /* #ifndef CGLTF_H_INCLUDED__ */ + +/* + * + * Stop now, if you are only interested in the API. + * Below, you find the implementation. + * + */ + +#ifdef __INTELLISENSE__ +/* This makes MSVC intellisense work. */ +#define CGLTF_IMPLEMENTATION +#endif + +#ifdef CGLTF_IMPLEMENTATION + +#include /* For uint8_t, uint32_t */ +#include /* For strncpy */ +#include /* For malloc, free */ + + +/* + * -- jsmn.h start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; +void jsmn_init(jsmn_parser *parser); +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); +/* + * -- jsmn.h end -- + */ + + +static const cgltf_size GltfHeaderSize = 12; +static const cgltf_size GltfChunkHeaderSize = 8; +static const uint32_t GltfMagic = 0x46546C67; +static const uint32_t GltfMagicJsonChunk = 0x4E4F534A; +static const uint32_t GltfMagicBinChunk = 0x004E4942; + +static void* cgltf_mem_alloc(void* user, cgltf_size size) +{ + return malloc(size); +} + +static void cgltf_mem_free(void* user, void* ptr) +{ + free(ptr); +} + +static cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data* out_data); + +cgltf_result cgltf_parse(const cgltf_options* options, const void* data, cgltf_size size, cgltf_data* out_data) +{ + if (size < GltfHeaderSize) + { + return cgltf_result_data_too_short; + } + + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + cgltf_options fixed_options = *options; + if (fixed_options.memory_alloc == NULL) + { + fixed_options.memory_alloc = &cgltf_mem_alloc; + } + if (fixed_options.memory_free == NULL) + { + fixed_options.memory_free = &cgltf_mem_free; + } + + uint32_t tmp; + // Magic + memcpy(&tmp, data, 4); + if (tmp != GltfMagic) + { + if (fixed_options.type == cgltf_file_type_invalid) + { + fixed_options.type = cgltf_file_type_gltf; + } + else + { + return cgltf_result_unknown_format; + } + } + + memset(out_data, 0, sizeof(cgltf_data)); + out_data->memory_free = fixed_options.memory_free; + out_data->memory_user_data = fixed_options.memory_user_data; + + if (fixed_options.type == cgltf_file_type_gltf) + { + out_data->file_type = cgltf_file_type_gltf; + return cgltf_parse_json(&fixed_options, data, size, out_data); + } + + const uint8_t* ptr = (const uint8_t*)data; + // Version + memcpy(&tmp, ptr + 4, 4); + out_data->version = tmp; + + // Total length + memcpy(&tmp, ptr + 8, 4); + if (tmp > size) + { + return cgltf_result_data_too_short; + } + + const uint8_t* json_chunk = ptr + GltfHeaderSize; + + // JSON chunk: length + uint32_t json_length; + memcpy(&json_length, json_chunk, 4); + if (GltfHeaderSize + GltfChunkHeaderSize + json_length > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: magic + memcpy(&tmp, json_chunk + 4, 4); + if (tmp != GltfMagicJsonChunk) + { + return cgltf_result_unknown_format; + } + + json_chunk += GltfChunkHeaderSize; + cgltf_result json_result = cgltf_parse_json(&fixed_options, json_chunk, json_length, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + out_data->file_type = cgltf_file_type_invalid; + if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize <= size) + { + // We can read another chunk + const uint8_t* bin_chunk = json_chunk + json_length; + + // Bin chunk: length + uint32_t bin_length; + memcpy(&bin_length, bin_chunk, 4); + if (GltfHeaderSize + GltfChunkHeaderSize + json_length + GltfChunkHeaderSize + bin_length > size) + { + return cgltf_result_data_too_short; + } + + // Bin chunk: magic + memcpy(&tmp, bin_chunk + 4, 4); + if (tmp != GltfMagicBinChunk) + { + return cgltf_result_unknown_format; + } + + bin_chunk += GltfChunkHeaderSize; + + out_data->file_type = cgltf_file_type_glb; + out_data->bin = bin_chunk; + out_data->bin_size = bin_length; + } + + return cgltf_result_success; +} + +void cgltf_free(cgltf_data* data) +{ + data->memory_free(data->memory_user_data, data->accessors); + data->memory_free(data->memory_user_data, data->buffer_views); + + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + data->memory_free(data->memory_user_data, data->buffers[i].uri); + } + data->memory_free(data->memory_user_data, data->buffers); + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + data->memory_free(data->memory_user_data, data->meshes[i].name); + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes); + } + data->memory_free(data->memory_user_data, data->meshes[i].primitives); + } + data->memory_free(data->memory_user_data, data->meshes); + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + data->memory_free(data->memory_user_data, data->materials[i].name); + } + + data->memory_free(data->memory_user_data, data->materials); + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + data->memory_free(data->memory_user_data, data->images[i].uri); + data->memory_free(data->memory_user_data, data->images[i].mime_type); + } + + data->memory_free(data->memory_user_data, data->images); + data->memory_free(data->memory_user_data, data->textures); + data->memory_free(data->memory_user_data, data->samplers); +} + +#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return -128; } + +static char cgltf_to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') + { + c = 'a' + (c - 'A'); + } + return c; +} + +static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); + int const str_len = strlen(str); + int const name_length = tok->end - tok->start; + if (name_length == str_len) + { + for (int i = 0; i < str_len; ++i) + { + char const a = cgltf_to_lower(*((const char*)json_chunk + tok->start + i)); + char const b = cgltf_to_lower(*(str + i)); + if (a < b) + { + return -1; + } + else if (a > b) + { + return 1; + } + } + return 0; + } + return 128; +} + +static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + char tmp[128]; + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + int size = tok->end - tok->start; + strncpy(tmp, + (const char*)json_chunk + tok->start, + size); + tmp[size] = 0; + return atoi(tmp); +} + +static cgltf_float cgltf_json_to_float(jsmntok_t const* tok, const uint8_t* json_chunk) { + char tmp[128]; + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + int size = tok->end - tok->start; + strncpy(tmp, + (const char*)json_chunk + tok->start, + size); + tmp[size] = 0; + return atof(tmp); +} + +static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) { + //TODO: error handling? + if (memcmp(json_chunk + tok->start, "true", 4) == 0) + return 1; + + return 0; +} + +static int cgltf_skip_json(jsmntok_t const* tokens, int i) +{ + if (tokens[i].type == JSMN_ARRAY) + { + int size = tokens[i].size; + ++i; + for (int j = 0; j < size; ++j) + { + i = cgltf_skip_json(tokens, i); + } + } + else if (tokens[i].type == JSMN_OBJECT) + { + int size = tokens[i].size; + ++i; + for (int j = 0; j < size; ++j) + { + i = cgltf_skip_json(tokens, i); + i = cgltf_skip_json(tokens, i); + } + } + else if (tokens[i].type == JSMN_PRIMITIVE + || tokens[i].type == JSMN_STRING) + { + return i + 1; + } + return i; +} + + +static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, + cgltf_primitive* out_prim) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + out_prim->indices = (void* )-1; + out_prim->material = NULL; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) + { + ++i; + out_prim->type + = (cgltf_primitive_type) + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + out_prim->indices = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) + { + ++i; + out_prim->material = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) + { + ++i; + if (tokens[i].type != JSMN_OBJECT) + { + return -1; + } + out_prim->attributes_count = tokens[i].size; + out_prim->attributes + = options->memory_alloc(options->memory_user_data, sizeof(cgltf_attribute) * tokens[i].size); + ++i; + for (cgltf_size iattr = 0; iattr < out_prim->attributes_count; ++iattr) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + out_prim->attributes[iattr].name = cgltf_attribute_type_invalid; + if (cgltf_json_strcmp(tokens+i, json_chunk, "POSITION") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_position; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "NORMAL") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_normal; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "TANGENT") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_tangent; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "TEXCOORD_0") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_texcoord_0; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "TEXCOORD_1") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_texcoord_1; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "COLOR_0") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_color_0; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "JOINTS_0") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_joints_0; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "WEIGHTS_0") == 0) + { + out_prim->attributes[iattr].name = cgltf_attribute_type_weights_0; + } + ++i; + out_prim->attributes[iattr].data = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size mesh_index, + cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_data->meshes[mesh_index].name = NULL; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + ++i; + int strsize = tokens[i].end - tokens[i].start; + out_data->meshes[mesh_index].name = options->memory_alloc(options->memory_user_data, strsize + 1); + strncpy(out_data->meshes[mesh_index].name, + (const char*)json_chunk + tokens[i].start, + strsize); + out_data->meshes[mesh_index].name[strsize] = 0; + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) + { + ++i; + if (tokens[i].type != JSMN_ARRAY) + { + return -1; + } + out_data->meshes[mesh_index].primitives_count = tokens[i].size; + out_data->meshes[mesh_index].primitives = options->memory_alloc(options->memory_user_data, sizeof(cgltf_primitive) * tokens[i].size); + ++i; + + for (cgltf_size prim_index = 0; + prim_index < out_data->meshes[mesh_index].primitives_count; + ++prim_index) + { + i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, + &out_data->meshes[mesh_index].primitives[prim_index]); + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_meshes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->meshes_count = tokens[i].size; + out_data->meshes = options->memory_alloc(options->memory_user_data, sizeof(cgltf_mesh) * out_data->meshes_count); + ++i; + for (cgltf_size j = 0 ; j < out_data->meshes_count; ++j) + { + i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, j, out_data); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size accessor_index, + cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + memset(&out_data->accessors[accessor_index], 0, sizeof(cgltf_accessor)); + out_data->accessors[accessor_index].buffer_view = (void*)-1; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_data->accessors[accessor_index].buffer_view = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_data->accessors[accessor_index].offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + int type = cgltf_json_to_int(tokens+i, json_chunk); + switch (type) + { + case 5120: + type = cgltf_component_type_r_8; + break; + case 5121: + type = cgltf_component_type_r_8u; + break; + case 5122: + type = cgltf_component_type_r_16; + break; + case 5123: + type = cgltf_component_type_r_16u; + break; + case 5125: + type = cgltf_component_type_r_32u; + break; + case 5126: + type = cgltf_component_type_r_32f; + break; + default: + type = cgltf_component_type_invalid; + break; + } + out_data->accessors[accessor_index].component_type = type; + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_data->accessors[accessor_index].count = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "SCALAR") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_scalar; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_vec2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_vec3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_vec4; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_mat2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_mat3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) + { + out_data->accessors[accessor_index].type = cgltf_type_mat4; + } + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_rgba(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_rgba* out) +{ + int components = tokens[i].size; + if (components >= 2) { + out->r = cgltf_json_to_float(tokens + ++i, json_chunk); + out->g = cgltf_json_to_float(tokens + ++i, json_chunk); + + if (components > 2) + out->b = cgltf_json_to_float(tokens + ++i, json_chunk); + + if (components > 3) + out->a = cgltf_json_to_float(tokens + ++i, json_chunk); + } + else { + out->r = cgltf_json_to_float(tokens + ++i, json_chunk); + out->g = out->r; + out->b = out->r; + out->a = out->r; + } + + return ++i; +} + +static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out) { + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) + { + ++i; + out->texture = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + ++i; + out->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + } + + return i; +} + +static int cgltf_parse_json_pbr(jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size mat_index, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) + { + ++i; + out_data->materials[mat_index].pbr.metallic_factor = + cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "roughnessFactor") == 0) + { + ++i; + out_data->materials[mat_index].pbr.roughness_factor = + cgltf_json_to_float(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorFactor") == 0) + { + i = cgltf_parse_json_rgba(tokens, i + 1, json_chunk, + &(out_data->materials[mat_index].pbr.base_color)); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &(out_data->materials[mat_index].pbr.base_color_texture)); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &(out_data->materials[mat_index].pbr.metallic_roughness_texture)); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size img_index, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + memset(&out_data->images[img_index], 0, sizeof(cgltf_image)); + int size = tokens[i].size; + ++i; + + out_data->images[img_index].buffer_view = (void*)-1; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) + { + ++i; + int strsize = tokens[i].end - tokens[i].start; + out_data->images[img_index].uri = options->memory_alloc(options->memory_user_data, strsize + 1); + strncpy(out_data->images[img_index].uri, + (const char*)json_chunk + tokens[i].start, + strsize); + out_data->images[img_index].uri[strsize] = 0; + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_data->images[img_index].buffer_view = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) + { + ++i; + int strsize = tokens[i].end - tokens[i].start; + out_data->images[img_index].mime_type = options->memory_alloc(options->memory_user_data, strsize + 1); + strncpy(out_data->images[img_index].mime_type, + (const char*)json_chunk + tokens[i].start, + strsize); + out_data->images[img_index].mime_type[strsize] = 0; + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + } + + return i; +} + +static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size smp_index, cgltf_data* out_data) { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + memset(&out_data->samplers[smp_index], 0, sizeof(cgltf_sampler)); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) + { + ++i; + out_data->samplers[smp_index].mag_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) + { + ++i; + out_data->samplers[smp_index].min_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) + { + ++i; + out_data->samplers[smp_index].wrap_s + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) + { + ++i; + out_data->samplers[smp_index].wrap_t + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + } + + return i; +} + + +static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size tex_index, cgltf_data* out_data) { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + memset(&out_data->textures[tex_index], 0, sizeof(cgltf_texture)); + out_data->textures[tex_index].image = (void*)-1; + out_data->textures[tex_index].sampler = (void*)-1; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) + { + ++i; + out_data->textures[tex_index].sampler + = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_data->textures[tex_index].image + = (void*)(size_t)cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + } + + return i; +} + +static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size mat_index, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + cgltf_material* material = &out_data->materials[mat_index]; + + memset(material, 0, sizeof(cgltf_material)); + material->emissive_texture.texture = (void*)-1; + material->emissive_texture.scale = 1.0f; + + material->normal_texture.texture = (void*)-1; + material->normal_texture.scale = 1.0f; + + material->occlusion_texture.texture = (void*)-1; + material->occlusion_texture.scale = 1.0f; + + material->pbr.base_color_texture.texture = (void*)-1; + material->pbr.base_color_texture.scale = 1.0f; + + material->pbr.metallic_roughness_texture.texture = (void*)-1; + material->pbr.metallic_roughness_texture.scale = 1.0f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + ++i; + int strsize = tokens[i].end - tokens[i].start; + material->name = options->memory_alloc(options->memory_user_data, strsize + 1); + strncpy(material->name, + (const char*)json_chunk + tokens[i].start, + strsize); + material->name[strsize] = 0; + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) + { + i = cgltf_parse_json_pbr(tokens, i+1, json_chunk, mat_index, out_data); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) + { + i = cgltf_parse_json_rgba(tokens, i + 1, json_chunk, + &(material->emissive_color)); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &(material->normal_texture)); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &(material->emissive_texture)); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &(material->occlusion_texture)); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) + { + ++i; + material->double_sided = + cgltf_json_to_bool(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->accessors_count = tokens[i].size; + out_data->accessors = options->memory_alloc(options->memory_user_data, sizeof(cgltf_accessor) * out_data->accessors_count); + ++i; + for (cgltf_size j = 0 ; j < out_data->accessors_count; ++j) + { + i = cgltf_parse_json_accessor(tokens, i, json_chunk, j, out_data); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_materials(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->materials_count = tokens[i].size; + out_data->materials = options->memory_alloc(options->memory_user_data, sizeof(cgltf_material) * out_data->materials_count); + ++i; + for (cgltf_size j = 0; j < out_data->materials_count; ++j) + { + i = cgltf_parse_json_material(options, tokens, i, json_chunk, j, out_data); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_images(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->images_count = tokens[i].size; + out_data->images = options->memory_alloc(options->memory_user_data, sizeof(cgltf_image) * out_data->images_count); + ++i; + + for (cgltf_size j = 0; j < out_data->images_count; ++j) { + i = cgltf_parse_json_image(options, tokens, i, json_chunk, j, out_data); + if (i < 0) { + return i; + } + } + return i; +} + +static int cgltf_parse_json_textures(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->textures_count = tokens[i].size; + out_data->textures = options->memory_alloc(options->memory_user_data, sizeof(cgltf_texture) * out_data->textures_count); + ++i; + + for (cgltf_size j = 0; j < out_data->textures_count; ++j) { + i = cgltf_parse_json_texture(options, tokens, i, json_chunk, j, out_data); + if (i < 0) { + return i; + } + } + return i; +} + +static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->samplers_count = tokens[i].size; + out_data->samplers = options->memory_alloc(options->memory_user_data, sizeof(cgltf_sampler) * out_data->samplers_count); + ++i; + + for (cgltf_size j = 0; j < out_data->samplers_count; ++j) { + i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, j, out_data); + if (i < 0) { + return i; + } + } + return i; +} + +static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size buffer_view_index, + cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + memset(&out_data->buffer_views[buffer_view_index], 0, sizeof(cgltf_buffer_view)); + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_data->buffer_views[buffer_view_index].buffer = + (void*)(size_t)cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_data->buffer_views[buffer_view_index].offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_data->buffer_views[buffer_view_index].size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_data->buffer_views[buffer_view_index].stride = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + int type = cgltf_json_to_int(tokens+i, json_chunk); + switch (type) + { + case 34962: + type = cgltf_buffer_view_type_vertices; + break; + case 34963: + type = cgltf_buffer_view_type_indices; + break; + default: + type = cgltf_buffer_view_type_invalid; + break; + } + out_data->buffer_views[buffer_view_index].type = type; + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->buffer_views_count = tokens[i].size; + out_data->buffer_views = options->memory_alloc(options->memory_user_data, sizeof(cgltf_buffer_view) * out_data->buffer_views_count); + ++i; + for (cgltf_size j = 0 ; j < out_data->buffer_views_count; ++j) + { + i = cgltf_parse_json_buffer_view(tokens, i, json_chunk, j, out_data); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* tokens, int i, + const uint8_t* json_chunk, cgltf_size buffer_index, + cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_data->buffers[buffer_index].uri = NULL; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_data->buffers[buffer_index].size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) + { + ++i; + int strsize = tokens[i].end - tokens[i].start; + out_data->buffers[buffer_index].uri = options->memory_alloc(options->memory_user_data, strsize + 1); + strncpy(out_data->buffers[buffer_index].uri, + (const char*)json_chunk + tokens[i].start, + strsize); + out_data->buffers[buffer_index].uri[strsize] = 0; + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + } + + return i; +} + +static int cgltf_parse_json_buffers(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + out_data->buffers_count = tokens[i].size; + out_data->buffers = options->memory_alloc(options->memory_user_data, sizeof(cgltf_buffer) * out_data->buffers_count); + ++i; + for (cgltf_size j = 0 ; j < out_data->buffers_count; ++j) + { + i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, j, out_data); + if (i < 0) + { + return i; + } + } + return i; +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) +{ + cgltf_type size = 0; + + switch (component_type) + { + case cgltf_component_type_rgb_32f: + size = 12; + break; + case cgltf_component_type_rgba_32f: + size = 16; + break; + case cgltf_component_type_rg_32f: + size = 8; + break; + case cgltf_component_type_rg_8: + size = 2; + break; + case cgltf_component_type_rg_16: + size = 4; + break; + case cgltf_component_type_rgba_8: + size = 4; + break; + case cgltf_component_type_rgba_16: + size = 8; + break; + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + size = 1; + break; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + size = 2; + break; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + size = 4; + break; + case cgltf_component_type_invalid: + default: + size = 0; + break; + } + + switch (type) + { + case cgltf_type_vec2: + size *= 2; + break; + case cgltf_type_vec3: + size *= 3; + break; + case cgltf_type_vec4: + size *= 4; + break; + case cgltf_type_mat2: + size *= 4; + break; + case cgltf_type_mat3: + size *= 9; + break; + case cgltf_type_mat4: + size *= 16; + break; + case cgltf_type_invalid: + case cgltf_type_scalar: + default: + size *= 1; + break; + } + + return size; +} + +cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data* out_data) +{ + jsmn_parser parser = {0}; + + if (options->json_token_count == 0) + { + options->json_token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + } + + jsmntok_t* tokens = options->memory_alloc(options->memory_user_data, sizeof(jsmntok_t) * options->json_token_count); + + jsmn_init(&parser); + + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); + + if (token_count < 0 + || tokens[0].type != JSMN_OBJECT) + { + return cgltf_result_invalid_json; + } + + // The root is an object. + + for (int i = 1; i < token_count; ) + { + jsmntok_t const* tok = &tokens[i]; + if (tok->type == JSMN_STRING + && i + 1 < token_count) + { + int const name_length = tok->end - tok->start; + if (name_length == 6 + && strncmp((const char*)json_chunk + tok->start, "meshes", 6) == 0) + { + i = cgltf_parse_json_meshes(options, tokens, i+1, json_chunk, out_data); + } + else if (name_length == 9 + && strncmp((const char*)json_chunk + tok->start, "accessors", 9) == 0) + { + i = cgltf_parse_json_accessors(options, tokens, i+1, json_chunk, out_data); + } + else if (name_length == 11 + && strncmp((const char*)json_chunk + tok->start, "bufferViews", 11) == 0) + { + i = cgltf_parse_json_buffer_views(options, tokens, i+1, json_chunk, out_data); + } + else if (name_length == 7 + && strncmp((const char*)json_chunk + tok->start, "buffers", 7) == 0) + { + i = cgltf_parse_json_buffers(options, tokens, i+1, json_chunk, out_data); + } + else if (name_length == 9 + && strncmp((const char*)json_chunk + tok->start, "materials", 9) == 0) + { + i = cgltf_parse_json_materials(options, tokens, i+1, json_chunk, out_data); + } + else if (name_length == 6 + && strncmp((const char*)json_chunk + tok->start, "images", 6) == 0) + { + i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); + } + else if (name_length == 8 + && strncmp((const char*)json_chunk + tok->start, "textures", 8) == 0) + { + i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); + } + else if (name_length == 8 + && strncmp((const char*)json_chunk + tok->start, "samplers", 8) == 0) + { + i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return cgltf_result_invalid_json; + } + } + } + + options->memory_free(options->memory_user_data, tokens); + + /* Fix up pointers */ + for (cgltf_size i = 0; i < out_data->meshes_count; ++i) + { + for (cgltf_size j = 0; j < out_data->meshes[i].primitives_count; ++j) + { + if (out_data->meshes[i].primitives[j].indices ==(void*)-1) + { + out_data->meshes[i].primitives[j].indices = NULL; + } + else + { + out_data->meshes[i].primitives[j].indices + = &out_data->accessors[(cgltf_size)out_data->meshes[i].primitives[j].indices]; + } + + for (cgltf_size k = 0; k < out_data->meshes[i].primitives[j].attributes_count; ++k) + { + out_data->meshes[i].primitives[j].attributes[k].data + = &out_data->accessors[(cgltf_size)out_data->meshes[i].primitives[j].attributes[k].data]; + } + } + } + + for (cgltf_size i = 0; i < out_data->accessors_count; ++i) + { + if (out_data->accessors[i].buffer_view == (void*)-1) + { + out_data->accessors[i].buffer_view = NULL; + } + else + { + out_data->accessors[i].buffer_view + = &out_data->buffer_views[(cgltf_size)out_data->accessors[i].buffer_view]; + out_data->accessors[i].stride = 0; + if (out_data->accessors[i].buffer_view) + { + out_data->accessors[i].stride = out_data->accessors[i].buffer_view->stride; + } + } + if (out_data->accessors[i].stride == 0) + { + out_data->accessors[i].stride = cgltf_calc_size(out_data->accessors[i].type, out_data->accessors[i].component_type); + } + } + + for (cgltf_size i = 0; i < out_data->textures_count; ++i) + { + if (out_data->textures[i].image == (void*)-1) + { + out_data->textures[i].image = NULL; + } + else + { + out_data->textures[i].image = + &out_data->images[(cgltf_size)out_data->textures[i].image]; + } + + if (out_data->textures[i].sampler == (void*)-1) + { + out_data->textures[i].sampler = NULL; + } + else + { + out_data->textures[i].sampler = + &out_data->samplers[(cgltf_size)out_data->textures[i].sampler]; + } + } + + for (cgltf_size i = 0; i < out_data->images_count; ++i) + { + if (out_data->images[i].buffer_view == (void*)-1) + { + out_data->images[i].buffer_view = NULL; + } + else + { + out_data->images[i].buffer_view + = &out_data->buffer_views[(cgltf_size)out_data->images[i].buffer_view]; + } + } + + for (cgltf_size i = 0; i < out_data->materials_count; ++i) + { + if (out_data->materials[i].emissive_texture.texture == (void*)-1) + { + out_data->materials[i].emissive_texture.texture = NULL; + } + else + { + out_data->materials[i].emissive_texture.texture = + &out_data->textures[(cgltf_size)out_data->materials[i].emissive_texture.texture]; + } + + if (out_data->materials[i].normal_texture.texture == (void*)-1) + { + out_data->materials[i].normal_texture.texture = NULL; + } + else + { + out_data->materials[i].normal_texture.texture = + &out_data->textures[(cgltf_size)out_data->materials[i].normal_texture.texture]; + } + + if (out_data->materials[i].occlusion_texture.texture == (void*)-1) + { + out_data->materials[i].occlusion_texture.texture = NULL; + } + else + { + out_data->materials[i].occlusion_texture.texture = + &out_data->textures[(cgltf_size)out_data->materials[i].occlusion_texture.texture]; + } + + if (out_data->materials[i].pbr.base_color_texture.texture == (void*)-1) + { + out_data->materials[i].pbr.base_color_texture.texture = NULL; + } + else + { + out_data->materials[i].pbr.base_color_texture.texture = + &out_data->textures[(cgltf_size)out_data->materials[i].pbr.base_color_texture.texture]; + } + + if (out_data->materials[i].pbr.metallic_roughness_texture.texture == (void*)-1) + { + out_data->materials[i].pbr.metallic_roughness_texture.texture = NULL; + } + else + { + out_data->materials[i].pbr.metallic_roughness_texture.texture = + &out_data->textures[(cgltf_size)out_data->materials[i].pbr.metallic_roughness_texture.texture]; + } + } + + for (cgltf_size i = 0; i < out_data->buffer_views_count; ++i) + { + out_data->buffer_views[i].buffer + = &out_data->buffers[(cgltf_size)out_data->buffer_views[i].buffer]; + } + + return cgltf_result_success; +} + +/* + * -- jsmn.c start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + */ +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} +/* + * -- jsmn.c end -- + */ + +#endif /* #ifdef CGLTF_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif diff --git a/src/models.c b/src/models.c index b1abe66d3..0d1145551 100644 --- a/src/models.c +++ b/src/models.c @@ -5,10 +5,10 @@ * CONFIGURATION: * * #define SUPPORT_FILEFORMAT_OBJ -* Selected desired fileformats to be supported for loading. -* * #define SUPPORT_FILEFORMAT_MTL -* Selected desired fileformats to be supported for loading. +* #define SUPPORT_FILEFORMAT_IQM +* #define SUPPORT_FILEFORMAT_GLTF +* Selected desired fileformats to be supported for model data loading. * * #define SUPPORT_MESH_GENERATION * Support procedural mesh generation functions, uses external par_shapes.h library @@ -48,8 +48,20 @@ #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -#define PAR_SHAPES_IMPLEMENTATION -#include "external/par_shapes.h" // Shapes 3d parametric generation +#if defined(SUPPORT_FILEFORMAT_IQM) + #define RIQM_IMPLEMENTATION + #include "external/riqm.h" // IQM file format loading +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) + #define CGLTF_IMPLEMENTATION + #include "external/cgltf.h" // glTF file format loading +#endif + +#if defined(SUPPORT_MESH_GENERATION) + #define PAR_SHAPES_IMPLEMENTATION + #include "external/par_shapes.h" // Shapes 3d parametric generation +#endif //---------------------------------------------------------------------------------- // Defines and Macros @@ -75,6 +87,12 @@ static Mesh LoadOBJ(const char *fileName); // Load OBJ mesh data #if defined(SUPPORT_FILEFORMAT_MTL) static Material LoadMTL(const char *fileName); // Load MTL material data #endif +#if defined(SUPPORT_FILEFORMAT_GLTF) +static Mesh LoadIQM(const char *fileName); // Load IQM mesh data +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) +static Mesh LoadGLTF(const char *fileName); // Load GLTF mesh data +#endif //---------------------------------------------------------------------------------- // Module Functions Definition @@ -2206,9 +2224,8 @@ void MeshBinormals(Mesh *mesh) Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] }; float tangentW = mesh->tangents[i*4 + 3]; - // TODO: Register computed binormal in mesh->binormal ? - // Vector3 binormal = Vector3Multiply( Vector3CrossProduct( normal, tangent ), tangentW ); + // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW); } } @@ -2631,3 +2648,59 @@ static Material LoadMTL(const char *fileName) return material; } #endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) +// Load IQM mesh data +static Mesh LoadIQM(const char *fileName) +{ + Mesh mesh = { 0 }; + + // TODO: Load IQM file + + return mesh; +} +#endif + +#if defined(SUPPORT_FILEFORMAT_GLTF) +// Load GLTF mesh data +static Mesh LoadGLTF(const char *fileName) +{ + Mesh mesh = { 0 }; + + // GLTF file loading + FILE *gltfFile = fopen(fileName, "rb"); + + if (gltfFile == NULL) + { + TraceLog(LOG_WARNING, "[%s] GLTF file could not be opened", fileName); + return mesh; + } + + fseek(gltfFile, 0, SEEK_END); + int size = ftell(gltfFile); + fseek(gltfFile, 0, SEEK_SET); + + void *buffer = malloc(size); + fread(buffer, size, 1, gltfFile); + + fclose(gltfFile); + + // GLTF data loading + cgltf_options options = {0}; + cgltf_data data; + cgltf_result result = cgltf_parse(&options, buffer, size, &data); + + if (result == cgltf_result_success) + { + printf("Type: %u\n", data.file_type); + printf("Version: %d\n", data.version); + printf("Meshes: %lu\n", data.meshes_count); + } + else TraceLog(LOG_WARNING, "[%s] GLTF data could not be loaded", fileName); + + free(buffer); + cgltf_free(&data); + + return mesh; +} +#endif diff --git a/src/raylib.h b/src/raylib.h index 1c0207ac3..8af8ab4da 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -442,6 +442,7 @@ typedef struct RenderTexture2D { // RenderTexture type, same as RenderTexture2D typedef RenderTexture2D RenderTexture; +// N-Patch layout info typedef struct NPatchInfo { Rectangle sourceRec; // Region in the texture int left; // left border offset