Coverage Report

Created: 2026-08-31 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tinygltf/tiny_gltf_v3.h
Line
Count
Source
1
/*
2
 * tiny_gltf_v3.h - C-first glTF 2.0 loader and writer API (v3)
3
 *
4
 * The MIT License (MIT)
5
 * Copyright (c) 2026 - Present: Syoyo Fujita
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 */
25
26
/*
27
 * Version: v3.0.0-alpha
28
 *
29
 * Ground-up C-centric API rewrite of tinygltf.
30
 * The default runtime implementation lives in tiny_gltf_v3.c.
31
 *
32
 * Key differences from v2:
33
 *   - Pure C POD structs (no STL containers in public API)
34
 *   - Arena-based memory management (single tg3_model_free() frees all)
35
 *   - Filesystem and image decoding OFF by default (opt-in)
36
 *   - Structured error reporting via tg3_error_stack
37
 *   - Streaming parse/write via callbacks
38
 *   - No RTTI, no exceptions required
39
 *   - C++20 coroutine facade (optional)
40
 *
41
 * Security considerations (read before processing untrusted glTF):
42
 *
43
 *   1. External URI loading. When TINYGLTF3_ENABLE_FS is defined and no custom
44
 *      tg3_fs_callbacks are supplied, the parser opens external buffer/image
45
 *      URIs through the libc default fopen(). The parser rejects URIs that
46
 *      contain '..' segments, leading '/' or '\\', Windows drive prefixes
47
 *      (e.g. "C:"), or NUL bytes — but it does NOT chroot or canonicalize the
48
 *      result. Production callers SHOULD provide a tg3_fs_callbacks with a
49
 *      read_file callback that confines reads to a known directory (e.g. via
50
 *      openat(AT_FDCWD, path, O_NOFOLLOW) plus a realpath() prefix check) when
51
 *      the input glTF is attacker-controlled.
52
 *
53
 *   2. Index validation. Many glTF fields are integer indices into model
54
 *      arrays (accessor.bufferView, primitive.material, scene.nodes[], etc.).
55
 *      With opts.validate_indices = 1 (the default) the parser rejects every
56
 *      out-of-range index after the structural parse and returns
57
 *      TG3_ERR_INVALID_INDEX. Set opts.validate_indices = 0 only when you
58
 *      need to round-trip raw or extension data and have your own validator.
59
 *
60
 *   3. Image decoding. The parser does not decode image bytes by default.
61
 *      Set opts.images_as_is = 1 (already the safe default for untrusted
62
 *      input) to skip any decoder and store raw bytes only.
63
 *
64
 *   4. Memory budget. The arena is capped at TINYGLTF3_MAX_MEMORY_BYTES
65
 *      (1 GB by default; configurable per-parse via tg3_memory_config).
66
 *      The parser returns TG3_ERR_OUT_OF_MEMORY rather than overcommitting.
67
 *
68
 *   5. Error message lifetime. Error strings on tg3_error_stack are
69
 *      arena-allocated and remain valid until tg3_model_free() is called.
70
 *      Read or copy them BEFORE freeing the model.
71
 */
72
73
#ifndef TINY_GLTF_V3_H_
74
#define TINY_GLTF_V3_H_
75
76
/* ======================================================================
77
 * Section 2: Configuration Macros
78
 * ====================================================================== */
79
80
/* Legacy single-translation-unit build mode: define in ONE C or C++ file */
81
/* #define TINYGLTF3_IMPLEMENTATION */
82
83
/* Opt-in features (OFF by default) */
84
/* #define TINYGLTF3_ENABLE_FS */
85
/* #define TINYGLTF3_ENABLE_STB_IMAGE */
86
/* #define TINYGLTF3_ENABLE_STB_IMAGE_WRITE */
87
88
/* Opt-out */
89
/* #define TINYGLTF3_NO_IMAGE_DECODE */
90
91
/* C++20 coroutines (auto-detected, or force) */
92
/* #define TINYGLTF3_ENABLE_COROUTINES */
93
94
/* SIMD for JSON parsing (forwarded to tinygltf_json.h) */
95
/* #define TINYGLTF3_JSON_SIMD_SSE2 */
96
/* #define TINYGLTF3_JSON_SIMD_AVX2 */
97
/* #define TINYGLTF3_JSON_SIMD_NEON */
98
99
/* Memory limits */
100
#ifndef TINYGLTF3_MAX_MEMORY_BYTES
101
1.12k
#define TINYGLTF3_MAX_MEMORY_BYTES (1ULL << 30) /* 1 GB */
102
#endif
103
104
#ifndef TINYGLTF3_MAX_NESTING_DEPTH
105
555
#define TINYGLTF3_MAX_NESTING_DEPTH 512
106
#endif
107
108
#ifndef TINYGLTF3_MAX_STRING_LENGTH
109
555
#define TINYGLTF3_MAX_STRING_LENGTH (64 * 1024 * 1024) /* 64 MB */
110
#endif
111
112
/* Linkage control */
113
#ifndef TINYGLTF3_API
114
#define TINYGLTF3_API
115
#endif
116
117
/* Assert override */
118
#ifndef TINYGLTF3_ASSERT
119
#ifndef TINYGLTF3_NO_STDLIB
120
#include <assert.h>
121
#define TINYGLTF3_ASSERT(x) assert(x)
122
#else
123
#define TINYGLTF3_ASSERT(x) ((void)(x))
124
#endif
125
#endif
126
127
/* ======================================================================
128
 * Section 3: C Includes
129
 * ====================================================================== */
130
131
#include <stddef.h>
132
#include <stdint.h>
133
#include <stdarg.h>
134
#ifndef TINYGLTF3_NO_STDLIB
135
#include <string.h>
136
#include <stdlib.h>
137
#endif
138
139
#ifndef TINYGLTF3_MALLOC
140
#ifndef TINYGLTF3_NO_STDLIB
141
1.39M
#define TINYGLTF3_MALLOC(sz) malloc(sz)
142
#else
143
#define TINYGLTF3_MALLOC(sz) NULL
144
#endif
145
#endif
146
147
#ifndef TINYGLTF3_REALLOC
148
#ifndef TINYGLTF3_NO_STDLIB
149
1.59M
#define TINYGLTF3_REALLOC(ptr, sz) realloc((ptr), (sz))
150
#else
151
#define TINYGLTF3_REALLOC(ptr, sz) NULL
152
#endif
153
#endif
154
155
#ifndef TINYGLTF3_FREE
156
#ifndef TINYGLTF3_NO_STDLIB
157
3.21M
#define TINYGLTF3_FREE(ptr) free(ptr)
158
#else
159
#define TINYGLTF3_FREE(ptr) ((void)(ptr))
160
#endif
161
#endif
162
163
/* ======================================================================
164
 * Section 4: Constants and Enums
165
 * ====================================================================== */
