summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/bgfx/3rdparty/meshoptimizer/tools
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/bgfx/3rdparty/meshoptimizer/tools')
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/cgltf.h4781
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/fast_obj.h1397
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/gltfpack.cpp3760
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/lodviewer.cpp618
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/meshloader.cpp9
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetester.cpp509
-rw-r--r--3rdparty/bgfx/3rdparty/meshoptimizer/tools/vcachetuner.cpp424
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");
+ }
+ }
+}