├── .github └── workflows │ └── build.yml ├── LICENSE ├── README.md ├── cgltf.h ├── cgltf_write.h ├── fuzz ├── data │ ├── BadBasisU.gltf │ ├── Box.glb │ ├── TriangleWithoutIndices.gltf │ └── WaterBottle.gltf ├── gltf.dict └── main.c └── test ├── CMakeLists.txt ├── main.c ├── test_all.py ├── test_conversion.cpp ├── test_math.cpp ├── test_strings.cpp ├── test_write.cpp └── test_write_glb.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | generic: 7 | strategy: 8 | matrix: 9 | os: [windows, ubuntu, macos] 10 | name: ${{matrix.os}} 11 | runs-on: ${{matrix.os}}-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: test 15 | run: cd test && python test_all.py 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2021 Johannes Kuhlmann 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :diamond_shape_with_a_dot_inside: cgltf 2 | **Single-file/stb-style C glTF loader and writer** 3 | 4 | [![Build Status](https://github.com/jkuhlmann/cgltf/workflows/build/badge.svg)](https://github.com/jkuhlmann/cgltf/actions) 5 | 6 | Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more! 7 | 8 | ## Usage: Loading 9 | Loading from file: 10 | ```c 11 | #define CGLTF_IMPLEMENTATION 12 | #include "cgltf.h" 13 | 14 | cgltf_options options = {0}; 15 | cgltf_data* data = NULL; 16 | cgltf_result result = cgltf_parse_file(&options, "scene.gltf", &data); 17 | if (result == cgltf_result_success) 18 | { 19 | /* TODO make awesome stuff */ 20 | cgltf_free(data); 21 | } 22 | ``` 23 | 24 | Loading from memory: 25 | ```c 26 | #define CGLTF_IMPLEMENTATION 27 | #include "cgltf.h" 28 | 29 | void* buf; /* Pointer to glb or gltf file data */ 30 | size_t size; /* Size of the file data */ 31 | 32 | cgltf_options options = {0}; 33 | cgltf_data* data = NULL; 34 | cgltf_result result = cgltf_parse(&options, buf, size, &data); 35 | if (result == cgltf_result_success) 36 | { 37 | /* TODO make awesome stuff */ 38 | cgltf_free(data); 39 | } 40 | ``` 41 | 42 | Note that cgltf does not load the contents of extra files such as buffers or images into memory by default. You'll need to read these files yourself using URIs from `data.buffers[]` or `data.images[]` respectively. 43 | For buffer data, you can alternatively call `cgltf_load_buffers`, which will use `FILE*` APIs to open and read buffer files. This automatically decodes base64 data URIs in buffers. For data URIs in images, you will need to use `cgltf_load_buffer_base64`. 44 | 45 | **For more in-depth documentation and a description of the public interface refer to the top of the `cgltf.h` file.** 46 | 47 | ## Usage: Writing 48 | When writing glTF data, you need a valid `cgltf_data` structure that represents a valid glTF document. You can construct such a structure yourself or load it using the loader functions described above. The writer functions do not deallocate any memory. So, you either have to do it manually or call `cgltf_free()` if you got the data by loading it from a glTF document. 49 | 50 | Writing to file: 51 | ```c 52 | #define CGLTF_IMPLEMENTATION 53 | #define CGLTF_WRITE_IMPLEMENTATION 54 | #include "cgltf_write.h" 55 | 56 | cgltf_options options = {0}; 57 | cgltf_data* data = /* TODO must be valid data */; 58 | cgltf_result result = cgltf_write_file(&options, "out.gltf", data); 59 | if (result != cgltf_result_success) 60 | { 61 | /* TODO handle error */ 62 | } 63 | ``` 64 | 65 | Writing to memory: 66 | ```c 67 | #define CGLTF_IMPLEMENTATION 68 | #define CGLTF_WRITE_IMPLEMENTATION 69 | #include "cgltf_write.h" 70 | cgltf_options options = {0}; 71 | cgltf_data* data = /* TODO must be valid data */; 72 | 73 | cgltf_size size = cgltf_write(&options, NULL, 0, data); 74 | 75 | char* buf = malloc(size); 76 | 77 | cgltf_size written = cgltf_write(&options, buf, size, data); 78 | if (written != size) 79 | { 80 | /* TODO handle error */ 81 | } 82 | ``` 83 | 84 | Note that cgltf does not write the contents of extra files such as buffers or images. You'll need to write this data yourself. 85 | 86 | **For more in-depth documentation and a description of the public interface refer to the top of the `cgltf_write.h` file.** 87 | 88 | 89 | ## Features 90 | cgltf supports core glTF 2.0: 91 | - glb (binary files) and gltf (JSON files) 92 | - meshes (including accessors, buffer views, buffers) 93 | - materials (including textures, samplers, images) 94 | - scenes and nodes 95 | - skins 96 | - animations 97 | - cameras 98 | - morph targets 99 | - extras data 100 | 101 | cgltf also supports some glTF extensions: 102 | - EXT_mesh_gpu_instancing 103 | - EXT_meshopt_compression 104 | - EXT_texture_webp 105 | - KHR_draco_mesh_compression (requires a library like [Google's Draco](https://github.com/google/draco) for decompression though) 106 | - KHR_lights_punctual 107 | - KHR_materials_anisotropy 108 | - KHR_materials_clearcoat 109 | - KHR_materials_diffuse_transmission 110 | - KHR_materials_dispersion 111 | - KHR_materials_emissive_strength 112 | - KHR_materials_ior 113 | - KHR_materials_iridescence 114 | - KHR_materials_pbrSpecularGlossiness 115 | - KHR_materials_sheen 116 | - KHR_materials_specular 117 | - KHR_materials_transmission 118 | - KHR_materials_unlit 119 | - KHR_materials_variants 120 | - KHR_materials_volume 121 | - KHR_texture_basisu (requires a library like [Binomial Basisu](https://github.com/BinomialLLC/basis_universal) for transcoding to native compressed texture) 122 | - KHR_texture_transform 123 | 124 | cgltf does **not** yet support unlisted extensions. However, unlisted extensions can be accessed via "extensions" member on objects. 125 | 126 | ## Building 127 | The easiest approach is to integrate the `cgltf.h` header file into your project. If you are unfamiliar with single-file C libraries (also known as stb-style libraries), this is how it goes: 128 | 129 | 1. Include `cgltf.h` where you need the functionality. 130 | 1. Have exactly one source file that defines `CGLTF_IMPLEMENTATION` before including `cgltf.h`. 131 | 1. Use the cgltf functions as described above. 132 | 133 | Support for writing can be found in a separate file called `cgltf_write.h` (which includes `cgltf.h`). Building it works analogously using the `CGLTF_WRITE_IMPLEMENTATION` define. 134 | 135 | ## Contributing 136 | Everyone is welcome to contribute to the library. If you find any problems, you can submit them using [GitHub's issue system](https://github.com/jkuhlmann/cgltf/issues). If you want to contribute code, you should fork the project and then send a pull request. 137 | 138 | 139 | ## Dependencies 140 | None. 141 | 142 | C headers being used by the implementation: 143 | ``` 144 | #include 145 | #include 146 | #include 147 | #include 148 | #include 149 | #include 150 | #include // If asserts are enabled. 151 | ``` 152 | 153 | Note, this library has a copy of the [JSMN JSON parser](https://github.com/zserge/jsmn) embedded in its source. 154 | 155 | ## Testing 156 | There is a Python script in the `test/` folder that retrieves the glTF 2.0 sample files from the glTF-Sample-Models repository (https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0) and runs the library against all gltf and glb files. 157 | 158 | Here's one way to build and run the test: 159 | 160 | cd test ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Debug 161 | make -j 162 | cd .. 163 | ./test_all.py 164 | 165 | There is also a llvm-fuzz test in `fuzz/`. See http://llvm.org/docs/LibFuzzer.html for more information. 166 | -------------------------------------------------------------------------------- /cgltf_write.h: -------------------------------------------------------------------------------- 1 | /** 2 | * cgltf_write - a single-file glTF 2.0 writer written in C99. 3 | * 4 | * Version: 1.15 5 | * 6 | * Website: https://github.com/jkuhlmann/cgltf 7 | * 8 | * Distributed under the MIT License, see notice at the end of this file. 9 | * 10 | * Building: 11 | * Include this file where you need the struct and function 12 | * declarations. Have exactly one source file where you define 13 | * `CGLTF_WRITE_IMPLEMENTATION` before including this file to get the 14 | * function definitions. 15 | * 16 | * Reference: 17 | * `cgltf_result cgltf_write_file(const cgltf_options* options, const char* 18 | * path, const cgltf_data* data)` writes a glTF data to the given file path. 19 | * If `options->type` is `cgltf_file_type_glb`, both JSON content and binary 20 | * buffer of the given glTF data will be written in a GLB format. 21 | * Otherwise, only the JSON part will be written. 22 | * External buffers and images are not written out. `data` is not deallocated. 23 | * 24 | * `cgltf_size cgltf_write(const cgltf_options* options, char* buffer, 25 | * cgltf_size size, const cgltf_data* data)` writes JSON into the given memory 26 | * buffer. Returns the number of bytes written to `buffer`, including a null 27 | * terminator. If buffer is null, returns the number of bytes that would have 28 | * been written. `data` is not deallocated. 29 | */ 30 | #ifndef CGLTF_WRITE_H_INCLUDED__ 31 | #define CGLTF_WRITE_H_INCLUDED__ 32 | 33 | #include "cgltf.h" 34 | 35 | #include 36 | #include 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data); 43 | cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif /* #ifndef CGLTF_WRITE_H_INCLUDED__ */ 50 | 51 | /* 52 | * 53 | * Stop now, if you are only interested in the API. 54 | * Below, you find the implementation. 55 | * 56 | */ 57 | 58 | #if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) 59 | /* This makes MSVC/CLion intellisense work. */ 60 | #define CGLTF_WRITE_IMPLEMENTATION 61 | #endif 62 | 63 | #ifdef CGLTF_WRITE_IMPLEMENTATION 64 | 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | 72 | #define CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM (1 << 0) 73 | #define CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT (1 << 1) 74 | #define CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS (1 << 2) 75 | #define CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL (1 << 3) 76 | #define CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION (1 << 4) 77 | #define CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT (1 << 5) 78 | #define CGLTF_EXTENSION_FLAG_MATERIALS_IOR (1 << 6) 79 | #define CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR (1 << 7) 80 | #define CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION (1 << 8) 81 | #define CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN (1 << 9) 82 | #define CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS (1 << 10) 83 | #define CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME (1 << 11) 84 | #define CGLTF_EXTENSION_FLAG_TEXTURE_BASISU (1 << 12) 85 | #define CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH (1 << 13) 86 | #define CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING (1 << 14) 87 | #define CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE (1 << 15) 88 | #define CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY (1 << 16) 89 | #define CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION (1 << 17) 90 | #define CGLTF_EXTENSION_FLAG_TEXTURE_WEBP (1 << 18) 91 | #define CGLTF_EXTENSION_FLAG_MATERIALS_DIFFUSE_TRANSMISSION (1 << 19) 92 | 93 | typedef struct { 94 | char* buffer; 95 | cgltf_size buffer_size; 96 | cgltf_size remaining; 97 | char* cursor; 98 | cgltf_size tmp; 99 | cgltf_size chars_written; 100 | const cgltf_data* data; 101 | int depth; 102 | const char* indent; 103 | int needs_comma; 104 | uint32_t extension_flags; 105 | uint32_t required_extension_flags; 106 | } cgltf_write_context; 107 | 108 | #define CGLTF_MIN(a, b) (a < b ? a : b) 109 | 110 | #ifdef FLT_DECIMAL_DIG 111 | // FLT_DECIMAL_DIG is C11 112 | #define CGLTF_DECIMAL_DIG (FLT_DECIMAL_DIG) 113 | #else 114 | #define CGLTF_DECIMAL_DIG 9 115 | #endif 116 | 117 | #define CGLTF_SPRINTF(...) { \ 118 | assert(context->cursor || (!context->cursor && context->remaining == 0)); \ 119 | context->tmp = snprintf ( context->cursor, context->remaining, __VA_ARGS__ ); \ 120 | context->chars_written += context->tmp; \ 121 | if (context->cursor) { \ 122 | context->cursor += context->tmp; \ 123 | context->remaining -= context->tmp; \ 124 | } } 125 | 126 | #define CGLTF_SNPRINTF(length, ...) { \ 127 | assert(context->cursor || (!context->cursor && context->remaining == 0)); \ 128 | context->tmp = snprintf ( context->cursor, CGLTF_MIN(length + 1, context->remaining), __VA_ARGS__ ); \ 129 | context->chars_written += length; \ 130 | if (context->cursor) { \ 131 | context->cursor += length; \ 132 | context->remaining -= length; \ 133 | } } 134 | 135 | #define CGLTF_WRITE_IDXPROP(label, val, start) if (val) { \ 136 | cgltf_write_indent(context); \ 137 | CGLTF_SPRINTF("\"%s\": %d", label, (int) (val - start)); \ 138 | context->needs_comma = 1; } 139 | 140 | #define CGLTF_WRITE_IDXARRPROP(label, dim, vals, start) if (vals) { \ 141 | cgltf_write_indent(context); \ 142 | CGLTF_SPRINTF("\"%s\": [", label); \ 143 | for (int i = 0; i < (int)(dim); ++i) { \ 144 | int idx = (int) (vals[i] - start); \ 145 | if (i != 0) CGLTF_SPRINTF(","); \ 146 | CGLTF_SPRINTF(" %d", idx); \ 147 | } \ 148 | CGLTF_SPRINTF(" ]"); \ 149 | context->needs_comma = 1; } 150 | 151 | #define CGLTF_WRITE_TEXTURE_INFO(label, info) if (info.texture) { \ 152 | cgltf_write_line(context, "\"" label "\": {"); \ 153 | CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ 154 | cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ 155 | if (info.has_transform) { \ 156 | context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ 157 | cgltf_write_texture_transform(context, &info.transform); \ 158 | } \ 159 | cgltf_write_line(context, "}"); } 160 | 161 | #define CGLTF_WRITE_NORMAL_TEXTURE_INFO(label, info) if (info.texture) { \ 162 | cgltf_write_line(context, "\"" label "\": {"); \ 163 | CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ 164 | cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ 165 | cgltf_write_floatprop(context, "scale", info.scale, 1.0f); \ 166 | if (info.has_transform) { \ 167 | context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ 168 | cgltf_write_texture_transform(context, &info.transform); \ 169 | } \ 170 | cgltf_write_line(context, "}"); } 171 | 172 | #define CGLTF_WRITE_OCCLUSION_TEXTURE_INFO(label, info) if (info.texture) { \ 173 | cgltf_write_line(context, "\"" label "\": {"); \ 174 | CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ 175 | cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ 176 | cgltf_write_floatprop(context, "strength", info.scale, 1.0f); \ 177 | if (info.has_transform) { \ 178 | context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ 179 | cgltf_write_texture_transform(context, &info.transform); \ 180 | } \ 181 | cgltf_write_line(context, "}"); } 182 | 183 | #ifndef CGLTF_CONSTS 184 | #define GlbHeaderSize 12 185 | #define GlbChunkHeaderSize 8 186 | static const uint32_t GlbVersion = 2; 187 | static const uint32_t GlbMagic = 0x46546C67; 188 | static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; 189 | static const uint32_t GlbMagicBinChunk = 0x004E4942; 190 | #define CGLTF_CONSTS 191 | #endif 192 | 193 | static void cgltf_write_indent(cgltf_write_context* context) 194 | { 195 | if (context->needs_comma) 196 | { 197 | CGLTF_SPRINTF(",\n"); 198 | context->needs_comma = 0; 199 | } 200 | else 201 | { 202 | CGLTF_SPRINTF("\n"); 203 | } 204 | for (int i = 0; i < context->depth; ++i) 205 | { 206 | CGLTF_SPRINTF("%s", context->indent); 207 | } 208 | } 209 | 210 | static void cgltf_write_line(cgltf_write_context* context, const char* line) 211 | { 212 | if (line[0] == ']' || line[0] == '}') 213 | { 214 | --context->depth; 215 | context->needs_comma = 0; 216 | } 217 | cgltf_write_indent(context); 218 | CGLTF_SPRINTF("%s", line); 219 | cgltf_size last = (cgltf_size)(strlen(line) - 1); 220 | if (line[0] == ']' || line[0] == '}') 221 | { 222 | context->needs_comma = 1; 223 | } 224 | if (line[last] == '[' || line[last] == '{') 225 | { 226 | ++context->depth; 227 | context->needs_comma = 0; 228 | } 229 | } 230 | 231 | static void cgltf_write_strprop(cgltf_write_context* context, const char* label, const char* val) 232 | { 233 | if (val) 234 | { 235 | cgltf_write_indent(context); 236 | CGLTF_SPRINTF("\"%s\": \"%s\"", label, val); 237 | context->needs_comma = 1; 238 | } 239 | } 240 | 241 | static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras) 242 | { 243 | if (extras->data) 244 | { 245 | cgltf_write_indent(context); 246 | CGLTF_SPRINTF("\"extras\": %s", extras->data); 247 | context->needs_comma = 1; 248 | } 249 | else 250 | { 251 | cgltf_size length = extras->end_offset - extras->start_offset; 252 | if (length > 0 && context->data->json) 253 | { 254 | char* json_string = ((char*) context->data->json) + extras->start_offset; 255 | cgltf_write_indent(context); 256 | CGLTF_SPRINTF("%s", "\"extras\": "); 257 | CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string); 258 | context->needs_comma = 1; 259 | } 260 | } 261 | } 262 | 263 | static void cgltf_write_stritem(cgltf_write_context* context, const char* item) 264 | { 265 | cgltf_write_indent(context); 266 | CGLTF_SPRINTF("\"%s\"", item); 267 | context->needs_comma = 1; 268 | } 269 | 270 | static void cgltf_write_intprop(cgltf_write_context* context, const char* label, int val, int def) 271 | { 272 | if (val != def) 273 | { 274 | cgltf_write_indent(context); 275 | CGLTF_SPRINTF("\"%s\": %d", label, val); 276 | context->needs_comma = 1; 277 | } 278 | } 279 | 280 | static void cgltf_write_sizeprop(cgltf_write_context* context, const char* label, cgltf_size val, cgltf_size def) 281 | { 282 | if (val != def) 283 | { 284 | cgltf_write_indent(context); 285 | CGLTF_SPRINTF("\"%s\": %zu", label, val); 286 | context->needs_comma = 1; 287 | } 288 | } 289 | 290 | static void cgltf_write_floatprop(cgltf_write_context* context, const char* label, float val, float def) 291 | { 292 | if (val != def) 293 | { 294 | cgltf_write_indent(context); 295 | CGLTF_SPRINTF("\"%s\": ", label); 296 | CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, val); 297 | context->needs_comma = 1; 298 | 299 | if (context->cursor) 300 | { 301 | char *decimal_comma = strchr(context->cursor - context->tmp, ','); 302 | if (decimal_comma) 303 | { 304 | *decimal_comma = '.'; 305 | } 306 | } 307 | } 308 | } 309 | 310 | static void cgltf_write_boolprop_optional(cgltf_write_context* context, const char* label, bool val, bool def) 311 | { 312 | if (val != def) 313 | { 314 | cgltf_write_indent(context); 315 | CGLTF_SPRINTF("\"%s\": %s", label, val ? "true" : "false"); 316 | context->needs_comma = 1; 317 | } 318 | } 319 | 320 | static void cgltf_write_floatarrayprop(cgltf_write_context* context, const char* label, const cgltf_float* vals, cgltf_size dim) 321 | { 322 | cgltf_write_indent(context); 323 | CGLTF_SPRINTF("\"%s\": [", label); 324 | for (cgltf_size i = 0; i < dim; ++i) 325 | { 326 | if (i != 0) 327 | { 328 | CGLTF_SPRINTF(", %.*g", CGLTF_DECIMAL_DIG, vals[i]); 329 | } 330 | else 331 | { 332 | CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, vals[i]); 333 | } 334 | } 335 | CGLTF_SPRINTF("]"); 336 | context->needs_comma = 1; 337 | } 338 | 339 | static bool cgltf_check_floatarray(const float* vals, int dim, float val) { 340 | while (dim--) 341 | { 342 | if (vals[dim] != val) 343 | { 344 | return true; 345 | } 346 | } 347 | return false; 348 | } 349 | 350 | static int cgltf_int_from_component_type(cgltf_component_type ctype) 351 | { 352 | switch (ctype) 353 | { 354 | case cgltf_component_type_r_8: return 5120; 355 | case cgltf_component_type_r_8u: return 5121; 356 | case cgltf_component_type_r_16: return 5122; 357 | case cgltf_component_type_r_16u: return 5123; 358 | case cgltf_component_type_r_32u: return 5125; 359 | case cgltf_component_type_r_32f: return 5126; 360 | default: return 0; 361 | } 362 | } 363 | 364 | static int cgltf_int_from_primitive_type(cgltf_primitive_type ctype) 365 | { 366 | switch (ctype) 367 | { 368 | case cgltf_primitive_type_points: return 0; 369 | case cgltf_primitive_type_lines: return 1; 370 | case cgltf_primitive_type_line_loop: return 2; 371 | case cgltf_primitive_type_line_strip: return 3; 372 | case cgltf_primitive_type_triangles: return 4; 373 | case cgltf_primitive_type_triangle_strip: return 5; 374 | case cgltf_primitive_type_triangle_fan: return 6; 375 | default: return -1; 376 | } 377 | } 378 | 379 | static const char* cgltf_str_from_alpha_mode(cgltf_alpha_mode alpha_mode) 380 | { 381 | switch (alpha_mode) 382 | { 383 | case cgltf_alpha_mode_mask: return "MASK"; 384 | case cgltf_alpha_mode_blend: return "BLEND"; 385 | default: return NULL; 386 | } 387 | } 388 | 389 | static const char* cgltf_str_from_type(cgltf_type type) 390 | { 391 | switch (type) 392 | { 393 | case cgltf_type_scalar: return "SCALAR"; 394 | case cgltf_type_vec2: return "VEC2"; 395 | case cgltf_type_vec3: return "VEC3"; 396 | case cgltf_type_vec4: return "VEC4"; 397 | case cgltf_type_mat2: return "MAT2"; 398 | case cgltf_type_mat3: return "MAT3"; 399 | case cgltf_type_mat4: return "MAT4"; 400 | default: return NULL; 401 | } 402 | } 403 | 404 | static cgltf_size cgltf_dim_from_type(cgltf_type type) 405 | { 406 | switch (type) 407 | { 408 | case cgltf_type_scalar: return 1; 409 | case cgltf_type_vec2: return 2; 410 | case cgltf_type_vec3: return 3; 411 | case cgltf_type_vec4: return 4; 412 | case cgltf_type_mat2: return 4; 413 | case cgltf_type_mat3: return 9; 414 | case cgltf_type_mat4: return 16; 415 | default: return 0; 416 | } 417 | } 418 | 419 | static const char* cgltf_str_from_camera_type(cgltf_camera_type camera_type) 420 | { 421 | switch (camera_type) 422 | { 423 | case cgltf_camera_type_perspective: return "perspective"; 424 | case cgltf_camera_type_orthographic: return "orthographic"; 425 | default: return NULL; 426 | } 427 | } 428 | 429 | static const char* cgltf_str_from_light_type(cgltf_light_type light_type) 430 | { 431 | switch (light_type) 432 | { 433 | case cgltf_light_type_directional: return "directional"; 434 | case cgltf_light_type_point: return "point"; 435 | case cgltf_light_type_spot: return "spot"; 436 | default: return NULL; 437 | } 438 | } 439 | 440 | static void cgltf_write_texture_transform(cgltf_write_context* context, const cgltf_texture_transform* transform) 441 | { 442 | cgltf_write_line(context, "\"extensions\": {"); 443 | cgltf_write_line(context, "\"KHR_texture_transform\": {"); 444 | if (cgltf_check_floatarray(transform->offset, 2, 0.0f)) 445 | { 446 | cgltf_write_floatarrayprop(context, "offset", transform->offset, 2); 447 | } 448 | cgltf_write_floatprop(context, "rotation", transform->rotation, 0.0f); 449 | if (cgltf_check_floatarray(transform->scale, 2, 1.0f)) 450 | { 451 | cgltf_write_floatarrayprop(context, "scale", transform->scale, 2); 452 | } 453 | if (transform->has_texcoord) 454 | { 455 | cgltf_write_intprop(context, "texCoord", transform->texcoord, -1); 456 | } 457 | cgltf_write_line(context, "}"); 458 | cgltf_write_line(context, "}"); 459 | } 460 | 461 | static void cgltf_write_asset(cgltf_write_context* context, const cgltf_asset* asset) 462 | { 463 | cgltf_write_line(context, "\"asset\": {"); 464 | cgltf_write_strprop(context, "copyright", asset->copyright); 465 | cgltf_write_strprop(context, "generator", asset->generator); 466 | cgltf_write_strprop(context, "version", asset->version); 467 | cgltf_write_strprop(context, "min_version", asset->min_version); 468 | cgltf_write_extras(context, &asset->extras); 469 | cgltf_write_line(context, "}"); 470 | } 471 | 472 | static void cgltf_write_primitive(cgltf_write_context* context, const cgltf_primitive* prim) 473 | { 474 | cgltf_write_intprop(context, "mode", cgltf_int_from_primitive_type(prim->type), 4); 475 | CGLTF_WRITE_IDXPROP("indices", prim->indices, context->data->accessors); 476 | CGLTF_WRITE_IDXPROP("material", prim->material, context->data->materials); 477 | cgltf_write_line(context, "\"attributes\": {"); 478 | for (cgltf_size i = 0; i < prim->attributes_count; ++i) 479 | { 480 | const cgltf_attribute* attr = prim->attributes + i; 481 | CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); 482 | } 483 | cgltf_write_line(context, "}"); 484 | 485 | if (prim->targets_count) 486 | { 487 | cgltf_write_line(context, "\"targets\": ["); 488 | for (cgltf_size i = 0; i < prim->targets_count; ++i) 489 | { 490 | cgltf_write_line(context, "{"); 491 | for (cgltf_size j = 0; j < prim->targets[i].attributes_count; ++j) 492 | { 493 | const cgltf_attribute* attr = prim->targets[i].attributes + j; 494 | CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); 495 | } 496 | cgltf_write_line(context, "}"); 497 | } 498 | cgltf_write_line(context, "]"); 499 | } 500 | cgltf_write_extras(context, &prim->extras); 501 | 502 | if (prim->has_draco_mesh_compression || prim->mappings_count > 0) 503 | { 504 | cgltf_write_line(context, "\"extensions\": {"); 505 | 506 | if (prim->has_draco_mesh_compression) 507 | { 508 | context->extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; 509 | if (prim->attributes_count == 0 || prim->indices == 0) 510 | { 511 | context->required_extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; 512 | } 513 | 514 | cgltf_write_line(context, "\"KHR_draco_mesh_compression\": {"); 515 | CGLTF_WRITE_IDXPROP("bufferView", prim->draco_mesh_compression.buffer_view, context->data->buffer_views); 516 | cgltf_write_line(context, "\"attributes\": {"); 517 | for (cgltf_size i = 0; i < prim->draco_mesh_compression.attributes_count; ++i) 518 | { 519 | const cgltf_attribute* attr = prim->draco_mesh_compression.attributes + i; 520 | CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); 521 | } 522 | cgltf_write_line(context, "}"); 523 | cgltf_write_line(context, "}"); 524 | } 525 | 526 | if (prim->mappings_count > 0) 527 | { 528 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; 529 | cgltf_write_line(context, "\"KHR_materials_variants\": {"); 530 | cgltf_write_line(context, "\"mappings\": ["); 531 | for (cgltf_size i = 0; i < prim->mappings_count; ++i) 532 | { 533 | const cgltf_material_mapping* map = prim->mappings + i; 534 | cgltf_write_line(context, "{"); 535 | CGLTF_WRITE_IDXPROP("material", map->material, context->data->materials); 536 | 537 | cgltf_write_indent(context); 538 | CGLTF_SPRINTF("\"variants\": [%d]", (int)map->variant); 539 | context->needs_comma = 1; 540 | 541 | cgltf_write_extras(context, &map->extras); 542 | cgltf_write_line(context, "}"); 543 | } 544 | cgltf_write_line(context, "]"); 545 | cgltf_write_line(context, "}"); 546 | } 547 | 548 | cgltf_write_line(context, "}"); 549 | } 550 | } 551 | 552 | static void cgltf_write_mesh(cgltf_write_context* context, const cgltf_mesh* mesh) 553 | { 554 | cgltf_write_line(context, "{"); 555 | cgltf_write_strprop(context, "name", mesh->name); 556 | 557 | cgltf_write_line(context, "\"primitives\": ["); 558 | for (cgltf_size i = 0; i < mesh->primitives_count; ++i) 559 | { 560 | cgltf_write_line(context, "{"); 561 | cgltf_write_primitive(context, mesh->primitives + i); 562 | cgltf_write_line(context, "}"); 563 | } 564 | cgltf_write_line(context, "]"); 565 | 566 | if (mesh->weights_count > 0) 567 | { 568 | cgltf_write_floatarrayprop(context, "weights", mesh->weights, mesh->weights_count); 569 | } 570 | 571 | cgltf_write_extras(context, &mesh->extras); 572 | cgltf_write_line(context, "}"); 573 | } 574 | 575 | static void cgltf_write_buffer_view(cgltf_write_context* context, const cgltf_buffer_view* view) 576 | { 577 | cgltf_write_line(context, "{"); 578 | cgltf_write_strprop(context, "name", view->name); 579 | CGLTF_WRITE_IDXPROP("buffer", view->buffer, context->data->buffers); 580 | cgltf_write_sizeprop(context, "byteLength", view->size, (cgltf_size)-1); 581 | cgltf_write_sizeprop(context, "byteOffset", view->offset, 0); 582 | cgltf_write_sizeprop(context, "byteStride", view->stride, 0); 583 | // NOTE: We skip writing "target" because the spec says its usage can be inferred. 584 | cgltf_write_extras(context, &view->extras); 585 | cgltf_write_line(context, "}"); 586 | } 587 | 588 | 589 | static void cgltf_write_buffer(cgltf_write_context* context, const cgltf_buffer* buffer) 590 | { 591 | cgltf_write_line(context, "{"); 592 | cgltf_write_strprop(context, "name", buffer->name); 593 | cgltf_write_strprop(context, "uri", buffer->uri); 594 | cgltf_write_sizeprop(context, "byteLength", buffer->size, (cgltf_size)-1); 595 | cgltf_write_extras(context, &buffer->extras); 596 | cgltf_write_line(context, "}"); 597 | } 598 | 599 | static void cgltf_write_material(cgltf_write_context* context, const cgltf_material* material) 600 | { 601 | cgltf_write_line(context, "{"); 602 | cgltf_write_strprop(context, "name", material->name); 603 | if (material->alpha_mode == cgltf_alpha_mode_mask) 604 | { 605 | cgltf_write_floatprop(context, "alphaCutoff", material->alpha_cutoff, 0.5f); 606 | } 607 | cgltf_write_boolprop_optional(context, "doubleSided", (bool)material->double_sided, false); 608 | // cgltf_write_boolprop_optional(context, "unlit", material->unlit, false); 609 | 610 | if (material->unlit) 611 | { 612 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT; 613 | } 614 | 615 | if (material->has_pbr_specular_glossiness) 616 | { 617 | context->extension_flags |= CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS; 618 | } 619 | 620 | if (material->has_clearcoat) 621 | { 622 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT; 623 | } 624 | 625 | if (material->has_transmission) 626 | { 627 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION; 628 | } 629 | 630 | if (material->has_volume) 631 | { 632 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME; 633 | } 634 | 635 | if (material->has_ior) 636 | { 637 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IOR; 638 | } 639 | 640 | if (material->has_specular) 641 | { 642 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR; 643 | } 644 | 645 | if (material->has_sheen) 646 | { 647 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN; 648 | } 649 | 650 | if (material->has_emissive_strength) 651 | { 652 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH; 653 | } 654 | 655 | if (material->has_iridescence) 656 | { 657 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE; 658 | } 659 | 660 | if (material->has_diffuse_transmission) 661 | { 662 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_DIFFUSE_TRANSMISSION; 663 | } 664 | 665 | if (material->has_anisotropy) 666 | { 667 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY; 668 | } 669 | 670 | if (material->has_dispersion) 671 | { 672 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION; 673 | } 674 | 675 | if (material->has_pbr_metallic_roughness) 676 | { 677 | const cgltf_pbr_metallic_roughness* params = &material->pbr_metallic_roughness; 678 | cgltf_write_line(context, "\"pbrMetallicRoughness\": {"); 679 | CGLTF_WRITE_TEXTURE_INFO("baseColorTexture", params->base_color_texture); 680 | CGLTF_WRITE_TEXTURE_INFO("metallicRoughnessTexture", params->metallic_roughness_texture); 681 | cgltf_write_floatprop(context, "metallicFactor", params->metallic_factor, 1.0f); 682 | cgltf_write_floatprop(context, "roughnessFactor", params->roughness_factor, 1.0f); 683 | if (cgltf_check_floatarray(params->base_color_factor, 4, 1.0f)) 684 | { 685 | cgltf_write_floatarrayprop(context, "baseColorFactor", params->base_color_factor, 4); 686 | } 687 | cgltf_write_line(context, "}"); 688 | } 689 | 690 | if (material->unlit || material->has_pbr_specular_glossiness || material->has_clearcoat || material->has_ior || material->has_specular || material->has_transmission || material->has_sheen || material->has_volume || material->has_emissive_strength || material->has_iridescence || material->has_anisotropy || material->has_dispersion || material->has_diffuse_transmission) 691 | { 692 | cgltf_write_line(context, "\"extensions\": {"); 693 | if (material->has_clearcoat) 694 | { 695 | const cgltf_clearcoat* params = &material->clearcoat; 696 | cgltf_write_line(context, "\"KHR_materials_clearcoat\": {"); 697 | CGLTF_WRITE_TEXTURE_INFO("clearcoatTexture", params->clearcoat_texture); 698 | CGLTF_WRITE_TEXTURE_INFO("clearcoatRoughnessTexture", params->clearcoat_roughness_texture); 699 | CGLTF_WRITE_NORMAL_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture); 700 | cgltf_write_floatprop(context, "clearcoatFactor", params->clearcoat_factor, 0.0f); 701 | cgltf_write_floatprop(context, "clearcoatRoughnessFactor", params->clearcoat_roughness_factor, 0.0f); 702 | cgltf_write_line(context, "}"); 703 | } 704 | if (material->has_ior) 705 | { 706 | const cgltf_ior* params = &material->ior; 707 | cgltf_write_line(context, "\"KHR_materials_ior\": {"); 708 | cgltf_write_floatprop(context, "ior", params->ior, 1.5f); 709 | cgltf_write_line(context, "}"); 710 | } 711 | if (material->has_specular) 712 | { 713 | const cgltf_specular* params = &material->specular; 714 | cgltf_write_line(context, "\"KHR_materials_specular\": {"); 715 | CGLTF_WRITE_TEXTURE_INFO("specularTexture", params->specular_texture); 716 | CGLTF_WRITE_TEXTURE_INFO("specularColorTexture", params->specular_color_texture); 717 | cgltf_write_floatprop(context, "specularFactor", params->specular_factor, 1.0f); 718 | if (cgltf_check_floatarray(params->specular_color_factor, 3, 1.0f)) 719 | { 720 | cgltf_write_floatarrayprop(context, "specularColorFactor", params->specular_color_factor, 3); 721 | } 722 | cgltf_write_line(context, "}"); 723 | } 724 | if (material->has_transmission) 725 | { 726 | const cgltf_transmission* params = &material->transmission; 727 | cgltf_write_line(context, "\"KHR_materials_transmission\": {"); 728 | CGLTF_WRITE_TEXTURE_INFO("transmissionTexture", params->transmission_texture); 729 | cgltf_write_floatprop(context, "transmissionFactor", params->transmission_factor, 0.0f); 730 | cgltf_write_line(context, "}"); 731 | } 732 | if (material->has_volume) 733 | { 734 | const cgltf_volume* params = &material->volume; 735 | cgltf_write_line(context, "\"KHR_materials_volume\": {"); 736 | CGLTF_WRITE_TEXTURE_INFO("thicknessTexture", params->thickness_texture); 737 | cgltf_write_floatprop(context, "thicknessFactor", params->thickness_factor, 0.0f); 738 | if (cgltf_check_floatarray(params->attenuation_color, 3, 1.0f)) 739 | { 740 | cgltf_write_floatarrayprop(context, "attenuationColor", params->attenuation_color, 3); 741 | } 742 | if (params->attenuation_distance < FLT_MAX) 743 | { 744 | cgltf_write_floatprop(context, "attenuationDistance", params->attenuation_distance, FLT_MAX); 745 | } 746 | cgltf_write_line(context, "}"); 747 | } 748 | if (material->has_sheen) 749 | { 750 | const cgltf_sheen* params = &material->sheen; 751 | cgltf_write_line(context, "\"KHR_materials_sheen\": {"); 752 | CGLTF_WRITE_TEXTURE_INFO("sheenColorTexture", params->sheen_color_texture); 753 | CGLTF_WRITE_TEXTURE_INFO("sheenRoughnessTexture", params->sheen_roughness_texture); 754 | if (cgltf_check_floatarray(params->sheen_color_factor, 3, 0.0f)) 755 | { 756 | cgltf_write_floatarrayprop(context, "sheenColorFactor", params->sheen_color_factor, 3); 757 | } 758 | cgltf_write_floatprop(context, "sheenRoughnessFactor", params->sheen_roughness_factor, 0.0f); 759 | cgltf_write_line(context, "}"); 760 | } 761 | if (material->has_pbr_specular_glossiness) 762 | { 763 | const cgltf_pbr_specular_glossiness* params = &material->pbr_specular_glossiness; 764 | cgltf_write_line(context, "\"KHR_materials_pbrSpecularGlossiness\": {"); 765 | CGLTF_WRITE_TEXTURE_INFO("diffuseTexture", params->diffuse_texture); 766 | CGLTF_WRITE_TEXTURE_INFO("specularGlossinessTexture", params->specular_glossiness_texture); 767 | if (cgltf_check_floatarray(params->diffuse_factor, 4, 1.0f)) 768 | { 769 | cgltf_write_floatarrayprop(context, "diffuseFactor", params->diffuse_factor, 4); 770 | } 771 | if (cgltf_check_floatarray(params->specular_factor, 3, 1.0f)) 772 | { 773 | cgltf_write_floatarrayprop(context, "specularFactor", params->specular_factor, 3); 774 | } 775 | cgltf_write_floatprop(context, "glossinessFactor", params->glossiness_factor, 1.0f); 776 | cgltf_write_line(context, "}"); 777 | } 778 | if (material->unlit) 779 | { 780 | cgltf_write_line(context, "\"KHR_materials_unlit\": {}"); 781 | } 782 | if (material->has_emissive_strength) 783 | { 784 | cgltf_write_line(context, "\"KHR_materials_emissive_strength\": {"); 785 | const cgltf_emissive_strength* params = &material->emissive_strength; 786 | cgltf_write_floatprop(context, "emissiveStrength", params->emissive_strength, 1.f); 787 | cgltf_write_line(context, "}"); 788 | } 789 | if (material->has_iridescence) 790 | { 791 | cgltf_write_line(context, "\"KHR_materials_iridescence\": {"); 792 | const cgltf_iridescence* params = &material->iridescence; 793 | cgltf_write_floatprop(context, "iridescenceFactor", params->iridescence_factor, 0.f); 794 | CGLTF_WRITE_TEXTURE_INFO("iridescenceTexture", params->iridescence_texture); 795 | cgltf_write_floatprop(context, "iridescenceIor", params->iridescence_ior, 1.3f); 796 | cgltf_write_floatprop(context, "iridescenceThicknessMinimum", params->iridescence_thickness_min, 100.f); 797 | cgltf_write_floatprop(context, "iridescenceThicknessMaximum", params->iridescence_thickness_max, 400.f); 798 | CGLTF_WRITE_TEXTURE_INFO("iridescenceThicknessTexture", params->iridescence_thickness_texture); 799 | cgltf_write_line(context, "}"); 800 | } 801 | if (material->has_diffuse_transmission) 802 | { 803 | const cgltf_diffuse_transmission* params = &material->diffuse_transmission; 804 | cgltf_write_line(context, "\"KHR_materials_diffuse_transmission\": {"); 805 | CGLTF_WRITE_TEXTURE_INFO("diffuseTransmissionTexture", params->diffuse_transmission_texture); 806 | cgltf_write_floatprop(context, "diffuseTransmissionFactor", params->diffuse_transmission_factor, 0.f); 807 | if (cgltf_check_floatarray(params->diffuse_transmission_color_factor, 3, 1.f)) 808 | { 809 | cgltf_write_floatarrayprop(context, "diffuseTransmissionColorFactor", params->diffuse_transmission_color_factor, 3); 810 | } 811 | CGLTF_WRITE_TEXTURE_INFO("diffuseTransmissionColorTexture", params->diffuse_transmission_color_texture); 812 | cgltf_write_line(context, "}"); 813 | } 814 | if (material->has_anisotropy) 815 | { 816 | cgltf_write_line(context, "\"KHR_materials_anisotropy\": {"); 817 | const cgltf_anisotropy* params = &material->anisotropy; 818 | cgltf_write_floatprop(context, "anisotropyStrength", params->anisotropy_strength, 0.f); 819 | cgltf_write_floatprop(context, "anisotropyRotation", params->anisotropy_rotation, 0.f); 820 | CGLTF_WRITE_TEXTURE_INFO("anisotropyTexture", params->anisotropy_texture); 821 | cgltf_write_line(context, "}"); 822 | } 823 | if (material->has_dispersion) 824 | { 825 | cgltf_write_line(context, "\"KHR_materials_dispersion\": {"); 826 | const cgltf_dispersion* params = &material->dispersion; 827 | cgltf_write_floatprop(context, "dispersion", params->dispersion, 0.f); 828 | cgltf_write_line(context, "}"); 829 | } 830 | cgltf_write_line(context, "}"); 831 | } 832 | 833 | CGLTF_WRITE_NORMAL_TEXTURE_INFO("normalTexture", material->normal_texture); 834 | CGLTF_WRITE_OCCLUSION_TEXTURE_INFO("occlusionTexture", material->occlusion_texture); 835 | CGLTF_WRITE_TEXTURE_INFO("emissiveTexture", material->emissive_texture); 836 | if (cgltf_check_floatarray(material->emissive_factor, 3, 0.0f)) 837 | { 838 | cgltf_write_floatarrayprop(context, "emissiveFactor", material->emissive_factor, 3); 839 | } 840 | cgltf_write_strprop(context, "alphaMode", cgltf_str_from_alpha_mode(material->alpha_mode)); 841 | cgltf_write_extras(context, &material->extras); 842 | cgltf_write_line(context, "}"); 843 | } 844 | 845 | static void cgltf_write_image(cgltf_write_context* context, const cgltf_image* image) 846 | { 847 | cgltf_write_line(context, "{"); 848 | cgltf_write_strprop(context, "name", image->name); 849 | cgltf_write_strprop(context, "uri", image->uri); 850 | CGLTF_WRITE_IDXPROP("bufferView", image->buffer_view, context->data->buffer_views); 851 | cgltf_write_strprop(context, "mimeType", image->mime_type); 852 | cgltf_write_extras(context, &image->extras); 853 | cgltf_write_line(context, "}"); 854 | } 855 | 856 | static void cgltf_write_texture(cgltf_write_context* context, const cgltf_texture* texture) 857 | { 858 | cgltf_write_line(context, "{"); 859 | cgltf_write_strprop(context, "name", texture->name); 860 | CGLTF_WRITE_IDXPROP("source", texture->image, context->data->images); 861 | CGLTF_WRITE_IDXPROP("sampler", texture->sampler, context->data->samplers); 862 | 863 | if (texture->has_basisu || texture->has_webp) 864 | { 865 | cgltf_write_line(context, "\"extensions\": {"); 866 | if (texture->has_basisu) 867 | { 868 | context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_BASISU; 869 | cgltf_write_line(context, "\"KHR_texture_basisu\": {"); 870 | CGLTF_WRITE_IDXPROP("source", texture->basisu_image, context->data->images); 871 | cgltf_write_line(context, "}"); 872 | } 873 | if (texture->has_webp) 874 | { 875 | context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_WEBP; 876 | cgltf_write_line(context, "\"EXT_texture_webp\": {"); 877 | CGLTF_WRITE_IDXPROP("source", texture->webp_image, context->data->images); 878 | cgltf_write_line(context, "}"); 879 | } 880 | cgltf_write_line(context, "}"); 881 | } 882 | cgltf_write_extras(context, &texture->extras); 883 | cgltf_write_line(context, "}"); 884 | } 885 | 886 | static void cgltf_write_skin(cgltf_write_context* context, const cgltf_skin* skin) 887 | { 888 | cgltf_write_line(context, "{"); 889 | CGLTF_WRITE_IDXPROP("skeleton", skin->skeleton, context->data->nodes); 890 | CGLTF_WRITE_IDXPROP("inverseBindMatrices", skin->inverse_bind_matrices, context->data->accessors); 891 | CGLTF_WRITE_IDXARRPROP("joints", skin->joints_count, skin->joints, context->data->nodes); 892 | cgltf_write_strprop(context, "name", skin->name); 893 | cgltf_write_extras(context, &skin->extras); 894 | cgltf_write_line(context, "}"); 895 | } 896 | 897 | static const char* cgltf_write_str_path_type(cgltf_animation_path_type path_type) 898 | { 899 | switch (path_type) 900 | { 901 | case cgltf_animation_path_type_translation: 902 | return "translation"; 903 | case cgltf_animation_path_type_rotation: 904 | return "rotation"; 905 | case cgltf_animation_path_type_scale: 906 | return "scale"; 907 | case cgltf_animation_path_type_weights: 908 | return "weights"; 909 | default: 910 | break; 911 | } 912 | return "invalid"; 913 | } 914 | 915 | static const char* cgltf_write_str_interpolation_type(cgltf_interpolation_type interpolation_type) 916 | { 917 | switch (interpolation_type) 918 | { 919 | case cgltf_interpolation_type_linear: 920 | return "LINEAR"; 921 | case cgltf_interpolation_type_step: 922 | return "STEP"; 923 | case cgltf_interpolation_type_cubic_spline: 924 | return "CUBICSPLINE"; 925 | default: 926 | break; 927 | } 928 | return "invalid"; 929 | } 930 | 931 | static void cgltf_write_path_type(cgltf_write_context* context, const char *label, cgltf_animation_path_type path_type) 932 | { 933 | cgltf_write_strprop(context, label, cgltf_write_str_path_type(path_type)); 934 | } 935 | 936 | static void cgltf_write_interpolation_type(cgltf_write_context* context, const char *label, cgltf_interpolation_type interpolation_type) 937 | { 938 | cgltf_write_strprop(context, label, cgltf_write_str_interpolation_type(interpolation_type)); 939 | } 940 | 941 | static void cgltf_write_animation_sampler(cgltf_write_context* context, const cgltf_animation_sampler* animation_sampler) 942 | { 943 | cgltf_write_line(context, "{"); 944 | cgltf_write_interpolation_type(context, "interpolation", animation_sampler->interpolation); 945 | CGLTF_WRITE_IDXPROP("input", animation_sampler->input, context->data->accessors); 946 | CGLTF_WRITE_IDXPROP("output", animation_sampler->output, context->data->accessors); 947 | cgltf_write_extras(context, &animation_sampler->extras); 948 | cgltf_write_line(context, "}"); 949 | } 950 | 951 | static void cgltf_write_animation_channel(cgltf_write_context* context, const cgltf_animation* animation, const cgltf_animation_channel* animation_channel) 952 | { 953 | cgltf_write_line(context, "{"); 954 | CGLTF_WRITE_IDXPROP("sampler", animation_channel->sampler, animation->samplers); 955 | cgltf_write_line(context, "\"target\": {"); 956 | CGLTF_WRITE_IDXPROP("node", animation_channel->target_node, context->data->nodes); 957 | cgltf_write_path_type(context, "path", animation_channel->target_path); 958 | cgltf_write_line(context, "}"); 959 | cgltf_write_extras(context, &animation_channel->extras); 960 | cgltf_write_line(context, "}"); 961 | } 962 | 963 | static void cgltf_write_animation(cgltf_write_context* context, const cgltf_animation* animation) 964 | { 965 | cgltf_write_line(context, "{"); 966 | cgltf_write_strprop(context, "name", animation->name); 967 | 968 | if (animation->samplers_count > 0) 969 | { 970 | cgltf_write_line(context, "\"samplers\": ["); 971 | for (cgltf_size i = 0; i < animation->samplers_count; ++i) 972 | { 973 | cgltf_write_animation_sampler(context, animation->samplers + i); 974 | } 975 | cgltf_write_line(context, "]"); 976 | } 977 | if (animation->channels_count > 0) 978 | { 979 | cgltf_write_line(context, "\"channels\": ["); 980 | for (cgltf_size i = 0; i < animation->channels_count; ++i) 981 | { 982 | cgltf_write_animation_channel(context, animation, animation->channels + i); 983 | } 984 | cgltf_write_line(context, "]"); 985 | } 986 | cgltf_write_extras(context, &animation->extras); 987 | cgltf_write_line(context, "}"); 988 | } 989 | 990 | static void cgltf_write_sampler(cgltf_write_context* context, const cgltf_sampler* sampler) 991 | { 992 | cgltf_write_line(context, "{"); 993 | cgltf_write_strprop(context, "name", sampler->name); 994 | cgltf_write_intprop(context, "magFilter", sampler->mag_filter, 0); 995 | cgltf_write_intprop(context, "minFilter", sampler->min_filter, 0); 996 | cgltf_write_intprop(context, "wrapS", sampler->wrap_s, 10497); 997 | cgltf_write_intprop(context, "wrapT", sampler->wrap_t, 10497); 998 | cgltf_write_extras(context, &sampler->extras); 999 | cgltf_write_line(context, "}"); 1000 | } 1001 | 1002 | static void cgltf_write_node(cgltf_write_context* context, const cgltf_node* node) 1003 | { 1004 | cgltf_write_line(context, "{"); 1005 | CGLTF_WRITE_IDXARRPROP("children", node->children_count, node->children, context->data->nodes); 1006 | CGLTF_WRITE_IDXPROP("mesh", node->mesh, context->data->meshes); 1007 | cgltf_write_strprop(context, "name", node->name); 1008 | if (node->has_matrix) 1009 | { 1010 | cgltf_write_floatarrayprop(context, "matrix", node->matrix, 16); 1011 | } 1012 | if (node->has_translation) 1013 | { 1014 | cgltf_write_floatarrayprop(context, "translation", node->translation, 3); 1015 | } 1016 | if (node->has_rotation) 1017 | { 1018 | cgltf_write_floatarrayprop(context, "rotation", node->rotation, 4); 1019 | } 1020 | if (node->has_scale) 1021 | { 1022 | cgltf_write_floatarrayprop(context, "scale", node->scale, 3); 1023 | } 1024 | if (node->skin) 1025 | { 1026 | CGLTF_WRITE_IDXPROP("skin", node->skin, context->data->skins); 1027 | } 1028 | 1029 | bool has_extension = node->light || (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0); 1030 | if(has_extension) 1031 | cgltf_write_line(context, "\"extensions\": {"); 1032 | 1033 | if (node->light) 1034 | { 1035 | context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; 1036 | cgltf_write_line(context, "\"KHR_lights_punctual\": {"); 1037 | CGLTF_WRITE_IDXPROP("light", node->light, context->data->lights); 1038 | cgltf_write_line(context, "}"); 1039 | } 1040 | 1041 | if (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0) 1042 | { 1043 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; 1044 | context->required_extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; 1045 | 1046 | cgltf_write_line(context, "\"EXT_mesh_gpu_instancing\": {"); 1047 | { 1048 | cgltf_write_line(context, "\"attributes\": {"); 1049 | { 1050 | for (cgltf_size i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i) 1051 | { 1052 | const cgltf_attribute* attr = node->mesh_gpu_instancing.attributes + i; 1053 | CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); 1054 | } 1055 | } 1056 | cgltf_write_line(context, "}"); 1057 | } 1058 | cgltf_write_line(context, "}"); 1059 | } 1060 | 1061 | if (has_extension) 1062 | cgltf_write_line(context, "}"); 1063 | 1064 | if (node->weights_count > 0) 1065 | { 1066 | cgltf_write_floatarrayprop(context, "weights", node->weights, node->weights_count); 1067 | } 1068 | 1069 | if (node->camera) 1070 | { 1071 | CGLTF_WRITE_IDXPROP("camera", node->camera, context->data->cameras); 1072 | } 1073 | 1074 | cgltf_write_extras(context, &node->extras); 1075 | cgltf_write_line(context, "}"); 1076 | } 1077 | 1078 | static void cgltf_write_scene(cgltf_write_context* context, const cgltf_scene* scene) 1079 | { 1080 | cgltf_write_line(context, "{"); 1081 | cgltf_write_strprop(context, "name", scene->name); 1082 | CGLTF_WRITE_IDXARRPROP("nodes", scene->nodes_count, scene->nodes, context->data->nodes); 1083 | cgltf_write_extras(context, &scene->extras); 1084 | cgltf_write_line(context, "}"); 1085 | } 1086 | 1087 | static void cgltf_write_accessor(cgltf_write_context* context, const cgltf_accessor* accessor) 1088 | { 1089 | cgltf_write_line(context, "{"); 1090 | cgltf_write_strprop(context, "name", accessor->name); 1091 | CGLTF_WRITE_IDXPROP("bufferView", accessor->buffer_view, context->data->buffer_views); 1092 | cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->component_type), 0); 1093 | cgltf_write_strprop(context, "type", cgltf_str_from_type(accessor->type)); 1094 | cgltf_size dim = cgltf_dim_from_type(accessor->type); 1095 | cgltf_write_boolprop_optional(context, "normalized", (bool)accessor->normalized, false); 1096 | cgltf_write_sizeprop(context, "byteOffset", (int)accessor->offset, 0); 1097 | cgltf_write_intprop(context, "count", (int)accessor->count, -1); 1098 | if (accessor->has_min) 1099 | { 1100 | cgltf_write_floatarrayprop(context, "min", accessor->min, dim); 1101 | } 1102 | if (accessor->has_max) 1103 | { 1104 | cgltf_write_floatarrayprop(context, "max", accessor->max, dim); 1105 | } 1106 | if (accessor->is_sparse) 1107 | { 1108 | cgltf_write_line(context, "\"sparse\": {"); 1109 | cgltf_write_intprop(context, "count", (int)accessor->sparse.count, 0); 1110 | cgltf_write_line(context, "\"indices\": {"); 1111 | cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.indices_byte_offset, 0); 1112 | CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.indices_buffer_view, context->data->buffer_views); 1113 | cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->sparse.indices_component_type), 0); 1114 | cgltf_write_line(context, "}"); 1115 | cgltf_write_line(context, "\"values\": {"); 1116 | cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.values_byte_offset, 0); 1117 | CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.values_buffer_view, context->data->buffer_views); 1118 | cgltf_write_line(context, "}"); 1119 | cgltf_write_line(context, "}"); 1120 | } 1121 | cgltf_write_extras(context, &accessor->extras); 1122 | cgltf_write_line(context, "}"); 1123 | } 1124 | 1125 | static void cgltf_write_camera(cgltf_write_context* context, const cgltf_camera* camera) 1126 | { 1127 | cgltf_write_line(context, "{"); 1128 | cgltf_write_strprop(context, "type", cgltf_str_from_camera_type(camera->type)); 1129 | if (camera->name) 1130 | { 1131 | cgltf_write_strprop(context, "name", camera->name); 1132 | } 1133 | 1134 | if (camera->type == cgltf_camera_type_orthographic) 1135 | { 1136 | cgltf_write_line(context, "\"orthographic\": {"); 1137 | cgltf_write_floatprop(context, "xmag", camera->data.orthographic.xmag, -1.0f); 1138 | cgltf_write_floatprop(context, "ymag", camera->data.orthographic.ymag, -1.0f); 1139 | cgltf_write_floatprop(context, "zfar", camera->data.orthographic.zfar, -1.0f); 1140 | cgltf_write_floatprop(context, "znear", camera->data.orthographic.znear, -1.0f); 1141 | cgltf_write_extras(context, &camera->data.orthographic.extras); 1142 | cgltf_write_line(context, "}"); 1143 | } 1144 | else if (camera->type == cgltf_camera_type_perspective) 1145 | { 1146 | cgltf_write_line(context, "\"perspective\": {"); 1147 | 1148 | if (camera->data.perspective.has_aspect_ratio) { 1149 | cgltf_write_floatprop(context, "aspectRatio", camera->data.perspective.aspect_ratio, -1.0f); 1150 | } 1151 | 1152 | cgltf_write_floatprop(context, "yfov", camera->data.perspective.yfov, -1.0f); 1153 | 1154 | if (camera->data.perspective.has_zfar) { 1155 | cgltf_write_floatprop(context, "zfar", camera->data.perspective.zfar, -1.0f); 1156 | } 1157 | 1158 | cgltf_write_floatprop(context, "znear", camera->data.perspective.znear, -1.0f); 1159 | cgltf_write_extras(context, &camera->data.perspective.extras); 1160 | cgltf_write_line(context, "}"); 1161 | } 1162 | cgltf_write_extras(context, &camera->extras); 1163 | cgltf_write_line(context, "}"); 1164 | } 1165 | 1166 | static void cgltf_write_light(cgltf_write_context* context, const cgltf_light* light) 1167 | { 1168 | context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; 1169 | 1170 | cgltf_write_line(context, "{"); 1171 | cgltf_write_strprop(context, "type", cgltf_str_from_light_type(light->type)); 1172 | if (light->name) 1173 | { 1174 | cgltf_write_strprop(context, "name", light->name); 1175 | } 1176 | if (cgltf_check_floatarray(light->color, 3, 1.0f)) 1177 | { 1178 | cgltf_write_floatarrayprop(context, "color", light->color, 3); 1179 | } 1180 | cgltf_write_floatprop(context, "intensity", light->intensity, 1.0f); 1181 | cgltf_write_floatprop(context, "range", light->range, 0.0f); 1182 | 1183 | if (light->type == cgltf_light_type_spot) 1184 | { 1185 | cgltf_write_line(context, "\"spot\": {"); 1186 | cgltf_write_floatprop(context, "innerConeAngle", light->spot_inner_cone_angle, 0.0f); 1187 | cgltf_write_floatprop(context, "outerConeAngle", light->spot_outer_cone_angle, 3.14159265358979323846f/4.0f); 1188 | cgltf_write_line(context, "}"); 1189 | } 1190 | cgltf_write_extras( context, &light->extras ); 1191 | cgltf_write_line(context, "}"); 1192 | } 1193 | 1194 | static void cgltf_write_variant(cgltf_write_context* context, const cgltf_material_variant* variant) 1195 | { 1196 | context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; 1197 | 1198 | cgltf_write_line(context, "{"); 1199 | cgltf_write_strprop(context, "name", variant->name); 1200 | cgltf_write_extras(context, &variant->extras); 1201 | cgltf_write_line(context, "}"); 1202 | } 1203 | 1204 | static void cgltf_write_glb(FILE* file, const void* json_buf, const cgltf_size json_size, const void* bin_buf, const cgltf_size bin_size) 1205 | { 1206 | char header[GlbHeaderSize]; 1207 | char chunk_header[GlbChunkHeaderSize]; 1208 | char json_pad[3] = { 0x20, 0x20, 0x20 }; 1209 | char bin_pad[3] = { 0, 0, 0 }; 1210 | 1211 | cgltf_size json_padsize = (json_size % 4 != 0) ? 4 - json_size % 4 : 0; 1212 | cgltf_size bin_padsize = (bin_size % 4 != 0) ? 4 - bin_size % 4 : 0; 1213 | cgltf_size total_size = GlbHeaderSize + GlbChunkHeaderSize + json_size + json_padsize; 1214 | if (bin_buf != NULL && bin_size > 0) { 1215 | total_size += GlbChunkHeaderSize + bin_size + bin_padsize; 1216 | } 1217 | 1218 | // Write a GLB header 1219 | memcpy(header, &GlbMagic, 4); 1220 | memcpy(header + 4, &GlbVersion, 4); 1221 | memcpy(header + 8, &total_size, 4); 1222 | fwrite(header, 1, GlbHeaderSize, file); 1223 | 1224 | // Write a JSON chunk (header & data) 1225 | uint32_t json_chunk_size = (uint32_t)(json_size + json_padsize); 1226 | memcpy(chunk_header, &json_chunk_size, 4); 1227 | memcpy(chunk_header + 4, &GlbMagicJsonChunk, 4); 1228 | fwrite(chunk_header, 1, GlbChunkHeaderSize, file); 1229 | 1230 | fwrite(json_buf, 1, json_size, file); 1231 | fwrite(json_pad, 1, json_padsize, file); 1232 | 1233 | if (bin_buf != NULL && bin_size > 0) { 1234 | // Write a binary chunk (header & data) 1235 | uint32_t bin_chunk_size = (uint32_t)(bin_size + bin_padsize); 1236 | memcpy(chunk_header, &bin_chunk_size, 4); 1237 | memcpy(chunk_header + 4, &GlbMagicBinChunk, 4); 1238 | fwrite(chunk_header, 1, GlbChunkHeaderSize, file); 1239 | 1240 | fwrite(bin_buf, 1, bin_size, file); 1241 | fwrite(bin_pad, 1, bin_padsize, file); 1242 | } 1243 | } 1244 | 1245 | cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data) 1246 | { 1247 | cgltf_size expected = cgltf_write(options, NULL, 0, data); 1248 | char* buffer = (char*) malloc(expected); 1249 | cgltf_size actual = cgltf_write(options, buffer, expected, data); 1250 | if (expected != actual) { 1251 | fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual); 1252 | } 1253 | FILE* file = fopen(path, "wb"); 1254 | if (!file) 1255 | { 1256 | return cgltf_result_file_not_found; 1257 | } 1258 | // Note that cgltf_write() includes a null terminator, which we omit from the file content. 1259 | if (options->type == cgltf_file_type_glb) { 1260 | cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size); 1261 | } else { 1262 | // Write a plain JSON file. 1263 | fwrite(buffer, actual - 1, 1, file); 1264 | } 1265 | fclose(file); 1266 | free(buffer); 1267 | return cgltf_result_success; 1268 | } 1269 | 1270 | static void cgltf_write_extensions(cgltf_write_context* context, uint32_t extension_flags) 1271 | { 1272 | if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM) { 1273 | cgltf_write_stritem(context, "KHR_texture_transform"); 1274 | } 1275 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT) { 1276 | cgltf_write_stritem(context, "KHR_materials_unlit"); 1277 | } 1278 | if (extension_flags & CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS) { 1279 | cgltf_write_stritem(context, "KHR_materials_pbrSpecularGlossiness"); 1280 | } 1281 | if (extension_flags & CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL) { 1282 | cgltf_write_stritem(context, "KHR_lights_punctual"); 1283 | } 1284 | if (extension_flags & CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION) { 1285 | cgltf_write_stritem(context, "KHR_draco_mesh_compression"); 1286 | } 1287 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT) { 1288 | cgltf_write_stritem(context, "KHR_materials_clearcoat"); 1289 | } 1290 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IOR) { 1291 | cgltf_write_stritem(context, "KHR_materials_ior"); 1292 | } 1293 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR) { 1294 | cgltf_write_stritem(context, "KHR_materials_specular"); 1295 | } 1296 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION) { 1297 | cgltf_write_stritem(context, "KHR_materials_transmission"); 1298 | } 1299 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN) { 1300 | cgltf_write_stritem(context, "KHR_materials_sheen"); 1301 | } 1302 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS) { 1303 | cgltf_write_stritem(context, "KHR_materials_variants"); 1304 | } 1305 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME) { 1306 | cgltf_write_stritem(context, "KHR_materials_volume"); 1307 | } 1308 | if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_BASISU) { 1309 | cgltf_write_stritem(context, "KHR_texture_basisu"); 1310 | } 1311 | if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_WEBP) { 1312 | cgltf_write_stritem(context, "EXT_texture_webp"); 1313 | } 1314 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH) { 1315 | cgltf_write_stritem(context, "KHR_materials_emissive_strength"); 1316 | } 1317 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE) { 1318 | cgltf_write_stritem(context, "KHR_materials_iridescence"); 1319 | } 1320 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_DIFFUSE_TRANSMISSION) { 1321 | cgltf_write_stritem(context, "KHR_materials_diffuse_transmission"); 1322 | } 1323 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY) { 1324 | cgltf_write_stritem(context, "KHR_materials_anisotropy"); 1325 | } 1326 | if (extension_flags & CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING) { 1327 | cgltf_write_stritem(context, "EXT_mesh_gpu_instancing"); 1328 | } 1329 | if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION) { 1330 | cgltf_write_stritem(context, "KHR_materials_dispersion"); 1331 | } 1332 | } 1333 | 1334 | cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data) 1335 | { 1336 | (void)options; 1337 | cgltf_write_context ctx; 1338 | ctx.buffer = buffer; 1339 | ctx.buffer_size = size; 1340 | ctx.remaining = size; 1341 | ctx.cursor = buffer; 1342 | ctx.chars_written = 0; 1343 | ctx.data = data; 1344 | ctx.depth = 1; 1345 | ctx.indent = " "; 1346 | ctx.needs_comma = 0; 1347 | ctx.extension_flags = 0; 1348 | ctx.required_extension_flags = 0; 1349 | 1350 | cgltf_write_context* context = &ctx; 1351 | 1352 | CGLTF_SPRINTF("{"); 1353 | 1354 | if (data->accessors_count > 0) 1355 | { 1356 | cgltf_write_line(context, "\"accessors\": ["); 1357 | for (cgltf_size i = 0; i < data->accessors_count; ++i) 1358 | { 1359 | cgltf_write_accessor(context, data->accessors + i); 1360 | } 1361 | cgltf_write_line(context, "]"); 1362 | } 1363 | 1364 | cgltf_write_asset(context, &data->asset); 1365 | 1366 | if (data->buffer_views_count > 0) 1367 | { 1368 | cgltf_write_line(context, "\"bufferViews\": ["); 1369 | for (cgltf_size i = 0; i < data->buffer_views_count; ++i) 1370 | { 1371 | cgltf_write_buffer_view(context, data->buffer_views + i); 1372 | } 1373 | cgltf_write_line(context, "]"); 1374 | } 1375 | 1376 | if (data->buffers_count > 0) 1377 | { 1378 | cgltf_write_line(context, "\"buffers\": ["); 1379 | for (cgltf_size i = 0; i < data->buffers_count; ++i) 1380 | { 1381 | cgltf_write_buffer(context, data->buffers + i); 1382 | } 1383 | cgltf_write_line(context, "]"); 1384 | } 1385 | 1386 | if (data->images_count > 0) 1387 | { 1388 | cgltf_write_line(context, "\"images\": ["); 1389 | for (cgltf_size i = 0; i < data->images_count; ++i) 1390 | { 1391 | cgltf_write_image(context, data->images + i); 1392 | } 1393 | cgltf_write_line(context, "]"); 1394 | } 1395 | 1396 | if (data->meshes_count > 0) 1397 | { 1398 | cgltf_write_line(context, "\"meshes\": ["); 1399 | for (cgltf_size i = 0; i < data->meshes_count; ++i) 1400 | { 1401 | cgltf_write_mesh(context, data->meshes + i); 1402 | } 1403 | cgltf_write_line(context, "]"); 1404 | } 1405 | 1406 | if (data->materials_count > 0) 1407 | { 1408 | cgltf_write_line(context, "\"materials\": ["); 1409 | for (cgltf_size i = 0; i < data->materials_count; ++i) 1410 | { 1411 | cgltf_write_material(context, data->materials + i); 1412 | } 1413 | cgltf_write_line(context, "]"); 1414 | } 1415 | 1416 | if (data->nodes_count > 0) 1417 | { 1418 | cgltf_write_line(context, "\"nodes\": ["); 1419 | for (cgltf_size i = 0; i < data->nodes_count; ++i) 1420 | { 1421 | cgltf_write_node(context, data->nodes + i); 1422 | } 1423 | cgltf_write_line(context, "]"); 1424 | } 1425 | 1426 | if (data->samplers_count > 0) 1427 | { 1428 | cgltf_write_line(context, "\"samplers\": ["); 1429 | for (cgltf_size i = 0; i < data->samplers_count; ++i) 1430 | { 1431 | cgltf_write_sampler(context, data->samplers + i); 1432 | } 1433 | cgltf_write_line(context, "]"); 1434 | } 1435 | 1436 | CGLTF_WRITE_IDXPROP("scene", data->scene, data->scenes); 1437 | 1438 | if (data->scenes_count > 0) 1439 | { 1440 | cgltf_write_line(context, "\"scenes\": ["); 1441 | for (cgltf_size i = 0; i < data->scenes_count; ++i) 1442 | { 1443 | cgltf_write_scene(context, data->scenes + i); 1444 | } 1445 | cgltf_write_line(context, "]"); 1446 | } 1447 | 1448 | if (data->textures_count > 0) 1449 | { 1450 | cgltf_write_line(context, "\"textures\": ["); 1451 | for (cgltf_size i = 0; i < data->textures_count; ++i) 1452 | { 1453 | cgltf_write_texture(context, data->textures + i); 1454 | } 1455 | cgltf_write_line(context, "]"); 1456 | } 1457 | 1458 | if (data->skins_count > 0) 1459 | { 1460 | cgltf_write_line(context, "\"skins\": ["); 1461 | for (cgltf_size i = 0; i < data->skins_count; ++i) 1462 | { 1463 | cgltf_write_skin(context, data->skins + i); 1464 | } 1465 | cgltf_write_line(context, "]"); 1466 | } 1467 | 1468 | if (data->animations_count > 0) 1469 | { 1470 | cgltf_write_line(context, "\"animations\": ["); 1471 | for (cgltf_size i = 0; i < data->animations_count; ++i) 1472 | { 1473 | cgltf_write_animation(context, data->animations + i); 1474 | } 1475 | cgltf_write_line(context, "]"); 1476 | } 1477 | 1478 | if (data->cameras_count > 0) 1479 | { 1480 | cgltf_write_line(context, "\"cameras\": ["); 1481 | for (cgltf_size i = 0; i < data->cameras_count; ++i) 1482 | { 1483 | cgltf_write_camera(context, data->cameras + i); 1484 | } 1485 | cgltf_write_line(context, "]"); 1486 | } 1487 | 1488 | if (data->lights_count > 0 || data->variants_count > 0) 1489 | { 1490 | cgltf_write_line(context, "\"extensions\": {"); 1491 | 1492 | if (data->lights_count > 0) 1493 | { 1494 | cgltf_write_line(context, "\"KHR_lights_punctual\": {"); 1495 | cgltf_write_line(context, "\"lights\": ["); 1496 | for (cgltf_size i = 0; i < data->lights_count; ++i) 1497 | { 1498 | cgltf_write_light(context, data->lights + i); 1499 | } 1500 | cgltf_write_line(context, "]"); 1501 | cgltf_write_line(context, "}"); 1502 | } 1503 | 1504 | if (data->variants_count) 1505 | { 1506 | cgltf_write_line(context, "\"KHR_materials_variants\": {"); 1507 | cgltf_write_line(context, "\"variants\": ["); 1508 | for (cgltf_size i = 0; i < data->variants_count; ++i) 1509 | { 1510 | cgltf_write_variant(context, data->variants + i); 1511 | } 1512 | cgltf_write_line(context, "]"); 1513 | cgltf_write_line(context, "}"); 1514 | } 1515 | 1516 | cgltf_write_line(context, "}"); 1517 | } 1518 | 1519 | if (context->extension_flags != 0) 1520 | { 1521 | cgltf_write_line(context, "\"extensionsUsed\": ["); 1522 | cgltf_write_extensions(context, context->extension_flags); 1523 | cgltf_write_line(context, "]"); 1524 | } 1525 | 1526 | if (context->required_extension_flags != 0) 1527 | { 1528 | cgltf_write_line(context, "\"extensionsRequired\": ["); 1529 | cgltf_write_extensions(context, context->required_extension_flags); 1530 | cgltf_write_line(context, "]"); 1531 | } 1532 | 1533 | cgltf_write_extras(context, &data->extras); 1534 | 1535 | CGLTF_SPRINTF("\n}\n"); 1536 | 1537 | // snprintf does not include the null terminator in its return value, so be sure to include it 1538 | // in the returned byte count. 1539 | return 1 + ctx.chars_written; 1540 | } 1541 | 1542 | #endif /* #ifdef CGLTF_WRITE_IMPLEMENTATION */ 1543 | 1544 | /* cgltf is distributed under MIT license: 1545 | * 1546 | * Copyright (c) 2019-2021 Philip Rideout 1547 | 1548 | * Permission is hereby granted, free of charge, to any person obtaining a copy 1549 | * of this software and associated documentation files (the "Software"), to deal 1550 | * in the Software without restriction, including without limitation the rights 1551 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1552 | * copies of the Software, and to permit persons to whom the Software is 1553 | * furnished to do so, subject to the following conditions: 1554 | 1555 | * The above copyright notice and this permission notice shall be included in all 1556 | * copies or substantial portions of the Software. 1557 | 1558 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1559 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1560 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1561 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1562 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1563 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1564 | * SOFTWARE. 1565 | */ 1566 | -------------------------------------------------------------------------------- /fuzz/data/BadBasisU.gltf: -------------------------------------------------------------------------------- 1 | {"textures":[{"extensions":{"KHR_texture_basisu":{""}}:{""""},""}]} -------------------------------------------------------------------------------- /fuzz/data/Box.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkuhlmann/cgltf/ebf875e8f9563d751fac24c91c0871c3d67f7b1f/fuzz/data/Box.glb -------------------------------------------------------------------------------- /fuzz/data/TriangleWithoutIndices.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scenes" : [ 3 | { 4 | "nodes" : [ 0 ] 5 | } 6 | ], 7 | 8 | "nodes" : [ 9 | { 10 | "mesh" : 0 11 | } 12 | ], 13 | 14 | "meshes" : [ 15 | { 16 | "primitives" : [ { 17 | "attributes" : { 18 | "POSITION" : 0 19 | } 20 | } ] 21 | } 22 | ], 23 | 24 | "buffers" : [ 25 | { 26 | "uri" : "data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA", 27 | "byteLength" : 36 28 | } 29 | ], 30 | "bufferViews" : [ 31 | { 32 | "buffer" : 0, 33 | "byteOffset" : 0, 34 | "byteLength" : 36, 35 | "target" : 34962 36 | } 37 | ], 38 | "accessors" : [ 39 | { 40 | "bufferView" : 0, 41 | "byteOffset" : 0, 42 | "componentType" : 5126, 43 | "count" : 3, 44 | "type" : "VEC3", 45 | "max" : [ 1.0, 1.0, 0.0 ], 46 | "min" : [ 0.0, 0.0, 0.0 ] 47 | } 48 | ], 49 | 50 | "asset" : { 51 | "version" : "2.0" 52 | } 53 | } -------------------------------------------------------------------------------- /fuzz/data/WaterBottle.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 0, 5 | "componentType": 5126, 6 | "count": 2549, 7 | "type": "VEC2" 8 | }, 9 | { 10 | "bufferView": 1, 11 | "componentType": 5126, 12 | "count": 2549, 13 | "type": "VEC3" 14 | }, 15 | { 16 | "bufferView": 2, 17 | "componentType": 5126, 18 | "count": 2549, 19 | "type": "VEC4" 20 | }, 21 | { 22 | "bufferView": 3, 23 | "componentType": 5126, 24 | "count": 2549, 25 | "type": "VEC3", 26 | "max": [ 27 | 0.05445001, 28 | 0.130220339, 29 | 0.0544500239 30 | ], 31 | "min": [ 32 | -0.05445001, 33 | -0.130220339, 34 | -0.0544500239 35 | ] 36 | }, 37 | { 38 | "bufferView": 4, 39 | "componentType": 5123, 40 | "count": 13530, 41 | "type": "SCALAR" 42 | } 43 | ], 44 | "asset": { 45 | "generator": "glTF Tools for Unity", 46 | "version": "2.0" 47 | }, 48 | "bufferViews": [ 49 | { 50 | "buffer": 0, 51 | "byteLength": 20392 52 | }, 53 | { 54 | "buffer": 0, 55 | "byteOffset": 20392, 56 | "byteLength": 30588 57 | }, 58 | { 59 | "buffer": 0, 60 | "byteOffset": 50980, 61 | "byteLength": 40784 62 | }, 63 | { 64 | "buffer": 0, 65 | "byteOffset": 91764, 66 | "byteLength": 30588 67 | }, 68 | { 69 | "buffer": 0, 70 | "byteOffset": 122352, 71 | "byteLength": 27060 72 | } 73 | ], 74 | "buffers": [ 75 | { 76 | "uri": "WaterBottle.bin", 77 | "byteLength": 149412 78 | } 79 | ], 80 | "images": [ 81 | { 82 | "uri": "WaterBottle_baseColor.png" 83 | }, 84 | { 85 | "uri": "WaterBottle_occlusionRoughnessMetallic.png" 86 | }, 87 | { 88 | "uri": "WaterBottle_normal.png" 89 | }, 90 | { 91 | "uri": "WaterBottle_emissive.png" 92 | } 93 | ], 94 | "meshes": [ 95 | { 96 | "primitives": [ 97 | { 98 | "attributes": { 99 | "TEXCOORD_0": 0, 100 | "NORMAL": 1, 101 | "TANGENT": 2, 102 | "POSITION": 3 103 | }, 104 | "indices": 4, 105 | "material": 0 106 | } 107 | ], 108 | "name": "WaterBottle" 109 | } 110 | ], 111 | "materials": [ 112 | { 113 | "pbrMetallicRoughness": { 114 | "baseColorTexture": { 115 | "index": 0 116 | }, 117 | "metallicRoughnessTexture": { 118 | "index": 1 119 | } 120 | }, 121 | "normalTexture": { 122 | "index": 2 123 | }, 124 | "occlusionTexture": { 125 | "index": 1 126 | }, 127 | "emissiveFactor": [ 128 | 1.0, 129 | 1.0, 130 | 1.0 131 | ], 132 | "emissiveTexture": { 133 | "index": 3 134 | }, 135 | "name": "BottleMat" 136 | } 137 | ], 138 | "nodes": [ 139 | { 140 | "mesh": 0, 141 | "rotation": [ 142 | 0.0, 143 | 1.0, 144 | 0.0, 145 | 0.0 146 | ], 147 | "name": "WaterBottle" 148 | } 149 | ], 150 | "scene": 0, 151 | "scenes": [ 152 | { 153 | "nodes": [ 154 | 0 155 | ] 156 | } 157 | ], 158 | "textures": [ 159 | { 160 | "source": 0 161 | }, 162 | { 163 | "source": 1 164 | }, 165 | { 166 | "source": 2 167 | }, 168 | { 169 | "source": 3 170 | } 171 | ] 172 | } -------------------------------------------------------------------------------- /fuzz/gltf.dict: -------------------------------------------------------------------------------- 1 | # 2 | # AFL dictionary for JSON 3 | # ----------------------- 4 | # 5 | # Just the very basics. 6 | # 7 | # Inspired by a dictionary by Jakub Wilk 8 | # 9 | 10 | "0" 11 | ",0" 12 | ":0" 13 | "0:" 14 | "-1.2e+3" 15 | 16 | "true" 17 | "false" 18 | "null" 19 | 20 | "\"\"" 21 | ",\"\"" 22 | ":\"\"" 23 | "\"\":" 24 | 25 | "{}" 26 | ",{}" 27 | ":{}" 28 | "{\"\":0}" 29 | "{{}}" 30 | 31 | "[]" 32 | ",[]" 33 | ":[]" 34 | "[0]" 35 | "[[]]" 36 | 37 | "''" 38 | "\\" 39 | "\\b" 40 | "\\f" 41 | "\\n" 42 | "\\r" 43 | "\\t" 44 | "\\u0000" 45 | "\\x00" 46 | "\\0" 47 | "\\uD800\\uDC00" 48 | "\\uDBFF\\uDFFF" 49 | 50 | "\"\":0" 51 | "//" 52 | "/**/" 53 | 54 | # 55 | # AFL dictionary for GLTF core 56 | # ----------------------- 57 | 58 | "5120" 59 | "5121" 60 | "5122" 61 | "5123" 62 | "5125" 63 | "5126" 64 | "\"BLEND\"" 65 | "\"CUBICSPLINE\"" 66 | "\"LINEAR\"" 67 | "\"MASK\"" 68 | "\"MAT2\"" 69 | "\"MAT3\"" 70 | "\"MAT4\"" 71 | "\"OPAQUE\"" 72 | "\"SCALAR\"" 73 | "\"STEP\"" 74 | "\"VEC2\"" 75 | "\"VEC3\"" 76 | "\"VEC4\"" 77 | "\"accessor\"" 78 | "\"accessors\"" 79 | "\"alphaCutoff\"" 80 | "\"alphaMode\"" 81 | "\"animations\"" 82 | "\"aspectRatio\"" 83 | "\"asset\"" 84 | "\"attributes\"" 85 | "\"baseColorFactor\"" 86 | "\"baseColorTexture\"" 87 | "\"bufferView\"" 88 | "\"bufferViews\"" 89 | "\"buffer\"" 90 | "\"buffers\"" 91 | "\"byteLength\"" 92 | "\"byteOffset\"" 93 | "\"byteStride\"" 94 | "\"camera\"" 95 | "\"cameras\"" 96 | "\"channel\"" 97 | "\"channels\"" 98 | "\"children\"" 99 | "\"componentType\"" 100 | "\"copyright\"" 101 | "\"count\"" 102 | "\"doubleSided\"" 103 | "\"emissiveFactor\"" 104 | "\"emissiveTexture\"" 105 | "\"extensionsRequired\"" 106 | "\"extensionsUsed\"" 107 | "\"extensions\"" 108 | "\"extras\"" 109 | "\"generator\"" 110 | "\"image\"" 111 | "\"images\"" 112 | "\"index\"" 113 | "\"indices\"" 114 | "\"input\"" 115 | "\"interpolation\"" 116 | "\"inverseBindMatrices\"" 117 | "\"joints\"" 118 | "\"magFilter\"" 119 | "\"material\"" 120 | "\"materials\"" 121 | "\"matrix\"" 122 | "\"max\"" 123 | "\"mesh\"" 124 | "\"meshes\"" 125 | "\"metallicFactor\"" 126 | "\"metallicRoughnessTexture\"" 127 | "\"mimeType\"" 128 | "\"minFilter\"" 129 | "\"minVersion\"" 130 | "\"min\"" 131 | "\"mode\"" 132 | "\"name\"" 133 | "\"node\"" 134 | "\"nodes\"" 135 | "\"normalTextureInfo\"" 136 | "\"normalTexture\"" 137 | "\"normalized\"" 138 | "\"occlusionTextureInfo\"" 139 | "\"occlusionTexture\"" 140 | "\"orthographic\"" 141 | "\"output\"" 142 | "\"path\"" 143 | "\"pbrMetallicRoughness\"" 144 | "\"perspective\"" 145 | "\"primitive\"" 146 | "\"primitives\"" 147 | "\"rotation\"" 148 | "\"roughnessFactor\"" 149 | "\"sampler\"" 150 | "\"samplers\"" 151 | "\"scale\"" 152 | "\"scene\"" 153 | "\"scenes\"" 154 | "\"skeleton\"" 155 | "\"skin\"" 156 | "\"skins\"" 157 | "\"source\"" 158 | "\"sparse\"" 159 | "\"strength\"" 160 | "\"target\"" 161 | "\"targets\"" 162 | "\"texCoord\"" 163 | "\"textureInfo\"" 164 | "\"texture\"" 165 | "\"textures\"" 166 | "\"translation\"" 167 | "\"type\"" 168 | "\"uri\"" 169 | "\"values\"" 170 | "\"version\"" 171 | "\"weights\"" 172 | "\"wrapS\"" 173 | "\"wrapT\"" 174 | "\"xmag\"" 175 | "\"yfov\"" 176 | "\"ymag\"" 177 | "\"zfar\"" 178 | "\"znear\"" 179 | 180 | # 181 | # AFL dictionary for GLTF extensions 182 | # ----------------------- 183 | "\"KHR_materials_unlit\"" 184 | "\"KHR_texture_basisu\"" 185 | 186 | "\"KHR_materials_pbrSpecularGlossiness\"" 187 | "\"diffuseFactor\"" 188 | "\"diffuseTexture\"" 189 | "\"specularFactor\"" 190 | "\"glossinessFactor\"" 191 | "\"specularGlossinessTexture\"" 192 | 193 | "\"KHR_texture_transform\"" 194 | "\"offset\"" 195 | "\"rotation\"" 196 | "\"scale\"" 197 | "\"texCoord\"" 198 | 199 | "\"KHR_lights_punctual\"" 200 | "\"color\"" 201 | "\"intensity\"" 202 | "\"type\"" 203 | "\"range\"" 204 | "\"innerConeAngle\"" 205 | "\"outerConeAngle\"" 206 | 207 | "\"KHR_materials_transmission\"" 208 | "\"transmissionFactor\"" 209 | "\"transmissionTexture\"" 210 | 211 | "\"KHR_materials_volume\"" 212 | "\"thicknessFactor\"" 213 | "\"thicknessTexture\"" 214 | "\"attenuationColor\"" 215 | "\"attenuationDistance\"" 216 | 217 | "\"KHR_materials_sheen\"" 218 | "\"sheenColorFactor\"" 219 | "\"sheenColorTexture\"" 220 | "\"sheenRoughnessFactor\"" 221 | "\"sheenRoughnessTexture\"" 222 | 223 | "\"KHR_materials_emissive_strength\"" 224 | "\"emissiveStrength"\"" 225 | -------------------------------------------------------------------------------- /fuzz/main.c: -------------------------------------------------------------------------------- 1 | /* How to fuzz: 2 | 3 | clang main.c -O2 -g -fsanitize=address,fuzzer -o fuzz 4 | cp -r data temp 5 | ./fuzz temp/ -dict=gltf.dict -jobs=12 -workers=12 6 | 7 | */ 8 | #define CGLTF_IMPLEMENTATION 9 | #include "../cgltf.h" 10 | 11 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) 12 | { 13 | cgltf_options options = {cgltf_file_type_invalid}; 14 | cgltf_data* data = NULL; 15 | cgltf_result res = cgltf_parse(&options, Data, Size, &data); 16 | if (res == cgltf_result_success) 17 | { 18 | cgltf_validate(data); 19 | cgltf_free(data); 20 | } 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 2.8 ) 2 | 3 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) 4 | 5 | set( EXE_NAME cgltf_test ) 6 | add_executable( ${EXE_NAME} main.c ) 7 | set_property( TARGET ${EXE_NAME} PROPERTY C_STANDARD 99 ) 8 | if(MSVC) 9 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 10 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 11 | else() 12 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 13 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 14 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 15 | endif() 16 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 17 | 18 | set( EXE_NAME test_conversion ) 19 | add_executable( ${EXE_NAME} test_conversion.cpp ) 20 | set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) 21 | if(MSVC) 22 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 23 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 24 | else() 25 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 26 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 27 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 28 | endif() 29 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 30 | 31 | set( EXE_NAME test_write ) 32 | add_executable( ${EXE_NAME} test_write.cpp ) 33 | set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) 34 | if(MSVC) 35 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 36 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 37 | else() 38 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 39 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 40 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 41 | endif() 42 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 43 | 44 | set( EXE_NAME test_write_glb ) 45 | add_executable( ${EXE_NAME} test_write_glb.cpp ) 46 | set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) 47 | if(MSVC) 48 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 49 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 50 | else() 51 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 52 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 53 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 54 | endif() 55 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 56 | 57 | set( EXE_NAME test_math ) 58 | add_executable( ${EXE_NAME} test_math.cpp ) 59 | set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) 60 | if(MSVC) 61 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 62 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 63 | else() 64 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 65 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 66 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 67 | endif() 68 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 69 | 70 | set( EXE_NAME test_strings ) 71 | add_executable( ${EXE_NAME} test_strings.cpp ) 72 | set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 ) 73 | if(MSVC) 74 | target_compile_options(${EXE_NAME} PRIVATE /W4 /WX) 75 | add_definitions( -D_CRT_SECURE_NO_WARNINGS) 76 | else() 77 | target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror) 78 | target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address) 79 | target_link_options(${EXE_NAME} PUBLIC -fsanitize=address) 80 | endif() 81 | install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin ) 82 | -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define CGLTF_IMPLEMENTATION 4 | #include "../cgltf.h" 5 | 6 | #include 7 | 8 | int main(int argc, char** argv) 9 | { 10 | if (argc < 2) 11 | { 12 | printf("err\n"); 13 | return -1; 14 | } 15 | 16 | cgltf_options options; 17 | memset(&options, 0, sizeof(cgltf_options)); 18 | cgltf_data* data = NULL; 19 | cgltf_result result = cgltf_parse_file(&options, argv[1], &data); 20 | 21 | if (result == cgltf_result_success) 22 | result = cgltf_load_buffers(&options, data, argv[1]); 23 | 24 | if (result == cgltf_result_success) 25 | result = cgltf_validate(data); 26 | 27 | printf("Result: %d\n", result); 28 | 29 | if (result == cgltf_result_success) 30 | { 31 | printf("Type: %u\n", data->file_type); 32 | printf("Meshes: %u\n", (unsigned)data->meshes_count); 33 | } 34 | 35 | cgltf_free(data); 36 | 37 | return result; 38 | } 39 | -------------------------------------------------------------------------------- /test/test_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys 4 | from sys import platform 5 | 6 | num_tested = 0 7 | num_errors = 0 8 | 9 | def get_executable_path(name): 10 | if platform == "win32": 11 | return "build\\Debug\\" + name 12 | else: 13 | return "build/" + name 14 | 15 | def is_ascii(s): 16 | return all(ord(c) < 128 for c in s) 17 | 18 | def collect_files(path, type, name): 19 | global num_tested 20 | global num_errors 21 | exe = get_executable_path(name) 22 | for the_file in os.listdir(path): 23 | file_path = os.path.join(os.path.normpath(path), the_file) 24 | if os.path.isfile(file_path): 25 | if the_file.endswith(type): 26 | num_tested = num_tested +1 27 | if is_ascii(file_path): 28 | print("### " + name + " " + file_path) 29 | result = os.system("{0} \"{1}\"".format(exe, file_path)) 30 | print("### Result: " + str(result) + "\n") 31 | if result != 0: 32 | num_errors = num_errors + 1 33 | print("Error.") 34 | sys.exit(1) 35 | elif os.path.isdir(file_path): 36 | collect_files(file_path, type, name) 37 | 38 | if __name__ == "__main__": 39 | if not os.path.exists("build/"): 40 | os.makedirs("build/") 41 | os.chdir("build/") 42 | os.system("cmake ..") 43 | if os.system("cmake --build .") != 0: 44 | print("Unable to build.") 45 | exit(1) 46 | os.chdir("..") 47 | if not os.path.exists("glTF-Sample-Models/"): 48 | os.system("git init glTF-Sample-Models") 49 | os.chdir("glTF-Sample-Models") 50 | os.system("git remote add origin https://github.com/KhronosGroup/glTF-Sample-Models.git") 51 | os.system("git config core.sparsecheckout true") 52 | f = open(".git/info/sparse-checkout", "w+") 53 | f.write("2.0/*\n") 54 | f.close() 55 | os.system("git pull --depth=1 origin main") 56 | os.chdir("..") 57 | collect_files("glTF-Sample-Models/2.0/", ".glb", "cgltf_test") 58 | collect_files("glTF-Sample-Models/2.0/", ".gltf", "cgltf_test") 59 | collect_files("glTF-Sample-Models/2.0/", ".glb", "test_conversion") 60 | collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_conversion") 61 | collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_write") 62 | collect_files("glTF-Sample-Models/2.0/", ".glb", "test_write_glb") 63 | 64 | result = os.system(get_executable_path("test_math")) 65 | if result != 0: 66 | num_errors = num_errors + 1 67 | print("Error.") 68 | sys.exit(1) 69 | 70 | result = os.system(get_executable_path("test_strings")) 71 | if result != 0: 72 | num_errors = num_errors + 1 73 | print("Error.") 74 | sys.exit(1) 75 | 76 | print("Tested files: " + str(num_tested)) 77 | print("Errors: " + str(num_errors)) 78 | -------------------------------------------------------------------------------- /test/test_conversion.cpp: -------------------------------------------------------------------------------- 1 | #define CGLTF_IMPLEMENTATION 2 | #include "../cgltf.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static bool is_near(cgltf_float a, cgltf_float b) 10 | { 11 | return std::abs(a - b) < 10 * std::numeric_limits::min(); 12 | } 13 | 14 | int main(int argc, char** argv) 15 | { 16 | if (argc < 2) 17 | { 18 | printf("err\n"); 19 | return -1; 20 | } 21 | 22 | cgltf_options options = {}; 23 | cgltf_data* data = NULL; 24 | cgltf_result result = cgltf_parse_file(&options, argv[1], &data); 25 | 26 | if (result == cgltf_result_success) 27 | result = cgltf_load_buffers(&options, data, argv[1]); 28 | 29 | // Skip files that use mesh compression since they require extra code to decompress accessor data 30 | for (size_t i = 0; i < data->extensions_used_count; ++i) 31 | if (strcmp(data->extensions_used[i], "KHR_draco_mesh_compression") == 0 || strcmp(data->extensions_used[i], "EXT_meshopt_compression") == 0) 32 | { 33 | cgltf_free(data); 34 | return 0; 35 | } 36 | 37 | if (result != cgltf_result_success) 38 | return result; 39 | 40 | //const cgltf_accessor* blobs = data->accessors; 41 | cgltf_float element_float[16]; 42 | cgltf_uint element_uint[4]; 43 | for (cgltf_size blob_index = 0; blob_index < data->accessors_count; ++blob_index) 44 | { 45 | const cgltf_accessor* blob = data->accessors + blob_index; 46 | if (blob->is_sparse) 47 | { 48 | cgltf_size nfloats = cgltf_num_components(blob->type) * blob->count; 49 | cgltf_float* dense = (cgltf_float*) malloc(nfloats * sizeof(cgltf_float)); 50 | if (cgltf_accessor_unpack_floats(blob, dense, nfloats) < nfloats) { 51 | printf("Unable to completely unpack a sparse accessor.\n"); 52 | return -1; 53 | } 54 | free(dense); 55 | continue; 56 | } 57 | if (blob->has_max && blob->has_min) 58 | { 59 | cgltf_float min0 = std::numeric_limits::max(); 60 | cgltf_float max0 = std::numeric_limits::lowest(); 61 | for (cgltf_size index = 0; index < blob->count; index++) 62 | { 63 | cgltf_accessor_read_float(blob, index, element_float, 16); 64 | min0 = std::min(min0, element_float[0]); 65 | max0 = std::max(max0, element_float[0]); 66 | } 67 | if (!is_near(min0, blob->min[0]) || !is_near(max0, blob->max[0])) 68 | { 69 | printf("Computed [%f, %f] but expected [%f, %f]\n", min0, max0, blob->min[0], blob->max[0]); 70 | return -1; 71 | } 72 | } 73 | if (blob->has_max && blob->has_min && blob->component_type != cgltf_component_type_r_32f && blob->component_type != cgltf_component_type_invalid) 74 | { 75 | cgltf_uint min0 = std::numeric_limits::max(); 76 | cgltf_uint max0 = std::numeric_limits::lowest(); 77 | for ( cgltf_size index = 0; index < blob->count; index++ ) 78 | { 79 | cgltf_accessor_read_uint( blob, index, element_uint, 4 ); 80 | min0 = std::min( min0, element_uint[0] ); 81 | max0 = std::max( max0, element_uint[0] ); 82 | } 83 | if ( min0 != (unsigned int) blob->min[0] || max0 != (unsigned int) blob->max[0] ) 84 | { 85 | printf( "Computed [%u, %u] but expected [%u, %u]\n", min0, max0, (unsigned int) blob->min[0], (unsigned int) blob->max[0] ); 86 | return -1; 87 | } 88 | } 89 | } 90 | 91 | cgltf_free(data); 92 | 93 | return result; 94 | } 95 | -------------------------------------------------------------------------------- /test/test_math.cpp: -------------------------------------------------------------------------------- 1 | #define CGLTF_IMPLEMENTATION 2 | #include "../cgltf.h" 3 | 4 | // Performs matrix-vector multiplication, as in (4x4) * (4x1) = (4x1) 5 | static void transform(const cgltf_float matrix[16], const cgltf_float source[4], cgltf_float target[4]) { 6 | target[0] = matrix[0] * source[0] + matrix[4] * source[1] + matrix[ 8] * source[2] + matrix[12] * source[3]; 7 | target[1] = matrix[1] * source[0] + matrix[5] * source[1] + matrix[ 9] * source[2] + matrix[13] * source[3]; 8 | target[2] = matrix[2] * source[0] + matrix[6] * source[1] + matrix[10] * source[2] + matrix[14] * source[3]; 9 | target[3] = matrix[3] * source[0] + matrix[7] * source[1] + matrix[11] * source[2] + matrix[15] * source[3]; 10 | } 11 | 12 | static void set(cgltf_float target[3], float x, float y, float z) { 13 | target[0] = x; 14 | target[1] = y; 15 | target[2] = z; 16 | } 17 | 18 | static void check(cgltf_float target[3], float x, float y, float z) { 19 | if (target[0] != x || target[1] != y || target[2] != z) { 20 | fprintf(stderr, "Mismatch detected.\n"); 21 | exit(1); 22 | } 23 | } 24 | 25 | int main(int, char**) 26 | { 27 | cgltf_node node = {}; 28 | 29 | cgltf_float matrix[16]; 30 | cgltf_float source[4] = {1, 2, 3, 1}; 31 | cgltf_float target[4]; 32 | 33 | set(node.scale, 1, 1, 1); 34 | set(node.translation, 1, 0, 0); 35 | cgltf_node_transform_local(&node, matrix); 36 | transform(matrix, source, target); 37 | check(target, 2, 2, 3); 38 | 39 | set(node.scale, 3, 1, 1); 40 | set(node.translation, 0, 0, 0); 41 | cgltf_node_transform_local(&node, matrix); 42 | transform(matrix, source, target); 43 | check(target, 3, 2, 3); 44 | 45 | set(node.scale, 1, 3, 1); 46 | set(node.translation, 1, 0, 0); 47 | cgltf_node_transform_local(&node, matrix); 48 | transform(matrix, source, target); 49 | check(target, 2, 6, 3); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /test/test_strings.cpp: -------------------------------------------------------------------------------- 1 | #define CGLTF_IMPLEMENTATION 2 | #include "../cgltf.h" 3 | 4 | #include 5 | 6 | static void check(const char* a, const char* b, cgltf_size size) { 7 | if (strcmp(a, b) != 0 || strlen(a) != size) { 8 | fprintf(stderr, "Mismatch detected.\n"); 9 | exit(1); 10 | } 11 | } 12 | 13 | int main(int, char**) 14 | { 15 | char string[64]; 16 | cgltf_size size; 17 | 18 | // cgltf_decode_string 19 | strcpy(string, ""); 20 | size = cgltf_decode_string(string); 21 | check(string, "", size); 22 | 23 | strcpy(string, "nothing to replace"); 24 | size = cgltf_decode_string(string); 25 | check(string, "nothing to replace", size); 26 | 27 | strcpy(string, "\\\" \\/ \\\\ \\b \\f \\r \\n \\t \\u0030"); 28 | size = cgltf_decode_string(string); 29 | check(string, "\" / \\ \b \f \r \n \t 0", size); 30 | 31 | strcpy(string, "test \\u121b\\u130d\\u1294\\u1276\\u127d test"); 32 | size = cgltf_decode_string(string); 33 | check(string, "test ማግኔቶች test", size); 34 | 35 | // cgltf_decode_uri 36 | strcpy(string, ""); 37 | size = cgltf_decode_uri(string); 38 | check(string, "", size); 39 | 40 | strcpy(string, "nothing to replace"); 41 | size = cgltf_decode_uri(string); 42 | check(string, "nothing to replace", size); 43 | 44 | strcpy(string, "%2F%D0%BA%D0%B8%D1%80%D0%B8%D0%BB%D0%BB%D0%B8%D1%86%D0%B0"); 45 | size = cgltf_decode_uri(string); 46 | check(string, "/кириллица", size); 47 | 48 | strcpy(string, "test%20%E1%88%9B%E1%8C%8D%E1%8A%94%E1%89%B6%E1%89%BD%20test"); 49 | size = cgltf_decode_uri(string); 50 | check(string, "test ማግኔቶች test", size); 51 | 52 | strcpy(string, "%%2F%X%AX%%2F%%"); 53 | size = cgltf_decode_uri(string); 54 | check(string, "%/%X%AX%/%%", size); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /test/test_write.cpp: -------------------------------------------------------------------------------- 1 | #define CGLTF_IMPLEMENTATION 2 | #define CGLTF_WRITE_IMPLEMENTATION 3 | #include "../cgltf_write.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main(int argc, char** argv) 11 | { 12 | if (argc < 2) 13 | { 14 | printf("err\n"); 15 | return -1; 16 | } 17 | 18 | cgltf_options options = {}; 19 | cgltf_data* data0 = NULL; 20 | cgltf_result result = cgltf_parse_file(&options, argv[1], &data0); 21 | 22 | // Silently skip over files that are unreadable since this is a writing test. 23 | if (result != cgltf_result_success) 24 | { 25 | return cgltf_result_success; 26 | } 27 | 28 | result = cgltf_write_file(&options, "out.gltf", data0); 29 | if (result != cgltf_result_success) 30 | { 31 | return result; 32 | } 33 | cgltf_data* data1 = NULL; 34 | result = cgltf_parse_file(&options, "out.gltf", &data1); 35 | if (result != cgltf_result_success) 36 | { 37 | return result; 38 | } 39 | if (data0->meshes_count != data1->meshes_count) { 40 | return -1; 41 | } 42 | cgltf_free(data1); 43 | cgltf_free(data0); 44 | return cgltf_result_success; 45 | } 46 | -------------------------------------------------------------------------------- /test/test_write_glb.cpp: -------------------------------------------------------------------------------- 1 | #define CGLTF_IMPLEMENTATION 2 | #define CGLTF_WRITE_IMPLEMENTATION 3 | #include "../cgltf_write.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main(int argc, char** argv) 12 | { 13 | if (argc < 2) 14 | { 15 | printf("err\n"); 16 | return -1; 17 | } 18 | 19 | cgltf_options options = {}; 20 | cgltf_data* data0 = NULL; 21 | cgltf_result result = cgltf_parse_file(&options, argv[1], &data0); 22 | 23 | // Silently skip over files that are unreadable since this is a writing test. 24 | if (result != cgltf_result_success) 25 | { 26 | return cgltf_result_success; 27 | } 28 | 29 | options.type = cgltf_file_type_glb; // Write back in a GLB format 30 | result = cgltf_write_file(&options, "out.glb", data0); 31 | if (result != cgltf_result_success) 32 | { 33 | return result; 34 | } 35 | 36 | cgltf_data* data1 = NULL; 37 | result = cgltf_parse_file(&options, "out.glb", &data1); 38 | if (result != cgltf_result_success) 39 | { 40 | return result; 41 | } 42 | 43 | if (data0->meshes_count != data1->meshes_count) { 44 | return -1; 45 | } 46 | 47 | // Compare binary buffers 48 | if (data0->bin_size != data1->bin_size) { 49 | return -1; 50 | } 51 | if (memcmp(data0->bin, data1->bin, data0->bin_size) != 0) { 52 | return -1; 53 | } 54 | 55 | cgltf_free(data1); 56 | cgltf_free(data0); 57 | return cgltf_result_success; 58 | } 59 | --------------------------------------------------------------------------------