166
167
#ifdef __cplusplus
168
extern "C" {
169
#endif
170
171
/* Primitive modes */
172
#define TG3_MODE_POINTS         0
173
#define TG3_MODE_LINE           1
174
#define TG3_MODE_LINE_LOOP      2
175
#define TG3_MODE_LINE_STRIP     3
176
215k
#define TG3_MODE_TRIANGLES      4
177
#define TG3_MODE_TRIANGLE_STRIP 5
178
#define TG3_MODE_TRIANGLE_FAN   6
179
180
/* Component types */
181
0
#define TG3_COMPONENT_TYPE_BYTE           5120
182
0
#define TG3_COMPONENT_TYPE_UNSIGNED_BYTE  5121
183
0
#define TG3_COMPONENT_TYPE_SHORT          5122
184
0
#define TG3_COMPONENT_TYPE_UNSIGNED_SHORT 5123
185
0
#define TG3_COMPONENT_TYPE_INT            5124
186
0
#define TG3_COMPONENT_TYPE_UNSIGNED_INT   5125
187
0
#define TG3_COMPONENT_TYPE_FLOAT          5126
188
0
#define TG3_COMPONENT_TYPE_DOUBLE         5130
189
190
/* Accessor types */
191
0
#define TG3_TYPE_VEC2   2
192
0
#define TG3_TYPE_VEC3   3
193
0
#define TG3_TYPE_VEC4   4
194
0
#define TG3_TYPE_MAT2   (32 + 2)
195
0
#define TG3_TYPE_MAT3   (32 + 3)
196
0
#define TG3_TYPE_MAT4   (32 + 4)
197
1
#define TG3_TYPE_SCALAR (64 + 1)
198
#define TG3_TYPE_VECTOR (64 + 4)
199
#define TG3_TYPE_MATRIX (64 + 16)
200
201
/* Texture filter */
202
#define TG3_TEXTURE_FILTER_NEAREST                9728
203
#define TG3_TEXTURE_FILTER_LINEAR                 9729
204
#define TG3_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST 9984
205
#define TG3_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST  9985
206
#define TG3_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR  9986
207
#define TG3_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR   9987
208
209
/* Texture wrap */
210
8.60k
#define TG3_TEXTURE_WRAP_REPEAT          10497
211
#define TG3_TEXTURE_WRAP_CLAMP_TO_EDGE   33071
212
#define TG3_TEXTURE_WRAP_MIRRORED_REPEAT 33648
213
214
/* Image format */
215
#define TG3_IMAGE_FORMAT_JPEG 0
216
#define TG3_IMAGE_FORMAT_PNG  1
217
#define TG3_IMAGE_FORMAT_BMP  2
218
#define TG3_IMAGE_FORMAT_GIF  3
219
220
/* Texture format */
221
#define TG3_TEXTURE_FORMAT_ALPHA           6406
222
#define TG3_TEXTURE_FORMAT_RGB             6407
223
#define TG3_TEXTURE_FORMAT_RGBA            6408
224
#define TG3_TEXTURE_FORMAT_LUMINANCE       6409
225
#define TG3_TEXTURE_FORMAT_LUMINANCE_ALPHA 6410
226
227
/* Texture target / type */
228
#define TG3_TEXTURE_TARGET_TEXTURE2D    3553
229
#define TG3_TEXTURE_TYPE_UNSIGNED_BYTE  5121
230
231
/* Buffer targets */
232
#define TG3_TARGET_ARRAY_BUFFER         34962
233
#define TG3_TARGET_ELEMENT_ARRAY_BUFFER 34963
234
235
/* Sentinel for absent index */
236
#define TG3_INDEX_NONE (-1)
237
238
/* Section check flags */
239
#define TG3_NO_REQUIRE       0x00
240
567
#define TG3_REQUIRE_VERSION  0x01
241
#define TG3_REQUIRE_SCENE    0x02
242
#define TG3_REQUIRE_SCENES   0x04
243
#define TG3_REQUIRE_NODES    0x08
244
#define TG3_REQUIRE_ACCESSORS    0x10
245
#define TG3_REQUIRE_BUFFERS      0x20
246
#define TG3_REQUIRE_BUFFER_VIEWS 0x40
247
#define TG3_REQUIRE_ALL          0x7f
248
249
/* Parse strictness */
250
typedef enum tg3_strictness {
251
    TG3_PERMISSIVE = 0,
252
    TG3_STRICT     = 1
253
} tg3_strictness;
254
255
/* ======================================================================
256
 * Section 5: Foundation Types
257
 * ====================================================================== */
258
259
typedef struct tg3_str {
260
    const char *data;
261
    uint32_t    len;
262
} tg3_str;
263
264
typedef struct tg3_span_i32 {
265
    const int32_t *data;
266
    uint32_t       count;
267
} tg3_span_i32;
268
269
typedef struct tg3_span_f64 {
270
    const double *data;
271
    uint32_t      count;
272
} tg3_span_f64;
273
274
typedef struct tg3_span_u8 {
275
    const uint8_t *data;
276
    uint64_t       count;
277
} tg3_span_u8;
278
279
typedef struct tg3_str_int_pair {
280
    tg3_str  key;
281
    int32_t  value;
282
} tg3_str_int_pair;
283
284
/* ======================================================================
285
 * Section 6: Allocator Interface
286
 * ====================================================================== */
287
288
typedef struct tg3_allocator {
289
    void *(*alloc)(size_t size, void *user_data);
290
    void *(*realloc)(void *ptr, size_t old_size, size_t new_size, void *user_data);
291
    void  (*free)(void *ptr, size_t size, void *user_data);
292
    void  *user_data;
293
} tg3_allocator;
294
295
/* ======================================================================
296
 * Section 7: Error Reporting
297
 * ====================================================================== */
298
299
typedef enum tg3_severity {
300
    TG3_SEVERITY_INFO    = 0,
301
    TG3_SEVERITY_WARNING = 1,
302
    TG3_SEVERITY_ERROR   = 2
303
} tg3_severity;
304
305
typedef enum tg3_error_code {
306
    TG3_OK = 0,
307
308
    /* I/O errors: 1-9 */
309
    TG3_ERR_FILE_NOT_FOUND     = 1,
310
    TG3_ERR_FILE_READ          = 2,
311
    TG3_ERR_FILE_WRITE         = 3,
312
    TG3_ERR_FILE_TOO_LARGE     = 4,
313
314
    /* JSON errors: 10-19 */
315
    TG3_ERR_JSON_PARSE         = 10,
316
    TG3_ERR_JSON_TYPE_MISMATCH = 11,
317
    TG3_ERR_JSON_MISSING_FIELD = 12,
318
    TG3_ERR_JSON_INVALID_VALUE = 13,
319
320
    /* GLB errors: 20-29 */
321
    TG3_ERR_GLB_INVALID_MAGIC  = 20,
322
    TG3_ERR_GLB_INVALID_VERSION = 21,
323
    TG3_ERR_GLB_INVALID_HEADER = 22,
324
    TG3_ERR_GLB_CHUNK_ERROR    = 23,
325
    TG3_ERR_GLB_SIZE_MISMATCH  = 24,
326
327
    /* Schema / validation errors: 30-49 */
328
    TG3_ERR_MISSING_REQUIRED   = 30,
329
    TG3_ERR_INVALID_INDEX      = 31,
330
    TG3_ERR_INVALID_TYPE       = 32,
331
    TG3_ERR_INVALID_VALUE      = 33,
332
    TG3_ERR_INVALID_ACCESSOR   = 34,
333
    TG3_ERR_INVALID_BUFFER     = 35,
334
    TG3_ERR_INVALID_BUFFER_VIEW = 36,
335
    TG3_ERR_INVALID_IMAGE      = 37,
336
    TG3_ERR_INVALID_MATERIAL   = 38,
337
    TG3_ERR_INVALID_MESH       = 39,
338
    TG3_ERR_INVALID_NODE       = 40,
339
    TG3_ERR_INVALID_ANIMATION  = 41,
340
    TG3_ERR_INVALID_SKIN       = 42,
341
    TG3_ERR_INVALID_CAMERA     = 43,
342
    TG3_ERR_INVALID_SCENE      = 44,
343
    TG3_ERR_BUFFER_SIZE_MISMATCH = 45,
344
345
    /* Resource errors: 50-59 */
346
    TG3_ERR_OUT_OF_MEMORY      = 50,
347
    TG3_ERR_DATA_URI_DECODE    = 51,
348
    TG3_ERR_BASE64_DECODE      = 52,
349
    TG3_ERR_EXTERNAL_RESOURCE  = 53,
350
    TG3_ERR_IMAGE_DECODE       = 54,
351
352
    /* Callback errors: 60-69 */
353
    TG3_ERR_CALLBACK_FAILED    = 60,
354
    TG3_ERR_FS_NOT_AVAILABLE   = 61,
355
356
    /* Streaming errors: 70-79 */
357
    TG3_ERR_STREAM_ABORTED     = 70,
358
359
    /* Writer errors: 80-89 */
360
    TG3_ERR_WRITE_FAILED       = 80,
361
    TG3_ERR_SERIALIZE_FAILED   = 81
362
} tg3_error_code;
363
364
typedef struct tg3_error_entry {
365
    tg3_severity   severity;
366
    tg3_error_code code;
367
    const char    *message;     /* Arena-owned, null-terminated */
368
    const char    *json_path;   /* e.g. "/meshes/0/primitives/1" or NULL */
369
    int64_t        byte_offset; /* -1 if unknown */
370
} tg3_error_entry;
371
372
typedef struct tg3_error_stack {
373
    tg3_error_entry *entries;
374
    uint32_t         count;
375
    uint32_t         capacity;
376
    int32_t          has_error; /* 1 if any entry with severity == ERROR */
377
} tg3_error_stack;
378
379
/* Error stack query functions */
380
TINYGLTF3_API int32_t  tg3_errors_has_error(const tg3_error_stack *es);
381
TINYGLTF3_API uint32_t tg3_errors_count(const tg3_error_stack *es);
382
TINYGLTF3_API const tg3_error_entry *tg3_errors_get(const tg3_error_stack *es,
383
                                                     uint32_t index);
384
385
/* ======================================================================
386
 * Section 8: Generic Value Type (for extras/extensions)
387
 * ====================================================================== */
388
389
typedef enum tg3_value_type {
390
    TG3_VALUE_NULL   = 0,
391
    TG3_VALUE_BOOL   = 1,
392
    TG3_VALUE_INT    = 2,
393
    TG3_VALUE_REAL   = 3,
394
    TG3_VALUE_STRING = 4,
395
    TG3_VALUE_ARRAY  = 5,
396
    TG3_VALUE_BINARY = 6,
397
    TG3_VALUE_OBJECT = 7
398
} tg3_value_type;
399
400
typedef struct tg3_kv_pair tg3_kv_pair;
401
402
typedef struct tg3_value {
403
    tg3_value_type type;
404
    union {
405
        int32_t  bool_val;
406
        int64_t  int_val;
407
        double   real_val;
408
    };
409
    tg3_str                string_val;
410
    const struct tg3_value *array_data;
411
    uint32_t               array_count;
412
    const tg3_kv_pair      *object_data;
413
    uint32_t               object_count;
414
    tg3_span_u8            binary_val;
415
} tg3_value;
416
417
struct tg3_kv_pair {
418
    tg3_str   key;
419
    tg3_value value;
420
};
421
422
typedef struct tg3_extension {
423
    tg3_str   name;
424
    tg3_value value;
425
} tg3_extension;
426
427
typedef struct tg3_extras_ext {
428
    const tg3_value     *extras;           /* NULL if absent */
429
    const tg3_extension *extensions;       /* Array */
430
    uint32_t             extensions_count;
431
    tg3_str              extras_json;      /* Raw JSON if store_original_json */
432
    tg3_str              extensions_json;
433
} tg3_extras_ext;
434
435
/* ======================================================================
436
 * Section 9: Core POD Structs
437
 * ====================================================================== */
438
439
/* --- Asset --- */
440
typedef struct tg3_asset {
441
    tg3_str       version;    /* Required, e.g. "2.0" */
442
    tg3_str       generator;
443
    tg3_str       min_version;
444
    tg3_str       copyright;
445
    tg3_extras_ext ext;
446
} tg3_asset;
447
448
/* --- Buffer --- */
449
typedef struct tg3_buffer {
450
    tg3_str       name;
451
    uint64_t      byte_length;  /* Declared buffer.byteLength */
452
    tg3_span_u8   data;
453
    tg3_str       uri;
454
    tg3_extras_ext ext;
455
} tg3_buffer;
456
457
/* --- BufferView --- */
458
typedef struct tg3_buffer_view {
459
    tg3_str       name;
460
    int32_t       buffer;       /* Index, required */
461
    uint64_t      byte_offset;
462
    uint64_t      byte_length;  /* Required */
463
    uint32_t      byte_stride;  /* 0 = tightly packed */
464
    int32_t       target;       /* 0 = unspecified */
465
    int32_t       draco_decoded;
466
    tg3_extras_ext ext;
467
} tg3_buffer_view;
468
469
/* --- Accessor Sparse --- */
470
typedef struct tg3_accessor_sparse_indices {
471
    uint64_t  byte_offset;
472
    int32_t   buffer_view; /* Required */
473
    int32_t   component_type; /* Required */
474
    tg3_extras_ext ext;
475
} tg3_accessor_sparse_indices;
476
477
typedef struct tg3_accessor_sparse_values {
478
    int32_t   buffer_view; /* Required */
479
    uint64_t  byte_offset;
480
    tg3_extras_ext ext;
481
} tg3_accessor_sparse_values;
482
483
typedef struct tg3_accessor_sparse {
484
    int32_t  count;      /* Required if sparse */
485
    int32_t  is_sparse;  /* 0 or 1 */
486
    tg3_accessor_sparse_indices indices;
487
    tg3_accessor_sparse_values  values;
488
    tg3_extras_ext ext;
489
} tg3_accessor_sparse;
490
491
/* --- Accessor --- */
492
typedef struct tg3_accessor {
493
    tg3_str       name;
494
    int32_t       buffer_view;    /* -1 if absent */
495
    uint64_t      byte_offset;
496
    int32_t       normalized;     /* 0 or 1 */
497
    int32_t       component_type; /* Required */
498
    uint64_t      count;          /* Required */
499
    int32_t       type;           /* Required: TG3_TYPE_* */
500
    const double *min_values;
501
    uint32_t      min_values_count;
502
    const double *max_values;
503
    uint32_t      max_values_count;
504
    tg3_accessor_sparse sparse;
505
    tg3_extras_ext ext;
506
} tg3_accessor;
507
508
/* --- Image --- */
509
typedef struct tg3_image {
510
    tg3_str       name;
511
    int32_t       width;
512
    int32_t       height;
513
    int32_t       component;  /* Channels */
514
    int32_t       bits;       /* Bits per channel */
515
    int32_t       pixel_type; /* Component type */
516
    tg3_span_u8   image;      /* Decoded pixel data (or raw if as_is) */
517
    int32_t       buffer_view; /* -1 if absent */
518
    tg3_str       mime_type;
519
    tg3_str       uri;
520
    int32_t       as_is;
521
    tg3_extras_ext ext;
522
} tg3_image;
523
524
/* --- Sampler --- */
525
typedef struct tg3_sampler {
526
    tg3_str  name;
527
    int32_t  min_filter;  /* -1 = unspecified */
528
    int32_t  mag_filter;  /* -1 = unspecified */
529
    int32_t  wrap_s;      /* Default: TG3_TEXTURE_WRAP_REPEAT */
530
    int32_t  wrap_t;      /* Default: TG3_TEXTURE_WRAP_REPEAT */
531
    tg3_extras_ext ext;
532
} tg3_sampler;
533
534
/* --- Texture --- */
535
typedef struct tg3_texture {
536
    tg3_str  name;
537
    int32_t  sampler;  /* -1 if absent */
538
    int32_t  source;   /* -1 if absent */
539
    tg3_extras_ext ext;
540
} tg3_texture;
541
542
/* --- TextureInfo --- */
543
typedef struct tg3_texture_info {
544
    int32_t  index;     /* -1 if absent */
545
    int32_t  tex_coord; /* Default: 0 */
546
    tg3_extras_ext ext;
547
} tg3_texture_info;
548
549
/* --- NormalTextureInfo --- */
550
typedef struct tg3_normal_texture_info {
551
    int32_t  index;
552
    int32_t  tex_coord;
553
    double   scale;     /* Default: 1.0 */
554
    tg3_extras_ext ext;
555
} tg3_normal_texture_info;
556
557
/* --- OcclusionTextureInfo --- */
558
typedef struct tg3_occlusion_texture_info {
559
    int32_t  index;
560
    int32_t  tex_coord;
561
    double   strength;  /* Default: 1.0 */
562
    tg3_extras_ext ext;
563
} tg3_occlusion_texture_info;
564
565
/* --- PBR Metallic Roughness --- */
566
typedef struct tg3_pbr_metallic_roughness {
567
    double             base_color_factor[4]; /* Default: {1,1,1,1} */
568
    tg3_texture_info   base_color_texture;
569
    double             metallic_factor;      /* Default: 1.0 */
570
    double             roughness_factor;     /* Default: 1.0 */
571
    tg3_texture_info   metallic_roughness_texture;
572
    tg3_extras_ext     ext;
573
} tg3_pbr_metallic_roughness;
574
575
/* --- Material --- */
576
typedef struct tg3_material {
577
    tg3_str                     name;
578
    double                      emissive_factor[3]; /* Default: {0,0,0} */
579
    tg3_str                     alpha_mode;         /* "OPAQUE","MASK","BLEND" */
580
    double                      alpha_cutoff;       /* Default: 0.5 */
581
    int32_t                     double_sided;       /* 0 or 1 */
582
    const int32_t              *lods;
583
    uint32_t                    lods_count;
584
    tg3_pbr_metallic_roughness  pbr_metallic_roughness;
585
    tg3_normal_texture_info     normal_texture;
586
    tg3_occlusion_texture_info  occlusion_texture;
587
    tg3_texture_info            emissive_texture;
588
    tg3_extras_ext              ext;
589
} tg3_material;
590
591
/* --- Primitive --- */
592
typedef struct tg3_primitive {
593
    const tg3_str_int_pair *attributes;
594
    uint32_t                attributes_count;
595
    int32_t                 material;  /* -1 if absent */
596
    int32_t                 indices;   /* -1 if absent */
597
    int32_t                 mode;      /* -1 = default (TRIANGLES) */
598
599
    /* Morph targets: array of arrays of attribute pairs */
600
    const tg3_str_int_pair *const *targets;
601
    const uint32_t         *target_attribute_counts;
602
    uint32_t                targets_count;
603
604
    tg3_extras_ext ext;
605
} tg3_primitive;
606
607
/* --- Mesh --- */
608
typedef struct tg3_mesh {
609
    tg3_str              name;
610
    const tg3_primitive *primitives;
611
    uint32_t             primitives_count;
612
    const double        *weights;
613
    uint32_t             weights_count;
614
    tg3_extras_ext       ext;
615
} tg3_mesh;
616
617
/* --- Node --- */
618
typedef struct tg3_node {
619
    tg3_str       name;
620
    int32_t       camera;     /* -1 if absent */
621
    int32_t       skin;       /* -1 if absent */
622
    int32_t       mesh;       /* -1 if absent */
623
    int32_t       light;      /* -1 if absent (KHR_lights_punctual) */
624
    int32_t       emitter;    /* -1 if absent (KHR_audio) */
625
626
    const int32_t *lods;
627
    uint32_t       lods_count;
628
    const int32_t *children;
629
    uint32_t       children_count;
630
631
    double         rotation[4];     /* Default: {0,0,0,1} */
632
    double         scale[3];        /* Default: {1,1,1} */
633
    double         translation[3];  /* Default: {0,0,0} */
634
    double         matrix[16];      /* Identity if not set */
635
    int32_t        has_matrix;      /* 1 if matrix was specified */
636
637
    const double  *weights;
638
    uint32_t       weights_count;
639
640
    tg3_extras_ext ext;
641
} tg3_node;
642
643
/* --- Skin --- */
644
typedef struct tg3_skin {
645
    tg3_str        name;
646
    int32_t        inverse_bind_matrices; /* -1 if absent */
647
    int32_t        skeleton;              /* -1 if absent */
648
    const int32_t *joints;
649
    uint32_t       joints_count;
650
    tg3_extras_ext ext;
651
} tg3_skin;
652
653
/* --- Animation --- */
654
typedef struct tg3_animation_channel_target {
655
    int32_t  node;    /* -1 if absent */
656
    tg3_str  path;    /* "translation","rotation","scale","weights" */
657
    tg3_extras_ext ext;
658
} tg3_animation_channel_target;
659
660
typedef struct tg3_animation_channel {
661
    int32_t  sampler; /* Required */
662
    tg3_animation_channel_target target;
663
    tg3_extras_ext ext;
664
} tg3_animation_channel;
665
666
typedef struct tg3_animation_sampler {
667
    int32_t  input;          /* Required */
668
    int32_t  output;         /* Required */
669
    tg3_str  interpolation;  /* "LINEAR","STEP","CUBICSPLINE" */
670
    tg3_extras_ext ext;
671
} tg3_animation_sampler;
672
673
typedef struct tg3_animation {
674
    tg3_str                      name;
675
    const tg3_animation_channel *channels;
676
    uint32_t                     channels_count;
677
    const tg3_animation_sampler *samplers;
678
    uint32_t                     samplers_count;
679
    tg3_extras_ext               ext;
680
} tg3_animation;
681
682
/* --- Camera --- */
683
typedef struct tg3_perspective_camera {
684
    double aspect_ratio;
685
    double yfov;
686
    double zfar;  /* 0 = infinite */
687
    double znear;
688
    tg3_extras_ext ext;
689
} tg3_perspective_camera;
690
691
typedef struct tg3_orthographic_camera {
692
    double xmag;
693
    double ymag;
694
    double zfar;
695
    double znear;
696
    tg3_extras_ext ext;
697
} tg3_orthographic_camera;
698
699
typedef struct tg3_camera {
700
    tg3_str                  name;
701
    tg3_str                  type; /* "perspective" or "orthographic" */
702
    tg3_perspective_camera   perspective;
703
    tg3_orthographic_camera  orthographic;
704
    tg3_extras_ext           ext;
705
} tg3_camera;
706
707
/* --- Scene --- */
708
typedef struct tg3_scene {
709
    tg3_str        name;
710
    const int32_t *nodes;
711
    uint32_t       nodes_count;
712
    const int32_t *audio_emitters;
713
    uint32_t       audio_emitters_count;
714
    tg3_extras_ext ext;
715
} tg3_scene;
716
717
/* --- Light (KHR_lights_punctual) --- */
718
typedef struct tg3_spot_light {
719
    double inner_cone_angle; /* Default: 0 */
720
    double outer_cone_angle; /* Default: PI/4 */
721
    tg3_extras_ext ext;
722
} tg3_spot_light;
723
724
typedef struct tg3_light {
725
    tg3_str        name;
726
    double         color[3];    /* Default: {1,1,1} */
727
    double         intensity;   /* Default: 1.0 */
728
    tg3_str        type;        /* "directional","point","spot" */
729
    double         range;       /* Default: 0 (infinite) */
730
    tg3_spot_light spot;
731
    tg3_extras_ext ext;
732
} tg3_light;
733
734
/* --- Audio (KHR_audio) --- */
735
typedef struct tg3_audio_source {
736
    tg3_str        name;
737
    tg3_str        uri;
738
    int32_t        buffer_view; /* -1 if absent */
739
    tg3_str        mime_type;
740
    tg3_extras_ext ext;
741
} tg3_audio_source;
742
743
typedef struct tg3_positional_emitter {
744
    double cone_inner_angle;   /* Default: 2*PI */
745
    double cone_outer_angle;   /* Default: 2*PI */
746
    double cone_outer_gain;    /* Default: 0 */
747
    double max_distance;       /* Default: 100 */
748
    double ref_distance;       /* Default: 1 */
749
    double rolloff_factor;     /* Default: 1 */
750
    tg3_extras_ext ext;
751
} tg3_positional_emitter;
752
753
typedef struct tg3_audio_emitter {
754
    tg3_str              name;
755
    double               gain;           /* Default: 1.0 */
756
    int32_t              loop;           /* Default: 0 */
757
    int32_t              playing;        /* Default: 0 */
758
    tg3_str              type;           /* "positional" or "global" */
759
    tg3_str              distance_model; /* "linear","inverse","exponential" */
760
    tg3_positional_emitter positional;
761
    int32_t              source;         /* -1 if absent */
762
    tg3_extras_ext       ext;
763
} tg3_audio_emitter;
764
765
/* ======================================================================
766
 * Section 10: Model Container
767
 * ====================================================================== */
768
769
/* Opaque arena type */
770
struct tg3_arena;
771
772
typedef struct tg3_model {
773
    struct tg3_arena *arena_;  /* Internal, all memory owned here */
774
775
    const tg3_accessor      *accessors;      uint32_t accessors_count;
776
    const tg3_animation     *animations;     uint32_t animations_count;
777
    const tg3_buffer        *buffers;        uint32_t buffers_count;
778
    const tg3_buffer_view   *buffer_views;   uint32_t buffer_views_count;
779
    const tg3_material      *materials;      uint32_t materials_count;
780
    const tg3_mesh          *meshes;         uint32_t meshes_count;
781
    const tg3_node          *nodes;          uint32_t nodes_count;
782
    const tg3_texture       *textures;       uint32_t textures_count;
783
    const tg3_image         *images;         uint32_t images_count;
784
    const tg3_skin          *skins;          uint32_t skins_count;
785
    const tg3_sampler       *samplers;       uint32_t samplers_count;
786
    const tg3_camera        *cameras;        uint32_t cameras_count;
787
    const tg3_scene         *scenes;         uint32_t scenes_count;
788
    const tg3_light         *lights;         uint32_t lights_count;
789
    const tg3_audio_emitter *audio_emitters; uint32_t audio_emitters_count;
790
    const tg3_audio_source  *audio_sources;  uint32_t audio_sources_count;
791
792
    int32_t    default_scene;
793
    const tg3_str *extensions_used;      uint32_t extensions_used_count;
794
    const tg3_str *extensions_required;  uint32_t extensions_required_count;
795
    tg3_asset  asset;
796
    tg3_extras_ext ext;
797
} tg3_model;
798
799
/* ======================================================================
800
 * Section 11: Callback Typedefs
801
 * ====================================================================== */
802
803
/* --- Filesystem Callbacks --- */
804
805
typedef int32_t (*tg3_file_exists_fn)(const char *path, uint32_t path_len,
806
                                      void *user_data);
807
808
typedef int32_t (*tg3_read_file_fn)(uint8_t **out_data, uint64_t *out_size,
809
                                    const char *path, uint32_t path_len,
810
                                    void *user_data);
811
812
typedef void (*tg3_free_file_fn)(uint8_t *data, uint64_t size,
813
                                  void *user_data);
814
815
typedef int32_t (*tg3_write_file_fn)(const char *path, uint32_t path_len,
816
                                     const uint8_t *data, uint64_t size,
817
                                     void *user_data);
818
819
typedef int32_t (*tg3_resolve_path_fn)(char *out_path, uint32_t out_cap,
820
                                       uint32_t *out_len,
821
                                       const char *path, uint32_t path_len,
822
                                       void *user_data);
823
824
typedef int32_t (*tg3_get_file_size_fn)(uint64_t *out_size,
825
                                        const char *path, uint32_t path_len,
826
                                        void *user_data);
827
828
typedef struct tg3_fs_callbacks {
829
    tg3_file_exists_fn   file_exists;
830
    tg3_read_file_fn     read_file;
831
    tg3_free_file_fn     free_file;
832
    tg3_write_file_fn    write_file;
833
    tg3_resolve_path_fn  resolve_path;
834
    tg3_get_file_size_fn get_file_size;
835
    void                *user_data;
836
} tg3_fs_callbacks;
837
838
/* --- Image Callbacks --- */
839
840
typedef struct tg3_image_request {
841
    const uint8_t *data;
842
    uint64_t       data_size;
843
    int32_t        image_index;
844
    int32_t        req_width;
845
    int32_t        req_height;
846
    const char    *mime_type;
847
} tg3_image_request;
848
849
typedef struct tg3_image_result {
850
    uint8_t  *pixels;     /* Caller must allocate */
851
    int32_t   width;
852
    int32_t   height;
853
    int32_t   component;
854
    int32_t   bits;
855
    int32_t   pixel_type;
856
} tg3_image_result;
857
858
typedef int32_t (*tg3_load_image_fn)(tg3_image_result *result,
859
                                     const tg3_image_request *request,
860
                                     void *user_data);
861
862
typedef void (*tg3_free_image_fn)(uint8_t *pixels, void *user_data);
863
864
typedef struct tg3_image_callbacks {
865
    tg3_load_image_fn  load_image;
866
    tg3_free_image_fn  free_image;
867
    void              *user_data;
868
} tg3_image_callbacks;
869
870
/* --- URI Callbacks --- */
871
872
typedef int32_t (*tg3_uri_encode_fn)(char *out, uint32_t out_cap,
873
                                     uint32_t *out_len,
874
                                     const char *uri, uint32_t uri_len,
875
                                     const char *obj_type,
876
                                     void *user_data);
877
878
typedef int32_t (*tg3_uri_decode_fn)(char *out, uint32_t out_cap,
879
                                     uint32_t *out_len,
880
                                     const char *uri, uint32_t uri_len,
881
                                     void *user_data);
882
883
typedef struct tg3_uri_callbacks {
884
    tg3_uri_encode_fn encode;
885
    tg3_uri_decode_fn decode;
886
    void             *user_data;
887
} tg3_uri_callbacks;
888
889
/* --- Streaming Callbacks --- */
890
891
typedef enum tg3_stream_action {
892
    TG3_STREAM_CONTINUE = 0,
893
    TG3_STREAM_ABORT    = 1,
894
    TG3_STREAM_SKIP     = 2
895
} tg3_stream_action;
896
897
typedef tg3_stream_action (*tg3_on_asset_fn)(const tg3_asset *a, void *ud);
898
typedef tg3_stream_action (*tg3_on_buffer_fn)(const tg3_buffer *b, int32_t idx, void *ud);
899
typedef tg3_stream_action (*tg3_on_buffer_view_fn)(const tg3_buffer_view *bv, int32_t idx, void *ud);
900
typedef tg3_stream_action (*tg3_on_accessor_fn)(const tg3_accessor *a, int32_t idx, void *ud);
901
typedef tg3_stream_action (*tg3_on_mesh_fn)(const tg3_mesh *m, int32_t idx, void *ud);
902
typedef tg3_stream_action (*tg3_on_node_fn)(const tg3_node *n, int32_t idx, void *ud);
903
typedef tg3_stream_action (*tg3_on_material_fn)(const tg3_material *m, int32_t idx, void *ud);
904
typedef tg3_stream_action (*tg3_on_texture_fn)(const tg3_texture *t, int32_t idx, void *ud);
905
typedef tg3_stream_action (*tg3_on_image_fn)(const tg3_image *img, int32_t idx, void *ud);
906
typedef tg3_stream_action (*tg3_on_sampler_fn)(const tg3_sampler *s, int32_t idx, void *ud);
907
typedef tg3_stream_action (*tg3_on_animation_fn)(const tg3_animation *a, int32_t idx, void *ud);
908
typedef tg3_stream_action (*tg3_on_skin_fn)(const tg3_skin *s, int32_t idx, void *ud);
909
typedef tg3_stream_action (*tg3_on_camera_fn)(const tg3_camera *c, int32_t idx, void *ud);
910
typedef tg3_stream_action (*tg3_on_scene_fn)(const tg3_scene *s, int32_t idx, void *ud);
911
typedef tg3_stream_action (*tg3_on_light_fn)(const tg3_light *l, int32_t idx, void *ud);
912
913
typedef struct tg3_stream_callbacks {
914
    tg3_on_asset_fn       on_asset;
915
    tg3_on_buffer_fn      on_buffer;
916
    tg3_on_buffer_view_fn on_buffer_view;
917
    tg3_on_accessor_fn    on_accessor;
918
    tg3_on_mesh_fn        on_mesh;
919
    tg3_on_node_fn        on_node;
920
    tg3_on_material_fn    on_material;
921
    tg3_on_texture_fn     on_texture;
922
    tg3_on_image_fn       on_image;
923
    tg3_on_sampler_fn     on_sampler;
924
    tg3_on_animation_fn   on_animation;
925
    tg3_on_skin_fn        on_skin;
926
    tg3_on_camera_fn      on_camera;
927
    tg3_on_scene_fn       on_scene;
928
    tg3_on_light_fn       on_light;
929
    void                 *user_data;
930
} tg3_stream_callbacks;
931
932
/* --- Progress Callback --- */
933
934
typedef struct tg3_progress_info {
935
    uint64_t    bytes_processed;
936
    uint64_t    bytes_total;
937
    uint32_t    elements_parsed;
938
    const char *current_section; /* e.g. "meshes", "nodes" */
939
} tg3_progress_info;
940
941
typedef int32_t (*tg3_progress_fn)(const tg3_progress_info *info,
942
                                    void *user_data);
943
944
/* --- Write Chunk Callback (for streaming writer) --- */
945
946
typedef int32_t (*tg3_write_chunk_fn)(const uint8_t *data, uint64_t size,
947
                                      void *user_data);
948
949
/* ======================================================================
950
 * Section 12: Options Structs
951
 * ====================================================================== */
952
953
typedef struct tg3_memory_config {
954
    uint64_t       memory_budget;     /* 0 = use TINYGLTF3_MAX_MEMORY_BYTES */
955
    uint64_t       max_single_alloc;  /* 0 = no limit */
956
    uint32_t       arena_block_size;  /* 0 = default (256KB) */
957
    tg3_allocator  allocator;         /* All zero = use malloc/free */
958
} tg3_memory_config;
959
960
typedef struct tg3_parse_options {
961
    uint32_t             required_sections; /* TG3_REQUIRE_* flags */
962
    tg3_strictness       strictness;
963
    tg3_memory_config    memory;
964
965
    tg3_fs_callbacks     fs;
966
    tg3_uri_callbacks    uri;
967
    tg3_image_callbacks  image;
968
    tg3_stream_callbacks *stream;  /* NULL = no streaming */
969
    tg3_progress_fn      progress;
970
    void                *progress_user_data;
971
972
    int32_t  images_as_is;              /* 1 = don't decode images */
973
    int32_t  preserve_image_channels;   /* 1 = keep original channels */
974
    int32_t  store_original_json;       /* 1 = store raw JSON strings */
975
    int32_t  skip_extras_values;        /* 1 = skip materializing extras and
976
                                        *     unknown extension value trees */
977
    int32_t  borrow_input_buffers;      /* 1 = GLB BIN buffer spans may point
978
                                        *     into caller-owned input data */
979
    int32_t  parse_float32;            /* 1 = parse JSON floats as float32 for speed
980
                                        *     (breaks strict double-precision conformance
981
                                        *      but sufficient for glTF data which is
982
                                        *      typically single-precision anyway) */
983
    int32_t  validate_indices;          /* 1 = reject out-of-range index fields
984
                                        *     after parse so naive consumers cannot
985
                                        *     dereference attacker-controlled indices.
986
                                        *     Default: 1. Set to 0 to skip (raw mode). */
987
    uint64_t max_external_file_size;    /* 0 = no limit */
988
} tg3_parse_options;
989
990
typedef struct tg3_write_options {
991
    int32_t          pretty_print;     /* 1 = indented JSON */
992
    int32_t          write_binary;     /* 1 = GLB format */
993
    int32_t          embed_images;     /* 1 = embed as data URIs */
994
    int32_t          embed_buffers;    /* 1 = embed as data URIs */
995
    int32_t          serialize_defaults; /* 1 = write default values */
996
    tg3_fs_callbacks fs;
997
    tg3_uri_callbacks uri;
998
    tg3_memory_config memory;
999
} tg3_write_options;
1000
1001
/* ======================================================================
1002
 * Section 13: Parser API
1003
 * ====================================================================== */
1004
1005
/* Parse JSON glTF from memory */
1006
TINYGLTF3_API tg3_error_code tg3_parse(
1007
    tg3_model *model, tg3_error_stack *errors,
1008
    const uint8_t *json_data, uint64_t json_size,
1009
    const char *base_dir, uint32_t base_dir_len,
1010
    const tg3_parse_options *options);
1011
1012
/* Parse GLB from memory */
1013
TINYGLTF3_API tg3_error_code tg3_parse_glb(
1014
    tg3_model *model, tg3_error_stack *errors,
1015
    const uint8_t *glb_data, uint64_t glb_size,
1016
    const char *base_dir, uint32_t base_dir_len,
1017
    const tg3_parse_options *options);
1018
1019
/* Auto-detect format (JSON or GLB) and parse */
1020
TINYGLTF3_API tg3_error_code tg3_parse_auto(
1021
    tg3_model *model, tg3_error_stack *errors,
1022
    const uint8_t *data, uint64_t size,
1023
    const char *base_dir, uint32_t base_dir_len,
1024
    const tg3_parse_options *options);
1025
1026
/* Parse from file (requires fs callbacks or TINYGLTF3_ENABLE_FS) */
1027
TINYGLTF3_API tg3_error_code tg3_parse_file(
1028
    tg3_model *model, tg3_error_stack *errors,
1029
    const char *filename, uint32_t filename_len,
1030
    const tg3_parse_options *options);
1031
1032
/* Free model and all arena memory */
1033
TINYGLTF3_API void tg3_model_free(tg3_model *model);
1034
1035
/* Initialize options to defaults */
1036
TINYGLTF3_API void tg3_parse_options_init(tg3_parse_options *options);
1037
TINYGLTF3_API void tg3_write_options_init(tg3_write_options *options);
1038
1039
/* Initialize error stack */
1040
TINYGLTF3_API void tg3_error_stack_init(tg3_error_stack *es);
1041
TINYGLTF3_API void tg3_error_stack_free(tg3_error_stack *es);
1042
1043
/* ======================================================================
1044
 * Section 14: Writer API
1045
 * ====================================================================== */
1046
1047
/* Write model to memory buffer */
1048
TINYGLTF3_API tg3_error_code tg3_write_to_memory(
1049
    const tg3_model *model, tg3_error_stack *errors,
1050
    uint8_t **out_data, uint64_t *out_size,
1051
    const tg3_write_options *options);
1052
1053
/* Write model to file */
1054
TINYGLTF3_API tg3_error_code tg3_write_to_file(
1055
    const tg3_model *model, tg3_error_stack *errors,
1056
    const char *filename, uint32_t filename_len,
1057
    const tg3_write_options *options);
1058
1059
/* Free memory from tg3_write_to_memory */
1060
TINYGLTF3_API void tg3_write_free(uint8_t *data, const tg3_write_options *options);
1061
1062
/* --- Streaming Writer --- */
1063
1064
typedef struct tg3_writer tg3_writer;
1065
1066
TINYGLTF3_API tg3_writer *tg3_writer_create(
1067
    tg3_write_chunk_fn chunk_fn, void *user_data,
1068
    const tg3_write_options *options);
1069
1070
TINYGLTF3_API tg3_error_code tg3_writer_begin(tg3_writer *w, const tg3_asset *asset);
1071
TINYGLTF3_API tg3_error_code tg3_writer_add_buffer(tg3_writer *w, const tg3_buffer *buf);
1072
TINYGLTF3_API tg3_error_code tg3_writer_add_buffer_view(tg3_writer *w, const tg3_buffer_view *bv);
1073
TINYGLTF3_API tg3_error_code tg3_writer_add_accessor(tg3_writer *w, const tg3_accessor *acc);
1074
TINYGLTF3_API tg3_error_code tg3_writer_add_mesh(tg3_writer *w, const tg3_mesh *mesh);
1075
TINYGLTF3_API tg3_error_code tg3_writer_add_node(tg3_writer *w, const tg3_node *node);
1076
TINYGLTF3_API tg3_error_code tg3_writer_add_material(tg3_writer *w, const tg3_material *mat);
1077
TINYGLTF3_API tg3_error_code tg3_writer_add_texture(tg3_writer *w, const tg3_texture *tex);
1078
TINYGLTF3_API tg3_error_code tg3_writer_add_image(tg3_writer *w, const tg3_image *img);
1079
TINYGLTF3_API tg3_error_code tg3_writer_add_sampler(tg3_writer *w, const tg3_sampler *samp);
1080
TINYGLTF3_API tg3_error_code tg3_writer_add_animation(tg3_writer *w, const tg3_animation *anim);
1081
TINYGLTF3_API tg3_error_code tg3_writer_add_skin(tg3_writer *w, const tg3_skin *skin);
1082
TINYGLTF3_API tg3_error_code tg3_writer_add_camera(tg3_writer *w, const tg3_camera *cam);
1083
TINYGLTF3_API tg3_error_code tg3_writer_add_scene(tg3_writer *w, const tg3_scene *scene);
1084
TINYGLTF3_API tg3_error_code tg3_writer_add_light(tg3_writer *w, const tg3_light *light);
1085
TINYGLTF3_API tg3_error_code tg3_writer_end(tg3_writer *w);
1086
TINYGLTF3_API void           tg3_writer_destroy(tg3_writer *w);
1087
1088
/* ======================================================================
1089
 * Section 15: Utility Functions
1090
 * ====================================================================== */
1091
1092
/* Get component size in bytes */
1093
TINYGLTF3_API int32_t tg3_component_size(int32_t component_type);
1094
1095
/* Get number of components for a type */
1096
TINYGLTF3_API int32_t tg3_num_components(int32_t type);
1097
1098
/* Compute byte stride for an accessor */
1099
TINYGLTF3_API int32_t tg3_accessor_byte_stride(const tg3_accessor *accessor,
1100
                                                const tg3_buffer_view *buffer_view);
1101
1102
/* Check if a string is a data URI */
1103
TINYGLTF3_API int32_t tg3_is_data_uri(const char *uri, uint32_t len);
1104
1105
/* tg3_str helpers */
1106
TINYGLTF3_API int32_t tg3_str_equals(tg3_str a, tg3_str b);
1107
TINYGLTF3_API int32_t tg3_str_equals_cstr(tg3_str a, const char *b);
1108
1109
#ifdef __cplusplus
1110
} /* extern "C" */
1111
#endif
1112
1113
/* ======================================================================
1114
 * Section 16: C++ Convenience Wrappers
1115
 * ====================================================================== */
1116
1117
#ifdef __cplusplus
1118
namespace tinygltf3 {
1119
1120
/* RAII model wrapper */
1121
class Model {
1122
public:
1123
0
    Model() { memset(&m_, 0, sizeof(m_)); m_.default_scene = -1; }
1124
0
    ~Model() { tg3_model_free(&m_); }
1125
1126
    Model(const Model &) = delete;
1127
    Model &operator=(const Model &) = delete;
1128
0
    Model(Model &&o) noexcept : m_(o.m_) { memset(&o.m_, 0, sizeof(o.m_)); }
1129
0
    Model &operator=(Model &&o) noexcept {
1130
0
        if (this != &o) { tg3_model_free(&m_); m_ = o.m_; memset(&o.m_, 0, sizeof(o.m_)); }
1131
0
        return *this;
1132
0
    }
1133
1134
0
    tg3_model       *get()       { return &m_; }
1135
0
    const tg3_model *get() const { return &m_; }
1136
0
    tg3_model       *operator->()       { return &m_; }
1137
0
    const tg3_model *operator->() const { return &m_; }
1138
1139
private:
1140
    tg3_model m_;
1141
};
1142
1143
/* RAII error stack wrapper */
1144
class ErrorStack {
1145
public:
1146
0
    ErrorStack()  { tg3_error_stack_init(&es_); }
1147
0
    ~ErrorStack() { tg3_error_stack_free(&es_); }
1148
1149
    ErrorStack(const ErrorStack &) = delete;
1150
    ErrorStack &operator=(const ErrorStack &) = delete;
1151
1152
0
    tg3_error_stack       *get()       { return &es_; }
1153
0
    const tg3_error_stack *get() const { return &es_; }
1154
1155
0
    bool has_error() const { return tg3_errors_has_error(&es_) != 0; }
1156
0
    uint32_t count() const { return tg3_errors_count(&es_); }
1157
0
    const tg3_error_entry *entry(uint32_t i) const { return tg3_errors_get(&es_, i); }
1158
1159
private:
1160
    tg3_error_stack es_;
1161
};
1162
1163
/* Parse helpers returning error code */
1164
inline tg3_error_code parse_file(Model &model, ErrorStack &errors,
1165
                                  const char *filename,
1166
0
                                  const tg3_parse_options *options = nullptr) {
1167
0
    tg3_parse_options opts;
1168
0
    if (!options) { tg3_parse_options_init(&opts); options = &opts; }
1169
0
    return tg3_parse_file(model.get(), errors.get(), filename,
1170
0
                          filename ? (uint32_t)strlen(filename) : 0, options);
1171
0
}
1172
1173
inline tg3_error_code parse(Model &model, ErrorStack &errors,
1174
                             const uint8_t *data, uint64_t size,
1175
                             const char *base_dir = "",
1176
0
                             const tg3_parse_options *options = nullptr) {
1177
0
    tg3_parse_options opts;
1178
0
    if (!options) { tg3_parse_options_init(&opts); options = &opts; }
1179
0
    return tg3_parse_auto(model.get(), errors.get(), data, size,
1180
0
                          base_dir, base_dir ? (uint32_t)strlen(base_dir) : 0,
1181
0
                          options);
1182
0
}
1183
1184
} /* namespace tinygltf3 */
1185
#endif /* __cplusplus */
1186
1187
/* ======================================================================
1188
 * Section 17: C++20 Coroutine Facade
1189
 * ====================================================================== */
