diff --git a/extern/README b/extern/README index f904fc3e41e..75c1e0393ca 100644 --- a/extern/README +++ b/extern/README @@ -1,4 +1,4 @@ When updating a library remember to: * Update the README.blender with the corresponding version. -* Update the THIRD-PARTY-LICENSE.txt document +* Update the THIRD-PARTY-LICENSES.txt document diff --git a/extern/tinygltf/README.blender b/extern/tinygltf/README.blender index 3dcd6d15f03..a7cdb3af5bf 100644 --- a/extern/tinygltf/README.blender +++ b/extern/tinygltf/README.blender @@ -1,5 +1,5 @@ Project: TinyGLTF URL: https://github.com/syoyo/tinygltf License: MIT -Upstream version: 2.8.3, 84a83d39f55d +Upstream version: 2.8.21, 4bfc1fc1807e Local modifications: None diff --git a/extern/tinygltf/tiny_gltf.h b/extern/tinygltf/tiny_gltf.h index c935b89df9a..7ef28f41ce5 100644 --- a/extern/tinygltf/tiny_gltf.h +++ b/extern/tinygltf/tiny_gltf.h @@ -25,32 +25,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// Version: -// - v2.8.1 Missed serialization texture sampler name fixed. PR#399. -// - v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397. -// - v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393. -// - v2.6.3 Fix GLB file with empty BIN chunk was not handled. PR#382 and PR#383. -// - v2.6.2 Fix out-of-bounds access of accessors. PR#379. -// - v2.6.1 Better GLB validation check when loading. -// - v2.6.0 Support serializing sparse accessor(Thanks to @fynv). -// Disable expanding file path for security(no use of awkward `wordexp` anymore). -// - v2.5.0 Add SetPreserveImageChannels() option to load image data as is. -// - v2.4.3 Fix null object output 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 comparison feature(Thanks to @Selmar). -// - v2.0.0 glTF 2.0!. +// Version: - v2.8.10 +// See https://github.com/syoyo/tinygltf/releases for release history. // // Tiny glTF loader is using following third party libraries: // @@ -72,15 +48,12 @@ #include #include -//Auto-detect C++14 standard version -#if !defined(TINYGLTF_USE_CPP14) && defined(__cplusplus) && (__cplusplus >= 201402L) +// Auto-detect C++14 standard version +#if !defined(TINYGLTF_USE_CPP14) && defined(__cplusplus) && \ + (__cplusplus >= 201402L) #define TINYGLTF_USE_CPP14 #endif -#ifndef TINYGLTF_USE_CPP14 -#include -#endif - #ifdef __ANDROID__ #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS #include @@ -222,6 +195,11 @@ typedef enum { OBJECT_TYPE } Type; +typedef enum { + Permissive, + Strict +} ParseStrictness; + static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { return 1; @@ -284,11 +262,7 @@ class Value { 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) { @@ -301,6 +275,7 @@ class Value { } 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); @@ -546,28 +521,30 @@ typedef std::map ParameterMap; typedef std::map ExtensionMap; struct AnimationChannel { - int sampler; // required - int target_node; // optional index of the node to target (alternative + 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() : sampler(-1), target_node(-1) {} + AnimationChannel() = default; DEFAULT_METHODS(AnimationChannel) bool operator==(const AnimationChannel &) const; }; struct AnimationSampler { - int input; // required - int output; // required + int input{-1}; // required + int output{-1}; // required std::string interpolation; // "LINEAR", "STEP","CUBICSPLINE" or user defined // string. default "LINEAR" Value extras; @@ -577,7 +554,7 @@ struct AnimationSampler { std::string extras_json_string; std::string extensions_json_string; - AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} + AnimationSampler() : interpolation("LINEAR") {} DEFAULT_METHODS(AnimationSampler) bool operator==(const AnimationSampler &) const; }; @@ -600,9 +577,9 @@ struct Animation { 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 + 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; @@ -611,10 +588,7 @@ struct Skin { std::string extras_json_string; std::string extensions_json_string; - Skin() { - inverseBindMatrices = -1; - skeleton = -1; - } + Skin() = default; DEFAULT_METHODS(Skin) bool operator==(const Skin &) const; }; @@ -645,25 +619,21 @@ struct Sampler { 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) {} + Sampler() = default; 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) + 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; // (required if no uri) + 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. @@ -681,16 +651,9 @@ struct Image { // 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; + bool as_is{false}; - Image() : as_is(false) { - bufferView = -1; - width = -1; - height = -1; - component = -1; - bits = -1; - pixel_type = -1; - } + Image() = default; DEFAULT_METHODS(Image) bool operator==(const Image &) const; @@ -699,8 +662,8 @@ struct Image { struct Texture { std::string name; - int sampler; - int source; + int sampler{-1}; + int source{-1}; Value extras; ExtensionMap extensions; @@ -708,16 +671,16 @@ struct Texture { std::string extras_json_string; std::string extensions_json_string; - Texture() : sampler(-1), source(-1) {} + Texture() = default; 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. + int index{-1}; // required. + int texCoord{0}; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. Value extras; ExtensionMap extensions; @@ -726,17 +689,18 @@ struct TextureInfo { std::string extras_json_string; std::string extensions_json_string; - TextureInfo() : index(-1), texCoord(0) {} + TextureInfo() = default; 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)) + 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; @@ -745,17 +709,17 @@ struct NormalTextureInfo { std::string extras_json_string; std::string extensions_json_string; - NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {} + NormalTextureInfo() = default; 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 + int index{-1}; // required + int texCoord{0}; // The set index of texture's TEXCOORD attribute used for // texture coordinate mapping. - double strength; // occludedColor = lerp(color, color * , ) + double strength{1.0}; // occludedColor = lerp(color, color * , ) Value extras; ExtensionMap extensions; @@ -764,17 +728,17 @@ struct OcclusionTextureInfo { std::string extras_json_string; std::string extensions_json_string; - OcclusionTextureInfo() : index(-1), texCoord(0), strength(1.0) {} + OcclusionTextureInfo() = default; 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] + std::vector baseColorFactor{1.0, 1.0, 1.0, 1.0}; // len = 4. default [1,1,1,1] TextureInfo baseColorTexture; - double metallicFactor; // default 1 - double roughnessFactor; // default 1 + double metallicFactor{1.0}; // default 1 + double roughnessFactor{1.0}; // default 1 TextureInfo metallicRoughnessTexture; Value extras; @@ -784,11 +748,9 @@ struct PbrMetallicRoughness { 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) {} + PbrMetallicRoughness() = default; DEFAULT_METHODS(PbrMetallicRoughness) + bool operator==(const PbrMetallicRoughness &) const; }; @@ -798,10 +760,11 @@ struct PbrMetallicRoughness { 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; + 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; @@ -821,7 +784,7 @@ struct Material { std::string extras_json_string; std::string extensions_json_string; - Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {} + Material() = default; DEFAULT_METHODS(Material) bool operator==(const Material &) const; @@ -845,26 +808,20 @@ struct BufferView { bool dracoDecoded{false}; // Flag indicating this has been draco decoded - BufferView() - : buffer(-1), - byteOffset(0), - byteLength(0), - byteStride(0), - target(0), - dracoDecoded(false) {} + BufferView() = default; DEFAULT_METHODS(BufferView) bool operator==(const BufferView &) const; }; struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor - // are not supported + int bufferView{-1}; // 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_*** .. + 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; @@ -877,19 +834,33 @@ struct Accessor { std::vector maxValues; // optional. integer value is promoted to double - struct { + struct Sparse { int count; bool isSparse; struct { - int byteOffset; + size_t byteOffset; int bufferView; int componentType; // a TINYGLTF_COMPONENT_TYPE_ value + Value extras; + ExtensionMap extensions; + std::string extras_json_string; + std::string extensions_json_string; } indices; struct { int bufferView; - int byteOffset; + size_t byteOffset; + Value extras; + ExtensionMap extensions; + std::string extras_json_string; + std::string extensions_json_string; } values; - } sparse; + 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. @@ -911,8 +882,8 @@ struct Accessor { return componentSizeInBytes * numComponents; } else { - // Check if byteStride is a multiple of the size of the accessor's component - // type. + // Check if byteStride is a multiple of the size of the accessor's + // component type. int componentSizeInBytes = GetComponentSizeInBytes(static_cast(componentType)); if (componentSizeInBytes <= 0) { @@ -929,12 +900,8 @@ struct Accessor { } Accessor() - : bufferView(-1), - byteOffset(0), - normalized(false), - componentType(-1), - count(0), - type(-1) { + + { sparse.isSparse = false; } DEFAULT_METHODS(Accessor) @@ -942,17 +909,12 @@ struct Accessor { }; struct PerspectiveCamera { - double aspectRatio; // min > 0 - double yfov; // required. min > 0 - double zfar; // min > 0 - double znear; // required. min > 0 + 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() - : aspectRatio(0.0), - yfov(0.0), - zfar(0.0) // 0 = use infinite projection matrix - , - znear(0.0) {} + PerspectiveCamera() = default; DEFAULT_METHODS(PerspectiveCamera) bool operator==(const PerspectiveCamera &) const; @@ -965,12 +927,12 @@ struct PerspectiveCamera { }; 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 + 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() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} + OrthographicCamera() = default; DEFAULT_METHODS(OrthographicCamera) bool operator==(const OrthographicCamera &) const; @@ -989,7 +951,7 @@ struct Camera { PerspectiveCamera perspective; OrthographicCamera orthographic; - Camera() {} + Camera() = default; DEFAULT_METHODS(Camera) bool operator==(const Camera &) const; @@ -1006,10 +968,10 @@ struct Primitive { // 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_*** + 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 @@ -1021,11 +983,7 @@ struct Primitive { std::string extras_json_string; std::string extensions_json_string; - Primitive() { - material = -1; - indices = -1; - mode = -1; - } + Primitive() = default; DEFAULT_METHODS(Primitive) bool operator==(const Primitive &) const; }; @@ -1048,17 +1006,20 @@ struct Mesh { class Node { public: - Node() : camera(-1), skin(-1), mesh(-1) {} + Node() = default; DEFAULT_METHODS(Node) bool operator==(const Node &) const; - int camera; // the index of the camera referenced by this node + int camera{-1}; // the index of the camera referenced by this node std::string name; - int skin; - int mesh; + 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 @@ -1112,6 +1073,7 @@ struct Asset { struct Scene { std::string name; std::vector nodes; + std::vector audioEmitters; // KHR_audio global emitters ExtensionMap extensions; Value extras; @@ -1126,10 +1088,10 @@ struct Scene { }; struct SpotLight { - double innerConeAngle; - double outerConeAngle; + double innerConeAngle{0.0}; + double outerConeAngle{0.7853981634}; - SpotLight() : innerConeAngle(0.0), outerConeAngle(0.7853981634) {} + SpotLight() = default; DEFAULT_METHODS(SpotLight) bool operator==(const SpotLight &) const; @@ -1149,7 +1111,7 @@ struct Light { double range{0.0}; // 0.0 = infinite SpotLight spot; - Light() : intensity(1.0), range(0.0) {} + Light() = default; DEFAULT_METHODS(Light) bool operator==(const Light &) const; @@ -1162,6 +1124,89 @@ struct Light { 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; @@ -1183,8 +1228,10 @@ class Model { std::vector cameras; std::vector scenes; std::vector lights; + std::vector audioEmitters; + std::vector audioSources; - int defaultScene = -1; + int defaultScene{-1}; std::vector extensionsUsed; std::vector extensionsRequired; @@ -1301,6 +1348,13 @@ typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &, const std::vector &, void *); +/// +/// GetFileSizeFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*GetFileSizeFunction)(size_t *filesize_out, std::string *err, + const std::string &abs_filename, + void *userdata); + /// /// A structure containing all required filesystem callbacks and a pointer to /// their user data. @@ -1310,6 +1364,8 @@ struct FsCallbacks { 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 }; @@ -1333,6 +1389,9 @@ bool ReadWholeFile(std::vector *out, std::string *err, 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 /// @@ -1345,13 +1404,13 @@ class TinyGLTF { #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. @@ -1411,6 +1470,11 @@ class TinyGLTF { bool embedImages, bool embedBuffers, bool prettyPrint, bool writeBinary); + /// + /// Sets the parsing strictness. + /// + void SetParseStrictness(ParseStrictness strictness); + /// /// Set callback to use for loading image data /// @@ -1472,6 +1536,17 @@ class TinyGLTF { preserve_image_channels_ = onoff; } + /// + /// 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; + } + + size_t GetMaxExternalFileSize() const { return max_external_file_size_; } + bool GetPreserveImageChannels() const { return preserve_image_channels_; } private: @@ -1489,6 +1564,8 @@ class TinyGLTF { size_t bin_size_ = 0; bool is_binary_ = false; + ParseStrictness strictness_ = ParseStrictness::Strict; + bool serialize_default_values_ = false; ///< Serialize default values? bool store_original_json_for_extras_and_extensions_ = false; @@ -1496,18 +1573,24 @@ class TinyGLTF { bool preserve_image_channels_ = false; /// Default false(expand channels to /// RGBA) for backward compatibility. + 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::FileExists, + &tinygltf::ExpandFilePath, + &tinygltf::ReadWholeFile, + &tinygltf::WriteWholeFile, + &tinygltf::GetFileSizeInBytes, nullptr // Fs callback user data #else - nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr // Fs callback user data #endif @@ -1550,8 +1633,10 @@ class TinyGLTF { #if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__) #include -//#include +// #include #ifndef TINYGLTF_NO_FS +#include // for is_directory check + #include #include #endif @@ -1694,7 +1779,7 @@ class TinyGLTF { #endif #elif !defined(__ANDROID__) && !defined(__OpenBSD__) -//#include +// #include #endif #if defined(__sparcv9) || defined(__powerpc__) @@ -1714,6 +1799,7 @@ namespace detail { // 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 = @@ -1725,6 +1811,7 @@ rapidjson::CrtAllocator &GetAllocator() { return s_CrtAllocator; } // 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; @@ -1771,6 +1858,7 @@ struct JsonDocument : public rapidjson::Document { #else 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; @@ -1785,8 +1873,8 @@ void JsonParse(JsonDocument &doc, const char *str, size_t length, doc = detail::json::parse(str, str + length, nullptr, throwExc); #endif } -} // namespace -} +} // namespace detail +} // namespace tinygltf #ifdef __APPLE__ #include "TargetConditionals.h" @@ -1929,6 +2017,17 @@ 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) && @@ -1968,6 +2067,7 @@ 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) && @@ -1978,6 +2078,15 @@ bool SpotLight::operator==(const SpotLight &other) const { 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) && @@ -2107,9 +2216,20 @@ static std::string FindFile(const std::vector &paths, return std::string(); } + // 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(); + } + + std::string cleaned_filepath = std::string(filepath.c_str()); + for (size_t i = 0; i < paths.size(); i++) { std::string absPath = - fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); + fs->ExpandFilePath(JoinPath(paths[i], cleaned_filepath), fs->user_data); if (fs->FileExists(absPath, fs->user_data)) { return absPath; } @@ -2355,7 +2475,8 @@ bool URIDecode(const std::string &in_uri, std::string *out_uri, 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) { + 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() ? @@ -2381,6 +2502,32 @@ static bool LoadExternalFile(std::vector *out, std::string *err, 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 (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; + } + } + std::vector buf; std::string fileReadErr; bool fileRead = @@ -2420,6 +2567,10 @@ static bool LoadExternalFile(std::vector *out, std::string *err, return true; } +void TinyGLTF::SetParseStrictness(ParseStrictness strictness) { + strictness_ = strictness; +} + void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { LoadImageData = func; load_image_user_data_ = user_data; @@ -2606,8 +2757,8 @@ bool WriteImageData(const std::string *basepath, const std::string *filename, if (embedImages) { // Embed base64-encoded image into URI if (data.size()) { - *out_uri = header + - base64_encode(&data[0], static_cast(data.size())); + *out_uri = header + base64_encode(&data[0], + static_cast(data.size())); } else { // Throw error? } @@ -2686,13 +2837,29 @@ bool FileExists(const std::string &abs_filename, void *) { } #else #ifdef _WIN32 -#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) +#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 + // TODO: is_directory check FILE *fp = nullptr; errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); if (err != 0) { @@ -2701,6 +2868,14 @@ bool FileExists(const std::string &abs_filename, void *) { #endif #else + 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) { @@ -2777,6 +2952,102 @@ std::string ExpandFilePath(const std::string &filepath, void *) { #endif } +bool GetFileSizeInBytes(size_t *filesize_out, std::string *err, + const std::string &filepath, void *userdata) { + (void)userdata; + +#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; + } + + 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); +#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::ifstream f(filepath.c_str(), std::ifstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File open 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; + } + + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + + // std::cout << "sz = " << sz << "\n"; + f.seekg(0, f.beg); + + 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; + } else if (sz >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Invalid file size : " + filepath + "\n"; + } + return false; + } + + (*filesize_out) = sz; + return true; +#endif +} + bool ReadWholeFile(std::vector *out, std::string *err, const std::string &filepath, void *) { #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS @@ -2832,8 +3103,21 @@ bool ReadWholeFile(std::vector *out, std::string *err, 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; + } + f.seekg(0, f.end); size_t sz = static_cast(f.tellg()); + + // std::cout << "sz = " << sz << "\n"; f.seekg(0, f.beg); if (int64_t(sz) < 0) { @@ -2847,6 +3131,11 @@ bool ReadWholeFile(std::vector *out, std::string *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); @@ -3207,7 +3496,8 @@ std::string GetKey(detail::json_const_iterator &it) { #endif } -bool FindMember(const detail::json &o, const char *member, detail::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; @@ -3220,6 +3510,36 @@ bool FindMember(const detail::json &o, const char *member, detail::json_const_it #endif } +bool FindMember(detail::json &o, const char *member, + detail::json_iterator &it) { +#ifdef TINYGLTF_USE_RAPIDJSON + if (!o.IsObject()) { + return false; + } + it = o.FindMember(member); + return it != o.MemberEnd(); +#else + it = o.find(member); + return it != o.end(); +#endif +} + +void Erase(detail::json &o, detail::json_iterator &it) { +#ifdef TINYGLTF_USE_RAPIDJSON + o.EraseMember(it); +#else + o.erase(it); +#endif +} + +bool IsEmpty(const detail::json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.ObjectEmpty(); +#else + return o.empty(); +#endif +} + const detail::json &GetValue(detail::json_const_iterator &it) { #ifdef TINYGLTF_USE_RAPIDJSON return it->value; @@ -3228,6 +3548,14 @@ const detail::json &GetValue(detail::json_const_iterator &it) { #endif } +detail::json &GetValue(detail::json_iterator &it) { +#ifdef TINYGLTF_USE_RAPIDJSON + return it->value; +#else + return it.value(); +#endif +} + std::string JsonToString(const detail::json &o, int spacing = -1) { #ifdef TINYGLTF_USE_RAPIDJSON using namespace rapidjson; @@ -3252,7 +3580,7 @@ std::string JsonToString(const detail::json &o, int spacing = -1) { #endif } -} // namespace +} // namespace detail static bool ParseJsonAsValue(Value *ret, const detail::json &o) { Value val{}; @@ -3361,7 +3689,8 @@ static bool ParseExtrasProperty(Value *ret, const detail::json &o) { return ParseJsonAsValue(ret, detail::GetValue(it)); } -static bool ParseBooleanProperty(bool *ret, std::string *err, const detail::json &o, +static bool ParseBooleanProperty(bool *ret, std::string *err, + const detail::json &o, const std::string &property, const bool required, const std::string &parent_node = "") { @@ -3410,7 +3739,8 @@ static bool ParseBooleanProperty(bool *ret, std::string *err, const detail::json return true; } -static bool ParseIntegerProperty(int *ret, std::string *err, const detail::json &o, +static bool ParseIntegerProperty(int *ret, std::string *err, + const detail::json &o, const std::string &property, const bool required, const std::string &parent_node = "") { @@ -3446,7 +3776,8 @@ static bool ParseIntegerProperty(int *ret, std::string *err, const detail::json return true; } -static bool ParseUnsignedProperty(size_t *ret, std::string *err, const detail::json &o, +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 = "") { @@ -3499,7 +3830,8 @@ static bool ParseUnsignedProperty(size_t *ret, std::string *err, const detail::j return true; } -static bool ParseNumberProperty(double *ret, std::string *err, const detail::json &o, +static bool ParseNumberProperty(double *ret, std::string *err, + const detail::json &o, const std::string &property, const bool required, const std::string &parent_node = "") { @@ -3538,8 +3870,8 @@ static bool ParseNumberProperty(double *ret, std::string *err, const detail::jso } static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, - const detail::json &o, const std::string &property, - bool required, + 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)) { @@ -3774,8 +4106,8 @@ static bool ParseJSONProperty(std::map *ret, } static bool ParseParameterProperty(Parameter *param, std::string *err, - const detail::json &o, const std::string &prop, - bool required) { + 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 @@ -3820,7 +4152,8 @@ static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, return false; } ExtensionMap extensions; - detail::json_const_iterator extIt = detail::ObjectBegin(obj); // it.value().begin(); + 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); @@ -3840,6 +4173,31 @@ static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, return true; } +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); + + 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; +} + 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"); @@ -3847,34 +4205,16 @@ static bool ParseAsset(Asset *asset, std::string *err, const detail::json &o, ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset"); - ParseExtensionsProperty(&asset->extensions, err, o); - - // Unity exporter version is added as extra here - ParseExtrasProperty(&(asset->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - asset->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - asset->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } - + 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, FsCallbacks *fs, - const URICallbacks *uri_cb, + const std::string &basedir, const size_t max_file_size, + FsCallbacks *fs, const URICallbacks *uri_cb, LoadImageDataFunction *LoadImageData = nullptr, void *load_image_user_data = nullptr) { // A glTF image must either reference a bufferView or an image uri @@ -3907,23 +4247,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, return false; } - ParseExtensionsProperty(&image->extensions, err, o); - ParseExtrasProperty(&image->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extensions", eit)) { - image->extensions_json_string = detail::JsonToString(detail::GetValue(eit)); - } - } - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extras", eit)) { - image->extras_json_string = detail::JsonToString(detail::GetValue(eit)); - } - } - } + ParseExtrasAndExtensions(image, err, o, + store_original_json_for_extras_and_extensions); if (hasBufferView) { int bufferView = -1; @@ -3973,8 +4298,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, 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"; + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; } return false; } @@ -3989,8 +4314,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, 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"; + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; } // Image loading failure is not critical to overall gltf loading. @@ -3999,11 +4324,12 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir, /* required */ false, /* required bytes */ 0, - /* checksize */ false, fs)) { + /* 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 = [" + image->name + - "]\n"; + std::to_string(image_idx) + "] name = \"" + decoded_uri + + "\"\n"; } // If the image cannot be loaded, keep uri as image->uri. return true; @@ -4012,8 +4338,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, if (img.empty()) { if (warn) { (*warn) += "Image data is empty for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "] \n"; + std::to_string(image_idx) + "] name = \"" + image->name + + "\" \n"; } return false; } @@ -4030,7 +4356,8 @@ static bool ParseImage(Image *image, const int image_idx, std::string *err, static_cast(img.size()), load_image_user_data); } -static bool ParseTexture(Texture *texture, std::string *err, const detail::json &o, +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; @@ -4043,23 +4370,8 @@ static bool ParseTexture(Texture *texture, std::string *err, const detail::json texture->sampler = sampler; texture->source = source; - ParseExtensionsProperty(&texture->extensions, err, o); - ParseExtrasProperty(&texture->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - texture->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - texture->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(texture, err, o, + store_original_json_for_extras_and_extensions); ParseStringProperty(&texture->name, err, o, "name", false); @@ -4080,23 +4392,8 @@ static bool ParseTextureInfo( ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); - ParseExtensionsProperty(&texinfo->extensions, err, o); - ParseExtrasProperty(&texinfo->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - texinfo->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -4116,23 +4413,8 @@ static bool ParseNormalTextureInfo( 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - texinfo->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -4152,23 +4434,8 @@ static bool ParseOcclusionTextureInfo( 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - texinfo->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - texinfo->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(texinfo, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -4176,7 +4443,8 @@ static bool ParseOcclusionTextureInfo( 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, bool is_binary = false, + 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; @@ -4228,7 +4496,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o, } if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri, basedir, /* required */ true, - byteLength, /* checkSize */ true, fs)) { + byteLength, /* checkSize */ true, + /* max_file_size */ max_buffer_size, fs)) { return false; } } @@ -4237,7 +4506,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o, if ((bin_size == 0) || (bin_data == nullptr)) { if (err) { - (*err) += "Invalid binary data in `Buffer', or GLB with empty BIN chunk.\n"; + (*err) += + "Invalid binary data in `Buffer', or GLB with empty BIN chunk.\n"; } return false; } @@ -4276,7 +4546,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o, } if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri, basedir, /* required */ true, byteLength, - /* checkSize */ true, fs)) { + /* checkSize */ true, + /* max file size */ max_buffer_size, fs)) { return false; } } @@ -4284,23 +4555,8 @@ static bool ParseBuffer(Buffer *buffer, std::string *err, const detail::json &o, ParseStringProperty(&buffer->name, err, o, "name", false); - ParseExtensionsProperty(&buffer->extensions, err, o); - ParseExtrasProperty(&buffer->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - buffer->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - buffer->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(buffer, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -4356,23 +4612,8 @@ static bool ParseBufferView( ParseStringProperty(&bufferView->name, err, o, "name", false); - ParseExtensionsProperty(&bufferView->extensions, err, o); - ParseExtrasProperty(&bufferView->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - bufferView->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - bufferView->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(bufferView, err, o, + store_original_json_for_extras_and_extensions); bufferView->buffer = buffer; bufferView->byteOffset = byteOffset; @@ -4381,15 +4622,19 @@ static bool ParseBufferView( return true; } -static bool ParseSparseAccessor(Accessor *accessor, std::string *err, - const detail::json &o) { - accessor->sparse.isSparse = 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)) { @@ -4405,37 +4650,45 @@ static bool ParseSparseAccessor(Accessor *accessor, std::string *err, const detail::json &indices_obj = detail::GetValue(indices_iterator); const detail::json &values_obj = detail::GetValue(values_iterator); - int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0; + 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; } - ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset", + 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, values_byte_offset = 0; + int values_buffer_view = 0; + size_t 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", + ParseUnsignedProperty(&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; + 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, +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"); @@ -4515,29 +4768,16 @@ static bool ParseAccessor(Accessor *accessor, std::string *err, const detail::js } } - ParseExtensionsProperty(&(accessor->extensions), err, o); - ParseExtrasProperty(&(accessor->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - accessor->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - accessor->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + 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, err, detail::GetValue(iterator)); + return ParseSparseAccessor(&accessor->sparse, err, + detail::GetValue(iterator), + store_original_json_for_extras_and_extensions); } return true; @@ -4637,8 +4877,9 @@ static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, } static bool ParseDracoExtension(Primitive *primitive, Model *model, - std::string *err, - const Value &dracoExtensionValue) { + std::string *err, std::string *warn, + const Value &dracoExtensionValue, + ParseStrictness strictness) { (void)err; auto bufferViewValue = dracoExtensionValue.Get("bufferView"); if (!bufferViewValue.IsInt()) return false; @@ -4670,6 +4911,33 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model, // 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; @@ -4735,9 +5003,11 @@ static bool ParseDracoExtension(Primitive *primitive, Model *model, } #endif -static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, +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) { + bool store_original_json_for_extras_and_extensions, + ParseStrictness strictness) { int material = -1; ParseIntegerProperty(&material, err, o, "material", false); primitive->material = material; @@ -4759,7 +5029,8 @@ static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, 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)); + for (detail::json_const_array_iterator i = + detail::ArrayBegin(detail::GetValue(targetsObject)); i != targetsObjectEnd; ++i) { std::map targetAttribues; @@ -4778,51 +5049,44 @@ static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, } } - ParseExtrasProperty(&(primitive->extras), o); - ParseExtensionsProperty(&primitive->extensions, err, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - primitive->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - primitive->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + 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, dracoExtension->second); + 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, const detail::json &o, - bool store_original_json_for_extras_and_extensions) { +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)); + 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, *i, - store_original_json_for_extras_and_extensions)) { + 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)); } @@ -4832,23 +5096,8 @@ static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, const detail:: // 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - mesh->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - mesh->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(mesh, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -4881,21 +5130,87 @@ static bool ParseNode(Node *node, std::string *err, const detail::json &o, ParseNumberArrayProperty(&node->weights, err, o, "weights", false); - ParseExtensionsProperty(&node->extensions, err, o); - ParseExtrasProperty(&(node->extras), o); + ParseExtrasAndExtensions(node, err, o, + store_original_json_for_extras_and_extensions); - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - node->extensions_json_string = detail::JsonToString(detail::GetValue(it)); + // 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; } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - node->extras_json_string = detail::JsonToString(detail::GetValue(it)); + } + 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; } } @@ -4935,7 +5250,8 @@ static bool ParsePbrMetallicRoughness( { detail::json_const_iterator it; if (detail::FindMember(o, "metallicRoughnessTexture", it)) { - ParseTextureInfo(&pbr->metallicRoughnessTexture, err, detail::GetValue(it), + ParseTextureInfo(&pbr->metallicRoughnessTexture, err, + detail::GetValue(it), store_original_json_for_extras_and_extensions); } } @@ -4943,35 +5259,30 @@ static bool ParsePbrMetallicRoughness( 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - pbr->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - pbr->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(pbr, err, o, + store_original_json_for_extras_and_extensions); return true; } -static bool ParseMaterial(Material *material, std::string *err, const detail::json &o, - bool store_original_json_for_extras_and_extensions) { +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 (material->emissiveFactor.size() != 3) { + 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 " @@ -5004,7 +5315,8 @@ static bool ParseMaterial(Material *material, std::string *err, const detail::js { detail::json_const_iterator it; if (detail::FindMember(o, "normalTexture", it)) { - ParseNormalTextureInfo(&material->normalTexture, err, detail::GetValue(it), + ParseNormalTextureInfo(&material->normalTexture, err, + detail::GetValue(it), store_original_json_for_extras_and_extensions); } } @@ -5012,7 +5324,8 @@ static bool ParseMaterial(Material *material, std::string *err, const detail::js { detail::json_const_iterator it; if (detail::FindMember(o, "occlusionTexture", it)) { - ParseOcclusionTextureInfo(&material->occlusionTexture, err, detail::GetValue(it), + ParseOcclusionTextureInfo(&material->occlusionTexture, err, + detail::GetValue(it), store_original_json_for_extras_and_extensions); } } @@ -5047,8 +5360,8 @@ static bool ParseMaterial(Material *material, std::string *err, const detail::js for (; itVal != itValEnd; ++itVal) { Parameter param; - if (ParseParameterProperty(¶m, err, values_object, detail::GetKey(itVal), - false)) { + if (ParseParameterProperty(¶m, err, values_object, + detail::GetKey(itVal), false)) { material->values.emplace(detail::GetKey(itVal), std::move(param)); } } @@ -5067,22 +5380,25 @@ static bool ParseMaterial(Material *material, std::string *err, const detail::js } } - material->extensions.clear(); - ParseExtensionsProperty(&material->extensions, err, o); - ParseExtrasProperty(&(material->extras), o); + material->extensions.clear(); // Note(agnat): Why? + ParseExtrasAndExtensions(material, err, o, + store_original_json_for_extras_and_extensions); - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extensions", eit)) { - material->extensions_json_string = detail::JsonToString(detail::GetValue(eit)); + 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()); } - } - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extras", eit)) { - material->extras_json_string = detail::JsonToString(detail::GetValue(eit)); + } else { + if (err) { + *err += + "Material has extension MSFT_lod, but does not reference " + "other materials via their ids.\n"; } + return false; } } @@ -5103,7 +5419,8 @@ static bool ParseAnimationChannel( } detail::json_const_iterator targetIt; - if (detail::FindMember(o, "target", targetIt) && detail::IsObject(detail::GetValue(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); @@ -5116,10 +5433,21 @@ static bool ParseAnimationChannel( 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, "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)); + } } } } @@ -5127,23 +5455,8 @@ static bool ParseAnimationChannel( channel->sampler = samplerIndex; channel->target_node = targetIndex; - ParseExtensionsProperty(&channel->extensions, err, o); - ParseExtrasProperty(&(channel->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - channel->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - channel->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(channel, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -5155,8 +5468,10 @@ static bool ParseAnimation(Animation *animation, std::string *err, 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)); + 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( @@ -5171,7 +5486,8 @@ static bool ParseAnimation(Animation *animation, std::string *err, { detail::json_const_iterator samplerIt; - if (detail::FindMember(o, "samplers", samplerIt) && detail::IsArray(detail::GetValue(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); @@ -5199,23 +5515,8 @@ static bool ParseAnimation(Animation *animation, std::string *err, } sampler.input = inputIndex; sampler.output = outputIndex; - ParseExtensionsProperty(&(sampler.extensions), err, o); - ParseExtrasProperty(&(sampler.extras), s); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extensions", eit)) { - sampler.extensions_json_string = detail::JsonToString(detail::GetValue(eit)); - } - } - { - detail::json_const_iterator eit; - if (detail::FindMember(o, "extras", eit)) { - sampler.extras_json_string = detail::JsonToString(detail::GetValue(eit)); - } - } - } + ParseExtrasAndExtensions(&sampler, err, o, + store_original_json_for_extras_and_extensions); animation->samplers.emplace_back(std::move(sampler)); } @@ -5224,28 +5525,14 @@ static bool ParseAnimation(Animation *animation, std::string *err, ParseStringProperty(&animation->name, err, o, "name", false); - ParseExtensionsProperty(&animation->extensions, err, o); - ParseExtrasProperty(&(animation->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - animation->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - animation->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + 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, +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); @@ -5270,23 +5557,8 @@ static bool ParseSampler(Sampler *sampler, std::string *err, const detail::json sampler->wrapT = wrapT; // sampler->wrapR = wrapR; - ParseExtensionsProperty(&(sampler->extensions), err, o); - ParseExtrasProperty(&(sampler->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - sampler->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - sampler->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(sampler, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -5309,23 +5581,8 @@ static bool ParseSkin(Skin *skin, std::string *err, const detail::json &o, 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - skin->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - skin->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(skin, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -5356,51 +5613,22 @@ static bool ParsePerspectiveCamera( camera->yfov = yfov; camera->znear = znear; - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - camera->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - camera->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + 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, +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); - ParseExtensionsProperty(&light->extensions, err, o); - ParseExtrasProperty(&light->extras, o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - light->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - light->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(light, err, o, + store_original_json_for_extras_and_extensions); // TODO(syoyo): Validate parameter values. @@ -5431,23 +5659,8 @@ static bool ParseOrthographicCamera( return false; } - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - camera->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - camera->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(camera, err, o, + store_original_json_for_extras_and_extensions); camera->xmag = xmag; camera->ymag = ymag; @@ -5529,23 +5742,8 @@ static bool ParseCamera(Camera *camera, std::string *err, const detail::json &o, ParseStringProperty(&camera->name, err, o, "name", false); - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - if (store_original_json_for_extras_and_extensions) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - camera->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - camera->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + ParseExtrasAndExtensions(camera, err, o, + store_original_json_for_extras_and_extensions); return true; } @@ -5587,27 +5785,117 @@ static bool ParseLight(Light *light, std::string *err, const detail::json &o, 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) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - light->extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - light->extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - } + 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, @@ -5659,7 +5947,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, { bool version_found = false; detail::json_const_iterator it; - if (detail::FindMember(v, "asset", it) && detail::IsObject(detail::GetValue(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; @@ -5681,9 +5970,11 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, // 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 { + 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)); + return detail::FindMember(_v, name, it) && + detail::IsArray(detail::GetValue(it)); }; { @@ -5749,7 +6040,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, // 1. Parse Asset { detail::json_const_iterator it; - if (detail::FindMember(v, "asset", it) && detail::IsObject(detail::GetValue(it))) { + if (detail::FindMember(v, "asset", it) && + detail::IsObject(detail::GetValue(it))) { const detail::json &root = detail::GetValue(it); ParseAsset(&model->asset, err, root, @@ -5757,28 +6049,7 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, } } -#ifdef TINYGLTF_USE_CPP14 - auto ForEachInArray = [](const detail::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 detail::json &_v, const char *member, - const std::function &cb) -> bool -#endif - { - 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; - }; + using detail::ForEachInArray; // 2. Parse extensionUsed { @@ -5811,7 +6082,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, Buffer buffer; if (!ParseBuffer(&buffer, err, o, store_original_json_for_extras_and_extensions_, &fs, - &uri_cb, base_dir, is_binary_, bin_data_, bin_size_)) { + &uri_cb, base_dir, max_external_file_size_, is_binary_, + bin_data_, bin_size_)) { return false; } @@ -5881,8 +6153,9 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, return false; } Mesh mesh; - if (!ParseMesh(&mesh, model, err, o, - store_original_json_for_extras_and_extensions_)) { + if (!ParseMesh(&mesh, model, err, warn, o, + store_original_json_for_extras_and_extensions_, + strictness_)) { return false; } @@ -5910,20 +6183,22 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, return false; } - auto bufferView = + const auto bufferView = model->accessors[size_t(primitive.indices)].bufferView; - if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) { + 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 } - - 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) { @@ -5944,7 +6219,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, 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()) { + if (bufferView >= 0 && + bufferView < (int)model->bufferViews.size()) { model->bufferViews[size_t(bufferView)].target = TINYGLTF_TARGET_ARRAY_BUFFER; } @@ -5987,30 +6263,11 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, } 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_) { - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extensions", it)) { - scene.extensions_json_string = detail::JsonToString(detail::GetValue(it)); - } - } - { - detail::json_const_iterator it; - if (detail::FindMember(o, "extras", it)) { - scene.extras_json_string = detail::JsonToString(detail::GetValue(it)); - } - } + if (!ParseScene(&scene, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; } model->scenes.emplace_back(std::move(scene)); @@ -6026,7 +6283,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, { detail::json_const_iterator rootIt; int iVal; - if (detail::FindMember(v, "scene", rootIt) && detail::GetInt(detail::GetValue(rootIt), iVal)) { + if (detail::FindMember(v, "scene", rootIt) && + detail::GetInt(detail::GetValue(rootIt), iVal)) { model->defaultScene = iVal; } } @@ -6043,8 +6301,9 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, Material material; ParseStringProperty(&material.name, err, o, "name", false); - if (!ParseMaterial(&material, err, o, - store_original_json_for_extras_and_extensions_)) { + if (!ParseMaterial(&material, err, warn, o, + store_original_json_for_extras_and_extensions_, + strictness_)) { return false; } @@ -6082,8 +6341,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, Image image; if (!ParseImage(&image, idx, err, warn, o, store_original_json_for_extras_and_extensions_, base_dir, - &fs, &uri_cb, &this->LoadImageData, - load_image_user_data)) { + max_external_file_size_, &fs, &uri_cb, + &this->LoadImageData, load_image_user_data)) { return false; } @@ -6258,13 +6517,15 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, } } - // 17. Parse Extensions - ParseExtensionsProperty(&model->extensions, err, v); + // 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))) { + 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)); @@ -6272,7 +6533,8 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, 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))) { + 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)) { @@ -6293,18 +6555,52 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, } } } + // 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)); + } + } + } } } } - // 19. Parse Extras - ParseExtrasProperty(&model->extras, v); - - if (store_original_json_for_extras_and_extensions_) { - model->extras_json_string = detail::JsonToString(v["extras"]); - model->extensions_json_string = detail::JsonToString(v["extensions"]); - } - return true; } @@ -6387,16 +6683,16 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, return false; } - unsigned int version; // 4 bytes - unsigned int length; // 4 bytes + 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); + memcpy(&length, bytes + 8, 4); // Total glb size, including header and all chunks. swap4(&length); - memcpy(&chunk0_length, bytes + 12, 4); // JSON data length + memcpy(&chunk0_length, bytes + 12, 4); // JSON data length swap4(&chunk0_length); memcpy(&chunk0_format, bytes + 16, 4); swap4(&chunk0_format); @@ -6411,13 +6707,16 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, // 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()) { + if (header_and_json_size > (std::numeric_limits::max)()) { // Do not allow 4GB or more GLB data. - (*err) = "Invalid glTF binary. GLB data exceeds 4GB."; + 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)) || + 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."; @@ -6432,77 +6731,105 @@ bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, 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"; + // 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. + // 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) - // 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 ((header_and_json_size + 12ull) > uint64_t(length)) { + // + // 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 4 bytes or more bytes, but got " + std::to_string((header_and_json_size + 12ull) - uint64_t(length)) + ".\n"; + (*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; // 4 bytes - unsigned int chunk1_format; // 4 bytes; - memcpy(&chunk1_length, bytes + header_and_json_size, 4); // JSON data length + 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); - //std::cout << "chunk1_length = " << chunk1_length << "\n"; - - if (chunk1_length < 4) { - if (err) { - (*err) = "Insufficient Chunk1(BIN) data size."; - } - return false; - } - - if ((chunk1_length % 4) != 0) { - if (err) { - (*err) = "BIN Chunk end does not aligned to a 4-byte boundary."; - } - return false; - } - - if (uint64_t(chunk1_length) + header_and_json_size > uint64_t(length)) { - if (err) { - (*err) = "BIN Chunk data length exceeds the GLB size."; - } - return false; - } - if (chunk1_format != 0x004e4942) { if (err) { - (*err) = "Invalid type for chunk1 data."; + (*err) = "Invalid chunkType for Chunk1."; } return false; } - //std::cout << "chunk1_length = " << chunk1_length << "\n"; + if (chunk1_length == 0) { - bin_data_ = bytes + header_and_json_size + - 8; // 4 bytes (bin_buffer_length) + 4 bytes(bin_buffer_format) + 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); } - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - chunk0_length); - is_binary_ = true; bool ret = LoadFromString(model, err, warn, @@ -6576,7 +6903,18 @@ void JsonAddMember(detail::json &o, const char *key, detail::json &&value) { if (!o.IsObject()) { o.SetObject(); } - o.AddMember(detail::json(key, detail::GetAllocator()), std::move(value), detail::GetAllocator()); + + // 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); #endif @@ -6614,7 +6952,7 @@ void JsonReserveArray(detail::json &o, size_t s) { (void)(o); (void)(s); } -} // namespace +} // namespace detail // typedef std::pair json_object_pair; @@ -6629,8 +6967,10 @@ static void SerializeNumberProperty(const std::string &key, T number, #ifdef TINYGLTF_USE_RAPIDJSON template <> -void SerializeNumberProperty(const std::string &key, size_t number, detail::json &obj) { - detail::JsonAddMember(obj, key.c_str(), detail::json(static_cast(number))); +void SerializeNumberProperty(const std::string &key, size_t number, + detail::json &obj) { + detail::JsonAddMember(obj, key.c_str(), + detail::json(static_cast(number))); } #endif @@ -6649,8 +6989,10 @@ static void SerializeNumberArrayProperty(const std::string &key, } 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())); + 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, @@ -6843,7 +7185,8 @@ static void SerializeParameterMap(ParameterMap ¶m, detail::json &o) { } #endif -static void SerializeExtensionMap(const ExtensionMap &extensions, detail::json &o) { +static void SerializeExtensionMap(const ExtensionMap &extensions, + detail::json &o) { if (!extensions.size()) return; detail::json extMap; @@ -6869,12 +7212,22 @@ static void SerializeExtensionMap(const ExtensionMap &extensions, detail::json & 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", int(accessor.byteOffset), o); + SerializeNumberProperty("byteOffset", accessor.byteOffset, o); SerializeNumberProperty("componentType", accessor.componentType, o); SerializeNumberProperty("count", accessor.count, o); @@ -6935,29 +7288,34 @@ static void SerializeGltfAccessor(const Accessor &accessor, detail::json &o) { SerializeStringProperty("type", type, o); if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); - if (accessor.extras.Type() != NULL_TYPE) { - SerializeValue("extras", accessor.extras, 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); - detail::JsonAddMember(sparse, "indices", std::move(indices)); - } - { - detail::json values; - SerializeNumberProperty("bufferView", accessor.sparse.values.bufferView, values); - SerializeNumberProperty("byteOffset", accessor.sparse.values.byteOffset, values); - detail::JsonAddMember(sparse, "values", std::move(values)); - } - detail::JsonAddMember(o, "sparse", std::move(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)); } } @@ -6967,22 +7325,19 @@ static void SerializeGltfAnimationChannel(const AnimationChannel &channel, { detail::json target; - if (channel.target_node > 0) { + 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)); } - if (channel.extras.Type() != NULL_TYPE) { - SerializeValue("extras", channel.extras, o); - } - - SerializeExtensionMap(channel.extensions, o); + SerializeExtrasAndExtensions(channel, o); } static void SerializeGltfAnimationSampler(const AnimationSampler &sampler, @@ -6991,12 +7346,11 @@ static void SerializeGltfAnimationSampler(const AnimationSampler &sampler, SerializeNumberProperty("output", sampler.output, o); SerializeStringProperty("interpolation", sampler.interpolation, o); - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } + SerializeExtrasAndExtensions(sampler, o); } -static void SerializeGltfAnimation(const Animation &animation, detail::json &o) { +static void SerializeGltfAnimation(const Animation &animation, + detail::json &o) { if (!animation.name.empty()) SerializeStringProperty("name", animation.name, o); @@ -7025,11 +7379,7 @@ static void SerializeGltfAnimation(const Animation &animation, detail::json &o) detail::JsonAddMember(o, "samplers", std::move(samplers)); } - if (animation.extras.Type() != NULL_TYPE) { - SerializeValue("extras", animation.extras, o); - } - - SerializeExtensionMap(animation.extensions, o); + SerializeExtrasAndExtensions(animation, o); } static void SerializeGltfAsset(const Asset &asset, detail::json &o) { @@ -7051,11 +7401,7 @@ static void SerializeGltfAsset(const Asset &asset, detail::json &o) { // TODO(syoyo): Do we need to check if `version` is greater or equal to 2.0? SerializeStringProperty("version", version, o); - if (asset.extras.Keys().size()) { - SerializeValue("extras", asset.extras, o); - } - - SerializeExtensionMap(asset.extensions, o); + SerializeExtrasAndExtensions(asset, o); } static void SerializeGltfBufferBin(const Buffer &buffer, detail::json &o, @@ -7065,9 +7411,7 @@ static void SerializeGltfBufferBin(const Buffer &buffer, detail::json &o, if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } + SerializeExtrasAndExtensions(buffer, o); } static void SerializeGltfBuffer(const Buffer &buffer, detail::json &o) { @@ -7076,9 +7420,7 @@ static void SerializeGltfBuffer(const Buffer &buffer, detail::json &o) { if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } + SerializeExtrasAndExtensions(buffer, o); } static bool SerializeGltfBuffer(const Buffer &buffer, detail::json &o, @@ -7090,13 +7432,12 @@ static bool SerializeGltfBuffer(const Buffer &buffer, detail::json &o, if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } + SerializeExtrasAndExtensions(buffer, o); return true; } -static void SerializeGltfBufferView(const BufferView &bufferView, detail::json &o) { +static void SerializeGltfBufferView(const BufferView &bufferView, + detail::json &o) { SerializeNumberProperty("buffer", bufferView.buffer, o); SerializeNumberProperty("byteLength", bufferView.byteLength, o); @@ -7117,9 +7458,7 @@ static void SerializeGltfBufferView(const BufferView &bufferView, detail::json & SerializeStringProperty("name", bufferView.name, o); } - if (bufferView.extras.Type() != NULL_TYPE) { - SerializeValue("extras", bufferView.extras, o); - } + SerializeExtrasAndExtensions(bufferView, o); } static void SerializeGltfImage(const Image &image, const std::string &uri, @@ -7137,25 +7476,18 @@ static void SerializeGltfImage(const Image &image, const std::string &uri, SerializeStringProperty("name", image.name, o); } - if (image.extras.Type() != NULL_TYPE) { - SerializeValue("extras", image.extras, o); - } - - SerializeExtensionMap(image.extensions, o); + SerializeExtrasAndExtensions(image, o); } -static void SerializeGltfTextureInfo(const TextureInfo &texinfo, detail::json &o) { +static void SerializeGltfTextureInfo(const TextureInfo &texinfo, + detail::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); + SerializeExtrasAndExtensions(texinfo, o); } static void SerializeGltfNormalTextureInfo(const NormalTextureInfo &texinfo, @@ -7170,11 +7502,7 @@ static void SerializeGltfNormalTextureInfo(const NormalTextureInfo &texinfo, SerializeNumberProperty("scale", texinfo.scale, o); } - if (texinfo.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texinfo.extras, o); - } - - SerializeExtensionMap(texinfo.extensions, o); + SerializeExtrasAndExtensions(texinfo, o); } static void SerializeGltfOcclusionTextureInfo( @@ -7189,11 +7517,7 @@ static void SerializeGltfOcclusionTextureInfo( SerializeNumberProperty("strength", texinfo.strength, o); } - if (texinfo.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texinfo.extras, o); - } - - SerializeExtensionMap(texinfo.extensions, o); + SerializeExtrasAndExtensions(texinfo, o); } static void SerializeGltfPbrMetallicRoughness(const PbrMetallicRoughness &pbr, @@ -7224,11 +7548,7 @@ static void SerializeGltfPbrMetallicRoughness(const PbrMetallicRoughness &pbr, detail::JsonAddMember(o, "metallicRoughnessTexture", std::move(texinfo)); } - SerializeExtensionMap(pbr.extensions, o); - - if (pbr.extras.Type() != NULL_TYPE) { - SerializeValue("extras", pbr.extras, o); - } + SerializeExtrasAndExtensions(pbr, o); } static void SerializeGltfMaterial(const Material &material, detail::json &o) { @@ -7284,7 +7604,8 @@ static void SerializeGltfMaterial(const Material &material, detail::json &o) { // importers (and validators). // if (!detail::JsonIsNull(pbrMetallicRoughness)) { - detail::JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); + detail::JsonAddMember(o, "pbrMetallicRoughness", + std::move(pbrMetallicRoughness)); } } @@ -7296,14 +7617,39 @@ static void SerializeGltfMaterial(const Material &material, detail::json &o) { } SerializeParameterMap(material.additionalValues, o); -#else - #endif - SerializeExtensionMap(material.extensions, o); + SerializeExtrasAndExtensions(material, o); - if (material.extras.Type() != NULL_TYPE) { - SerializeValue("extras", material.extras, 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); + } + } } } @@ -7351,11 +7697,7 @@ static void SerializeGltfMesh(const Mesh &mesh, detail::json &o) { detail::JsonAddMember(primitive, "targets", std::move(targets)); } - SerializeExtensionMap(gltfPrimitive.extensions, primitive); - - if (gltfPrimitive.extras.Type() != NULL_TYPE) { - SerializeValue("extras", gltfPrimitive.extras, primitive); - } + SerializeExtrasAndExtensions(gltfPrimitive, primitive); detail::JsonPushBack(primitives, std::move(primitive)); } @@ -7370,19 +7712,13 @@ static void SerializeGltfMesh(const Mesh &mesh, detail::json &o) { SerializeStringProperty("name", mesh.name, o); } - SerializeExtensionMap(mesh.extensions, o); - if (mesh.extras.Type() != NULL_TYPE) { - SerializeValue("extras", mesh.extras, o); - } + SerializeExtrasAndExtensions(mesh, o); } static void SerializeSpotLight(const SpotLight &spot, detail::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); - } + SerializeExtrasAndExtensions(spot, o); } static void SerializeGltfLight(const Light &light, detail::json &o) { @@ -7398,10 +7734,61 @@ static void SerializeGltfLight(const Light &light, detail::json &o) { SerializeSpotLight(light.spot, spot); detail::JsonAddMember(o, "spot", std::move(spot)); } - SerializeExtensionMap(light.extensions, o); - if (light.extras.Type() != NULL_TYPE) { - SerializeValue("extras", light.extras, o); + 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) { @@ -7433,11 +7820,107 @@ static void SerializeGltfNode(const Node &node, detail::json &o) { SerializeNumberArrayProperty("weights", node.weights, o); } - if (node.extras.Type() != NULL_TYPE) { - SerializeValue("extras", node.extras, 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); + } + } } - SerializeExtensionMap(node.extensions, o); if (!node.name.empty()) SerializeStringProperty("name", node.name, o); SerializeNumberArrayProperty("children", node.children, o); } @@ -7456,9 +7939,7 @@ static void SerializeGltfSampler(const Sampler &sampler, detail::json &o) { SerializeNumberProperty("wrapS", sampler.wrapS, o); SerializeNumberProperty("wrapT", sampler.wrapT, o); - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } + SerializeExtrasAndExtensions(sampler, o); } static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, @@ -7468,9 +7949,7 @@ static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, SerializeNumberProperty("xmag", camera.xmag, o); SerializeNumberProperty("ymag", camera.ymag, o); - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } + SerializeExtrasAndExtensions(camera, o); } static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, @@ -7485,9 +7964,7 @@ static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, SerializeNumberProperty("yfov", camera.yfov, o); } - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } + SerializeExtrasAndExtensions(camera, o); } static void SerializeGltfCamera(const Camera &camera, detail::json &o) { @@ -7508,10 +7985,7 @@ static void SerializeGltfCamera(const Camera &camera, detail::json &o) { // ??? } - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - SerializeExtensionMap(camera.extensions, o); + SerializeExtrasAndExtensions(camera, o); } static void SerializeGltfScene(const Scene &scene, detail::json &o) { @@ -7520,10 +7994,39 @@ static void SerializeGltfScene(const Scene &scene, detail::json &o) { if (scene.name.size()) { SerializeStringProperty("name", scene.name, o); } - if (scene.extras.Type() != NULL_TYPE) { - SerializeValue("extras", scene.extras, 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); + } + } } - SerializeExtensionMap(scene.extensions, o); } static void SerializeGltfSkin(const Skin &skin, detail::json &o) { @@ -7541,6 +8044,8 @@ static void SerializeGltfSkin(const Skin &skin, detail::json &o) { if (skin.name.size()) { SerializeStringProperty("name", skin.name, o); } + + SerializeExtrasAndExtensions(skin, o); } static void SerializeGltfTexture(const Texture &texture, detail::json &o) { @@ -7553,10 +8058,7 @@ static void SerializeGltfTexture(const Texture &texture, detail::json &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); + SerializeExtrasAndExtensions(texture, o); } /// @@ -7654,6 +8156,16 @@ static void SerializeGltfModel(const Model *model, detail::json &o) { 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)); @@ -7671,6 +8183,15 @@ static void SerializeGltfModel(const Model *model, detail::json &o) { 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)); @@ -7724,8 +8245,8 @@ static void SerializeGltfModel(const Model *model, detail::json &o) { detail::JsonAddMember(o, "cameras", std::move(cameras)); } - // EXTENSIONS - SerializeExtensionMap(model->extensions, o); + // EXTRAS & EXTENSIONS + SerializeExtrasAndExtensions(*model, o); auto extensionsUsed = model->extensionsUsed; @@ -7749,7 +8270,8 @@ static void SerializeGltfModel(const Model *model, detail::json &o) { } } - detail::JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn)); + detail::JsonAddMember(ext_j, "KHR_lights_punctual", + std::move(khr_lights_cmn)); detail::JsonAddMember(o, "extensions", std::move(ext_j)); @@ -7767,20 +8289,60 @@ static void SerializeGltfModel(const Model *model, detail::json &o) { } } + // 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"); + } + } + } + // 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; + return stream.good(); } static bool WriteGltfFile(const std::string &output, @@ -7863,8 +8425,8 @@ static bool WriteBinaryGltfStream(std::ostream &stream, } } - // TODO: Check error on stream.write - return true; + stream.flush(); + return stream.good(); } static bool WriteBinaryGltfFile(const std::string &output, @@ -7920,7 +8482,7 @@ bool TinyGLTF::WriteGltfSceneToStream(const Model *model, std::ostream &stream, for (unsigned int i = 0; i < model->images.size(); ++i) { detail::json image; - std::string dummystring = ""; + 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 @@ -7937,9 +8499,11 @@ bool TinyGLTF::WriteGltfSceneToStream(const Model *model, std::ostream &stream, } if (writeBinary) { - return WriteBinaryGltfStream(stream, detail::JsonToString(output), binBuffer); + return WriteBinaryGltfStream(stream, detail::JsonToString(output), + binBuffer); } else { - return WriteGltfStream(stream, detail::JsonToString(output, prettyPrint ? 2 : -1)); + return WriteGltfStream(stream, + detail::JsonToString(output, prettyPrint ? 2 : -1)); } } @@ -8043,9 +8607,11 @@ bool TinyGLTF::WriteGltfSceneToFile(const Model *model, } if (writeBinary) { - return WriteBinaryGltfFile(filename, detail::JsonToString(output), binBuffer); + return WriteBinaryGltfFile(filename, detail::JsonToString(output), + binBuffer); } else { - return WriteGltfFile(filename, detail::JsonToString(output, (prettyPrint ? 2 : -1))); + return WriteGltfFile(filename, + detail::JsonToString(output, (prettyPrint ? 2 : -1))); } } diff --git a/release/license/THIRD-PARTY-LICENSES.txt b/release/license/THIRD-PARTY-LICENSES.txt index 337807f80eb..ac8174c1712 100644 --- a/release/license/THIRD-PARTY-LICENSES.txt +++ b/release/license/THIRD-PARTY-LICENSES.txt @@ -3508,7 +3508,7 @@ Jonathan Shewchuk and Leonidas Guibas. All rights reserved. Copyright (c) 2017 Thibaut Goetghebuer-Planchon ** sse2neon; version 1.6.0 -- https://github.com/DLTcollab/sse2neon Copyright sse2neon contributors -** TinyGLTF; version 2.5.0 -- https://github.com/syoyo/tinygltf +** TinyGLTF; version 2.8.21 -- https://github.com/syoyo/tinygltf Copyright (c) 2017 Syoyo Fujita, AurĂ©lien Chatelain and many contributors ** Wayland protocols; version 1.31 -- https://gitlab.freedesktop.org/wayland/wayland-protocols