From 14d2eb9f78648a6a8f8db5f8e1d2bb6f403d5e89 Mon Sep 17 00:00:00 2001 From: Dennis Brakhane Date: Sat, 9 Aug 2025 09:53:48 +0200 Subject: [PATCH] gltf: fix saving for current tinygltf versions, tinygltf back to v2.9.6 (#1198) --- Editor/ModelImporter_GLTF.cpp | 56 +- Editor/tiny_gltf.h | 14783 +++++++++++++++++--------------- 2 files changed, 7857 insertions(+), 6982 deletions(-) diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index e2d0d3c27..68244b8b8 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -9,7 +9,6 @@ #include #define TINYGLTF_IMPLEMENTATION -#define TINYGLTF_NO_FS #define TINYGLTF_NO_STB_IMAGE #define TINYGLTF_NO_STB_IMAGE_WRITE #include "tiny_gltf.h" @@ -23,9 +22,11 @@ using namespace wi::scene; using namespace wi::ecs; using json = nlohmann::json; -namespace tinygltf +namespace wi::tinygltf { + using namespace ::tinygltf; + bool FileExists(const std::string& abs_filename, void*) { return wi::helper::FileExists(abs_filename); } @@ -88,6 +89,13 @@ namespace tinygltf return wi::helper::FileWrite(filepath, contents.data(), contents.size()); } + bool GetFileSizeInBytes(size_t* filesize_out, std::string* err, + const std::string& filepath, void* userdata) + { + *filesize_out = wi::helper::FileSize(filepath); + return true; + } + bool LoadImageData(Image *image, const int image_idx, std::string *err, std::string *warn, int req_width, int req_height, const unsigned char *bytes, int size, void *userdata) @@ -118,8 +126,10 @@ namespace tinygltf return true; } - bool WriteImageData(const std::string* basepath, const std::string* filename, - Image* image, bool embedImages, void*) + bool WriteImageData( + const std::string* basepath, const std::string* filename, + const Image* image, bool embedImages, const FsCallbacks*, const URICallbacks*, + std::string* out_uri, void*) { assert(0); // TODO return false; @@ -512,21 +522,24 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) std::string warn; tinygltf::FsCallbacks callbacks; - callbacks.ReadWholeFile = tinygltf::ReadWholeFile; - callbacks.WriteWholeFile = tinygltf::WriteWholeFile; - callbacks.FileExists = tinygltf::FileExists; - callbacks.ExpandFilePath = tinygltf::ExpandFilePath; - loader.SetFsCallbacks(callbacks); + callbacks.ReadWholeFile = wi::tinygltf::ReadWholeFile; + callbacks.WriteWholeFile = wi::tinygltf::WriteWholeFile; + callbacks.FileExists = wi::tinygltf::FileExists; + callbacks.GetFileSizeInBytes = wi::tinygltf::GetFileSizeInBytes; + callbacks.ExpandFilePath = wi::tinygltf::ExpandFilePath; + + bool ret = loader.SetFsCallbacks(callbacks); + assert(ret); wi::resourcemanager::ResourceSerializer seri; // keep this alive to not delete loaded images while importing gltf - loader.SetImageLoader(tinygltf::LoadImageData, &seri); - loader.SetImageWriter(tinygltf::WriteImageData, nullptr); - + loader.SetImageLoader(wi::tinygltf::LoadImageData, &seri); + loader.SetImageWriter(wi::tinygltf::WriteImageData, nullptr); + LoaderState state; state.scene = &scene; wi::vector filedata; - bool ret = wi::helper::FileRead(fileName, filedata); + ret = wi::helper::FileRead(fileName, filedata); if (ret) { @@ -3941,11 +3954,13 @@ void ExportModel_GLTF(const std::string& filename, Scene& scene) tinygltf::TinyGLTF writer; tinygltf::FsCallbacks callbacks; - callbacks.ReadWholeFile = tinygltf::ReadWholeFile; - callbacks.WriteWholeFile = tinygltf::WriteWholeFile; - callbacks.FileExists = tinygltf::FileExists; - callbacks.ExpandFilePath = tinygltf::ExpandFilePath; - writer.SetFsCallbacks(callbacks); + callbacks.ReadWholeFile = wi::tinygltf::ReadWholeFile; + callbacks.WriteWholeFile = wi::tinygltf::WriteWholeFile; + callbacks.FileExists = wi::tinygltf::FileExists; + callbacks.ExpandFilePath = wi::tinygltf::ExpandFilePath; + callbacks.GetFileSizeInBytes = wi::tinygltf::GetFileSizeInBytes; + bool res = writer.SetFsCallbacks(callbacks); + assert(res); LoaderState state; state.scene = &scene; @@ -5242,13 +5257,14 @@ void ExportModel_GLTF(const std::string& filename, Scene& scene) auto file_extension = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(filename)); if(file_extension == "GLB") { - writer.WriteGltfSceneToFile(&state.gltfModel, filename, false, true, true, true); + res = writer.WriteGltfSceneToFile(&state.gltfModel, filename, false, true, true, true); } else { - writer.WriteGltfSceneToFile(&state.gltfModel, filename, false, false, true, false); + res = writer.WriteGltfSceneToFile(&state.gltfModel, filename, false, false, true, false); } + assert(res); // Restore scene world orientation FlipZAxis(state); wiscene.Update(0.f); diff --git a/Editor/tiny_gltf.h b/Editor/tiny_gltf.h index 5a433e402..b6478bc3e 100644 --- a/Editor/tiny_gltf.h +++ b/Editor/tiny_gltf.h @@ -25,24 +25,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// Version: -// - v2.5.0 Add SetPreserveImageChannels() option to load image data as is. -// - v2.4.3 Fix null object output when when material has all default -// parameters. -// - v2.4.2 Decode percent-encoded URI. -// - v2.4.1 Fix some glTF object class does not have `extensions` and/or -// `extras` property. -// - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone). -// - v2.3.1 Set default value of minFilter and magFilter in Sampler to -1. -// - v2.3.0 Modified Material representation according to glTF 2.0 schema -// (and introduced TextureInfo class) -// Change the behavior of `Value::IsNumber`. It return true either the -// value is int or real. -// - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks -// to @Ybalrid) -// - v2.1.0 Add draco compression. -// - v2.0.1 Add comparsion feature(Thanks to @Selmar). -// - v2.0.0 glTF 2.0!. +// Version: - v2.9.6 +// See https://github.com/syoyo/tinygltf/releases for release history. // // Tiny glTF loader is using following third party libraries: // @@ -59,15 +43,13 @@ #include #include #include +#include #include #include #include +#include #include -#ifndef TINYGLTF_USE_CPP14 -#include -#endif - #ifdef __ANDROID__ #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS #include @@ -108,7 +90,11 @@ namespace tinygltf { #define TINYGLTF_COMPONENT_TYPE_INT (5124) #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125) #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) -#define TINYGLTF_COMPONENT_TYPE_DOUBLE (5130) // OpenGL double type. Note that some of glTF 2.0 validator does not support double type even the schema seems allow any value of integer: https://github.com/KhronosGroup/glTF/blob/b9884a2fd45130b4d673dd6c8a706ee21ee5c5f7/specification/2.0/schema/accessor.schema.json#L22 +#define TINYGLTF_COMPONENT_TYPE_DOUBLE \ + (5130) // OpenGL double type. Note that some of glTF 2.0 validator does not + // support double type even the schema seems allow any value of + // integer: + // https://github.com/KhronosGroup/glTF/blob/b9884a2fd45130b4d673dd6c8a706ee21ee5c5f7/specification/2.0/schema/accessor.schema.json#L22 #define TINYGLTF_TEXTURE_FILTER_NEAREST (9728) #define TINYGLTF_TEXTURE_FILTER_LINEAR (9729) @@ -121,7 +107,7 @@ namespace tinygltf { #define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071) #define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648) - // Redeclarations of the above for technique.parameters. +// Redeclarations of the above for technique.parameters. #define TINYGLTF_PARAMETER_TYPE_BYTE (5120) #define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121) #define TINYGLTF_PARAMETER_TYPE_SHORT (5122) @@ -186,240 +172,223 @@ namespace tinygltf { #ifdef __ANDROID__ #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - AAssetManager* asset_manager = nullptr; +#ifdef TINYGLTF_IMPLEMENTATION +AAssetManager *asset_manager = nullptr; +#else +extern AAssetManager *asset_manager; +#endif #endif #endif - typedef enum { - NULL_TYPE, - REAL_TYPE, - INT_TYPE, - BOOL_TYPE, - STRING_TYPE, - ARRAY_TYPE, - BINARY_TYPE, - OBJECT_TYPE - } Type; +typedef enum { + NULL_TYPE, + REAL_TYPE, + INT_TYPE, + BOOL_TYPE, + STRING_TYPE, + ARRAY_TYPE, + BINARY_TYPE, + OBJECT_TYPE +} Type; - static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { - if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { - return 1; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - return 1; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { - return 2; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - return 2; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { - return 8; - } - else { - // Unknown componenty type - return -1; - } - } +typedef enum { + Permissive, + Strict +} ParseStrictness; - static inline int32_t GetNumComponentsInType(uint32_t ty) { - if (ty == TINYGLTF_TYPE_SCALAR) { - return 1; - } - else if (ty == TINYGLTF_TYPE_VEC2) { - return 2; - } - else if (ty == TINYGLTF_TYPE_VEC3) { - return 3; - } - else if (ty == TINYGLTF_TYPE_VEC4) { - return 4; - } - else if (ty == TINYGLTF_TYPE_MAT2) { - return 4; - } - else if (ty == TINYGLTF_TYPE_MAT3) { - return 9; - } - else if (ty == TINYGLTF_TYPE_MAT4) { - return 16; - } - else { - // Unknown componenty type - return -1; - } - } +static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { + if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return 8; + } else { + // Unknown component type + return -1; + } +} - // TODO(syoyo): Move these functions to TinyGLTF class - bool IsDataURI(const std::string& in); - bool DecodeDataURI(std::vector* out, std::string& mime_type, - const std::string& in, size_t reqBytes, bool checkSize); +static inline int32_t GetNumComponentsInType(uint32_t ty) { + if (ty == TINYGLTF_TYPE_SCALAR) { + return 1; + } else if (ty == TINYGLTF_TYPE_VEC2) { + return 2; + } else if (ty == TINYGLTF_TYPE_VEC3) { + return 3; + } else if (ty == TINYGLTF_TYPE_VEC4) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT2) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT3) { + return 9; + } else if (ty == TINYGLTF_TYPE_MAT4) { + return 16; + } else { + // Unknown component type + return -1; + } +} + +// TODO(syoyo): Move these functions to TinyGLTF class +bool IsDataURI(const std::string &in); +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize); #ifdef __clang__ #pragma clang diagnostic push - // Suppress warning for : static Value null_value +// Suppress warning for : static Value null_value #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wpadded" #endif // Simple class to represent JSON object - class Value { - public: - typedef std::vector Array; - typedef std::map Object; +class Value { + public: + typedef std::vector Array; + typedef std::map Object; - Value() - : type_(NULL_TYPE), - int_value_(0), - real_value_(0.0), - boolean_value_(false) { - } + Value() = default; - explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } - explicit Value(int i) : type_(INT_TYPE) { - int_value_ = i; - real_value_ = i; - } - explicit Value(double n) : type_(REAL_TYPE) { real_value_ = n; } - explicit Value(const std::string& s) : type_(STRING_TYPE) { - string_value_ = s; - } - explicit Value(std::string&& s) - : type_(STRING_TYPE), string_value_(std::move(s)) { - } - explicit Value(const unsigned char* p, size_t n) : type_(BINARY_TYPE) { - binary_value_.resize(n); - memcpy(binary_value_.data(), p, n); - } - explicit Value(std::vector&& v) noexcept - : type_(BINARY_TYPE), - binary_value_(std::move(v)) { - } - explicit Value(const Array& a) : type_(ARRAY_TYPE) { array_value_ = a; } - explicit Value(Array&& a) noexcept : type_(ARRAY_TYPE), - array_value_(std::move(a)) { - } + explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } + explicit Value(int i) : type_(INT_TYPE) { + int_value_ = i; + real_value_ = i; + } + explicit Value(double n) : type_(REAL_TYPE) { real_value_ = n; } + explicit Value(const std::string &s) : type_(STRING_TYPE) { + string_value_ = s; + } + explicit Value(std::string &&s) + : type_(STRING_TYPE), string_value_(std::move(s)) {} + explicit Value(const char *s) : type_(STRING_TYPE) { string_value_ = s; } + explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) { + binary_value_.resize(n); + memcpy(binary_value_.data(), p, n); + } + explicit Value(std::vector &&v) noexcept + : type_(BINARY_TYPE), + binary_value_(std::move(v)) {} + explicit Value(const Array &a) : type_(ARRAY_TYPE) { array_value_ = a; } + explicit Value(Array &&a) noexcept : type_(ARRAY_TYPE), + array_value_(std::move(a)) {} - explicit Value(const Object& o) : type_(OBJECT_TYPE) { object_value_ = o; } - explicit Value(Object&& o) noexcept : type_(OBJECT_TYPE), - object_value_(std::move(o)) { - } + explicit Value(const Object &o) : type_(OBJECT_TYPE) { object_value_ = o; } + explicit Value(Object &&o) noexcept : type_(OBJECT_TYPE), + object_value_(std::move(o)) {} - DEFAULT_METHODS(Value) + DEFAULT_METHODS(Value) - char Type() const { return static_cast(type_); } + char Type() const { return static_cast(type_); } - bool IsBool() const { return (type_ == BOOL_TYPE); } + bool IsBool() const { return (type_ == BOOL_TYPE); } - bool IsInt() const { return (type_ == INT_TYPE); } + bool IsInt() const { return (type_ == INT_TYPE); } - bool IsNumber() const { return (type_ == REAL_TYPE) || (type_ == INT_TYPE); } + bool IsNumber() const { return (type_ == REAL_TYPE) || (type_ == INT_TYPE); } - bool IsReal() const { return (type_ == REAL_TYPE); } + bool IsReal() const { return (type_ == REAL_TYPE); } - bool IsString() const { return (type_ == STRING_TYPE); } + bool IsString() const { return (type_ == STRING_TYPE); } - bool IsBinary() const { return (type_ == BINARY_TYPE); } + bool IsBinary() const { return (type_ == BINARY_TYPE); } - bool IsArray() const { return (type_ == ARRAY_TYPE); } + bool IsArray() const { return (type_ == ARRAY_TYPE); } - bool IsObject() const { return (type_ == OBJECT_TYPE); } + bool IsObject() const { return (type_ == OBJECT_TYPE); } - // Use this function if you want to have number value as double. - double GetNumberAsDouble() const { - if (type_ == INT_TYPE) { - return double(int_value_); - } - else { - return real_value_; - } - } + // Use this function if you want to have number value as double. + double GetNumberAsDouble() const { + if (type_ == INT_TYPE) { + return double(int_value_); + } else { + return real_value_; + } + } - // Use this function if you want to have number value as int. - // TODO(syoyo): Support int value larger than 32 bits - int GetNumberAsInt() const { - if (type_ == REAL_TYPE) { - return int(real_value_); - } - else { - return int_value_; - } - } + // Use this function if you want to have number value as int. + // TODO(syoyo): Support int value larger than 32 bits + int GetNumberAsInt() const { + if (type_ == REAL_TYPE) { + return int(real_value_); + } else { + return int_value_; + } + } - // Accessor - template - const T& Get() const; - template - T& Get(); + // Accessor + template + const T &Get() const; + template + T &Get(); - // Lookup value from an array - const Value& Get(int idx) const { - static Value null_value; - assert(IsArray()); - assert(idx >= 0); - return (static_cast(idx) < array_value_.size()) - ? array_value_[static_cast(idx)] - : null_value; - } + // Lookup value from an array + const Value &Get(size_t idx) const { + static Value null_value; + assert(IsArray()); + return (idx < array_value_.size()) + ? array_value_[idx] + : null_value; + } - // Lookup value from a key-value pair - const Value& Get(const std::string& key) const { - static Value null_value; - assert(IsObject()); - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? it->second : null_value; - } + // Lookup value from a key-value pair + const Value &Get(const std::string &key) const { + static Value null_value; + assert(IsObject()); + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? it->second : null_value; + } - size_t ArrayLen() const { - if (!IsArray()) return 0; - return array_value_.size(); - } + size_t ArrayLen() const { + if (!IsArray()) return 0; + return array_value_.size(); + } - // Valid only for object type. - bool Has(const std::string& key) const { - if (!IsObject()) return false; - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? true : false; - } + // Valid only for object type. + bool Has(const std::string &key) const { + if (!IsObject()) return false; + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? true : false; + } - // List keys - std::vector Keys() const { - std::vector keys; - if (!IsObject()) return keys; // empty + // List keys + std::vector Keys() const { + std::vector keys; + if (!IsObject()) return keys; // empty - for (Object::const_iterator it = object_value_.begin(); - it != object_value_.end(); ++it) { - keys.push_back(it->first); - } + for (Object::const_iterator it = object_value_.begin(); + it != object_value_.end(); ++it) { + keys.push_back(it->first); + } - return keys; - } + return keys; + } - size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } + size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } - bool operator==(const tinygltf::Value& other) const; + bool operator==(const tinygltf::Value &other) const; - protected: - int type_ = NULL_TYPE; + protected: + int type_ = NULL_TYPE; - int int_value_ = 0; - double real_value_ = 0.0; - std::string string_value_; - std::vector binary_value_; - Array array_value_; - Object object_value_; - bool boolean_value_ = false; - }; + int int_value_ = 0; + double real_value_ = 0.0; + std::string string_value_; + std::vector binary_value_; + Array array_value_; + Object object_value_; + bool boolean_value_ = false; +}; #ifdef __clang__ #pragma clang diagnostic pop @@ -428,19 +397,19 @@ namespace tinygltf { #define TINYGLTF_VALUE_GET(ctype, var) \ template <> \ inline const ctype &Value::Get() const { \ - return var; \ + return var; \ } \ template <> \ inline ctype &Value::Get() { \ - return var; \ + return var; \ } - TINYGLTF_VALUE_GET(bool, boolean_value_) - TINYGLTF_VALUE_GET(double, real_value_) - TINYGLTF_VALUE_GET(int, int_value_) - TINYGLTF_VALUE_GET(std::string, string_value_) - TINYGLTF_VALUE_GET(std::vector, binary_value_) - TINYGLTF_VALUE_GET(Value::Array, array_value_) - TINYGLTF_VALUE_GET(Value::Object, object_value_) +TINYGLTF_VALUE_GET(bool, boolean_value_) +TINYGLTF_VALUE_GET(double, real_value_) +TINYGLTF_VALUE_GET(int, int_value_) +TINYGLTF_VALUE_GET(std::string, string_value_) +TINYGLTF_VALUE_GET(std::vector, binary_value_) +TINYGLTF_VALUE_GET(Value::Array, array_value_) +TINYGLTF_VALUE_GET(Value::Object, object_value_) #undef TINYGLTF_VALUE_GET #ifdef __clang__ @@ -449,90 +418,90 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wpadded" #endif - /// Agregate object for representing a color - using ColorValue = std::array; +/// Aggregate object for representing a color +using ColorValue = std::array; - // === legacy interface ==== - // TODO(syoyo): Deprecate `Parameter` class. - struct Parameter { - bool bool_value = false; - bool has_number_value = false; - std::string string_value; - std::vector number_array; - std::map json_double_value; - double number_value = 0.0; +// === legacy interface ==== +// TODO(syoyo): Deprecate `Parameter` class. +struct Parameter { + bool bool_value = false; + bool has_number_value = false; + std::string string_value; + std::vector number_array; + std::map json_double_value; + double number_value = 0.0; - // context sensitive methods. depending the type of the Parameter you are - // accessing, these are either valid or not - // If this parameter represent a texture map in a material, will return the - // texture index + // context sensitive methods. depending the type of the Parameter you are + // accessing, these are either valid or not + // If this parameter represent a texture map in a material, will return the + // texture index - /// Return the index of a texture if this Parameter is a texture map. - /// Returned value is only valid if the parameter represent a texture from a - /// material - int TextureIndex() const { - const auto it = json_double_value.find("index"); - if (it != std::end(json_double_value)) { - return int(it->second); - } - return -1; - } + /// Return the index of a texture if this Parameter is a texture map. + /// Returned value is only valid if the parameter represent a texture from a + /// material + int TextureIndex() const { + const auto it = json_double_value.find("index"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + return -1; + } - /// Return the index of a texture coordinate set if this Parameter is a - /// texture map. Returned value is only valid if the parameter represent a - /// texture from a material - int TextureTexCoord() const { - const auto it = json_double_value.find("texCoord"); - if (it != std::end(json_double_value)) { - return int(it->second); - } - // As per the spec, if texCoord is ommited, this parameter is 0 - return 0; - } + /// Return the index of a texture coordinate set if this Parameter is a + /// texture map. Returned value is only valid if the parameter represent a + /// texture from a material + int TextureTexCoord() const { + const auto it = json_double_value.find("texCoord"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + // As per the spec, if texCoord is omitted, this parameter is 0 + return 0; + } - /// Return the scale of a texture if this Parameter is a normal texture map. - /// Returned value is only valid if the parameter represent a normal texture - /// from a material - double TextureScale() const { - const auto it = json_double_value.find("scale"); - if (it != std::end(json_double_value)) { - return it->second; - } - // As per the spec, if scale is ommited, this paramter is 1 - return 1; - } + /// Return the scale of a texture if this Parameter is a normal texture map. + /// Returned value is only valid if the parameter represent a normal texture + /// from a material + double TextureScale() const { + const auto it = json_double_value.find("scale"); + if (it != std::end(json_double_value)) { + return it->second; + } + // As per the spec, if scale is omitted, this parameter is 1 + return 1; + } - /// Return the strength of a texture if this Parameter is a an occlusion map. - /// Returned value is only valid if the parameter represent an occlusion map - /// from a material - double TextureStrength() const { - const auto it = json_double_value.find("strength"); - if (it != std::end(json_double_value)) { - return it->second; - } - // As per the spec, if strenghth is ommited, this parameter is 1 - return 1; - } + /// Return the strength of a texture if this Parameter is a an occlusion map. + /// Returned value is only valid if the parameter represent an occlusion map + /// from a material + double TextureStrength() const { + const auto it = json_double_value.find("strength"); + if (it != std::end(json_double_value)) { + return it->second; + } + // As per the spec, if strength is omitted, this parameter is 1 + return 1; + } - /// Material factor, like the roughness or metalness of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - double Factor() const { return number_value; } + /// Material factor, like the roughness or metalness of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + double Factor() const { return number_value; } - /// Return the color of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - ColorValue ColorFactor() const { - return { - {// this agregate intialize the std::array object, and uses C++11 RVO. - number_array[0], number_array[1], number_array[2], - (number_array.size() > 3 ? number_array[3] : 1.0)} }; - } + /// Return the color of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + ColorValue ColorFactor() const { + return { + {// this aggregate initialize the std::array object, and uses C++11 RVO. + number_array[0], number_array[1], number_array[2], + (number_array.size() > 3 ? number_array[3] : 1.0)}}; + } - Parameter() = default; - DEFAULT_METHODS(Parameter) - bool operator==(const Parameter&) const; - }; + Parameter() = default; + DEFAULT_METHODS(Parameter) + bool operator==(const Parameter &) const; +}; #ifdef __clang__ #pragma clang diagnostic pop @@ -543,948 +512,1123 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wpadded" #endif - typedef std::map ParameterMap; - typedef std::map ExtensionMap; - - struct AnimationChannel { - int sampler; // required - int target_node; // required (index of the node to target) - std::string target_path; // required in ["translation", "rotation", "scale", - // "weights"] - Value extras; - ExtensionMap extensions; - ExtensionMap target_extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - std::string target_extensions_json_string; - - AnimationChannel() : sampler(-1), target_node(-1) {} - DEFAULT_METHODS(AnimationChannel) - bool operator==(const AnimationChannel&) const; - }; - - struct AnimationSampler { - int input; // required - int output; // required - std::string interpolation; // "LINEAR", "STEP","CUBICSPLINE" or user defined - // string. default "LINEAR" - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} - DEFAULT_METHODS(AnimationSampler) - bool operator==(const AnimationSampler&) const; - }; - - struct Animation { - std::string name; - std::vector channels; - std::vector samplers; - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Animation() = default; - DEFAULT_METHODS(Animation) - bool operator==(const Animation&) const; - }; - - struct Skin { - std::string name; - int inverseBindMatrices; // required here but not in the spec - int skeleton; // The index of the node used as a skeleton root - std::vector joints; // Indices of skeleton nodes - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Skin() { - inverseBindMatrices = -1; - skeleton = -1; - } - DEFAULT_METHODS(Skin) - bool operator==(const Skin&) const; - }; - - struct Sampler { - std::string name; - // glTF 2.0 spec does not define default value for `minFilter` and - // `magFilter`. Set -1 in TinyGLTF(issue #186) - int minFilter = - -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR", - // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST", - // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] - int magFilter = - -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"] - int wrapS = - TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", - // "REPEAT"], default "REPEAT" - int wrapT = - TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", - // "REPEAT"], default "REPEAT" -//int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently not used. - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Sampler() - : minFilter(-1), - magFilter(-1), - wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), - wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) { - } - DEFAULT_METHODS(Sampler) - bool operator==(const Sampler&) const; - }; - - struct Image { - std::string name; - int width; - int height; - int component; - int bits; // bit depth per channel. 8(byte), 16 or 32. - int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually - // UBYTE(bits = 8) or USHORT(bits = 16) - std::vector image; - int bufferView; // (required if no uri) - std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", - // "image/bmp", "image/gif"] - std::string uri; // (required if no mimeType) uri is not decoded(e.g. - // whitespace may be represented as %20) - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg - // compressed for "image/jpeg" mime) This feature is good if you use custom - // image loader function. (e.g. delayed decoding of images for faster glTF - // parsing) Default parser for Image does not provide as-is loading feature at - // the moment. (You can manipulate this by providing your own LoadImageData - // function) - bool as_is; - - Image() : as_is(false) { - bufferView = -1; - width = -1; - height = -1; - component = -1; - bits = -1; - pixel_type = -1; - } - DEFAULT_METHODS(Image) - - bool operator==(const Image&) const; - }; - - struct Texture { - std::string name; - - int sampler; - int source; - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Texture() : sampler(-1), source(-1) {} - DEFAULT_METHODS(Texture) - - bool operator==(const Texture&) const; - }; - - struct TextureInfo { - int index = -1; // required. - int texCoord; // The set index of texture's TEXCOORD attribute used for - // texture coordinate mapping. - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - TextureInfo() : index(-1), texCoord(0) {} - DEFAULT_METHODS(TextureInfo) - bool operator==(const TextureInfo&) const; - }; - - struct NormalTextureInfo { - int index = -1; // required - int texCoord; // The set index of texture's TEXCOORD attribute used for - // texture coordinate mapping. - double scale; // scaledNormal = normalize(( - // * 2.0 - 1.0) * vec3(, , 1.0)) - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {} - DEFAULT_METHODS(NormalTextureInfo) - bool operator==(const NormalTextureInfo&) const; - }; - - struct OcclusionTextureInfo { - int index = -1; // required - int texCoord; // The set index of texture's TEXCOORD attribute used for - // texture coordinate mapping. - double strength; // occludedColor = lerp(color, color * , ) - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - OcclusionTextureInfo() : index(-1), texCoord(0), strength(1.0) {} - DEFAULT_METHODS(OcclusionTextureInfo) - bool operator==(const OcclusionTextureInfo&) const; - }; - - // pbrMetallicRoughness class defined in glTF 2.0 spec. - struct PbrMetallicRoughness { - std::vector baseColorFactor; // len = 4. default [1,1,1,1] - TextureInfo baseColorTexture; - double metallicFactor; // default 1 - double roughnessFactor; // default 1 - TextureInfo metallicRoughnessTexture; - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - PbrMetallicRoughness() - : baseColorFactor(std::vector{1.0, 1.0, 1.0, 1.0}), - metallicFactor(1.0), - roughnessFactor(1.0) { - } - DEFAULT_METHODS(PbrMetallicRoughness) - bool operator==(const PbrMetallicRoughness&) const; - }; - - // Each extension should be stored in a ParameterMap. - // members not in the values could be included in the ParameterMap - // to keep a single material model - struct Material { - std::string name; - - std::vector emissiveFactor; // length 3. default [0, 0, 0] - std::string alphaMode; // default "OPAQUE" - double alphaCutoff; // default 0.5 - bool doubleSided; // default false; - - PbrMetallicRoughness pbrMetallicRoughness; - - NormalTextureInfo normalTexture; - OcclusionTextureInfo occlusionTexture; - TextureInfo emissiveTexture; - - // For backward compatibility - // TODO(syoyo): Remove `values` and `additionalValues` in the next release. - ParameterMap values; - ParameterMap additionalValues; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {} - DEFAULT_METHODS(Material) - - bool operator==(const Material&) const; - }; - - struct BufferView { - std::string name; - int buffer{ -1 }; // Required - size_t byteOffset{ 0 }; // minimum 0, default 0 - size_t byteLength{ 0 }; // required, minimum 1. 0 = invalid - size_t byteStride{ 0 }; // minimum 4, maximum 252 (multiple of 4), default 0 = - // understood to be tightly packed - int target{ 0 }; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices - // or atttribs. Could be 0 for other data - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - bool dracoDecoded{ false }; // Flag indicating this has been draco decoded - - BufferView() - : buffer(-1), - byteOffset(0), - byteLength(0), - byteStride(0), - target(0), - dracoDecoded(false) { - } - DEFAULT_METHODS(BufferView) - bool operator==(const BufferView&) const; - }; - - struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor - // are not supported - std::string name; - size_t byteOffset; - bool normalized; // optional. - int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** - size_t count; // required - int type; // (required) One of TINYGLTF_TYPE_*** .. - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - std::vector - minValues; // optional. integer value is promoted to double - std::vector - maxValues; // optional. integer value is promoted to double - - struct { - int count; - bool isSparse; - struct { - int byteOffset; - int bufferView; - int componentType; // a TINYGLTF_COMPONENT_TYPE_ value - } indices; - struct { - int bufferView; - int byteOffset; - } values; - } sparse; - - /// - /// Utility function to compute byteStride for a given bufferView object. - /// Returns -1 upon invalid glTF value or parameter configuration. - /// - int ByteStride(const BufferView& bufferViewObject) const { - if (bufferViewObject.byteStride == 0) { - // Assume data is tightly packed. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } - - int numComponents = GetNumComponentsInType(static_cast(type)); - if (numComponents <= 0) { - return -1; - } - - return componentSizeInBytes * numComponents; - } - else { - // Check if byteStride is a mulple of the size of the accessor's component - // type. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } - - if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { - return -1; - } - return static_cast(bufferViewObject.byteStride); - } - - // unreachable return 0; - } - - Accessor() - : bufferView(-1), - byteOffset(0), - normalized(false), - componentType(-1), - count(0), - type(-1) { - sparse.isSparse = false; - } - DEFAULT_METHODS(Accessor) - bool operator==(const tinygltf::Accessor&) const; - }; - - struct PerspectiveCamera { - double aspectRatio; // min > 0 - double yfov; // required. min > 0 - double zfar; // min > 0 - double znear; // required. min > 0 - - PerspectiveCamera() - : aspectRatio(0.0), - yfov(0.0), - zfar(0.0) // 0 = use infinite projecton matrix - , - znear(0.0) { - } - DEFAULT_METHODS(PerspectiveCamera) - bool operator==(const PerspectiveCamera&) const; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - struct OrthographicCamera { - double xmag; // required. must not be zero. - double ymag; // required. must not be zero. - double zfar; // required. `zfar` must be greater than `znear`. - double znear; // required - - OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} - DEFAULT_METHODS(OrthographicCamera) - bool operator==(const OrthographicCamera&) const; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - struct Camera { - std::string type; // required. "perspective" or "orthographic" - std::string name; - - PerspectiveCamera perspective; - OrthographicCamera orthographic; - - Camera() {} - DEFAULT_METHODS(Camera) - bool operator==(const Camera&) const; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - struct Primitive { - std::map attributes; // (required) A dictionary object of - // integer, where each integer - // is the index of the accessor - // containing an attribute. - int material; // The index of the material to apply to this primitive - // when rendering. - int indices; // The index of the accessor that contains the indices. - int mode; // one of TINYGLTF_MODE_*** - std::vector > targets; // array of morph targets, - // where each target is a dict with attribues in ["POSITION, "NORMAL", - // "TANGENT"] pointing - // to their corresponding accessors - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Primitive() { - material = -1; - indices = -1; - mode = -1; - } - DEFAULT_METHODS(Primitive) - bool operator==(const Primitive&) const; - }; - - struct Mesh { - std::string name; - std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Mesh() = default; - DEFAULT_METHODS(Mesh) - bool operator==(const Mesh&) const; - }; - - class Node { - public: - Node() : camera(-1), skin(-1), mesh(-1) {} - - DEFAULT_METHODS(Node) - - bool operator==(const Node&) const; - - int camera; // the index of the camera referenced by this node - - std::string name; - int skin; - int mesh; - std::vector children; - std::vector rotation; // length must be 0 or 4 - std::vector scale; // length must be 0 or 3 - std::vector translation; // length must be 0 or 3 - std::vector matrix; // length must be 0 or 16 - std::vector weights; // The weights of the instantiated Morph Target - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - struct Buffer { - std::string name; - std::vector data; - std::string - uri; // considered as required here but not in the spec (need to clarify) - // uri is not decoded(e.g. whitespace may be represented as %20) - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Buffer() = default; - DEFAULT_METHODS(Buffer) - bool operator==(const Buffer&) const; - }; - - struct Asset { - std::string version = "2.0"; // required - std::string generator; - std::string minVersion; - std::string copyright; - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Asset() = default; - DEFAULT_METHODS(Asset) - bool operator==(const Asset&) const; - }; - - struct Scene { - std::string name; - std::vector nodes; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - - Scene() = default; - DEFAULT_METHODS(Scene) - bool operator==(const Scene&) const; - }; - - struct SpotLight { - double innerConeAngle; - double outerConeAngle; - - SpotLight() : innerConeAngle(0.0), outerConeAngle(0.7853981634) {} - DEFAULT_METHODS(SpotLight) - bool operator==(const SpotLight&) const; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - struct Light { - std::string name; - std::vector color; - double intensity{ 1.0 }; - std::string type; - double range{ 0.0 }; // 0.0 = inifinite - SpotLight spot; - - Light() : intensity(1.0), range(0.0) {} - DEFAULT_METHODS(Light) - - bool operator==(const Light&) const; - - ExtensionMap extensions; - Value extras; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - class Model { - public: - Model() = default; - DEFAULT_METHODS(Model) - - bool operator==(const Model&) const; - - std::vector accessors; - std::vector animations; - std::vector buffers; - std::vector bufferViews; - std::vector materials; - std::vector meshes; - std::vector nodes; - std::vector textures; - std::vector images; - std::vector skins; - std::vector samplers; - std::vector cameras; - std::vector scenes; - std::vector lights; - - int defaultScene = -1; - std::vector extensionsUsed; - std::vector extensionsRequired; - - Asset asset; - - Value extras; - ExtensionMap extensions; - - // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. - std::string extras_json_string; - std::string extensions_json_string; - }; - - enum SectionCheck { - NO_REQUIRE = 0x00, - REQUIRE_VERSION = 0x01, - REQUIRE_SCENE = 0x02, - REQUIRE_SCENES = 0x04, - REQUIRE_NODES = 0x08, - REQUIRE_ACCESSORS = 0x10, - REQUIRE_BUFFERS = 0x20, - REQUIRE_BUFFER_VIEWS = 0x40, - REQUIRE_ALL = 0x7f - }; - - /// - /// LoadImageDataFunction type. Signature for custom image loading callbacks. - /// - typedef bool (*LoadImageDataFunction)(Image*, const int, std::string*, - std::string*, int, int, - const unsigned char*, int, - void* user_pointer); - - /// - /// WriteImageDataFunction type. Signature for custom image writing callbacks. - /// - typedef bool (*WriteImageDataFunction)(const std::string*, const std::string*, - Image*, bool, void*); +typedef std::map ParameterMap; +typedef std::map ExtensionMap; + +struct AnimationChannel { + int sampler{-1}; // required + int target_node{-1}; // optional index of the node to target (alternative + // target should be provided by extension) + std::string target_path; // required with standard values of ["translation", + // "rotation", "scale", "weights"] + Value extras; + ExtensionMap extensions; + Value target_extras; + ExtensionMap target_extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + std::string target_extras_json_string; + std::string target_extensions_json_string; + + AnimationChannel() = default; + DEFAULT_METHODS(AnimationChannel) + bool operator==(const AnimationChannel &) const; +}; + +struct AnimationSampler { + int input{-1}; // required + int output{-1}; // required + std::string interpolation; // "LINEAR", "STEP","CUBICSPLINE" or user defined + // string. default "LINEAR" + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + AnimationSampler() : interpolation("LINEAR") {} + DEFAULT_METHODS(AnimationSampler) + bool operator==(const AnimationSampler &) const; +}; + +struct Animation { + std::string name; + std::vector channels; + std::vector samplers; + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Animation() = default; + DEFAULT_METHODS(Animation) + bool operator==(const Animation &) const; +}; + +struct Skin { + std::string name; + int inverseBindMatrices{-1}; // required here but not in the spec + int skeleton{-1}; // The index of the node used as a skeleton root + std::vector joints; // Indices of skeleton nodes + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Skin() = default; + DEFAULT_METHODS(Skin) + bool operator==(const Skin &) const; +}; + +struct Sampler { + std::string name; + // glTF 2.0 spec does not define default value for `minFilter` and + // `magFilter`. Set -1 in TinyGLTF(issue #186) + int minFilter = + -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR", + // "NEAREST_MIPMAP_NEAREST", "LINEAR_MIPMAP_NEAREST", + // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] + int magFilter = + -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"] + int wrapS = + TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", + // "REPEAT"], default "REPEAT" + int wrapT = + TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", + // "REPEAT"], default "REPEAT" + // int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently + // not used. + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Sampler() = default; + DEFAULT_METHODS(Sampler) + bool operator==(const Sampler &) const; +}; + +struct Image { + std::string name; + int width{-1}; + int height{-1}; + int component{-1}; + int bits{-1}; // bit depth per channel. 8(byte), 16 or 32. + int pixel_type{-1}; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually + // UBYTE(bits = 8) or USHORT(bits = 16) + std::vector image; + int bufferView{-1}; // (required if no uri) + std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", + // "image/bmp", "image/gif"] + std::string uri; // (required if no mimeType) uri is not decoded(e.g. + // whitespace may be represented as %20) + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg + // compressed for "image/jpeg" mime) This feature is good if you use custom + // image loader function. (e.g. delayed decoding of images for faster glTF + // parsing). + bool as_is{false}; + + Image() = default; + DEFAULT_METHODS(Image) + + bool operator==(const Image &) const; +}; + +struct Texture { + std::string name; + + int sampler{-1}; + int source{-1}; + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Texture() = default; + DEFAULT_METHODS(Texture) + + bool operator==(const Texture &) const; +}; + +struct TextureInfo { + int index{-1}; // required. + int texCoord{0}; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + TextureInfo() = default; + DEFAULT_METHODS(TextureInfo) + bool operator==(const TextureInfo &) const; +}; + +struct NormalTextureInfo { + int index{-1}; // required + int texCoord{0}; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + double scale{ + 1.0}; // scaledNormal = normalize(( + // * 2.0 - 1.0) * vec3(, , 1.0)) + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + NormalTextureInfo() = default; + DEFAULT_METHODS(NormalTextureInfo) + bool operator==(const NormalTextureInfo &) const; +}; + +struct OcclusionTextureInfo { + int index{-1}; // required + int texCoord{0}; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + double strength{1.0}; // occludedColor = lerp(color, color * , ) + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + OcclusionTextureInfo() = default; + DEFAULT_METHODS(OcclusionTextureInfo) + bool operator==(const OcclusionTextureInfo &) const; +}; + +// pbrMetallicRoughness class defined in glTF 2.0 spec. +struct PbrMetallicRoughness { + std::vector baseColorFactor{1.0, 1.0, 1.0, 1.0}; // len = 4. default [1,1,1,1] + TextureInfo baseColorTexture; + double metallicFactor{1.0}; // default 1 + double roughnessFactor{1.0}; // default 1 + TextureInfo metallicRoughnessTexture; + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + PbrMetallicRoughness() = default; + DEFAULT_METHODS(PbrMetallicRoughness) + + bool operator==(const PbrMetallicRoughness &) const; +}; + +// Each extension should be stored in a ParameterMap. +// members not in the values could be included in the ParameterMap +// to keep a single material model +struct Material { + std::string name; + + std::vector emissiveFactor{0.0, 0.0, 0.0}; // length 3. default [0, 0, 0] + std::string alphaMode{"OPAQUE"}; // default "OPAQUE" + double alphaCutoff{0.5}; // default 0.5 + bool doubleSided{false}; // default false + std::vector lods; // level of detail materials (MSFT_lod) + + PbrMetallicRoughness pbrMetallicRoughness; + + NormalTextureInfo normalTexture; + OcclusionTextureInfo occlusionTexture; + TextureInfo emissiveTexture; + + // For backward compatibility + // TODO(syoyo): Remove `values` and `additionalValues` in the next release. + ParameterMap values; + ParameterMap additionalValues; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Material() = default; + DEFAULT_METHODS(Material) + + bool operator==(const Material &) const; +}; + +struct BufferView { + std::string name; + int buffer{-1}; // Required + size_t byteOffset{0}; // minimum 0, default 0 + size_t byteLength{0}; // required, minimum 1. 0 = invalid + size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 = + // understood to be tightly packed + int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices + // or attribs. Could be 0 for other data + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + bool dracoDecoded{false}; // Flag indicating this has been draco decoded + + BufferView() = default; + DEFAULT_METHODS(BufferView) + bool operator==(const BufferView &) const; +}; + +struct Accessor { + int bufferView{-1}; // optional in spec but required here since sparse + // accessor are not supported + std::string name; + size_t byteOffset{0}; + bool normalized{false}; // optional. + int componentType{-1}; // (required) One of TINYGLTF_COMPONENT_TYPE_*** + size_t count{0}; // required + int type{-1}; // (required) One of TINYGLTF_TYPE_*** .. + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + std::vector + minValues; // optional. integer value is promoted to double + std::vector + maxValues; // optional. integer value is promoted to double + + struct Sparse { + int count{0}; + bool isSparse{false}; + struct { + size_t byteOffset{0}; + int bufferView{-1}; + int componentType{-1}; // a TINYGLTF_COMPONENT_TYPE_ value + Value extras; + ExtensionMap extensions; + std::string extras_json_string; + std::string extensions_json_string; + } indices; + struct { + int bufferView{-1}; + size_t byteOffset{0}; + Value extras; + ExtensionMap extensions; + std::string extras_json_string; + std::string extensions_json_string; + } values; + Value extras; + ExtensionMap extensions; + std::string extras_json_string; + std::string extensions_json_string; + }; + + Sparse sparse; + + /// + /// Utility function to compute byteStride for a given bufferView object. + /// Returns -1 upon invalid glTF value or parameter configuration. + /// + int ByteStride(const BufferView &bufferViewObject) const { + if (bufferViewObject.byteStride == 0) { + // Assume data is tightly packed. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + int numComponents = GetNumComponentsInType(static_cast(type)); + if (numComponents <= 0) { + return -1; + } + + return componentSizeInBytes * numComponents; + } else { + // Check if byteStride is a multiple of the size of the accessor's + // component type. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { + return -1; + } + return static_cast(bufferViewObject.byteStride); + } + + // unreachable return 0; + } + + Accessor() = default; + DEFAULT_METHODS(Accessor) + bool operator==(const tinygltf::Accessor &) const; +}; + +struct PerspectiveCamera { + double aspectRatio{0.0}; // min > 0 + double yfov{0.0}; // required. min > 0 + double zfar{0.0}; // min > 0 + double znear{0.0}; // required. min > 0 + + PerspectiveCamera() = default; + DEFAULT_METHODS(PerspectiveCamera) + bool operator==(const PerspectiveCamera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct OrthographicCamera { + double xmag{0.0}; // required. must not be zero. + double ymag{0.0}; // required. must not be zero. + double zfar{0.0}; // required. `zfar` must be greater than `znear`. + double znear{0.0}; // required + + OrthographicCamera() = default; + DEFAULT_METHODS(OrthographicCamera) + bool operator==(const OrthographicCamera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Camera { + std::string type; // required. "perspective" or "orthographic" + std::string name; + + PerspectiveCamera perspective; + OrthographicCamera orthographic; + + Camera() = default; + DEFAULT_METHODS(Camera) + bool operator==(const Camera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Primitive { + std::map attributes; // (required) A dictionary object of + // integer, where each integer + // is the index of the accessor + // containing an attribute. + int material{-1}; // The index of the material to apply to this primitive + // when rendering. + int indices{-1}; // The index of the accessor that contains the indices. + int mode{-1}; // one of TINYGLTF_MODE_*** + std::vector > targets; // array of morph targets, + // where each target is a dict with attributes in ["POSITION, "NORMAL", + // "TANGENT"] pointing + // to their corresponding accessors + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Primitive() = default; + DEFAULT_METHODS(Primitive) + bool operator==(const Primitive &) const; +}; + +struct Mesh { + std::string name; + std::vector primitives; + std::vector weights; // weights to be applied to the Morph Targets + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Mesh() = default; + DEFAULT_METHODS(Mesh) + bool operator==(const Mesh &) const; +}; + +class Node { + public: + Node() = default; + + DEFAULT_METHODS(Node) + + bool operator==(const Node &) const; + + int camera{-1}; // the index of the camera referenced by this node + + std::string name; + int skin{-1}; + int mesh{-1}; + int light{-1}; // light source index (KHR_lights_punctual) + int emitter{-1}; // audio emitter index (KHR_audio) + std::vector lods; // level of detail nodes (MSFT_lod) + std::vector children; + std::vector rotation; // length must be 0 or 4 + std::vector scale; // length must be 0 or 3 + std::vector translation; // length must be 0 or 3 + std::vector matrix; // length must be 0 or 16 + std::vector weights; // The weights of the instantiated Morph Target + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Buffer { + std::string name; + std::vector data; + std::string + uri; // considered as required here but not in the spec (need to clarify) + // uri is not decoded(e.g. whitespace may be represented as %20) + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Buffer() = default; + DEFAULT_METHODS(Buffer) + bool operator==(const Buffer &) const; +}; + +struct Asset { + std::string version = "2.0"; // required + std::string generator; + std::string minVersion; + std::string copyright; + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Asset() = default; + DEFAULT_METHODS(Asset) + bool operator==(const Asset &) const; +}; + +struct Scene { + std::string name; + std::vector nodes; + std::vector audioEmitters; // KHR_audio global emitters + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Scene() = default; + DEFAULT_METHODS(Scene) + bool operator==(const Scene &) const; +}; + +struct SpotLight { + double innerConeAngle{0.0}; + double outerConeAngle{0.7853981634}; + + SpotLight() = default; + DEFAULT_METHODS(SpotLight) + bool operator==(const SpotLight &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Light { + std::string name; + std::vector color; + double intensity{1.0}; + std::string type; + double range{0.0}; // 0.0 = infinite + SpotLight spot; + + Light() = default; + DEFAULT_METHODS(Light) + + bool operator==(const Light &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct PositionalEmitter { + double coneInnerAngle{6.283185307179586}; + double coneOuterAngle{6.283185307179586}; + double coneOuterGain{0.0}; + double maxDistance{100.0}; + double refDistance{1.0}; + double rolloffFactor{1.0}; + + PositionalEmitter() = default; + DEFAULT_METHODS(PositionalEmitter) + bool operator==(const PositionalEmitter &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct AudioEmitter { + std::string name; + double gain{1.0}; + bool loop{false}; + bool playing{false}; + std::string + type; // positional - Positional audio emitters. Using sound cones, the + // orientation is +Z having the same front side for a glTF asset. + // global - Global audio emitters are not affected by the position + // of audio listeners. coneInnerAngle, coneOuterAngle, + // coneOuterGain, distanceModel, maxDistance, refDistance, and + // rolloffFactor should all be ignored when set. + std::string + distanceModel; // linear - A linear distance model calculating the + // gain induced by the distance according to: 1.0 + // - rolloffFactor * (distance - refDistance) / + // (maxDistance - refDistance) + // inverse - (default) An inverse distance model + // calculating the gain induced by the distance according + // to: refDistance / (refDistance + rolloffFactor * + // (Math.max(distance, refDistance) - refDistance)) + // exponential - An exponential distance model calculating + // the gain induced by the distance according to: + // pow((Math.max(distance, refDistance) / refDistance, + // -rolloffFactor)) + PositionalEmitter positional; + int source{-1}; + + AudioEmitter() : type("global"), distanceModel("inverse") {} + DEFAULT_METHODS(AudioEmitter) + + bool operator==(const AudioEmitter &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct AudioSource { + std::string name; + std::string uri; + int bufferView{-1}; // (required if no uri) + std::string + mimeType; // (required if no uri) The audio's MIME type. Required if + // bufferView is defined. Unless specified by another + // extension, the only supported mimeType is audio/mpeg. + + AudioSource() = default; + DEFAULT_METHODS(AudioSource) + + bool operator==(const AudioSource &) const; + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +class Model { + public: + Model() = default; + DEFAULT_METHODS(Model) + + bool operator==(const Model &) const; + + std::vector accessors; + std::vector animations; + std::vector buffers; + std::vector bufferViews; + std::vector materials; + std::vector meshes; + std::vector nodes; + std::vector textures; + std::vector images; + std::vector skins; + std::vector samplers; + std::vector cameras; + std::vector scenes; + std::vector lights; + std::vector audioEmitters; + std::vector audioSources; + + int defaultScene{-1}; + std::vector extensionsUsed; + std::vector extensionsRequired; + + Asset asset; + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +enum SectionCheck { + NO_REQUIRE = 0x00, + REQUIRE_VERSION = 0x01, + REQUIRE_SCENE = 0x02, + REQUIRE_SCENES = 0x04, + REQUIRE_NODES = 0x08, + REQUIRE_ACCESSORS = 0x10, + REQUIRE_BUFFERS = 0x20, + REQUIRE_BUFFER_VIEWS = 0x40, + REQUIRE_ALL = 0x7f +}; + +/// +/// URIEncodeFunction type. Signature for custom URI encoding of external +/// resources such as .bin and image files. Used by tinygltf to re-encode the +/// final location of saved files. object_type may be used to encode buffer and +/// image URIs differently, for example. See +/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#uris +/// +using URIEncodeFunction = std::function; + +/// +/// URIDecodeFunction type. Signature for custom URI decoding of external +/// resources such as .bin and image files. Used by tinygltf when computing +/// filenames to write resources. +/// +using URIDecodeFunction = + std::function; + +// Declaration of default uri decode function +bool URIDecode(const std::string &in_uri, std::string *out_uri, + void *user_data); + +/// +/// A structure containing URI callbacks and a pointer to their user data. +/// +struct URICallbacks { + URIEncodeFunction encode; // Optional encode method + URIDecodeFunction decode; // Required decode method + + void *user_data; // An argument that is passed to all uri callbacks +}; + +/// +/// FileExistsFunction type. Signature for custom filesystem callbacks. +/// +using FileExistsFunction = std::function; + +/// +/// ExpandFilePathFunction type. Signature for custom filesystem callbacks. +/// +using ExpandFilePathFunction = + std::function; + +/// +/// ReadWholeFileFunction type. Signature for custom filesystem callbacks. +/// +using ReadWholeFileFunction = std::function *, std::string *, const std::string &, void *)>; + +/// +/// WriteWholeFileFunction type. Signature for custom filesystem callbacks. +/// +using WriteWholeFileFunction = + std::function &, void *)>; + +/// +/// GetFileSizeFunction type. Signature for custom filesystem callbacks. +/// +using GetFileSizeFunction = + std::function; + +/// +/// A structure containing all required filesystem callbacks and a pointer to +/// their user data. +/// +struct FsCallbacks { + FileExistsFunction FileExists; + ExpandFilePathFunction ExpandFilePath; + ReadWholeFileFunction ReadWholeFile; + WriteWholeFileFunction WriteWholeFile; + GetFileSizeFunction GetFileSizeInBytes; // To avoid GetFileSize Win32 API, + // add `InBytes` suffix. + + void *user_data; // An argument that is passed to all fs callbacks +}; + +#ifndef TINYGLTF_NO_FS +// Declaration of default filesystem callbacks + +bool FileExists(const std::string &abs_filename, void *); + +/// +/// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to +/// `C:\\Users\\tinygltf\\AppData`) +/// +/// @param[in] filepath File path string. Assume UTF-8 +/// @param[in] userdata User data. Set to `nullptr` if you don't need it. +/// +std::string ExpandFilePath(const std::string &filepath, void *userdata); + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *); + +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *); + +bool GetFileSizeInBytes(size_t *filesize_out, std::string *err, + const std::string &filepath, void *); +#endif + +/// +/// LoadImageDataFunction type. Signature for custom image loading callbacks. +/// +using LoadImageDataFunction = std::function; + +/// +/// WriteImageDataFunction type. Signature for custom image writing callbacks. +/// The out_uri parameter becomes the URI written to the gltf and may reference +/// a file or contain a data URI. +/// +using WriteImageDataFunction = std::function; #ifndef TINYGLTF_NO_STB_IMAGE - // Declaration of default image loader callback - bool LoadImageData(Image* image, const int image_idx, std::string* err, - std::string* warn, int req_width, int req_height, - const unsigned char* bytes, int size, void*); +// Declaration of default image loader callback +bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *); #endif #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - // Declaration of default image writer callback - bool WriteImageData(const std::string* basepath, const std::string* filename, - Image* image, bool embedImages, void*); +// Declaration of default image writer callback +bool WriteImageData(const std::string *basepath, const std::string *filename, + const Image *image, bool embedImages, + const FsCallbacks* fs_cb, const URICallbacks *uri_cb, + std::string *out_uri, void *); #endif - /// - /// FilExistsFunction type. Signature for custom filesystem callbacks. - /// - typedef bool (*FileExistsFunction)(const std::string& abs_filename, void*); - - /// - /// ExpandFilePathFunction type. Signature for custom filesystem callbacks. - /// - typedef std::string(*ExpandFilePathFunction)(const std::string&, void*); - - /// - /// ReadWholeFileFunction type. Signature for custom filesystem callbacks. - /// - typedef bool (*ReadWholeFileFunction)(std::vector*, - std::string*, const std::string&, - void*); - - /// - /// WriteWholeFileFunction type. Signature for custom filesystem callbacks. - /// - typedef bool (*WriteWholeFileFunction)(std::string*, const std::string&, - const std::vector&, - void*); - - /// - /// A structure containing all required filesystem callbacks and a pointer to - /// their user data. - /// - struct FsCallbacks { - FileExistsFunction FileExists; - ExpandFilePathFunction ExpandFilePath; - ReadWholeFileFunction ReadWholeFile; - WriteWholeFileFunction WriteWholeFile; - - void* user_data; // An argument that is passed to all fs callbacks - }; - -#ifndef TINYGLTF_NO_FS - // Declaration of default filesystem callbacks - - bool FileExists(const std::string& abs_filename, void*); - - /// - /// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to - /// `C:\\Users\\tinygltf\\AppData`) - /// - /// @param[in] filepath File path string. Assume UTF-8 - /// @param[in] userdata User data. Set to `nullptr` if you don't need it. - /// - std::string ExpandFilePath(const std::string& filepath, void* userdata); - - bool ReadWholeFile(std::vector* out, std::string* err, - const std::string& filepath, void*); - - bool WriteWholeFile(std::string* err, const std::string& filepath, - const std::vector& contents, void*); -#endif - - /// - /// glTF Parser/Serialier context. - /// - class TinyGLTF { - public: +/// +/// glTF Parser/Serializer context. +/// +class TinyGLTF { + public: #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++98-compat" #endif - TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} + TinyGLTF() = default; #ifdef __clang__ #pragma clang diagnostic pop #endif - ~TinyGLTF() {} + ~TinyGLTF() = default; - /// - /// Loads glTF ASCII asset from a file. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromFile(Model* model, std::string* err, std::string* warn, - const std::string& filename, - unsigned int check_sections = REQUIRE_VERSION); + /// + /// Loads glTF ASCII asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF ASCII asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromString(Model* model, std::string* err, std::string* warn, - const char* str, const unsigned int length, - const std::string& base_dir, - unsigned int check_sections = REQUIRE_VERSION); + /// + /// Loads glTF ASCII asset from string(memory). + /// `length` = strlen(str); + /// `base_dir` is a search path of glTF asset(e.g. images). Path Must be an + /// expanded path (e.g. no tilde(`~`), no environment variables). Set warning + /// message to `warn` for example it fails to load asserts. Returns false and + /// set error string to `err` if there's an error. + /// + bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF binary asset from a file. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromFile(Model* model, std::string* err, std::string* warn, - const std::string& filename, - unsigned int check_sections = REQUIRE_VERSION); + /// + /// Loads glTF binary asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF binary asset from memory. - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromMemory(Model* model, std::string* err, std::string* warn, - const unsigned char* bytes, - const unsigned int length, - const std::string& base_dir = "", - unsigned int check_sections = REQUIRE_VERSION); + /// + /// Loads glTF binary asset from memory. + /// `length` = strlen(str); + /// `base_dir` is a search path of glTF asset(e.g. images). Path Must be an + /// expanded path (e.g. no tilde(`~`), no environment variables). + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, + const unsigned char *bytes, + const unsigned int length, + const std::string &base_dir = "", + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Write glTF to stream, buffers and images will be embeded - /// - bool WriteGltfSceneToStream(Model* model, std::ostream& stream, - bool prettyPrint, bool writeBinary); + /// + /// Write glTF to stream, buffers and images will be embedded + /// + bool WriteGltfSceneToStream(const Model *model, std::ostream &stream, + bool prettyPrint, bool writeBinary); - /// - /// Write glTF to file. - /// - bool WriteGltfSceneToFile(Model* model, const std::string& filename, - bool embedImages, bool embedBuffers, - bool prettyPrint, bool writeBinary); + /// + /// Write glTF to file. + /// + bool WriteGltfSceneToFile(const Model *model, const std::string &filename, + bool embedImages, bool embedBuffers, + bool prettyPrint, bool writeBinary); - /// - /// Set callback to use for loading image data - /// - void SetImageLoader(LoadImageDataFunction LoadImageData, void* user_data); + /// + /// Sets the parsing strictness. + /// + void SetParseStrictness(ParseStrictness strictness); - /// - /// Unset(remove) callback of loading image data - /// - void RemoveImageLoader(); + /// + /// Set callback to use for loading image data. Passing the nullptr is akin to + /// calling RemoveImageLoader(). + /// + void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); - /// - /// Set callback to use for writing image data - /// - void SetImageWriter(WriteImageDataFunction WriteImageData, void* user_data); + /// + /// Unset(remove) callback of loading image data + /// + void RemoveImageLoader(); - /// - /// Set callbacks to use for filesystem (fs) access and their user data - /// - void SetFsCallbacks(FsCallbacks callbacks); + /// + /// Set callback to use for writing image data + /// + void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); - /// - /// Set serializing default values(default = false). - /// When true, default values are force serialized to .glTF. - /// This may be helpfull if you want to serialize a full description of glTF - /// data. - /// - /// TODO(LTE): Supply parsing option as function arguments to - /// `LoadASCIIFromFile()` and others, not by a class method - /// - void SetSerializeDefaultValues(const bool enabled) { - serialize_default_values_ = enabled; - } + /// + /// Set callbacks to use for URI encoding and decoding and their user data. + /// Returns false if there is an error with the callbacks. If err is not + /// nullptr, explanation will be written there. + /// + bool SetURICallbacks(URICallbacks callbacks, std::string* err = nullptr); - bool GetSerializeDefaultValues() const { return serialize_default_values_; } + /// + /// Set callbacks to use for filesystem (fs) access and their user data. + /// Returns false if there is an error with the callbacks. If err is not + /// nullptr, explanation will be written there. + /// + bool SetFsCallbacks(FsCallbacks callbacks, std::string* err = nullptr); - /// - /// Store original JSON string for `extras` and `extensions`. - /// This feature will be useful when the user want to reconstruct custom data - /// structure from JSON string. - /// - void SetStoreOriginalJSONForExtrasAndExtensions(const bool enabled) { - store_original_json_for_extras_and_extensions_ = enabled; - } + /// + /// Set serializing default values(default = false). + /// When true, default values are force serialized to .glTF. + /// This may be helpful if you want to serialize a full description of glTF + /// data. + /// + /// TODO(LTE): Supply parsing option as function arguments to + /// `LoadASCIIFromFile()` and others, not by a class method + /// + void SetSerializeDefaultValues(const bool enabled) { + serialize_default_values_ = enabled; + } - bool GetStoreOriginalJSONForExtrasAndExtensions() const { - return store_original_json_for_extras_and_extensions_; - } + bool GetSerializeDefaultValues() const { return serialize_default_values_; } - /// - /// Specify whether preserve image channales when loading images or not. - /// (Not effective when the user suppy their own LoadImageData callbacks) - /// - void SetPreserveImageChannels(bool onoff) { - preserve_image_channels_ = onoff; - } + /// + /// Store original JSON string for `extras` and `extensions`. + /// This feature will be useful when the user want to reconstruct custom data + /// structure from JSON string. + /// + void SetStoreOriginalJSONForExtrasAndExtensions(const bool enabled) { + store_original_json_for_extras_and_extensions_ = enabled; + } - bool GetPreserveImageChannels() const { return preserve_image_channels_; } + bool GetStoreOriginalJSONForExtrasAndExtensions() const { + return store_original_json_for_extras_and_extensions_; + } - private: - /// - /// Loads glTF asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadFromString(Model* model, std::string* err, std::string* warn, - const char* str, const unsigned int length, - const std::string& base_dir, unsigned int check_sections); + /// + /// Specify whether preserve image channels when loading images or not. + /// (Not effective when the user supplies their own LoadImageData callbacks) + /// + void SetPreserveImageChannels(bool onoff) { + preserve_image_channels_ = onoff; + } - const unsigned char* bin_data_ = nullptr; - size_t bin_size_ = 0; - bool is_binary_ = false; + bool GetPreserveImageChannels() const { return preserve_image_channels_; } - bool serialize_default_values_ = false; ///< Serialize default values? + /// + /// Specifiy whether image data is decoded/decompressed during load, or left as is + /// + void SetImagesAsIs(bool onoff) { + images_as_is_ = onoff; + } - bool store_original_json_for_extras_and_extensions_ = false; + bool GetImagesAsIs() const { return images_as_is_; } - bool preserve_image_channels_ = false; /// Default false(expand channels to - /// RGBA) for backward compatibility. + /// + /// Set maximum allowed external file size in bytes. + /// Default: 2GB + /// Only effective for built-in ReadWholeFileFunction FS function. + /// + void SetMaxExternalFileSize(size_t max_bytes) { + max_external_file_size_ = max_bytes; + } -// Warning & error messages - std::string warn_; - std::string err_; + size_t GetMaxExternalFileSize() const { return max_external_file_size_; } - FsCallbacks fs = { - #ifndef TINYGLTF_NO_FS - & tinygltf::FileExists, &tinygltf::ExpandFilePath, - &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, + private: + /// + /// Loads glTF asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, unsigned int check_sections); - nullptr // Fs callback user data - #else - nullptr, nullptr, nullptr, nullptr, + const unsigned char *bin_data_ = nullptr; + size_t bin_size_ = 0; + bool is_binary_ = false; - nullptr // Fs callback user data - #endif - }; + ParseStrictness strictness_ = ParseStrictness::Strict; - LoadImageDataFunction LoadImageData = + bool serialize_default_values_ = false; ///< Serialize default values? + + bool store_original_json_for_extras_and_extensions_ = false; + + bool preserve_image_channels_ = false; /// Default false(expand channels to + /// RGBA) for backward compatibility. + + bool images_as_is_ = false; /// Default false (decode/decompress images) + + size_t max_external_file_size_{ + size_t((std::numeric_limits::max)())}; // Default 2GB + + // Warning & error messages + std::string warn_; + std::string err_; + + FsCallbacks fs = { +#ifndef TINYGLTF_NO_FS + &tinygltf::FileExists, + &tinygltf::ExpandFilePath, + &tinygltf::ReadWholeFile, + &tinygltf::WriteWholeFile, + &tinygltf::GetFileSizeInBytes, + + nullptr // Fs callback user data +#else + nullptr, nullptr, nullptr, nullptr, nullptr, + + nullptr // Fs callback user data +#endif + }; + + URICallbacks uri_cb = { + // Use paths as-is by default. This will use JSON string escaping. + nullptr, + // Decode all URIs before using them as paths as the application may have + // percent encoded them. + &tinygltf::URIDecode, + // URI callback user data + nullptr}; + + LoadImageDataFunction LoadImageData = #ifndef TINYGLTF_NO_STB_IMAGE - & tinygltf::LoadImageData; + &tinygltf::LoadImageData; #else - nullptr; + nullptr; #endif - void* load_image_user_data_{ nullptr }; - bool user_image_loader_{ false }; + void *load_image_user_data_{nullptr}; + bool user_image_loader_{false}; - WriteImageDataFunction WriteImageData = + WriteImageDataFunction WriteImageData = #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - & tinygltf::WriteImageData; + &tinygltf::WriteImageData; #else - nullptr; + nullptr; #endif - void* write_image_user_data_{ nullptr }; - }; + void *write_image_user_data_{nullptr}; +}; #ifdef __clang__ #pragma clang diagnostic pop // -Wpadded @@ -1496,8 +1640,10 @@ namespace tinygltf { #if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__) #include -//#include +// #include #ifndef TINYGLTF_NO_FS +#include // for is_directory check + #include #include #endif @@ -1558,7 +1704,7 @@ namespace tinygltf { #endif #endif -// Disable GCC warnigs +// Disable GCC warnings #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" @@ -1607,7 +1753,7 @@ namespace tinygltf { // issue 143. // Define NOMINMAX to avoid min/max defines, -// but undef it after included windows.h +// but undef it after included Windows.h #ifndef NOMINMAX #define TINYGLTF_INTERNAL_NOMINMAX #define NOMINMAX @@ -1617,7 +1763,11 @@ namespace tinygltf { #define WIN32_LEAN_AND_MEAN #define TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN #endif -#include // include API for expanding a file path +#ifndef __MINGW32__ +#include // include API for expanding a file path +#else +#include +#endif #ifdef TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN @@ -1636,7 +1786,7 @@ namespace tinygltf { #endif #elif !defined(__ANDROID__) && !defined(__OpenBSD__) -#include +// #include #endif #if defined(__sparcv9) || defined(__powerpc__) @@ -1647,62 +1797,65 @@ namespace tinygltf { #endif #endif -namespace { +namespace tinygltf { +namespace detail { #ifdef TINYGLTF_USE_RAPIDJSON #ifdef TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR - // This uses the RapidJSON CRTAllocator. It is thread safe and multiple - // documents may be active at once. - using json = - rapidjson::GenericValue, rapidjson::CrtAllocator>; - using json_const_iterator = json::ConstMemberIterator; - using json_const_array_iterator = json const*; - using JsonDocument = - rapidjson::GenericDocument, rapidjson::CrtAllocator>; - rapidjson::CrtAllocator s_CrtAllocator; // stateless and thread safe - rapidjson::CrtAllocator& GetAllocator() { return s_CrtAllocator; } +// This uses the RapidJSON CRTAllocator. It is thread safe and multiple +// documents may be active at once. +using json = + rapidjson::GenericValue, rapidjson::CrtAllocator>; +using json_iterator = json::MemberIterator; +using json_const_iterator = json::ConstMemberIterator; +using json_const_array_iterator = json const *; +using JsonDocument = + rapidjson::GenericDocument, rapidjson::CrtAllocator>; +rapidjson::CrtAllocator s_CrtAllocator; // stateless and thread safe +rapidjson::CrtAllocator &GetAllocator() { return s_CrtAllocator; } #else - // This uses the default RapidJSON MemoryPoolAllocator. It is very fast, but - // not thread safe. Only a single JsonDocument may be active at any one time, - // meaning only a single gltf load/save can be active any one time. - using json = rapidjson::Value; - using json_const_iterator = json::ConstMemberIterator; - using json_const_array_iterator = json const*; - rapidjson::Document* s_pActiveDocument = nullptr; - rapidjson::Document::AllocatorType& GetAllocator() { - assert(s_pActiveDocument); // Root json node must be JsonDocument type - return s_pActiveDocument->GetAllocator(); - } +// This uses the default RapidJSON MemoryPoolAllocator. It is very fast, but +// not thread safe. Only a single JsonDocument may be active at any one time, +// meaning only a single gltf load/save can be active any one time. +using json = rapidjson::Value; +using json_iterator = json::MemberIterator; +using json_const_iterator = json::ConstMemberIterator; +using json_const_array_iterator = json const *; +rapidjson::Document *s_pActiveDocument = nullptr; +rapidjson::Document::AllocatorType &GetAllocator() { + assert(s_pActiveDocument); // Root json node must be JsonDocument type + return s_pActiveDocument->GetAllocator(); +} #ifdef __clang__ #pragma clang diagnostic push - // Suppress JsonDocument(JsonDocument &&rhs) noexcept +// Suppress JsonDocument(JsonDocument &&rhs) noexcept #pragma clang diagnostic ignored "-Wunused-member-function" #endif - struct JsonDocument : public rapidjson::Document { - JsonDocument() { - assert(s_pActiveDocument == - nullptr); // When using default allocator, only one document can be - // active at a time, if you need multiple active at once, - // define TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR - s_pActiveDocument = this; - } - JsonDocument(const JsonDocument&) = delete; - JsonDocument(JsonDocument&& rhs) noexcept - : rapidjson::Document(std::move(rhs)) { - s_pActiveDocument = this; - rhs.isNil = true; - } - ~JsonDocument() { - if (!isNil) { - s_pActiveDocument = nullptr; - } - } +struct JsonDocument : public rapidjson::Document { + JsonDocument() { + assert(s_pActiveDocument == + nullptr); // When using default allocator, only one document can be + // active at a time, if you need multiple active at once, + // define TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR + s_pActiveDocument = this; + } + JsonDocument(const JsonDocument &) = delete; + JsonDocument(JsonDocument &&rhs) noexcept + : rapidjson::Document(std::move(rhs)) { + s_pActiveDocument = this; + rhs.isNil = true; + } + ~JsonDocument() { + if (!isNil) { + s_pActiveDocument = nullptr; + } + } - private: - bool isNil = false; - }; + private: + bool isNil = false; +}; #ifdef __clang__ #pragma clang diagnostic pop @@ -1711,22 +1864,24 @@ namespace { #endif // TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR #else - using nlohmann::json; - using json_const_iterator = json::const_iterator; - using json_const_array_iterator = json_const_iterator; - using JsonDocument = json; +using nlohmann::json; +using json_iterator = json::iterator; +using json_const_iterator = json::const_iterator; +using json_const_array_iterator = json_const_iterator; +using JsonDocument = json; #endif - void JsonParse(JsonDocument& doc, const char* str, size_t length, - bool throwExc = false) { +void JsonParse(JsonDocument &doc, const char *str, size_t length, + bool throwExc = false) { #ifdef TINYGLTF_USE_RAPIDJSON - (void)throwExc; - doc.Parse(str, length); + (void)throwExc; + doc.Parse(str, length); #else - doc = json::parse(str, str + length, nullptr, throwExc); + doc = detail::json::parse(str, str + length, nullptr, throwExc); #endif - } -} // namespace +} +} // namespace detail +} // namespace tinygltf #ifdef __APPLE__ #include "TargetConditionals.h" @@ -1739,376 +1894,407 @@ namespace { namespace tinygltf { - /// - /// Internal LoadImageDataOption struct. - /// This struct is passed through `user_pointer` in LoadImageData. - /// The struct is not passed when the user supply their own LoadImageData - /// callbacks. - /// - struct LoadImageDataOption { - // true: preserve image channels(e.g. load as RGB image if the image has RGB - // channels) default `false`(channels are expanded to RGBA for backward - // compatiblity). - bool preserve_channels{ false }; - }; +/// +/// Internal LoadImageDataOption struct. +/// This struct is passed through `user_pointer` in LoadImageData. +/// The struct is not passed when the user supply their own LoadImageData +/// callbacks. +/// +struct LoadImageDataOption { + // true: preserve image channels(e.g. load as RGB image if the image has RGB + // channels) default `false`(channels are expanded to RGBA for backward + // compatibility). + bool preserve_channels{false}; + // true: do not decode/decompress image data. + // default `false`: decode/decompress image data. + bool as_is{false}; +}; - // Equals function for Value, for recursivity - static bool Equals(const tinygltf::Value& one, const tinygltf::Value& other) { - if (one.Type() != other.Type()) return false; +// Equals function for Value, for recursivity +static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) { + if (one.Type() != other.Type()) return false; - switch (one.Type()) { - case NULL_TYPE: - return true; - case BOOL_TYPE: - return one.Get() == other.Get(); - case REAL_TYPE: - return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); - case INT_TYPE: - return one.Get() == other.Get(); - case OBJECT_TYPE: { - auto oneObj = one.Get(); - auto otherObj = other.Get(); - if (oneObj.size() != otherObj.size()) return false; - for (auto& it : oneObj) { - auto otherIt = otherObj.find(it.first); - if (otherIt == otherObj.end()) return false; + switch (one.Type()) { + case NULL_TYPE: + return true; + case BOOL_TYPE: + return one.Get() == other.Get(); + case REAL_TYPE: + return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); + case INT_TYPE: + return one.Get() == other.Get(); + case OBJECT_TYPE: { + auto oneObj = one.Get(); + auto otherObj = other.Get(); + if (oneObj.size() != otherObj.size()) return false; + for (auto &it : oneObj) { + auto otherIt = otherObj.find(it.first); + if (otherIt == otherObj.end()) return false; - if (!Equals(it.second, otherIt->second)) return false; - } - return true; - } - case ARRAY_TYPE: { - if (one.Size() != other.Size()) return false; - for (int i = 0; i < int(one.Size()); ++i) - if (!Equals(one.Get(i), other.Get(i))) return false; - return true; - } - case STRING_TYPE: - return one.Get() == other.Get(); - case BINARY_TYPE: - return one.Get >() == - other.Get >(); - default: { - // unhandled type - return false; - } - } - } + if (!Equals(it.second, otherIt->second)) return false; + } + return true; + } + case ARRAY_TYPE: { + if (one.Size() != other.Size()) return false; + for (size_t i = 0; i < one.Size(); ++i) + if (!Equals(one.Get(i), other.Get(i))) return false; + return true; + } + case STRING_TYPE: + return one.Get() == other.Get(); + case BINARY_TYPE: + return one.Get >() == + other.Get >(); + default: { + // unhandled type + return false; + } + } +} - // Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON - static bool Equals(const std::vector& one, - const std::vector& other) { - if (one.size() != other.size()) return false; - for (int i = 0; i < int(one.size()); ++i) { - if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; - } - return true; - } +// Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON +static bool Equals(const std::vector &one, + const std::vector &other) { + if (one.size() != other.size()) return false; + for (int i = 0; i < int(one.size()); ++i) { + if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; + } + return true; +} - bool Accessor::operator==(const Accessor& other) const { - return this->bufferView == other.bufferView && - this->byteOffset == other.byteOffset && - this->componentType == other.componentType && - this->count == other.count && this->extensions == other.extensions && - this->extras == other.extras && - Equals(this->maxValues, other.maxValues) && - Equals(this->minValues, other.minValues) && this->name == other.name && - this->normalized == other.normalized && this->type == other.type; - } - bool Animation::operator==(const Animation& other) const { - return this->channels == other.channels && - this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->samplers == other.samplers; - } - bool AnimationChannel::operator==(const AnimationChannel& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->target_node == other.target_node && - this->target_path == other.target_path && - this->sampler == other.sampler; - } - bool AnimationSampler::operator==(const AnimationSampler& other) const { - return this->extras == other.extras && this->extensions == other.extensions && - this->input == other.input && - this->interpolation == other.interpolation && - this->output == other.output; - } - bool Asset::operator==(const Asset& other) const { - return this->copyright == other.copyright && - this->extensions == other.extensions && this->extras == other.extras && - this->generator == other.generator && - this->minVersion == other.minVersion && this->version == other.version; - } - bool Buffer::operator==(const Buffer& other) const { - return this->data == other.data && this->extensions == other.extensions && - this->extras == other.extras && this->name == other.name && - this->uri == other.uri; - } - bool BufferView::operator==(const BufferView& other) const { - return this->buffer == other.buffer && this->byteLength == other.byteLength && - this->byteOffset == other.byteOffset && - this->byteStride == other.byteStride && this->name == other.name && - this->target == other.target && this->extensions == other.extensions && - this->extras == other.extras && - this->dracoDecoded == other.dracoDecoded; - } - bool Camera::operator==(const Camera& other) const { - return this->name == other.name && this->extensions == other.extensions && - this->extras == other.extras && - this->orthographic == other.orthographic && - this->perspective == other.perspective && this->type == other.type; - } - bool Image::operator==(const Image& other) const { - return this->bufferView == other.bufferView && - this->component == other.component && - this->extensions == other.extensions && this->extras == other.extras && - this->height == other.height && this->image == other.image && - this->mimeType == other.mimeType && this->name == other.name && - this->uri == other.uri && this->width == other.width; - } - bool Light::operator==(const Light& other) const { - return Equals(this->color, other.color) && this->name == other.name && - this->type == other.type; - } - bool Material::operator==(const Material& other) const { - return (this->pbrMetallicRoughness == other.pbrMetallicRoughness) && - (this->normalTexture == other.normalTexture) && - (this->occlusionTexture == other.occlusionTexture) && - (this->emissiveTexture == other.emissiveTexture) && - Equals(this->emissiveFactor, other.emissiveFactor) && - (this->alphaMode == other.alphaMode) && - TINYGLTF_DOUBLE_EQUAL(this->alphaCutoff, other.alphaCutoff) && - (this->doubleSided == other.doubleSided) && - (this->extensions == other.extensions) && - (this->extras == other.extras) && (this->values == other.values) && - (this->additionalValues == other.additionalValues) && - (this->name == other.name); - } - bool Mesh::operator==(const Mesh& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && Equals(this->weights, other.weights) && - this->primitives == other.primitives; - } - bool Model::operator==(const Model& other) const { - return this->accessors == other.accessors && - this->animations == other.animations && this->asset == other.asset && - this->buffers == other.buffers && - this->bufferViews == other.bufferViews && - this->cameras == other.cameras && - this->defaultScene == other.defaultScene && - this->extensions == other.extensions && - this->extensionsRequired == other.extensionsRequired && - this->extensionsUsed == other.extensionsUsed && - this->extras == other.extras && this->images == other.images && - this->lights == other.lights && this->materials == other.materials && - this->meshes == other.meshes && this->nodes == other.nodes && - this->samplers == other.samplers && this->scenes == other.scenes && - this->skins == other.skins && this->textures == other.textures; - } - bool Node::operator==(const Node& other) const { - return this->camera == other.camera && this->children == other.children && - this->extensions == other.extensions && this->extras == other.extras && - Equals(this->matrix, other.matrix) && this->mesh == other.mesh && - this->name == other.name && Equals(this->rotation, other.rotation) && - Equals(this->scale, other.scale) && this->skin == other.skin && - Equals(this->translation, other.translation) && - Equals(this->weights, other.weights); - } - bool SpotLight::operator==(const SpotLight& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - TINYGLTF_DOUBLE_EQUAL(this->innerConeAngle, other.innerConeAngle) && - TINYGLTF_DOUBLE_EQUAL(this->outerConeAngle, other.outerConeAngle); - } - bool OrthographicCamera::operator==(const OrthographicCamera& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && - TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && - TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && - TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); - } - bool Parameter::operator==(const Parameter& other) const { - if (this->bool_value != other.bool_value || - this->has_number_value != other.has_number_value) - return false; +bool Accessor::operator==(const Accessor &other) const { + return this->bufferView == other.bufferView && + this->byteOffset == other.byteOffset && + this->componentType == other.componentType && + this->count == other.count && this->extensions == other.extensions && + this->extras == other.extras && + Equals(this->maxValues, other.maxValues) && + Equals(this->minValues, other.minValues) && this->name == other.name && + this->normalized == other.normalized && this->type == other.type; +} +bool Animation::operator==(const Animation &other) const { + return this->channels == other.channels && + this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->samplers == other.samplers; +} +bool AnimationChannel::operator==(const AnimationChannel &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->target_node == other.target_node && + this->target_path == other.target_path && + this->sampler == other.sampler; +} +bool AnimationSampler::operator==(const AnimationSampler &other) const { + return this->extras == other.extras && this->extensions == other.extensions && + this->input == other.input && + this->interpolation == other.interpolation && + this->output == other.output; +} +bool Asset::operator==(const Asset &other) const { + return this->copyright == other.copyright && + this->extensions == other.extensions && this->extras == other.extras && + this->generator == other.generator && + this->minVersion == other.minVersion && this->version == other.version; +} +bool Buffer::operator==(const Buffer &other) const { + return this->data == other.data && this->extensions == other.extensions && + this->extras == other.extras && this->name == other.name && + this->uri == other.uri; +} +bool BufferView::operator==(const BufferView &other) const { + return this->buffer == other.buffer && this->byteLength == other.byteLength && + this->byteOffset == other.byteOffset && + this->byteStride == other.byteStride && this->name == other.name && + this->target == other.target && this->extensions == other.extensions && + this->extras == other.extras && + this->dracoDecoded == other.dracoDecoded; +} +bool Camera::operator==(const Camera &other) const { + return this->name == other.name && this->extensions == other.extensions && + this->extras == other.extras && + this->orthographic == other.orthographic && + this->perspective == other.perspective && this->type == other.type; +} +bool Image::operator==(const Image &other) const { + return this->bufferView == other.bufferView && + this->component == other.component && + this->extensions == other.extensions && this->extras == other.extras && + this->height == other.height && this->image == other.image && + this->mimeType == other.mimeType && this->name == other.name && + this->uri == other.uri && this->width == other.width; +} +bool Light::operator==(const Light &other) const { + return Equals(this->color, other.color) && this->name == other.name && + this->type == other.type; +} +bool AudioEmitter::operator==(const AudioEmitter &other) const { + return this->name == other.name && + TINYGLTF_DOUBLE_EQUAL(this->gain, other.gain) && + this->loop == other.loop && this->playing == other.playing && + this->type == other.type && + this->distanceModel == other.distanceModel && + this->source == other.source; +} +bool AudioSource::operator==(const AudioSource &other) const { + return this->name == other.name && this->uri == other.uri; +} +bool Material::operator==(const Material &other) const { + return (this->pbrMetallicRoughness == other.pbrMetallicRoughness) && + (this->normalTexture == other.normalTexture) && + (this->occlusionTexture == other.occlusionTexture) && + (this->emissiveTexture == other.emissiveTexture) && + Equals(this->emissiveFactor, other.emissiveFactor) && + (this->alphaMode == other.alphaMode) && + TINYGLTF_DOUBLE_EQUAL(this->alphaCutoff, other.alphaCutoff) && + (this->doubleSided == other.doubleSided) && + (this->extensions == other.extensions) && + (this->extras == other.extras) && (this->values == other.values) && + (this->additionalValues == other.additionalValues) && + (this->name == other.name); +} +bool Mesh::operator==(const Mesh &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && Equals(this->weights, other.weights) && + this->primitives == other.primitives; +} +bool Model::operator==(const Model &other) const { + return this->accessors == other.accessors && + this->animations == other.animations && this->asset == other.asset && + this->buffers == other.buffers && + this->bufferViews == other.bufferViews && + this->cameras == other.cameras && + this->defaultScene == other.defaultScene && + this->extensions == other.extensions && + this->extensionsRequired == other.extensionsRequired && + this->extensionsUsed == other.extensionsUsed && + this->extras == other.extras && this->images == other.images && + this->lights == other.lights && this->materials == other.materials && + this->meshes == other.meshes && this->nodes == other.nodes && + this->samplers == other.samplers && this->scenes == other.scenes && + this->skins == other.skins && this->textures == other.textures; +} +bool Node::operator==(const Node &other) const { + return this->camera == other.camera && this->children == other.children && + this->extensions == other.extensions && this->extras == other.extras && + Equals(this->matrix, other.matrix) && this->mesh == other.mesh && + (this->light == other.light) && (this->emitter == other.emitter) && + this->name == other.name && Equals(this->rotation, other.rotation) && + Equals(this->scale, other.scale) && this->skin == other.skin && + Equals(this->translation, other.translation) && + Equals(this->weights, other.weights); +} +bool SpotLight::operator==(const SpotLight &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->innerConeAngle, other.innerConeAngle) && + TINYGLTF_DOUBLE_EQUAL(this->outerConeAngle, other.outerConeAngle); +} +bool PositionalEmitter::operator==(const PositionalEmitter &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->coneInnerAngle, other.coneInnerAngle) && + TINYGLTF_DOUBLE_EQUAL(this->coneOuterAngle, other.coneOuterAngle) && + TINYGLTF_DOUBLE_EQUAL(this->coneOuterGain, other.coneOuterGain) && + TINYGLTF_DOUBLE_EQUAL(this->maxDistance, other.maxDistance) && + TINYGLTF_DOUBLE_EQUAL(this->refDistance, other.refDistance) && + TINYGLTF_DOUBLE_EQUAL(this->rolloffFactor, other.rolloffFactor); +} +bool OrthographicCamera::operator==(const OrthographicCamera &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && + TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Parameter::operator==(const Parameter &other) const { + if (this->bool_value != other.bool_value || + this->has_number_value != other.has_number_value) + return false; - if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) - return false; + if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) + return false; - if (this->json_double_value.size() != other.json_double_value.size()) - return false; - for (auto& it : this->json_double_value) { - auto otherIt = other.json_double_value.find(it.first); - if (otherIt == other.json_double_value.end()) return false; + if (this->json_double_value.size() != other.json_double_value.size()) + return false; + for (auto &it : this->json_double_value) { + auto otherIt = other.json_double_value.find(it.first); + if (otherIt == other.json_double_value.end()) return false; - if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; - } + if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; + } - if (!Equals(this->number_array, other.number_array)) return false; + if (!Equals(this->number_array, other.number_array)) return false; - if (this->string_value != other.string_value) return false; + if (this->string_value != other.string_value) return false; - return true; - } - bool PerspectiveCamera::operator==(const PerspectiveCamera& other) const { - return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && - this->extensions == other.extensions && this->extras == other.extras && - TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && - TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && - TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); - } - bool Primitive::operator==(const Primitive& other) const { - return this->attributes == other.attributes && this->extras == other.extras && - this->indices == other.indices && this->material == other.material && - this->mode == other.mode && this->targets == other.targets; - } - bool Sampler::operator==(const Sampler& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->magFilter == other.magFilter && - this->minFilter == other.minFilter && this->name == other.name && - this->wrapS == other.wrapS && - this->wrapT == other.wrapT; + return true; +} +bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const { + return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && + this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Primitive::operator==(const Primitive &other) const { + return this->attributes == other.attributes && this->extras == other.extras && + this->indices == other.indices && this->material == other.material && + this->mode == other.mode && this->targets == other.targets; +} +bool Sampler::operator==(const Sampler &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->magFilter == other.magFilter && + this->minFilter == other.minFilter && this->name == other.name && + this->wrapS == other.wrapS && this->wrapT == other.wrapT; - //this->wrapR == other.wrapR - } - bool Scene::operator==(const Scene& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->nodes == other.nodes; - } - bool Skin::operator==(const Skin& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->inverseBindMatrices == other.inverseBindMatrices && - this->joints == other.joints && this->name == other.name && - this->skeleton == other.skeleton; - } - bool Texture::operator==(const Texture& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->sampler == other.sampler && - this->source == other.source; - } - bool TextureInfo::operator==(const TextureInfo& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->index == other.index && this->texCoord == other.texCoord; - } - bool NormalTextureInfo::operator==(const NormalTextureInfo& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->index == other.index && this->texCoord == other.texCoord && - TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale); - } - bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->index == other.index && this->texCoord == other.texCoord && - TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength); - } - bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness& other) const { - return this->extensions == other.extensions && this->extras == other.extras && - (this->baseColorTexture == other.baseColorTexture) && - (this->metallicRoughnessTexture == other.metallicRoughnessTexture) && - Equals(this->baseColorFactor, other.baseColorFactor) && - TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) && - TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor); - } - bool Value::operator==(const Value& other) const { - return Equals(*this, other); - } + // this->wrapR == other.wrapR +} +bool Scene::operator==(const Scene &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->nodes == other.nodes; +} +bool Skin::operator==(const Skin &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->inverseBindMatrices == other.inverseBindMatrices && + this->joints == other.joints && this->name == other.name && + this->skeleton == other.skeleton; +} +bool Texture::operator==(const Texture &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->sampler == other.sampler && + this->source == other.source; +} +bool TextureInfo::operator==(const TextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord; +} +bool NormalTextureInfo::operator==(const NormalTextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord && + TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale); +} +bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord && + TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength); +} +bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + (this->baseColorTexture == other.baseColorTexture) && + (this->metallicRoughnessTexture == other.metallicRoughnessTexture) && + Equals(this->baseColorFactor, other.baseColorFactor) && + TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) && + TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor); +} +bool Value::operator==(const Value &other) const { + return Equals(*this, other); +} - static void swap4(unsigned int* val) { +static void swap4(unsigned int *val) { #ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; + (void)val; #else - unsigned int tmp = *val; - unsigned char* dst = reinterpret_cast(val); - unsigned char* src = reinterpret_cast(&tmp); + unsigned int tmp = *val; + unsigned char *dst = reinterpret_cast(val); + unsigned char *src = reinterpret_cast(&tmp); - dst[0] = src[3]; - dst[1] = src[2]; - dst[2] = src[1]; - dst[3] = src[0]; + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; #endif - } +} - static std::string JoinPath(const std::string& path0, - const std::string& path1) { - if (path0.empty()) { - return path1; - } - else { - // check '/' - char lastChar = *path0.rbegin(); - if (lastChar != '/') { - return path0 + std::string("/") + path1; - } - else { - return path0 + path1; - } - } - } +static std::string JoinPath(const std::string &path0, + const std::string &path1) { + if (path0.empty()) { + return path1; + } else { + // check '/' + char lastChar = *path0.rbegin(); + if (lastChar != '/') { + return path0 + std::string("/") + path1; + } else { + return path0 + path1; + } + } +} - static std::string FindFile(const std::vector& paths, - const std::string& filepath, FsCallbacks* fs) { - if (fs == nullptr || fs->ExpandFilePath == nullptr || - fs->FileExists == nullptr) { - // Error, fs callback[s] missing - return std::string(); - } +static std::string FindFile(const std::vector &paths, + const std::string &filepath, FsCallbacks *fs) { + if (fs == nullptr || fs->ExpandFilePath == nullptr || + fs->FileExists == nullptr) { + // Error, fs callback[s] missing + return std::string(); + } - for (size_t i = 0; i < paths.size(); i++) { - std::string absPath = - fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); - if (fs->FileExists(absPath, fs->user_data)) { - return absPath; - } - } + // https://github.com/syoyo/tinygltf/issues/416 + // Use strlen() since std::string's size/length reports the number of elements + // in the buffer, not the length of string(null-terminated) strip + // null-character in the middle of string. + size_t slength = strlen(filepath.c_str()); + if (slength == 0) { + return std::string(); + } - return std::string(); - } + std::string cleaned_filepath = std::string(filepath.c_str()); - static std::string GetFilePathExtension(const std::string& FileName) { - if (FileName.find_last_of(".") != std::string::npos) - return FileName.substr(FileName.find_last_of(".") + 1); - return ""; - } + for (size_t i = 0; i < paths.size(); i++) { + std::string absPath = + fs->ExpandFilePath(JoinPath(paths[i], cleaned_filepath), fs->user_data); + if (fs->FileExists(absPath, fs->user_data)) { + return absPath; + } + } - static std::string GetBaseDir(const std::string& filepath) { - if (filepath.find_last_of("/\\") != std::string::npos) - return filepath.substr(0, filepath.find_last_of("/\\")); - return ""; - } + return std::string(); +} - static std::string GetBaseFilename(const std::string& filepath) { - auto idx = filepath.find_last_of("/\\"); - if (idx != std::string::npos) - return filepath.substr(idx + 1); - return filepath; - } +static std::string GetFilePathExtension(const std::string &FileName) { + if (FileName.find_last_of(".") != std::string::npos) + return FileName.substr(FileName.find_last_of(".") + 1); + return ""; +} - std::string base64_encode(unsigned char const*, unsigned int len); - std::string base64_decode(std::string const& s); +static std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\") + 1); + return ""; +} - /* - base64.cpp and base64.h +static std::string GetBaseFilename(const std::string &filepath) { + auto idx = filepath.find_last_of("/\\"); + if (idx != std::string::npos) return filepath.substr(idx + 1); + return filepath; +} - Copyright (C) 2004-2008 René Nyffenegger +std::string base64_encode(unsigned char const *, unsigned int len); +std::string base64_decode(std::string const &s); - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. +/* + base64.cpp and base64.h - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: + Copyright (C) 2004-2008 René Nyffenegger - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: - 3. This notice may not be removed or altered from any source distribution. + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. - René Nyffenegger rene.nyffenegger@adp-gmbh.ch + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. - */ + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ #ifdef __clang__ #pragma clang diagnostic push @@ -2116,5765 +2302,6438 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wconversion" #endif - static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); - } +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} - std::string base64_encode(unsigned char const* bytes_to_encode, - unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; +std::string base64_encode(unsigned char const *bytes_to_encode, + unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - const char* base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + const char *base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; - i = 0; - } - } + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } - if (i) { - for (j = i; j < 3; j++) char_array_3[j] = '\0'; + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; - while ((i++ < 3)) ret += '='; - } + while ((i++ < 3)) ret += '='; + } - return ret; - } + return ret; +} - std::string base64_decode(std::string const& encoded_string) { - int in_len = static_cast(encoded_string.size()); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; +std::string base64_decode(std::string const &encoded_string) { + int in_len = static_cast(encoded_string.size()); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; - const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; - while (in_len-- && (encoded_string[in_] != '=') && - is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) { - for (i = 0; i < 4; i++) - char_array_4[i] = - static_cast(base64_chars.find(char_array_4[i])); + while (in_len-- && (encoded_string[in_] != '=') && + is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = + static_cast(base64_chars.find(char_array_4[i])); - char_array_3[0] = - (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (i = 0; (i < 3); i++) ret += char_array_3[i]; - i = 0; - } - } + for (i = 0; (i < 3); i++) ret += char_array_3[i]; + i = 0; + } + } - if (i) { - for (j = i; j < 4; j++) char_array_4[j] = 0; + if (i) { + for (j = i; j < 4; j++) char_array_4[j] = 0; - for (j = 0; j < 4; j++) - char_array_4[j] = - static_cast(base64_chars.find(char_array_4[j])); + for (j = 0; j < 4; j++) + char_array_4[j] = + static_cast(base64_chars.find(char_array_4[j])); - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } - return ret; - } + return ret; +} #ifdef __clang__ #pragma clang diagnostic pop #endif - // https://github.com/syoyo/tinygltf/issues/228 - // TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri - // decoding? - // - // Uri Decoding from DLIB - // http://dlib.net/dlib/server/server_http.cpp.html - // --- dlib begin ------------------------------------------------------------ - // Copyright (C) 2003 Davis E. King (davis@dlib.net) - // License: Boost Software License - // Boost Software License - Version 1.0 - August 17th, 2003 +// https://github.com/syoyo/tinygltf/issues/228 +// TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri +// decoding? +// +// Uri Decoding from DLIB +// http://dlib.net/dlib/server/server_http.cpp.html +// --- dlib begin ------------------------------------------------------------ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License +// Boost Software License - Version 1.0 - August 17th, 2003 - // Permission is hereby granted, free of charge, to any person or organization - // obtaining a copy of the software and accompanying documentation covered by - // this license (the "Software") to use, reproduce, display, distribute, - // execute, and transmit the Software, and to prepare derivative works of the - // Software, and to permit third-parties to whom the Software is furnished to - // do so, all subject to the following: - // The copyright notices in the Software and this entire statement, including - // the above license grant, this restriction and the following disclaimer, - // must be included in all copies of the Software, in whole or in part, and - // all derivative works of the Software, unless such copies or derivative - // works are solely in the form of machine-executable object code generated by - // a source language processor. - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - // DEALINGS IN THE SOFTWARE. - // - namespace dlib { +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +namespace dlib { - inline unsigned char from_hex(unsigned char ch) { - if (ch <= '9' && ch >= '0') - ch -= '0'; - else if (ch <= 'f' && ch >= 'a') - ch -= 'a' - 10; - else if (ch <= 'F' && ch >= 'A') - ch -= 'A' - 10; - else - ch = 0; - return ch; - } +inline unsigned char from_hex(unsigned char ch) { + if (ch <= '9' && ch >= '0') + ch -= '0'; + else if (ch <= 'f' && ch >= 'a') + ch -= 'a' - 10; + else if (ch <= 'F' && ch >= 'A') + ch -= 'A' - 10; + else + ch = 0; + return ch; +} - static const std::string urldecode(const std::string& str) { - using namespace std; - string result; - string::size_type i; - for (i = 0; i < str.size(); ++i) { - if (str[i] == '+') { - result += ' '; - } - else if (str[i] == '%' && str.size() > i + 2) { - const unsigned char ch1 = - from_hex(static_cast(str[i + 1])); - const unsigned char ch2 = - from_hex(static_cast(str[i + 2])); - const unsigned char ch = static_cast((ch1 << 4) | ch2); - result += static_cast(ch); - i += 2; - } - else { - result += str[i]; - } - } - return result; - } +static const std::string urldecode(const std::string &str) { + using namespace std; + string result; + string::size_type i; + for (i = 0; i < str.size(); ++i) { + if (str[i] == '+') { + result += ' '; + } else if (str[i] == '%' && str.size() > i + 2) { + const unsigned char ch1 = + from_hex(static_cast(str[i + 1])); + const unsigned char ch2 = + from_hex(static_cast(str[i + 2])); + const unsigned char ch = static_cast((ch1 << 4) | ch2); + result += static_cast(ch); + i += 2; + } else { + result += str[i]; + } + } + return result; +} - } // namespace dlib - // --- dlib end -------------------------------------------------------------- +} // namespace dlib +// --- dlib end -------------------------------------------------------------- - static bool LoadExternalFile(std::vector* out, std::string* err, - std::string* warn, const std::string& filename, - const std::string& basedir, bool required, - size_t reqBytes, bool checkSize, FsCallbacks* fs) { - if (fs == nullptr || fs->FileExists == nullptr || - fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { - // This is a developer error, assert() ? - if (err) { - (*err) += "FS callback[s] not set\n"; - } - return false; - } +bool URIDecode(const std::string &in_uri, std::string *out_uri, + void *user_data) { + (void)user_data; + *out_uri = dlib::urldecode(in_uri); + return true; +} - std::string* failMsgOut = required ? err : warn; +static bool LoadExternalFile(std::vector *out, std::string *err, + std::string *warn, const std::string &filename, + const std::string &basedir, bool required, + size_t reqBytes, bool checkSize, + size_t maxFileSize, FsCallbacks *fs) { + if (fs == nullptr || fs->FileExists == nullptr || + fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { + // This is a developer error, assert() ? + if (err) { + (*err) += "FS callback[s] not set\n"; + } + return false; + } - out->clear(); + std::string *failMsgOut = required ? err : warn; - std::vector paths; - paths.push_back(basedir); - paths.push_back("."); + out->clear(); - std::string filepath = FindFile(paths, filename, fs); - if (filepath.empty() || filename.empty()) { - if (failMsgOut) { - (*failMsgOut) += "File not found : " + filename + "\n"; - } - return false; - } + std::vector paths; + paths.push_back(basedir); + paths.push_back("."); - std::vector buf; - std::string fileReadErr; - bool fileRead = - fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); - if (!fileRead) { - if (failMsgOut) { - (*failMsgOut) += - "File read error : " + filepath + " : " + fileReadErr + "\n"; - } - return false; - } + std::string filepath = FindFile(paths, filename, fs); + if (filepath.empty() || filename.empty()) { + if (failMsgOut) { + (*failMsgOut) += "File not found : " + filename + "\n"; + } + return false; + } - size_t sz = buf.size(); - if (sz == 0) { - if (failMsgOut) { - (*failMsgOut) += "File is empty : " + filepath + "\n"; - } - return false; - } + // Check file size + if (fs->GetFileSizeInBytes) { + size_t file_size{0}; + std::string _err; + bool ok = + fs->GetFileSizeInBytes(&file_size, &_err, filepath, fs->user_data); + if (!ok) { + if (_err.size()) { + if (failMsgOut) { + (*failMsgOut) += "Getting file size failed : " + filename + + ", err = " + _err + "\n"; + } + } + return false; + } - if (checkSize) { - if (reqBytes == sz) { - out->swap(buf); - return true; - } - else { - std::stringstream ss; - ss << "File size mismatch : " << filepath << ", requestedBytes " - << reqBytes << ", but got " << sz << std::endl; - if (failMsgOut) { - (*failMsgOut) += ss.str(); - } - return false; - } - } + if (file_size > maxFileSize) { + if (failMsgOut) { + (*failMsgOut) += "File size " + std::to_string(file_size) + + " exceeds maximum allowed file size " + + std::to_string(maxFileSize) + " : " + filepath + "\n"; + } + return false; + } + } - out->swap(buf); - return true; - } + std::vector buf; + std::string fileReadErr; + bool fileRead = + fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); + if (!fileRead) { + if (failMsgOut) { + (*failMsgOut) += + "File read error : " + filepath + " : " + fileReadErr + "\n"; + } + return false; + } - void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void* user_data) { - LoadImageData = func; - load_image_user_data_ = user_data; - user_image_loader_ = true; - } + size_t sz = buf.size(); + if (sz == 0) { + if (failMsgOut) { + (*failMsgOut) += "File is empty : " + filepath + "\n"; + } + return false; + } - void TinyGLTF::RemoveImageLoader() { - LoadImageData = + if (checkSize) { + if (reqBytes == sz) { + out->swap(buf); + return true; + } else { + std::stringstream ss; + ss << "File size mismatch : " << filepath << ", requestedBytes " + << reqBytes << ", but got " << sz << std::endl; + if (failMsgOut) { + (*failMsgOut) += ss.str(); + } + return false; + } + } + + out->swap(buf); + return true; +} + +void TinyGLTF::SetParseStrictness(ParseStrictness strictness) { + strictness_ = strictness; +} + +void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { + if (func == nullptr) { + RemoveImageLoader(); + return; + } + LoadImageData = std::move(func); + load_image_user_data_ = user_data; + user_image_loader_ = true; +} + +void TinyGLTF::RemoveImageLoader() { + LoadImageData = #ifndef TINYGLTF_NO_STB_IMAGE - & tinygltf::LoadImageData; + &tinygltf::LoadImageData; #else - nullptr; + nullptr; #endif - load_image_user_data_ = nullptr; - user_image_loader_ = false; - } + load_image_user_data_ = nullptr; + user_image_loader_ = false; +} #ifndef TINYGLTF_NO_STB_IMAGE - bool LoadImageData(Image* image, const int image_idx, std::string* err, - std::string* warn, int req_width, int req_height, - const unsigned char* bytes, int size, void* user_data) { - (void)warn; +bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *user_data) { + (void)warn; - LoadImageDataOption option; - if (user_data) { - option = *reinterpret_cast(user_data); - } + LoadImageDataOption option; + if (user_data) { + option = *reinterpret_cast(user_data); + } - int w = 0, h = 0, comp = 0, req_comp = 0; + int w = 0, h = 0, comp = 0, req_comp = 0; - unsigned char* data = nullptr; + // Try to decode image header + if (!stbi_info_from_memory(bytes, size, &w, &h, &comp)) { + // On failure, if we load images as is, we just warn. + std::string* msgOut = option.as_is ? warn : err; + if (msgOut) { + (*msgOut) += + "Unknown image format. STB cannot decode image header for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; + } + if (!option.as_is) { + // If we decode images, error out. + return false; + } else { + // If we load images as is, we copy the image data, + // set all image properties to invalid, and report success. + image->width = image->height = image->component = -1; + image->bits = image->pixel_type = -1; + image->image.resize(static_cast(size)); + std::copy(bytes, bytes + size, image->image.begin()); + return true; + } + } - // preserve_channels true: Use channels stored in the image file. - // false: force 32-bit textures for common Vulkan compatibility. It appears - // that some GPU drivers do not support 24-bit images for Vulkan - req_comp = option.preserve_channels ? 0 : 4; - int bits = 8; - int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + int bits = 8; + int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; - // It is possible that the image we want to load is a 16bit per channel image - // We are going to attempt to load it as 16bit per channel, and if it worked, - // set the image data accodingly. We are casting the returned pointer into - // unsigned char, because we are representing "bytes". But we are updating - // the Image metadata to signal that this image uses 2 bytes (16bits) per - // channel: - if (stbi_is_16_bit_from_memory(bytes, size)) { - data = reinterpret_cast( - stbi_load_16_from_memory(bytes, size, &w, &h, &comp, req_comp)); - if (data) { - bits = 16; - pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; - } - } + if (stbi_is_16_bit_from_memory(bytes, size)) { + bits = 16; + pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + } - // at this point, if data is still NULL, it means that the image wasn't - // 16bit per channel, we are going to load it as a normal 8bit per channel - // mage as we used to do: - // if image cannot be decoded, ignore parsing and keep it by its path - // don't break in this case - // FIXME we should only enter this function if the image is embedded. If - // image->uri references - // an image file, it should be left as it is. Image loading should not be - // mandatory (to support other formats) - if (!data) data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); - if (!data) { - // NOTE: you can use `warn` instead of `err` - if (err) { - (*err) += - "Unknown image format. STB cannot decode image data for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; - } - return false; - } + // preserve_channels true: Use channels stored in the image file. + // false: force 32-bit textures for common Vulkan compatibility. It appears + // that some GPU drivers do not support 24-bit images for Vulkan + req_comp = (option.preserve_channels || option.as_is) ? 0 : 4; - if ((w < 1) || (h < 1)) { - stbi_image_free(data); - if (err) { - (*err) += "Invalid image data for image[" + std::to_string(image_idx) + - "] name = \"" + image->name + "\"\n"; - } - return false; - } + unsigned char* data = nullptr; + // Perform image decoding if requested + if (!option.as_is) { + // If the image is marked as 16 bit per channel, attempt to decode it as such first. + // If that fails, we are going to attempt to load it as 8 bit per channel image. + if (bits == 16) { + data = reinterpret_cast(stbi_load_16_from_memory(bytes, size, &w, &h, &comp, req_comp)); + } + // Load as 8 bit per channel data + if (!data) { + data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); + if (!data) { + if (err) { + (*err) += + "Unknown image format. STB cannot decode image data for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; + } + return false; + } + // If we were succesful, mark as 8 bit + bits = 8; + pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + } + } - if (req_width > 0) { - if (req_width != w) { - stbi_image_free(data); - if (err) { - (*err) += "Image width mismatch for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - } + if ((w < 1) || (h < 1)) { + stbi_image_free(data); + if (err) { + (*err) += "Invalid image data for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\"\n"; + } + return false; + } - if (req_height > 0) { - if (req_height != h) { - stbi_image_free(data); - if (err) { - (*err) += "Image height mismatch. for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - } + if (req_width > 0) { + if (req_width != w) { + stbi_image_free(data); + if (err) { + (*err) += "Image width mismatch for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } - if (req_comp != 0) { - // loaded data has `req_comp` channels(components) - comp = req_comp; - } + if (req_height > 0) { + if (req_height != h) { + stbi_image_free(data); + if (err) { + (*err) += "Image height mismatch. for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } - image->width = w; - image->height = h; - image->component = comp; - image->bits = bits; - image->pixel_type = pixel_type; - image->image.resize(static_cast(w * h * comp) * size_t(bits / 8)); - std::copy(data, data + w * h * comp * (bits / 8), image->image.begin()); - stbi_image_free(data); + if (req_comp != 0) { + // loaded data has `req_comp` channels(components) + comp = req_comp; + } - return true; - } + image->width = w; + image->height = h; + image->component = comp; + image->bits = bits; + image->pixel_type = pixel_type; + image->as_is = option.as_is; + + if (option.as_is) { + // Store the original image data + image->image.resize(static_cast(size)); + std::copy(bytes, bytes + size, image->image.begin()); + } + else { + // Store the decoded image data + image->image.resize(static_cast(w * h * comp) * size_t(bits / 8)); + std::copy(data, data + w * h * comp * (bits / 8), image->image.begin()); + } + + stbi_image_free(data); + return true; +} #endif - void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void* user_data) { - WriteImageData = func; - write_image_user_data_ = user_data; - } +void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { + WriteImageData = std::move(func); + write_image_user_data_ = user_data; +} #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - static void WriteToMemory_stbi(void* context, void* data, int size) { - std::vector* buffer = - reinterpret_cast *>(context); +static void WriteToMemory_stbi(void *context, void *data, int size) { + std::vector *buffer = + reinterpret_cast *>(context); - unsigned char* pData = reinterpret_cast(data); + unsigned char *pData = reinterpret_cast(data); - buffer->insert(buffer->end(), pData, pData + size); - } + buffer->insert(buffer->end(), pData, pData + size); +} - bool WriteImageData(const std::string* basepath, const std::string* filename, - Image* image, bool embedImages, void* fsPtr) { - const std::string ext = GetFilePathExtension(*filename); +bool WriteImageData(const std::string *basepath, const std::string *filename, + const Image *image, bool embedImages, + const FsCallbacks* fs_cb, const URICallbacks *uri_cb, + std::string *out_uri, void *) { + // Early out on empty images, report the original uri if the image was not written. + if (image->image.empty()) { + *out_uri = *filename; + return true; + } - // Write image to temporary buffer - std::string header; - std::vector data; + const std::string ext = GetFilePathExtension(*filename); - if (ext == "png") { - if ((image->bits != 8) || - (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) { - // Unsupported pixel format - return false; - } + // Write image to temporary buffer + std::string header; + std::vector data; - if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0], 0)) { - return false; - } - header = "data:image/png;base64,"; - } - else if (ext == "jpg") { - if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0], 100)) { - return false; - } - header = "data:image/jpeg;base64,"; - } - else if (ext == "bmp") { - if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0])) { - return false; - } - header = "data:image/bmp;base64,"; - } - else if (!embedImages) { - // Error: can't output requested format to file - return false; - } + // If the image data is already encoded, take it as is + if (image->as_is) { + data = image->image; + } - if (embedImages) { - // Embed base64-encoded image into URI - if (data.size()) { - image->uri = - header + - base64_encode(&data[0], static_cast(data.size())); - } - else { - // Throw error? - } - } - else { - // Write image to disc - FsCallbacks* fs = reinterpret_cast(fsPtr); - if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) { - const std::string imagefilepath = JoinPath(*basepath, *filename); - std::string writeError; - if (!fs->WriteWholeFile(&writeError, imagefilepath, data, - fs->user_data)) { - // Could not write image file to disc; Throw error ? - return false; - } - } - else { - // Throw error? - } - image->uri = *filename; - } + if (ext == "png") { + if (!image->as_is) { + if ((image->bits != 8) || + (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) { + // Unsupported pixel format + return false; + } - return true; - } + if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 0)) { + return false; + } + } + header = "data:image/png;base64,"; + } else if (ext == "jpg") { + if (!image->as_is && + !stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 100)) { + return false; + } + header = "data:image/jpeg;base64,"; + } else if (ext == "bmp") { + if (!image->as_is && + !stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0])) { + return false; + } + header = "data:image/bmp;base64,"; + } else if (!embedImages) { + // Error: can't output requested format to file + return false; + } + + if (embedImages) { + // Embed base64-encoded image into URI + if (data.size()) { + *out_uri = header + base64_encode(&data[0], + static_cast(data.size())); + } else { + // Throw error? + } + } else { + // Write image to disc + if ((fs_cb != nullptr) && (fs_cb->WriteWholeFile != nullptr)) { + const std::string imagefilepath = JoinPath(*basepath, *filename); + std::string writeError; + if (!fs_cb->WriteWholeFile(&writeError, imagefilepath, data, + fs_cb->user_data)) { + // Could not write image file to disc; Throw error ? + return false; + } + } else { + // Throw error? + } + if (uri_cb->encode) { + if (!uri_cb->encode(*filename, "image", out_uri, uri_cb->user_data)) { + return false; + } + } else { + *out_uri = *filename; + } + } + + return true; +} #endif - void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } +bool TinyGLTF::SetURICallbacks(URICallbacks callbacks, std::string* err) { + if (callbacks.decode == nullptr) { + if (err != nullptr) { + *err = "URI Callback require a non-null decode function."; + } + return false; + } + + if (callbacks.decode) { + uri_cb = std::move(callbacks); + } + return true; +} + +bool TinyGLTF::SetFsCallbacks(FsCallbacks callbacks, std::string *err) { + // If callbacks are defined at all, they must all be defined. + if (callbacks.FileExists == nullptr || callbacks.ExpandFilePath == nullptr || + callbacks.ReadWholeFile == nullptr || + callbacks.WriteWholeFile == nullptr || + callbacks.GetFileSizeInBytes == nullptr) { + if (err != nullptr) { + *err = + "FS Callbacks must be completely defined. At least one callback is " + "null."; + } + return false; + } + fs = std::move(callbacks); + return true; +} #ifdef _WIN32 - static inline std::wstring UTF8ToWchar(const std::string& str) { - int wstr_size = - MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); - std::wstring wstr(wstr_size, 0); - MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], - (int)wstr.size()); - return wstr; - } +static inline std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + std::wstring wstr((size_t)wstr_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], + (int)wstr.size()); + return wstr; +} - static inline std::string WcharToUTF8(const std::wstring& wstr) { - int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), - nullptr, 0, NULL, NULL); - std::string str(str_size, 0); - WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0], - (int)str.size(), NULL, NULL); - return str; - } +static inline std::string WcharToUTF8(const std::wstring &wstr) { + int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), + nullptr, 0, nullptr, nullptr); + std::string str((size_t)str_size, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0], + (int)str.size(), nullptr, nullptr); + return str; +} #endif #ifndef TINYGLTF_NO_FS - // Default implementations of filesystem functions +// Default implementations of filesystem functions - bool FileExists(const std::string& abs_filename, void*) { - bool ret; +bool FileExists(const std::string &abs_filename, void *) { + bool ret; #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - if (asset_manager) { - AAsset* asset = AAssetManager_open(asset_manager, abs_filename.c_str(), - AASSET_MODE_STREAMING); - if (!asset) { - return false; - } - AAsset_close(asset); - ret = true; - } - else { - return false; - } + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + return false; + } + AAsset_close(asset); + ret = true; + } else { + return false; + } #else #ifdef _WIN32 -#if defined(_MSC_VER) || defined(__GLIBCXX__) - FILE* fp = nullptr; - errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb"); - if (err != 0) { - return false; - } +#if defined(_MSC_VER) || defined(_LIBCPP_VERSION) + + // First check if a file is a directory. + DWORD result = GetFileAttributesW(UTF8ToWchar(abs_filename).c_str()); + if (result == INVALID_FILE_ATTRIBUTES) { + return false; + } + if (result & FILE_ATTRIBUTE_DIRECTORY) { + return false; + } + + FILE *fp = nullptr; + errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb"); + if (err != 0) { + return false; + } +#elif defined(__GLIBCXX__) + FILE *fp = fopen(abs_filename.c_str(), "rb"); + if (!fp) { + return false; + } #else - FILE* fp = nullptr; - errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); - if (err != 0) { - return false; - } + // TODO: is_directory check + FILE *fp = nullptr; + errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); + if (err != 0) { + return false; + } #endif #else - FILE* fp = fopen(abs_filename.c_str(), "rb"); + struct stat sb; + if (stat(abs_filename.c_str(), &sb)) { + return false; + } + if (S_ISDIR(sb.st_mode)) { + return false; + } + + FILE *fp = fopen(abs_filename.c_str(), "rb"); #endif - if (fp) { - ret = true; - fclose(fp); - } - else { - ret = false; - } + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } #endif - return ret; - } + return ret; +} - std::string ExpandFilePath(const std::string& filepath, void*) { +std::string ExpandFilePath(const std::string &filepath, void *) { + // https://github.com/syoyo/tinygltf/issues/368 + // + // No file path expansion in built-in FS function anymore, since glTF URI + // should not contain tilde('~') and environment variables, and for security + // reason(`wordexp`). + // + // Users need to supply `base_dir`(in `LoadASCIIFromString`, + // `LoadBinaryFromMemory`) in expanded absolute path. + + return filepath; + +#if 0 #ifdef _WIN32 - // Assume input `filepath` is encoded in UTF-8 - std::wstring wfilepath = UTF8ToWchar(filepath); - DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0); - wchar_t* wstr = new wchar_t[wlen]; - ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen); + // Assume input `filepath` is encoded in UTF-8 + std::wstring wfilepath = UTF8ToWchar(filepath); + DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0); + wchar_t *wstr = new wchar_t[wlen]; + ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen); - std::wstring ws(wstr); - delete[] wstr; - return WcharToUTF8(ws); + std::wstring ws(wstr); + delete[] wstr; + return WcharToUTF8(ws); #else #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ - defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__OpenBSD__) - // no expansion - std::string s = filepath; + defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__OpenBSD__) + // no expansion + std::string s = filepath; #else - std::string s; - wordexp_t p; + std::string s; + wordexp_t p; - if (filepath.empty()) { - return ""; - } + if (filepath.empty()) { + return ""; + } - // Quote the string to keep any spaces in filepath intact. - std::string quoted_path = "\"" + filepath + "\""; - // char** w; - int ret = wordexp(quoted_path.c_str(), &p, 0); - if (ret) { - // err - s = filepath; - return s; - } + // Quote the string to keep any spaces in filepath intact. + std::string quoted_path = "\"" + filepath + "\""; + // char** w; + int ret = wordexp(quoted_path.c_str(), &p, 0); + if (ret) { + // err + s = filepath; + return s; + } - // Use first element only. - if (p.we_wordv) { - s = std::string(p.we_wordv[0]); - wordfree(&p); - } - else { - s = filepath; - } + // Use first element only. + if (p.we_wordv) { + s = std::string(p.we_wordv[0]); + wordfree(&p); + } else { + s = filepath; + } #endif - return s; + return s; #endif - } +#endif +} + +bool GetFileSizeInBytes(size_t *filesize_out, std::string *err, + const std::string &filepath, void *userdata) { + (void)userdata; - bool ReadWholeFile(std::vector* out, std::string* err, - const std::string& filepath, void*) { #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - if (asset_manager) { - AAsset* asset = AAssetManager_open(asset_manager, filepath.c_str(), - AASSET_MODE_STREAMING); - if (!asset) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } - size_t size = AAsset_getLength(asset); - if (size == 0) { - if (err) { - (*err) += "Invalid file size : " + filepath + - " (does the path point to a directory?)"; - } - return false; - } - out->resize(size); - AAsset_read(asset, reinterpret_cast(&out->at(0)), size); - AAsset_close(asset); - return true; - } - else { - if (err) { - (*err) += "No asset manager specified : " + filepath + "\n"; - } - return false; - } + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + + if (size == 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + + return true; + } else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } #else #ifdef _WIN32 #if defined(__GLIBCXX__) // mingw - int file_descriptor = - _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY); - __gnu_cxx::stdio_filebuf wfile_buf(file_descriptor, std::ios_base::in); - std::istream f(&wfile_buf); + int file_descriptor = + _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf(file_descriptor, std::ios_base::in); + std::istream f(&wfile_buf); #elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) - // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept - // `wchar_t *` - std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); + // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept + // `wchar_t *` + std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); #else - // Unknown compiler/runtime - std::ifstream f(filepath.c_str(), std::ifstream::binary); + // Unknown compiler/runtime + std::ifstream f(filepath.c_str(), std::ifstream::binary); #endif #else - std::ifstream f(filepath.c_str(), std::ifstream::binary); + std::ifstream f(filepath.c_str(), std::ifstream::binary); #endif - if (!f) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } + if (!f) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } - f.seekg(0, f.end); - size_t sz = static_cast(f.tellg()); - f.seekg(0, f.beg); + // For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only) + f.peek(); + if (!f) { + if (err) { + (*err) += + "File read error. Maybe empty file or invalid file : " + filepath + + "\n"; + } + return false; + } - if (int64_t(sz) < 0) { - if (err) { - (*err) += "Invalid file size : " + filepath + - " (does the path point to a directory?)"; - } - return false; - } - else if (sz == 0) { - if (err) { - (*err) += "File is empty : " + filepath + "\n"; - } - return false; - } + f.seekg(0, f.end); + const auto sz = f.tellg(); - out->resize(sz); - f.read(reinterpret_cast(&out->at(0)), - static_cast(sz)); + // std::cout << "sz = " << sz << "\n"; + f.seekg(0, f.beg); - return true; + if (sz < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == std::streamoff(0)) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } else if (sz >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Invalid file size : " + filepath + "\n"; + } + return false; + } + + (*filesize_out) = static_cast(sz); + return true; #endif - } +} - bool WriteWholeFile(std::string* err, const std::string& filepath, - const std::vector& contents, void*) { +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *) { +#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + if (size == 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + out->resize(static_cast(size)); + AAsset_read(asset, reinterpret_cast(&out->at(0)), size); + AAsset_close(asset); + return true; + } else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } +#else #ifdef _WIN32 #if defined(__GLIBCXX__) // mingw - int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), - _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); - __gnu_cxx::stdio_filebuf wfile_buf( - file_descriptor, std::ios_base::out | std::ios_base::binary); - std::ostream f(&wfile_buf); -#elif defined(_MSC_VER) - std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary); -#else // clang? - std::ofstream f(filepath.c_str(), std::ofstream::binary); + int file_descriptor = + _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf(file_descriptor, std::ios_base::in); + std::istream f(&wfile_buf); +#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) + // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept + // `wchar_t *` + std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); +#else + // Unknown compiler/runtime + std::ifstream f(filepath.c_str(), std::ifstream::binary); #endif #else - std::ofstream f(filepath.c_str(), std::ofstream::binary); + std::ifstream f(filepath.c_str(), std::ifstream::binary); #endif - if (!f) { - if (err) { - (*err) += "File open error for writing : " + filepath + "\n"; - } - return false; - } + if (!f) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } - f.write(reinterpret_cast(&contents.at(0)), - static_cast(contents.size())); - if (!f) { - if (err) { - (*err) += "File write error: " + filepath + "\n"; - } - return false; - } + // For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only) + f.peek(); + if (!f) { + if (err) { + (*err) += + "File read error. Maybe empty file or invalid file : " + filepath + + "\n"; + } + return false; + } - return true; - } + f.seekg(0, f.end); + const auto sz = f.tellg(); + + // std::cout << "sz = " << sz << "\n"; + f.seekg(0, f.beg); + + if (sz < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == std::streamoff(0)) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } else if (sz >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Invalid file size : " + filepath + "\n"; + } + return false; + } + + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + + return true; +#endif +} + +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *) { +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY, _S_IWRITE); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream f(&wfile_buf); +#elif defined(_MSC_VER) + std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary); +#else // clang? + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif +#else + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File open error for writing : " + filepath + "\n"; + } + return false; + } + + f.write(reinterpret_cast(&contents.at(0)), + static_cast(contents.size())); + if (!f) { + if (err) { + (*err) += "File write error: " + filepath + "\n"; + } + return false; + } + + return true; +} #endif // TINYGLTF_NO_FS - static std::string MimeToExt(const std::string& mimeType) { - if (mimeType == "image/jpeg") { - return "jpg"; - } - else if (mimeType == "image/png") { - return "png"; - } - else if (mimeType == "image/bmp") { - return "bmp"; - } - else if (mimeType == "image/gif") { - return "gif"; - } +static std::string MimeToExt(const std::string &mimeType) { + if (mimeType == "image/jpeg") { + return "jpg"; + } else if (mimeType == "image/png") { + return "png"; + } else if (mimeType == "image/bmp") { + return "bmp"; + } else if (mimeType == "image/gif") { + return "gif"; + } - return ""; - } + return ""; +} - static void UpdateImageObject(Image& image, std::string& baseDir, int index, - bool embedImages, - WriteImageDataFunction* WriteImageData = nullptr, - void* user_data = nullptr) { - std::string filename; - std::string ext; - // If image has uri, use it it as a filename - if (image.uri.size()) { - filename = GetBaseFilename(image.uri); - ext = GetFilePathExtension(filename); - } - else if (image.bufferView != -1) { - // If there's no URI and the data exists in a buffer, - // don't change properties or write images - } - else if (image.name.size()) { - ext = MimeToExt(image.mimeType); - // Otherwise use name as filename - filename = image.name + "." + ext; - } - else { - ext = MimeToExt(image.mimeType); - // Fallback to index of image as filename - filename = std::to_string(index) + "." + ext; - } +static bool UpdateImageObject(const Image &image, std::string &baseDir, + int index, bool embedImages, + const FsCallbacks *fs_cb, + const URICallbacks *uri_cb, + const WriteImageDataFunction& WriteImageData, + void *user_data, std::string *out_uri) { + std::string filename; + std::string ext; + // If image has uri, use it as a filename + if (image.uri.size()) { + std::string decoded_uri; + if (!uri_cb->decode(image.uri, &decoded_uri, uri_cb->user_data)) { + // A decode failure results in a failure to write the gltf. + return false; + } + filename = GetBaseFilename(decoded_uri); + ext = GetFilePathExtension(filename); + } else if (image.bufferView != -1) { + // If there's no URI and the data exists in a buffer, + // don't change properties or write images + } else if (image.name.size()) { + ext = MimeToExt(image.mimeType); + // Otherwise use name as filename + filename = image.name + "." + ext; + } else { + ext = MimeToExt(image.mimeType); + // Fallback to index of image as filename + filename = std::to_string(index) + "." + ext; + } - // If callback is set, modify image data object - if (*WriteImageData != nullptr && !filename.empty()) { - std::string uri; - (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); - } - } + // If callback is set, modify image data object. + // Note that the callback is also invoked for images without data. + // The default callback implementation simply returns true for + // empty images and sets the out URI to filename. + bool imageWritten = false; + if (WriteImageData != nullptr && !filename.empty()) { + imageWritten = WriteImageData(&baseDir, &filename, &image, embedImages, + fs_cb, uri_cb, out_uri, user_data); + if (!imageWritten) { + return false; + } + } - bool IsDataURI(const std::string& in) { - std::string header = "data:application/octet-stream;base64,"; - if (in.find(header) == 0) { - return true; - } + // Use the original uri if the image was not written. + if (!imageWritten) { + *out_uri = image.uri; + } - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - return true; - } + return true; +} - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - return true; - } +bool IsDataURI(const std::string &in) { + std::string header = "data:application/octet-stream;base64,"; + if (in.find(header) == 0) { + return true; + } - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - return true; - } + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + return true; + } - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - return true; - } + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + return true; + } - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - return true; - } + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + return true; + } - header = "data:application/gltf-buffer;base64,"; - if (in.find(header) == 0) { - return true; - } + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + return true; + } - return false; - } + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + return true; + } - bool DecodeDataURI(std::vector* out, std::string& mime_type, - const std::string& in, size_t reqBytes, bool checkSize) { - std::string header = "data:application/octet-stream;base64,"; - std::string data; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); // cut mime string. - } + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + return true; + } - if (data.empty()) { - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - mime_type = "image/jpeg"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } + return false; +} - if (data.empty()) { - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - mime_type = "image/png"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize) { + std::string header = "data:application/octet-stream;base64,"; + std::string data; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); // cut mime string. + } - if (data.empty()) { - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - mime_type = "image/bmp"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } + if (data.empty()) { + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + mime_type = "image/jpeg"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } - if (data.empty()) { - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - mime_type = "image/gif"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } + if (data.empty()) { + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + mime_type = "image/png"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } - if (data.empty()) { - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - mime_type = "text/plain"; - data = base64_decode(in.substr(header.size())); - } - } + if (data.empty()) { + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + mime_type = "image/bmp"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } - if (data.empty()) { - header = "data:application/gltf-buffer;base64,"; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); - } - } + if (data.empty()) { + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + mime_type = "image/gif"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } - // TODO(syoyo): Allow empty buffer? #229 - if (data.empty()) { - return false; - } + if (data.empty()) { + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + mime_type = "text/plain"; + data = base64_decode(in.substr(header.size())); + } + } - if (checkSize) { - if (data.size() != reqBytes) { - return false; - } - out->resize(reqBytes); - } - else { - out->resize(data.size()); - } - std::copy(data.begin(), data.end(), out->begin()); - return true; - } + if (data.empty()) { + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); + } + } - namespace { - bool GetInt(const json& o, int& val) { + // TODO(syoyo): Allow empty buffer? #229 + if (data.empty()) { + return false; + } + + if (checkSize) { + if (data.size() != reqBytes) { + return false; + } + out->resize(reqBytes); + } else { + out->resize(data.size()); + } + std::copy(data.begin(), data.end(), out->begin()); + return true; +} + +namespace detail { +bool GetInt(const detail::json &o, int &val) { #ifdef TINYGLTF_USE_RAPIDJSON - if (!o.IsDouble()) { - if (o.IsInt()) { - val = o.GetInt(); - return true; - } - else if (o.IsUint()) { - val = static_cast(o.GetUint()); - return true; - } - else if (o.IsInt64()) { - val = static_cast(o.GetInt64()); - return true; - } - else if (o.IsUint64()) { - val = static_cast(o.GetUint64()); - return true; - } - } + if (!o.IsDouble()) { + if (o.IsInt()) { + val = o.GetInt(); + return true; + } else if (o.IsUint()) { + val = static_cast(o.GetUint()); + return true; + } else if (o.IsInt64()) { + val = static_cast(o.GetInt64()); + return true; + } else if (o.IsUint64()) { + val = static_cast(o.GetUint64()); + return true; + } + } - return false; + return false; #else - auto type = o.type(); + auto type = o.type(); - if ((type == json::value_t::number_integer) || - (type == json::value_t::number_unsigned)) { - val = static_cast(o.get()); - return true; - } + if ((type == detail::json::value_t::number_integer) || + (type == detail::json::value_t::number_unsigned)) { + val = static_cast(o.get()); + return true; + } - return false; + return false; #endif - } +} #ifdef TINYGLTF_USE_RAPIDJSON - bool GetDouble(const json& o, double& val) { - if (o.IsDouble()) { - val = o.GetDouble(); - return true; - } +bool GetDouble(const detail::json &o, double &val) { + if (o.IsDouble()) { + val = o.GetDouble(); + return true; + } - return false; - } + return false; +} #endif - bool GetNumber(const json& o, double& val) { +bool GetNumber(const detail::json &o, double &val) { #ifdef TINYGLTF_USE_RAPIDJSON - if (o.IsNumber()) { - val = o.GetDouble(); - return true; - } + if (o.IsNumber()) { + val = o.GetDouble(); + return true; + } - return false; + return false; #else - if (o.is_number()) { - val = o.get(); - return true; - } + if (o.is_number()) { + val = o.get(); + return true; + } - return false; + return false; #endif - } +} - bool GetString(const json& o, std::string& val) { +bool GetString(const detail::json &o, std::string &val) { #ifdef TINYGLTF_USE_RAPIDJSON - if (o.IsString()) { - val = o.GetString(); - return true; - } + if (o.IsString()) { + val = o.GetString(); + return true; + } - return false; + return false; #else - if (o.type() == json::value_t::string) { - val = o.get(); - return true; - } + if (o.type() == detail::json::value_t::string) { + val = o.get(); + return true; + } - return false; + return false; #endif - } +} - bool IsArray(const json& o) { +bool IsArray(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.IsArray(); + return o.IsArray(); #else - return o.is_array(); + return o.is_array(); #endif - } +} - json_const_array_iterator ArrayBegin(const json& o) { +detail::json_const_array_iterator ArrayBegin(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.Begin(); + return o.Begin(); #else - return o.begin(); + return o.begin(); #endif - } +} - json_const_array_iterator ArrayEnd(const json& o) { +detail::json_const_array_iterator ArrayEnd(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.End(); + return o.End(); #else - return o.end(); + return o.end(); #endif - } +} - bool IsObject(const json& o) { +bool IsObject(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.IsObject(); + return o.IsObject(); #else - return o.is_object(); + return o.is_object(); #endif - } +} - json_const_iterator ObjectBegin(const json& o) { +detail::json_const_iterator ObjectBegin(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.MemberBegin(); + return o.MemberBegin(); #else - return o.begin(); + return o.begin(); #endif - } +} - json_const_iterator ObjectEnd(const json& o) { +detail::json_const_iterator ObjectEnd(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.MemberEnd(); + return o.MemberEnd(); #else - return o.end(); + return o.end(); #endif - } +} - // Making this a const char* results in a pointer to a temporary when - // TINYGLTF_USE_RAPIDJSON is off. - std::string GetKey(json_const_iterator& it) { +// Making this a const char* results in a pointer to a temporary when +// TINYGLTF_USE_RAPIDJSON is off. +std::string GetKey(detail::json_const_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - return it->name.GetString(); + return it->name.GetString(); #else - return it.key().c_str(); + return it.key().c_str(); #endif - } +} - bool FindMember(const json& o, const char* member, json_const_iterator& it) { +bool FindMember(const detail::json &o, const char *member, + detail::json_const_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - if (!o.IsObject()) { - return false; - } - it = o.FindMember(member); - return it != o.MemberEnd(); + if (!o.IsObject()) { + return false; + } + it = o.FindMember(member); + return it != o.MemberEnd(); #else - it = o.find(member); - return it != o.end(); + it = o.find(member); + return it != o.end(); #endif - } +} - const json& GetValue(json_const_iterator& it) { +bool FindMember(detail::json &o, const char *member, + detail::json_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - return it->value; + if (!o.IsObject()) { + return false; + } + it = o.FindMember(member); + return it != o.MemberEnd(); #else - return it.value(); + it = o.find(member); + return it != o.end(); #endif - } +} - std::string JsonToString(const json& o, int spacing = -1) { +void Erase(detail::json &o, detail::json_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - using namespace rapidjson; - StringBuffer buffer; - if (spacing == -1) { - Writer writer(buffer); - // TODO: Better error handling. - // https://github.com/syoyo/tinygltf/issues/332 - if (!o.Accept(writer)) { - return "tiny_gltf::JsonToString() failed rapidjson conversion"; - } - } - else { - PrettyWriter writer(buffer); - writer.SetIndent(' ', uint32_t(spacing)); - if (!o.Accept(writer)) { - return "tiny_gltf::JsonToString() failed rapidjson conversion"; - } - } - return buffer.GetString(); + o.EraseMember(it); #else - return o.dump(spacing); + o.erase(it); #endif - } +} - } // namespace - - static bool ParseJsonAsValue(Value* ret, const json& o) { - Value val{}; +bool IsEmpty(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - using rapidjson::Type; - switch (o.GetType()) { - case Type::kObjectType: { - Value::Object value_object; - for (auto it = o.MemberBegin(); it != o.MemberEnd(); ++it) { - Value entry; - ParseJsonAsValue(&entry, it->value); - if (entry.Type() != NULL_TYPE) - value_object.emplace(GetKey(it), std::move(entry)); - } - if (value_object.size() > 0) val = Value(std::move(value_object)); - } break; - case Type::kArrayType: { - Value::Array value_array; - value_array.reserve(o.Size()); - for (auto it = o.Begin(); it != o.End(); ++it) { - Value entry; - ParseJsonAsValue(&entry, *it); - if (entry.Type() != NULL_TYPE) - value_array.emplace_back(std::move(entry)); - } - if (value_array.size() > 0) val = Value(std::move(value_array)); - } break; - case Type::kStringType: - val = Value(std::string(o.GetString())); - break; - case Type::kFalseType: - case Type::kTrueType: - val = Value(o.GetBool()); - break; - case Type::kNumberType: - if (!o.IsDouble()) { - int i = 0; - GetInt(o, i); - val = Value(i); - } - else { - double d = 0.0; - GetDouble(o, d); - val = Value(d); - } - break; - case Type::kNullType: - break; - // all types are covered, so no `case default` - } + return o.ObjectEmpty(); #else - switch (o.type()) { - case json::value_t::object: { - Value::Object value_object; - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) - value_object.emplace(it.key(), std::move(entry)); - } - if (value_object.size() > 0) val = Value(std::move(value_object)); - } break; - case json::value_t::array: { - Value::Array value_array; - value_array.reserve(o.size()); - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) - value_array.emplace_back(std::move(entry)); - } - if (value_array.size() > 0) val = Value(std::move(value_array)); - } break; - case json::value_t::string: - val = Value(o.get()); - break; - case json::value_t::boolean: - val = Value(o.get()); - break; - case json::value_t::number_integer: - case json::value_t::number_unsigned: - val = Value(static_cast(o.get())); - break; - case json::value_t::number_float: - val = Value(o.get()); - break; - case json::value_t::null: - case json::value_t::discarded: - case json::value_t::binary: - // default: - break; - } + return o.empty(); #endif - if (ret) *ret = std::move(val); +} - return val.Type() != NULL_TYPE; - } - - static bool ParseExtrasProperty(Value* ret, const json& o) { - json_const_iterator it; - if (!FindMember(o, "extras", it)) { - return false; - } - - return ParseJsonAsValue(ret, GetValue(it)); - } - - static bool ParseBooleanProperty(bool* ret, std::string* err, const json& o, - const std::string& property, - const bool required, - const std::string& parent_node = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - auto& value = GetValue(it); - - bool isBoolean; - bool boolValue = false; +const detail::json &GetValue(detail::json_const_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - isBoolean = value.IsBool(); - if (isBoolean) { - boolValue = value.GetBool(); - } + return it->value; #else - isBoolean = value.is_boolean(); - if (isBoolean) { - boolValue = value.get(); - } + return it.value(); #endif - if (!isBoolean) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a bool type.\n"; - } - } - return false; - } +} - if (ret) { - (*ret) = boolValue; - } - - return true; - } - - static bool ParseIntegerProperty(int* ret, std::string* err, const json& o, - const std::string& property, - const bool required, - const std::string& parent_node = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - int intValue; - bool isInt = GetInt(GetValue(it), intValue); - if (!isInt) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an integer type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = intValue; - } - - return true; - } - - static bool ParseUnsignedProperty(size_t* ret, std::string* err, const json& o, - const std::string& property, - const bool required, - const std::string& parent_node = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - auto& value = GetValue(it); - - size_t uValue = 0; - bool isUValue; +detail::json &GetValue(detail::json_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON - isUValue = false; - if (value.IsUint()) { - uValue = value.GetUint(); - isUValue = true; - } - else if (value.IsUint64()) { - uValue = value.GetUint64(); - isUValue = true; - } + return it->value; #else - isUValue = value.is_number_unsigned(); - if (isUValue) { - uValue = value.get(); - } + return it.value(); #endif - if (!isUValue) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a positive integer.\n"; - } - } - return false; - } +} - if (ret) { - (*ret) = uValue; - } +std::string JsonToString(const detail::json &o, int spacing = -1) { +#ifdef TINYGLTF_USE_RAPIDJSON + using namespace rapidjson; + StringBuffer buffer; + if (spacing == -1) { + Writer writer(buffer); + // TODO: Better error handling. + // https://github.com/syoyo/tinygltf/issues/332 + if (!o.Accept(writer)) { + return "tiny_gltf::JsonToString() failed rapidjson conversion"; + } + } else { + PrettyWriter writer(buffer); + writer.SetIndent(' ', uint32_t(spacing)); + if (!o.Accept(writer)) { + return "tiny_gltf::JsonToString() failed rapidjson conversion"; + } + } + return buffer.GetString(); +#else + return o.dump(spacing); +#endif +} - return true; - } +} // namespace detail - static bool ParseNumberProperty(double* ret, std::string* err, const json& o, - const std::string& property, - const bool required, - const std::string& parent_node = "") { - json_const_iterator it; +static bool ParseJsonAsValue(Value *ret, const detail::json &o) { + Value val{}; +#ifdef TINYGLTF_USE_RAPIDJSON + using rapidjson::Type; + switch (o.GetType()) { + case Type::kObjectType: { + Value::Object value_object; + for (auto it = o.MemberBegin(); it != o.MemberEnd(); ++it) { + Value entry; + ParseJsonAsValue(&entry, it->value); + if (entry.Type() != NULL_TYPE) + value_object.emplace(detail::GetKey(it), std::move(entry)); + } + if (value_object.size() > 0) val = Value(std::move(value_object)); + } break; + case Type::kArrayType: { + Value::Array value_array; + value_array.reserve(o.Size()); + for (auto it = o.Begin(); it != o.End(); ++it) { + Value entry; + ParseJsonAsValue(&entry, *it); + if (entry.Type() != NULL_TYPE) + value_array.emplace_back(std::move(entry)); + } + if (value_array.size() > 0) val = Value(std::move(value_array)); + } break; + case Type::kStringType: + val = Value(std::string(o.GetString())); + break; + case Type::kFalseType: + case Type::kTrueType: + val = Value(o.GetBool()); + break; + case Type::kNumberType: + if (!o.IsDouble()) { + int i = 0; + detail::GetInt(o, i); + val = Value(i); + } else { + double d = 0.0; + detail::GetDouble(o, d); + val = Value(d); + } + break; + case Type::kNullType: + break; + // all types are covered, so no `case default` + } +#else + switch (o.type()) { + case detail::json::value_t::object: { + Value::Object value_object; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) + value_object.emplace(it.key(), std::move(entry)); + } + if (value_object.size() > 0) val = Value(std::move(value_object)); + } break; + case detail::json::value_t::array: { + Value::Array value_array; + value_array.reserve(o.size()); + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) + value_array.emplace_back(std::move(entry)); + } + if (value_array.size() > 0) val = Value(std::move(value_array)); + } break; + case detail::json::value_t::string: + val = Value(o.get()); + break; + case detail::json::value_t::boolean: + val = Value(o.get()); + break; + case detail::json::value_t::number_integer: + case detail::json::value_t::number_unsigned: + val = Value(static_cast(o.get())); + break; + case detail::json::value_t::number_float: + val = Value(o.get()); + break; + case detail::json::value_t::null: + case detail::json::value_t::discarded: + case detail::json::value_t::binary: + // default: + break; + } +#endif + const bool isNotNull = val.Type() != NULL_TYPE; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } + if (ret) *ret = std::move(val); - double numberValue; - bool isNumber = GetNumber(GetValue(it), numberValue); + return isNotNull; +} - if (!isNumber) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number type.\n"; - } - } - return false; - } +static bool ParseExtrasProperty(Value *ret, const detail::json &o) { + detail::json_const_iterator it; + if (!detail::FindMember(o, "extras", it)) { + return false; + } - if (ret) { - (*ret) = numberValue; - } + return ParseJsonAsValue(ret, detail::GetValue(it)); +} - return true; - } +static bool ParseBooleanProperty(bool *ret, std::string *err, + const detail::json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - static bool ParseNumberArrayProperty(std::vector* ret, std::string* err, - const json& o, const std::string& property, - bool required, - const std::string& parent_node = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } + auto &value = detail::GetValue(it); - if (!IsArray(GetValue(it))) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } + bool isBoolean; + bool boolValue = false; +#ifdef TINYGLTF_USE_RAPIDJSON + isBoolean = value.IsBool(); + if (isBoolean) { + boolValue = value.GetBool(); + } +#else + isBoolean = value.is_boolean(); + if (isBoolean) { + boolValue = value.get(); + } +#endif + if (!isBoolean) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a bool type.\n"; + } + } + return false; + } - ret->clear(); - auto end = ArrayEnd(GetValue(it)); - for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) { - double numberValue; - const bool isNumber = GetNumber(*i, numberValue); - if (!isNumber) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number.\n"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - ret->push_back(numberValue); - } + if (ret) { + (*ret) = boolValue; + } - return true; - } + return true; +} - static bool ParseIntegerArrayProperty(std::vector* ret, std::string* err, - const json& o, - const std::string& property, - bool required, - const std::string& parent_node = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } +static bool ParseIntegerProperty(int *ret, std::string *err, + const detail::json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - if (!IsArray(GetValue(it))) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } + int intValue; + bool isInt = detail::GetInt(detail::GetValue(it), intValue); + if (!isInt) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + } + } + return false; + } - ret->clear(); - auto end = ArrayEnd(GetValue(it)); - for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) { - int numberValue; - bool isNumber = GetInt(*i, numberValue); - if (!isNumber) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an integer type.\n"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - ret->push_back(numberValue); - } + if (ret) { + (*ret) = intValue; + } - return true; - } + return true; +} - static bool ParseStringProperty( - std::string* ret, std::string* err, const json& o, - const std::string& property, bool required, - const std::string& parent_node = std::string()) { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (parent_node.empty()) { - (*err) += ".\n"; - } - else { - (*err) += " in `" + parent_node + "'.\n"; - } - } - } - return false; - } +static bool ParseUnsignedProperty(size_t *ret, std::string *err, + const detail::json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - std::string strValue; - if (!GetString(GetValue(it), strValue)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a string type.\n"; - } - } - return false; - } + auto &value = detail::GetValue(it); - if (ret) { - (*ret) = std::move(strValue); - } + size_t uValue = 0; + bool isUValue; +#ifdef TINYGLTF_USE_RAPIDJSON + isUValue = false; + if (value.IsUint()) { + uValue = value.GetUint(); + isUValue = true; + } else if (value.IsUint64()) { + uValue = value.GetUint64(); + isUValue = true; + } +#else + isUValue = value.is_number_unsigned(); + if (isUValue) { + uValue = value.get(); + } +#endif + if (!isUValue) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a positive integer.\n"; + } + } + return false; + } - return true; - } + if (ret) { + (*ret) = uValue; + } - static bool ParseStringIntegerProperty(std::map* ret, - std::string* err, const json& o, - const std::string& property, - bool required, - const std::string& parent = "") { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - if (!parent.empty()) { - (*err) += - "'" + property + "' property is missing in " + parent + ".\n"; - } - else { - (*err) += "'" + property + "' property is missing.\n"; - } - } - } - return false; - } + return true; +} - const json& dict = GetValue(it); +static bool ParseNumberProperty(double *ret, std::string *err, + const detail::json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; - // Make sure we are dealing with an object / dictionary. - if (!IsObject(dict)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an object.\n"; - } - } - return false; - } + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - ret->clear(); + double numberValue; + bool isNumber = detail::GetNumber(detail::GetValue(it), numberValue); - json_const_iterator dictIt(ObjectBegin(dict)); - json_const_iterator dictItEnd(ObjectEnd(dict)); + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number type.\n"; + } + } + return false; + } - for (; dictIt != dictItEnd; ++dictIt) { - int intVal; - if (!GetInt(GetValue(dictIt), intVal)) { - if (required) { - if (err) { - (*err) += "'" + property + "' value is not an integer type.\n"; - } - } - return false; - } + if (ret) { + (*ret) = numberValue; + } - // Insert into the list. - (*ret)[GetKey(dictIt)] = intVal; - } - return true; - } + return true; +} - static bool ParseJSONProperty(std::map* ret, - std::string* err, const json& o, - const std::string& property, bool required) { - json_const_iterator it; - if (!FindMember(o, property.c_str(), it)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing. \n'"; - } - } - return false; - } +static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, + const detail::json &o, + const std::string &property, bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - const json& obj = GetValue(it); + if (!detail::IsArray(detail::GetValue(it))) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - if (!IsObject(obj)) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a JSON object.\n"; - } - } - return false; - } + ret->clear(); + auto end = detail::ArrayEnd(detail::GetValue(it)); + for (auto i = detail::ArrayBegin(detail::GetValue(it)); i != end; ++i) { + double numberValue; + const bool isNumber = detail::GetNumber(*i, numberValue); + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(numberValue); + } - ret->clear(); + return true; +} - json_const_iterator it2(ObjectBegin(obj)); - json_const_iterator itEnd(ObjectEnd(obj)); - for (; it2 != itEnd; ++it2) { - double numVal; - if (GetNumber(GetValue(it2), numVal)) - ret->emplace(std::string(GetKey(it2)), numVal); - } +static bool ParseIntegerArrayProperty(std::vector *ret, std::string *err, + const detail::json &o, + const std::string &property, + bool required, + const std::string &parent_node = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - return true; - } + if (!detail::IsArray(detail::GetValue(it))) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } - static bool ParseParameterProperty(Parameter* param, std::string* err, - const json& o, const std::string& prop, - bool required) { - // A parameter value can either be a string or an array of either a boolean or - // a number. Booleans of any kind aren't supported here. Granted, it - // complicates the Parameter structure and breaks it semantically in the sense - // that the client probably works off the assumption that if the string is - // empty the vector is used, etc. Would a tagged union work? - if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { - // Found string property. - return true; - } - else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, - false)) { - // Found a number array. - return true; - } - else if (ParseNumberProperty(¶m->number_value, err, o, prop, false)) { - return param->has_number_value = true; - } - else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, - false)) { - return true; - } - else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { - return true; - } - else { - if (required) { - if (err) { - (*err) += "parameter must be a string or number / number array.\n"; - } - } - return false; - } - } + ret->clear(); + auto end = detail::ArrayEnd(detail::GetValue(it)); + for (auto i = detail::ArrayBegin(detail::GetValue(it)); i != end; ++i) { + int numberValue; + bool isNumber = detail::GetInt(*i, numberValue); + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(numberValue); + } - static bool ParseExtensionsProperty(ExtensionMap* ret, std::string* err, - const json& o) { - (void)err; + return true; +} - json_const_iterator it; - if (!FindMember(o, "extensions", it)) { - return false; - } +static bool ParseStringProperty( + std::string *ret, std::string *err, const detail::json &o, + const std::string &property, bool required, + const std::string &parent_node = std::string()) { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (parent_node.empty()) { + (*err) += ".\n"; + } else { + (*err) += " in `" + parent_node + "'.\n"; + } + } + } + return false; + } - auto& obj = GetValue(it); - if (!IsObject(obj)) { - return false; - } - ExtensionMap extensions; - json_const_iterator extIt = ObjectBegin(obj); // it.value().begin(); - json_const_iterator extEnd = ObjectEnd(obj); - for (; extIt != extEnd; ++extIt) { - auto& itObj = GetValue(extIt); - if (!IsObject(itObj)) continue; - std::string key(GetKey(extIt)); - if (!ParseJsonAsValue(&extensions[key], itObj)) { - if (!key.empty()) { - // create empty object so that an extension object is still of type - // object - extensions[key] = Value{ Value::Object{} }; - } - } - } - if (ret) { - (*ret) = std::move(extensions); - } - return true; - } + std::string strValue; + if (!detail::GetString(detail::GetValue(it), strValue)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a string type.\n"; + } + } + return false; + } - static bool ParseAsset(Asset* asset, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); - ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); - ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); - ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset"); + if (ret) { + (*ret) = std::move(strValue); + } - ParseExtensionsProperty(&asset->extensions, err, o); + return true; +} - // Unity exporter version is added as extra here - ParseExtrasProperty(&(asset->extras), o); +static bool ParseStringIntegerProperty(std::map *ret, + std::string *err, const detail::json &o, + const std::string &property, + bool required, + const std::string &parent = "") { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + if (!parent.empty()) { + (*err) += + "'" + property + "' property is missing in " + parent + ".\n"; + } else { + (*err) += "'" + property + "' property is missing.\n"; + } + } + } + return false; + } - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - asset->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - asset->extras_json_string = JsonToString(GetValue(it)); - } - } - } + const detail::json &dict = detail::GetValue(it); - return true; - } + // Make sure we are dealing with an object / dictionary. + if (!detail::IsObject(dict)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an object.\n"; + } + } + return false; + } - static bool ParseImage(Image* image, const int image_idx, std::string* err, - std::string* warn, const json& o, - bool store_original_json_for_extras_and_extensions, - const std::string& basedir, FsCallbacks* fs, - LoadImageDataFunction* LoadImageData = nullptr, - void* load_image_user_data = nullptr) { - // A glTF image must either reference a bufferView or an image uri + ret->clear(); - // schema says oneOf [`bufferView`, `uri`] - // TODO(syoyo): Check the type of each parameters. - json_const_iterator it; - bool hasBufferView = FindMember(o, "bufferView", it); - bool hasURI = FindMember(o, "uri", it); + detail::json_const_iterator dictIt(detail::ObjectBegin(dict)); + detail::json_const_iterator dictItEnd(detail::ObjectEnd(dict)); - ParseStringProperty(&image->name, err, o, "name", false); + for (; dictIt != dictItEnd; ++dictIt) { + int intVal; + if (!detail::GetInt(detail::GetValue(dictIt), intVal)) { + if (required) { + if (err) { + (*err) += "'" + property + "' value is not an integer type.\n"; + } + } + return false; + } - if (hasBufferView && hasURI) { - // Should not both defined. - if (err) { - (*err) += - "Only one of `bufferView` or `uri` should be defined, but both are " - "defined for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + "\"\n"; - } - return false; - } + // Insert into the list. + (*ret)[detail::GetKey(dictIt)] = intVal; + } + return true; +} - if (!hasBufferView && !hasURI) { - if (err) { - (*err) += "Neither required `bufferView` nor `uri` defined for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } +static bool ParseJSONProperty(std::map *ret, + std::string *err, const detail::json &o, + const std::string &property, bool required) { + detail::json_const_iterator it; + if (!detail::FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing. \n'"; + } + } + return false; + } - ParseExtensionsProperty(&image->extensions, err, o); - ParseExtrasProperty(&image->extras, o); + const detail::json &obj = detail::GetValue(it); - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator eit; - if (FindMember(o, "extensions", eit)) { - image->extensions_json_string = JsonToString(GetValue(eit)); - } - } - { - json_const_iterator eit; - if (FindMember(o, "extras", eit)) { - image->extras_json_string = JsonToString(GetValue(eit)); - } - } - } + if (!detail::IsObject(obj)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a JSON object.\n"; + } + } + return false; + } - if (hasBufferView) { - int bufferView = -1; - if (!ParseIntegerProperty(&bufferView, err, o, "bufferView", true)) { - if (err) { - (*err) += "Failed to parse `bufferView` for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } + ret->clear(); - std::string mime_type; - ParseStringProperty(&mime_type, err, o, "mimeType", false); + detail::json_const_iterator it2(detail::ObjectBegin(obj)); + detail::json_const_iterator itEnd(detail::ObjectEnd(obj)); + for (; it2 != itEnd; ++it2) { + double numVal; + if (detail::GetNumber(detail::GetValue(it2), numVal)) + ret->emplace(std::string(detail::GetKey(it2)), numVal); + } - int width = 0; - ParseIntegerProperty(&width, err, o, "width", false); + return true; +} - int height = 0; - ParseIntegerProperty(&height, err, o, "height", false); +static bool ParseParameterProperty(Parameter *param, std::string *err, + const detail::json &o, + const std::string &prop, bool required) { + // A parameter value can either be a string or an array of either a boolean or + // a number. Booleans of any kind aren't supported here. Granted, it + // complicates the Parameter structure and breaks it semantically in the sense + // that the client probably works off the assumption that if the string is + // empty the vector is used, etc. Would a tagged union work? + if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { + // Found string property. + return true; + } else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, + false)) { + // Found a number array. + return true; + } else if (ParseNumberProperty(¶m->number_value, err, o, prop, false)) { + param->has_number_value = true; + return true; + } else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, + false)) { + return true; + } else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { + return true; + } else { + if (required) { + if (err) { + (*err) += "parameter must be a string or number / number array.\n"; + } + } + return false; + } +} - // Just only save some information here. Loading actual image data from - // bufferView is done after this `ParseImage` function. - image->bufferView = bufferView; - image->mimeType = mime_type; - image->width = width; - image->height = height; +static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, + const detail::json &o) { + (void)err; - return true; - } + detail::json_const_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + return false; + } - // Parse URI & Load image data. + auto &obj = detail::GetValue(it); + if (!detail::IsObject(obj)) { + return false; + } + ExtensionMap extensions; + detail::json_const_iterator extIt = + detail::ObjectBegin(obj); // it.value().begin(); + detail::json_const_iterator extEnd = detail::ObjectEnd(obj); + for (; extIt != extEnd; ++extIt) { + auto &itObj = detail::GetValue(extIt); + if (!detail::IsObject(itObj)) continue; + std::string key(detail::GetKey(extIt)); + if (!ParseJsonAsValue(&extensions[key], itObj)) { + if (!key.empty()) { + // create empty object so that an extension object is still of type + // object + extensions[key] = Value{Value::Object{}}; + } + } + } + if (ret) { + (*ret) = std::move(extensions); + } + return true; +} - std::string uri; - std::string tmp_err; - if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { - if (err) { - (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) + - "] name = \"" + image->name + "\".\n"; - } - return false; - } +template +static bool ParseExtrasAndExtensions(GltfType *target, std::string *err, + const detail::json &o, + bool store_json_strings) { + ParseExtensionsProperty(&target->extensions, err, o); + ParseExtrasProperty(&target->extras, o); - std::vector img; + if (store_json_strings) { + { + detail::json_const_iterator it; + if (detail::FindMember(o, "extensions", it)) { + target->extensions_json_string = + detail::JsonToString(detail::GetValue(it)); + } + } + { + detail::json_const_iterator it; + if (detail::FindMember(o, "extras", it)) { + target->extras_json_string = detail::JsonToString(detail::GetValue(it)); + } + } + } + return true; +} - if (IsDataURI(uri)) { - if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri' for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "]\n"; - } - return false; - } - } - else { - // Assume external file - // Keep texture path (for textures that cannot be decoded) - image->uri = uri; +static bool ParseAsset(Asset *asset, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); + ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); + ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); + ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset"); + + ParseExtrasAndExtensions(asset, err, o, + store_original_json_for_extras_and_extensions); + return true; +} + +static bool ParseImage(Image *image, const int image_idx, std::string *err, + std::string *warn, const detail::json &o, + bool store_original_json_for_extras_and_extensions, + const std::string &basedir, const size_t max_file_size, + FsCallbacks *fs, const URICallbacks *uri_cb, + const LoadImageDataFunction& LoadImageData = nullptr, + void *load_image_user_data = nullptr) { + // A glTF image must either reference a bufferView or an image uri + + // schema says oneOf [`bufferView`, `uri`] + // TODO(syoyo): Check the type of each parameters. + detail::json_const_iterator it; + bool hasBufferView = detail::FindMember(o, "bufferView", it); + bool hasURI = detail::FindMember(o, "uri", it); + + ParseStringProperty(&image->name, err, o, "name", false); + + if (hasBufferView && hasURI) { + // Should not both defined. + if (err) { + (*err) += + "Only one of `bufferView` or `uri` should be defined, but both are " + "defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\"\n"; + } + return false; + } + + if (!hasBufferView && !hasURI) { + if (err) { + (*err) += "Neither required `bufferView` nor `uri` defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + ParseExtrasAndExtensions(image, err, o, + store_original_json_for_extras_and_extensions); + + if (hasBufferView) { + int bufferView = -1; + if (!ParseIntegerProperty(&bufferView, err, o, "bufferView", true)) { + if (err) { + (*err) += "Failed to parse `bufferView` for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + std::string mime_type; + ParseStringProperty(&mime_type, err, o, "mimeType", false); + + int width = 0; + ParseIntegerProperty(&width, err, o, "width", false); + + int height = 0; + ParseIntegerProperty(&height, err, o, "height", false); + + // Just only save some information here. Loading actual image data from + // bufferView is done after this `ParseImage` function. + image->bufferView = bufferView; + image->mimeType = mime_type; + image->width = width; + image->height = height; + + return true; + } + + // Parse URI & Load image data. + + std::string uri; + std::string tmp_err; + if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { + if (err) { + (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\".\n"; + } + return false; + } + + std::vector img; + + if (IsDataURI(uri)) { + if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri' for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } else { + // Assume external file + // Unconditionally keep the external URI of the image + image->uri = uri; #ifdef TINYGLTF_NO_EXTERNAL_IMAGE - return true; -#endif - std::string decoded_uri = dlib::urldecode(uri); - if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir, - /* required */ false, /* required bytes */ 0, - /* checksize */ false, fs)) { - if (warn) { - (*warn) += "Failed to load external 'uri' for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "]\n"; - } - // If the image cannot be loaded, keep uri as image->uri. - return true; - } - - if (img.empty()) { - if (warn) { - (*warn) += "Image data is empty for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "] \n"; - } - return false; - } - } - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(0), - static_cast(img.size()), load_image_user_data); - } - - static bool ParseTexture(Texture* texture, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions, - const std::string& basedir) { - (void)basedir; - int sampler = -1; - int source = -1; - ParseIntegerProperty(&sampler, err, o, "sampler", false); - - ParseIntegerProperty(&source, err, o, "source", false); - - texture->sampler = sampler; - texture->source = source; - - ParseExtensionsProperty(&texture->extensions, err, o); - ParseExtrasProperty(&texture->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - texture->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - texture->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - ParseStringProperty(&texture->name, err, o, "name", false); - - return true; - } - - static bool ParseTextureInfo( - TextureInfo* texinfo, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (texinfo == nullptr) { - return false; - } - - if (!ParseIntegerProperty(&texinfo->index, err, o, "index", - /* required */ true, "TextureInfo")) { - return false; - } - - ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); - - ParseExtensionsProperty(&texinfo->extensions, err, o); - ParseExtrasProperty(&texinfo->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - texinfo->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseNormalTextureInfo( - NormalTextureInfo* texinfo, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (texinfo == nullptr) { - return false; - } - - if (!ParseIntegerProperty(&texinfo->index, err, o, "index", - /* required */ true, "NormalTextureInfo")) { - return false; - } - - ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); - ParseNumberProperty(&texinfo->scale, err, o, "scale", false); - - ParseExtensionsProperty(&texinfo->extensions, err, o); - ParseExtrasProperty(&texinfo->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - texinfo->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseOcclusionTextureInfo( - OcclusionTextureInfo* texinfo, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (texinfo == nullptr) { - return false; - } - - if (!ParseIntegerProperty(&texinfo->index, err, o, "index", - /* required */ true, "NormalTextureInfo")) { - return false; - } - - ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); - ParseNumberProperty(&texinfo->strength, err, o, "strength", false); - - ParseExtensionsProperty(&texinfo->extensions, err, o); - ParseExtrasProperty(&texinfo->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - texinfo->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseBuffer(Buffer* buffer, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions, - FsCallbacks* fs, const std::string& basedir, - bool is_binary = false, - const unsigned char* bin_data = nullptr, - size_t bin_size = 0) { - size_t byteLength; - if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, - "Buffer")) { - return false; - } - - // In glTF 2.0, uri is not mandatory anymore - buffer->uri.clear(); - ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); - - // having an empty uri for a non embedded image should not be valid - if (!is_binary && buffer->uri.empty()) { - if (err) { - (*err) += "'uri' is missing from non binary glTF file buffer.\n"; - } - } - - json_const_iterator type; - if (FindMember(o, "type", type)) { - std::string typeStr; - if (GetString(GetValue(type), typeStr)) { - if (typeStr.compare("arraybuffer") == 0) { - // buffer.type = "arraybuffer"; - } - } - } - - if (is_binary) { - // Still binary glTF accepts external dataURI. - if (!buffer->uri.empty()) { - // First try embedded data URI. - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, - true)) { - if (err) { - (*err) += - "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } - else { - // External .bin file. - std::string decoded_uri = dlib::urldecode(buffer->uri); - if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, - decoded_uri, basedir, /* required */ true, - byteLength, /* checkSize */ true, fs)) { - return false; - } - } - } - else { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == nullptr)) { - if (err) { - (*err) += "Invalid binary data in `Buffer'.\n"; - } - return false; - } - - if (byteLength > bin_size) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteLength'. Must be equal or less than binary size: " - "`byteLength' = " - << byteLength << ", binary size = " << bin_size << std::endl; - (*err) += ss.str(); - } - return false; - } - - // Read buffer data - buffer->data.resize(static_cast(byteLength)); - memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); - } - - } - else { - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, - true)) { - if (err) { - (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } - else { - // Assume external .bin file. - std::string decoded_uri = dlib::urldecode(buffer->uri); - if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri, - basedir, /* required */ true, byteLength, - /* checkSize */ true, fs)) { - return false; - } - } - } - - ParseStringProperty(&buffer->name, err, o, "name", false); - - ParseExtensionsProperty(&buffer->extensions, err, o); - ParseExtrasProperty(&buffer->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - buffer->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - buffer->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseBufferView( - BufferView* bufferView, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - int buffer = -1; - if (!ParseIntegerProperty(&buffer, err, o, "buffer", true, "BufferView")) { - return false; - } - - size_t byteOffset = 0; - ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false); - - size_t byteLength = 1; - if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, - "BufferView")) { - return false; - } - - size_t byteStride = 0; - if (!ParseUnsignedProperty(&byteStride, err, o, "byteStride", false)) { - // Spec says: When byteStride of referenced bufferView is not defined, it - // means that accessor elements are tightly packed, i.e., effective stride - // equals the size of the element. - // We cannot determine the actual byteStride until Accessor are parsed, thus - // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) - byteStride = 0; - } - - if ((byteStride > 252) || ((byteStride % 4) != 0)) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " - "4 : " - << byteStride << std::endl; - - (*err) += ss.str(); - } - return false; - } - - int target = 0; - ParseIntegerProperty(&target, err, o, "target", false); - if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) || - (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { - // OK - } - else { - target = 0; - } - bufferView->target = target; - - ParseStringProperty(&bufferView->name, err, o, "name", false); - - ParseExtensionsProperty(&bufferView->extensions, err, o); - ParseExtrasProperty(&bufferView->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - bufferView->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - bufferView->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - bufferView->buffer = buffer; - bufferView->byteOffset = byteOffset; - bufferView->byteLength = byteLength; - bufferView->byteStride = byteStride; - return true; - } - - static bool ParseSparseAccessor(Accessor* accessor, std::string* err, - const json& o) { - accessor->sparse.isSparse = true; - - int count = 0; - if (!ParseIntegerProperty(&count, err, o, "count", true, "SparseAccessor")) { - return false; - } - - json_const_iterator indices_iterator; - json_const_iterator values_iterator; - if (!FindMember(o, "indices", indices_iterator)) { - (*err) = "the sparse object of this accessor doesn't have indices"; - return false; - } - - if (!FindMember(o, "values", values_iterator)) { - (*err) = "the sparse object ob ths accessor doesn't have values"; - return false; - } - - const json& indices_obj = GetValue(indices_iterator); - const json& values_obj = GetValue(values_iterator); - - int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0; - if (!ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView", - true, "SparseAccessor")) { - return false; - } - ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset", - false); - if (!ParseIntegerProperty(&component_type, err, indices_obj, "componentType", - true, "SparseAccessor")) { - return false; - } - - int values_buffer_view = 0, values_byte_offset = 0; - if (!ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView", - true, "SparseAccessor")) { - return false; - } - ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset", - false); - - accessor->sparse.count = count; - accessor->sparse.indices.bufferView = indices_buffer_view; - accessor->sparse.indices.byteOffset = indices_byte_offset; - accessor->sparse.indices.componentType = component_type; - accessor->sparse.values.bufferView = values_buffer_view; - accessor->sparse.values.byteOffset = values_byte_offset; - - return true; - } - - static bool ParseAccessor(Accessor* accessor, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - int bufferView = -1; - ParseIntegerProperty(&bufferView, err, o, "bufferView", false, "Accessor"); - - size_t byteOffset = 0; - ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); - - bool normalized = false; - ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); - - size_t componentType = 0; - if (!ParseUnsignedProperty(&componentType, err, o, "componentType", true, - "Accessor")) { - return false; - } - - size_t count = 0; - if (!ParseUnsignedProperty(&count, err, o, "count", true, "Accessor")) { - return false; - } - - std::string type; - if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { - return false; - } - - if (type.compare("SCALAR") == 0) { - accessor->type = TINYGLTF_TYPE_SCALAR; - } - else if (type.compare("VEC2") == 0) { - accessor->type = TINYGLTF_TYPE_VEC2; - } - else if (type.compare("VEC3") == 0) { - accessor->type = TINYGLTF_TYPE_VEC3; - } - else if (type.compare("VEC4") == 0) { - accessor->type = TINYGLTF_TYPE_VEC4; - } - else if (type.compare("MAT2") == 0) { - accessor->type = TINYGLTF_TYPE_MAT2; - } - else if (type.compare("MAT3") == 0) { - accessor->type = TINYGLTF_TYPE_MAT3; - } - else if (type.compare("MAT4") == 0) { - accessor->type = TINYGLTF_TYPE_MAT4; - } - else { - std::stringstream ss; - ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - - ParseStringProperty(&accessor->name, err, o, "name", false); - - accessor->minValues.clear(); - accessor->maxValues.clear(); - ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, - "Accessor"); - - ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, - "Accessor"); - - accessor->count = count; - accessor->bufferView = bufferView; - accessor->byteOffset = byteOffset; - accessor->normalized = normalized; - { - if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE && - componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { - // OK - accessor->componentType = int(componentType); - } - else { - std::stringstream ss; - ss << "Invalid `componentType` in accessor. Got " << componentType - << "\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - } - - ParseExtensionsProperty(&(accessor->extensions), err, o); - ParseExtrasProperty(&(accessor->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - accessor->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - accessor->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - // check if accessor has a "sparse" object: - json_const_iterator iterator; - if (FindMember(o, "sparse", iterator)) { - // here this accessor has a "sparse" subobject - return ParseSparseAccessor(accessor, err, GetValue(iterator)); - } - - return true; - } - -#ifdef TINYGLTF_ENABLE_DRACO - - static void DecodeIndexBuffer(draco::Mesh* mesh, size_t componentSize, - std::vector& outBuffer) { - if (componentSize == 4) { - assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize); - memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0], - outBuffer.size()); - } - else { - size_t faceStride = componentSize * 3; - for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) { - const draco::Mesh::Face& face = mesh->face(f); - if (componentSize == 2) { - uint16_t indices[3] = { (uint16_t)face[0].value(), - (uint16_t)face[1].value(), - (uint16_t)face[2].value() }; - memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], - faceStride); - } - else { - uint8_t indices[3] = { (uint8_t)face[0].value(), - (uint8_t)face[1].value(), - (uint8_t)face[2].value() }; - memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], - faceStride); - } - } - } - } - - template - static bool GetAttributeForAllPoints(draco::Mesh* mesh, - const draco::PointAttribute* pAttribute, - std::vector& outBuffer) { - size_t byteOffset = 0; - T values[4] = { 0, 0, 0, 0 }; - for (draco::PointIndex i(0); i < mesh->num_points(); ++i) { - const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i); - if (!pAttribute->ConvertValue(val_index, pAttribute->num_components(), - values)) - return false; - - memcpy(outBuffer.data() + byteOffset, &values[0], - sizeof(T) * pAttribute->num_components()); - byteOffset += sizeof(T) * pAttribute->num_components(); - } - - return true; - } - - static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh* mesh, - const draco::PointAttribute* pAttribute, - std::vector& outBuffer) { - bool decodeResult = false; - switch (componentType) { - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_BYTE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_SHORT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_INT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_FLOAT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_DOUBLE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - default: - return false; - } - - return decodeResult; - } - - static bool ParseDracoExtension(Primitive* primitive, Model* model, - std::string* err, - const Value& dracoExtensionValue) { - (void)err; - auto bufferViewValue = dracoExtensionValue.Get("bufferView"); - if (!bufferViewValue.IsInt()) return false; - auto attributesValue = dracoExtensionValue.Get("attributes"); - if (!attributesValue.IsObject()) return false; - - auto attributesObject = attributesValue.Get(); - int bufferView = bufferViewValue.Get(); - - BufferView& view = model->bufferViews[bufferView]; - Buffer& buffer = model->buffers[view.buffer]; - // BufferView has already been decoded - if (view.dracoDecoded) return true; - view.dracoDecoded = true; - - const char* bufferViewData = - reinterpret_cast(buffer.data.data() + view.byteOffset); - size_t bufferViewSize = view.byteLength; - - // decode draco - draco::DecoderBuffer decoderBuffer; - decoderBuffer.Init(bufferViewData, bufferViewSize); - draco::Decoder decoder; - auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); - if (!decodeResult.ok()) { - return false; - } - const std::unique_ptr& mesh = decodeResult.value(); - - // create new bufferView for indices - if (primitive->indices >= 0) { - int32_t componentSize = GetComponentSizeInBytes( - model->accessors[primitive->indices].componentType); - Buffer decodedIndexBuffer; - decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize); - - DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data); - - model->buffers.emplace_back(std::move(decodedIndexBuffer)); - - BufferView decodedIndexBufferView; - decodedIndexBufferView.buffer = int(model->buffers.size() - 1); - decodedIndexBufferView.byteLength = - int(mesh->num_faces() * 3 * componentSize); - decodedIndexBufferView.byteOffset = 0; - decodedIndexBufferView.byteStride = 0; - decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; - model->bufferViews.emplace_back(std::move(decodedIndexBufferView)); - - model->accessors[primitive->indices].bufferView = - int(model->bufferViews.size() - 1); - model->accessors[primitive->indices].count = int(mesh->num_faces() * 3); - } - - for (const auto& attribute : attributesObject) { - if (!attribute.second.IsInt()) return false; - auto primitiveAttribute = primitive->attributes.find(attribute.first); - if (primitiveAttribute == primitive->attributes.end()) return false; - - int dracoAttributeIndex = attribute.second.Get(); - const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); - const auto componentType = - model->accessors[primitiveAttribute->second].componentType; - - // Create a new buffer for this decoded buffer - Buffer decodedBuffer; - size_t bufferSize = mesh->num_points() * pAttribute->num_components() * - GetComponentSizeInBytes(componentType); - decodedBuffer.data.resize(bufferSize); - - if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute, - decodedBuffer.data)) - return false; - - model->buffers.emplace_back(std::move(decodedBuffer)); - - BufferView decodedBufferView; - decodedBufferView.buffer = int(model->buffers.size() - 1); - decodedBufferView.byteLength = bufferSize; - decodedBufferView.byteOffset = pAttribute->byte_offset(); - decodedBufferView.byteStride = pAttribute->byte_stride(); - decodedBufferView.target = primitive->indices >= 0 - ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER - : TINYGLTF_TARGET_ARRAY_BUFFER; - model->bufferViews.emplace_back(std::move(decodedBufferView)); - - model->accessors[primitiveAttribute->second].bufferView = - int(model->bufferViews.size() - 1); - model->accessors[primitiveAttribute->second].count = - int(mesh->num_points()); - } - - return true; - } -#endif - - static bool ParsePrimitive(Primitive* primitive, Model* model, std::string* err, - const json& o, - bool store_original_json_for_extras_and_extensions) { - int material = -1; - ParseIntegerProperty(&material, err, o, "material", false); - primitive->material = material; - - int mode = TINYGLTF_MODE_TRIANGLES; - ParseIntegerProperty(&mode, err, o, "mode", false); - primitive->mode = mode; // Why only triangled were supported ? - - int indices = -1; - ParseIntegerProperty(&indices, err, o, "indices", false); - primitive->indices = indices; - if (!ParseStringIntegerProperty(&primitive->attributes, err, o, "attributes", - true, "Primitive")) { - return false; - } - - // Look for morph targets - json_const_iterator targetsObject; - if (FindMember(o, "targets", targetsObject) && - IsArray(GetValue(targetsObject))) { - auto targetsObjectEnd = ArrayEnd(GetValue(targetsObject)); - for (json_const_array_iterator i = ArrayBegin(GetValue(targetsObject)); - i != targetsObjectEnd; ++i) { - std::map targetAttribues; - - const json& dict = *i; - if (IsObject(dict)) { - json_const_iterator dictIt(ObjectBegin(dict)); - json_const_iterator dictItEnd(ObjectEnd(dict)); - - for (; dictIt != dictItEnd; ++dictIt) { - int iVal; - if (GetInt(GetValue(dictIt), iVal)) - targetAttribues[GetKey(dictIt)] = iVal; - } - primitive->targets.emplace_back(std::move(targetAttribues)); - } - } - } - - ParseExtrasProperty(&(primitive->extras), o); - ParseExtensionsProperty(&primitive->extensions, err, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - primitive->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - primitive->extras_json_string = JsonToString(GetValue(it)); - } - } - } - -#ifdef TINYGLTF_ENABLE_DRACO - auto dracoExtension = - primitive->extensions.find("KHR_draco_mesh_compression"); - if (dracoExtension != primitive->extensions.end()) { - ParseDracoExtension(primitive, model, err, dracoExtension->second); - } + return true; #else - (void)model; + std::string decoded_uri; + if (!uri_cb->decode(uri, &decoded_uri, uri_cb->user_data)) { + if (warn) { + (*warn) += "Failed to decode 'uri' for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + + // Image loading failure is not critical to overall gltf loading. + return true; + } + + if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir, + /* required */ false, /* required bytes */ 0, + /* checksize */ false, + /* max file size */ max_file_size, fs)) { + if (warn) { + (*warn) += "Failed to load external 'uri' for image[" + + std::to_string(image_idx) + "] name = \"" + decoded_uri + + "\"\n"; + } + // If the image cannot be loaded, keep uri as image->uri. + return true; + } + + if (img.empty()) { + if (warn) { + (*warn) += "Image data is empty for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\" \n"; + } + return false; + } +#endif + } + + if (LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + + return LoadImageData(image, image_idx, err, warn, 0, 0, &img.at(0), + static_cast(img.size()), load_image_user_data); +} + +static bool ParseTexture(Texture *texture, std::string *err, + const detail::json &o, + bool store_original_json_for_extras_and_extensions, + const std::string &basedir) { + (void)basedir; + int sampler = -1; + int source = -1; + ParseIntegerProperty(&sampler, err, o, "sampler", false); + + ParseIntegerProperty(&source, err, o, "source", false); + + texture->sampler = sampler; + texture->source = source; + + ParseExtrasAndExtensions(texture, err, o, + store_original_json_for_extras_and_extensions); + + ParseStringProperty(&texture->name, err, o, "name", false); + + return true; +} + +static bool ParseTextureInfo( + TextureInfo *texinfo, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "TextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseNormalTextureInfo( + NormalTextureInfo *texinfo, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "NormalTextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + ParseNumberProperty(&texinfo->scale, err, o, "scale", false); + + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseOcclusionTextureInfo( + OcclusionTextureInfo *texinfo, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "NormalTextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + ParseNumberProperty(&texinfo->strength, err, o, "strength", false); + + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions, + FsCallbacks *fs, const URICallbacks *uri_cb, + const std::string &basedir, + const size_t max_buffer_size, bool is_binary = false, + const unsigned char *bin_data = nullptr, + size_t bin_size = 0) { + size_t byteLength; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "Buffer")) { + return false; + } + + // In glTF 2.0, uri is not mandatory anymore + buffer->uri.clear(); + ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); + + // having an empty uri for a non embedded image should not be valid + if (!is_binary && buffer->uri.empty()) { + if (err) { + (*err) += "'uri' is missing from non binary glTF file buffer.\n"; + } + } + + detail::json_const_iterator type; + if (detail::FindMember(o, "type", type)) { + std::string typeStr; + if (detail::GetString(detail::GetValue(type), typeStr)) { + if (typeStr.compare("arraybuffer") == 0) { + // buffer.type = "arraybuffer"; + } + } + } + + if (is_binary) { + // Still binary glTF accepts external dataURI. + if (!buffer->uri.empty()) { + // First try embedded data URI. + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, + true)) { + if (err) { + (*err) += + "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // External .bin file. + std::string decoded_uri; + if (!uri_cb->decode(buffer->uri, &decoded_uri, uri_cb->user_data)) { + return false; + } + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, + decoded_uri, basedir, /* required */ true, + byteLength, /* checkSize */ true, + /* max_file_size */ max_buffer_size, fs)) { + return false; + } + } + } else { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == nullptr)) { + if (err) { + (*err) += + "Invalid binary data in `Buffer', or GLB with empty BIN chunk.\n"; + } + return false; + } + + if (byteLength > bin_size) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteLength'. Must be equal or less than binary size: " + "`byteLength' = " + << byteLength << ", binary size = " << bin_size << std::endl; + (*err) += ss.str(); + } + return false; + } + + // Read buffer data + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); + } + + } else { + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, + true)) { + if (err) { + (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // Assume external .bin file. + std::string decoded_uri; + if (!uri_cb->decode(buffer->uri, &decoded_uri, uri_cb->user_data)) { + return false; + } + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri, + basedir, /* required */ true, byteLength, + /* checkSize */ true, + /* max file size */ max_buffer_size, fs)) { + return false; + } + } + } + + ParseStringProperty(&buffer->name, err, o, "name", false); + + ParseExtrasAndExtensions(buffer, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseBufferView( + BufferView *bufferView, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + int buffer = -1; + if (!ParseIntegerProperty(&buffer, err, o, "buffer", true, "BufferView")) { + return false; + } + + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false); + + size_t byteLength = 1; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "BufferView")) { + return false; + } + + size_t byteStride = 0; + if (!ParseUnsignedProperty(&byteStride, err, o, "byteStride", false)) { + // Spec says: When byteStride of referenced bufferView is not defined, it + // means that accessor elements are tightly packed, i.e., effective stride + // equals the size of the element. + // We cannot determine the actual byteStride until Accessor are parsed, thus + // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) + byteStride = 0; + } + + if ((byteStride > 252) || ((byteStride % 4) != 0)) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " + "4 : " + << byteStride << std::endl; + + (*err) += ss.str(); + } + return false; + } + + int target = 0; + ParseIntegerProperty(&target, err, o, "target", false); + if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) || + (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { + // OK + } else { + target = 0; + } + bufferView->target = target; + + ParseStringProperty(&bufferView->name, err, o, "name", false); + + ParseExtrasAndExtensions(bufferView, err, o, + store_original_json_for_extras_and_extensions); + + bufferView->buffer = buffer; + bufferView->byteOffset = byteOffset; + bufferView->byteLength = byteLength; + bufferView->byteStride = byteStride; + return true; +} + +static bool ParseSparseAccessor( + Accessor::Sparse *sparse, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + sparse->isSparse = true; + + int count = 0; + if (!ParseIntegerProperty(&count, err, o, "count", true, "SparseAccessor")) { + return false; + } + + ParseExtrasAndExtensions(sparse, err, o, + store_original_json_for_extras_and_extensions); + + detail::json_const_iterator indices_iterator; + detail::json_const_iterator values_iterator; + if (!detail::FindMember(o, "indices", indices_iterator)) { + (*err) = "the sparse object of this accessor doesn't have indices"; + return false; + } + + if (!detail::FindMember(o, "values", values_iterator)) { + (*err) = "the sparse object of this accessor doesn't have values"; + return false; + } + + const detail::json &indices_obj = detail::GetValue(indices_iterator); + const detail::json &values_obj = detail::GetValue(values_iterator); + + int indices_buffer_view = 0, component_type = 0; + size_t indices_byte_offset = 0; + if (!ParseIntegerProperty(&indices_buffer_view, err, indices_obj, + "bufferView", true, "SparseAccessor")) { + return false; + } + ParseUnsignedProperty(&indices_byte_offset, err, indices_obj, "byteOffset", + false); + if (!ParseIntegerProperty(&component_type, err, indices_obj, "componentType", + true, "SparseAccessor")) { + return false; + } + + int values_buffer_view = 0; + size_t values_byte_offset = 0; + if (!ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView", + true, "SparseAccessor")) { + return false; + } + ParseUnsignedProperty(&values_byte_offset, err, values_obj, "byteOffset", + false); + + sparse->count = count; + sparse->indices.bufferView = indices_buffer_view; + sparse->indices.byteOffset = indices_byte_offset; + sparse->indices.componentType = component_type; + ParseExtrasAndExtensions(&sparse->indices, err, indices_obj, + store_original_json_for_extras_and_extensions); + + sparse->values.bufferView = values_buffer_view; + sparse->values.byteOffset = values_byte_offset; + ParseExtrasAndExtensions(&sparse->values, err, values_obj, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseAccessor(Accessor *accessor, std::string *err, + const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + int bufferView = -1; + ParseIntegerProperty(&bufferView, err, o, "bufferView", false, "Accessor"); + + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); + + bool normalized = false; + ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); + + size_t componentType = 0; + if (!ParseUnsignedProperty(&componentType, err, o, "componentType", true, + "Accessor")) { + return false; + } + + size_t count = 0; + if (!ParseUnsignedProperty(&count, err, o, "count", true, "Accessor")) { + return false; + } + + std::string type; + if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { + return false; + } + + if (type.compare("SCALAR") == 0) { + accessor->type = TINYGLTF_TYPE_SCALAR; + } else if (type.compare("VEC2") == 0) { + accessor->type = TINYGLTF_TYPE_VEC2; + } else if (type.compare("VEC3") == 0) { + accessor->type = TINYGLTF_TYPE_VEC3; + } else if (type.compare("VEC4") == 0) { + accessor->type = TINYGLTF_TYPE_VEC4; + } else if (type.compare("MAT2") == 0) { + accessor->type = TINYGLTF_TYPE_MAT2; + } else if (type.compare("MAT3") == 0) { + accessor->type = TINYGLTF_TYPE_MAT3; + } else if (type.compare("MAT4") == 0) { + accessor->type = TINYGLTF_TYPE_MAT4; + } else { + std::stringstream ss; + ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&accessor->name, err, o, "name", false); + + accessor->minValues.clear(); + accessor->maxValues.clear(); + ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, + "Accessor"); + + ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, + "Accessor"); + + accessor->count = count; + accessor->bufferView = bufferView; + accessor->byteOffset = byteOffset; + accessor->normalized = normalized; + { + if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE && + componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { + // OK + accessor->componentType = int(componentType); + } else { + std::stringstream ss; + ss << "Invalid `componentType` in accessor. Got " << componentType + << "\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + } + + ParseExtrasAndExtensions(accessor, err, o, + store_original_json_for_extras_and_extensions); + + // check if accessor has a "sparse" object: + detail::json_const_iterator iterator; + if (detail::FindMember(o, "sparse", iterator)) { + // here this accessor has a "sparse" subobject + return ParseSparseAccessor(&accessor->sparse, err, + detail::GetValue(iterator), + store_original_json_for_extras_and_extensions); + } + + return true; +} + +#ifdef TINYGLTF_ENABLE_DRACO + +static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize, + std::vector &outBuffer) { + if (componentSize == 4) { + assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize); + memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0], + outBuffer.size()); + } else { + size_t faceStride = componentSize * 3; + for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) { + const draco::Mesh::Face &face = mesh->face(f); + if (componentSize == 2) { + uint16_t indices[3] = {(uint16_t)face[0].value(), + (uint16_t)face[1].value(), + (uint16_t)face[2].value()}; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } else { + uint8_t indices[3] = {(uint8_t)face[0].value(), + (uint8_t)face[1].value(), + (uint8_t)face[2].value()}; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } + } + } +} + +template +static bool GetAttributeForAllPoints(draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + size_t byteOffset = 0; + T values[4] = {0, 0, 0, 0}; + for (draco::PointIndex i(0); i < mesh->num_points(); ++i) { + const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i); + if (!pAttribute->ConvertValue(val_index, pAttribute->num_components(), + values)) + return false; + + memcpy(outBuffer.data() + byteOffset, &values[0], + sizeof(T) * pAttribute->num_components()); + byteOffset += sizeof(T) * pAttribute->num_components(); + } + + return true; +} + +static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + bool decodeResult = false; + switch (componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_DOUBLE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + default: + return false; + } + + return decodeResult; +} + +static bool ParseDracoExtension(Primitive *primitive, Model *model, + std::string *err, std::string *warn, + const Value &dracoExtensionValue, + ParseStrictness strictness) { + (void)err; + auto bufferViewValue = dracoExtensionValue.Get("bufferView"); + if (!bufferViewValue.IsInt()) return false; + auto attributesValue = dracoExtensionValue.Get("attributes"); + if (!attributesValue.IsObject()) return false; + + auto attributesObject = attributesValue.Get(); + int bufferView = bufferViewValue.Get(); + + BufferView &view = model->bufferViews[bufferView]; + Buffer &buffer = model->buffers[view.buffer]; + // BufferView has already been decoded + if (view.dracoDecoded) return true; + view.dracoDecoded = true; + + const char *bufferViewData = + reinterpret_cast(buffer.data.data() + view.byteOffset); + size_t bufferViewSize = view.byteLength; + + // decode draco + draco::DecoderBuffer decoderBuffer; + decoderBuffer.Init(bufferViewData, bufferViewSize); + draco::Decoder decoder; + auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); + if (!decodeResult.ok()) { + return false; + } + const std::unique_ptr &mesh = decodeResult.value(); + + // create new bufferView for indices + if (primitive->indices >= 0) { + if (strictness == ParseStrictness::Permissive) { + const draco::PointIndex::ValueType numPoint = mesh->num_points(); + // handle the situation where the stored component type does not match the + // required type for the actual number of stored points + int supposedComponentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + if (numPoint < static_cast( + std::numeric_limits::max())) { + supposedComponentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + } else if ( + numPoint < static_cast( + std::numeric_limits::max())) { + supposedComponentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + } else { + supposedComponentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; + } + + if (supposedComponentType > model->accessors[primitive->indices].componentType) { + if (warn) { + (*warn) += + "GLTF component type " + std::to_string(model->accessors[primitive->indices].componentType) + + " is not sufficient for number of stored points," + " treating as " + std::to_string(supposedComponentType) + "\n"; + } + model->accessors[primitive->indices].componentType = supposedComponentType; + } + } + + int32_t componentSize = GetComponentSizeInBytes( + model->accessors[primitive->indices].componentType); + Buffer decodedIndexBuffer; + decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize); + + DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data); + + model->buffers.emplace_back(std::move(decodedIndexBuffer)); + + BufferView decodedIndexBufferView; + decodedIndexBufferView.buffer = int(model->buffers.size() - 1); + decodedIndexBufferView.byteLength = + int(mesh->num_faces() * 3 * componentSize); + decodedIndexBufferView.byteOffset = 0; + decodedIndexBufferView.byteStride = 0; + decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedIndexBufferView)); + + model->accessors[primitive->indices].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitive->indices].count = int(mesh->num_faces() * 3); + } + + for (const auto &attribute : attributesObject) { + if (!attribute.second.IsInt()) return false; + auto primitiveAttribute = primitive->attributes.find(attribute.first); + if (primitiveAttribute == primitive->attributes.end()) return false; + + int dracoAttributeIndex = attribute.second.Get(); + const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); + const auto componentType = + model->accessors[primitiveAttribute->second].componentType; + + // Create a new buffer for this decoded buffer + Buffer decodedBuffer; + size_t bufferSize = mesh->num_points() * pAttribute->num_components() * + GetComponentSizeInBytes(componentType); + decodedBuffer.data.resize(bufferSize); + + if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute, + decodedBuffer.data)) + return false; + + model->buffers.emplace_back(std::move(decodedBuffer)); + + BufferView decodedBufferView; + decodedBufferView.buffer = int(model->buffers.size() - 1); + decodedBufferView.byteLength = bufferSize; + decodedBufferView.byteOffset = pAttribute->byte_offset(); + decodedBufferView.byteStride = pAttribute->byte_stride(); + decodedBufferView.target = primitive->indices >= 0 + ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER + : TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedBufferView)); + + model->accessors[primitiveAttribute->second].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitiveAttribute->second].count = + int(mesh->num_points()); + } + + return true; +} #endif - return true; - } - - static bool ParseMesh(Mesh* mesh, Model* model, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&mesh->name, err, o, "name", false); - - mesh->primitives.clear(); - json_const_iterator primObject; - if (FindMember(o, "primitives", primObject) && - IsArray(GetValue(primObject))) { - json_const_array_iterator primEnd = ArrayEnd(GetValue(primObject)); - for (json_const_array_iterator i = ArrayBegin(GetValue(primObject)); - i != primEnd; ++i) { - Primitive primitive; - if (ParsePrimitive(&primitive, model, err, *i, - store_original_json_for_extras_and_extensions)) { - // Only add the primitive if the parsing succeeds. - mesh->primitives.emplace_back(std::move(primitive)); - } - } - } - - // Should probably check if has targets and if dimensions fit - ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); - - ParseExtensionsProperty(&mesh->extensions, err, o); - ParseExtrasProperty(&(mesh->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - mesh->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - mesh->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseNode(Node* node, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&node->name, err, o, "name", false); - - int skin = -1; - ParseIntegerProperty(&skin, err, o, "skin", false); - node->skin = skin; - - // Matrix and T/R/S are exclusive - if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { - ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); - ParseNumberArrayProperty(&node->scale, err, o, "scale", false); - ParseNumberArrayProperty(&node->translation, err, o, "translation", false); - } - - int camera = -1; - ParseIntegerProperty(&camera, err, o, "camera", false); - node->camera = camera; - - int mesh = -1; - ParseIntegerProperty(&mesh, err, o, "mesh", false); - node->mesh = mesh; - - node->children.clear(); - ParseIntegerArrayProperty(&node->children, err, o, "children", false); - - ParseNumberArrayProperty(&node->weights, err, o, "weights", false); - - ParseExtensionsProperty(&node->extensions, err, o); - ParseExtrasProperty(&(node->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - node->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - node->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParsePbrMetallicRoughness( - PbrMetallicRoughness* pbr, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (pbr == nullptr) { - return false; - } - - std::vector baseColorFactor; - if (ParseNumberArrayProperty(&baseColorFactor, err, o, "baseColorFactor", - /* required */ false)) { - if (baseColorFactor.size() != 4) { - if (err) { - (*err) += - "Array length of `baseColorFactor` parameter in " - "pbrMetallicRoughness must be 4, but got " + - std::to_string(baseColorFactor.size()) + "\n"; - } - return false; - } - pbr->baseColorFactor = baseColorFactor; - } - - { - json_const_iterator it; - if (FindMember(o, "baseColorTexture", it)) { - ParseTextureInfo(&pbr->baseColorTexture, err, GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - { - json_const_iterator it; - if (FindMember(o, "metallicRoughnessTexture", it)) { - ParseTextureInfo(&pbr->metallicRoughnessTexture, err, GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - ParseNumberProperty(&pbr->metallicFactor, err, o, "metallicFactor", false); - ParseNumberProperty(&pbr->roughnessFactor, err, o, "roughnessFactor", false); - - ParseExtensionsProperty(&pbr->extensions, err, o); - ParseExtrasProperty(&pbr->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - pbr->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - pbr->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseMaterial(Material* material, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&material->name, err, o, "name", /* required */ false); - - if (ParseNumberArrayProperty(&material->emissiveFactor, err, o, - "emissiveFactor", - /* required */ false)) { - if (material->emissiveFactor.size() != 3) { - if (err) { - (*err) += - "Array length of `emissiveFactor` parameter in " - "material must be 3, but got " + - std::to_string(material->emissiveFactor.size()) + "\n"; - } - return false; - } - } - else { - // fill with default values - material->emissiveFactor = { 0.0, 0.0, 0.0 }; - } - - ParseStringProperty(&material->alphaMode, err, o, "alphaMode", - /* required */ false); - ParseNumberProperty(&material->alphaCutoff, err, o, "alphaCutoff", - /* required */ false); - ParseBooleanProperty(&material->doubleSided, err, o, "doubleSided", - /* required */ false); - - { - json_const_iterator it; - if (FindMember(o, "pbrMetallicRoughness", it)) { - ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err, - GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - { - json_const_iterator it; - if (FindMember(o, "normalTexture", it)) { - ParseNormalTextureInfo(&material->normalTexture, err, GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - { - json_const_iterator it; - if (FindMember(o, "occlusionTexture", it)) { - ParseOcclusionTextureInfo(&material->occlusionTexture, err, GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - { - json_const_iterator it; - if (FindMember(o, "emissiveTexture", it)) { - ParseTextureInfo(&material->emissiveTexture, err, GetValue(it), - store_original_json_for_extras_and_extensions); - } - } - - // Old code path. For backward compatibility, we still store material values - // as Parameter. This will create duplicated information for - // example(pbrMetallicRoughness), but should be neglible in terms of memory - // consumption. - // TODO(syoyo): Remove in the next major release. - material->values.clear(); - material->additionalValues.clear(); - - json_const_iterator it(ObjectBegin(o)); - json_const_iterator itEnd(ObjectEnd(o)); - - for (; it != itEnd; ++it) { - std::string key(GetKey(it)); - if (key == "pbrMetallicRoughness") { - if (IsObject(GetValue(it))) { - const json& values_object = GetValue(it); - - json_const_iterator itVal(ObjectBegin(values_object)); - json_const_iterator itValEnd(ObjectEnd(values_object)); - - for (; itVal != itValEnd; ++itVal) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, GetKey(itVal), - false)) { - material->values.emplace(GetKey(itVal), std::move(param)); - } - } - } - } - else if (key == "extensions" || key == "extras") { - // done later, skip, otherwise poorly parsed contents will be saved in the - // parametermap and serialized again later - } - else { - Parameter param; - if (ParseParameterProperty(¶m, err, o, key, false)) { - // names of materials have already been parsed. Putting it in this map - // doesn't correctly reflext the glTF specification - if (key != "name") - material->additionalValues.emplace(std::move(key), std::move(param)); - } - } - } - - material->extensions.clear(); - ParseExtensionsProperty(&material->extensions, err, o); - ParseExtrasProperty(&(material->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator eit; - if (FindMember(o, "extensions", eit)) { - material->extensions_json_string = JsonToString(GetValue(eit)); - } - } - { - json_const_iterator eit; - if (FindMember(o, "extras", eit)) { - material->extras_json_string = JsonToString(GetValue(eit)); - } - } - } - - return true; - } - - static bool ParseAnimationChannel( - AnimationChannel* channel, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - int samplerIndex = -1; - int targetIndex = -1; - if (!ParseIntegerProperty(&samplerIndex, err, o, "sampler", true, - "AnimationChannel")) { - if (err) { - (*err) += "`sampler` field is missing in animation channels\n"; - } - return false; - } - - json_const_iterator targetIt; - if (FindMember(o, "target", targetIt) && IsObject(GetValue(targetIt))) { - const json& target_object = GetValue(targetIt); - - if (!ParseIntegerProperty(&targetIndex, err, target_object, "node", true)) { - if (err) { - (*err) += "`node` field is missing in animation.channels.target\n"; - } - return false; - } - - if (!ParseStringProperty(&channel->target_path, err, target_object, "path", - true)) { - if (err) { - (*err) += "`path` field is missing in animation.channels.target\n"; - } - return false; - } - ParseExtensionsProperty(&channel->target_extensions, err, target_object); - if (store_original_json_for_extras_and_extensions) { - json_const_iterator it; - if (FindMember(target_object, "extensions", it)) { - channel->target_extensions_json_string = JsonToString(GetValue(it)); - } - } - } - - channel->sampler = samplerIndex; - channel->target_node = targetIndex; - - ParseExtensionsProperty(&channel->extensions, err, o); - ParseExtrasProperty(&(channel->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - channel->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - channel->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseAnimation(Animation* animation, std::string* err, - const json& o, - bool store_original_json_for_extras_and_extensions) { - { - json_const_iterator channelsIt; - if (FindMember(o, "channels", channelsIt) && - IsArray(GetValue(channelsIt))) { - json_const_array_iterator channelEnd = ArrayEnd(GetValue(channelsIt)); - for (json_const_array_iterator i = ArrayBegin(GetValue(channelsIt)); - i != channelEnd; ++i) { - AnimationChannel channel; - if (ParseAnimationChannel( - &channel, err, *i, - store_original_json_for_extras_and_extensions)) { - // Only add the channel if the parsing succeeds. - animation->channels.emplace_back(std::move(channel)); - } - } - } - } - - { - json_const_iterator samplerIt; - if (FindMember(o, "samplers", samplerIt) && IsArray(GetValue(samplerIt))) { - const json& sampler_array = GetValue(samplerIt); - - json_const_array_iterator it = ArrayBegin(sampler_array); - json_const_array_iterator itEnd = ArrayEnd(sampler_array); - - for (; it != itEnd; ++it) { - const json& s = *it; - - AnimationSampler sampler; - int inputIndex = -1; - int outputIndex = -1; - if (!ParseIntegerProperty(&inputIndex, err, s, "input", true)) { - if (err) { - (*err) += "`input` field is missing in animation.sampler\n"; - } - return false; - } - ParseStringProperty(&sampler.interpolation, err, s, "interpolation", - false); - if (!ParseIntegerProperty(&outputIndex, err, s, "output", true)) { - if (err) { - (*err) += "`output` field is missing in animation.sampler\n"; - } - return false; - } - sampler.input = inputIndex; - sampler.output = outputIndex; - ParseExtensionsProperty(&(sampler.extensions), err, o); - ParseExtrasProperty(&(sampler.extras), s); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator eit; - if (FindMember(o, "extensions", eit)) { - sampler.extensions_json_string = JsonToString(GetValue(eit)); - } - } - { - json_const_iterator eit; - if (FindMember(o, "extras", eit)) { - sampler.extras_json_string = JsonToString(GetValue(eit)); - } - } - } - - animation->samplers.emplace_back(std::move(sampler)); - } - } - } - - ParseStringProperty(&animation->name, err, o, "name", false); - - ParseExtensionsProperty(&animation->extensions, err, o); - ParseExtrasProperty(&(animation->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - animation->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - animation->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseSampler(Sampler* sampler, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&sampler->name, err, o, "name", false); - - int minFilter = -1; - int magFilter = -1; - int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; - int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; - //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; - ParseIntegerProperty(&minFilter, err, o, "minFilter", false); - ParseIntegerProperty(&magFilter, err, o, "magFilter", false); - ParseIntegerProperty(&wrapS, err, o, "wrapS", false); - ParseIntegerProperty(&wrapT, err, o, "wrapT", false); - //ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension - - // TODO(syoyo): Check the value is alloed one. - // (e.g. we allow 9728(NEAREST), but don't allow 9727) - - sampler->minFilter = minFilter; - sampler->magFilter = magFilter; - sampler->wrapS = wrapS; - sampler->wrapT = wrapT; - //sampler->wrapR = wrapR; - - ParseExtensionsProperty(&(sampler->extensions), err, o); - ParseExtrasProperty(&(sampler->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - sampler->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - sampler->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseSkin(Skin* skin, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); - - std::vector joints; - if (!ParseIntegerArrayProperty(&joints, err, o, "joints", false, "Skin")) { - return false; - } - skin->joints = std::move(joints); - - int skeleton = -1; - ParseIntegerProperty(&skeleton, err, o, "skeleton", false, "Skin"); - skin->skeleton = skeleton; - - int invBind = -1; - ParseIntegerProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); - skin->inverseBindMatrices = invBind; - - ParseExtensionsProperty(&(skin->extensions), err, o); - ParseExtrasProperty(&(skin->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - skin->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - skin->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParsePerspectiveCamera( - PerspectiveCamera* camera, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - double yfov = 0.0; - if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "PerspectiveCamera")) { - return false; - } - - double aspectRatio = 0.0; // = invalid - ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, - "PerspectiveCamera"); - - double zfar = 0.0; // = invalid - ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); - - camera->aspectRatio = aspectRatio; - camera->zfar = zfar; - camera->yfov = yfov; - camera->znear = znear; - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - camera->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - camera->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - // TODO(syoyo): Validate parameter values. - - return true; - } - - static bool ParseSpotLight(SpotLight* light, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - ParseNumberProperty(&light->innerConeAngle, err, o, "innerConeAngle", false); - ParseNumberProperty(&light->outerConeAngle, err, o, "outerConeAngle", false); - - ParseExtensionsProperty(&light->extensions, err, o); - ParseExtrasProperty(&light->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - light->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - light->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - // TODO(syoyo): Validate parameter values. - - return true; - } - - static bool ParseOrthographicCamera( - OrthographicCamera* camera, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - double xmag = 0.0; - if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { - return false; - } - - double ymag = 0.0; - if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { - return false; - } - - double zfar = 0.0; - if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "OrthographicCamera")) { - return false; - } - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - camera->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - camera->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - camera->xmag = xmag; - camera->ymag = ymag; - camera->zfar = zfar; - camera->znear = znear; - - // TODO(syoyo): Validate parameter values. - - return true; - } - - static bool ParseCamera(Camera* camera, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { - return false; - } - - if (camera->type.compare("orthographic") == 0) { - json_const_iterator orthoIt; - if (!FindMember(o, "orthographic", orthoIt)) { - if (err) { - std::stringstream ss; - ss << "Orhographic camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json& v = GetValue(orthoIt); - if (!IsObject(v)) { - if (err) { - std::stringstream ss; - ss << "\"orthographic\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParseOrthographicCamera( - &camera->orthographic, err, v, - store_original_json_for_extras_and_extensions)) { - return false; - } - } - else if (camera->type.compare("perspective") == 0) { - json_const_iterator perspIt; - if (!FindMember(o, "perspective", perspIt)) { - if (err) { - std::stringstream ss; - ss << "Perspective camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json& v = GetValue(perspIt); - if (!IsObject(v)) { - if (err) { - std::stringstream ss; - ss << "\"perspective\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParsePerspectiveCamera( - &camera->perspective, err, v, - store_original_json_for_extras_and_extensions)) { - return false; - } - } - else { - if (err) { - std::stringstream ss; - ss << "Invalid camera type: \"" << camera->type - << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; - (*err) += ss.str(); - } - return false; - } - - ParseStringProperty(&camera->name, err, o, "name", false); - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - camera->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - camera->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - static bool ParseLight(Light* light, std::string* err, const json& o, - bool store_original_json_for_extras_and_extensions) { - if (!ParseStringProperty(&light->type, err, o, "type", true)) { - return false; - } - - if (light->type == "spot") { - json_const_iterator spotIt; - if (!FindMember(o, "spot", spotIt)) { - if (err) { - std::stringstream ss; - ss << "Spot light description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json& v = GetValue(spotIt); - if (!IsObject(v)) { - if (err) { - std::stringstream ss; - ss << "\"spot\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParseSpotLight(&light->spot, err, v, - store_original_json_for_extras_and_extensions)) { - return false; - } - } - - ParseStringProperty(&light->name, err, o, "name", false); - ParseNumberArrayProperty(&light->color, err, o, "color", false); - ParseNumberProperty(&light->range, err, o, "range", false); - ParseNumberProperty(&light->intensity, err, o, "intensity", false); - ParseExtensionsProperty(&light->extensions, err, o); - ParseExtrasProperty(&(light->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - light->extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - light->extras_json_string = JsonToString(GetValue(it)); - } - } - } - - return true; - } - - bool TinyGLTF::LoadFromString(Model* model, std::string* err, std::string* warn, - const char* json_str, - unsigned int json_str_length, - const std::string& base_dir, - unsigned int check_sections) { - if (json_str_length < 4) { - if (err) { - (*err) = "JSON string too short.\n"; - } - return false; - } - - JsonDocument v; +static bool ParsePrimitive(Primitive *primitive, Model *model, + std::string *err, std::string *warn, + const detail::json &o, + bool store_original_json_for_extras_and_extensions, + ParseStrictness strictness) { + int material = -1; + ParseIntegerProperty(&material, err, o, "material", false); + primitive->material = material; + + int mode = TINYGLTF_MODE_TRIANGLES; + ParseIntegerProperty(&mode, err, o, "mode", false); + primitive->mode = mode; // Why only triangles were supported ? + + int indices = -1; + ParseIntegerProperty(&indices, err, o, "indices", false); + primitive->indices = indices; + if (!ParseStringIntegerProperty(&primitive->attributes, err, o, "attributes", + true, "Primitive")) { + return false; + } + + // Look for morph targets + detail::json_const_iterator targetsObject; + if (detail::FindMember(o, "targets", targetsObject) && + detail::IsArray(detail::GetValue(targetsObject))) { + auto targetsObjectEnd = detail::ArrayEnd(detail::GetValue(targetsObject)); + for (detail::json_const_array_iterator i = + detail::ArrayBegin(detail::GetValue(targetsObject)); + i != targetsObjectEnd; ++i) { + std::map targetAttribues; + + const detail::json &dict = *i; + if (detail::IsObject(dict)) { + detail::json_const_iterator dictIt(detail::ObjectBegin(dict)); + detail::json_const_iterator dictItEnd(detail::ObjectEnd(dict)); + + for (; dictIt != dictItEnd; ++dictIt) { + int iVal; + if (detail::GetInt(detail::GetValue(dictIt), iVal)) + targetAttribues[detail::GetKey(dictIt)] = iVal; + } + primitive->targets.emplace_back(std::move(targetAttribues)); + } + } + } + + ParseExtrasAndExtensions(primitive, err, o, + store_original_json_for_extras_and_extensions); + +#ifdef TINYGLTF_ENABLE_DRACO + auto dracoExtension = + primitive->extensions.find("KHR_draco_mesh_compression"); + if (dracoExtension != primitive->extensions.end()) { + ParseDracoExtension(primitive, model, err, warn, dracoExtension->second, strictness); + } +#else + (void)model; + (void)warn; + (void)strictness; +#endif + + return true; +} + +static bool ParseMesh(Mesh *mesh, Model *model, + std::string *err, std::string *warn, + const detail::json &o, + bool store_original_json_for_extras_and_extensions, + ParseStrictness strictness) { + ParseStringProperty(&mesh->name, err, o, "name", false); + + mesh->primitives.clear(); + detail::json_const_iterator primObject; + if (detail::FindMember(o, "primitives", primObject) && + detail::IsArray(detail::GetValue(primObject))) { + detail::json_const_array_iterator primEnd = + detail::ArrayEnd(detail::GetValue(primObject)); + for (detail::json_const_array_iterator i = + detail::ArrayBegin(detail::GetValue(primObject)); + i != primEnd; ++i) { + Primitive primitive; + if (ParsePrimitive(&primitive, model, err, warn, *i, + store_original_json_for_extras_and_extensions, + strictness)) { + // Only add the primitive if the parsing succeeds. + mesh->primitives.emplace_back(std::move(primitive)); + } + } + } + + // Should probably check if has targets and if dimensions fit + ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); + + ParseExtrasAndExtensions(mesh, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseNode(Node *node, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&node->name, err, o, "name", false); + + int skin = -1; + ParseIntegerProperty(&skin, err, o, "skin", false); + node->skin = skin; + + // Matrix and T/R/S are exclusive + if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { + ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); + ParseNumberArrayProperty(&node->scale, err, o, "scale", false); + ParseNumberArrayProperty(&node->translation, err, o, "translation", false); + } + + int camera = -1; + ParseIntegerProperty(&camera, err, o, "camera", false); + node->camera = camera; + + int mesh = -1; + ParseIntegerProperty(&mesh, err, o, "mesh", false); + node->mesh = mesh; + + node->children.clear(); + ParseIntegerArrayProperty(&node->children, err, o, "children", false); + + ParseNumberArrayProperty(&node->weights, err, o, "weights", false); + + ParseExtrasAndExtensions(node, err, o, + store_original_json_for_extras_and_extensions); + + // KHR_lights_punctual: parse light source reference + int light = -1; + if (node->extensions.count("KHR_lights_punctual") != 0) { + auto const &light_ext = node->extensions["KHR_lights_punctual"]; + if (light_ext.Has("light")) { + light = light_ext.Get("light").GetNumberAsInt(); + } else { + if (err) { + *err += + "Node has extension KHR_lights_punctual, but does not reference " + "a light source.\n"; + } + return false; + } + } + node->light = light; + + // KHR_audio: parse audio source reference + int emitter = -1; + if (node->extensions.count("KHR_audio") != 0) { + auto const &audio_ext = node->extensions["KHR_audio"]; + if (audio_ext.Has("emitter")) { + emitter = audio_ext.Get("emitter").GetNumberAsInt(); + } else { + if (err) { + *err += + "Node has extension KHR_audio, but does not reference " + "a audio emitter.\n"; + } + return false; + } + } + node->emitter = emitter; + + node->lods.clear(); + if (node->extensions.count("MSFT_lod") != 0) { + auto const &msft_lod_ext = node->extensions["MSFT_lod"]; + if (msft_lod_ext.Has("ids")) { + auto idsArr = msft_lod_ext.Get("ids"); + for (size_t i = 0; i < idsArr.ArrayLen(); ++i) { + node->lods.emplace_back(idsArr.Get(i).GetNumberAsInt()); + } + } else { + if (err) { + *err += + "Node has extension MSFT_lod, but does not reference " + "other nodes via their ids.\n"; + } + return false; + } + } + + return true; +} + +static bool ParseScene(Scene *scene, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&scene->name, err, o, "name", false); + ParseIntegerArrayProperty(&scene->nodes, err, o, "nodes", false); + + ParseExtrasAndExtensions(scene, err, o, + store_original_json_for_extras_and_extensions); + + // Parse KHR_audio global emitters + if (scene->extensions.count("KHR_audio") != 0) { + auto const &audio_ext = scene->extensions["KHR_audio"]; + if (audio_ext.Has("emitters")) { + auto emittersArr = audio_ext.Get("emitters"); + for (size_t i = 0; i < emittersArr.ArrayLen(); ++i) { + scene->audioEmitters.emplace_back(emittersArr.Get(i).GetNumberAsInt()); + } + } else { + if (err) { + *err += + "Node has extension KHR_audio, but does not reference " + "a audio emitter.\n"; + } + return false; + } + } + + return true; +} + +static bool ParsePbrMetallicRoughness( + PbrMetallicRoughness *pbr, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (pbr == nullptr) { + return false; + } + + std::vector baseColorFactor; + if (ParseNumberArrayProperty(&baseColorFactor, err, o, "baseColorFactor", + /* required */ false)) { + if (baseColorFactor.size() != 4) { + if (err) { + (*err) += + "Array length of `baseColorFactor` parameter in " + "pbrMetallicRoughness must be 4, but got " + + std::to_string(baseColorFactor.size()) + "\n"; + } + return false; + } + pbr->baseColorFactor = baseColorFactor; + } + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "baseColorTexture", it)) { + ParseTextureInfo(&pbr->baseColorTexture, err, detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "metallicRoughnessTexture", it)) { + ParseTextureInfo(&pbr->metallicRoughnessTexture, err, + detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + ParseNumberProperty(&pbr->metallicFactor, err, o, "metallicFactor", false); + ParseNumberProperty(&pbr->roughnessFactor, err, o, "roughnessFactor", false); + + ParseExtrasAndExtensions(pbr, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseMaterial(Material *material, std::string *err, std::string *warn, + const detail::json &o, + bool store_original_json_for_extras_and_extensions, + ParseStrictness strictness) { + ParseStringProperty(&material->name, err, o, "name", /* required */ false); + + if (ParseNumberArrayProperty(&material->emissiveFactor, err, o, + "emissiveFactor", + /* required */ false)) { + if (strictness==ParseStrictness::Permissive && material->emissiveFactor.size() == 4) { + if (warn) { + (*warn) += + "Array length of `emissiveFactor` parameter in " + "material must be 3, but got 4\n"; + } + material->emissiveFactor.resize(3); + } + else if (material->emissiveFactor.size() != 3) { + if (err) { + (*err) += + "Array length of `emissiveFactor` parameter in " + "material must be 3, but got " + + std::to_string(material->emissiveFactor.size()) + "\n"; + } + return false; + } + } else { + // fill with default values + material->emissiveFactor = {0.0, 0.0, 0.0}; + } + + ParseStringProperty(&material->alphaMode, err, o, "alphaMode", + /* required */ false); + ParseNumberProperty(&material->alphaCutoff, err, o, "alphaCutoff", + /* required */ false); + ParseBooleanProperty(&material->doubleSided, err, o, "doubleSided", + /* required */ false); + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "pbrMetallicRoughness", it)) { + ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err, + detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "normalTexture", it)) { + ParseNormalTextureInfo(&material->normalTexture, err, + detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "occlusionTexture", it)) { + ParseOcclusionTextureInfo(&material->occlusionTexture, err, + detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "emissiveTexture", it)) { + ParseTextureInfo(&material->emissiveTexture, err, detail::GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + // Old code path. For backward compatibility, we still store material values + // as Parameter. This will create duplicated information for + // example(pbrMetallicRoughness), but should be negligible in terms of memory + // consumption. + // TODO(syoyo): Remove in the next major release. + material->values.clear(); + material->additionalValues.clear(); + + detail::json_const_iterator it(detail::ObjectBegin(o)); + detail::json_const_iterator itEnd(detail::ObjectEnd(o)); + + for (; it != itEnd; ++it) { + std::string key(detail::GetKey(it)); + if (key == "pbrMetallicRoughness") { + if (detail::IsObject(detail::GetValue(it))) { + const detail::json &values_object = detail::GetValue(it); + + detail::json_const_iterator itVal(detail::ObjectBegin(values_object)); + detail::json_const_iterator itValEnd(detail::ObjectEnd(values_object)); + + for (; itVal != itValEnd; ++itVal) { + Parameter param; + if (ParseParameterProperty(¶m, err, values_object, + detail::GetKey(itVal), false)) { + material->values.emplace(detail::GetKey(itVal), std::move(param)); + } + } + } + } else if (key == "extensions" || key == "extras") { + // done later, skip, otherwise poorly parsed contents will be saved in the + // parametermap and serialized again later + } else { + Parameter param; + if (ParseParameterProperty(¶m, err, o, key, false)) { + // names of materials have already been parsed. Putting it in this map + // doesn't correctly reflect the glTF specification + if (key != "name") + material->additionalValues.emplace(std::move(key), std::move(param)); + } + } + } + + material->extensions.clear(); // Note(agnat): Why? + ParseExtrasAndExtensions(material, err, o, + store_original_json_for_extras_and_extensions); + + material->lods.clear(); + if (material->extensions.count("MSFT_lod") != 0) { + auto const &msft_lod_ext = material->extensions["MSFT_lod"]; + if (msft_lod_ext.Has("ids")) { + auto idsArr = msft_lod_ext.Get("ids"); + for (size_t i = 0; i < idsArr.ArrayLen(); ++i) { + material->lods.emplace_back(idsArr.Get(i).GetNumberAsInt()); + } + } else { + if (err) { + *err += + "Material has extension MSFT_lod, but does not reference " + "other materials via their ids.\n"; + } + return false; + } + } + + return true; +} + +static bool ParseAnimationChannel( + AnimationChannel *channel, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + int samplerIndex = -1; + int targetIndex = -1; + if (!ParseIntegerProperty(&samplerIndex, err, o, "sampler", true, + "AnimationChannel")) { + if (err) { + (*err) += "`sampler` field is missing in animation channels\n"; + } + return false; + } + + detail::json_const_iterator targetIt; + if (detail::FindMember(o, "target", targetIt) && + detail::IsObject(detail::GetValue(targetIt))) { + const detail::json &target_object = detail::GetValue(targetIt); + + ParseIntegerProperty(&targetIndex, err, target_object, "node", false); + + if (!ParseStringProperty(&channel->target_path, err, target_object, "path", + true)) { + if (err) { + (*err) += "`path` field is missing in animation.channels.target\n"; + } + return false; + } + ParseExtensionsProperty(&channel->target_extensions, err, target_object); + ParseExtrasProperty(&channel->target_extras, target_object); + if (store_original_json_for_extras_and_extensions) { + { + detail::json_const_iterator it; + if (detail::FindMember(target_object, "extensions", it)) { + channel->target_extensions_json_string = + detail::JsonToString(detail::GetValue(it)); + } + } + { + detail::json_const_iterator it; + if (detail::FindMember(target_object, "extras", it)) { + channel->target_extras_json_string = + detail::JsonToString(detail::GetValue(it)); + } + } + } + } + + channel->sampler = samplerIndex; + channel->target_node = targetIndex; + + ParseExtrasAndExtensions(channel, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseAnimation(Animation *animation, std::string *err, + const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + { + detail::json_const_iterator channelsIt; + if (detail::FindMember(o, "channels", channelsIt) && + detail::IsArray(detail::GetValue(channelsIt))) { + detail::json_const_array_iterator channelEnd = + detail::ArrayEnd(detail::GetValue(channelsIt)); + for (detail::json_const_array_iterator i = + detail::ArrayBegin(detail::GetValue(channelsIt)); + i != channelEnd; ++i) { + AnimationChannel channel; + if (ParseAnimationChannel( + &channel, err, *i, + store_original_json_for_extras_and_extensions)) { + // Only add the channel if the parsing succeeds. + animation->channels.emplace_back(std::move(channel)); + } + } + } + } + + { + detail::json_const_iterator samplerIt; + if (detail::FindMember(o, "samplers", samplerIt) && + detail::IsArray(detail::GetValue(samplerIt))) { + const detail::json &sampler_array = detail::GetValue(samplerIt); + + detail::json_const_array_iterator it = detail::ArrayBegin(sampler_array); + detail::json_const_array_iterator itEnd = detail::ArrayEnd(sampler_array); + + for (; it != itEnd; ++it) { + const detail::json &s = *it; + + AnimationSampler sampler; + int inputIndex = -1; + int outputIndex = -1; + if (!ParseIntegerProperty(&inputIndex, err, s, "input", true)) { + if (err) { + (*err) += "`input` field is missing in animation.sampler\n"; + } + return false; + } + ParseStringProperty(&sampler.interpolation, err, s, "interpolation", + false); + if (!ParseIntegerProperty(&outputIndex, err, s, "output", true)) { + if (err) { + (*err) += "`output` field is missing in animation.sampler\n"; + } + return false; + } + sampler.input = inputIndex; + sampler.output = outputIndex; + ParseExtrasAndExtensions(&sampler, err, s, + store_original_json_for_extras_and_extensions); + + animation->samplers.emplace_back(std::move(sampler)); + } + } + } + + ParseStringProperty(&animation->name, err, o, "name", false); + + ParseExtrasAndExtensions(animation, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseSampler(Sampler *sampler, std::string *err, + const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&sampler->name, err, o, "name", false); + + int minFilter = -1; + int magFilter = -1; + int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; + int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; + // int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; + ParseIntegerProperty(&minFilter, err, o, "minFilter", false); + ParseIntegerProperty(&magFilter, err, o, "magFilter", false); + ParseIntegerProperty(&wrapS, err, o, "wrapS", false); + ParseIntegerProperty(&wrapT, err, o, "wrapT", false); + // ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf + // extension + + // TODO(syoyo): Check the value is allowed one. + // (e.g. we allow 9728(NEAREST), but don't allow 9727) + + sampler->minFilter = minFilter; + sampler->magFilter = magFilter; + sampler->wrapS = wrapS; + sampler->wrapT = wrapT; + // sampler->wrapR = wrapR; + + ParseExtrasAndExtensions(sampler, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseSkin(Skin *skin, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); + + std::vector joints; + if (!ParseIntegerArrayProperty(&joints, err, o, "joints", false, "Skin")) { + return false; + } + skin->joints = std::move(joints); + + int skeleton = -1; + ParseIntegerProperty(&skeleton, err, o, "skeleton", false, "Skin"); + skin->skeleton = skeleton; + + int invBind = -1; + ParseIntegerProperty(&invBind, err, o, "inverseBindMatrices", false, "Skin"); + skin->inverseBindMatrices = invBind; + + ParseExtrasAndExtensions(skin, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParsePerspectiveCamera( + PerspectiveCamera *camera, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + double yfov = 0.0; + if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "PerspectiveCamera")) { + return false; + } + + double aspectRatio = 0.0; // = invalid + ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, + "PerspectiveCamera"); + + double zfar = 0.0; // = invalid + ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); + + camera->aspectRatio = aspectRatio; + camera->zfar = zfar; + camera->yfov = yfov; + camera->znear = znear; + + ParseExtrasAndExtensions(camera, err, o, + store_original_json_for_extras_and_extensions); + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseSpotLight(SpotLight *light, std::string *err, + const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseNumberProperty(&light->innerConeAngle, err, o, "innerConeAngle", false); + ParseNumberProperty(&light->outerConeAngle, err, o, "outerConeAngle", false); + + ParseExtrasAndExtensions(light, err, o, + store_original_json_for_extras_and_extensions); + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseOrthographicCamera( + OrthographicCamera *camera, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + double xmag = 0.0; + if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { + return false; + } + + double ymag = 0.0; + if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { + return false; + } + + double zfar = 0.0; + if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "OrthographicCamera")) { + return false; + } + + ParseExtrasAndExtensions(camera, err, o, + store_original_json_for_extras_and_extensions); + + camera->xmag = xmag; + camera->ymag = ymag; + camera->zfar = zfar; + camera->znear = znear; + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseCamera(Camera *camera, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { + return false; + } + + if (camera->type.compare("orthographic") == 0) { + detail::json_const_iterator orthoIt; + if (!detail::FindMember(o, "orthographic", orthoIt)) { + if (err) { + std::stringstream ss; + ss << "Orthographic camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const detail::json &v = detail::GetValue(orthoIt); + if (!detail::IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"orthographic\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseOrthographicCamera( + &camera->orthographic, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } else if (camera->type.compare("perspective") == 0) { + detail::json_const_iterator perspIt; + if (!detail::FindMember(o, "perspective", perspIt)) { + if (err) { + std::stringstream ss; + ss << "Perspective camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const detail::json &v = detail::GetValue(perspIt); + if (!detail::IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"perspective\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePerspectiveCamera( + &camera->perspective, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } else { + if (err) { + std::stringstream ss; + ss << "Invalid camera type: \"" << camera->type + << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&camera->name, err, o, "name", false); + + ParseExtrasAndExtensions(camera, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseLight(Light *light, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (!ParseStringProperty(&light->type, err, o, "type", true)) { + return false; + } + + if (light->type == "spot") { + detail::json_const_iterator spotIt; + if (!detail::FindMember(o, "spot", spotIt)) { + if (err) { + std::stringstream ss; + ss << "Spot light description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const detail::json &v = detail::GetValue(spotIt); + if (!detail::IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"spot\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseSpotLight(&light->spot, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } + + ParseStringProperty(&light->name, err, o, "name", false); + ParseNumberArrayProperty(&light->color, err, o, "color", false); + ParseNumberProperty(&light->range, err, o, "range", false); + ParseNumberProperty(&light->intensity, err, o, "intensity", false); + + ParseExtrasAndExtensions(light, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParsePositionalEmitter( + PositionalEmitter *positional, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseNumberProperty(&positional->coneInnerAngle, err, o, "coneInnerAngle", + false); + ParseNumberProperty(&positional->coneOuterAngle, err, o, "coneOuterAngle", + false); + ParseNumberProperty(&positional->coneOuterGain, err, o, "coneOuterGain", + false); + ParseNumberProperty(&positional->maxDistance, err, o, "maxDistance", false); + ParseNumberProperty(&positional->refDistance, err, o, "refDistance", false); + ParseNumberProperty(&positional->rolloffFactor, err, o, "rolloffFactor", + false); + + ParseExtrasAndExtensions(positional, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseAudioEmitter( + AudioEmitter *emitter, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + if (!ParseStringProperty(&emitter->type, err, o, "type", true)) { + return false; + } + + if (emitter->type == "positional") { + detail::json_const_iterator positionalIt; + if (!detail::FindMember(o, "positional", positionalIt)) { + if (err) { + std::stringstream ss; + ss << "Positional emitter description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const detail::json &v = detail::GetValue(positionalIt); + if (!detail::IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"positional\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePositionalEmitter( + &emitter->positional, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } + + ParseStringProperty(&emitter->name, err, o, "name", false); + ParseNumberProperty(&emitter->gain, err, o, "gain", false); + ParseBooleanProperty(&emitter->loop, err, o, "loop", false); + ParseBooleanProperty(&emitter->playing, err, o, "playing", false); + ParseStringProperty(&emitter->distanceModel, err, o, "distanceModel", false); + ParseIntegerProperty(&emitter->source, err, o, "source", true); + + ParseExtrasAndExtensions(emitter, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +static bool ParseAudioSource( + AudioSource *source, std::string *err, const detail::json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&source->name, err, o, "name", false); + ParseStringProperty(&source->uri, err, o, "uri", false); + + if (source->uri.empty()) { + ParseIntegerProperty(&source->bufferView, err, o, "bufferView", true); + ParseStringProperty(&source->mimeType, err, o, "mimeType", true); + } + + ParseExtrasAndExtensions(source, err, o, + store_original_json_for_extras_and_extensions); + + return true; +} + +namespace detail { + +template +bool ForEachInArray(const detail::json &_v, const char *member, Callback &&cb) { + detail::json_const_iterator itm; + if (detail::FindMember(_v, member, itm) && + detail::IsArray(detail::GetValue(itm))) { + const detail::json &root = detail::GetValue(itm); + auto it = detail::ArrayBegin(root); + auto end = detail::ArrayEnd(root); + for (; it != end; ++it) { + if (!cb(*it)) return false; + } + } + return true; +}; + +} // end of namespace detail + +bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, + const char *json_str, + unsigned int json_str_length, + const std::string &base_dir, + unsigned int check_sections) { + if (json_str_length < 4) { + if (err) { + (*err) = "JSON string too short.\n"; + } + return false; + } + + detail::JsonDocument v; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ - defined(_CPPUNWIND)) && \ - !defined(TINYGLTF_NOEXCEPTION) - try { - JsonParse(v, json_str, json_str_length, true); + defined(_CPPUNWIND)) && \ + !defined(TINYGLTF_NOEXCEPTION) + try { + detail::JsonParse(v, json_str, json_str_length, true); - } - catch (const std::exception& e) { - if (err) { - (*err) = e.what(); - } - return false; - } + } catch (const std::exception &e) { + if (err) { + (*err) = e.what(); + } + return false; + } #else - { - JsonParse(v, json_str, json_str_length); + { + detail::JsonParse(v, json_str, json_str_length); - if (!IsObject(v)) { - // Assume parsing was failed. - if (err) { - (*err) = "Failed to parse JSON object\n"; - } - return false; - } - } + if (!detail::IsObject(v)) { + // Assume parsing was failed. + if (err) { + (*err) = "Failed to parse JSON object\n"; + } + return false; + } + } #endif - if (!IsObject(v)) { - // root is not an object. - if (err) { - (*err) = "Root element is not a JSON object\n"; - } - return false; - } - - { - bool version_found = false; - json_const_iterator it; - if (FindMember(v, "asset", it) && IsObject(GetValue(it))) { - auto& itObj = GetValue(it); - json_const_iterator version_it; - std::string versionStr; - if (FindMember(itObj, "version", version_it) && - GetString(GetValue(version_it), versionStr)) { - version_found = true; - } - } - if (version_found) { - // OK - } - else if (check_sections & REQUIRE_VERSION) { - if (err) { - (*err) += "\"asset\" object not found in .gltf or not an object type\n"; - } - return false; - } - } - - // scene is not mandatory. - // FIXME Maybe a better way to handle it than removing the code - - auto IsArrayMemberPresent = [](const json& _v, const char* name) -> bool { - json_const_iterator it; - return FindMember(_v, name, it) && IsArray(GetValue(it)); - }; - - { - if ((check_sections & REQUIRE_SCENES) && - !IsArrayMemberPresent(v, "scenes")) { - if (err) { - (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; - } - return false; - } - } - - { - if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) { - if (err) { - (*err) += "\"nodes\" object not found in .gltf\n"; - } - return false; - } - } - - { - if ((check_sections & REQUIRE_ACCESSORS) && - !IsArrayMemberPresent(v, "accessors")) { - if (err) { - (*err) += "\"accessors\" object not found in .gltf\n"; - } - return false; - } - } - - { - if ((check_sections & REQUIRE_BUFFERS) && - !IsArrayMemberPresent(v, "buffers")) { - if (err) { - (*err) += "\"buffers\" object not found in .gltf\n"; - } - return false; - } - } - - { - if ((check_sections & REQUIRE_BUFFER_VIEWS) && - !IsArrayMemberPresent(v, "bufferViews")) { - if (err) { - (*err) += "\"bufferViews\" object not found in .gltf\n"; - } - return false; - } - } - - model->buffers.clear(); - model->bufferViews.clear(); - model->accessors.clear(); - model->meshes.clear(); - model->cameras.clear(); - model->nodes.clear(); - model->extensionsUsed.clear(); - model->extensionsRequired.clear(); - model->extensions.clear(); - model->defaultScene = -1; - - // 1. Parse Asset - { - json_const_iterator it; - if (FindMember(v, "asset", it) && IsObject(GetValue(it))) { - const json& root = GetValue(it); - - ParseAsset(&model->asset, err, root, - store_original_json_for_extras_and_extensions_); - } - } - -#ifdef TINYGLTF_USE_CPP14 - auto ForEachInArray = [](const json& _v, const char* member, - const auto& cb) -> bool -#else - // The std::function<> implementation can be less efficient because it will - // allocate heap when the size of the captured lambda is above 16 bytes with - // clang and gcc, but it does not require C++14. - auto ForEachInArray = [](const json& _v, const char* member, - const std::function& cb) -> bool -#endif - { - json_const_iterator itm; - if (FindMember(_v, member, itm) && IsArray(GetValue(itm))) { - const json& root = GetValue(itm); - auto it = ArrayBegin(root); - auto end = ArrayEnd(root); - for (; it != end; ++it) { - if (!cb(*it)) return false; - } - } - return true; - }; - - // 2. Parse extensionUsed - { - ForEachInArray(v, "extensionsUsed", [&](const json& o) { - std::string str; - GetString(o, str); - model->extensionsUsed.emplace_back(std::move(str)); - return true; - }); - } - - { - ForEachInArray(v, "extensionsRequired", [&](const json& o) { - std::string str; - GetString(o, str); - model->extensionsRequired.emplace_back(std::move(str)); - return true; - }); - } - - // 3. Parse Buffer - { - bool success = ForEachInArray(v, "buffers", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`buffers' does not contain an JSON object."; - } - return false; - } - Buffer buffer; - if (!ParseBuffer(&buffer, err, o, - store_original_json_for_extras_and_extensions_, &fs, - base_dir, is_binary_, bin_data_, bin_size_)) { - return false; - } - - model->buffers.emplace_back(std::move(buffer)); - return true; - }); - - if (!success) { - return false; - } - } - // 4. Parse BufferView - { - bool success = ForEachInArray(v, "bufferViews", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`bufferViews' does not contain an JSON object."; - } - return false; - } - BufferView bufferView; - if (!ParseBufferView(&bufferView, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->bufferViews.emplace_back(std::move(bufferView)); - return true; - }); - - if (!success) { - return false; - } - } - - // 5. Parse Accessor - { - bool success = ForEachInArray(v, "accessors", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`accessors' does not contain an JSON object."; - } - return false; - } - Accessor accessor; - if (!ParseAccessor(&accessor, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->accessors.emplace_back(std::move(accessor)); - return true; - }); - - if (!success) { - return false; - } - } - - // 6. Parse Mesh - { - bool success = ForEachInArray(v, "meshes", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`meshes' does not contain an JSON object."; - } - return false; - } - Mesh mesh; - if (!ParseMesh(&mesh, model, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->meshes.emplace_back(std::move(mesh)); - return true; - }); - - if (!success) { - return false; - } - } - - // Assign missing bufferView target types - // - Look for missing Mesh indices - // - Look for missing Mesh attributes - for (auto& mesh : model->meshes) { - for (auto& primitive : mesh.primitives) { - if (primitive.indices > - -1) // has indices from parsing step, must be Element Array Buffer - { - if (size_t(primitive.indices) >= model->accessors.size()) { - if (err) { - (*err) += "primitive indices accessor out of bounds"; - } - return false; - } - - auto bufferView = - model->accessors[size_t(primitive.indices)].bufferView; - if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) { - if (err) { - (*err) += "accessor[" + std::to_string(primitive.indices) + - "] invalid bufferView"; - } - return false; - } - - model->bufferViews[size_t(bufferView)].target = - TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; - // we could optionally check if acessors' bufferView type is Scalar, as - // it should be - } - - for (auto& attribute : primitive.attributes) { - model - ->bufferViews[size_t( - model->accessors[size_t(attribute.second)].bufferView)] - .target = TINYGLTF_TARGET_ARRAY_BUFFER; - } - - for (auto& target : primitive.targets) { - for (auto& attribute : target) { - auto bufferView = - model->accessors[size_t(attribute.second)].bufferView; - // bufferView could be null(-1) for sparse morph target - if (bufferView >= 0) { - model->bufferViews[size_t(bufferView)].target = - TINYGLTF_TARGET_ARRAY_BUFFER; - } - } - } - } - } - - // 7. Parse Node - { - bool success = ForEachInArray(v, "nodes", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`nodes' does not contain an JSON object."; - } - return false; - } - Node node; - if (!ParseNode(&node, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->nodes.emplace_back(std::move(node)); - return true; - }); - - if (!success) { - return false; - } - } - - // 8. Parse scenes. - { - bool success = ForEachInArray(v, "scenes", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`scenes' does not contain an JSON object."; - } - return false; - } - std::vector nodes; - ParseIntegerArrayProperty(&nodes, err, o, "nodes", false); - - Scene scene; - scene.nodes = std::move(nodes); - - ParseStringProperty(&scene.name, err, o, "name", false); - - ParseExtensionsProperty(&scene.extensions, err, o); - ParseExtrasProperty(&scene.extras, o); - - if (store_original_json_for_extras_and_extensions_) { - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - scene.extensions_json_string = JsonToString(GetValue(it)); - } - } - { - json_const_iterator it; - if (FindMember(o, "extras", it)) { - scene.extras_json_string = JsonToString(GetValue(it)); - } - } - } - - model->scenes.emplace_back(std::move(scene)); - return true; - }); - - if (!success) { - return false; - } - } - - // 9. Parse default scenes. - { - json_const_iterator rootIt; - int iVal; - if (FindMember(v, "scene", rootIt) && GetInt(GetValue(rootIt), iVal)) { - model->defaultScene = iVal; - } - } - - // 10. Parse Material - { - bool success = ForEachInArray(v, "materials", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`materials' does not contain an JSON object."; - } - return false; - } - Material material; - ParseStringProperty(&material.name, err, o, "name", false); - - if (!ParseMaterial(&material, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->materials.emplace_back(std::move(material)); - return true; - }); - - if (!success) { - return false; - } - } - - // 11. Parse Image - void* load_image_user_data{ nullptr }; - - LoadImageDataOption load_image_option; - - if (user_image_loader_) { - // Use user supplied pointer - load_image_user_data = load_image_user_data_; - } - else { - load_image_option.preserve_channels = preserve_image_channels_; - load_image_user_data = reinterpret_cast(&load_image_option); - } - - { - int idx = 0; - bool success = ForEachInArray(v, "images", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "image[" + std::to_string(idx) + "] is not a JSON object."; - } - return false; - } - Image image; - if (!ParseImage(&image, idx, err, warn, o, - store_original_json_for_extras_and_extensions_, base_dir, - &fs, &this->LoadImageData, load_image_user_data)) { - return false; - } - - if (image.bufferView != -1) { - // Load image from the buffer view. - if (size_t(image.bufferView) >= model->bufferViews.size()) { - if (err) { - std::stringstream ss; - ss << "image[" << idx << "] bufferView \"" << image.bufferView - << "\" not found in the scene." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const BufferView& bufferView = - model->bufferViews[size_t(image.bufferView)]; - if (size_t(bufferView.buffer) >= model->buffers.size()) { - if (err) { - std::stringstream ss; - ss << "image[" << idx << "] buffer \"" << bufferView.buffer - << "\" not found in the scene." << std::endl; - (*err) += ss.str(); - } - return false; - } - const Buffer& buffer = model->buffers[size_t(bufferView.buffer)]; - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - bool ret = LoadImageData( - &image, idx, err, warn, image.width, image.height, - &buffer.data[bufferView.byteOffset], - static_cast(bufferView.byteLength), load_image_user_data); - if (!ret) { - return false; - } - } - - model->images.emplace_back(std::move(image)); - ++idx; - return true; - }); - - if (!success) { - return false; - } - } - - // 12. Parse Texture - { - bool success = ForEachInArray(v, "textures", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`textures' does not contain an JSON object."; - } - return false; - } - Texture texture; - if (!ParseTexture(&texture, err, o, - store_original_json_for_extras_and_extensions_, - base_dir)) { - return false; - } - - model->textures.emplace_back(std::move(texture)); - return true; - }); - - if (!success) { - return false; - } - } - - // 13. Parse Animation - { - bool success = ForEachInArray(v, "animations", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`animations' does not contain an JSON object."; - } - return false; - } - Animation animation; - if (!ParseAnimation(&animation, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->animations.emplace_back(std::move(animation)); - return true; - }); - - if (!success) { - return false; - } - } - - // 14. Parse Skin - { - bool success = ForEachInArray(v, "skins", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`skins' does not contain an JSON object."; - } - return false; - } - Skin skin; - if (!ParseSkin(&skin, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->skins.emplace_back(std::move(skin)); - return true; - }); - - if (!success) { - return false; - } - } - - // 15. Parse Sampler - { - bool success = ForEachInArray(v, "samplers", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`samplers' does not contain an JSON object."; - } - return false; - } - Sampler sampler; - if (!ParseSampler(&sampler, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->samplers.emplace_back(std::move(sampler)); - return true; - }); - - if (!success) { - return false; - } - } - - // 16. Parse Camera - { - bool success = ForEachInArray(v, "cameras", [&](const json& o) { - if (!IsObject(o)) { - if (err) { - (*err) += "`cameras' does not contain an JSON object."; - } - return false; - } - Camera camera; - if (!ParseCamera(&camera, err, o, - store_original_json_for_extras_and_extensions_)) { - return false; - } - - model->cameras.emplace_back(std::move(camera)); - return true; - }); - - if (!success) { - return false; - } - } - - // 17. Parse Extensions - ParseExtensionsProperty(&model->extensions, err, v); - - // 18. Specific extension implementations - { - json_const_iterator rootIt; - if (FindMember(v, "extensions", rootIt) && IsObject(GetValue(rootIt))) { - const json& root = GetValue(rootIt); - - json_const_iterator it(ObjectBegin(root)); - json_const_iterator itEnd(ObjectEnd(root)); - for (; it != itEnd; ++it) { - // parse KHR_lights_punctual extension - std::string key(GetKey(it)); - if ((key == "KHR_lights_punctual") && IsObject(GetValue(it))) { - const json& object = GetValue(it); - json_const_iterator itLight; - if (FindMember(object, "lights", itLight)) { - const json& lights = GetValue(itLight); - if (!IsArray(lights)) { - continue; - } - - auto arrayIt(ArrayBegin(lights)); - auto arrayItEnd(ArrayEnd(lights)); - for (; arrayIt != arrayItEnd; ++arrayIt) { - Light light; - if (!ParseLight(&light, err, *arrayIt, - store_original_json_for_extras_and_extensions_)) { - return false; - } - model->lights.emplace_back(std::move(light)); - } - } - } - } - } - } - - // 19. Parse Extras - ParseExtrasProperty(&model->extras, v); - - if (store_original_json_for_extras_and_extensions_) { - model->extras_json_string = JsonToString(v["extras"]); - model->extensions_json_string = JsonToString(v["extensions"]); - } - - return true; - } - - bool TinyGLTF::LoadASCIIFromString(Model* model, std::string* err, - std::string* warn, const char* str, - unsigned int length, - const std::string& base_dir, - unsigned int check_sections) { - is_binary_ = false; - bin_data_ = nullptr; - bin_size_ = 0; - - return LoadFromString(model, err, warn, str, length, base_dir, - check_sections); - } - - bool TinyGLTF::LoadASCIIFromFile(Model* model, std::string* err, - std::string* warn, const std::string& filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - size_t sz = data.size(); - if (sz == 0) { - if (err) { - (*err) = "Empty file."; - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadASCIIFromString( - model, err, warn, reinterpret_cast(&data.at(0)), - static_cast(data.size()), basedir, check_sections); - - return ret; - } - - bool TinyGLTF::LoadBinaryFromMemory(Model* model, std::string* err, - std::string* warn, - const unsigned char* bytes, - unsigned int size, - const std::string& base_dir, - unsigned int check_sections) { - if (size < 20) { - if (err) { - (*err) = "Too short data size for glTF Binary."; - } - return false; - } - - if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && - bytes[3] == 'F') { - // ok - } - else { - if (err) { - (*err) = "Invalid magic."; - } - return false; - } - - unsigned int version; // 4 bytes - unsigned int length; // 4 bytes - unsigned int model_length; // 4 bytes - unsigned int model_format; // 4 bytes; - - // @todo { Endian swap for big endian machine. } - memcpy(&version, bytes + 4, 4); - swap4(&version); - memcpy(&length, bytes + 8, 4); - swap4(&length); - memcpy(&model_length, bytes + 12, 4); - swap4(&model_length); - memcpy(&model_format, bytes + 16, 4); - swap4(&model_format); - - // In case the Bin buffer is not present, the size is exactly 20 + size of - // JSON contents, - // so use "greater than" operator. - if ((20 + model_length > size) || (model_length < 1) || (length > size) || - (20 + model_length > length) || - (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. - if (err) { - (*err) = "Invalid glTF binary."; - } - return false; - } - - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - model_length); - - is_binary_ = true; - bin_data_ = bytes + 20 + model_length + - 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) - bin_size_ = - length - (20 + model_length); // extract header + JSON scene data. - - bool ret = LoadFromString(model, err, warn, - reinterpret_cast(&bytes[20]), - model_length, base_dir, check_sections); - if (!ret) { - return ret; - } - - return true; - } - - bool TinyGLTF::LoadBinaryFromFile(Model* model, std::string* err, - std::string* warn, - const std::string& filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), - static_cast(data.size()), - basedir, check_sections); - - return ret; - } - - /////////////////////// - // GLTF Serialization - /////////////////////// - namespace { - json JsonFromString(const char* s) { + if (!detail::IsObject(v)) { + // root is not an object. + if (err) { + (*err) = "Root element is not a JSON object\n"; + } + return false; + } + + { + bool version_found = false; + detail::json_const_iterator it; + if (detail::FindMember(v, "asset", it) && + detail::IsObject(detail::GetValue(it))) { + auto &itObj = detail::GetValue(it); + detail::json_const_iterator version_it; + std::string versionStr; + if (detail::FindMember(itObj, "version", version_it) && + detail::GetString(detail::GetValue(version_it), versionStr)) { + version_found = true; + } + } + if (version_found) { + // OK + } else if (check_sections & REQUIRE_VERSION) { + if (err) { + (*err) += "\"asset\" object not found in .gltf or not an object type\n"; + } + return false; + } + } + + // scene is not mandatory. + // FIXME Maybe a better way to handle it than removing the code + + auto IsArrayMemberPresent = [](const detail::json &_v, + const char *name) -> bool { + detail::json_const_iterator it; + return detail::FindMember(_v, name, it) && + detail::IsArray(detail::GetValue(it)); + }; + + { + if ((check_sections & REQUIRE_SCENES) && + !IsArrayMemberPresent(v, "scenes")) { + if (err) { + (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) { + if (err) { + (*err) += "\"nodes\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_ACCESSORS) && + !IsArrayMemberPresent(v, "accessors")) { + if (err) { + (*err) += "\"accessors\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_BUFFERS) && + !IsArrayMemberPresent(v, "buffers")) { + if (err) { + (*err) += "\"buffers\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_BUFFER_VIEWS) && + !IsArrayMemberPresent(v, "bufferViews")) { + if (err) { + (*err) += "\"bufferViews\" object not found in .gltf\n"; + } + return false; + } + } + + // Reset the model + (*model) = Model(); + + // 1. Parse Asset + { + detail::json_const_iterator it; + if (detail::FindMember(v, "asset", it) && + detail::IsObject(detail::GetValue(it))) { + const detail::json &root = detail::GetValue(it); + + ParseAsset(&model->asset, err, root, + store_original_json_for_extras_and_extensions_); + } + } + + using detail::ForEachInArray; + + // 2. Parse extensionUsed + { + ForEachInArray(v, "extensionsUsed", [&](const detail::json &o) { + std::string str; + detail::GetString(o, str); + model->extensionsUsed.emplace_back(std::move(str)); + return true; + }); + } + + { + ForEachInArray(v, "extensionsRequired", [&](const detail::json &o) { + std::string str; + detail::GetString(o, str); + model->extensionsRequired.emplace_back(std::move(str)); + return true; + }); + } + + // 3. Parse Buffer + { + bool success = ForEachInArray(v, "buffers", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`buffers' does not contain an JSON object."; + } + return false; + } + Buffer buffer; + if (!ParseBuffer(&buffer, err, o, + store_original_json_for_extras_and_extensions_, &fs, + &uri_cb, base_dir, max_external_file_size_, is_binary_, + bin_data_, bin_size_)) { + return false; + } + + model->buffers.emplace_back(std::move(buffer)); + return true; + }); + + if (!success) { + return false; + } + } + // 4. Parse BufferView + { + bool success = ForEachInArray(v, "bufferViews", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`bufferViews' does not contain an JSON object."; + } + return false; + } + BufferView bufferView; + if (!ParseBufferView(&bufferView, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->bufferViews.emplace_back(std::move(bufferView)); + return true; + }); + + if (!success) { + return false; + } + } + + // 5. Parse Accessor + { + bool success = ForEachInArray(v, "accessors", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`accessors' does not contain an JSON object."; + } + return false; + } + Accessor accessor; + if (!ParseAccessor(&accessor, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->accessors.emplace_back(std::move(accessor)); + return true; + }); + + if (!success) { + return false; + } + } + + // 6. Parse Mesh + { + bool success = ForEachInArray(v, "meshes", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`meshes' does not contain an JSON object."; + } + return false; + } + Mesh mesh; + if (!ParseMesh(&mesh, model, err, warn, o, + store_original_json_for_extras_and_extensions_, + strictness_)) { + return false; + } + + model->meshes.emplace_back(std::move(mesh)); + return true; + }); + + if (!success) { + return false; + } + } + + // Assign missing bufferView target types + // - Look for missing Mesh indices + // - Look for missing Mesh attributes + for (auto &mesh : model->meshes) { + for (auto &primitive : mesh.primitives) { + if (primitive.indices > + -1) // has indices from parsing step, must be Element Array Buffer + { + if (size_t(primitive.indices) >= model->accessors.size()) { + if (err) { + (*err) += "primitive indices accessor out of bounds"; + } + return false; + } + + const auto bufferView = + model->accessors[size_t(primitive.indices)].bufferView; + if (bufferView < 0) { + // skip, bufferView could be null(-1) for certain extensions + } else if (size_t(bufferView) >= model->bufferViews.size()) { + if (err) { + (*err) += "accessor[" + std::to_string(primitive.indices) + + "] invalid bufferView"; + } + return false; + } else { + model->bufferViews[size_t(bufferView)].target = + TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + // we could optionally check if accessors' bufferView type is Scalar, as + // it should be + } + } + + for (auto &attribute : primitive.attributes) { + const auto accessorsIndex = size_t(attribute.second); + if (accessorsIndex < model->accessors.size()) { + const auto bufferView = model->accessors[accessorsIndex].bufferView; + // bufferView could be null(-1) for sparse morph target + if (bufferView >= 0 && bufferView < (int)model->bufferViews.size()) { + model->bufferViews[size_t(bufferView)].target = + TINYGLTF_TARGET_ARRAY_BUFFER; + } + } + } + + for (auto &target : primitive.targets) { + for (auto &attribute : target) { + const auto accessorsIndex = size_t(attribute.second); + if (accessorsIndex < model->accessors.size()) { + const auto bufferView = model->accessors[accessorsIndex].bufferView; + // bufferView could be null(-1) for sparse morph target + if (bufferView >= 0 && + bufferView < (int)model->bufferViews.size()) { + model->bufferViews[size_t(bufferView)].target = + TINYGLTF_TARGET_ARRAY_BUFFER; + } + } + } + } + } + } + + // 7. Parse Node + { + bool success = ForEachInArray(v, "nodes", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`nodes' does not contain an JSON object."; + } + return false; + } + Node node; + if (!ParseNode(&node, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->nodes.emplace_back(std::move(node)); + return true; + }); + + if (!success) { + return false; + } + } + + // 8. Parse scenes. + { + bool success = ForEachInArray(v, "scenes", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`scenes' does not contain an JSON object."; + } + return false; + } + + Scene scene; + if (!ParseScene(&scene, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->scenes.emplace_back(std::move(scene)); + return true; + }); + + if (!success) { + return false; + } + } + + // 9. Parse default scenes. + { + detail::json_const_iterator rootIt; + int iVal; + if (detail::FindMember(v, "scene", rootIt) && + detail::GetInt(detail::GetValue(rootIt), iVal)) { + model->defaultScene = iVal; + } + } + + // 10. Parse Material + { + bool success = ForEachInArray(v, "materials", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`materials' does not contain an JSON object."; + } + return false; + } + Material material; + ParseStringProperty(&material.name, err, o, "name", false); + + if (!ParseMaterial(&material, err, warn, o, + store_original_json_for_extras_and_extensions_, + strictness_)) { + return false; + } + + model->materials.emplace_back(std::move(material)); + return true; + }); + + if (!success) { + return false; + } + } + + // 11. Parse Image + void *load_image_user_data{nullptr}; + + LoadImageDataOption load_image_option; + + if (user_image_loader_) { + // Use user supplied pointer + load_image_user_data = load_image_user_data_; + } else { + load_image_option.preserve_channels = preserve_image_channels_; + load_image_option.as_is = images_as_is_; + load_image_user_data = reinterpret_cast(&load_image_option); + } + + { + int idx = 0; + bool success = ForEachInArray(v, "images", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "image[" + std::to_string(idx) + "] is not a JSON object."; + } + return false; + } + Image image; + if (!ParseImage(&image, idx, err, warn, o, + store_original_json_for_extras_and_extensions_, base_dir, + max_external_file_size_, &fs, &uri_cb, + this->LoadImageData, load_image_user_data)) { + return false; + } + + if (image.bufferView != -1) { + // Load image from the buffer view. + if (size_t(image.bufferView) >= model->bufferViews.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] bufferView \"" << image.bufferView + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const BufferView &bufferView = + model->bufferViews[size_t(image.bufferView)]; + if (size_t(bufferView.buffer) >= model->buffers.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] buffer \"" << bufferView.buffer + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; + if (bufferView.byteOffset >= buffer.data.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] bufferView \"" << image.bufferView + << "\" indexed out of bounds of its buffer." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + bool ret = LoadImageData( + &image, idx, err, warn, image.width, image.height, + &buffer.data[bufferView.byteOffset], + static_cast(bufferView.byteLength), load_image_user_data); + if (!ret) { + return false; + } + } + + model->images.emplace_back(std::move(image)); + ++idx; + return true; + }); + + if (!success) { + return false; + } + } + + // 12. Parse Texture + { + bool success = ForEachInArray(v, "textures", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`textures' does not contain an JSON object."; + } + return false; + } + Texture texture; + if (!ParseTexture(&texture, err, o, + store_original_json_for_extras_and_extensions_, + base_dir)) { + return false; + } + + model->textures.emplace_back(std::move(texture)); + return true; + }); + + if (!success) { + return false; + } + } + + // 13. Parse Animation + { + bool success = ForEachInArray(v, "animations", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`animations' does not contain an JSON object."; + } + return false; + } + Animation animation; + if (!ParseAnimation(&animation, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->animations.emplace_back(std::move(animation)); + return true; + }); + + if (!success) { + return false; + } + } + + // 14. Parse Skin + { + bool success = ForEachInArray(v, "skins", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`skins' does not contain an JSON object."; + } + return false; + } + Skin skin; + if (!ParseSkin(&skin, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->skins.emplace_back(std::move(skin)); + return true; + }); + + if (!success) { + return false; + } + } + + // 15. Parse Sampler + { + bool success = ForEachInArray(v, "samplers", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`samplers' does not contain an JSON object."; + } + return false; + } + Sampler sampler; + if (!ParseSampler(&sampler, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->samplers.emplace_back(std::move(sampler)); + return true; + }); + + if (!success) { + return false; + } + } + + // 16. Parse Camera + { + bool success = ForEachInArray(v, "cameras", [&](const detail::json &o) { + if (!detail::IsObject(o)) { + if (err) { + (*err) += "`cameras' does not contain an JSON object."; + } + return false; + } + Camera camera; + if (!ParseCamera(&camera, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->cameras.emplace_back(std::move(camera)); + return true; + }); + + if (!success) { + return false; + } + } + + // 17. Parse Extras & Extensions + ParseExtrasAndExtensions(model, err, v, + store_original_json_for_extras_and_extensions_); + + // 18. Specific extension implementations + { + detail::json_const_iterator rootIt; + if (detail::FindMember(v, "extensions", rootIt) && + detail::IsObject(detail::GetValue(rootIt))) { + const detail::json &root = detail::GetValue(rootIt); + + detail::json_const_iterator it(detail::ObjectBegin(root)); + detail::json_const_iterator itEnd(detail::ObjectEnd(root)); + for (; it != itEnd; ++it) { + // parse KHR_lights_punctual extension + std::string key(detail::GetKey(it)); + if ((key == "KHR_lights_punctual") && + detail::IsObject(detail::GetValue(it))) { + const detail::json &object = detail::GetValue(it); + detail::json_const_iterator itLight; + if (detail::FindMember(object, "lights", itLight)) { + const detail::json &lights = detail::GetValue(itLight); + if (!detail::IsArray(lights)) { + continue; + } + + auto arrayIt(detail::ArrayBegin(lights)); + auto arrayItEnd(detail::ArrayEnd(lights)); + for (; arrayIt != arrayItEnd; ++arrayIt) { + Light light; + if (!ParseLight(&light, err, *arrayIt, + store_original_json_for_extras_and_extensions_)) { + return false; + } + model->lights.emplace_back(std::move(light)); + } + } + } + // parse KHR_audio extension + if ((key == "KHR_audio") && detail::IsObject(detail::GetValue(it))) { + const detail::json &object = detail::GetValue(it); + detail::json_const_iterator itKhrAudio; + if (detail::FindMember(object, "emitters", itKhrAudio)) { + const detail::json &emitters = detail::GetValue(itKhrAudio); + if (!detail::IsArray(emitters)) { + continue; + } + + auto arrayIt(detail::ArrayBegin(emitters)); + auto arrayItEnd(detail::ArrayEnd(emitters)); + for (; arrayIt != arrayItEnd; ++arrayIt) { + AudioEmitter emitter; + if (!ParseAudioEmitter( + &emitter, err, *arrayIt, + store_original_json_for_extras_and_extensions_)) { + return false; + } + model->audioEmitters.emplace_back(std::move(emitter)); + } + } + + if (detail::FindMember(object, "sources", itKhrAudio)) { + const detail::json &sources = detail::GetValue(itKhrAudio); + if (!detail::IsArray(sources)) { + continue; + } + + auto arrayIt(detail::ArrayBegin(sources)); + auto arrayItEnd(detail::ArrayEnd(sources)); + for (; arrayIt != arrayItEnd; ++arrayIt) { + AudioSource source; + if (!ParseAudioSource( + &source, err, *arrayIt, + store_original_json_for_extras_and_extensions_)) { + return false; + } + model->audioSources.emplace_back(std::move(source)); + } + } + } + } + } + } + + return true; +} + +bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, + std::string *warn, const char *str, + unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + is_binary_ = false; + bin_data_ = nullptr; + bin_size_ = 0; + + return LoadFromString(model, err, warn, str, length, base_dir, + check_sections); +} + +bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, + std::string *warn, const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + size_t sz = data.size(); + if (sz == 0) { + if (err) { + (*err) = "Empty file."; + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadASCIIFromString( + model, err, warn, reinterpret_cast(&data.at(0)), + static_cast(data.size()), basedir, check_sections); + + return ret; +} + +bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, + std::string *warn, + const unsigned char *bytes, + unsigned int size, + const std::string &base_dir, + unsigned int check_sections) { + if (size < 20) { + if (err) { + (*err) = "Too short data size for glTF Binary."; + } + return false; + } + + if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && + bytes[3] == 'F') { + // ok + } else { + if (err) { + (*err) = "Invalid magic."; + } + return false; + } + + unsigned int version; // 4 bytes + unsigned int length; // 4 bytes + unsigned int chunk0_length; // 4 bytes + unsigned int chunk0_format; // 4 bytes; + + memcpy(&version, bytes + 4, 4); + swap4(&version); + memcpy(&length, bytes + 8, 4); // Total glb size, including header and all chunks. + swap4(&length); + memcpy(&chunk0_length, bytes + 12, 4); // JSON data length + swap4(&chunk0_length); + memcpy(&chunk0_format, bytes + 16, 4); + swap4(&chunk0_format); + + // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-gltf-layout + // + // In case the Bin buffer is not present, the size is exactly 20 + size of + // JSON contents, + // so use "greater than" operator. + // + // https://github.com/syoyo/tinygltf/issues/372 + // Use 64bit uint to avoid integer overflow. + uint64_t header_and_json_size = 20ull + uint64_t(chunk0_length); + + if (header_and_json_size > (std::numeric_limits::max)()) { + // Do not allow 4GB or more GLB data. + if (err) { + (*err) = "Invalid glTF binary. GLB data exceeds 4GB."; + } + return false; + } + + if ((header_and_json_size > uint64_t(size)) || (chunk0_length < 1) || + (length > size) || (header_and_json_size > uint64_t(length)) || + (chunk0_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. + if (err) { + (*err) = "Invalid glTF binary."; + } + return false; + } + + // Padding check + // The start and the end of each chunk must be aligned to a 4-byte boundary. + // No padding check for chunk0 start since its 4byte-boundary is ensured. + if ((header_and_json_size % 4) != 0) { + if (err) { + (*err) = "JSON Chunk end does not aligned to a 4-byte boundary."; + } + return false; + } + + // std::cout << "header_and_json_size = " << header_and_json_size << "\n"; + // std::cout << "length = " << length << "\n"; + + // Chunk1(BIN) data + // The spec says: When the binary buffer is empty or when it is stored by + // other means, this chunk SHOULD be omitted. So when header + JSON data == + // binary size, Chunk1 is omitted. + if (header_and_json_size == uint64_t(length)) { + bin_data_ = nullptr; + bin_size_ = 0; + } else { + // Read Chunk1 info(BIN data) + // + // issue-440: + // 'SHOULD' in glTF spec means 'RECOMMENDED', + // So there is a situation that Chunk1(BIN) is composed of zero-sized BIN data + // (chunksize(0) + binformat(BIN) = 8bytes). + // + if ((header_and_json_size + 8ull) > uint64_t(length)) { + if (err) { + (*err) = + "Insufficient storage space for Chunk1(BIN data). At least Chunk1 " + "Must have 8 or more bytes, but got " + + std::to_string((header_and_json_size + 8ull) - uint64_t(length)) + + ".\n"; + } + return false; + } + + unsigned int chunk1_length{0}; // 4 bytes + unsigned int chunk1_format{0}; // 4 bytes; + memcpy(&chunk1_length, bytes + header_and_json_size, + 4); // Bin data length + swap4(&chunk1_length); + memcpy(&chunk1_format, bytes + header_and_json_size + 4, 4); + swap4(&chunk1_format); + + if (chunk1_format != 0x004e4942) { + if (err) { + (*err) = "Invalid chunkType for Chunk1."; + } + return false; + } + + if (chunk1_length == 0) { + + if (header_and_json_size + 8 > uint64_t(length)) { + if (err) { + (*err) = "BIN Chunk header location exceeds the GLB size."; + } + return false; + } + + bin_data_ = nullptr; + + } else { + + // When BIN chunk size is not zero, at least Chunk1 should have 12 bytes(8 bytes(header) + 4 bytes(bin + // payload could be 1~3 bytes, but need to be aligned to 4 bytes) + + if (chunk1_length < 4) { + if (err) { + (*err) = "Insufficient Chunk1(BIN) data size."; + } + return false; + } + + if ((chunk1_length % 4) != 0) { + if (strictness_==ParseStrictness::Permissive) { + if (warn) { + (*warn) += "BIN Chunk end is not aligned to a 4-byte boundary.\n"; + } + } + else { + if (err) { + (*err) = "BIN Chunk end is not aligned to a 4-byte boundary."; + } + return false; + } + } + + // +8 chunk1 header size. + if (uint64_t(chunk1_length) + header_and_json_size + 8 > uint64_t(length)) { + if (err) { + (*err) = "BIN Chunk data length exceeds the GLB size."; + } + return false; + } + + bin_data_ = bytes + header_and_json_size + + 8; // 4 bytes (bin_buffer_length) + 4 bytes(bin_buffer_format) + } + + bin_size_ = size_t(chunk1_length); + } + + is_binary_ = true; + + bool ret = LoadFromString(model, err, warn, + reinterpret_cast(&bytes[20]), + chunk0_length, base_dir, check_sections); + if (!ret) { + return ret; + } + + return true; +} + +bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, + std::string *warn, + const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), + static_cast(data.size()), + basedir, check_sections); + + return ret; +} + +/////////////////////// +// GLTF Serialization +/////////////////////// +namespace detail { +detail::json JsonFromString(const char *s) { #ifdef TINYGLTF_USE_RAPIDJSON - return json(s, GetAllocator()); + return detail::json(s, detail::GetAllocator()); #else - return json(s); + return detail::json(s); #endif - } +} - void JsonAssign(json& dest, const json& src) { +void JsonAssign(detail::json &dest, const detail::json &src) { #ifdef TINYGLTF_USE_RAPIDJSON - dest.CopyFrom(src, GetAllocator()); + dest.CopyFrom(src, detail::GetAllocator()); #else - dest = src; + dest = src; #endif - } +} - void JsonAddMember(json& o, const char* key, json&& value) { +void JsonAddMember(detail::json &o, const char *key, detail::json &&value) { #ifdef TINYGLTF_USE_RAPIDJSON - if (!o.IsObject()) { - o.SetObject(); - } - o.AddMember(json(key, GetAllocator()), std::move(value), GetAllocator()); + if (!o.IsObject()) { + o.SetObject(); + } + + // Issue 420. + // AddMember may create duplicated key, so use [] API when a key already + // exists. + // https://github.com/Tencent/rapidjson/issues/771#issuecomment-254386863 + detail::json_const_iterator it; + if (detail::FindMember(o, key, it)) { + o[key] = std::move(value); // replace + } else { + o.AddMember(detail::json(key, detail::GetAllocator()), std::move(value), + detail::GetAllocator()); + } #else - o[key] = std::move(value); + o[key] = std::move(value); #endif - } +} - void JsonPushBack(json& o, json&& value) { +void JsonPushBack(detail::json &o, detail::json &&value) { #ifdef TINYGLTF_USE_RAPIDJSON - o.PushBack(std::move(value), GetAllocator()); + o.PushBack(std::move(value), detail::GetAllocator()); #else - o.push_back(std::move(value)); + o.push_back(std::move(value)); #endif - } +} - bool JsonIsNull(const json& o) { +bool JsonIsNull(const detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - return o.IsNull(); + return o.IsNull(); #else - return o.is_null(); + return o.is_null(); #endif - } +} - void JsonSetObject(json& o) { +void JsonSetObject(detail::json &o) { #ifdef TINYGLTF_USE_RAPIDJSON - o.SetObject(); + o.SetObject(); #else - o = o.object({}); + o = o.object({}); #endif - } +} - void JsonReserveArray(json& o, size_t s) { +void JsonReserveArray(detail::json &o, size_t s) { #ifdef TINYGLTF_USE_RAPIDJSON - o.SetArray(); - o.Reserve(static_cast(s), GetAllocator()); + o.SetArray(); + o.Reserve(static_cast(s), detail::GetAllocator()); #endif - (void)(o); - (void)(s); - } - } // namespace + (void)(o); + (void)(s); +} +} // namespace detail - // typedef std::pair json_object_pair; +// typedef std::pair json_object_pair; - template - static void SerializeNumberProperty(const std::string& key, T number, - json& obj) { - // obj.insert( - // json_object_pair(key, json(static_cast(number)))); - // obj[key] = static_cast(number); - JsonAddMember(obj, key.c_str(), json(number)); - } +template +static void SerializeNumberProperty(const std::string &key, T number, + detail::json &obj) { + // obj.insert( + // json_object_pair(key, detail::json(static_cast(number)))); + // obj[key] = static_cast(number); + detail::JsonAddMember(obj, key.c_str(), detail::json(number)); +} #ifdef TINYGLTF_USE_RAPIDJSON - template <> - void SerializeNumberProperty(const std::string& key, size_t number, json& obj) { - JsonAddMember(obj, key.c_str(), json(static_cast(number))); - } +template <> +void SerializeNumberProperty(const std::string &key, size_t number, + detail::json &obj) { + detail::JsonAddMember(obj, key.c_str(), + detail::json(static_cast(number))); +} #endif - template - static void SerializeNumberArrayProperty(const std::string& key, - const std::vector& value, - json& obj) { - if (value.empty()) return; +template +static void SerializeNumberArrayProperty(const std::string &key, + const std::vector &value, + detail::json &obj) { + if (value.empty()) return; - json ary; - JsonReserveArray(ary, value.size()); - for (const auto& s : value) { - JsonPushBack(ary, json(s)); - } - JsonAddMember(obj, key.c_str(), std::move(ary)); - } + detail::json ary; + detail::JsonReserveArray(ary, value.size()); + for (const auto &s : value) { + detail::JsonPushBack(ary, detail::json(s)); + } + detail::JsonAddMember(obj, key.c_str(), std::move(ary)); +} - static void SerializeStringProperty(const std::string& key, - const std::string& value, json& obj) { - JsonAddMember(obj, key.c_str(), JsonFromString(value.c_str())); - } +static void SerializeStringProperty(const std::string &key, + const std::string &value, + detail::json &obj) { + detail::JsonAddMember(obj, key.c_str(), + detail::JsonFromString(value.c_str())); +} - static void SerializeStringArrayProperty(const std::string& key, - const std::vector& value, - json& obj) { - json ary; - JsonReserveArray(ary, value.size()); - for (auto& s : value) { - JsonPushBack(ary, JsonFromString(s.c_str())); - } - JsonAddMember(obj, key.c_str(), std::move(ary)); - } +static void SerializeStringArrayProperty(const std::string &key, + const std::vector &value, + detail::json &obj) { + detail::json ary; + detail::JsonReserveArray(ary, value.size()); + for (auto &s : value) { + detail::JsonPushBack(ary, detail::JsonFromString(s.c_str())); + } + detail::JsonAddMember(obj, key.c_str(), std::move(ary)); +} - static bool ValueToJson(const Value& value, json* ret) { - json obj; +static bool ValueToJson(const Value &value, detail::json *ret) { + detail::json obj; #ifdef TINYGLTF_USE_RAPIDJSON - switch (value.Type()) { - case REAL_TYPE: - obj.SetDouble(value.Get()); - break; - case INT_TYPE: - obj.SetInt(value.Get()); - break; - case BOOL_TYPE: - obj.SetBool(value.Get()); - break; - case STRING_TYPE: - obj.SetString(value.Get().c_str(), GetAllocator()); - break; - case ARRAY_TYPE: { - obj.SetArray(); - obj.Reserve(static_cast(value.ArrayLen()), - GetAllocator()); - for (unsigned int i = 0; i < value.ArrayLen(); ++i) { - Value elementValue = value.Get(int(i)); - json elementJson; - if (ValueToJson(value.Get(int(i)), &elementJson)) - obj.PushBack(std::move(elementJson), GetAllocator()); - } - break; - } - case BINARY_TYPE: - // TODO - // obj = json(value.Get>()); - return false; - break; - case OBJECT_TYPE: { - obj.SetObject(); - Value::Object objMap = value.Get(); - for (auto& it : objMap) { - json elementJson; - if (ValueToJson(it.second, &elementJson)) { - obj.AddMember(json(it.first.c_str(), GetAllocator()), - std::move(elementJson), GetAllocator()); - } - } - break; - } - case NULL_TYPE: - default: - return false; - } + switch (value.Type()) { + case REAL_TYPE: + obj.SetDouble(value.Get()); + break; + case INT_TYPE: + obj.SetInt(value.Get()); + break; + case BOOL_TYPE: + obj.SetBool(value.Get()); + break; + case STRING_TYPE: + obj.SetString(value.Get().c_str(), detail::GetAllocator()); + break; + case ARRAY_TYPE: { + obj.SetArray(); + obj.Reserve(static_cast(value.ArrayLen()), + detail::GetAllocator()); + for (unsigned int i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(int(i)); + detail::json elementJson; + if (ValueToJson(value.Get(int(i)), &elementJson)) + obj.PushBack(std::move(elementJson), detail::GetAllocator()); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = detail::json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + obj.SetObject(); + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + detail::json elementJson; + if (ValueToJson(it.second, &elementJson)) { + obj.AddMember(detail::json(it.first.c_str(), detail::GetAllocator()), + std::move(elementJson), detail::GetAllocator()); + } + } + break; + } + case NULL_TYPE: + default: + return false; + } #else - switch (value.Type()) { - case REAL_TYPE: - obj = json(value.Get()); - break; - case INT_TYPE: - obj = json(value.Get()); - break; - case BOOL_TYPE: - obj = json(value.Get()); - break; - case STRING_TYPE: - obj = json(value.Get()); - break; - case ARRAY_TYPE: { - for (unsigned int i = 0; i < value.ArrayLen(); ++i) { - Value elementValue = value.Get(int(i)); - json elementJson; - if (ValueToJson(value.Get(int(i)), &elementJson)) - obj.push_back(elementJson); - } - break; - } - case BINARY_TYPE: - // TODO - // obj = json(value.Get>()); - return false; - break; - case OBJECT_TYPE: { - Value::Object objMap = value.Get(); - for (auto& it : objMap) { - json elementJson; - if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; - } - break; - } - case NULL_TYPE: - default: - return false; - } + switch (value.Type()) { + case REAL_TYPE: + obj = detail::json(value.Get()); + break; + case INT_TYPE: + obj = detail::json(value.Get()); + break; + case BOOL_TYPE: + obj = detail::json(value.Get()); + break; + case STRING_TYPE: + obj = detail::json(value.Get()); + break; + case ARRAY_TYPE: { + for (size_t i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(i); + detail::json elementJson; + if (ValueToJson(value.Get(i), &elementJson)) + obj.push_back(elementJson); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + detail::json elementJson; + if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; + } + break; + } + case NULL_TYPE: + default: + return false; + } #endif - if (ret) *ret = std::move(obj); - return true; - } + if (ret) *ret = std::move(obj); + return true; +} - static void SerializeValue(const std::string& key, const Value& value, - json& obj) { - json ret; - if (ValueToJson(value, &ret)) { - JsonAddMember(obj, key.c_str(), std::move(ret)); - } - } +static void SerializeValue(const std::string &key, const Value &value, + detail::json &obj) { + detail::json ret; + if (ValueToJson(value, &ret)) { + detail::JsonAddMember(obj, key.c_str(), std::move(ret)); + } +} - static void SerializeGltfBufferData(const std::vector& data, - json& o) { - std::string header = "data:application/octet-stream;base64,"; - if (data.size() > 0) { - std::string encodedData = - base64_encode(&data[0], static_cast(data.size())); - SerializeStringProperty("uri", header + encodedData, o); - } - else { - // Issue #229 - // size 0 is allowd. Just emit mime header. - SerializeStringProperty("uri", header, o); - } - } +static void SerializeGltfBufferData(const std::vector &data, + detail::json &o) { + std::string header = "data:application/octet-stream;base64,"; + if (data.size() > 0) { + std::string encodedData = + base64_encode(&data[0], static_cast(data.size())); + SerializeStringProperty("uri", header + encodedData, o); + } else { + // Issue #229 + // size 0 is allowed. Just emit mime header. + SerializeStringProperty("uri", header, o); + } +} - static bool SerializeGltfBufferData(const std::vector& data, - const std::string& binFilename) { +static bool SerializeGltfBufferData(const std::vector &data, + const std::string &binFilename) { +#ifndef TINYGLTF_NO_FS #ifdef _WIN32 #if defined(__GLIBCXX__) // mingw - int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(), - _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); - __gnu_cxx::stdio_filebuf wfile_buf( - file_descriptor, std::ios_base::out | std::ios_base::binary); - std::ostream output(&wfile_buf); - if (!wfile_buf.is_open()) return false; + int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY, _S_IWRITE); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream output(&wfile_buf); + if (!wfile_buf.is_open()) return false; #elif defined(_MSC_VER) - std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary); - if (!output.is_open()) return false; + std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary); + if (!output.is_open()) return false; #else - std::ofstream output(binFilename.c_str(), std::ofstream::binary); - if (!output.is_open()) return false; + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + if (!output.is_open()) return false; #endif #else - std::ofstream output(binFilename.c_str(), std::ofstream::binary); - if (!output.is_open()) return false; + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + if (!output.is_open()) return false; #endif - if (data.size() > 0) { - output.write(reinterpret_cast(&data[0]), - std::streamsize(data.size())); - } - else { - // Issue #229 - // size 0 will be still valid buffer data. - // write empty file. - } - return true; - } + if (data.size() > 0) { + output.write(reinterpret_cast(&data[0]), + std::streamsize(data.size())); + } else { + // Issue #229 + // size 0 will be still valid buffer data. + // write empty file. + } + return true; +#else + return false; +#endif +} #if 0 // FIXME(syoyo): not used. will be removed in the future release. - static void SerializeParameterMap(ParameterMap& param, json& o) { - for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); - ++paramIt) { - if (paramIt->second.number_array.size()) { - SerializeNumberArrayProperty(paramIt->first, - paramIt->second.number_array, o); - } - else if (paramIt->second.json_double_value.size()) { - json json_double_value; - for (std::map::iterator it = - paramIt->second.json_double_value.begin(); - it != paramIt->second.json_double_value.end(); ++it) { - if (it->first == "index") { - json_double_value[it->first] = paramIt->second.TextureIndex(); - } - else { - json_double_value[it->first] = it->second; - } - } +static void SerializeParameterMap(ParameterMap ¶m, detail::json &o) { + for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); + ++paramIt) { + if (paramIt->second.number_array.size()) { + SerializeNumberArrayProperty(paramIt->first, + paramIt->second.number_array, o); + } else if (paramIt->second.json_double_value.size()) { + detail::json json_double_value; + for (std::map::iterator it = + paramIt->second.json_double_value.begin(); + it != paramIt->second.json_double_value.end(); ++it) { + if (it->first == "index") { + json_double_value[it->first] = paramIt->second.TextureIndex(); + } else { + json_double_value[it->first] = it->second; + } + } - o[paramIt->first] = json_double_value; - } - else if (!paramIt->second.string_value.empty()) { - SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); - } - else if (paramIt->second.has_number_value) { - o[paramIt->first] = paramIt->second.number_value; - } - else { - o[paramIt->first] = paramIt->second.bool_value; - } - } - } + o[paramIt->first] = json_double_value; + } else if (!paramIt->second.string_value.empty()) { + SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); + } else if (paramIt->second.has_number_value) { + o[paramIt->first] = paramIt->second.number_value; + } else { + o[paramIt->first] = paramIt->second.bool_value; + } + } +} #endif - static void SerializeExtensionMap(const ExtensionMap& extensions, json& o) { - if (!extensions.size()) return; - - json extMap; - for (ExtensionMap::const_iterator extIt = extensions.begin(); - extIt != extensions.end(); ++extIt) { - // Allow an empty object for extension(#97) - json ret; - bool isNull = true; - if (ValueToJson(extIt->second, &ret)) { - isNull = JsonIsNull(ret); - JsonAddMember(extMap, extIt->first.c_str(), std::move(ret)); - } - if (isNull) { - if (!(extIt->first.empty())) { // name should not be empty, but for sure - // create empty object so that an extension name is still included in - // json. - json empty; - JsonSetObject(empty); - JsonAddMember(extMap, extIt->first.c_str(), std::move(empty)); - } - } - } - JsonAddMember(o, "extensions", std::move(extMap)); - } - - static void SerializeGltfAccessor(Accessor& accessor, json& o) { - if (accessor.bufferView >= 0) - SerializeNumberProperty("bufferView", accessor.bufferView, o); - - if (accessor.byteOffset != 0) - SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); - - SerializeNumberProperty("componentType", accessor.componentType, o); - SerializeNumberProperty("count", accessor.count, o); - - if ((accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) || - (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)) { - SerializeNumberArrayProperty("min", accessor.minValues, o); - SerializeNumberArrayProperty("max", accessor.maxValues, o); - } - else { - // Issue #301. Serialize as integer. - // Assume int value is within [-2**31-1, 2**31-1] - { - std::vector values; - std::transform(accessor.minValues.begin(), accessor.minValues.end(), - std::back_inserter(values), - [](double v) { return static_cast(v); }); - - SerializeNumberArrayProperty("min", values, o); - } - - { - std::vector values; - std::transform(accessor.maxValues.begin(), accessor.maxValues.end(), - std::back_inserter(values), - [](double v) { return static_cast(v); }); - - SerializeNumberArrayProperty("max", values, o); - } - } - - if (accessor.normalized) - SerializeValue("normalized", Value(accessor.normalized), o); - std::string type; - switch (accessor.type) { - case TINYGLTF_TYPE_SCALAR: - type = "SCALAR"; - break; - case TINYGLTF_TYPE_VEC2: - type = "VEC2"; - break; - case TINYGLTF_TYPE_VEC3: - type = "VEC3"; - break; - case TINYGLTF_TYPE_VEC4: - type = "VEC4"; - break; - case TINYGLTF_TYPE_MAT2: - type = "MAT2"; - break; - case TINYGLTF_TYPE_MAT3: - type = "MAT3"; - break; - case TINYGLTF_TYPE_MAT4: - type = "MAT4"; - break; - } - - SerializeStringProperty("type", type, o); - if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); - - if (accessor.extras.Type() != NULL_TYPE) { - SerializeValue("extras", accessor.extras, o); - } - - // sparse - if (accessor.sparse.isSparse) - { - json sparse; - SerializeNumberProperty("count", accessor.sparse.count, sparse); - { - json indices; - SerializeNumberProperty("bufferView", accessor.sparse.indices.bufferView, indices); - SerializeNumberProperty("byteOffset", accessor.sparse.indices.byteOffset, indices); - SerializeNumberProperty("componentType", accessor.sparse.indices.componentType, indices); - JsonAddMember(sparse, "indices", std::move(indices)); - } - { - json values; - SerializeNumberProperty("bufferView", accessor.sparse.values.bufferView, values); - SerializeNumberProperty("byteOffset", accessor.sparse.values.byteOffset, values); - JsonAddMember(sparse, "values", std::move(values)); - } - JsonAddMember(o, "sparse", std::move(sparse)); - } - } - - static void SerializeGltfAnimationChannel(AnimationChannel& channel, json& o) { - SerializeNumberProperty("sampler", channel.sampler, o); - { - json target; - SerializeNumberProperty("node", channel.target_node, target); - SerializeStringProperty("path", channel.target_path, target); - - SerializeExtensionMap(channel.target_extensions, target); - - JsonAddMember(o, "target", std::move(target)); - } - - if (channel.extras.Type() != NULL_TYPE) { - SerializeValue("extras", channel.extras, o); - } - - SerializeExtensionMap(channel.extensions, o); - } - - static void SerializeGltfAnimationSampler(AnimationSampler& sampler, json& o) { - SerializeNumberProperty("input", sampler.input, o); - SerializeNumberProperty("output", sampler.output, o); - SerializeStringProperty("interpolation", sampler.interpolation, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } - } - - static void SerializeGltfAnimation(Animation& animation, json& o) { - if (!animation.name.empty()) - SerializeStringProperty("name", animation.name, o); - - { - json channels; - JsonReserveArray(channels, animation.channels.size()); - for (unsigned int i = 0; i < animation.channels.size(); ++i) { - json channel; - AnimationChannel gltfChannel = animation.channels[i]; - SerializeGltfAnimationChannel(gltfChannel, channel); - JsonPushBack(channels, std::move(channel)); - } - - JsonAddMember(o, "channels", std::move(channels)); - } - - { - json samplers; - JsonReserveArray(samplers, animation.samplers.size()); - for (unsigned int i = 0; i < animation.samplers.size(); ++i) { - json sampler; - AnimationSampler gltfSampler = animation.samplers[i]; - SerializeGltfAnimationSampler(gltfSampler, sampler); - JsonPushBack(samplers, std::move(sampler)); - } - JsonAddMember(o, "samplers", std::move(samplers)); - } - - if (animation.extras.Type() != NULL_TYPE) { - SerializeValue("extras", animation.extras, o); - } - - SerializeExtensionMap(animation.extensions, o); - } - - static void SerializeGltfAsset(Asset& asset, json& o) { - if (!asset.generator.empty()) { - SerializeStringProperty("generator", asset.generator, o); - } - - if (!asset.copyright.empty()) { - SerializeStringProperty("copyright", asset.copyright, o); - } - - if (asset.version.empty()) { - // Just in case - // `version` must be defined - asset.version = "2.0"; - } - - // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0? - SerializeStringProperty("version", asset.version, o); - - if (asset.extras.Keys().size()) { - SerializeValue("extras", asset.extras, o); - } - - SerializeExtensionMap(asset.extensions, o); - } - - static void SerializeGltfBufferBin(Buffer& buffer, json& o, - std::vector& binBuffer) { - SerializeNumberProperty("byteLength", buffer.data.size(), o); - binBuffer = buffer.data; - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } - } - - static void SerializeGltfBuffer(Buffer& buffer, json& o) { - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeGltfBufferData(buffer.data, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } - } - - static bool SerializeGltfBuffer(Buffer& buffer, json& o, - const std::string& binFilename, - const std::string& binBaseFilename) { - if (!SerializeGltfBufferData(buffer.data, binFilename)) return false; - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeStringProperty("uri", binBaseFilename, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } - return true; - } - - static void SerializeGltfBufferView(BufferView& bufferView, json& o) { - SerializeNumberProperty("buffer", bufferView.buffer, o); - SerializeNumberProperty("byteLength", bufferView.byteLength, o); - - // byteStride is optional, minimum allowed is 4 - if (bufferView.byteStride >= 4) { - SerializeNumberProperty("byteStride", bufferView.byteStride, o); - } - // byteOffset is optional, default is 0 - if (bufferView.byteOffset > 0) { - SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); - } - // Target is optional, check if it contains a valid value - if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || - bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { - SerializeNumberProperty("target", bufferView.target, o); - } - if (bufferView.name.size()) { - SerializeStringProperty("name", bufferView.name, o); - } - - if (bufferView.extras.Type() != NULL_TYPE) { - SerializeValue("extras", bufferView.extras, o); - } - } - - static void SerializeGltfImage(Image& image, json& o) { - // if uri empty, the mimeType and bufferview should be set - if (image.uri.empty()) { - SerializeStringProperty("mimeType", image.mimeType, o); - SerializeNumberProperty("bufferView", image.bufferView, o); - } - else { - // TODO(syoyo): dlib::urilencode? - SerializeStringProperty("uri", image.uri, o); - } - - if (image.name.size()) { - SerializeStringProperty("name", image.name, o); - } - - if (image.extras.Type() != NULL_TYPE) { - SerializeValue("extras", image.extras, o); - } - - SerializeExtensionMap(image.extensions, o); - } - - static void SerializeGltfTextureInfo(TextureInfo& texinfo, json& o) { - SerializeNumberProperty("index", texinfo.index, o); - - if (texinfo.texCoord != 0) { - SerializeNumberProperty("texCoord", texinfo.texCoord, o); - } - - if (texinfo.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texinfo.extras, o); - } - - SerializeExtensionMap(texinfo.extensions, o); - } - - static void SerializeGltfNormalTextureInfo(NormalTextureInfo& texinfo, - json& o) { - SerializeNumberProperty("index", texinfo.index, o); - - if (texinfo.texCoord != 0) { - SerializeNumberProperty("texCoord", texinfo.texCoord, o); - } - - if (!TINYGLTF_DOUBLE_EQUAL(texinfo.scale, 1.0)) { - SerializeNumberProperty("scale", texinfo.scale, o); - } - - if (texinfo.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texinfo.extras, o); - } - - SerializeExtensionMap(texinfo.extensions, o); - } - - static void SerializeGltfOcclusionTextureInfo(OcclusionTextureInfo& texinfo, - json& o) { - SerializeNumberProperty("index", texinfo.index, o); - - if (texinfo.texCoord != 0) { - SerializeNumberProperty("texCoord", texinfo.texCoord, o); - } - - if (!TINYGLTF_DOUBLE_EQUAL(texinfo.strength, 1.0)) { - SerializeNumberProperty("strength", texinfo.strength, o); - } - - if (texinfo.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texinfo.extras, o); - } - - SerializeExtensionMap(texinfo.extensions, o); - } - - static void SerializeGltfPbrMetallicRoughness(PbrMetallicRoughness& pbr, - json& o) { - std::vector default_baseColorFactor = { 1.0, 1.0, 1.0, 1.0 }; - if (!Equals(pbr.baseColorFactor, default_baseColorFactor)) { - SerializeNumberArrayProperty("baseColorFactor", pbr.baseColorFactor, - o); - } - - if (!TINYGLTF_DOUBLE_EQUAL(pbr.metallicFactor, 1.0)) { - SerializeNumberProperty("metallicFactor", pbr.metallicFactor, o); - } - - if (!TINYGLTF_DOUBLE_EQUAL(pbr.roughnessFactor, 1.0)) { - SerializeNumberProperty("roughnessFactor", pbr.roughnessFactor, o); - } - - if (pbr.baseColorTexture.index > -1) { - json texinfo; - SerializeGltfTextureInfo(pbr.baseColorTexture, texinfo); - JsonAddMember(o, "baseColorTexture", std::move(texinfo)); - } - - if (pbr.metallicRoughnessTexture.index > -1) { - json texinfo; - SerializeGltfTextureInfo(pbr.metallicRoughnessTexture, texinfo); - JsonAddMember(o, "metallicRoughnessTexture", std::move(texinfo)); - } - - SerializeExtensionMap(pbr.extensions, o); - - if (pbr.extras.Type() != NULL_TYPE) { - SerializeValue("extras", pbr.extras, o); - } - } - - static void SerializeGltfMaterial(Material& material, json& o) { - if (material.name.size()) { - SerializeStringProperty("name", material.name, o); - } - - // QUESTION(syoyo): Write material parameters regardless of its default value? - - if (!TINYGLTF_DOUBLE_EQUAL(material.alphaCutoff, 0.5)) { - SerializeNumberProperty("alphaCutoff", material.alphaCutoff, o); - } - - if (material.alphaMode.compare("OPAQUE") != 0) { - SerializeStringProperty("alphaMode", material.alphaMode, o); - } - - if (material.doubleSided != false) - JsonAddMember(o, "doubleSided", json(material.doubleSided)); - - if (material.normalTexture.index > -1) { - json texinfo; - SerializeGltfNormalTextureInfo(material.normalTexture, texinfo); - JsonAddMember(o, "normalTexture", std::move(texinfo)); - } - - if (material.occlusionTexture.index > -1) { - json texinfo; - SerializeGltfOcclusionTextureInfo(material.occlusionTexture, texinfo); - JsonAddMember(o, "occlusionTexture", std::move(texinfo)); - } - - if (material.emissiveTexture.index > -1) { - json texinfo; - SerializeGltfTextureInfo(material.emissiveTexture, texinfo); - JsonAddMember(o, "emissiveTexture", std::move(texinfo)); - } - - std::vector default_emissiveFactor = { 0.0, 0.0, 0.0 }; - if (!Equals(material.emissiveFactor, default_emissiveFactor)) { - SerializeNumberArrayProperty("emissiveFactor", - material.emissiveFactor, o); - } - - { - json pbrMetallicRoughness; - SerializeGltfPbrMetallicRoughness(material.pbrMetallicRoughness, - pbrMetallicRoughness); - // Issue 204 - // Do not serialize `pbrMetallicRoughness` if pbrMetallicRoughness has all - // default values(json is null). Otherwise it will serialize to - // `pbrMetallicRoughness : null`, which cannot be read by other glTF - // importers(and validators). - // - if (!JsonIsNull(pbrMetallicRoughness)) { - JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); - } - } +static void SerializeExtensionMap(const ExtensionMap &extensions, + detail::json &o) { + if (!extensions.size()) return; + + detail::json extMap; + for (ExtensionMap::const_iterator extIt = extensions.begin(); + extIt != extensions.end(); ++extIt) { + // Allow an empty object for extension(#97) + detail::json ret; + bool isNull = true; + if (ValueToJson(extIt->second, &ret)) { + isNull = detail::JsonIsNull(ret); + detail::JsonAddMember(extMap, extIt->first.c_str(), std::move(ret)); + } + if (isNull) { + if (!(extIt->first.empty())) { // name should not be empty, but for sure + // create empty object so that an extension name is still included in + // json. + detail::json empty; + detail::JsonSetObject(empty); + detail::JsonAddMember(extMap, extIt->first.c_str(), std::move(empty)); + } + } + } + detail::JsonAddMember(o, "extensions", std::move(extMap)); +} + +static void SerializeExtras(const Value &extras, detail::json &o) { + if (extras.Type() != NULL_TYPE) SerializeValue("extras", extras, o); +} + +template +void SerializeExtrasAndExtensions(const GltfType &obj, detail::json &o) { + SerializeExtensionMap(obj.extensions, o); + SerializeExtras(obj.extras, o); +} + +static void SerializeGltfAccessor(const Accessor &accessor, detail::json &o) { + if (accessor.bufferView >= 0) + SerializeNumberProperty("bufferView", accessor.bufferView, o); + + if (accessor.byteOffset != 0) + SerializeNumberProperty("byteOffset", accessor.byteOffset, o); + + SerializeNumberProperty("componentType", accessor.componentType, o); + SerializeNumberProperty("count", accessor.count, o); + + if ((accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) || + (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)) { + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + } else { + // Issue #301. Serialize as integer. + // Assume int value is within [-2**31-1, 2**31-1] + { + std::vector values; + std::transform(accessor.minValues.begin(), accessor.minValues.end(), + std::back_inserter(values), + [](double v) { return static_cast(v); }); + + SerializeNumberArrayProperty("min", values, o); + } + + { + std::vector values; + std::transform(accessor.maxValues.begin(), accessor.maxValues.end(), + std::back_inserter(values), + [](double v) { return static_cast(v); }); + + SerializeNumberArrayProperty("max", values, o); + } + } + + if (accessor.normalized) + SerializeValue("normalized", Value(accessor.normalized), o); + std::string type; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + type = "SCALAR"; + break; + case TINYGLTF_TYPE_VEC2: + type = "VEC2"; + break; + case TINYGLTF_TYPE_VEC3: + type = "VEC3"; + break; + case TINYGLTF_TYPE_VEC4: + type = "VEC4"; + break; + case TINYGLTF_TYPE_MAT2: + type = "MAT2"; + break; + case TINYGLTF_TYPE_MAT3: + type = "MAT3"; + break; + case TINYGLTF_TYPE_MAT4: + type = "MAT4"; + break; + } + + SerializeStringProperty("type", type, o); + if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); + + SerializeExtrasAndExtensions(accessor, o); + + // sparse + if (accessor.sparse.isSparse) { + detail::json sparse; + SerializeNumberProperty("count", accessor.sparse.count, sparse); + { + detail::json indices; + SerializeNumberProperty("bufferView", + accessor.sparse.indices.bufferView, indices); + SerializeNumberProperty("byteOffset", + accessor.sparse.indices.byteOffset, indices); + SerializeNumberProperty( + "componentType", accessor.sparse.indices.componentType, indices); + SerializeExtrasAndExtensions(accessor.sparse.indices, indices); + detail::JsonAddMember(sparse, "indices", std::move(indices)); + } + { + detail::json values; + SerializeNumberProperty("bufferView", + accessor.sparse.values.bufferView, values); + SerializeNumberProperty("byteOffset", + accessor.sparse.values.byteOffset, values); + SerializeExtrasAndExtensions(accessor.sparse.values, values); + detail::JsonAddMember(sparse, "values", std::move(values)); + } + SerializeExtrasAndExtensions(accessor.sparse, sparse); + detail::JsonAddMember(o, "sparse", std::move(sparse)); + } +} + +static void SerializeGltfAnimationChannel(const AnimationChannel &channel, + detail::json &o) { + SerializeNumberProperty("sampler", channel.sampler, o); + { + detail::json target; + + if (channel.target_node >= 0) { + SerializeNumberProperty("node", channel.target_node, target); + } + + SerializeStringProperty("path", channel.target_path, target); + + SerializeExtensionMap(channel.target_extensions, target); + SerializeExtras(channel.target_extras, target); + + detail::JsonAddMember(o, "target", std::move(target)); + } + + SerializeExtrasAndExtensions(channel, o); +} + +static void SerializeGltfAnimationSampler(const AnimationSampler &sampler, + detail::json &o) { + SerializeNumberProperty("input", sampler.input, o); + SerializeNumberProperty("output", sampler.output, o); + SerializeStringProperty("interpolation", sampler.interpolation, o); + + SerializeExtrasAndExtensions(sampler, o); +} + +static void SerializeGltfAnimation(const Animation &animation, + detail::json &o) { + if (!animation.name.empty()) + SerializeStringProperty("name", animation.name, o); + + { + detail::json channels; + detail::JsonReserveArray(channels, animation.channels.size()); + for (unsigned int i = 0; i < animation.channels.size(); ++i) { + detail::json channel; + AnimationChannel gltfChannel = animation.channels[i]; + SerializeGltfAnimationChannel(gltfChannel, channel); + detail::JsonPushBack(channels, std::move(channel)); + } + + detail::JsonAddMember(o, "channels", std::move(channels)); + } + + { + detail::json samplers; + detail::JsonReserveArray(samplers, animation.samplers.size()); + for (unsigned int i = 0; i < animation.samplers.size(); ++i) { + detail::json sampler; + AnimationSampler gltfSampler = animation.samplers[i]; + SerializeGltfAnimationSampler(gltfSampler, sampler); + detail::JsonPushBack(samplers, std::move(sampler)); + } + detail::JsonAddMember(o, "samplers", std::move(samplers)); + } + + SerializeExtrasAndExtensions(animation, o); +} + +static void SerializeGltfAsset(const Asset &asset, detail::json &o) { + if (!asset.generator.empty()) { + SerializeStringProperty("generator", asset.generator, o); + } + + if (!asset.copyright.empty()) { + SerializeStringProperty("copyright", asset.copyright, o); + } + + auto version = asset.version; + if (version.empty()) { + // Just in case + // `version` must be defined + version = "2.0"; + } + + // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0? + SerializeStringProperty("version", version, o); + + SerializeExtrasAndExtensions(asset, o); +} + +static void SerializeGltfBufferBin(const Buffer &buffer, detail::json &o, + std::vector &binBuffer) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + binBuffer = buffer.data; + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + SerializeExtrasAndExtensions(buffer, o); +} + +static void SerializeGltfBuffer(const Buffer &buffer, detail::json &o) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeGltfBufferData(buffer.data, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + SerializeExtrasAndExtensions(buffer, o); +} + +static bool SerializeGltfBuffer(const Buffer &buffer, detail::json &o, + const std::string &binFilename, + const std::string &binUri) { + if (!SerializeGltfBufferData(buffer.data, binFilename)) return false; + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeStringProperty("uri", binUri, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + SerializeExtrasAndExtensions(buffer, o); + return true; +} + +static void SerializeGltfBufferView(const BufferView &bufferView, + detail::json &o) { + SerializeNumberProperty("buffer", bufferView.buffer, o); + SerializeNumberProperty("byteLength", bufferView.byteLength, o); + + // byteStride is optional, minimum allowed is 4 + if (bufferView.byteStride >= 4) { + SerializeNumberProperty("byteStride", bufferView.byteStride, o); + } + // byteOffset is optional, default is 0 + if (bufferView.byteOffset > 0) { + SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); + } + // Target is optional, check if it contains a valid value + if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || + bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { + SerializeNumberProperty("target", bufferView.target, o); + } + if (bufferView.name.size()) { + SerializeStringProperty("name", bufferView.name, o); + } + + SerializeExtrasAndExtensions(bufferView, o); +} + +static void SerializeGltfImage(const Image &image, const std::string &uri, + detail::json &o) { + // From 2.7.0, we look for `uri` parameter, not `Image.uri` + // if uri is empty, the mimeType and bufferview should be set + if (uri.empty()) { + SerializeStringProperty("mimeType", image.mimeType, o); + SerializeNumberProperty("bufferView", image.bufferView, o); + } else { + SerializeStringProperty("uri", uri, o); + } + + if (image.name.size()) { + SerializeStringProperty("name", image.name, o); + } + + SerializeExtrasAndExtensions(image, o); +} + +static void SerializeGltfTextureInfo(const TextureInfo &texinfo, + detail::json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + SerializeExtrasAndExtensions(texinfo, o); +} + +static void SerializeGltfNormalTextureInfo(const NormalTextureInfo &texinfo, + detail::json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(texinfo.scale, 1.0)) { + SerializeNumberProperty("scale", texinfo.scale, o); + } + + SerializeExtrasAndExtensions(texinfo, o); +} + +static void SerializeGltfOcclusionTextureInfo( + const OcclusionTextureInfo &texinfo, detail::json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(texinfo.strength, 1.0)) { + SerializeNumberProperty("strength", texinfo.strength, o); + } + + SerializeExtrasAndExtensions(texinfo, o); +} + +static void SerializeGltfPbrMetallicRoughness(const PbrMetallicRoughness &pbr, + detail::json &o) { + std::vector default_baseColorFactor = {1.0, 1.0, 1.0, 1.0}; + if (!Equals(pbr.baseColorFactor, default_baseColorFactor)) { + SerializeNumberArrayProperty("baseColorFactor", pbr.baseColorFactor, + o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(pbr.metallicFactor, 1.0)) { + SerializeNumberProperty("metallicFactor", pbr.metallicFactor, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(pbr.roughnessFactor, 1.0)) { + SerializeNumberProperty("roughnessFactor", pbr.roughnessFactor, o); + } + + if (pbr.baseColorTexture.index > -1) { + detail::json texinfo; + SerializeGltfTextureInfo(pbr.baseColorTexture, texinfo); + detail::JsonAddMember(o, "baseColorTexture", std::move(texinfo)); + } + + if (pbr.metallicRoughnessTexture.index > -1) { + detail::json texinfo; + SerializeGltfTextureInfo(pbr.metallicRoughnessTexture, texinfo); + detail::JsonAddMember(o, "metallicRoughnessTexture", std::move(texinfo)); + } + + SerializeExtrasAndExtensions(pbr, o); +} + +static void SerializeGltfMaterial(const Material &material, detail::json &o) { + if (material.name.size()) { + SerializeStringProperty("name", material.name, o); + } + + // QUESTION(syoyo): Write material parameters regardless of its default value? + + if (!TINYGLTF_DOUBLE_EQUAL(material.alphaCutoff, 0.5)) { + SerializeNumberProperty("alphaCutoff", material.alphaCutoff, o); + } + + if (material.alphaMode.compare("OPAQUE") != 0) { + SerializeStringProperty("alphaMode", material.alphaMode, o); + } + + if (material.doubleSided != false) + detail::JsonAddMember(o, "doubleSided", detail::json(material.doubleSided)); + + if (material.normalTexture.index > -1) { + detail::json texinfo; + SerializeGltfNormalTextureInfo(material.normalTexture, texinfo); + detail::JsonAddMember(o, "normalTexture", std::move(texinfo)); + } + + if (material.occlusionTexture.index > -1) { + detail::json texinfo; + SerializeGltfOcclusionTextureInfo(material.occlusionTexture, texinfo); + detail::JsonAddMember(o, "occlusionTexture", std::move(texinfo)); + } + + if (material.emissiveTexture.index > -1) { + detail::json texinfo; + SerializeGltfTextureInfo(material.emissiveTexture, texinfo); + detail::JsonAddMember(o, "emissiveTexture", std::move(texinfo)); + } + + std::vector default_emissiveFactor = {0.0, 0.0, 0.0}; + if (!Equals(material.emissiveFactor, default_emissiveFactor)) { + SerializeNumberArrayProperty("emissiveFactor", + material.emissiveFactor, o); + } + + { + detail::json pbrMetallicRoughness; + SerializeGltfPbrMetallicRoughness(material.pbrMetallicRoughness, + pbrMetallicRoughness); + // Issue 204 + // Do not serialize `pbrMetallicRoughness` if pbrMetallicRoughness has all + // default values(json is null). Otherwise it will serialize to + // `pbrMetallicRoughness : null`, which cannot be read by other glTF + // importers (and validators). + // + if (!detail::JsonIsNull(pbrMetallicRoughness)) { + detail::JsonAddMember(o, "pbrMetallicRoughness", + std::move(pbrMetallicRoughness)); + } + } #if 0 // legacy way. just for the record. - if (material.values.size()) { - json pbrMetallicRoughness; - SerializeParameterMap(material.values, pbrMetallicRoughness); - JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); - } - - SerializeParameterMap(material.additionalValues, o); -#else + if (material.values.size()) { + detail::json pbrMetallicRoughness; + SerializeParameterMap(material.values, pbrMetallicRoughness); + detail::JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); + } + SerializeParameterMap(material.additionalValues, o); #endif - SerializeExtensionMap(material.extensions, o); - - if (material.extras.Type() != NULL_TYPE) { - SerializeValue("extras", material.extras, o); - } - } - - static void SerializeGltfMesh(Mesh& mesh, json& o) { - json primitives; - JsonReserveArray(primitives, mesh.primitives.size()); - for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { - json primitive; - const Primitive& gltfPrimitive = mesh.primitives[i]; // don't make a copy - { - json attributes; - for (auto attrIt = gltfPrimitive.attributes.begin(); - attrIt != gltfPrimitive.attributes.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, attributes); - } - - JsonAddMember(primitive, "attributes", std::move(attributes)); - } - - // Indicies is optional - if (gltfPrimitive.indices > -1) { - SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); - } - // Material is optional - if (gltfPrimitive.material > -1) { - SerializeNumberProperty("material", gltfPrimitive.material, - primitive); - } - SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); - - // Morph targets - if (gltfPrimitive.targets.size()) { - json targets; - JsonReserveArray(targets, gltfPrimitive.targets.size()); - for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { - json targetAttributes; - std::map targetData = gltfPrimitive.targets[k]; - for (std::map::iterator attrIt = targetData.begin(); - attrIt != targetData.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, - targetAttributes); - } - JsonPushBack(targets, std::move(targetAttributes)); - } - JsonAddMember(primitive, "targets", std::move(targets)); - } - - SerializeExtensionMap(gltfPrimitive.extensions, primitive); - - if (gltfPrimitive.extras.Type() != NULL_TYPE) { - SerializeValue("extras", gltfPrimitive.extras, primitive); - } - - JsonPushBack(primitives, std::move(primitive)); - } - - JsonAddMember(o, "primitives", std::move(primitives)); - - if (mesh.weights.size()) { - SerializeNumberArrayProperty("weights", mesh.weights, o); - } - - if (mesh.name.size()) { - SerializeStringProperty("name", mesh.name, o); - } - - SerializeExtensionMap(mesh.extensions, o); - if (mesh.extras.Type() != NULL_TYPE) { - SerializeValue("extras", mesh.extras, o); - } - } - - static void SerializeSpotLight(SpotLight& spot, json& o) { - SerializeNumberProperty("innerConeAngle", spot.innerConeAngle, o); - SerializeNumberProperty("outerConeAngle", spot.outerConeAngle, o); - SerializeExtensionMap(spot.extensions, o); - if (spot.extras.Type() != NULL_TYPE) { - SerializeValue("extras", spot.extras, o); - } - } - - static void SerializeGltfLight(Light& light, json& o) { - if (!light.name.empty()) SerializeStringProperty("name", light.name, o); - SerializeNumberProperty("intensity", light.intensity, o); - if (light.range > 0.0) { - SerializeNumberProperty("range", light.range, o); - } - SerializeNumberArrayProperty("color", light.color, o); - SerializeStringProperty("type", light.type, o); - if (light.type == "spot") { - json spot; - SerializeSpotLight(light.spot, spot); - JsonAddMember(o, "spot", std::move(spot)); - } - SerializeExtensionMap(light.extensions, o); - if (light.extras.Type() != NULL_TYPE) { - SerializeValue("extras", light.extras, o); - } - } - - static void SerializeGltfNode(Node& node, json& o) { - if (node.translation.size() > 0) { - SerializeNumberArrayProperty("translation", node.translation, o); - } - if (node.rotation.size() > 0) { - SerializeNumberArrayProperty("rotation", node.rotation, o); - } - if (node.scale.size() > 0) { - SerializeNumberArrayProperty("scale", node.scale, o); - } - if (node.matrix.size() > 0) { - SerializeNumberArrayProperty("matrix", node.matrix, o); - } - if (node.mesh != -1) { - SerializeNumberProperty("mesh", node.mesh, o); - } - - if (node.skin != -1) { - SerializeNumberProperty("skin", node.skin, o); - } - - if (node.camera != -1) { - SerializeNumberProperty("camera", node.camera, o); - } - - if (node.weights.size() > 0) { - SerializeNumberArrayProperty("weights", node.weights, o); - } - - if (node.extras.Type() != NULL_TYPE) { - SerializeValue("extras", node.extras, o); - } - - SerializeExtensionMap(node.extensions, o); - if (!node.name.empty()) SerializeStringProperty("name", node.name, o); - SerializeNumberArrayProperty("children", node.children, o); - } - - static void SerializeGltfSampler(Sampler& sampler, json& o) { - if (sampler.magFilter != -1) { - SerializeNumberProperty("magFilter", sampler.magFilter, o); - } - if (sampler.minFilter != -1) { - SerializeNumberProperty("minFilter", sampler.minFilter, o); - } - //SerializeNumberProperty("wrapR", sampler.wrapR, o); - SerializeNumberProperty("wrapS", sampler.wrapS, o); - SerializeNumberProperty("wrapT", sampler.wrapT, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } - } - - static void SerializeGltfOrthographicCamera(const OrthographicCamera& camera, - json& o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - SerializeNumberProperty("xmag", camera.xmag, o); - SerializeNumberProperty("ymag", camera.ymag, o); - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - } - - static void SerializeGltfPerspectiveCamera(const PerspectiveCamera& camera, - json& o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - if (camera.aspectRatio > 0) { - SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); - } - - if (camera.yfov > 0) { - SerializeNumberProperty("yfov", camera.yfov, o); - } - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - } - - static void SerializeGltfCamera(const Camera& camera, json& o) { - SerializeStringProperty("type", camera.type, o); - if (!camera.name.empty()) { - SerializeStringProperty("name", camera.name, o); - } - - if (camera.type.compare("orthographic") == 0) { - json orthographic; - SerializeGltfOrthographicCamera(camera.orthographic, orthographic); - JsonAddMember(o, "orthographic", std::move(orthographic)); - } - else if (camera.type.compare("perspective") == 0) { - json perspective; - SerializeGltfPerspectiveCamera(camera.perspective, perspective); - JsonAddMember(o, "perspective", std::move(perspective)); - } - else { - // ??? - } - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - SerializeExtensionMap(camera.extensions, o); - } - - static void SerializeGltfScene(Scene& scene, json& o) { - SerializeNumberArrayProperty("nodes", scene.nodes, o); - - if (scene.name.size()) { - SerializeStringProperty("name", scene.name, o); - } - if (scene.extras.Type() != NULL_TYPE) { - SerializeValue("extras", scene.extras, o); - } - SerializeExtensionMap(scene.extensions, o); - } - - static void SerializeGltfSkin(Skin& skin, json& o) { - // required - SerializeNumberArrayProperty("joints", skin.joints, o); - - if (skin.inverseBindMatrices >= 0) { - SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); - } - - if (skin.skeleton >= 0) { - SerializeNumberProperty("skeleton", skin.skeleton, o); - } - - if (skin.name.size()) { - SerializeStringProperty("name", skin.name, o); - } - } - - static void SerializeGltfTexture(Texture& texture, json& o) { - if (texture.sampler > -1) { - SerializeNumberProperty("sampler", texture.sampler, o); - } - if (texture.source > -1) { - SerializeNumberProperty("source", texture.source, o); - } - if (texture.name.size()) { - SerializeStringProperty("name", texture.name, o); - } - if (texture.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texture.extras, o); - } - SerializeExtensionMap(texture.extensions, o); - } - - /// - /// Serialize all properties except buffers and images. - /// - static void SerializeGltfModel(Model* model, json& o) { - // ACCESSORS - if (model->accessors.size()) { - json accessors; - JsonReserveArray(accessors, model->accessors.size()); - for (unsigned int i = 0; i < model->accessors.size(); ++i) { - json accessor; - SerializeGltfAccessor(model->accessors[i], accessor); - JsonPushBack(accessors, std::move(accessor)); - } - JsonAddMember(o, "accessors", std::move(accessors)); - } - - // ANIMATIONS - if (model->animations.size()) { - json animations; - JsonReserveArray(animations, model->animations.size()); - for (unsigned int i = 0; i < model->animations.size(); ++i) { - if (model->animations[i].channels.size()) { - json animation; - SerializeGltfAnimation(model->animations[i], animation); - JsonPushBack(animations, std::move(animation)); - } - } - - JsonAddMember(o, "animations", std::move(animations)); - } - - // ASSET - json asset; - SerializeGltfAsset(model->asset, asset); - JsonAddMember(o, "asset", std::move(asset)); - - // BUFFERVIEWS - if (model->bufferViews.size()) { - json bufferViews; - JsonReserveArray(bufferViews, model->bufferViews.size()); - for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { - json bufferView; - SerializeGltfBufferView(model->bufferViews[i], bufferView); - JsonPushBack(bufferViews, std::move(bufferView)); - } - JsonAddMember(o, "bufferViews", std::move(bufferViews)); - } - - // Extensions required - if (model->extensionsRequired.size()) { - SerializeStringArrayProperty("extensionsRequired", - model->extensionsRequired, o); - } - - // MATERIALS - if (model->materials.size()) { - json materials; - JsonReserveArray(materials, model->materials.size()); - for (unsigned int i = 0; i < model->materials.size(); ++i) { - json material; - SerializeGltfMaterial(model->materials[i], material); - - if (JsonIsNull(material)) { - // Issue 294. - // `material` does not have any required parameters - // so the result may be null(unmodified) when all material parameters - // have default value. - // - // null is not allowed thus we create an empty JSON object. - JsonSetObject(material); - } - JsonPushBack(materials, std::move(material)); - } - JsonAddMember(o, "materials", std::move(materials)); - } - - // MESHES - if (model->meshes.size()) { - json meshes; - JsonReserveArray(meshes, model->meshes.size()); - for (unsigned int i = 0; i < model->meshes.size(); ++i) { - json mesh; - SerializeGltfMesh(model->meshes[i], mesh); - JsonPushBack(meshes, std::move(mesh)); - } - JsonAddMember(o, "meshes", std::move(meshes)); - } - - // NODES - if (model->nodes.size()) { - json nodes; - JsonReserveArray(nodes, model->nodes.size()); - for (unsigned int i = 0; i < model->nodes.size(); ++i) { - json node; - SerializeGltfNode(model->nodes[i], node); - JsonPushBack(nodes, std::move(node)); - } - JsonAddMember(o, "nodes", std::move(nodes)); - } - - // SCENE - if (model->defaultScene > -1) { - SerializeNumberProperty("scene", model->defaultScene, o); - } - - // SCENES - if (model->scenes.size()) { - json scenes; - JsonReserveArray(scenes, model->scenes.size()); - for (unsigned int i = 0; i < model->scenes.size(); ++i) { - json currentScene; - SerializeGltfScene(model->scenes[i], currentScene); - JsonPushBack(scenes, std::move(currentScene)); - } - JsonAddMember(o, "scenes", std::move(scenes)); - } - - // SKINS - if (model->skins.size()) { - json skins; - JsonReserveArray(skins, model->skins.size()); - for (unsigned int i = 0; i < model->skins.size(); ++i) { - json skin; - SerializeGltfSkin(model->skins[i], skin); - JsonPushBack(skins, std::move(skin)); - } - JsonAddMember(o, "skins", std::move(skins)); - } - - // TEXTURES - if (model->textures.size()) { - json textures; - JsonReserveArray(textures, model->textures.size()); - for (unsigned int i = 0; i < model->textures.size(); ++i) { - json texture; - SerializeGltfTexture(model->textures[i], texture); - JsonPushBack(textures, std::move(texture)); - } - JsonAddMember(o, "textures", std::move(textures)); - } - - // SAMPLERS - if (model->samplers.size()) { - json samplers; - JsonReserveArray(samplers, model->samplers.size()); - for (unsigned int i = 0; i < model->samplers.size(); ++i) { - json sampler; - SerializeGltfSampler(model->samplers[i], sampler); - JsonPushBack(samplers, std::move(sampler)); - } - JsonAddMember(o, "samplers", std::move(samplers)); - } - - // CAMERAS - if (model->cameras.size()) { - json cameras; - JsonReserveArray(cameras, model->cameras.size()); - for (unsigned int i = 0; i < model->cameras.size(); ++i) { - json camera; - SerializeGltfCamera(model->cameras[i], camera); - JsonPushBack(cameras, std::move(camera)); - } - JsonAddMember(o, "cameras", std::move(cameras)); - } - - // EXTENSIONS - SerializeExtensionMap(model->extensions, o); - - auto extensionsUsed = model->extensionsUsed; - - // LIGHTS as KHR_lights_punctual - if (model->lights.size()) { - json lights; - JsonReserveArray(lights, model->lights.size()); - for (unsigned int i = 0; i < model->lights.size(); ++i) { - json light; - SerializeGltfLight(model->lights[i], light); - JsonPushBack(lights, std::move(light)); - } - json khr_lights_cmn; - JsonAddMember(khr_lights_cmn, "lights", std::move(lights)); - json ext_j; - - { - json_const_iterator it; - if (FindMember(o, "extensions", it)) { - JsonAssign(ext_j, GetValue(it)); - } - } - - JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn)); - - JsonAddMember(o, "extensions", std::move(ext_j)); - - // Also add "KHR_lights_punctual" to `extensionsUsed` - { - auto has_khr_lights_punctual = - std::find_if(extensionsUsed.begin(), extensionsUsed.end(), - [](const std::string& s) { - return (s.compare("KHR_lights_punctual") == 0); - }); - - if (has_khr_lights_punctual == extensionsUsed.end()) { - extensionsUsed.push_back("KHR_lights_punctual"); - } - } - } - - // Extensions used - if (extensionsUsed.size()) { - SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o); - } - - // EXTRAS - if (model->extras.Type() != NULL_TYPE) { - SerializeValue("extras", model->extras, o); - } - } - - static bool WriteGltfStream(std::ostream& stream, const std::string& content) { - stream << content << std::endl; - return true; - } - - static bool WriteGltfFile(const std::string& output, - const std::string& content) { + SerializeExtrasAndExtensions(material, o); + + // MSFT_lod + if (!material.lods.empty()) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "MSFT_lod", it)) { + detail::json lod; + detail::JsonSetObject(lod); + detail::JsonAddMember(extensions, "MSFT_lod", std::move(lod)); + detail::FindMember(extensions, "MSFT_lod", it); + } + SerializeNumberArrayProperty("ids", material.lods, detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "MSFT_lod", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } +} + +static void SerializeGltfMesh(const Mesh &mesh, detail::json &o) { + detail::json primitives; + detail::JsonReserveArray(primitives, mesh.primitives.size()); + for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { + detail::json primitive; + const Primitive &gltfPrimitive = mesh.primitives[i]; // don't make a copy + { + detail::json attributes; + for (auto attrIt = gltfPrimitive.attributes.begin(); + attrIt != gltfPrimitive.attributes.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, attributes); + } + + detail::JsonAddMember(primitive, "attributes", std::move(attributes)); + } + + // Indices is optional + if (gltfPrimitive.indices > -1) { + SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); + } + // Material is optional + if (gltfPrimitive.material > -1) { + SerializeNumberProperty("material", gltfPrimitive.material, + primitive); + } + SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); + + // Morph targets + if (gltfPrimitive.targets.size()) { + detail::json targets; + detail::JsonReserveArray(targets, gltfPrimitive.targets.size()); + for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { + detail::json targetAttributes; + std::map targetData = gltfPrimitive.targets[k]; + for (std::map::iterator attrIt = targetData.begin(); + attrIt != targetData.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, + targetAttributes); + } + detail::JsonPushBack(targets, std::move(targetAttributes)); + } + detail::JsonAddMember(primitive, "targets", std::move(targets)); + } + + SerializeExtrasAndExtensions(gltfPrimitive, primitive); + + detail::JsonPushBack(primitives, std::move(primitive)); + } + + detail::JsonAddMember(o, "primitives", std::move(primitives)); + + if (mesh.weights.size()) { + SerializeNumberArrayProperty("weights", mesh.weights, o); + } + + if (mesh.name.size()) { + SerializeStringProperty("name", mesh.name, o); + } + + SerializeExtrasAndExtensions(mesh, o); +} + +static void SerializeSpotLight(const SpotLight &spot, detail::json &o) { + SerializeNumberProperty("innerConeAngle", spot.innerConeAngle, o); + SerializeNumberProperty("outerConeAngle", spot.outerConeAngle, o); + SerializeExtrasAndExtensions(spot, o); +} + +static void SerializeGltfLight(const Light &light, detail::json &o) { + if (!light.name.empty()) SerializeStringProperty("name", light.name, o); + SerializeNumberProperty("intensity", light.intensity, o); + if (light.range > 0.0) { + SerializeNumberProperty("range", light.range, o); + } + SerializeNumberArrayProperty("color", light.color, o); + SerializeStringProperty("type", light.type, o); + if (light.type == "spot") { + detail::json spot; + SerializeSpotLight(light.spot, spot); + detail::JsonAddMember(o, "spot", std::move(spot)); + } + SerializeExtrasAndExtensions(light, o); +} + +static void SerializeGltfPositionalEmitter(const PositionalEmitter &positional, + detail::json &o) { + if (!TINYGLTF_DOUBLE_EQUAL(positional.coneInnerAngle, 6.283185307179586)) + SerializeNumberProperty("coneInnerAngle", positional.coneInnerAngle, o); + if (!TINYGLTF_DOUBLE_EQUAL(positional.coneOuterAngle, 6.283185307179586)) + SerializeNumberProperty("coneOuterAngle", positional.coneOuterAngle, o); + if (positional.coneOuterGain > 0.0) + SerializeNumberProperty("coneOuterGain", positional.coneOuterGain, o); + if (!TINYGLTF_DOUBLE_EQUAL(positional.maxDistance, 100.0)) + SerializeNumberProperty("maxDistance", positional.maxDistance, o); + if (!TINYGLTF_DOUBLE_EQUAL(positional.refDistance, 1.0)) + SerializeNumberProperty("refDistance", positional.refDistance, o); + if (!TINYGLTF_DOUBLE_EQUAL(positional.rolloffFactor, 1.0)) + SerializeNumberProperty("rolloffFactor", positional.rolloffFactor, o); + + SerializeExtrasAndExtensions(positional, o); +} + +static void SerializeGltfAudioEmitter(const AudioEmitter &emitter, + detail::json &o) { + if (!emitter.name.empty()) SerializeStringProperty("name", emitter.name, o); + if (!TINYGLTF_DOUBLE_EQUAL(emitter.gain, 1.0)) + SerializeNumberProperty("gain", emitter.gain, o); + if (emitter.loop) SerializeNumberProperty("loop", emitter.loop, o); + if (emitter.playing) SerializeNumberProperty("playing", emitter.playing, o); + if (!emitter.type.empty()) SerializeStringProperty("type", emitter.type, o); + if (!emitter.distanceModel.empty()) + SerializeStringProperty("distanceModel", emitter.distanceModel, o); + if (emitter.type == "positional") { + detail::json positional; + SerializeGltfPositionalEmitter(emitter.positional, positional); + detail::JsonAddMember(o, "positional", std::move(positional)); + } + SerializeNumberProperty("source", emitter.source, o); + SerializeExtrasAndExtensions(emitter, o); +} + +static void SerializeGltfAudioSource(const AudioSource &source, + detail::json &o) { + std::string name; + std::string uri; + std::string mimeType; // (required if no uri) ["audio/mp3", "audio/ogg", + // "audio/wav", "audio/m4a"] + + if (!source.name.empty()) SerializeStringProperty("name", source.name, o); + if (source.uri.empty()) { + SerializeStringProperty("mimeType", source.mimeType, o); + SerializeNumberProperty("bufferView", source.bufferView, o); + } else { + SerializeStringProperty("uri", source.uri, o); + } + SerializeExtrasAndExtensions(source, o); +} + +static void SerializeGltfNode(const Node &node, detail::json &o) { + if (node.translation.size() > 0) { + SerializeNumberArrayProperty("translation", node.translation, o); + } + if (node.rotation.size() > 0) { + SerializeNumberArrayProperty("rotation", node.rotation, o); + } + if (node.scale.size() > 0) { + SerializeNumberArrayProperty("scale", node.scale, o); + } + if (node.matrix.size() > 0) { + SerializeNumberArrayProperty("matrix", node.matrix, o); + } + if (node.mesh != -1) { + SerializeNumberProperty("mesh", node.mesh, o); + } + + if (node.skin != -1) { + SerializeNumberProperty("skin", node.skin, o); + } + + if (node.camera != -1) { + SerializeNumberProperty("camera", node.camera, o); + } + + if (node.weights.size() > 0) { + SerializeNumberArrayProperty("weights", node.weights, o); + } + + SerializeExtrasAndExtensions(node, o); + + // Note(agnat): If the asset was loaded from disk, the node may already + // contain the KHR_lights_punctual extension. If it was constructed in + // memory it does not. In any case we update the JSON property using + // the value from the struct. Last, if the node does not have a light + // reference but the extension is still present, we remove it. + if (node.light != -1) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "KHR_lights_punctual", it)) { + detail::json lights_punctual; + detail::JsonSetObject(lights_punctual); + detail::JsonAddMember(extensions, "KHR_lights_punctual", + std::move(lights_punctual)); + detail::FindMember(extensions, "KHR_lights_punctual", it); + } + SerializeNumberProperty("light", node.light, detail::GetValue(it)); + } else { + // node has no light ref (any longer)... so we clean up + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "KHR_lights_punctual", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } + + // KHR_audio + if (node.emitter != -1) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "KHR_audio", it)) { + detail::json audio; + detail::JsonSetObject(audio); + detail::JsonAddMember(extensions, "KHR_audio", std::move(audio)); + detail::FindMember(extensions, "KHR_audio", it); + } + SerializeNumberProperty("emitter", node.emitter, detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "KHR_audio", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } + + // MSFT_lod + if (!node.lods.empty()) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "MSFT_lod", it)) { + detail::json lod; + detail::JsonSetObject(lod); + detail::JsonAddMember(extensions, "MSFT_lod", std::move(lod)); + detail::FindMember(extensions, "MSFT_lod", it); + } + SerializeNumberArrayProperty("ids", node.lods, detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "MSFT_lod", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } + + if (!node.name.empty()) SerializeStringProperty("name", node.name, o); + SerializeNumberArrayProperty("children", node.children, o); +} + +static void SerializeGltfSampler(const Sampler &sampler, detail::json &o) { + if (!sampler.name.empty()) { + SerializeStringProperty("name", sampler.name, o); + } + if (sampler.magFilter != -1) { + SerializeNumberProperty("magFilter", sampler.magFilter, o); + } + if (sampler.minFilter != -1) { + SerializeNumberProperty("minFilter", sampler.minFilter, o); + } + // SerializeNumberProperty("wrapR", sampler.wrapR, o); + SerializeNumberProperty("wrapS", sampler.wrapS, o); + SerializeNumberProperty("wrapT", sampler.wrapT, o); + + SerializeExtrasAndExtensions(sampler, o); +} + +static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, + detail::json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + SerializeNumberProperty("xmag", camera.xmag, o); + SerializeNumberProperty("ymag", camera.ymag, o); + + SerializeExtrasAndExtensions(camera, o); +} + +static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, + detail::json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + if (camera.aspectRatio > 0) { + SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); + } + + if (camera.yfov > 0) { + SerializeNumberProperty("yfov", camera.yfov, o); + } + + SerializeExtrasAndExtensions(camera, o); +} + +static void SerializeGltfCamera(const Camera &camera, detail::json &o) { + SerializeStringProperty("type", camera.type, o); + if (!camera.name.empty()) { + SerializeStringProperty("name", camera.name, o); + } + + if (camera.type.compare("orthographic") == 0) { + detail::json orthographic; + SerializeGltfOrthographicCamera(camera.orthographic, orthographic); + detail::JsonAddMember(o, "orthographic", std::move(orthographic)); + } else if (camera.type.compare("perspective") == 0) { + detail::json perspective; + SerializeGltfPerspectiveCamera(camera.perspective, perspective); + detail::JsonAddMember(o, "perspective", std::move(perspective)); + } else { + // ??? + } + + SerializeExtrasAndExtensions(camera, o); +} + +static void SerializeGltfScene(const Scene &scene, detail::json &o) { + SerializeNumberArrayProperty("nodes", scene.nodes, o); + + if (scene.name.size()) { + SerializeStringProperty("name", scene.name, o); + } + SerializeExtrasAndExtensions(scene, o); + + // KHR_audio + if (!scene.audioEmitters.empty()) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "KHR_audio", it)) { + detail::json audio; + detail::JsonSetObject(audio); + detail::JsonAddMember(extensions, "KHR_audio", std::move(audio)); + detail::FindMember(o, "KHR_audio", it); + } + SerializeNumberArrayProperty("emitters", scene.audioEmitters, + detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "KHR_audio", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } +} + +static void SerializeGltfSkin(const Skin &skin, detail::json &o) { + // required + SerializeNumberArrayProperty("joints", skin.joints, o); + + if (skin.inverseBindMatrices >= 0) { + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + } + + if (skin.skeleton >= 0) { + SerializeNumberProperty("skeleton", skin.skeleton, o); + } + + if (skin.name.size()) { + SerializeStringProperty("name", skin.name, o); + } + + SerializeExtrasAndExtensions(skin, o); +} + +static void SerializeGltfTexture(const Texture &texture, detail::json &o) { + if (texture.sampler > -1) { + SerializeNumberProperty("sampler", texture.sampler, o); + } + if (texture.source > -1) { + SerializeNumberProperty("source", texture.source, o); + } + if (texture.name.size()) { + SerializeStringProperty("name", texture.name, o); + } + SerializeExtrasAndExtensions(texture, o); +} + +/// +/// Serialize all properties except buffers and images. +/// +static void SerializeGltfModel(const Model *model, detail::json &o) { + // ACCESSORS + if (model->accessors.size()) { + detail::json accessors; + detail::JsonReserveArray(accessors, model->accessors.size()); + for (unsigned int i = 0; i < model->accessors.size(); ++i) { + detail::json accessor; + SerializeGltfAccessor(model->accessors[i], accessor); + detail::JsonPushBack(accessors, std::move(accessor)); + } + detail::JsonAddMember(o, "accessors", std::move(accessors)); + } + + // ANIMATIONS + if (model->animations.size()) { + detail::json animations; + detail::JsonReserveArray(animations, model->animations.size()); + for (unsigned int i = 0; i < model->animations.size(); ++i) { + if (model->animations[i].channels.size()) { + detail::json animation; + SerializeGltfAnimation(model->animations[i], animation); + detail::JsonPushBack(animations, std::move(animation)); + } + } + + detail::JsonAddMember(o, "animations", std::move(animations)); + } + + // ASSET + detail::json asset; + SerializeGltfAsset(model->asset, asset); + detail::JsonAddMember(o, "asset", std::move(asset)); + + // BUFFERVIEWS + if (model->bufferViews.size()) { + detail::json bufferViews; + detail::JsonReserveArray(bufferViews, model->bufferViews.size()); + for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { + detail::json bufferView; + SerializeGltfBufferView(model->bufferViews[i], bufferView); + detail::JsonPushBack(bufferViews, std::move(bufferView)); + } + detail::JsonAddMember(o, "bufferViews", std::move(bufferViews)); + } + + // Extensions required + if (model->extensionsRequired.size()) { + SerializeStringArrayProperty("extensionsRequired", + model->extensionsRequired, o); + } + + // MATERIALS + if (model->materials.size()) { + detail::json materials; + detail::JsonReserveArray(materials, model->materials.size()); + for (unsigned int i = 0; i < model->materials.size(); ++i) { + detail::json material; + SerializeGltfMaterial(model->materials[i], material); + + if (detail::JsonIsNull(material)) { + // Issue 294. + // `material` does not have any required parameters + // so the result may be null(unmodified) when all material parameters + // have default value. + // + // null is not allowed thus we create an empty JSON object. + detail::JsonSetObject(material); + } + detail::JsonPushBack(materials, std::move(material)); + } + detail::JsonAddMember(o, "materials", std::move(materials)); + } + + // MESHES + if (model->meshes.size()) { + detail::json meshes; + detail::JsonReserveArray(meshes, model->meshes.size()); + for (unsigned int i = 0; i < model->meshes.size(); ++i) { + detail::json mesh; + SerializeGltfMesh(model->meshes[i], mesh); + detail::JsonPushBack(meshes, std::move(mesh)); + } + detail::JsonAddMember(o, "meshes", std::move(meshes)); + } + + // NODES + if (model->nodes.size()) { + detail::json nodes; + detail::JsonReserveArray(nodes, model->nodes.size()); + for (unsigned int i = 0; i < model->nodes.size(); ++i) { + detail::json node; + SerializeGltfNode(model->nodes[i], node); + + if (detail::JsonIsNull(node)) { + // Issue 457. + // `node` does not have any required parameters, + // so the result may be null(unmodified) when all node parameters + // have default value. + // + // null is not allowed thus we create an empty JSON object. + detail::JsonSetObject(node); + } + detail::JsonPushBack(nodes, std::move(node)); + } + detail::JsonAddMember(o, "nodes", std::move(nodes)); + } + + // SCENE + if (model->defaultScene > -1) { + SerializeNumberProperty("scene", model->defaultScene, o); + } + + // SCENES + if (model->scenes.size()) { + detail::json scenes; + detail::JsonReserveArray(scenes, model->scenes.size()); + for (unsigned int i = 0; i < model->scenes.size(); ++i) { + detail::json currentScene; + SerializeGltfScene(model->scenes[i], currentScene); + if (detail::JsonIsNull(currentScene)) { + // Issue 464. + // `scene` does not have any required parameters, + // so the result may be null(unmodified) when all scene parameters + // have default value. + // + // null is not allowed thus we create an empty JSON object. + detail::JsonSetObject(currentScene); + } + detail::JsonPushBack(scenes, std::move(currentScene)); + } + detail::JsonAddMember(o, "scenes", std::move(scenes)); + } + + // SKINS + if (model->skins.size()) { + detail::json skins; + detail::JsonReserveArray(skins, model->skins.size()); + for (unsigned int i = 0; i < model->skins.size(); ++i) { + detail::json skin; + SerializeGltfSkin(model->skins[i], skin); + detail::JsonPushBack(skins, std::move(skin)); + } + detail::JsonAddMember(o, "skins", std::move(skins)); + } + + // TEXTURES + if (model->textures.size()) { + detail::json textures; + detail::JsonReserveArray(textures, model->textures.size()); + for (unsigned int i = 0; i < model->textures.size(); ++i) { + detail::json texture; + SerializeGltfTexture(model->textures[i], texture); + detail::JsonPushBack(textures, std::move(texture)); + } + detail::JsonAddMember(o, "textures", std::move(textures)); + } + + // SAMPLERS + if (model->samplers.size()) { + detail::json samplers; + detail::JsonReserveArray(samplers, model->samplers.size()); + for (unsigned int i = 0; i < model->samplers.size(); ++i) { + detail::json sampler; + SerializeGltfSampler(model->samplers[i], sampler); + detail::JsonPushBack(samplers, std::move(sampler)); + } + detail::JsonAddMember(o, "samplers", std::move(samplers)); + } + + // CAMERAS + if (model->cameras.size()) { + detail::json cameras; + detail::JsonReserveArray(cameras, model->cameras.size()); + for (unsigned int i = 0; i < model->cameras.size(); ++i) { + detail::json camera; + SerializeGltfCamera(model->cameras[i], camera); + detail::JsonPushBack(cameras, std::move(camera)); + } + detail::JsonAddMember(o, "cameras", std::move(cameras)); + } + + // EXTRAS & EXTENSIONS + SerializeExtrasAndExtensions(*model, o); + + auto extensionsUsed = model->extensionsUsed; + + // LIGHTS as KHR_lights_punctual + if (model->lights.size()) { + detail::json lights; + detail::JsonReserveArray(lights, model->lights.size()); + for (unsigned int i = 0; i < model->lights.size(); ++i) { + detail::json light; + SerializeGltfLight(model->lights[i], light); + detail::JsonPushBack(lights, std::move(light)); + } + detail::json khr_lights_cmn; + detail::JsonAddMember(khr_lights_cmn, "lights", std::move(lights)); + detail::json ext_j; + + { + detail::json_const_iterator it; + if (detail::FindMember(o, "extensions", it)) { + detail::JsonAssign(ext_j, detail::GetValue(it)); + } + } + + detail::JsonAddMember(ext_j, "KHR_lights_punctual", + std::move(khr_lights_cmn)); + + detail::JsonAddMember(o, "extensions", std::move(ext_j)); + + // Also add "KHR_lights_punctual" to `extensionsUsed` + { + auto has_khr_lights_punctual = + std::find_if(extensionsUsed.begin(), extensionsUsed.end(), + [](const std::string &s) { + return (s.compare("KHR_lights_punctual") == 0); + }); + + if (has_khr_lights_punctual == extensionsUsed.end()) { + extensionsUsed.push_back("KHR_lights_punctual"); + } + } + } + + // KHR_audio + if (!model->audioEmitters.empty() || !model->audioSources.empty()) { + detail::json emitters; + detail::JsonReserveArray(emitters, model->audioEmitters.size()); + for (unsigned int i = 0; i < model->audioEmitters.size(); ++i) { + detail::json emitter; + SerializeGltfAudioEmitter(model->audioEmitters[i], emitter); + detail::JsonPushBack(emitters, std::move(emitter)); + } + detail::json khr_audio_cmn; + detail::JsonAddMember(khr_audio_cmn, "emitters", std::move(emitters)); + + detail::json sources; + detail::JsonReserveArray(sources, model->audioSources.size()); + for (unsigned int i = 0; i < model->audioSources.size(); ++i) { + detail::json source; + SerializeGltfAudioSource(model->audioSources[i], source); + detail::JsonPushBack(sources, std::move(source)); + } + detail::JsonAddMember(khr_audio_cmn, "sources", std::move(sources)); + + detail::json ext_j; + { + detail::json_const_iterator it; + if (detail::FindMember(o, "extensions", it)) { + detail::JsonAssign(ext_j, detail::GetValue(it)); + } + } + + detail::JsonAddMember(ext_j, "KHR_audio", std::move(khr_audio_cmn)); + + detail::JsonAddMember(o, "extensions", std::move(ext_j)); + + // Also add "KHR_audio" to `extensionsUsed` + { + auto has_khr_audio = std::find_if( + extensionsUsed.begin(), extensionsUsed.end(), + [](const std::string &s) { return (s.compare("KHR_audio") == 0); }); + + if (has_khr_audio == extensionsUsed.end()) { + extensionsUsed.push_back("KHR_audio"); + } + } + } + + // MSFT_lod + + // Look if there is a node that employs MSFT_lod + auto msft_lod_nodes_it = std::find_if( + model->nodes.begin(), model->nodes.end(), + [](const Node& node) { return !node.lods.empty(); }); + + // Look if there is a material that employs MSFT_lod + auto msft_lod_materials_it = std::find_if( + model->materials.begin(), model->materials.end(), + [](const Material& material) {return !material.lods.empty(); }); + + // If either a node or a material employ MSFT_lod, then we need + // to add MSFT_lod to the list of used extensions. + if (msft_lod_nodes_it != model->nodes.end() || msft_lod_materials_it != model->materials.end()) { + // First check if MSFT_lod is already registered as used extension + auto has_msft_lod = std::find_if( + extensionsUsed.begin(), extensionsUsed.end(), + [](const std::string &s) { return (s.compare("MSFT_lod") == 0); }); + + // If MSFT_lod is not registered yet, add it + if (has_msft_lod == extensionsUsed.end()) { + extensionsUsed.push_back("MSFT_lod"); + } + } + + // Extensions used + if (extensionsUsed.size()) { + SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o); + } +} + +static bool WriteGltfStream(std::ostream &stream, const std::string &content) { + stream << content << std::endl; + return stream.good(); +} + +static bool WriteGltfFile(const std::string &output, + const std::string &content) { +#ifndef TINYGLTF_NO_FS #ifdef _WIN32 #if defined(_MSC_VER) - std::ofstream gltfFile(UTF8ToWchar(output).c_str()); + std::ofstream gltfFile(UTF8ToWchar(output).c_str()); #elif defined(__GLIBCXX__) - int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), - _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); - __gnu_cxx::stdio_filebuf wfile_buf( - file_descriptor, std::ios_base::out | std::ios_base::binary); - std::ostream gltfFile(&wfile_buf); - if (!wfile_buf.is_open()) return false; + int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY, _S_IWRITE); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream gltfFile(&wfile_buf); + if (!wfile_buf.is_open()) return false; #else - std::ofstream gltfFile(output.c_str()); - if (!gltfFile.is_open()) return false; + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; #endif #else - std::ofstream gltfFile(output.c_str()); - if (!gltfFile.is_open()) return false; + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; #endif - return WriteGltfStream(gltfFile, content); - } + return WriteGltfStream(gltfFile, content); +#else + return false; +#endif +} - static bool WriteBinaryGltfStream(std::ostream& stream, - const std::string& content, - const std::vector& binBuffer) { - const std::string header = "glTF"; - const int version = 2; +static bool WriteBinaryGltfStream(std::ostream &stream, + const std::string &content, + const std::vector &binBuffer) { + const std::string header = "glTF"; + const int version = 2; - const uint32_t content_size = uint32_t(content.size()); - const uint32_t binBuffer_size = uint32_t(binBuffer.size()); - // determine number of padding bytes required to ensure 4 byte alignment - const uint32_t content_padding_size = content_size % 4 == 0 ? 0 : 4 - content_size % 4; - const uint32_t bin_padding_size = binBuffer_size % 4 == 0 ? 0 : 4 - binBuffer_size % 4; + const uint32_t content_size = uint32_t(content.size()); + const uint32_t binBuffer_size = uint32_t(binBuffer.size()); + // determine number of padding bytes required to ensure 4 byte alignment + const uint32_t content_padding_size = + content_size % 4 == 0 ? 0 : 4 - content_size % 4; + const uint32_t bin_padding_size = + binBuffer_size % 4 == 0 ? 0 : 4 - binBuffer_size % 4; - // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info. - // Chunk data must be located at 4-byte boundary, which may require padding - const uint32_t length = - 12 + 8 + content_size + content_padding_size + - (binBuffer_size ? (8 + binBuffer_size + bin_padding_size) : 0); + // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info. + // Chunk data must be located at 4-byte boundary, which may require padding + const uint32_t length = + 12 + 8 + content_size + content_padding_size + + (binBuffer_size ? (8 + binBuffer_size + bin_padding_size) : 0); - stream.write(header.c_str(), std::streamsize(header.size())); - stream.write(reinterpret_cast(&version), sizeof(version)); - stream.write(reinterpret_cast(&length), sizeof(length)); + stream.write(header.c_str(), std::streamsize(header.size())); + stream.write(reinterpret_cast(&version), sizeof(version)); + stream.write(reinterpret_cast(&length), sizeof(length)); - // JSON chunk info, then JSON data - const uint32_t model_length = uint32_t(content.size()) + content_padding_size; - const uint32_t model_format = 0x4E4F534A; - stream.write(reinterpret_cast(&model_length), - sizeof(model_length)); - stream.write(reinterpret_cast(&model_format), - sizeof(model_format)); - stream.write(content.c_str(), std::streamsize(content.size())); + // JSON chunk info, then JSON data + const uint32_t model_length = uint32_t(content.size()) + content_padding_size; + const uint32_t model_format = 0x4E4F534A; + stream.write(reinterpret_cast(&model_length), + sizeof(model_length)); + stream.write(reinterpret_cast(&model_format), + sizeof(model_format)); + stream.write(content.c_str(), std::streamsize(content.size())); - // Chunk must be multiplies of 4, so pad with spaces - if (content_padding_size > 0) { - const std::string padding = std::string(size_t(content_padding_size), ' '); - stream.write(padding.c_str(), std::streamsize(padding.size())); - } - if (binBuffer.size() > 0) { - // BIN chunk info, then BIN data - const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size; - const uint32_t bin_format = 0x004e4942; - stream.write(reinterpret_cast(&bin_length), - sizeof(bin_length)); - stream.write(reinterpret_cast(&bin_format), - sizeof(bin_format)); - stream.write(reinterpret_cast(binBuffer.data()), - std::streamsize(binBuffer.size())); - // Chunksize must be multiplies of 4, so pad with zeroes - if (bin_padding_size > 0) { - const std::vector padding = - std::vector(size_t(bin_padding_size), 0); - stream.write(reinterpret_cast(padding.data()), - std::streamsize(padding.size())); - } - } + // Chunk must be multiplies of 4, so pad with spaces + if (content_padding_size > 0) { + const std::string padding = std::string(size_t(content_padding_size), ' '); + stream.write(padding.c_str(), std::streamsize(padding.size())); + } + if (binBuffer.size() > 0) { + // BIN chunk info, then BIN data + const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size; + const uint32_t bin_format = 0x004e4942; + stream.write(reinterpret_cast(&bin_length), + sizeof(bin_length)); + stream.write(reinterpret_cast(&bin_format), + sizeof(bin_format)); + stream.write(reinterpret_cast(binBuffer.data()), + std::streamsize(binBuffer.size())); + // Chunksize must be multiplies of 4, so pad with zeroes + if (bin_padding_size > 0) { + const std::vector padding = + std::vector(size_t(bin_padding_size), 0); + stream.write(reinterpret_cast(padding.data()), + std::streamsize(padding.size())); + } + } - // TODO: Check error on stream.write - return true; - } + stream.flush(); + return stream.good(); +} - static bool WriteBinaryGltfFile(const std::string& output, - const std::string& content, - const std::vector& binBuffer) { +static bool WriteBinaryGltfFile(const std::string &output, + const std::string &content, + const std::vector &binBuffer) { +#ifndef TINYGLTF_NO_FS #ifdef _WIN32 #if defined(_MSC_VER) - std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary); + std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary); #elif defined(__GLIBCXX__) - int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), - _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); - __gnu_cxx::stdio_filebuf wfile_buf( - file_descriptor, std::ios_base::out | std::ios_base::binary); - std::ostream gltfFile(&wfile_buf); + int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY, _S_IWRITE); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream gltfFile(&wfile_buf); #else - std::ofstream gltfFile(output.c_str(), std::ios::binary); + std::ofstream gltfFile(output.c_str(), std::ios::binary); #endif #else - std::ofstream gltfFile(output.c_str(), std::ios::binary); + std::ofstream gltfFile(output.c_str(), std::ios::binary); #endif - return WriteBinaryGltfStream(gltfFile, content, binBuffer); - } + return WriteBinaryGltfStream(gltfFile, content, binBuffer); +#else + return false; +#endif +} - bool TinyGLTF::WriteGltfSceneToStream(Model* model, std::ostream& stream, - bool prettyPrint = true, - bool writeBinary = false) { - JsonDocument output; +bool TinyGLTF::WriteGltfSceneToStream(const Model *model, std::ostream &stream, + bool prettyPrint = true, + bool writeBinary = false) { + detail::JsonDocument output; - /// Serialize all properties except buffers and images. - SerializeGltfModel(model, output); + /// Serialize all properties except buffers and images. + SerializeGltfModel(model, output); - // BUFFERS - std::vector binBuffer; - if (model->buffers.size()) { - json buffers; - JsonReserveArray(buffers, model->buffers.size()); - for (unsigned int i = 0; i < model->buffers.size(); ++i) { - json buffer; - if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { - SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); - } - else { - SerializeGltfBuffer(model->buffers[i], buffer); - } - JsonPushBack(buffers, std::move(buffer)); - } - JsonAddMember(output, "buffers", std::move(buffers)); - } + // BUFFERS + std::vector binBuffer; + if (model->buffers.size()) { + detail::json buffers; + detail::JsonReserveArray(buffers, model->buffers.size()); + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + detail::json buffer; + if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { + SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); + } else { + SerializeGltfBuffer(model->buffers[i], buffer); + } + detail::JsonPushBack(buffers, std::move(buffer)); + } + detail::JsonAddMember(output, "buffers", std::move(buffers)); + } - // IMAGES - if (model->images.size()) { - json images; - JsonReserveArray(images, model->images.size()); - for (unsigned int i = 0; i < model->images.size(); ++i) { - json image; + // IMAGES + if (model->images.size()) { + detail::json images; + detail::JsonReserveArray(images, model->images.size()); + for (unsigned int i = 0; i < model->images.size(); ++i) { + detail::json image; - std::string dummystring = ""; - // UpdateImageObject need baseDir but only uses it if embeddedImages is - // enabled, since we won't write separate images when writing to a stream - // we - UpdateImageObject(model->images[i], dummystring, int(i), true, - &this->WriteImageData, this->write_image_user_data_); - SerializeGltfImage(model->images[i], image); - JsonPushBack(images, std::move(image)); - } - JsonAddMember(output, "images", std::move(images)); - } + std::string dummystring; + // UpdateImageObject need baseDir but only uses it if embeddedImages is + // enabled, since we won't write separate images when writing to a stream + // we + std::string uri; + if (!UpdateImageObject(model->images[i], dummystring, int(i), true, + &fs, &uri_cb, this->WriteImageData, + this->write_image_user_data_, &uri)) { + return false; + } + SerializeGltfImage(model->images[i], uri, image); + detail::JsonPushBack(images, std::move(image)); + } + detail::JsonAddMember(output, "images", std::move(images)); + } - if (writeBinary) { - return WriteBinaryGltfStream(stream, JsonToString(output), binBuffer); - } - else { - return WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1)); - } + if (writeBinary) { + return WriteBinaryGltfStream(stream, detail::JsonToString(output), + binBuffer); + } else { + return WriteGltfStream(stream, + detail::JsonToString(output, prettyPrint ? 2 : -1)); + } +} - } +bool TinyGLTF::WriteGltfSceneToFile(const Model *model, + const std::string &filename, + bool embedImages = false, + bool embedBuffers = false, + bool prettyPrint = true, + bool writeBinary = false) { + detail::JsonDocument output; + std::string defaultBinFilename = GetBaseFilename(filename); + std::string defaultBinFileExt = ".bin"; + std::string::size_type pos = + defaultBinFilename.rfind('.', defaultBinFilename.length()); - bool TinyGLTF::WriteGltfSceneToFile(Model* model, const std::string& filename, - bool embedImages = false, - bool embedBuffers = false, - bool prettyPrint = true, - bool writeBinary = false) { - JsonDocument output; - std::string defaultBinFilename = GetBaseFilename(filename); - std::string defaultBinFileExt = ".bin"; - std::string::size_type pos = - defaultBinFilename.rfind('.', defaultBinFilename.length()); + if (pos != std::string::npos) { + defaultBinFilename = defaultBinFilename.substr(0, pos); + } + std::string baseDir = GetBaseDir(filename); + if (baseDir.empty()) { + baseDir = "./"; + } + /// Serialize all properties except buffers and images. + SerializeGltfModel(model, output); - if (pos != std::string::npos) { - defaultBinFilename = defaultBinFilename.substr(0, pos); - } - std::string baseDir = GetBaseDir(filename); - if (baseDir.empty()) { - baseDir = "./"; - } - /// Serialize all properties except buffers and images. - SerializeGltfModel(model, output); + // BUFFERS + std::vector usedFilenames; + std::vector binBuffer; + if (model->buffers.size()) { + detail::json buffers; + detail::JsonReserveArray(buffers, model->buffers.size()); + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + detail::json buffer; + if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { + SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); + } else if (embedBuffers) { + SerializeGltfBuffer(model->buffers[i], buffer); + } else { + std::string binSavePath; + std::string binFilename; + std::string binUri; + if (!model->buffers[i].uri.empty() && + !IsDataURI(model->buffers[i].uri)) { + binUri = model->buffers[i].uri; + if (!uri_cb.decode(binUri, &binFilename, uri_cb.user_data)) { + return false; + } + } else { + binFilename = defaultBinFilename + defaultBinFileExt; + bool inUse = true; + int numUsed = 0; + while (inUse) { + inUse = false; + for (const std::string &usedName : usedFilenames) { + if (binFilename.compare(usedName) != 0) continue; + inUse = true; + binFilename = defaultBinFilename + std::to_string(numUsed++) + + defaultBinFileExt; + break; + } + } - // BUFFERS - std::vector usedUris; - std::vector binBuffer; - if (model->buffers.size()) { - json buffers; - JsonReserveArray(buffers, model->buffers.size()); - for (unsigned int i = 0; i < model->buffers.size(); ++i) { - json buffer; - if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { - SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); - } - else if (embedBuffers) { - SerializeGltfBuffer(model->buffers[i], buffer); - } - else { - std::string binSavePath; - std::string binUri; - if (!model->buffers[i].uri.empty() && - !IsDataURI(model->buffers[i].uri)) { - binUri = model->buffers[i].uri; - } - else { - binUri = defaultBinFilename + defaultBinFileExt; - bool inUse = true; - int numUsed = 0; - while (inUse) { - inUse = false; - for (const std::string& usedName : usedUris) { - if (binUri.compare(usedName) != 0) continue; - inUse = true; - binUri = defaultBinFilename + std::to_string(numUsed++) + - defaultBinFileExt; - break; - } - } - } - usedUris.push_back(binUri); - binSavePath = JoinPath(baseDir, binUri); - if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, - binUri)) { - return false; - } - } - JsonPushBack(buffers, std::move(buffer)); - } - JsonAddMember(output, "buffers", std::move(buffers)); - } + if (uri_cb.encode) { + if (!uri_cb.encode(binFilename, "buffer", &binUri, + uri_cb.user_data)) { + return false; + } + } else { + binUri = binFilename; + } + } + usedFilenames.push_back(binFilename); + binSavePath = JoinPath(baseDir, binFilename); + if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, + binUri)) { + return false; + } + } + detail::JsonPushBack(buffers, std::move(buffer)); + } + detail::JsonAddMember(output, "buffers", std::move(buffers)); + } - // IMAGES - if (model->images.size()) { - json images; - JsonReserveArray(images, model->images.size()); - for (unsigned int i = 0; i < model->images.size(); ++i) { - json image; + // IMAGES + if (model->images.size()) { + detail::json images; + detail::JsonReserveArray(images, model->images.size()); + for (unsigned int i = 0; i < model->images.size(); ++i) { + detail::json image; - UpdateImageObject(model->images[i], baseDir, int(i), embedImages, - &this->WriteImageData, this->write_image_user_data_); - SerializeGltfImage(model->images[i], image); - JsonPushBack(images, std::move(image)); - } - JsonAddMember(output, "images", std::move(images)); - } + std::string uri; + if (!UpdateImageObject(model->images[i], baseDir, int(i), embedImages, + &fs, &uri_cb, this->WriteImageData, + this->write_image_user_data_, &uri)) { + return false; + } + SerializeGltfImage(model->images[i], uri, image); + detail::JsonPushBack(images, std::move(image)); + } + detail::JsonAddMember(output, "images", std::move(images)); + } - if (writeBinary) { - return WriteBinaryGltfFile(filename, JsonToString(output), binBuffer); - } - else { - return WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1))); - } - - } + if (writeBinary) { + return WriteBinaryGltfFile(filename, detail::JsonToString(output), + binBuffer); + } else { + return WriteGltfFile(filename, + detail::JsonToString(output, (prettyPrint ? 2 : -1))); + } +} } // namespace tinygltf