1190
1191
#ifdef __cplusplus
1192
#if defined(TINYGLTF3_ENABLE_COROUTINES) || \
1193
    (defined(__cpp_impl_coroutine) && __cpp_impl_coroutine >= 201902L && \
1194
     defined(__cpp_lib_coroutine) && __cpp_lib_coroutine >= 201902L)
1195
1196
#include <coroutine>
1197
1198
namespace tinygltf3 {
1199
1200
struct ParsedElement {
1201
    enum Kind {
1202
        Asset, Buffer, BufferView, Accessor, Mesh, Node, Material,
1203
        Texture, Image, Sampler, Animation, Skin, Camera, Scene,
1204
        Light, Done, Error
1205
    };
1206
1207
    Kind    kind;
1208
    int32_t index;
1209
1210
    union {
1211
        const tg3_asset      *asset;
1212
        const tg3_buffer     *buffer;
1213
        const tg3_buffer_view *buffer_view;
1214
        const tg3_accessor   *accessor;
1215
        const tg3_mesh       *mesh;
1216
        const tg3_node       *node;
1217
        const tg3_material   *material;
1218
        const tg3_texture    *texture;
1219
        const tg3_image      *image;
1220
        const tg3_sampler    *sampler;
1221
        const tg3_animation  *animation;
1222
        const tg3_skin       *skin;
1223
        const tg3_camera     *camera;
1224
        const tg3_scene      *scene;
1225
        const tg3_light      *light;
1226
        const void           *ptr;
1227
    };
1228
1229
    tg3_error_code error_code;
1230
};
1231
1232
class ParseGenerator {
1233
public:
1234
    struct promise_type {
1235
        ParsedElement current_;
1236
        std::suspend_always initial_suspend() noexcept { return {}; }
1237
        std::suspend_always final_suspend() noexcept { return {}; }
1238
        ParseGenerator get_return_object() {
1239
            return ParseGenerator(
1240
                std::coroutine_handle<promise_type>::from_promise(*this));
1241
        }
1242
        std::suspend_always yield_value(ParsedElement elem) noexcept {
1243
            current_ = elem;
1244
            return {};
1245
        }
1246
        void return_void() {}
1247
        void unhandled_exception() {}
1248
    };
1249
1250
    ParseGenerator() : handle_(nullptr) {}
1251
    explicit ParseGenerator(std::coroutine_handle<promise_type> h) : handle_(h) {}
1252
    ~ParseGenerator() { if (handle_) handle_.destroy(); }
1253
1254
    ParseGenerator(const ParseGenerator &) = delete;
1255
    ParseGenerator &operator=(const ParseGenerator &) = delete;
1256
    ParseGenerator(ParseGenerator &&o) noexcept : handle_(o.handle_) { o.handle_ = nullptr; }
1257
    ParseGenerator &operator=(ParseGenerator &&o) noexcept {
1258
        if (this != &o) { if (handle_) handle_.destroy(); handle_ = o.handle_; o.handle_ = nullptr; }
1259
        return *this;
1260
    }
1261
1262
    bool next() {
1263
        if (!handle_ || handle_.done()) return false;
1264
        handle_.resume();
1265
        return !handle_.done();
1266
    }
1267
1268
    const ParsedElement &current() const { return handle_.promise().current_; }
1269
    bool done() const { return !handle_ || handle_.done(); }
1270
1271
private:
1272
    std::coroutine_handle<promise_type> handle_;
1273
};
1274
1275
/* Coroutine parse entry point — declaration only, implemented in TINYGLTF3_IMPLEMENTATION */
1276
ParseGenerator tg3_parse_coro(
1277
    const uint8_t *data, uint64_t size,
1278
    const char *base_dir, uint32_t base_dir_len,
1279
    tg3_model *model, tg3_error_stack *errors,
1280
    const tg3_parse_options *options);
1281
1282
} /* namespace tinygltf3 */
1283
1284
#endif /* coroutines */
1285
#endif /* __cplusplus */
1286
1287
/* ======================================================================
1288
 * Section 18: Implementation
1289
 * ====================================================================== */
1290
1291
#ifdef TINYGLTF3_IMPLEMENTATION
1292
#define TINYGLTF3_SOURCE_INCLUDED_FROM_HEADER 1
1293
#include "tiny_gltf_v3.c"
1294
#undef TINYGLTF3_SOURCE_INCLUDED_FROM_HEADER
1295
1296
#if 0
1297
1298
#if !defined(__cplusplus)
1299
#error "TINYGLTF3_IMPLEMENTATION requires a C++ translation unit (compile as .cpp)"
1300
#endif
1301
1302
/* Include JSON parser */
1303
#include "tinygltf_json.h"
1304
1305
#include <stdio.h>
1306
#include <math.h>
1307
1308
/* Implementation uses C++ features from tinygltf_json.h */
1309
#include <string>
1310
#include <algorithm>
1311
1312
/* Forward SIMD macros to tinygltf_json.h */
1313
#ifdef TINYGLTF3_JSON_SIMD_SSE2
1314
#ifndef TINYGLTF_JSON_SIMD_SSE2
1315
#define TINYGLTF_JSON_SIMD_SSE2
1316
#endif
1317
#endif
1318
#ifdef TINYGLTF3_JSON_SIMD_AVX2
1319
#ifndef TINYGLTF_JSON_SIMD_AVX2
1320
#define TINYGLTF_JSON_SIMD_AVX2
1321
#endif
1322
#endif
1323
#ifdef TINYGLTF3_JSON_SIMD_NEON
1324
#ifndef TINYGLTF_JSON_SIMD_NEON
1325
#define TINYGLTF_JSON_SIMD_NEON
1326
#endif
1327
#endif
1328
1329
/* ======================================================================
1330
 * Internal: Arena Allocator
1331
 * ====================================================================== */
1332
1333
#define TG3__ARENA_DEFAULT_BLOCK_SIZE (256u * 1024u) /* 256 KB */
1334
#define TG3__ARENA_ALIGNMENT 8
1335
1336
typedef struct tg3__arena_block {
1337
    struct tg3__arena_block *next;
1338
    uint8_t *base;
1339
    size_t   used;
1340
    size_t   capacity;
1341
} tg3__arena_block;
1342
1343
struct tg3_arena {
1344
    tg3__arena_block *head;
1345
    tg3__arena_block *current;
1346
    size_t            total_allocated;
1347
    size_t            memory_budget;
1348
    size_t            block_size;
1349
    tg3_allocator     alloc;
1350
};
1351
1352
static void *tg3__default_alloc(size_t size, void *ud) {
1353
    (void)ud;
1354
    return malloc(size);
1355
}
1356
static void *tg3__default_realloc(void *ptr, size_t old_size, size_t new_size, void *ud) {
1357
    (void)old_size; (void)ud;
1358
    return realloc(ptr, new_size);
1359
}
1360
static void tg3__default_free(void *ptr, size_t size, void *ud) {
1361
    (void)size; (void)ud;
1362
    free(ptr);
1363
}
1364
1365
static tg3_arena *tg3__arena_create(const tg3_memory_config *config) {
1366
    tg3_allocator alloc;
1367
    if (config && config->allocator.alloc) {
1368
        alloc = config->allocator;
1369
    } else {
1370
        alloc.alloc = tg3__default_alloc;
1371
        alloc.realloc = tg3__default_realloc;
1372
        alloc.free = tg3__default_free;
1373
        alloc.user_data = NULL;
1374
    }
1375
1376
    tg3_arena *arena = (tg3_arena *)alloc.alloc(sizeof(tg3_arena), alloc.user_data);
1377
    if (!arena) return NULL;
1378
1379
    memset(arena, 0, sizeof(tg3_arena));
1380
    arena->alloc = alloc;
1381
    arena->block_size = (config && config->arena_block_size > 0)
1382
        ? config->arena_block_size : TG3__ARENA_DEFAULT_BLOCK_SIZE;
1383
    arena->memory_budget = (config && config->memory_budget > 0)
1384
        ? (size_t)config->memory_budget : (size_t)TINYGLTF3_MAX_MEMORY_BYTES;
1385
1386
    return arena;
1387
}
1388
1389
static tg3__arena_block *tg3__arena_new_block(tg3_arena *arena, size_t min_size) {
1390
    size_t cap = arena->block_size;
1391
    if (cap < min_size) cap = min_size;
1392
1393
    if (arena->total_allocated + sizeof(tg3__arena_block) + cap > arena->memory_budget) {
1394
        return NULL; /* OOM */
1395
    }
1396
1397
    uint8_t *raw = (uint8_t *)arena->alloc.alloc(
1398
        sizeof(tg3__arena_block) + cap, arena->alloc.user_data);
1399
    if (!raw) return NULL;
1400
1401
    tg3__arena_block *block = (tg3__arena_block *)raw;
1402
    block->base = raw + sizeof(tg3__arena_block);
1403
    block->used = 0;
1404
    block->capacity = cap;
1405
    block->next = NULL;
1406
1407
    arena->total_allocated += sizeof(tg3__arena_block) + cap;
1408
1409
    if (arena->current) {
1410
        arena->current->next = block;
1411
    } else {
1412
        arena->head = block;
1413
    }
1414
    arena->current = block;
1415
1416
    return block;
1417
}
1418
1419
static void *tg3__arena_alloc(tg3_arena *arena, size_t size) {
1420
    if (size == 0) return NULL;
1421
1422
    /* Align up */
1423
    size = (size + (TG3__ARENA_ALIGNMENT - 1)) & ~(size_t)(TG3__ARENA_ALIGNMENT - 1);
1424
1425
    tg3__arena_block *block = arena->current;
1426
    if (!block || block->used + size > block->capacity) {
1427
        block = tg3__arena_new_block(arena, size);
1428
        if (!block) return NULL;
1429
    }
1430
1431
    void *ptr = block->base + block->used;
1432
    block->used += size;
1433
    return ptr;
1434
}
1435
1436
static char *tg3__arena_strdup(tg3_arena *arena, const char *s, size_t len) {
1437
    if (!s) return NULL;
1438
    /* Allocate len+1 bytes; when len==0 this produces a 1-byte "\0" buffer so
1439
     * that empty strings (data!=NULL, len==0) remain distinguishable from
1440
     * absent strings (data==NULL, len==0). */
1441
    char *dst = (char *)tg3__arena_alloc(arena, len + 1);
1442
    if (!dst) return NULL;
1443
    if (len > 0) memcpy(dst, s, len);
1444
    dst[len] = '\0';
1445
    return dst;
1446
}
1447
1448
static tg3_str tg3__arena_str(tg3_arena *arena, const char *s, uint32_t len) {
1449
    tg3_str result;
1450
    result.data = tg3__arena_strdup(arena, s, len);
1451
    result.len = result.data ? len : 0;
1452
    return result;
1453
}
1454
1455
static tg3_str tg3__arena_str_from_std(tg3_arena *arena, const std::string &s) {
1456
    return tg3__arena_str(arena, s.c_str(), (uint32_t)s.size());
1457
}
1458
1459
static void tg3__arena_destroy(tg3_arena *arena) {
1460
    if (!arena) return;
1461
    tg3_allocator alloc = arena->alloc;
1462
    tg3__arena_block *block = arena->head;
1463
    while (block) {
1464
        tg3__arena_block *next = block->next;
1465
        size_t block_total = sizeof(tg3__arena_block) + block->capacity;
1466
        alloc.free(block, block_total, alloc.user_data);
1467
        block = next;
1468
    }
1469
    alloc.free(arena, sizeof(tg3_arena), alloc.user_data);
1470
}
1471
1472
/* ======================================================================
1473
 * Internal: Error Stack Implementation
1474
 * ====================================================================== */
1475
1476
static void tg3__error_push(tg3_error_stack *es, tg3_severity sev,
1477
                             tg3_error_code code, const char *msg,
1478
                             const char *json_path, int64_t byte_offset) {
1479
    if (!es) return;
1480
1481
    if (es->count >= es->capacity) {
1482
        uint32_t new_cap = es->capacity ? es->capacity * 2 : 16;
1483
        tg3_error_entry *new_entries = (tg3_error_entry *)realloc(
1484
            es->entries, new_cap * sizeof(tg3_error_entry));
1485
        if (!new_entries) return; /* Drop error on OOM */
1486
        es->entries = new_entries;
1487
        es->capacity = new_cap;
1488
    }
1489
1490
    tg3_error_entry *e = &es->entries[es->count++];
1491
    e->severity = sev;
1492
    e->code = code;
1493
    e->message = msg; /* Caller must ensure lifetime (arena or static) */
1494
    e->json_path = json_path;
1495
    e->byte_offset = byte_offset;
1496
1497
    if (sev == TG3_SEVERITY_ERROR) es->has_error = 1;
1498
}
1499
1500
/* Push an error with a dynamically formatted message allocated from arena */
1501
static void tg3__error_pushf(tg3_error_stack *es, tg3_arena *arena,
1502
                              tg3_severity sev, tg3_error_code code,
1503
                              const char *json_path, const char *fmt, ...) {
1504
    if (!es) return;
1505
    char buf[1024];
1506
    va_list ap;
1507
    va_start(ap, fmt);
1508
    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
1509
    va_end(ap);
1510
    if (n < 0) n = 0;
1511
    if ((size_t)n >= sizeof(buf)) n = (int)(sizeof(buf) - 1);
1512
1513
    const char *msg = buf;
1514
    if (arena) {
1515
        char *dup = tg3__arena_strdup(arena, buf, (size_t)n);
1516
        if (dup) msg = dup;
1517
    }
1518
    tg3__error_push(es, sev, code, msg, json_path, -1);
1519
}
1520
1521
/* ======================================================================
1522
 * Public: Error Stack API
1523
 * ====================================================================== */
1524
1525
TINYGLTF3_API int32_t tg3_errors_has_error(const tg3_error_stack *es) {
1526
    return es ? es->has_error : 0;
1527
}
1528
1529
TINYGLTF3_API uint32_t tg3_errors_count(const tg3_error_stack *es) {
1530
    return es ? es->count : 0;
1531
}
1532
1533
TINYGLTF3_API const tg3_error_entry *tg3_errors_get(const tg3_error_stack *es,
1534
                                                     uint32_t index) {
1535
    if (!es || index >= es->count) return NULL;
1536
    return &es->entries[index];
1537
}
1538
1539
TINYGLTF3_API void tg3_error_stack_init(tg3_error_stack *es) {
1540
    if (!es) return;
1541
    memset(es, 0, sizeof(tg3_error_stack));
1542
}
1543
1544
TINYGLTF3_API void tg3_error_stack_free(tg3_error_stack *es) {
1545
    if (!es) return;
1546
    free(es->entries);
1547
    memset(es, 0, sizeof(tg3_error_stack));
1548
}
1549
1550
/* ======================================================================
1551
 * Public: Options Init
1552
 * ====================================================================== */
1553
1554
TINYGLTF3_API void tg3_parse_options_init(tg3_parse_options *options) {
1555
    if (!options) return;
1556
    memset(options, 0, sizeof(tg3_parse_options));
1557
    options->required_sections = TG3_REQUIRE_VERSION;
1558
    options->strictness = TG3_PERMISSIVE;
1559
}
1560
1561
TINYGLTF3_API void tg3_write_options_init(tg3_write_options *options) {
1562
    if (!options) return;
1563
    memset(options, 0, sizeof(tg3_write_options));
1564
    options->pretty_print = 1;
1565
}
1566
1567
/* ======================================================================
1568
 * Public: Utility Functions
1569
 * ====================================================================== */
1570
1571
TINYGLTF3_API int32_t tg3_component_size(int32_t component_type) {
1572
    switch (component_type) {
1573
        case TG3_COMPONENT_TYPE_BYTE:           return 1;
1574
        case TG3_COMPONENT_TYPE_UNSIGNED_BYTE:  return 1;
1575
        case TG3_COMPONENT_TYPE_SHORT:          return 2;
1576
        case TG3_COMPONENT_TYPE_UNSIGNED_SHORT: return 2;
1577
        case TG3_COMPONENT_TYPE_INT:            return 4;
1578
        case TG3_COMPONENT_TYPE_UNSIGNED_INT:   return 4;
1579
        case TG3_COMPONENT_TYPE_FLOAT:          return 4;
1580
        case TG3_COMPONENT_TYPE_DOUBLE:         return 8;
1581
        default: return -1;
1582
    }
1583
}
1584
1585
TINYGLTF3_API int32_t tg3_num_components(int32_t type) {
1586
    switch (type) {
1587
        case TG3_TYPE_SCALAR: return 1;
1588
        case TG3_TYPE_VEC2:   return 2;
1589
        case TG3_TYPE_VEC3:   return 3;
1590
        case TG3_TYPE_VEC4:   return 4;
1591
        case TG3_TYPE_MAT2:   return 4;
1592
        case TG3_TYPE_MAT3:   return 9;
1593
        case TG3_TYPE_MAT4:   return 16;
1594
        default: return -1;
1595
    }
1596
}
1597
1598
TINYGLTF3_API int32_t tg3_accessor_byte_stride(const tg3_accessor *accessor,
1599
                                                const tg3_buffer_view *bv) {
1600
    if (bv && bv->byte_stride > 0) return (int32_t)bv->byte_stride;
1601
    int32_t comp = tg3_component_size(accessor->component_type);
1602
    int32_t num = tg3_num_components(accessor->type);
1603
    if (comp < 0 || num < 0) return -1;
1604
    return comp * num;
1605
}
1606
1607
TINYGLTF3_API int32_t tg3_str_equals(tg3_str a, tg3_str b) {
1608
    if (a.len != b.len) return 0;
1609
    if (a.len == 0) return 1;
1610
    return memcmp(a.data, b.data, a.len) == 0 ? 1 : 0;
1611
}
1612
1613
TINYGLTF3_API int32_t tg3_str_equals_cstr(tg3_str a, const char *b) {
1614
    if (!b) return a.len == 0 ? 1 : 0;
1615
    uint32_t blen = (uint32_t)strlen(b);
1616
    if (a.len != blen) return 0;
1617
    if (a.len == 0) return 1;
1618
    return memcmp(a.data, b, a.len) == 0 ? 1 : 0;
1619
}
1620
1621
/* ======================================================================
1622
 * Internal: Base64 Encode/Decode
1623
 * ====================================================================== */
1624
1625
static const char tg3__b64_chars[] =
1626
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1627
1628
static int tg3__b64_is_valid(unsigned char c) {
1629
    return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
1630
           (c >= '0' && c <= '9') || c == '+' || c == '/' || c == '=';
1631
}
1632
1633
static int tg3__b64_decode_char(unsigned char c) {
1634
    if (c >= 'A' && c <= 'Z') return c - 'A';
1635
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
1636
    if (c >= '0' && c <= '9') return c - '0' + 52;
1637
    if (c == '+') return 62;
1638
    if (c == '/') return 63;
1639
    return -1;
1640
}
1641
1642
static uint8_t *tg3__b64_decode(const char *input, size_t input_len,
1643
                                 size_t *out_len, tg3_arena *arena) {
1644
    if (input_len == 0) { *out_len = 0; return NULL; }
1645
1646
    /* Strip trailing padding */
1647
    size_t pad = 0;
1648
    while (input_len > 0 && input[input_len - 1] == '=') { ++pad; --input_len; }
1649
1650
    size_t decoded_len = (input_len * 3) / 4;
1651
    uint8_t *out = (uint8_t *)tg3__arena_alloc(arena, decoded_len + 1);
1652
    if (!out) { *out_len = 0; return NULL; }
1653
1654
    size_t j = 0;
1655
    uint32_t accum = 0;
1656
    int bits = 0;
1657
1658
    for (size_t i = 0; i < input_len; ++i) {
1659
        int val = tg3__b64_decode_char((unsigned char)input[i]);
1660
        if (val < 0) continue; /* skip whitespace/invalid */
1661
        accum = (accum << 6) | (uint32_t)val;
1662
        bits += 6;
1663
        if (bits >= 8) {
1664
            bits -= 8;
1665
            out[j++] = (uint8_t)((accum >> bits) & 0xFF);
1666
        }
1667
    }
1668
1669
    *out_len = j;
1670
    return out;
1671
}
1672
1673
static char *tg3__b64_encode(const uint8_t *input, size_t input_len,
1674
                              size_t *out_len) {
1675
    size_t enc_len = ((input_len + 2) / 3) * 4;
1676
    char *out = (char *)malloc(enc_len + 1);
1677
    if (!out) { *out_len = 0; return NULL; }
1678
1679
    size_t j = 0;
1680
    for (size_t i = 0; i < input_len; i += 3) {
1681
        uint32_t a = input[i];
1682
        uint32_t b = (i + 1 < input_len) ? input[i + 1] : 0;
1683
        uint32_t c = (i + 2 < input_len) ? input[i + 2] : 0;
1684
        uint32_t triple = (a << 16) | (b << 8) | c;
1685
1686
        out[j++] = tg3__b64_chars[(triple >> 18) & 0x3F];
1687
        out[j++] = tg3__b64_chars[(triple >> 12) & 0x3F];
1688
        out[j++] = (i + 1 < input_len) ? tg3__b64_chars[(triple >> 6) & 0x3F] : '=';
1689
        out[j++] = (i + 2 < input_len) ? tg3__b64_chars[triple & 0x3F] : '=';
1690
    }
1691
    out[j] = '\0';
1692
    *out_len = j;
1693
    return out;
1694
}
1695
1696
/* ======================================================================
1697
 * Internal: Data URI Handling
1698
 * ====================================================================== */
1699
1700
TINYGLTF3_API int32_t tg3_is_data_uri(const char *uri, uint32_t len) {
1701
    if (len < 5) return 0;
1702
    return (memcmp(uri, "data:", 5) == 0) ? 1 : 0;
1703
}
1704
1705
typedef struct tg3__data_uri_result {
1706
    const char *data_start;
1707
    size_t      data_len;
1708
    char        mime_type[64];
1709
} tg3__data_uri_result;
1710
1711
static int tg3__parse_data_uri(const char *uri, uint32_t uri_len,
1712
                                tg3__data_uri_result *result) {
1713
    /* Expected format: data:<mime>;base64,<data> */
1714
    if (uri_len < 5 || memcmp(uri, "data:", 5) != 0) return 0;
1715
1716
    const char *p = uri + 5;
1717
    const char *end = uri + uri_len;
1718
1719
    /* Find semicolon */
1720
    const char *semi = p;
1721
    while (semi < end && *semi != ';') ++semi;
1722
    if (semi >= end) return 0;
1723
1724
    /* Extract MIME type */
1725
    size_t mime_len = (size_t)(semi - p);
1726
    if (mime_len >= sizeof(result->mime_type)) mime_len = sizeof(result->mime_type) - 1;
1727
    memcpy(result->mime_type, p, mime_len);
1728
    result->mime_type[mime_len] = '\0';
1729
1730
    /* Skip ";base64," */
1731
    p = semi + 1;
1732
    if (end - p < 7 || memcmp(p, "base64,", 7) != 0) return 0;
1733
    p += 7;
1734
1735
    result->data_start = p;
1736
    result->data_len = (size_t)(end - p);
1737
    return 1;
1738
}
1739
1740
static uint8_t *tg3__decode_data_uri(tg3_arena *arena, const char *uri,
1741
                                      uint32_t uri_len, size_t *out_len,
1742
                                      char *out_mime, size_t out_mime_cap) {
1743
    tg3__data_uri_result dr;
1744
    if (!tg3__parse_data_uri(uri, uri_len, &dr)) {
1745
        *out_len = 0;
1746
        return NULL;
1747
    }
1748
1749
    if (out_mime && out_mime_cap > 0) {
1750
        size_t mlen = strlen(dr.mime_type);
1751
        if (mlen >= out_mime_cap) mlen = out_mime_cap - 1;
1752
        memcpy(out_mime, dr.mime_type, mlen);
1753
        out_mime[mlen] = '\0';
1754
    }
1755
1756
    return tg3__b64_decode(dr.data_start, dr.data_len, out_len, arena);
1757
}
1758
1759
/* ======================================================================
1760
 * Internal: Parse Context
1761
 * ====================================================================== */
1762
1763
typedef struct tg3__parse_ctx {
1764
    tg3_arena        *arena;
1765
    tg3_error_stack  *errors;
1766
    tg3_parse_options opts;
1767
    const char       *base_dir;
1768
    uint32_t          base_dir_len;
1769
1770
    /* GLB binary chunk */
1771
    const uint8_t    *bin_data;
1772
    uint64_t          bin_size;
1773
    int32_t           is_binary;
1774
} tg3__parse_ctx;
1775
1776
/* ======================================================================
1777
 * Internal: JSON Property Helpers
1778
 * ====================================================================== */
1779
1780
/* Type alias for JSON */
1781
typedef tinygltf_json tg3__json;
1782
1783
static int tg3__json_has(const tg3__json &o, const char *key) {
1784
    auto it = o.find(key);
1785
    return (it != o.end()) ? 1 : 0;
1786
}
1787
1788
static int tg3__parse_string(tg3__parse_ctx *ctx, const tg3__json &o,
1789
                              const char *key, tg3_str *out,
1790
                              int required, const char *parent) {
1791
    auto it = o.find(key);
1792
    if (it == o.end()) {
1793
        if (required) {
1794
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1795
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1796
                             "Missing required field '%s'", key);
1797
            return 0;
1798
        }
1799
        out->data = NULL; out->len = 0;
1800
        return 1;
1801
    }
