diff options
Diffstat (limited to '3rdparty/bgfx/3rdparty/meshoptimizer/tools')
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/cgltf.h | 4781 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/fast_obj.h | 1397 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp | 3760 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/lodviewer.cpp | 618 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/meshloader.cpp | 9 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetester.cpp | 509 | ||||
-rw-r--r-- | 3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetuner.cpp | 424 |
7 files changed, 11498 insertions, 0 deletions
diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/cgltf.h b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/cgltf.h new file mode 100644 index 00000000000..6b7ff41e28a --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/cgltf.h @@ -0,0 +1,4781 @@ +/** + * cgltf - a single-file glTF 2.0 parser written in C99. + * + * Version: 1.3 + * + * Website: https://github.com/jkuhlmann/cgltf + * + * Distributed under the MIT License, see notice at the end of this file. + * + * Building: + * Include this file where you need the struct and function + * declarations. Have exactly one source file where you define + * `CGLTF_IMPLEMENTATION` before including this file to get the + * function definitions. + * + * Reference: + * `cgltf_result cgltf_parse(const cgltf_options*, const void*, + * cgltf_size, cgltf_data**)` parses both glTF and GLB data. If + * this function returns `cgltf_result_success`, you have to call + * `cgltf_free()` on the created `cgltf_data*` variable. + * Note that contents of external files for buffers and images are not + * automatically loaded. You'll need to read these files yourself using + * URIs in the `cgltf_data` structure. + * + * `cgltf_options` is the struct passed to `cgltf_parse()` to control + * parts of the parsing process. You can use it to force the file type + * and provide memory allocation callbacks. Should be zero-initialized + * to trigger default behavior. + * + * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. + * It generally mirrors the glTF format as described by the spec (see + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). + * + * `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data` + * variable. + * + * `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*, + * const char* gltf_path)` can be optionally called to open and read buffer + * files using the `FILE*` APIs. The `gltf_path` argument is the path to + * the original glTF file, which allows the parser to resolve the path to + * buffer files. + * + * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, + * cgltf_size size, const char* base64, void** out_data)` decodes + * base64-encoded data content. Used internally by `cgltf_load_buffers()` + * and may be useful if you're not dealing with normal files. + * + * `cgltf_result cgltf_parse_file(const cgltf_options* options, const + * char* path, cgltf_data** out_data)` can be used to open the given + * file using `FILE*` APIs and parse the data using `cgltf_parse()`. + * + * `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional + * checks to make sure the parsed glTF data is valid. + * + * `cgltf_node_transform_local` converts the translation / rotation / scale properties of a node + * into a mat4. + * + * `cgltf_node_transform_world` calls `cgltf_node_transform_local` on every ancestor in order + * to compute the root-to-node transformation. + * + * `cgltf_accessor_read_float` reads a certain element from an accessor and converts it to + * floating point, assuming that `cgltf_load_buffers` has already been called. The passed-in element + * size is the number of floats in the output buffer, which should be in the range [1, 16]. Returns + * false if the passed-in element_size is too small, or if the accessor is sparse. + * + * `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t + * and only works with single-component data types. + * + * `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*, + * char* dest, cgltf_size* dest_size)` allows to retrieve the "extras" data that + * can be attached to many glTF objects (which can be arbitrary JSON data). The + * `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data + * as it appears in the complete glTF JSON data. This function copies the extras data + * into the provided buffer. If `dest` is NULL, the length of the data is written into + * `dest_size`. You can then parse this data using your own JSON parser + * or, if you've included the cgltf implementation using the integrated JSMN JSON parser. + */ +#ifndef CGLTF_H_INCLUDED__ +#define CGLTF_H_INCLUDED__ + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef size_t cgltf_size; +typedef float cgltf_float; +typedef int cgltf_int; +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; /* invalid == auto detect */ + 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_gltf, + cgltf_result_invalid_options, + cgltf_result_file_not_found, + cgltf_result_io_error, + cgltf_result_out_of_memory, +} 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, + cgltf_attribute_type_color, + cgltf_attribute_type_joints, + cgltf_attribute_type_weights, +} cgltf_attribute_type; + +typedef enum cgltf_component_type +{ + cgltf_component_type_invalid, + cgltf_component_type_r_8, /* BYTE */ + cgltf_component_type_r_8u, /* UNSIGNED_BYTE */ + cgltf_component_type_r_16, /* SHORT */ + cgltf_component_type_r_16u, /* UNSIGNED_SHORT */ + cgltf_component_type_r_32u, /* UNSIGNED_INT */ + cgltf_component_type_r_32f, /* FLOAT */ +} 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_primitive_type_points, + cgltf_primitive_type_lines, + cgltf_primitive_type_line_loop, + cgltf_primitive_type_line_strip, + cgltf_primitive_type_triangles, + cgltf_primitive_type_triangle_strip, + cgltf_primitive_type_triangle_fan, +} cgltf_primitive_type; + +typedef enum cgltf_alpha_mode +{ + cgltf_alpha_mode_opaque, + cgltf_alpha_mode_mask, + cgltf_alpha_mode_blend, +} cgltf_alpha_mode; + +typedef enum cgltf_animation_path_type { + cgltf_animation_path_type_invalid, + cgltf_animation_path_type_translation, + cgltf_animation_path_type_rotation, + cgltf_animation_path_type_scale, + cgltf_animation_path_type_weights, +} cgltf_animation_path_type; + +typedef enum cgltf_interpolation_type { + cgltf_interpolation_type_linear, + cgltf_interpolation_type_step, + cgltf_interpolation_type_cubic_spline, +} cgltf_interpolation_type; + +typedef enum cgltf_camera_type { + cgltf_camera_type_invalid, + cgltf_camera_type_perspective, + cgltf_camera_type_orthographic, +} cgltf_camera_type; + +typedef enum cgltf_light_type { + cgltf_light_type_invalid, + cgltf_light_type_directional, + cgltf_light_type_point, + cgltf_light_type_spot, +} cgltf_light_type; + +typedef struct cgltf_extras { + cgltf_size start_offset; + cgltf_size end_offset; +} cgltf_extras; + +typedef struct cgltf_buffer +{ + cgltf_size size; + char* uri; + void* data; /* loaded by cgltf_load_buffers */ + cgltf_extras extras; +} 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_extras extras; +} cgltf_buffer_view; + +typedef struct cgltf_accessor_sparse +{ + cgltf_size count; + cgltf_buffer_view* indices_buffer_view; + cgltf_size indices_byte_offset; + cgltf_component_type indices_component_type; + cgltf_buffer_view* values_buffer_view; + cgltf_size values_byte_offset; + cgltf_extras extras; + cgltf_extras indices_extras; + cgltf_extras values_extras; +} cgltf_accessor_sparse; + +typedef struct cgltf_accessor +{ + cgltf_component_type component_type; + cgltf_bool normalized; + cgltf_type type; + cgltf_size offset; + cgltf_size count; + cgltf_size stride; + cgltf_buffer_view* buffer_view; + cgltf_bool has_min; + cgltf_float min[16]; + cgltf_bool has_max; + cgltf_float max[16]; + cgltf_bool is_sparse; + cgltf_accessor_sparse sparse; + cgltf_extras extras; +} cgltf_accessor; + +typedef struct cgltf_attribute +{ + char* name; + cgltf_attribute_type type; + cgltf_int index; + cgltf_accessor* data; +} cgltf_attribute; + +typedef struct cgltf_image +{ + char* name; + char* uri; + cgltf_buffer_view* buffer_view; + char* mime_type; + cgltf_extras extras; +} cgltf_image; + +typedef struct cgltf_sampler +{ + cgltf_int mag_filter; + cgltf_int min_filter; + cgltf_int wrap_s; + cgltf_int wrap_t; + cgltf_extras extras; +} cgltf_sampler; + +typedef struct cgltf_texture +{ + char* name; + cgltf_image* image; + cgltf_sampler* sampler; + cgltf_extras extras; +} cgltf_texture; + +typedef struct cgltf_texture_transform +{ + cgltf_float offset[2]; + cgltf_float rotation; + cgltf_float scale[2]; + cgltf_int texcoord; +} cgltf_texture_transform; + +typedef struct cgltf_texture_view +{ + cgltf_texture* texture; + cgltf_int texcoord; + cgltf_float scale; /* equivalent to strength for occlusion_texture */ + cgltf_bool has_transform; + cgltf_texture_transform transform; + cgltf_extras extras; +} cgltf_texture_view; + +typedef struct cgltf_pbr_metallic_roughness +{ + cgltf_texture_view base_color_texture; + cgltf_texture_view metallic_roughness_texture; + + cgltf_float base_color_factor[4]; + cgltf_float metallic_factor; + cgltf_float roughness_factor; + + cgltf_extras extras; +} cgltf_pbr_metallic_roughness; + +typedef struct cgltf_pbr_specular_glossiness +{ + cgltf_texture_view diffuse_texture; + cgltf_texture_view specular_glossiness_texture; + + cgltf_float diffuse_factor[4]; + cgltf_float specular_factor[3]; + cgltf_float glossiness_factor; +} cgltf_pbr_specular_glossiness; + +typedef struct cgltf_material +{ + char* name; + cgltf_bool has_pbr_metallic_roughness; + cgltf_bool has_pbr_specular_glossiness; + cgltf_pbr_metallic_roughness pbr_metallic_roughness; + cgltf_pbr_specular_glossiness pbr_specular_glossiness; + cgltf_texture_view normal_texture; + cgltf_texture_view occlusion_texture; + cgltf_texture_view emissive_texture; + cgltf_float emissive_factor[3]; + cgltf_alpha_mode alpha_mode; + cgltf_float alpha_cutoff; + cgltf_bool double_sided; + cgltf_bool unlit; + cgltf_extras extras; +} cgltf_material; + +typedef struct cgltf_morph_target { + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_morph_target; + +typedef struct cgltf_primitive { + cgltf_primitive_type type; + cgltf_accessor* indices; + cgltf_material* material; + cgltf_attribute* attributes; + cgltf_size attributes_count; + cgltf_morph_target* targets; + cgltf_size targets_count; + cgltf_extras extras; +} cgltf_primitive; + +typedef struct cgltf_mesh { + char* name; + cgltf_primitive* primitives; + cgltf_size primitives_count; + cgltf_float* weights; + cgltf_size weights_count; + char** target_names; + cgltf_size target_names_count; + cgltf_extras extras; +} cgltf_mesh; + +typedef struct cgltf_node cgltf_node; + +typedef struct cgltf_skin { + char* name; + cgltf_node** joints; + cgltf_size joints_count; + cgltf_node* skeleton; + cgltf_accessor* inverse_bind_matrices; + cgltf_extras extras; +} cgltf_skin; + +typedef struct cgltf_camera_perspective { + cgltf_float aspect_ratio; + cgltf_float yfov; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_perspective; + +typedef struct cgltf_camera_orthographic { + cgltf_float xmag; + cgltf_float ymag; + cgltf_float zfar; + cgltf_float znear; + cgltf_extras extras; +} cgltf_camera_orthographic; + +typedef struct cgltf_camera { + char* name; + cgltf_camera_type type; + union { + cgltf_camera_perspective perspective; + cgltf_camera_orthographic orthographic; + }; + cgltf_extras extras; +} cgltf_camera; + +typedef struct cgltf_light { + char* name; + cgltf_float color[3]; + cgltf_float intensity; + cgltf_light_type type; + cgltf_float range; + cgltf_float spot_inner_cone_angle; + cgltf_float spot_outer_cone_angle; +} cgltf_light; + +struct cgltf_node { + char* name; + cgltf_node* parent; + cgltf_node** children; + cgltf_size children_count; + cgltf_skin* skin; + cgltf_mesh* mesh; + cgltf_camera* camera; + cgltf_light* light; + cgltf_float* weights; + cgltf_size weights_count; + cgltf_bool has_translation; + cgltf_bool has_rotation; + cgltf_bool has_scale; + cgltf_bool has_matrix; + cgltf_float translation[3]; + cgltf_float rotation[4]; + cgltf_float scale[3]; + cgltf_float matrix[16]; + cgltf_extras extras; +}; + +typedef struct cgltf_scene { + char* name; + cgltf_node** nodes; + cgltf_size nodes_count; + cgltf_extras extras; +} cgltf_scene; + +typedef struct cgltf_animation_sampler { + cgltf_accessor* input; + cgltf_accessor* output; + cgltf_interpolation_type interpolation; + cgltf_extras extras; +} cgltf_animation_sampler; + +typedef struct cgltf_animation_channel { + cgltf_animation_sampler* sampler; + cgltf_node* target_node; + cgltf_animation_path_type target_path; + cgltf_extras extras; +} cgltf_animation_channel; + +typedef struct cgltf_animation { + char* name; + cgltf_animation_sampler* samplers; + cgltf_size samplers_count; + cgltf_animation_channel* channels; + cgltf_size channels_count; + cgltf_extras extras; +} cgltf_animation; + +typedef struct cgltf_asset { + char* copyright; + char* generator; + char* version; + char* min_version; + cgltf_extras extras; +} cgltf_asset; + +typedef struct cgltf_data +{ + cgltf_file_type file_type; + void* file_data; + + cgltf_asset asset; + + 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; + + cgltf_skin* skins; + cgltf_size skins_count; + + cgltf_camera* cameras; + cgltf_size cameras_count; + + cgltf_light* lights; + cgltf_size lights_count; + + cgltf_node* nodes; + cgltf_size nodes_count; + + cgltf_scene* scenes; + cgltf_size scenes_count; + + cgltf_scene* scene; + + cgltf_animation* animations; + cgltf_size animations_count; + + cgltf_extras extras; + + char** extensions_used; + cgltf_size extensions_used_count; + + char** extensions_required; + cgltf_size extensions_required_count; + + const char* json; + cgltf_size json_size; + + 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); + +cgltf_result cgltf_parse_file( + const cgltf_options* options, + const char* path, + cgltf_data** out_data); + +cgltf_result cgltf_load_buffers( + const cgltf_options* options, + cgltf_data* data, + const char* gltf_path); + + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); + +cgltf_result cgltf_validate( + cgltf_data* data); + +void cgltf_free(cgltf_data* data); + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix); +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix); + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size); +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index); + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size); + +#ifdef __cplusplus +} +#endif + +#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 <stdint.h> /* For uint8_t, uint32_t */ +#include <string.h> /* For strncpy */ +#include <stdlib.h> /* For malloc, free */ +#include <stdio.h> /* For fopen */ +#include <limits.h> /* For UINT_MAX etc */ + +/* JSMN_PARENT_LINKS is necessary to make parsing large structures linear in input size */ +#define JSMN_PARENT_LINKS + +/* JSMN_STRICT is necessary to reject invalid JSON documents */ +#define JSMN_STRICT + +/* + * -- 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; +static void jsmn_init(jsmn_parser *parser); +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens); +/* + * -- jsmn.h end -- + */ + + +static const cgltf_size GlbHeaderSize = 12; +static const cgltf_size GlbChunkHeaderSize = 8; +static const uint32_t GlbVersion = 2; +static const uint32_t GlbMagic = 0x46546C67; +static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; +static const uint32_t GlbMagicBinChunk = 0x004E4942; + +static void* cgltf_default_alloc(void* user, cgltf_size size) +{ + (void)user; + return malloc(size); +} + +static void cgltf_default_free(void* user, void* ptr) +{ + (void)user; + free(ptr); +} + +static void* cgltf_calloc(cgltf_options* options, size_t element_size, cgltf_size count) +{ + if (SIZE_MAX / element_size < count) + { + return NULL; + } + void* result = options->memory_alloc(options->memory_user_data, element_size * count); + if (!result) + { + return NULL; + } + memset(result, 0, element_size * count); + return result; +} + +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 < GlbHeaderSize) + { + 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_default_alloc; + } + if (fixed_options.memory_free == NULL) + { + fixed_options.memory_free = &cgltf_default_free; + } + + uint32_t tmp; + // Magic + memcpy(&tmp, data, 4); + if (tmp != GlbMagic) + { + if (fixed_options.type == cgltf_file_type_invalid) + { + fixed_options.type = cgltf_file_type_gltf; + } + else if (fixed_options.type == cgltf_file_type_glb) + { + return cgltf_result_unknown_format; + } + } + + if (fixed_options.type == cgltf_file_type_gltf) + { + cgltf_result json_result = cgltf_parse_json(&fixed_options, (const uint8_t*)data, size, out_data); + if (json_result != cgltf_result_success) + { + return json_result; + } + + (*out_data)->file_type = cgltf_file_type_gltf; + + return cgltf_result_success; + } + + const uint8_t* ptr = (const uint8_t*)data; + // Version + memcpy(&tmp, ptr + 4, 4); + uint32_t version = tmp; + if (version != GlbVersion) + { + return cgltf_result_unknown_format; + } + + // Total length + memcpy(&tmp, ptr + 8, 4); + if (tmp > size) + { + return cgltf_result_data_too_short; + } + + const uint8_t* json_chunk = ptr + GlbHeaderSize; + + if (GlbHeaderSize + GlbChunkHeaderSize > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: length + uint32_t json_length; + memcpy(&json_length, json_chunk, 4); + if (GlbHeaderSize + GlbChunkHeaderSize + json_length > size) + { + return cgltf_result_data_too_short; + } + + // JSON chunk: magic + memcpy(&tmp, json_chunk + 4, 4); + if (tmp != GlbMagicJsonChunk) + { + return cgltf_result_unknown_format; + } + + json_chunk += GlbChunkHeaderSize; + + const void* bin = 0; + cgltf_size bin_size = 0; + + if (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize <= 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 (GlbHeaderSize + GlbChunkHeaderSize + json_length + GlbChunkHeaderSize + bin_length > size) + { + return cgltf_result_data_too_short; + } + + // Bin chunk: magic + memcpy(&tmp, bin_chunk + 4, 4); + if (tmp != GlbMagicBinChunk) + { + return cgltf_result_unknown_format; + } + + bin_chunk += GlbChunkHeaderSize; + + bin = bin_chunk; + bin_size = bin_length; + } + + 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_glb; + (*out_data)->bin = bin; + (*out_data)->bin_size = bin_size; + + return cgltf_result_success; +} + +cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cgltf_data** out_data) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + void* (*memory_alloc)(void*, cgltf_size) = options->memory_alloc ? options->memory_alloc : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory_free ? options->memory_free : &cgltf_default_free; + + FILE* file = fopen(path, "rb"); + if (!file) + { + return cgltf_result_file_not_found; + } + + fseek(file, 0, SEEK_END); + + long length = ftell(file); + if (length < 0) + { + fclose(file); + return cgltf_result_io_error; + } + + fseek(file, 0, SEEK_SET); + + char* file_data = (char*)memory_alloc(options->memory_user_data, length); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size file_size = (cgltf_size)length; + cgltf_size read_size = fread(file_data, 1, file_size, file); + + fclose(file); + + if (read_size != file_size) + { + memory_free(options->memory_user_data, file_data); + return cgltf_result_io_error; + } + + cgltf_result result = cgltf_parse(options, file_data, file_size, out_data); + + if (result != cgltf_result_success) + { + memory_free(options->memory_user_data, file_data); + return result; + } + + (*out_data)->file_data = file_data; + + return cgltf_result_success; +} + +static void cgltf_combine_paths(char* path, const char* base, const char* uri) +{ + const char* s0 = strrchr(base, '/'); + const char* s1 = strrchr(base, '\\'); + const char* slash = s0 ? (s1 && s1 > s0 ? s1 : s0) : s1; + + if (slash) + { + size_t prefix = slash - base + 1; + + strncpy(path, base, prefix); + strcpy(path + prefix, uri); + } + else + { + strcpy(path, uri); + } +} + +static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_size size, const char* uri, const char* gltf_path, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory_alloc ? options->memory_alloc : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory_free ? options->memory_free : &cgltf_default_free; + + char* path = (char*)memory_alloc(options->memory_user_data, strlen(uri) + strlen(gltf_path) + 1); + if (!path) + { + return cgltf_result_out_of_memory; + } + + cgltf_combine_paths(path, gltf_path, uri); + + FILE* file = fopen(path, "rb"); + + memory_free(options->memory_user_data, path); + + if (!file) + { + return cgltf_result_file_not_found; + } + + char* file_data = (char*)memory_alloc(options->memory_user_data, size); + if (!file_data) + { + fclose(file); + return cgltf_result_out_of_memory; + } + + cgltf_size read_size = fread(file_data, 1, size, file); + + fclose(file); + + if (read_size != size) + { + memory_free(options->memory_user_data, file_data); + return cgltf_result_io_error; + } + + *out_data = file_data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) +{ + void* (*memory_alloc)(void*, cgltf_size) = options->memory_alloc ? options->memory_alloc : &cgltf_default_alloc; + void (*memory_free)(void*, void*) = options->memory_free ? options->memory_free : &cgltf_default_free; + + unsigned char* data = (unsigned char*)memory_alloc(options->memory_user_data, size); + if (!data) + { + return cgltf_result_out_of_memory; + } + + unsigned int buffer = 0; + unsigned int buffer_bits = 0; + + for (cgltf_size i = 0; i < size; ++i) + { + while (buffer_bits < 8) + { + char ch = *base64++; + + int index = + (unsigned)(ch - 'A') < 26 ? (ch - 'A') : + (unsigned)(ch - 'a') < 26 ? (ch - 'a') + 26 : + (unsigned)(ch - '0') < 10 ? (ch - '0') + 52 : + ch == '+' ? 62 : + ch == '/' ? 63 : + -1; + + if (index < 0) + { + memory_free(options->memory_user_data, data); + return cgltf_result_io_error; + } + + buffer = (buffer << 6) | index; + buffer_bits += 6; + } + + data[i] = (unsigned char)(buffer >> (buffer_bits - 8)); + buffer_bits -= 8; + } + + *out_data = data; + + return cgltf_result_success; +} + +cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) +{ + if (options == NULL) + { + return cgltf_result_invalid_options; + } + + if (data->buffers_count && data->buffers[0].data == NULL && data->buffers[0].uri == NULL && data->bin) + { + if (data->bin_size < data->buffers[0].size) + { + return cgltf_result_data_too_short; + } + + data->buffers[0].data = (void*)data->bin; + } + + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + if (data->buffers[i].data) + { + continue; + } + + const char* uri = data->buffers[i].uri; + + if (uri == NULL) + { + continue; + } + + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + else if (strstr(uri, "://") == NULL && gltf_path) + { + cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data); + + if (res != cgltf_result_success) + { + return res; + } + } + else + { + return cgltf_result_unknown_format; + } + } + + return cgltf_result_success; +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type); + +static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_size offset, cgltf_component_type component_type, cgltf_size count) +{ + char* data = (char*)buffer_view->buffer->data + offset + buffer_view->offset; + cgltf_size bound = 0; + + switch (component_type) + { + case cgltf_component_type_r_8u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned char*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_16u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned short*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + case cgltf_component_type_r_32u: + for (size_t i = 0; i < count; ++i) + { + cgltf_size v = ((unsigned int*)data)[i]; + bound = bound > v ? bound : v; + } + break; + + default: + ; + } + + return bound; +} + +cgltf_result cgltf_validate(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_accessor* accessor = &data->accessors[i]; + + cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); + + if (accessor->buffer_view) + { + cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; + + if (accessor->buffer_view->size < req_size) + { + return cgltf_result_data_too_short; + } + } + + if (accessor->is_sparse) + { + cgltf_accessor_sparse* sparse = &accessor->sparse; + + cgltf_size indices_component_size = cgltf_calc_size(cgltf_type_scalar, sparse->indices_component_type); + cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; + cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; + + if (sparse->indices_buffer_view->size < indices_req_size || + sparse->values_buffer_view->size < values_req_size) + { + return cgltf_result_data_too_short; + } + + if (sparse->indices_component_type != cgltf_component_type_r_8u && + sparse->indices_component_type != cgltf_component_type_r_16u && + sparse->indices_component_type != cgltf_component_type_r_32u) + { + return cgltf_result_invalid_gltf; + } + + if (sparse->indices_buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); + + if (index_bound >= accessor->count) + { + return cgltf_result_data_too_short; + } + } + } + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; + + if (data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size) + { + return cgltf_result_data_too_short; + } + } + + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + if (data->meshes[i].weights) + { + if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count) + { + return cgltf_result_invalid_gltf; + } + } + + if (data->meshes[i].target_names) + { + if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count) + { + return cgltf_result_invalid_gltf; + } + } + + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + if (data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count) + { + return cgltf_result_invalid_gltf; + } + + if (data->meshes[i].primitives[j].attributes_count) + { + cgltf_accessor* first = data->meshes[i].primitives[j].attributes[0].data; + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + if (data->meshes[i].primitives[j].attributes[k].data->count != first->count) + { + return cgltf_result_invalid_gltf; + } + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + if (data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count) + { + return cgltf_result_invalid_gltf; + } + } + } + + cgltf_accessor* indices = data->meshes[i].primitives[j].indices; + + if (indices && + indices->component_type != cgltf_component_type_r_8u && + indices->component_type != cgltf_component_type_r_16u && + indices->component_type != cgltf_component_type_r_32u) + { + return cgltf_result_invalid_gltf; + } + + if (indices && indices->buffer_view && indices->buffer_view->buffer->data) + { + cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); + + if (index_bound >= first->count) + { + return cgltf_result_data_too_short; + } + } + } + } + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + if (data->nodes[i].weights && data->nodes[i].mesh) + { + if (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count) + { + return cgltf_result_invalid_gltf; + } + } + } + + return cgltf_result_success; +} + +cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size) +{ + cgltf_size json_size = extras->end_offset - extras->start_offset; + + if (!dest) + { + if (dest_size) + { + *dest_size = json_size + 1; + return cgltf_result_success; + } + return cgltf_result_invalid_options; + } + + if (*dest_size + 1 < json_size) + { + strncpy(dest, data->json + extras->start_offset, *dest_size - 1); + dest[*dest_size - 1] = 0; + } + else + { + strncpy(dest, data->json + extras->start_offset, json_size); + dest[json_size] = 0; + } + + return cgltf_result_success; +} + +void cgltf_free(cgltf_data* data) +{ + if (!data) + { + return; + } + + data->memory_free(data->memory_user_data, data->asset.copyright); + data->memory_free(data->memory_user_data, data->asset.generator); + data->memory_free(data->memory_user_data, data->asset.version); + data->memory_free(data->memory_user_data, data->asset.min_version); + + 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) + { + if (data->buffers[i].data != data->bin) + { + data->memory_free(data->memory_user_data, data->buffers[i].data); + } + + 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) + { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes[k].name); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].attributes); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets[k].attributes[m].name); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets[k].attributes); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives[j].targets); + } + + data->memory_free(data->memory_user_data, data->meshes[i].primitives); + data->memory_free(data->memory_user_data, data->meshes[i].weights); + + for (cgltf_size j = 0; j < data->meshes[i].target_names_count; ++j) + { + data->memory_free(data->memory_user_data, data->meshes[i].target_names[j]); + } + + data->memory_free(data->memory_user_data, data->meshes[i].target_names); + } + + 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].name); + 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); + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + data->memory_free(data->memory_user_data, data->textures[i].name); + } + + data->memory_free(data->memory_user_data, data->textures); + + data->memory_free(data->memory_user_data, data->samplers); + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + data->memory_free(data->memory_user_data, data->skins[i].name); + data->memory_free(data->memory_user_data, data->skins[i].joints); + } + + data->memory_free(data->memory_user_data, data->skins); + + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + data->memory_free(data->memory_user_data, data->cameras[i].name); + } + + data->memory_free(data->memory_user_data, data->cameras); + + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + data->memory_free(data->memory_user_data, data->lights[i].name); + } + + data->memory_free(data->memory_user_data, data->lights); + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + data->memory_free(data->memory_user_data, data->nodes[i].name); + data->memory_free(data->memory_user_data, data->nodes[i].children); + data->memory_free(data->memory_user_data, data->nodes[i].weights); + } + + data->memory_free(data->memory_user_data, data->nodes); + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + data->memory_free(data->memory_user_data, data->scenes[i].name); + data->memory_free(data->memory_user_data, data->scenes[i].nodes); + } + + data->memory_free(data->memory_user_data, data->scenes); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + data->memory_free(data->memory_user_data, data->animations[i].name); + data->memory_free(data->memory_user_data, data->animations[i].samplers); + data->memory_free(data->memory_user_data, data->animations[i].channels); + } + + data->memory_free(data->memory_user_data, data->animations); + + for (cgltf_size i = 0; i < data->extensions_used_count; ++i) + { + data->memory_free(data->memory_user_data, data->extensions_used[i]); + } + + data->memory_free(data->memory_user_data, data->extensions_used); + + for (cgltf_size i = 0; i < data->extensions_required_count; ++i) + { + data->memory_free(data->memory_user_data, data->extensions_required[i]); + } + + data->memory_free(data->memory_user_data, data->extensions_required); + + data->memory_free(data->memory_user_data, data->file_data); + + data->memory_free(data->memory_user_data, data); +} + +void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + + if (node->has_matrix) + { + memcpy(lm, node->matrix, sizeof(float) * 16); + } + else + { + float tx = node->translation[0]; + float ty = node->translation[1]; + float tz = node->translation[2]; + + float qx = node->rotation[0]; + float qy = node->rotation[1]; + float qz = node->rotation[2]; + float qw = node->rotation[3]; + + float sx = node->scale[0]; + float sy = node->scale[1]; + float sz = node->scale[2]; + + lm[0] = (1 - 2 * qy*qy - 2 * qz*qz) * sx; + lm[1] = (2 * qx*qy + 2 * qz*qw) * sx; + lm[2] = (2 * qx*qz - 2 * qy*qw) * sx; + lm[3] = 0.f; + + lm[4] = (2 * qx*qy - 2 * qz*qw) * sy; + lm[5] = (1 - 2 * qx*qx - 2 * qz*qz) * sy; + lm[6] = (2 * qy*qz + 2 * qx*qw) * sy; + lm[7] = 0.f; + + lm[8] = (2 * qx*qz + 2 * qy*qw) * sz; + lm[9] = (2 * qy*qz - 2 * qx*qw) * sz; + lm[10] = (1 - 2 * qx*qx - 2 * qy*qy) * sz; + lm[11] = 0.f; + + lm[12] = tx; + lm[13] = ty; + lm[14] = tz; + lm[15] = 1.f; + } +} + +void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix) +{ + cgltf_float* lm = out_matrix; + cgltf_node_transform_local(node, lm); + + const cgltf_node* parent = node->parent; + + while (parent) + { + float pm[16]; + cgltf_node_transform_local(parent, pm); + + for (int i = 0; i < 4; ++i) + { + float l0 = lm[i * 4 + 0]; + float l1 = lm[i * 4 + 1]; + float l2 = lm[i * 4 + 2]; + + float r0 = l0 * pm[0] + l1 * pm[4] + l2 * pm[8]; + float r1 = l0 * pm[1] + l1 * pm[5] + l2 * pm[9]; + float r2 = l0 * pm[2] + l1 * pm[6] + l2 * pm[10]; + + lm[i * 4 + 0] = r0; + lm[i * 4 + 1] = r1; + lm[i * 4 + 2] = r2; + } + + lm[12] += pm[12]; + lm[13] += pm[13]; + lm[14] += pm[14]; + + parent = parent->parent; + } +} + +static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) +{ + switch (component_type) + { + case cgltf_component_type_r_16: + return *((const int16_t*) in); + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_size)*((const float*) in); + case cgltf_component_type_r_8: + return *((const int8_t*) in); + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + default: + return 0; + } +} + +static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, cgltf_bool normalized) +{ + if (component_type == cgltf_component_type_r_32f) + { + return *((const float*) in); + } + + if (normalized) + { + switch (component_type) + { + // note: glTF spec doesn't currently define normalized conversions for 32-bit integers + case cgltf_component_type_r_16: + return *((const int16_t*) in) / (cgltf_float)32767; + case cgltf_component_type_r_16u: + return *((const uint16_t*) in) / (cgltf_float)65535; + case cgltf_component_type_r_8: + return *((const int8_t*) in) / (cgltf_float)127; + case cgltf_component_type_r_8u: + return *((const uint8_t*) in) / (cgltf_float)255; + default: + return 0; + } + } + + return (cgltf_float)cgltf_component_read_index(in, component_type); +} + +static cgltf_size cgltf_num_components(cgltf_type type); +static cgltf_size cgltf_component_size(cgltf_component_type component_type); + +static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, cgltf_size element_size) +{ + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) { + return 0; + } + + // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. + + cgltf_size component_size = cgltf_component_size(component_type); + + if (type == cgltf_type_mat2 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 5, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 1) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 1, component_type, normalized); + out[2] = cgltf_component_read_float(element + 2, component_type, normalized); + out[3] = cgltf_component_read_float(element + 4, component_type, normalized); + out[4] = cgltf_component_read_float(element + 5, component_type, normalized); + out[5] = cgltf_component_read_float(element + 6, component_type, normalized); + out[6] = cgltf_component_read_float(element + 8, component_type, normalized); + out[7] = cgltf_component_read_float(element + 9, component_type, normalized); + out[8] = cgltf_component_read_float(element + 10, component_type, normalized); + return 1; + } + + if (type == cgltf_type_mat3 && component_size == 2) + { + out[0] = cgltf_component_read_float(element, component_type, normalized); + out[1] = cgltf_component_read_float(element + 2, component_type, normalized); + out[2] = cgltf_component_read_float(element + 4, component_type, normalized); + out[3] = cgltf_component_read_float(element + 8, component_type, normalized); + out[4] = cgltf_component_read_float(element + 10, component_type, normalized); + out[5] = cgltf_component_read_float(element + 12, component_type, normalized); + out[6] = cgltf_component_read_float(element + 16, component_type, normalized); + out[7] = cgltf_component_read_float(element + 18, component_type, normalized); + out[8] = cgltf_component_read_float(element + 20, component_type, normalized); + return 1; + } + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); + } + return 1; +} + + +cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) +{ + if (accessor->is_sparse || accessor->buffer_view == NULL) + { + return 0; + } + + cgltf_size offset = accessor->offset + accessor->buffer_view->offset; + const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; + element += offset + accessor->stride * index; + return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); +} + +cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) +{ + if (accessor->buffer_view) + { + cgltf_size offset = accessor->offset + accessor->buffer_view->offset; + const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; + element += offset + accessor->stride * index; + return cgltf_component_read_index(element, accessor->component_type); + } + + return 0; +} + +#define CGLTF_ERROR_JSON -1 +#define CGLTF_ERROR_NOMEM -2 + +#define CGLTF_CHECK_TOKTYPE(tok_, type_) if ((tok_).type != (type_)) { return CGLTF_ERROR_JSON; } +#define CGLTF_CHECK_KEY(tok_) if ((tok_).type != JSMN_STRING || (tok_).size == 0) { return CGLTF_ERROR_JSON; } /* checking size for 0 verifies that a value follows the key */ + +#define CGLTF_PTRINDEX(type, idx) (type*)(cgltf_size)(idx + 1) +#define CGLTF_PTRFIXUP(var, data, size) if (var) { if ((cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; } +#define CGLTF_PTRFIXUP_REQ(var, data, size) if (!var || (cgltf_size)var > size) { return CGLTF_ERROR_JSON; } var = &data[(cgltf_size)var-1]; + +static int cgltf_json_strcmp(jsmntok_t const* tok, const uint8_t* json_chunk, const char* str) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_STRING); + size_t const str_len = strlen(str); + size_t const name_length = tok->end - tok->start; + return (str_len == name_length) ? strncmp((const char*)json_chunk + tok->start, str, str_len) : 128; +} + +static int cgltf_json_to_int(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : sizeof(tmp) - 1; + 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) +{ + CGLTF_CHECK_TOKTYPE(*tok, JSMN_PRIMITIVE); + char tmp[128]; + int size = (cgltf_size)(tok->end - tok->start) < sizeof(tmp) ? tok->end - tok->start : sizeof(tmp) - 1; + strncpy(tmp, (const char*)json_chunk + tok->start, size); + tmp[size] = 0; + return (cgltf_float)atof(tmp); +} + +static cgltf_bool cgltf_json_to_bool(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int size = tok->end - tok->start; + return size == 4 && memcmp(json_chunk + tok->start, "true", 4) == 0; +} + +static int cgltf_skip_json(jsmntok_t const* tokens, int i) +{ + int end = i + 1; + + while (i < end) + { + switch (tokens[i].type) + { + case JSMN_OBJECT: + end += tokens[i].size * 2; + break; + + case JSMN_ARRAY: + end += tokens[i].size; + break; + + case JSMN_PRIMITIVE: + case JSMN_STRING: + break; + + default: + return -1; + } + + i++; + } + + return i; +} + +static void cgltf_fill_float_array(float* out_array, int size, float value) +{ + for (int j = 0; j < size; ++j) + { + out_array[j] = value; + } +} + +static int cgltf_parse_json_float_array(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, float* out_array, int size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (tokens[i].size != size) + { + return CGLTF_ERROR_JSON; + } + ++i; + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_array[j] = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + return i; +} + +static int cgltf_parse_json_string(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char** out_string) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + if (*out_string) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].end - tokens[i].start; + char* result = (char*)options->memory_alloc(options->memory_user_data, size + 1); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(result, (const char*)json_chunk + tokens[i].start, size); + result[size] = 0; + *out_string = result; + return i + 1; +} + +static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, size_t element_size, void** out_array, cgltf_size* out_size) +{ + (void)json_chunk; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + if (*out_array) + { + return CGLTF_ERROR_JSON; + } + int size = tokens[i].size; + void* result = cgltf_calloc(options, element_size, size); + if (!result) + { + return CGLTF_ERROR_NOMEM; + } + *out_array = result; + *out_size = size; + return i + 1; +} + +static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < *out_size; ++j) + { + i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); + if (i < 0) + { + return i; + } + } + return i; +} + +static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) +{ + const char* us = strchr(name, '_'); + size_t len = us ? us - name : strlen(name); + + if (len == 8 && strncmp(name, "POSITION", 8) == 0) + { + *out_type = cgltf_attribute_type_position; + } + else if (len == 6 && strncmp(name, "NORMAL", 6) == 0) + { + *out_type = cgltf_attribute_type_normal; + } + else if (len == 7 && strncmp(name, "TANGENT", 7) == 0) + { + *out_type = cgltf_attribute_type_tangent; + } + else if (len == 8 && strncmp(name, "TEXCOORD", 8) == 0) + { + *out_type = cgltf_attribute_type_texcoord; + } + else if (len == 5 && strncmp(name, "COLOR", 5) == 0) + { + *out_type = cgltf_attribute_type_color; + } + else if (len == 6 && strncmp(name, "JOINTS", 6) == 0) + { + *out_type = cgltf_attribute_type_joints; + } + else if (len == 7 && strncmp(name, "WEIGHTS", 7) == 0) + { + *out_type = cgltf_attribute_type_weights; + } + else + { + *out_type = cgltf_attribute_type_invalid; + } + + if (us && *out_type != cgltf_attribute_type_invalid) + { + *out_index = atoi(us + 1); + } +} + +static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_attribute** out_attributes, cgltf_size* out_attributes_count) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + if (*out_attributes) + { + return CGLTF_ERROR_JSON; + } + + *out_attributes_count = tokens[i].size; + *out_attributes = (cgltf_attribute*)cgltf_calloc(options, sizeof(cgltf_attribute), *out_attributes_count); + ++i; + + if (!*out_attributes) + { + return CGLTF_ERROR_NOMEM; + } + + for (cgltf_size j = 0; j < *out_attributes_count; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + i = cgltf_parse_json_string(options, tokens, i, json_chunk, &(*out_attributes)[j].name); + if (i < 0) + { + return CGLTF_ERROR_JSON; + } + + cgltf_parse_attribute_type((*out_attributes)[j].name, &(*out_attributes)[j].type, &(*out_attributes)[j].index); + + (*out_attributes)[j].data = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + return i; +} + +static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras) +{ + (void)json_chunk; + out_extras->start_offset = tokens[i].start; + out_extras->end_offset = tokens[i].end; + i = cgltf_skip_json(tokens, i); + 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); + + out_prim->type = cgltf_primitive_type_triangles; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + 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 = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "material") == 0) + { + ++i; + out_prim->material = CGLTF_PTRINDEX(cgltf_material, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_prim->attributes, &out_prim->attributes_count); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "targets") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_morph_target), (void**)&out_prim->targets, &out_prim->targets_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_prim->targets_count; ++k) + { + i = cgltf_parse_json_attribute_list(options, tokens, i, json_chunk, &out_prim->targets[k].attributes, &out_prim->targets[k].attributes_count); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_mesh* out_mesh) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_mesh->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "primitives") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_primitive), (void**)&out_mesh->primitives, &out_mesh->primitives_count); + if (i < 0) + { + return i; + } + + for (cgltf_size prim_index = 0; prim_index < out_mesh->primitives_count; ++prim_index) + { + i = cgltf_parse_json_primitive(options, tokens, i, json_chunk, &out_mesh->primitives[prim_index]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_mesh->weights, &out_mesh->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_mesh->weights, (int)out_mesh->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + ++i; + + out_mesh->extras.start_offset = tokens[i].start; + out_mesh->extras.end_offset = tokens[i].end; + + if (tokens[i].type == JSMN_OBJECT) + { + int extras_size = tokens[i].size; + ++i; + + for (int k = 0; k < extras_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i); + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_mesh), (void**)&out_data->meshes, &out_data->meshes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->meshes_count; ++j) + { + i = cgltf_parse_json_mesh(options, tokens, i, json_chunk, &out_data->meshes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, const uint8_t* json_chunk) +{ + int type = cgltf_json_to_int(tok, json_chunk); + + switch (type) + { + case 5120: + return cgltf_component_type_r_8; + case 5121: + return cgltf_component_type_r_8u; + case 5122: + return cgltf_component_type_r_16; + case 5123: + return cgltf_component_type_r_16u; + case 5125: + return cgltf_component_type_r_32u; + case 5126: + return cgltf_component_type_r_32f; + default: + return cgltf_component_type_invalid; + } +} + +static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_sparse->count = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "indices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int indices_size = tokens[i].size; + ++i; + + for (int k = 0; k < indices_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->indices_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->indices_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_sparse->indices_component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "values") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int values_size = tokens[i].size; + ++i; + + for (int k = 0; k < values_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_sparse->values_buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_sparse->values_byte_offset = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + 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_accessor* out_accessor) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_accessor->offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "componentType") == 0) + { + ++i; + out_accessor->component_type = cgltf_json_to_component_type(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "normalized") == 0) + { + ++i; + out_accessor->normalized = cgltf_json_to_bool(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_accessor->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_accessor->type = cgltf_type_scalar; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC2") == 0) + { + out_accessor->type = cgltf_type_vec2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC3") == 0) + { + out_accessor->type = cgltf_type_vec3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "VEC4") == 0) + { + out_accessor->type = cgltf_type_vec4; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT2") == 0) + { + out_accessor->type = cgltf_type_mat2; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT3") == 0) + { + out_accessor->type = cgltf_type_mat3; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "MAT4") == 0) + { + out_accessor->type = cgltf_type_mat4; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "min") == 0) + { + ++i; + out_accessor->has_min = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int min_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->min, min_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "max") == 0) + { + ++i; + out_accessor->has_max = 1; + // note: we can't parse the precise number of elements since type may not have been computed yet + int max_size = tokens[i].size > 16 ? 16 : tokens[i].size; + i = cgltf_parse_json_float_array(tokens, i, json_chunk, out_accessor->max, max_size); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) + { + out_accessor->is_sparse = 1; + i = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_transform* out_texture_transform) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "offset") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->offset, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "rotation") == 0) + { + ++i; + out_texture_transform->rotation = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_texture_transform->scale, 2); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_transform->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_texture_view->scale = 1.0f; + cgltf_fill_float_array(out_texture_view->transform.scale, 2, 1.0f); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "index") == 0) + { + ++i; + out_texture_view->texture = CGLTF_PTRINDEX(cgltf_texture, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "texCoord") == 0) + { + ++i; + out_texture_view->texcoord = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scale") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "strength") == 0) + { + ++i; + out_texture_view->scale = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture_view->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_texture_transform") == 0) + { + out_texture_view->has_transform = 1; + i = cgltf_parse_json_texture_transform(tokens, i + 1, json_chunk, &out_texture_view->transform); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_metallic_roughness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "metallicFactor") == 0) + { + ++i; + out_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_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_float_array(tokens, i + 1, json_chunk, out_pbr->base_color_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_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_pbr->metallic_roughness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_pbr->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_pbr_specular_glossiness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->diffuse_factor, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_pbr->specular_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "glossinessFactor") == 0) + { + ++i; + out_pbr->glossiness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_image* out_image) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->uri); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + { + ++i; + out_image->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "mimeType") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->mime_type); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_image->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_sampler->wrap_s = 10497; + out_sampler->wrap_t = 10497; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) + { + ++i; + out_sampler->mag_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "minFilter") == 0) + { + ++i; + out_sampler->min_filter + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapS") == 0) + { + ++i; + out_sampler->wrap_s + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "wrapT") == 0) + { + ++i; + out_sampler->wrap_t + = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + + +static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_texture->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "sampler") == 0) + { + ++i; + out_texture->sampler = CGLTF_PTRINDEX(cgltf_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "source") == 0) + { + ++i; + out_texture->image = CGLTF_PTRINDEX(cgltf_image, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material* out_material) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + cgltf_fill_float_array(out_material->pbr_metallic_roughness.base_color_factor, 4, 1.0f); + out_material->pbr_metallic_roughness.metallic_factor = 1.0f; + out_material->pbr_metallic_roughness.roughness_factor = 1.0f; + + cgltf_fill_float_array(out_material->pbr_specular_glossiness.diffuse_factor, 4, 1.0f); + cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); + out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; + + out_material->alpha_cutoff = 0.5f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_material->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) + { + out_material->has_pbr_metallic_roughness = 1; + i = cgltf_parse_json_pbr_metallic_roughness(tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_material->emissive_factor, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->normal_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->occlusion_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) + { + i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + &out_material->emissive_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "OPAQUE") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_opaque; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "MASK") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_mask; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "BLEND") == 0) + { + out_material->alpha_mode = cgltf_alpha_mode_blend; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaCutoff") == 0) + { + ++i; + out_material->alpha_cutoff = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "doubleSided") == 0) + { + ++i; + out_material->double_sided = + cgltf_json_to_bool(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_material->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) + { + out_material->has_pbr_specular_glossiness = 1; + i = cgltf_parse_json_pbr_specular_glossiness(tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) + { + out_material->unlit = 1; + i = cgltf_skip_json(tokens, i+1); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_accessor), (void**)&out_data->accessors, &out_data->accessors_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->accessors_count; ++j) + { + i = cgltf_parse_json_accessor(tokens, i, json_chunk, &out_data->accessors[j]); + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material), (void**)&out_data->materials, &out_data->materials_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->materials_count; ++j) + { + i = cgltf_parse_json_material(options, tokens, i, json_chunk, &out_data->materials[j]); + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_image), (void**)&out_data->images, &out_data->images_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->images_count; ++j) + { + i = cgltf_parse_json_image(options, tokens, i, json_chunk, &out_data->images[j]); + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_texture), (void**)&out_data->textures, &out_data->textures_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->textures_count; ++j) + { + i = cgltf_parse_json_texture(options, tokens, i, json_chunk, &out_data->textures[j]); + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_sampler), (void**)&out_data->samplers, &out_data->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->samplers_count; ++j) + { + i = cgltf_parse_json_sampler(options, tokens, i, json_chunk, &out_data->samplers[j]); + 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_buffer_view* out_buffer_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_buffer_view->offset = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer_view->size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_buffer_view->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_buffer_view->type = (cgltf_buffer_view_type)type; + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer_view), (void**)&out_data->buffer_views, &out_data->buffer_views_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) + { + i = cgltf_parse_json_buffer_view(tokens, i, json_chunk, &out_data->buffer_views[j]); + 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_buffer* out_buffer) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_buffer->size = + cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "uri") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->uri); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + 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) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_buffer), (void**)&out_data->buffers, &out_data->buffers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->buffers_count; ++j) + { + i = cgltf_parse_json_buffer(options, tokens, i, json_chunk, &out_data->buffers[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_skin* out_skin) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_skin->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "joints") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_skin->joints, &out_skin->joints_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_skin->joints_count; ++k) + { + out_skin->joints[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skeleton") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->skeleton = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "inverseBindMatrices") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_skin->inverse_bind_matrices = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_skins(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_skin), (void**)&out_data->skins, &out_data->skins_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->skins_count; ++j) + { + i = cgltf_parse_json_skin(options, tokens, i, json_chunk, &out_data->skins[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_camera* out_camera) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_camera->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "type") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "perspective") == 0) + { + out_camera->type = cgltf_camera_type_perspective; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "orthographic") == 0) + { + out_camera->type = cgltf_camera_type_orthographic; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "perspective") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_perspective; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) + { + ++i; + out_camera->perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "yfov") == 0) + { + ++i; + out_camera->perspective.yfov = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->perspective.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->perspective.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "orthographic") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + out_camera->type = cgltf_camera_type_orthographic; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "xmag") == 0) + { + ++i; + out_camera->orthographic.xmag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "ymag") == 0) + { + ++i; + out_camera->orthographic.ymag = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) + { + ++i; + out_camera->orthographic.zfar = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "znear") == 0) + { + ++i; + out_camera->orthographic.znear = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->orthographic.extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_cameras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_camera), (void**)&out_data->cameras, &out_data->cameras_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->cameras_count; ++j) + { + i = cgltf_parse_json_camera(options, tokens, i, json_chunk, &out_data->cameras[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_light(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_light* out_light) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_light->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "color") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_light->color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "intensity") == 0) + { + ++i; + out_light->intensity = cgltf_json_to_float(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, "directional") == 0) + { + out_light->type = cgltf_light_type_directional; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "point") == 0) + { + out_light->type = cgltf_light_type_point; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "spot") == 0) + { + out_light->type = cgltf_light_type_spot; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "range") == 0) + { + ++i; + out_light->range = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "spot") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int k = 0; k < data_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "innerConeAngle") == 0) + { + ++i; + out_light->spot_inner_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "outerConeAngle") == 0) + { + ++i; + out_light->spot_outer_cone_angle = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_lights(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_light), (void**)&out_data->lights, &out_data->lights_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->lights_count; ++j) + { + i = cgltf_parse_json_light(options, tokens, i, json_chunk, &out_data->lights[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_node* out_node) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + out_node->rotation[3] = 1.0f; + out_node->scale[0] = 1.0f; + out_node->scale[1] = 1.0f; + out_node->scale[2] = 1.0f; + out_node->matrix[0] = 1.0f; + out_node->matrix[5] = 1.0f; + out_node->matrix[10] = 1.0f; + out_node->matrix[15] = 1.0f; + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_node->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "children") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_node->children, &out_node->children_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_node->children_count; ++k) + { + out_node->children[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mesh") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->mesh = CGLTF_PTRINDEX(cgltf_mesh, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "skin") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->skin = CGLTF_PTRINDEX(cgltf_skin, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "camera") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->camera = CGLTF_PTRINDEX(cgltf_camera, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_node->has_translation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->translation, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_node->has_rotation = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->rotation, 4); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_node->has_scale = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->scale, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "matrix") == 0) + { + out_node->has_matrix = 1; + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_node->matrix, 16); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "weights") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_float), (void**)&out_node->weights, &out_node->weights_count); + if (i < 0) + { + return i; + } + + i = cgltf_parse_json_float_array(tokens, i - 1, json_chunk, out_node->weights, (int)out_node->weights_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_node->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "light") == 0) + { + ++i; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_PRIMITIVE); + out_node->light = CGLTF_PTRINDEX(cgltf_light, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_nodes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_node), (void**)&out_data->nodes, &out_data->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->nodes_count; ++j) + { + i = cgltf_parse_json_node(options, tokens, i, json_chunk, &out_data->nodes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_scene* out_scene) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_scene->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_node*), (void**)&out_scene->nodes, &out_scene->nodes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_scene->nodes_count; ++k) + { + out_scene->nodes[k] = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_scenes(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_scene), (void**)&out_data->scenes, &out_data->scenes_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->scenes_count; ++j) + { + i = cgltf_parse_json_scene(options, tokens, i, json_chunk, &out_data->scenes[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_sampler* out_sampler) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "input") == 0) + { + ++i; + out_sampler->input = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "output") == 0) + { + ++i; + out_sampler->output = CGLTF_PTRINDEX(cgltf_accessor, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "interpolation") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens + i, json_chunk, "LINEAR") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_linear; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "STEP") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_step; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "CUBICSPLINE") == 0) + { + out_sampler->interpolation = cgltf_interpolation_type_cubic_spline; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation_channel* out_channel) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "sampler") == 0) + { + ++i; + out_channel->sampler = CGLTF_PTRINDEX(cgltf_animation_sampler, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "target") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int target_size = tokens[i].size; + ++i; + + for (int k = 0; k < target_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "node") == 0) + { + ++i; + out_channel->target_node = CGLTF_PTRINDEX(cgltf_node, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "path") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "translation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_translation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "rotation") == 0) + { + out_channel->target_path = cgltf_animation_path_type_rotation; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "scale") == 0) + { + out_channel->target_path = cgltf_animation_path_type_scale; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "weights") == 0) + { + out_channel->target_path = cgltf_animation_path_type_weights; + } + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_animation* out_animation) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_animation->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_sampler), (void**)&out_animation->samplers, &out_animation->samplers_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->samplers_count; ++k) + { + i = cgltf_parse_json_animation_sampler(options, tokens, i, json_chunk, &out_animation->samplers[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "channels") == 0) + { + i = cgltf_parse_json_array(options, tokens, i + 1, json_chunk, sizeof(cgltf_animation_channel), (void**)&out_animation->channels, &out_animation->channels_count); + if (i < 0) + { + return i; + } + + for (cgltf_size k = 0; k < out_animation->channels_count; ++k) + { + i = cgltf_parse_json_animation_channel(options, tokens, i, json_chunk, &out_animation->channels[k]); + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_animation), (void**)&out_data->animations, &out_data->animations_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->animations_count; ++j) + { + i = cgltf_parse_json_animation(options, tokens, i, json_chunk, &out_data->animations[j]); + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "copyright") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->copyright); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "generator") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->generator); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "version") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->version); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "minVersion") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_asset->min_version); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static cgltf_size cgltf_num_components(cgltf_type type) { + switch (type) + { + case cgltf_type_vec2: + return 2; + case cgltf_type_vec3: + return 3; + case cgltf_type_vec4: + return 4; + case cgltf_type_mat2: + return 4; + case cgltf_type_mat3: + return 9; + case cgltf_type_mat4: + return 16; + case cgltf_type_invalid: + case cgltf_type_scalar: + default: + return 1; + } +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + default: + return 0; + } +} + +static cgltf_size cgltf_calc_size(cgltf_type type, cgltf_component_type component_type) +{ + cgltf_size component_size = cgltf_component_size(component_type); + if (type == cgltf_type_mat2 && component_size == 1) + { + return 8 * component_size; + } + else if (type == cgltf_type_mat3 && (component_size == 1 || component_size == 2)) + { + return 12 * component_size; + } + return component_size * cgltf_num_components(type); +} + +static int cgltf_fixup_pointers(cgltf_data* out_data); + +static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "asset") == 0) + { + i = cgltf_parse_json_asset(options, tokens, i + 1, json_chunk, &out_data->asset); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "meshes") == 0) + { + i = cgltf_parse_json_meshes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "accessors") == 0) + { + i = cgltf_parse_json_accessors(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferViews") == 0) + { + i = cgltf_parse_json_buffer_views(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "buffers") == 0) + { + i = cgltf_parse_json_buffers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "materials") == 0) + { + i = cgltf_parse_json_materials(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "images") == 0) + { + i = cgltf_parse_json_images(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "textures") == 0) + { + i = cgltf_parse_json_textures(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "samplers") == 0) + { + i = cgltf_parse_json_samplers(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "skins") == 0) + { + i = cgltf_parse_json_skins(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "cameras") == 0) + { + i = cgltf_parse_json_cameras(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "nodes") == 0) + { + i = cgltf_parse_json_nodes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scenes") == 0) + { + i = cgltf_parse_json_scenes(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "scene") == 0) + { + ++i; + out_data->scene = CGLTF_PTRINDEX(cgltf_scene, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "animations") == 0) + { + i = cgltf_parse_json_animations(options, tokens, i + 1, json_chunk, out_data); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_data->extras); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int extensions_size = tokens[i].size; + ++i; + + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_lights_punctual") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "lights") == 0) + { + i = cgltf_parse_json_lights(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsUsed") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_used, &out_data->extensions_used_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensionsRequired") == 0) + { + i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_data->extensions_required, &out_data->extensions_required_count); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +cgltf_result cgltf_parse_json(cgltf_options* options, const uint8_t* json_chunk, cgltf_size size, cgltf_data** out_data) +{ + jsmn_parser parser = { 0, 0, 0 }; + + if (options->json_token_count == 0) + { + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, NULL, 0); + + if (token_count <= 0) + { + return cgltf_result_invalid_json; + } + + options->json_token_count = token_count; + } + + jsmntok_t* tokens = (jsmntok_t*)options->memory_alloc(options->memory_user_data, sizeof(jsmntok_t) * (options->json_token_count + 1)); + + if (!tokens) + { + return cgltf_result_out_of_memory; + } + + jsmn_init(&parser); + + int token_count = jsmn_parse(&parser, (const char*)json_chunk, size, tokens, options->json_token_count); + + if (token_count <= 0) + { + options->memory_free(options->memory_user_data, tokens); + return cgltf_result_invalid_json; + } + + // this makes sure that we always have an UNDEFINED token at the end of the stream + // for invalid JSON inputs this makes sure we don't perform out of bound reads of token data + tokens[token_count].type = JSMN_UNDEFINED; + + cgltf_data* data = (cgltf_data*)options->memory_alloc(options->memory_user_data, sizeof(cgltf_data)); + + if (!data) + { + options->memory_free(options->memory_user_data, tokens); + return cgltf_result_out_of_memory; + } + + memset(data, 0, sizeof(cgltf_data)); + data->memory_free = options->memory_free; + data->memory_user_data = options->memory_user_data; + + int i = cgltf_parse_json_root(options, tokens, 0, json_chunk, data); + + options->memory_free(options->memory_user_data, tokens); + + if (i < 0) + { + cgltf_free(data); + return (i == CGLTF_ERROR_NOMEM) ? cgltf_result_out_of_memory : cgltf_result_invalid_gltf; + } + + if (cgltf_fixup_pointers(data) < 0) + { + cgltf_free(data); + return cgltf_result_invalid_gltf; + } + + data->json = (const char*)json_chunk; + data->json_size = size; + + *out_data = data; + + return cgltf_result_success; +} + +static int cgltf_fixup_pointers(cgltf_data* data) +{ + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) + { + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].indices, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP(data->meshes[i].primitives[j].material, data->materials, data->materials_count); + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].attributes[k].data, data->accessors, data->accessors_count); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) + { + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); + } + } + } + } + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + CGLTF_PTRFIXUP(data->accessors[i].buffer_view, data->buffer_views, data->buffer_views_count); + + if (data->accessors[i].is_sparse) + { + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.indices_buffer_view, data->buffer_views, data->buffer_views_count); + CGLTF_PTRFIXUP_REQ(data->accessors[i].sparse.values_buffer_view, data->buffer_views, data->buffer_views_count); + } + + if (data->accessors[i].buffer_view) + { + data->accessors[i].stride = data->accessors[i].buffer_view->stride; + } + + if (data->accessors[i].stride == 0) + { + data->accessors[i].stride = cgltf_calc_size(data->accessors[i].type, data->accessors[i].component_type); + } + } + + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + CGLTF_PTRFIXUP(data->textures[i].image, data->images, data->images_count); + CGLTF_PTRFIXUP(data->textures[i].sampler, data->samplers, data->samplers_count); + } + + for (cgltf_size i = 0; i < data->images_count; ++i) + { + CGLTF_PTRFIXUP(data->images[i].buffer_view, data->buffer_views, data->buffer_views_count); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + CGLTF_PTRFIXUP(data->materials[i].normal_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].emissive_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].occlusion_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.base_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); + } + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); + } + + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + for (cgltf_size j = 0; j < data->skins[i].joints_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->skins[i].joints[j], data->nodes, data->nodes_count); + } + + CGLTF_PTRFIXUP(data->skins[i].skeleton, data->nodes, data->nodes_count); + CGLTF_PTRFIXUP(data->skins[i].inverse_bind_matrices, data->accessors, data->accessors_count); + } + + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + for (cgltf_size j = 0; j < data->nodes[i].children_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->nodes[i].children[j], data->nodes, data->nodes_count); + + if (data->nodes[i].children[j]->parent) + { + return CGLTF_ERROR_JSON; + } + + data->nodes[i].children[j]->parent = &data->nodes[i]; + } + + CGLTF_PTRFIXUP(data->nodes[i].mesh, data->meshes, data->meshes_count); + CGLTF_PTRFIXUP(data->nodes[i].skin, data->skins, data->skins_count); + CGLTF_PTRFIXUP(data->nodes[i].camera, data->cameras, data->cameras_count); + CGLTF_PTRFIXUP(data->nodes[i].light, data->lights, data->lights_count); + } + + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->scenes[i].nodes[j], data->nodes, data->nodes_count); + + if (data->scenes[i].nodes[j]->parent) + { + return CGLTF_ERROR_JSON; + } + } + } + + CGLTF_PTRFIXUP(data->scene, data->scenes, data->scenes_count); + + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].input, data->accessors, data->accessors_count); + CGLTF_PTRFIXUP_REQ(data->animations[i].samplers[j].output, data->accessors, data->accessors_count); + } + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + CGLTF_PTRFIXUP_REQ(data->animations[i].channels[j].sampler, data->animations[i].samplers, data->animations[i].samplers_count); + CGLTF_PTRFIXUP(data->animations[i].channels[j].target_node, data->nodes, data->nodes_count); + } + } + + return 0; +} + +/* + * -- jsmn.c start -- + * Source: https://github.com/zserge/jsmn + * License: MIT + * + * Copyright (c) 2010 Serge A. Zaitsev + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * 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. + */ +static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, size_t 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. + */ +static void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} +/* + * -- jsmn.c end -- + */ + +#endif /* #ifdef CGLTF_IMPLEMENTATION */ + +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2018 Johannes Kuhlmann + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/fast_obj.h b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/fast_obj.h new file mode 100644 index 00000000000..2f7f4a48e7b --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/fast_obj.h @@ -0,0 +1,1397 @@ +/* + * + * MIT License + * + * Copyright (c) 2018 Richard Knight + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifndef FAST_OBJ_HDR +#define FAST_OBJ_HDR + + +typedef struct +{ + /* Texture name from .mtl file */ + char* name; + + /* Resolved path to texture */ + char* path; + +} fastObjTexture; + + +typedef struct +{ + /* Material name */ + char* name; + + /* Parameters */ + float Ka[3]; /* Ambient */ + float Kd[3]; /* Diffuse */ + float Ks[3]; /* Specular */ + float Ke[3]; /* Emission */ + float Kt[3]; /* Transmittance */ + float Ns; /* Shininess */ + float Ni; /* Index of refraction */ + float Tf[3]; /* Transmission filter */ + float d; /* Disolve (alpha) */ + int illum; /* Illumination model */ + + /* Texture maps */ + fastObjTexture map_Ka; + fastObjTexture map_Kd; + fastObjTexture map_Ks; + fastObjTexture map_Ke; + fastObjTexture map_Kt; + fastObjTexture map_Ns; + fastObjTexture map_Ni; + fastObjTexture map_d; + fastObjTexture map_bump; + + +} fastObjMaterial; + + +typedef struct +{ + unsigned int p; + unsigned int t; + unsigned int n; + +} fastObjIndex; + + +typedef struct +{ + /* Group name */ + char* name; + + /* Number of faces */ + unsigned int face_count; + + /* First face in fastObjMesh face_* arrays */ + unsigned int face_offset; + + /* First index in fastObjMesh indices array */ + unsigned int index_offset; + +} fastObjGroup; + + +typedef struct +{ + /* Vertex data */ + unsigned int position_count; + float* positions; + + unsigned int texcoord_count; + float* texcoords; + + unsigned int normal_count; + float* normals; + + /* Face data: one element for each face */ + unsigned int face_count; + unsigned int* face_vertices; + unsigned int* face_materials; + + /* Index data: one element for each face vertex */ + fastObjIndex* indices; + + /* Materials */ + unsigned int material_count; + fastObjMaterial* materials; + + /* Mesh groups */ + unsigned int group_count; + fastObjGroup* groups; + +} fastObjMesh; + + +fastObjMesh* fast_obj_read(const char* path); +void fast_obj_destroy(fastObjMesh* mesh); + +#endif + + +#ifdef FAST_OBJ_IMPLEMENTATION + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifndef FAST_OBJ_REALLOC +#define FAST_OBJ_REALLOC realloc +#endif + +#ifndef FAST_OBJ_FREE +#define FAST_OBJ_FREE free +#endif + +#ifdef _WIN32 +#define FAST_OBJ_SEPARATOR '\\' +#define FAST_OBJ_OTHER_SEP '/' +#else +#define FAST_OBJ_SEPARATOR '/' +#define FAST_OBJ_OTHER_SEP '\\' +#endif + + +/* Size of buffer to read into */ +#define BUFFER_SIZE 65536 + +/* Max supported power when parsing float */ +#define MAX_POWER 20 + +typedef struct +{ + /* Final mesh */ + fastObjMesh* mesh; + + /* Current group */ + fastObjGroup group; + + /* Current material index */ + unsigned int material; + + /* Current line in file */ + unsigned int line; + + /* Base path for materials/textures */ + char* base; + +} fastObjData; + + +static const +double POWER_10_POS[MAX_POWER] = +{ + 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9, + 1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19, +}; + +static const +double POWER_10_NEG[MAX_POWER] = +{ + 1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8, 1.0e-9, + 1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19, +}; + + +static +void* memory_realloc(void* ptr, size_t bytes) +{ + return FAST_OBJ_REALLOC(ptr, bytes); +} + + +static +void memory_dealloc(void* ptr) +{ + FAST_OBJ_FREE(ptr); +} + + +#define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0) +#define array_push(_arr, _val) (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0) +#define array_size(_arr) ((_arr) ? _array_size(_arr) : 0) +#define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0) +#define array_empty(_arr) (array_size(_arr) == 0) + +#define _array_header(_arr) ((unsigned int*)(_arr) - 2) +#define _array_size(_arr) (_array_header(_arr)[0]) +#define _array_capacity(_arr) (_array_header(_arr)[1]) +#define _array_ngrow(_arr, _n) ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr))) +#define _array_mgrow(_arr, _n) (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1) +#define _array_grow(_arr, _n) (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr)))) + + +static +void* array_realloc(void* ptr, unsigned int n, unsigned int b) +{ + unsigned int sz = array_size(ptr); + unsigned int nsz = sz + n; + unsigned int cap = array_capacity(ptr); + unsigned int ncap = 3 * cap / 2; + unsigned int* r; + + + if (ncap < nsz) + ncap = nsz; + ncap = (ncap + 15) & ~15u; + + r = (unsigned int*)(memory_realloc(ptr ? _array_header(ptr) : 0, b * ncap + 2 * sizeof(unsigned int))); + if (!r) + return 0; + + r[0] = sz; + r[1] = ncap; + + return (r + 2); +} + + +static +void* file_open(const char* path) +{ + return fopen(path, "rb"); +} + + +static +void file_close(void* file) +{ + FILE* f; + + f = (FILE*)(file); + fclose(f); +} + + +static +size_t file_read(void* file, void* dst, size_t bytes) +{ + FILE* f; + + f = (FILE*)(file); + return fread(dst, 1, bytes, f); +} + + +static +unsigned long file_size(void* file) +{ + FILE* f; + long p; + long n; + + f = (FILE*)(file); + + p = ftell(f); + fseek(f, 0, SEEK_END); + n = ftell(f); + fseek(f, p, SEEK_SET); + + if (n > 0) + return (unsigned long)(n); + else + return 0; +} + + +static +char* string_copy(const char* s, const char* e) +{ + size_t n; + char* p; + + n = (size_t)(e - s); + p = (char*)(memory_realloc(0, n + 1)); + if (p) + { + memcpy(p, s, n); + p[n] = '\0'; + } + + return p; +} + + +static +char* string_substr(const char* s, size_t a, size_t b) +{ + return string_copy(s + a, s + b); +} + + +static +char* string_concat(const char* a, const char* s, const char* e) +{ + size_t an; + size_t sn; + char* p; + + an = a ? strlen(a) : 0; + sn = (size_t)(e - s); + p = (char*)(memory_realloc(0, an + sn + 1)); + if (p) + { + if (a) + memcpy(p, a, an); + memcpy(p + an, s, sn); + p[an + sn] = '\0'; + } + + return p; +} + + +static +int string_equal(const char* a, const char* s, const char* e) +{ + while (*a++ == *s++ && s != e) + ; + + return (*a == '\0' && s == e); +} + + +static +int string_find_last(const char* s, char c, size_t* p) +{ + const char* e; + + e = s + strlen(s); + while (e > s) + { + e--; + + if (*e == c) + { + *p = (size_t)(e - s); + return 1; + } + } + + return 0; +} + + +static +void string_fix_separators(char* s) +{ + while (*s) + { + if (*s == FAST_OBJ_OTHER_SEP) + *s = FAST_OBJ_SEPARATOR; + s++; + } +} + + +static +int is_whitespace(char c) +{ + return (c == ' ' || c == '\t' || c == '\r'); +} + + +static +int is_newline(char c) +{ + return (c == '\n'); +} + + +static +int is_digit(char c) +{ + return (c >= '0' && c <= '9'); +} + + +static +int is_exponent(char c) +{ + return (c == 'e' || c == 'E'); +} + + +static +const char* skip_whitespace(const char* ptr) +{ + while (is_whitespace(*ptr)) + ptr++; + + return ptr; +} + + +static +const char* skip_line(const char* ptr) +{ + while (!is_newline(*ptr++)) + ; + + return ptr; +} + + +static +fastObjGroup group_default(void) +{ + fastObjGroup group; + + group.name = 0; + group.face_count = 0; + group.face_offset = 0; + group.index_offset = 0; + + return group; +} + + +static +void group_clean(fastObjGroup* group) +{ + memory_dealloc(group->name); +} + + +static +void flush_output(fastObjData* data) +{ + /* Add group if not empty */ + if (data->group.face_count > 0) + array_push(data->mesh->groups, data->group); + else + group_clean(&data->group); + + /* Reset for more data */ + data->group = group_default(); + data->group.face_offset = array_size(data->mesh->face_vertices); + data->group.index_offset = array_size(data->mesh->indices); +} + + +static +const char* parse_int(const char* ptr, int* val) +{ + int sign; + int num; + + + if (*ptr == '-') + { + sign = -1; + ptr++; + } + else + { + sign = +1; + } + + num = 0; + while (is_digit(*ptr)) + num = 10 * num + (*ptr++ - '0'); + + *val = sign * num; + + return ptr; +} + + +static +const char* parse_float(const char* ptr, float* val) +{ + double sign; + double num; + double fra; + double div; + int eval; + const double* powers; + + + ptr = skip_whitespace(ptr); + + switch (*ptr) + { + case '+': + sign = 1.0; + ptr++; + break; + + case '-': + sign = -1.0; + ptr++; + break; + + default: + sign = 1.0; + break; + } + + + num = 0.0; + while (is_digit(*ptr)) + num = 10.0 * num + (double)(*ptr++ - '0'); + + if (*ptr == '.') + ptr++; + + fra = 0.0; + div = 1.0; + + while (is_digit(*ptr)) + { + fra = 10.0 * fra + (double)(*ptr++ - '0'); + div *= 10.0; + } + + num += fra / div; + + if (is_exponent(*ptr)) + { + ptr++; + + switch (*ptr) + { + case '+': + powers = POWER_10_POS; + ptr++; + break; + + case '-': + powers = POWER_10_NEG; + ptr++; + break; + + default: + powers = POWER_10_POS; + break; + } + + eval = 0; + while (is_digit(*ptr)) + eval = 10 * eval + (*ptr++ - '0'); + + num *= (eval >= MAX_POWER) ? 0.0 : powers[eval]; + } + + *val = (float)(sign * num); + + return ptr; +} + + +static +const char* parse_vertex(fastObjData* data, const char* ptr) +{ + unsigned int ii; + float v; + + + for (ii = 0; ii < 3; ii++) + { + ptr = parse_float(ptr, &v); + array_push(data->mesh->positions, v); + } + + return ptr; +} + + +static +const char* parse_texcoord(fastObjData* data, const char* ptr) +{ + unsigned int ii; + float v; + + + for (ii = 0; ii < 2; ii++) + { + ptr = parse_float(ptr, &v); + array_push(data->mesh->texcoords, v); + } + + return ptr; +} + + +static +const char* parse_normal(fastObjData* data, const char* ptr) +{ + unsigned int ii; + float v; + + + for (ii = 0; ii < 3; ii++) + { + ptr = parse_float(ptr, &v); + array_push(data->mesh->normals, v); + } + + return ptr; +} + + +static +const char* parse_face(fastObjData* data, const char* ptr) +{ + unsigned int count; + fastObjIndex vn; + int v; + int t; + int n; + + + ptr = skip_whitespace(ptr); + + count = 0; + while (!is_newline(*ptr)) + { + v = 0; + t = 0; + n = 0; + + ptr = parse_int(ptr, &v); + if (*ptr == '/') + { + ptr++; + if (*ptr != '/') + ptr = parse_int(ptr, &t); + + if (*ptr == '/') + { + ptr++; + ptr = parse_int(ptr, &n); + } + } + + if (v < 0) + vn.p = (array_size(data->mesh->positions) / 3) - (unsigned int)(-v); + else + vn.p = (unsigned int)(v); + + if (t < 0) + vn.t = (array_size(data->mesh->texcoords) / 2) - (unsigned int)(-t); + else if (t > 0) + vn.t = (unsigned int)(t); + else + vn.t = 0; + + if (n < 0) + vn.n = (array_size(data->mesh->normals) / 3) - (unsigned int)(-n); + else if (n > 0) + vn.n = (unsigned int)(n); + else + vn.n = 0; + + array_push(data->mesh->indices, vn); + count++; + + ptr = skip_whitespace(ptr); + } + + array_push(data->mesh->face_vertices, count); + array_push(data->mesh->face_materials, data->material); + + data->group.face_count++; + + return ptr; +} + + +static +const char* parse_group(fastObjData* data, const char* ptr) +{ + const char* s; + const char* e; + + + ptr = skip_whitespace(ptr); + + s = ptr; + while (!is_whitespace(*ptr) && !is_newline(*ptr)) + ptr++; + + e = ptr; + + flush_output(data); + data->group.name = string_copy(s, e); + + return ptr; +} + + +static +fastObjTexture map_default(void) +{ + fastObjTexture map; + + map.name = 0; + map.path = 0; + + return map; +} + + +static +fastObjMaterial mtl_default(void) +{ + fastObjMaterial mtl; + + mtl.name = 0; + + mtl.Ka[0] = 0.0; + mtl.Ka[1] = 0.0; + mtl.Ka[2] = 0.0; + mtl.Kd[0] = 1.0; + mtl.Kd[1] = 1.0; + mtl.Kd[2] = 1.0; + mtl.Ks[0] = 0.0; + mtl.Ks[1] = 0.0; + mtl.Ks[2] = 0.0; + mtl.Ke[0] = 0.0; + mtl.Ke[1] = 0.0; + mtl.Ke[2] = 0.0; + mtl.Kt[0] = 0.0; + mtl.Kt[1] = 0.0; + mtl.Kt[2] = 0.0; + mtl.Ns = 1.0; + mtl.Ni = 1.0; + mtl.Tf[0] = 1.0; + mtl.Tf[1] = 1.0; + mtl.Tf[2] = 1.0; + mtl.d = 1.0; + mtl.illum = 1; + + mtl.map_Ka = map_default(); + mtl.map_Kd = map_default(); + mtl.map_Ks = map_default(); + mtl.map_Ke = map_default(); + mtl.map_Kt = map_default(); + mtl.map_Ns = map_default(); + mtl.map_Ni = map_default(); + mtl.map_d = map_default(); + mtl.map_bump = map_default(); + + return mtl; +} + + +static +const char* parse_usemtl(fastObjData* data, const char* ptr) +{ + const char* s; + const char* e; + unsigned int idx; + fastObjMaterial* mtl; + + + ptr = skip_whitespace(ptr); + + /* Parse the material name */ + s = ptr; + while (!is_whitespace(*ptr) && !is_newline(*ptr)) + ptr++; + + e = ptr; + + + /* If there are no materials yet, add a dummy invalid material at index 0 */ + if (array_empty(data->mesh->materials)) + array_push(data->mesh->materials, mtl_default()); + + + /* Find an existing material with the same name */ + idx = 0; + while (idx < array_size(data->mesh->materials)) + { + mtl = &data->mesh->materials[idx]; + if (mtl->name && string_equal(mtl->name, s, e)) + break; + + idx++; + } + + if (idx == array_size(data->mesh->materials)) + idx = 0; + + data->material = idx; + + return ptr; +} + + +static +void map_clean(fastObjTexture* map) +{ + memory_dealloc(map->name); + memory_dealloc(map->path); +} + + +static +void mtl_clean(fastObjMaterial* mtl) +{ + map_clean(&mtl->map_Ka); + map_clean(&mtl->map_Kd); + map_clean(&mtl->map_Ks); + map_clean(&mtl->map_Ke); + map_clean(&mtl->map_Kt); + map_clean(&mtl->map_Ns); + map_clean(&mtl->map_Ni); + map_clean(&mtl->map_d); + map_clean(&mtl->map_bump); + + memory_dealloc(mtl->name); +} + + +static +const char* read_mtl_int(const char* p, int* v) +{ + return parse_int(p, v); +} + + +static +const char* read_mtl_single(const char* p, float* v) +{ + return parse_float(p, v); +} + + +static +const char* read_mtl_triple(const char* p, float v[3]) +{ + p = read_mtl_single(p, &v[0]); + p = read_mtl_single(p, &v[1]); + p = read_mtl_single(p, &v[2]); + + return p; +} + + +static +const char* read_map(fastObjData* data, const char* ptr, fastObjTexture* map) +{ + const char* s; + const char* e; + char* name; + char* path; + + ptr = skip_whitespace(ptr); + + /* Don't support options at present */ + if (*ptr == '-') + return ptr; + + + /* Read name */ + s = ptr; + while (!is_whitespace(*ptr) && !is_newline(*ptr)) + ptr++; + + e = ptr; + + name = string_copy(s, e); + + path = string_concat(data->base, s, e); + string_fix_separators(path); + + map->name = name; + map->path = path; + + return e; +} + + +static +int read_mtllib(fastObjData* data, void* file) +{ + unsigned long n; + const char* s; + char* contents; + size_t l; + const char* p; + const char* e; + int found_d; + fastObjMaterial mtl; + + + /* Read entire file */ + n = file_size(file); + + contents = (char*)(memory_realloc(0, n + 1)); + if (!contents) + return 0; + + l = file_read(file, contents, n); + contents[l] = '\n'; + + mtl = mtl_default(); + + found_d = 0; + + p = contents; + e = contents + l; + while (p < e) + { + p = skip_whitespace(p); + + switch (*p) + { + case 'n': + p++; + if (p[0] == 'e' && + p[1] == 'w' && + p[2] == 'm' && + p[3] == 't' && + p[4] == 'l' && + is_whitespace(p[5])) + { + /* Push previous material (if there is one) */ + if (mtl.name) + { + array_push(data->mesh->materials, mtl); + mtl = mtl_default(); + } + + + /* Read name */ + p += 5; + + while (is_whitespace(*p)) + p++; + + s = p; + while (!is_whitespace(*p) && !is_newline(*p)) + p++; + + mtl.name = string_copy(s, p); + } + break; + + case 'K': + if (p[1] == 'a') + p = read_mtl_triple(p + 2, mtl.Ka); + else if (p[1] == 'd') + p = read_mtl_triple(p + 2, mtl.Kd); + else if (p[1] == 's') + p = read_mtl_triple(p + 2, mtl.Ks); + else if (p[1] == 'e') + p = read_mtl_triple(p + 2, mtl.Ke); + else if (p[1] == 't') + p = read_mtl_triple(p + 2, mtl.Kt); + break; + + case 'N': + if (p[1] == 's') + p = read_mtl_single(p + 2, &mtl.Ns); + else if (p[1] == 'i') + p = read_mtl_single(p + 2, &mtl.Ni); + break; + + case 'T': + if (p[1] == 'r') + { + float Tr; + p = read_mtl_single(p + 2, &Tr); + if (!found_d) + { + /* Ignore Tr if we've already read d */ + mtl.d = 1.0f - Tr; + } + } + else if (p[1] == 'f') + p = read_mtl_triple(p + 2, mtl.Tf); + break; + + case 'd': + if (is_whitespace(p[1])) + { + p = read_mtl_single(p + 1, &mtl.d); + found_d = 1; + } + break; + + case 'i': + p++; + if (p[0] == 'l' && + p[1] == 'l' && + p[2] == 'u' && + p[3] == 'm' && + is_whitespace(p[4])) + { + p = read_mtl_int(p + 4, &mtl.illum); + } + break; + + case 'm': + p++; + if (p[0] == 'a' && + p[1] == 'p' && + p[2] == '_') + { + p += 3; + if (*p == 'K') + { + p++; + if (is_whitespace(p[1])) + { + if (*p == 'a') + p = read_map(data, p + 1, &mtl.map_Ka); + else if (*p == 'd') + p = read_map(data, p + 1, &mtl.map_Kd); + else if (*p == 's') + p = read_map(data, p + 1, &mtl.map_Ks); + else if (*p == 'e') + p = read_map(data, p + 1, &mtl.map_Ke); + else if (*p == 't') + p = read_map(data, p + 1, &mtl.map_Kt); + } + } + else if (*p == 'N') + { + p++; + if (is_whitespace(p[1])) + { + if (*p == 's') + p = read_map(data, p + 1, &mtl.map_Ns); + else if (*p == 'i') + p = read_map(data, p + 1, &mtl.map_Ni); + } + } + else if (*p == 'd') + { + p++; + if (is_whitespace(*p)) + p = read_map(data, p, &mtl.map_d); + } + else if (p[0] == 'b' && + p[1] == 'u' && + p[2] == 'm' && + p[3] == 'p' && + is_whitespace(p[4])) + { + p = read_map(data, p + 4, &mtl.map_d); + } + } + break; + + case '#': + break; + } + + p = skip_line(p); + } + + /* Push final material */ + if (mtl.name) + array_push(data->mesh->materials, mtl); + + memory_dealloc(contents); + + return 1; +} + + +static +const char* parse_mtllib(fastObjData* data, const char* ptr) +{ + const char* s; + const char* e; + char* lib; + void* file; + + + ptr = skip_whitespace(ptr); + + s = ptr; + while (!is_whitespace(*ptr) && !is_newline(*ptr)) + ptr++; + + e = ptr; + + lib = string_concat(data->base, s, e); + if (lib) + { + string_fix_separators(lib); + + file = file_open(lib); + if (file) + { + read_mtllib(data, file); + file_close(file); + } + + memory_dealloc(lib); + } + + return ptr; +} + + +static +void parse_buffer(fastObjData* data, const char* ptr, const char* end) +{ + const char* p; + + + p = ptr; + while (p != end) + { + p = skip_whitespace(p); + + switch (*p) + { + case 'v': + p++; + + switch (*p++) + { + case ' ': + case '\t': + p = parse_vertex(data, p); + break; + + case 't': + p = parse_texcoord(data, p); + break; + + case 'n': + p = parse_normal(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'f': + p++; + + switch (*p++) + { + case ' ': + case '\t': + p = parse_face(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'g': + p++; + + switch (*p++) + { + case ' ': + case '\t': + p = parse_group(data, p); + break; + + default: + p--; /* roll p++ back in case *p was a newline */ + } + break; + + case 'm': + p++; + if (p[0] == 't' && + p[1] == 'l' && + p[2] == 'l' && + p[3] == 'i' && + p[4] == 'b' && + is_whitespace(p[5])) + p = parse_mtllib(data, p + 5); + break; + + case 'u': + p++; + if (p[0] == 's' && + p[1] == 'e' && + p[2] == 'm' && + p[3] == 't' && + p[4] == 'l' && + is_whitespace(p[5])) + p = parse_usemtl(data, p + 5); + break; + + case '#': + break; + } + + p = skip_line(p); + + data->line++; + } +} + + +void fast_obj_destroy(fastObjMesh* m) +{ + unsigned int ii; + + + for (ii = 0; ii < array_size(m->groups); ii++) + group_clean(&m->groups[ii]); + + for (ii = 0; ii < array_size(m->materials); ii++) + mtl_clean(&m->materials[ii]); + + array_clean(m->positions); + array_clean(m->texcoords); + array_clean(m->normals); + array_clean(m->face_vertices); + array_clean(m->face_materials); + array_clean(m->indices); + array_clean(m->groups); + array_clean(m->materials); + + memory_dealloc(m); +} + + +fastObjMesh* fast_obj_read(const char* path) +{ + fastObjData data; + fastObjMesh* m; + void* file; + char* buffer; + char* start; + char* end; + char* last; + unsigned int read; + size_t sep; + unsigned int bytes; + + + /* Open file */ + file = file_open(path); + if (!file) + return 0; + + + /* Empty mesh */ + m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh))); + if (!m) + return 0; + + m->positions = 0; + m->texcoords = 0; + m->normals = 0; + m->face_vertices = 0; + m->face_materials = 0; + m->indices = 0; + m->materials = 0; + m->groups = 0; + + + /* Add dummy position/texcoord/normal */ + array_push(m->positions, 0.0f); + array_push(m->positions, 0.0f); + array_push(m->positions, 0.0f); + + array_push(m->texcoords, 0.0f); + array_push(m->texcoords, 0.0f); + + array_push(m->normals, 0.0f); + array_push(m->normals, 0.0f); + array_push(m->normals, 1.0f); + + + /* Data needed during parsing */ + data.mesh = m; + data.group = group_default(); + data.material = 0; + data.line = 1; + data.base = 0; + + + /* Find base path for materials/textures */ + if (string_find_last(path, FAST_OBJ_SEPARATOR, &sep)) + data.base = string_substr(path, 0, sep + 1); + + + /* Create buffer for reading file */ + buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char))); + if (!buffer) + return 0; + + start = buffer; + for (;;) + { + /* Read another buffer's worth from file */ + read = (unsigned int)(file_read(file, start, BUFFER_SIZE)); + if (read == 0 && start == buffer) + break; + + + /* Ensure buffer ends in a newline */ + if (read < BUFFER_SIZE) + { + if (read == 0 || start[read - 1] != '\n') + start[read++] = '\n'; + } + + end = start + read; + if (end == buffer) + break; + + + /* Find last new line */ + last = end; + while (last > buffer) + { + last--; + if (*last == '\n') + break; + } + + + /* Check there actually is a new line */ + if (*last != '\n') + break; + + last++; + + + /* Process buffer */ + parse_buffer(&data, buffer, last); + + + /* Copy overflow for next buffer */ + bytes = (unsigned int)(end - last); + memcpy(buffer, last, bytes); + start = buffer + bytes; + } + + + /* Flush final group */ + flush_output(&data); + group_clean(&data.group); + + m->position_count = array_size(m->positions) / 3; + m->texcoord_count = array_size(m->texcoords) / 2; + m->normal_count = array_size(m->normals) / 3; + m->face_count = array_size(m->face_vertices); + m->material_count = array_size(m->materials); + m->group_count = array_size(m->groups); + + + /* Clean up */ + memory_dealloc(buffer); + memory_dealloc(data.base); + + file_close(file); + + return m; +} + +#endif + diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp new file mode 100644 index 00000000000..aa6c49bb0d9 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp @@ -0,0 +1,3760 @@ +// gltfpack is part of meshoptimizer library; see meshoptimizer.h for version/license details +// +// gltfpack is a command-line tool that takes a glTF file as an input and can produce two types of files: +// - regular glb/gltf files that use data that has been optimized for GPU consumption using various cache optimizers +// and quantization +// - packed glb/gltf files that additionally use meshoptimizer codecs to reduce the size of vertex/index data; these +// files can be further compressed with deflate/etc. +// +// To load regular glb files, it should be sufficient to use a standard glTF loader (although note that these files +// use quantized position/texture coordinates that are technically invalid per spec; THREE.js and BabylonJS support +// these files out of the box). +// To load packed glb files, meshoptimizer vertex decoder needs to be integrated into the loader; demo/GLTFLoader.js +// contains a work-in-progress loader - please note that the extension specification isn't ready yet so the format +// will change! + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef _CRT_NONSTDC_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS +#endif + +#include "../src/meshoptimizer.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include <float.h> +#include <limits.h> +#include <math.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "cgltf.h" +#include "fast_obj.h" + +struct Attr +{ + float f[4]; +}; + +struct Stream +{ + cgltf_attribute_type type; + int index; + int target; // 0 = base mesh, 1+ = morph target + + std::vector<Attr> data; +}; + +struct Mesh +{ + cgltf_node* node; + + cgltf_material* material; + cgltf_skin* skin; + + cgltf_primitive_type type; + + std::vector<Stream> streams; + std::vector<unsigned int> indices; + + size_t targets; + std::vector<float> target_weights; + std::vector<const char*> target_names; +}; + +struct Settings +{ + int pos_bits; + int tex_bits; + int nrm_bits; + bool nrm_unit; + + int anim_freq; + bool anim_const; + + bool keep_named; + + float simplify_threshold; + bool simplify_aggressive; + + bool compress; + int verbose; +}; + +struct QuantizationParams +{ + float pos_offset[3]; + float pos_scale; + int pos_bits; + + float uv_offset[2]; + float uv_scale[2]; + int uv_bits; +}; + +struct StreamFormat +{ + cgltf_type type; + cgltf_component_type component_type; + bool normalized; + size_t stride; +}; + +struct NodeInfo +{ + bool keep; + bool animated; + + unsigned int animated_paths; + + int remap; + std::vector<size_t> meshes; +}; + +struct MaterialInfo +{ + bool keep; + + int remap; +}; + +struct BufferView +{ + enum Kind + { + Kind_Vertex, + Kind_Index, + Kind_Skin, + Kind_Time, + Kind_Keyframe, + Kind_Image, + Kind_Count + }; + + Kind kind; + int variant; + size_t stride; + bool compressed; + + std::string data; + + size_t bytes; +}; + +const char* getError(cgltf_result result) +{ + switch (result) + { + case cgltf_result_file_not_found: + return "file not found"; + + case cgltf_result_io_error: + return "I/O error"; + + case cgltf_result_invalid_json: + return "invalid JSON"; + + case cgltf_result_invalid_gltf: + return "invalid GLTF"; + + case cgltf_result_out_of_memory: + return "out of memory"; + + default: + return "unknown error"; + } +} + +cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0) +{ + for (size_t i = 0; i < attribute_count; ++i) + if (attributes[i].type == type && attributes[i].index == index) + return attributes[i].data; + + return 0; +} + +void transformPosition(float* ptr, const float* transform) +{ + float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12]; + float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13]; + float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14]; + + ptr[0] = x; + ptr[1] = y; + ptr[2] = z; +} + +void transformNormal(float* ptr, const float* transform) +{ + float x = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8]; + float y = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9]; + float z = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10]; + + float l = sqrtf(x * x + y * y + z * z); + float s = (l == 0.f) ? 0.f : 1 / l; + + ptr[0] = x * s; + ptr[1] = y * s; + ptr[2] = z * s; +} + +void transformMesh(Mesh& mesh, const cgltf_node* node) +{ + float transform[16]; + cgltf_node_transform_world(node, transform); + + for (size_t si = 0; si < mesh.streams.size(); ++si) + { + Stream& stream = mesh.streams[si]; + + if (stream.type == cgltf_attribute_type_position) + { + for (size_t i = 0; i < stream.data.size(); ++i) + transformPosition(stream.data[i].f, transform); + } + else if (stream.type == cgltf_attribute_type_normal || stream.type == cgltf_attribute_type_tangent) + { + for (size_t i = 0; i < stream.data.size(); ++i) + transformNormal(stream.data[i].f, transform); + } + } +} + +void parseMeshesGltf(cgltf_data* data, std::vector<Mesh>& meshes) +{ + for (size_t ni = 0; ni < data->nodes_count; ++ni) + { + cgltf_node& node = data->nodes[ni]; + + if (!node.mesh) + continue; + + const cgltf_mesh& mesh = *node.mesh; + int mesh_id = int(&mesh - data->meshes); + + for (size_t pi = 0; pi < mesh.primitives_count; ++pi) + { + const cgltf_primitive& primitive = mesh.primitives[pi]; + + if (primitive.type != cgltf_primitive_type_triangles && primitive.type != cgltf_primitive_type_points) + { + fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because type %d is not supported\n", int(pi), mesh_id, primitive.type); + continue; + } + + if (primitive.type == cgltf_primitive_type_points && primitive.indices) + { + fprintf(stderr, "Warning: ignoring primitive %d of mesh %d because indexed points are not supported\n", int(pi), mesh_id); + continue; + } + + Mesh result; + + result.node = &node; + + result.material = primitive.material; + result.skin = node.skin; + + result.type = primitive.type; + + if (primitive.indices) + { + result.indices.resize(primitive.indices->count); + for (size_t i = 0; i < primitive.indices->count; ++i) + result.indices[i] = unsigned(cgltf_accessor_read_index(primitive.indices, i)); + } + else if (primitive.type != cgltf_primitive_type_points) + { + size_t count = primitive.attributes ? primitive.attributes[0].data->count : 0; + + // note, while we could generate a good index buffer, reindexMesh will take care of this + result.indices.resize(count); + for (size_t i = 0; i < count; ++i) + result.indices[i] = unsigned(i); + } + + for (size_t ai = 0; ai < primitive.attributes_count; ++ai) + { + const cgltf_attribute& attr = primitive.attributes[ai]; + + if (attr.type == cgltf_attribute_type_invalid) + { + fprintf(stderr, "Warning: ignoring unknown attribute %s in primitive %d of mesh %d\n", attr.name, int(pi), mesh_id); + continue; + } + + Stream s = {attr.type, attr.index}; + s.data.resize(attr.data->count); + + for (size_t i = 0; i < attr.data->count; ++i) + cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4); + + result.streams.push_back(s); + } + + for (size_t ti = 0; ti < primitive.targets_count; ++ti) + { + const cgltf_morph_target& target = primitive.targets[ti]; + + for (size_t ai = 0; ai < target.attributes_count; ++ai) + { + const cgltf_attribute& attr = target.attributes[ai]; + + if (attr.type == cgltf_attribute_type_invalid) + { + fprintf(stderr, "Warning: ignoring unknown attribute %s in morph target %d of primitive %d of mesh %d\n", attr.name, int(ti), int(pi), mesh_id); + continue; + } + + Stream s = {attr.type, attr.index, int(ti + 1)}; + s.data.resize(attr.data->count); + + for (size_t i = 0; i < attr.data->count; ++i) + cgltf_accessor_read_float(attr.data, i, s.data[i].f, 4); + + result.streams.push_back(s); + } + } + + result.targets = primitive.targets_count; + result.target_weights.assign(mesh.weights, mesh.weights + mesh.weights_count); + result.target_names.assign(mesh.target_names, mesh.target_names + mesh.target_names_count); + + meshes.push_back(result); + } + } +} + +void defaultFree(void*, void* p) +{ + free(p); +} + +int textureIndex(const std::vector<std::string>& textures, const char* name) +{ + for (size_t i = 0; i < textures.size(); ++i) + if (textures[i] == name) + return int(i); + + return -1; +} + +cgltf_data* parseSceneObj(fastObjMesh* obj) +{ + cgltf_data* data = (cgltf_data*)calloc(1, sizeof(cgltf_data)); + data->memory_free = defaultFree; + + std::vector<std::string> textures; + + for (unsigned int mi = 0; mi < obj->material_count; ++mi) + { + fastObjMaterial& om = obj->materials[mi]; + + if (om.map_Kd.name && textureIndex(textures, om.map_Kd.name) < 0) + textures.push_back(om.map_Kd.name); + } + + data->images = (cgltf_image*)calloc(textures.size(), sizeof(cgltf_image)); + data->images_count = textures.size(); + + for (size_t i = 0; i < textures.size(); ++i) + { + data->images[i].uri = strdup(textures[i].c_str()); + } + + data->textures = (cgltf_texture*)calloc(textures.size(), sizeof(cgltf_texture)); + data->textures_count = textures.size(); + + for (size_t i = 0; i < textures.size(); ++i) + { + data->textures[i].image = &data->images[i]; + } + + data->materials = (cgltf_material*)calloc(obj->material_count, sizeof(cgltf_material)); + data->materials_count = obj->material_count; + + for (unsigned int mi = 0; mi < obj->material_count; ++mi) + { + cgltf_material& gm = data->materials[mi]; + fastObjMaterial& om = obj->materials[mi]; + + gm.has_pbr_metallic_roughness = true; + gm.pbr_metallic_roughness.base_color_factor[0] = 1.0f; + gm.pbr_metallic_roughness.base_color_factor[1] = 1.0f; + gm.pbr_metallic_roughness.base_color_factor[2] = 1.0f; + gm.pbr_metallic_roughness.base_color_factor[3] = 1.0f; + gm.pbr_metallic_roughness.metallic_factor = 0.0f; + gm.pbr_metallic_roughness.roughness_factor = 1.0f; + + gm.alpha_cutoff = 0.5f; + + if (om.map_Kd.name) + { + gm.pbr_metallic_roughness.base_color_texture.texture = &data->textures[textureIndex(textures, om.map_Kd.name)]; + gm.pbr_metallic_roughness.base_color_texture.scale = 1.0f; + + gm.alpha_mode = (om.illum == 4 || om.illum == 6 || om.illum == 7 || om.illum == 9) ? cgltf_alpha_mode_mask : cgltf_alpha_mode_opaque; + } + + if (om.map_d.name) + { + gm.alpha_mode = cgltf_alpha_mode_blend; + } + } + + return data; +} + +void parseMeshesObj(fastObjMesh* obj, cgltf_data* data, std::vector<Mesh>& meshes) +{ + unsigned int material_count = std::max(obj->material_count, 1u); + + std::vector<size_t> vertex_count(material_count); + std::vector<size_t> index_count(material_count); + + for (unsigned int fi = 0; fi < obj->face_count; ++fi) + { + unsigned int mi = obj->face_materials[fi]; + + vertex_count[mi] += obj->face_vertices[fi]; + index_count[mi] += (obj->face_vertices[fi] - 2) * 3; + } + + std::vector<size_t> mesh_index(material_count); + + for (unsigned int mi = 0; mi < material_count; ++mi) + { + if (index_count[mi] == 0) + continue; + + mesh_index[mi] = meshes.size(); + + meshes.push_back(Mesh()); + + Mesh& mesh = meshes.back(); + + if (data->materials_count) + { + assert(mi < data->materials_count); + mesh.material = &data->materials[mi]; + } + + mesh.type = cgltf_primitive_type_triangles; + + mesh.streams.resize(3); + mesh.streams[0].type = cgltf_attribute_type_position; + mesh.streams[0].data.resize(vertex_count[mi]); + mesh.streams[1].type = cgltf_attribute_type_normal; + mesh.streams[1].data.resize(vertex_count[mi]); + mesh.streams[2].type = cgltf_attribute_type_texcoord; + mesh.streams[2].data.resize(vertex_count[mi]); + mesh.indices.resize(index_count[mi]); + mesh.targets = 0; + } + + std::vector<size_t> vertex_offset(material_count); + std::vector<size_t> index_offset(material_count); + + size_t group_offset = 0; + + for (unsigned int fi = 0; fi < obj->face_count; ++fi) + { + unsigned int mi = obj->face_materials[fi]; + Mesh& mesh = meshes[mesh_index[mi]]; + + size_t vo = vertex_offset[mi]; + size_t io = index_offset[mi]; + + for (unsigned int vi = 0; vi < obj->face_vertices[fi]; ++vi) + { + fastObjIndex ii = obj->indices[group_offset + vi]; + + Attr p = {{obj->positions[ii.p * 3 + 0], obj->positions[ii.p * 3 + 1], obj->positions[ii.p * 3 + 2]}}; + Attr n = {{obj->normals[ii.n * 3 + 0], obj->normals[ii.n * 3 + 1], obj->normals[ii.n * 3 + 2]}}; + Attr t = {{obj->texcoords[ii.t * 2 + 0], 1.f - obj->texcoords[ii.t * 2 + 1]}}; + + mesh.streams[0].data[vo + vi] = p; + mesh.streams[1].data[vo + vi] = n; + mesh.streams[2].data[vo + vi] = t; + } + + for (unsigned int vi = 2; vi < obj->face_vertices[fi]; ++vi) + { + size_t to = io + (vi - 2) * 3; + + mesh.indices[to + 0] = unsigned(vo); + mesh.indices[to + 1] = unsigned(vo + vi - 1); + mesh.indices[to + 2] = unsigned(vo + vi); + } + + vertex_offset[mi] += obj->face_vertices[fi]; + index_offset[mi] += (obj->face_vertices[fi] - 2) * 3; + group_offset += obj->face_vertices[fi]; + } +} + +bool areTextureViewsEqual(const cgltf_texture_view& lhs, const cgltf_texture_view& rhs) +{ + if (lhs.has_transform != rhs.has_transform) + return false; + + if (lhs.has_transform) + { + const cgltf_texture_transform& lt = lhs.transform; + const cgltf_texture_transform& rt = rhs.transform; + + if (memcmp(lt.offset, rt.offset, sizeof(cgltf_float) * 2) != 0) + return false; + + if (lt.rotation != rt.rotation) + return false; + + if (memcmp(lt.scale, rt.scale, sizeof(cgltf_float) * 2) != 0) + return false; + + if (lt.texcoord != rt.texcoord) + return false; + } + + if (lhs.texture != rhs.texture) + return false; + + if (lhs.texcoord != rhs.texcoord) + return false; + + if (lhs.scale != rhs.scale) + return false; + + return true; +} + +bool areMaterialsEqual(const cgltf_material& lhs, const cgltf_material& rhs) +{ + if (lhs.has_pbr_metallic_roughness != rhs.has_pbr_metallic_roughness) + return false; + + if (lhs.has_pbr_metallic_roughness) + { + const cgltf_pbr_metallic_roughness& lpbr = lhs.pbr_metallic_roughness; + const cgltf_pbr_metallic_roughness& rpbr = rhs.pbr_metallic_roughness; + + if (!areTextureViewsEqual(lpbr.base_color_texture, rpbr.base_color_texture)) + return false; + + if (!areTextureViewsEqual(lpbr.metallic_roughness_texture, rpbr.metallic_roughness_texture)) + return false; + + if (memcmp(lpbr.base_color_factor, rpbr.base_color_factor, sizeof(cgltf_float) * 4) != 0) + return false; + + if (lpbr.metallic_factor != rpbr.metallic_factor) + return false; + + if (lpbr.roughness_factor != rpbr.roughness_factor) + return false; + } + + if (lhs.has_pbr_specular_glossiness != rhs.has_pbr_specular_glossiness) + return false; + + if (lhs.has_pbr_specular_glossiness) + { + const cgltf_pbr_specular_glossiness& lpbr = lhs.pbr_specular_glossiness; + const cgltf_pbr_specular_glossiness& rpbr = rhs.pbr_specular_glossiness; + + if (!areTextureViewsEqual(lpbr.diffuse_texture, rpbr.diffuse_texture)) + return false; + + if (!areTextureViewsEqual(lpbr.specular_glossiness_texture, rpbr.specular_glossiness_texture)) + return false; + + if (memcmp(lpbr.diffuse_factor, rpbr.diffuse_factor, sizeof(cgltf_float) * 4) != 0) + return false; + + if (memcmp(lpbr.specular_factor, rpbr.specular_factor, sizeof(cgltf_float) * 3) != 0) + return false; + + if (lpbr.glossiness_factor != rpbr.glossiness_factor) + return false; + } + + if (!areTextureViewsEqual(lhs.normal_texture, rhs.normal_texture)) + return false; + + if (!areTextureViewsEqual(lhs.occlusion_texture, rhs.occlusion_texture)) + return false; + + if (!areTextureViewsEqual(lhs.emissive_texture, rhs.emissive_texture)) + return false; + + if (memcmp(lhs.emissive_factor, rhs.emissive_factor, sizeof(cgltf_float) * 3) != 0) + return false; + + if (lhs.alpha_mode != rhs.alpha_mode) + return false; + + if (lhs.alpha_cutoff != rhs.alpha_cutoff) + return false; + + if (lhs.double_sided != rhs.double_sided) + return false; + + if (lhs.unlit != rhs.unlit) + return false; + + return true; +} + +void mergeMeshMaterials(cgltf_data* data, std::vector<Mesh>& meshes) +{ + for (size_t i = 0; i < meshes.size(); ++i) + { + Mesh& mesh = meshes[i]; + + if (!mesh.material) + continue; + + for (int j = 0; j < mesh.material - data->materials; ++j) + { + if (areMaterialsEqual(*mesh.material, data->materials[j])) + { + mesh.material = &data->materials[j]; + break; + } + } + } +} + +bool compareMeshTargets(const Mesh& lhs, const Mesh& rhs) +{ + if (lhs.targets != rhs.targets) + return false; + + if (lhs.target_weights.size() != rhs.target_weights.size()) + return false; + + for (size_t i = 0; i < lhs.target_weights.size(); ++i) + if (lhs.target_weights[i] != rhs.target_weights[i]) + return false; + + if (lhs.target_names.size() != rhs.target_names.size()) + return false; + + for (size_t i = 0; i < lhs.target_names.size(); ++i) + if (strcmp(lhs.target_names[i], rhs.target_names[i]) != 0) + return false; + + return true; +} + +bool canMergeMeshes(const Mesh& lhs, const Mesh& rhs, const Settings& settings) +{ + if (lhs.node != rhs.node) + { + if (!lhs.node || !rhs.node) + return false; + + if (lhs.node->parent != rhs.node->parent) + return false; + + bool lhs_transform = lhs.node->has_translation | lhs.node->has_rotation | lhs.node->has_scale | lhs.node->has_matrix | (!!lhs.node->weights); + bool rhs_transform = rhs.node->has_translation | rhs.node->has_rotation | rhs.node->has_scale | rhs.node->has_matrix | (!!rhs.node->weights); + + if (lhs_transform || rhs_transform) + return false; + + if (settings.keep_named) + { + if (lhs.node->name && *lhs.node->name) + return false; + + if (rhs.node->name && *rhs.node->name) + return false; + } + + // we can merge nodes that don't have transforms of their own and have the same parent + // this is helpful when instead of splitting mesh into primitives, DCCs split mesh into mesh nodes + } + + if (lhs.material != rhs.material) + return false; + + if (lhs.skin != rhs.skin) + return false; + + if (lhs.type != rhs.type) + return false; + + if (!compareMeshTargets(lhs, rhs)) + return false; + + if (lhs.indices.empty() != rhs.indices.empty()) + return false; + + if (lhs.streams.size() != rhs.streams.size()) + return false; + + for (size_t i = 0; i < lhs.streams.size(); ++i) + if (lhs.streams[i].type != rhs.streams[i].type || lhs.streams[i].index != rhs.streams[i].index || lhs.streams[i].target != rhs.streams[i].target) + return false; + + return true; +} + +void mergeMeshes(Mesh& target, const Mesh& mesh) +{ + assert(target.streams.size() == mesh.streams.size()); + + size_t vertex_offset = target.streams[0].data.size(); + size_t index_offset = target.indices.size(); + + for (size_t i = 0; i < target.streams.size(); ++i) + target.streams[i].data.insert(target.streams[i].data.end(), mesh.streams[i].data.begin(), mesh.streams[i].data.end()); + + target.indices.resize(target.indices.size() + mesh.indices.size()); + + size_t index_count = mesh.indices.size(); + + for (size_t i = 0; i < index_count; ++i) + target.indices[index_offset + i] = unsigned(vertex_offset + mesh.indices[i]); +} + +void mergeMeshes(std::vector<Mesh>& meshes, const Settings& settings) +{ + size_t write = 0; + + for (size_t i = 0; i < meshes.size(); ++i) + { + if (meshes[i].streams.empty()) + continue; + + Mesh& target = meshes[write]; + + if (i != write) + { + Mesh& mesh = meshes[i]; + + // note: this copy is expensive; we could use move in C++11 or swap manually which is a bit painful... + target = mesh; + + mesh.streams.clear(); + mesh.indices.clear(); + } + + size_t target_vertices = target.streams[0].data.size(); + size_t target_indices = target.indices.size(); + + for (size_t j = i + 1; j < meshes.size(); ++j) + { + Mesh& mesh = meshes[j]; + + if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) + { + target_vertices += mesh.streams[0].data.size(); + target_indices += mesh.indices.size(); + } + } + + for (size_t j = 0; j < target.streams.size(); ++j) + target.streams[j].data.reserve(target_vertices); + + target.indices.reserve(target_indices); + + for (size_t j = i + 1; j < meshes.size(); ++j) + { + Mesh& mesh = meshes[j]; + + if (!mesh.streams.empty() && canMergeMeshes(target, mesh, settings)) + { + mergeMeshes(target, mesh); + + mesh.streams.clear(); + mesh.indices.clear(); + } + } + + assert(target.streams[0].data.size() == target_vertices); + assert(target.indices.size() == target_indices); + + write++; + } + + meshes.resize(write); +} + +void reindexMesh(Mesh& mesh) +{ + size_t total_vertices = mesh.streams[0].data.size(); + size_t total_indices = mesh.indices.size(); + + std::vector<meshopt_Stream> streams; + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + if (mesh.streams[i].target) + continue; + + assert(mesh.streams[i].data.size() == total_vertices); + + meshopt_Stream stream = {&mesh.streams[i].data[0], sizeof(Attr), sizeof(Attr)}; + streams.push_back(stream); + } + + std::vector<unsigned int> remap(total_vertices); + size_t unique_vertices = meshopt_generateVertexRemapMulti(&remap[0], &mesh.indices[0], total_indices, total_vertices, &streams[0], streams.size()); + assert(unique_vertices <= total_vertices); + + meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], total_indices, &remap[0]); + + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + assert(mesh.streams[i].data.size() == total_vertices); + + meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], total_vertices, sizeof(Attr), &remap[0]); + mesh.streams[i].data.resize(unique_vertices); + } +} + +const Stream* getPositionStream(const Mesh& mesh) +{ + for (size_t i = 0; i < mesh.streams.size(); ++i) + if (mesh.streams[i].type == cgltf_attribute_type_position) + return &mesh.streams[i]; + + return 0; +} + +void simplifyMesh(Mesh& mesh, float threshold, bool aggressive) +{ + if (threshold >= 1) + return; + + const Stream* positions = getPositionStream(mesh); + if (!positions) + return; + + size_t vertex_count = mesh.streams[0].data.size(); + + size_t target_index_count = size_t(double(mesh.indices.size() / 3) * threshold) * 3; + float target_error = 1e-2f; + + if (target_index_count < 1) + return; + + std::vector<unsigned int> indices(mesh.indices.size()); + indices.resize(meshopt_simplify(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count, target_error)); + mesh.indices.swap(indices); + + // Note: if the simplifier got stuck, we can try to reindex without normals/tangents and retry + // For now we simply fall back to aggressive simplifier instead + + // if the mesh is complex enough and the precise simplifier got "stuck", we'll try to simplify using the sloppy simplifier which is guaranteed to reach the target count + if (aggressive && target_index_count > 50 * 3 && mesh.indices.size() > target_index_count) + { + indices.resize(meshopt_simplifySloppy(&indices[0], &mesh.indices[0], mesh.indices.size(), positions->data[0].f, vertex_count, sizeof(Attr), target_index_count)); + mesh.indices.swap(indices); + } +} + +void optimizeMesh(Mesh& mesh) +{ + size_t vertex_count = mesh.streams[0].data.size(); + + meshopt_optimizeVertexCache(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), vertex_count); + + std::vector<unsigned int> remap(vertex_count); + size_t unique_vertices = meshopt_optimizeVertexFetchRemap(&remap[0], &mesh.indices[0], mesh.indices.size(), vertex_count); + assert(unique_vertices <= vertex_count); + + meshopt_remapIndexBuffer(&mesh.indices[0], &mesh.indices[0], mesh.indices.size(), &remap[0]); + + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + assert(mesh.streams[i].data.size() == vertex_count); + + meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); + mesh.streams[i].data.resize(unique_vertices); + } +} + +void simplifyPointMesh(Mesh& mesh, float threshold) +{ + if (threshold >= 1) + return; + + const Stream* positions = getPositionStream(mesh); + if (!positions) + return; + + size_t vertex_count = mesh.streams[0].data.size(); + + size_t target_vertex_count = size_t(double(vertex_count) * threshold); + + if (target_vertex_count < 1) + return; + + std::vector<unsigned int> indices(target_vertex_count); + indices.resize(meshopt_simplifyPoints(&indices[0], positions->data[0].f, vertex_count, sizeof(Attr), target_vertex_count)); + + std::vector<Attr> scratch(indices.size()); + + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + std::vector<Attr>& data = mesh.streams[i].data; + + assert(data.size() == vertex_count); + + for (size_t j = 0; j < indices.size(); ++j) + scratch[j] = data[indices[j]]; + + data = scratch; + } +} + +void sortPointMesh(Mesh& mesh) +{ + const Stream* positions = getPositionStream(mesh); + if (!positions) + return; + + size_t vertex_count = mesh.streams[0].data.size(); + + std::vector<unsigned int> remap(vertex_count); + meshopt_spatialSortRemap(&remap[0], positions->data[0].f, vertex_count, sizeof(Attr)); + + for (size_t i = 0; i < mesh.streams.size(); ++i) + { + assert(mesh.streams[i].data.size() == vertex_count); + + meshopt_remapVertexBuffer(&mesh.streams[i].data[0], &mesh.streams[i].data[0], vertex_count, sizeof(Attr), &remap[0]); + } +} + +bool getAttributeBounds(const std::vector<Mesh>& meshes, cgltf_attribute_type type, Attr& min, Attr& max) +{ + min.f[0] = min.f[1] = min.f[2] = min.f[3] = +FLT_MAX; + max.f[0] = max.f[1] = max.f[2] = max.f[3] = -FLT_MAX; + + Attr pad = {}; + + bool valid = false; + + for (size_t i = 0; i < meshes.size(); ++i) + { + const Mesh& mesh = meshes[i]; + + for (size_t j = 0; j < mesh.streams.size(); ++j) + { + const Stream& s = mesh.streams[j]; + + if (s.type == type) + { + if (s.target == 0) + { + for (size_t k = 0; k < s.data.size(); ++k) + { + const Attr& a = s.data[k]; + + min.f[0] = std::min(min.f[0], a.f[0]); + min.f[1] = std::min(min.f[1], a.f[1]); + min.f[2] = std::min(min.f[2], a.f[2]); + min.f[3] = std::min(min.f[3], a.f[3]); + + max.f[0] = std::max(max.f[0], a.f[0]); + max.f[1] = std::max(max.f[1], a.f[1]); + max.f[2] = std::max(max.f[2], a.f[2]); + max.f[3] = std::max(max.f[3], a.f[3]); + + valid = true; + } + } + else + { + for (size_t k = 0; k < s.data.size(); ++k) + { + const Attr& a = s.data[k]; + + pad.f[0] = std::max(pad.f[0], fabsf(a.f[0])); + pad.f[1] = std::max(pad.f[1], fabsf(a.f[1])); + pad.f[2] = std::max(pad.f[2], fabsf(a.f[2])); + pad.f[3] = std::max(pad.f[3], fabsf(a.f[3])); + } + } + } + } + } + + if (valid) + { + for (int k = 0; k < 4; ++k) + { + min.f[k] -= pad.f[k]; + max.f[k] += pad.f[k]; + } + } + + return valid; +} + +QuantizationParams prepareQuantization(const std::vector<Mesh>& meshes, const Settings& settings) +{ + QuantizationParams result = {}; + + result.pos_bits = settings.pos_bits; + + Attr pos_min, pos_max; + if (getAttributeBounds(meshes, cgltf_attribute_type_position, pos_min, pos_max)) + { + result.pos_offset[0] = pos_min.f[0]; + result.pos_offset[1] = pos_min.f[1]; + result.pos_offset[2] = pos_min.f[2]; + result.pos_scale = std::max(pos_max.f[0] - pos_min.f[0], std::max(pos_max.f[1] - pos_min.f[1], pos_max.f[2] - pos_min.f[2])); + } + + result.uv_bits = settings.tex_bits; + + Attr uv_min, uv_max; + if (getAttributeBounds(meshes, cgltf_attribute_type_texcoord, uv_min, uv_max)) + { + result.uv_offset[0] = uv_min.f[0]; + result.uv_offset[1] = uv_min.f[1]; + result.uv_scale[0] = uv_max.f[0] - uv_min.f[0]; + result.uv_scale[1] = uv_max.f[1] - uv_min.f[1]; + } + + return result; +} + +void rescaleNormal(float& nx, float& ny, float& nz) +{ + // scale the normal to make sure the largest component is +-1.0 + // this reduces the entropy of the normal by ~1.5 bits without losing precision + // it's better to use octahedral encoding but that requires special shader support + float nm = std::max(fabsf(nx), std::max(fabsf(ny), fabsf(nz))); + float ns = nm == 0.f ? 0.f : 1 / nm; + + nx *= ns; + ny *= ns; + nz *= ns; +} + +void renormalizeWeights(uint8_t (&w)[4]) +{ + int sum = w[0] + w[1] + w[2] + w[3]; + + if (sum == 255) + return; + + // we assume that the total error is limited to 0.5/component = 2 + // this means that it's acceptable to adjust the max. component to compensate for the error + int max = 0; + + for (int k = 1; k < 4; ++k) + if (w[k] > w[max]) + max = k; + + w[max] += uint8_t(255 - sum); +} + +StreamFormat writeVertexStream(std::string& bin, const Stream& stream, const QuantizationParams& params, const Settings& settings, bool has_targets) +{ + if (stream.type == cgltf_attribute_type_position) + { + if (stream.target == 0) + { + float pos_rscale = params.pos_scale == 0.f ? 0.f : 1.f / params.pos_scale; + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint16_t v[4] = { + uint16_t(meshopt_quantizeUnorm((a.f[0] - params.pos_offset[0]) * pos_rscale, params.pos_bits)), + uint16_t(meshopt_quantizeUnorm((a.f[1] - params.pos_offset[1]) * pos_rscale, params.pos_bits)), + uint16_t(meshopt_quantizeUnorm((a.f[2] - params.pos_offset[2]) * pos_rscale, params.pos_bits)), + 0}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16u, false, 8}; + return format; + } + else + { + float pos_rscale = params.pos_scale == 0.f ? 0.f : 1.f / params.pos_scale; + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + int16_t v[4] = { + int16_t((a.f[0] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[0]) * pos_rscale, params.pos_bits)), + int16_t((a.f[1] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[1]) * pos_rscale, params.pos_bits)), + int16_t((a.f[2] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[2]) * pos_rscale, params.pos_bits)), + 0}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, false, 8}; + return format; + } + } + else if (stream.type == cgltf_attribute_type_texcoord) + { + float uv_rscale[2] = { + params.uv_scale[0] == 0.f ? 0.f : 1.f / params.uv_scale[0], + params.uv_scale[1] == 0.f ? 0.f : 1.f / params.uv_scale[1], + }; + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint16_t v[2] = { + uint16_t(meshopt_quantizeUnorm((a.f[0] - params.uv_offset[0]) * uv_rscale[0], params.uv_bits)), + uint16_t(meshopt_quantizeUnorm((a.f[1] - params.uv_offset[1]) * uv_rscale[1], params.uv_bits)), + }; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec2, cgltf_component_type_r_16u, false, 4}; + return format; + } + else if (stream.type == cgltf_attribute_type_normal) + { + bool nrm_unit = has_targets || settings.nrm_unit; + int bits = nrm_unit ? (settings.nrm_bits > 8 ? 16 : 8) : settings.nrm_bits; + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + float nx = a.f[0], ny = a.f[1], nz = a.f[2]; + + if (!nrm_unit) + rescaleNormal(nx, ny, nz); + + if (bits > 8) + { + int16_t v[4] = { + int16_t(meshopt_quantizeSnorm(nx, bits)), + int16_t(meshopt_quantizeSnorm(ny, bits)), + int16_t(meshopt_quantizeSnorm(nz, bits)), + 0}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + else + { + int8_t v[4] = { + int8_t(meshopt_quantizeSnorm(nx, bits)), + int8_t(meshopt_quantizeSnorm(ny, bits)), + int8_t(meshopt_quantizeSnorm(nz, bits)), + 0}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + } + + if (bits > 8) + { + StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_16, true, 8}; + return format; + } + else + { + StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_8, true, 4}; + return format; + } + } + else if (stream.type == cgltf_attribute_type_tangent) + { + bool nrm_unit = has_targets || settings.nrm_unit; + int bits = nrm_unit ? (settings.nrm_bits > 8 ? 16 : 8) : settings.nrm_bits; + + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + float nx = a.f[0], ny = a.f[1], nz = a.f[2], nw = a.f[3]; + + if (!nrm_unit) + rescaleNormal(nx, ny, nz); + + if (bits > 8) + { + int16_t v[4] = { + int16_t(meshopt_quantizeSnorm(nx, bits)), + int16_t(meshopt_quantizeSnorm(ny, bits)), + int16_t(meshopt_quantizeSnorm(nz, bits)), + int16_t(meshopt_quantizeSnorm(nw, 8))}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + else + { + int8_t v[4] = { + int8_t(meshopt_quantizeSnorm(nx, bits)), + int8_t(meshopt_quantizeSnorm(ny, bits)), + int8_t(meshopt_quantizeSnorm(nz, bits)), + int8_t(meshopt_quantizeSnorm(nw, 8))}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + } + + cgltf_type type = (stream.target == 0) ? cgltf_type_vec4 : cgltf_type_vec3; + + if (bits > 8) + { + StreamFormat format = {type, cgltf_component_type_r_16, true, 8}; + return format; + } + else + { + StreamFormat format = {type, cgltf_component_type_r_8, true, 4}; + return format; + } + } + else if (stream.type == cgltf_attribute_type_color) + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint8_t v[4] = { + uint8_t(meshopt_quantizeUnorm(a.f[0], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[1], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[2], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[3], 8))}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4}; + return format; + } + else if (stream.type == cgltf_attribute_type_weights) + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint8_t v[4] = { + uint8_t(meshopt_quantizeUnorm(a.f[0], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[1], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[2], 8)), + uint8_t(meshopt_quantizeUnorm(a.f[3], 8))}; + + renormalizeWeights(v); + + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, true, 4}; + return format; + } + else if (stream.type == cgltf_attribute_type_joints) + { + unsigned int maxj = 0; + + for (size_t i = 0; i < stream.data.size(); ++i) + maxj = std::max(maxj, unsigned(stream.data[i].f[0])); + + assert(maxj <= 65535); + + if (maxj <= 255) + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint8_t v[4] = { + uint8_t(a.f[0]), + uint8_t(a.f[1]), + uint8_t(a.f[2]), + uint8_t(a.f[3])}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_8u, false, 4}; + return format; + } + else + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + uint16_t v[4] = { + uint16_t(a.f[0]), + uint16_t(a.f[1]), + uint16_t(a.f[2]), + uint16_t(a.f[3])}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16u, false, 8}; + return format; + } + } + else + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + float v[4] = { + a.f[0], + a.f[1], + a.f[2], + a.f[3]}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_32f, false, 16}; + return format; + } +} + +void getPositionBounds(int min[3], int max[3], const Stream& stream, const QuantizationParams& params) +{ + assert(stream.type == cgltf_attribute_type_position); + assert(stream.data.size() > 0); + + min[0] = min[1] = min[2] = INT_MAX; + max[0] = max[1] = max[2] = INT_MIN; + + float pos_rscale = params.pos_scale == 0.f ? 0.f : 1.f / params.pos_scale; + + if (stream.target == 0) + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + for (int k = 0; k < 3; ++k) + { + int v = meshopt_quantizeUnorm((a.f[k] - params.pos_offset[k]) * pos_rscale, params.pos_bits); + + min[k] = std::min(min[k], v); + max[k] = std::max(max[k], v); + } + } + } + else + { + for (size_t i = 0; i < stream.data.size(); ++i) + { + const Attr& a = stream.data[i]; + + for (int k = 0; k < 3; ++k) + { + int v = (a.f[k] >= 0.f ? 1 : -1) * meshopt_quantizeUnorm(fabsf(a.f[k]) * pos_rscale, params.pos_bits); + + min[k] = std::min(min[k], v); + max[k] = std::max(max[k], v); + } + } + } +} + +StreamFormat writeIndexStream(std::string& bin, const std::vector<unsigned int>& stream) +{ + unsigned int maxi = 0; + for (size_t i = 0; i < stream.size(); ++i) + maxi = std::max(maxi, stream[i]); + + // save 16-bit indices if we can; note that we can't use restart index (65535) + if (maxi < 65535) + { + for (size_t i = 0; i < stream.size(); ++i) + { + uint16_t v[1] = {uint16_t(stream[i])}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_16u, false, 2}; + return format; + } + else + { + for (size_t i = 0; i < stream.size(); ++i) + { + uint32_t v[1] = {stream[i]}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32u, false, 4}; + return format; + } +} + +StreamFormat writeTimeStream(std::string& bin, const std::vector<float>& data) +{ + for (size_t i = 0; i < data.size(); ++i) + { + float v[1] = {data[i]}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_32f, false, 4}; + return format; +} + +StreamFormat writeKeyframeStream(std::string& bin, cgltf_animation_path_type type, const std::vector<Attr>& data) +{ + if (type == cgltf_animation_path_type_rotation) + { + for (size_t i = 0; i < data.size(); ++i) + { + const Attr& a = data[i]; + + int16_t v[4] = { + int16_t(meshopt_quantizeSnorm(a.f[0], 16)), + int16_t(meshopt_quantizeSnorm(a.f[1], 16)), + int16_t(meshopt_quantizeSnorm(a.f[2], 16)), + int16_t(meshopt_quantizeSnorm(a.f[3], 16)), + }; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec4, cgltf_component_type_r_16, true, 8}; + return format; + } + else if (type == cgltf_animation_path_type_weights) + { + for (size_t i = 0; i < data.size(); ++i) + { + const Attr& a = data[i]; + + uint8_t v[1] = {uint8_t(meshopt_quantizeUnorm(a.f[0], 8))}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_scalar, cgltf_component_type_r_8u, true, 1}; + return format; + } + else + { + for (size_t i = 0; i < data.size(); ++i) + { + const Attr& a = data[i]; + + float v[3] = {a.f[0], a.f[1], a.f[2]}; + bin.append(reinterpret_cast<const char*>(v), sizeof(v)); + } + + StreamFormat format = {cgltf_type_vec3, cgltf_component_type_r_32f, false, 12}; + return format; + } +} + +void compressVertexStream(std::string& bin, const std::string& data, size_t count, size_t stride) +{ + assert(data.size() == count * stride); + + std::vector<unsigned char> compressed(meshopt_encodeVertexBufferBound(count, stride)); + size_t size = meshopt_encodeVertexBuffer(&compressed[0], compressed.size(), data.c_str(), count, stride); + + bin.append(reinterpret_cast<const char*>(&compressed[0]), size); +} + +void compressIndexStream(std::string& bin, const std::string& data, size_t count, size_t stride) +{ + assert(stride == 2 || stride == 4); + assert(data.size() == count * stride); + + std::vector<unsigned char> compressed(meshopt_encodeIndexBufferBound(count, count * 3)); + size_t size = 0; + + if (stride == 2) + size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint16_t*>(data.c_str()), count); + else + size = meshopt_encodeIndexBuffer(&compressed[0], compressed.size(), reinterpret_cast<const uint32_t*>(data.c_str()), count); + + bin.append(reinterpret_cast<const char*>(&compressed[0]), size); +} + +void comma(std::string& s) +{ + char ch = s.empty() ? 0 : s[s.size() - 1]; + + if (ch != 0 && ch != '[' && ch != '{') + s += ","; +} + +void append(std::string& s, size_t v) +{ + char buf[32]; + sprintf(buf, "%zu", v); + s += buf; +} + +void append(std::string& s, float v) +{ + char buf[512]; + sprintf(buf, "%.9g", v); + s += buf; +} + +void append(std::string& s, const char* v) +{ + s += v; +} + +void append(std::string& s, const std::string& v) +{ + s += v; +} + +const char* componentType(cgltf_component_type type) +{ + switch (type) + { + case cgltf_component_type_r_8: + return "5120"; + case cgltf_component_type_r_8u: + return "5121"; + case cgltf_component_type_r_16: + return "5122"; + case cgltf_component_type_r_16u: + return "5123"; + case cgltf_component_type_r_32u: + return "5125"; + case cgltf_component_type_r_32f: + return "5126"; + default: + return "0"; + } +} + +const char* shapeType(cgltf_type type) +{ + switch (type) + { + case cgltf_type_scalar: + return "SCALAR"; + case cgltf_type_vec2: + return "VEC2"; + case cgltf_type_vec3: + return "VEC3"; + case cgltf_type_vec4: + return "VEC4"; + case cgltf_type_mat2: + return "MAT2"; + case cgltf_type_mat3: + return "MAT3"; + case cgltf_type_mat4: + return "MAT4"; + default: + return ""; + } +} + +const char* attributeType(cgltf_attribute_type type) +{ + switch (type) + { + case cgltf_attribute_type_position: + return "POSITION"; + case cgltf_attribute_type_normal: + return "NORMAL"; + case cgltf_attribute_type_tangent: + return "TANGENT"; + case cgltf_attribute_type_texcoord: + return "TEXCOORD"; + case cgltf_attribute_type_color: + return "COLOR"; + case cgltf_attribute_type_joints: + return "JOINTS"; + case cgltf_attribute_type_weights: + return "WEIGHTS"; + default: + return "ATTRIBUTE"; + } +} + +const char* animationPath(cgltf_animation_path_type type) +{ + switch (type) + { + case cgltf_animation_path_type_translation: + return "translation"; + case cgltf_animation_path_type_rotation: + return "rotation"; + case cgltf_animation_path_type_scale: + return "scale"; + case cgltf_animation_path_type_weights: + return "weights"; + default: + return ""; + } +} + +const char* lightType(cgltf_light_type type) +{ + switch (type) + { + case cgltf_light_type_directional: + return "directional"; + case cgltf_light_type_point: + return "point"; + case cgltf_light_type_spot: + return "spot"; + default: + return ""; + } +} + +void writeTextureInfo(std::string& json, const cgltf_data* data, const cgltf_texture_view& view, const QuantizationParams& qp) +{ + assert(view.texture); + + cgltf_texture_transform transform = {}; + + if (view.has_transform) + { + transform = view.transform; + } + else + { + transform.scale[0] = transform.scale[1] = 1.f; + } + + transform.offset[0] += qp.uv_offset[0]; + transform.offset[1] += qp.uv_offset[1]; + transform.scale[0] *= qp.uv_scale[0] / float((1 << qp.uv_bits) - 1); + transform.scale[1] *= qp.uv_scale[1] / float((1 << qp.uv_bits) - 1); + + append(json, "{\"index\":"); + append(json, size_t(view.texture - data->textures)); + append(json, ",\"texCoord\":"); + append(json, size_t(view.texcoord)); + append(json, ",\"extensions\":{\"KHR_texture_transform\":{"); + append(json, "\"offset\":["); + append(json, transform.offset[0]); + append(json, ","); + append(json, transform.offset[1]); + append(json, "],\"scale\":["); + append(json, transform.scale[0]); + append(json, ","); + append(json, transform.scale[1]); + append(json, "]"); + if (transform.rotation != 0.f) + { + append(json, ",\"rotation\":"); + append(json, transform.rotation); + } + append(json, "}}}"); +} + +void writeMaterialInfo(std::string& json, const cgltf_data* data, const cgltf_material& material, const QuantizationParams& qp) +{ + static const float white[4] = {1, 1, 1, 1}; + static const float black[4] = {0, 0, 0, 0}; + + if (material.has_pbr_metallic_roughness) + { + const cgltf_pbr_metallic_roughness& pbr = material.pbr_metallic_roughness; + + comma(json); + append(json, "\"pbrMetallicRoughness\":{"); + if (memcmp(pbr.base_color_factor, white, 16) != 0) + { + comma(json); + append(json, "\"baseColorFactor\":["); + append(json, pbr.base_color_factor[0]); + append(json, ","); + append(json, pbr.base_color_factor[1]); + append(json, ","); + append(json, pbr.base_color_factor[2]); + append(json, ","); + append(json, pbr.base_color_factor[3]); + append(json, "]"); + } + if (pbr.base_color_texture.texture) + { + comma(json); + append(json, "\"baseColorTexture\":"); + writeTextureInfo(json, data, pbr.base_color_texture, qp); + } + if (pbr.metallic_factor != 1) + { + comma(json); + append(json, "\"metallicFactor\":"); + append(json, pbr.metallic_factor); + } + if (pbr.roughness_factor != 1) + { + comma(json); + append(json, "\"roughnessFactor\":"); + append(json, pbr.roughness_factor); + } + if (pbr.metallic_roughness_texture.texture) + { + comma(json); + append(json, "\"metallicRoughnessTexture\":"); + writeTextureInfo(json, data, pbr.metallic_roughness_texture, qp); + } + append(json, "}"); + } + + if (material.normal_texture.texture) + { + comma(json); + append(json, "\"normalTexture\":"); + writeTextureInfo(json, data, material.normal_texture, qp); + } + + if (material.occlusion_texture.texture) + { + comma(json); + append(json, "\"occlusionTexture\":"); + writeTextureInfo(json, data, material.occlusion_texture, qp); + } + + if (material.emissive_texture.texture) + { + comma(json); + append(json, "\"emissiveTexture\":"); + writeTextureInfo(json, data, material.emissive_texture, qp); + } + + if (memcmp(material.emissive_factor, black, 12) != 0) + { + comma(json); + append(json, "\"emissiveFactor\":["); + append(json, material.emissive_factor[0]); + append(json, ","); + append(json, material.emissive_factor[1]); + append(json, ","); + append(json, material.emissive_factor[2]); + append(json, "]"); + } + + if (material.alpha_mode != cgltf_alpha_mode_opaque) + { + comma(json); + append(json, "\"alphaMode\":"); + append(json, (material.alpha_mode == cgltf_alpha_mode_blend) ? "\"BLEND\"" : "\"MASK\""); + } + + if (material.alpha_cutoff != 0.5f) + { + comma(json); + append(json, "\"alphaCutoff\":"); + append(json, material.alpha_cutoff); + } + + if (material.double_sided) + { + comma(json); + append(json, "\"doubleSided\":true"); + } + + if (material.has_pbr_specular_glossiness || material.unlit) + { + comma(json); + append(json, "\"extensions\":{"); + + if (material.has_pbr_specular_glossiness) + { + const cgltf_pbr_specular_glossiness& pbr = material.pbr_specular_glossiness; + + comma(json); + append(json, "\"KHR_materials_pbrSpecularGlossiness\":{"); + if (pbr.diffuse_texture.texture) + { + comma(json); + append(json, "\"diffuseTexture\":"); + writeTextureInfo(json, data, pbr.diffuse_texture, qp); + } + if (pbr.specular_glossiness_texture.texture) + { + comma(json); + append(json, "\"specularGlossinessTexture\":"); + writeTextureInfo(json, data, pbr.specular_glossiness_texture, qp); + } + if (memcmp(pbr.diffuse_factor, white, 16) != 0) + { + comma(json); + append(json, "\"diffuseFactor\":["); + append(json, pbr.diffuse_factor[0]); + append(json, ","); + append(json, pbr.diffuse_factor[1]); + append(json, ","); + append(json, pbr.diffuse_factor[2]); + append(json, ","); + append(json, pbr.diffuse_factor[3]); + append(json, "]"); + } + if (memcmp(pbr.specular_factor, white, 12) != 0) + { + comma(json); + append(json, "\"specularFactor\":["); + append(json, pbr.specular_factor[0]); + append(json, ","); + append(json, pbr.specular_factor[1]); + append(json, ","); + append(json, pbr.specular_factor[2]); + append(json, "]"); + } + if (pbr.glossiness_factor != 1) + { + comma(json); + append(json, "\"glossinessFactor\":"); + append(json, pbr.glossiness_factor); + } + append(json, "}"); + } + if (material.unlit) + { + comma(json); + append(json, "\"KHR_materials_unlit\":{}"); + } + + append(json, "}"); + } +} + +bool usesTextureSet(const cgltf_material& material, int set) +{ + if (material.has_pbr_metallic_roughness) + { + const cgltf_pbr_metallic_roughness& pbr = material.pbr_metallic_roughness; + + if (pbr.base_color_texture.texture && pbr.base_color_texture.texcoord == set) + return true; + + if (pbr.metallic_roughness_texture.texture && pbr.metallic_roughness_texture.texcoord == set) + return true; + } + + if (material.has_pbr_specular_glossiness) + { + const cgltf_pbr_specular_glossiness& pbr = material.pbr_specular_glossiness; + + if (pbr.diffuse_texture.texture && pbr.diffuse_texture.texcoord == set) + return true; + + if (pbr.specular_glossiness_texture.texture && pbr.specular_glossiness_texture.texcoord == set) + return true; + } + + if (material.normal_texture.texture && material.normal_texture.texcoord == set) + return true; + + if (material.occlusion_texture.texture && material.occlusion_texture.texcoord == set) + return true; + + if (material.emissive_texture.texture && material.emissive_texture.texcoord == set) + return true; + + return false; +} + +size_t getBufferView(std::vector<BufferView>& views, BufferView::Kind kind, int variant, size_t stride, bool compressed) +{ + if (variant >= 0) + { + for (size_t i = 0; i < views.size(); ++i) + if (views[i].kind == kind && views[i].variant == variant && views[i].stride == stride && views[i].compressed == compressed) + return i; + } + + BufferView view = {kind, variant, stride, compressed}; + views.push_back(view); + + return views.size() - 1; +} + +void writeBufferView(std::string& json, BufferView::Kind kind, size_t count, size_t stride, size_t bin_offset, size_t bin_size, int compression) +{ + append(json, "{\"buffer\":0"); + append(json, ",\"byteLength\":"); + append(json, bin_size); + append(json, ",\"byteOffset\":"); + append(json, bin_offset); + if (kind == BufferView::Kind_Vertex) + { + append(json, ",\"byteStride\":"); + append(json, stride); + } + if (kind == BufferView::Kind_Vertex || kind == BufferView::Kind_Index) + { + append(json, ",\"target\":"); + append(json, (kind == BufferView::Kind_Vertex) ? "34962" : "34963"); + } + if (compression >= 0) + { + append(json, ",\"extensions\":{"); + append(json, "\"MESHOPT_compression\":{"); + append(json, "\"mode\":"); + append(json, size_t(compression)); + append(json, ",\"count\":"); + append(json, count); + append(json, ",\"byteStride\":"); + append(json, stride); + append(json, "}}"); + } + append(json, "}"); +} + +void writeAccessor(std::string& json, size_t view, size_t offset, cgltf_type type, cgltf_component_type component_type, bool normalized, size_t count, const float* min = 0, const float* max = 0, size_t numminmax = 0) +{ + append(json, "{\"bufferView\":"); + append(json, view); + append(json, ",\"byteOffset\":"); + append(json, offset); + append(json, ",\"componentType\":"); + append(json, componentType(component_type)); + append(json, ",\"count\":"); + append(json, count); + append(json, ",\"type\":\""); + append(json, shapeType(type)); + append(json, "\""); + + if (normalized) + { + append(json, ",\"normalized\":true"); + } + + if (min && max) + { + assert(numminmax); + + append(json, ",\"min\":["); + for (size_t k = 0; k < numminmax; ++k) + { + comma(json); + append(json, min[k]); + } + append(json, "],\"max\":["); + for (size_t k = 0; k < numminmax; ++k) + { + comma(json); + append(json, max[k]); + } + append(json, "]"); + } + + append(json, "}"); +} + +float getDelta(const Attr& l, const Attr& r, cgltf_animation_path_type type) +{ + if (type == cgltf_animation_path_type_rotation) + { + float error = 1.f - fabsf(l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]); + + return error; + } + else + { + float error = 0; + for (int k = 0; k < 4; ++k) + error += fabsf(r.f[k] - l.f[k]); + + return error; + } +} + +bool isTrackConstant(const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node) +{ + const float tolerance = 1e-3f; + + size_t value_stride = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? 3 : 1; + size_t value_offset = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? 1 : 0; + + size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1; + + assert(sampler.input->count * value_stride * components == sampler.output->count); + + for (size_t j = 0; j < components; ++j) + { + Attr first = {}; + cgltf_accessor_read_float(sampler.output, j * value_stride + value_offset, first.f, 4); + + for (size_t i = 1; i < sampler.input->count; ++i) + { + Attr attr = {}; + cgltf_accessor_read_float(sampler.output, (i * components + j) * value_stride + value_offset, attr.f, 4); + + if (getDelta(first, attr, type) > tolerance) + return false; + } + + if (sampler.interpolation == cgltf_interpolation_type_cubic_spline) + { + for (size_t i = 0; i < sampler.input->count; ++i) + { + for (int k = 0; k < 2; ++k) + { + Attr t = {}; + cgltf_accessor_read_float(sampler.output, (i * components + j) * 3 + k * 2, t.f, 4); + + float error = fabsf(t.f[0]) + fabsf(t.f[1]) + fabsf(t.f[2]) + fabsf(t.f[3]); + + if (error > tolerance) + return false; + } + } + } + } + + return true; +} + +Attr interpolateLinear(const Attr& l, const Attr& r, float t, cgltf_animation_path_type type) +{ + if (type == cgltf_animation_path_type_rotation) + { + // Approximating slerp, https://zeux.io/2015/07/23/approximating-slerp/ + // We also handle quaternion double-cover + float ca = l.f[0] * r.f[0] + l.f[1] * r.f[1] + l.f[2] * r.f[2] + l.f[3] * r.f[3]; + + float d = fabsf(ca); + float A = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f)); + float B = 0.848013f + d * (-1.06021f + d * 0.215638f); + float k = A * (t - 0.5f) * (t - 0.5f) + B; + float ot = t + t * (t - 0.5f) * (t - 1) * k; + + float t0 = 1 - ot; + float t1 = ca > 0 ? ot : -ot; + + Attr lerp = {{ + l.f[0] * t0 + r.f[0] * t1, + l.f[1] * t0 + r.f[1] * t1, + l.f[2] * t0 + r.f[2] * t1, + l.f[3] * t0 + r.f[3] * t1, + }}; + + float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); + + if (len > 0.f) + { + lerp.f[0] /= len; + lerp.f[1] /= len; + lerp.f[2] /= len; + lerp.f[3] /= len; + } + + return lerp; + } + else + { + Attr lerp = {{ + l.f[0] * (1 - t) + r.f[0] * t, + l.f[1] * (1 - t) + r.f[1] * t, + l.f[2] * (1 - t) + r.f[2] * t, + l.f[3] * (1 - t) + r.f[3] * t, + }}; + + return lerp; + } +} + +Attr interpolateHermite(const Attr& v0, const Attr& t0, const Attr& v1, const Attr& t1, float t, float dt, cgltf_animation_path_type type) +{ + float s0 = 1 + t * t * (2 * t - 3); + float s1 = t + t * t * (t - 2); + float s2 = 1 - s0; + float s3 = t * t * (t - 1); + + float ts1 = dt * s1; + float ts3 = dt * s3; + + Attr lerp = {{ + s0 * v0.f[0] + ts1 * t0.f[0] + s2 * v1.f[0] + ts3 * t1.f[0], + s0 * v0.f[1] + ts1 * t0.f[1] + s2 * v1.f[1] + ts3 * t1.f[1], + s0 * v0.f[2] + ts1 * t0.f[2] + s2 * v1.f[2] + ts3 * t1.f[2], + s0 * v0.f[3] + ts1 * t0.f[3] + s2 * v1.f[3] + ts3 * t1.f[3], + }}; + + if (type == cgltf_animation_path_type_rotation) + { + float len = sqrtf(lerp.f[0] * lerp.f[0] + lerp.f[1] * lerp.f[1] + lerp.f[2] * lerp.f[2] + lerp.f[3] * lerp.f[3]); + + if (len > 0.f) + { + lerp.f[0] /= len; + lerp.f[1] /= len; + lerp.f[2] /= len; + lerp.f[3] /= len; + } + } + + return lerp; +} + +void resampleKeyframes(std::vector<Attr>& data, const cgltf_animation_sampler& sampler, cgltf_animation_path_type type, cgltf_node* target_node, int frames, float mint, int freq) +{ + size_t components = (type == cgltf_animation_path_type_weights) ? target_node->mesh->primitives[0].targets_count : 1; + + size_t cursor = 0; + + for (int i = 0; i < frames; ++i) + { + float time = mint + float(i) / freq; + + while (cursor + 1 < sampler.input->count) + { + float next_time = 0; + cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1); + + if (next_time > time) + break; + + cursor++; + } + + if (cursor + 1 < sampler.input->count) + { + float cursor_time = 0; + float next_time = 0; + cgltf_accessor_read_float(sampler.input, cursor + 0, &cursor_time, 1); + cgltf_accessor_read_float(sampler.input, cursor + 1, &next_time, 1); + + float range = next_time - cursor_time; + float inv_range = (range == 0.f) ? 0.f : 1.f / (next_time - cursor_time); + float t = std::max(0.f, std::min(1.f, (time - cursor_time) * inv_range)); + + for (size_t j = 0; j < components; ++j) + { + switch (sampler.interpolation) + { + case cgltf_interpolation_type_linear: + { + Attr v0 = {}; + Attr v1 = {}; + cgltf_accessor_read_float(sampler.output, (cursor + 0) * components + j, v0.f, 4); + cgltf_accessor_read_float(sampler.output, (cursor + 1) * components + j, v1.f, 4); + data.push_back(interpolateLinear(v0, v1, t, type)); + } + break; + + case cgltf_interpolation_type_step: + { + Attr v = {}; + cgltf_accessor_read_float(sampler.output, cursor * components + j, v.f, 4); + data.push_back(v); + } + break; + + case cgltf_interpolation_type_cubic_spline: + { + Attr v0 = {}; + Attr b0 = {}; + Attr a1 = {}; + Attr v1 = {}; + cgltf_accessor_read_float(sampler.output, (cursor * 3 + 1) * components + j, v0.f, 4); + cgltf_accessor_read_float(sampler.output, (cursor * 3 + 2) * components + j, b0.f, 4); + cgltf_accessor_read_float(sampler.output, (cursor * 3 + 3) * components + j, a1.f, 4); + cgltf_accessor_read_float(sampler.output, (cursor * 3 + 4) * components + j, v1.f, 4); + data.push_back(interpolateHermite(v0, b0, v1, a1, t, range, type)); + } + break; + + default: + assert(!"Unknown interpolation type"); + } + } + } + else + { + size_t offset = (sampler.interpolation == cgltf_interpolation_type_cubic_spline) ? cursor * 3 + 1 : cursor; + + for (size_t j = 0; j < components; ++j) + { + Attr v = {}; + cgltf_accessor_read_float(sampler.output, offset * components + j, v.f, 4); + data.push_back(v); + } + } + } +} + +void markAnimated(cgltf_data* data, std::vector<NodeInfo>& nodes) +{ + for (size_t i = 0; i < data->animations_count; ++i) + { + const cgltf_animation& animation = data->animations[i]; + + for (size_t j = 0; j < animation.channels_count; ++j) + { + const cgltf_animation_channel& channel = animation.channels[j]; + const cgltf_animation_sampler& sampler = *channel.sampler; + + if (!channel.target_node) + continue; + + NodeInfo& ni = nodes[channel.target_node - data->nodes]; + + // mark nodes that have animation tracks that change their base transform as animated + if (!isTrackConstant(sampler, channel.target_path, channel.target_node)) + { + ni.animated_paths |= (1 << channel.target_path); + } + else + { + Attr base = {}; + + switch (channel.target_path) + { + case cgltf_animation_path_type_translation: + memcpy(base.f, channel.target_node->translation, 3 * sizeof(float)); + break; + case cgltf_animation_path_type_rotation: + memcpy(base.f, channel.target_node->rotation, 4 * sizeof(float)); + break; + case cgltf_animation_path_type_scale: + memcpy(base.f, channel.target_node->scale, 3 * sizeof(float)); + break; + default:; + } + + Attr first = {}; + cgltf_accessor_read_float(sampler.output, 0, first.f, 4); + + const float tolerance = 1e-3f; + + if (getDelta(base, first, channel.target_path) > tolerance) + { + ni.animated_paths |= (1 << channel.target_path); + } + } + } + } + + for (size_t i = 0; i < data->nodes_count; ++i) + { + NodeInfo& ni = nodes[i]; + + for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) + ni.animated |= nodes[node - data->nodes].animated_paths != 0; + } +} + +void markNeededNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, const std::vector<Mesh>& meshes, const Settings& settings) +{ + // mark all joints as kept + for (size_t i = 0; i < data->skins_count; ++i) + { + const cgltf_skin& skin = data->skins[i]; + + // for now we keep all joints directly referenced by the skin and the entire ancestry tree; we keep names for joints as well + for (size_t j = 0; j < skin.joints_count; ++j) + { + NodeInfo& ni = nodes[skin.joints[j] - data->nodes]; + + ni.keep = true; + } + } + + // mark all animated nodes as kept + for (size_t i = 0; i < data->animations_count; ++i) + { + const cgltf_animation& animation = data->animations[i]; + + for (size_t j = 0; j < animation.channels_count; ++j) + { + const cgltf_animation_channel& channel = animation.channels[j]; + + if (channel.target_node) + { + NodeInfo& ni = nodes[channel.target_node - data->nodes]; + + ni.keep = true; + } + } + } + + // mark all mesh nodes as kept + for (size_t i = 0; i < meshes.size(); ++i) + { + const Mesh& mesh = meshes[i]; + + if (mesh.node) + { + NodeInfo& ni = nodes[mesh.node - data->nodes]; + + ni.keep = true; + } + } + + // mark all light/camera nodes as kept + for (size_t i = 0; i < data->nodes_count; ++i) + { + const cgltf_node& node = data->nodes[i]; + + if (node.light || node.camera) + { + nodes[i].keep = true; + } + } + + // mark all named nodes as needed (if -kn is specified) + if (settings.keep_named) + { + for (size_t i = 0; i < data->nodes_count; ++i) + { + const cgltf_node& node = data->nodes[i]; + + if (node.name && *node.name) + { + nodes[i].keep = true; + } + } + } +} + +void markNeededMaterials(cgltf_data* data, std::vector<MaterialInfo>& materials, const std::vector<Mesh>& meshes) +{ + // mark all used materials as kept + for (size_t i = 0; i < meshes.size(); ++i) + { + const Mesh& mesh = meshes[i]; + + if (mesh.material) + { + MaterialInfo& mi = materials[mesh.material - data->materials]; + + mi.keep = true; + } + } +} + +void remapNodes(cgltf_data* data, std::vector<NodeInfo>& nodes, size_t& node_offset) +{ + // to keep a node, we currently need to keep the entire ancestry chain + for (size_t i = 0; i < data->nodes_count; ++i) + { + if (!nodes[i].keep) + continue; + + for (cgltf_node* node = &data->nodes[i]; node; node = node->parent) + nodes[node - data->nodes].keep = true; + } + + // generate sequential indices for all nodes; they aren't sorted topologically + for (size_t i = 0; i < data->nodes_count; ++i) + { + NodeInfo& ni = nodes[i]; + + if (ni.keep) + { + ni.remap = int(node_offset); + + node_offset++; + } + } +} + +bool parseDataUri(const char* uri, std::string& mime_type, std::string& result) +{ + if (strncmp(uri, "data:", 5) == 0) + { + const char* comma = strchr(uri, ','); + + if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0) + { + const char* base64 = comma + 1; + size_t base64_size = strlen(base64); + size_t size = base64_size - base64_size / 4; + + if (base64_size >= 2) + { + size -= base64[base64_size - 2] == '='; + size -= base64[base64_size - 1] == '='; + } + + void* data = 0; + + cgltf_options options = {}; + cgltf_result res = cgltf_load_buffer_base64(&options, size, base64, &data); + + if (res != cgltf_result_success) + return false; + + mime_type = std::string(uri + 5, comma - 7); + result = std::string(static_cast<const char*>(data), size); + + free(data); + + return true; + } + } + + return false; +} + +void writeEmbeddedImage(std::string& json, std::vector<BufferView>& views, const char* data, size_t size, const char* mime_type) +{ + size_t view = getBufferView(views, BufferView::Kind_Image, -1, 1, false); + + assert(views[view].data.empty()); + views[view].data.assign(data, size); + + append(json, "\"bufferView\":"); + append(json, view); + append(json, ",\"mimeType\":\""); + append(json, mime_type); + append(json, "\""); +} + +void writeMeshAttributes(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, int target, const QuantizationParams& qp, const Settings& settings) +{ + std::string scratch; + + for (size_t j = 0; j < mesh.streams.size(); ++j) + { + const Stream& stream = mesh.streams[j]; + + if (stream.target != target) + continue; + + if (stream.type == cgltf_attribute_type_texcoord && (!mesh.material || !usesTextureSet(*mesh.material, stream.index))) + continue; + + if (stream.type == cgltf_attribute_type_tangent && (!mesh.material || !mesh.material->normal_texture.texture)) + continue; + + if ((stream.type == cgltf_attribute_type_joints || stream.type == cgltf_attribute_type_weights) && !mesh.skin) + continue; + + scratch.clear(); + StreamFormat format = writeVertexStream(scratch, stream, qp, settings, mesh.targets > 0); + + size_t view = getBufferView(views, BufferView::Kind_Vertex, stream.type, format.stride, settings.compress); + size_t offset = views[view].data.size(); + views[view].data += scratch; + + comma(json_accessors); + if (stream.type == cgltf_attribute_type_position) + { + int min[3] = {}; + int max[3] = {}; + getPositionBounds(min, max, stream, qp); + + float minf[3] = {float(min[0]), float(min[1]), float(min[2])}; + float maxf[3] = {float(max[0]), float(max[1]), float(max[2])}; + + writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size(), minf, maxf, 3); + } + else + { + writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, stream.data.size()); + } + + size_t vertex_accr = accr_offset++; + + comma(json); + append(json, "\""); + append(json, attributeType(stream.type)); + if (stream.type != cgltf_attribute_type_position && stream.type != cgltf_attribute_type_normal && stream.type != cgltf_attribute_type_tangent) + { + append(json, "_"); + append(json, size_t(stream.index)); + } + append(json, "\":"); + append(json, vertex_accr); + } +} + +size_t writeMeshIndices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const Mesh& mesh, const Settings& settings) +{ + std::string scratch; + StreamFormat format = writeIndexStream(scratch, mesh.indices); + + // note: we prefer to merge all index streams together; however, index codec currently doesn't handle concatenated index streams well and loses compression ratio + int variant = settings.compress ? -1 : 0; + + size_t view = getBufferView(views, BufferView::Kind_Index, variant, format.stride, settings.compress); + size_t offset = views[view].data.size(); + views[view].data += scratch; + + comma(json_accessors); + writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, mesh.indices.size()); + + size_t index_accr = accr_offset++; + + return index_accr; +} + +size_t writeAnimationTime(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, float mint, int frames, const Settings& settings) +{ + std::vector<float> time(frames); + + for (int j = 0; j < frames; ++j) + time[j] = mint + float(j) / settings.anim_freq; + + std::string scratch; + StreamFormat format = writeTimeStream(scratch, time); + + size_t view = getBufferView(views, BufferView::Kind_Time, 0, format.stride, settings.compress); + size_t offset = views[view].data.size(); + views[view].data += scratch; + + comma(json_accessors); + writeAccessor(json_accessors, view, offset, cgltf_type_scalar, format.component_type, format.normalized, frames, &time.front(), &time.back(), 1); + + size_t time_accr = accr_offset++; + + return time_accr; +} + +size_t writeJointBindMatrices(std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_skin& skin, const QuantizationParams& qp, const Settings& settings) +{ + std::string scratch; + + for (size_t j = 0; j < skin.joints_count; ++j) + { + float transform[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + + if (skin.inverse_bind_matrices) + { + cgltf_accessor_read_float(skin.inverse_bind_matrices, j, transform, 16); + } + + float node_scale = qp.pos_scale / float((1 << qp.pos_bits) - 1); + + // pos_offset has to be applied first, thus it results in an offset rotated by the bind matrix + transform[12] += qp.pos_offset[0] * transform[0] + qp.pos_offset[1] * transform[4] + qp.pos_offset[2] * transform[8]; + transform[13] += qp.pos_offset[0] * transform[1] + qp.pos_offset[1] * transform[5] + qp.pos_offset[2] * transform[9]; + transform[14] += qp.pos_offset[0] * transform[2] + qp.pos_offset[1] * transform[6] + qp.pos_offset[2] * transform[10]; + + // node_scale will be applied before the rotation/scale from transform + for (int k = 0; k < 12; ++k) + transform[k] *= node_scale; + + scratch.append(reinterpret_cast<const char*>(transform), sizeof(transform)); + } + + size_t view = getBufferView(views, BufferView::Kind_Skin, 0, 64, settings.compress); + size_t offset = views[view].data.size(); + views[view].data += scratch; + + comma(json_accessors); + writeAccessor(json_accessors, view, offset, cgltf_type_mat4, cgltf_component_type_r_32f, false, skin.joints_count); + + size_t matrix_accr = accr_offset++; + + return matrix_accr; +} + +void writeMeshNode(std::string& json, size_t mesh_offset, const Mesh& mesh, cgltf_data* data, const QuantizationParams& qp) +{ + float node_scale = qp.pos_scale / float((1 << qp.pos_bits) - 1); + + comma(json); + append(json, "{\"mesh\":"); + append(json, mesh_offset); + if (mesh.skin) + { + comma(json); + append(json, "\"skin\":"); + append(json, size_t(mesh.skin - data->skins)); + } + append(json, ",\"translation\":["); + append(json, qp.pos_offset[0]); + append(json, ","); + append(json, qp.pos_offset[1]); + append(json, ","); + append(json, qp.pos_offset[2]); + append(json, "],\"scale\":["); + append(json, node_scale); + append(json, ","); + append(json, node_scale); + append(json, ","); + append(json, node_scale); + append(json, "]"); + if (mesh.node && mesh.node->weights_count) + { + append(json, ",\"weights\":["); + for (size_t j = 0; j < mesh.node->weights_count; ++j) + { + comma(json); + append(json, mesh.node->weights[j]); + } + append(json, "]"); + } + append(json, "}"); +} + +void writeNode(std::string& json, const cgltf_node& node, const std::vector<NodeInfo>& nodes, cgltf_data* data) +{ + const NodeInfo& ni = nodes[&node - data->nodes]; + + comma(json); + append(json, "{"); + if (node.name && *node.name) + { + comma(json); + append(json, "\"name\":\""); + append(json, node.name); + append(json, "\""); + } + if (node.has_translation) + { + comma(json); + append(json, "\"translation\":["); + append(json, node.translation[0]); + append(json, ","); + append(json, node.translation[1]); + append(json, ","); + append(json, node.translation[2]); + append(json, "]"); + } + if (node.has_rotation) + { + comma(json); + append(json, "\"rotation\":["); + append(json, node.rotation[0]); + append(json, ","); + append(json, node.rotation[1]); + append(json, ","); + append(json, node.rotation[2]); + append(json, ","); + append(json, node.rotation[3]); + append(json, "]"); + } + if (node.has_scale) + { + comma(json); + append(json, "\"scale\":["); + append(json, node.scale[0]); + append(json, ","); + append(json, node.scale[1]); + append(json, ","); + append(json, node.scale[2]); + append(json, "]"); + } + if (node.has_matrix) + { + comma(json); + append(json, "\"matrix\":["); + for (int k = 0; k < 16; ++k) + { + comma(json); + append(json, node.matrix[k]); + } + append(json, "]"); + } + if (node.children_count || !ni.meshes.empty()) + { + comma(json); + append(json, "\"children\":["); + for (size_t j = 0; j < node.children_count; ++j) + { + const NodeInfo& ci = nodes[node.children[j] - data->nodes]; + + if (ci.keep) + { + comma(json); + append(json, size_t(ci.remap)); + } + } + for (size_t j = 0; j < ni.meshes.size(); ++j) + { + comma(json); + append(json, ni.meshes[j]); + } + append(json, "]"); + } + if (node.camera) + { + comma(json); + append(json, "\"camera\":"); + append(json, size_t(node.camera - data->cameras)); + } + if (node.light) + { + comma(json); + append(json, "\"extensions\":{\"KHR_lights_punctual\":{\"light\":"); + append(json, size_t(node.light - data->lights)); + append(json, "}}"); + } + append(json, "}"); +} + +void writeAnimation(std::string& json, std::vector<BufferView>& views, std::string& json_accessors, size_t& accr_offset, const cgltf_animation& animation, cgltf_data* data, const std::vector<NodeInfo>& nodes, const Settings& settings) +{ + std::vector<const cgltf_animation_channel*> tracks; + + for (size_t j = 0; j < animation.channels_count; ++j) + { + const cgltf_animation_channel& channel = animation.channels[j]; + + if (!channel.target_node) + { + fprintf(stderr, "Warning: ignoring channel %d of animation %d because it has no target node\n", int(j), int(&animation - data->animations)); + continue; + } + + const NodeInfo& ni = nodes[channel.target_node - data->nodes]; + + if (!ni.keep) + continue; + + if (!settings.anim_const && (ni.animated_paths & (1 << channel.target_path)) == 0) + continue; + + tracks.push_back(&channel); + } + + if (tracks.empty()) + { + fprintf(stderr, "Warning: ignoring animation %d because it has no valid tracks\n", int(&animation - data->animations)); + return; + } + + float mint = 0, maxt = 0; + bool needs_time = false; + bool needs_pose = false; + + for (size_t j = 0; j < tracks.size(); ++j) + { + const cgltf_animation_channel& channel = *tracks[j]; + const cgltf_animation_sampler& sampler = *channel.sampler; + + mint = std::min(mint, sampler.input->min[0]); + maxt = std::max(maxt, sampler.input->max[0]); + + bool tc = isTrackConstant(sampler, channel.target_path, channel.target_node); + + needs_time = needs_time || !tc; + needs_pose = needs_pose || tc; + } + + // round the number of frames to nearest but favor the "up" direction + // this means that at 10 Hz resampling, we will try to preserve the last frame <10ms + // but if the last frame is <2ms we favor just removing this data + int frames = 1 + int((maxt - mint) * settings.anim_freq + 0.8f); + + size_t time_accr = needs_time ? writeAnimationTime(views, json_accessors, accr_offset, mint, frames, settings) : 0; + size_t pose_accr = needs_pose ? writeAnimationTime(views, json_accessors, accr_offset, mint, 1, settings) : 0; + + std::string json_samplers; + std::string json_channels; + + size_t track_offset = 0; + + for (size_t j = 0; j < tracks.size(); ++j) + { + const cgltf_animation_channel& channel = *tracks[j]; + const cgltf_animation_sampler& sampler = *channel.sampler; + + bool tc = isTrackConstant(sampler, channel.target_path, channel.target_node); + + std::vector<Attr> track; + resampleKeyframes(track, sampler, channel.target_path, channel.target_node, tc ? 1 : frames, mint, settings.anim_freq); + + std::string scratch; + StreamFormat format = writeKeyframeStream(scratch, channel.target_path, track); + + size_t view = getBufferView(views, BufferView::Kind_Keyframe, channel.target_path, format.stride, settings.compress && channel.target_path != cgltf_animation_path_type_weights); + size_t offset = views[view].data.size(); + views[view].data += scratch; + + comma(json_accessors); + writeAccessor(json_accessors, view, offset, format.type, format.component_type, format.normalized, track.size()); + + size_t data_accr = accr_offset++; + + comma(json_samplers); + append(json_samplers, "{\"input\":"); + append(json_samplers, tc ? pose_accr : time_accr); + append(json_samplers, ",\"output\":"); + append(json_samplers, data_accr); + append(json_samplers, "}"); + + const NodeInfo& tni = nodes[channel.target_node - data->nodes]; + size_t target_node = size_t(tni.remap); + + if (channel.target_path == cgltf_animation_path_type_weights) + { + assert(tni.meshes.size() == 1); + target_node = tni.meshes[0]; + } + + comma(json_channels); + append(json_channels, "{\"sampler\":"); + append(json_channels, track_offset); + append(json_channels, ",\"target\":{\"node\":"); + append(json_channels, target_node); + append(json_channels, ",\"path\":\""); + append(json_channels, animationPath(channel.target_path)); + append(json_channels, "\"}}"); + + track_offset++; + } + + comma(json); + append(json, "{"); + if (animation.name && *animation.name) + { + append(json, "\"name\":\""); + append(json, animation.name); + append(json, "\","); + } + append(json, "\"samplers\":["); + append(json, json_samplers); + append(json, "],\"channels\":["); + append(json, json_channels); + append(json, "]}"); +} + +void writeCamera(std::string& json, const cgltf_camera& camera) +{ + comma(json); + append(json, "{"); + + switch (camera.type) + { + case cgltf_camera_type_perspective: + append(json, "\"type\":\"perspective\",\"perspective\":{"); + append(json, "\"yfov\":"); + append(json, camera.perspective.yfov); + append(json, ",\"znear\":"); + append(json, camera.perspective.znear); + if (camera.perspective.aspect_ratio != 0.f) + { + append(json, ",\"aspectRatio\":"); + append(json, camera.perspective.aspect_ratio); + } + if (camera.perspective.zfar != 0.f) + { + append(json, ",\"zfar\":"); + append(json, camera.perspective.zfar); + } + append(json, "}"); + break; + + case cgltf_camera_type_orthographic: + append(json, "\"type\":\"orthographic\",\"orthographic\":{"); + append(json, "\"xmag\":"); + append(json, camera.orthographic.xmag); + append(json, ",\"ymag\":"); + append(json, camera.orthographic.ymag); + append(json, ",\"znear\":"); + append(json, camera.orthographic.znear); + append(json, ",\"zfar\":"); + append(json, camera.orthographic.zfar); + append(json, "}"); + break; + + default: + fprintf(stderr, "Warning: skipping camera of unknown type\n"); + } + + append(json, "}"); +} + +void writeLight(std::string& json, const cgltf_light& light) +{ + static const float white[3] = {1, 1, 1}; + + comma(json); + append(json, "{\"type\":\""); + append(json, lightType(light.type)); + append(json, "\""); + if (memcmp(light.color, white, sizeof(white)) != 0) + { + comma(json); + append(json, "\"color\":["); + append(json, light.color[0]); + append(json, ","); + append(json, light.color[1]); + append(json, ","); + append(json, light.color[2]); + append(json, "]"); + } + if (light.intensity != 1.f) + { + comma(json); + append(json, "\"intensity\":"); + append(json, light.intensity); + } + if (light.range != 0.f) + { + comma(json); + append(json, "\"range\":"); + append(json, light.range); + } + if (light.type == cgltf_light_type_spot) + { + comma(json); + append(json, "\"spot\":{"); + append(json, "\"innerConeAngle\":"); + append(json, light.spot_inner_cone_angle); + append(json, ",\"outerConeAngle\":"); + append(json, light.spot_outer_cone_angle == 0.f ? 0.78539816339f : light.spot_outer_cone_angle); + append(json, "}"); + } + append(json, "}"); +} + +void printMeshStats(const std::vector<Mesh>& meshes, const char* name) +{ + size_t triangles = 0; + size_t vertices = 0; + + for (size_t i = 0; i < meshes.size(); ++i) + { + const Mesh& mesh = meshes[i]; + + triangles += mesh.indices.size() / 3; + vertices += mesh.streams.empty() ? 0 : mesh.streams[0].data.size(); + } + + printf("%s: %d triangles, %d vertices\n", name, int(triangles), int(vertices)); +} + +void printAttributeStats(const std::vector<BufferView>& views, BufferView::Kind kind, const char* name) +{ + for (size_t i = 0; i < views.size(); ++i) + { + const BufferView& view = views[i]; + + if (view.kind != kind) + continue; + + const char* variant = "unknown"; + + switch (kind) + { + case BufferView::Kind_Vertex: + variant = attributeType(cgltf_attribute_type(view.variant)); + break; + + case BufferView::Kind_Index: + variant = "index"; + break; + + case BufferView::Kind_Keyframe: + variant = animationPath(cgltf_animation_path_type(view.variant)); + break; + + default:; + } + + size_t count = view.data.size() / view.stride; + + printf("stats: %s %s: compressed %d bytes (%.1f bits), raw %d bytes (%d bits)\n", + name, + variant, + int(view.bytes), + double(view.bytes) / double(count) * 8, + int(view.data.size()), + int(view.stride * 8)); + } +} + +void process(cgltf_data* data, std::vector<Mesh>& meshes, const Settings& settings, std::string& json, std::string& bin) +{ + if (settings.verbose) + { + printf("input: %d nodes, %d meshes (%d primitives), %d materials, %d skins, %d animations\n", + int(data->nodes_count), int(data->meshes_count), int(meshes.size()), int(data->materials_count), int(data->skins_count), int(data->animations_count)); + } + + std::vector<NodeInfo> nodes(data->nodes_count); + + markAnimated(data, nodes); + + for (size_t i = 0; i < meshes.size(); ++i) + { + Mesh& mesh = meshes[i]; + + // note: when -kn is specified, we keep mesh-node attachment so that named nodes can be transformed + if (mesh.node && !settings.keep_named) + { + NodeInfo& ni = nodes[mesh.node - data->nodes]; + + // we transform all non-skinned non-animated meshes to world space + // this makes sure that quantization doesn't introduce gaps if the original scene was watertight + if (!ni.animated && !mesh.skin && mesh.targets == 0) + { + transformMesh(mesh, mesh.node); + mesh.node = 0; + } + + // skinned and animated meshes will be anchored to the same node that they used to be in + // for animated meshes, this is important since they need to be transformed by the same animation + // for skinned meshes, in theory this isn't important since the transform of the skinned node doesn't matter; in practice this affects generated bounding box in three.js + } + } + + mergeMeshMaterials(data, meshes); + mergeMeshes(meshes, settings); + + markNeededNodes(data, nodes, meshes, settings); + + std::vector<MaterialInfo> materials(data->materials_count); + + markNeededMaterials(data, materials, meshes); + + if (settings.verbose) + { + printMeshStats(meshes, "input"); + } + + for (size_t i = 0; i < meshes.size(); ++i) + { + Mesh& mesh = meshes[i]; + + switch (mesh.type) + { + case cgltf_primitive_type_points: + assert(mesh.indices.empty()); + simplifyPointMesh(mesh, settings.simplify_threshold); + sortPointMesh(mesh); + break; + + case cgltf_primitive_type_triangles: + reindexMesh(mesh); + simplifyMesh(mesh, settings.simplify_threshold, settings.simplify_aggressive); + optimizeMesh(mesh); + break; + + default: + assert(!"Unknown primitive type"); + } + } + + if (settings.verbose) + { + printMeshStats(meshes, "output"); + } + + QuantizationParams qp = prepareQuantization(meshes, settings); + + std::string json_images; + std::string json_textures; + std::string json_materials; + std::string json_accessors; + std::string json_meshes; + std::string json_nodes; + std::string json_skins; + std::string json_roots; + std::string json_animations; + std::string json_cameras; + std::string json_lights; + + std::vector<BufferView> views; + + bool ext_pbr_specular_glossiness = false; + bool ext_unlit = false; + + size_t accr_offset = 0; + size_t node_offset = 0; + size_t mesh_offset = 0; + size_t material_offset = 0; + + for (size_t i = 0; i < data->images_count; ++i) + { + const cgltf_image& image = data->images[i]; + + comma(json_images); + append(json_images, "{"); + if (image.uri) + { + std::string mime_type; + std::string img; + + if (parseDataUri(image.uri, mime_type, img)) + { + writeEmbeddedImage(json_images, views, img.c_str(), img.size(), mime_type.c_str()); + } + else + { + append(json_images, "\"uri\":\""); + append(json_images, image.uri); + append(json_images, "\""); + } + } + else if (image.buffer_view && image.buffer_view->buffer->data && image.mime_type) + { + const char* img = static_cast<const char*>(image.buffer_view->buffer->data) + image.buffer_view->offset; + size_t size = image.buffer_view->size; + + writeEmbeddedImage(json_images, views, img, size, image.mime_type); + } + else + { + fprintf(stderr, "Warning: ignoring image %d since it has no URI and no valid buffer data\n", int(i)); + } + + append(json_images, "}"); + } + + for (size_t i = 0; i < data->textures_count; ++i) + { + const cgltf_texture& texture = data->textures[i]; + + comma(json_textures); + append(json_textures, "{"); + if (texture.image) + { + append(json_textures, "\"source\":"); + append(json_textures, size_t(texture.image - data->images)); + } + append(json_textures, "}"); + } + + for (size_t i = 0; i < data->materials_count; ++i) + { + MaterialInfo& mi = materials[i]; + + if (!mi.keep) + continue; + + const cgltf_material& material = data->materials[i]; + + comma(json_materials); + append(json_materials, "{"); + writeMaterialInfo(json_materials, data, material, qp); + append(json_materials, "}"); + + mi.remap = int(material_offset); + material_offset++; + + ext_pbr_specular_glossiness = ext_pbr_specular_glossiness || material.has_pbr_specular_glossiness; + ext_unlit = ext_unlit || material.unlit; + } + + for (size_t i = 0; i < meshes.size(); ++i) + { + const Mesh& mesh = meshes[i]; + + comma(json_meshes); + append(json_meshes, "{\"primitives\":["); + + size_t pi = i; + for (; pi < meshes.size(); ++pi) + { + const Mesh& prim = meshes[pi]; + + if (prim.node != mesh.node || prim.skin != mesh.skin || prim.targets != mesh.targets) + break; + + if (!compareMeshTargets(mesh, prim)) + break; + + comma(json_meshes); + append(json_meshes, "{\"attributes\":{"); + writeMeshAttributes(json_meshes, views, json_accessors, accr_offset, prim, 0, qp, settings); + append(json_meshes, "}"); + append(json_meshes, ",\"mode\":"); + append(json_meshes, size_t(prim.type)); + + if (mesh.targets) + { + append(json_meshes, ",\"targets\":["); + for (size_t j = 0; j < mesh.targets; ++j) + { + comma(json_meshes); + append(json_meshes, "{"); + writeMeshAttributes(json_meshes, views, json_accessors, accr_offset, prim, int(1 + j), qp, settings); + append(json_meshes, "}"); + } + append(json_meshes, "]"); + } + + if (!prim.indices.empty()) + { + size_t index_accr = writeMeshIndices(views, json_accessors, accr_offset, prim, settings); + + append(json_meshes, ",\"indices\":"); + append(json_meshes, index_accr); + } + + if (prim.material) + { + MaterialInfo& mi = materials[prim.material - data->materials]; + + assert(mi.keep); + append(json_meshes, ",\"material\":"); + append(json_meshes, size_t(mi.remap)); + } + + append(json_meshes, "}"); + } + + append(json_meshes, "]"); + + if (mesh.target_weights.size()) + { + append(json_meshes, ",\"weights\":["); + for (size_t j = 0; j < mesh.target_weights.size(); ++j) + { + comma(json_meshes); + append(json_meshes, mesh.target_weights[j]); + } + append(json_meshes, "]"); + } + + if (mesh.target_names.size()) + { + append(json_meshes, ",\"extras\":{\"targetNames\":["); + for (size_t j = 0; j < mesh.target_names.size(); ++j) + { + comma(json_meshes); + append(json_meshes, "\""); + append(json_meshes, mesh.target_names[j]); + append(json_meshes, "\""); + } + append(json_meshes, "]}"); + } + + append(json_meshes, "}"); + + writeMeshNode(json_nodes, mesh_offset, mesh, data, qp); + + if (mesh.node) + { + NodeInfo& ni = nodes[mesh.node - data->nodes]; + + assert(ni.keep); + ni.meshes.push_back(node_offset); + } + else + { + comma(json_roots); + append(json_roots, node_offset); + } + + node_offset++; + mesh_offset++; + + // skip all meshes that we've written in this iteration + assert(pi > i); + i = pi - 1; + } + + remapNodes(data, nodes, node_offset); + + for (size_t i = 0; i < data->nodes_count; ++i) + { + NodeInfo& ni = nodes[i]; + + if (!ni.keep) + continue; + + const cgltf_node& node = data->nodes[i]; + + if (!node.parent) + { + comma(json_roots); + append(json_roots, size_t(ni.remap)); + } + + writeNode(json_nodes, node, nodes, data); + } + + for (size_t i = 0; i < data->skins_count; ++i) + { + const cgltf_skin& skin = data->skins[i]; + + size_t matrix_accr = writeJointBindMatrices(views, json_accessors, accr_offset, skin, qp, settings); + + comma(json_skins); + append(json_skins, "{"); + append(json_skins, "\"joints\":["); + for (size_t j = 0; j < skin.joints_count; ++j) + { + comma(json_skins); + append(json_skins, size_t(nodes[skin.joints[j] - data->nodes].remap)); + } + append(json_skins, "]"); + append(json_skins, ",\"inverseBindMatrices\":"); + append(json_skins, matrix_accr); + if (skin.skeleton) + { + comma(json_skins); + append(json_skins, "\"skeleton\":"); + append(json_skins, size_t(nodes[skin.skeleton - data->nodes].remap)); + } + append(json_skins, "}"); + } + + for (size_t i = 0; i < data->animations_count; ++i) + { + const cgltf_animation& animation = data->animations[i]; + + writeAnimation(json_animations, views, json_accessors, accr_offset, animation, data, nodes, settings); + } + + for (size_t i = 0; i < data->cameras_count; ++i) + { + const cgltf_camera& camera = data->cameras[i]; + + writeCamera(json_cameras, camera); + } + + for (size_t i = 0; i < data->lights_count; ++i) + { + const cgltf_light& light = data->lights[i]; + + writeLight(json_lights, light); + } + + char version[32]; + sprintf(version, "%d.%d", MESHOPTIMIZER_VERSION / 1000, (MESHOPTIMIZER_VERSION % 1000) / 10); + + append(json, "\"asset\":{"); + append(json, "\"version\":\"2.0\",\"generator\":\"gltfpack "); + append(json, version); + append(json, "\""); + if (data->asset.extras.start_offset) + { + append(json, ",\"extras\":"); + json.append(data->json + data->asset.extras.start_offset, data->json + data->asset.extras.end_offset); + } + append(json, "}"); + append(json, ",\"extensionsUsed\":["); + append(json, "\"MESHOPT_quantized_geometry\""); + if (settings.compress) + { + comma(json); + append(json, "\"MESHOPT_compression\""); + } + if (!json_textures.empty()) + { + comma(json); + append(json, "\"KHR_texture_transform\""); + } + if (ext_pbr_specular_glossiness) + { + comma(json); + append(json, "\"KHR_materials_pbrSpecularGlossiness\""); + } + if (ext_unlit) + { + comma(json); + append(json, "\"KHR_materials_unlit\""); + } + if (data->lights_count) + { + comma(json); + append(json, "\"KHR_lights_punctual\""); + } + append(json, "]"); + if (settings.compress) + { + append(json, ",\"extensionsRequired\":["); + // Note: ideally we should include MESHOPT_quantized_geometry in the required extension list (regardless of compression) + // This extension *only* allows the use of quantized attributes for positions/normals/etc. This happens to be supported + // by popular JS frameworks, however, Babylon.JS refuses to load files with unsupported required extensions. + // For now we don't include it in the list, which will be fixed at some point once this extension becomes official. + append(json, "\"MESHOPT_compression\""); + append(json, "]"); + } + + size_t bytes[BufferView::Kind_Count] = {}; + + if (!views.empty()) + { + append(json, ",\"bufferViews\":["); + for (size_t i = 0; i < views.size(); ++i) + { + BufferView& view = views[i]; + + size_t offset = bin.size(); + size_t count = view.data.size() / view.stride; + + int compression = -1; + + if (view.compressed) + { + if (view.kind == BufferView::Kind_Index) + { + compressIndexStream(bin, view.data, count, view.stride); + compression = 1; + } + else + { + compressVertexStream(bin, view.data, count, view.stride); + compression = 0; + } + } + else + { + bin += view.data; + } + + comma(json); + writeBufferView(json, view.kind, count, view.stride, offset, bin.size() - offset, compression); + + view.bytes = bin.size() - offset; + bytes[view.kind] += view.bytes; + + // align each bufferView by 4 bytes + bin.resize((bin.size() + 3) & ~3); + } + append(json, "]"); + } + if (!json_accessors.empty()) + { + append(json, ",\"accessors\":["); + append(json, json_accessors); + append(json, "]"); + } + if (!json_images.empty()) + { + append(json, ",\"images\":["); + append(json, json_images); + append(json, "]"); + } + if (!json_textures.empty()) + { + append(json, ",\"textures\":["); + append(json, json_textures); + append(json, "]"); + } + if (!json_materials.empty()) + { + append(json, ",\"materials\":["); + append(json, json_materials); + append(json, "]"); + } + if (!json_meshes.empty()) + { + append(json, ",\"meshes\":["); + append(json, json_meshes); + append(json, "]"); + } + if (!json_skins.empty()) + { + append(json, ",\"skins\":["); + append(json, json_skins); + append(json, "]"); + } + if (!json_animations.empty()) + { + append(json, ",\"animations\":["); + append(json, json_animations); + append(json, "]"); + } + if (!json_roots.empty()) + { + append(json, ",\"nodes\":["); + append(json, json_nodes); + append(json, "],\"scenes\":["); + append(json, "{\"nodes\":["); + append(json, json_roots); + append(json, "]}]"); + } + if (!json_cameras.empty()) + { + append(json, ",\"cameras\":["); + append(json, json_cameras); + append(json, "]"); + } + if (!json_lights.empty()) + { + append(json, ",\"extensions\":{\"KHR_lights_punctual\":{\"lights\":["); + append(json, json_lights); + append(json, "]}}"); + } + if (!json_roots.empty()) + { + append(json, ",\"scene\":0"); + } + + if (settings.verbose) + { + printf("output: %d nodes, %d meshes (%d primitives), %d materials\n", int(node_offset), int(mesh_offset), int(meshes.size()), int(material_offset)); + printf("output: JSON %d bytes, buffers %d bytes\n", int(json.size()), int(bin.size())); + printf("output: buffers: vertex %d bytes, index %d bytes, skin %d bytes, time %d bytes, keyframe %d bytes, image %d bytes\n", + int(bytes[BufferView::Kind_Vertex]), int(bytes[BufferView::Kind_Index]), int(bytes[BufferView::Kind_Skin]), + int(bytes[BufferView::Kind_Time]), int(bytes[BufferView::Kind_Keyframe]), int(bytes[BufferView::Kind_Image])); + } + + if (settings.verbose > 1) + { + printAttributeStats(views, BufferView::Kind_Vertex, "vertex"); + printAttributeStats(views, BufferView::Kind_Index, "index"); + printAttributeStats(views, BufferView::Kind_Keyframe, "keyframe"); + } +} + +void writeU32(FILE* out, uint32_t data) +{ + fwrite(&data, 4, 1, out); +} + +bool requiresExtension(cgltf_data* data, const char* name) +{ + for (size_t i = 0; i < data->extensions_required_count; ++i) + if (strcmp(data->extensions_required[i], name) == 0) + return true; + + return false; +} + +int gltfpack(const char* input, const char* output, const Settings& settings) +{ + cgltf_data* data = 0; + std::vector<Mesh> meshes; + + const char* iext = strrchr(input, '.'); + + if (iext && (strcmp(iext, ".gltf") == 0 || strcmp(iext, ".GLTF") == 0 || strcmp(iext, ".glb") == 0 || strcmp(iext, ".GLB") == 0)) + { + cgltf_options options = {}; + cgltf_result result = cgltf_parse_file(&options, input, &data); + result = (result == cgltf_result_success) ? cgltf_validate(data) : result; + result = (result == cgltf_result_success) ? cgltf_load_buffers(&options, data, input) : result; + + const char* error = NULL; + + if (result != cgltf_result_success) + error = getError(result); + else if (requiresExtension(data, "KHR_draco_mesh_compression")) + error = "file requires Draco mesh compression support"; + else if (requiresExtension(data, "MESHOPT_compression")) + error = "file has already been compressed using gltfpack"; + + if (error) + { + fprintf(stderr, "Error loading %s: %s\n", input, error); + cgltf_free(data); + return 2; + } + + parseMeshesGltf(data, meshes); + } + else if (iext && (strcmp(iext, ".obj") == 0 || strcmp(iext, ".OBJ") == 0)) + { + fastObjMesh* obj = fast_obj_read(input); + + if (!obj) + { + fprintf(stderr, "Error loading %s: file not found\n", input); + cgltf_free(data); + return 2; + } + + data = parseSceneObj(obj); + parseMeshesObj(obj, data, meshes); + + fast_obj_destroy(obj); + } + else + { + fprintf(stderr, "Error loading %s: unknown extension (expected .gltf or .glb or .obj)\n", input); + return 2; + } + + std::string json, bin; + process(data, meshes, settings, json, bin); + + cgltf_free(data); + + if (!output) + { + return 0; + } + + const char* oext = strrchr(output, '.'); + + if (oext && (strcmp(oext, ".gltf") == 0 || strcmp(oext, ".GLTF") == 0)) + { + std::string binpath = output; + binpath.replace(binpath.size() - 5, 5, ".bin"); + + std::string binname = binpath; + std::string::size_type slash = binname.find_last_of("/\\"); + if (slash != std::string::npos) + binname.erase(0, slash + 1); + + FILE* outjson = fopen(output, "wb"); + FILE* outbin = fopen(binpath.c_str(), "wb"); + if (!outjson || !outbin) + { + fprintf(stderr, "Error saving %s\n", output); + return 4; + } + + fprintf(outjson, "{\"buffers\":[{\"uri\":\"%s\",\"byteLength\":%zu}],", binname.c_str(), bin.size()); + fwrite(json.c_str(), json.size(), 1, outjson); + fprintf(outjson, "}"); + + fwrite(bin.c_str(), bin.size(), 1, outbin); + + fclose(outjson); + fclose(outbin); + } + else if (oext && (strcmp(oext, ".glb") == 0 || strcmp(oext, ".GLB") == 0)) + { + FILE* out = fopen(output, "wb"); + if (!out) + { + fprintf(stderr, "Error saving %s\n", output); + return 4; + } + + char bufferspec[64]; + sprintf(bufferspec, "{\"buffers\":[{\"byteLength\":%zu}],", bin.size()); + + json.insert(0, bufferspec); + json.push_back('}'); + + while (json.size() % 4) + json.push_back(' '); + + while (bin.size() % 4) + bin.push_back('\0'); + + writeU32(out, 0x46546C67); + writeU32(out, 2); + writeU32(out, uint32_t(12 + 8 + json.size() + 8 + bin.size())); + + writeU32(out, uint32_t(json.size())); + writeU32(out, 0x4E4F534A); + fwrite(json.c_str(), json.size(), 1, out); + + writeU32(out, uint32_t(bin.size())); + writeU32(out, 0x004E4942); + fwrite(bin.c_str(), bin.size(), 1, out); + + fclose(out); + } + else + { + fprintf(stderr, "Error saving %s: unknown extension (expected .gltf or .glb)\n", output); + return 4; + } + + return 0; +} + +int main(int argc, char** argv) +{ + Settings settings = {}; + settings.pos_bits = 14; + settings.tex_bits = 12; + settings.nrm_bits = 8; + settings.anim_freq = 30; + settings.simplify_threshold = 1.f; + + const char* input = 0; + const char* output = 0; + bool help = false; + int test = 0; + + for (int i = 1; i < argc; ++i) + { + const char* arg = argv[i]; + + if (strcmp(arg, "-vp") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) + { + settings.pos_bits = atoi(argv[++i]); + } + else if (strcmp(arg, "-vt") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) + { + settings.tex_bits = atoi(argv[++i]); + } + else if (strcmp(arg, "-vn") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) + { + settings.nrm_bits = atoi(argv[++i]); + } + else if (strcmp(arg, "-vu") == 0) + { + settings.nrm_unit = true; + } + else if (strcmp(arg, "-af") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) + { + settings.anim_freq = atoi(argv[++i]); + } + else if (strcmp(arg, "-ac") == 0) + { + settings.anim_const = true; + } + else if (strcmp(arg, "-kn") == 0) + { + settings.keep_named = true; + } + else if (strcmp(arg, "-si") == 0 && i + 1 < argc && isdigit(argv[i + 1][0])) + { + settings.simplify_threshold = float(atof(argv[++i])); + } + else if (strcmp(arg, "-sa") == 0) + { + settings.simplify_aggressive = true; + } + else if (strcmp(arg, "-i") == 0 && i + 1 < argc && !input) + { + input = argv[++i]; + } + else if (strcmp(arg, "-o") == 0 && i + 1 < argc && !output) + { + output = argv[++i]; + } + else if (strcmp(arg, "-c") == 0) + { + settings.compress = true; + } + else if (strcmp(arg, "-v") == 0) + { + settings.verbose = 1; + } + else if (strcmp(arg, "-vv") == 0) + { + settings.verbose = 2; + } + else if (strcmp(arg, "-h") == 0) + { + help = true; + } + else if (strcmp(arg, "-test") == 0) + { + test = i + 1; + break; + } + else + { + fprintf(stderr, "Unrecognized option %s\n", arg); + return 1; + } + } + + if (test) + { + for (int i = test; i < argc; ++i) + { + printf("%s\n", argv[i]); + gltfpack(argv[i], NULL, settings); + } + + return 0; + } + + if (!input || !output || help) + { + fprintf(stderr, "Usage: gltfpack [options] -i input -o output\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, "-i file: input file to process, .obj/.gltf/.glb\n"); + fprintf(stderr, "-o file: output file path, .gltf/.glb\n"); + fprintf(stderr, "-vp N: use N-bit quantization for positions (default: 14; N should be between 1 and 16)\n"); + fprintf(stderr, "-vt N: use N-bit quantization for texture corodinates (default: 12; N should be between 1 and 16)\n"); + fprintf(stderr, "-vn N: use N-bit quantization for normals and tangents (default: 8; N should be between 1 and 16)\n"); + fprintf(stderr, "-vu: use unit-length normal/tangent vectors (default: off)\n"); + fprintf(stderr, "-af N: resample animations at N Hz (default: 30)\n"); + fprintf(stderr, "-ac: keep constant animation tracks even if they don't modify the node transform\n"); + fprintf(stderr, "-kn: keep named nodes and meshes attached to named nodes so that named nodes can be transformed externally\n"); + fprintf(stderr, "-si R: simplify meshes to achieve the ratio R (default: 1; R should be between 0 and 1)\n"); + fprintf(stderr, "-sa: aggressively simplify to the target ratio disregarding quality\n"); + fprintf(stderr, "-c: produce compressed glb files\n"); + fprintf(stderr, "-v: verbose output\n"); + fprintf(stderr, "-h: display this help and exit\n"); + + return 1; + } + + return gltfpack(input, output, settings); +} diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/lodviewer.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/lodviewer.cpp new file mode 100644 index 00000000000..54dc4c90967 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/lodviewer.cpp @@ -0,0 +1,618 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include "../src/meshoptimizer.h" +#include "fast_obj.h" +#include "cgltf.h" + +#include <algorithm> +#include <cmath> +#include <cstdio> +#include <ctime> +#include <vector> + +#include <GLFW/glfw3.h> + +#ifdef _WIN32 +#pragma comment(lib, "opengl32.lib") +#endif + +extern unsigned char* meshopt_simplifyDebugKind; +extern unsigned int* meshopt_simplifyDebugLoop; + +#ifndef TRACE +unsigned char* meshopt_simplifyDebugKind; +unsigned int* meshopt_simplifyDebugLoop; +#endif + +struct Options +{ + bool wireframe; + enum + { + Mode_Default, + Mode_Texture, + Mode_Normals, + Mode_UV, + Mode_Kind, + } mode; +}; + +struct Vertex +{ + float px, py, pz; + float nx, ny, nz; + float tx, ty; +}; + +struct Mesh +{ + std::vector<Vertex> vertices; + std::vector<unsigned int> indices; + + // TODO: this is debug only visualization and will go away at some point + std::vector<unsigned char> kinds; + std::vector<unsigned int> loop; +}; + +Mesh parseObj(const char* path) +{ + fastObjMesh* obj = fast_obj_read(path); + if (!obj) + { + printf("Error loading %s: file not found\n", path); + return Mesh(); + } + + size_t total_indices = 0; + + for (unsigned int i = 0; i < obj->face_count; ++i) + total_indices += 3 * (obj->face_vertices[i] - 2); + + std::vector<Vertex> vertices(total_indices); + + size_t vertex_offset = 0; + size_t index_offset = 0; + + for (unsigned int i = 0; i < obj->face_count; ++i) + { + for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) + { + fastObjIndex gi = obj->indices[index_offset + j]; + + Vertex v = + { + obj->positions[gi.p * 3 + 0], + obj->positions[gi.p * 3 + 1], + obj->positions[gi.p * 3 + 2], + obj->normals[gi.n * 3 + 0], + obj->normals[gi.n * 3 + 1], + obj->normals[gi.n * 3 + 2], + obj->texcoords[gi.t * 2 + 0], + obj->texcoords[gi.t * 2 + 1], + }; + + // triangulate polygon on the fly; offset-3 is always the first polygon vertex + if (j >= 3) + { + vertices[vertex_offset + 0] = vertices[vertex_offset - 3]; + vertices[vertex_offset + 1] = vertices[vertex_offset - 1]; + vertex_offset += 2; + } + + vertices[vertex_offset] = v; + vertex_offset++; + } + + index_offset += obj->face_vertices[i]; + } + + fast_obj_destroy(obj); + + Mesh result; + + std::vector<unsigned int> remap(total_indices); + size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex)); + + result.indices.resize(total_indices); + meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]); + + result.vertices.resize(total_vertices); + meshopt_remapVertexBuffer(&result.vertices[0], &vertices[0], total_indices, sizeof(Vertex), &remap[0]); + + return result; +} + +cgltf_accessor* getAccessor(const cgltf_attribute* attributes, size_t attribute_count, cgltf_attribute_type type, int index = 0) +{ + for (size_t i = 0; i < attribute_count; ++i) + if (attributes[i].type == type && attributes[i].index == index) + return attributes[i].data; + + return 0; +} + +Mesh parseGltf(const char* path) +{ + cgltf_options options = {}; + cgltf_data* data = 0; + cgltf_result res = cgltf_parse_file(&options, path, &data); + + if (res != cgltf_result_success) + { + return Mesh(); + } + + res = cgltf_load_buffers(&options, data, path); + if (res != cgltf_result_success) + { + cgltf_free(data); + return Mesh(); + } + + res = cgltf_validate(data); + if (res != cgltf_result_success) + { + cgltf_free(data); + return Mesh(); + } + + size_t total_vertices = 0; + size_t total_indices = 0; + + for (size_t ni = 0; ni < data->nodes_count; ++ni) + { + if (!data->nodes[ni].mesh) + continue; + + const cgltf_mesh& mesh = *data->nodes[ni].mesh; + + for (size_t pi = 0; pi < mesh.primitives_count; ++pi) + { + const cgltf_primitive& primitive = mesh.primitives[pi]; + + cgltf_accessor* ai = primitive.indices; + cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position); + + if (!ai || !ap) + continue; + + total_vertices += ap->count; + total_indices += ai->count; + } + } + + Mesh result; + result.vertices.resize(total_vertices); + result.indices.resize(total_indices); + + size_t vertex_offset = 0; + size_t index_offset = 0; + + for (size_t ni = 0; ni < data->nodes_count; ++ni) + { + if (!data->nodes[ni].mesh) + continue; + + const cgltf_mesh& mesh = *data->nodes[ni].mesh; + + float transform[16]; + cgltf_node_transform_world(&data->nodes[ni], transform); + + for (size_t pi = 0; pi < mesh.primitives_count; ++pi) + { + const cgltf_primitive& primitive = mesh.primitives[pi]; + + cgltf_accessor* ai = primitive.indices; + cgltf_accessor* ap = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_position); + + if (!ai || !ap) + continue; + + for (size_t i = 0; i < ai->count; ++i) + result.indices[index_offset + i] = unsigned(vertex_offset + cgltf_accessor_read_index(ai, i)); + + { + for (size_t i = 0; i < ap->count; ++i) + { + float ptr[3]; + cgltf_accessor_read_float(ap, i, ptr, 3); + + result.vertices[vertex_offset + i].px = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8] + transform[12]; + result.vertices[vertex_offset + i].py = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9] + transform[13]; + result.vertices[vertex_offset + i].pz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10] + transform[14]; + } + } + + if (cgltf_accessor* an = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_normal)) + { + for (size_t i = 0; i < ap->count; ++i) + { + float ptr[3]; + cgltf_accessor_read_float(an, i, ptr, 3); + + result.vertices[vertex_offset + i].nx = ptr[0] * transform[0] + ptr[1] * transform[4] + ptr[2] * transform[8]; + result.vertices[vertex_offset + i].ny = ptr[0] * transform[1] + ptr[1] * transform[5] + ptr[2] * transform[9]; + result.vertices[vertex_offset + i].nz = ptr[0] * transform[2] + ptr[1] * transform[6] + ptr[2] * transform[10]; + } + } + + if (cgltf_accessor* at = getAccessor(primitive.attributes, primitive.attributes_count, cgltf_attribute_type_texcoord)) + { + for (size_t i = 0; i < ap->count; ++i) + { + float ptr[2]; + cgltf_accessor_read_float(at, i, ptr, 2); + + result.vertices[vertex_offset + i].tx = ptr[0]; + result.vertices[vertex_offset + i].ty = ptr[1]; + } + } + + vertex_offset += ap->count; + index_offset += ai->count; + } + } + + std::vector<unsigned int> remap(total_indices); + size_t unique_vertices = meshopt_generateVertexRemap(&remap[0], &result.indices[0], total_indices, &result.vertices[0], total_vertices, sizeof(Vertex)); + + meshopt_remapIndexBuffer(&result.indices[0], &result.indices[0], total_indices, &remap[0]); + meshopt_remapVertexBuffer(&result.vertices[0], &result.vertices[0], total_vertices, sizeof(Vertex), &remap[0]); + + result.vertices.resize(unique_vertices); + + cgltf_free(data); + + return result; +} + +Mesh loadMesh(const char* path) +{ + if (strstr(path, ".obj")) + return parseObj(path); + + if (strstr(path, ".gltf") || strstr(path, ".glb")) + return parseGltf(path); + + return Mesh(); +} + +bool saveObj(const Mesh& mesh, const char* path) +{ + std::vector<Vertex> verts = mesh.vertices; + std::vector<unsigned int> tris = mesh.indices; + size_t vertcount = meshopt_optimizeVertexFetch(verts.data(), tris.data(), tris.size(), verts.data(), verts.size(), sizeof(Vertex)); + + FILE* obj = fopen(path, "w"); + if (!obj) + return false; + + for (size_t i = 0; i < vertcount; ++i) + { + fprintf(obj, "v %f %f %f\n", verts[i].px, verts[i].py, verts[i].pz); + fprintf(obj, "vn %f %f %f\n", verts[i].nx, verts[i].ny, verts[i].nz); + fprintf(obj, "vt %f %f %f\n", verts[i].tx, verts[i].ty, 0.f); + } + + for (size_t i = 0; i < tris.size(); i += 3) + { + unsigned int i0 = tris[i + 0] + 1; + unsigned int i1 = tris[i + 1] + 1; + unsigned int i2 = tris[i + 2] + 1; + + fprintf(obj, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", i0, i0, i0, i1, i1, i1, i2, i2, i2); + } + + fclose(obj); + + return true; +} + +Mesh optimize(const Mesh& mesh, int lod) +{ + float threshold = powf(0.5f, float(lod)); + size_t target_index_count = size_t(mesh.indices.size() * threshold); + float target_error = 1e-2f; + + Mesh result = mesh; + result.kinds.resize(result.vertices.size()); + result.loop.resize(result.vertices.size()); + meshopt_simplifyDebugKind = &result.kinds[0]; + meshopt_simplifyDebugLoop = &result.loop[0]; + result.indices.resize(meshopt_simplify(&result.indices[0], &result.indices[0], mesh.indices.size(), &mesh.vertices[0].px, mesh.vertices.size(), sizeof(Vertex), target_index_count, target_error)); + + return result; +} + +void display(int x, int y, int width, int height, const Mesh& mesh, const Options& options) +{ + glViewport(x, y, width, height); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glRotatef(0.f, 0.f, 1.f, 0.f); + + glPolygonMode(GL_FRONT_AND_BACK, options.wireframe ? GL_LINE : GL_FILL); + + float centerx = 0; + float centery = 0; + float centerz = 0; + float centeru = 0; + float centerv = 0; + + for (size_t i = 0; i < mesh.vertices.size(); ++i) + { + const Vertex& v = mesh.vertices[i]; + + centerx += v.px; + centery += v.py; + centerz += v.pz; + centeru += v.tx; + centerv += v.ty; + } + + centerx /= float(mesh.vertices.size()); + centery /= float(mesh.vertices.size()); + centerz /= float(mesh.vertices.size()); + centeru /= float(mesh.vertices.size()); + centerv /= float(mesh.vertices.size()); + + float extent = 0; + float extentuv = 0; + + for (size_t i = 0; i < mesh.vertices.size(); ++i) + { + const Vertex& v = mesh.vertices[i]; + + extent = std::max(extent, fabsf(v.px - centerx)); + extent = std::max(extent, fabsf(v.py - centery)); + extent = std::max(extent, fabsf(v.pz - centerz)); + extentuv = std::max(extentuv, fabsf(v.tx - centeru)); + extentuv = std::max(extentuv, fabsf(v.ty - centerv)); + } + + extent *= 1.1f; + extentuv *= 1.1f; + + float scalex = width > height ? float(height) / float(width) : 1; + float scaley = height > width ? float(width) / float(height) : 1; + + glBegin(GL_TRIANGLES); + + for (size_t i = 0; i < mesh.indices.size(); ++i) + { + const Vertex& v = mesh.vertices[mesh.indices[i]]; + + float intensity = -(v.pz - centerz) / extent * 0.5f + 0.5f; + + switch (options.mode) + { + case Options::Mode_UV: + glColor3f(intensity, intensity, intensity); + glVertex3f((v.tx - centeru) / extentuv * scalex, (v.ty - centerv) / extentuv * scaley, 0); + break; + + case Options::Mode_Texture: + glColor3f(v.tx - floorf(v.tx), v.ty - floorf(v.ty), 0.5f); + glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent); + break; + + case Options::Mode_Normals: + glColor3f(v.nx * 0.5f + 0.5f, v.ny * 0.5f + 0.5f, v.nz * 0.5f + 0.5f); + glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent); + break; + + default: + glColor3f(intensity, intensity, intensity); + glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent); + } + } + + glEnd(); + + float zbias = 1e-3f; + + if (options.mode == Options::Mode_Kind && !mesh.kinds.empty() && !mesh.loop.empty()) + { + glLineWidth(1); + + glBegin(GL_LINES); + + for (size_t i = 0; i < mesh.indices.size(); ++i) + { + unsigned int a = mesh.indices[i]; + unsigned int b = mesh.loop[a]; + + if (b != ~0u) + { + const Vertex& v0 = mesh.vertices[a]; + const Vertex& v1 = mesh.vertices[b]; + + unsigned char kind = mesh.kinds[a]; + + glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3); + glVertex3f((v0.px - centerx) / extent * scalex, (v0.py - centery) / extent * scaley, (v0.pz - centerz) / extent - zbias); + glVertex3f((v1.px - centerx) / extent * scalex, (v1.py - centery) / extent * scaley, (v1.pz - centerz) / extent - zbias); + } + } + + glEnd(); + + glPointSize(3); + + glBegin(GL_POINTS); + + for (size_t i = 0; i < mesh.indices.size(); ++i) + { + const Vertex& v = mesh.vertices[mesh.indices[i]]; + unsigned char kind = mesh.kinds[mesh.indices[i]]; + + if (kind != 0) + { + glColor3f(kind == 0 || kind == 4, kind == 0 || kind == 2 || kind == 3, kind == 0 || kind == 1 || kind == 3); + glVertex3f((v.px - centerx) / extent * scalex, (v.py - centery) / extent * scaley, (v.pz - centerz) / extent - zbias * 2); + } + } + + glEnd(); + } +} + +void stats(GLFWwindow* window, const char* path, unsigned int triangles, int lod, double time) +{ + char title[256]; + snprintf(title, sizeof(title), "%s: LOD %d - %d triangles (%.1f msec)", path, lod, triangles, time * 1000); + + glfwSetWindowTitle(window, title); +} + +struct File +{ + Mesh basemesh; + Mesh lodmesh; + const char* path; +}; + +std::vector<File> files; +Options options; +bool redraw; + +void keyhandler(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + if (action == GLFW_PRESS) + { + if (key == GLFW_KEY_W) + { + options.wireframe = !options.wireframe; + redraw = true; + } + else if (key == GLFW_KEY_T) + { + options.mode = options.mode == Options::Mode_Texture ? Options::Mode_Default : Options::Mode_Texture; + redraw = true; + } + else if (key == GLFW_KEY_N) + { + options.mode = options.mode == Options::Mode_Normals ? Options::Mode_Default : Options::Mode_Normals; + redraw = true; + } + else if (key == GLFW_KEY_U) + { + options.mode = options.mode == Options::Mode_UV ? Options::Mode_Default : Options::Mode_UV; + redraw = true; + } + else if (key == GLFW_KEY_K) + { + options.mode = options.mode == Options::Mode_Kind ? Options::Mode_Default : Options::Mode_Kind; + redraw = true; + } + else if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9) + { + int lod = int(key - GLFW_KEY_0); + + unsigned int triangles = 0; + + clock_t start = clock(); + for (auto& f : files) + { + f.lodmesh = optimize(f.basemesh, lod); + triangles += unsigned(f.lodmesh.indices.size() / 3); + } + clock_t end = clock(); + + stats(window, files[0].path, triangles, lod, double(end - start) / CLOCKS_PER_SEC); + redraw = true; + } + else if (key == GLFW_KEY_S) + { + int i = 0; + + for (auto& f : files) + { + char path[32]; + sprintf(path, "result%d.obj", i); + + saveObj(f.lodmesh, path); + + printf("Saved LOD of %s to %s\n", f.path, path); + } + } + } +} + +void sizehandler(GLFWwindow* window, int width, int height) +{ + redraw = true; +} + +int main(int argc, char** argv) +{ + if (argc <= 1) + { + printf("Usage: %s [.obj files]\n", argv[0]); + return 0; + } + + unsigned int basetriangles = 0; + + for (int i = 1; i < argc; ++i) + { + files.emplace_back(); + File& f = files.back(); + + f.path = argv[i]; + f.basemesh = loadMesh(f.path); + f.lodmesh = optimize(f.basemesh, 0); + + basetriangles += unsigned(f.basemesh.indices.size() / 3); + } + + glfwInit(); + + GLFWwindow* window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL); + glfwMakeContextCurrent(window); + + stats(window, files[0].path, basetriangles, 0, 0); + + glfwSetKeyCallback(window, keyhandler); + glfwSetWindowSizeCallback(window, sizehandler); + + redraw = true; + + while (!glfwWindowShouldClose(window)) + { + if (redraw) + { + redraw = false; + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + glViewport(0, 0, width, height); + glClearDepth(1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + int cols = int(ceil(sqrt(double(files.size())))); + int rows = int(ceil(double(files.size()) / cols)); + + int tilew = width / cols; + int tileh = height / rows; + + for (size_t i = 0; i < files.size(); ++i) + { + File& f = files[i]; + int x = int(i) % cols; + int y = int(i) / cols; + + display(x * tilew, y * tileh, tilew, tileh, f.lodmesh, options); + } + + glfwSwapBuffers(window); + } + + glfwWaitEvents(); + } +} diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/meshloader.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/meshloader.cpp new file mode 100644 index 00000000000..b479f4a79c6 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/meshloader.cpp @@ -0,0 +1,9 @@ +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +#define FAST_OBJ_IMPLEMENTATION +#include "fast_obj.h" diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetester.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetester.cpp new file mode 100644 index 00000000000..6083b839005 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetester.cpp @@ -0,0 +1,509 @@ +#ifdef _WIN32 +#include <assert.h> +#include <d3d11.h> +#include <d3dcompiler.h> +#include <stdio.h> + +#include <cassert> +#include <cmath> + +#include <algorithm> +#include <vector> + +#include "../src/meshoptimizer.h" +#include "fast_obj.h" + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "d3dcompiler.lib") +#pragma comment(lib, "dxgi.lib") + +void stripGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, bool prefetch) +{ + if (prefetch) + { + for (int x = x0; x < x1; x++) + { + indices.push_back(x + 0); + indices.push_back(x + 0); + indices.push_back(x + 1); + } + } + + for (int y = y0; y < y1; y++) + { + for (int x = x0; x < x1; x++) + { + indices.push_back((width + 1) * (y + 0) + (x + 0)); + indices.push_back((width + 1) * (y + 1) + (x + 0)); + indices.push_back((width + 1) * (y + 0) + (x + 1)); + + indices.push_back((width + 1) * (y + 0) + (x + 1)); + indices.push_back((width + 1) * (y + 1) + (x + 0)); + indices.push_back((width + 1) * (y + 1) + (x + 1)); + } + } +} + +void gridGen(std::vector<unsigned int>& indices, int x0, int x1, int y0, int y1, int width, int cacheSize, bool prefetch) +{ + if (x1 - x0 + 1 < cacheSize) + { + bool prefetchStrip = 2 * (x1 - x0) + 1 > cacheSize && prefetch; + + stripGen(indices, x0, x1, y0, y1, width, prefetchStrip); + } + else + { + int xm = x0 + cacheSize - 2; + gridGen(indices, x0, xm, y0, y1, width, cacheSize, prefetch); + gridGen(indices, xm, x1, y0, y1, width, cacheSize, prefetch); + } +} + +unsigned int queryVSInvocations(ID3D11Device* device, ID3D11DeviceContext* context, const unsigned int* indices, size_t index_count) +{ + if (index_count == 0) + return 0; + + ID3D11Buffer* ib = 0; + + { + D3D11_BUFFER_DESC bd = {}; + + bd.Usage = D3D11_USAGE_DYNAMIC; + bd.ByteWidth = index_count * 4; + bd.BindFlags = D3D11_BIND_INDEX_BUFFER; + bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + device->CreateBuffer(&bd, 0, &ib); + + D3D11_MAPPED_SUBRESOURCE ms; + context->Map(ib, 0, D3D11_MAP_WRITE_DISCARD, 0, &ms); + memcpy(ms.pData, indices, index_count * 4); + context->Unmap(ib, 0); + } + + context->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + context->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0); + + D3D11_QUERY_DESC qdesc = {D3D11_QUERY_PIPELINE_STATISTICS}; + ID3D11Query* query = 0; + device->CreateQuery(&qdesc, &query); + + context->Begin(query); + context->DrawIndexed(index_count, 0, 0); + context->End(query); + + D3D11_QUERY_DATA_PIPELINE_STATISTICS stats = {}; + while (S_FALSE == context->GetData(query, &stats, sizeof(stats), 0)) + ; + + query->Release(); + ib->Release(); + + assert(stats.IAVertices == index_count); + + return stats.VSInvocations; +} + +void setupShaders(ID3D11Device* device, ID3D11DeviceContext* context) +{ + // load and compile the two shaders + const char* shaders = + "#define ATTRIBUTES 5\n" + "struct Foo { float4 v[ATTRIBUTES]; };" + "float4 VS(uint index: SV_VertexId, out Foo foo: FOO): SV_Position { uint i = index % 3; [unroll] for (int j = 0; j < ATTRIBUTES; j++) foo.v[j] = j; return float4(i != 0, i != 2, 0, 1); }" + "float4 PS(Foo foo: FOO): SV_Target { float4 result = 0; [unroll] for (int j = 0; j < ATTRIBUTES; j++) result += foo.v[j]; return result; }"; + + ID3DBlob* vsblob = 0; + ID3DBlob* psblob = 0; + D3DCompile(shaders, strlen(shaders), 0, 0, 0, "VS", "vs_5_0", 0, 0, &vsblob, 0); + D3DCompile(shaders, strlen(shaders), 0, 0, 0, "PS", "ps_5_0", 0, 0, &psblob, 0); + + ID3D11VertexShader* vs = 0; + ID3D11PixelShader* ps = 0; + device->CreateVertexShader(vsblob->GetBufferPointer(), vsblob->GetBufferSize(), 0, &vs); + device->CreatePixelShader(psblob->GetBufferPointer(), psblob->GetBufferSize(), 0, &ps); + + context->VSSetShader(vs, 0, 0); + context->PSSetShader(ps, 0, 0); +} + +template <typename Cache> +void inspectCache(Cache cache) +{ + unsigned int max_cache_size = 200; + unsigned int grid_size = 100; + + for (unsigned int cache_size = 3; cache_size <= max_cache_size; cache_size += 1) + { + std::vector<unsigned int> grid1; + gridGen(grid1, 0, grid_size, 0, grid_size, grid_size, cache_size, true); + + std::vector<unsigned int> grid2; + gridGen(grid2, 0, grid_size, 0, grid_size, grid_size, cache_size, false); + + std::vector<unsigned int> grid3; + gridGen(grid3, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles + meshopt_optimizeVertexCacheFifo(&grid3[0], &grid3[0], grid3.size(), (grid_size + 1) * (grid_size + 1), cache_size); + + std::vector<unsigned int> grid4; + gridGen(grid4, 0, grid_size, 0, grid_size, grid_size, grid_size * 4, false); // this generates a simple indexed grid without striping/degenerate triangles + meshopt_optimizeVertexCache(&grid4[0], &grid4[0], grid4.size(), (grid_size + 1) * (grid_size + 1)); + + unsigned int invocations1 = cache(&grid1[0], grid1.size()); + unsigned int invocations2 = cache(&grid2[0], grid2.size()); + unsigned int invocations3 = cache(&grid3[0], grid3.size()); + unsigned int invocations4 = cache(&grid4[0], grid4.size()); + + unsigned int ideal_invocations = (grid_size + 1) * (grid_size + 1); + + printf("%d, %f, %f, %f, %f\n", cache_size, + double(invocations1) / double(ideal_invocations), + double(invocations2) / double(ideal_invocations), + double(invocations3) / double(ideal_invocations), + double(invocations4) / double(ideal_invocations)); + } +} + +void testCache(IDXGIAdapter* adapter) +{ + ID3D11Device* device = 0; + ID3D11DeviceContext* context = 0; + D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context); + + setupShaders(device, context); + + inspectCache([&](const unsigned int* indices, size_t index_count) { return queryVSInvocations(device, context, indices, index_count); }); +} + +void testCacheSequence(IDXGIAdapter* adapter, int argc, char** argv) +{ + ID3D11Device* device = 0; + ID3D11DeviceContext* context = 0; + D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context); + + setupShaders(device, context); + + std::vector<unsigned int> ib; + + for (int i = 2; i < argc; ++i) + { + char* end; + int i0 = strtol(argv[i], &end, 10); + + if (end[0] == '-') + { + int i1 = strtol(end + 1, &end, 10); + + if (end[0] != 0) + { + printf("Unrecognized index range: %s\n", argv[i]); + return; + } + + if (i0 < i1) + { + for (int ii = i0; ii <= i1; ++ii) + ib.push_back(ii); + } + else + { + for (int ii = i0; ii >= i1; --ii) + ib.push_back(ii); + } + } + else if (end[0] == '*') + { + int i1 = strtol(end + 1, &end, 10); + + if (end[0] != 0 || i1 == 0) + { + printf("Unrecognized index range: %s\n", argv[i]); + return; + } + + for (int ii = 0; ii < i1; ++ii) + ib.push_back(i0); + } + else if (end[0] == 'x') + { + int i1 = strtol(end + 1, &end, 10); + + if (end[0] != 0) + { + printf("Unrecognized index range: %s\n", argv[i]); + return; + } + + stripGen(ib, 0, i0, 0, i1, i0, true); + } + else if (end[0] == 0) + { + ib.push_back(i0); + } + else + { + printf("Unrecognized index range: %s\n", argv[i]); + return; + } + } + + if (ib.size() % 3) + ib.resize(ib.size() - ib.size() % 3); + + std::vector<bool> xformed(ib.size()); + + for (size_t i = 0; i < ib.size(); i += 3) + { + unsigned int inv0 = i == 0 ? 0 : queryVSInvocations(device, context, ib.data(), i); + unsigned int inv1 = queryVSInvocations(device, context, ib.data(), i + 3); + + assert(inv0 <= inv1); + assert(inv0 + 3 >= inv1); + + switch (inv1 - inv0) + { + case 0: + xformed[i + 0] = xformed[i + 1] = xformed[i + 2] = false; + break; + + case 3: + xformed[i + 0] = xformed[i + 1] = xformed[i + 2] = true; + break; + + case 1: + case 2: + { + unsigned int a = ib[i + 0]; + unsigned int b = ib[i + 1]; + unsigned int c = ib[i + 2]; + + ib[i + 0] = ib[i + 1] = ib[i + 2] = a; + unsigned int inva = queryVSInvocations(device, context, ib.data(), i + 3); + + ib[i + 1] = ib[i + 2] = b; + unsigned int invb = queryVSInvocations(device, context, ib.data(), i + 3); + + ib[i + 2] = c; + unsigned int invc = queryVSInvocations(device, context, ib.data(), i + 3); + + assert(inv0 <= inva && inva <= inv1); + assert(inv0 <= invb && invb <= inv1); + assert(inv0 <= invc && invc <= inv1); + + if (inv1 - inv0 == 1 && a == c && inva == inv1 && invb == inv0 && invc == inv1) + { + xformed[i + 0] = false; + xformed[i + 1] = false; + xformed[i + 2] = true; + } + else + { + assert(inva <= invb); + assert(invb <= invc); + + xformed[i + 0] = inva == inv0 + 1; + xformed[i + 1] = invb == inva + 1; + xformed[i + 2] = invc == invb + 1; + } + break; + } + } + } + + unsigned int xformed_total = 0; + + for (size_t i = 0; i < ib.size(); ++i) + xformed_total += xformed[i]; + + printf("// Sequence: %d indices", int(ib.size())); + + for (size_t i = 0; i < ib.size(); ++i) + { + if (i % 12 == 0) + { + printf("\n// %3d*3:", int(i / 3)); + } + + if (xformed[i]) + printf(" %3d*", ib[i]); + else + printf(" %3d ", ib[i]); + } + + printf("\n"); + + std::vector<unsigned int> cached; + + for (size_t i = 0; i < ib.size(); ++i) + { + unsigned int index = ib[i]; + unsigned int inv0 = queryVSInvocations(device, context, ib.data(), ib.size()); + + ib.push_back(index); + ib.push_back(index); + ib.push_back(index); + + unsigned int inv1 = queryVSInvocations(device, context, ib.data(), ib.size()); + + ib.resize(ib.size() - 3); + + if (inv1 == inv0) + cached.push_back(index); + } + + std::sort(cached.begin(), cached.end()); + cached.erase(std::unique(cached.begin(), cached.end()), cached.end()); + + printf("// Cached :"); + + for (size_t i = 0; i < cached.size(); ++i) + printf(" %d", cached[i]); + + printf(" (%d)\n", int(cached.size())); + + unsigned int invocations = queryVSInvocations(device, context, ib.data(), ib.size()); + + printf("// Invocations: %d\n", invocations); + + assert(xformed_total == invocations); +} + +void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv) +{ + ID3D11Device* device = 0; + ID3D11DeviceContext* context = 0; + D3D11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, 0, 0, D3D11_SDK_VERSION, &device, 0, &context); + + setupShaders(device, context); + + bool stat = false; + + double atvr_sum = 0; + double atvr_count = 0; + + unsigned int total_invocations = 0; + unsigned int total_vertices = 0; + + for (int i = 1; i < argc; ++i) + { + const char* path = argv[i]; + + if (strcmp(path, "--stat") == 0) + { + stat = true; + continue; + } + + fastObjMesh* obj = fast_obj_read(path); + if (!obj) + { + printf("Error loading %s: file not found\n", path); + continue; + } + + std::vector<unsigned int> ib1; + + size_t index_offset = 0; + + for (unsigned int i = 0; i < obj->face_count; ++i) + { + for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) + { + fastObjIndex gi = obj->indices[index_offset + j]; + + // triangulate polygon on the fly; offset-3 is always the first polygon vertex + if (j >= 3) + { + unsigned int i0 = ib1[ib1.size() - 3]; + unsigned int i1 = ib1[ib1.size() - 1]; + + ib1.push_back(i0); + ib1.push_back(i1); + } + + ib1.push_back(gi.p); + } + + index_offset += obj->face_vertices[i]; + } + + unsigned int vertex_count = obj->position_count; + unsigned int index_count = ib1.size(); + + unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count); + + if (stat) + { + std::vector<unsigned int> ib2(ib1.size()); + meshopt_optimizeVertexCache(&ib2[0], &ib1[0], ib1.size(), vertex_count); + + unsigned int invocations = queryVSInvocations(device, context, ib2.data(), index_count); + + atvr_sum += double(invocations) / double(vertex_count); + atvr_count += 1; + + total_invocations += invocations; + total_vertices += vertex_count; + } + else + { + printf("%s: baseline %f\n", path, double(invocations1) / double(vertex_count)); + + std::vector<unsigned int> ib3(ib1.size()); + meshopt_optimizeVertexCache(&ib3[0], &ib1[0], ib1.size(), vertex_count); + + unsigned int invocations3 = queryVSInvocations(device, context, ib3.data(), index_count); + + printf("%s: forsyth %f\n", path, double(invocations3) / double(vertex_count)); + + for (unsigned int cache_size = 12; cache_size <= 24; ++cache_size) + { + std::vector<unsigned int> ib2(ib1.size()); + meshopt_optimizeVertexCacheFifo(&ib2[0], &ib1[0], ib1.size(), vertex_count, cache_size); + + unsigned int invocations2 = queryVSInvocations(device, context, ib2.data(), index_count); + + printf("%s: tipsify(%d) %f\n", path, cache_size, double(invocations2) / double(vertex_count)); + } + } + } + + if (stat) + { + printf("ATVR: average %f cumulative %f; %d vertices\n", atvr_sum / atvr_count, double(total_invocations) / double(total_vertices), total_vertices); + } +} + +int main(int argc, char** argv) +{ + IDXGIFactory1* factory = 0; + CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory); + + IDXGIAdapter* adapter = NULL; + for (unsigned int index = 0; SUCCEEDED(factory->EnumAdapters(index, &adapter)); ++index) + { + DXGI_ADAPTER_DESC ad = {}; + adapter->GetDesc(&ad); + + if (ad.VendorId == 0x1414 && ad.DeviceId == 0x8c) + continue; // Skip Microsoft Basic Render Driver + + printf("// GPU %d: %S (Vendor %04x Device %04x)\n", index, ad.Description, ad.VendorId, ad.DeviceId); + + if (argc == 1) + { + testCache(adapter); + } + else if (argc > 1 && strcmp(argv[1], "--") == 0) + { + testCacheSequence(adapter, argc, argv); + } + else + { + testCacheMeshes(adapter, argc, argv); + } + } +} +#endif diff --git a/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetuner.cpp b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetuner.cpp new file mode 100644 index 00000000000..22d32fd8926 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetuner.cpp @@ -0,0 +1,424 @@ +#include "../src/meshoptimizer.h" +#include "fast_obj.h" + +#include <algorithm> +#include <functional> +#include <vector> + +#include <cmath> +#include <cstdint> +#include <cstdio> +#include <cstring> + +const int kCacheSizeMax = 16; +const int kValenceMax = 8; + +namespace meshopt +{ +extern thread_local float kVertexScoreTableCache[1 + kCacheSizeMax]; +extern thread_local float kVertexScoreTableLive[1 + kValenceMax]; +} // namespace meshopt + +struct { int cache, warp, triangle; } profiles[] = +{ + {14, 64, 128}, // AMD GCN + {32, 32, 32}, // NVidia Pascal + // { 16, 32, 32 }, // NVidia Kepler, Maxwell + // { 128, 0, 0 }, // Intel +}; + +const int Profile_Count = sizeof(profiles) / sizeof(profiles[0]); + +struct pcg32_random_t +{ + uint64_t state; + uint64_t inc; +}; + +#define PCG32_INITIALIZER { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL } + +uint32_t pcg32_random_r(pcg32_random_t* rng) +{ + uint64_t oldstate = rng->state; + // Advance internal state + rng->state = oldstate * 6364136223846793005ULL + (rng->inc | 1); + // Calculate output function (XSH RR), uses old state for max ILP + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +pcg32_random_t rngstate = PCG32_INITIALIZER; + +float rand01() +{ + return pcg32_random_r(&rngstate) / float(1ull << 32); +} + +uint32_t rand32() +{ + return pcg32_random_r(&rngstate); +} + +struct State +{ + float cache[kCacheSizeMax]; + float live[kValenceMax]; + float fitness; +}; + +struct Mesh +{ + size_t vertex_count; + std::vector<unsigned int> indices; + + float atvr_base[Profile_Count]; +}; + +Mesh gridmesh(unsigned int N) +{ + Mesh result; + + result.vertex_count = (N + 1) * (N + 1); + result.indices.reserve(N * N * 6); + + for (unsigned int y = 0; y < N; ++y) + for (unsigned int x = 0; x < N; ++x) + { + result.indices.push_back((y + 0) * (N + 1) + (x + 0)); + result.indices.push_back((y + 0) * (N + 1) + (x + 1)); + result.indices.push_back((y + 1) * (N + 1) + (x + 0)); + + result.indices.push_back((y + 1) * (N + 1) + (x + 0)); + result.indices.push_back((y + 0) * (N + 1) + (x + 1)); + result.indices.push_back((y + 1) * (N + 1) + (x + 1)); + } + + return result; +} + +Mesh objmesh(const char* path) +{ + fastObjMesh* obj = fast_obj_read(path); + if (!obj) + { + printf("Error loading %s: file not found\n", path); + return Mesh(); + } + + size_t total_indices = 0; + + for (unsigned int i = 0; i < obj->face_count; ++i) + total_indices += 3 * (obj->face_vertices[i] - 2); + + struct Vertex + { + float px, py, pz; + float nx, ny, nz; + float tx, ty; + }; + + std::vector<Vertex> vertices(total_indices); + + size_t vertex_offset = 0; + size_t index_offset = 0; + + for (unsigned int i = 0; i < obj->face_count; ++i) + { + for (unsigned int j = 0; j < obj->face_vertices[i]; ++j) + { + fastObjIndex gi = obj->indices[index_offset + j]; + + Vertex v = + { + obj->positions[gi.p * 3 + 0], + obj->positions[gi.p * 3 + 1], + obj->positions[gi.p * 3 + 2], + obj->normals[gi.n * 3 + 0], + obj->normals[gi.n * 3 + 1], + obj->normals[gi.n * 3 + 2], + obj->texcoords[gi.t * 2 + 0], + obj->texcoords[gi.t * 2 + 1], + }; + + // triangulate polygon on the fly; offset-3 is always the first polygon vertex + if (j >= 3) + { + vertices[vertex_offset + 0] = vertices[vertex_offset - 3]; + vertices[vertex_offset + 1] = vertices[vertex_offset - 1]; + vertex_offset += 2; + } + + vertices[vertex_offset] = v; + vertex_offset++; + } + + index_offset += obj->face_vertices[i]; + } + + fast_obj_destroy(obj); + + Mesh result; + + std::vector<unsigned int> remap(total_indices); + + size_t total_vertices = meshopt_generateVertexRemap(&remap[0], NULL, total_indices, &vertices[0], total_indices, sizeof(Vertex)); + + result.indices.resize(total_indices); + meshopt_remapIndexBuffer(&result.indices[0], NULL, total_indices, &remap[0]); + + result.vertex_count = total_vertices; + + return result; +} + +void compute_atvr(const State& state, const Mesh& mesh, float result[Profile_Count]) +{ + memcpy(meshopt::kVertexScoreTableCache + 1, state.cache, kCacheSizeMax * sizeof(float)); + memcpy(meshopt::kVertexScoreTableLive + 1, state.live, kValenceMax * sizeof(float)); + + std::vector<unsigned int> indices(mesh.indices.size()); + + meshopt_optimizeVertexCache(&indices[0], &mesh.indices[0], mesh.indices.size(), mesh.vertex_count); + + for (int profile = 0; profile < Profile_Count; ++profile) + result[profile] = meshopt_analyzeVertexCache(&indices[0], indices.size(), mesh.vertex_count, profiles[profile].cache, profiles[profile].warp, profiles[profile].triangle).atvr; +} + +float fitness_score(const State& state, const std::vector<Mesh>& meshes) +{ + float result = 0; + float count = 0; + + for (auto& mesh : meshes) + { + float atvr[Profile_Count]; + compute_atvr(state, mesh, atvr); + + for (int profile = 0; profile < Profile_Count; ++profile) + { + result += mesh.atvr_base[profile] / atvr[profile]; + count += 1; + } + } + + return result / count; +} + +std::vector<State> gen0(size_t count, const std::vector<Mesh>& meshes) +{ + std::vector<State> result; + + for (size_t i = 0; i < count; ++i) + { + State state = {}; + + for (int j = 0; j < kCacheSizeMax; ++j) + state.cache[j] = rand01(); + + for (int j = 0; j < kValenceMax; ++j) + state.live[j] = rand01(); + + state.fitness = fitness_score(state, meshes); + + result.push_back(state); + } + + return result; +} + +// https://en.wikipedia.org/wiki/Differential_evolution +// Good Parameters for Differential Evolution. Magnus Erik Hvass Pedersen, 2010 +std::pair<State, float> genN(std::vector<State>& seed, const std::vector<Mesh>& meshes, float crossover = 0.8803f, float weight = 0.4717f) +{ + std::vector<State> result(seed.size()); + + for (size_t i = 0; i < seed.size(); ++i) + { + for (;;) + { + int a = rand32() % seed.size(); + int b = rand32() % seed.size(); + int c = rand32() % seed.size(); + + if (a == b || a == c || b == c || a == int(i) || b == int(i) || c == int(i)) + continue; + + int rc = rand32() % kCacheSizeMax; + int rl = rand32() % kValenceMax; + + for (int j = 0; j < kCacheSizeMax; ++j) + { + float r = rand01(); + + if (r < crossover || j == rc) + result[i].cache[j] = std::max(0.f, std::min(1.f, seed[a].cache[j] + weight * (seed[b].cache[j] - seed[c].cache[j]))); + else + result[i].cache[j] = seed[i].cache[j]; + } + + for (int j = 0; j < kValenceMax; ++j) + { + float r = rand01(); + + if (r < crossover || j == rl) + result[i].live[j] = std::max(0.f, std::min(1.f, seed[a].live[j] + weight * (seed[b].live[j] - seed[c].live[j]))); + else + result[i].live[j] = seed[i].live[j]; + } + + break; + } + } + + #pragma omp parallel for + for (size_t i = 0; i < seed.size(); ++i) + { + result[i].fitness = fitness_score(result[i], meshes); + } + + State best = {}; + float bestfit = 0; + + for (size_t i = 0; i < seed.size(); ++i) + { + if (result[i].fitness > seed[i].fitness) + seed[i] = result[i]; + + if (seed[i].fitness > bestfit) + { + best = seed[i]; + bestfit = seed[i].fitness; + } + } + + return std::make_pair(best, bestfit); +} + +bool load_state(const char* path, std::vector<State>& result) +{ + FILE* file = fopen(path, "rb"); + if (!file) + return false; + + State state; + + result.clear(); + + while (fread(&state, sizeof(State), 1, file) == 1) + result.push_back(state); + + fclose(file); + + return true; +} + +bool save_state(const char* path, const std::vector<State>& result) +{ + FILE* file = fopen(path, "wb"); + if (!file) + return false; + + for (auto& state : result) + { + if (fwrite(&state, sizeof(State), 1, file) != 1) + { + fclose(file); + return false; + } + } + + return fclose(file) == 0; +} + +void dump_state(const State& state) +{ + printf("cache:"); + for (int i = 0; i < kCacheSizeMax; ++i) + { + printf(" %.3f", state.cache[i]); + } + printf("\n"); + + printf("live:"); + for (int i = 0; i < kValenceMax; ++i) + { + printf(" %.3f", state.live[i]); + } + printf("\n"); +} + +int main(int argc, char** argv) +{ + bool annealing = false; + + State baseline; + memcpy(baseline.cache, meshopt::kVertexScoreTableCache + 1, kCacheSizeMax * sizeof(float)); + memcpy(baseline.live, meshopt::kVertexScoreTableLive + 1, kValenceMax * sizeof(float)); + + std::vector<Mesh> meshes; + + meshes.push_back(gridmesh(50)); + + for (int i = 1; i < argc; ++i) + meshes.push_back(objmesh(argv[i])); + + size_t total_triangles = 0; + + for (auto& mesh : meshes) + { + compute_atvr(baseline, mesh, mesh.atvr_base); + + total_triangles += mesh.indices.size() / 3; + } + + std::vector<State> pop; + size_t gen = 0; + + if (load_state("mutator.state", pop)) + { + printf("Loaded %d state vectors\n", int(pop.size())); + } + else + { + pop = gen0(95, meshes); + } + + printf("%d meshes, %.1fM triangles\n", int(meshes.size()), double(total_triangles) / 1e6); + + float atvr_0[Profile_Count]; + float atvr_N[Profile_Count]; + compute_atvr(baseline, meshes[0], atvr_0); + compute_atvr(baseline, meshes.back(), atvr_N); + + printf("baseline: grid %f %f %s %f %f\n", atvr_0[0], atvr_0[1], argv[argc - 1], atvr_N[0], atvr_N[1]); + + for (;;) + { + auto best = genN(pop, meshes); + gen++; + + compute_atvr(best.first, meshes[0], atvr_0); + compute_atvr(best.first, meshes.back(), atvr_N); + + printf("%d: fitness %f; grid %f %f %s %f %f\n", int(gen), best.second, atvr_0[0], atvr_0[1], argv[argc - 1], atvr_N[0], atvr_N[1]); + + if (gen % 100 == 0) + { + char buf[128]; + sprintf(buf, "gcloud logging write vcache-log \"fitness %f; grid %f %f %s %f %f\"", best.second, atvr_0[0], atvr_0[1], argv[argc - 1], atvr_N[0], atvr_N[1]); + int rc = system(buf); + (void)rc; + } + + dump_state(best.first); + + if (save_state("mutator.state-temp", pop) && rename("mutator.state-temp", "mutator.state") == 0) + { + } + else + { + printf("ERROR: Can't save state\n"); + } + } +} |