1//
2// Header-only tiny glTF 2.0 loader and serializer.
3//
4//
5// The MIT License (MIT)
6//
7// Copyright (c) 2015 - Present Syoyo Fujita, Aurélien Chatelain and many
8// contributors.
9//
10// Permission is hereby granted, free of charge, to any person obtaining a copy
11// of this software and associated documentation files (the "Software"), to deal
12// in the Software without restriction, including without limitation the rights
13// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14// copies of the Software, and to permit persons to whom the Software is
15// furnished to do so, subject to the following conditions:
16//
17// The above copyright notice and this permission notice shall be included in
18// all copies or substantial portions of the Software.
19//
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26// THE SOFTWARE.
27
28// Version:
29// - v2.6.3 Fix GLB file with empty BIN chunk was not handled. PR#382 and PR#383.
30// - v2.6.2 Fix out-of-bounds access of accessors. PR#379.
31// - v2.6.1 Better GLB validation check when loading.
32// - v2.6.0 Support serializing sparse accessor(Thanks to @fynv).
33// Disable expanding file path for security(no use of awkward `wordexp` anymore).
34// - v2.5.0 Add SetPreserveImageChannels() option to load image data as is.
35// - v2.4.3 Fix null object output when when material has all default
36// parameters.
37// - v2.4.2 Decode percent-encoded URI.
38// - v2.4.1 Fix some glTF object class does not have `extensions` and/or
39// `extras` property.
40// - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone).
41// - v2.3.1 Set default value of minFilter and magFilter in Sampler to -1.
42// - v2.3.0 Modified Material representation according to glTF 2.0 schema
43// (and introduced TextureInfo class)
44// Change the behavior of `Value::IsNumber`. It return true either the
45// value is int or real.
46// - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks
47// to @Ybalrid)
48// - v2.1.0 Add draco compression.
49// - v2.0.1 Add comparsion feature(Thanks to @Selmar).
50// - v2.0.0 glTF 2.0!.
51//
52// Tiny glTF loader is using following third party libraries:
53//
54// - jsonhpp: C++ JSON library.
55// - base64: base64 decode/encode library.
56// - stb_image: Image loading library.
57//
58#ifndef TINY_GLTF_H_
59#define TINY_GLTF_H_
60
61#include <array>
62#include <cassert>
63#include <cmath> // std::fabs
64#include <cstdint>
65#include <cstdlib>
66#include <cstring>
67#include <limits>
68#include <map>
69#include <string>
70#include <vector>
71
72//Auto-detect C++14 standard version
73#if !defined(TINYGLTF_USE_CPP14) && defined(__cplusplus) && (__cplusplus >= 201402L)
74#define TINYGLTF_USE_CPP14
75#endif
76
77#ifndef TINYGLTF_USE_CPP14
78#include <functional>
79#endif
80
81#ifdef __ANDROID__
82#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
83#include <android/asset_manager.h>
84#endif
85#endif
86
87#ifdef __GNUC__
88#if (__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ <= 8))
89#define TINYGLTF_NOEXCEPT
90#else
91#define TINYGLTF_NOEXCEPT noexcept
92#endif
93#else
94#define TINYGLTF_NOEXCEPT noexcept
95#endif
96
97#define DEFAULT_METHODS(x) \
98 ~x() = default; \
99 x(const x &) = default; \
100 x(x &&) TINYGLTF_NOEXCEPT = default; \
101 x &operator=(const x &) = default; \
102 x &operator=(x &&) TINYGLTF_NOEXCEPT = default;
103
104namespace tinygltf {
105
106#define TINYGLTF_MODE_POINTS (0)
107#define TINYGLTF_MODE_LINE (1)
108#define TINYGLTF_MODE_LINE_LOOP (2)
109#define TINYGLTF_MODE_LINE_STRIP (3)
110#define TINYGLTF_MODE_TRIANGLES (4)
111#define TINYGLTF_MODE_TRIANGLE_STRIP (5)
112#define TINYGLTF_MODE_TRIANGLE_FAN (6)
113
114#define TINYGLTF_COMPONENT_TYPE_BYTE (5120)
115#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121)
116#define TINYGLTF_COMPONENT_TYPE_SHORT (5122)
117#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123)
118#define TINYGLTF_COMPONENT_TYPE_INT (5124)
119#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125)
120#define TINYGLTF_COMPONENT_TYPE_FLOAT (5126)
121#define TINYGLTF_COMPONENT_TYPE_DOUBLE \
122 (5130) // OpenGL double type. Note that some of glTF 2.0 validator does not
123 // support double type even the schema seems allow any value of
124 // integer:
125 // https://github.com/KhronosGroup/glTF/blob/b9884a2fd45130b4d673dd6c8a706ee21ee5c5f7/specification/2.0/schema/accessor.schema.json#L22
126
127#define TINYGLTF_TEXTURE_FILTER_NEAREST (9728)
128#define TINYGLTF_TEXTURE_FILTER_LINEAR (9729)
129#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST (9984)
130#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST (9985)
131#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR (9986)
132#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR (9987)
133
134#define TINYGLTF_TEXTURE_WRAP_REPEAT (10497)
135#define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071)
136#define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648)
137
138// Redeclarations of the above for technique.parameters.
139#define TINYGLTF_PARAMETER_TYPE_BYTE (5120)
140#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121)
141#define TINYGLTF_PARAMETER_TYPE_SHORT (5122)
142#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123)
143#define TINYGLTF_PARAMETER_TYPE_INT (5124)
144#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT (5125)
145#define TINYGLTF_PARAMETER_TYPE_FLOAT (5126)
146
147#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664)
148#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665)
149#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666)
150
151#define TINYGLTF_PARAMETER_TYPE_INT_VEC2 (35667)
152#define TINYGLTF_PARAMETER_TYPE_INT_VEC3 (35668)
153#define TINYGLTF_PARAMETER_TYPE_INT_VEC4 (35669)
154
155#define TINYGLTF_PARAMETER_TYPE_BOOL (35670)
156#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 (35671)
157#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 (35672)
158#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 (35673)
159
160#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 (35674)
161#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 (35675)
162#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 (35676)
163
164#define TINYGLTF_PARAMETER_TYPE_SAMPLER_2D (35678)
165
166// End parameter types
167
168#define TINYGLTF_TYPE_VEC2 (2)
169#define TINYGLTF_TYPE_VEC3 (3)
170#define TINYGLTF_TYPE_VEC4 (4)
171#define TINYGLTF_TYPE_MAT2 (32 + 2)
172#define TINYGLTF_TYPE_MAT3 (32 + 3)
173#define TINYGLTF_TYPE_MAT4 (32 + 4)
174#define TINYGLTF_TYPE_SCALAR (64 + 1)
175#define TINYGLTF_TYPE_VECTOR (64 + 4)
176#define TINYGLTF_TYPE_MATRIX (64 + 16)
177
178#define TINYGLTF_IMAGE_FORMAT_JPEG (0)
179#define TINYGLTF_IMAGE_FORMAT_PNG (1)
180#define TINYGLTF_IMAGE_FORMAT_BMP (2)
181#define TINYGLTF_IMAGE_FORMAT_GIF (3)
182
183#define TINYGLTF_TEXTURE_FORMAT_ALPHA (6406)
184#define TINYGLTF_TEXTURE_FORMAT_RGB (6407)
185#define TINYGLTF_TEXTURE_FORMAT_RGBA (6408)
186#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE (6409)
187#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE_ALPHA (6410)
188
189#define TINYGLTF_TEXTURE_TARGET_TEXTURE2D (3553)
190#define TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE (5121)
191
192#define TINYGLTF_TARGET_ARRAY_BUFFER (34962)
193#define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963)
194
195#define TINYGLTF_SHADER_TYPE_VERTEX_SHADER (35633)
196#define TINYGLTF_SHADER_TYPE_FRAGMENT_SHADER (35632)
197
198#define TINYGLTF_DOUBLE_EPS (1.e-12)
199#define TINYGLTF_DOUBLE_EQUAL(a, b) (std::fabs((b) - (a)) < TINYGLTF_DOUBLE_EPS)
200
201#ifdef __ANDROID__
202#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
203AAssetManager *asset_manager = nullptr;
204#endif
205#endif
206
207typedef enum {
208 NULL_TYPE,
209 REAL_TYPE,
210 INT_TYPE,
211 BOOL_TYPE,
212 STRING_TYPE,
213 ARRAY_TYPE,
214 BINARY_TYPE,
215 OBJECT_TYPE
216} Type;
217
218static inline int32_t GetComponentSizeInBytes(uint32_t componentType) {
219 if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) {
220 return 1;
221 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
222 return 1;
223 } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) {
224 return 2;
225 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
226 return 2;
227 } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) {
228 return 4;
229 } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
230 return 4;
231 } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) {
232 return 4;
233 } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) {
234 return 8;
235 } else {
236 // Unknown componenty type
237 return -1;
238 }
239}
240
241static inline int32_t GetNumComponentsInType(uint32_t ty) {
242 if (ty == TINYGLTF_TYPE_SCALAR) {
243 return 1;
244 } else if (ty == TINYGLTF_TYPE_VEC2) {
245 return 2;
246 } else if (ty == TINYGLTF_TYPE_VEC3) {
247 return 3;
248 } else if (ty == TINYGLTF_TYPE_VEC4) {
249 return 4;
250 } else if (ty == TINYGLTF_TYPE_MAT2) {
251 return 4;
252 } else if (ty == TINYGLTF_TYPE_MAT3) {
253 return 9;
254 } else if (ty == TINYGLTF_TYPE_MAT4) {
255 return 16;
256 } else {
257 // Unknown componenty type
258 return -1;
259 }
260}
261
262// TODO(syoyo): Move these functions to TinyGLTF class
263bool IsDataURI(const std::string &in);
264bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
265 const std::string &in, size_t reqBytes, bool checkSize);
266
267#ifdef __clang__
268#pragma clang diagnostic push
269// Suppress warning for : static Value null_value
270#pragma clang diagnostic ignored "-Wexit-time-destructors"
271#pragma clang diagnostic ignored "-Wpadded"
272#endif
273
274// Simple class to represent JSON object
275class Value {
276 public:
277 typedef std::vector<Value> Array;
278 typedef std::map<std::string, Value> Object;
279
280 Value()
281 : type_(NULL_TYPE),
282 int_value_(0),
283 real_value_(0.0),
284 boolean_value_(false) {}
285
286 explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; }
287 explicit Value(int i) : type_(INT_TYPE) {
288 int_value_ = i;
289 real_value_ = i;
290 }
291 explicit Value(double n) : type_(REAL_TYPE) { real_value_ = n; }
292 explicit Value(const std::string &s) : type_(STRING_TYPE) {
293 string_value_ = s;
294 }
295 explicit Value(std::string &&s)
296 : type_(STRING_TYPE), string_value_(std::move(s)) {}
297 explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) {
298 binary_value_.resize(sz: n);
299 memcpy(dest: binary_value_.data(), src: p, n: n);
300 }
301 explicit Value(std::vector<unsigned char> &&v) noexcept
302 : type_(BINARY_TYPE),
303 binary_value_(std::move(v)) {}
304 explicit Value(const Array &a) : type_(ARRAY_TYPE) { array_value_ = a; }
305 explicit Value(Array &&a) noexcept : type_(ARRAY_TYPE),
306 array_value_(std::move(a)) {}
307
308 explicit Value(const Object &o) : type_(OBJECT_TYPE) { object_value_ = o; }
309 explicit Value(Object &&o) noexcept : type_(OBJECT_TYPE),
310 object_value_(std::move(o)) {}
311
312 DEFAULT_METHODS(Value)
313
314 char Type() const { return static_cast<char>(type_); }
315
316 bool IsBool() const { return (type_ == BOOL_TYPE); }
317
318 bool IsInt() const { return (type_ == INT_TYPE); }
319
320 bool IsNumber() const { return (type_ == REAL_TYPE) || (type_ == INT_TYPE); }
321
322 bool IsReal() const { return (type_ == REAL_TYPE); }
323
324 bool IsString() const { return (type_ == STRING_TYPE); }
325
326 bool IsBinary() const { return (type_ == BINARY_TYPE); }
327
328 bool IsArray() const { return (type_ == ARRAY_TYPE); }
329
330 bool IsObject() const { return (type_ == OBJECT_TYPE); }
331
332 // Use this function if you want to have number value as double.
333 double GetNumberAsDouble() const {
334 if (type_ == INT_TYPE) {
335 return double(int_value_);
336 } else {
337 return real_value_;
338 }
339 }
340
341 // Use this function if you want to have number value as int.
342 // TODO(syoyo): Support int value larger than 32 bits
343 int GetNumberAsInt() const {
344 if (type_ == REAL_TYPE) {
345 return int(real_value_);
346 } else {
347 return int_value_;
348 }
349 }
350
351 // Accessor
352 template <typename T>
353 const T &Get() const;
354 template <typename T>
355 T &Get();
356
357 // Lookup value from an array
358 const Value &Get(int idx) const {
359 static Value null_value;
360 assert(IsArray());
361 assert(idx >= 0);
362 return (static_cast<size_t>(idx) < array_value_.size())
363 ? array_value_[static_cast<size_t>(idx)]
364 : null_value;
365 }
366
367 // Lookup value from a key-value pair
368 const Value &Get(const std::string &key) const {
369 static Value null_value;
370 assert(IsObject());
371 Object::const_iterator it = object_value_.find(k: key);
372 return (it != object_value_.end()) ? it->second : null_value;
373 }
374
375 size_t ArrayLen() const {
376 if (!IsArray()) return 0;
377 return array_value_.size();
378 }
379
380 // Valid only for object type.
381 bool Has(const std::string &key) const {
382 if (!IsObject()) return false;
383 Object::const_iterator it = object_value_.find(k: key);
384 return (it != object_value_.end()) ? true : false;
385 }
386
387 // List keys
388 std::vector<std::string> Keys() const {
389 std::vector<std::string> keys;
390 if (!IsObject()) return keys; // empty
391
392 for (Object::const_iterator it = object_value_.begin();
393 it != object_value_.end(); ++it) {
394 keys.push_back(x: it->first);
395 }
396
397 return keys;
398 }
399
400 size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); }
401
402 bool operator==(const tinygltf::Value &other) const;
403
404 protected:
405 int type_ = NULL_TYPE;
406
407 int int_value_ = 0;
408 double real_value_ = 0.0;
409 std::string string_value_;
410 std::vector<unsigned char> binary_value_;
411 Array array_value_;
412 Object object_value_;
413 bool boolean_value_ = false;
414};
415
416#ifdef __clang__
417#pragma clang diagnostic pop
418#endif
419
420#define TINYGLTF_VALUE_GET(ctype, var) \
421 template <> \
422 inline const ctype &Value::Get<ctype>() const { \
423 return var; \
424 } \
425 template <> \
426 inline ctype &Value::Get<ctype>() { \
427 return var; \
428 }
429TINYGLTF_VALUE_GET(bool, boolean_value_)
430TINYGLTF_VALUE_GET(double, real_value_)
431TINYGLTF_VALUE_GET(int, int_value_)
432TINYGLTF_VALUE_GET(std::string, string_value_)
433TINYGLTF_VALUE_GET(std::vector<unsigned char>, binary_value_)
434TINYGLTF_VALUE_GET(Value::Array, array_value_)
435TINYGLTF_VALUE_GET(Value::Object, object_value_)
436#undef TINYGLTF_VALUE_GET
437
438#ifdef __clang__
439#pragma clang diagnostic push
440#pragma clang diagnostic ignored "-Wc++98-compat"
441#pragma clang diagnostic ignored "-Wpadded"
442#endif
443
444/// Agregate object for representing a color
445using ColorValue = std::array<double, 4>;
446
447// === legacy interface ====
448// TODO(syoyo): Deprecate `Parameter` class.
449struct Parameter {
450 bool bool_value = false;
451 bool has_number_value = false;
452 std::string string_value;
453 std::vector<double> number_array;
454 std::map<std::string, double> json_double_value;
455 double number_value = 0.0;
456
457 // context sensitive methods. depending the type of the Parameter you are
458 // accessing, these are either valid or not
459 // If this parameter represent a texture map in a material, will return the
460 // texture index
461
462 /// Return the index of a texture if this Parameter is a texture map.
463 /// Returned value is only valid if the parameter represent a texture from a
464 /// material
465 int TextureIndex() const {
466 const auto it = json_double_value.find(k: "index");
467 if (it != std::end(c: json_double_value)) {
468 return int(it->second);
469 }
470 return -1;
471 }
472
473 /// Return the index of a texture coordinate set if this Parameter is a
474 /// texture map. Returned value is only valid if the parameter represent a
475 /// texture from a material
476 int TextureTexCoord() const {
477 const auto it = json_double_value.find(k: "texCoord");
478 if (it != std::end(c: json_double_value)) {
479 return int(it->second);
480 }
481 // As per the spec, if texCoord is ommited, this parameter is 0
482 return 0;
483 }
484
485 /// Return the scale of a texture if this Parameter is a normal texture map.
486 /// Returned value is only valid if the parameter represent a normal texture
487 /// from a material
488 double TextureScale() const {
489 const auto it = json_double_value.find(k: "scale");
490 if (it != std::end(c: json_double_value)) {
491 return it->second;
492 }
493 // As per the spec, if scale is ommited, this paramter is 1
494 return 1;
495 }
496
497 /// Return the strength of a texture if this Parameter is a an occlusion map.
498 /// Returned value is only valid if the parameter represent an occlusion map
499 /// from a material
500 double TextureStrength() const {
501 const auto it = json_double_value.find(k: "strength");
502 if (it != std::end(c: json_double_value)) {
503 return it->second;
504 }
505 // As per the spec, if strenghth is ommited, this parameter is 1
506 return 1;
507 }
508
509 /// Material factor, like the roughness or metalness of a material
510 /// Returned value is only valid if the parameter represent a texture from a
511 /// material
512 double Factor() const { return number_value; }
513
514 /// Return the color of a material
515 /// Returned value is only valid if the parameter represent a texture from a
516 /// material
517 ColorValue ColorFactor() const {
518 return {
519 {// this agregate intialize the std::array object, and uses C++11 RVO.
520 number_array[0], number_array[1], number_array[2],
521 (number_array.size() > 3 ? number_array[3] : 1.0)}};
522 }
523
524 Parameter() = default;
525 DEFAULT_METHODS(Parameter)
526 bool operator==(const Parameter &) const;
527};
528
529#ifdef __clang__
530#pragma clang diagnostic pop
531#endif
532
533#ifdef __clang__
534#pragma clang diagnostic push
535#pragma clang diagnostic ignored "-Wpadded"
536#endif
537
538typedef std::map<std::string, Parameter> ParameterMap;
539typedef std::map<std::string, Value> ExtensionMap;
540
541struct AnimationChannel {
542 int sampler; // required
543 int target_node; // required (index of the node to target)
544 std::string target_path; // required in ["translation", "rotation", "scale",
545 // "weights"]
546 Value extras;
547 ExtensionMap extensions;
548 ExtensionMap target_extensions;
549
550 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
551 std::string extras_json_string;
552 std::string extensions_json_string;
553 std::string target_extensions_json_string;
554
555 AnimationChannel() : sampler(-1), target_node(-1) {}
556 DEFAULT_METHODS(AnimationChannel)
557 bool operator==(const AnimationChannel &) const;
558};
559
560struct AnimationSampler {
561 int input; // required
562 int output; // required
563 std::string interpolation; // "LINEAR", "STEP","CUBICSPLINE" or user defined
564 // string. default "LINEAR"
565 Value extras;
566 ExtensionMap extensions;
567
568 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
569 std::string extras_json_string;
570 std::string extensions_json_string;
571
572 AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {}
573 DEFAULT_METHODS(AnimationSampler)
574 bool operator==(const AnimationSampler &) const;
575};
576
577struct Animation {
578 std::string name;
579 std::vector<AnimationChannel> channels;
580 std::vector<AnimationSampler> samplers;
581 Value extras;
582 ExtensionMap extensions;
583
584 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
585 std::string extras_json_string;
586 std::string extensions_json_string;
587
588 Animation() = default;
589 DEFAULT_METHODS(Animation)
590 bool operator==(const Animation &) const;
591};
592
593struct Skin {
594 std::string name;
595 int inverseBindMatrices; // required here but not in the spec
596 int skeleton; // The index of the node used as a skeleton root
597 std::vector<int> joints; // Indices of skeleton nodes
598
599 Value extras;
600 ExtensionMap extensions;
601
602 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
603 std::string extras_json_string;
604 std::string extensions_json_string;
605
606 Skin() {
607 inverseBindMatrices = -1;
608 skeleton = -1;
609 }
610 DEFAULT_METHODS(Skin)
611 bool operator==(const Skin &) const;
612};
613
614struct Sampler {
615 std::string name;
616 // glTF 2.0 spec does not define default value for `minFilter` and
617 // `magFilter`. Set -1 in TinyGLTF(issue #186)
618 int minFilter =
619 -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR",
620 // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST",
621 // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"]
622 int magFilter =
623 -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"]
624 int wrapS =
625 TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
626 // "REPEAT"], default "REPEAT"
627 int wrapT =
628 TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
629 // "REPEAT"], default "REPEAT"
630 // int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently
631 // not used.
632
633 Value extras;
634 ExtensionMap extensions;
635
636 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
637 std::string extras_json_string;
638 std::string extensions_json_string;
639
640 Sampler()
641 : minFilter(-1),
642 magFilter(-1),
643 wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT),
644 wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
645 DEFAULT_METHODS(Sampler)
646 bool operator==(const Sampler &) const;
647};
648
649struct Image {
650 std::string name;
651 int width;
652 int height;
653 int component;
654 int bits; // bit depth per channel. 8(byte), 16 or 32.
655 int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually
656 // UBYTE(bits = 8) or USHORT(bits = 16)
657 std::vector<unsigned char> image;
658 int bufferView; // (required if no uri)
659 std::string mimeType; // (required if no uri) ["image/jpeg", "image/png",
660 // "image/bmp", "image/gif"]
661 std::string uri; // (required if no mimeType) uri is not decoded(e.g.
662 // whitespace may be represented as %20)
663 Value extras;
664 ExtensionMap extensions;
665
666 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
667 std::string extras_json_string;
668 std::string extensions_json_string;
669
670 // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg
671 // compressed for "image/jpeg" mime) This feature is good if you use custom
672 // image loader function. (e.g. delayed decoding of images for faster glTF
673 // parsing) Default parser for Image does not provide as-is loading feature at
674 // the moment. (You can manipulate this by providing your own LoadImageData
675 // function)
676 bool as_is;
677
678 Image() : as_is(false) {
679 bufferView = -1;
680 width = -1;
681 height = -1;
682 component = -1;
683 bits = -1;
684 pixel_type = -1;
685 }
686 DEFAULT_METHODS(Image)
687
688 bool operator==(const Image &) const;
689};
690
691struct Texture {
692 std::string name;
693
694 int sampler;
695 int source;
696 Value extras;
697 ExtensionMap extensions;
698
699 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
700 std::string extras_json_string;
701 std::string extensions_json_string;
702
703 Texture() : sampler(-1), source(-1) {}
704 DEFAULT_METHODS(Texture)
705
706 bool operator==(const Texture &) const;
707};
708
709struct TextureInfo {
710 int index = -1; // required.
711 int texCoord; // The set index of texture's TEXCOORD attribute used for
712 // texture coordinate mapping.
713
714 Value extras;
715 ExtensionMap extensions;
716
717 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
718 std::string extras_json_string;
719 std::string extensions_json_string;
720
721 TextureInfo() : index(-1), texCoord(0) {}
722 DEFAULT_METHODS(TextureInfo)
723 bool operator==(const TextureInfo &) const;
724};
725
726struct NormalTextureInfo {
727 int index = -1; // required
728 int texCoord; // The set index of texture's TEXCOORD attribute used for
729 // texture coordinate mapping.
730 double scale; // scaledNormal = normalize((<sampled normal texture value>
731 // * 2.0 - 1.0) * vec3(<normal scale>, <normal scale>, 1.0))
732
733 Value extras;
734 ExtensionMap extensions;
735
736 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
737 std::string extras_json_string;
738 std::string extensions_json_string;
739
740 NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {}
741 DEFAULT_METHODS(NormalTextureInfo)
742 bool operator==(const NormalTextureInfo &) const;
743};
744
745struct OcclusionTextureInfo {
746 int index = -1; // required
747 int texCoord; // The set index of texture's TEXCOORD attribute used for
748 // texture coordinate mapping.
749 double strength; // occludedColor = lerp(color, color * <sampled occlusion
750 // texture value>, <occlusion strength>)
751
752 Value extras;
753 ExtensionMap extensions;
754
755 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
756 std::string extras_json_string;
757 std::string extensions_json_string;
758
759 OcclusionTextureInfo() : index(-1), texCoord(0), strength(1.0) {}
760 DEFAULT_METHODS(OcclusionTextureInfo)
761 bool operator==(const OcclusionTextureInfo &) const;
762};
763
764// pbrMetallicRoughness class defined in glTF 2.0 spec.
765struct PbrMetallicRoughness {
766 std::vector<double> baseColorFactor; // len = 4. default [1,1,1,1]
767 TextureInfo baseColorTexture;
768 double metallicFactor; // default 1
769 double roughnessFactor; // default 1
770 TextureInfo metallicRoughnessTexture;
771
772 Value extras;
773 ExtensionMap extensions;
774
775 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
776 std::string extras_json_string;
777 std::string extensions_json_string;
778
779 PbrMetallicRoughness()
780 : baseColorFactor(std::vector<double>{1.0, 1.0, 1.0, 1.0}),
781 metallicFactor(1.0),
782 roughnessFactor(1.0) {}
783 DEFAULT_METHODS(PbrMetallicRoughness)
784 bool operator==(const PbrMetallicRoughness &) const;
785};
786
787// Each extension should be stored in a ParameterMap.
788// members not in the values could be included in the ParameterMap
789// to keep a single material model
790struct Material {
791 std::string name;
792
793 std::vector<double> emissiveFactor; // length 3. default [0, 0, 0]
794 std::string alphaMode; // default "OPAQUE"
795 double alphaCutoff; // default 0.5
796 bool doubleSided; // default false;
797
798 PbrMetallicRoughness pbrMetallicRoughness;
799
800 NormalTextureInfo normalTexture;
801 OcclusionTextureInfo occlusionTexture;
802 TextureInfo emissiveTexture;
803
804 // For backward compatibility
805 // TODO(syoyo): Remove `values` and `additionalValues` in the next release.
806 ParameterMap values;
807 ParameterMap additionalValues;
808
809 ExtensionMap extensions;
810 Value extras;
811
812 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
813 std::string extras_json_string;
814 std::string extensions_json_string;
815
816 Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {}
817 DEFAULT_METHODS(Material)
818
819 bool operator==(const Material &) const;
820};
821
822struct BufferView {
823 std::string name;
824 int buffer{-1}; // Required
825 size_t byteOffset{0}; // minimum 0, default 0
826 size_t byteLength{0}; // required, minimum 1. 0 = invalid
827 size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 =
828 // understood to be tightly packed
829 int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices
830 // or atttribs. Could be 0 for other data
831 Value extras;
832 ExtensionMap extensions;
833
834 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
835 std::string extras_json_string;
836 std::string extensions_json_string;
837
838 bool dracoDecoded{false}; // Flag indicating this has been draco decoded
839
840 BufferView()
841 : buffer(-1),
842 byteOffset(0),
843 byteLength(0),
844 byteStride(0),
845 target(0),
846 dracoDecoded(false) {}
847 DEFAULT_METHODS(BufferView)
848 bool operator==(const BufferView &) const;
849};
850
851struct Accessor {
852 int bufferView; // optional in spec but required here since sparse accessor
853 // are not supported
854 std::string name;
855 size_t byteOffset;
856 bool normalized; // optional.
857 int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_***
858 size_t count; // required
859 int type; // (required) One of TINYGLTF_TYPE_*** ..
860 Value extras;
861 ExtensionMap extensions;
862
863 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
864 std::string extras_json_string;
865 std::string extensions_json_string;
866
867 std::vector<double>
868 minValues; // optional. integer value is promoted to double
869 std::vector<double>
870 maxValues; // optional. integer value is promoted to double
871
872 struct {
873 int count;
874 bool isSparse;
875 struct {
876 int byteOffset;
877 int bufferView;
878 int componentType; // a TINYGLTF_COMPONENT_TYPE_ value
879 } indices;
880 struct {
881 int bufferView;
882 int byteOffset;
883 } values;
884 } sparse;
885
886 ///
887 /// Utility function to compute byteStride for a given bufferView object.
888 /// Returns -1 upon invalid glTF value or parameter configuration.
889 ///
890 int ByteStride(const BufferView &bufferViewObject) const {
891 if (bufferViewObject.byteStride == 0) {
892 // Assume data is tightly packed.
893 int componentSizeInBytes =
894 GetComponentSizeInBytes(componentType: static_cast<uint32_t>(componentType));
895 if (componentSizeInBytes <= 0) {
896 return -1;
897 }
898
899 int numComponents = GetNumComponentsInType(ty: static_cast<uint32_t>(type));
900 if (numComponents <= 0) {
901 return -1;
902 }
903
904 return componentSizeInBytes * numComponents;
905 } else {
906 // Check if byteStride is a mulple of the size of the accessor's component
907 // type.
908 int componentSizeInBytes =
909 GetComponentSizeInBytes(componentType: static_cast<uint32_t>(componentType));
910 if (componentSizeInBytes <= 0) {
911 return -1;
912 }
913
914 if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) {
915 return -1;
916 }
917 return static_cast<int>(bufferViewObject.byteStride);
918 }
919
920 // unreachable return 0;
921 }
922
923 Accessor()
924 : bufferView(-1),
925 byteOffset(0),
926 normalized(false),
927 componentType(-1),
928 count(0),
929 type(-1) {
930 sparse.isSparse = false;
931 }
932 DEFAULT_METHODS(Accessor)
933 bool operator==(const tinygltf::Accessor &) const;
934};
935
936struct PerspectiveCamera {
937 double aspectRatio; // min > 0
938 double yfov; // required. min > 0
939 double zfar; // min > 0
940 double znear; // required. min > 0
941
942 PerspectiveCamera()
943 : aspectRatio(0.0),
944 yfov(0.0),
945 zfar(0.0) // 0 = use infinite projecton matrix
946 ,
947 znear(0.0) {}
948 DEFAULT_METHODS(PerspectiveCamera)
949 bool operator==(const PerspectiveCamera &) const;
950
951 ExtensionMap extensions;
952 Value extras;
953
954 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
955 std::string extras_json_string;
956 std::string extensions_json_string;
957};
958
959struct OrthographicCamera {
960 double xmag; // required. must not be zero.
961 double ymag; // required. must not be zero.
962 double zfar; // required. `zfar` must be greater than `znear`.
963 double znear; // required
964
965 OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {}
966 DEFAULT_METHODS(OrthographicCamera)
967 bool operator==(const OrthographicCamera &) const;
968
969 ExtensionMap extensions;
970 Value extras;
971
972 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
973 std::string extras_json_string;
974 std::string extensions_json_string;
975};
976
977struct Camera {
978 std::string type; // required. "perspective" or "orthographic"
979 std::string name;
980
981 PerspectiveCamera perspective;
982 OrthographicCamera orthographic;
983
984 Camera() {}
985 DEFAULT_METHODS(Camera)
986 bool operator==(const Camera &) const;
987
988 ExtensionMap extensions;
989 Value extras;
990
991 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
992 std::string extras_json_string;
993 std::string extensions_json_string;
994};
995
996struct Primitive {
997 std::map<std::string, int> attributes; // (required) A dictionary object of
998 // integer, where each integer
999 // is the index of the accessor
1000 // containing an attribute.
1001 int material; // The index of the material to apply to this primitive
1002 // when rendering.
1003 int indices; // The index of the accessor that contains the indices.
1004 int mode; // one of TINYGLTF_MODE_***
1005 std::vector<std::map<std::string, int> > targets; // array of morph targets,
1006 // where each target is a dict with attribues in ["POSITION, "NORMAL",
1007 // "TANGENT"] pointing
1008 // to their corresponding accessors
1009 ExtensionMap extensions;
1010 Value extras;
1011
1012 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1013 std::string extras_json_string;
1014 std::string extensions_json_string;
1015
1016 Primitive() {
1017 material = -1;
1018 indices = -1;
1019 mode = -1;
1020 }
1021 DEFAULT_METHODS(Primitive)
1022 bool operator==(const Primitive &) const;
1023};
1024
1025struct Mesh {
1026 std::string name;
1027 std::vector<Primitive> primitives;
1028 std::vector<double> weights; // weights to be applied to the Morph Targets
1029 ExtensionMap extensions;
1030 Value extras;
1031
1032 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1033 std::string extras_json_string;
1034 std::string extensions_json_string;
1035
1036 Mesh() = default;
1037 DEFAULT_METHODS(Mesh)
1038 bool operator==(const Mesh &) const;
1039};
1040
1041class Node {
1042 public:
1043 Node() : camera(-1), skin(-1), mesh(-1) {}
1044
1045 DEFAULT_METHODS(Node)
1046
1047 bool operator==(const Node &) const;
1048
1049 int camera; // the index of the camera referenced by this node
1050
1051 std::string name;
1052 int skin;
1053 int mesh;
1054 std::vector<int> children;
1055 std::vector<double> rotation; // length must be 0 or 4
1056 std::vector<double> scale; // length must be 0 or 3
1057 std::vector<double> translation; // length must be 0 or 3
1058 std::vector<double> matrix; // length must be 0 or 16
1059 std::vector<double> weights; // The weights of the instantiated Morph Target
1060
1061 ExtensionMap extensions;
1062 Value extras;
1063
1064 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1065 std::string extras_json_string;
1066 std::string extensions_json_string;
1067};
1068
1069struct Buffer {
1070 std::string name;
1071 std::vector<unsigned char> data;
1072 std::string
1073 uri; // considered as required here but not in the spec (need to clarify)
1074 // uri is not decoded(e.g. whitespace may be represented as %20)
1075 Value extras;
1076 ExtensionMap extensions;
1077
1078 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1079 std::string extras_json_string;
1080 std::string extensions_json_string;
1081
1082 Buffer() = default;
1083 DEFAULT_METHODS(Buffer)
1084 bool operator==(const Buffer &) const;
1085};
1086
1087struct Asset {
1088 std::string version = "2.0"; // required
1089 std::string generator;
1090 std::string minVersion;
1091 std::string copyright;
1092 ExtensionMap extensions;
1093 Value extras;
1094
1095 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1096 std::string extras_json_string;
1097 std::string extensions_json_string;
1098
1099 Asset() = default;
1100 DEFAULT_METHODS(Asset)
1101 bool operator==(const Asset &) const;
1102};
1103
1104struct Scene {
1105 std::string name;
1106 std::vector<int> nodes;
1107
1108 ExtensionMap extensions;
1109 Value extras;
1110
1111 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1112 std::string extras_json_string;
1113 std::string extensions_json_string;
1114
1115 Scene() = default;
1116 DEFAULT_METHODS(Scene)
1117 bool operator==(const Scene &) const;
1118};
1119
1120struct SpotLight {
1121 double innerConeAngle;
1122 double outerConeAngle;
1123
1124 SpotLight() : innerConeAngle(0.0), outerConeAngle(0.7853981634) {}
1125 DEFAULT_METHODS(SpotLight)
1126 bool operator==(const SpotLight &) const;
1127
1128 ExtensionMap extensions;
1129 Value extras;
1130
1131 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1132 std::string extras_json_string;
1133 std::string extensions_json_string;
1134};
1135
1136struct Light {
1137 std::string name;
1138 std::vector<double> color;
1139 double intensity{1.0};
1140 std::string type;
1141 double range{0.0}; // 0.0 = inifinite
1142 SpotLight spot;
1143
1144 Light() : intensity(1.0), range(0.0) {}
1145 DEFAULT_METHODS(Light)
1146
1147 bool operator==(const Light &) const;
1148
1149 ExtensionMap extensions;
1150 Value extras;
1151
1152 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1153 std::string extras_json_string;
1154 std::string extensions_json_string;
1155};
1156
1157class Model {
1158 public:
1159 Model() = default;
1160 DEFAULT_METHODS(Model)
1161
1162 bool operator==(const Model &) const;
1163
1164 std::vector<Accessor> accessors;
1165 std::vector<Animation> animations;
1166 std::vector<Buffer> buffers;
1167 std::vector<BufferView> bufferViews;
1168 std::vector<Material> materials;
1169 std::vector<Mesh> meshes;
1170 std::vector<Node> nodes;
1171 std::vector<Texture> textures;
1172 std::vector<Image> images;
1173 std::vector<Skin> skins;
1174 std::vector<Sampler> samplers;
1175 std::vector<Camera> cameras;
1176 std::vector<Scene> scenes;
1177 std::vector<Light> lights;
1178
1179 int defaultScene = -1;
1180 std::vector<std::string> extensionsUsed;
1181 std::vector<std::string> extensionsRequired;
1182
1183 Asset asset;
1184
1185 Value extras;
1186 ExtensionMap extensions;
1187
1188 // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
1189 std::string extras_json_string;
1190 std::string extensions_json_string;
1191};
1192
1193enum SectionCheck {
1194 NO_REQUIRE = 0x00,
1195 REQUIRE_VERSION = 0x01,
1196 REQUIRE_SCENE = 0x02,
1197 REQUIRE_SCENES = 0x04,
1198 REQUIRE_NODES = 0x08,
1199 REQUIRE_ACCESSORS = 0x10,
1200 REQUIRE_BUFFERS = 0x20,
1201 REQUIRE_BUFFER_VIEWS = 0x40,
1202 REQUIRE_ALL = 0x7f
1203};
1204
1205///
1206/// LoadImageDataFunction type. Signature for custom image loading callbacks.
1207///
1208typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *,
1209 std::string *, int, int,
1210 const unsigned char *, int,
1211 void *user_pointer);
1212
1213///
1214/// WriteImageDataFunction type. Signature for custom image writing callbacks.
1215///
1216typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *,
1217 Image *, bool, void *);
1218
1219#ifndef TINYGLTF_NO_STB_IMAGE
1220// Declaration of default image loader callback
1221bool LoadImageData(Image *image, const int image_idx, std::string *err,
1222 std::string *warn, int req_width, int req_height,
1223 const unsigned char *bytes, int size, void *);
1224#endif
1225
1226#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
1227// Declaration of default image writer callback
1228bool WriteImageData(const std::string *basepath, const std::string *filename,
1229 Image *image, bool embedImages, void *);
1230#endif
1231
1232///
1233/// FilExistsFunction type. Signature for custom filesystem callbacks.
1234///
1235typedef bool (*FileExistsFunction)(const std::string &abs_filename, void *);
1236
1237///
1238/// ExpandFilePathFunction type. Signature for custom filesystem callbacks.
1239///
1240typedef std::string (*ExpandFilePathFunction)(const std::string &, void *);
1241
1242///
1243/// ReadWholeFileFunction type. Signature for custom filesystem callbacks.
1244///
1245typedef bool (*ReadWholeFileFunction)(std::vector<unsigned char> *,
1246 std::string *, const std::string &,
1247 void *);
1248
1249///
1250/// WriteWholeFileFunction type. Signature for custom filesystem callbacks.
1251///
1252typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &,
1253 const std::vector<unsigned char> &,
1254 void *);
1255
1256///
1257/// A structure containing all required filesystem callbacks and a pointer to
1258/// their user data.
1259///
1260struct FsCallbacks {
1261 FileExistsFunction FileExists;
1262 ExpandFilePathFunction ExpandFilePath;
1263 ReadWholeFileFunction ReadWholeFile;
1264 WriteWholeFileFunction WriteWholeFile;
1265
1266 void *user_data; // An argument that is passed to all fs callbacks
1267};
1268
1269#ifndef TINYGLTF_NO_FS
1270// Declaration of default filesystem callbacks
1271
1272bool FileExists(const std::string &abs_filename, void *);
1273
1274///
1275/// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to
1276/// `C:\\Users\\tinygltf\\AppData`)
1277///
1278/// @param[in] filepath File path string. Assume UTF-8
1279/// @param[in] userdata User data. Set to `nullptr` if you don't need it.
1280///
1281std::string ExpandFilePath(const std::string &filepath, void *userdata);
1282
1283bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
1284 const std::string &filepath, void *);
1285
1286bool WriteWholeFile(std::string *err, const std::string &filepath,
1287 const std::vector<unsigned char> &contents, void *);
1288#endif
1289
1290///
1291/// glTF Parser/Serialier context.
1292///
1293class TinyGLTF {
1294 public:
1295#ifdef __clang__
1296#pragma clang diagnostic push
1297#pragma clang diagnostic ignored "-Wc++98-compat"
1298#endif
1299
1300 TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {}
1301
1302#ifdef __clang__
1303#pragma clang diagnostic pop
1304#endif
1305
1306 ~TinyGLTF() {}
1307
1308 ///
1309 /// Loads glTF ASCII asset from a file.
1310 /// Set warning message to `warn` for example it fails to load asserts.
1311 /// Returns false and set error string to `err` if there's an error.
1312 ///
1313 bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn,
1314 const std::string &filename,
1315 unsigned int check_sections = REQUIRE_VERSION);
1316
1317 ///
1318 /// Loads glTF ASCII asset from string(memory).
1319 /// `length` = strlen(str);
1320 /// `base_dir` is a search path of glTF asset(e.g. images). Path Must be an
1321 /// expanded path (e.g. no tilde(`~`), no environment variables). Set warning
1322 /// message to `warn` for example it fails to load asserts. Returns false and
1323 /// set error string to `err` if there's an error.
1324 ///
1325 bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn,
1326 const char *str, const unsigned int length,
1327 const std::string &base_dir,
1328 unsigned int check_sections = REQUIRE_VERSION);
1329
1330 ///
1331 /// Loads glTF binary asset from a file.
1332 /// Set warning message to `warn` for example it fails to load asserts.
1333 /// Returns false and set error string to `err` if there's an error.
1334 ///
1335 bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn,
1336 const std::string &filename,
1337 unsigned int check_sections = REQUIRE_VERSION);
1338
1339 ///
1340 /// Loads glTF binary asset from memory.
1341 /// `length` = strlen(str);
1342 /// `base_dir` is a search path of glTF asset(e.g. images). Path Must be an
1343 /// expanded path (e.g. no tilde(`~`), no environment variables).
1344 /// Set warning message to `warn` for example it fails to load asserts.
1345 /// Returns false and set error string to `err` if there's an error.
1346 ///
1347 bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn,
1348 const unsigned char *bytes,
1349 const unsigned int length,
1350 const std::string &base_dir = "",
1351 unsigned int check_sections = REQUIRE_VERSION);
1352
1353 ///
1354 /// Write glTF to stream, buffers and images will be embeded
1355 ///
1356 bool WriteGltfSceneToStream(Model *model, std::ostream &stream,
1357 bool prettyPrint, bool writeBinary);
1358
1359 ///
1360 /// Write glTF to file.
1361 ///
1362 bool WriteGltfSceneToFile(Model *model, const std::string &filename,
1363 bool embedImages, bool embedBuffers,
1364 bool prettyPrint, bool writeBinary);
1365
1366 ///
1367 /// Set callback to use for loading image data
1368 ///
1369 void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
1370
1371 ///
1372 /// Unset(remove) callback of loading image data
1373 ///
1374 void RemoveImageLoader();
1375
1376 ///
1377 /// Set callback to use for writing image data
1378 ///
1379 void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
1380
1381 ///
1382 /// Set callbacks to use for filesystem (fs) access and their user data
1383 ///
1384 void SetFsCallbacks(FsCallbacks callbacks);
1385
1386 ///
1387 /// Set serializing default values(default = false).
1388 /// When true, default values are force serialized to .glTF.
1389 /// This may be helpfull if you want to serialize a full description of glTF
1390 /// data.
1391 ///
1392 /// TODO(LTE): Supply parsing option as function arguments to
1393 /// `LoadASCIIFromFile()` and others, not by a class method
1394 ///
1395 void SetSerializeDefaultValues(const bool enabled) {
1396 serialize_default_values_ = enabled;
1397 }
1398
1399 bool GetSerializeDefaultValues() const { return serialize_default_values_; }
1400
1401 ///
1402 /// Store original JSON string for `extras` and `extensions`.
1403 /// This feature will be useful when the user want to reconstruct custom data
1404 /// structure from JSON string.
1405 ///
1406 void SetStoreOriginalJSONForExtrasAndExtensions(const bool enabled) {
1407 store_original_json_for_extras_and_extensions_ = enabled;
1408 }
1409
1410 bool GetStoreOriginalJSONForExtrasAndExtensions() const {
1411 return store_original_json_for_extras_and_extensions_;
1412 }
1413
1414 ///
1415 /// Specify whether preserve image channales when loading images or not.
1416 /// (Not effective when the user suppy their own LoadImageData callbacks)
1417 ///
1418 void SetPreserveImageChannels(bool onoff) {
1419 preserve_image_channels_ = onoff;
1420 }
1421
1422 bool GetPreserveImageChannels() const { return preserve_image_channels_; }
1423
1424 private:
1425 ///
1426 /// Loads glTF asset from string(memory).
1427 /// `length` = strlen(str);
1428 /// Set warning message to `warn` for example it fails to load asserts
1429 /// Returns false and set error string to `err` if there's an error.
1430 ///
1431 bool LoadFromString(Model *model, std::string *err, std::string *warn,
1432 const char *str, const unsigned int length,
1433 const std::string &base_dir, unsigned int check_sections);
1434
1435 const unsigned char *bin_data_ = nullptr;
1436 size_t bin_size_ = 0;
1437 bool is_binary_ = false;
1438
1439 bool serialize_default_values_ = false; ///< Serialize default values?
1440
1441 bool store_original_json_for_extras_and_extensions_ = false;
1442
1443 bool preserve_image_channels_ = false; /// Default false(expand channels to
1444 /// RGBA) for backward compatibility.
1445
1446 // Warning & error messages
1447 std::string warn_;
1448 std::string err_;
1449
1450 FsCallbacks fs = {
1451#ifndef TINYGLTF_NO_FS
1452 .FileExists: &tinygltf::FileExists, .ExpandFilePath: &tinygltf::ExpandFilePath,
1453 .ReadWholeFile: &tinygltf::ReadWholeFile, .WriteWholeFile: &tinygltf::WriteWholeFile,
1454
1455 .user_data: nullptr // Fs callback user data
1456#else
1457 nullptr, nullptr, nullptr, nullptr,
1458
1459 nullptr // Fs callback user data
1460#endif
1461 };
1462
1463 LoadImageDataFunction LoadImageData =
1464#ifndef TINYGLTF_NO_STB_IMAGE
1465 &tinygltf::LoadImageData;
1466#else
1467 nullptr;
1468#endif
1469 void *load_image_user_data_{nullptr};
1470 bool user_image_loader_{false};
1471
1472 WriteImageDataFunction WriteImageData =
1473#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
1474 &tinygltf::WriteImageData;
1475#else
1476 nullptr;
1477#endif
1478 void *write_image_user_data_{nullptr};
1479};
1480
1481#ifdef __clang__
1482#pragma clang diagnostic pop // -Wpadded
1483#endif
1484
1485} // namespace tinygltf
1486
1487#endif // TINY_GLTF_H_
1488
1489#if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__)
1490#include <algorithm>
1491//#include <cassert>
1492#ifndef TINYGLTF_NO_FS
1493#include <cstdio>
1494#include <fstream>
1495#endif
1496#include <sstream>
1497
1498#ifdef __clang__
1499// Disable some warnings for external files.
1500#pragma clang diagnostic push
1501#pragma clang diagnostic ignored "-Wfloat-equal"
1502#pragma clang diagnostic ignored "-Wexit-time-destructors"
1503#pragma clang diagnostic ignored "-Wconversion"
1504#pragma clang diagnostic ignored "-Wold-style-cast"
1505#pragma clang diagnostic ignored "-Wglobal-constructors"
1506#if __has_warning("-Wreserved-id-macro")
1507#pragma clang diagnostic ignored "-Wreserved-id-macro"
1508#endif
1509#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
1510#pragma clang diagnostic ignored "-Wpadded"
1511#pragma clang diagnostic ignored "-Wc++98-compat"
1512#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
1513#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
1514#pragma clang diagnostic ignored "-Wswitch-enum"
1515#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
1516#pragma clang diagnostic ignored "-Wweak-vtables"
1517#pragma clang diagnostic ignored "-Wcovered-switch-default"
1518#if __has_warning("-Wdouble-promotion")
1519#pragma clang diagnostic ignored "-Wdouble-promotion"
1520#endif
1521#if __has_warning("-Wcomma")
1522#pragma clang diagnostic ignored "-Wcomma"
1523#endif
1524#if __has_warning("-Wzero-as-null-pointer-constant")
1525#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
1526#endif
1527#if __has_warning("-Wcast-qual")
1528#pragma clang diagnostic ignored "-Wcast-qual"
1529#endif
1530#if __has_warning("-Wmissing-variable-declarations")
1531#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
1532#endif
1533#if __has_warning("-Wmissing-prototypes")
1534#pragma clang diagnostic ignored "-Wmissing-prototypes"
1535#endif
1536#if __has_warning("-Wcast-align")
1537#pragma clang diagnostic ignored "-Wcast-align"
1538#endif
1539#if __has_warning("-Wnewline-eof")
1540#pragma clang diagnostic ignored "-Wnewline-eof"
1541#endif
1542#if __has_warning("-Wunused-parameter")
1543#pragma clang diagnostic ignored "-Wunused-parameter"
1544#endif
1545#if __has_warning("-Wmismatched-tags")
1546#pragma clang diagnostic ignored "-Wmismatched-tags"
1547#endif
1548#if __has_warning("-Wextra-semi-stmt")
1549#pragma clang diagnostic ignored "-Wextra-semi-stmt"
1550#endif
1551#endif
1552
1553// Disable GCC warnigs
1554#ifdef __GNUC__
1555#pragma GCC diagnostic push
1556#pragma GCC diagnostic ignored "-Wtype-limits"
1557#endif // __GNUC__
1558
1559#ifndef TINYGLTF_NO_INCLUDE_JSON
1560#ifndef TINYGLTF_USE_RAPIDJSON
1561#include "json.hpp"
1562#else
1563#ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON
1564#include "document.h"
1565#include "prettywriter.h"
1566#include "rapidjson.h"
1567#include "stringbuffer.h"
1568#include "writer.h"
1569#endif
1570#endif
1571#endif
1572
1573#ifdef TINYGLTF_ENABLE_DRACO
1574#include "draco/compression/decode.h"
1575#include "draco/core/decoder_buffer.h"
1576#endif
1577
1578#ifndef TINYGLTF_NO_STB_IMAGE
1579#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE
1580#include "stb_image.h"
1581#endif
1582#endif
1583
1584#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
1585#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
1586#include "stb_image_write.h"
1587#endif
1588#endif
1589
1590#ifdef __clang__
1591#pragma clang diagnostic pop
1592#endif
1593
1594#ifdef __GNUC__
1595#pragma GCC diagnostic pop
1596#endif
1597
1598#ifdef _WIN32
1599
1600// issue 143.
1601// Define NOMINMAX to avoid min/max defines,
1602// but undef it after included windows.h
1603#ifndef NOMINMAX
1604#define TINYGLTF_INTERNAL_NOMINMAX
1605#define NOMINMAX
1606#endif
1607
1608#ifndef WIN32_LEAN_AND_MEAN
1609#define WIN32_LEAN_AND_MEAN
1610#define TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN
1611#endif
1612#include <windows.h> // include API for expanding a file path
1613
1614#ifdef TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN
1615#undef WIN32_LEAN_AND_MEAN
1616#endif
1617
1618#if defined(TINYGLTF_INTERNAL_NOMINMAX)
1619#undef NOMINMAX
1620#endif
1621
1622#if defined(__GLIBCXX__) // mingw
1623
1624#include <fcntl.h> // _O_RDONLY
1625
1626#include <ext/stdio_filebuf.h> // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
1627
1628#endif
1629
1630#elif !defined(__ANDROID__) && !defined(__OpenBSD__)
1631//#include <wordexp.h>
1632#endif
1633
1634#if defined(__sparcv9) || defined(__powerpc__)
1635// Big endian
1636#else
1637#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
1638#define TINYGLTF_LITTLE_ENDIAN 1
1639#endif
1640#endif
1641
1642namespace {
1643#ifdef TINYGLTF_USE_RAPIDJSON
1644
1645#ifdef TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
1646// This uses the RapidJSON CRTAllocator. It is thread safe and multiple
1647// documents may be active at once.
1648using json =
1649 rapidjson::GenericValue<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
1650using json_const_iterator = json::ConstMemberIterator;
1651using json_const_array_iterator = json const *;
1652using JsonDocument =
1653 rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
1654rapidjson::CrtAllocator s_CrtAllocator; // stateless and thread safe
1655rapidjson::CrtAllocator &GetAllocator() { return s_CrtAllocator; }
1656#else
1657// This uses the default RapidJSON MemoryPoolAllocator. It is very fast, but
1658// not thread safe. Only a single JsonDocument may be active at any one time,
1659// meaning only a single gltf load/save can be active any one time.
1660using json = rapidjson::Value;
1661using json_const_iterator = json::ConstMemberIterator;
1662using json_const_array_iterator = json const *;
1663rapidjson::Document *s_pActiveDocument = nullptr;
1664rapidjson::Document::AllocatorType &GetAllocator() {
1665 assert(s_pActiveDocument); // Root json node must be JsonDocument type
1666 return s_pActiveDocument->GetAllocator();
1667}
1668
1669#ifdef __clang__
1670#pragma clang diagnostic push
1671// Suppress JsonDocument(JsonDocument &&rhs) noexcept
1672#pragma clang diagnostic ignored "-Wunused-member-function"
1673#endif
1674
1675struct JsonDocument : public rapidjson::Document {
1676 JsonDocument() {
1677 assert(s_pActiveDocument ==
1678 nullptr); // When using default allocator, only one document can be
1679 // active at a time, if you need multiple active at once,
1680 // define TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
1681 s_pActiveDocument = this;
1682 }
1683 JsonDocument(const JsonDocument &) = delete;
1684 JsonDocument(JsonDocument &&rhs) noexcept
1685 : rapidjson::Document(std::move(rhs)) {
1686 s_pActiveDocument = this;
1687 rhs.isNil = true;
1688 }
1689 ~JsonDocument() {
1690 if (!isNil) {
1691 s_pActiveDocument = nullptr;
1692 }
1693 }
1694
1695 private:
1696 bool isNil = false;
1697};
1698
1699#ifdef __clang__
1700#pragma clang diagnostic pop
1701#endif
1702
1703#endif // TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
1704
1705#else
1706using nlohmann::json;
1707using json_const_iterator = json::const_iterator;
1708using json_const_array_iterator = json_const_iterator;
1709using JsonDocument = json;
1710#endif
1711
1712void JsonParse(JsonDocument &doc, const char *str, size_t length,
1713 bool throwExc = false) {
1714#ifdef TINYGLTF_USE_RAPIDJSON
1715 (void)throwExc;
1716 doc.Parse(str, length);
1717#else
1718 doc = json::parse(first: str, last: str + length, cb: nullptr, allow_exceptions: throwExc);
1719#endif
1720}
1721} // namespace
1722
1723#ifdef __APPLE__
1724#include "TargetConditionals.h"
1725#endif
1726
1727#ifdef __clang__
1728#pragma clang diagnostic push
1729#pragma clang diagnostic ignored "-Wc++98-compat"
1730#endif
1731
1732namespace tinygltf {
1733
1734///
1735/// Internal LoadImageDataOption struct.
1736/// This struct is passed through `user_pointer` in LoadImageData.
1737/// The struct is not passed when the user supply their own LoadImageData
1738/// callbacks.
1739///
1740struct LoadImageDataOption {
1741 // true: preserve image channels(e.g. load as RGB image if the image has RGB
1742 // channels) default `false`(channels are expanded to RGBA for backward
1743 // compatiblity).
1744 bool preserve_channels{false};
1745};
1746
1747// Equals function for Value, for recursivity
1748static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) {
1749 if (one.Type() != other.Type()) return false;
1750
1751 switch (one.Type()) {
1752 case NULL_TYPE:
1753 return true;
1754 case BOOL_TYPE:
1755 return one.Get<bool>() == other.Get<bool>();
1756 case REAL_TYPE:
1757 return TINYGLTF_DOUBLE_EQUAL(one.Get<double>(), other.Get<double>());
1758 case INT_TYPE:
1759 return one.Get<int>() == other.Get<int>();
1760 case OBJECT_TYPE: {
1761 auto oneObj = one.Get<tinygltf::Value::Object>();
1762 auto otherObj = other.Get<tinygltf::Value::Object>();
1763 if (oneObj.size() != otherObj.size()) return false;
1764 for (auto &it : oneObj) {
1765 auto otherIt = otherObj.find(k: it.first);
1766 if (otherIt == otherObj.end()) return false;
1767
1768 if (!Equals(one: it.second, other: otherIt->second)) return false;
1769 }
1770 return true;
1771 }
1772 case ARRAY_TYPE: {
1773 if (one.Size() != other.Size()) return false;
1774 for (int i = 0; i < int(one.Size()); ++i)
1775 if (!Equals(one: one.Get(idx: i), other: other.Get(idx: i))) return false;
1776 return true;
1777 }
1778 case STRING_TYPE:
1779 return one.Get<std::string>() == other.Get<std::string>();
1780 case BINARY_TYPE:
1781 return one.Get<std::vector<unsigned char> >() ==
1782 other.Get<std::vector<unsigned char> >();
1783 default: {
1784 // unhandled type
1785 return false;
1786 }
1787 }
1788}
1789
1790// Equals function for std::vector<double> using TINYGLTF_DOUBLE_EPSILON
1791static bool Equals(const std::vector<double> &one,
1792 const std::vector<double> &other) {
1793 if (one.size() != other.size()) return false;
1794 for (int i = 0; i < int(one.size()); ++i) {
1795 if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false;
1796 }
1797 return true;
1798}
1799
1800bool Accessor::operator==(const Accessor &other) const {
1801 return this->bufferView == other.bufferView &&
1802 this->byteOffset == other.byteOffset &&
1803 this->componentType == other.componentType &&
1804 this->count == other.count && this->extensions == other.extensions &&
1805 this->extras == other.extras &&
1806 Equals(one: this->maxValues, other: other.maxValues) &&
1807 Equals(one: this->minValues, other: other.minValues) && this->name == other.name &&
1808 this->normalized == other.normalized && this->type == other.type;
1809}
1810bool Animation::operator==(const Animation &other) const {
1811 return this->channels == other.channels &&
1812 this->extensions == other.extensions && this->extras == other.extras &&
1813 this->name == other.name && this->samplers == other.samplers;
1814}
1815bool AnimationChannel::operator==(const AnimationChannel &other) const {
1816 return this->extensions == other.extensions && this->extras == other.extras &&
1817 this->target_node == other.target_node &&
1818 this->target_path == other.target_path &&
1819 this->sampler == other.sampler;
1820}
1821bool AnimationSampler::operator==(const AnimationSampler &other) const {
1822 return this->extras == other.extras && this->extensions == other.extensions &&
1823 this->input == other.input &&
1824 this->interpolation == other.interpolation &&
1825 this->output == other.output;
1826}
1827bool Asset::operator==(const Asset &other) const {
1828 return this->copyright == other.copyright &&
1829 this->extensions == other.extensions && this->extras == other.extras &&
1830 this->generator == other.generator &&
1831 this->minVersion == other.minVersion && this->version == other.version;
1832}
1833bool Buffer::operator==(const Buffer &other) const {
1834 return this->data == other.data && this->extensions == other.extensions &&
1835 this->extras == other.extras && this->name == other.name &&
1836 this->uri == other.uri;
1837}
1838bool BufferView::operator==(const BufferView &other) const {
1839 return this->buffer == other.buffer && this->byteLength == other.byteLength &&
1840 this->byteOffset == other.byteOffset &&
1841 this->byteStride == other.byteStride && this->name == other.name &&
1842 this->target == other.target && this->extensions == other.extensions &&
1843 this->extras == other.extras &&
1844 this->dracoDecoded == other.dracoDecoded;
1845}
1846bool Camera::operator==(const Camera &other) const {
1847 return this->name == other.name && this->extensions == other.extensions &&
1848 this->extras == other.extras &&
1849 this->orthographic == other.orthographic &&
1850 this->perspective == other.perspective && this->type == other.type;
1851}
1852bool Image::operator==(const Image &other) const {
1853 return this->bufferView == other.bufferView &&
1854 this->component == other.component &&
1855 this->extensions == other.extensions && this->extras == other.extras &&
1856 this->height == other.height && this->image == other.image &&
1857 this->mimeType == other.mimeType && this->name == other.name &&
1858 this->uri == other.uri && this->width == other.width;
1859}
1860bool Light::operator==(const Light &other) const {
1861 return Equals(one: this->color, other: other.color) && this->name == other.name &&
1862 this->type == other.type;
1863}
1864bool Material::operator==(const Material &other) const {
1865 return (this->pbrMetallicRoughness == other.pbrMetallicRoughness) &&
1866 (this->normalTexture == other.normalTexture) &&
1867 (this->occlusionTexture == other.occlusionTexture) &&
1868 (this->emissiveTexture == other.emissiveTexture) &&
1869 Equals(one: this->emissiveFactor, other: other.emissiveFactor) &&
1870 (this->alphaMode == other.alphaMode) &&
1871 TINYGLTF_DOUBLE_EQUAL(this->alphaCutoff, other.alphaCutoff) &&
1872 (this->doubleSided == other.doubleSided) &&
1873 (this->extensions == other.extensions) &&
1874 (this->extras == other.extras) && (this->values == other.values) &&
1875 (this->additionalValues == other.additionalValues) &&
1876 (this->name == other.name);
1877}
1878bool Mesh::operator==(const Mesh &other) const {
1879 return this->extensions == other.extensions && this->extras == other.extras &&
1880 this->name == other.name && Equals(one: this->weights, other: other.weights) &&
1881 this->primitives == other.primitives;
1882}
1883bool Model::operator==(const Model &other) const {
1884 return this->accessors == other.accessors &&
1885 this->animations == other.animations && this->asset == other.asset &&
1886 this->buffers == other.buffers &&
1887 this->bufferViews == other.bufferViews &&
1888 this->cameras == other.cameras &&
1889 this->defaultScene == other.defaultScene &&
1890 this->extensions == other.extensions &&
1891 this->extensionsRequired == other.extensionsRequired &&
1892 this->extensionsUsed == other.extensionsUsed &&
1893 this->extras == other.extras && this->images == other.images &&
1894 this->lights == other.lights && this->materials == other.materials &&
1895 this->meshes == other.meshes && this->nodes == other.nodes &&
1896 this->samplers == other.samplers && this->scenes == other.scenes &&
1897 this->skins == other.skins && this->textures == other.textures;
1898}
1899bool Node::operator==(const Node &other) const {
1900 return this->camera == other.camera && this->children == other.children &&
1901 this->extensions == other.extensions && this->extras == other.extras &&
1902 Equals(one: this->matrix, other: other.matrix) && this->mesh == other.mesh &&
1903 this->name == other.name && Equals(one: this->rotation, other: other.rotation) &&
1904 Equals(one: this->scale, other: other.scale) && this->skin == other.skin &&
1905 Equals(one: this->translation, other: other.translation) &&
1906 Equals(one: this->weights, other: other.weights);
1907}
1908bool SpotLight::operator==(const SpotLight &other) const {
1909 return this->extensions == other.extensions && this->extras == other.extras &&
1910 TINYGLTF_DOUBLE_EQUAL(this->innerConeAngle, other.innerConeAngle) &&
1911 TINYGLTF_DOUBLE_EQUAL(this->outerConeAngle, other.outerConeAngle);
1912}
1913bool OrthographicCamera::operator==(const OrthographicCamera &other) const {
1914 return this->extensions == other.extensions && this->extras == other.extras &&
1915 TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) &&
1916 TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) &&
1917 TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) &&
1918 TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear);
1919}
1920bool Parameter::operator==(const Parameter &other) const {
1921 if (this->bool_value != other.bool_value ||
1922 this->has_number_value != other.has_number_value)
1923 return false;
1924
1925 if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value))
1926 return false;
1927
1928 if (this->json_double_value.size() != other.json_double_value.size())
1929 return false;
1930 for (auto &it : this->json_double_value) {
1931 auto otherIt = other.json_double_value.find(k: it.first);
1932 if (otherIt == other.json_double_value.end()) return false;
1933
1934 if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false;
1935 }
1936
1937 if (!Equals(one: this->number_array, other: other.number_array)) return false;
1938
1939 if (this->string_value != other.string_value) return false;
1940
1941 return true;
1942}
1943bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const {
1944 return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) &&
1945 this->extensions == other.extensions && this->extras == other.extras &&
1946 TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) &&
1947 TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) &&
1948 TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear);
1949}
1950bool Primitive::operator==(const Primitive &other) const {
1951 return this->attributes == other.attributes && this->extras == other.extras &&
1952 this->indices == other.indices && this->material == other.material &&
1953 this->mode == other.mode && this->targets == other.targets;
1954}
1955bool Sampler::operator==(const Sampler &other) const {
1956 return this->extensions == other.extensions && this->extras == other.extras &&
1957 this->magFilter == other.magFilter &&
1958 this->minFilter == other.minFilter && this->name == other.name &&
1959 this->wrapS == other.wrapS && this->wrapT == other.wrapT;
1960
1961 // this->wrapR == other.wrapR
1962}
1963bool Scene::operator==(const Scene &other) const {
1964 return this->extensions == other.extensions && this->extras == other.extras &&
1965 this->name == other.name && this->nodes == other.nodes;
1966}
1967bool Skin::operator==(const Skin &other) const {
1968 return this->extensions == other.extensions && this->extras == other.extras &&
1969 this->inverseBindMatrices == other.inverseBindMatrices &&
1970 this->joints == other.joints && this->name == other.name &&
1971 this->skeleton == other.skeleton;
1972}
1973bool Texture::operator==(const Texture &other) const {
1974 return this->extensions == other.extensions && this->extras == other.extras &&
1975 this->name == other.name && this->sampler == other.sampler &&
1976 this->source == other.source;
1977}
1978bool TextureInfo::operator==(const TextureInfo &other) const {
1979 return this->extensions == other.extensions && this->extras == other.extras &&
1980 this->index == other.index && this->texCoord == other.texCoord;
1981}
1982bool NormalTextureInfo::operator==(const NormalTextureInfo &other) const {
1983 return this->extensions == other.extensions && this->extras == other.extras &&
1984 this->index == other.index && this->texCoord == other.texCoord &&
1985 TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale);
1986}
1987bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo &other) const {
1988 return this->extensions == other.extensions && this->extras == other.extras &&
1989 this->index == other.index && this->texCoord == other.texCoord &&
1990 TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength);
1991}
1992bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness &other) const {
1993 return this->extensions == other.extensions && this->extras == other.extras &&
1994 (this->baseColorTexture == other.baseColorTexture) &&
1995 (this->metallicRoughnessTexture == other.metallicRoughnessTexture) &&
1996 Equals(one: this->baseColorFactor, other: other.baseColorFactor) &&
1997 TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) &&
1998 TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor);
1999}
2000bool Value::operator==(const Value &other) const {
2001 return Equals(one: *this, other);
2002}
2003
2004static void swap4(unsigned int *val) {
2005#ifdef TINYGLTF_LITTLE_ENDIAN
2006 (void)val;
2007#else
2008 unsigned int tmp = *val;
2009 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
2010 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
2011
2012 dst[0] = src[3];
2013 dst[1] = src[2];
2014 dst[2] = src[1];
2015 dst[3] = src[0];
2016#endif
2017}
2018
2019static std::string JoinPath(const std::string &path0,
2020 const std::string &path1) {
2021 if (path0.empty()) {
2022 return path1;
2023 } else {
2024 // check '/'
2025 char lastChar = *path0.rbegin();
2026 if (lastChar != '/') {
2027 return path0 + std::string("/") + path1;
2028 } else {
2029 return path0 + path1;
2030 }
2031 }
2032}
2033
2034static std::string FindFile(const std::vector<std::string> &paths,
2035 const std::string &filepath, FsCallbacks *fs) {
2036 if (fs == nullptr || fs->ExpandFilePath == nullptr ||
2037 fs->FileExists == nullptr) {
2038 // Error, fs callback[s] missing
2039 return std::string();
2040 }
2041
2042 for (size_t i = 0; i < paths.size(); i++) {
2043 std::string absPath =
2044 fs->ExpandFilePath(JoinPath(path0: paths[i], path1: filepath), fs->user_data);
2045 if (fs->FileExists(absPath, fs->user_data)) {
2046 return absPath;
2047 }
2048 }
2049
2050 return std::string();
2051}
2052
2053static std::string GetFilePathExtension(const std::string &FileName) {
2054 if (FileName.find_last_of(s: ".") != std::string::npos)
2055 return FileName.substr(pos: FileName.find_last_of(s: ".") + 1);
2056 return "";
2057}
2058
2059static std::string GetBaseDir(const std::string &filepath) {
2060 if (filepath.find_last_of(s: "/\\") != std::string::npos)
2061 return filepath.substr(pos: 0, n: filepath.find_last_of(s: "/\\"));
2062 return "";
2063}
2064
2065static std::string GetBaseFilename(const std::string &filepath) {
2066 auto idx = filepath.find_last_of(s: "/\\");
2067 if (idx != std::string::npos) return filepath.substr(pos: idx + 1);
2068 return filepath;
2069}
2070
2071std::string base64_encode(unsigned char const *, unsigned int len);
2072std::string base64_decode(std::string const &s);
2073
2074/*
2075 base64.cpp and base64.h
2076
2077 Copyright (C) 2004-2008 René Nyffenegger
2078
2079 This source code is provided 'as-is', without any express or implied
2080 warranty. In no event will the author be held liable for any damages
2081 arising from the use of this software.
2082
2083 Permission is granted to anyone to use this software for any purpose,
2084 including commercial applications, and to alter it and redistribute it
2085 freely, subject to the following restrictions:
2086
2087 1. The origin of this source code must not be misrepresented; you must not
2088 claim that you wrote the original source code. If you use this source code
2089 in a product, an acknowledgment in the product documentation would be
2090 appreciated but is not required.
2091
2092 2. Altered source versions must be plainly marked as such, and must not be
2093 misrepresented as being the original source code.
2094
2095 3. This notice may not be removed or altered from any source distribution.
2096
2097 René Nyffenegger rene.nyffenegger@adp-gmbh.ch
2098
2099*/
2100
2101#ifdef __clang__
2102#pragma clang diagnostic push
2103#pragma clang diagnostic ignored "-Wsign-conversion"
2104#pragma clang diagnostic ignored "-Wconversion"
2105#endif
2106
2107static inline bool is_base64(unsigned char c) {
2108 return (isalnum(c) || (c == '+') || (c == '/'));
2109}
2110
2111std::string base64_encode(unsigned char const *bytes_to_encode,
2112 unsigned int in_len) {
2113 std::string ret;
2114 int i = 0;
2115 int j = 0;
2116 unsigned char char_array_3[3];
2117 unsigned char char_array_4[4];
2118
2119 const char *base64_chars =
2120 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2121 "abcdefghijklmnopqrstuvwxyz"
2122 "0123456789+/";
2123
2124 while (in_len--) {
2125 char_array_3[i++] = *(bytes_to_encode++);
2126 if (i == 3) {
2127 char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
2128 char_array_4[1] =
2129 ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
2130 char_array_4[2] =
2131 ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
2132 char_array_4[3] = char_array_3[2] & 0x3f;
2133
2134 for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]];
2135 i = 0;
2136 }
2137 }
2138
2139 if (i) {
2140 for (j = i; j < 3; j++) char_array_3[j] = '\0';
2141
2142 char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
2143 char_array_4[1] =
2144 ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
2145 char_array_4[2] =
2146 ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
2147
2148 for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]];
2149
2150 while ((i++ < 3)) ret += '=';
2151 }
2152
2153 return ret;
2154}
2155
2156std::string base64_decode(std::string const &encoded_string) {
2157 int in_len = static_cast<int>(encoded_string.size());
2158 int i = 0;
2159 int j = 0;
2160 int in_ = 0;
2161 unsigned char char_array_4[4], char_array_3[3];
2162 std::string ret;
2163
2164 const std::string base64_chars =
2165 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2166 "abcdefghijklmnopqrstuvwxyz"
2167 "0123456789+/";
2168
2169 while (in_len-- && (encoded_string[in_] != '=') &&
2170 is_base64(c: encoded_string[in_])) {
2171 char_array_4[i++] = encoded_string[in_];
2172 in_++;
2173 if (i == 4) {
2174 for (i = 0; i < 4; i++)
2175 char_array_4[i] =
2176 static_cast<unsigned char>(base64_chars.find(c: char_array_4[i]));
2177
2178 char_array_3[0] =
2179 (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
2180 char_array_3[1] =
2181 ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
2182 char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
2183
2184 for (i = 0; (i < 3); i++) ret += char_array_3[i];
2185 i = 0;
2186 }
2187 }
2188
2189 if (i) {
2190 for (j = i; j < 4; j++) char_array_4[j] = 0;
2191
2192 for (j = 0; j < 4; j++)
2193 char_array_4[j] =
2194 static_cast<unsigned char>(base64_chars.find(c: char_array_4[j]));
2195
2196 char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
2197 char_array_3[1] =
2198 ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
2199 char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
2200
2201 for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
2202 }
2203
2204 return ret;
2205}
2206#ifdef __clang__
2207#pragma clang diagnostic pop
2208#endif
2209
2210// https://github.com/syoyo/tinygltf/issues/228
2211// TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri
2212// decoding?
2213//
2214// Uri Decoding from DLIB
2215// http://dlib.net/dlib/server/server_http.cpp.html
2216// --- dlib begin ------------------------------------------------------------
2217// Copyright (C) 2003 Davis E. King (davis@dlib.net)
2218// License: Boost Software License
2219// Boost Software License - Version 1.0 - August 17th, 2003
2220
2221// Permission is hereby granted, free of charge, to any person or organization
2222// obtaining a copy of the software and accompanying documentation covered by
2223// this license (the "Software") to use, reproduce, display, distribute,
2224// execute, and transmit the Software, and to prepare derivative works of the
2225// Software, and to permit third-parties to whom the Software is furnished to
2226// do so, all subject to the following:
2227// The copyright notices in the Software and this entire statement, including
2228// the above license grant, this restriction and the following disclaimer,
2229// must be included in all copies of the Software, in whole or in part, and
2230// all derivative works of the Software, unless such copies or derivative
2231// works are solely in the form of machine-executable object code generated by
2232// a source language processor.
2233// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2234// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2235// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
2236// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
2237// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
2238// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2239// DEALINGS IN THE SOFTWARE.
2240//
2241namespace dlib {
2242
2243inline unsigned char from_hex(unsigned char ch) {
2244 if (ch <= '9' && ch >= '0')
2245 ch -= '0';
2246 else if (ch <= 'f' && ch >= 'a')
2247 ch -= 'a' - 10;
2248 else if (ch <= 'F' && ch >= 'A')
2249 ch -= 'A' - 10;
2250 else
2251 ch = 0;
2252 return ch;
2253}
2254
2255static const std::string urldecode(const std::string &str) {
2256 using namespace std;
2257 string result;
2258 string::size_type i;
2259 for (i = 0; i < str.size(); ++i) {
2260 if (str[i] == '+') {
2261 result += ' ';
2262 } else if (str[i] == '%' && str.size() > i + 2) {
2263 const unsigned char ch1 =
2264 from_hex(ch: static_cast<unsigned char>(str[i + 1]));
2265 const unsigned char ch2 =
2266 from_hex(ch: static_cast<unsigned char>(str[i + 2]));
2267 const unsigned char ch = static_cast<unsigned char>((ch1 << 4) | ch2);
2268 result += static_cast<char>(ch);
2269 i += 2;
2270 } else {
2271 result += str[i];
2272 }
2273 }
2274 return result;
2275}
2276
2277} // namespace dlib
2278// --- dlib end --------------------------------------------------------------
2279
2280static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
2281 std::string *warn, const std::string &filename,
2282 const std::string &basedir, bool required,
2283 size_t reqBytes, bool checkSize, FsCallbacks *fs) {
2284 if (fs == nullptr || fs->FileExists == nullptr ||
2285 fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) {
2286 // This is a developer error, assert() ?
2287 if (err) {
2288 (*err) += "FS callback[s] not set\n";
2289 }
2290 return false;
2291 }
2292
2293 std::string *failMsgOut = required ? err : warn;
2294
2295 out->clear();
2296
2297 std::vector<std::string> paths;
2298 paths.push_back(x: basedir);
2299 paths.push_back(x: ".");
2300
2301 std::string filepath = FindFile(paths, filepath: filename, fs);
2302 if (filepath.empty() || filename.empty()) {
2303 if (failMsgOut) {
2304 (*failMsgOut) += "File not found : " + filename + "\n";
2305 }
2306 return false;
2307 }
2308
2309 std::vector<unsigned char> buf;
2310 std::string fileReadErr;
2311 bool fileRead =
2312 fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data);
2313 if (!fileRead) {
2314 if (failMsgOut) {
2315 (*failMsgOut) +=
2316 "File read error : " + filepath + " : " + fileReadErr + "\n";
2317 }
2318 return false;
2319 }
2320
2321 size_t sz = buf.size();
2322 if (sz == 0) {
2323 if (failMsgOut) {
2324 (*failMsgOut) += "File is empty : " + filepath + "\n";
2325 }
2326 return false;
2327 }
2328
2329 if (checkSize) {
2330 if (reqBytes == sz) {
2331 out->swap(x&: buf);
2332 return true;
2333 } else {
2334 std::stringstream ss;
2335 ss << "File size mismatch : " << filepath << ", requestedBytes "
2336 << reqBytes << ", but got " << sz << std::endl;
2337 if (failMsgOut) {
2338 (*failMsgOut) += ss.str();
2339 }
2340 return false;
2341 }
2342 }
2343
2344 out->swap(x&: buf);
2345 return true;
2346}
2347
2348void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
2349 LoadImageData = func;
2350 load_image_user_data_ = user_data;
2351 user_image_loader_ = true;
2352}
2353
2354void TinyGLTF::RemoveImageLoader() {
2355 LoadImageData =
2356#ifndef TINYGLTF_NO_STB_IMAGE
2357 &tinygltf::LoadImageData;
2358#else
2359 nullptr;
2360#endif
2361
2362 load_image_user_data_ = nullptr;
2363 user_image_loader_ = false;
2364}
2365
2366#ifndef TINYGLTF_NO_STB_IMAGE
2367bool LoadImageData(Image *image, const int image_idx, std::string *err,
2368 std::string *warn, int req_width, int req_height,
2369 const unsigned char *bytes, int size, void *user_data) {
2370 (void)warn;
2371
2372 LoadImageDataOption option;
2373 if (user_data) {
2374 option = *reinterpret_cast<LoadImageDataOption *>(user_data);
2375 }
2376
2377 int w = 0, h = 0, comp = 0, req_comp = 0;
2378
2379 unsigned char *data = nullptr;
2380
2381 // preserve_channels true: Use channels stored in the image file.
2382 // false: force 32-bit textures for common Vulkan compatibility. It appears
2383 // that some GPU drivers do not support 24-bit images for Vulkan
2384 req_comp = option.preserve_channels ? 0 : 4;
2385 int bits = 8;
2386 int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
2387
2388 // It is possible that the image we want to load is a 16bit per channel image
2389 // We are going to attempt to load it as 16bit per channel, and if it worked,
2390 // set the image data accodingly. We are casting the returned pointer into
2391 // unsigned char, because we are representing "bytes". But we are updating
2392 // the Image metadata to signal that this image uses 2 bytes (16bits) per
2393 // channel:
2394 if (stbi_is_16_bit_from_memory(buffer: bytes, len: size)) {
2395 data = reinterpret_cast<unsigned char *>(
2396 stbi_load_16_from_memory(buffer: bytes, len: size, x: &w, y: &h, channels_in_file: &comp, desired_channels: req_comp));
2397 if (data) {
2398 bits = 16;
2399 pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
2400 }
2401 }
2402
2403 // at this point, if data is still NULL, it means that the image wasn't
2404 // 16bit per channel, we are going to load it as a normal 8bit per channel
2405 // mage as we used to do:
2406 // if image cannot be decoded, ignore parsing and keep it by its path
2407 // don't break in this case
2408 // FIXME we should only enter this function if the image is embedded. If
2409 // image->uri references
2410 // an image file, it should be left as it is. Image loading should not be
2411 // mandatory (to support other formats)
2412 if (!data) data = stbi_load_from_memory(buffer: bytes, len: size, x: &w, y: &h, comp: &comp, req_comp);
2413 if (!data) {
2414 // NOTE: you can use `warn` instead of `err`
2415 if (err) {
2416 (*err) +=
2417 "Unknown image format. STB cannot decode image data for image[" +
2418 std::to_string(val: image_idx) + "] name = \"" + image->name + "\".\n";
2419 }
2420 return false;
2421 }
2422
2423 if ((w < 1) || (h < 1)) {
2424 stbi_image_free(retval_from_stbi_load: data);
2425 if (err) {
2426 (*err) += "Invalid image data for image[" + std::to_string(val: image_idx) +
2427 "] name = \"" + image->name + "\"\n";
2428 }
2429 return false;
2430 }
2431
2432 if (req_width > 0) {
2433 if (req_width != w) {
2434 stbi_image_free(retval_from_stbi_load: data);
2435 if (err) {
2436 (*err) += "Image width mismatch for image[" +
2437 std::to_string(val: image_idx) + "] name = \"" + image->name +
2438 "\"\n";
2439 }
2440 return false;
2441 }
2442 }
2443
2444 if (req_height > 0) {
2445 if (req_height != h) {
2446 stbi_image_free(retval_from_stbi_load: data);
2447 if (err) {
2448 (*err) += "Image height mismatch. for image[" +
2449 std::to_string(val: image_idx) + "] name = \"" + image->name +
2450 "\"\n";
2451 }
2452 return false;
2453 }
2454 }
2455
2456 if (req_comp != 0) {
2457 // loaded data has `req_comp` channels(components)
2458 comp = req_comp;
2459 }
2460
2461 image->width = w;
2462 image->height = h;
2463 image->component = comp;
2464 image->bits = bits;
2465 image->pixel_type = pixel_type;
2466 image->image.resize(sz: static_cast<size_t>(w * h * comp) * size_t(bits / 8));
2467 std::copy(first: data, last: data + w * h * comp * (bits / 8), result: image->image.begin());
2468 stbi_image_free(retval_from_stbi_load: data);
2469
2470 return true;
2471}
2472#endif
2473
2474void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) {
2475 WriteImageData = func;
2476 write_image_user_data_ = user_data;
2477}
2478
2479#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
2480static void WriteToMemory_stbi(void *context, void *data, int size) {
2481 std::vector<unsigned char> *buffer =
2482 reinterpret_cast<std::vector<unsigned char> *>(context);
2483
2484 unsigned char *pData = reinterpret_cast<unsigned char *>(data);
2485
2486 buffer->insert(position: buffer->end(), first: pData, last: pData + size);
2487}
2488
2489bool WriteImageData(const std::string *basepath, const std::string *filename,
2490 Image *image, bool embedImages, void *fsPtr) {
2491 const std::string ext = GetFilePathExtension(FileName: *filename);
2492
2493 // Write image to temporary buffer
2494 std::string header;
2495 std::vector<unsigned char> data;
2496
2497 if (ext == "png") {
2498 if ((image->bits != 8) ||
2499 (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) {
2500 // Unsupported pixel format
2501 return false;
2502 }
2503
2504 if (!stbi_write_png_to_func(func: WriteToMemory_stbi, context: &data, x: image->width,
2505 y: image->height, comp: image->component,
2506 data: &image->image[0], stride_bytes: 0)) {
2507 return false;
2508 }
2509 header = "data:image/png;base64,";
2510 } else if (ext == "jpg") {
2511 if (!stbi_write_jpg_to_func(func: WriteToMemory_stbi, context: &data, x: image->width,
2512 y: image->height, comp: image->component,
2513 data: &image->image[0], quality: 100)) {
2514 return false;
2515 }
2516 header = "data:image/jpeg;base64,";
2517 } else if (ext == "bmp") {
2518 if (!stbi_write_bmp_to_func(func: WriteToMemory_stbi, context: &data, x: image->width,
2519 y: image->height, comp: image->component,
2520 data: &image->image[0])) {
2521 return false;
2522 }
2523 header = "data:image/bmp;base64,";
2524 } else if (!embedImages) {
2525 // Error: can't output requested format to file
2526 return false;
2527 }
2528
2529 if (embedImages) {
2530 // Embed base64-encoded image into URI
2531 if (data.size()) {
2532 image->uri =
2533 header +
2534 base64_encode(bytes_to_encode: &data[0], in_len: static_cast<unsigned int>(data.size()));
2535 } else {
2536 // Throw error?
2537 }
2538 } else {
2539 // Write image to disc
2540 FsCallbacks *fs = reinterpret_cast<FsCallbacks *>(fsPtr);
2541 if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) {
2542 const std::string imagefilepath = JoinPath(path0: *basepath, path1: *filename);
2543 std::string writeError;
2544 if (!fs->WriteWholeFile(&writeError, imagefilepath, data,
2545 fs->user_data)) {
2546 // Could not write image file to disc; Throw error ?
2547 return false;
2548 }
2549 } else {
2550 // Throw error?
2551 }
2552 image->uri = *filename;
2553 }
2554
2555 return true;
2556}
2557#endif
2558
2559void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; }
2560
2561#ifdef _WIN32
2562static inline std::wstring UTF8ToWchar(const std::string &str) {
2563 int wstr_size =
2564 MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
2565 std::wstring wstr(wstr_size, 0);
2566 MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0],
2567 (int)wstr.size());
2568 return wstr;
2569}
2570
2571static inline std::string WcharToUTF8(const std::wstring &wstr) {
2572 int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(),
2573 nullptr, 0, NULL, NULL);
2574 std::string str(str_size, 0);
2575 WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0],
2576 (int)str.size(), NULL, NULL);
2577 return str;
2578}
2579#endif
2580
2581#ifndef TINYGLTF_NO_FS
2582// Default implementations of filesystem functions
2583
2584bool FileExists(const std::string &abs_filename, void *) {
2585 bool ret;
2586#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
2587 if (asset_manager) {
2588 AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(),
2589 AASSET_MODE_STREAMING);
2590 if (!asset) {
2591 return false;
2592 }
2593 AAsset_close(asset);
2594 ret = true;
2595 } else {
2596 return false;
2597 }
2598#else
2599#ifdef _WIN32
2600#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
2601 FILE *fp = nullptr;
2602 errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb");
2603 if (err != 0) {
2604 return false;
2605 }
2606#else
2607 FILE *fp = nullptr;
2608 errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
2609 if (err != 0) {
2610 return false;
2611 }
2612#endif
2613
2614#else
2615 FILE *fp = fopen(filename: abs_filename.c_str(), modes: "rb");
2616#endif
2617 if (fp) {
2618 ret = true;
2619 fclose(stream: fp);
2620 } else {
2621 ret = false;
2622 }
2623#endif
2624
2625 return ret;
2626}
2627
2628std::string ExpandFilePath(const std::string &filepath, void *) {
2629 // https://github.com/syoyo/tinygltf/issues/368
2630 //
2631 // No file path expansion in built-in FS function anymore, since glTF URI
2632 // should not contain tilde('~') and environment variables, and for security
2633 // reason(`wordexp`).
2634 //
2635 // Users need to supply `base_dir`(in `LoadASCIIFromString`,
2636 // `LoadBinaryFromMemory`) in expanded absolute path.
2637
2638 return filepath;
2639
2640#if 0
2641#ifdef _WIN32
2642 // Assume input `filepath` is encoded in UTF-8
2643 std::wstring wfilepath = UTF8ToWchar(filepath);
2644 DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0);
2645 wchar_t *wstr = new wchar_t[wlen];
2646 ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen);
2647
2648 std::wstring ws(wstr);
2649 delete[] wstr;
2650 return WcharToUTF8(ws);
2651
2652#else
2653
2654#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
2655 defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__OpenBSD__)
2656 // no expansion
2657 std::string s = filepath;
2658#else
2659 std::string s;
2660 wordexp_t p;
2661
2662 if (filepath.empty()) {
2663 return "";
2664 }
2665
2666 // Quote the string to keep any spaces in filepath intact.
2667 std::string quoted_path = "\"" + filepath + "\"";
2668 // char** w;
2669 int ret = wordexp(quoted_path.c_str(), &p, 0);
2670 if (ret) {
2671 // err
2672 s = filepath;
2673 return s;
2674 }
2675
2676 // Use first element only.
2677 if (p.we_wordv) {
2678 s = std::string(p.we_wordv[0]);
2679 wordfree(&p);
2680 } else {
2681 s = filepath;
2682 }
2683
2684#endif
2685
2686 return s;
2687#endif
2688#endif
2689}
2690
2691bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
2692 const std::string &filepath, void *) {
2693#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
2694 if (asset_manager) {
2695 AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(),
2696 AASSET_MODE_STREAMING);
2697 if (!asset) {
2698 if (err) {
2699 (*err) += "File open error : " + filepath + "\n";
2700 }
2701 return false;
2702 }
2703 size_t size = AAsset_getLength(asset);
2704 if (size == 0) {
2705 if (err) {
2706 (*err) += "Invalid file size : " + filepath +
2707 " (does the path point to a directory?)";
2708 }
2709 return false;
2710 }
2711 out->resize(size);
2712 AAsset_read(asset, reinterpret_cast<char *>(&out->at(0)), size);
2713 AAsset_close(asset);
2714 return true;
2715 } else {
2716 if (err) {
2717 (*err) += "No asset manager specified : " + filepath + "\n";
2718 }
2719 return false;
2720 }
2721#else
2722#ifdef _WIN32
2723#if defined(__GLIBCXX__) // mingw
2724 int file_descriptor =
2725 _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
2726 __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
2727 std::istream f(&wfile_buf);
2728#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION)
2729 // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept
2730 // `wchar_t *`
2731 std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
2732#else
2733 // Unknown compiler/runtime
2734 std::ifstream f(filepath.c_str(), std::ifstream::binary);
2735#endif
2736#else
2737 std::ifstream f(filepath.c_str(), std::ifstream::binary);
2738#endif
2739 if (!f) {
2740 if (err) {
2741 (*err) += "File open error : " + filepath + "\n";
2742 }
2743 return false;
2744 }
2745
2746 f.seekg(off: 0, dir: f.end);
2747 size_t sz = static_cast<size_t>(f.tellg());
2748 f.seekg(off: 0, dir: f.beg);
2749
2750 if (int64_t(sz) < 0) {
2751 if (err) {
2752 (*err) += "Invalid file size : " + filepath +
2753 " (does the path point to a directory?)";
2754 }
2755 return false;
2756 } else if (sz == 0) {
2757 if (err) {
2758 (*err) += "File is empty : " + filepath + "\n";
2759 }
2760 return false;
2761 }
2762
2763 out->resize(sz: sz);
2764 f.read(s: reinterpret_cast<char *>(&out->at(n: 0)),
2765 n: static_cast<std::streamsize>(sz));
2766
2767 return true;
2768#endif
2769}
2770
2771bool WriteWholeFile(std::string *err, const std::string &filepath,
2772 const std::vector<unsigned char> &contents, void *) {
2773#ifdef _WIN32
2774#if defined(__GLIBCXX__) // mingw
2775 int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(),
2776 _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
2777 __gnu_cxx::stdio_filebuf<char> wfile_buf(
2778 file_descriptor, std::ios_base::out | std::ios_base::binary);
2779 std::ostream f(&wfile_buf);
2780#elif defined(_MSC_VER)
2781 std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary);
2782#else // clang?
2783 std::ofstream f(filepath.c_str(), std::ofstream::binary);
2784#endif
2785#else
2786 std::ofstream f(filepath.c_str(), std::ofstream::binary);
2787#endif
2788 if (!f) {
2789 if (err) {
2790 (*err) += "File open error for writing : " + filepath + "\n";
2791 }
2792 return false;
2793 }
2794
2795 f.write(s: reinterpret_cast<const char *>(&contents.at(n: 0)),
2796 n: static_cast<std::streamsize>(contents.size()));
2797 if (!f) {
2798 if (err) {
2799 (*err) += "File write error: " + filepath + "\n";
2800 }
2801 return false;
2802 }
2803
2804 return true;
2805}
2806
2807#endif // TINYGLTF_NO_FS
2808
2809static std::string MimeToExt(const std::string &mimeType) {
2810 if (mimeType == "image/jpeg") {
2811 return "jpg";
2812 } else if (mimeType == "image/png") {
2813 return "png";
2814 } else if (mimeType == "image/bmp") {
2815 return "bmp";
2816 } else if (mimeType == "image/gif") {
2817 return "gif";
2818 }
2819
2820 return "";
2821}
2822
2823static void UpdateImageObject(Image &image, std::string &baseDir, int index,
2824 bool embedImages,
2825 WriteImageDataFunction *WriteImageData = nullptr,
2826 void *user_data = nullptr) {
2827 std::string filename;
2828 std::string ext;
2829 // If image has uri, use it it as a filename
2830 if (image.uri.size()) {
2831 filename = GetBaseFilename(filepath: image.uri);
2832 ext = GetFilePathExtension(FileName: filename);
2833 } else if (image.bufferView != -1) {
2834 // If there's no URI and the data exists in a buffer,
2835 // don't change properties or write images
2836 } else if (image.name.size()) {
2837 ext = MimeToExt(mimeType: image.mimeType);
2838 // Otherwise use name as filename
2839 filename = image.name + "." + ext;
2840 } else {
2841 ext = MimeToExt(mimeType: image.mimeType);
2842 // Fallback to index of image as filename
2843 filename = std::to_string(val: index) + "." + ext;
2844 }
2845
2846 // If callback is set, modify image data object
2847 if (*WriteImageData != nullptr && !filename.empty()) {
2848 std::string uri;
2849 (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data);
2850 }
2851}
2852
2853bool IsDataURI(const std::string &in) {
2854 std::string header = "data:application/octet-stream;base64,";
2855 if (in.find(str: header) == 0) {
2856 return true;
2857 }
2858
2859 header = "data:image/jpeg;base64,";
2860 if (in.find(str: header) == 0) {
2861 return true;
2862 }
2863
2864 header = "data:image/png;base64,";
2865 if (in.find(str: header) == 0) {
2866 return true;
2867 }
2868
2869 header = "data:image/bmp;base64,";
2870 if (in.find(str: header) == 0) {
2871 return true;
2872 }
2873
2874 header = "data:image/gif;base64,";
2875 if (in.find(str: header) == 0) {
2876 return true;
2877 }
2878
2879 header = "data:text/plain;base64,";
2880 if (in.find(str: header) == 0) {
2881 return true;
2882 }
2883
2884 header = "data:application/gltf-buffer;base64,";
2885 if (in.find(str: header) == 0) {
2886 return true;
2887 }
2888
2889 return false;
2890}
2891
2892bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
2893 const std::string &in, size_t reqBytes, bool checkSize) {
2894 std::string header = "data:application/octet-stream;base64,";
2895 std::string data;
2896 if (in.find(str: header) == 0) {
2897 data = base64_decode(encoded_string: in.substr(pos: header.size())); // cut mime string.
2898 }
2899
2900 if (data.empty()) {
2901 header = "data:image/jpeg;base64,";
2902 if (in.find(str: header) == 0) {
2903 mime_type = "image/jpeg";
2904 data = base64_decode(encoded_string: in.substr(pos: header.size())); // cut mime string.
2905 }
2906 }
2907
2908 if (data.empty()) {
2909 header = "data:image/png;base64,";
2910 if (in.find(str: header) == 0) {
2911 mime_type = "image/png";
2912 data = base64_decode(encoded_string: in.substr(pos: header.size())); // cut mime string.
2913 }
2914 }
2915
2916 if (data.empty()) {
2917 header = "data:image/bmp;base64,";
2918 if (in.find(str: header) == 0) {
2919 mime_type = "image/bmp";
2920 data = base64_decode(encoded_string: in.substr(pos: header.size())); // cut mime string.
2921 }
2922 }
2923
2924 if (data.empty()) {
2925 header = "data:image/gif;base64,";
2926 if (in.find(str: header) == 0) {
2927 mime_type = "image/gif";
2928 data = base64_decode(encoded_string: in.substr(pos: header.size())); // cut mime string.
2929 }
2930 }
2931
2932 if (data.empty()) {
2933 header = "data:text/plain;base64,";
2934 if (in.find(str: header) == 0) {
2935 mime_type = "text/plain";
2936 data = base64_decode(encoded_string: in.substr(pos: header.size()));
2937 }
2938 }
2939
2940 if (data.empty()) {
2941 header = "data:application/gltf-buffer;base64,";
2942 if (in.find(str: header) == 0) {
2943 data = base64_decode(encoded_string: in.substr(pos: header.size()));
2944 }
2945 }
2946
2947 // TODO(syoyo): Allow empty buffer? #229
2948 if (data.empty()) {
2949 return false;
2950 }
2951
2952 if (checkSize) {
2953 if (data.size() != reqBytes) {
2954 return false;
2955 }
2956 out->resize(sz: reqBytes);
2957 } else {
2958 out->resize(sz: data.size());
2959 }
2960 std::copy(first: data.begin(), last: data.end(), result: out->begin());
2961 return true;
2962}
2963
2964namespace {
2965bool GetInt(const json &o, int &val) {
2966#ifdef TINYGLTF_USE_RAPIDJSON
2967 if (!o.IsDouble()) {
2968 if (o.IsInt()) {
2969 val = o.GetInt();
2970 return true;
2971 } else if (o.IsUint()) {
2972 val = static_cast<int>(o.GetUint());
2973 return true;
2974 } else if (o.IsInt64()) {
2975 val = static_cast<int>(o.GetInt64());
2976 return true;
2977 } else if (o.IsUint64()) {
2978 val = static_cast<int>(o.GetUint64());
2979 return true;
2980 }
2981 }
2982
2983 return false;
2984#else
2985 auto type = o.type();
2986
2987 if ((type == json::value_t::number_integer) ||
2988 (type == json::value_t::number_unsigned)) {
2989 val = static_cast<int>(o.get<int64_t>());
2990 return true;
2991 }
2992
2993 return false;
2994#endif
2995}
2996
2997#ifdef TINYGLTF_USE_RAPIDJSON
2998bool GetDouble(const json &o, double &val) {
2999 if (o.IsDouble()) {
3000 val = o.GetDouble();
3001 return true;
3002 }
3003
3004 return false;
3005}
3006#endif
3007
3008bool GetNumber(const json &o, double &val) {
3009#ifdef TINYGLTF_USE_RAPIDJSON
3010 if (o.IsNumber()) {
3011 val = o.GetDouble();
3012 return true;
3013 }
3014
3015 return false;
3016#else
3017 if (o.is_number()) {
3018 val = o.get<double>();
3019 return true;
3020 }
3021
3022 return false;
3023#endif
3024}
3025
3026bool GetString(const json &o, std::string &val) {
3027#ifdef TINYGLTF_USE_RAPIDJSON
3028 if (o.IsString()) {
3029 val = o.GetString();
3030 return true;
3031 }
3032
3033 return false;
3034#else
3035 if (o.type() == json::value_t::string) {
3036 val = o.get<std::string>();
3037 return true;
3038 }
3039
3040 return false;
3041#endif
3042}
3043
3044bool IsArray(const json &o) {
3045#ifdef TINYGLTF_USE_RAPIDJSON
3046 return o.IsArray();
3047#else
3048 return o.is_array();
3049#endif
3050}
3051
3052json_const_array_iterator ArrayBegin(const json &o) {
3053#ifdef TINYGLTF_USE_RAPIDJSON
3054 return o.Begin();
3055#else
3056 return o.begin();
3057#endif
3058}
3059
3060json_const_array_iterator ArrayEnd(const json &o) {
3061#ifdef TINYGLTF_USE_RAPIDJSON
3062 return o.End();
3063#else
3064 return o.end();
3065#endif
3066}
3067
3068bool IsObject(const json &o) {
3069#ifdef TINYGLTF_USE_RAPIDJSON
3070 return o.IsObject();
3071#else
3072 return o.is_object();
3073#endif
3074}
3075
3076json_const_iterator ObjectBegin(const json &o) {
3077#ifdef TINYGLTF_USE_RAPIDJSON
3078 return o.MemberBegin();
3079#else
3080 return o.begin();
3081#endif
3082}
3083
3084json_const_iterator ObjectEnd(const json &o) {
3085#ifdef TINYGLTF_USE_RAPIDJSON
3086 return o.MemberEnd();
3087#else
3088 return o.end();
3089#endif
3090}
3091
3092// Making this a const char* results in a pointer to a temporary when
3093// TINYGLTF_USE_RAPIDJSON is off.
3094std::string GetKey(json_const_iterator &it) {
3095#ifdef TINYGLTF_USE_RAPIDJSON
3096 return it->name.GetString();
3097#else
3098 return it.key().c_str();
3099#endif
3100}
3101
3102bool FindMember(const json &o, const char *member, json_const_iterator &it) {
3103#ifdef TINYGLTF_USE_RAPIDJSON
3104 if (!o.IsObject()) {
3105 return false;
3106 }
3107 it = o.FindMember(member);
3108 return it != o.MemberEnd();
3109#else
3110 it = o.find(key&: member);
3111 return it != o.end();
3112#endif
3113}
3114
3115const json &GetValue(json_const_iterator &it) {
3116#ifdef TINYGLTF_USE_RAPIDJSON
3117 return it->value;
3118#else
3119 return it.value();
3120#endif
3121}
3122
3123std::string JsonToString(const json &o, int spacing = -1) {
3124#ifdef TINYGLTF_USE_RAPIDJSON
3125 using namespace rapidjson;
3126 StringBuffer buffer;
3127 if (spacing == -1) {
3128 Writer<StringBuffer> writer(buffer);
3129 // TODO: Better error handling.
3130 // https://github.com/syoyo/tinygltf/issues/332
3131 if (!o.Accept(writer)) {
3132 return "tiny_gltf::JsonToString() failed rapidjson conversion";
3133 }
3134 } else {
3135 PrettyWriter<StringBuffer> writer(buffer);
3136 writer.SetIndent(' ', uint32_t(spacing));
3137 if (!o.Accept(writer)) {
3138 return "tiny_gltf::JsonToString() failed rapidjson conversion";
3139 }
3140 }
3141 return buffer.GetString();
3142#else
3143 return o.dump(indent: spacing);
3144#endif
3145}
3146
3147} // namespace
3148
3149static bool ParseJsonAsValue(Value *ret, const json &o) {
3150 Value val{};
3151#ifdef TINYGLTF_USE_RAPIDJSON
3152 using rapidjson::Type;
3153 switch (o.GetType()) {
3154 case Type::kObjectType: {
3155 Value::Object value_object;
3156 for (auto it = o.MemberBegin(); it != o.MemberEnd(); ++it) {
3157 Value entry;
3158 ParseJsonAsValue(&entry, it->value);
3159 if (entry.Type() != NULL_TYPE)
3160 value_object.emplace(GetKey(it), std::move(entry));
3161 }
3162 if (value_object.size() > 0) val = Value(std::move(value_object));
3163 } break;
3164 case Type::kArrayType: {
3165 Value::Array value_array;
3166 value_array.reserve(o.Size());
3167 for (auto it = o.Begin(); it != o.End(); ++it) {
3168 Value entry;
3169 ParseJsonAsValue(&entry, *it);
3170 if (entry.Type() != NULL_TYPE)
3171 value_array.emplace_back(std::move(entry));
3172 }
3173 if (value_array.size() > 0) val = Value(std::move(value_array));
3174 } break;
3175 case Type::kStringType:
3176 val = Value(std::string(o.GetString()));
3177 break;
3178 case Type::kFalseType:
3179 case Type::kTrueType:
3180 val = Value(o.GetBool());
3181 break;
3182 case Type::kNumberType:
3183 if (!o.IsDouble()) {
3184 int i = 0;
3185 GetInt(o, i);
3186 val = Value(i);
3187 } else {
3188 double d = 0.0;
3189 GetDouble(o, d);
3190 val = Value(d);
3191 }
3192 break;
3193 case Type::kNullType:
3194 break;
3195 // all types are covered, so no `case default`
3196 }
3197#else
3198 switch (o.type()) {
3199 case json::value_t::object: {
3200 Value::Object value_object;
3201 for (auto it = o.begin(); it != o.end(); it++) {
3202 Value entry;
3203 ParseJsonAsValue(ret: &entry, o: it.value());
3204 if (entry.Type() != NULL_TYPE)
3205 value_object.emplace(args: it.key(), args: std::move(entry));
3206 }
3207 if (value_object.size() > 0) val = Value(std::move(value_object));
3208 } break;
3209 case json::value_t::array: {
3210 Value::Array value_array;
3211 value_array.reserve(n: o.size());
3212 for (auto it = o.begin(); it != o.end(); it++) {
3213 Value entry;
3214 ParseJsonAsValue(ret: &entry, o: it.value());
3215 if (entry.Type() != NULL_TYPE)
3216 value_array.emplace_back(args: std::move(entry));
3217 }
3218 if (value_array.size() > 0) val = Value(std::move(value_array));
3219 } break;
3220 case json::value_t::string:
3221 val = Value(o.get<std::string>());
3222 break;
3223 case json::value_t::boolean:
3224 val = Value(o.get<bool>());
3225 break;
3226 case json::value_t::number_integer:
3227 case json::value_t::number_unsigned:
3228 val = Value(static_cast<int>(o.get<int64_t>()));
3229 break;
3230 case json::value_t::number_float:
3231 val = Value(o.get<double>());
3232 break;
3233 case json::value_t::null:
3234 case json::value_t::discarded:
3235 case json::value_t::binary:
3236 // default:
3237 break;
3238 }
3239#endif
3240 const bool isNotNull = val.Type() != NULL_TYPE;
3241
3242 if (ret) *ret = std::move(val);
3243
3244 return isNotNull;
3245}
3246
3247static bool ParseExtrasProperty(Value *ret, const json &o) {
3248 json_const_iterator it;
3249 if (!FindMember(o, member: "extras", it)) {
3250 return false;
3251 }
3252
3253 return ParseJsonAsValue(ret, o: GetValue(it));
3254}
3255
3256static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o,
3257 const std::string &property,
3258 const bool required,
3259 const std::string &parent_node = "") {
3260 json_const_iterator it;
3261 if (!FindMember(o, member: property.c_str(), it)) {
3262 if (required) {
3263 if (err) {
3264 (*err) += "'" + property + "' property is missing";
3265 if (!parent_node.empty()) {
3266 (*err) += " in " + parent_node;
3267 }
3268 (*err) += ".\n";
3269 }
3270 }
3271 return false;
3272 }
3273
3274 auto &value = GetValue(it);
3275
3276 bool isBoolean;
3277 bool boolValue = false;
3278#ifdef TINYGLTF_USE_RAPIDJSON
3279 isBoolean = value.IsBool();
3280 if (isBoolean) {
3281 boolValue = value.GetBool();
3282 }
3283#else
3284 isBoolean = value.is_boolean();
3285 if (isBoolean) {
3286 boolValue = value.get<bool>();
3287 }
3288#endif
3289 if (!isBoolean) {
3290 if (required) {
3291 if (err) {
3292 (*err) += "'" + property + "' property is not a bool type.\n";
3293 }
3294 }
3295 return false;
3296 }
3297
3298 if (ret) {
3299 (*ret) = boolValue;
3300 }
3301
3302 return true;
3303}
3304
3305static bool ParseIntegerProperty(int *ret, std::string *err, const json &o,
3306 const std::string &property,
3307 const bool required,
3308 const std::string &parent_node = "") {
3309 json_const_iterator it;
3310 if (!FindMember(o, member: property.c_str(), it)) {
3311 if (required) {
3312 if (err) {
3313 (*err) += "'" + property + "' property is missing";
3314 if (!parent_node.empty()) {
3315 (*err) += " in " + parent_node;
3316 }
3317 (*err) += ".\n";
3318 }
3319 }
3320 return false;
3321 }
3322
3323 int intValue;
3324 bool isInt = GetInt(o: GetValue(it), val&: intValue);
3325 if (!isInt) {
3326 if (required) {
3327 if (err) {
3328 (*err) += "'" + property + "' property is not an integer type.\n";
3329 }
3330 }
3331 return false;
3332 }
3333
3334 if (ret) {
3335 (*ret) = intValue;
3336 }
3337
3338 return true;
3339}
3340
3341static bool ParseUnsignedProperty(size_t *ret, std::string *err, const json &o,
3342 const std::string &property,
3343 const bool required,
3344 const std::string &parent_node = "") {
3345 json_const_iterator it;
3346 if (!FindMember(o, member: property.c_str(), it)) {
3347 if (required) {
3348 if (err) {
3349 (*err) += "'" + property + "' property is missing";
3350 if (!parent_node.empty()) {
3351 (*err) += " in " + parent_node;
3352 }
3353 (*err) += ".\n";
3354 }
3355 }
3356 return false;
3357 }
3358
3359 auto &value = GetValue(it);
3360
3361 size_t uValue = 0;
3362 bool isUValue;
3363#ifdef TINYGLTF_USE_RAPIDJSON
3364 isUValue = false;
3365 if (value.IsUint()) {
3366 uValue = value.GetUint();
3367 isUValue = true;
3368 } else if (value.IsUint64()) {
3369 uValue = value.GetUint64();
3370 isUValue = true;
3371 }
3372#else
3373 isUValue = value.is_number_unsigned();
3374 if (isUValue) {
3375 uValue = value.get<size_t>();
3376 }
3377#endif
3378 if (!isUValue) {
3379 if (required) {
3380 if (err) {
3381 (*err) += "'" + property + "' property is not a positive integer.\n";
3382 }
3383 }
3384 return false;
3385 }
3386
3387 if (ret) {
3388 (*ret) = uValue;
3389 }
3390
3391 return true;
3392}
3393
3394static bool ParseNumberProperty(double *ret, std::string *err, const json &o,
3395 const std::string &property,
3396 const bool required,
3397 const std::string &parent_node = "") {
3398 json_const_iterator it;
3399
3400 if (!FindMember(o, member: property.c_str(), it)) {
3401 if (required) {
3402 if (err) {
3403 (*err) += "'" + property + "' property is missing";
3404 if (!parent_node.empty()) {
3405 (*err) += " in " + parent_node;
3406 }
3407 (*err) += ".\n";
3408 }
3409 }
3410 return false;
3411 }
3412
3413 double numberValue;
3414 bool isNumber = GetNumber(o: GetValue(it), val&: numberValue);
3415
3416 if (!isNumber) {
3417 if (required) {
3418 if (err) {
3419 (*err) += "'" + property + "' property is not a number type.\n";
3420 }
3421 }
3422 return false;
3423 }
3424
3425 if (ret) {
3426 (*ret) = numberValue;
3427 }
3428
3429 return true;
3430}
3431
3432static bool ParseNumberArrayProperty(std::vector<double> *ret, std::string *err,
3433 const json &o, const std::string &property,
3434 bool required,
3435 const std::string &parent_node = "") {
3436 json_const_iterator it;
3437 if (!FindMember(o, member: property.c_str(), it)) {
3438 if (required) {
3439 if (err) {
3440 (*err) += "'" + property + "' property is missing";
3441 if (!parent_node.empty()) {
3442 (*err) += " in " + parent_node;
3443 }
3444 (*err) += ".\n";
3445 }
3446 }
3447 return false;
3448 }
3449
3450 if (!IsArray(o: GetValue(it))) {
3451 if (required) {
3452 if (err) {
3453 (*err) += "'" + property + "' property is not an array";
3454 if (!parent_node.empty()) {
3455 (*err) += " in " + parent_node;
3456 }
3457 (*err) += ".\n";
3458 }
3459 }
3460 return false;
3461 }
3462
3463 ret->clear();
3464 auto end = ArrayEnd(o: GetValue(it));
3465 for (auto i = ArrayBegin(o: GetValue(it)); i != end; ++i) {
3466 double numberValue;
3467 const bool isNumber = GetNumber(o: *i, val&: numberValue);
3468 if (!isNumber) {
3469 if (required) {
3470 if (err) {
3471 (*err) += "'" + property + "' property is not a number.\n";
3472 if (!parent_node.empty()) {
3473 (*err) += " in " + parent_node;
3474 }
3475 (*err) += ".\n";
3476 }
3477 }
3478 return false;
3479 }
3480 ret->push_back(x: numberValue);
3481 }
3482
3483 return true;
3484}
3485
3486static bool ParseIntegerArrayProperty(std::vector<int> *ret, std::string *err,
3487 const json &o,
3488 const std::string &property,
3489 bool required,
3490 const std::string &parent_node = "") {
3491 json_const_iterator it;
3492 if (!FindMember(o, member: property.c_str(), it)) {
3493 if (required) {
3494 if (err) {
3495 (*err) += "'" + property + "' property is missing";
3496 if (!parent_node.empty()) {
3497 (*err) += " in " + parent_node;
3498 }
3499 (*err) += ".\n";
3500 }
3501 }
3502 return false;
3503 }
3504
3505 if (!IsArray(o: GetValue(it))) {
3506 if (required) {
3507 if (err) {
3508 (*err) += "'" + property + "' property is not an array";
3509 if (!parent_node.empty()) {
3510 (*err) += " in " + parent_node;
3511 }
3512 (*err) += ".\n";
3513 }
3514 }
3515 return false;
3516 }
3517
3518 ret->clear();
3519 auto end = ArrayEnd(o: GetValue(it));
3520 for (auto i = ArrayBegin(o: GetValue(it)); i != end; ++i) {
3521 int numberValue;
3522 bool isNumber = GetInt(o: *i, val&: numberValue);
3523 if (!isNumber) {
3524 if (required) {
3525 if (err) {
3526 (*err) += "'" + property + "' property is not an integer type.\n";
3527 if (!parent_node.empty()) {
3528 (*err) += " in " + parent_node;
3529 }
3530 (*err) += ".\n";
3531 }
3532 }
3533 return false;
3534 }
3535 ret->push_back(x: numberValue);
3536 }
3537
3538 return true;
3539}
3540
3541static bool ParseStringProperty(
3542 std::string *ret, std::string *err, const json &o,
3543 const std::string &property, bool required,
3544 const std::string &parent_node = std::string()) {
3545 json_const_iterator it;
3546 if (!FindMember(o, member: property.c_str(), it)) {
3547 if (required) {
3548 if (err) {
3549 (*err) += "'" + property + "' property is missing";
3550 if (parent_node.empty()) {
3551 (*err) += ".\n";
3552 } else {
3553 (*err) += " in `" + parent_node + "'.\n";
3554 }
3555 }
3556 }
3557 return false;
3558 }
3559
3560 std::string strValue;
3561 if (!GetString(o: GetValue(it), val&: strValue)) {
3562 if (required) {
3563 if (err) {
3564 (*err) += "'" + property + "' property is not a string type.\n";
3565 }
3566 }
3567 return false;
3568 }
3569
3570 if (ret) {
3571 (*ret) = std::move(strValue);
3572 }
3573
3574 return true;
3575}
3576
3577static bool ParseStringIntegerProperty(std::map<std::string, int> *ret,
3578 std::string *err, const json &o,
3579 const std::string &property,
3580 bool required,
3581 const std::string &parent = "") {
3582 json_const_iterator it;
3583 if (!FindMember(o, member: property.c_str(), it)) {
3584 if (required) {
3585 if (err) {
3586 if (!parent.empty()) {
3587 (*err) +=
3588 "'" + property + "' property is missing in " + parent + ".\n";
3589 } else {
3590 (*err) += "'" + property + "' property is missing.\n";
3591 }
3592 }
3593 }
3594 return false;
3595 }
3596
3597 const json &dict = GetValue(it);
3598
3599 // Make sure we are dealing with an object / dictionary.
3600 if (!IsObject(o: dict)) {
3601 if (required) {
3602 if (err) {
3603 (*err) += "'" + property + "' property is not an object.\n";
3604 }
3605 }
3606 return false;
3607 }
3608
3609 ret->clear();
3610
3611 json_const_iterator dictIt(ObjectBegin(o: dict));
3612 json_const_iterator dictItEnd(ObjectEnd(o: dict));
3613
3614 for (; dictIt != dictItEnd; ++dictIt) {
3615 int intVal;
3616 if (!GetInt(o: GetValue(it&: dictIt), val&: intVal)) {
3617 if (required) {
3618 if (err) {
3619 (*err) += "'" + property + "' value is not an integer type.\n";
3620 }
3621 }
3622 return false;
3623 }
3624
3625 // Insert into the list.
3626 (*ret)[GetKey(it&: dictIt)] = intVal;
3627 }
3628 return true;
3629}
3630
3631static bool ParseJSONProperty(std::map<std::string, double> *ret,
3632 std::string *err, const json &o,
3633 const std::string &property, bool required) {
3634 json_const_iterator it;
3635 if (!FindMember(o, member: property.c_str(), it)) {
3636 if (required) {
3637 if (err) {
3638 (*err) += "'" + property + "' property is missing. \n'";
3639 }
3640 }
3641 return false;
3642 }
3643
3644 const json &obj = GetValue(it);
3645
3646 if (!IsObject(o: obj)) {
3647 if (required) {
3648 if (err) {
3649 (*err) += "'" + property + "' property is not a JSON object.\n";
3650 }
3651 }
3652 return false;
3653 }
3654
3655 ret->clear();
3656
3657 json_const_iterator it2(ObjectBegin(o: obj));
3658 json_const_iterator itEnd(ObjectEnd(o: obj));
3659 for (; it2 != itEnd; ++it2) {
3660 double numVal;
3661 if (GetNumber(o: GetValue(it&: it2), val&: numVal))
3662 ret->emplace(args: std::string(GetKey(it&: it2)), args&: numVal);
3663 }
3664
3665 return true;
3666}
3667
3668static bool ParseParameterProperty(Parameter *param, std::string *err,
3669 const json &o, const std::string &prop,
3670 bool required) {
3671 // A parameter value can either be a string or an array of either a boolean or
3672 // a number. Booleans of any kind aren't supported here. Granted, it
3673 // complicates the Parameter structure and breaks it semantically in the sense
3674 // that the client probably works off the assumption that if the string is
3675 // empty the vector is used, etc. Would a tagged union work?
3676 if (ParseStringProperty(ret: &param->string_value, err, o, property: prop, required: false)) {
3677 // Found string property.
3678 return true;
3679 } else if (ParseNumberArrayProperty(ret: &param->number_array, err, o, property: prop,
3680 required: false)) {
3681 // Found a number array.
3682 return true;
3683 } else if (ParseNumberProperty(ret: &param->number_value, err, o, property: prop, required: false)) {
3684 param->has_number_value = true;
3685 return true;
3686 } else if (ParseJSONProperty(ret: &param->json_double_value, err, o, property: prop,
3687 required: false)) {
3688 return true;
3689 } else if (ParseBooleanProperty(ret: &param->bool_value, err, o, property: prop, required: false)) {
3690 return true;
3691 } else {
3692 if (required) {
3693 if (err) {
3694 (*err) += "parameter must be a string or number / number array.\n";
3695 }
3696 }
3697 return false;
3698 }
3699}
3700
3701static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err,
3702 const json &o) {
3703 (void)err;
3704
3705 json_const_iterator it;
3706 if (!FindMember(o, member: "extensions", it)) {
3707 return false;
3708 }
3709
3710 auto &obj = GetValue(it);
3711 if (!IsObject(o: obj)) {
3712 return false;
3713 }
3714 ExtensionMap extensions;
3715 json_const_iterator extIt = ObjectBegin(o: obj); // it.value().begin();
3716 json_const_iterator extEnd = ObjectEnd(o: obj);
3717 for (; extIt != extEnd; ++extIt) {
3718 auto &itObj = GetValue(it&: extIt);
3719 if (!IsObject(o: itObj)) continue;
3720 std::string key(GetKey(it&: extIt));
3721 if (!ParseJsonAsValue(ret: &extensions[key], o: itObj)) {
3722 if (!key.empty()) {
3723 // create empty object so that an extension object is still of type
3724 // object
3725 extensions[key] = Value{Value::Object{}};
3726 }
3727 }
3728 }
3729 if (ret) {
3730 (*ret) = std::move(extensions);
3731 }
3732 return true;
3733}
3734
3735static bool ParseAsset(Asset *asset, std::string *err, const json &o,
3736 bool store_original_json_for_extras_and_extensions) {
3737 ParseStringProperty(ret: &asset->version, err, o, property: "version", required: true, parent_node: "Asset");
3738 ParseStringProperty(ret: &asset->generator, err, o, property: "generator", required: false, parent_node: "Asset");
3739 ParseStringProperty(ret: &asset->minVersion, err, o, property: "minVersion", required: false, parent_node: "Asset");
3740 ParseStringProperty(ret: &asset->copyright, err, o, property: "copyright", required: false, parent_node: "Asset");
3741
3742 ParseExtensionsProperty(ret: &asset->extensions, err, o);
3743
3744 // Unity exporter version is added as extra here
3745 ParseExtrasProperty(ret: &(asset->extras), o);
3746
3747 if (store_original_json_for_extras_and_extensions) {
3748 {
3749 json_const_iterator it;
3750 if (FindMember(o, member: "extensions", it)) {
3751 asset->extensions_json_string = JsonToString(o: GetValue(it));
3752 }
3753 }
3754 {
3755 json_const_iterator it;
3756 if (FindMember(o, member: "extras", it)) {
3757 asset->extras_json_string = JsonToString(o: GetValue(it));
3758 }
3759 }
3760 }
3761
3762 return true;
3763}
3764
3765static bool ParseImage(Image *image, const int image_idx, std::string *err,
3766 std::string *warn, const json &o,
3767 bool store_original_json_for_extras_and_extensions,
3768 const std::string &basedir, FsCallbacks *fs,
3769 LoadImageDataFunction *LoadImageData = nullptr,
3770 void *load_image_user_data = nullptr) {
3771 // A glTF image must either reference a bufferView or an image uri
3772
3773 // schema says oneOf [`bufferView`, `uri`]
3774 // TODO(syoyo): Check the type of each parameters.
3775 json_const_iterator it;
3776 bool hasBufferView = FindMember(o, member: "bufferView", it);
3777 bool hasURI = FindMember(o, member: "uri", it);
3778
3779 ParseStringProperty(ret: &image->name, err, o, property: "name", required: false);
3780
3781 if (hasBufferView && hasURI) {
3782 // Should not both defined.
3783 if (err) {
3784 (*err) +=
3785 "Only one of `bufferView` or `uri` should be defined, but both are "
3786 "defined for image[" +
3787 std::to_string(val: image_idx) + "] name = \"" + image->name + "\"\n";
3788 }
3789 return false;
3790 }
3791
3792 if (!hasBufferView && !hasURI) {
3793 if (err) {
3794 (*err) += "Neither required `bufferView` nor `uri` defined for image[" +
3795 std::to_string(val: image_idx) + "] name = \"" + image->name +
3796 "\"\n";
3797 }
3798 return false;
3799 }
3800
3801 ParseExtensionsProperty(ret: &image->extensions, err, o);
3802 ParseExtrasProperty(ret: &image->extras, o);
3803
3804 if (store_original_json_for_extras_and_extensions) {
3805 {
3806 json_const_iterator eit;
3807 if (FindMember(o, member: "extensions", it&: eit)) {
3808 image->extensions_json_string = JsonToString(o: GetValue(it&: eit));
3809 }
3810 }
3811 {
3812 json_const_iterator eit;
3813 if (FindMember(o, member: "extras", it&: eit)) {
3814 image->extras_json_string = JsonToString(o: GetValue(it&: eit));
3815 }
3816 }
3817 }
3818
3819 if (hasBufferView) {
3820 int bufferView = -1;
3821 if (!ParseIntegerProperty(ret: &bufferView, err, o, property: "bufferView", required: true)) {
3822 if (err) {
3823 (*err) += "Failed to parse `bufferView` for image[" +
3824 std::to_string(val: image_idx) + "] name = \"" + image->name +
3825 "\"\n";
3826 }
3827 return false;
3828 }
3829
3830 std::string mime_type;
3831 ParseStringProperty(ret: &mime_type, err, o, property: "mimeType", required: false);
3832
3833 int width = 0;
3834 ParseIntegerProperty(ret: &width, err, o, property: "width", required: false);
3835
3836 int height = 0;
3837 ParseIntegerProperty(ret: &height, err, o, property: "height", required: false);
3838
3839 // Just only save some information here. Loading actual image data from
3840 // bufferView is done after this `ParseImage` function.
3841 image->bufferView = bufferView;
3842 image->mimeType = mime_type;
3843 image->width = width;
3844 image->height = height;
3845
3846 return true;
3847 }
3848
3849 // Parse URI & Load image data.
3850
3851 std::string uri;
3852 std::string tmp_err;
3853 if (!ParseStringProperty(ret: &uri, err: &tmp_err, o, property: "uri", required: true)) {
3854 if (err) {
3855 (*err) += "Failed to parse `uri` for image[" + std::to_string(val: image_idx) +
3856 "] name = \"" + image->name + "\".\n";
3857 }
3858 return false;
3859 }
3860
3861 std::vector<unsigned char> img;
3862
3863 if (IsDataURI(in: uri)) {
3864 if (!DecodeDataURI(out: &img, mime_type&: image->mimeType, in: uri, reqBytes: 0, checkSize: false)) {
3865 if (err) {
3866 (*err) += "Failed to decode 'uri' for image[" +
3867 std::to_string(val: image_idx) + "] name = [" + image->name +
3868 "]\n";
3869 }
3870 return false;
3871 }
3872 } else {
3873 // Assume external file
3874 // Keep texture path (for textures that cannot be decoded)
3875 image->uri = uri;
3876#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
3877 return true;
3878#else
3879 std::string decoded_uri = dlib::urldecode(str: uri);
3880 if (!LoadExternalFile(out: &img, err, warn, filename: decoded_uri, basedir,
3881 /* required */ required: false, /* required bytes */ reqBytes: 0,
3882 /* checksize */ checkSize: false, fs)) {
3883 if (warn) {
3884 (*warn) += "Failed to load external 'uri' for image[" +
3885 std::to_string(val: image_idx) + "] name = [" + image->name +
3886 "]\n";
3887 }
3888 // If the image cannot be loaded, keep uri as image->uri.
3889 return true;
3890 }
3891
3892 if (img.empty()) {
3893 if (warn) {
3894 (*warn) += "Image data is empty for image[" +
3895 std::to_string(val: image_idx) + "] name = [" + image->name +
3896 "] \n";
3897 }
3898 return false;
3899 }
3900#endif
3901 }
3902
3903 if (*LoadImageData == nullptr) {
3904 if (err) {
3905 (*err) += "No LoadImageData callback specified.\n";
3906 }
3907 return false;
3908 }
3909 return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(n: 0),
3910 static_cast<int>(img.size()), load_image_user_data);
3911}
3912
3913static bool ParseTexture(Texture *texture, std::string *err, const json &o,
3914 bool store_original_json_for_extras_and_extensions,
3915 const std::string &basedir) {
3916 (void)basedir;
3917 int sampler = -1;
3918 int source = -1;
3919 ParseIntegerProperty(ret: &sampler, err, o, property: "sampler", required: false);
3920
3921 ParseIntegerProperty(ret: &source, err, o, property: "source", required: false);
3922
3923 texture->sampler = sampler;
3924 texture->source = source;
3925
3926 ParseExtensionsProperty(ret: &texture->extensions, err, o);
3927 ParseExtrasProperty(ret: &texture->extras, o);
3928
3929 if (store_original_json_for_extras_and_extensions) {
3930 {
3931 json_const_iterator it;
3932 if (FindMember(o, member: "extensions", it)) {
3933 texture->extensions_json_string = JsonToString(o: GetValue(it));
3934 }
3935 }
3936 {
3937 json_const_iterator it;
3938 if (FindMember(o, member: "extras", it)) {
3939 texture->extras_json_string = JsonToString(o: GetValue(it));
3940 }
3941 }
3942 }
3943
3944 ParseStringProperty(ret: &texture->name, err, o, property: "name", required: false);
3945
3946 return true;
3947}
3948
3949static bool ParseTextureInfo(
3950 TextureInfo *texinfo, std::string *err, const json &o,
3951 bool store_original_json_for_extras_and_extensions) {
3952 if (texinfo == nullptr) {
3953 return false;
3954 }
3955
3956 if (!ParseIntegerProperty(ret: &texinfo->index, err, o, property: "index",
3957 /* required */ required: true, parent_node: "TextureInfo")) {
3958 return false;
3959 }
3960
3961 ParseIntegerProperty(ret: &texinfo->texCoord, err, o, property: "texCoord", required: false);
3962
3963 ParseExtensionsProperty(ret: &texinfo->extensions, err, o);
3964 ParseExtrasProperty(ret: &texinfo->extras, o);
3965
3966 if (store_original_json_for_extras_and_extensions) {
3967 {
3968 json_const_iterator it;
3969 if (FindMember(o, member: "extensions", it)) {
3970 texinfo->extensions_json_string = JsonToString(o: GetValue(it));
3971 }
3972 }
3973 {
3974 json_const_iterator it;
3975 if (FindMember(o, member: "extras", it)) {
3976 texinfo->extras_json_string = JsonToString(o: GetValue(it));
3977 }
3978 }
3979 }
3980
3981 return true;
3982}
3983
3984static bool ParseNormalTextureInfo(
3985 NormalTextureInfo *texinfo, std::string *err, const json &o,
3986 bool store_original_json_for_extras_and_extensions) {
3987 if (texinfo == nullptr) {
3988 return false;
3989 }
3990
3991 if (!ParseIntegerProperty(ret: &texinfo->index, err, o, property: "index",
3992 /* required */ required: true, parent_node: "NormalTextureInfo")) {
3993 return false;
3994 }
3995
3996 ParseIntegerProperty(ret: &texinfo->texCoord, err, o, property: "texCoord", required: false);
3997 ParseNumberProperty(ret: &texinfo->scale, err, o, property: "scale", required: false);
3998
3999 ParseExtensionsProperty(ret: &texinfo->extensions, err, o);
4000 ParseExtrasProperty(ret: &texinfo->extras, o);
4001
4002 if (store_original_json_for_extras_and_extensions) {
4003 {
4004 json_const_iterator it;
4005 if (FindMember(o, member: "extensions", it)) {
4006 texinfo->extensions_json_string = JsonToString(o: GetValue(it));
4007 }
4008 }
4009 {
4010 json_const_iterator it;
4011 if (FindMember(o, member: "extras", it)) {
4012 texinfo->extras_json_string = JsonToString(o: GetValue(it));
4013 }
4014 }
4015 }
4016
4017 return true;
4018}
4019
4020static bool ParseOcclusionTextureInfo(
4021 OcclusionTextureInfo *texinfo, std::string *err, const json &o,
4022 bool store_original_json_for_extras_and_extensions) {
4023 if (texinfo == nullptr) {
4024 return false;
4025 }
4026
4027 if (!ParseIntegerProperty(ret: &texinfo->index, err, o, property: "index",
4028 /* required */ required: true, parent_node: "NormalTextureInfo")) {
4029 return false;
4030 }
4031
4032 ParseIntegerProperty(ret: &texinfo->texCoord, err, o, property: "texCoord", required: false);
4033 ParseNumberProperty(ret: &texinfo->strength, err, o, property: "strength", required: false);
4034
4035 ParseExtensionsProperty(ret: &texinfo->extensions, err, o);
4036 ParseExtrasProperty(ret: &texinfo->extras, o);
4037
4038 if (store_original_json_for_extras_and_extensions) {
4039 {
4040 json_const_iterator it;
4041 if (FindMember(o, member: "extensions", it)) {
4042 texinfo->extensions_json_string = JsonToString(o: GetValue(it));
4043 }
4044 }
4045 {
4046 json_const_iterator it;
4047 if (FindMember(o, member: "extras", it)) {
4048 texinfo->extras_json_string = JsonToString(o: GetValue(it));
4049 }
4050 }
4051 }
4052
4053 return true;
4054}
4055
4056static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
4057 bool store_original_json_for_extras_and_extensions,
4058 FsCallbacks *fs, const std::string &basedir,
4059 bool is_binary = false,
4060 const unsigned char *bin_data = nullptr,
4061 size_t bin_size = 0) {
4062 size_t byteLength;
4063 if (!ParseUnsignedProperty(ret: &byteLength, err, o, property: "byteLength", required: true,
4064 parent_node: "Buffer")) {
4065 return false;
4066 }
4067
4068 // In glTF 2.0, uri is not mandatory anymore
4069 buffer->uri.clear();
4070 ParseStringProperty(ret: &buffer->uri, err, o, property: "uri", required: false, parent_node: "Buffer");
4071
4072 // having an empty uri for a non embedded image should not be valid
4073 if (!is_binary && buffer->uri.empty()) {
4074 if (err) {
4075 (*err) += "'uri' is missing from non binary glTF file buffer.\n";
4076 }
4077 }
4078
4079 json_const_iterator type;
4080 if (FindMember(o, member: "type", it&: type)) {
4081 std::string typeStr;
4082 if (GetString(o: GetValue(it&: type), val&: typeStr)) {
4083 if (typeStr.compare(s: "arraybuffer") == 0) {
4084 // buffer.type = "arraybuffer";
4085 }
4086 }
4087 }
4088
4089 if (is_binary) {
4090 // Still binary glTF accepts external dataURI.
4091 if (!buffer->uri.empty()) {
4092 // First try embedded data URI.
4093 if (IsDataURI(in: buffer->uri)) {
4094 std::string mime_type;
4095 if (!DecodeDataURI(out: &buffer->data, mime_type, in: buffer->uri, reqBytes: byteLength,
4096 checkSize: true)) {
4097 if (err) {
4098 (*err) +=
4099 "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n";
4100 }
4101 return false;
4102 }
4103 } else {
4104 // External .bin file.
4105 std::string decoded_uri = dlib::urldecode(str: buffer->uri);
4106 if (!LoadExternalFile(out: &buffer->data, err, /* warn */ warn: nullptr,
4107 filename: decoded_uri, basedir, /* required */ required: true,
4108 reqBytes: byteLength, /* checkSize */ checkSize: true, fs)) {
4109 return false;
4110 }
4111 }
4112 } else {
4113 // load data from (embedded) binary data
4114
4115 if ((bin_size == 0) || (bin_data == nullptr)) {
4116 if (err) {
4117 (*err) += "Invalid binary data in `Buffer', or GLB with empty BIN chunk.\n";
4118 }
4119 return false;
4120 }
4121
4122 if (byteLength > bin_size) {
4123 if (err) {
4124 std::stringstream ss;
4125 ss << "Invalid `byteLength'. Must be equal or less than binary size: "
4126 "`byteLength' = "
4127 << byteLength << ", binary size = " << bin_size << std::endl;
4128 (*err) += ss.str();
4129 }
4130 return false;
4131 }
4132
4133 // Read buffer data
4134 buffer->data.resize(sz: static_cast<size_t>(byteLength));
4135 memcpy(dest: &(buffer->data.at(n: 0)), src: bin_data, n: static_cast<size_t>(byteLength));
4136 }
4137
4138 } else {
4139 if (IsDataURI(in: buffer->uri)) {
4140 std::string mime_type;
4141 if (!DecodeDataURI(out: &buffer->data, mime_type, in: buffer->uri, reqBytes: byteLength,
4142 checkSize: true)) {
4143 if (err) {
4144 (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n";
4145 }
4146 return false;
4147 }
4148 } else {
4149 // Assume external .bin file.
4150 std::string decoded_uri = dlib::urldecode(str: buffer->uri);
4151 if (!LoadExternalFile(out: &buffer->data, err, /* warn */ warn: nullptr, filename: decoded_uri,
4152 basedir, /* required */ required: true, reqBytes: byteLength,
4153 /* checkSize */ checkSize: true, fs)) {
4154 return false;
4155 }
4156 }
4157 }
4158
4159 ParseStringProperty(ret: &buffer->name, err, o, property: "name", required: false);
4160
4161 ParseExtensionsProperty(ret: &buffer->extensions, err, o);
4162 ParseExtrasProperty(ret: &buffer->extras, o);
4163
4164 if (store_original_json_for_extras_and_extensions) {
4165 {
4166 json_const_iterator it;
4167 if (FindMember(o, member: "extensions", it)) {
4168 buffer->extensions_json_string = JsonToString(o: GetValue(it));
4169 }
4170 }
4171 {
4172 json_const_iterator it;
4173 if (FindMember(o, member: "extras", it)) {
4174 buffer->extras_json_string = JsonToString(o: GetValue(it));
4175 }
4176 }
4177 }
4178
4179 return true;
4180}
4181
4182static bool ParseBufferView(
4183 BufferView *bufferView, std::string *err, const json &o,
4184 bool store_original_json_for_extras_and_extensions) {
4185 int buffer = -1;
4186 if (!ParseIntegerProperty(ret: &buffer, err, o, property: "buffer", required: true, parent_node: "BufferView")) {
4187 return false;
4188 }
4189
4190 size_t byteOffset = 0;
4191 ParseUnsignedProperty(ret: &byteOffset, err, o, property: "byteOffset", required: false);
4192
4193 size_t byteLength = 1;
4194 if (!ParseUnsignedProperty(ret: &byteLength, err, o, property: "byteLength", required: true,
4195 parent_node: "BufferView")) {
4196 return false;
4197 }
4198
4199 size_t byteStride = 0;
4200 if (!ParseUnsignedProperty(ret: &byteStride, err, o, property: "byteStride", required: false)) {
4201 // Spec says: When byteStride of referenced bufferView is not defined, it
4202 // means that accessor elements are tightly packed, i.e., effective stride
4203 // equals the size of the element.
4204 // We cannot determine the actual byteStride until Accessor are parsed, thus
4205 // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner)
4206 byteStride = 0;
4207 }
4208
4209 if ((byteStride > 252) || ((byteStride % 4) != 0)) {
4210 if (err) {
4211 std::stringstream ss;
4212 ss << "Invalid `byteStride' value. `byteStride' must be the multiple of "
4213 "4 : "
4214 << byteStride << std::endl;
4215
4216 (*err) += ss.str();
4217 }
4218 return false;
4219 }
4220
4221 int target = 0;
4222 ParseIntegerProperty(ret: &target, err, o, property: "target", required: false);
4223 if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) ||
4224 (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) {
4225 // OK
4226 } else {
4227 target = 0;
4228 }
4229 bufferView->target = target;
4230
4231 ParseStringProperty(ret: &bufferView->name, err, o, property: "name", required: false);
4232
4233 ParseExtensionsProperty(ret: &bufferView->extensions, err, o);
4234 ParseExtrasProperty(ret: &bufferView->extras, o);
4235
4236 if (store_original_json_for_extras_and_extensions) {
4237 {
4238 json_const_iterator it;
4239 if (FindMember(o, member: "extensions", it)) {
4240 bufferView->extensions_json_string = JsonToString(o: GetValue(it));
4241 }
4242 }
4243 {
4244 json_const_iterator it;
4245 if (FindMember(o, member: "extras", it)) {
4246 bufferView->extras_json_string = JsonToString(o: GetValue(it));
4247 }
4248 }
4249 }
4250
4251 bufferView->buffer = buffer;
4252 bufferView->byteOffset = byteOffset;
4253 bufferView->byteLength = byteLength;
4254 bufferView->byteStride = byteStride;
4255 return true;
4256}
4257
4258static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
4259 const json &o) {
4260 accessor->sparse.isSparse = true;
4261
4262 int count = 0;
4263 if (!ParseIntegerProperty(ret: &count, err, o, property: "count", required: true, parent_node: "SparseAccessor")) {
4264 return false;
4265 }
4266
4267 json_const_iterator indices_iterator;
4268 json_const_iterator values_iterator;
4269 if (!FindMember(o, member: "indices", it&: indices_iterator)) {
4270 (*err) = "the sparse object of this accessor doesn't have indices";
4271 return false;
4272 }
4273
4274 if (!FindMember(o, member: "values", it&: values_iterator)) {
4275 (*err) = "the sparse object ob ths accessor doesn't have values";
4276 return false;
4277 }
4278
4279 const json &indices_obj = GetValue(it&: indices_iterator);
4280 const json &values_obj = GetValue(it&: values_iterator);
4281
4282 int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0;
4283 if (!ParseIntegerProperty(ret: &indices_buffer_view, err, o: indices_obj,
4284 property: "bufferView", required: true, parent_node: "SparseAccessor")) {
4285 return false;
4286 }
4287 ParseIntegerProperty(ret: &indices_byte_offset, err, o: indices_obj, property: "byteOffset",
4288 required: false);
4289 if (!ParseIntegerProperty(ret: &component_type, err, o: indices_obj, property: "componentType",
4290 required: true, parent_node: "SparseAccessor")) {
4291 return false;
4292 }
4293
4294 int values_buffer_view = 0, values_byte_offset = 0;
4295 if (!ParseIntegerProperty(ret: &values_buffer_view, err, o: values_obj, property: "bufferView",
4296 required: true, parent_node: "SparseAccessor")) {
4297 return false;
4298 }
4299 ParseIntegerProperty(ret: &values_byte_offset, err, o: values_obj, property: "byteOffset",
4300 required: false);
4301
4302 accessor->sparse.count = count;
4303 accessor->sparse.indices.bufferView = indices_buffer_view;
4304 accessor->sparse.indices.byteOffset = indices_byte_offset;
4305 accessor->sparse.indices.componentType = component_type;
4306 accessor->sparse.values.bufferView = values_buffer_view;
4307 accessor->sparse.values.byteOffset = values_byte_offset;
4308
4309 return true;
4310}
4311
4312static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o,
4313 bool store_original_json_for_extras_and_extensions) {
4314 int bufferView = -1;
4315 ParseIntegerProperty(ret: &bufferView, err, o, property: "bufferView", required: false, parent_node: "Accessor");
4316
4317 size_t byteOffset = 0;
4318 ParseUnsignedProperty(ret: &byteOffset, err, o, property: "byteOffset", required: false, parent_node: "Accessor");
4319
4320 bool normalized = false;
4321 ParseBooleanProperty(ret: &normalized, err, o, property: "normalized", required: false, parent_node: "Accessor");
4322
4323 size_t componentType = 0;
4324 if (!ParseUnsignedProperty(ret: &componentType, err, o, property: "componentType", required: true,
4325 parent_node: "Accessor")) {
4326 return false;
4327 }
4328
4329 size_t count = 0;
4330 if (!ParseUnsignedProperty(ret: &count, err, o, property: "count", required: true, parent_node: "Accessor")) {
4331 return false;
4332 }
4333
4334 std::string type;
4335 if (!ParseStringProperty(ret: &type, err, o, property: "type", required: true, parent_node: "Accessor")) {
4336 return false;
4337 }
4338
4339 if (type.compare(s: "SCALAR") == 0) {
4340 accessor->type = TINYGLTF_TYPE_SCALAR;
4341 } else if (type.compare(s: "VEC2") == 0) {
4342 accessor->type = TINYGLTF_TYPE_VEC2;
4343 } else if (type.compare(s: "VEC3") == 0) {
4344 accessor->type = TINYGLTF_TYPE_VEC3;
4345 } else if (type.compare(s: "VEC4") == 0) {
4346 accessor->type = TINYGLTF_TYPE_VEC4;
4347 } else if (type.compare(s: "MAT2") == 0) {
4348 accessor->type = TINYGLTF_TYPE_MAT2;
4349 } else if (type.compare(s: "MAT3") == 0) {
4350 accessor->type = TINYGLTF_TYPE_MAT3;
4351 } else if (type.compare(s: "MAT4") == 0) {
4352 accessor->type = TINYGLTF_TYPE_MAT4;
4353 } else {
4354 std::stringstream ss;
4355 ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n";
4356 if (err) {
4357 (*err) += ss.str();
4358 }
4359 return false;
4360 }
4361
4362 ParseStringProperty(ret: &accessor->name, err, o, property: "name", required: false);
4363
4364 accessor->minValues.clear();
4365 accessor->maxValues.clear();
4366 ParseNumberArrayProperty(ret: &accessor->minValues, err, o, property: "min", required: false,
4367 parent_node: "Accessor");
4368
4369 ParseNumberArrayProperty(ret: &accessor->maxValues, err, o, property: "max", required: false,
4370 parent_node: "Accessor");
4371
4372 accessor->count = count;
4373 accessor->bufferView = bufferView;
4374 accessor->byteOffset = byteOffset;
4375 accessor->normalized = normalized;
4376 {
4377 if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE &&
4378 componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) {
4379 // OK
4380 accessor->componentType = int(componentType);
4381 } else {
4382 std::stringstream ss;
4383 ss << "Invalid `componentType` in accessor. Got " << componentType
4384 << "\n";
4385 if (err) {
4386 (*err) += ss.str();
4387 }
4388 return false;
4389 }
4390 }
4391
4392 ParseExtensionsProperty(ret: &(accessor->extensions), err, o);
4393 ParseExtrasProperty(ret: &(accessor->extras), o);
4394
4395 if (store_original_json_for_extras_and_extensions) {
4396 {
4397 json_const_iterator it;
4398 if (FindMember(o, member: "extensions", it)) {
4399 accessor->extensions_json_string = JsonToString(o: GetValue(it));
4400 }
4401 }
4402 {
4403 json_const_iterator it;
4404 if (FindMember(o, member: "extras", it)) {
4405 accessor->extras_json_string = JsonToString(o: GetValue(it));
4406 }
4407 }
4408 }
4409
4410 // check if accessor has a "sparse" object:
4411 json_const_iterator iterator;
4412 if (FindMember(o, member: "sparse", it&: iterator)) {
4413 // here this accessor has a "sparse" subobject
4414 return ParseSparseAccessor(accessor, err, o: GetValue(it&: iterator));
4415 }
4416
4417 return true;
4418}
4419
4420#ifdef TINYGLTF_ENABLE_DRACO
4421
4422static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize,
4423 std::vector<uint8_t> &outBuffer) {
4424 if (componentSize == 4) {
4425 assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize);
4426 memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0],
4427 outBuffer.size());
4428 } else {
4429 size_t faceStride = componentSize * 3;
4430 for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) {
4431 const draco::Mesh::Face &face = mesh->face(f);
4432 if (componentSize == 2) {
4433 uint16_t indices[3] = {(uint16_t)face[0].value(),
4434 (uint16_t)face[1].value(),
4435 (uint16_t)face[2].value()};
4436 memcpy(outBuffer.data() + f.value() * faceStride, &indices[0],
4437 faceStride);
4438 } else {
4439 uint8_t indices[3] = {(uint8_t)face[0].value(),
4440 (uint8_t)face[1].value(),
4441 (uint8_t)face[2].value()};
4442 memcpy(outBuffer.data() + f.value() * faceStride, &indices[0],
4443 faceStride);
4444 }
4445 }
4446 }
4447}
4448
4449template <typename T>
4450static bool GetAttributeForAllPoints(draco::Mesh *mesh,
4451 const draco::PointAttribute *pAttribute,
4452 std::vector<uint8_t> &outBuffer) {
4453 size_t byteOffset = 0;
4454 T values[4] = {0, 0, 0, 0};
4455 for (draco::PointIndex i(0); i < mesh->num_points(); ++i) {
4456 const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i);
4457 if (!pAttribute->ConvertValue<T>(val_index, pAttribute->num_components(),
4458 values))
4459 return false;
4460
4461 memcpy(outBuffer.data() + byteOffset, &values[0],
4462 sizeof(T) * pAttribute->num_components());
4463 byteOffset += sizeof(T) * pAttribute->num_components();
4464 }
4465
4466 return true;
4467}
4468
4469static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh,
4470 const draco::PointAttribute *pAttribute,
4471 std::vector<uint8_t> &outBuffer) {
4472 bool decodeResult = false;
4473 switch (componentType) {
4474 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
4475 decodeResult =
4476 GetAttributeForAllPoints<uint8_t>(mesh, pAttribute, outBuffer);
4477 break;
4478 case TINYGLTF_COMPONENT_TYPE_BYTE:
4479 decodeResult =
4480 GetAttributeForAllPoints<int8_t>(mesh, pAttribute, outBuffer);
4481 break;
4482 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
4483 decodeResult =
4484 GetAttributeForAllPoints<uint16_t>(mesh, pAttribute, outBuffer);
4485 break;
4486 case TINYGLTF_COMPONENT_TYPE_SHORT:
4487 decodeResult =
4488 GetAttributeForAllPoints<int16_t>(mesh, pAttribute, outBuffer);
4489 break;
4490 case TINYGLTF_COMPONENT_TYPE_INT:
4491 decodeResult =
4492 GetAttributeForAllPoints<int32_t>(mesh, pAttribute, outBuffer);
4493 break;
4494 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
4495 decodeResult =
4496 GetAttributeForAllPoints<uint32_t>(mesh, pAttribute, outBuffer);
4497 break;
4498 case TINYGLTF_COMPONENT_TYPE_FLOAT:
4499 decodeResult =
4500 GetAttributeForAllPoints<float>(mesh, pAttribute, outBuffer);
4501 break;
4502 case TINYGLTF_COMPONENT_TYPE_DOUBLE:
4503 decodeResult =
4504 GetAttributeForAllPoints<double>(mesh, pAttribute, outBuffer);
4505 break;
4506 default:
4507 return false;
4508 }
4509
4510 return decodeResult;
4511}
4512
4513static bool ParseDracoExtension(Primitive *primitive, Model *model,
4514 std::string *err,
4515 const Value &dracoExtensionValue) {
4516 (void)err;
4517 auto bufferViewValue = dracoExtensionValue.Get("bufferView");
4518 if (!bufferViewValue.IsInt()) return false;
4519 auto attributesValue = dracoExtensionValue.Get("attributes");
4520 if (!attributesValue.IsObject()) return false;
4521
4522 auto attributesObject = attributesValue.Get<Value::Object>();
4523 int bufferView = bufferViewValue.Get<int>();
4524
4525 BufferView &view = model->bufferViews[bufferView];
4526 Buffer &buffer = model->buffers[view.buffer];
4527 // BufferView has already been decoded
4528 if (view.dracoDecoded) return true;
4529 view.dracoDecoded = true;
4530
4531 const char *bufferViewData =
4532 reinterpret_cast<const char *>(buffer.data.data() + view.byteOffset);
4533 size_t bufferViewSize = view.byteLength;
4534
4535 // decode draco
4536 draco::DecoderBuffer decoderBuffer;
4537 decoderBuffer.Init(bufferViewData, bufferViewSize);
4538 draco::Decoder decoder;
4539 auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer);
4540 if (!decodeResult.ok()) {
4541 return false;
4542 }
4543 const std::unique_ptr<draco::Mesh> &mesh = decodeResult.value();
4544
4545 // create new bufferView for indices
4546 if (primitive->indices >= 0) {
4547 int32_t componentSize = GetComponentSizeInBytes(
4548 model->accessors[primitive->indices].componentType);
4549 Buffer decodedIndexBuffer;
4550 decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize);
4551
4552 DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data);
4553
4554 model->buffers.emplace_back(std::move(decodedIndexBuffer));
4555
4556 BufferView decodedIndexBufferView;
4557 decodedIndexBufferView.buffer = int(model->buffers.size() - 1);
4558 decodedIndexBufferView.byteLength =
4559 int(mesh->num_faces() * 3 * componentSize);
4560 decodedIndexBufferView.byteOffset = 0;
4561 decodedIndexBufferView.byteStride = 0;
4562 decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
4563 model->bufferViews.emplace_back(std::move(decodedIndexBufferView));
4564
4565 model->accessors[primitive->indices].bufferView =
4566 int(model->bufferViews.size() - 1);
4567 model->accessors[primitive->indices].count = int(mesh->num_faces() * 3);
4568 }
4569
4570 for (const auto &attribute : attributesObject) {
4571 if (!attribute.second.IsInt()) return false;
4572 auto primitiveAttribute = primitive->attributes.find(attribute.first);
4573 if (primitiveAttribute == primitive->attributes.end()) return false;
4574
4575 int dracoAttributeIndex = attribute.second.Get<int>();
4576 const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex);
4577 const auto componentType =
4578 model->accessors[primitiveAttribute->second].componentType;
4579
4580 // Create a new buffer for this decoded buffer
4581 Buffer decodedBuffer;
4582 size_t bufferSize = mesh->num_points() * pAttribute->num_components() *
4583 GetComponentSizeInBytes(componentType);
4584 decodedBuffer.data.resize(bufferSize);
4585
4586 if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute,
4587 decodedBuffer.data))
4588 return false;
4589
4590 model->buffers.emplace_back(std::move(decodedBuffer));
4591
4592 BufferView decodedBufferView;
4593 decodedBufferView.buffer = int(model->buffers.size() - 1);
4594 decodedBufferView.byteLength = bufferSize;
4595 decodedBufferView.byteOffset = pAttribute->byte_offset();
4596 decodedBufferView.byteStride = pAttribute->byte_stride();
4597 decodedBufferView.target = primitive->indices >= 0
4598 ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER
4599 : TINYGLTF_TARGET_ARRAY_BUFFER;
4600 model->bufferViews.emplace_back(std::move(decodedBufferView));
4601
4602 model->accessors[primitiveAttribute->second].bufferView =
4603 int(model->bufferViews.size() - 1);
4604 model->accessors[primitiveAttribute->second].count =
4605 int(mesh->num_points());
4606 }
4607
4608 return true;
4609}
4610#endif
4611
4612static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err,
4613 const json &o,
4614 bool store_original_json_for_extras_and_extensions) {
4615 int material = -1;
4616 ParseIntegerProperty(ret: &material, err, o, property: "material", required: false);
4617 primitive->material = material;
4618
4619 int mode = TINYGLTF_MODE_TRIANGLES;
4620 ParseIntegerProperty(ret: &mode, err, o, property: "mode", required: false);
4621 primitive->mode = mode; // Why only triangled were supported ?
4622
4623 int indices = -1;
4624 ParseIntegerProperty(ret: &indices, err, o, property: "indices", required: false);
4625 primitive->indices = indices;
4626 if (!ParseStringIntegerProperty(ret: &primitive->attributes, err, o, property: "attributes",
4627 required: true, parent: "Primitive")) {
4628 return false;
4629 }
4630
4631 // Look for morph targets
4632 json_const_iterator targetsObject;
4633 if (FindMember(o, member: "targets", it&: targetsObject) &&
4634 IsArray(o: GetValue(it&: targetsObject))) {
4635 auto targetsObjectEnd = ArrayEnd(o: GetValue(it&: targetsObject));
4636 for (json_const_array_iterator i = ArrayBegin(o: GetValue(it&: targetsObject));
4637 i != targetsObjectEnd; ++i) {
4638 std::map<std::string, int> targetAttribues;
4639
4640 const json &dict = *i;
4641 if (IsObject(o: dict)) {
4642 json_const_iterator dictIt(ObjectBegin(o: dict));
4643 json_const_iterator dictItEnd(ObjectEnd(o: dict));
4644
4645 for (; dictIt != dictItEnd; ++dictIt) {
4646 int iVal;
4647 if (GetInt(o: GetValue(it&: dictIt), val&: iVal))
4648 targetAttribues[GetKey(it&: dictIt)] = iVal;
4649 }
4650 primitive->targets.emplace_back(args: std::move(targetAttribues));
4651 }
4652 }
4653 }
4654
4655 ParseExtrasProperty(ret: &(primitive->extras), o);
4656 ParseExtensionsProperty(ret: &primitive->extensions, err, o);
4657
4658 if (store_original_json_for_extras_and_extensions) {
4659 {
4660 json_const_iterator it;
4661 if (FindMember(o, member: "extensions", it)) {
4662 primitive->extensions_json_string = JsonToString(o: GetValue(it));
4663 }
4664 }
4665 {
4666 json_const_iterator it;
4667 if (FindMember(o, member: "extras", it)) {
4668 primitive->extras_json_string = JsonToString(o: GetValue(it));
4669 }
4670 }
4671 }
4672
4673#ifdef TINYGLTF_ENABLE_DRACO
4674 auto dracoExtension =
4675 primitive->extensions.find("KHR_draco_mesh_compression");
4676 if (dracoExtension != primitive->extensions.end()) {
4677 ParseDracoExtension(primitive, model, err, dracoExtension->second);
4678 }
4679#else
4680 (void)model;
4681#endif
4682
4683 return true;
4684}
4685
4686static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, const json &o,
4687 bool store_original_json_for_extras_and_extensions) {
4688 ParseStringProperty(ret: &mesh->name, err, o, property: "name", required: false);
4689
4690 mesh->primitives.clear();
4691 json_const_iterator primObject;
4692 if (FindMember(o, member: "primitives", it&: primObject) &&
4693 IsArray(o: GetValue(it&: primObject))) {
4694 json_const_array_iterator primEnd = ArrayEnd(o: GetValue(it&: primObject));
4695 for (json_const_array_iterator i = ArrayBegin(o: GetValue(it&: primObject));
4696 i != primEnd; ++i) {
4697 Primitive primitive;
4698 if (ParsePrimitive(primitive: &primitive, model, err, o: *i,
4699 store_original_json_for_extras_and_extensions)) {
4700 // Only add the primitive if the parsing succeeds.
4701 mesh->primitives.emplace_back(args: std::move(primitive));
4702 }
4703 }
4704 }
4705
4706 // Should probably check if has targets and if dimensions fit
4707 ParseNumberArrayProperty(ret: &mesh->weights, err, o, property: "weights", required: false);
4708
4709 ParseExtensionsProperty(ret: &mesh->extensions, err, o);
4710 ParseExtrasProperty(ret: &(mesh->extras), o);
4711
4712 if (store_original_json_for_extras_and_extensions) {
4713 {
4714 json_const_iterator it;
4715 if (FindMember(o, member: "extensions", it)) {
4716 mesh->extensions_json_string = JsonToString(o: GetValue(it));
4717 }
4718 }
4719 {
4720 json_const_iterator it;
4721 if (FindMember(o, member: "extras", it)) {
4722 mesh->extras_json_string = JsonToString(o: GetValue(it));
4723 }
4724 }
4725 }
4726
4727 return true;
4728}
4729
4730static bool ParseNode(Node *node, std::string *err, const json &o,
4731 bool store_original_json_for_extras_and_extensions) {
4732 ParseStringProperty(ret: &node->name, err, o, property: "name", required: false);
4733
4734 int skin = -1;
4735 ParseIntegerProperty(ret: &skin, err, o, property: "skin", required: false);
4736 node->skin = skin;
4737
4738 // Matrix and T/R/S are exclusive
4739 if (!ParseNumberArrayProperty(ret: &node->matrix, err, o, property: "matrix", required: false)) {
4740 ParseNumberArrayProperty(ret: &node->rotation, err, o, property: "rotation", required: false);
4741 ParseNumberArrayProperty(ret: &node->scale, err, o, property: "scale", required: false);
4742 ParseNumberArrayProperty(ret: &node->translation, err, o, property: "translation", required: false);
4743 }
4744
4745 int camera = -1;
4746 ParseIntegerProperty(ret: &camera, err, o, property: "camera", required: false);
4747 node->camera = camera;
4748
4749 int mesh = -1;
4750 ParseIntegerProperty(ret: &mesh, err, o, property: "mesh", required: false);
4751 node->mesh = mesh;
4752
4753 node->children.clear();
4754 ParseIntegerArrayProperty(ret: &node->children, err, o, property: "children", required: false);
4755
4756 ParseNumberArrayProperty(ret: &node->weights, err, o, property: "weights", required: false);
4757
4758 ParseExtensionsProperty(ret: &node->extensions, err, o);
4759 ParseExtrasProperty(ret: &(node->extras), o);
4760
4761 if (store_original_json_for_extras_and_extensions) {
4762 {
4763 json_const_iterator it;
4764 if (FindMember(o, member: "extensions", it)) {
4765 node->extensions_json_string = JsonToString(o: GetValue(it));
4766 }
4767 }
4768 {
4769 json_const_iterator it;
4770 if (FindMember(o, member: "extras", it)) {
4771 node->extras_json_string = JsonToString(o: GetValue(it));
4772 }
4773 }
4774 }
4775
4776 return true;
4777}
4778
4779static bool ParsePbrMetallicRoughness(
4780 PbrMetallicRoughness *pbr, std::string *err, const json &o,
4781 bool store_original_json_for_extras_and_extensions) {
4782 if (pbr == nullptr) {
4783 return false;
4784 }
4785
4786 std::vector<double> baseColorFactor;
4787 if (ParseNumberArrayProperty(ret: &baseColorFactor, err, o, property: "baseColorFactor",
4788 /* required */ required: false)) {
4789 if (baseColorFactor.size() != 4) {
4790 if (err) {
4791 (*err) +=
4792 "Array length of `baseColorFactor` parameter in "
4793 "pbrMetallicRoughness must be 4, but got " +
4794 std::to_string(val: baseColorFactor.size()) + "\n";
4795 }
4796 return false;
4797 }
4798 pbr->baseColorFactor = baseColorFactor;
4799 }
4800
4801 {
4802 json_const_iterator it;
4803 if (FindMember(o, member: "baseColorTexture", it)) {
4804 ParseTextureInfo(texinfo: &pbr->baseColorTexture, err, o: GetValue(it),
4805 store_original_json_for_extras_and_extensions);
4806 }
4807 }
4808
4809 {
4810 json_const_iterator it;
4811 if (FindMember(o, member: "metallicRoughnessTexture", it)) {
4812 ParseTextureInfo(texinfo: &pbr->metallicRoughnessTexture, err, o: GetValue(it),
4813 store_original_json_for_extras_and_extensions);
4814 }
4815 }
4816
4817 ParseNumberProperty(ret: &pbr->metallicFactor, err, o, property: "metallicFactor", required: false);
4818 ParseNumberProperty(ret: &pbr->roughnessFactor, err, o, property: "roughnessFactor", required: false);
4819
4820 ParseExtensionsProperty(ret: &pbr->extensions, err, o);
4821 ParseExtrasProperty(ret: &pbr->extras, o);
4822
4823 if (store_original_json_for_extras_and_extensions) {
4824 {
4825 json_const_iterator it;
4826 if (FindMember(o, member: "extensions", it)) {
4827 pbr->extensions_json_string = JsonToString(o: GetValue(it));
4828 }
4829 }
4830 {
4831 json_const_iterator it;
4832 if (FindMember(o, member: "extras", it)) {
4833 pbr->extras_json_string = JsonToString(o: GetValue(it));
4834 }
4835 }
4836 }
4837
4838 return true;
4839}
4840
4841static bool ParseMaterial(Material *material, std::string *err, const json &o,
4842 bool store_original_json_for_extras_and_extensions) {
4843 ParseStringProperty(ret: &material->name, err, o, property: "name", /* required */ required: false);
4844
4845 if (ParseNumberArrayProperty(ret: &material->emissiveFactor, err, o,
4846 property: "emissiveFactor",
4847 /* required */ required: false)) {
4848 if (material->emissiveFactor.size() != 3) {
4849 if (err) {
4850 (*err) +=
4851 "Array length of `emissiveFactor` parameter in "
4852 "material must be 3, but got " +
4853 std::to_string(val: material->emissiveFactor.size()) + "\n";
4854 }
4855 return false;
4856 }
4857 } else {
4858 // fill with default values
4859 material->emissiveFactor = {0.0, 0.0, 0.0};
4860 }
4861
4862 ParseStringProperty(ret: &material->alphaMode, err, o, property: "alphaMode",
4863 /* required */ required: false);
4864 ParseNumberProperty(ret: &material->alphaCutoff, err, o, property: "alphaCutoff",
4865 /* required */ required: false);
4866 ParseBooleanProperty(ret: &material->doubleSided, err, o, property: "doubleSided",
4867 /* required */ required: false);
4868
4869 {
4870 json_const_iterator it;
4871 if (FindMember(o, member: "pbrMetallicRoughness", it)) {
4872 ParsePbrMetallicRoughness(pbr: &material->pbrMetallicRoughness, err,
4873 o: GetValue(it),
4874 store_original_json_for_extras_and_extensions);
4875 }
4876 }
4877
4878 {
4879 json_const_iterator it;
4880 if (FindMember(o, member: "normalTexture", it)) {
4881 ParseNormalTextureInfo(texinfo: &material->normalTexture, err, o: GetValue(it),
4882 store_original_json_for_extras_and_extensions);
4883 }
4884 }
4885
4886 {
4887 json_const_iterator it;
4888 if (FindMember(o, member: "occlusionTexture", it)) {
4889 ParseOcclusionTextureInfo(texinfo: &material->occlusionTexture, err, o: GetValue(it),
4890 store_original_json_for_extras_and_extensions);
4891 }
4892 }
4893
4894 {
4895 json_const_iterator it;
4896 if (FindMember(o, member: "emissiveTexture", it)) {
4897 ParseTextureInfo(texinfo: &material->emissiveTexture, err, o: GetValue(it),
4898 store_original_json_for_extras_and_extensions);
4899 }
4900 }
4901
4902 // Old code path. For backward compatibility, we still store material values
4903 // as Parameter. This will create duplicated information for
4904 // example(pbrMetallicRoughness), but should be neglible in terms of memory
4905 // consumption.
4906 // TODO(syoyo): Remove in the next major release.
4907 material->values.clear();
4908 material->additionalValues.clear();
4909
4910 json_const_iterator it(ObjectBegin(o));
4911 json_const_iterator itEnd(ObjectEnd(o));
4912
4913 for (; it != itEnd; ++it) {
4914 std::string key(GetKey(it));
4915 if (key == "pbrMetallicRoughness") {
4916 if (IsObject(o: GetValue(it))) {
4917 const json &values_object = GetValue(it);
4918
4919 json_const_iterator itVal(ObjectBegin(o: values_object));
4920 json_const_iterator itValEnd(ObjectEnd(o: values_object));
4921
4922 for (; itVal != itValEnd; ++itVal) {
4923 Parameter param;
4924 if (ParseParameterProperty(param: &param, err, o: values_object, prop: GetKey(it&: itVal),
4925 required: false)) {
4926 material->values.emplace(args: GetKey(it&: itVal), args: std::move(param));
4927 }
4928 }
4929 }
4930 } else if (key == "extensions" || key == "extras") {
4931 // done later, skip, otherwise poorly parsed contents will be saved in the
4932 // parametermap and serialized again later
4933 } else {
4934 Parameter param;
4935 if (ParseParameterProperty(param: &param, err, o, prop: key, required: false)) {
4936 // names of materials have already been parsed. Putting it in this map
4937 // doesn't correctly reflext the glTF specification
4938 if (key != "name")
4939 material->additionalValues.emplace(args: std::move(key), args: std::move(param));
4940 }
4941 }
4942 }
4943
4944 material->extensions.clear();
4945 ParseExtensionsProperty(ret: &material->extensions, err, o);
4946 ParseExtrasProperty(ret: &(material->extras), o);
4947
4948 if (store_original_json_for_extras_and_extensions) {
4949 {
4950 json_const_iterator eit;
4951 if (FindMember(o, member: "extensions", it&: eit)) {
4952 material->extensions_json_string = JsonToString(o: GetValue(it&: eit));
4953 }
4954 }
4955 {
4956 json_const_iterator eit;
4957 if (FindMember(o, member: "extras", it&: eit)) {
4958 material->extras_json_string = JsonToString(o: GetValue(it&: eit));
4959 }
4960 }
4961 }
4962
4963 return true;
4964}
4965
4966static bool ParseAnimationChannel(
4967 AnimationChannel *channel, std::string *err, const json &o,
4968 bool store_original_json_for_extras_and_extensions) {
4969 int samplerIndex = -1;
4970 int targetIndex = -1;
4971 if (!ParseIntegerProperty(ret: &samplerIndex, err, o, property: "sampler", required: true,
4972 parent_node: "AnimationChannel")) {
4973 if (err) {
4974 (*err) += "`sampler` field is missing in animation channels\n";
4975 }
4976 return false;
4977 }
4978
4979 json_const_iterator targetIt;
4980 if (FindMember(o, member: "target", it&: targetIt) && IsObject(o: GetValue(it&: targetIt))) {
4981 const json &target_object = GetValue(it&: targetIt);
4982
4983 if (!ParseIntegerProperty(ret: &targetIndex, err, o: target_object, property: "node", required: true)) {
4984 if (err) {
4985 (*err) += "`node` field is missing in animation.channels.target\n";
4986 }
4987 return false;
4988 }
4989
4990 if (!ParseStringProperty(ret: &channel->target_path, err, o: target_object, property: "path",
4991 required: true)) {
4992 if (err) {
4993 (*err) += "`path` field is missing in animation.channels.target\n";
4994 }
4995 return false;
4996 }
4997 ParseExtensionsProperty(ret: &channel->target_extensions, err, o: target_object);
4998 if (store_original_json_for_extras_and_extensions) {
4999 json_const_iterator it;
5000 if (FindMember(o: target_object, member: "extensions", it)) {
5001 channel->target_extensions_json_string = JsonToString(o: GetValue(it));
5002 }
5003 }
5004 }
5005
5006 channel->sampler = samplerIndex;
5007 channel->target_node = targetIndex;
5008
5009 ParseExtensionsProperty(ret: &channel->extensions, err, o);
5010 ParseExtrasProperty(ret: &(channel->extras), o);
5011
5012 if (store_original_json_for_extras_and_extensions) {
5013 {
5014 json_const_iterator it;
5015 if (FindMember(o, member: "extensions", it)) {
5016 channel->extensions_json_string = JsonToString(o: GetValue(it));
5017 }
5018 }
5019 {
5020 json_const_iterator it;
5021 if (FindMember(o, member: "extras", it)) {
5022 channel->extras_json_string = JsonToString(o: GetValue(it));
5023 }
5024 }
5025 }
5026
5027 return true;
5028}
5029
5030static bool ParseAnimation(Animation *animation, std::string *err,
5031 const json &o,
5032 bool store_original_json_for_extras_and_extensions) {
5033 {
5034 json_const_iterator channelsIt;
5035 if (FindMember(o, member: "channels", it&: channelsIt) &&
5036 IsArray(o: GetValue(it&: channelsIt))) {
5037 json_const_array_iterator channelEnd = ArrayEnd(o: GetValue(it&: channelsIt));
5038 for (json_const_array_iterator i = ArrayBegin(o: GetValue(it&: channelsIt));
5039 i != channelEnd; ++i) {
5040 AnimationChannel channel;
5041 if (ParseAnimationChannel(
5042 channel: &channel, err, o: *i,
5043 store_original_json_for_extras_and_extensions)) {
5044 // Only add the channel if the parsing succeeds.
5045 animation->channels.emplace_back(args: std::move(channel));
5046 }
5047 }
5048 }
5049 }
5050
5051 {
5052 json_const_iterator samplerIt;
5053 if (FindMember(o, member: "samplers", it&: samplerIt) && IsArray(o: GetValue(it&: samplerIt))) {
5054 const json &sampler_array = GetValue(it&: samplerIt);
5055
5056 json_const_array_iterator it = ArrayBegin(o: sampler_array);
5057 json_const_array_iterator itEnd = ArrayEnd(o: sampler_array);
5058
5059 for (; it != itEnd; ++it) {
5060 const json &s = *it;
5061
5062 AnimationSampler sampler;
5063 int inputIndex = -1;
5064 int outputIndex = -1;
5065 if (!ParseIntegerProperty(ret: &inputIndex, err, o: s, property: "input", required: true)) {
5066 if (err) {
5067 (*err) += "`input` field is missing in animation.sampler\n";
5068 }
5069 return false;
5070 }
5071 ParseStringProperty(ret: &sampler.interpolation, err, o: s, property: "interpolation",
5072 required: false);
5073 if (!ParseIntegerProperty(ret: &outputIndex, err, o: s, property: "output", required: true)) {
5074 if (err) {
5075 (*err) += "`output` field is missing in animation.sampler\n";
5076 }
5077 return false;
5078 }
5079 sampler.input = inputIndex;
5080 sampler.output = outputIndex;
5081 ParseExtensionsProperty(ret: &(sampler.extensions), err, o);
5082 ParseExtrasProperty(ret: &(sampler.extras), o: s);
5083
5084 if (store_original_json_for_extras_and_extensions) {
5085 {
5086 json_const_iterator eit;
5087 if (FindMember(o, member: "extensions", it&: eit)) {
5088 sampler.extensions_json_string = JsonToString(o: GetValue(it&: eit));
5089 }
5090 }
5091 {
5092 json_const_iterator eit;
5093 if (FindMember(o, member: "extras", it&: eit)) {
5094 sampler.extras_json_string = JsonToString(o: GetValue(it&: eit));
5095 }
5096 }
5097 }
5098
5099 animation->samplers.emplace_back(args: std::move(sampler));
5100 }
5101 }
5102 }
5103
5104 ParseStringProperty(ret: &animation->name, err, o, property: "name", required: false);
5105
5106 ParseExtensionsProperty(ret: &animation->extensions, err, o);
5107 ParseExtrasProperty(ret: &(animation->extras), o);
5108
5109 if (store_original_json_for_extras_and_extensions) {
5110 {
5111 json_const_iterator it;
5112 if (FindMember(o, member: "extensions", it)) {
5113 animation->extensions_json_string = JsonToString(o: GetValue(it));
5114 }
5115 }
5116 {
5117 json_const_iterator it;
5118 if (FindMember(o, member: "extras", it)) {
5119 animation->extras_json_string = JsonToString(o: GetValue(it));
5120 }
5121 }
5122 }
5123
5124 return true;
5125}
5126
5127static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
5128 bool store_original_json_for_extras_and_extensions) {
5129 ParseStringProperty(ret: &sampler->name, err, o, property: "name", required: false);
5130
5131 int minFilter = -1;
5132 int magFilter = -1;
5133 int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT;
5134 int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT;
5135 // int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
5136 ParseIntegerProperty(ret: &minFilter, err, o, property: "minFilter", required: false);
5137 ParseIntegerProperty(ret: &magFilter, err, o, property: "magFilter", required: false);
5138 ParseIntegerProperty(ret: &wrapS, err, o, property: "wrapS", required: false);
5139 ParseIntegerProperty(ret: &wrapT, err, o, property: "wrapT", required: false);
5140 // ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf
5141 // extension
5142
5143 // TODO(syoyo): Check the value is alloed one.
5144 // (e.g. we allow 9728(NEAREST), but don't allow 9727)
5145
5146 sampler->minFilter = minFilter;
5147 sampler->magFilter = magFilter;
5148 sampler->wrapS = wrapS;
5149 sampler->wrapT = wrapT;
5150 // sampler->wrapR = wrapR;
5151
5152 ParseExtensionsProperty(ret: &(sampler->extensions), err, o);
5153 ParseExtrasProperty(ret: &(sampler->extras), o);
5154
5155 if (store_original_json_for_extras_and_extensions) {
5156 {
5157 json_const_iterator it;
5158 if (FindMember(o, member: "extensions", it)) {
5159 sampler->extensions_json_string = JsonToString(o: GetValue(it));
5160 }
5161 }
5162 {
5163 json_const_iterator it;
5164 if (FindMember(o, member: "extras", it)) {
5165 sampler->extras_json_string = JsonToString(o: GetValue(it));
5166 }
5167 }
5168 }
5169
5170 return true;
5171}
5172
5173static bool ParseSkin(Skin *skin, std::string *err, const json &o,
5174 bool store_original_json_for_extras_and_extensions) {
5175 ParseStringProperty(ret: &skin->name, err, o, property: "name", required: false, parent_node: "Skin");
5176
5177 std::vector<int> joints;
5178 if (!ParseIntegerArrayProperty(ret: &joints, err, o, property: "joints", required: false, parent_node: "Skin")) {
5179 return false;
5180 }
5181 skin->joints = std::move(joints);
5182
5183 int skeleton = -1;
5184 ParseIntegerProperty(ret: &skeleton, err, o, property: "skeleton", required: false, parent_node: "Skin");
5185 skin->skeleton = skeleton;
5186
5187 int invBind = -1;
5188 ParseIntegerProperty(ret: &invBind, err, o, property: "inverseBindMatrices", required: true, parent_node: "Skin");
5189 skin->inverseBindMatrices = invBind;
5190
5191 ParseExtensionsProperty(ret: &(skin->extensions), err, o);
5192 ParseExtrasProperty(ret: &(skin->extras), o);
5193
5194 if (store_original_json_for_extras_and_extensions) {
5195 {
5196 json_const_iterator it;
5197 if (FindMember(o, member: "extensions", it)) {
5198 skin->extensions_json_string = JsonToString(o: GetValue(it));
5199 }
5200 }
5201 {
5202 json_const_iterator it;
5203 if (FindMember(o, member: "extras", it)) {
5204 skin->extras_json_string = JsonToString(o: GetValue(it));
5205 }
5206 }
5207 }
5208
5209 return true;
5210}
5211
5212static bool ParsePerspectiveCamera(
5213 PerspectiveCamera *camera, std::string *err, const json &o,
5214 bool store_original_json_for_extras_and_extensions) {
5215 double yfov = 0.0;
5216 if (!ParseNumberProperty(ret: &yfov, err, o, property: "yfov", required: true, parent_node: "OrthographicCamera")) {
5217 return false;
5218 }
5219
5220 double znear = 0.0;
5221 if (!ParseNumberProperty(ret: &znear, err, o, property: "znear", required: true,
5222 parent_node: "PerspectiveCamera")) {
5223 return false;
5224 }
5225
5226 double aspectRatio = 0.0; // = invalid
5227 ParseNumberProperty(ret: &aspectRatio, err, o, property: "aspectRatio", required: false,
5228 parent_node: "PerspectiveCamera");
5229
5230 double zfar = 0.0; // = invalid
5231 ParseNumberProperty(ret: &zfar, err, o, property: "zfar", required: false, parent_node: "PerspectiveCamera");
5232
5233 camera->aspectRatio = aspectRatio;
5234 camera->zfar = zfar;
5235 camera->yfov = yfov;
5236 camera->znear = znear;
5237
5238 ParseExtensionsProperty(ret: &camera->extensions, err, o);
5239 ParseExtrasProperty(ret: &(camera->extras), o);
5240
5241 if (store_original_json_for_extras_and_extensions) {
5242 {
5243 json_const_iterator it;
5244 if (FindMember(o, member: "extensions", it)) {
5245 camera->extensions_json_string = JsonToString(o: GetValue(it));
5246 }
5247 }
5248 {
5249 json_const_iterator it;
5250 if (FindMember(o, member: "extras", it)) {
5251 camera->extras_json_string = JsonToString(o: GetValue(it));
5252 }
5253 }
5254 }
5255
5256 // TODO(syoyo): Validate parameter values.
5257
5258 return true;
5259}
5260
5261static bool ParseSpotLight(SpotLight *light, std::string *err, const json &o,
5262 bool store_original_json_for_extras_and_extensions) {
5263 ParseNumberProperty(ret: &light->innerConeAngle, err, o, property: "innerConeAngle", required: false);
5264 ParseNumberProperty(ret: &light->outerConeAngle, err, o, property: "outerConeAngle", required: false);
5265
5266 ParseExtensionsProperty(ret: &light->extensions, err, o);
5267 ParseExtrasProperty(ret: &light->extras, o);
5268
5269 if (store_original_json_for_extras_and_extensions) {
5270 {
5271 json_const_iterator it;
5272 if (FindMember(o, member: "extensions", it)) {
5273 light->extensions_json_string = JsonToString(o: GetValue(it));
5274 }
5275 }
5276 {
5277 json_const_iterator it;
5278 if (FindMember(o, member: "extras", it)) {
5279 light->extras_json_string = JsonToString(o: GetValue(it));
5280 }
5281 }
5282 }
5283
5284 // TODO(syoyo): Validate parameter values.
5285
5286 return true;
5287}
5288
5289static bool ParseOrthographicCamera(
5290 OrthographicCamera *camera, std::string *err, const json &o,
5291 bool store_original_json_for_extras_and_extensions) {
5292 double xmag = 0.0;
5293 if (!ParseNumberProperty(ret: &xmag, err, o, property: "xmag", required: true, parent_node: "OrthographicCamera")) {
5294 return false;
5295 }
5296
5297 double ymag = 0.0;
5298 if (!ParseNumberProperty(ret: &ymag, err, o, property: "ymag", required: true, parent_node: "OrthographicCamera")) {
5299 return false;
5300 }
5301
5302 double zfar = 0.0;
5303 if (!ParseNumberProperty(ret: &zfar, err, o, property: "zfar", required: true, parent_node: "OrthographicCamera")) {
5304 return false;
5305 }
5306
5307 double znear = 0.0;
5308 if (!ParseNumberProperty(ret: &znear, err, o, property: "znear", required: true,
5309 parent_node: "OrthographicCamera")) {
5310 return false;
5311 }
5312
5313 ParseExtensionsProperty(ret: &camera->extensions, err, o);
5314 ParseExtrasProperty(ret: &(camera->extras), o);
5315
5316 if (store_original_json_for_extras_and_extensions) {
5317 {
5318 json_const_iterator it;
5319 if (FindMember(o, member: "extensions", it)) {
5320 camera->extensions_json_string = JsonToString(o: GetValue(it));
5321 }
5322 }
5323 {
5324 json_const_iterator it;
5325 if (FindMember(o, member: "extras", it)) {
5326 camera->extras_json_string = JsonToString(o: GetValue(it));
5327 }
5328 }
5329 }
5330
5331 camera->xmag = xmag;
5332 camera->ymag = ymag;
5333 camera->zfar = zfar;
5334 camera->znear = znear;
5335
5336 // TODO(syoyo): Validate parameter values.
5337
5338 return true;
5339}
5340
5341static bool ParseCamera(Camera *camera, std::string *err, const json &o,
5342 bool store_original_json_for_extras_and_extensions) {
5343 if (!ParseStringProperty(ret: &camera->type, err, o, property: "type", required: true, parent_node: "Camera")) {
5344 return false;
5345 }
5346
5347 if (camera->type.compare(s: "orthographic") == 0) {
5348 json_const_iterator orthoIt;
5349 if (!FindMember(o, member: "orthographic", it&: orthoIt)) {
5350 if (err) {
5351 std::stringstream ss;
5352 ss << "Orhographic camera description not found." << std::endl;
5353 (*err) += ss.str();
5354 }
5355 return false;
5356 }
5357
5358 const json &v = GetValue(it&: orthoIt);
5359 if (!IsObject(o: v)) {
5360 if (err) {
5361 std::stringstream ss;
5362 ss << "\"orthographic\" is not a JSON object." << std::endl;
5363 (*err) += ss.str();
5364 }
5365 return false;
5366 }
5367
5368 if (!ParseOrthographicCamera(
5369 camera: &camera->orthographic, err, o: v,
5370 store_original_json_for_extras_and_extensions)) {
5371 return false;
5372 }
5373 } else if (camera->type.compare(s: "perspective") == 0) {
5374 json_const_iterator perspIt;
5375 if (!FindMember(o, member: "perspective", it&: perspIt)) {
5376 if (err) {
5377 std::stringstream ss;
5378 ss << "Perspective camera description not found." << std::endl;
5379 (*err) += ss.str();
5380 }
5381 return false;
5382 }
5383
5384 const json &v = GetValue(it&: perspIt);
5385 if (!IsObject(o: v)) {
5386 if (err) {
5387 std::stringstream ss;
5388 ss << "\"perspective\" is not a JSON object." << std::endl;
5389 (*err) += ss.str();
5390 }
5391 return false;
5392 }
5393
5394 if (!ParsePerspectiveCamera(
5395 camera: &camera->perspective, err, o: v,
5396 store_original_json_for_extras_and_extensions)) {
5397 return false;
5398 }
5399 } else {
5400 if (err) {
5401 std::stringstream ss;
5402 ss << "Invalid camera type: \"" << camera->type
5403 << "\". Must be \"perspective\" or \"orthographic\"" << std::endl;
5404 (*err) += ss.str();
5405 }
5406 return false;
5407 }
5408
5409 ParseStringProperty(ret: &camera->name, err, o, property: "name", required: false);
5410
5411 ParseExtensionsProperty(ret: &camera->extensions, err, o);
5412 ParseExtrasProperty(ret: &(camera->extras), o);
5413
5414 if (store_original_json_for_extras_and_extensions) {
5415 {
5416 json_const_iterator it;
5417 if (FindMember(o, member: "extensions", it)) {
5418 camera->extensions_json_string = JsonToString(o: GetValue(it));
5419 }
5420 }
5421 {
5422 json_const_iterator it;
5423 if (FindMember(o, member: "extras", it)) {
5424 camera->extras_json_string = JsonToString(o: GetValue(it));
5425 }
5426 }
5427 }
5428
5429 return true;
5430}
5431
5432static bool ParseLight(Light *light, std::string *err, const json &o,
5433 bool store_original_json_for_extras_and_extensions) {
5434 if (!ParseStringProperty(ret: &light->type, err, o, property: "type", required: true)) {
5435 return false;
5436 }
5437
5438 if (light->type == "spot") {
5439 json_const_iterator spotIt;
5440 if (!FindMember(o, member: "spot", it&: spotIt)) {
5441 if (err) {
5442 std::stringstream ss;
5443 ss << "Spot light description not found." << std::endl;
5444 (*err) += ss.str();
5445 }
5446 return false;
5447 }
5448
5449 const json &v = GetValue(it&: spotIt);
5450 if (!IsObject(o: v)) {
5451 if (err) {
5452 std::stringstream ss;
5453 ss << "\"spot\" is not a JSON object." << std::endl;
5454 (*err) += ss.str();
5455 }
5456 return false;
5457 }
5458
5459 if (!ParseSpotLight(light: &light->spot, err, o: v,
5460 store_original_json_for_extras_and_extensions)) {
5461 return false;
5462 }
5463 }
5464
5465 ParseStringProperty(ret: &light->name, err, o, property: "name", required: false);
5466 ParseNumberArrayProperty(ret: &light->color, err, o, property: "color", required: false);
5467 ParseNumberProperty(ret: &light->range, err, o, property: "range", required: false);
5468 ParseNumberProperty(ret: &light->intensity, err, o, property: "intensity", required: false);
5469 ParseExtensionsProperty(ret: &light->extensions, err, o);
5470 ParseExtrasProperty(ret: &(light->extras), o);
5471
5472 if (store_original_json_for_extras_and_extensions) {
5473 {
5474 json_const_iterator it;
5475 if (FindMember(o, member: "extensions", it)) {
5476 light->extensions_json_string = JsonToString(o: GetValue(it));
5477 }
5478 }
5479 {
5480 json_const_iterator it;
5481 if (FindMember(o, member: "extras", it)) {
5482 light->extras_json_string = JsonToString(o: GetValue(it));
5483 }
5484 }
5485 }
5486
5487 return true;
5488}
5489
5490bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
5491 const char *json_str,
5492 unsigned int json_str_length,
5493 const std::string &base_dir,
5494 unsigned int check_sections) {
5495 if (json_str_length < 4) {
5496 if (err) {
5497 (*err) = "JSON string too short.\n";
5498 }
5499 return false;
5500 }
5501
5502 JsonDocument v;
5503
5504#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
5505 defined(_CPPUNWIND)) && \
5506 !defined(TINYGLTF_NOEXCEPTION)
5507 try {
5508 JsonParse(v, json_str, json_str_length, true);
5509
5510 } catch (const std::exception &e) {
5511 if (err) {
5512 (*err) = e.what();
5513 }
5514 return false;
5515 }
5516#else
5517 {
5518 JsonParse(doc&: v, str: json_str, length: json_str_length);
5519
5520 if (!IsObject(o: v)) {
5521 // Assume parsing was failed.
5522 if (err) {
5523 (*err) = "Failed to parse JSON object\n";
5524 }
5525 return false;
5526 }
5527 }
5528#endif
5529
5530 if (!IsObject(o: v)) {
5531 // root is not an object.
5532 if (err) {
5533 (*err) = "Root element is not a JSON object\n";
5534 }
5535 return false;
5536 }
5537
5538 {
5539 bool version_found = false;
5540 json_const_iterator it;
5541 if (FindMember(o: v, member: "asset", it) && IsObject(o: GetValue(it))) {
5542 auto &itObj = GetValue(it);
5543 json_const_iterator version_it;
5544 std::string versionStr;
5545 if (FindMember(o: itObj, member: "version", it&: version_it) &&
5546 GetString(o: GetValue(it&: version_it), val&: versionStr)) {
5547 version_found = true;
5548 }
5549 }
5550 if (version_found) {
5551 // OK
5552 } else if (check_sections & REQUIRE_VERSION) {
5553 if (err) {
5554 (*err) += "\"asset\" object not found in .gltf or not an object type\n";
5555 }
5556 return false;
5557 }
5558 }
5559
5560 // scene is not mandatory.
5561 // FIXME Maybe a better way to handle it than removing the code
5562
5563 auto IsArrayMemberPresent = [](const json &_v, const char *name) -> bool {
5564 json_const_iterator it;
5565 return FindMember(o: _v, member: name, it) && IsArray(o: GetValue(it));
5566 };
5567
5568 {
5569 if ((check_sections & REQUIRE_SCENES) &&
5570 !IsArrayMemberPresent(v, "scenes")) {
5571 if (err) {
5572 (*err) += "\"scenes\" object not found in .gltf or not an array type\n";
5573 }
5574 return false;
5575 }
5576 }
5577
5578 {
5579 if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) {
5580 if (err) {
5581 (*err) += "\"nodes\" object not found in .gltf\n";
5582 }
5583 return false;
5584 }
5585 }
5586
5587 {
5588 if ((check_sections & REQUIRE_ACCESSORS) &&
5589 !IsArrayMemberPresent(v, "accessors")) {
5590 if (err) {
5591 (*err) += "\"accessors\" object not found in .gltf\n";
5592 }
5593 return false;
5594 }
5595 }
5596
5597 {
5598 if ((check_sections & REQUIRE_BUFFERS) &&
5599 !IsArrayMemberPresent(v, "buffers")) {
5600 if (err) {
5601 (*err) += "\"buffers\" object not found in .gltf\n";
5602 }
5603 return false;
5604 }
5605 }
5606
5607 {
5608 if ((check_sections & REQUIRE_BUFFER_VIEWS) &&
5609 !IsArrayMemberPresent(v, "bufferViews")) {
5610 if (err) {
5611 (*err) += "\"bufferViews\" object not found in .gltf\n";
5612 }
5613 return false;
5614 }
5615 }
5616
5617 model->buffers.clear();
5618 model->bufferViews.clear();
5619 model->accessors.clear();
5620 model->meshes.clear();
5621 model->cameras.clear();
5622 model->nodes.clear();
5623 model->extensionsUsed.clear();
5624 model->extensionsRequired.clear();
5625 model->extensions.clear();
5626 model->defaultScene = -1;
5627
5628 // 1. Parse Asset
5629 {
5630 json_const_iterator it;
5631 if (FindMember(o: v, member: "asset", it) && IsObject(o: GetValue(it))) {
5632 const json &root = GetValue(it);
5633
5634 ParseAsset(asset: &model->asset, err, o: root,
5635 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_);
5636 }
5637 }
5638
5639#ifdef TINYGLTF_USE_CPP14
5640 auto ForEachInArray = [](const json &_v, const char *member,
5641 const auto &cb) -> bool
5642#else
5643 // The std::function<> implementation can be less efficient because it will
5644 // allocate heap when the size of the captured lambda is above 16 bytes with
5645 // clang and gcc, but it does not require C++14.
5646 auto ForEachInArray = [](const json &_v, const char *member,
5647 const std::function<bool(const json &)> &cb) -> bool
5648#endif
5649 {
5650 json_const_iterator itm;
5651 if (FindMember(o: _v, member, it&: itm) && IsArray(o: GetValue(it&: itm))) {
5652 const json &root = GetValue(it&: itm);
5653 auto it = ArrayBegin(o: root);
5654 auto end = ArrayEnd(o: root);
5655 for (; it != end; ++it) {
5656 if (!cb(*it)) return false;
5657 }
5658 }
5659 return true;
5660 };
5661
5662 // 2. Parse extensionUsed
5663 {
5664 ForEachInArray(v, "extensionsUsed", [&](const json &o) {
5665 std::string str;
5666 GetString(o, val&: str);
5667 model->extensionsUsed.emplace_back(args: std::move(str));
5668 return true;
5669 });
5670 }
5671
5672 {
5673 ForEachInArray(v, "extensionsRequired", [&](const json &o) {
5674 std::string str;
5675 GetString(o, val&: str);
5676 model->extensionsRequired.emplace_back(args: std::move(str));
5677 return true;
5678 });
5679 }
5680
5681 // 3. Parse Buffer
5682 {
5683 bool success = ForEachInArray(v, "buffers", [&](const json &o) {
5684 if (!IsObject(o)) {
5685 if (err) {
5686 (*err) += "`buffers' does not contain an JSON object.";
5687 }
5688 return false;
5689 }
5690 Buffer buffer;
5691 if (!ParseBuffer(buffer: &buffer, err, o,
5692 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_, fs: &fs,
5693 basedir: base_dir, is_binary: is_binary_, bin_data: bin_data_, bin_size: bin_size_)) {
5694 return false;
5695 }
5696
5697 model->buffers.emplace_back(args: std::move(buffer));
5698 return true;
5699 });
5700
5701 if (!success) {
5702 return false;
5703 }
5704 }
5705 // 4. Parse BufferView
5706 {
5707 bool success = ForEachInArray(v, "bufferViews", [&](const json &o) {
5708 if (!IsObject(o)) {
5709 if (err) {
5710 (*err) += "`bufferViews' does not contain an JSON object.";
5711 }
5712 return false;
5713 }
5714 BufferView bufferView;
5715 if (!ParseBufferView(bufferView: &bufferView, err, o,
5716 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
5717 return false;
5718 }
5719
5720 model->bufferViews.emplace_back(args: std::move(bufferView));
5721 return true;
5722 });
5723
5724 if (!success) {
5725 return false;
5726 }
5727 }
5728
5729 // 5. Parse Accessor
5730 {
5731 bool success = ForEachInArray(v, "accessors", [&](const json &o) {
5732 if (!IsObject(o)) {
5733 if (err) {
5734 (*err) += "`accessors' does not contain an JSON object.";
5735 }
5736 return false;
5737 }
5738 Accessor accessor;
5739 if (!ParseAccessor(accessor: &accessor, err, o,
5740 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
5741 return false;
5742 }
5743
5744 model->accessors.emplace_back(args: std::move(accessor));
5745 return true;
5746 });
5747
5748 if (!success) {
5749 return false;
5750 }
5751 }
5752
5753 // 6. Parse Mesh
5754 {
5755 bool success = ForEachInArray(v, "meshes", [&](const json &o) {
5756 if (!IsObject(o)) {
5757 if (err) {
5758 (*err) += "`meshes' does not contain an JSON object.";
5759 }
5760 return false;
5761 }
5762 Mesh mesh;
5763 if (!ParseMesh(mesh: &mesh, model, err, o,
5764 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
5765 return false;
5766 }
5767
5768 model->meshes.emplace_back(args: std::move(mesh));
5769 return true;
5770 });
5771
5772 if (!success) {
5773 return false;
5774 }
5775 }
5776
5777 // Assign missing bufferView target types
5778 // - Look for missing Mesh indices
5779 // - Look for missing Mesh attributes
5780 for (auto &mesh : model->meshes) {
5781 for (auto &primitive : mesh.primitives) {
5782 if (primitive.indices >
5783 -1) // has indices from parsing step, must be Element Array Buffer
5784 {
5785 if (size_t(primitive.indices) >= model->accessors.size()) {
5786 if (err) {
5787 (*err) += "primitive indices accessor out of bounds";
5788 }
5789 return false;
5790 }
5791
5792 auto bufferView =
5793 model->accessors[size_t(primitive.indices)].bufferView;
5794 if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) {
5795 if (err) {
5796 (*err) += "accessor[" + std::to_string(val: primitive.indices) +
5797 "] invalid bufferView";
5798 }
5799 return false;
5800 }
5801
5802 model->bufferViews[size_t(bufferView)].target =
5803 TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
5804 // we could optionally check if acessors' bufferView type is Scalar, as
5805 // it should be
5806 }
5807
5808 for (auto &attribute : primitive.attributes) {
5809 const auto accessorsIndex = size_t(attribute.second);
5810 if (accessorsIndex < model->accessors.size()) {
5811 const auto bufferView = model->accessors[accessorsIndex].bufferView;
5812 // bufferView could be null(-1) for sparse morph target
5813 if (bufferView >= 0 && bufferView < model->bufferViews.size()) {
5814 model->bufferViews[size_t(bufferView)].target =
5815 TINYGLTF_TARGET_ARRAY_BUFFER;
5816 }
5817 }
5818 }
5819
5820 for (auto &target : primitive.targets) {
5821 for (auto &attribute : target) {
5822 const auto accessorsIndex = size_t(attribute.second);
5823 if (accessorsIndex < model->accessors.size()) {
5824 const auto bufferView = model->accessors[accessorsIndex].bufferView;
5825 // bufferView could be null(-1) for sparse morph target
5826 if (bufferView >= 0 && bufferView < model->bufferViews.size()) {
5827 model->bufferViews[size_t(bufferView)].target =
5828 TINYGLTF_TARGET_ARRAY_BUFFER;
5829 }
5830 }
5831 }
5832 }
5833 }
5834 }
5835
5836 // 7. Parse Node
5837 {
5838 bool success = ForEachInArray(v, "nodes", [&](const json &o) {
5839 if (!IsObject(o)) {
5840 if (err) {
5841 (*err) += "`nodes' does not contain an JSON object.";
5842 }
5843 return false;
5844 }
5845 Node node;
5846 if (!ParseNode(node: &node, err, o,
5847 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
5848 return false;
5849 }
5850
5851 model->nodes.emplace_back(args: std::move(node));
5852 return true;
5853 });
5854
5855 if (!success) {
5856 return false;
5857 }
5858 }
5859
5860 // 8. Parse scenes.
5861 {
5862 bool success = ForEachInArray(v, "scenes", [&](const json &o) {
5863 if (!IsObject(o)) {
5864 if (err) {
5865 (*err) += "`scenes' does not contain an JSON object.";
5866 }
5867 return false;
5868 }
5869 std::vector<int> nodes;
5870 ParseIntegerArrayProperty(ret: &nodes, err, o, property: "nodes", required: false);
5871
5872 Scene scene;
5873 scene.nodes = std::move(nodes);
5874
5875 ParseStringProperty(ret: &scene.name, err, o, property: "name", required: false);
5876
5877 ParseExtensionsProperty(ret: &scene.extensions, err, o);
5878 ParseExtrasProperty(ret: &scene.extras, o);
5879
5880 if (store_original_json_for_extras_and_extensions_) {
5881 {
5882 json_const_iterator it;
5883 if (FindMember(o, member: "extensions", it)) {
5884 scene.extensions_json_string = JsonToString(o: GetValue(it));
5885 }
5886 }
5887 {
5888 json_const_iterator it;
5889 if (FindMember(o, member: "extras", it)) {
5890 scene.extras_json_string = JsonToString(o: GetValue(it));
5891 }
5892 }
5893 }
5894
5895 model->scenes.emplace_back(args: std::move(scene));
5896 return true;
5897 });
5898
5899 if (!success) {
5900 return false;
5901 }
5902 }
5903
5904 // 9. Parse default scenes.
5905 {
5906 json_const_iterator rootIt;
5907 int iVal;
5908 if (FindMember(o: v, member: "scene", it&: rootIt) && GetInt(o: GetValue(it&: rootIt), val&: iVal)) {
5909 model->defaultScene = iVal;
5910 }
5911 }
5912
5913 // 10. Parse Material
5914 {
5915 bool success = ForEachInArray(v, "materials", [&](const json &o) {
5916 if (!IsObject(o)) {
5917 if (err) {
5918 (*err) += "`materials' does not contain an JSON object.";
5919 }
5920 return false;
5921 }
5922 Material material;
5923 ParseStringProperty(ret: &material.name, err, o, property: "name", required: false);
5924
5925 if (!ParseMaterial(material: &material, err, o,
5926 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
5927 return false;
5928 }
5929
5930 model->materials.emplace_back(args: std::move(material));
5931 return true;
5932 });
5933
5934 if (!success) {
5935 return false;
5936 }
5937 }
5938
5939 // 11. Parse Image
5940 void *load_image_user_data{nullptr};
5941
5942 LoadImageDataOption load_image_option;
5943
5944 if (user_image_loader_) {
5945 // Use user supplied pointer
5946 load_image_user_data = load_image_user_data_;
5947 } else {
5948 load_image_option.preserve_channels = preserve_image_channels_;
5949 load_image_user_data = reinterpret_cast<void *>(&load_image_option);
5950 }
5951
5952 {
5953 int idx = 0;
5954 bool success = ForEachInArray(v, "images", [&](const json &o) {
5955 if (!IsObject(o)) {
5956 if (err) {
5957 (*err) += "image[" + std::to_string(val: idx) + "] is not a JSON object.";
5958 }
5959 return false;
5960 }
5961 Image image;
5962 if (!ParseImage(image: &image, image_idx: idx, err, warn, o,
5963 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_, basedir: base_dir,
5964 fs: &fs, LoadImageData: &this->LoadImageData, load_image_user_data)) {
5965 return false;
5966 }
5967
5968 if (image.bufferView != -1) {
5969 // Load image from the buffer view.
5970 if (size_t(image.bufferView) >= model->bufferViews.size()) {
5971 if (err) {
5972 std::stringstream ss;
5973 ss << "image[" << idx << "] bufferView \"" << image.bufferView
5974 << "\" not found in the scene." << std::endl;
5975 (*err) += ss.str();
5976 }
5977 return false;
5978 }
5979
5980 const BufferView &bufferView =
5981 model->bufferViews[size_t(image.bufferView)];
5982 if (size_t(bufferView.buffer) >= model->buffers.size()) {
5983 if (err) {
5984 std::stringstream ss;
5985 ss << "image[" << idx << "] buffer \"" << bufferView.buffer
5986 << "\" not found in the scene." << std::endl;
5987 (*err) += ss.str();
5988 }
5989 return false;
5990 }
5991 const Buffer &buffer = model->buffers[size_t(bufferView.buffer)];
5992
5993 if (*LoadImageData == nullptr) {
5994 if (err) {
5995 (*err) += "No LoadImageData callback specified.\n";
5996 }
5997 return false;
5998 }
5999 bool ret = LoadImageData(
6000 &image, idx, err, warn, image.width, image.height,
6001 &buffer.data[bufferView.byteOffset],
6002 static_cast<int>(bufferView.byteLength), load_image_user_data);
6003 if (!ret) {
6004 return false;
6005 }
6006 }
6007
6008 model->images.emplace_back(args: std::move(image));
6009 ++idx;
6010 return true;
6011 });
6012
6013 if (!success) {
6014 return false;
6015 }
6016 }
6017
6018 // 12. Parse Texture
6019 {
6020 bool success = ForEachInArray(v, "textures", [&](const json &o) {
6021 if (!IsObject(o)) {
6022 if (err) {
6023 (*err) += "`textures' does not contain an JSON object.";
6024 }
6025 return false;
6026 }
6027 Texture texture;
6028 if (!ParseTexture(texture: &texture, err, o,
6029 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_,
6030 basedir: base_dir)) {
6031 return false;
6032 }
6033
6034 model->textures.emplace_back(args: std::move(texture));
6035 return true;
6036 });
6037
6038 if (!success) {
6039 return false;
6040 }
6041 }
6042
6043 // 13. Parse Animation
6044 {
6045 bool success = ForEachInArray(v, "animations", [&](const json &o) {
6046 if (!IsObject(o)) {
6047 if (err) {
6048 (*err) += "`animations' does not contain an JSON object.";
6049 }
6050 return false;
6051 }
6052 Animation animation;
6053 if (!ParseAnimation(animation: &animation, err, o,
6054 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
6055 return false;
6056 }
6057
6058 model->animations.emplace_back(args: std::move(animation));
6059 return true;
6060 });
6061
6062 if (!success) {
6063 return false;
6064 }
6065 }
6066
6067 // 14. Parse Skin
6068 {
6069 bool success = ForEachInArray(v, "skins", [&](const json &o) {
6070 if (!IsObject(o)) {
6071 if (err) {
6072 (*err) += "`skins' does not contain an JSON object.";
6073 }
6074 return false;
6075 }
6076 Skin skin;
6077 if (!ParseSkin(skin: &skin, err, o,
6078 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
6079 return false;
6080 }
6081
6082 model->skins.emplace_back(args: std::move(skin));
6083 return true;
6084 });
6085
6086 if (!success) {
6087 return false;
6088 }
6089 }
6090
6091 // 15. Parse Sampler
6092 {
6093 bool success = ForEachInArray(v, "samplers", [&](const json &o) {
6094 if (!IsObject(o)) {
6095 if (err) {
6096 (*err) += "`samplers' does not contain an JSON object.";
6097 }
6098 return false;
6099 }
6100 Sampler sampler;
6101 if (!ParseSampler(sampler: &sampler, err, o,
6102 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
6103 return false;
6104 }
6105
6106 model->samplers.emplace_back(args: std::move(sampler));
6107 return true;
6108 });
6109
6110 if (!success) {
6111 return false;
6112 }
6113 }
6114
6115 // 16. Parse Camera
6116 {
6117 bool success = ForEachInArray(v, "cameras", [&](const json &o) {
6118 if (!IsObject(o)) {
6119 if (err) {
6120 (*err) += "`cameras' does not contain an JSON object.";
6121 }
6122 return false;
6123 }
6124 Camera camera;
6125 if (!ParseCamera(camera: &camera, err, o,
6126 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
6127 return false;
6128 }
6129
6130 model->cameras.emplace_back(args: std::move(camera));
6131 return true;
6132 });
6133
6134 if (!success) {
6135 return false;
6136 }
6137 }
6138
6139 // 17. Parse Extensions
6140 ParseExtensionsProperty(ret: &model->extensions, err, o: v);
6141
6142 // 18. Specific extension implementations
6143 {
6144 json_const_iterator rootIt;
6145 if (FindMember(o: v, member: "extensions", it&: rootIt) && IsObject(o: GetValue(it&: rootIt))) {
6146 const json &root = GetValue(it&: rootIt);
6147
6148 json_const_iterator it(ObjectBegin(o: root));
6149 json_const_iterator itEnd(ObjectEnd(o: root));
6150 for (; it != itEnd; ++it) {
6151 // parse KHR_lights_punctual extension
6152 std::string key(GetKey(it));
6153 if ((key == "KHR_lights_punctual") && IsObject(o: GetValue(it))) {
6154 const json &object = GetValue(it);
6155 json_const_iterator itLight;
6156 if (FindMember(o: object, member: "lights", it&: itLight)) {
6157 const json &lights = GetValue(it&: itLight);
6158 if (!IsArray(o: lights)) {
6159 continue;
6160 }
6161
6162 auto arrayIt(ArrayBegin(o: lights));
6163 auto arrayItEnd(ArrayEnd(o: lights));
6164 for (; arrayIt != arrayItEnd; ++arrayIt) {
6165 Light light;
6166 if (!ParseLight(light: &light, err, o: *arrayIt,
6167 store_original_json_for_extras_and_extensions: store_original_json_for_extras_and_extensions_)) {
6168 return false;
6169 }
6170 model->lights.emplace_back(args: std::move(light));
6171 }
6172 }
6173 }
6174 }
6175 }
6176 }
6177
6178 // 19. Parse Extras
6179 ParseExtrasProperty(ret: &model->extras, o: v);
6180
6181 if (store_original_json_for_extras_and_extensions_) {
6182 model->extras_json_string = JsonToString(o: v["extras"]);
6183 model->extensions_json_string = JsonToString(o: v["extensions"]);
6184 }
6185
6186 return true;
6187}
6188
6189bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err,
6190 std::string *warn, const char *str,
6191 unsigned int length,
6192 const std::string &base_dir,
6193 unsigned int check_sections) {
6194 is_binary_ = false;
6195 bin_data_ = nullptr;
6196 bin_size_ = 0;
6197
6198 return LoadFromString(model, err, warn, json_str: str, json_str_length: length, base_dir,
6199 check_sections);
6200}
6201
6202bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
6203 std::string *warn, const std::string &filename,
6204 unsigned int check_sections) {
6205 std::stringstream ss;
6206
6207 if (fs.ReadWholeFile == nullptr) {
6208 // Programmer error, assert() ?
6209 ss << "Failed to read file: " << filename
6210 << ": one or more FS callback not set" << std::endl;
6211 if (err) {
6212 (*err) = ss.str();
6213 }
6214 return false;
6215 }
6216
6217 std::vector<unsigned char> data;
6218 std::string fileerr;
6219 bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
6220 if (!fileread) {
6221 ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
6222 if (err) {
6223 (*err) = ss.str();
6224 }
6225 return false;
6226 }
6227
6228 size_t sz = data.size();
6229 if (sz == 0) {
6230 if (err) {
6231 (*err) = "Empty file.";
6232 }
6233 return false;
6234 }
6235
6236 std::string basedir = GetBaseDir(filepath: filename);
6237
6238 bool ret = LoadASCIIFromString(
6239 model, err, warn, str: reinterpret_cast<const char *>(&data.at(n: 0)),
6240 length: static_cast<unsigned int>(data.size()), base_dir: basedir, check_sections);
6241
6242 return ret;
6243}
6244
6245bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err,
6246 std::string *warn,
6247 const unsigned char *bytes,
6248 unsigned int size,
6249 const std::string &base_dir,
6250 unsigned int check_sections) {
6251 if (size < 20) {
6252 if (err) {
6253 (*err) = "Too short data size for glTF Binary.";
6254 }
6255 return false;
6256 }
6257
6258 if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' &&
6259 bytes[3] == 'F') {
6260 // ok
6261 } else {
6262 if (err) {
6263 (*err) = "Invalid magic.";
6264 }
6265 return false;
6266 }
6267
6268 unsigned int version; // 4 bytes
6269 unsigned int length; // 4 bytes
6270 unsigned int chunk0_length; // 4 bytes
6271 unsigned int chunk0_format; // 4 bytes;
6272
6273 memcpy(dest: &version, src: bytes + 4, n: 4);
6274 swap4(val: &version);
6275 memcpy(dest: &length, src: bytes + 8, n: 4);
6276 swap4(val: &length);
6277 memcpy(dest: &chunk0_length, src: bytes + 12, n: 4); // JSON data length
6278 swap4(val: &chunk0_length);
6279 memcpy(dest: &chunk0_format, src: bytes + 16, n: 4);
6280 swap4(val: &chunk0_format);
6281
6282 // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-gltf-layout
6283 //
6284 // In case the Bin buffer is not present, the size is exactly 20 + size of
6285 // JSON contents,
6286 // so use "greater than" operator.
6287 //
6288 // https://github.com/syoyo/tinygltf/issues/372
6289 // Use 64bit uint to avoid integer overflow.
6290 uint64_t header_and_json_size = 20ull + uint64_t(chunk0_length);
6291
6292 if (header_and_json_size > std::numeric_limits<uint32_t>::max()) {
6293 // Do not allow 4GB or more GLB data.
6294 (*err) = "Invalid glTF binary. GLB data exceeds 4GB.";
6295 }
6296
6297 if ((header_and_json_size > uint64_t(size)) || (chunk0_length < 1) || (length > size) ||
6298 (header_and_json_size > uint64_t(length)) ||
6299 (chunk0_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format.
6300 if (err) {
6301 (*err) = "Invalid glTF binary.";
6302 }
6303 return false;
6304 }
6305
6306 // Padding check
6307 // The start and the end of each chunk must be aligned to a 4-byte boundary.
6308 // No padding check for chunk0 start since its 4byte-boundary is ensured.
6309 if ((header_and_json_size % 4) != 0) {
6310 if (err) {
6311 (*err) = "JSON Chunk end does not aligned to a 4-byte boundary.";
6312 }
6313 }
6314
6315 //std::cout << "header_and_json_size = " << header_and_json_size << "\n";
6316 //std::cout << "length = " << length << "\n";
6317
6318 // Chunk1(BIN) data
6319 // The spec says: When the binary buffer is empty or when it is stored by other means, this chunk SHOULD be omitted.
6320 // So when header + JSON data == binary size, Chunk1 is omitted.
6321 if (header_and_json_size == uint64_t(length)) {
6322
6323 bin_data_ = nullptr;
6324 bin_size_ = 0;
6325 } else {
6326 // Read Chunk1 info(BIN data)
6327 // At least Chunk1 should have 12 bytes(8 bytes(header) + 4 bytes(bin payload could be 1~3 bytes, but need to be aliged to 4 bytes)
6328 if ((header_and_json_size + 12ull) > uint64_t(length)) {
6329 if (err) {
6330 (*err) = "Insufficient storage space for Chunk1(BIN data). At least Chunk1 Must have 4 bytes or more bytes, but got " + std::to_string(val: (header_and_json_size + 12ull) - uint64_t(length)) + ".\n";
6331 }
6332 return false;
6333 }
6334
6335 unsigned int chunk1_length; // 4 bytes
6336 unsigned int chunk1_format; // 4 bytes;
6337 memcpy(dest: &chunk1_length, src: bytes + header_and_json_size, n: 4); // JSON data length
6338 swap4(val: &chunk1_length);
6339 memcpy(dest: &chunk1_format, src: bytes + header_and_json_size + 4, n: 4);
6340 swap4(val: &chunk1_format);
6341
6342 //std::cout << "chunk1_length = " << chunk1_length << "\n";
6343
6344 if (chunk1_length < 4) {
6345 if (err) {
6346 (*err) = "Insufficient Chunk1(BIN) data size.";
6347 }
6348 return false;
6349 }
6350
6351 if ((chunk1_length % 4) != 0) {
6352 if (err) {
6353 (*err) = "BIN Chunk end does not aligned to a 4-byte boundary.";
6354 }
6355 return false;
6356 }
6357
6358 if (uint64_t(chunk1_length) + header_and_json_size > uint64_t(length)) {
6359 if (err) {
6360 (*err) = "BIN Chunk data length exceeds the GLB size.";
6361 }
6362 return false;
6363 }
6364
6365 if (chunk1_format != 0x004e4942) {
6366 if (err) {
6367 (*err) = "Invlid type for chunk1 data.";
6368 }
6369 return false;
6370 }
6371
6372 //std::cout << "chunk1_length = " << chunk1_length << "\n";
6373
6374 bin_data_ = bytes + header_and_json_size +
6375 8; // 4 bytes (bin_buffer_length) + 4 bytes(bin_buffer_format)
6376
6377 bin_size_ = size_t(chunk1_length);
6378 }
6379
6380 // Extract JSON string.
6381 std::string jsonString(reinterpret_cast<const char *>(&bytes[20]),
6382 chunk0_length);
6383
6384 is_binary_ = true;
6385
6386 bool ret = LoadFromString(model, err, warn,
6387 json_str: reinterpret_cast<const char *>(&bytes[20]),
6388 json_str_length: chunk0_length, base_dir, check_sections);
6389 if (!ret) {
6390 return ret;
6391 }
6392
6393 return true;
6394}
6395
6396bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err,
6397 std::string *warn,
6398 const std::string &filename,
6399 unsigned int check_sections) {
6400 std::stringstream ss;
6401
6402 if (fs.ReadWholeFile == nullptr) {
6403 // Programmer error, assert() ?
6404 ss << "Failed to read file: " << filename
6405 << ": one or more FS callback not set" << std::endl;
6406 if (err) {
6407 (*err) = ss.str();
6408 }
6409 return false;
6410 }
6411
6412 std::vector<unsigned char> data;
6413 std::string fileerr;
6414 bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
6415 if (!fileread) {
6416 ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
6417 if (err) {
6418 (*err) = ss.str();
6419 }
6420 return false;
6421 }
6422
6423 std::string basedir = GetBaseDir(filepath: filename);
6424
6425 bool ret = LoadBinaryFromMemory(model, err, warn, bytes: &data.at(n: 0),
6426 size: static_cast<unsigned int>(data.size()),
6427 base_dir: basedir, check_sections);
6428
6429 return ret;
6430}
6431
6432///////////////////////
6433// GLTF Serialization
6434///////////////////////
6435namespace {
6436json JsonFromString(const char *s) {
6437#ifdef TINYGLTF_USE_RAPIDJSON
6438 return json(s, GetAllocator());
6439#else
6440 return json(s);
6441#endif
6442}
6443
6444void JsonAssign(json &dest, const json &src) {
6445#ifdef TINYGLTF_USE_RAPIDJSON
6446 dest.CopyFrom(src, GetAllocator());
6447#else
6448 dest = src;
6449#endif
6450}
6451
6452void JsonAddMember(json &o, const char *key, json &&value) {
6453#ifdef TINYGLTF_USE_RAPIDJSON
6454 if (!o.IsObject()) {
6455 o.SetObject();
6456 }
6457 o.AddMember(json(key, GetAllocator()), std::move(value), GetAllocator());
6458#else
6459 o[key] = std::move(value);
6460#endif
6461}
6462
6463void JsonPushBack(json &o, json &&value) {
6464#ifdef TINYGLTF_USE_RAPIDJSON
6465 o.PushBack(std::move(value), GetAllocator());
6466#else
6467 o.push_back(val: std::move(value));
6468#endif
6469}
6470
6471bool JsonIsNull(const json &o) {
6472#ifdef TINYGLTF_USE_RAPIDJSON
6473 return o.IsNull();
6474#else
6475 return o.is_null();
6476#endif
6477}
6478
6479void JsonSetObject(json &o) {
6480#ifdef TINYGLTF_USE_RAPIDJSON
6481 o.SetObject();
6482#else
6483 o = o.object(init: {});
6484#endif
6485}
6486
6487void JsonReserveArray(json &o, size_t s) {
6488#ifdef TINYGLTF_USE_RAPIDJSON
6489 o.SetArray();
6490 o.Reserve(static_cast<rapidjson::SizeType>(s), GetAllocator());
6491#endif
6492 (void)(o);
6493 (void)(s);
6494}
6495} // namespace
6496
6497// typedef std::pair<std::string, json> json_object_pair;
6498
6499template <typename T>
6500static void SerializeNumberProperty(const std::string &key, T number,
6501 json &obj) {
6502 // obj.insert(
6503 // json_object_pair(key, json(static_cast<double>(number))));
6504 // obj[key] = static_cast<double>(number);
6505 JsonAddMember(o&: obj, key: key.c_str(), value: json(number));
6506}
6507
6508#ifdef TINYGLTF_USE_RAPIDJSON
6509template <>
6510void SerializeNumberProperty(const std::string &key, size_t number, json &obj) {
6511 JsonAddMember(obj, key.c_str(), json(static_cast<uint64_t>(number)));
6512}
6513#endif
6514
6515template <typename T>
6516static void SerializeNumberArrayProperty(const std::string &key,
6517 const std::vector<T> &value,
6518 json &obj) {
6519 if (value.empty()) return;
6520
6521 json ary;
6522 JsonReserveArray(ary, value.size());
6523 for (const auto &s : value) {
6524 JsonPushBack(o&: ary, value: json(s));
6525 }
6526 JsonAddMember(o&: obj, key: key.c_str(), value: std::move(ary));
6527}
6528
6529static void SerializeStringProperty(const std::string &key,
6530 const std::string &value, json &obj) {
6531 JsonAddMember(o&: obj, key: key.c_str(), value: JsonFromString(s: value.c_str()));
6532}
6533
6534static void SerializeStringArrayProperty(const std::string &key,
6535 const std::vector<std::string> &value,
6536 json &obj) {
6537 json ary;
6538 JsonReserveArray(o&: ary, s: value.size());
6539 for (auto &s : value) {
6540 JsonPushBack(o&: ary, value: JsonFromString(s: s.c_str()));
6541 }
6542 JsonAddMember(o&: obj, key: key.c_str(), value: std::move(ary));
6543}
6544
6545static bool ValueToJson(const Value &value, json *ret) {
6546 json obj;
6547#ifdef TINYGLTF_USE_RAPIDJSON
6548 switch (value.Type()) {
6549 case REAL_TYPE:
6550 obj.SetDouble(value.Get<double>());
6551 break;
6552 case INT_TYPE:
6553 obj.SetInt(value.Get<int>());
6554 break;
6555 case BOOL_TYPE:
6556 obj.SetBool(value.Get<bool>());
6557 break;
6558 case STRING_TYPE:
6559 obj.SetString(value.Get<std::string>().c_str(), GetAllocator());
6560 break;
6561 case ARRAY_TYPE: {
6562 obj.SetArray();
6563 obj.Reserve(static_cast<rapidjson::SizeType>(value.ArrayLen()),
6564 GetAllocator());
6565 for (unsigned int i = 0; i < value.ArrayLen(); ++i) {
6566 Value elementValue = value.Get(int(i));
6567 json elementJson;
6568 if (ValueToJson(value.Get(int(i)), &elementJson))
6569 obj.PushBack(std::move(elementJson), GetAllocator());
6570 }
6571 break;
6572 }
6573 case BINARY_TYPE:
6574 // TODO
6575 // obj = json(value.Get<std::vector<unsigned char>>());
6576 return false;
6577 break;
6578 case OBJECT_TYPE: {
6579 obj.SetObject();
6580 Value::Object objMap = value.Get<Value::Object>();
6581 for (auto &it : objMap) {
6582 json elementJson;
6583 if (ValueToJson(it.second, &elementJson)) {
6584 obj.AddMember(json(it.first.c_str(), GetAllocator()),
6585 std::move(elementJson), GetAllocator());
6586 }
6587 }
6588 break;
6589 }
6590 case NULL_TYPE:
6591 default:
6592 return false;
6593 }
6594#else
6595 switch (value.Type()) {
6596 case REAL_TYPE:
6597 obj = json(value.Get<double>());
6598 break;
6599 case INT_TYPE:
6600 obj = json(value.Get<int>());
6601 break;
6602 case BOOL_TYPE:
6603 obj = json(value.Get<bool>());
6604 break;
6605 case STRING_TYPE:
6606 obj = json(value.Get<std::string>());
6607 break;
6608 case ARRAY_TYPE: {
6609 for (unsigned int i = 0; i < value.ArrayLen(); ++i) {
6610 Value elementValue = value.Get(idx: int(i));
6611 json elementJson;
6612 if (ValueToJson(value: value.Get(idx: int(i)), ret: &elementJson))
6613 obj.push_back(val: elementJson);
6614 }
6615 break;
6616 }
6617 case BINARY_TYPE:
6618 // TODO
6619 // obj = json(value.Get<std::vector<unsigned char>>());
6620 return false;
6621 break;
6622 case OBJECT_TYPE: {
6623 Value::Object objMap = value.Get<Value::Object>();
6624 for (auto &it : objMap) {
6625 json elementJson;
6626 if (ValueToJson(value: it.second, ret: &elementJson)) obj[it.first] = elementJson;
6627 }
6628 break;
6629 }
6630 case NULL_TYPE:
6631 default:
6632 return false;
6633 }
6634#endif
6635 if (ret) *ret = std::move(obj);
6636 return true;
6637}
6638
6639static void SerializeValue(const std::string &key, const Value &value,
6640 json &obj) {
6641 json ret;
6642 if (ValueToJson(value, ret: &ret)) {
6643 JsonAddMember(o&: obj, key: key.c_str(), value: std::move(ret));
6644 }
6645}
6646
6647static void SerializeGltfBufferData(const std::vector<unsigned char> &data,
6648 json &o) {
6649 std::string header = "data:application/octet-stream;base64,";
6650 if (data.size() > 0) {
6651 std::string encodedData =
6652 base64_encode(bytes_to_encode: &data[0], in_len: static_cast<unsigned int>(data.size()));
6653 SerializeStringProperty(key: "uri", value: header + encodedData, obj&: o);
6654 } else {
6655 // Issue #229
6656 // size 0 is allowd. Just emit mime header.
6657 SerializeStringProperty(key: "uri", value: header, obj&: o);
6658 }
6659}
6660
6661static bool SerializeGltfBufferData(const std::vector<unsigned char> &data,
6662 const std::string &binFilename) {
6663#ifdef _WIN32
6664#if defined(__GLIBCXX__) // mingw
6665 int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(),
6666 _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
6667 __gnu_cxx::stdio_filebuf<char> wfile_buf(
6668 file_descriptor, std::ios_base::out | std::ios_base::binary);
6669 std::ostream output(&wfile_buf);
6670 if (!wfile_buf.is_open()) return false;
6671#elif defined(_MSC_VER)
6672 std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary);
6673 if (!output.is_open()) return false;
6674#else
6675 std::ofstream output(binFilename.c_str(), std::ofstream::binary);
6676 if (!output.is_open()) return false;
6677#endif
6678#else
6679 std::ofstream output(binFilename.c_str(), std::ofstream::binary);
6680 if (!output.is_open()) return false;
6681#endif
6682 if (data.size() > 0) {
6683 output.write(s: reinterpret_cast<const char *>(&data[0]),
6684 n: std::streamsize(data.size()));
6685 } else {
6686 // Issue #229
6687 // size 0 will be still valid buffer data.
6688 // write empty file.
6689 }
6690 return true;
6691}
6692
6693#if 0 // FIXME(syoyo): not used. will be removed in the future release.
6694static void SerializeParameterMap(ParameterMap &param, json &o) {
6695 for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end();
6696 ++paramIt) {
6697 if (paramIt->second.number_array.size()) {
6698 SerializeNumberArrayProperty<double>(paramIt->first,
6699 paramIt->second.number_array, o);
6700 } else if (paramIt->second.json_double_value.size()) {
6701 json json_double_value;
6702 for (std::map<std::string, double>::iterator it =
6703 paramIt->second.json_double_value.begin();
6704 it != paramIt->second.json_double_value.end(); ++it) {
6705 if (it->first == "index") {
6706 json_double_value[it->first] = paramIt->second.TextureIndex();
6707 } else {
6708 json_double_value[it->first] = it->second;
6709 }
6710 }
6711
6712 o[paramIt->first] = json_double_value;
6713 } else if (!paramIt->second.string_value.empty()) {
6714 SerializeStringProperty(paramIt->first, paramIt->second.string_value, o);
6715 } else if (paramIt->second.has_number_value) {
6716 o[paramIt->first] = paramIt->second.number_value;
6717 } else {
6718 o[paramIt->first] = paramIt->second.bool_value;
6719 }
6720 }
6721}
6722#endif
6723
6724static void SerializeExtensionMap(const ExtensionMap &extensions, json &o) {
6725 if (!extensions.size()) return;
6726
6727 json extMap;
6728 for (ExtensionMap::const_iterator extIt = extensions.begin();
6729 extIt != extensions.end(); ++extIt) {
6730 // Allow an empty object for extension(#97)
6731 json ret;
6732 bool isNull = true;
6733 if (ValueToJson(value: extIt->second, ret: &ret)) {
6734 isNull = JsonIsNull(o: ret);
6735 JsonAddMember(o&: extMap, key: extIt->first.c_str(), value: std::move(ret));
6736 }
6737 if (isNull) {
6738 if (!(extIt->first.empty())) { // name should not be empty, but for sure
6739 // create empty object so that an extension name is still included in
6740 // json.
6741 json empty;
6742 JsonSetObject(o&: empty);
6743 JsonAddMember(o&: extMap, key: extIt->first.c_str(), value: std::move(empty));
6744 }
6745 }
6746 }
6747 JsonAddMember(o, key: "extensions", value: std::move(extMap));
6748}
6749
6750static void SerializeGltfAccessor(Accessor &accessor, json &o) {
6751 if (accessor.bufferView >= 0)
6752 SerializeNumberProperty<int>(key: "bufferView", number: accessor.bufferView, obj&: o);
6753
6754 if (accessor.byteOffset != 0)
6755 SerializeNumberProperty<int>(key: "byteOffset", number: int(accessor.byteOffset), obj&: o);
6756
6757 SerializeNumberProperty<int>(key: "componentType", number: accessor.componentType, obj&: o);
6758 SerializeNumberProperty<size_t>(key: "count", number: accessor.count, obj&: o);
6759
6760 if ((accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) ||
6761 (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)) {
6762 SerializeNumberArrayProperty<double>(key: "min", value: accessor.minValues, obj&: o);
6763 SerializeNumberArrayProperty<double>(key: "max", value: accessor.maxValues, obj&: o);
6764 } else {
6765 // Issue #301. Serialize as integer.
6766 // Assume int value is within [-2**31-1, 2**31-1]
6767 {
6768 std::vector<int> values;
6769 std::transform(first: accessor.minValues.begin(), last: accessor.minValues.end(),
6770 result: std::back_inserter(x&: values),
6771 op: [](double v) { return static_cast<int>(v); });
6772
6773 SerializeNumberArrayProperty<int>(key: "min", value: values, obj&: o);
6774 }
6775
6776 {
6777 std::vector<int> values;
6778 std::transform(first: accessor.maxValues.begin(), last: accessor.maxValues.end(),
6779 result: std::back_inserter(x&: values),
6780 op: [](double v) { return static_cast<int>(v); });
6781
6782 SerializeNumberArrayProperty<int>(key: "max", value: values, obj&: o);
6783 }
6784 }
6785
6786 if (accessor.normalized)
6787 SerializeValue(key: "normalized", value: Value(accessor.normalized), obj&: o);
6788 std::string type;
6789 switch (accessor.type) {
6790 case TINYGLTF_TYPE_SCALAR:
6791 type = "SCALAR";
6792 break;
6793 case TINYGLTF_TYPE_VEC2:
6794 type = "VEC2";
6795 break;
6796 case TINYGLTF_TYPE_VEC3:
6797 type = "VEC3";
6798 break;
6799 case TINYGLTF_TYPE_VEC4:
6800 type = "VEC4";
6801 break;
6802 case TINYGLTF_TYPE_MAT2:
6803 type = "MAT2";
6804 break;
6805 case TINYGLTF_TYPE_MAT3:
6806 type = "MAT3";
6807 break;
6808 case TINYGLTF_TYPE_MAT4:
6809 type = "MAT4";
6810 break;
6811 }
6812
6813 SerializeStringProperty(key: "type", value: type, obj&: o);
6814 if (!accessor.name.empty()) SerializeStringProperty(key: "name", value: accessor.name, obj&: o);
6815
6816 if (accessor.extras.Type() != NULL_TYPE) {
6817 SerializeValue(key: "extras", value: accessor.extras, obj&: o);
6818 }
6819
6820 // sparse
6821 if (accessor.sparse.isSparse)
6822 {
6823 json sparse;
6824 SerializeNumberProperty<int>(key: "count", number: accessor.sparse.count, obj&: sparse);
6825 {
6826 json indices;
6827 SerializeNumberProperty<int>(key: "bufferView", number: accessor.sparse.indices.bufferView, obj&: indices);
6828 SerializeNumberProperty<int>(key: "byteOffset", number: accessor.sparse.indices.byteOffset, obj&: indices);
6829 SerializeNumberProperty<int>(key: "componentType", number: accessor.sparse.indices.componentType, obj&: indices);
6830 JsonAddMember(o&: sparse, key: "indices", value: std::move(indices));
6831 }
6832 {
6833 json values;
6834 SerializeNumberProperty<int>(key: "bufferView", number: accessor.sparse.values.bufferView, obj&: values);
6835 SerializeNumberProperty<int>(key: "byteOffset", number: accessor.sparse.values.byteOffset, obj&: values);
6836 JsonAddMember(o&: sparse, key: "values", value: std::move(values));
6837 }
6838 JsonAddMember(o, key: "sparse", value: std::move(sparse));
6839 }
6840}
6841
6842static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) {
6843 SerializeNumberProperty(key: "sampler", number: channel.sampler, obj&: o);
6844 {
6845 json target;
6846 SerializeNumberProperty(key: "node", number: channel.target_node, obj&: target);
6847 SerializeStringProperty(key: "path", value: channel.target_path, obj&: target);
6848
6849 SerializeExtensionMap(extensions: channel.target_extensions, o&: target);
6850
6851 JsonAddMember(o, key: "target", value: std::move(target));
6852 }
6853
6854 if (channel.extras.Type() != NULL_TYPE) {
6855 SerializeValue(key: "extras", value: channel.extras, obj&: o);
6856 }
6857
6858 SerializeExtensionMap(extensions: channel.extensions, o);
6859}
6860
6861static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) {
6862 SerializeNumberProperty(key: "input", number: sampler.input, obj&: o);
6863 SerializeNumberProperty(key: "output", number: sampler.output, obj&: o);
6864 SerializeStringProperty(key: "interpolation", value: sampler.interpolation, obj&: o);
6865
6866 if (sampler.extras.Type() != NULL_TYPE) {
6867 SerializeValue(key: "extras", value: sampler.extras, obj&: o);
6868 }
6869}
6870
6871static void SerializeGltfAnimation(Animation &animation, json &o) {
6872 if (!animation.name.empty())
6873 SerializeStringProperty(key: "name", value: animation.name, obj&: o);
6874
6875 {
6876 json channels;
6877 JsonReserveArray(o&: channels, s: animation.channels.size());
6878 for (unsigned int i = 0; i < animation.channels.size(); ++i) {
6879 json channel;
6880 AnimationChannel gltfChannel = animation.channels[i];
6881 SerializeGltfAnimationChannel(channel&: gltfChannel, o&: channel);
6882 JsonPushBack(o&: channels, value: std::move(channel));
6883 }
6884
6885 JsonAddMember(o, key: "channels", value: std::move(channels));
6886 }
6887
6888 {
6889 json samplers;
6890 JsonReserveArray(o&: samplers, s: animation.samplers.size());
6891 for (unsigned int i = 0; i < animation.samplers.size(); ++i) {
6892 json sampler;
6893 AnimationSampler gltfSampler = animation.samplers[i];
6894 SerializeGltfAnimationSampler(sampler&: gltfSampler, o&: sampler);
6895 JsonPushBack(o&: samplers, value: std::move(sampler));
6896 }
6897 JsonAddMember(o, key: "samplers", value: std::move(samplers));
6898 }
6899
6900 if (animation.extras.Type() != NULL_TYPE) {
6901 SerializeValue(key: "extras", value: animation.extras, obj&: o);
6902 }
6903
6904 SerializeExtensionMap(extensions: animation.extensions, o);
6905}
6906
6907static void SerializeGltfAsset(Asset &asset, json &o) {
6908 if (!asset.generator.empty()) {
6909 SerializeStringProperty(key: "generator", value: asset.generator, obj&: o);
6910 }
6911
6912 if (!asset.copyright.empty()) {
6913 SerializeStringProperty(key: "copyright", value: asset.copyright, obj&: o);
6914 }
6915
6916 if (asset.version.empty()) {
6917 // Just in case
6918 // `version` must be defined
6919 asset.version = "2.0";
6920 }
6921
6922 // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0?
6923 SerializeStringProperty(key: "version", value: asset.version, obj&: o);
6924
6925 if (asset.extras.Keys().size()) {
6926 SerializeValue(key: "extras", value: asset.extras, obj&: o);
6927 }
6928
6929 SerializeExtensionMap(extensions: asset.extensions, o);
6930}
6931
6932static void SerializeGltfBufferBin(Buffer &buffer, json &o,
6933 std::vector<unsigned char> &binBuffer) {
6934 SerializeNumberProperty(key: "byteLength", number: buffer.data.size(), obj&: o);
6935 binBuffer = buffer.data;
6936
6937 if (buffer.name.size()) SerializeStringProperty(key: "name", value: buffer.name, obj&: o);
6938
6939 if (buffer.extras.Type() != NULL_TYPE) {
6940 SerializeValue(key: "extras", value: buffer.extras, obj&: o);
6941 }
6942}
6943
6944static void SerializeGltfBuffer(Buffer &buffer, json &o) {
6945 SerializeNumberProperty(key: "byteLength", number: buffer.data.size(), obj&: o);
6946 SerializeGltfBufferData(data: buffer.data, o);
6947
6948 if (buffer.name.size()) SerializeStringProperty(key: "name", value: buffer.name, obj&: o);
6949
6950 if (buffer.extras.Type() != NULL_TYPE) {
6951 SerializeValue(key: "extras", value: buffer.extras, obj&: o);
6952 }
6953}
6954
6955static bool SerializeGltfBuffer(Buffer &buffer, json &o,
6956 const std::string &binFilename,
6957 const std::string &binBaseFilename) {
6958 if (!SerializeGltfBufferData(data: buffer.data, binFilename)) return false;
6959 SerializeNumberProperty(key: "byteLength", number: buffer.data.size(), obj&: o);
6960 SerializeStringProperty(key: "uri", value: binBaseFilename, obj&: o);
6961
6962 if (buffer.name.size()) SerializeStringProperty(key: "name", value: buffer.name, obj&: o);
6963
6964 if (buffer.extras.Type() != NULL_TYPE) {
6965 SerializeValue(key: "extras", value: buffer.extras, obj&: o);
6966 }
6967 return true;
6968}
6969
6970static void SerializeGltfBufferView(BufferView &bufferView, json &o) {
6971 SerializeNumberProperty(key: "buffer", number: bufferView.buffer, obj&: o);
6972 SerializeNumberProperty<size_t>(key: "byteLength", number: bufferView.byteLength, obj&: o);
6973
6974 // byteStride is optional, minimum allowed is 4
6975 if (bufferView.byteStride >= 4) {
6976 SerializeNumberProperty<size_t>(key: "byteStride", number: bufferView.byteStride, obj&: o);
6977 }
6978 // byteOffset is optional, default is 0
6979 if (bufferView.byteOffset > 0) {
6980 SerializeNumberProperty<size_t>(key: "byteOffset", number: bufferView.byteOffset, obj&: o);
6981 }
6982 // Target is optional, check if it contains a valid value
6983 if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER ||
6984 bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
6985 SerializeNumberProperty(key: "target", number: bufferView.target, obj&: o);
6986 }
6987 if (bufferView.name.size()) {
6988 SerializeStringProperty(key: "name", value: bufferView.name, obj&: o);
6989 }
6990
6991 if (bufferView.extras.Type() != NULL_TYPE) {
6992 SerializeValue(key: "extras", value: bufferView.extras, obj&: o);
6993 }
6994}
6995
6996static void SerializeGltfImage(Image &image, json &o) {
6997 // if uri empty, the mimeType and bufferview should be set
6998 if (image.uri.empty()) {
6999 SerializeStringProperty(key: "mimeType", value: image.mimeType, obj&: o);
7000 SerializeNumberProperty<int>(key: "bufferView", number: image.bufferView, obj&: o);
7001 } else {
7002 // TODO(syoyo): dlib::urilencode?
7003 SerializeStringProperty(key: "uri", value: image.uri, obj&: o);
7004 }
7005
7006 if (image.name.size()) {
7007 SerializeStringProperty(key: "name", value: image.name, obj&: o);
7008 }
7009
7010 if (image.extras.Type() != NULL_TYPE) {
7011 SerializeValue(key: "extras", value: image.extras, obj&: o);
7012 }
7013
7014 SerializeExtensionMap(extensions: image.extensions, o);
7015}
7016
7017static void SerializeGltfTextureInfo(TextureInfo &texinfo, json &o) {
7018 SerializeNumberProperty(key: "index", number: texinfo.index, obj&: o);
7019
7020 if (texinfo.texCoord != 0) {
7021 SerializeNumberProperty(key: "texCoord", number: texinfo.texCoord, obj&: o);
7022 }
7023
7024 if (texinfo.extras.Type() != NULL_TYPE) {
7025 SerializeValue(key: "extras", value: texinfo.extras, obj&: o);
7026 }
7027
7028 SerializeExtensionMap(extensions: texinfo.extensions, o);
7029}
7030
7031static void SerializeGltfNormalTextureInfo(NormalTextureInfo &texinfo,
7032 json &o) {
7033 SerializeNumberProperty(key: "index", number: texinfo.index, obj&: o);
7034
7035 if (texinfo.texCoord != 0) {
7036 SerializeNumberProperty(key: "texCoord", number: texinfo.texCoord, obj&: o);
7037 }
7038
7039 if (!TINYGLTF_DOUBLE_EQUAL(texinfo.scale, 1.0)) {
7040 SerializeNumberProperty(key: "scale", number: texinfo.scale, obj&: o);
7041 }
7042
7043 if (texinfo.extras.Type() != NULL_TYPE) {
7044 SerializeValue(key: "extras", value: texinfo.extras, obj&: o);
7045 }
7046
7047 SerializeExtensionMap(extensions: texinfo.extensions, o);
7048}
7049
7050static void SerializeGltfOcclusionTextureInfo(OcclusionTextureInfo &texinfo,
7051 json &o) {
7052 SerializeNumberProperty(key: "index", number: texinfo.index, obj&: o);
7053
7054 if (texinfo.texCoord != 0) {
7055 SerializeNumberProperty(key: "texCoord", number: texinfo.texCoord, obj&: o);
7056 }
7057
7058 if (!TINYGLTF_DOUBLE_EQUAL(texinfo.strength, 1.0)) {
7059 SerializeNumberProperty(key: "strength", number: texinfo.strength, obj&: o);
7060 }
7061
7062 if (texinfo.extras.Type() != NULL_TYPE) {
7063 SerializeValue(key: "extras", value: texinfo.extras, obj&: o);
7064 }
7065
7066 SerializeExtensionMap(extensions: texinfo.extensions, o);
7067}
7068
7069static void SerializeGltfPbrMetallicRoughness(PbrMetallicRoughness &pbr,
7070 json &o) {
7071 std::vector<double> default_baseColorFactor = {1.0, 1.0, 1.0, 1.0};
7072 if (!Equals(one: pbr.baseColorFactor, other: default_baseColorFactor)) {
7073 SerializeNumberArrayProperty<double>(key: "baseColorFactor", value: pbr.baseColorFactor,
7074 obj&: o);
7075 }
7076
7077 if (!TINYGLTF_DOUBLE_EQUAL(pbr.metallicFactor, 1.0)) {
7078 SerializeNumberProperty(key: "metallicFactor", number: pbr.metallicFactor, obj&: o);
7079 }
7080
7081 if (!TINYGLTF_DOUBLE_EQUAL(pbr.roughnessFactor, 1.0)) {
7082 SerializeNumberProperty(key: "roughnessFactor", number: pbr.roughnessFactor, obj&: o);
7083 }
7084
7085 if (pbr.baseColorTexture.index > -1) {
7086 json texinfo;
7087 SerializeGltfTextureInfo(texinfo&: pbr.baseColorTexture, o&: texinfo);
7088 JsonAddMember(o, key: "baseColorTexture", value: std::move(texinfo));
7089 }
7090
7091 if (pbr.metallicRoughnessTexture.index > -1) {
7092 json texinfo;
7093 SerializeGltfTextureInfo(texinfo&: pbr.metallicRoughnessTexture, o&: texinfo);
7094 JsonAddMember(o, key: "metallicRoughnessTexture", value: std::move(texinfo));
7095 }
7096
7097 SerializeExtensionMap(extensions: pbr.extensions, o);
7098
7099 if (pbr.extras.Type() != NULL_TYPE) {
7100 SerializeValue(key: "extras", value: pbr.extras, obj&: o);
7101 }
7102}
7103
7104static void SerializeGltfMaterial(Material &material, json &o) {
7105 if (material.name.size()) {
7106 SerializeStringProperty(key: "name", value: material.name, obj&: o);
7107 }
7108
7109 // QUESTION(syoyo): Write material parameters regardless of its default value?
7110
7111 if (!TINYGLTF_DOUBLE_EQUAL(material.alphaCutoff, 0.5)) {
7112 SerializeNumberProperty(key: "alphaCutoff", number: material.alphaCutoff, obj&: o);
7113 }
7114
7115 if (material.alphaMode.compare(s: "OPAQUE") != 0) {
7116 SerializeStringProperty(key: "alphaMode", value: material.alphaMode, obj&: o);
7117 }
7118
7119 if (material.doubleSided != false)
7120 JsonAddMember(o, key: "doubleSided", value: json(material.doubleSided));
7121
7122 if (material.normalTexture.index > -1) {
7123 json texinfo;
7124 SerializeGltfNormalTextureInfo(texinfo&: material.normalTexture, o&: texinfo);
7125 JsonAddMember(o, key: "normalTexture", value: std::move(texinfo));
7126 }
7127
7128 if (material.occlusionTexture.index > -1) {
7129 json texinfo;
7130 SerializeGltfOcclusionTextureInfo(texinfo&: material.occlusionTexture, o&: texinfo);
7131 JsonAddMember(o, key: "occlusionTexture", value: std::move(texinfo));
7132 }
7133
7134 if (material.emissiveTexture.index > -1) {
7135 json texinfo;
7136 SerializeGltfTextureInfo(texinfo&: material.emissiveTexture, o&: texinfo);
7137 JsonAddMember(o, key: "emissiveTexture", value: std::move(texinfo));
7138 }
7139
7140 std::vector<double> default_emissiveFactor = {0.0, 0.0, 0.0};
7141 if (!Equals(one: material.emissiveFactor, other: default_emissiveFactor)) {
7142 SerializeNumberArrayProperty<double>(key: "emissiveFactor",
7143 value: material.emissiveFactor, obj&: o);
7144 }
7145
7146 {
7147 json pbrMetallicRoughness;
7148 SerializeGltfPbrMetallicRoughness(pbr&: material.pbrMetallicRoughness,
7149 o&: pbrMetallicRoughness);
7150 // Issue 204
7151 // Do not serialize `pbrMetallicRoughness` if pbrMetallicRoughness has all
7152 // default values(json is null). Otherwise it will serialize to
7153 // `pbrMetallicRoughness : null`, which cannot be read by other glTF
7154 // importers(and validators).
7155 //
7156 if (!JsonIsNull(o: pbrMetallicRoughness)) {
7157 JsonAddMember(o, key: "pbrMetallicRoughness", value: std::move(pbrMetallicRoughness));
7158 }
7159 }
7160
7161#if 0 // legacy way. just for the record.
7162 if (material.values.size()) {
7163 json pbrMetallicRoughness;
7164 SerializeParameterMap(material.values, pbrMetallicRoughness);
7165 JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness));
7166 }
7167
7168 SerializeParameterMap(material.additionalValues, o);
7169#else
7170
7171#endif
7172
7173 SerializeExtensionMap(extensions: material.extensions, o);
7174
7175 if (material.extras.Type() != NULL_TYPE) {
7176 SerializeValue(key: "extras", value: material.extras, obj&: o);
7177 }
7178}
7179
7180static void SerializeGltfMesh(Mesh &mesh, json &o) {
7181 json primitives;
7182 JsonReserveArray(o&: primitives, s: mesh.primitives.size());
7183 for (unsigned int i = 0; i < mesh.primitives.size(); ++i) {
7184 json primitive;
7185 const Primitive &gltfPrimitive = mesh.primitives[i]; // don't make a copy
7186 {
7187 json attributes;
7188 for (auto attrIt = gltfPrimitive.attributes.begin();
7189 attrIt != gltfPrimitive.attributes.end(); ++attrIt) {
7190 SerializeNumberProperty<int>(key: attrIt->first, number: attrIt->second, obj&: attributes);
7191 }
7192
7193 JsonAddMember(o&: primitive, key: "attributes", value: std::move(attributes));
7194 }
7195
7196 // Indicies is optional
7197 if (gltfPrimitive.indices > -1) {
7198 SerializeNumberProperty<int>(key: "indices", number: gltfPrimitive.indices, obj&: primitive);
7199 }
7200 // Material is optional
7201 if (gltfPrimitive.material > -1) {
7202 SerializeNumberProperty<int>(key: "material", number: gltfPrimitive.material,
7203 obj&: primitive);
7204 }
7205 SerializeNumberProperty<int>(key: "mode", number: gltfPrimitive.mode, obj&: primitive);
7206
7207 // Morph targets
7208 if (gltfPrimitive.targets.size()) {
7209 json targets;
7210 JsonReserveArray(o&: targets, s: gltfPrimitive.targets.size());
7211 for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) {
7212 json targetAttributes;
7213 std::map<std::string, int> targetData = gltfPrimitive.targets[k];
7214 for (std::map<std::string, int>::iterator attrIt = targetData.begin();
7215 attrIt != targetData.end(); ++attrIt) {
7216 SerializeNumberProperty<int>(key: attrIt->first, number: attrIt->second,
7217 obj&: targetAttributes);
7218 }
7219 JsonPushBack(o&: targets, value: std::move(targetAttributes));
7220 }
7221 JsonAddMember(o&: primitive, key: "targets", value: std::move(targets));
7222 }
7223
7224 SerializeExtensionMap(extensions: gltfPrimitive.extensions, o&: primitive);
7225
7226 if (gltfPrimitive.extras.Type() != NULL_TYPE) {
7227 SerializeValue(key: "extras", value: gltfPrimitive.extras, obj&: primitive);
7228 }
7229
7230 JsonPushBack(o&: primitives, value: std::move(primitive));
7231 }
7232
7233 JsonAddMember(o, key: "primitives", value: std::move(primitives));
7234
7235 if (mesh.weights.size()) {
7236 SerializeNumberArrayProperty<double>(key: "weights", value: mesh.weights, obj&: o);
7237 }
7238
7239 if (mesh.name.size()) {
7240 SerializeStringProperty(key: "name", value: mesh.name, obj&: o);
7241 }
7242
7243 SerializeExtensionMap(extensions: mesh.extensions, o);
7244 if (mesh.extras.Type() != NULL_TYPE) {
7245 SerializeValue(key: "extras", value: mesh.extras, obj&: o);
7246 }
7247}
7248
7249static void SerializeSpotLight(SpotLight &spot, json &o) {
7250 SerializeNumberProperty(key: "innerConeAngle", number: spot.innerConeAngle, obj&: o);
7251 SerializeNumberProperty(key: "outerConeAngle", number: spot.outerConeAngle, obj&: o);
7252 SerializeExtensionMap(extensions: spot.extensions, o);
7253 if (spot.extras.Type() != NULL_TYPE) {
7254 SerializeValue(key: "extras", value: spot.extras, obj&: o);
7255 }
7256}
7257
7258static void SerializeGltfLight(Light &light, json &o) {
7259 if (!light.name.empty()) SerializeStringProperty(key: "name", value: light.name, obj&: o);
7260 SerializeNumberProperty(key: "intensity", number: light.intensity, obj&: o);
7261 if (light.range > 0.0) {
7262 SerializeNumberProperty(key: "range", number: light.range, obj&: o);
7263 }
7264 SerializeNumberArrayProperty(key: "color", value: light.color, obj&: o);
7265 SerializeStringProperty(key: "type", value: light.type, obj&: o);
7266 if (light.type == "spot") {
7267 json spot;
7268 SerializeSpotLight(spot&: light.spot, o&: spot);
7269 JsonAddMember(o, key: "spot", value: std::move(spot));
7270 }
7271 SerializeExtensionMap(extensions: light.extensions, o);
7272 if (light.extras.Type() != NULL_TYPE) {
7273 SerializeValue(key: "extras", value: light.extras, obj&: o);
7274 }
7275}
7276
7277static void SerializeGltfNode(Node &node, json &o) {
7278 if (node.translation.size() > 0) {
7279 SerializeNumberArrayProperty<double>(key: "translation", value: node.translation, obj&: o);
7280 }
7281 if (node.rotation.size() > 0) {
7282 SerializeNumberArrayProperty<double>(key: "rotation", value: node.rotation, obj&: o);
7283 }
7284 if (node.scale.size() > 0) {
7285 SerializeNumberArrayProperty<double>(key: "scale", value: node.scale, obj&: o);
7286 }
7287 if (node.matrix.size() > 0) {
7288 SerializeNumberArrayProperty<double>(key: "matrix", value: node.matrix, obj&: o);
7289 }
7290 if (node.mesh != -1) {
7291 SerializeNumberProperty<int>(key: "mesh", number: node.mesh, obj&: o);
7292 }
7293
7294 if (node.skin != -1) {
7295 SerializeNumberProperty<int>(key: "skin", number: node.skin, obj&: o);
7296 }
7297
7298 if (node.camera != -1) {
7299 SerializeNumberProperty<int>(key: "camera", number: node.camera, obj&: o);
7300 }
7301
7302 if (node.weights.size() > 0) {
7303 SerializeNumberArrayProperty<double>(key: "weights", value: node.weights, obj&: o);
7304 }
7305
7306 if (node.extras.Type() != NULL_TYPE) {
7307 SerializeValue(key: "extras", value: node.extras, obj&: o);
7308 }
7309
7310 SerializeExtensionMap(extensions: node.extensions, o);
7311 if (!node.name.empty()) SerializeStringProperty(key: "name", value: node.name, obj&: o);
7312 SerializeNumberArrayProperty<int>(key: "children", value: node.children, obj&: o);
7313}
7314
7315static void SerializeGltfSampler(Sampler &sampler, json &o) {
7316 if (sampler.magFilter != -1) {
7317 SerializeNumberProperty(key: "magFilter", number: sampler.magFilter, obj&: o);
7318 }
7319 if (sampler.minFilter != -1) {
7320 SerializeNumberProperty(key: "minFilter", number: sampler.minFilter, obj&: o);
7321 }
7322 // SerializeNumberProperty("wrapR", sampler.wrapR, o);
7323 SerializeNumberProperty(key: "wrapS", number: sampler.wrapS, obj&: o);
7324 SerializeNumberProperty(key: "wrapT", number: sampler.wrapT, obj&: o);
7325
7326 if (sampler.extras.Type() != NULL_TYPE) {
7327 SerializeValue(key: "extras", value: sampler.extras, obj&: o);
7328 }
7329}
7330
7331static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera,
7332 json &o) {
7333 SerializeNumberProperty(key: "zfar", number: camera.zfar, obj&: o);
7334 SerializeNumberProperty(key: "znear", number: camera.znear, obj&: o);
7335 SerializeNumberProperty(key: "xmag", number: camera.xmag, obj&: o);
7336 SerializeNumberProperty(key: "ymag", number: camera.ymag, obj&: o);
7337
7338 if (camera.extras.Type() != NULL_TYPE) {
7339 SerializeValue(key: "extras", value: camera.extras, obj&: o);
7340 }
7341}
7342
7343static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera,
7344 json &o) {
7345 SerializeNumberProperty(key: "zfar", number: camera.zfar, obj&: o);
7346 SerializeNumberProperty(key: "znear", number: camera.znear, obj&: o);
7347 if (camera.aspectRatio > 0) {
7348 SerializeNumberProperty(key: "aspectRatio", number: camera.aspectRatio, obj&: o);
7349 }
7350
7351 if (camera.yfov > 0) {
7352 SerializeNumberProperty(key: "yfov", number: camera.yfov, obj&: o);
7353 }
7354
7355 if (camera.extras.Type() != NULL_TYPE) {
7356 SerializeValue(key: "extras", value: camera.extras, obj&: o);
7357 }
7358}
7359
7360static void SerializeGltfCamera(const Camera &camera, json &o) {
7361 SerializeStringProperty(key: "type", value: camera.type, obj&: o);
7362 if (!camera.name.empty()) {
7363 SerializeStringProperty(key: "name", value: camera.name, obj&: o);
7364 }
7365
7366 if (camera.type.compare(s: "orthographic") == 0) {
7367 json orthographic;
7368 SerializeGltfOrthographicCamera(camera: camera.orthographic, o&: orthographic);
7369 JsonAddMember(o, key: "orthographic", value: std::move(orthographic));
7370 } else if (camera.type.compare(s: "perspective") == 0) {
7371 json perspective;
7372 SerializeGltfPerspectiveCamera(camera: camera.perspective, o&: perspective);
7373 JsonAddMember(o, key: "perspective", value: std::move(perspective));
7374 } else {
7375 // ???
7376 }
7377
7378 if (camera.extras.Type() != NULL_TYPE) {
7379 SerializeValue(key: "extras", value: camera.extras, obj&: o);
7380 }
7381 SerializeExtensionMap(extensions: camera.extensions, o);
7382}
7383
7384static void SerializeGltfScene(Scene &scene, json &o) {
7385 SerializeNumberArrayProperty<int>(key: "nodes", value: scene.nodes, obj&: o);
7386
7387 if (scene.name.size()) {
7388 SerializeStringProperty(key: "name", value: scene.name, obj&: o);
7389 }
7390 if (scene.extras.Type() != NULL_TYPE) {
7391 SerializeValue(key: "extras", value: scene.extras, obj&: o);
7392 }
7393 SerializeExtensionMap(extensions: scene.extensions, o);
7394}
7395
7396static void SerializeGltfSkin(Skin &skin, json &o) {
7397 // required
7398 SerializeNumberArrayProperty<int>(key: "joints", value: skin.joints, obj&: o);
7399
7400 if (skin.inverseBindMatrices >= 0) {
7401 SerializeNumberProperty(key: "inverseBindMatrices", number: skin.inverseBindMatrices, obj&: o);
7402 }
7403
7404 if (skin.skeleton >= 0) {
7405 SerializeNumberProperty(key: "skeleton", number: skin.skeleton, obj&: o);
7406 }
7407
7408 if (skin.name.size()) {
7409 SerializeStringProperty(key: "name", value: skin.name, obj&: o);
7410 }
7411}
7412
7413static void SerializeGltfTexture(Texture &texture, json &o) {
7414 if (texture.sampler > -1) {
7415 SerializeNumberProperty(key: "sampler", number: texture.sampler, obj&: o);
7416 }
7417 if (texture.source > -1) {
7418 SerializeNumberProperty(key: "source", number: texture.source, obj&: o);
7419 }
7420 if (texture.name.size()) {
7421 SerializeStringProperty(key: "name", value: texture.name, obj&: o);
7422 }
7423 if (texture.extras.Type() != NULL_TYPE) {
7424 SerializeValue(key: "extras", value: texture.extras, obj&: o);
7425 }
7426 SerializeExtensionMap(extensions: texture.extensions, o);
7427}
7428
7429///
7430/// Serialize all properties except buffers and images.
7431///
7432static void SerializeGltfModel(Model *model, json &o) {
7433 // ACCESSORS
7434 if (model->accessors.size()) {
7435 json accessors;
7436 JsonReserveArray(o&: accessors, s: model->accessors.size());
7437 for (unsigned int i = 0; i < model->accessors.size(); ++i) {
7438 json accessor;
7439 SerializeGltfAccessor(accessor&: model->accessors[i], o&: accessor);
7440 JsonPushBack(o&: accessors, value: std::move(accessor));
7441 }
7442 JsonAddMember(o, key: "accessors", value: std::move(accessors));
7443 }
7444
7445 // ANIMATIONS
7446 if (model->animations.size()) {
7447 json animations;
7448 JsonReserveArray(o&: animations, s: model->animations.size());
7449 for (unsigned int i = 0; i < model->animations.size(); ++i) {
7450 if (model->animations[i].channels.size()) {
7451 json animation;
7452 SerializeGltfAnimation(animation&: model->animations[i], o&: animation);
7453 JsonPushBack(o&: animations, value: std::move(animation));
7454 }
7455 }
7456
7457 JsonAddMember(o, key: "animations", value: std::move(animations));
7458 }
7459
7460 // ASSET
7461 json asset;
7462 SerializeGltfAsset(asset&: model->asset, o&: asset);
7463 JsonAddMember(o, key: "asset", value: std::move(asset));
7464
7465 // BUFFERVIEWS
7466 if (model->bufferViews.size()) {
7467 json bufferViews;
7468 JsonReserveArray(o&: bufferViews, s: model->bufferViews.size());
7469 for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
7470 json bufferView;
7471 SerializeGltfBufferView(bufferView&: model->bufferViews[i], o&: bufferView);
7472 JsonPushBack(o&: bufferViews, value: std::move(bufferView));
7473 }
7474 JsonAddMember(o, key: "bufferViews", value: std::move(bufferViews));
7475 }
7476
7477 // Extensions required
7478 if (model->extensionsRequired.size()) {
7479 SerializeStringArrayProperty(key: "extensionsRequired",
7480 value: model->extensionsRequired, obj&: o);
7481 }
7482
7483 // MATERIALS
7484 if (model->materials.size()) {
7485 json materials;
7486 JsonReserveArray(o&: materials, s: model->materials.size());
7487 for (unsigned int i = 0; i < model->materials.size(); ++i) {
7488 json material;
7489 SerializeGltfMaterial(material&: model->materials[i], o&: material);
7490
7491 if (JsonIsNull(o: material)) {
7492 // Issue 294.
7493 // `material` does not have any required parameters
7494 // so the result may be null(unmodified) when all material parameters
7495 // have default value.
7496 //
7497 // null is not allowed thus we create an empty JSON object.
7498 JsonSetObject(o&: material);
7499 }
7500 JsonPushBack(o&: materials, value: std::move(material));
7501 }
7502 JsonAddMember(o, key: "materials", value: std::move(materials));
7503 }
7504
7505 // MESHES
7506 if (model->meshes.size()) {
7507 json meshes;
7508 JsonReserveArray(o&: meshes, s: model->meshes.size());
7509 for (unsigned int i = 0; i < model->meshes.size(); ++i) {
7510 json mesh;
7511 SerializeGltfMesh(mesh&: model->meshes[i], o&: mesh);
7512 JsonPushBack(o&: meshes, value: std::move(mesh));
7513 }
7514 JsonAddMember(o, key: "meshes", value: std::move(meshes));
7515 }
7516
7517 // NODES
7518 if (model->nodes.size()) {
7519 json nodes;
7520 JsonReserveArray(o&: nodes, s: model->nodes.size());
7521 for (unsigned int i = 0; i < model->nodes.size(); ++i) {
7522 json node;
7523 SerializeGltfNode(node&: model->nodes[i], o&: node);
7524 JsonPushBack(o&: nodes, value: std::move(node));
7525 }
7526 JsonAddMember(o, key: "nodes", value: std::move(nodes));
7527 }
7528
7529 // SCENE
7530 if (model->defaultScene > -1) {
7531 SerializeNumberProperty<int>(key: "scene", number: model->defaultScene, obj&: o);
7532 }
7533
7534 // SCENES
7535 if (model->scenes.size()) {
7536 json scenes;
7537 JsonReserveArray(o&: scenes, s: model->scenes.size());
7538 for (unsigned int i = 0; i < model->scenes.size(); ++i) {
7539 json currentScene;
7540 SerializeGltfScene(scene&: model->scenes[i], o&: currentScene);
7541 JsonPushBack(o&: scenes, value: std::move(currentScene));
7542 }
7543 JsonAddMember(o, key: "scenes", value: std::move(scenes));
7544 }
7545
7546 // SKINS
7547 if (model->skins.size()) {
7548 json skins;
7549 JsonReserveArray(o&: skins, s: model->skins.size());
7550 for (unsigned int i = 0; i < model->skins.size(); ++i) {
7551 json skin;
7552 SerializeGltfSkin(skin&: model->skins[i], o&: skin);
7553 JsonPushBack(o&: skins, value: std::move(skin));
7554 }
7555 JsonAddMember(o, key: "skins", value: std::move(skins));
7556 }
7557
7558 // TEXTURES
7559 if (model->textures.size()) {
7560 json textures;
7561 JsonReserveArray(o&: textures, s: model->textures.size());
7562 for (unsigned int i = 0; i < model->textures.size(); ++i) {
7563 json texture;
7564 SerializeGltfTexture(texture&: model->textures[i], o&: texture);
7565 JsonPushBack(o&: textures, value: std::move(texture));
7566 }
7567 JsonAddMember(o, key: "textures", value: std::move(textures));
7568 }
7569
7570 // SAMPLERS
7571 if (model->samplers.size()) {
7572 json samplers;
7573 JsonReserveArray(o&: samplers, s: model->samplers.size());
7574 for (unsigned int i = 0; i < model->samplers.size(); ++i) {
7575 json sampler;
7576 SerializeGltfSampler(sampler&: model->samplers[i], o&: sampler);
7577 JsonPushBack(o&: samplers, value: std::move(sampler));
7578 }
7579 JsonAddMember(o, key: "samplers", value: std::move(samplers));
7580 }
7581
7582 // CAMERAS
7583 if (model->cameras.size()) {
7584 json cameras;
7585 JsonReserveArray(o&: cameras, s: model->cameras.size());
7586 for (unsigned int i = 0; i < model->cameras.size(); ++i) {
7587 json camera;
7588 SerializeGltfCamera(camera: model->cameras[i], o&: camera);
7589 JsonPushBack(o&: cameras, value: std::move(camera));
7590 }
7591 JsonAddMember(o, key: "cameras", value: std::move(cameras));
7592 }
7593
7594 // EXTENSIONS
7595 SerializeExtensionMap(extensions: model->extensions, o);
7596
7597 auto extensionsUsed = model->extensionsUsed;
7598
7599 // LIGHTS as KHR_lights_punctual
7600 if (model->lights.size()) {
7601 json lights;
7602 JsonReserveArray(o&: lights, s: model->lights.size());
7603 for (unsigned int i = 0; i < model->lights.size(); ++i) {
7604 json light;
7605 SerializeGltfLight(light&: model->lights[i], o&: light);
7606 JsonPushBack(o&: lights, value: std::move(light));
7607 }
7608 json khr_lights_cmn;
7609 JsonAddMember(o&: khr_lights_cmn, key: "lights", value: std::move(lights));
7610 json ext_j;
7611
7612 {
7613 json_const_iterator it;
7614 if (FindMember(o, member: "extensions", it)) {
7615 JsonAssign(dest&: ext_j, src: GetValue(it));
7616 }
7617 }
7618
7619 JsonAddMember(o&: ext_j, key: "KHR_lights_punctual", value: std::move(khr_lights_cmn));
7620
7621 JsonAddMember(o, key: "extensions", value: std::move(ext_j));
7622
7623 // Also add "KHR_lights_punctual" to `extensionsUsed`
7624 {
7625 auto has_khr_lights_punctual =
7626 std::find_if(first: extensionsUsed.begin(), last: extensionsUsed.end(),
7627 pred: [](const std::string &s) {
7628 return (s.compare(s: "KHR_lights_punctual") == 0);
7629 });
7630
7631 if (has_khr_lights_punctual == extensionsUsed.end()) {
7632 extensionsUsed.push_back(x: "KHR_lights_punctual");
7633 }
7634 }
7635 }
7636
7637 // Extensions used
7638 if (extensionsUsed.size()) {
7639 SerializeStringArrayProperty(key: "extensionsUsed", value: extensionsUsed, obj&: o);
7640 }
7641
7642 // EXTRAS
7643 if (model->extras.Type() != NULL_TYPE) {
7644 SerializeValue(key: "extras", value: model->extras, obj&: o);
7645 }
7646}
7647
7648static bool WriteGltfStream(std::ostream &stream, const std::string &content) {
7649 stream << content << std::endl;
7650 return true;
7651}
7652
7653static bool WriteGltfFile(const std::string &output,
7654 const std::string &content) {
7655#ifdef _WIN32
7656#if defined(_MSC_VER)
7657 std::ofstream gltfFile(UTF8ToWchar(output).c_str());
7658#elif defined(__GLIBCXX__)
7659 int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
7660 _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
7661 __gnu_cxx::stdio_filebuf<char> wfile_buf(
7662 file_descriptor, std::ios_base::out | std::ios_base::binary);
7663 std::ostream gltfFile(&wfile_buf);
7664 if (!wfile_buf.is_open()) return false;
7665#else
7666 std::ofstream gltfFile(output.c_str());
7667 if (!gltfFile.is_open()) return false;
7668#endif
7669#else
7670 std::ofstream gltfFile(output.c_str());
7671 if (!gltfFile.is_open()) return false;
7672#endif
7673 return WriteGltfStream(stream&: gltfFile, content);
7674}
7675
7676static bool WriteBinaryGltfStream(std::ostream &stream,
7677 const std::string &content,
7678 const std::vector<unsigned char> &binBuffer) {
7679 const std::string header = "glTF";
7680 const int version = 2;
7681
7682 const uint32_t content_size = uint32_t(content.size());
7683 const uint32_t binBuffer_size = uint32_t(binBuffer.size());
7684 // determine number of padding bytes required to ensure 4 byte alignment
7685 const uint32_t content_padding_size =
7686 content_size % 4 == 0 ? 0 : 4 - content_size % 4;
7687 const uint32_t bin_padding_size =
7688 binBuffer_size % 4 == 0 ? 0 : 4 - binBuffer_size % 4;
7689
7690 // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
7691 // Chunk data must be located at 4-byte boundary, which may require padding
7692 const uint32_t length =
7693 12 + 8 + content_size + content_padding_size +
7694 (binBuffer_size ? (8 + binBuffer_size + bin_padding_size) : 0);
7695
7696 stream.write(s: header.c_str(), n: std::streamsize(header.size()));
7697 stream.write(s: reinterpret_cast<const char *>(&version), n: sizeof(version));
7698 stream.write(s: reinterpret_cast<const char *>(&length), n: sizeof(length));
7699
7700 // JSON chunk info, then JSON data
7701 const uint32_t model_length = uint32_t(content.size()) + content_padding_size;
7702 const uint32_t model_format = 0x4E4F534A;
7703 stream.write(s: reinterpret_cast<const char *>(&model_length),
7704 n: sizeof(model_length));
7705 stream.write(s: reinterpret_cast<const char *>(&model_format),
7706 n: sizeof(model_format));
7707 stream.write(s: content.c_str(), n: std::streamsize(content.size()));
7708
7709 // Chunk must be multiplies of 4, so pad with spaces
7710 if (content_padding_size > 0) {
7711 const std::string padding = std::string(size_t(content_padding_size), ' ');
7712 stream.write(s: padding.c_str(), n: std::streamsize(padding.size()));
7713 }
7714 if (binBuffer.size() > 0) {
7715 // BIN chunk info, then BIN data
7716 const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size;
7717 const uint32_t bin_format = 0x004e4942;
7718 stream.write(s: reinterpret_cast<const char *>(&bin_length),
7719 n: sizeof(bin_length));
7720 stream.write(s: reinterpret_cast<const char *>(&bin_format),
7721 n: sizeof(bin_format));
7722 stream.write(s: reinterpret_cast<const char *>(binBuffer.data()),
7723 n: std::streamsize(binBuffer.size()));
7724 // Chunksize must be multiplies of 4, so pad with zeroes
7725 if (bin_padding_size > 0) {
7726 const std::vector<unsigned char> padding =
7727 std::vector<unsigned char>(size_t(bin_padding_size), 0);
7728 stream.write(s: reinterpret_cast<const char *>(padding.data()),
7729 n: std::streamsize(padding.size()));
7730 }
7731 }
7732
7733 // TODO: Check error on stream.write
7734 return true;
7735}
7736
7737static bool WriteBinaryGltfFile(const std::string &output,
7738 const std::string &content,
7739 const std::vector<unsigned char> &binBuffer) {
7740#ifdef _WIN32
7741#if defined(_MSC_VER)
7742 std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary);
7743#elif defined(__GLIBCXX__)
7744 int file_descriptor = _wopen(UTF8ToWchar(output).c_str(),
7745 _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY);
7746 __gnu_cxx::stdio_filebuf<char> wfile_buf(
7747 file_descriptor, std::ios_base::out | std::ios_base::binary);
7748 std::ostream gltfFile(&wfile_buf);
7749#else
7750 std::ofstream gltfFile(output.c_str(), std::ios::binary);
7751#endif
7752#else
7753 std::ofstream gltfFile(output.c_str(), std::ios::binary);
7754#endif
7755 return WriteBinaryGltfStream(stream&: gltfFile, content, binBuffer);
7756}
7757
7758bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
7759 bool prettyPrint = true,
7760 bool writeBinary = false) {
7761 JsonDocument output;
7762
7763 /// Serialize all properties except buffers and images.
7764 SerializeGltfModel(model, o&: output);
7765
7766 // BUFFERS
7767 std::vector<unsigned char> binBuffer;
7768 if (model->buffers.size()) {
7769 json buffers;
7770 JsonReserveArray(o&: buffers, s: model->buffers.size());
7771 for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7772 json buffer;
7773 if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
7774 SerializeGltfBufferBin(buffer&: model->buffers[i], o&: buffer, binBuffer);
7775 } else {
7776 SerializeGltfBuffer(buffer&: model->buffers[i], o&: buffer);
7777 }
7778 JsonPushBack(o&: buffers, value: std::move(buffer));
7779 }
7780 JsonAddMember(o&: output, key: "buffers", value: std::move(buffers));
7781 }
7782
7783 // IMAGES
7784 if (model->images.size()) {
7785 json images;
7786 JsonReserveArray(o&: images, s: model->images.size());
7787 for (unsigned int i = 0; i < model->images.size(); ++i) {
7788 json image;
7789
7790 std::string dummystring = "";
7791 // UpdateImageObject need baseDir but only uses it if embeddedImages is
7792 // enabled, since we won't write separate images when writing to a stream
7793 // we
7794 UpdateImageObject(image&: model->images[i], baseDir&: dummystring, index: int(i), embedImages: true,
7795 WriteImageData: &this->WriteImageData, user_data: this->write_image_user_data_);
7796 SerializeGltfImage(image&: model->images[i], o&: image);
7797 JsonPushBack(o&: images, value: std::move(image));
7798 }
7799 JsonAddMember(o&: output, key: "images", value: std::move(images));
7800 }
7801
7802 if (writeBinary) {
7803 return WriteBinaryGltfStream(stream, content: JsonToString(o: output), binBuffer);
7804 } else {
7805 return WriteGltfStream(stream, content: JsonToString(o: output, spacing: prettyPrint ? 2 : -1));
7806 }
7807
7808}
7809
7810bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
7811 bool embedImages = false,
7812 bool embedBuffers = false,
7813 bool prettyPrint = true,
7814 bool writeBinary = false) {
7815 JsonDocument output;
7816 std::string defaultBinFilename = GetBaseFilename(filepath: filename);
7817 std::string defaultBinFileExt = ".bin";
7818 std::string::size_type pos =
7819 defaultBinFilename.rfind(c: '.', pos: defaultBinFilename.length());
7820
7821 if (pos != std::string::npos) {
7822 defaultBinFilename = defaultBinFilename.substr(pos: 0, n: pos);
7823 }
7824 std::string baseDir = GetBaseDir(filepath: filename);
7825 if (baseDir.empty()) {
7826 baseDir = "./";
7827 }
7828 /// Serialize all properties except buffers and images.
7829 SerializeGltfModel(model, o&: output);
7830
7831 // BUFFERS
7832 std::vector<std::string> usedUris;
7833 std::vector<unsigned char> binBuffer;
7834 if (model->buffers.size()) {
7835 json buffers;
7836 JsonReserveArray(o&: buffers, s: model->buffers.size());
7837 for (unsigned int i = 0; i < model->buffers.size(); ++i) {
7838 json buffer;
7839 if (writeBinary && i == 0 && model->buffers[i].uri.empty()) {
7840 SerializeGltfBufferBin(buffer&: model->buffers[i], o&: buffer, binBuffer);
7841 } else if (embedBuffers) {
7842 SerializeGltfBuffer(buffer&: model->buffers[i], o&: buffer);
7843 } else {
7844 std::string binSavePath;
7845 std::string binUri;
7846 if (!model->buffers[i].uri.empty() &&
7847 !IsDataURI(in: model->buffers[i].uri)) {
7848 binUri = model->buffers[i].uri;
7849 } else {
7850 binUri = defaultBinFilename + defaultBinFileExt;
7851 bool inUse = true;
7852 int numUsed = 0;
7853 while (inUse) {
7854 inUse = false;
7855 for (const std::string &usedName : usedUris) {
7856 if (binUri.compare(str: usedName) != 0) continue;
7857 inUse = true;
7858 binUri = defaultBinFilename + std::to_string(val: numUsed++) +
7859 defaultBinFileExt;
7860 break;
7861 }
7862 }
7863 }
7864 usedUris.push_back(x: binUri);
7865 binSavePath = JoinPath(path0: baseDir, path1: binUri);
7866 if (!SerializeGltfBuffer(buffer&: model->buffers[i], o&: buffer, binFilename: binSavePath,
7867 binBaseFilename: binUri)) {
7868 return false;
7869 }
7870 }
7871 JsonPushBack(o&: buffers, value: std::move(buffer));
7872 }
7873 JsonAddMember(o&: output, key: "buffers", value: std::move(buffers));
7874 }
7875
7876 // IMAGES
7877 if (model->images.size()) {
7878 json images;
7879 JsonReserveArray(o&: images, s: model->images.size());
7880 for (unsigned int i = 0; i < model->images.size(); ++i) {
7881 json image;
7882
7883 UpdateImageObject(image&: model->images[i], baseDir, index: int(i), embedImages,
7884 WriteImageData: &this->WriteImageData, user_data: this->write_image_user_data_);
7885 SerializeGltfImage(image&: model->images[i], o&: image);
7886 JsonPushBack(o&: images, value: std::move(image));
7887 }
7888 JsonAddMember(o&: output, key: "images", value: std::move(images));
7889 }
7890
7891 if (writeBinary) {
7892 return WriteBinaryGltfFile(output: filename, content: JsonToString(o: output), binBuffer);
7893 } else {
7894 return WriteGltfFile(output: filename, content: JsonToString(o: output, spacing: (prettyPrint ? 2 : -1)));
7895 }
7896
7897}
7898
7899} // namespace tinygltf
7900
7901#ifdef __clang__
7902#pragma clang diagnostic pop
7903#endif
7904
7905#endif // TINYGLTF_IMPLEMENTATION
7906

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com

source code of flutter_engine/third_party/tinygltf/tiny_gltf.h