1802
    if (!it->is_string()) {
1803
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1804
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1805
                         "Field '%s' must be a string", key);
1806
        return 0;
1807
    }
1808
    std::string s = it->get<std::string>();
1809
    *out = tg3__arena_str_from_std(ctx->arena, s);
1810
    return 1;
1811
}
1812
1813
static int tg3__parse_int(tg3__parse_ctx *ctx, const tg3__json &o,
1814
                           const char *key, int32_t *out,
1815
                           int required, const char *parent) {
1816
    auto it = o.find(key);
1817
    if (it == o.end()) {
1818
        if (required) {
1819
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1820
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1821
                             "Missing required field '%s'", key);
1822
            return 0;
1823
        }
1824
        return 1;
1825
    }
1826
    if (!it->is_number()) {
1827
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1828
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1829
                         "Field '%s' must be a number", key);
1830
        return 0;
1831
    }
1832
    if (it->is_number_integer()) {
1833
        int64_t v = it->get<int64_t>();
1834
        if (v < (int64_t)INT32_MIN || v > (int64_t)INT32_MAX) {
1835
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1836
                             TG3_ERR_JSON_TYPE_MISMATCH, parent,
1837
                             "Field '%s' value %" PRId64 " is out of range for int32", key, v);
1838
            return 0;
1839
        }
1840
        *out = (int32_t)v;
1841
    } else {
1842
        double d = it->get<double>();
1843
        if (d < (double)INT32_MIN || d > (double)INT32_MAX) {
1844
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1845
                             TG3_ERR_JSON_TYPE_MISMATCH, parent,
1846
                             "Field '%s' value %f is out of range for int32", key, d);
1847
            return 0;
1848
        }
1849
        *out = (int32_t)d;
1850
    }
1851
    return 1;
1852
}
1853
1854
static int tg3__parse_uint64(tg3__parse_ctx *ctx, const tg3__json &o,
1855
                              const char *key, uint64_t *out,
1856
                              int required, const char *parent) {
1857
    auto it = o.find(key);
1858
    if (it == o.end()) {
1859
        if (required) {
1860
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1861
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1862
                             "Missing required field '%s'", key);
1863
            return 0;
1864
        }
1865
        return 1;
1866
    }
1867
    if (!it->is_number()) {
1868
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1869
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1870
                         "Field '%s' must be a number", key);
1871
        return 0;
1872
    }
1873
    if (it->is_number_integer()) {
1874
        int64_t v = it->get<int64_t>();
1875
        *out = (v >= 0) ? (uint64_t)v : 0;
1876
    } else {
1877
        *out = (uint64_t)it->get<double>();
1878
    }
1879
    return 1;
1880
}
1881
1882
static int tg3__parse_double(tg3__parse_ctx *ctx, const tg3__json &o,
1883
                              const char *key, double *out,
1884
                              int required, const char *parent) {
1885
    auto it = o.find(key);
1886
    if (it == o.end()) {
1887
        if (required) {
1888
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1889
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1890
                             "Missing required field '%s'", key);
1891
            return 0;
1892
        }
1893
        return 1;
1894
    }
1895
    if (!it->is_number()) {
1896
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1897
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1898
                         "Field '%s' must be a number", key);
1899
        return 0;
1900
    }
1901
    *out = it->get<double>();
1902
    return 1;
1903
}
1904
1905
static int tg3__parse_bool(tg3__parse_ctx *ctx, const tg3__json &o,
1906
                            const char *key, int32_t *out,
1907
                            int required, const char *parent) {
1908
    auto it = o.find(key);
1909
    if (it == o.end()) {
1910
        if (required) {
1911
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1912
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1913
                             "Missing required field '%s'", key);
1914
            return 0;
1915
        }
1916
        return 1;
1917
    }
1918
    if (!it->is_boolean()) {
1919
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1920
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1921
                         "Field '%s' must be a boolean", key);
1922
        return 0;
1923
    }
1924
    *out = it->get<bool>() ? 1 : 0;
1925
    return 1;
1926
}
1927
1928
static int tg3__parse_number_array(tg3__parse_ctx *ctx, const tg3__json &o,
1929
                                    const char *key, const double **out,
1930
                                    uint32_t *out_count,
1931
                                    int required, const char *parent) {
1932
    auto it = o.find(key);
1933
    if (it == o.end()) {
1934
        if (required) {
1935
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1936
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1937
                             "Missing required field '%s'", key);
1938
            return 0;
1939
        }
1940
        *out = NULL; *out_count = 0;
1941
        return 1;
1942
    }
1943
    if (!it->is_array()) {
1944
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1945
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1946
                         "Field '%s' must be an array", key);
1947
        return 0;
1948
    }
1949
    uint32_t count = (uint32_t)it->size();
1950
    if (count == 0) { *out = NULL; *out_count = 0; return 1; }
1951
1952
    double *arr = (double *)tg3__arena_alloc(ctx->arena, count * sizeof(double));
1953
    if (!arr) {
1954
        tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
1955
                        "OOM allocating number array", parent, -1);
1956
        return 0;
1957
    }
1958
    uint32_t i = 0;
1959
    for (auto eit = it->begin(); eit != it->end(); ++eit, ++i) {
1960
        arr[i] = eit->get<double>();
1961
    }
1962
    *out = arr;
1963
    *out_count = count;
1964
    return 1;
1965
}
1966
1967
static int tg3__parse_int_array(tg3__parse_ctx *ctx, const tg3__json &o,
1968
                                 const char *key, const int32_t **out,
1969
                                 uint32_t *out_count,
1970
                                 int required, const char *parent) {
1971
    auto it = o.find(key);
1972
    if (it == o.end()) {
1973
        if (required) {
1974
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1975
                             TG3_ERR_JSON_MISSING_FIELD, parent,
1976
                             "Missing required field '%s'", key);
1977
            return 0;
1978
        }
1979
        *out = NULL; *out_count = 0;
1980
        return 1;
1981
    }
1982
    if (!it->is_array()) {
1983
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
1984
                         TG3_ERR_JSON_TYPE_MISMATCH, parent,
1985
                         "Field '%s' must be an array", key);
1986
        return 0;
1987
    }
1988
    uint32_t count = (uint32_t)it->size();
1989
    if (count == 0) { *out = NULL; *out_count = 0; return 1; }
1990
1991
    int32_t *arr = (int32_t *)tg3__arena_alloc(ctx->arena, count * sizeof(int32_t));
1992
    if (!arr) {
1993
        tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
1994
                        "OOM allocating int array", parent, -1);
1995
        return 0;
1996
    }
1997
    uint32_t i = 0;
1998
    for (auto eit = it->begin(); eit != it->end(); ++eit, ++i) {
1999
        arr[i] = eit->get<int>();
2000
    }
2001
    *out = arr;
2002
    *out_count = count;
2003
    return 1;
2004
}
2005
2006
static void tg3__parse_number_to_fixed(const tg3__json &o, const char *key,
2007
                                        double *out, uint32_t max_count) {
2008
    auto it = o.find(key);
2009
    if (it == o.end() || !it->is_array()) return;
2010
    uint32_t i = 0;
2011
    for (auto eit = it->begin(); eit != it->end() && i < max_count; ++eit, ++i) {
2012
        out[i] = eit->get<double>();
2013
    }
2014
}
2015
2016
/* Parse string array */
2017
static int tg3__parse_string_array(tg3__parse_ctx *ctx, const tg3__json &o,
2018
                                    const char *key, const tg3_str **out,
2019
                                    uint32_t *out_count,
2020
                                    int required, const char *parent) {
2021
    auto it = o.find(key);
2022
    if (it == o.end()) {
2023
        if (required) {
2024
            tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
2025
                             TG3_ERR_JSON_MISSING_FIELD, parent,
2026
                             "Missing required field '%s'", key);
2027
            return 0;
2028
        }
2029
        *out = NULL; *out_count = 0;
2030
        return 1;
2031
    }
2032
    if (!it->is_array()) return 0;
2033
    uint32_t count = (uint32_t)it->size();
2034
    if (count == 0) { *out = NULL; *out_count = 0; return 1; }
2035
2036
    tg3_str *arr = (tg3_str *)tg3__arena_alloc(ctx->arena, count * sizeof(tg3_str));
2037
    if (!arr) return 0;
2038
    uint32_t i = 0;
2039
    for (auto eit = it->begin(); eit != it->end(); ++eit, ++i) {
2040
        std::string s = eit->get<std::string>();
2041
        arr[i] = tg3__arena_str_from_std(ctx->arena, s);
2042
    }
2043
    *out = arr;
2044
    *out_count = count;
2045
    return 1;
2046
}
2047
2048
/* ======================================================================
2049
 * Internal: Value Conversion (JSON -> tg3_value)
2050
 * ====================================================================== */
2051
2052
static tg3_value tg3__json_to_value(tg3__parse_ctx *ctx, const tg3__json &j) {
2053
    tg3_value v;
2054
    memset(&v, 0, sizeof(v));
2055
2056
    if (j.is_null()) {
2057
        v.type = TG3_VALUE_NULL;
2058
    } else if (j.is_boolean()) {
2059
        v.type = TG3_VALUE_BOOL;
2060
        v.bool_val = j.get<bool>() ? 1 : 0;
2061
    } else if (j.is_number_integer()) {
2062
        v.type = TG3_VALUE_INT;
2063
        v.int_val = j.get<int64_t>();
2064
    } else if (j.is_number_float()) {
2065
        v.type = TG3_VALUE_REAL;
2066
        v.real_val = j.get<double>();
2067
    } else if (j.is_string()) {
2068
        v.type = TG3_VALUE_STRING;
2069
        std::string s = j.get<std::string>();
2070
        v.string_val = tg3__arena_str_from_std(ctx->arena, s);
2071
    } else if (j.is_array()) {
2072
        v.type = TG3_VALUE_ARRAY;
2073
        uint32_t count = (uint32_t)j.size();
2074
        if (count > 0) {
2075
            tg3_value *arr = (tg3_value *)tg3__arena_alloc(
2076
                ctx->arena, count * sizeof(tg3_value));
2077
            if (arr) {
2078
                uint32_t i = 0;
2079
                for (auto it = j.begin(); it != j.end(); ++it, ++i) {
2080
                    arr[i] = tg3__json_to_value(ctx, *it);
2081
                }
2082
                v.array_data = arr;
2083
                v.array_count = count;
2084
            }
2085
        }
2086
    } else if (j.is_object()) {
2087
        v.type = TG3_VALUE_OBJECT;
2088
        uint32_t count = (uint32_t)j.size();
2089
        if (count > 0) {
2090
            tg3_kv_pair *pairs = (tg3_kv_pair *)tg3__arena_alloc(
2091
                ctx->arena, count * sizeof(tg3_kv_pair));
2092
            if (pairs) {
2093
                uint32_t i = 0;
2094
                for (auto it = j.begin(); it != j.end(); ++it, ++i) {
2095
                    std::string k = it.key();
2096
                    pairs[i].key = tg3__arena_str_from_std(ctx->arena, k);
2097
                    pairs[i].value = tg3__json_to_value(ctx, *it);
2098
                }
2099
                v.object_data = pairs;
2100
                v.object_count = count;
2101
            }
2102
        }
2103
    }
2104
    return v;
2105
}
2106
2107
/* ======================================================================
2108
 * Internal: Parse Extras and Extensions
2109
 * ====================================================================== */
2110
2111
static void tg3__init_extras_ext(tg3_extras_ext *ee) {
2112
    memset(ee, 0, sizeof(tg3_extras_ext));
2113
}
2114
2115
static void tg3__parse_extras_and_extensions(tg3__parse_ctx *ctx,
2116
                                              const tg3__json &o,
2117
                                              tg3_extras_ext *ee) {
2118
    /* Extras */
2119
    auto extras_it = o.find("extras");
2120
    if (extras_it != o.end()) {
2121
        tg3_value *ev = (tg3_value *)tg3__arena_alloc(ctx->arena, sizeof(tg3_value));
2122
        if (ev) {
2123
            *ev = tg3__json_to_value(ctx, *extras_it);
2124
            ee->extras = ev;
2125
        }
2126
        if (ctx->opts.store_original_json) {
2127
            std::string raw = extras_it->dump();
2128
            ee->extras_json = tg3__arena_str_from_std(ctx->arena, raw);
2129
        }
2130
    }
2131
2132
    /* Extensions */
2133
    auto ext_it = o.find("extensions");
2134
    if (ext_it != o.end() && ext_it->is_object()) {
2135
        uint32_t count = (uint32_t)ext_it->size();
2136
        if (count > 0) {
2137
            tg3_extension *exts = (tg3_extension *)tg3__arena_alloc(
2138
                ctx->arena, count * sizeof(tg3_extension));
2139
            if (exts) {
2140
                uint32_t i = 0;
2141
                for (auto it = ext_it->begin(); it != ext_it->end(); ++it, ++i) {
2142
                    std::string k = it.key();
2143
                    exts[i].name = tg3__arena_str_from_std(ctx->arena, k);
2144
                    exts[i].value = tg3__json_to_value(ctx, *it);
2145
                }
2146
                ee->extensions = exts;
2147
                ee->extensions_count = count;
2148
            }
2149
        }
2150
        if (ctx->opts.store_original_json) {
2151
            std::string raw = ext_it->dump();
2152
            ee->extensions_json = tg3__arena_str_from_std(ctx->arena, raw);
2153
        }
2154
    }
2155
}
2156
2157
/* ======================================================================
2158
 * Internal: Init functions for default struct values
2159
 * ====================================================================== */
2160
2161
static void tg3__init_texture_info(tg3_texture_info *ti) {
2162
    memset(ti, 0, sizeof(tg3_texture_info));
2163
    ti->index = -1;
2164
    ti->tex_coord = 0;
2165
}
2166
2167
static void tg3__init_normal_texture_info(tg3_normal_texture_info *ti) {
2168
    memset(ti, 0, sizeof(tg3_normal_texture_info));
2169
    ti->index = -1;
2170
    ti->tex_coord = 0;
2171
    ti->scale = 1.0;
2172
}
2173
2174
static void tg3__init_occlusion_texture_info(tg3_occlusion_texture_info *ti) {
2175
    memset(ti, 0, sizeof(tg3_occlusion_texture_info));
2176
    ti->index = -1;
2177
    ti->tex_coord = 0;
2178
    ti->strength = 1.0;
2179
}
2180
2181
static void tg3__init_pbr(tg3_pbr_metallic_roughness *pbr) {
2182
    memset(pbr, 0, sizeof(tg3_pbr_metallic_roughness));
2183
    pbr->base_color_factor[0] = 1.0;
2184
    pbr->base_color_factor[1] = 1.0;
2185
    pbr->base_color_factor[2] = 1.0;
2186
    pbr->base_color_factor[3] = 1.0;
2187
    pbr->metallic_factor = 1.0;
2188
    pbr->roughness_factor = 1.0;
2189
    tg3__init_texture_info(&pbr->base_color_texture);
2190
    tg3__init_texture_info(&pbr->metallic_roughness_texture);
2191
}
2192
2193
static void tg3__init_node(tg3_node *n) {
2194
    memset(n, 0, sizeof(tg3_node));
2195
    n->camera = -1;
2196
    n->skin = -1;
2197
    n->mesh = -1;
2198
    n->light = -1;
2199
    n->emitter = -1;
2200
    n->rotation[3] = 1.0; /* w=1 identity quaternion */
2201
    n->scale[0] = 1.0;
2202
    n->scale[1] = 1.0;
2203
    n->scale[2] = 1.0;
2204
    /* Identity matrix */
2205
    n->matrix[0]  = 1.0;
2206
    n->matrix[5]  = 1.0;
2207
    n->matrix[10] = 1.0;
2208
    n->matrix[15] = 1.0;
2209
}
2210
2211
/* ======================================================================
2212
 * Internal: Entity Parse Functions
2213
 * ====================================================================== */
2214
2215
static int tg3__parse_asset(tg3__parse_ctx *ctx, const tg3__json &o,
2216
                             tg3_asset *asset) {
2217
    memset(asset, 0, sizeof(tg3_asset));
2218
    tg3__parse_string(ctx, o, "version", &asset->version, 0, "/asset");
2219
    tg3__parse_string(ctx, o, "generator", &asset->generator, 0, "/asset");
2220
    tg3__parse_string(ctx, o, "minVersion", &asset->min_version, 0, "/asset");
2221
    tg3__parse_string(ctx, o, "copyright", &asset->copyright, 0, "/asset");
2222
    tg3__parse_extras_and_extensions(ctx, o, &asset->ext);
2223
    return 1;
2224
}
2225
2226
static int tg3__parse_texture_info(tg3__parse_ctx *ctx, const tg3__json &o,
2227
                                    const char *key, tg3_texture_info *ti) {
2228
    tg3__init_texture_info(ti);
2229
    auto it = o.find(key);
2230
    if (it == o.end()) return 1; /* Optional */
2231
    if (!it->is_object()) return 0;
2232
    tg3__parse_int(ctx, *it, "index", &ti->index, 0, key);
2233
    tg3__parse_int(ctx, *it, "texCoord", &ti->tex_coord, 0, key);
2234
    tg3__parse_extras_and_extensions(ctx, *it, &ti->ext);
2235
    return 1;
2236
}
2237
2238
static int tg3__parse_normal_texture_info(tg3__parse_ctx *ctx, const tg3__json &o,
2239
                                           const char *key,
2240
                                           tg3_normal_texture_info *ti) {
2241
    tg3__init_normal_texture_info(ti);
2242
    auto it = o.find(key);
2243
    if (it == o.end()) return 1;
2244
    if (!it->is_object()) return 0;
2245
    tg3__parse_int(ctx, *it, "index", &ti->index, 0, key);
2246
    tg3__parse_int(ctx, *it, "texCoord", &ti->tex_coord, 0, key);
2247
    tg3__parse_double(ctx, *it, "scale", &ti->scale, 0, key);
2248
    tg3__parse_extras_and_extensions(ctx, *it, &ti->ext);
2249
    return 1;
2250
}
2251
2252
static int tg3__parse_occlusion_texture_info(tg3__parse_ctx *ctx,
2253
                                              const tg3__json &o,
2254
                                              const char *key,
2255
                                              tg3_occlusion_texture_info *ti) {
2256
    tg3__init_occlusion_texture_info(ti);
2257
    auto it = o.find(key);
2258
    if (it == o.end()) return 1;
2259
    if (!it->is_object()) return 0;
2260
    tg3__parse_int(ctx, *it, "index", &ti->index, 0, key);
2261
    tg3__parse_int(ctx, *it, "texCoord", &ti->tex_coord, 0, key);
2262
    tg3__parse_double(ctx, *it, "strength", &ti->strength, 0, key);
2263
    tg3__parse_extras_and_extensions(ctx, *it, &ti->ext);
2264
    return 1;
2265
}
2266
2267
static int tg3__accessor_type_from_string(const char *s, size_t len) {
2268
    if (len == 6 && memcmp(s, "SCALAR", 6) == 0) return TG3_TYPE_SCALAR;
2269
    if (len == 4 && memcmp(s, "VEC2", 4) == 0) return TG3_TYPE_VEC2;
2270
    if (len == 4 && memcmp(s, "VEC3", 4) == 0) return TG3_TYPE_VEC3;
2271
    if (len == 4 && memcmp(s, "VEC4", 4) == 0) return TG3_TYPE_VEC4;
2272
    if (len == 4 && memcmp(s, "MAT2", 4) == 0) return TG3_TYPE_MAT2;
2273
    if (len == 4 && memcmp(s, "MAT3", 4) == 0) return TG3_TYPE_MAT3;
2274
    if (len == 4 && memcmp(s, "MAT4", 4) == 0) return TG3_TYPE_MAT4;
2275
    return -1;
2276
}
2277
2278
static const char *tg3__accessor_type_to_string(int type) {
2279
    switch (type) {
2280
        case TG3_TYPE_SCALAR: return "SCALAR";
2281
        case TG3_TYPE_VEC2:   return "VEC2";
2282
        case TG3_TYPE_VEC3:   return "VEC3";
2283
        case TG3_TYPE_VEC4:   return "VEC4";
2284
        case TG3_TYPE_MAT2:   return "MAT2";
2285
        case TG3_TYPE_MAT3:   return "MAT3";
2286
        case TG3_TYPE_MAT4:   return "MAT4";
2287
        default: return "";
2288
    }
2289
}
2290
2291
static int tg3__parse_accessor_sparse(tg3__parse_ctx *ctx, const tg3__json &o,
2292
                                       tg3_accessor_sparse *sparse) {
2293
    memset(sparse, 0, sizeof(tg3_accessor_sparse));
2294
    sparse->indices.buffer_view = -1;
2295
    sparse->values.buffer_view = -1;
2296
2297
    auto it = o.find("sparse");
2298
    if (it == o.end()) return 1;
2299
    if (!it->is_object()) return 0;
2300
2301
    sparse->is_sparse = 1;
2302
    tg3__parse_int(ctx, *it, "count", &sparse->count, 1, "/sparse");
2303
2304
    auto idx_it = it->find("indices");
2305
    if (idx_it != it->end() && idx_it->is_object()) {
2306
        tg3__parse_int(ctx, *idx_it, "bufferView",
2307
                       &sparse->indices.buffer_view, 1, "/sparse/indices");
2308
        tg3__parse_int(ctx, *idx_it, "componentType",
2309
                       &sparse->indices.component_type, 1, "/sparse/indices");
2310
        uint64_t bo = 0;
2311
        tg3__parse_uint64(ctx, *idx_it, "byteOffset", &bo, 0, "/sparse/indices");
2312
        sparse->indices.byte_offset = bo;
2313
        tg3__parse_extras_and_extensions(ctx, *idx_it, &sparse->indices.ext);
2314
    }
2315
2316
    auto val_it = it->find("values");
2317
    if (val_it != it->end() && val_it->is_object()) {
2318
        tg3__parse_int(ctx, *val_it, "bufferView",
2319
                       &sparse->values.buffer_view, 1, "/sparse/values");
2320
        uint64_t bo = 0;
2321
        tg3__parse_uint64(ctx, *val_it, "byteOffset", &bo, 0, "/sparse/values");
2322
        sparse->values.byte_offset = bo;
2323
        tg3__parse_extras_and_extensions(ctx, *val_it, &sparse->values.ext);
2324
    }
2325
2326
    tg3__parse_extras_and_extensions(ctx, *it, &sparse->ext);
2327
    return 1;
2328
}
2329
2330
static int tg3__parse_accessor(tg3__parse_ctx *ctx, const tg3__json &o,
2331
                                tg3_accessor *acc) {
2332
    memset(acc, 0, sizeof(tg3_accessor));
2333
    acc->buffer_view = -1;
2334
    acc->component_type = -1;
2335
    acc->type = -1;
2336
2337
    tg3__parse_string(ctx, o, "name", &acc->name, 0, "/accessor");
2338
    tg3__parse_int(ctx, o, "bufferView", &acc->buffer_view, 0, "/accessor");
2339
2340
    uint64_t bo = 0;
2341
    tg3__parse_uint64(ctx, o, "byteOffset", &bo, 0, "/accessor");
2342
    acc->byte_offset = bo;
2343
2344
    tg3__parse_bool(ctx, o, "normalized", &acc->normalized, 0, "/accessor");
2345
    tg3__parse_int(ctx, o, "componentType", &acc->component_type, 1, "/accessor");
2346
2347
    uint64_t cnt = 0;
2348
    tg3__parse_uint64(ctx, o, "count", &cnt, 1, "/accessor");
2349
    acc->count = cnt;
2350
2351
    /* Parse type string */
2352
    tg3_str type_str = {0, 0};
2353
    tg3__parse_string(ctx, o, "type", &type_str, 1, "/accessor");
2354
    if (type_str.data) {
2355
        acc->type = tg3__accessor_type_from_string(type_str.data, type_str.len);
2356
    }
2357
2358
    tg3__parse_number_array(ctx, o, "min", &acc->min_values, &acc->min_values_count,
2359
                             0, "/accessor");
2360
    tg3__parse_number_array(ctx, o, "max", &acc->max_values, &acc->max_values_count,
2361
                             0, "/accessor");
2362
2363
    tg3__parse_accessor_sparse(ctx, o, &acc->sparse);
2364
    tg3__parse_extras_and_extensions(ctx, o, &acc->ext);
2365
    return 1;
2366
}
2367
2368
static int tg3__load_external_file(tg3__parse_ctx *ctx, uint8_t **out_data,
2369
                                    uint64_t *out_size, const char *uri,
2370
                                    uint32_t uri_len) {
2371
    if (!ctx->opts.fs.read_file) {
2372
        tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_FS_NOT_AVAILABLE,
2373
                        "No filesystem callbacks available", NULL, -1);
2374
        return 0;
2375
    }
2376
2377
    /* Build full path: base_dir + "/" + uri */
2378
    char path_buf[4096];
2379
    uint32_t path_len = 0;
2380
    if (ctx->base_dir_len > 0) {
2381
        if (ctx->base_dir_len + 1 + uri_len >= sizeof(path_buf)) return 0;
2382
        memcpy(path_buf, ctx->base_dir, ctx->base_dir_len);
2383
        path_len = ctx->base_dir_len;
2384
        if (path_buf[path_len - 1] != '/' && path_buf[path_len - 1] != '\\') {
2385
            path_buf[path_len++] = '/';
2386
        }
2387
    }
2388
    if (path_len + uri_len >= sizeof(path_buf)) return 0;
2389
    memcpy(path_buf + path_len, uri, uri_len);
2390
    path_len += uri_len;
2391
    path_buf[path_len] = '\0';
2392
2393
    int32_t ok = ctx->opts.fs.read_file(out_data, out_size, path_buf, path_len,
2394
                                         ctx->opts.fs.user_data);
2395
    if (!ok) {
2396
        tg3__error_pushf(ctx->errors, ctx->arena, TG3_SEVERITY_ERROR,
2397
                         TG3_ERR_FILE_READ, NULL, "Failed to read file: %s", path_buf);
2398
        return 0;
2399
    }
2400
    return 1;
2401
}
2402
2403
static int tg3__parse_buffer(tg3__parse_ctx *ctx, const tg3__json &o,
2404
                              tg3_buffer *buf, int32_t buf_idx) {
2405
    memset(buf, 0, sizeof(tg3_buffer));
2406
    tg3__parse_string(ctx, o, "name", &buf->name, 0, "/buffer");
2407
    tg3__parse_string(ctx, o, "uri", &buf->uri, 0, "/buffer");
2408
2409
    uint64_t byte_length = 0;
2410
    tg3__parse_uint64(ctx, o, "byteLength", &byte_length, 1, "/buffer");
2411
2412
    /* Load buffer data */
2413
    if (ctx->is_binary && buf_idx == 0 && buf->uri.len == 0) {
2414
        /* GLB: first buffer uses binary chunk */
2415
        if (!ctx->bin_data || ctx->bin_size < byte_length) {
2416
            tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
2417
                            TG3_ERR_BUFFER_SIZE_MISMATCH,
2418
                            "GLB BIN chunk missing or smaller than buffer.byteLength",
2419
                            NULL, -1);
2420
            return 0;
2421
        }
2422
        uint8_t *data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)byte_length);
2423
        if (!data) {
2424
            tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
2425
                            TG3_ERR_OUT_OF_MEMORY, "OOM for buffer data", NULL, -1);
2426
            return 0;
2427
        }
2428
        memcpy(data, ctx->bin_data, (size_t)byte_length);
2429
        buf->data.data = data;
2430
        buf->data.count = byte_length;
2431
    } else if (buf->uri.len > 0) {
2432
        if (tg3_is_data_uri(buf->uri.data, buf->uri.len)) {
2433
            /* Data URI */
2434
            size_t decoded_len = 0;
2435
            char mime[64] = {0};
2436
            uint8_t *decoded = tg3__decode_data_uri(ctx->arena, buf->uri.data,
2437
                                                     buf->uri.len, &decoded_len,
2438
                                                     mime, sizeof(mime));
2439
            if (!decoded && byte_length > 0) {
2440
                tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR,
2441
                                TG3_ERR_DATA_URI_DECODE,
2442
                                "Failed to decode buffer data URI", NULL, -1);
2443
                return 0;
2444
            }
2445
            buf->data.data = decoded;
2446
            buf->data.count = decoded_len;
2447
        } else {
2448
            /* External file */
2449
            uint8_t *file_data = NULL;
2450
            uint64_t file_size = 0;
2451
            if (tg3__load_external_file(ctx, &file_data, &file_size,
2452
                                         buf->uri.data, buf->uri.len)) {
2453
                /* Copy into arena */
2454
                uint8_t *data = (uint8_t *)tg3__arena_alloc(ctx->arena, (size_t)file_size);
2455
                if (data) {
2456
                    memcpy(data, file_data, (size_t)file_size);
2457
                    buf->data.data = data;
2458
                    buf->data.count = file_size;
2459
                }
2460
                /* Free file data via callback */
2461
                if (ctx->opts.fs.free_file) {
2462
                    ctx->opts.fs.free_file(file_data, file_size,
2463
                                           ctx->opts.fs.user_data);
2464
                }
2465
            }
2466
        }
2467
    }
2468
2469
    tg3__parse_extras_and_extensions(ctx, o, &buf->ext);
2470
    return 1;
2471
}
2472
2473
static int tg3__parse_buffer_view(tg3__parse_ctx *ctx, const tg3__json &o,
2474
                                   tg3_buffer_view *bv) {
2475
    memset(bv, 0, sizeof(tg3_buffer_view));
2476
    bv->buffer = -1;
2477
2478
    tg3__parse_string(ctx, o, "name", &bv->name, 0, "/bufferView");
2479
    tg3__parse_int(ctx, o, "buffer", &bv->buffer, 1, "/bufferView");
2480
2481
    uint64_t val = 0;
2482
    tg3__parse_uint64(ctx, o, "byteOffset", &val, 0, "/bufferView");
2483
    bv->byte_offset = val;
2484
    val = 0;
2485
    tg3__parse_uint64(ctx, o, "byteLength", &val, 1, "/bufferView");
2486
    bv->byte_length = val;
2487
2488
    int32_t stride = 0;
2489
    tg3__parse_int(ctx, o, "byteStride", &stride, 0, "/bufferView");
2490
    bv->byte_stride = (uint32_t)stride;
2491
2492
    tg3__parse_int(ctx, o, "target", &bv->target, 0, "/bufferView");
2493
    tg3__parse_extras_and_extensions(ctx, o, &bv->ext);
2494
    return 1;
2495
}
2496
2497
static int tg3__parse_image(tg3__parse_ctx *ctx, const tg3__json &o,
2498
                             tg3_image *img, int32_t /*img_idx*/) {
2499
    memset(img, 0, sizeof(tg3_image));
2500
    img->width = -1;
2501
    img->height = -1;
2502
    img->component = -1;
2503
    img->bits = -1;
2504
    img->pixel_type = -1;
2505
    img->buffer_view = -1;
2506
2507
    tg3__parse_string(ctx, o, "name", &img->name, 0, "/image");
2508
    tg3__parse_string(ctx, o, "uri", &img->uri, 0, "/image");
2509
    tg3__parse_string(ctx, o, "mimeType", &img->mime_type, 0, "/image");
2510
    tg3__parse_int(ctx, o, "bufferView", &img->buffer_view, 0, "/image");
2511
2512
    if (ctx->opts.images_as_is) {
2513
        img->as_is = 1;
2514
    }
2515
2516
    tg3__parse_extras_and_extensions(ctx, o, &img->ext);
2517
    return 1;
2518
}
2519
2520
static int tg3__parse_sampler(tg3__parse_ctx *ctx, const tg3__json &o,
2521
                               tg3_sampler *samp) {
2522
    memset(samp, 0, sizeof(tg3_sampler));
2523
    samp->min_filter = -1;
2524
    samp->mag_filter = -1;
2525
    samp->wrap_s = TG3_TEXTURE_WRAP_REPEAT;
2526
    samp->wrap_t = TG3_TEXTURE_WRAP_REPEAT;
2527
2528
    tg3__parse_string(ctx, o, "name", &samp->name, 0, "/sampler");
2529
    tg3__parse_int(ctx, o, "minFilter", &samp->min_filter, 0, "/sampler");
2530
    tg3__parse_int(ctx, o, "magFilter", &samp->mag_filter, 0, "/sampler");
2531
    tg3__parse_int(ctx, o, "wrapS", &samp->wrap_s, 0, "/sampler");
2532
    tg3__parse_int(ctx, o, "wrapT", &samp->wrap_t, 0, "/sampler");
2533
    tg3__parse_extras_and_extensions(ctx, o, &samp->ext);
2534
    return 1;
2535
}
2536
2537
static int tg3__parse_texture(tg3__parse_ctx *ctx, const tg3__json &o,
2538
                               tg3_texture *tex) {
2539
    memset(tex, 0, sizeof(tg3_texture));
2540
    tex->sampler = -1;
2541
    tex->source = -1;
2542
2543
    tg3__parse_string(ctx, o, "name", &tex->name, 0, "/texture");
2544
    tg3__parse_int(ctx, o, "sampler", &tex->sampler, 0, "/texture");
2545
    tg3__parse_int(ctx, o, "source", &tex->source, 0, "/texture");
2546
    tg3__parse_extras_and_extensions(ctx, o, &tex->ext);
2547
    return 1;
2548
}
2549
2550
static int tg3__parse_material(tg3__parse_ctx *ctx, const tg3__json &o,
2551
                                tg3_material *mat) {
2552
    memset(mat, 0, sizeof(tg3_material));
2553
    tg3__init_pbr(&mat->pbr_metallic_roughness);
2554
    tg3__init_normal_texture_info(&mat->normal_texture);
2555
    tg3__init_occlusion_texture_info(&mat->occlusion_texture);
2556
    tg3__init_texture_info(&mat->emissive_texture);
2557
    mat->alpha_cutoff = 0.5;
2558
2559
    tg3__parse_string(ctx, o, "name", &mat->name, 0, "/material");
2560
2561
    /* Emissive factor */
2562
    tg3__parse_number_to_fixed(o, "emissiveFactor", mat->emissive_factor, 3);
2563
2564
    /* Alpha mode */
2565
    tg3_str alpha_mode = {0, 0};
2566
    tg3__parse_string(ctx, o, "alphaMode", &alpha_mode, 0, "/material");
2567
    if (alpha_mode.len > 0) {
2568
        mat->alpha_mode = alpha_mode;
2569
    } else {
2570
        mat->alpha_mode = tg3__arena_str(ctx->arena, "OPAQUE", 6);
2571
    }
2572
2573
    tg3__parse_double(ctx, o, "alphaCutoff", &mat->alpha_cutoff, 0, "/material");
2574
    tg3__parse_bool(ctx, o, "doubleSided", &mat->double_sided, 0, "/material");
2575
2576
    /* PBR */
2577
    auto pbr_it = o.find("pbrMetallicRoughness");
2578
    if (pbr_it != o.end() && pbr_it->is_object()) {
2579
        tg3__parse_number_to_fixed(*pbr_it, "baseColorFactor",
2580
                                    mat->pbr_metallic_roughness.base_color_factor, 4);
2581
        tg3__parse_double(ctx, *pbr_it, "metallicFactor",
2582
                          &mat->pbr_metallic_roughness.metallic_factor, 0,
2583
                          "/material/pbrMetallicRoughness");
2584
        tg3__parse_double(ctx, *pbr_it, "roughnessFactor",
2585
                          &mat->pbr_metallic_roughness.roughness_factor, 0,
2586
                          "/material/pbrMetallicRoughness");
2587
        tg3__parse_texture_info(ctx, *pbr_it, "baseColorTexture",
2588
                                &mat->pbr_metallic_roughness.base_color_texture);
2589
        tg3__parse_texture_info(ctx, *pbr_it, "metallicRoughnessTexture",
2590
                                &mat->pbr_metallic_roughness.metallic_roughness_texture);
2591
        tg3__parse_extras_and_extensions(ctx, *pbr_it,
2592
                                          &mat->pbr_metallic_roughness.ext);
2593
    }
2594
2595
    tg3__parse_normal_texture_info(ctx, o, "normalTexture", &mat->normal_texture);
2596
    tg3__parse_occlusion_texture_info(ctx, o, "occlusionTexture",
2597
                                       &mat->occlusion_texture);
2598
    tg3__parse_texture_info(ctx, o, "emissiveTexture", &mat->emissive_texture);
2599
2600
    /* MSFT_lod */
2601
    auto ext_it = o.find("extensions");
2602
    if (ext_it != o.end() && ext_it->is_object()) {
2603
        auto lod_it = ext_it->find("MSFT_lod");
2604
        if (lod_it != ext_it->end() && lod_it->is_object()) {
2605
            tg3__parse_int_array(ctx, *lod_it, "ids",
2606
                                 &mat->lods, &mat->lods_count, 0,
2607
                                 "/material/extensions/MSFT_lod");
2608
        }
2609
    }
2610
2611
    tg3__parse_extras_and_extensions(ctx, o, &mat->ext);
2612
    return 1;
2613
}
2614
2615
static int tg3__parse_primitive(tg3__parse_ctx *ctx, const tg3__json &o,
2616
                                 tg3_primitive *prim) {
2617
    memset(prim, 0, sizeof(tg3_primitive));
2618
    prim->material = -1;
2619
    prim->indices = -1;
2620
    prim->mode = TG3_MODE_TRIANGLES;
2621
2622
    tg3__parse_int(ctx, o, "material", &prim->material, 0, "/primitive");
2623
    tg3__parse_int(ctx, o, "indices", &prim->indices, 0, "/primitive");
2624
    tg3__parse_int(ctx, o, "mode", &prim->mode, 0, "/primitive");
2625
2626
    /* Attributes */
2627
    auto attr_it = o.find("attributes");
2628
    if (attr_it != o.end() && attr_it->is_object()) {
2629
        uint32_t count = (uint32_t)attr_it->size();
2630
        if (count > 0) {
2631
            tg3_str_int_pair *attrs = (tg3_str_int_pair *)tg3__arena_alloc(
2632
                ctx->arena, count * sizeof(tg3_str_int_pair));
2633
            if (attrs) {
2634
                uint32_t i = 0;
2635
                for (auto it = attr_it->begin(); it != attr_it->end(); ++it, ++i) {
2636
                    std::string k = it.key();
2637
                    attrs[i].key = tg3__arena_str_from_std(ctx->arena, k);
2638
                    attrs[i].value = it->get<int>();
2639
                }
2640
                prim->attributes = attrs;
2641
                prim->attributes_count = count;
2642
            }
2643
        }
2644
    }
2645
2646
    /* Morph targets */
2647
    auto targets_it = o.find("targets");
2648
    if (targets_it != o.end() && targets_it->is_array()) {
2649
        uint32_t tcount = (uint32_t)targets_it->size();
2650
        if (tcount > 0) {
2651
            const tg3_str_int_pair **target_arrays =
2652
                (const tg3_str_int_pair **)tg3__arena_alloc(
2653
                    ctx->arena, tcount * sizeof(tg3_str_int_pair *));
2654
            uint32_t *target_counts = (uint32_t *)tg3__arena_alloc(
2655
                ctx->arena, tcount * sizeof(uint32_t));
2656
            if (target_arrays && target_counts) {
2657
                uint32_t ti = 0;
2658
                for (auto tit = targets_it->begin(); tit != targets_it->end(); ++tit, ++ti) {
2659
                    if (!tit->is_object()) {
2660
                        target_arrays[ti] = NULL;
2661
                        target_counts[ti] = 0;
2662
                        continue;
2663
                    }
2664
                    uint32_t acount = (uint32_t)tit->size();
2665
                    tg3_str_int_pair *tattrs = (tg3_str_int_pair *)tg3__arena_alloc(
2666
                        ctx->arena, acount * sizeof(tg3_str_int_pair));
2667
                    if (tattrs) {
2668
                        uint32_t ai = 0;
2669
                        for (auto ait = tit->begin(); ait != tit->end(); ++ait, ++ai) {
2670
                            std::string k = ait.key();
2671
                            tattrs[ai].key = tg3__arena_str_from_std(ctx->arena, k);
2672
                            tattrs[ai].value = ait->get<int>();
2673
                        }
2674
                    }
2675
                    target_arrays[ti] = tattrs;
2676
                    target_counts[ti] = acount;
2677
                }
2678
                prim->targets = target_arrays;
2679
                prim->target_attribute_counts = target_counts;
2680
                prim->targets_count = tcount;
2681
            }
2682
        }
2683
    }
2684
2685
    tg3__parse_extras_and_extensions(ctx, o, &prim->ext);
2686
    return 1;
2687
}
2688
2689
static int tg3__parse_mesh(tg3__parse_ctx *ctx, const tg3__json &o,
2690
                            tg3_mesh *mesh) {
2691
    memset(mesh, 0, sizeof(tg3_mesh));
2692
    tg3__parse_string(ctx, o, "name", &mesh->name, 0, "/mesh");
2693
2694
    /* Primitives */
2695
    auto prim_it = o.find("primitives");
2696
    if (prim_it != o.end() && prim_it->is_array()) {
2697
        uint32_t count = (uint32_t)prim_it->size();
2698
        if (count > 0) {
2699
            tg3_primitive *prims = (tg3_primitive *)tg3__arena_alloc(
2700
                ctx->arena, count * sizeof(tg3_primitive));
2701
            if (prims) {
2702
                uint32_t i = 0;
2703
                for (auto it = prim_it->begin(); it != prim_it->end(); ++it, ++i) {
2704
                    tg3__parse_primitive(ctx, *it, &prims[i]);
2705
                }
2706
                mesh->primitives = prims;
2707
                mesh->primitives_count = count;
2708
            }
2709
        }
2710
    }
2711
2712
    tg3__parse_number_array(ctx, o, "weights", &mesh->weights,
2713
                             &mesh->weights_count, 0, "/mesh");
2714
    tg3__parse_extras_and_extensions(ctx, o, &mesh->ext);
2715
    return 1;
2716
}
2717
2718
static int tg3__parse_node(tg3__parse_ctx *ctx, const tg3__json &o,
2719
                            tg3_node *node) {
2720
    tg3__init_node(node);
2721
2722
    tg3__parse_string(ctx, o, "name", &node->name, 0, "/node");
2723
    tg3__parse_int(ctx, o, "camera", &node->camera, 0, "/node");
2724
    tg3__parse_int(ctx, o, "skin", &node->skin, 0, "/node");
2725
    tg3__parse_int(ctx, o, "mesh", &node->mesh, 0, "/node");
2726
2727
    tg3__parse_int_array(ctx, o, "children", &node->children,
2728
                          &node->children_count, 0, "/node");
2729
2730
    /* TRS */
2731
    if (tg3__json_has(o, "matrix")) {
2732
        tg3__parse_number_to_fixed(o, "matrix", node->matrix, 16);
2733
        node->has_matrix = 1;
2734
    }
2735
    if (tg3__json_has(o, "translation")) {
2736
        tg3__parse_number_to_fixed(o, "translation", node->translation, 3);
2737
    }
2738
    if (tg3__json_has(o, "rotation")) {
2739
        tg3__parse_number_to_fixed(o, "rotation", node->rotation, 4);
2740
    }
2741
    if (tg3__json_has(o, "scale")) {
2742
        tg3__parse_number_to_fixed(o, "scale", node->scale, 3);
2743
    }
2744
2745
    tg3__parse_number_array(ctx, o, "weights", &node->weights,
2746
                             &node->weights_count, 0, "/node");
2747
2748
    /* Extensions: KHR_lights_punctual, KHR_audio, MSFT_lod */
2749
    auto ext_it = o.find("extensions");
2750
    if (ext_it != o.end() && ext_it->is_object()) {
2751
        auto khr_lights = ext_it->find("KHR_lights_punctual");
2752
        if (khr_lights != ext_it->end() && khr_lights->is_object()) {
2753
            tg3__parse_int(ctx, *khr_lights, "light", &node->light, 0,
2754
                          "/node/extensions/KHR_lights_punctual");
2755
        }
2756
        auto khr_audio = ext_it->find("KHR_audio");
2757
        if (khr_audio != ext_it->end() && khr_audio->is_object()) {
2758
            tg3__parse_int(ctx, *khr_audio, "emitter", &node->emitter, 0,
2759
                          "/node/extensions/KHR_audio");
2760
        }
2761
        auto msft_lod = ext_it->find("MSFT_lod");
2762
        if (msft_lod != ext_it->end() && msft_lod->is_object()) {
2763
            tg3__parse_int_array(ctx, *msft_lod, "ids",
2764
                                 &node->lods, &node->lods_count, 0,
2765
                                 "/node/extensions/MSFT_lod");
2766
        }
2767
    }
2768
2769
    tg3__parse_extras_and_extensions(ctx, o, &node->ext);
2770
    return 1;
2771
}
2772
2773
static int tg3__parse_skin(tg3__parse_ctx *ctx, const tg3__json &o,
2774
                            tg3_skin *skin) {
2775
    memset(skin, 0, sizeof(tg3_skin));
2776
    skin->inverse_bind_matrices = -1;
2777
    skin->skeleton = -1;
2778
2779
    tg3__parse_string(ctx, o, "name", &skin->name, 0, "/skin");
2780
    tg3__parse_int(ctx, o, "inverseBindMatrices", &skin->inverse_bind_matrices,
2781
                   0, "/skin");
2782
    tg3__parse_int(ctx, o, "skeleton", &skin->skeleton, 0, "/skin");
2783
    tg3__parse_int_array(ctx, o, "joints", &skin->joints,
2784
                          &skin->joints_count, 1, "/skin");
2785
    tg3__parse_extras_and_extensions(ctx, o, &skin->ext);
2786
    return 1;
2787
}
2788
2789
static int tg3__parse_animation(tg3__parse_ctx *ctx, const tg3__json &o,
2790
                                 tg3_animation *anim) {
2791
    memset(anim, 0, sizeof(tg3_animation));
2792
    tg3__parse_string(ctx, o, "name", &anim->name, 0, "/animation");
2793
2794
    /* Channels */
2795
    auto ch_it = o.find("channels");
2796
    if (ch_it != o.end() && ch_it->is_array()) {
2797
        uint32_t count = (uint32_t)ch_it->size();
2798
        if (count > 0) {
2799
            tg3_animation_channel *channels = (tg3_animation_channel *)tg3__arena_alloc(
2800
                ctx->arena, count * sizeof(tg3_animation_channel));
2801
            if (channels) {
2802
                uint32_t i = 0;
2803
                for (auto it = ch_it->begin(); it != ch_it->end(); ++it, ++i) {
2804
                    memset(&channels[i], 0, sizeof(tg3_animation_channel));
2805
                    channels[i].sampler = -1;
2806
                    channels[i].target.node = -1;
2807
2808
                    tg3__parse_int(ctx, *it, "sampler", &channels[i].sampler,
2809
                                   1, "/animation/channel");
2810
2811
                    auto tgt_it = it->find("target");
2812
                    if (tgt_it != it->end() && tgt_it->is_object()) {
2813
                        tg3__parse_int(ctx, *tgt_it, "node",
2814
                                       &channels[i].target.node, 0,
2815
                                       "/animation/channel/target");
2816
                        tg3__parse_string(ctx, *tgt_it, "path",
2817
                                          &channels[i].target.path, 1,
2818
                                          "/animation/channel/target");
2819
                        tg3__parse_extras_and_extensions(ctx, *tgt_it,
2820
                                                          &channels[i].target.ext);
2821
                    }
2822
                    tg3__parse_extras_and_extensions(ctx, *it, &channels[i].ext);
2823
                }
2824
                anim->channels = channels;
2825
                anim->channels_count = count;
2826
            }
2827
        }
2828
    }
2829
2830
    /* Samplers */
2831
    auto samp_it = o.find("samplers");
2832
    if (samp_it != o.end() && samp_it->is_array()) {
2833
        uint32_t count = (uint32_t)samp_it->size();
2834
        if (count > 0) {
2835
            tg3_animation_sampler *samplers = (tg3_animation_sampler *)tg3__arena_alloc(
2836
                ctx->arena, count * sizeof(tg3_animation_sampler));
2837
            if (samplers) {
2838
                uint32_t i = 0;
2839
                for (auto it = samp_it->begin(); it != samp_it->end(); ++it, ++i) {
2840
                    memset(&samplers[i], 0, sizeof(tg3_animation_sampler));
2841
                    samplers[i].input = -1;
2842
                    samplers[i].output = -1;
2843
2844
                    tg3__parse_int(ctx, *it, "input", &samplers[i].input,
2845
                                   1, "/animation/sampler");
2846
                    tg3__parse_int(ctx, *it, "output", &samplers[i].output,
2847
                                   1, "/animation/sampler");
2848
2849
                    tg3_str interp = {0, 0};
2850
                    tg3__parse_string(ctx, *it, "interpolation", &interp,
2851
                                      0, "/animation/sampler");
2852
                    if (interp.len > 0) {
2853
                        samplers[i].interpolation = interp;
2854
                    } else {
2855
                        samplers[i].interpolation = tg3__arena_str(ctx->arena,
2856
                                                                     "LINEAR", 6);
2857
                    }
2858
                    tg3__parse_extras_and_extensions(ctx, *it, &samplers[i].ext);
2859
                }
2860
                anim->samplers = samplers;
2861
                anim->samplers_count = count;
2862
            }
2863
        }
2864
    }
2865
2866
    tg3__parse_extras_and_extensions(ctx, o, &anim->ext);
2867
    return 1;
2868
}
2869
2870
static int tg3__parse_camera(tg3__parse_ctx *ctx, const tg3__json &o,
2871
                              tg3_camera *cam) {
2872
    memset(cam, 0, sizeof(tg3_camera));
2873
    tg3__parse_string(ctx, o, "name", &cam->name, 0, "/camera");
2874
    tg3__parse_string(ctx, o, "type", &cam->type, 1, "/camera");
2875
2876
    if (cam->type.data && tg3_str_equals_cstr(cam->type, "perspective")) {
2877
        auto p_it = o.find("perspective");
2878
        if (p_it != o.end() && p_it->is_object()) {
2879
            tg3__parse_double(ctx, *p_it, "aspectRatio",
2880
                              &cam->perspective.aspect_ratio, 0, "/camera/perspective");
2881
            tg3__parse_double(ctx, *p_it, "yfov",
2882
                              &cam->perspective.yfov, 1, "/camera/perspective");
2883
            tg3__parse_double(ctx, *p_it, "zfar",
2884
                              &cam->perspective.zfar, 0, "/camera/perspective");
2885
            tg3__parse_double(ctx, *p_it, "znear",
2886
                              &cam->perspective.znear, 1, "/camera/perspective");
2887
            tg3__parse_extras_and_extensions(ctx, *p_it, &cam->perspective.ext);
2888
        }
2889
    } else if (cam->type.data && tg3_str_equals_cstr(cam->type, "orthographic")) {
2890
        auto o_it = o.find("orthographic");
2891
        if (o_it != o.end() && o_it->is_object()) {
2892
            tg3__parse_double(ctx, *o_it, "xmag",
2893
                              &cam->orthographic.xmag, 1, "/camera/orthographic");
2894
            tg3__parse_double(ctx, *o_it, "ymag",
2895
                              &cam->orthographic.ymag, 1, "/camera/orthographic");
2896
            tg3__parse_double(ctx, *o_it, "zfar",
2897
                              &cam->orthographic.zfar, 1, "/camera/orthographic");
2898
            tg3__parse_double(ctx, *o_it, "znear",
2899
                              &cam->orthographic.znear, 1, "/camera/orthographic");
2900
            tg3__parse_extras_and_extensions(ctx, *o_it, &cam->orthographic.ext);
2901
        }
2902
    }
2903
2904
    tg3__parse_extras_and_extensions(ctx, o, &cam->ext);
2905
    return 1;
2906
}
2907
2908
static int tg3__parse_scene(tg3__parse_ctx *ctx, const tg3__json &o,
2909
                             tg3_scene *scene) {
2910
    memset(scene, 0, sizeof(tg3_scene));
2911
    tg3__parse_string(ctx, o, "name", &scene->name, 0, "/scene");
2912
    tg3__parse_int_array(ctx, o, "nodes", &scene->nodes,
2913
                          &scene->nodes_count, 0, "/scene");
2914
2915
    /* KHR_audio emitters */
2916
    auto ext_it = o.find("extensions");
2917
    if (ext_it != o.end() && ext_it->is_object()) {
2918
        auto audio_it = ext_it->find("KHR_audio");
2919
        if (audio_it != ext_it->end() && audio_it->is_object()) {
2920
            tg3__parse_int_array(ctx, *audio_it, "emitters",
2921
                                 &scene->audio_emitters,
2922
                                 &scene->audio_emitters_count, 0,
2923
                                 "/scene/extensions/KHR_audio");
2924
        }
2925
    }
2926
2927
    tg3__parse_extras_and_extensions(ctx, o, &scene->ext);
2928
    return 1;
2929
}
2930
2931
static int tg3__parse_light(tg3__parse_ctx *ctx, const tg3__json &o,
2932
                             tg3_light *light) {
2933
    memset(light, 0, sizeof(tg3_light));
2934
    light->color[0] = 1.0;
2935
    light->color[1] = 1.0;
2936
    light->color[2] = 1.0;
2937
    light->intensity = 1.0;
2938
    light->spot.outer_cone_angle = 0.7853981634;
2939
2940
    tg3__parse_string(ctx, o, "name", &light->name, 0, "/light");
2941
    tg3__parse_string(ctx, o, "type", &light->type, 1, "/light");
2942
    tg3__parse_double(ctx, o, "intensity", &light->intensity, 0, "/light");
2943
    tg3__parse_double(ctx, o, "range", &light->range, 0, "/light");
2944
    tg3__parse_number_to_fixed(o, "color", light->color, 3);
2945
2946
    auto spot_it = o.find("spot");
2947
    if (spot_it != o.end() && spot_it->is_object()) {
2948
        tg3__parse_double(ctx, *spot_it, "innerConeAngle",
2949
                          &light->spot.inner_cone_angle, 0, "/light/spot");
2950
        tg3__parse_double(ctx, *spot_it, "outerConeAngle",
2951
                          &light->spot.outer_cone_angle, 0, "/light/spot");
2952
        tg3__parse_extras_and_extensions(ctx, *spot_it, &light->spot.ext);
2953
    }
2954
2955
    tg3__parse_extras_and_extensions(ctx, o, &light->ext);
2956
    return 1;
2957
}
2958
2959
static int tg3__parse_audio_source(tg3__parse_ctx *ctx, const tg3__json &o,
2960
                                    tg3_audio_source *src) {
2961
    memset(src, 0, sizeof(tg3_audio_source));
2962
    src->buffer_view = -1;
2963
2964
    tg3__parse_string(ctx, o, "name", &src->name, 0, "/audioSource");
2965
    tg3__parse_string(ctx, o, "uri", &src->uri, 0, "/audioSource");
2966
    tg3__parse_int(ctx, o, "bufferView", &src->buffer_view, 0, "/audioSource");
2967
    tg3__parse_string(ctx, o, "mimeType", &src->mime_type, 0, "/audioSource");
2968
    tg3__parse_extras_and_extensions(ctx, o, &src->ext);
2969
    return 1;
2970
}
2971
2972
static int tg3__parse_audio_emitter(tg3__parse_ctx *ctx, const tg3__json &o,
2973
                                     tg3_audio_emitter *emitter) {
2974
    memset(emitter, 0, sizeof(tg3_audio_emitter));
2975
    emitter->gain = 1.0;
2976
    emitter->source = -1;
2977
    emitter->positional.cone_inner_angle = 6.283185307179586;
2978
    emitter->positional.cone_outer_angle = 6.283185307179586;
2979
    emitter->positional.max_distance = 100.0;
2980
    emitter->positional.ref_distance = 1.0;
2981
    emitter->positional.rolloff_factor = 1.0;
2982
2983
    tg3__parse_string(ctx, o, "name", &emitter->name, 0, "/audioEmitter");
2984
    tg3__parse_double(ctx, o, "gain", &emitter->gain, 0, "/audioEmitter");
2985
    tg3__parse_bool(ctx, o, "loop", &emitter->loop, 0, "/audioEmitter");
2986
    tg3__parse_bool(ctx, o, "playing", &emitter->playing, 0, "/audioEmitter");
2987
    tg3__parse_string(ctx, o, "type", &emitter->type, 0, "/audioEmitter");
2988
    tg3__parse_string(ctx, o, "distanceModel", &emitter->distance_model,
2989
                       0, "/audioEmitter");
2990
    tg3__parse_int(ctx, o, "source", &emitter->source, 0, "/audioEmitter");
2991
2992
    auto pos_it = o.find("positional");
2993
    if (pos_it != o.end() && pos_it->is_object()) {
2994
        tg3__parse_double(ctx, *pos_it, "coneInnerAngle",
2995
                          &emitter->positional.cone_inner_angle, 0, "/positional");
2996
        tg3__parse_double(ctx, *pos_it, "coneOuterAngle",
2997
                          &emitter->positional.cone_outer_angle, 0, "/positional");
2998
        tg3__parse_double(ctx, *pos_it, "coneOuterGain",
2999
                          &emitter->positional.cone_outer_gain, 0, "/positional");
3000
        tg3__parse_double(ctx, *pos_it, "maxDistance",
3001
                          &emitter->positional.max_distance, 0, "/positional");
3002
        tg3__parse_double(ctx, *pos_it, "refDistance",
3003
                          &emitter->positional.ref_distance, 0, "/positional");
3004
        tg3__parse_double(ctx, *pos_it, "rolloffFactor",
3005
                          &emitter->positional.rolloff_factor, 0, "/positional");
3006
        tg3__parse_extras_and_extensions(ctx, *pos_it, &emitter->positional.ext);
3007
    }
3008
3009
    tg3__parse_extras_and_extensions(ctx, o, &emitter->ext);
3010
    return 1;
3011
}
3012
3013
/* ======================================================================
3014
 * Internal: Portable variadic-comma helper
3015
 *
3016
 * TG3__COMMA_VA_ARGS(__VA_ARGS__) expands to  , __VA_ARGS__  when the
3017
 * argument list is non-empty, and to nothing when it is empty.
3018
 *
3019
 * - C++20 and later: uses the standard __VA_OPT__(,) token.
3020
 * - C++17 and earlier: falls back to the widely-supported GNU/MSVC
3021
 *   ##__VA_ARGS__ extension.
3022
 * ====================================================================== */
3023
#if __cplusplus >= 202002L
3024
#  define TG3__COMMA_VA_ARGS(...) __VA_OPT__(,) __VA_ARGS__
3025
#else
3026
#  define TG3__COMMA_VA_ARGS(...) , ##__VA_ARGS__
3027
#endif
3028
3029
/* ======================================================================
3030
 * Internal: Array Parse Macro
3031
 * ====================================================================== */
3032
3033
#define TG3__PARSE_ARRAY(ctx, json_doc, json_key, Type, model_field, count_field, parse_fn, ...) \
3034
    do { \
3035
        auto _arr_it = (json_doc).find(json_key); \
3036
        if (_arr_it != (json_doc).end() && _arr_it->is_array()) { \
3037
            uint32_t _count = (uint32_t)_arr_it->size(); \
3038
            if (_count > 0) { \
3039
                Type *_items = (Type *)tg3__arena_alloc((ctx)->arena, \
3040
                    _count * sizeof(Type)); \
3041
                if (_items) { \
3042
                    uint32_t _i = 0; \
3043
                    for (auto _it = _arr_it->begin(); _it != _arr_it->end(); ++_it, ++_i) { \
3044
                        parse_fn((ctx), *_it, &_items[_i] TG3__COMMA_VA_ARGS(__VA_ARGS__)); \
3045
                    } \
3046
                    (model_field) = _items; \
3047
                    (count_field) = _count; \
3048
                } \
3049
            } \
3050
        } \
3051
    } while (0)
3052
3053
/* Variant without extra args and with index param */
3054
#define TG3__PARSE_ARRAY_IDX(ctx, json_doc, json_key, Type, model_field, count_field, parse_fn) \
3055
    do { \
3056
        auto _arr_it = (json_doc).find(json_key); \
3057
        if (_arr_it != (json_doc).end() && _arr_it->is_array()) { \
3058
            uint32_t _count = (uint32_t)_arr_it->size(); \
3059
            if (_count > 0) { \
3060
                Type *_items = (Type *)tg3__arena_alloc((ctx)->arena, \
3061
                    _count * sizeof(Type)); \
3062
                if (_items) { \
3063
                    uint32_t _i = 0; \
3064
                    for (auto _it = _arr_it->begin(); _it != _arr_it->end(); ++_it, ++_i) { \
3065
                        if (!_it->is_object()) { \
3066
                            tg3__error_pushf((ctx)->errors, (ctx)->arena, \
3067
                                TG3_SEVERITY_ERROR, TG3_ERR_JSON_TYPE_MISMATCH, \
3068
                                json_key, "Element %u must be an object", _i); \
3069
                            continue; \
3070
                        } \
3071
                        parse_fn((ctx), *_it, &_items[_i], (int32_t)_i); \
3072
                    } \
3073
                    (model_field) = _items; \
3074
                    (count_field) = _count; \
3075
                } \
3076
            } \
3077
        } \
3078
    } while (0)
3079
3080
/* Simpler variant for entities without index */
3081
#define TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, json_key, Type, model_field, count_field, parse_fn) \
3082
    do { \
3083
        auto _arr_it = (json_doc).find(json_key); \
3084
        if (_arr_it != (json_doc).end() && _arr_it->is_array()) { \
3085
            uint32_t _count = (uint32_t)_arr_it->size(); \
3086
            if (_count > 0) { \
3087
                Type *_items = (Type *)tg3__arena_alloc((ctx)->arena, \
3088
                    _count * sizeof(Type)); \
3089
                if (_items) { \
3090
                    uint32_t _i = 0; \
3091
                    for (auto _it = _arr_it->begin(); _it != _arr_it->end(); ++_it, ++_i) { \
3092
                        if (!_it->is_object()) { \
3093
                            tg3__error_pushf((ctx)->errors, (ctx)->arena, \
3094
                                TG3_SEVERITY_ERROR, TG3_ERR_JSON_TYPE_MISMATCH, \
3095
                                json_key, "Element %u must be an object", _i); \
3096
                            continue; \
3097
                        } \
3098
                        parse_fn((ctx), *_it, &_items[_i]); \
3099
                    } \
3100
                    (model_field) = _items; \
3101
                    (count_field) = _count; \
3102
                } \
3103
            } \
3104
        } \
3105
    } while (0)
3106
3107
/* ======================================================================
3108
 * Internal: Main Parse Orchestrator
3109
 * ====================================================================== */
3110
3111
static tg3_error_code tg3__parse_from_json(tg3__parse_ctx *ctx,
3112
                                            const tg3__json &json_doc,
3113
                                            tg3_model *model) {
3114
    /* Asset */
3115
    auto asset_it = json_doc.find("asset");
3116
    if (asset_it != json_doc.end() && asset_it->is_object()) {
3117
        tg3__parse_asset(ctx, *asset_it, &model->asset);
3118
    } else if (ctx->opts.required_sections & TG3_REQUIRE_VERSION) {
3119
        tg3__error_push(ctx->errors, TG3_SEVERITY_ERROR, TG3_ERR_MISSING_REQUIRED,
3120
                        "Missing required 'asset' property", "/", -1);
3121
        return TG3_ERR_MISSING_REQUIRED;
3122
    }
3123
3124
    /* Extensions used/required */
3125
    tg3__parse_string_array(ctx, json_doc, "extensionsUsed",
3126
                             &model->extensions_used,
3127
                             &model->extensions_used_count, 0, "/");
3128
    tg3__parse_string_array(ctx, json_doc, "extensionsRequired",
3129
                             &model->extensions_required,
3130
                             &model->extensions_required_count, 0, "/");
3131
3132
    /* Default scene */
3133
    model->default_scene = -1;
3134
    tg3__parse_int(ctx, json_doc, "scene", &model->default_scene, 0, "/");
3135
3136
    /* Streaming callback helper macro */
3137
    #define TG3__STREAM_CB(type_name, cb_name, model_arr, model_cnt) \
3138
        if (ctx->opts.stream && ctx->opts.stream->cb_name) { \
3139
            for (uint32_t _si = 0; _si < model_cnt; ++_si) { \
3140
                tg3_stream_action _sa = ctx->opts.stream->cb_name( \
3141
                    &model_arr[_si], (int32_t)_si, ctx->opts.stream->user_data); \
3142
                if (_sa == TG3_STREAM_ABORT) return TG3_ERR_STREAM_ABORTED; \
3143
            } \
3144
        }
3145
3146
    /* Parse all entity arrays */
3147
    TG3__PARSE_ARRAY_IDX(ctx, json_doc, "buffers", tg3_buffer,
3148
                         model->buffers, model->buffers_count, tg3__parse_buffer);
3149
    TG3__STREAM_CB(buffer, on_buffer, model->buffers, model->buffers_count);
3150
3151
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "bufferViews", tg3_buffer_view,
3152
                            model->buffer_views, model->buffer_views_count,
3153
                            tg3__parse_buffer_view);
3154
    TG3__STREAM_CB(buffer_view, on_buffer_view, model->buffer_views,
3155
                   model->buffer_views_count);
3156
3157
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "accessors", tg3_accessor,
3158
                            model->accessors, model->accessors_count,
3159
                            tg3__parse_accessor);
3160
    TG3__STREAM_CB(accessor, on_accessor, model->accessors, model->accessors_count);
3161
3162
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "meshes", tg3_mesh,
3163
                            model->meshes, model->meshes_count, tg3__parse_mesh);
3164
    TG3__STREAM_CB(mesh, on_mesh, model->meshes, model->meshes_count);
3165
3166
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "nodes", tg3_node,
3167
                            model->nodes, model->nodes_count, tg3__parse_node);
3168
    TG3__STREAM_CB(node, on_node, model->nodes, model->nodes_count);
3169
3170
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "materials", tg3_material,
3171
                            model->materials, model->materials_count,
3172
                            tg3__parse_material);
3173
    TG3__STREAM_CB(material, on_material, model->materials, model->materials_count);
3174
3175
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "textures", tg3_texture,
3176
                            model->textures, model->textures_count,
3177
                            tg3__parse_texture);
3178
    TG3__STREAM_CB(texture, on_texture, model->textures, model->textures_count);
3179
3180
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "samplers", tg3_sampler,
3181
                            model->samplers, model->samplers_count,
3182
                            tg3__parse_sampler);
3183
    TG3__STREAM_CB(sampler, on_sampler, model->samplers, model->samplers_count);
3184
3185
    TG3__PARSE_ARRAY_IDX(ctx, json_doc, "images", tg3_image,
3186
                         model->images, model->images_count, tg3__parse_image);
3187
    TG3__STREAM_CB(image, on_image, model->images, model->images_count);
3188
3189
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "skins", tg3_skin,
3190
                            model->skins, model->skins_count, tg3__parse_skin);
3191
    TG3__STREAM_CB(skin, on_skin, model->skins, model->skins_count);
3192
3193
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "animations", tg3_animation,
3194
                            model->animations, model->animations_count,
3195
                            tg3__parse_animation);
3196
    TG3__STREAM_CB(animation, on_animation, model->animations,
3197
                   model->animations_count);
3198
3199
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "cameras", tg3_camera,
3200
                            model->cameras, model->cameras_count,
3201
                            tg3__parse_camera);
3202
    TG3__STREAM_CB(camera, on_camera, model->cameras, model->cameras_count);
3203
3204
    TG3__PARSE_ARRAY_SIMPLE(ctx, json_doc, "scenes", tg3_scene,
3205
                            model->scenes, model->scenes_count,
3206
                            tg3__parse_scene);
3207
    TG3__STREAM_CB(scene, on_scene, model->scenes, model->scenes_count);
3208
3209
    /* KHR_lights_punctual */
3210
    auto ext_it = json_doc.find("extensions");
3211
    if (ext_it != json_doc.end() && ext_it->is_object()) {
3212
        auto lights_ext = ext_it->find("KHR_lights_punctual");
3213
        if (lights_ext != ext_it->end() && lights_ext->is_object()) {
3214
            TG3__PARSE_ARRAY_SIMPLE(ctx, *lights_ext, "lights", tg3_light,
3215
                                    model->lights, model->lights_count,
3216
                                    tg3__parse_light);
3217
            TG3__STREAM_CB(light, on_light, model->lights, model->lights_count);
3218
        }
3219
3220
        /* KHR_audio */
3221
        auto audio_ext = ext_it->find("KHR_audio");
3222
        if (audio_ext != ext_it->end() && audio_ext->is_object()) {
3223
            TG3__PARSE_ARRAY_SIMPLE(ctx, *audio_ext, "sources", tg3_audio_source,
3224
                                    model->audio_sources, model->audio_sources_count,
3225
                                    tg3__parse_audio_source);
3226
            TG3__PARSE_ARRAY_SIMPLE(ctx, *audio_ext, "emitters", tg3_audio_emitter,
3227
                                    model->audio_emitters, model->audio_emitters_count,
3228
                                    tg3__parse_audio_emitter);
3229
        }
3230
    }
3231
3232
    /* Root extras/extensions */
3233
    tg3__parse_extras_and_extensions(ctx, json_doc, &model->ext);
3234
3235
    #undef TG3__STREAM_CB
3236
3237
    return ctx->errors->has_error ? TG3_ERR_JSON_PARSE : TG3_OK;
3238
}
3239
3240
/* ======================================================================
3241
 * Internal: GLB Parsing
3242
 * ====================================================================== */
3243
3244
static tg3_error_code tg3__parse_glb_header(const uint8_t *data, uint64_t size,
3245
                                             const uint8_t **json_out,
3246
                                             uint64_t *json_size_out,
3247
                                             const uint8_t **bin_out,
3248
                                             uint64_t *bin_size_out,
3249
                                             tg3_error_stack *errors) {
3250
    *json_out = NULL; *json_size_out = 0;
3251
    *bin_out = NULL; *bin_size_out = 0;
3252
3253
    if (size < 12) {
3254
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_INVALID_HEADER,
3255
                        "GLB data too small for header", NULL, -1);
3256
        return TG3_ERR_GLB_INVALID_HEADER;
3257
    }
3258
3259
    /* Check magic: 'glTF' */
3260
    if (data[0] != 'g' || data[1] != 'l' || data[2] != 'T' || data[3] != 'F') {
3261
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_INVALID_MAGIC,
3262
                        "Invalid GLB magic bytes", NULL, -1);
3263
        return TG3_ERR_GLB_INVALID_MAGIC;
3264
    }
3265
3266
    /* Version */
3267
    uint32_t version;
3268
    memcpy(&version, data + 4, 4);
3269
    if (version != 2) {
3270
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_INVALID_VERSION,
3271
                        "Unsupported GLB version (expected 2)", NULL, -1);
3272
        return TG3_ERR_GLB_INVALID_VERSION;
3273
    }
3274
3275
    /* Total length */
3276
    uint32_t total_length;
3277
    memcpy(&total_length, data + 8, 4);
3278
    if ((uint64_t)total_length > size) {
3279
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_SIZE_MISMATCH,
3280
                        "GLB total length exceeds data size", NULL, -1);
3281
        return TG3_ERR_GLB_SIZE_MISMATCH;
3282
    }
3283
3284
    if (total_length < 20) {
3285
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_INVALID_HEADER,
3286
                        "GLB too small for JSON chunk header", NULL, -1);
3287
        return TG3_ERR_GLB_INVALID_HEADER;
3288
    }
3289
3290
    /* Chunk 0: JSON */
3291
    uint32_t chunk0_length, chunk0_type;
3292
    memcpy(&chunk0_length, data + 12, 4);
3293
    memcpy(&chunk0_type, data + 16, 4);
3294
3295
    if (chunk0_type != 0x4E4F534A) { /* 'JSON' in LE */
3296
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_CHUNK_ERROR,
3297
                        "First GLB chunk is not JSON", NULL, -1);
3298
        return TG3_ERR_GLB_CHUNK_ERROR;
3299
    }
3300
3301
    if (20 + (uint64_t)chunk0_length > total_length) {
3302
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_GLB_CHUNK_ERROR,
3303
                        "JSON chunk length exceeds GLB size", NULL, -1);
3304
        return TG3_ERR_GLB_CHUNK_ERROR;
3305
    }
3306
3307
    *json_out = data + 20;
3308
    *json_size_out = chunk0_length;
3309
3310
    /* Chunk 1: BIN (optional) */
3311
    uint64_t bin_offset = 20 + (uint64_t)chunk0_length;
3312
    /* Align to 4 bytes */
3313
    bin_offset = (bin_offset + 3) & ~(uint64_t)3;
3314
3315
    if (bin_offset + 8 <= total_length) {
3316
        uint32_t chunk1_length, chunk1_type;
3317
        memcpy(&chunk1_length, data + bin_offset, 4);
3318
        memcpy(&chunk1_type, data + bin_offset + 4, 4);
3319
3320
        if (chunk1_type == 0x004E4942) { /* 'BIN\0' in LE */
3321
            if (bin_offset + 8 + chunk1_length <= total_length) {
3322
                *bin_out = data + bin_offset + 8;
3323
                *bin_size_out = chunk1_length;
3324
            }
3325
        }
3326
    }
3327
3328
    return TG3_OK;
3329
}
3330
3331
/* ======================================================================
3332
 * Optional: Default FS Callbacks
3333
 * ====================================================================== */
3334
3335
#ifdef TINYGLTF3_ENABLE_FS
3336
3337
static int32_t tg3__fs_file_exists(const char *path, uint32_t path_len,
3338
                                    void *ud) {
3339
    (void)ud; (void)path_len;
3340
    FILE *f = fopen(path, "rb");
3341
    if (f) { fclose(f); return 1; }
3342
    return 0;
3343
}
3344
3345
static int32_t tg3__fs_read_file(uint8_t **out_data, uint64_t *out_size,
3346
                                  const char *path, uint32_t path_len,
3347
                                  void *ud) {
3348
    (void)ud; (void)path_len;
3349
    FILE *f = fopen(path, "rb");
3350
    if (!f) return 0;
3351
3352
    fseek(f, 0, SEEK_END);
3353
    long sz = ftell(f);
3354
    fseek(f, 0, SEEK_SET);
3355
    if (sz < 0) { fclose(f); return 0; }
3356
3357
    uint8_t *data = (uint8_t *)malloc((size_t)sz);
3358
    if (!data) { fclose(f); return 0; }
3359
3360
    size_t read = fread(data, 1, (size_t)sz, f);
3361
    fclose(f);
3362
3363
    if ((long)read != sz) { free(data); return 0; }
3364
3365
    *out_data = data;
3366
    *out_size = (uint64_t)sz;
3367
    return 1;
3368
}
3369
3370
static void tg3__fs_free_file(uint8_t *data, uint64_t size, void *ud) {
3371
    (void)size; (void)ud;
3372
    free(data);
3373
}
3374
3375
static int32_t tg3__fs_write_file(const char *path, uint32_t path_len,
3376
                                   const uint8_t *data, uint64_t size,
3377
                                   void *ud) {
3378
    (void)ud; (void)path_len;
3379
    FILE *f = fopen(path, "wb");
3380
    if (!f) return 0;
3381
    size_t written = fwrite(data, 1, (size_t)size, f);
3382
    fclose(f);
3383
    return (written == (size_t)size) ? 1 : 0;
3384
}
3385
3386
static void tg3__set_default_fs(tg3_fs_callbacks *fs) {
3387
    if (!fs->read_file) fs->read_file = tg3__fs_read_file;
3388
    if (!fs->free_file) fs->free_file = tg3__fs_free_file;
3389
    if (!fs->file_exists) fs->file_exists = tg3__fs_file_exists;
3390
    if (!fs->write_file) fs->write_file = tg3__fs_write_file;
3391
}
3392
3393
#endif /* TINYGLTF3_ENABLE_FS */
3394
3395
/* ======================================================================
3396
 * Internal: Model Init Helper
3397
 * ====================================================================== */
3398
3399
static void tg3__model_init(tg3_model *model) {
3400
    memset(model, 0, sizeof(tg3_model));
3401
    model->default_scene = -1;
3402
}
3403
3404
/* ======================================================================
3405
 * Public: Parser API Implementation
3406
 * ====================================================================== */
3407
3408
TINYGLTF3_API tg3_error_code tg3_parse(
3409
    tg3_model *model, tg3_error_stack *errors,
3410
    const uint8_t *json_data, uint64_t json_size,
3411
    const char *base_dir, uint32_t base_dir_len,
3412
    const tg3_parse_options *options) {
3413
3414
    tg3_parse_options default_opts;
3415
    if (!options) {
3416
        tg3_parse_options_init(&default_opts);
3417
        options = &default_opts;
3418
    }
3419
3420
    tg3__model_init(model);
3421
3422
    tg3_arena *arena = tg3__arena_create(&options->memory);
3423
    if (!arena) {
3424
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
3425
                        "Failed to create arena", NULL, -1);
3426
        return TG3_ERR_OUT_OF_MEMORY;
3427
    }
3428
    model->arena_ = arena;
3429
3430
    /* Parse JSON */
3431
    tg3__json json_doc = options->parse_float32
3432
        ? tg3__json::parse_float32(
3433
              (const char *)json_data, (const char *)json_data + json_size)
3434
        : tg3__json::parse(
3435
              (const char *)json_data, (const char *)json_data + json_size,
3436
              nullptr, false);
3437
3438
    if (json_doc.is_null()) {
3439
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE,
3440
                        "Failed to parse JSON", NULL, -1);
3441
        return TG3_ERR_JSON_PARSE;
3442
    }
3443
3444
    if (!json_doc.is_object()) {
3445
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE,
3446
                        "JSON root must be an object", NULL, -1);
3447
        return TG3_ERR_JSON_PARSE;
3448
    }
3449
3450
    tg3__parse_ctx ctx;
3451
    memset(&ctx, 0, sizeof(ctx));
3452
    ctx.arena = arena;
3453
    ctx.errors = errors;
3454
    ctx.opts = *options;
3455
    ctx.base_dir = base_dir;
3456
    ctx.base_dir_len = base_dir_len;
3457
    ctx.is_binary = 0;
3458
3459
#ifdef TINYGLTF3_ENABLE_FS
3460
    tg3__set_default_fs(&ctx.opts.fs);
3461
#endif
3462
3463
    return tg3__parse_from_json(&ctx, json_doc, model);
3464
}
3465
3466
TINYGLTF3_API tg3_error_code tg3_parse_glb(
3467
    tg3_model *model, tg3_error_stack *errors,
3468
    const uint8_t *glb_data, uint64_t glb_size,
3469
    const char *base_dir, uint32_t base_dir_len,
3470
    const tg3_parse_options *options) {
3471
3472
    const uint8_t *json_chunk = NULL;
3473
    uint64_t json_chunk_size = 0;
3474
    const uint8_t *bin_chunk = NULL;
3475
    uint64_t bin_chunk_size = 0;
3476
3477
    tg3_error_code err = tg3__parse_glb_header(glb_data, glb_size,
3478
                                                &json_chunk, &json_chunk_size,
3479
                                                &bin_chunk, &bin_chunk_size,
3480
                                                errors);
3481
    if (err != TG3_OK) return err;
3482
3483
    tg3_parse_options default_opts;
3484
    if (!options) {
3485
        tg3_parse_options_init(&default_opts);
3486
        options = &default_opts;
3487
    }
3488
3489
    tg3__model_init(model);
3490
3491
    tg3_arena *arena = tg3__arena_create(&options->memory);
3492
    if (!arena) {
3493
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
3494
                        "Failed to create arena", NULL, -1);
3495
        return TG3_ERR_OUT_OF_MEMORY;
3496
    }
3497
    model->arena_ = arena;
3498
3499
    /* Parse JSON chunk */
3500
    tg3__json json_doc = options->parse_float32
3501
        ? tg3__json::parse_float32(
3502
              (const char *)json_chunk, (const char *)json_chunk + json_chunk_size)
3503
        : tg3__json::parse(
3504
              (const char *)json_chunk, (const char *)json_chunk + json_chunk_size,
3505
              nullptr, false);
3506
3507
    if (json_doc.is_null() || !json_doc.is_object()) {
3508
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_JSON_PARSE,
3509
                        "Failed to parse GLB JSON chunk", NULL, -1);
3510
        return TG3_ERR_JSON_PARSE;
3511
    }
3512
3513
    tg3__parse_ctx ctx;
3514
    memset(&ctx, 0, sizeof(ctx));
3515
    ctx.arena = arena;
3516
    ctx.errors = errors;
3517
    ctx.opts = *options;
3518
    ctx.base_dir = base_dir;
3519
    ctx.base_dir_len = base_dir_len;
3520
    ctx.is_binary = 1;
3521
    ctx.bin_data = bin_chunk;
3522
    ctx.bin_size = bin_chunk_size;
3523
3524
#ifdef TINYGLTF3_ENABLE_FS
3525
    tg3__set_default_fs(&ctx.opts.fs);
3526
#endif
3527
3528
    return tg3__parse_from_json(&ctx, json_doc, model);
3529
}
3530
3531
TINYGLTF3_API tg3_error_code tg3_parse_auto(
3532
    tg3_model *model, tg3_error_stack *errors,
3533
    const uint8_t *data, uint64_t size,
3534
    const char *base_dir, uint32_t base_dir_len,
3535
    const tg3_parse_options *options) {
3536
3537
    /* Check for GLB magic */
3538
    if (size >= 4 && data[0] == 'g' && data[1] == 'l' &&
3539
        data[2] == 'T' && data[3] == 'F') {
3540
        return tg3_parse_glb(model, errors, data, size,
3541
                              base_dir, base_dir_len, options);
3542
    }
3543
    return tg3_parse(model, errors, data, size,
3544
                      base_dir, base_dir_len, options);
3545
}
3546
3547
TINYGLTF3_API tg3_error_code tg3_parse_file(
3548
    tg3_model *model, tg3_error_stack *errors,
3549
    const char *filename, uint32_t filename_len,
3550
    const tg3_parse_options *options) {
3551
3552
    tg3_parse_options opts;
3553
    if (options) {
3554
        opts = *options;
3555
    } else {
3556
        tg3_parse_options_init(&opts);
3557
    }
3558
3559
#ifdef TINYGLTF3_ENABLE_FS
3560
    tg3__set_default_fs(&opts.fs);
3561
#endif
3562
3563
    if (!opts.fs.read_file) {
3564
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_FS_NOT_AVAILABLE,
3565
                        "No filesystem callbacks. Define TINYGLTF3_ENABLE_FS "
3566
                        "or provide fs callbacks.", NULL, -1);
3567
        return TG3_ERR_FS_NOT_AVAILABLE;
3568
    }
3569
3570
    /* Read file */
3571
    uint8_t *file_data = NULL;
3572
    uint64_t file_size = 0;
3573
    int32_t ok = opts.fs.read_file(&file_data, &file_size, filename,
3574
                                    filename_len, opts.fs.user_data);
3575
    if (!ok || !file_data) {
3576
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_FILE_NOT_FOUND,
3577
                        "Failed to read file", NULL, -1);
3578
        return TG3_ERR_FILE_NOT_FOUND;
3579
    }
3580
3581
    /* Extract base directory */
3582
    char base_dir_buf[4096] = {0};
3583
    uint32_t base_dir_len = 0;
3584
    if (filename && filename_len > 0) {
3585
        /* Find last separator */
3586
        const char *last_sep = NULL;
3587
        for (uint32_t i = 0; i < filename_len; ++i) {
3588
            if (filename[i] == '/' || filename[i] == '\\') {
3589
                last_sep = filename + i;
3590
            }
3591
        }
3592
        if (last_sep) {
3593
            base_dir_len = (uint32_t)(last_sep - filename);
3594
            if (base_dir_len >= sizeof(base_dir_buf))
3595
                base_dir_len = (uint32_t)(sizeof(base_dir_buf) - 1);
3596
            memcpy(base_dir_buf, filename, base_dir_len);
3597
            base_dir_buf[base_dir_len] = '\0';
3598
        }
3599
    }
3600
3601
    tg3_error_code result = tg3_parse_auto(model, errors, file_data, file_size,
3602
                                            base_dir_buf, base_dir_len, &opts);
3603
3604
    /* Free file data */
3605
    if (opts.fs.free_file) {
3606
        opts.fs.free_file(file_data, file_size, opts.fs.user_data);
3607
    }
3608
3609
    return result;
3610
}
3611
3612
TINYGLTF3_API void tg3_model_free(tg3_model *model) {
3613
    if (!model) return;
3614
    if (model->arena_) {
3615
        tg3__arena_destroy(model->arena_);
3616
    }
3617
    memset(model, 0, sizeof(tg3_model));
3618
    model->default_scene = -1;
3619
}
3620
3621
/* ======================================================================
3622
 * Internal: JSON Serialization Helpers
3623
 * ====================================================================== */
3624
3625
static void tg3__serialize_str(tg3__json &o, const char *key, tg3_str s) {
3626
    if (s.data && s.len > 0) {
3627
        o[key] = std::string(s.data, s.len);
3628
    }
3629
}
3630
3631
static void tg3__serialize_int(tg3__json &o, const char *key, int32_t val,
3632
                                int32_t default_val, int write_defaults) {
3633
    if (val != default_val || write_defaults) {
3634
        o[key] = val;
3635
    }
3636
}
3637
3638
static void tg3__serialize_uint64(tg3__json &o, const char *key, uint64_t val,
3639
                                   uint64_t default_val, int write_defaults) {
3640
    if (val != default_val || write_defaults) {
3641
        o[key] = (int64_t)val;
3642
    }
3643
}
3644
3645
static void tg3__serialize_double(tg3__json &o, const char *key, double val,
3646
                                   double default_val, int write_defaults) {
3647
    if (fabs(val - default_val) > 1e-12 || write_defaults) {
3648
        o[key] = val;
3649
    }
3650
}
3651
3652
static void tg3__serialize_bool(tg3__json &o, const char *key, int32_t val,
3653
                                 int32_t default_val, int write_defaults) {
3654
    if (val != default_val || write_defaults) {
3655
        o[key] = (val != 0);
3656
    }
3657
}
3658
3659
static void tg3__serialize_double_array(tg3__json &o, const char *key,
3660
                                         const double *arr, uint32_t count) {
3661
    if (!arr || count == 0) return;
3662
    tg3__json jarr;
3663
    jarr.set_array();
3664
    for (uint32_t i = 0; i < count; ++i) {
3665
        jarr.push_back(tg3__json(arr[i]));
3666
    }
3667
    o[key] = static_cast<tg3__json&&>(jarr);
3668
}
3669
3670
static void tg3__serialize_int_array(tg3__json &o, const char *key,
3671
                                      const int32_t *arr, uint32_t count) {
3672
    if (!arr || count == 0) return;
3673
    tg3__json jarr;
3674
    jarr.set_array();
3675
    for (uint32_t i = 0; i < count; ++i) {
3676
        jarr.push_back(tg3__json(arr[i]));
3677
    }
3678
    o[key] = static_cast<tg3__json&&>(jarr);
3679
}
3680
3681
static void tg3__serialize_string_array(tg3__json &o, const char *key,
3682
                                         const tg3_str *arr, uint32_t count) {
3683
    if (!arr || count == 0) return;
3684
    tg3__json jarr;
3685
    jarr.set_array();
3686
    for (uint32_t i = 0; i < count; ++i) {
3687
        if (arr[i].data) {
3688
            jarr.push_back(tg3__json(std::string(arr[i].data, arr[i].len)));
3689
        }
3690
    }
3691
    o[key] = static_cast<tg3__json&&>(jarr);
3692
}
3693
3694
static tg3__json tg3__value_to_json(const tg3_value *v) {
3695
    if (!v) return tg3__json();
3696
    switch (v->type) {
3697
        case TG3_VALUE_NULL:   return tg3__json();
3698
        case TG3_VALUE_BOOL:   return tg3__json(v->bool_val != 0);
3699
        case TG3_VALUE_INT:    return tg3__json(v->int_val);
3700
        case TG3_VALUE_REAL:   return tg3__json(v->real_val);
3701
        case TG3_VALUE_STRING:
3702
            return tg3__json(std::string(v->string_val.data ? v->string_val.data : "",
3703
                                          v->string_val.len));
3704
        case TG3_VALUE_ARRAY: {
3705
            tg3__json arr;
3706
            arr.set_array();
3707
            for (uint32_t i = 0; i < v->array_count; ++i) {
3708
                arr.push_back(tg3__value_to_json(&v->array_data[i]));
3709
            }
3710
            return arr;
3711
        }
3712
        case TG3_VALUE_OBJECT: {
3713
            tg3__json obj = tg3__json::object();
3714
            for (uint32_t i = 0; i < v->object_count; ++i) {
3715
                std::string k(v->object_data[i].key.data ? v->object_data[i].key.data : "",
3716
                              v->object_data[i].key.len);
3717
                obj[k.c_str()] = tg3__value_to_json(&v->object_data[i].value);
3718
            }
3719
            return obj;
3720
        }
3721
        default: return tg3__json();
3722
    }
3723
}
3724
3725
static void tg3__serialize_extras_ext(tg3__json &o, const tg3_extras_ext *ee) {
3726
    if (!ee) return;
3727
    if (ee->extras) {
3728
        o["extras"] = tg3__value_to_json(ee->extras);
3729
    }
3730
    if (ee->extensions && ee->extensions_count > 0) {
3731
        tg3__json exts = tg3__json::object();
3732
        for (uint32_t i = 0; i < ee->extensions_count; ++i) {
3733
            std::string name(ee->extensions[i].name.data ? ee->extensions[i].name.data : "",
3734
                             ee->extensions[i].name.len);
3735
            exts[name.c_str()] = tg3__value_to_json(&ee->extensions[i].value);
3736
        }
3737
        o["extensions"] = static_cast<tg3__json&&>(exts);
3738
    }
3739
}
3740
3741
/* ======================================================================
3742
 * Internal: Entity Serialize Functions
3743
 * ====================================================================== */
3744
3745
static void tg3__serialize_texture_info(tg3__json &parent, const char *key,
3746
                                         const tg3_texture_info *ti, int wd) {
3747
    if (ti->index < 0) return;
3748
    tg3__json o = tg3__json::object();
3749
    o["index"] = ti->index;
3750
    tg3__serialize_int(o, "texCoord", ti->tex_coord, 0, wd);
3751
    tg3__serialize_extras_ext(o, &ti->ext);
3752
    parent[key] = static_cast<tg3__json&&>(o);
3753
}
3754
3755
static void tg3__serialize_normal_texture_info(tg3__json &parent, const char *key,
3756
                                                const tg3_normal_texture_info *ti,
3757
                                                int wd) {
3758
    if (ti->index < 0) return;
3759
    tg3__json o = tg3__json::object();
3760
    o["index"] = ti->index;
3761
    tg3__serialize_int(o, "texCoord", ti->tex_coord, 0, wd);
3762
    tg3__serialize_double(o, "scale", ti->scale, 1.0, wd);
3763
    tg3__serialize_extras_ext(o, &ti->ext);
3764
    parent[key] = static_cast<tg3__json&&>(o);
3765
}
3766
3767
static void tg3__serialize_occlusion_texture_info(tg3__json &parent, const char *key,
3768
                                                   const tg3_occlusion_texture_info *ti,
3769
                                                   int wd) {
3770
    if (ti->index < 0) return;
3771
    tg3__json o = tg3__json::object();
3772
    o["index"] = ti->index;
3773
    tg3__serialize_int(o, "texCoord", ti->tex_coord, 0, wd);
3774
    tg3__serialize_double(o, "strength", ti->strength, 1.0, wd);
3775
    tg3__serialize_extras_ext(o, &ti->ext);
3776
    parent[key] = static_cast<tg3__json&&>(o);
3777
}
3778
3779
static tg3__json tg3__serialize_asset(const tg3_asset *a, int wd) {
3780
    (void)wd;
3781
    tg3__json o = tg3__json::object();
3782
    tg3__serialize_str(o, "version", a->version);
3783
    tg3__serialize_str(o, "generator", a->generator);
3784
    tg3__serialize_str(o, "minVersion", a->min_version);
3785
    tg3__serialize_str(o, "copyright", a->copyright);
3786
    tg3__serialize_extras_ext(o, &a->ext);
3787
    return o;
3788
}
3789
3790
static tg3__json tg3__serialize_buffer(const tg3_buffer *b, int wd,
3791
                                        int embed) {
3792
    (void)wd;
3793
    tg3__json o = tg3__json::object();
3794
    tg3__serialize_str(o, "name", b->name);
3795
    o["byteLength"] = (int64_t)b->data.count;
3796
3797
    if (b->uri.data && b->uri.len > 0) {
3798
        tg3__serialize_str(o, "uri", b->uri);
3799
    } else if (embed && b->data.data && b->data.count > 0) {
3800
        /* Encode as data URI */
3801
        size_t enc_len = 0;
3802
        char *encoded = tg3__b64_encode(b->data.data, (size_t)b->data.count,
3803
                                         &enc_len);
3804
        if (encoded) {
3805
            std::string uri = "data:application/octet-stream;base64,";
3806
            uri.append(encoded, enc_len);
3807
            o["uri"] = uri;
3808
            free(encoded);
3809
        }
3810
    }
3811
3812
    tg3__serialize_extras_ext(o, &b->ext);
3813
    return o;
3814
}
3815
3816
static tg3__json tg3__serialize_buffer_view(const tg3_buffer_view *bv, int wd) {
3817
    tg3__json o = tg3__json::object();
3818
    tg3__serialize_str(o, "name", bv->name);
3819
    o["buffer"] = bv->buffer;
3820
    o["byteLength"] = (int64_t)bv->byte_length;
3821
    tg3__serialize_uint64(o, "byteOffset", bv->byte_offset, 0, wd);
3822
    if (bv->byte_stride > 0) o["byteStride"] = (int)bv->byte_stride;
3823
    tg3__serialize_int(o, "target", bv->target, 0, wd);
3824
    tg3__serialize_extras_ext(o, &bv->ext);
3825
    return o;
3826
}
3827
3828
static tg3__json tg3__serialize_accessor(const tg3_accessor *acc, int wd) {
3829
    tg3__json o = tg3__json::object();
3830
    tg3__serialize_str(o, "name", acc->name);
3831
    if (acc->buffer_view >= 0) o["bufferView"] = acc->buffer_view;
3832
    tg3__serialize_uint64(o, "byteOffset", acc->byte_offset, 0, wd);
3833
    o["componentType"] = acc->component_type;
3834
    o["count"] = (int64_t)acc->count;
3835
    o["type"] = tg3__accessor_type_to_string(acc->type);
3836
    tg3__serialize_bool(o, "normalized", acc->normalized, 0, wd);
3837
3838
    tg3__serialize_double_array(o, "min", acc->min_values, acc->min_values_count);
3839
    tg3__serialize_double_array(o, "max", acc->max_values, acc->max_values_count);
3840
3841
    if (acc->sparse.is_sparse) {
3842
        tg3__json sparse = tg3__json::object();
3843
        sparse["count"] = acc->sparse.count;
3844
3845
        tg3__json indices = tg3__json::object();
3846
        indices["bufferView"] = acc->sparse.indices.buffer_view;
3847
        indices["componentType"] = acc->sparse.indices.component_type;
3848
        tg3__serialize_uint64(indices, "byteOffset",
3849
                              acc->sparse.indices.byte_offset, 0, wd);
3850
        tg3__serialize_extras_ext(indices, &acc->sparse.indices.ext);
3851
        sparse["indices"] = static_cast<tg3__json&&>(indices);
3852
3853
        tg3__json values = tg3__json::object();
3854
        values["bufferView"] = acc->sparse.values.buffer_view;
3855
        tg3__serialize_uint64(values, "byteOffset",
3856
                              acc->sparse.values.byte_offset, 0, wd);
3857
        tg3__serialize_extras_ext(values, &acc->sparse.values.ext);
3858
        sparse["values"] = static_cast<tg3__json&&>(values);
3859
3860
        tg3__serialize_extras_ext(sparse, &acc->sparse.ext);
3861
        o["sparse"] = static_cast<tg3__json&&>(sparse);
3862
    }
3863
3864
    tg3__serialize_extras_ext(o, &acc->ext);
3865
    return o;
3866
}
3867
3868
static tg3__json tg3__serialize_image(const tg3_image *img, int wd, int embed) {
3869
    (void)wd; (void)embed;
3870
    tg3__json o = tg3__json::object();
3871
    tg3__serialize_str(o, "name", img->name);
3872
    tg3__serialize_str(o, "uri", img->uri);
3873
    tg3__serialize_str(o, "mimeType", img->mime_type);
3874
    if (img->buffer_view >= 0) o["bufferView"] = img->buffer_view;
3875
    tg3__serialize_extras_ext(o, &img->ext);
3876
    return o;
3877
}
3878
3879
static tg3__json tg3__serialize_sampler(const tg3_sampler *s, int wd) {
3880
    tg3__json o = tg3__json::object();
3881
    tg3__serialize_str(o, "name", s->name);
3882
    if (s->min_filter >= 0) o["minFilter"] = s->min_filter;
3883
    if (s->mag_filter >= 0) o["magFilter"] = s->mag_filter;
3884
    tg3__serialize_int(o, "wrapS", s->wrap_s, TG3_TEXTURE_WRAP_REPEAT, wd);
3885
    tg3__serialize_int(o, "wrapT", s->wrap_t, TG3_TEXTURE_WRAP_REPEAT, wd);
3886
    tg3__serialize_extras_ext(o, &s->ext);
3887
    return o;
3888
}
3889
3890
static tg3__json tg3__serialize_texture(const tg3_texture *t, int wd) {
3891
    (void)wd;
3892
    tg3__json o = tg3__json::object();
3893
    tg3__serialize_str(o, "name", t->name);
3894
    if (t->sampler >= 0) o["sampler"] = t->sampler;
3895
    if (t->source >= 0) o["source"] = t->source;
3896
    tg3__serialize_extras_ext(o, &t->ext);
3897
    return o;
3898
}
3899
3900
static tg3__json tg3__serialize_material(const tg3_material *m, int wd) {
3901
    tg3__json o = tg3__json::object();
3902
    tg3__serialize_str(o, "name", m->name);
3903
3904
    /* PBR */
3905
    tg3__json pbr = tg3__json::object();
3906
    int has_pbr = 0;
3907
3908
    const tg3_pbr_metallic_roughness *p = &m->pbr_metallic_roughness;
3909
    if (p->base_color_factor[0] != 1.0 || p->base_color_factor[1] != 1.0 ||
3910
        p->base_color_factor[2] != 1.0 || p->base_color_factor[3] != 1.0 || wd) {
3911
        tg3__serialize_double_array(pbr, "baseColorFactor",
3912
                                    p->base_color_factor, 4);
3913
        has_pbr = 1;
3914
    }
3915
    tg3__serialize_double(pbr, "metallicFactor", p->metallic_factor, 1.0, wd);
3916
    if (fabs(p->metallic_factor - 1.0) > 1e-12 || wd) has_pbr = 1;
3917
    tg3__serialize_double(pbr, "roughnessFactor", p->roughness_factor, 1.0, wd);
3918
    if (fabs(p->roughness_factor - 1.0) > 1e-12 || wd) has_pbr = 1;
3919
3920
    if (p->base_color_texture.index >= 0) {
3921
        tg3__serialize_texture_info(pbr, "baseColorTexture",
3922
                                    &p->base_color_texture, wd);
3923
        has_pbr = 1;
3924
    }
3925
    if (p->metallic_roughness_texture.index >= 0) {
3926
        tg3__serialize_texture_info(pbr, "metallicRoughnessTexture",
3927
                                    &p->metallic_roughness_texture, wd);
3928
        has_pbr = 1;
3929
    }
3930
    tg3__serialize_extras_ext(pbr, &p->ext);
3931
    if (has_pbr || wd) {
3932
        o["pbrMetallicRoughness"] = static_cast<tg3__json&&>(pbr);
3933
    }
3934
3935
    tg3__serialize_normal_texture_info(o, "normalTexture", &m->normal_texture, wd);
3936
    tg3__serialize_occlusion_texture_info(o, "occlusionTexture",
3937
                                           &m->occlusion_texture, wd);
3938
    tg3__serialize_texture_info(o, "emissiveTexture", &m->emissive_texture, wd);
3939
3940
    if (m->emissive_factor[0] != 0.0 || m->emissive_factor[1] != 0.0 ||
3941
        m->emissive_factor[2] != 0.0 || wd) {
3942
        tg3__serialize_double_array(o, "emissiveFactor", m->emissive_factor, 3);
3943
    }
3944
3945
    if (m->alpha_mode.data && !tg3_str_equals_cstr(m->alpha_mode, "OPAQUE")) {
3946
        tg3__serialize_str(o, "alphaMode", m->alpha_mode);
3947
    }
3948
    tg3__serialize_double(o, "alphaCutoff", m->alpha_cutoff, 0.5, wd);
3949
    tg3__serialize_bool(o, "doubleSided", m->double_sided, 0, wd);
3950
3951
    tg3__serialize_extras_ext(o, &m->ext);
3952
    return o;
3953
}
3954
3955
static tg3__json tg3__serialize_primitive(const tg3_primitive *p, int wd) {
3956
    tg3__json o = tg3__json::object();
3957
3958
    /* Attributes */
3959
    if (p->attributes && p->attributes_count > 0) {
3960
        tg3__json attrs = tg3__json::object();
3961
        for (uint32_t i = 0; i < p->attributes_count; ++i) {
3962
            std::string k(p->attributes[i].key.data ? p->attributes[i].key.data : "",
3963
                          p->attributes[i].key.len);
3964
            attrs[k.c_str()] = p->attributes[i].value;
3965
        }
3966
        o["attributes"] = static_cast<tg3__json&&>(attrs);
3967
    }
3968
3969
    if (p->indices >= 0) o["indices"] = p->indices;
3970
    if (p->material >= 0) o["material"] = p->material;
3971
    tg3__serialize_int(o, "mode", p->mode, TG3_MODE_TRIANGLES, wd);
3972
3973
    /* Morph targets */
3974
    if (p->targets && p->targets_count > 0) {
3975
        tg3__json targets;
3976
        targets.set_array();
3977
        for (uint32_t t = 0; t < p->targets_count; ++t) {
3978
            tg3__json tgt = tg3__json::object();
3979
            uint32_t acount = p->target_attribute_counts ?
3980
                              p->target_attribute_counts[t] : 0;
3981
            const tg3_str_int_pair *tattrs = p->targets[t];
3982
            for (uint32_t a = 0; a < acount; ++a) {
3983
                std::string k(tattrs[a].key.data ? tattrs[a].key.data : "",
3984
                              tattrs[a].key.len);
3985
                tgt[k.c_str()] = tattrs[a].value;
3986
            }
3987
            targets.push_back(static_cast<tg3__json&&>(tgt));
3988
        }
3989
        o["targets"] = static_cast<tg3__json&&>(targets);
3990
    }
3991
3992
    tg3__serialize_extras_ext(o, &p->ext);
3993
    return o;
3994
}
3995
3996
static tg3__json tg3__serialize_mesh(const tg3_mesh *m, int wd) {
3997
    tg3__json o = tg3__json::object();
3998
    tg3__serialize_str(o, "name", m->name);
3999
4000
    if (m->primitives && m->primitives_count > 0) {
4001
        tg3__json prims;
4002
        prims.set_array();
4003
        for (uint32_t i = 0; i < m->primitives_count; ++i) {
4004
            prims.push_back(tg3__serialize_primitive(&m->primitives[i], wd));
4005
        }
4006
        o["primitives"] = static_cast<tg3__json&&>(prims);
4007
    }
4008
4009
    tg3__serialize_double_array(o, "weights", m->weights, m->weights_count);
4010
    tg3__serialize_extras_ext(o, &m->ext);
4011
    return o;
4012
}
4013
4014
static tg3__json tg3__serialize_node(const tg3_node *n, int wd) {
4015
    tg3__json o = tg3__json::object();
4016
    tg3__serialize_str(o, "name", n->name);
4017
4018
    if (n->camera >= 0) o["camera"] = n->camera;
4019
    if (n->skin >= 0) o["skin"] = n->skin;
4020
    if (n->mesh >= 0) o["mesh"] = n->mesh;
4021
4022
    tg3__serialize_int_array(o, "children", n->children, n->children_count);
4023
4024
    if (n->has_matrix) {
4025
        tg3__serialize_double_array(o, "matrix", n->matrix, 16);
4026
    } else {
4027
        int has_t = (n->translation[0] != 0.0 || n->translation[1] != 0.0 ||
4028
                     n->translation[2] != 0.0);
4029
        int has_r = (n->rotation[0] != 0.0 || n->rotation[1] != 0.0 ||
4030
                     n->rotation[2] != 0.0 || n->rotation[3] != 1.0);
4031
        int has_s = (n->scale[0] != 1.0 || n->scale[1] != 1.0 ||
4032
                     n->scale[2] != 1.0);
4033
4034
        if (has_t || wd) tg3__serialize_double_array(o, "translation", n->translation, 3);
4035
        if (has_r || wd) tg3__serialize_double_array(o, "rotation", n->rotation, 4);
4036
        if (has_s || wd) tg3__serialize_double_array(o, "scale", n->scale, 3);
4037
    }
4038
4039
    tg3__serialize_double_array(o, "weights", n->weights, n->weights_count);
4040
4041
    /* Extensions for lights / audio / lod */
4042
    int has_ext = (n->light >= 0 || n->emitter >= 0 ||
4043
                   (n->lods && n->lods_count > 0));
4044
    if (has_ext) {
4045
        /* Check if extensions already set by extras_ext */
4046
        auto existing = o.find("extensions");
4047
        tg3__json exts = (existing != o.end()) ?
4048
                         tg3__json(*existing) : tg3__json::object();
4049
4050
        if (n->light >= 0) {
4051
            tg3__json lp = tg3__json::object();
4052
            lp["light"] = n->light;
4053
            exts["KHR_lights_punctual"] = static_cast<tg3__json&&>(lp);
4054
        }
4055
        if (n->emitter >= 0) {
4056
            tg3__json ae = tg3__json::object();
4057
            ae["emitter"] = n->emitter;
4058
            exts["KHR_audio"] = static_cast<tg3__json&&>(ae);
4059
        }
4060
        if (n->lods && n->lods_count > 0) {
4061
            tg3__json lod = tg3__json::object();
4062
            tg3__serialize_int_array(lod, "ids", n->lods, n->lods_count);
4063
            exts["MSFT_lod"] = static_cast<tg3__json&&>(lod);
4064
        }
4065
        o["extensions"] = static_cast<tg3__json&&>(exts);
4066
    }
4067
4068
    tg3__serialize_extras_ext(o, &n->ext);
4069
    return o;
4070
}
4071
4072
static tg3__json tg3__serialize_skin(const tg3_skin *s, int wd) {
4073
    (void)wd;
4074
    tg3__json o = tg3__json::object();
4075
    tg3__serialize_str(o, "name", s->name);
4076
    if (s->inverse_bind_matrices >= 0) o["inverseBindMatrices"] = s->inverse_bind_matrices;
4077
    if (s->skeleton >= 0) o["skeleton"] = s->skeleton;
4078
    tg3__serialize_int_array(o, "joints", s->joints, s->joints_count);
4079
    tg3__serialize_extras_ext(o, &s->ext);
4080
    return o;
4081
}
4082
4083
static tg3__json tg3__serialize_animation(const tg3_animation *a, int wd) {
4084
    (void)wd;
4085
    tg3__json o = tg3__json::object();
4086
    tg3__serialize_str(o, "name", a->name);
4087
4088
    if (a->channels && a->channels_count > 0) {
4089
        tg3__json channels;
4090
        channels.set_array();
4091
        for (uint32_t i = 0; i < a->channels_count; ++i) {
4092
            tg3__json ch = tg3__json::object();
4093
            ch["sampler"] = a->channels[i].sampler;
4094
            tg3__json tgt = tg3__json::object();
4095
            if (a->channels[i].target.node >= 0)
4096
                tgt["node"] = a->channels[i].target.node;
4097
            tg3__serialize_str(tgt, "path", a->channels[i].target.path);
4098
            tg3__serialize_extras_ext(tgt, &a->channels[i].target.ext);
4099
            ch["target"] = static_cast<tg3__json&&>(tgt);
4100
            tg3__serialize_extras_ext(ch, &a->channels[i].ext);
4101
            channels.push_back(static_cast<tg3__json&&>(ch));
4102
        }
4103
        o["channels"] = static_cast<tg3__json&&>(channels);
4104
    }
4105
4106
    if (a->samplers && a->samplers_count > 0) {
4107
        tg3__json samplers;
4108
        samplers.set_array();
4109
        for (uint32_t i = 0; i < a->samplers_count; ++i) {
4110
            tg3__json s = tg3__json::object();
4111
            s["input"] = a->samplers[i].input;
4112
            s["output"] = a->samplers[i].output;
4113
            tg3__serialize_str(s, "interpolation", a->samplers[i].interpolation);
4114
            tg3__serialize_extras_ext(s, &a->samplers[i].ext);
4115
            samplers.push_back(static_cast<tg3__json&&>(s));
4116
        }
4117
        o["samplers"] = static_cast<tg3__json&&>(samplers);
4118
    }
4119
4120
    tg3__serialize_extras_ext(o, &a->ext);
4121
    return o;
4122
}
4123
4124
static tg3__json tg3__serialize_camera(const tg3_camera *c, int wd) {
4125
    (void)wd;
4126
    tg3__json o = tg3__json::object();
4127
    tg3__serialize_str(o, "name", c->name);
4128
    tg3__serialize_str(o, "type", c->type);
4129
4130
    if (c->type.data && tg3_str_equals_cstr(c->type, "perspective")) {
4131
        tg3__json p = tg3__json::object();
4132
        if (c->perspective.aspect_ratio > 0)
4133
            p["aspectRatio"] = c->perspective.aspect_ratio;
4134
        p["yfov"] = c->perspective.yfov;
4135
        if (c->perspective.zfar > 0) p["zfar"] = c->perspective.zfar;
4136
        p["znear"] = c->perspective.znear;
4137
        tg3__serialize_extras_ext(p, &c->perspective.ext);
4138
        o["perspective"] = static_cast<tg3__json&&>(p);
4139
    } else if (c->type.data && tg3_str_equals_cstr(c->type, "orthographic")) {
4140
        tg3__json orth = tg3__json::object();
4141
        orth["xmag"] = c->orthographic.xmag;
4142
        orth["ymag"] = c->orthographic.ymag;
4143
        orth["zfar"] = c->orthographic.zfar;
4144
        orth["znear"] = c->orthographic.znear;
4145
        tg3__serialize_extras_ext(orth, &c->orthographic.ext);
4146
        o["orthographic"] = static_cast<tg3__json&&>(orth);
4147
    }
4148
4149
    tg3__serialize_extras_ext(o, &c->ext);
4150
    return o;
4151
}
4152
4153
static tg3__json tg3__serialize_scene(const tg3_scene *s, int wd) {
4154
    (void)wd;
4155
    tg3__json o = tg3__json::object();
4156
    tg3__serialize_str(o, "name", s->name);
4157
    tg3__serialize_int_array(o, "nodes", s->nodes, s->nodes_count);
4158
    tg3__serialize_extras_ext(o, &s->ext);
4159
    return o;
4160
}
4161
4162
static tg3__json tg3__serialize_light(const tg3_light *l, int wd) {
4163
    tg3__json o = tg3__json::object();
4164
    tg3__serialize_str(o, "name", l->name);
4165
    tg3__serialize_str(o, "type", l->type);
4166
    tg3__serialize_double(o, "intensity", l->intensity, 1.0, wd);
4167
    tg3__serialize_double(o, "range", l->range, 0.0, wd);
4168
4169
    if (l->color[0] != 1.0 || l->color[1] != 1.0 || l->color[2] != 1.0 || wd) {
4170
        tg3__serialize_double_array(o, "color", l->color, 3);
4171
    }
4172
4173
    if (l->type.data && tg3_str_equals_cstr(l->type, "spot")) {
4174
        tg3__json spot = tg3__json::object();
4175
        tg3__serialize_double(spot, "innerConeAngle", l->spot.inner_cone_angle, 0.0, wd);
4176
        tg3__serialize_double(spot, "outerConeAngle", l->spot.outer_cone_angle,
4177
                              0.7853981634, wd);
4178
        tg3__serialize_extras_ext(spot, &l->spot.ext);
4179
        o["spot"] = static_cast<tg3__json&&>(spot);
4180
    }
4181
4182
    tg3__serialize_extras_ext(o, &l->ext);
4183
    return o;
4184
}
4185
4186
/* ======================================================================
4187
 * Internal: Main Model Serializer
4188
 * ====================================================================== */
4189
4190
static tg3__json tg3__serialize_model(const tg3_model *model, int wd,
4191
                                       int embed_images, int embed_buffers) {
4192
    tg3__json root = tg3__json::object();
4193
4194
    /* Asset */
4195
    root["asset"] = tg3__serialize_asset(&model->asset, wd);
4196
4197
    /* Default scene */
4198
    if (model->default_scene >= 0) {
4199
        root["scene"] = model->default_scene;
4200
    }
4201
4202
    /* Extensions used/required */
4203
    tg3__serialize_string_array(root, "extensionsUsed",
4204
                                 model->extensions_used,
4205
                                 model->extensions_used_count);
4206
    tg3__serialize_string_array(root, "extensionsRequired",
4207
                                 model->extensions_required,
4208
                                 model->extensions_required_count);
4209
4210
    /* Entity arrays */
4211
    #define TG3__SERIALIZE_ARRAY(key, arr, cnt, fn, ...) \
4212
        if ((arr) && (cnt) > 0) { \
4213
            tg3__json jarr; jarr.set_array(); \
4214
            for (uint32_t _i = 0; _i < (cnt); ++_i) { \
4215
                jarr.push_back(fn(&(arr)[_i] TG3__COMMA_VA_ARGS(__VA_ARGS__))); \
4216
            } \
4217
            root[key] = static_cast<tg3__json&&>(jarr); \
4218
        }
4219
4220
    TG3__SERIALIZE_ARRAY("buffers", model->buffers, model->buffers_count,
4221
                         tg3__serialize_buffer, wd, embed_buffers);
4222
    TG3__SERIALIZE_ARRAY("bufferViews", model->buffer_views,
4223
                         model->buffer_views_count, tg3__serialize_buffer_view, wd);
4224
    TG3__SERIALIZE_ARRAY("accessors", model->accessors, model->accessors_count,
4225
                         tg3__serialize_accessor, wd);
4226
    TG3__SERIALIZE_ARRAY("meshes", model->meshes, model->meshes_count,
4227
                         tg3__serialize_mesh, wd);
4228
    TG3__SERIALIZE_ARRAY("nodes", model->nodes, model->nodes_count,
4229
                         tg3__serialize_node, wd);
4230
    TG3__SERIALIZE_ARRAY("materials", model->materials, model->materials_count,
4231
                         tg3__serialize_material, wd);
4232
    TG3__SERIALIZE_ARRAY("textures", model->textures, model->textures_count,
4233
                         tg3__serialize_texture, wd);
4234
    TG3__SERIALIZE_ARRAY("samplers", model->samplers, model->samplers_count,
4235
                         tg3__serialize_sampler, wd);
4236
    TG3__SERIALIZE_ARRAY("images", model->images, model->images_count,
4237
                         tg3__serialize_image, wd, embed_images);
4238
    TG3__SERIALIZE_ARRAY("skins", model->skins, model->skins_count,
4239
                         tg3__serialize_skin, wd);
4240
    TG3__SERIALIZE_ARRAY("animations", model->animations, model->animations_count,
4241
                         tg3__serialize_animation, wd);
4242
    TG3__SERIALIZE_ARRAY("cameras", model->cameras, model->cameras_count,
4243
                         tg3__serialize_camera, wd);
4244
    TG3__SERIALIZE_ARRAY("scenes", model->scenes, model->scenes_count,
4245
                         tg3__serialize_scene, wd);
4246
4247
    /* KHR_lights_punctual */
4248
    if (model->lights && model->lights_count > 0) {
4249
        tg3__json lights_ext = tg3__json::object();
4250
        tg3__json lights;
4251
        lights.set_array();
4252
        for (uint32_t i = 0; i < model->lights_count; ++i) {
4253
            lights.push_back(tg3__serialize_light(&model->lights[i], wd));
4254
        }
4255
        lights_ext["lights"] = static_cast<tg3__json&&>(lights);
4256
4257
        auto ext_it = root.find("extensions");
4258
        tg3__json exts = (ext_it != root.end()) ?
4259
                         tg3__json(*ext_it) : tg3__json::object();
4260
        exts["KHR_lights_punctual"] = static_cast<tg3__json&&>(lights_ext);
4261
        root["extensions"] = static_cast<tg3__json&&>(exts);
4262
    }
4263
4264
    /* Root extras/extensions */
4265
    tg3__serialize_extras_ext(root, &model->ext);
4266
4267
    #undef TG3__SERIALIZE_ARRAY
4268
    return root;
4269
}
4270
4271
/* ======================================================================
4272
 * Public: Writer API Implementation
4273
 * ====================================================================== */
4274
4275
TINYGLTF3_API tg3_error_code tg3_write_to_memory(
4276
    const tg3_model *model, tg3_error_stack *errors,
4277
    uint8_t **out_data, uint64_t *out_size,
4278
    const tg3_write_options *options) {
4279
4280
    tg3_write_options default_opts;
4281
    if (!options) {
4282
        tg3_write_options_init(&default_opts);
4283
        options = &default_opts;
4284
    }
4285
4286
    int wd = options->serialize_defaults;
4287
    tg3__json root = tg3__serialize_model(model, wd,
4288
                                           options->embed_images,
4289
                                           options->embed_buffers);
4290
4291
    int indent = options->pretty_print ? 2 : -1;
4292
    std::string json_str = root.dump(indent);
4293
4294
    if (options->write_binary) {
4295
        /* GLB format */
4296
        uint32_t json_len = (uint32_t)json_str.size();
4297
        /* Pad JSON to 4-byte alignment with spaces */
4298
        uint32_t json_padded = (json_len + 3) & ~3u;
4299
4300
        /* Collect binary buffer data */
4301
        const uint8_t *bin_data = NULL;
4302
        uint64_t bin_len = 0;
4303
        if (model->buffers_count > 0 && model->buffers[0].data.data) {
4304
            bin_data = model->buffers[0].data.data;
4305
            bin_len = model->buffers[0].data.count;
4306
        }
4307
        uint32_t bin_padded = ((uint32_t)bin_len + 3) & ~3u;
4308
4309
        uint32_t total = 12; /* Header */
4310
        total += 8 + json_padded; /* JSON chunk */
4311
        if (bin_data && bin_len > 0) {
4312
            total += 8 + bin_padded; /* BIN chunk */
4313
        }
4314
4315
        uint8_t *glb = (uint8_t *)malloc(total);
4316
        if (!glb) {
4317
            tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
4318
                            "OOM allocating GLB output", NULL, -1);
4319
            return TG3_ERR_OUT_OF_MEMORY;
4320
        }
4321
4322
        /* Header */
4323
        memcpy(glb, "glTF", 4);
4324
        uint32_t version = 2;
4325
        memcpy(glb + 4, &version, 4);
4326
        memcpy(glb + 8, &total, 4);
4327
4328
        /* JSON chunk */
4329
        memcpy(glb + 12, &json_padded, 4);
4330
        uint32_t json_type = 0x4E4F534A;
4331
        memcpy(glb + 16, &json_type, 4);
4332
        memcpy(glb + 20, json_str.c_str(), json_len);
4333
        /* Pad with spaces */
4334
        for (uint32_t i = json_len; i < json_padded; ++i) {
4335
            glb[20 + i] = ' ';
4336
        }
4337
4338
        /* BIN chunk */
4339
        if (bin_data && bin_len > 0) {
4340
            uint32_t bin_off = 20 + json_padded;
4341
            memcpy(glb + bin_off, &bin_padded, 4);
4342
            uint32_t bin_type = 0x004E4942;
4343
            memcpy(glb + bin_off + 4, &bin_type, 4);
4344
            memcpy(glb + bin_off + 8, bin_data, (size_t)bin_len);
4345
            /* Pad with zeros */
4346
            for (uint32_t i = (uint32_t)bin_len; i < bin_padded; ++i) {
4347
                glb[bin_off + 8 + i] = 0;
4348
            }
4349
        }
4350
4351
        *out_data = glb;
4352
        *out_size = total;
4353
    } else {
4354
        /* JSON format */
4355
        uint64_t sz = json_str.size();
4356
        uint8_t *data = (uint8_t *)malloc((size_t)sz);
4357
        if (!data) {
4358
            tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_OUT_OF_MEMORY,
4359
                            "OOM allocating JSON output", NULL, -1);
4360
            return TG3_ERR_OUT_OF_MEMORY;
4361
        }
4362
        memcpy(data, json_str.c_str(), (size_t)sz);
4363
        *out_data = data;
4364
        *out_size = sz;
4365
    }
4366
4367
    return TG3_OK;
4368
}
4369
4370
TINYGLTF3_API tg3_error_code tg3_write_to_file(
4371
    const tg3_model *model, tg3_error_stack *errors,
4372
    const char *filename, uint32_t filename_len,
4373
    const tg3_write_options *options) {
4374
4375
    tg3_write_options opts;
4376
    if (options) {
4377
        opts = *options;
4378
    } else {
4379
        tg3_write_options_init(&opts);
4380
    }
4381
4382
#ifdef TINYGLTF3_ENABLE_FS
4383
    tg3__set_default_fs(&opts.fs);
4384
#endif
4385
4386
    if (!opts.fs.write_file) {
4387
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_FS_NOT_AVAILABLE,
4388
                        "No filesystem write callback", NULL, -1);
4389
        return TG3_ERR_FS_NOT_AVAILABLE;
4390
    }
4391
4392
    uint8_t *data = NULL;
4393
    uint64_t size = 0;
4394
    tg3_error_code err = tg3_write_to_memory(model, errors, &data, &size, &opts);
4395
    if (err != TG3_OK) return err;
4396
4397
    int32_t ok = opts.fs.write_file(filename, filename_len, data, size,
4398
                                     opts.fs.user_data);
4399
    free(data);
4400
4401
    if (!ok) {
4402
        tg3__error_push(errors, TG3_SEVERITY_ERROR, TG3_ERR_FILE_WRITE,
4403
                        "Failed to write file", NULL, -1);
4404
        return TG3_ERR_FILE_WRITE;
4405
    }
4406
4407
    return TG3_OK;
4408
}
4409
4410
TINYGLTF3_API void tg3_write_free(uint8_t *data, const tg3_write_options *options) {
4411
    (void)options;
4412
    free(data);
4413
}
4414
4415
/* ======================================================================
4416
 * Streaming Writer (stub implementation)
4417
 * ====================================================================== */
4418
4419
struct tg3_writer {
4420
    tg3_write_chunk_fn chunk_fn;
4421
    void              *user_data;
4422
    tg3_write_options  options;
4423
    tg3__json          root;
4424
    int                begun;
4425
};
4426
4427
TINYGLTF3_API tg3_writer *tg3_writer_create(
4428
    tg3_write_chunk_fn chunk_fn, void *user_data,
4429
    const tg3_write_options *options) {
4430
    tg3_writer *w = new (std::nothrow) tg3_writer();
4431
    if (!w) return NULL;
4432
    w->chunk_fn = chunk_fn;
4433
    w->user_data = user_data;
4434
    if (options) w->options = *options;
4435
    else tg3_write_options_init(&w->options);
4436
    w->root = tg3__json::object();
4437
    return w;
4438
}
4439
4440
TINYGLTF3_API tg3_error_code tg3_writer_begin(tg3_writer *w, const tg3_asset *asset) {
4441
    if (!w) return TG3_ERR_WRITE_FAILED;
4442
    w->root["asset"] = tg3__serialize_asset(asset, w->options.serialize_defaults);
4443
    w->begun = 1;
4444
    return TG3_OK;
4445
}
4446
4447
#define TG3__WRITER_ADD_IMPL(name, Type, json_key, serialize_fn, ...) \
4448
    TINYGLTF3_API tg3_error_code tg3_writer_add_##name(tg3_writer *w, const Type *item) { \
4449
        if (!w || !w->begun) return TG3_ERR_WRITE_FAILED; \
4450
        auto it = w->root.find(json_key); \
4451
        if (it == w->root.end()) { \
4452
            tg3__json arr; arr.set_array(); \
4453
            w->root[json_key] = static_cast<tg3__json&&>(arr); \
4454
        } \
4455
        w->root[json_key].push_back( \
4456
            serialize_fn(item, w->options.serialize_defaults TG3__COMMA_VA_ARGS(__VA_ARGS__))); \
4457
        return TG3_OK; \
4458
    }
4459
4460
#define TG3__WRITER_ADD_SIMPLE(name, Type, json_key, serialize_fn) \
4461
    TINYGLTF3_API tg3_error_code tg3_writer_add_##name(tg3_writer *w, const Type *item) { \
4462
        if (!w || !w->begun) return TG3_ERR_WRITE_FAILED; \
4463
        auto it = w->root.find(json_key); \
4464
        if (it == w->root.end()) { \
4465
            tg3__json arr; arr.set_array(); \
4466
            w->root[json_key] = static_cast<tg3__json&&>(arr); \
4467
        } \
4468
        w->root[json_key].push_back( \
4469
            serialize_fn(item, w->options.serialize_defaults)); \
4470
        return TG3_OK; \
4471
    }
4472
4473
TG3__WRITER_ADD_IMPL(buffer, tg3_buffer, "buffers", tg3__serialize_buffer,
4474
                     w->options.embed_buffers)
4475
TG3__WRITER_ADD_SIMPLE(buffer_view, tg3_buffer_view, "bufferViews",
4476
                       tg3__serialize_buffer_view)
4477
TG3__WRITER_ADD_SIMPLE(accessor, tg3_accessor, "accessors", tg3__serialize_accessor)
4478
TG3__WRITER_ADD_SIMPLE(mesh, tg3_mesh, "meshes", tg3__serialize_mesh)
4479
TG3__WRITER_ADD_SIMPLE(node, tg3_node, "nodes", tg3__serialize_node)
4480
TG3__WRITER_ADD_SIMPLE(material, tg3_material, "materials", tg3__serialize_material)
4481
TG3__WRITER_ADD_SIMPLE(texture, tg3_texture, "textures", tg3__serialize_texture)
4482
TG3__WRITER_ADD_IMPL(image, tg3_image, "images", tg3__serialize_image,
4483
                     w->options.embed_images)
4484
TG3__WRITER_ADD_SIMPLE(sampler, tg3_sampler, "samplers", tg3__serialize_sampler)
4485
TG3__WRITER_ADD_SIMPLE(animation, tg3_animation, "animations", tg3__serialize_animation)
4486
TG3__WRITER_ADD_SIMPLE(skin, tg3_skin, "skins", tg3__serialize_skin)
4487
TG3__WRITER_ADD_SIMPLE(camera, tg3_camera, "cameras", tg3__serialize_camera)
4488
TG3__WRITER_ADD_SIMPLE(scene, tg3_scene, "scenes", tg3__serialize_scene)
4489
TG3__WRITER_ADD_SIMPLE(light, tg3_light, "lights", tg3__serialize_light)
4490
4491
#undef TG3__WRITER_ADD_IMPL
4492
#undef TG3__WRITER_ADD_SIMPLE
4493
4494
TINYGLTF3_API tg3_error_code tg3_writer_end(tg3_writer *w) {
4495
    if (!w || !w->begun || !w->chunk_fn) return TG3_ERR_WRITE_FAILED;
4496
4497
    int indent = w->options.pretty_print ? 2 : -1;
4498
    std::string json_str = w->root.dump(indent);
4499
4500
    int32_t ok = w->chunk_fn((const uint8_t *)json_str.c_str(),
4501
                              json_str.size(), w->user_data);
4502
    return ok ? TG3_OK : TG3_ERR_WRITE_FAILED;
4503
}
4504
4505
TINYGLTF3_API void tg3_writer_destroy(tg3_writer *w) {
4506
    delete w;
4507
}
4508
4509
#endif /* legacy header-only v3 implementation */
4510
#endif /* TINYGLTF3_IMPLEMENTATION */
4511
4512
#endif /* TINY_GLTF_V3_H_ */