diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index 07785ee47..3682c9c40 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -261,6 +261,7 @@ void LightWindow::Create(EditorComponent* editor) params.extensions.push_back("dds"); params.extensions.push_back("png"); params.extensions.push_back("jpg"); + params.extensions.push_back("jpeg"); params.extensions.push_back("tga"); wiHelper::FileDialog(params, [this, light, i](std::string fileName) { wiEvent::Subscribe_Once(SYSTEM_EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { diff --git a/Editor/MaterialWindow.cpp b/Editor/MaterialWindow.cpp index 9378798af..4a160499a 100644 --- a/Editor/MaterialWindow.cpp +++ b/Editor/MaterialWindow.cpp @@ -48,6 +48,7 @@ void MaterialWindow::Create(EditorComponent* editor) MaterialComponent* material = wiScene::GetScene().materials.GetComponent(entity); if (material != nullptr) material->SetUseSpecularGlossinessWorkflow(args.bValue); + SetEntity(entity); }); AddWidget(&specularGlossinessCheckBox); @@ -195,7 +196,7 @@ void MaterialWindow::Create(EditorComponent* editor) AddWidget(&roughnessSlider); reflectanceSlider.Create(0, 1, 0.5f, 1000, "Reflectance: "); - reflectanceSlider.SetTooltip("Adjust the overall surface reflectivity."); + reflectanceSlider.SetTooltip("Adjust the overall surface reflectivity.\nNote: this is not available in specular-glossiness workflow"); reflectanceSlider.SetSize(XMFLOAT2(wid, hei)); reflectanceSlider.SetPos(XMFLOAT2(x, y += step)); reflectanceSlider.OnSlide([&](wiEventArgs args) { @@ -206,7 +207,7 @@ void MaterialWindow::Create(EditorComponent* editor) AddWidget(&reflectanceSlider); metalnessSlider.Create(0, 1, 0.0f, 1000, "Metalness: "); - metalnessSlider.SetTooltip("The more metal-like the surface is, the more the its color will contribute to the reflection color."); + metalnessSlider.SetTooltip("The more metal-like the surface is, the more the its color will contribute to the reflection color.\nNote: this is not available in specular-glossiness workflow"); metalnessSlider.SetSize(XMFLOAT2(wid, hei)); metalnessSlider.SetPos(XMFLOAT2(x, y += step)); metalnessSlider.OnSlide([&](wiEventArgs args) { @@ -512,7 +513,7 @@ void MaterialWindow::Create(EditorComponent* editor) texture_surface_Button.Create("SurfaceMap"); texture_surface_Button.SetText(""); - texture_surface_Button.SetTooltip("Load the surface property texture: R: Occlusion, G: Roughness, B: Metalness, A: Reflectance"); + texture_surface_Button.SetTooltip("Load the surface property texture.\nDefault workflow: R: Occlusion, G: Roughness, B: Metalness, A: Reflectance\nSpecular-glossiness workflow: RGB: Specular color (f0), A: smoothness"); texture_surface_Button.SetPos(XMFLOAT2(x + 122, y)); texture_surface_Button.SetSize(XMFLOAT2(260, 20)); texture_surface_Button.OnClick([&](wiEventArgs args) { @@ -751,6 +752,7 @@ void MaterialWindow::Create(EditorComponent* editor) colorComboBox.SetSize(XMFLOAT2(120, hei)); colorComboBox.SetPos(XMFLOAT2(x + 150, y += step)); colorComboBox.AddItem("Base color"); + colorComboBox.AddItem("Specular color"); colorComboBox.AddItem("Emissive color"); colorComboBox.AddItem("Subsurface color"); colorComboBox.SetTooltip("Choose the destination data of the color picker."); @@ -773,12 +775,15 @@ void MaterialWindow::Create(EditorComponent* editor) material->SetBaseColor(args.color.toFloat4()); break; case 1: + material->SetSpecularColor(args.color.toFloat4()); + break; + case 2: { XMFLOAT3 col = args.color.toFloat3(); material->SetEmissiveColor(XMFLOAT4(col.x, col.y, col.z, material->GetEmissiveStrength())); } break; - case 2: + case 3: material->SetSubsurfaceScatteringColor(args.color.toFloat3()); break; } @@ -866,9 +871,12 @@ void MaterialWindow::SetEntity(Entity entity) colorPicker.SetPickColor(wiColor::fromFloat4(material->baseColor)); break; case 1: - colorPicker.SetPickColor(wiColor::fromFloat3(XMFLOAT3(material->emissiveColor.x, material->emissiveColor.y, material->emissiveColor.z))); + colorPicker.SetPickColor(wiColor::fromFloat4(material->specularColor)); break; case 2: + colorPicker.SetPickColor(wiColor::fromFloat3(XMFLOAT3(material->emissiveColor.x, material->emissiveColor.y, material->emissiveColor.z))); + break; + case 3: colorPicker.SetPickColor(wiColor::fromFloat3(XMFLOAT3(material->subsurfaceScattering.x, material->subsurfaceScattering.y, material->subsurfaceScattering.z))); break; } @@ -891,6 +899,12 @@ void MaterialWindow::SetEntity(Entity entity) } shadingRateComboBox.SetEnabled(wiRenderer::GetDevice()->CheckCapability(GRAPHICSDEVICE_CAPABILITY_VARIABLE_RATE_SHADING)); + + if (material->IsUsingSpecularGlossinessWorkflow()) + { + reflectanceSlider.SetEnabled(false); + metalnessSlider.SetEnabled(false); + } } else { diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 632ba17c7..b9da11c4a 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -498,6 +498,7 @@ void MeshWindow::Create(EditorComponent* editor) params.extensions.push_back("dds"); params.extensions.push_back("png"); params.extensions.push_back("jpg"); + params.extensions.push_back("jpeg"); params.extensions.push_back("tga"); wiHelper::FileDialog(params, [=](std::string fileName) { wiEvent::Subscribe_Once(SYSTEM_EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { diff --git a/Editor/ModelImporter_GLTF.cpp b/Editor/ModelImporter_GLTF.cpp index f602a288b..a201be0da 100644 --- a/Editor/ModelImporter_GLTF.cpp +++ b/Editor/ModelImporter_GLTF.cpp @@ -549,14 +549,14 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) material.baseColor.z = factor.ArrayLen() > 2 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get() : factor.Get(2).Get()) : 1.0f; material.baseColor.w = factor.ArrayLen() > 3 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get() : factor.Get(3).Get()) : 1.0f; } - //if (specularGlossinessWorkflow->second.Has("specularFactor")) - //{ - // auto& factor = specularGlossinessWorkflow->second.Get("specularFactor"); - // material.baseColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get() : factor.Get(0).Get()) : 1.0f; - // material.baseColor.y = factor.ArrayLen() > 0 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get() : factor.Get(1).Get()) : 1.0f; - // material.baseColor.z = factor.ArrayLen() > 0 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get() : factor.Get(2).Get()) : 1.0f; - // material.baseColor.w = factor.ArrayLen() > 0 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get() : factor.Get(3).Get()) : 1.0f; - //} + if (specularGlossinessWorkflow->second.Has("specularFactor")) + { + auto& factor = specularGlossinessWorkflow->second.Get("specularFactor"); + material.specularColor.x = factor.ArrayLen() > 0 ? float(factor.Get(0).IsNumber() ? factor.Get(0).Get() : factor.Get(0).Get()) : 1.0f; + material.specularColor.y = factor.ArrayLen() > 0 ? float(factor.Get(1).IsNumber() ? factor.Get(1).Get() : factor.Get(1).Get()) : 1.0f; + material.specularColor.z = factor.ArrayLen() > 0 ? float(factor.Get(2).IsNumber() ? factor.Get(2).Get() : factor.Get(2).Get()) : 1.0f; + material.specularColor.w = factor.ArrayLen() > 0 ? float(factor.Get(3).IsNumber() ? factor.Get(3).Get() : factor.Get(3).Get()) : 1.0f; + } if (specularGlossinessWorkflow->second.Has("glossinessFactor")) { auto& factor = specularGlossinessWorkflow->second.Get("glossinessFactor"); @@ -605,6 +605,7 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) mesh.subsets.back().indexCount = (uint32_t)indexCount; mesh.subsets.back().materialID = scene.materials.GetEntity(max(0, prim.material)); + MaterialComponent* material = scene.materials.GetComponent(mesh.subsets.back().materialID); uint32_t vertexOffset = (uint32_t)mesh.vertex_positions.size(); @@ -774,6 +775,10 @@ void ImportModel_GLTF(const std::string& fileName, Scene& scene) } else if (!attr_name.compare("COLOR_0")) { + if(material != nullptr) + { + material->SetUseVertexColors(true); + } mesh.vertex_colors.resize(vertexOffset + vertexCount); assert(stride == 16); for (size_t i = 0; i < vertexCount; ++i) diff --git a/Editor/PostprocessWindow.cpp b/Editor/PostprocessWindow.cpp index 5b7b6b754..4eeaf0914 100644 --- a/Editor/PostprocessWindow.cpp +++ b/Editor/PostprocessWindow.cpp @@ -290,6 +290,7 @@ void PostprocessWindow::Create(EditorComponent* editor) params.extensions.push_back("dds"); params.extensions.push_back("png"); params.extensions.push_back("jpg"); + params.extensions.push_back("jpeg"); params.extensions.push_back("tga"); wiHelper::FileDialog(params, [=](std::string fileName) { wiEvent::Subscribe_Once(SYSTEM_EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { diff --git a/Editor/RendererWindow.cpp b/Editor/RendererWindow.cpp index e12de6acc..f13cf81be 100644 --- a/Editor/RendererWindow.cpp +++ b/Editor/RendererWindow.cpp @@ -155,7 +155,7 @@ void RendererWindow::Create(EditorComponent* editor) AddWidget(&wireFrameCheckBox); variableRateShadingClassificationCheckBox.Create("VRS Classification: "); - variableRateShadingClassificationCheckBox.SetTooltip("Enable classification of variable rate shading on the screen. Less important parts will be shaded with lesser resolution.\nDX12 only and requires Tier1 hardware support for variable shading rate"); + variableRateShadingClassificationCheckBox.SetTooltip("Enable classification of variable rate shading on the screen. Less important parts will be shaded with lesser resolution.\nRequires Tier2 support for variable shading rate"); variableRateShadingClassificationCheckBox.SetPos(XMFLOAT2(x, y += step)); variableRateShadingClassificationCheckBox.SetSize(XMFLOAT2(itemheight, itemheight)); variableRateShadingClassificationCheckBox.OnClick([](wiEventArgs args) { diff --git a/Editor/tiny_gltf.h b/Editor/tiny_gltf.h index c87f4ee64..87e3fb80d 100644 --- a/Editor/tiny_gltf.h +++ b/Editor/tiny_gltf.h @@ -4,7 +4,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 - 2019 Syoyo Fujita, AurĂ©lien Chatelain and many +// Copyright (c) 2015 - 2020 Syoyo Fujita, AurĂ©lien Chatelain and many // contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,6 +26,17 @@ // THE SOFTWARE. // Version: +// - v2.5.0 Add SetPreserveImageChannels() option to load image data as is. +// - v2.4.3 Fix null object output when when material has all default parameters. +// - v2.4.2 Decode percent-encoded URI. +// - v2.4.1 Fix some glTF object class does not have `extensions` and/or +// `extras` property. +// - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone). +// - v2.3.1 Set default value of minFilter and magFilter in Sampler to -1. +// - v2.3.0 Modified Material representation according to glTF 2.0 schema +// (and introduced TextureInfo class) +// Change the behavior of `Value::IsNumber`. It return true either the +// value is int or real. // - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks // to @Ybalrid) // - v2.1.0 Add draco compression. @@ -43,19 +54,42 @@ #include #include +#include // std::fabs #include #include #include +#include #include #include #include +#ifndef TINYGLTF_USE_CPP14 +#include +#endif + #ifdef __ANDROID__ #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS #include #endif #endif +#ifdef __GNUC__ +#if (__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ <= 8)) +#define TINYGLTF_NOEXCEPT +#else +#define TINYGLTF_NOEXCEPT noexcept +#endif +#else +#define TINYGLTF_NOEXCEPT noexcept +#endif + +#define DEFAULT_METHODS(x) \ + ~x() = default; \ + x(const x &) = default; \ + x(x &&) TINYGLTF_NOEXCEPT = default; \ + x &operator=(const x &) = default; \ + x &operator=(x &&) TINYGLTF_NOEXCEPT = default; + namespace tinygltf { #define TINYGLTF_MODE_POINTS (0) @@ -86,7 +120,7 @@ namespace tinygltf { #define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071) #define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648) - // Redeclarations of the above for technique.parameters. +// Redeclarations of the above for technique.parameters. #define TINYGLTF_PARAMETER_TYPE_BYTE (5120) #define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121) #define TINYGLTF_PARAMETER_TYPE_SHORT (5122) @@ -151,197 +185,219 @@ namespace tinygltf { #ifdef __ANDROID__ #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - AAssetManager *asset_manager = nullptr; +AAssetManager *asset_manager = nullptr; #endif #endif - typedef enum { - NULL_TYPE = 0, - NUMBER_TYPE = 1, - INT_TYPE = 2, - BOOL_TYPE = 3, - STRING_TYPE = 4, - ARRAY_TYPE = 5, - BINARY_TYPE = 6, - OBJECT_TYPE = 7 - } Type; +typedef enum { + NULL_TYPE = 0, + REAL_TYPE = 1, + INT_TYPE = 2, + BOOL_TYPE = 3, + STRING_TYPE = 4, + ARRAY_TYPE = 5, + BINARY_TYPE = 6, + OBJECT_TYPE = 7 +} Type; - static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { - if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { - return 1; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { - return 1; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { - return 2; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - return 2; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { - return 4; - } - else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { - return 8; - } - else { - // Unknown componenty type - return -1; - } - } +static inline int32_t GetComponentSizeInBytes(uint32_t componentType) { + if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + return 1; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + return 2; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return 4; + } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return 8; + } else { + // Unknown componenty type + return -1; + } +} - static inline int32_t GetTypeSizeInBytes(uint32_t ty) { - if (ty == TINYGLTF_TYPE_SCALAR) { - return 1; - } - else if (ty == TINYGLTF_TYPE_VEC2) { - return 2; - } - else if (ty == TINYGLTF_TYPE_VEC3) { - return 3; - } - else if (ty == TINYGLTF_TYPE_VEC4) { - return 4; - } - else if (ty == TINYGLTF_TYPE_MAT2) { - return 4; - } - else if (ty == TINYGLTF_TYPE_MAT3) { - return 9; - } - else if (ty == TINYGLTF_TYPE_MAT4) { - return 16; - } - else { - // Unknown componenty type - return -1; - } - } +static inline int32_t GetNumComponentsInType(uint32_t ty) { + if (ty == TINYGLTF_TYPE_SCALAR) { + return 1; + } else if (ty == TINYGLTF_TYPE_VEC2) { + return 2; + } else if (ty == TINYGLTF_TYPE_VEC3) { + return 3; + } else if (ty == TINYGLTF_TYPE_VEC4) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT2) { + return 4; + } else if (ty == TINYGLTF_TYPE_MAT3) { + return 9; + } else if (ty == TINYGLTF_TYPE_MAT4) { + return 16; + } else { + // Unknown componenty type + return -1; + } +} - bool IsDataURI(const std::string &in); - bool DecodeDataURI(std::vector *out, std::string &mime_type, - const std::string &in, size_t reqBytes, bool checkSize); +// TODO(syoyo): Move these functions to TinyGLTF class +bool IsDataURI(const std::string &in); +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize); #ifdef __clang__ #pragma clang diagnostic push - // Suppress warning for : static Value null_value - // https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang +// Suppress warning for : static Value null_value +// https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wpadded" #endif // Simple class to represent JSON object - class Value { - public: - typedef std::vector Array; - typedef std::map Object; +class Value { + public: + typedef std::vector Array; + typedef std::map Object; - Value() : type_(NULL_TYPE) {} + Value() + : type_(NULL_TYPE), + int_value_(0), + real_value_(0.0), + boolean_value_(false) {} - explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } - explicit Value(int i) : type_(INT_TYPE) { int_value_ = i; } - explicit Value(double n) : type_(NUMBER_TYPE) { number_value_ = n; } - explicit Value(const std::string &s) : type_(STRING_TYPE) { - string_value_ = s; - } - explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) { - binary_value_.resize(n); - memcpy(binary_value_.data(), p, n); - } - explicit Value(const Array &a) : type_(ARRAY_TYPE) { - array_value_ = Array(a); - } - explicit Value(const Object &o) : type_(OBJECT_TYPE) { - object_value_ = Object(o); - } + explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; } + explicit Value(int i) : type_(INT_TYPE) { + int_value_ = i; + real_value_ = i; + } + explicit Value(double n) : type_(REAL_TYPE) { real_value_ = n; } + explicit Value(const std::string &s) : type_(STRING_TYPE) { + string_value_ = s; + } + explicit Value(std::string &&s) + : type_(STRING_TYPE), string_value_(std::move(s)) {} + explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) { + binary_value_.resize(n); + memcpy(binary_value_.data(), p, n); + } + explicit Value(std::vector &&v) noexcept + : type_(BINARY_TYPE), + binary_value_(std::move(v)) {} + explicit Value(const Array &a) : type_(ARRAY_TYPE) { array_value_ = a; } + explicit Value(Array &&a) noexcept : type_(ARRAY_TYPE), + array_value_(std::move(a)) {} - char Type() const { return static_cast(type_); } + explicit Value(const Object &o) : type_(OBJECT_TYPE) { object_value_ = o; } + explicit Value(Object &&o) noexcept : type_(OBJECT_TYPE), + object_value_(std::move(o)) {} - bool IsBool() const { return (type_ == BOOL_TYPE); } + DEFAULT_METHODS(Value) - bool IsInt() const { return (type_ == INT_TYPE); } + char Type() const { return static_cast(type_); } - bool IsNumber() const { return (type_ == NUMBER_TYPE); } + bool IsBool() const { return (type_ == BOOL_TYPE); } - bool IsString() const { return (type_ == STRING_TYPE); } + bool IsInt() const { return (type_ == INT_TYPE); } - bool IsBinary() const { return (type_ == BINARY_TYPE); } + bool IsNumber() const { return (type_ == REAL_TYPE) || (type_ == INT_TYPE); } - bool IsArray() const { return (type_ == ARRAY_TYPE); } + bool IsReal() const { return (type_ == REAL_TYPE); } - bool IsObject() const { return (type_ == OBJECT_TYPE); } + bool IsString() const { return (type_ == STRING_TYPE); } - // Accessor - template - const T &Get() const; - template - T &Get(); + bool IsBinary() const { return (type_ == BINARY_TYPE); } - // Lookup value from an array - const Value &Get(int idx) const { - static Value null_value; - assert(IsArray()); - assert(idx >= 0); - return (static_cast(idx) < array_value_.size()) - ? array_value_[static_cast(idx)] - : null_value; - } + bool IsArray() const { return (type_ == ARRAY_TYPE); } - // Lookup value from a key-value pair - const Value &Get(const std::string &key) const { - static Value null_value; - assert(IsObject()); - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? it->second : null_value; - } + bool IsObject() const { return (type_ == OBJECT_TYPE); } - size_t ArrayLen() const { - if (!IsArray()) return 0; - return array_value_.size(); - } + // Use this function if you want to have number value as double. + double GetNumberAsDouble() const { + if (type_ == INT_TYPE) { + return double(int_value_); + } else { + return real_value_; + } + } - // Valid only for object type. - bool Has(const std::string &key) const { - if (!IsObject()) return false; - Object::const_iterator it = object_value_.find(key); - return (it != object_value_.end()) ? true : false; - } + // Use this function if you want to have number value as int. + // TODO(syoyo): Support int value larger than 32 bits + int GetNumberAsInt() const { + if (type_ == REAL_TYPE) { + return int(real_value_); + } else { + return int_value_; + } + } - // List keys - std::vector Keys() const { - std::vector keys; - if (!IsObject()) return keys; // empty + // Accessor + template + const T &Get() const; + template + T &Get(); - for (Object::const_iterator it = object_value_.begin(); - it != object_value_.end(); ++it) { - keys.push_back(it->first); - } + // Lookup value from an array + const Value &Get(int idx) const { + static Value null_value; + assert(IsArray()); + assert(idx >= 0); + return (static_cast(idx) < array_value_.size()) + ? array_value_[static_cast(idx)] + : null_value; + } - return keys; - } + // Lookup value from a key-value pair + const Value &Get(const std::string &key) const { + static Value null_value; + assert(IsObject()); + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? it->second : null_value; + } - size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } + size_t ArrayLen() const { + if (!IsArray()) return 0; + return array_value_.size(); + } - bool operator==(const tinygltf::Value &other) const; + // Valid only for object type. + bool Has(const std::string &key) const { + if (!IsObject()) return false; + Object::const_iterator it = object_value_.find(key); + return (it != object_value_.end()) ? true : false; + } - protected: - int type_; + // List keys + std::vector Keys() const { + std::vector keys; + if (!IsObject()) return keys; // empty - int int_value_; - double number_value_; - std::string string_value_; - std::vector binary_value_; - Array array_value_; - Object object_value_; - bool boolean_value_; - }; + for (Object::const_iterator it = object_value_.begin(); + it != object_value_.end(); ++it) { + keys.push_back(it->first); + } + + return keys; + } + + size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); } + + bool operator==(const tinygltf::Value &other) const; + + protected: + int type_ = NULL_TYPE; + + int int_value_ = 0; + double real_value_ = 0.0; + std::string string_value_; + std::vector binary_value_; + Array array_value_; + Object object_value_; + bool boolean_value_ = false; +}; #ifdef __clang__ #pragma clang diagnostic pop @@ -356,13 +412,13 @@ namespace tinygltf { inline ctype &Value::Get() { \ return var; \ } - TINYGLTF_VALUE_GET(bool, boolean_value_) - TINYGLTF_VALUE_GET(double, number_value_) - TINYGLTF_VALUE_GET(int, int_value_) - TINYGLTF_VALUE_GET(std::string, string_value_) - TINYGLTF_VALUE_GET(std::vector, binary_value_) - TINYGLTF_VALUE_GET(Value::Array, array_value_) - TINYGLTF_VALUE_GET(Value::Object, object_value_) +TINYGLTF_VALUE_GET(bool, boolean_value_) +TINYGLTF_VALUE_GET(double, real_value_) +TINYGLTF_VALUE_GET(int, int_value_) +TINYGLTF_VALUE_GET(std::string, string_value_) +TINYGLTF_VALUE_GET(std::vector, binary_value_) +TINYGLTF_VALUE_GET(Value::Array, array_value_) +TINYGLTF_VALUE_GET(Value::Object, object_value_) #undef TINYGLTF_VALUE_GET #ifdef __clang__ @@ -371,60 +427,90 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wpadded" #endif - /// Agregate object for representing a color - using ColorValue = std::array; +/// Agregate object for representing a color +using ColorValue = std::array; - struct Parameter { - bool bool_value = false; - bool has_number_value = false; - std::string string_value; - std::vector number_array; - std::map json_double_value; - double number_value = 0.0; - // context sensitive methods. depending the type of the Parameter you are - // accessing, these are either valid or not - // If this parameter represent a texture map in a material, will return the - // texture index +// === legacy interface ==== +// TODO(syoyo): Deprecate `Parameter` class. +struct Parameter { + bool bool_value = false; + bool has_number_value = false; + std::string string_value; + std::vector number_array; + std::map json_double_value; + double number_value = 0.0; - /// Return the index of a texture if this Parameter is a texture map. - /// Returned value is only valid if the parameter represent a texture from a - /// material - int TextureIndex() const { - const auto it = json_double_value.find("index"); - if (it != std::end(json_double_value)) { - return int(it->second); - } - return -1; - } + // context sensitive methods. depending the type of the Parameter you are + // accessing, these are either valid or not + // If this parameter represent a texture map in a material, will return the + // texture index - /// Return the index of a texture coordinate set if this Parameter is a - /// texture map. Returned value is only valid if the parameter represent a - /// texture from a material - int TextureTexCoord() const { - const auto it = json_double_value.find("texCoord"); - if (it != std::end(json_double_value)) { - return int(it->second); - } - return 0; - } + /// Return the index of a texture if this Parameter is a texture map. + /// Returned value is only valid if the parameter represent a texture from a + /// material + int TextureIndex() const { + const auto it = json_double_value.find("index"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + return -1; + } - /// Material factor, like the roughness or metalness of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - double Factor() const { return number_value; } + /// Return the index of a texture coordinate set if this Parameter is a + /// texture map. Returned value is only valid if the parameter represent a + /// texture from a material + int TextureTexCoord() const { + const auto it = json_double_value.find("texCoord"); + if (it != std::end(json_double_value)) { + return int(it->second); + } + // As per the spec, if texCoord is ommited, this parameter is 0 + return 0; + } - /// Return the color of a material - /// Returned value is only valid if the parameter represent a texture from a - /// material - ColorValue ColorFactor() const { - return { - {// this agregate intialize the std::array object, and uses C++11 RVO. - number_array[0], number_array[1], number_array[2], - (number_array.size() > 3 ? number_array[3] : 1.0)} }; - } + /// Return the scale of a texture if this Parameter is a normal texture map. + /// Returned value is only valid if the parameter represent a normal texture + /// from a material + double TextureScale() const { + const auto it = json_double_value.find("scale"); + if (it != std::end(json_double_value)) { + return it->second; + } + // As per the spec, if scale is ommited, this paramter is 1 + return 1; + } - bool operator==(const Parameter &) const; - }; + /// Return the strength of a texture if this Parameter is a an occlusion map. + /// Returned value is only valid if the parameter represent an occlusion map + /// from a material + double TextureStrength() const { + const auto it = json_double_value.find("strength"); + if (it != std::end(json_double_value)) { + return it->second; + } + // As per the spec, if strenghth is ommited, this parameter is 1 + return 1; + } + + /// Material factor, like the roughness or metalness of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + double Factor() const { return number_value; } + + /// Return the color of a material + /// Returned value is only valid if the parameter represent a texture from a + /// material + ColorValue ColorFactor() const { + return { + {// this agregate intialize the std::array object, and uses C++11 RVO. + number_array[0], number_array[1], number_array[2], + (number_array.size() > 3 ? number_array[3] : 1.0)}}; + } + + Parameter() = default; + DEFAULT_METHODS(Parameter) + bool operator==(const Parameter &) const; +}; #ifdef __clang__ #pragma clang diagnostic pop @@ -435,621 +521,937 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wpadded" #endif - typedef std::map ParameterMap; - typedef std::map ExtensionMap; - - struct AnimationChannel { - int sampler; // required - int target_node; // required (index of the node to target) - std::string target_path; // required in ["translation", "rotation", "scale", - // "weights"] - Value extras; - - AnimationChannel() : sampler(-1), target_node(-1) {} - bool operator==(const AnimationChannel &) const; - }; - - struct AnimationSampler { - int input; // required - int output; // required - std::string interpolation; // in ["LINEAR", "STEP", "CATMULLROMSPLINE", - // "CUBICSPLINE"], default "LINEAR" - Value extras; - - AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} - bool operator==(const AnimationSampler &) const; - }; - - struct Animation { - std::string name; - std::vector channels; - std::vector samplers; - Value extras; - - bool operator==(const Animation &) const; - }; - - struct Skin { - std::string name; - int inverseBindMatrices; // required here but not in the spec - int skeleton; // The index of the node used as a skeleton root - std::vector joints; // Indices of skeleton nodes - - Skin() { - inverseBindMatrices = -1; - skeleton = -1; - } - bool operator==(const Skin &) const; - }; - - struct Sampler { - std::string name; - int minFilter; // ["NEAREST", "LINEAR", "NEAREST_MIPMAP_LINEAR", - // "LINEAR_MIPMAP_NEAREST", "NEAREST_MIPMAP_LINEAR", - // "LINEAR_MIPMAP_LINEAR"] - int magFilter; // ["NEAREST", "LINEAR"] - int wrapS; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default - // "REPEAT" - int wrapT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", "REPEAT"], default - // "REPEAT" - int wrapR; // TinyGLTF extension - Value extras; - - Sampler() - : minFilter(TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR), - magFilter(TINYGLTF_TEXTURE_FILTER_LINEAR), - wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), - wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT), - wrapR(TINYGLTF_TEXTURE_WRAP_REPEAT) {} - bool operator==(const Sampler &) const; - }; - - struct Image { - std::string name; - int width; - int height; - int component; - int bits; // bit depth per channel. 8(byte), 16 or 32. - int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually - // UBYTE(bits = 8) or USHORT(bits = 16) - std::vector image; - int bufferView; // (required if no uri) - std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", - // "image/bmp", "image/gif"] - std::string uri; // (required if no mimeType) - Value extras; - ExtensionMap extensions; - - // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg - // compressed for "image/jpeg" mime) This feature is good if you use custom - // image loader function. (e.g. delayed decoding of images for faster glTF - // parsing) Default parser for Image does not provide as-is loading feature at - // the moment. (You can manipulate this by providing your own LoadImageData - // function) - bool as_is; - - Image() : as_is(false) { - bufferView = -1; - width = -1; - height = -1; - component = -1; - } - bool operator==(const Image &) const; - }; - - struct Texture { - std::string name; - - int sampler; - int source; - Value extras; - ExtensionMap extensions; - - Texture() : sampler(-1), source(-1) {} - bool operator==(const Texture &) const; - }; - - // Each extension should be stored in a ParameterMap. - // members not in the values could be included in the ParameterMap - // to keep a single material model - struct Material { - std::string name; - - ParameterMap values; // PBR metal/roughness workflow - ParameterMap additionalValues; // normal/occlusion/emissive values - - ExtensionMap extensions; - Value extras; - - bool operator==(const Material &) const; - }; - - struct BufferView { - std::string name; - int buffer; // Required - size_t byteOffset; // minimum 0, default 0 - size_t byteLength; // required, minimum 1 - size_t byteStride; // minimum 4, maximum 252 (multiple of 4), default 0 = - // understood to be tightly packed - int target; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] - Value extras; - bool dracoDecoded; // Flag indicating this has been draco decoded - - BufferView() : byteOffset(0), byteStride(0), dracoDecoded(false) {} - bool operator==(const BufferView &) const; - }; - - struct Accessor { - int bufferView; // optional in spec but required here since sparse accessor - // are not supported - std::string name; - size_t byteOffset; - bool normalized; // optinal. - int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** - size_t count; // required - int type; // (required) One of TINYGLTF_TYPE_*** .. - Value extras; - - std::vector minValues; // optional - std::vector maxValues; // optional - - struct { - int count; - bool isSparse; - struct { - int byteOffset; - int bufferView; - int componentType; // a TINYGLTF_COMPONENT_TYPE_ value - } indices; - struct { - int bufferView; - int byteOffset; - } values; - } sparse; - - /// - /// Utility function to compute byteStride for a given bufferView object. - /// Returns -1 upon invalid glTF value or parameter configuration. - /// - int ByteStride(const BufferView &bufferViewObject) const { - if (bufferViewObject.byteStride == 0) { - // Assume data is tightly packed. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } - - int typeSizeInBytes = GetTypeSizeInBytes(static_cast(type)); - if (typeSizeInBytes <= 0) { - return -1; - } - - return componentSizeInBytes * typeSizeInBytes; - } - else { - // Check if byteStride is a mulple of the size of the accessor's component - // type. - int componentSizeInBytes = - GetComponentSizeInBytes(static_cast(componentType)); - if (componentSizeInBytes <= 0) { - return -1; - } - - if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { - return -1; - } - return static_cast(bufferViewObject.byteStride); - } - - return 0; - } - - Accessor() { - bufferView = -1; - sparse.isSparse = false; - } - bool operator==(const tinygltf::Accessor &) const; - }; - - struct PerspectiveCamera { - double aspectRatio; // min > 0 - double yfov; // required. min > 0 - double zfar; // min > 0 - double znear; // required. min > 0 - - PerspectiveCamera() - : aspectRatio(0.0), - yfov(0.0), - zfar(0.0) // 0 = use infinite projecton matrix - , - znear(0.0) {} - bool operator==(const PerspectiveCamera &) const; - - ExtensionMap extensions; - Value extras; - }; - - struct OrthographicCamera { - double xmag; // required. must not be zero. - double ymag; // required. must not be zero. - double zfar; // required. `zfar` must be greater than `znear`. - double znear; // required - - OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} - bool operator==(const OrthographicCamera &) const; - - ExtensionMap extensions; - Value extras; - }; - - struct Camera { - std::string type; // required. "perspective" or "orthographic" - std::string name; - - PerspectiveCamera perspective; - OrthographicCamera orthographic; - - Camera() {} - bool operator==(const Camera &) const; - - ExtensionMap extensions; - Value extras; - }; - - struct Primitive { - std::map attributes; // (required) A dictionary object of - // integer, where each integer - // is the index of the accessor - // containing an attribute. - int material; // The index of the material to apply to this primitive - // when rendering. - int indices; // The index of the accessor that contains the indices. - int mode; // one of TINYGLTF_MODE_*** - std::vector > targets; // array of morph targets, - // where each target is a dict with attribues in ["POSITION, "NORMAL", - // "TANGENT"] pointing - // to their corresponding accessors - ExtensionMap extensions; - Value extras; - - Primitive() { - material = -1; - indices = -1; - } - bool operator==(const Primitive &) const; - }; - - struct Mesh { - std::string name; - std::vector primitives; - std::vector weights; // weights to be applied to the Morph Targets - std::vector > targets; - ExtensionMap extensions; - Value extras; - - bool operator==(const Mesh &) const; - }; - - class Node { - public: - Node() : camera(-1), skin(-1), mesh(-1) {} - - Node(const Node &rhs) { - camera = rhs.camera; - - name = rhs.name; - skin = rhs.skin; - mesh = rhs.mesh; - children = rhs.children; - rotation = rhs.rotation; - scale = rhs.scale; - translation = rhs.translation; - matrix = rhs.matrix; - weights = rhs.weights; - - extensions = rhs.extensions; - extras = rhs.extras; - } - ~Node() {} - bool operator==(const Node &) const; - - int camera; // the index of the camera referenced by this node - - std::string name; - int skin; - int mesh; - std::vector children; - std::vector rotation; // length must be 0 or 4 - std::vector scale; // length must be 0 or 3 - std::vector translation; // length must be 0 or 3 - std::vector matrix; // length must be 0 or 16 - std::vector weights; // The weights of the instantiated Morph Target - - ExtensionMap extensions; - Value extras; - }; - - struct Buffer { - std::string name; - std::vector data; - std::string - uri; // considered as required here but not in the spec (need to clarify) - Value extras; - - bool operator==(const Buffer &) const; - }; - - struct Asset { - std::string version; // required - std::string generator; - std::string minVersion; - std::string copyright; - ExtensionMap extensions; - Value extras; - - bool operator==(const Asset &) const; - }; - - struct Scene { - std::string name; - std::vector nodes; - - ExtensionMap extensions; - Value extras; - - bool operator==(const Scene &) const; - }; - - struct Light { - std::string name; - std::vector color; - std::string type; - - bool operator==(const Light &) const; - }; - - class Model { - public: - Model() {} - ~Model() {} - bool operator==(const Model &) const; - - std::vector accessors; - std::vector animations; - std::vector buffers; - std::vector bufferViews; - std::vector materials; - std::vector meshes; - std::vector nodes; - std::vector textures; - std::vector images; - std::vector skins; - std::vector samplers; - std::vector cameras; - std::vector scenes; - std::vector lights; - ExtensionMap extensions; - - int defaultScene; - std::vector extensionsUsed; - std::vector extensionsRequired; - - Asset asset; - - Value extras; - }; - - enum SectionCheck { - NO_REQUIRE = 0x00, - REQUIRE_SCENE = 0x01, - REQUIRE_SCENES = 0x02, - REQUIRE_NODES = 0x04, - REQUIRE_ACCESSORS = 0x08, - REQUIRE_BUFFERS = 0x10, - REQUIRE_BUFFER_VIEWS = 0x20, - REQUIRE_ALL = 0x3f - }; - - /// - /// LoadImageDataFunction type. Signature for custom image loading callbacks. - /// - typedef bool(*LoadImageDataFunction)(Image *, const int, std::string *, - std::string *, int, int, - const unsigned char *, int, void *); - - /// - /// WriteImageDataFunction type. Signature for custom image writing callbacks. - /// - typedef bool(*WriteImageDataFunction)(const std::string *, const std::string *, - Image *, bool, void *); +typedef std::map ParameterMap; +typedef std::map ExtensionMap; + +struct AnimationChannel { + int sampler; // required + int target_node; // required (index of the node to target) + std::string target_path; // required in ["translation", "rotation", "scale", + // "weights"] + Value extras; + ExtensionMap extensions; + ExtensionMap target_extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + std::string target_extensions_json_string; + + AnimationChannel() : sampler(-1), target_node(-1) {} + DEFAULT_METHODS(AnimationChannel) + bool operator==(const AnimationChannel &) const; +}; + +struct AnimationSampler { + int input; // required + int output; // required + std::string interpolation; // "LINEAR", "STEP","CUBICSPLINE" or user defined + // string. default "LINEAR" + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {} + DEFAULT_METHODS(AnimationSampler) + bool operator==(const AnimationSampler &) const; +}; + +struct Animation { + std::string name; + std::vector channels; + std::vector samplers; + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Animation() = default; + DEFAULT_METHODS(Animation) + bool operator==(const Animation &) const; +}; + +struct Skin { + std::string name; + int inverseBindMatrices; // required here but not in the spec + int skeleton; // The index of the node used as a skeleton root + std::vector joints; // Indices of skeleton nodes + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Skin() { + inverseBindMatrices = -1; + skeleton = -1; + } + DEFAULT_METHODS(Skin) + bool operator==(const Skin &) const; +}; + +struct Sampler { + std::string name; + // glTF 2.0 spec does not define default value for `minFilter` and + // `magFilter`. Set -1 in TinyGLTF(issue #186) + int minFilter = + -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR", + // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST", + // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"] + int magFilter = + -1; // optional. -1 = no filter defined. ["NEAREST", "LINEAR"] + int wrapS = + TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", + // "REPEAT"], default "REPEAT" + int wrapT = + TINYGLTF_TEXTURE_WRAP_REPEAT; // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT", + // "REPEAT"], default "REPEAT" + //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; // TinyGLTF extension. currently not used. + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Sampler() + : minFilter(-1), + magFilter(-1), + wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT), + wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT) {} + DEFAULT_METHODS(Sampler) + bool operator==(const Sampler &) const; +}; + +struct Image { + std::string name; + int width; + int height; + int component; + int bits; // bit depth per channel. 8(byte), 16 or 32. + int pixel_type; // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually + // UBYTE(bits = 8) or USHORT(bits = 16) + std::vector image; + int bufferView; // (required if no uri) + std::string mimeType; // (required if no uri) ["image/jpeg", "image/png", + // "image/bmp", "image/gif"] + std::string uri; // (required if no mimeType) uri is not decoded(e.g. + // whitespace may be represented as %20) + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg + // compressed for "image/jpeg" mime) This feature is good if you use custom + // image loader function. (e.g. delayed decoding of images for faster glTF + // parsing) Default parser for Image does not provide as-is loading feature at + // the moment. (You can manipulate this by providing your own LoadImageData + // function) + bool as_is; + + Image() : as_is(false) { + bufferView = -1; + width = -1; + height = -1; + component = -1; + bits = -1; + pixel_type = -1; + } + DEFAULT_METHODS(Image) + + bool operator==(const Image &) const; +}; + +struct Texture { + std::string name; + + int sampler; + int source; + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Texture() : sampler(-1), source(-1) {} + DEFAULT_METHODS(Texture) + + bool operator==(const Texture &) const; +}; + +struct TextureInfo { + int index = -1; // required. + int texCoord; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + TextureInfo() : index(-1), texCoord(0) {} + DEFAULT_METHODS(TextureInfo) + bool operator==(const TextureInfo &) const; +}; + +struct NormalTextureInfo { + int index = -1; // required + int texCoord; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + double scale; // scaledNormal = normalize(( + // * 2.0 - 1.0) * vec3(, , 1.0)) + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {} + DEFAULT_METHODS(NormalTextureInfo) + bool operator==(const NormalTextureInfo &) const; +}; + +struct OcclusionTextureInfo { + int index = -1; // required + int texCoord; // The set index of texture's TEXCOORD attribute used for + // texture coordinate mapping. + double strength; // occludedColor = lerp(color, color * , ) + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + OcclusionTextureInfo() : index(-1), texCoord(0), strength(1.0) {} + DEFAULT_METHODS(OcclusionTextureInfo) + bool operator==(const OcclusionTextureInfo &) const; +}; + +// pbrMetallicRoughness class defined in glTF 2.0 spec. +struct PbrMetallicRoughness { + std::vector baseColorFactor; // len = 4. default [1,1,1,1] + TextureInfo baseColorTexture; + double metallicFactor; // default 1 + double roughnessFactor; // default 1 + TextureInfo metallicRoughnessTexture; + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + PbrMetallicRoughness() + : baseColorFactor(std::vector{1.0, 1.0, 1.0, 1.0}), + metallicFactor(1.0), + roughnessFactor(1.0) {} + DEFAULT_METHODS(PbrMetallicRoughness) + bool operator==(const PbrMetallicRoughness &) const; +}; + +// Each extension should be stored in a ParameterMap. +// members not in the values could be included in the ParameterMap +// to keep a single material model +struct Material { + std::string name; + + std::vector emissiveFactor; // length 3. default [0, 0, 0] + std::string alphaMode; // default "OPAQUE" + double alphaCutoff; // default 0.5 + bool doubleSided; // default false; + + PbrMetallicRoughness pbrMetallicRoughness; + + NormalTextureInfo normalTexture; + OcclusionTextureInfo occlusionTexture; + TextureInfo emissiveTexture; + + // For backward compatibility + // TODO(syoyo): Remove `values` and `additionalValues` in the next release. + ParameterMap values; + ParameterMap additionalValues; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {} + DEFAULT_METHODS(Material) + + bool operator==(const Material &) const; +}; + +struct BufferView { + std::string name; + int buffer{-1}; // Required + size_t byteOffset{0}; // minimum 0, default 0 + size_t byteLength{0}; // required, minimum 1. 0 = invalid + size_t byteStride{0}; // minimum 4, maximum 252 (multiple of 4), default 0 = + // understood to be tightly packed + int target{0}; // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices + // or atttribs. Could be 0 for other data + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + bool dracoDecoded{false}; // Flag indicating this has been draco decoded + + BufferView() + : buffer(-1), + byteOffset(0), + byteLength(0), + byteStride(0), + target(0), + dracoDecoded(false) {} + DEFAULT_METHODS(BufferView) + bool operator==(const BufferView &) const; +}; + +struct Accessor { + int bufferView; // optional in spec but required here since sparse accessor + // are not supported + std::string name; + size_t byteOffset; + bool normalized; // optional. + int componentType; // (required) One of TINYGLTF_COMPONENT_TYPE_*** + size_t count; // required + int type; // (required) One of TINYGLTF_TYPE_*** .. + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + std::vector minValues; // optional + std::vector maxValues; // optional + + struct { + int count; + bool isSparse; + struct { + int byteOffset; + int bufferView; + int componentType; // a TINYGLTF_COMPONENT_TYPE_ value + } indices; + struct { + int bufferView; + int byteOffset; + } values; + } sparse; + + /// + /// Utility function to compute byteStride for a given bufferView object. + /// Returns -1 upon invalid glTF value or parameter configuration. + /// + int ByteStride(const BufferView &bufferViewObject) const { + if (bufferViewObject.byteStride == 0) { + // Assume data is tightly packed. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + int numComponents = GetNumComponentsInType(static_cast(type)); + if (numComponents <= 0) { + return -1; + } + + return componentSizeInBytes * numComponents; + } else { + // Check if byteStride is a mulple of the size of the accessor's component + // type. + int componentSizeInBytes = + GetComponentSizeInBytes(static_cast(componentType)); + if (componentSizeInBytes <= 0) { + return -1; + } + + if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) { + return -1; + } + return static_cast(bufferViewObject.byteStride); + } + + // unreachable return 0; + } + + Accessor() + : bufferView(-1), + byteOffset(0), + normalized(false), + componentType(-1), + count(0), + type(-1) { + sparse.isSparse = false; + } + DEFAULT_METHODS(Accessor) + bool operator==(const tinygltf::Accessor &) const; +}; + +struct PerspectiveCamera { + double aspectRatio; // min > 0 + double yfov; // required. min > 0 + double zfar; // min > 0 + double znear; // required. min > 0 + + PerspectiveCamera() + : aspectRatio(0.0), + yfov(0.0), + zfar(0.0) // 0 = use infinite projecton matrix + , + znear(0.0) {} + DEFAULT_METHODS(PerspectiveCamera) + bool operator==(const PerspectiveCamera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct OrthographicCamera { + double xmag; // required. must not be zero. + double ymag; // required. must not be zero. + double zfar; // required. `zfar` must be greater than `znear`. + double znear; // required + + OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {} + DEFAULT_METHODS(OrthographicCamera) + bool operator==(const OrthographicCamera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Camera { + std::string type; // required. "perspective" or "orthographic" + std::string name; + + PerspectiveCamera perspective; + OrthographicCamera orthographic; + + Camera() {} + DEFAULT_METHODS(Camera) + bool operator==(const Camera &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Primitive { + std::map attributes; // (required) A dictionary object of + // integer, where each integer + // is the index of the accessor + // containing an attribute. + int material; // The index of the material to apply to this primitive + // when rendering. + int indices; // The index of the accessor that contains the indices. + int mode; // one of TINYGLTF_MODE_*** + std::vector > targets; // array of morph targets, + // where each target is a dict with attribues in ["POSITION, "NORMAL", + // "TANGENT"] pointing + // to their corresponding accessors + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Primitive() { + material = -1; + indices = -1; + mode = -1; + } + DEFAULT_METHODS(Primitive) + bool operator==(const Primitive &) const; +}; + +struct Mesh { + std::string name; + std::vector primitives; + std::vector weights; // weights to be applied to the Morph Targets + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Mesh() = default; + DEFAULT_METHODS(Mesh) + bool operator==(const Mesh &) const; +}; + +class Node { + public: + Node() : camera(-1), skin(-1), mesh(-1) {} + + DEFAULT_METHODS(Node) + + bool operator==(const Node &) const; + + int camera; // the index of the camera referenced by this node + + std::string name; + int skin; + int mesh; + std::vector children; + std::vector rotation; // length must be 0 or 4 + std::vector scale; // length must be 0 or 3 + std::vector translation; // length must be 0 or 3 + std::vector matrix; // length must be 0 or 16 + std::vector weights; // The weights of the instantiated Morph Target + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Buffer { + std::string name; + std::vector data; + std::string + uri; // considered as required here but not in the spec (need to clarify) + // uri is not decoded(e.g. whitespace may be represented as %20) + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Buffer() = default; + DEFAULT_METHODS(Buffer) + bool operator==(const Buffer &) const; +}; + +struct Asset { + std::string version; // required + std::string generator; + std::string minVersion; + std::string copyright; + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Asset() = default; + DEFAULT_METHODS(Asset) + bool operator==(const Asset &) const; +}; + +struct Scene { + std::string name; + std::vector nodes; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; + + Scene() = default; + DEFAULT_METHODS(Scene) + bool operator==(const Scene &) const; +}; + +struct SpotLight { + double innerConeAngle; + double outerConeAngle; + + SpotLight() : innerConeAngle(0.0), outerConeAngle(0.7853981634) {} + DEFAULT_METHODS(SpotLight) + bool operator==(const SpotLight &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +struct Light { + std::string name; + std::vector color; + double intensity{1.0}; + std::string type; + double range{0.0}; // 0.0 = inifinite + SpotLight spot; + + Light() : intensity(1.0), range(0.0) {} + DEFAULT_METHODS(Light) + + bool operator==(const Light &) const; + + ExtensionMap extensions; + Value extras; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +class Model { + public: + Model() = default; + DEFAULT_METHODS(Model) + + bool operator==(const Model &) const; + + std::vector accessors; + std::vector animations; + std::vector buffers; + std::vector bufferViews; + std::vector materials; + std::vector meshes; + std::vector nodes; + std::vector textures; + std::vector images; + std::vector skins; + std::vector samplers; + std::vector cameras; + std::vector scenes; + std::vector lights; + + int defaultScene = -1; + std::vector extensionsUsed; + std::vector extensionsRequired; + + Asset asset; + + Value extras; + ExtensionMap extensions; + + // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled. + std::string extras_json_string; + std::string extensions_json_string; +}; + +enum SectionCheck { + NO_REQUIRE = 0x00, + REQUIRE_VERSION = 0x01, + REQUIRE_SCENE = 0x02, + REQUIRE_SCENES = 0x04, + REQUIRE_NODES = 0x08, + REQUIRE_ACCESSORS = 0x10, + REQUIRE_BUFFERS = 0x20, + REQUIRE_BUFFER_VIEWS = 0x40, + REQUIRE_ALL = 0x7f +}; + +/// +/// LoadImageDataFunction type. Signature for custom image loading callbacks. +/// +typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *, + std::string *, int, int, + const unsigned char *, int, void *user_pointer); + +/// +/// WriteImageDataFunction type. Signature for custom image writing callbacks. +/// +typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *, + Image *, bool, void *); #ifndef TINYGLTF_NO_STB_IMAGE - // Declaration of default image loader callback - bool LoadImageData(Image *image, const int image_idx, std::string *err, - std::string *warn, int req_width, int req_height, - const unsigned char *bytes, int size, void *); +// Declaration of default image loader callback +bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *); #endif #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - // Declaration of default image writer callback - bool WriteImageData(const std::string *basepath, const std::string *filename, - Image *image, bool embedImages, void *); +// Declaration of default image writer callback +bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *); #endif - /// - /// FilExistsFunction type. Signature for custom filesystem callbacks. - /// - typedef bool(*FileExistsFunction)(const std::string &abs_filename, void *); +/// +/// FilExistsFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*FileExistsFunction)(const std::string &abs_filename, void *); - /// - /// ExpandFilePathFunction type. Signature for custom filesystem callbacks. - /// - typedef std::string(*ExpandFilePathFunction)(const std::string &, void *); +/// +/// ExpandFilePathFunction type. Signature for custom filesystem callbacks. +/// +typedef std::string (*ExpandFilePathFunction)(const std::string &, void *); - /// - /// ReadWholeFileFunction type. Signature for custom filesystem callbacks. - /// - typedef bool(*ReadWholeFileFunction)(std::vector *, - std::string *, const std::string &, - void *); +/// +/// ReadWholeFileFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*ReadWholeFileFunction)(std::vector *, + std::string *, const std::string &, + void *); - /// - /// WriteWholeFileFunction type. Signature for custom filesystem callbacks. - /// - typedef bool(*WriteWholeFileFunction)(std::string *, const std::string &, - const std::vector &, - void *); +/// +/// WriteWholeFileFunction type. Signature for custom filesystem callbacks. +/// +typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &, + const std::vector &, + void *); - /// - /// A structure containing all required filesystem callbacks and a pointer to - /// their user data. - /// - struct FsCallbacks { - FileExistsFunction FileExists; - ExpandFilePathFunction ExpandFilePath; - ReadWholeFileFunction ReadWholeFile; - WriteWholeFileFunction WriteWholeFile; +/// +/// A structure containing all required filesystem callbacks and a pointer to +/// their user data. +/// +struct FsCallbacks { + FileExistsFunction FileExists; + ExpandFilePathFunction ExpandFilePath; + ReadWholeFileFunction ReadWholeFile; + WriteWholeFileFunction WriteWholeFile; - void *user_data; // An argument that is passed to all fs callbacks - }; + void *user_data; // An argument that is passed to all fs callbacks +}; #ifndef TINYGLTF_NO_FS - // Declaration of default filesystem callbacks +// Declaration of default filesystem callbacks - bool FileExists(const std::string &abs_filename, void *); +bool FileExists(const std::string &abs_filename, void *); - std::string ExpandFilePath(const std::string &filepath, void *); +/// +/// Expand file path(e.g. `~` to home directory on posix, `%APPDATA%` to +/// `C:\\Users\\tinygltf\\AppData`) +/// +/// @param[in] filepath File path string. Assume UTF-8 +/// @param[in] userdata User data. Set to `nullptr` if you don't need it. +/// +std::string ExpandFilePath(const std::string &filepath, void *userdata); - bool ReadWholeFile(std::vector *out, std::string *err, - const std::string &filepath, void *); +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *); - bool WriteWholeFile(std::string *err, const std::string &filepath, - const std::vector &contents, void *); +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *); #endif - class TinyGLTF { - public: +/// +/// glTF Parser/Serialier context. +/// +class TinyGLTF { + public: #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++98-compat" #endif - TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} + TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} #ifdef __clang__ #pragma clang diagnostic pop #endif - ~TinyGLTF() {} + ~TinyGLTF() {} - /// - /// Loads glTF ASCII asset from a file. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF ASCII asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF ASCII asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, - const char *str, const unsigned int length, - const std::string &base_dir, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF ASCII asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF binary asset from a file. - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, - const std::string &filename, - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF binary asset from a file. + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn, + const std::string &filename, + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Loads glTF binary asset from memory. - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts. - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, - const unsigned char *bytes, - const unsigned int length, - const std::string &base_dir = "", - unsigned int check_sections = REQUIRE_ALL); + /// + /// Loads glTF binary asset from memory. + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts. + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, + const unsigned char *bytes, + const unsigned int length, + const std::string &base_dir = "", + unsigned int check_sections = REQUIRE_VERSION); - /// - /// Write glTF to file. - /// - bool WriteGltfSceneToFile(Model *model, const std::string &filename, - bool embedImages, bool embedBuffers, - bool prettyPrint, bool writeBinary); + /// + /// Write glTF to stream, buffers and images will be embeded + /// + bool WriteGltfSceneToStream(Model *model, std::ostream &stream, + bool prettyPrint, bool writeBinary); - /// - /// Set callback to use for loading image data - /// - void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); + /// + /// Write glTF to file. + /// + bool WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages, bool embedBuffers, + bool prettyPrint, bool writeBinary); - /// - /// Set callback to use for writing image data - /// - void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); + /// + /// Set callback to use for loading image data + /// + void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data); - /// - /// Set callbacks to use for filesystem (fs) access and their user data - /// - void SetFsCallbacks(FsCallbacks callbacks); + /// + /// Unset(remove) callback of loading image data + /// + void RemoveImageLoader(); - private: - /// - /// Loads glTF asset from string(memory). - /// `length` = strlen(str); - /// Set warning message to `warn` for example it fails to load asserts - /// Returns false and set error string to `err` if there's an error. - /// - bool LoadFromString(Model *model, std::string *err, std::string *warn, - const char *str, const unsigned int length, - const std::string &base_dir, unsigned int check_sections); + /// + /// Set callback to use for writing image data + /// + void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data); - const unsigned char *bin_data_; - size_t bin_size_; - bool is_binary_; + /// + /// Set callbacks to use for filesystem (fs) access and their user data + /// + void SetFsCallbacks(FsCallbacks callbacks); - FsCallbacks fs = { - #ifndef TINYGLTF_NO_FS - &tinygltf::FileExists, &tinygltf::ExpandFilePath, - &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, + /// + /// Set serializing default values(default = false). + /// When true, default values are force serialized to .glTF. + /// This may be helpfull if you want to serialize a full description of glTF + /// data. + /// + /// TODO(LTE): Supply parsing option as function arguments to + /// `LoadASCIIFromFile()` and others, not by a class method + /// + void SetSerializeDefaultValues(const bool enabled) { + serialize_default_values_ = enabled; + } - nullptr // Fs callback user data - #else - nullptr, nullptr, nullptr, nullptr, + bool GetSerializeDefaultValues() const { return serialize_default_values_; } - nullptr // Fs callback user data - #endif - }; + /// + /// Store original JSON string for `extras` and `extensions`. + /// This feature will be useful when the user want to reconstruct custom data + /// structure from JSON string. + /// + void SetStoreOriginalJSONForExtrasAndExtensions(const bool enabled) { + store_original_json_for_extras_and_extensions_ = enabled; + } - LoadImageDataFunction LoadImageData = + bool GetStoreOriginalJSONForExtrasAndExtensions() const { + return store_original_json_for_extras_and_extensions_; + } + + /// + /// Specify whether preserve image channales when loading images or not. + /// (Not effective when the user suppy their own LoadImageData callbacks) + /// + void SetPreserveImageChannels(bool onoff) { + preserve_image_channels_ = onoff; + } + + bool GetPreserveImageChannels() const { + return preserve_image_channels_; + } + + private: + /// + /// Loads glTF asset from string(memory). + /// `length` = strlen(str); + /// Set warning message to `warn` for example it fails to load asserts + /// Returns false and set error string to `err` if there's an error. + /// + bool LoadFromString(Model *model, std::string *err, std::string *warn, + const char *str, const unsigned int length, + const std::string &base_dir, unsigned int check_sections); + + const unsigned char *bin_data_ = nullptr; + size_t bin_size_ = 0; + bool is_binary_ = false; + + bool serialize_default_values_ = false; ///< Serialize default values? + + bool store_original_json_for_extras_and_extensions_ = false; + + bool preserve_image_channels_ = false; /// Default false(expand channels to RGBA) for backward compatibility. + + FsCallbacks fs = { +#ifndef TINYGLTF_NO_FS + &tinygltf::FileExists, &tinygltf::ExpandFilePath, + &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile, + + nullptr // Fs callback user data +#else + nullptr, nullptr, nullptr, nullptr, + + nullptr // Fs callback user data +#endif + }; + + LoadImageDataFunction LoadImageData = #ifndef TINYGLTF_NO_STB_IMAGE - &tinygltf::LoadImageData; + &tinygltf::LoadImageData; #else - nullptr; + nullptr; #endif - void *load_image_user_data_ = reinterpret_cast(&fs); + void *load_image_user_data_{nullptr}; + bool user_image_loader_{false}; - WriteImageDataFunction WriteImageData = + WriteImageDataFunction WriteImageData = #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - &tinygltf::WriteImageData; + &tinygltf::WriteImageData; #else - nullptr; + nullptr; #endif - void *write_image_user_data_ = reinterpret_cast(&fs); - }; + void *write_image_user_data_{nullptr}; +}; #ifdef __clang__ #pragma clang diagnostic pop // -Wpadded @@ -1063,6 +1465,7 @@ namespace tinygltf { #include //#include #ifndef TINYGLTF_NO_FS +#include #include #endif #include @@ -1075,7 +1478,9 @@ namespace tinygltf { #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wglobal-constructors" +#if __has_warning("-Wreserved-id-macro") #pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" #pragma clang diagnostic ignored "-Wpadded" #pragma clang diagnostic ignored "-Wc++98-compat" @@ -1115,6 +1520,9 @@ namespace tinygltf { #if __has_warning("-Wmismatched-tags") #pragma clang diagnostic ignored "-Wmismatched-tags" #endif +#if __has_warning("-Wextra-semi-stmt") +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#endif #endif // Disable GCC warnigs @@ -1124,7 +1532,15 @@ namespace tinygltf { #endif // __GNUC__ #ifndef TINYGLTF_NO_INCLUDE_JSON +#ifndef TINYGLTF_USE_RAPIDJSON #include "json.hpp" +#else +#include "document.h" +#include "prettywriter.h" +#include "rapidjson.h" +#include "stringbuffer.h" +#include "writer.h" +#endif #endif #ifdef TINYGLTF_ENABLE_DRACO @@ -1176,6 +1592,14 @@ namespace tinygltf { #undef NOMINMAX #endif +#if defined(__GLIBCXX__) // mingw + +#include // _O_RDONLY + +#include // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf) + +#endif + #elif !defined(__ANDROID__) #include #endif @@ -1188,7 +1612,86 @@ namespace tinygltf { #endif #endif +namespace { +#ifdef TINYGLTF_USE_RAPIDJSON + +#ifdef TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR +// This uses the RapidJSON CRTAllocator. It is thread safe and multiple +// documents may be active at once. +using json = + rapidjson::GenericValue, rapidjson::CrtAllocator>; +using json_const_iterator = json::ConstMemberIterator; +using json_const_array_iterator = json const *; +using JsonDocument = + rapidjson::GenericDocument, rapidjson::CrtAllocator>; +rapidjson::CrtAllocator s_CrtAllocator; // stateless and thread safe +rapidjson::CrtAllocator &GetAllocator() { return s_CrtAllocator; } +#else +// This uses the default RapidJSON MemoryPoolAllocator. It is very fast, but +// not thread safe. Only a single JsonDocument may be active at any one time, +// meaning only a single gltf load/save can be active any one time. +using json = rapidjson::Value; +using json_const_iterator = json::ConstMemberIterator; +using json_const_array_iterator = json const *; +rapidjson::Document *s_pActiveDocument = nullptr; +rapidjson::Document::AllocatorType &GetAllocator() { + assert(s_pActiveDocument); // Root json node must be JsonDocument type + return s_pActiveDocument->GetAllocator(); +} + +#ifdef __clang__ +#pragma clang diagnostic push +// Suppress JsonDocument(JsonDocument &&rhs) noexcept +#pragma clang diagnostic ignored "-Wunused-member-function" +#endif + +struct JsonDocument : public rapidjson::Document { + JsonDocument() { + assert(s_pActiveDocument == + nullptr); // When using default allocator, only one document can be + // active at a time, if you need multiple active at once, + // define TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR + s_pActiveDocument = this; + } + JsonDocument(const JsonDocument &) = delete; + JsonDocument(JsonDocument &&rhs) noexcept + : rapidjson::Document(std::move(rhs)) { + s_pActiveDocument = this; + rhs.isNil = true; + } + ~JsonDocument() { + if (!isNil) { + s_pActiveDocument = nullptr; + } + } + + private: + bool isNil = false; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR + +#else using nlohmann::json; +using json_const_iterator = json::const_iterator; +using json_const_array_iterator = json_const_iterator; +using JsonDocument = json; +#endif + +void JsonParse(JsonDocument &doc, const char *str, size_t length, + bool throwExc = false) { +#ifdef TINYGLTF_USE_RAPIDJSON + (void)throwExc; + doc.Parse(str, length); +#else + doc = json::parse(str, str + length, nullptr, throwExc); +#endif +} +} // namespace #ifdef __APPLE__ #include "TargetConditionals.h" @@ -1201,4176 +1704,6010 @@ using nlohmann::json; namespace tinygltf { - // Equals function for Value, for recursivity - static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) { - if (one.Type() != other.Type()) return false; +/// +/// Internal LoadImageDataOption struct. +/// This struct is passed through `user_pointer` in LoadImageData. +/// The struct is not passed when the user supply their own LoadImageData callbacks. +/// +struct LoadImageDataOption +{ + // true: preserve image channels(e.g. load as RGB image if the image has RGB channels) + // default `false`(channels are expanded to RGBA for backward compatiblity). + bool preserve_channels{false}; - switch (one.Type()) { - case NULL_TYPE: - return true; - case BOOL_TYPE: - return one.Get() == other.Get(); - case NUMBER_TYPE: - return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); - case INT_TYPE: - return one.Get() == other.Get(); - case OBJECT_TYPE: { - auto oneObj = one.Get(); - auto otherObj = other.Get(); - if (oneObj.size() != otherObj.size()) return false; - for (auto &it : oneObj) { - auto otherIt = otherObj.find(it.first); - if (otherIt == otherObj.end()) return false; +}; - if (!Equals(it.second, otherIt->second)) return false; - } - return true; - } - case ARRAY_TYPE: { - if (one.Size() != other.Size()) return false; - for (int i = 0; i < int(one.Size()); ++i) - if (Equals(one.Get(i), other.Get(i))) return false; - return true; - } - case STRING_TYPE: - return one.Get() == other.Get(); - case BINARY_TYPE: - return one.Get >() == - other.Get >(); - default: { - // unhandled type - return false; - } - } - } +// Equals function for Value, for recursivity +static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) { + if (one.Type() != other.Type()) return false; - // Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON - static bool Equals(const std::vector &one, - const std::vector &other) { - if (one.size() != other.size()) return false; - for (int i = 0; i < int(one.size()); ++i) { - if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; - } - return true; - } + switch (one.Type()) { + case NULL_TYPE: + return true; + case BOOL_TYPE: + return one.Get() == other.Get(); + case REAL_TYPE: + return TINYGLTF_DOUBLE_EQUAL(one.Get(), other.Get()); + case INT_TYPE: + return one.Get() == other.Get(); + case OBJECT_TYPE: { + auto oneObj = one.Get(); + auto otherObj = other.Get(); + if (oneObj.size() != otherObj.size()) return false; + for (auto &it : oneObj) { + auto otherIt = otherObj.find(it.first); + if (otherIt == otherObj.end()) return false; - bool Accessor::operator==(const Accessor &other) const { - return this->bufferView == other.bufferView && - this->byteOffset == other.byteOffset && - this->componentType == other.componentType && - this->count == other.count && this->extras == other.extras && - Equals(this->maxValues, other.maxValues) && - Equals(this->minValues, other.minValues) && this->name == other.name && - this->normalized == other.normalized && this->type == other.type; - } - bool Animation::operator==(const Animation &other) const { - return this->channels == other.channels && this->extras == other.extras && - this->name == other.name && this->samplers == other.samplers; - } - bool AnimationChannel::operator==(const AnimationChannel &other) const { - return this->extras == other.extras && - this->target_node == other.target_node && - this->target_path == other.target_path && - this->sampler == other.sampler; - } - bool AnimationSampler::operator==(const AnimationSampler &other) const { - return this->extras == other.extras && this->input == other.input && - this->interpolation == other.interpolation && - this->output == other.output; - } - bool Asset::operator==(const Asset &other) const { - return this->copyright == other.copyright && - this->extensions == other.extensions && this->extras == other.extras && - this->generator == other.generator && - this->minVersion == other.minVersion && this->version == other.version; - } - bool Buffer::operator==(const Buffer &other) const { - return this->data == other.data && this->extras == other.extras && - this->name == other.name && this->uri == other.uri; - } - bool BufferView::operator==(const BufferView &other) const { - return this->buffer == other.buffer && this->byteLength == other.byteLength && - this->byteOffset == other.byteOffset && - this->byteStride == other.byteStride && this->name == other.name && - this->target == other.target && this->extras == other.extras && - this->dracoDecoded == other.dracoDecoded; - } - bool Camera::operator==(const Camera &other) const { - return this->name == other.name && this->extensions == other.extensions && - this->extras == other.extras && - this->orthographic == other.orthographic && - this->perspective == other.perspective && this->type == other.type; - } - bool Image::operator==(const Image &other) const { - return this->bufferView == other.bufferView && - this->component == other.component && this->extras == other.extras && - this->height == other.height && this->image == other.image && - this->mimeType == other.mimeType && this->name == other.name && - this->uri == other.uri && this->width == other.width; - } - bool Light::operator==(const Light &other) const { - return Equals(this->color, other.color) && this->name == other.name && - this->type == other.type; - } - bool Material::operator==(const Material &other) const { - return this->additionalValues == other.additionalValues && - this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->values == other.values; - } - bool Mesh::operator==(const Mesh &other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->primitives == other.primitives && - this->targets == other.targets && Equals(this->weights, other.weights); - } - bool Model::operator==(const Model &other) const { - return this->accessors == other.accessors && - this->animations == other.animations && this->asset == other.asset && - this->buffers == other.buffers && - this->bufferViews == other.bufferViews && - this->cameras == other.cameras && - this->defaultScene == other.defaultScene && - this->extensions == other.extensions && - this->extensionsRequired == other.extensionsRequired && - this->extensionsUsed == other.extensionsUsed && - this->extras == other.extras && this->images == other.images && - this->lights == other.lights && this->materials == other.materials && - this->meshes == other.meshes && this->nodes == other.nodes && - this->samplers == other.samplers && this->scenes == other.scenes && - this->skins == other.skins && this->textures == other.textures; - } - bool Node::operator==(const Node &other) const { - return this->camera == other.camera && this->children == other.children && - this->extensions == other.extensions && this->extras == other.extras && - Equals(this->matrix, other.matrix) && this->mesh == other.mesh && - this->name == other.name && Equals(this->rotation, other.rotation) && - Equals(this->scale, other.scale) && this->skin == other.skin && - Equals(this->translation, other.translation) && - Equals(this->weights, other.weights); - } - bool OrthographicCamera::operator==(const OrthographicCamera &other) const { - return this->extensions == other.extensions && this->extras == other.extras && - TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && - TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && - TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && - TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); - } - bool Parameter::operator==(const Parameter &other) const { - if (this->bool_value != other.bool_value || - this->has_number_value != other.has_number_value) - return false; + if (!Equals(it.second, otherIt->second)) return false; + } + return true; + } + case ARRAY_TYPE: { + if (one.Size() != other.Size()) return false; + for (int i = 0; i < int(one.Size()); ++i) + if (!Equals(one.Get(i), other.Get(i))) return false; + return true; + } + case STRING_TYPE: + return one.Get() == other.Get(); + case BINARY_TYPE: + return one.Get >() == + other.Get >(); + default: { + // unhandled type + return false; + } + } +} - if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) - return false; +// Equals function for std::vector using TINYGLTF_DOUBLE_EPSILON +static bool Equals(const std::vector &one, + const std::vector &other) { + if (one.size() != other.size()) return false; + for (int i = 0; i < int(one.size()); ++i) { + if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false; + } + return true; +} - if (this->json_double_value.size() != other.json_double_value.size()) - return false; - for (auto &it : this->json_double_value) { - auto otherIt = other.json_double_value.find(it.first); - if (otherIt == other.json_double_value.end()) return false; +bool Accessor::operator==(const Accessor &other) const { + return this->bufferView == other.bufferView && + this->byteOffset == other.byteOffset && + this->componentType == other.componentType && + this->count == other.count && this->extensions == other.extensions && + this->extras == other.extras && + Equals(this->maxValues, other.maxValues) && + Equals(this->minValues, other.minValues) && this->name == other.name && + this->normalized == other.normalized && this->type == other.type; +} +bool Animation::operator==(const Animation &other) const { + return this->channels == other.channels && + this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->samplers == other.samplers; +} +bool AnimationChannel::operator==(const AnimationChannel &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->target_node == other.target_node && + this->target_path == other.target_path && + this->sampler == other.sampler; +} +bool AnimationSampler::operator==(const AnimationSampler &other) const { + return this->extras == other.extras && this->extensions == other.extensions && + this->input == other.input && + this->interpolation == other.interpolation && + this->output == other.output; +} +bool Asset::operator==(const Asset &other) const { + return this->copyright == other.copyright && + this->extensions == other.extensions && this->extras == other.extras && + this->generator == other.generator && + this->minVersion == other.minVersion && this->version == other.version; +} +bool Buffer::operator==(const Buffer &other) const { + return this->data == other.data && this->extensions == other.extensions && + this->extras == other.extras && this->name == other.name && + this->uri == other.uri; +} +bool BufferView::operator==(const BufferView &other) const { + return this->buffer == other.buffer && this->byteLength == other.byteLength && + this->byteOffset == other.byteOffset && + this->byteStride == other.byteStride && this->name == other.name && + this->target == other.target && this->extensions == other.extensions && + this->extras == other.extras && + this->dracoDecoded == other.dracoDecoded; +} +bool Camera::operator==(const Camera &other) const { + return this->name == other.name && this->extensions == other.extensions && + this->extras == other.extras && + this->orthographic == other.orthographic && + this->perspective == other.perspective && this->type == other.type; +} +bool Image::operator==(const Image &other) const { + return this->bufferView == other.bufferView && + this->component == other.component && + this->extensions == other.extensions && this->extras == other.extras && + this->height == other.height && this->image == other.image && + this->mimeType == other.mimeType && this->name == other.name && + this->uri == other.uri && this->width == other.width; +} +bool Light::operator==(const Light &other) const { + return Equals(this->color, other.color) && this->name == other.name && + this->type == other.type; +} +bool Material::operator==(const Material &other) const { + return (this->pbrMetallicRoughness == other.pbrMetallicRoughness) && + (this->normalTexture == other.normalTexture) && + (this->occlusionTexture == other.occlusionTexture) && + (this->emissiveTexture == other.emissiveTexture) && + Equals(this->emissiveFactor, other.emissiveFactor) && + (this->alphaMode == other.alphaMode) && + TINYGLTF_DOUBLE_EQUAL(this->alphaCutoff, other.alphaCutoff) && + (this->doubleSided == other.doubleSided) && + (this->extensions == other.extensions) && + (this->extras == other.extras) && (this->values == other.values) && + (this->additionalValues == other.additionalValues) && + (this->name == other.name); +} +bool Mesh::operator==(const Mesh &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && Equals(this->weights, other.weights) && + this->primitives == other.primitives; +} +bool Model::operator==(const Model &other) const { + return this->accessors == other.accessors && + this->animations == other.animations && this->asset == other.asset && + this->buffers == other.buffers && + this->bufferViews == other.bufferViews && + this->cameras == other.cameras && + this->defaultScene == other.defaultScene && + this->extensions == other.extensions && + this->extensionsRequired == other.extensionsRequired && + this->extensionsUsed == other.extensionsUsed && + this->extras == other.extras && this->images == other.images && + this->lights == other.lights && this->materials == other.materials && + this->meshes == other.meshes && this->nodes == other.nodes && + this->samplers == other.samplers && this->scenes == other.scenes && + this->skins == other.skins && this->textures == other.textures; +} +bool Node::operator==(const Node &other) const { + return this->camera == other.camera && this->children == other.children && + this->extensions == other.extensions && this->extras == other.extras && + Equals(this->matrix, other.matrix) && this->mesh == other.mesh && + this->name == other.name && Equals(this->rotation, other.rotation) && + Equals(this->scale, other.scale) && this->skin == other.skin && + Equals(this->translation, other.translation) && + Equals(this->weights, other.weights); +} +bool SpotLight::operator==(const SpotLight &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->innerConeAngle, other.innerConeAngle) && + TINYGLTF_DOUBLE_EQUAL(this->outerConeAngle, other.outerConeAngle); +} +bool OrthographicCamera::operator==(const OrthographicCamera &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) && + TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Parameter::operator==(const Parameter &other) const { + if (this->bool_value != other.bool_value || + this->has_number_value != other.has_number_value) + return false; - if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; - } + if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value)) + return false; - if (!Equals(this->number_array, other.number_array)) return false; + if (this->json_double_value.size() != other.json_double_value.size()) + return false; + for (auto &it : this->json_double_value) { + auto otherIt = other.json_double_value.find(it.first); + if (otherIt == other.json_double_value.end()) return false; - if (this->string_value != other.string_value) return false; + if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false; + } - return true; - } - bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const { - return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && - this->extensions == other.extensions && this->extras == other.extras && - TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && - TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && - TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); - } - bool Primitive::operator==(const Primitive &other) const { - return this->attributes == other.attributes && this->extras == other.extras && - this->indices == other.indices && this->material == other.material && - this->mode == other.mode && this->targets == other.targets; - } - bool Sampler::operator==(const Sampler &other) const { - return this->extras == other.extras && this->magFilter == other.magFilter && - this->minFilter == other.minFilter && this->name == other.name && - this->wrapR == other.wrapR && this->wrapS == other.wrapS && - this->wrapT == other.wrapT; - } - bool Scene::operator==(const Scene &other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->nodes == other.nodes; - ; - } - bool Skin::operator==(const Skin &other) const { - return this->inverseBindMatrices == other.inverseBindMatrices && - this->joints == other.joints && this->name == other.name && - this->skeleton == other.skeleton; - } - bool Texture::operator==(const Texture &other) const { - return this->extensions == other.extensions && this->extras == other.extras && - this->name == other.name && this->sampler == other.sampler && - this->source == other.source; - } - bool Value::operator==(const Value &other) const { - return Equals(*this, other); - } + if (!Equals(this->number_array, other.number_array)) return false; - static void swap4(unsigned int *val) { + if (this->string_value != other.string_value) return false; + + return true; +} +bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const { + return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) && + this->extensions == other.extensions && this->extras == other.extras && + TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) && + TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) && + TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear); +} +bool Primitive::operator==(const Primitive &other) const { + return this->attributes == other.attributes && this->extras == other.extras && + this->indices == other.indices && this->material == other.material && + this->mode == other.mode && this->targets == other.targets; +} +bool Sampler::operator==(const Sampler &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->magFilter == other.magFilter && + this->minFilter == other.minFilter && this->name == other.name && + this->wrapT == other.wrapT; + + //this->wrapR == other.wrapR && this->wrapS == other.wrapS && +} +bool Scene::operator==(const Scene &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->nodes == other.nodes; +} +bool Skin::operator==(const Skin &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->inverseBindMatrices == other.inverseBindMatrices && + this->joints == other.joints && this->name == other.name && + this->skeleton == other.skeleton; +} +bool Texture::operator==(const Texture &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->name == other.name && this->sampler == other.sampler && + this->source == other.source; +} +bool TextureInfo::operator==(const TextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord; +} +bool NormalTextureInfo::operator==(const NormalTextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord && + TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale); +} +bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + this->index == other.index && this->texCoord == other.texCoord && + TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength); +} +bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness &other) const { + return this->extensions == other.extensions && this->extras == other.extras && + (this->baseColorTexture == other.baseColorTexture) && + (this->metallicRoughnessTexture == other.metallicRoughnessTexture) && + Equals(this->baseColorFactor, other.baseColorFactor) && + TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) && + TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor); +} +bool Value::operator==(const Value &other) const { + return Equals(*this, other); +} + +static void swap4(unsigned int *val) { #ifdef TINYGLTF_LITTLE_ENDIAN - (void)val; + (void)val; #else - unsigned int tmp = *val; - unsigned char *dst = reinterpret_cast(val); - unsigned char *src = reinterpret_cast(&tmp); + unsigned int tmp = *val; + unsigned char *dst = reinterpret_cast(val); + unsigned char *src = reinterpret_cast(&tmp); - dst[0] = src[3]; - dst[1] = src[2]; - dst[2] = src[1]; - dst[3] = src[0]; + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; #endif - } +} - static std::string JoinPath(const std::string &path0, - const std::string &path1) { - if (path0.empty()) { - return path1; - } - else { - // check '/' - char lastChar = *path0.rbegin(); - if (lastChar != '/') { - return path0 + std::string("/") + path1; - } - else { - return path0 + path1; - } - } - } +static std::string JoinPath(const std::string &path0, + const std::string &path1) { + if (path0.empty()) { + return path1; + } else { + // check '/' + char lastChar = *path0.rbegin(); + if (lastChar != '/') { + return path0 + std::string("/") + path1; + } else { + return path0 + path1; + } + } +} - static std::string FindFile(const std::vector &paths, - const std::string &filepath, FsCallbacks *fs) { - if (fs == nullptr || fs->ExpandFilePath == nullptr || - fs->FileExists == nullptr) { - // Error, fs callback[s] missing - return std::string(); - } +static std::string FindFile(const std::vector &paths, + const std::string &filepath, FsCallbacks *fs) { + if (fs == nullptr || fs->ExpandFilePath == nullptr || + fs->FileExists == nullptr) { + // Error, fs callback[s] missing + return std::string(); + } - for (size_t i = 0; i < paths.size(); i++) { - std::string absPath = - fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); - if (fs->FileExists(absPath, fs->user_data)) { - return absPath; - } - } + for (size_t i = 0; i < paths.size(); i++) { + std::string absPath = + fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data); + if (fs->FileExists(absPath, fs->user_data)) { + return absPath; + } + } - return std::string(); - } + return std::string(); +} - static std::string GetFilePathExtension(const std::string &FileName) { - if (FileName.find_last_of(".") != std::string::npos) - return FileName.substr(FileName.find_last_of(".") + 1); - return ""; - } +static std::string GetFilePathExtension(const std::string &FileName) { + if (FileName.find_last_of(".") != std::string::npos) + return FileName.substr(FileName.find_last_of(".") + 1); + return ""; +} - static std::string GetBaseDir(const std::string &filepath) { - if (filepath.find_last_of("/\\") != std::string::npos) - return filepath.substr(0, filepath.find_last_of("/\\")); - return ""; - } +static std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} - // https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path - static std::string GetBaseFilename(const std::string &filepath) { - return filepath.substr(filepath.find_last_of("/\\") + 1); - } +// https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path +static std::string GetBaseFilename(const std::string &filepath) { + return filepath.substr(filepath.find_last_of("/\\") + 1); +} - std::string base64_encode(unsigned char const *, unsigned int len); - std::string base64_decode(std::string const &s); +std::string base64_encode(unsigned char const *, unsigned int len); +std::string base64_decode(std::string const &s); - /* - base64.cpp and base64.h +/* + base64.cpp and base64.h - Copyright (C) 2004-2008 RenĂ© Nyffenegger + Copyright (C) 2004-2008 RenĂ© Nyffenegger - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. - 3. This notice may not be removed or altered from any source distribution. + 3. This notice may not be removed or altered from any source distribution. - RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch + RenĂ© Nyffenegger rene.nyffenegger@adp-gmbh.ch - */ +*/ #ifdef __clang__ #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wglobal-constructors" #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wconversion" #endif - static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); - } +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} - std::string base64_encode(unsigned char const *bytes_to_encode, - unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; +std::string base64_encode(unsigned char const *bytes_to_encode, + unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + const char *base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; - for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; - i = 0; - } - } + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - if (i) { - for (j = i; j < 3; j++) char_array_3[j] = '\0'; + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = - ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = - ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; - for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - while ((i++ < 3)) ret += '='; - } + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; - return ret; - } + while ((i++ < 3)) ret += '='; + } - std::string base64_decode(std::string const &encoded_string) { - int in_len = static_cast(encoded_string.size()); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; + return ret; +} - while (in_len-- && (encoded_string[in_] != '=') && - is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; - in_++; - if (i == 4) { - for (i = 0; i < 4; i++) - char_array_4[i] = - static_cast(base64_chars.find(char_array_4[i])); +std::string base64_decode(std::string const &encoded_string) { + int in_len = static_cast(encoded_string.size()); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; - char_array_3[0] = - (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; - for (i = 0; (i < 3); i++) ret += char_array_3[i]; - i = 0; - } - } + while (in_len-- && (encoded_string[in_] != '=') && + is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = + static_cast(base64_chars.find(char_array_4[i])); - if (i) { - for (j = i; j < 4; j++) char_array_4[j] = 0; + char_array_3[0] = + (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (j = 0; j < 4; j++) - char_array_4[j] = - static_cast(base64_chars.find(char_array_4[j])); + for (i = 0; (i < 3); i++) ret += char_array_3[i]; + i = 0; + } + } - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = - ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + if (i) { + for (j = i; j < 4; j++) char_array_4[j] = 0; - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } + for (j = 0; j < 4; j++) + char_array_4[j] = + static_cast(base64_chars.find(char_array_4[j])); - return ret; - } + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} #ifdef __clang__ #pragma clang diagnostic pop #endif - static bool LoadExternalFile(std::vector *out, std::string *err, - std::string *warn, const std::string &filename, - const std::string &basedir, bool required, - size_t reqBytes, bool checkSize, FsCallbacks *fs) { - if (fs == nullptr || fs->FileExists == nullptr || - fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { - // This is a developer error, assert() ? - if (err) { - (*err) += "FS callback[s] not set\n"; - } - return false; - } +// https://github.com/syoyo/tinygltf/issues/228 +// TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri +// decoding? +// +// https://stackoverflow.com/questions/18307429/encode-decode-url-in-c +// http://dlib.net/dlib/server/server_http.cpp.html - std::string *failMsgOut = required ? err : warn; +// --- dlib beign ------------------------------------------------------------ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. - out->clear(); +namespace dlib { - std::vector paths; - paths.push_back(basedir); - paths.push_back("."); +#if 0 + inline unsigned char to_hex( unsigned char x ) + { + return x + (x > 9 ? ('A'-10) : '0'); + } - std::string filepath = FindFile(paths, filename, fs); - if (filepath.empty() || filename.empty()) { - if (failMsgOut) { - (*failMsgOut) += "File not found : " + filename + "\n"; - } - return false; - } + const std::string urlencode( const std::string& s ) + { + std::ostringstream os; - std::vector buf; - std::string fileReadErr; - bool fileRead = - fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); - if (!fileRead) { - if (failMsgOut) { - (*failMsgOut) += - "File read error : " + filepath + " : " + fileReadErr + "\n"; - } - return false; - } + for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci ) + { + if ( (*ci >= 'a' && *ci <= 'z') || + (*ci >= 'A' && *ci <= 'Z') || + (*ci >= '0' && *ci <= '9') ) + { // allowed + os << *ci; + } + else if ( *ci == ' ') + { + os << '+'; + } + else + { + os << '%' << to_hex(static_cast(*ci >> 4)) << to_hex(static_cast(*ci % 16)); + } + } - size_t sz = buf.size(); - if (sz == 0) { - if (failMsgOut) { - (*failMsgOut) += "File is empty : " + filepath + "\n"; - } - return false; - } + return os.str(); + } +#endif - if (checkSize) { - if (reqBytes == sz) { - out->swap(buf); - return true; - } - else { - std::stringstream ss; - ss << "File size mismatch : " << filepath << ", requestedBytes " - << reqBytes << ", but got " << sz << std::endl; - if (failMsgOut) { - (*failMsgOut) += ss.str(); - } - return false; - } - } +inline unsigned char from_hex(unsigned char ch) { + if (ch <= '9' && ch >= '0') + ch -= '0'; + else if (ch <= 'f' && ch >= 'a') + ch -= 'a' - 10; + else if (ch <= 'F' && ch >= 'A') + ch -= 'A' - 10; + else + ch = 0; + return ch; +} - out->swap(buf); - return true; - } +static const std::string urldecode(const std::string &str) { + using namespace std; + string result; + string::size_type i; + for (i = 0; i < str.size(); ++i) { + if (str[i] == '+') { + result += ' '; + } else if (str[i] == '%' && str.size() > i + 2) { + const unsigned char ch1 = + from_hex(static_cast(str[i + 1])); + const unsigned char ch2 = + from_hex(static_cast(str[i + 2])); + const unsigned char ch = static_cast((ch1 << 4) | ch2); + result += static_cast(ch); + i += 2; + } else { + result += str[i]; + } + } + return result; +} - void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { - LoadImageData = func; - load_image_user_data_ = user_data; - } +} // namespace dlib +// --- dlib end -------------------------------------------------------------- + +static bool LoadExternalFile(std::vector *out, std::string *err, + std::string *warn, const std::string &filename, + const std::string &basedir, bool required, + size_t reqBytes, bool checkSize, FsCallbacks *fs) { + if (fs == nullptr || fs->FileExists == nullptr || + fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) { + // This is a developer error, assert() ? + if (err) { + (*err) += "FS callback[s] not set\n"; + } + return false; + } + + std::string *failMsgOut = required ? err : warn; + + out->clear(); + + std::vector paths; + paths.push_back(basedir); + paths.push_back("."); + + std::string filepath = FindFile(paths, filename, fs); + if (filepath.empty() || filename.empty()) { + if (failMsgOut) { + (*failMsgOut) += "File not found : " + filename + "\n"; + } + return false; + } + + std::vector buf; + std::string fileReadErr; + bool fileRead = + fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data); + if (!fileRead) { + if (failMsgOut) { + (*failMsgOut) += + "File read error : " + filepath + " : " + fileReadErr + "\n"; + } + return false; + } + + size_t sz = buf.size(); + if (sz == 0) { + if (failMsgOut) { + (*failMsgOut) += "File is empty : " + filepath + "\n"; + } + return false; + } + + if (checkSize) { + if (reqBytes == sz) { + out->swap(buf); + return true; + } else { + std::stringstream ss; + ss << "File size mismatch : " << filepath << ", requestedBytes " + << reqBytes << ", but got " << sz << std::endl; + if (failMsgOut) { + (*failMsgOut) += ss.str(); + } + return false; + } + } + + out->swap(buf); + return true; +} + +void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) { + LoadImageData = func; + load_image_user_data_ = user_data; + user_image_loader_ = true; +} + +void TinyGLTF::RemoveImageLoader() { + LoadImageData = +#ifndef TINYGLTF_NO_STB_IMAGE + &tinygltf::LoadImageData; +#else + nullptr; +#endif + + load_image_user_data_ = nullptr; + user_image_loader_ = false; +} #ifndef TINYGLTF_NO_STB_IMAGE - bool LoadImageData(Image *image, const int image_idx, std::string *err, - std::string *warn, int req_width, int req_height, - const unsigned char *bytes, int size, void *user_data) { - (void)user_data; - (void)warn; +bool LoadImageData(Image *image, const int image_idx, std::string *err, + std::string *warn, int req_width, int req_height, + const unsigned char *bytes, int size, void *user_data) { + (void)warn; - int w, h, comp, req_comp; + LoadImageDataOption option; + if (user_data) { + option = *reinterpret_cast(user_data); + } - unsigned char *data = nullptr; + int w = 0, h = 0, comp = 0, req_comp = 0; - // force 32-bit textures for common Vulkan compatibility. It appears that - // some GPU drivers do not support 24-bit images for Vulkan - req_comp = 4; - int bits = 8; - int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + unsigned char *data = nullptr; - // It is possible that the image we want to load is a 16bit per channel image - // We are going to attempt to load it as 16bit per channel, and if it worked, - // set the image data accodingly. We are casting the returned pointer into - // unsigned char, because we are representing "bytes". But we are updating - // the Image metadata to signal that this image uses 2 bytes (16bits) per - // channel: - if (stbi_is_16_bit_from_memory(bytes, size)) { - data = (unsigned char *)stbi_load_16_from_memory(bytes, size, &w, &h, &comp, - req_comp); - if (data) { - bits = 16; - pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; - } - } + // preserve_channels true: Use channels stored in the image file. + // false: force 32-bit textures for common Vulkan compatibility. It appears that + // some GPU drivers do not support 24-bit images for Vulkan + req_comp = option.preserve_channels ? 0 : 4; + int bits = 8; + int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; - // at this point, if data is still NULL, it means that the image wasn't - // 16bit per channel, we are going to load it as a normal 8bit per channel - // mage as we used to do: - // if image cannot be decoded, ignore parsing and keep it by its path - // don't break in this case - // FIXME we should only enter this function if the image is embedded. If - // image->uri references - // an image file, it should be left as it is. Image loading should not be - // mandatory (to support other formats) - if (!data) data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); - if (!data) { - // NOTE: you can use `warn` instead of `err` - if (err) { - (*err) += - "Unknown image format. STB cannot decode image data for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; - } - return false; - } + // It is possible that the image we want to load is a 16bit per channel image + // We are going to attempt to load it as 16bit per channel, and if it worked, + // set the image data accodingly. We are casting the returned pointer into + // unsigned char, because we are representing "bytes". But we are updating + // the Image metadata to signal that this image uses 2 bytes (16bits) per + // channel: + if (stbi_is_16_bit_from_memory(bytes, size)) { + data = reinterpret_cast( + stbi_load_16_from_memory(bytes, size, &w, &h, &comp, req_comp)); + if (data) { + bits = 16; + pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT; + } + } - if (w < 1 || h < 1) { - stbi_image_free(data); - if (err) { - (*err) += "Invalid image data for image[" + std::to_string(image_idx) + - "] name = \"" + image->name + "\"\n"; - } - return false; - } + // at this point, if data is still NULL, it means that the image wasn't + // 16bit per channel, we are going to load it as a normal 8bit per channel + // mage as we used to do: + // if image cannot be decoded, ignore parsing and keep it by its path + // don't break in this case + // FIXME we should only enter this function if the image is embedded. If + // image->uri references + // an image file, it should be left as it is. Image loading should not be + // mandatory (to support other formats) + if (!data) data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp); + if (!data) { + // NOTE: you can use `warn` instead of `err` + if (err) { + (*err) += + "Unknown image format. STB cannot decode image data for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\".\n"; + } + return false; + } - if (req_width > 0) { - if (req_width != w) { - stbi_image_free(data); - if (err) { - (*err) += "Image width mismatch for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - } + if ((w < 1) || (h < 1)) { + stbi_image_free(data); + if (err) { + (*err) += "Invalid image data for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\"\n"; + } + return false; + } - if (req_height > 0) { - if (req_height != h) { - stbi_image_free(data); - if (err) { - (*err) += "Image height mismatch. for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - } + if (req_width > 0) { + if (req_width != w) { + stbi_image_free(data); + if (err) { + (*err) += "Image width mismatch for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } - image->width = w; - image->height = h; - image->component = req_comp; - image->bits = bits; - image->pixel_type = pixel_type; - image->image.resize(static_cast(w * h * req_comp) * (bits / 8)); - std::copy(data, data + w * h * req_comp * (bits / 8), image->image.begin()); - stbi_image_free(data); + if (req_height > 0) { + if (req_height != h) { + stbi_image_free(data); + if (err) { + (*err) += "Image height mismatch. for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + } - return true; - } + if (req_comp != 0) { + // loaded data has `req_comp` channels(components) + comp = req_comp; + } + + image->width = w; + image->height = h; + image->component = comp; + image->bits = bits; + image->pixel_type = pixel_type; + image->image.resize(static_cast(w * h * comp) * size_t(bits / 8)); + std::copy(data, data + w * h * comp * (bits / 8), image->image.begin()); + stbi_image_free(data); + + return true; +} #endif - void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { - WriteImageData = func; - write_image_user_data_ = user_data; - } +void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) { + WriteImageData = func; + write_image_user_data_ = user_data; +} #ifndef TINYGLTF_NO_STB_IMAGE_WRITE - static void WriteToMemory_stbi(void *context, void *data, int size) { - std::vector *buffer = - reinterpret_cast *>(context); +static void WriteToMemory_stbi(void *context, void *data, int size) { + std::vector *buffer = + reinterpret_cast *>(context); - unsigned char *pData = reinterpret_cast(data); + unsigned char *pData = reinterpret_cast(data); - buffer->insert(buffer->end(), pData, pData + size); - } + buffer->insert(buffer->end(), pData, pData + size); +} - bool WriteImageData(const std::string *basepath, const std::string *filename, - Image *image, bool embedImages, void *fsPtr) { - const std::string ext = GetFilePathExtension(*filename); +bool WriteImageData(const std::string *basepath, const std::string *filename, + Image *image, bool embedImages, void *fsPtr) { + const std::string ext = GetFilePathExtension(*filename); - // Write image to temporary buffer - std::string header; - std::vector data; + // Write image to temporary buffer + std::string header; + std::vector data; - if (ext == "png") { - if ((image->bits != 8) || - (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) { - // Unsupported pixel format - return false; - } + if (ext == "png") { + if ((image->bits != 8) || + (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) { + // Unsupported pixel format + return false; + } - if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0], 0)) { - return false; - } - header = "data:image/png;base64,"; - } - else if (ext == "jpg") { - if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0], 100)) { - return false; - } - header = "data:image/jpeg;base64,"; - } - else if (ext == "bmp") { - if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, - image->height, image->component, - &image->image[0])) { - return false; - } - header = "data:image/bmp;base64,"; - } - else if (!embedImages) { - // Error: can't output requested format to file - return false; - } + if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 0)) { + return false; + } + header = "data:image/png;base64,"; + } else if (ext == "jpg") { + if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0], 100)) { + return false; + } + header = "data:image/jpeg;base64,"; + } else if (ext == "bmp") { + if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width, + image->height, image->component, + &image->image[0])) { + return false; + } + header = "data:image/bmp;base64,"; + } else if (!embedImages) { + // Error: can't output requested format to file + return false; + } - if (embedImages) { - // Embed base64-encoded image into URI - if (data.size()) { - image->uri = - header + - base64_encode(&data[0], static_cast(data.size())); - } - else { - // Throw error? - } - } - else { - // Write image to disc - FsCallbacks *fs = reinterpret_cast(fsPtr); - if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) { - const std::string imagefilepath = JoinPath(*basepath, *filename); - std::string writeError; - if (!fs->WriteWholeFile(&writeError, imagefilepath, data, - fs->user_data)) { - // Could not write image file to disc; Throw error ? - return false; - } - } - else { - // Throw error? - } - image->uri = *filename; - } + if (embedImages) { + // Embed base64-encoded image into URI + if (data.size()) { + image->uri = + header + + base64_encode(&data[0], static_cast(data.size())); + } else { + // Throw error? + } + } else { + // Write image to disc + FsCallbacks *fs = reinterpret_cast(fsPtr); + if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) { + const std::string imagefilepath = JoinPath(*basepath, *filename); + std::string writeError; + if (!fs->WriteWholeFile(&writeError, imagefilepath, data, + fs->user_data)) { + // Could not write image file to disc; Throw error ? + return false; + } + } else { + // Throw error? + } + image->uri = *filename; + } - return true; - } + return true; +} #endif - void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } +void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; } + +#ifdef _WIN32 +static inline std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + std::wstring wstr(wstr_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], + (int)wstr.size()); + return wstr; +} + +static inline std::string WcharToUTF8(const std::wstring &wstr) { + int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), + nullptr, 0, NULL, NULL); + std::string str(str_size, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), &str[0], + (int)str.size(), NULL, NULL); + return str; +} +#endif #ifndef TINYGLTF_NO_FS - // Default implementations of filesystem functions +// Default implementations of filesystem functions - bool FileExists(const std::string &abs_filename, void *) { - bool ret; +bool FileExists(const std::string &abs_filename, void *) { + bool ret; #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - if (asset_manager) { - AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(), - AASSET_MODE_STREAMING); - if (!asset) { - return false; - } - AAsset_close(asset); - ret = true; - } - else { - return false; - } + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + return false; + } + AAsset_close(asset); + ret = true; + } else { + return false; + } #else #ifdef _WIN32 - FILE *fp; - errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); - if (err != 0) { - return false; - } +#if defined(_MSC_VER) || defined(__GLIBCXX__) + FILE *fp = nullptr; + errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb"); + if (err != 0) { + return false; + } #else - FILE *fp = fopen(abs_filename.c_str(), "rb"); -#endif - if (fp) { - ret = true; - fclose(fp); - } - else { - ret = false; - } + FILE *fp = nullptr; + errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb"); + if (err != 0) { + return false; + } #endif - return ret; - } +#else + FILE *fp = fopen(abs_filename.c_str(), "rb"); +#endif + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } +#endif - std::string ExpandFilePath(const std::string &filepath, void *) { + return ret; +} + +std::string ExpandFilePath(const std::string &filepath, void *) { #ifdef _WIN32 - DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); - char *str = new char[len]; - ExpandEnvironmentStringsA(filepath.c_str(), str, len); + // Assume input `filepath` is encoded in UTF-8 + std::wstring wfilepath = UTF8ToWchar(filepath); + DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0); + wchar_t *wstr = new wchar_t[wlen]; + ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen); - std::string s(str); + std::wstring ws(wstr); + delete[] wstr; + return WcharToUTF8(ws); - delete[] str; - - return s; #else #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \ defined(__ANDROID__) || defined(__EMSCRIPTEN__) - // no expansion - std::string s = filepath; + // no expansion + std::string s = filepath; #else - std::string s; - wordexp_t p; + std::string s; + wordexp_t p; - if (filepath.empty()) { - return ""; - } + if (filepath.empty()) { + return ""; + } - // char** w; - int ret = wordexp(filepath.c_str(), &p, 0); - if (ret) { - // err - s = filepath; - return s; - } + // Quote the string to keep any spaces in filepath intact. + std::string quoted_path = "\"" + filepath + "\""; + // char** w; + int ret = wordexp(quoted_path.c_str(), &p, 0); + if (ret) { + // err + s = filepath; + return s; + } - // Use first element only. - if (p.we_wordv) { - s = std::string(p.we_wordv[0]); - wordfree(&p); - } - else { - s = filepath; - } + // Use first element only. + if (p.we_wordv) { + s = std::string(p.we_wordv[0]); + wordfree(&p); + } else { + s = filepath; + } #endif - return s; + return s; #endif - } +} - bool ReadWholeFile(std::vector *out, std::string *err, - const std::string &filepath, void *) { +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *) { #ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS - if (asset_manager) { - AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), - AASSET_MODE_STREAMING); - if (!asset) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } - size_t size = AAsset_getLength(asset); - if (size <= 0) { - if (err) { - (*err) += "Invalid file size : " + filepath + - " (does the path point to a directory?)"; - } - } - out->resize(size); - AAsset_read(asset, reinterpret_cast(&out->at(0)), size); - AAsset_close(asset); - return true; - } - else { - if (err) { - (*err) += "No asset manager specified : " + filepath + "\n"; - } - return false; - } + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + if (size == 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + out->resize(size); + AAsset_read(asset, reinterpret_cast(&out->at(0)), size); + AAsset_close(asset); + return true; + } else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } #else - std::ifstream f(filepath.c_str(), std::ifstream::binary); - if (!f) { - if (err) { - (*err) += "File open error : " + filepath + "\n"; - } - return false; - } - - f.seekg(0, f.end); - size_t sz = static_cast(f.tellg()); - f.seekg(0, f.beg); - - if (int(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; - } - - out->resize(sz); - f.read(reinterpret_cast(&out->at(0)), - static_cast(sz)); - f.close(); - - return true; +#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; + } - bool WriteWholeFile(std::string *err, const std::string &filepath, - const std::vector &contents, void *) { - std::ofstream f(filepath.c_str(), std::ofstream::binary); - if (!f) { - if (err) { - (*err) += "File open error for writing : " + filepath + "\n"; - } - return false; - } + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + f.seekg(0, f.beg); - f.write(reinterpret_cast(&contents.at(0)), - static_cast(contents.size())); - if (!f) { - if (err) { - (*err) += "File write error: " + filepath + "\n"; - } - return false; - } + if (int64_t(sz) < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == 0) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } - f.close(); - return true; - } + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + + return true; +#endif +} + +bool WriteWholeFile(std::string *err, const std::string &filepath, + const std::vector &contents, void *) { +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream f(&wfile_buf); +#elif defined(_MSC_VER) + std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary); +#else // clang? + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif +#else + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File open error for writing : " + filepath + "\n"; + } + return false; + } + + f.write(reinterpret_cast(&contents.at(0)), + static_cast(contents.size())); + if (!f) { + if (err) { + (*err) += "File write error: " + filepath + "\n"; + } + return false; + } + + return true; +} #endif // TINYGLTF_NO_FS - static std::string MimeToExt(const std::string &mimeType) { - if (mimeType == "image/jpeg") { - return "jpg"; - } - else if (mimeType == "image/png") { - return "png"; - } - else if (mimeType == "image/bmp") { - return "bmp"; - } - else if (mimeType == "image/gif") { - return "gif"; - } - - return ""; - } - - static void UpdateImageObject(Image &image, std::string &baseDir, int index, - bool embedImages, - WriteImageDataFunction *WriteImageData = nullptr, - void *user_data = nullptr) { - std::string filename; - std::string ext; - - // If image have uri. Use it it as a filename - if (image.uri.size()) { - filename = GetBaseFilename(image.uri); - ext = GetFilePathExtension(filename); - - } - else if (image.name.size()) { - ext = MimeToExt(image.mimeType); - // Otherwise use name as filename - filename = image.name + "." + ext; - } - else { - ext = MimeToExt(image.mimeType); - // Fallback to index of image as filename - filename = std::to_string(index) + "." + ext; - } - - // If callback is set, modify image data object - if (*WriteImageData != nullptr) { - std::string uri; - (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); - } - } - - bool IsDataURI(const std::string &in) { - std::string header = "data:application/octet-stream;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - return true; - } - - header = "data:application/gltf-buffer;base64,"; - if (in.find(header) == 0) { - return true; - } - - return false; - } - - bool DecodeDataURI(std::vector *out, std::string &mime_type, - const std::string &in, size_t reqBytes, bool checkSize) { - std::string header = "data:application/octet-stream;base64,"; - std::string data; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); // cut mime string. - } - - if (data.empty()) { - header = "data:image/jpeg;base64,"; - if (in.find(header) == 0) { - mime_type = "image/jpeg"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/png;base64,"; - if (in.find(header) == 0) { - mime_type = "image/png"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/bmp;base64,"; - if (in.find(header) == 0) { - mime_type = "image/bmp"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:image/gif;base64,"; - if (in.find(header) == 0) { - mime_type = "image/gif"; - data = base64_decode(in.substr(header.size())); // cut mime string. - } - } - - if (data.empty()) { - header = "data:text/plain;base64,"; - if (in.find(header) == 0) { - mime_type = "text/plain"; - data = base64_decode(in.substr(header.size())); - } - } - - if (data.empty()) { - header = "data:application/gltf-buffer;base64,"; - if (in.find(header) == 0) { - data = base64_decode(in.substr(header.size())); - } - } - - if (data.empty()) { - return false; - } - - if (checkSize) { - if (data.size() != reqBytes) { - return false; - } - out->resize(reqBytes); - } - else { - out->resize(data.size()); - } - std::copy(data.begin(), data.end(), out->begin()); - return true; - } - - static bool ParseJsonAsValue(Value *ret, const json &o) { - Value val{}; - switch (o.type()) { - case json::value_t::object: { - Value::Object value_object; - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) value_object[it.key()] = entry; - } - if (value_object.size() > 0) val = Value(value_object); - } break; - case json::value_t::array: { - Value::Array value_array; - for (auto it = o.begin(); it != o.end(); it++) { - Value entry; - ParseJsonAsValue(&entry, it.value()); - if (entry.Type() != NULL_TYPE) value_array.push_back(entry); - } - if (value_array.size() > 0) val = Value(value_array); - } break; - case json::value_t::string: - val = Value(o.get()); - break; - case json::value_t::boolean: - val = Value(o.get()); - break; - case json::value_t::number_integer: - case json::value_t::number_unsigned: - val = Value(static_cast(o.get())); - break; - case json::value_t::number_float: - val = Value(o.get()); - break; - case json::value_t::null: - case json::value_t::discarded: - // default: - break; - } - if (ret) *ret = val; - - return val.Type() != NULL_TYPE; - } - - static bool ParseExtrasProperty(Value *ret, const json &o) { - json::const_iterator it = o.find("extras"); - if (it == o.end()) { - return false; - } - - return ParseJsonAsValue(ret, it.value()); - } - - static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, - const std::string &property, - const bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_boolean()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a bool type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value().get(); - } - - return true; - } - - static bool ParseNumberProperty(double *ret, std::string *err, const json &o, - const std::string &property, - const bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value().get(); - } - - return true; - } - - static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, - const json &o, const std::string &property, - bool required, - const std::string &parent_node = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - if (!it.value().is_array()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an array"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - - ret->clear(); - for (json::const_iterator i = it.value().begin(); i != it.value().end(); - i++) { - if (!i.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a number.\n"; - if (!parent_node.empty()) { - (*err) += " in " + parent_node; - } - (*err) += ".\n"; - } - } - return false; - } - ret->push_back(i.value()); - } - - return true; - } - - static bool ParseStringProperty( - std::string *ret, std::string *err, const json &o, - const std::string &property, bool required, - const std::string &parent_node = std::string()) { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing"; - if (parent_node.empty()) { - (*err) += ".\n"; - } - else { - (*err) += " in `" + parent_node + "'.\n"; - } - } - } - return false; - } - - if (!it.value().is_string()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a string type.\n"; - } - } - return false; - } - - if (ret) { - (*ret) = it.value().get(); - } - - return true; - } - - static bool ParseStringIntProperty(std::map *ret, - std::string *err, const json &o, - const std::string &property, bool required, - const std::string &parent = "") { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - if (!parent.empty()) { - (*err) += - "'" + property + "' property is missing in " + parent + ".\n"; - } - else { - (*err) += "'" + property + "' property is missing.\n"; - } - } - } - return false; - } - - // Make sure we are dealing with an object / dictionary. - if (!it.value().is_object()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not an object.\n"; - } - } - return false; - } - - ret->clear(); - const json &dict = it.value(); - - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - if (!dictIt.value().is_number()) { - if (required) { - if (err) { - (*err) += "'" + property + "' value is not an int.\n"; - } - } - return false; - } - - // Insert into the list. - (*ret)[dictIt.key()] = static_cast(dictIt.value()); - } - return true; - } - - static bool ParseJSONProperty(std::map *ret, - std::string *err, const json &o, - const std::string &property, bool required) { - json::const_iterator it = o.find(property); - if (it == o.end()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is missing. \n'"; - } - } - return false; - } - - if (!it.value().is_object()) { - if (required) { - if (err) { - (*err) += "'" + property + "' property is not a JSON object.\n"; - } - } - return false; - } - - ret->clear(); - const json &obj = it.value(); - json::const_iterator it2(obj.begin()); - json::const_iterator itEnd(obj.end()); - for (; it2 != itEnd; it2++) { - if (it2.value().is_number()) - ret->insert(std::pair(it2.key(), it2.value())); - } - - return true; - } - - static bool ParseParameterProperty(Parameter *param, std::string *err, - const json &o, const std::string &prop, - bool required) { - // A parameter value can either be a string or an array of either a boolean or - // a number. Booleans of any kind aren't supported here. Granted, it - // complicates the Parameter structure and breaks it semantically in the sense - // that the client probably works off the assumption that if the string is - // empty the vector is used, etc. Would a tagged union work? - if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { - // Found string property. - return true; - } - else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, - false)) { - // Found a number array. - return true; - } - else if (ParseNumberProperty(¶m->number_value, err, o, prop, false)) { - return param->has_number_value = true; - } - else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, - false)) { - return true; - } - else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { - return true; - } - else { - if (required) { - if (err) { - (*err) += "parameter must be a string or number / number array.\n"; - } - } - return false; - } - } - - static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, - const json &o) { - (void)err; - - json::const_iterator it = o.find("extensions"); - if (it == o.end()) { - return false; - } - if (!it.value().is_object()) { - return false; - } - ExtensionMap extensions; - json::const_iterator extIt = it.value().begin(); - for (; extIt != it.value().end(); extIt++) { - if (!extIt.value().is_object()) continue; - if (!ParseJsonAsValue(&extensions[extIt.key()], extIt.value())) { - if (!extIt.key().empty()) { - // create empty object so that an extension object is still of type - // object - extensions[extIt.key()] = Value{ Value::Object{} }; - } - } - } - if (ret) { - (*ret) = extensions; - } - return true; - } - - static bool ParseAsset(Asset *asset, std::string *err, const json &o) { - ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); - ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); - ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); - - ParseExtensionsProperty(&asset->extensions, err, o); - - // Unity exporter version is added as extra here - ParseExtrasProperty(&(asset->extras), o); - - return true; - } - - static bool ParseImage(Image *image, const int image_idx, std::string *err, - std::string *warn, const json &o, - const std::string &basedir, FsCallbacks *fs, - LoadImageDataFunction *LoadImageData = nullptr, - void *load_image_user_data = nullptr) { - // A glTF image must either reference a bufferView or an image uri - - // schema says oneOf [`bufferView`, `uri`] - // TODO(syoyo): Check the type of each parameters. - bool hasBufferView = (o.find("bufferView") != o.end()); - bool hasURI = (o.find("uri") != o.end()); - - ParseStringProperty(&image->name, err, o, "name", false); - - if (hasBufferView && hasURI) { - // Should not both defined. - if (err) { - (*err) += - "Only one of `bufferView` or `uri` should be defined, but both are " - "defined for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + "\"\n"; - } - return false; - } - - if (!hasBufferView && !hasURI) { - if (err) { - (*err) += "Neither required `bufferView` nor `uri` defined for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - - ParseExtensionsProperty(&image->extensions, err, o); - ParseExtrasProperty(&image->extras, o); - - if (hasBufferView) { - double bufferView = -1; - if (!ParseNumberProperty(&bufferView, err, o, "bufferView", true)) { - if (err) { - (*err) += "Failed to parse `bufferView` for image[" + - std::to_string(image_idx) + "] name = \"" + image->name + - "\"\n"; - } - return false; - } - - std::string mime_type; - ParseStringProperty(&mime_type, err, o, "mimeType", false); - - double width = 0.0; - ParseNumberProperty(&width, err, o, "width", false); - - double height = 0.0; - ParseNumberProperty(&height, err, o, "height", false); - - // Just only save some information here. Loading actual image data from - // bufferView is done after this `ParseImage` function. - image->bufferView = static_cast(bufferView); - image->mimeType = mime_type; - image->width = static_cast(width); - image->height = static_cast(height); - - return true; - } - - // Parse URI & Load image data. - - std::string uri; - std::string tmp_err; - if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { - if (err) { - (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) + - "] name = \"" + image->name + "\".\n"; - } - return false; - } - - std::vector img; - - if (IsDataURI(uri)) { - if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { - if (err) { - (*err) += "Failed to decode 'uri' for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "]\n"; - } - return false; - } - } - else { - // Assume external file - // Keep texture path (for textures that cannot be decoded) - image->uri = uri; -#ifdef TINYGLTF_NO_EXTERNAL_IMAGE - return true; -#endif - if (!LoadExternalFile(&img, err, warn, uri, basedir, false, 0, false, fs)) { - if (warn) { - (*warn) += "Failed to load external 'uri' for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "]\n"; - } - // If the image cannot be loaded, keep uri as image->uri. - return true; - } - - if (img.empty()) { - if (warn) { - (*warn) += "Image data is empty for image[" + - std::to_string(image_idx) + "] name = [" + image->name + - "] \n"; - } - return false; - } - } - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(0), - static_cast(img.size()), load_image_user_data); - } - - static bool ParseTexture(Texture *texture, std::string *err, const json &o, - const std::string &basedir) { - (void)basedir; - double sampler = -1.0; - double source = -1.0; - ParseNumberProperty(&sampler, err, o, "sampler", false); - - ParseNumberProperty(&source, err, o, "source", false); - - texture->sampler = static_cast(sampler); - texture->source = static_cast(source); - - ParseExtensionsProperty(&texture->extensions, err, o); - ParseExtrasProperty(&texture->extras, o); - - ParseStringProperty(&texture->name, err, o, "name", false); - - return true; - } - - static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, - FsCallbacks *fs, const std::string &basedir, - bool is_binary = false, - const unsigned char *bin_data = nullptr, - size_t bin_size = 0) { - double byteLength; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, "Buffer")) { - return false; - } - - // In glTF 2.0, uri is not mandatory anymore - buffer->uri.clear(); - ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); - - // having an empty uri for a non embedded image should not be valid - if (!is_binary && buffer->uri.empty()) { - if (err) { - (*err) += "'uri' is missing from non binary glTF file buffer.\n"; - } - } - - json::const_iterator type = o.find("type"); - if (type != o.end()) { - if (type.value().is_string()) { - const std::string &ty = type.value(); - if (ty.compare("arraybuffer") == 0) { - // buffer.type = "arraybuffer"; - } - } - } - - size_t bytes = static_cast(byteLength); - if (is_binary) { - // Still binary glTF accepts external dataURI. - if (!buffer->uri.empty()) { - // First try embedded data URI. - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, - true)) { - if (err) { - (*err) += - "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } - else { - // External .bin file. - if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, - buffer->uri, basedir, true, bytes, true, fs)) { - return false; - } - } - } - else { - // load data from (embedded) binary data - - if ((bin_size == 0) || (bin_data == nullptr)) { - if (err) { - (*err) += "Invalid binary data in `Buffer'.\n"; - } - return false; - } - - if (byteLength > bin_size) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteLength'. Must be equal or less than binary size: " - "`byteLength' = " - << byteLength << ", binary size = " << bin_size << std::endl; - (*err) += ss.str(); - } - return false; - } - - // Read buffer data - buffer->data.resize(static_cast(byteLength)); - memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); - } - - } - else { - if (IsDataURI(buffer->uri)) { - std::string mime_type; - if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, bytes, true)) { - if (err) { - (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; - } - return false; - } - } - else { - // Assume external .bin file. - if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, buffer->uri, - basedir, true, bytes, true, fs)) { - return false; - } - } - } - - ParseStringProperty(&buffer->name, err, o, "name", false); - - return true; - } - - static bool ParseBufferView(BufferView *bufferView, std::string *err, - const json &o) { - double buffer = -1.0; - if (!ParseNumberProperty(&buffer, err, o, "buffer", true, "BufferView")) { - return false; - } - - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false); - - double byteLength = 1.0; - if (!ParseNumberProperty(&byteLength, err, o, "byteLength", true, - "BufferView")) { - return false; - } - - size_t byteStride = 0; - double byteStrideValue = 0.0; - if (!ParseNumberProperty(&byteStrideValue, err, o, "byteStride", false)) { - // Spec says: When byteStride of referenced bufferView is not defined, it - // means that accessor elements are tightly packed, i.e., effective stride - // equals the size of the element. - // We cannot determine the actual byteStride until Accessor are parsed, thus - // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) - byteStride = 0; - } - else { - byteStride = static_cast(byteStrideValue); - } - - if ((byteStride > 252) || ((byteStride % 4) != 0)) { - if (err) { - std::stringstream ss; - ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " - "4 : " - << byteStride << std::endl; - - (*err) += ss.str(); - } - return false; - } - - double target = 0.0; - ParseNumberProperty(&target, err, o, "target", false); - int targetValue = static_cast(target); - if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || - (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { - // OK - } - else { - targetValue = 0; - } - bufferView->target = targetValue; - - ParseStringProperty(&bufferView->name, err, o, "name", false); - - bufferView->buffer = static_cast(buffer); - bufferView->byteOffset = static_cast(byteOffset); - bufferView->byteLength = static_cast(byteLength); - bufferView->byteStride = static_cast(byteStride); - return true; - } - - static bool ParseSparseAccessor(Accessor *accessor, std::string *err, - const json &o) { - accessor->sparse.isSparse = true; - - double count = 0.0; - ParseNumberProperty(&count, err, o, "count", true); - - const auto indices_iterator = o.find("indices"); - const auto values_iterator = o.find("values"); - if (indices_iterator == o.end()) { - (*err) = "the sparse object of this accessor doesn't have indices"; - return false; - } - - if (values_iterator == o.end()) { - (*err) = "the sparse object ob ths accessor doesn't have values"; - return false; - } - - const json &indices_obj = *indices_iterator; - const json &values_obj = *values_iterator; - - double indices_buffer_view = 0.0, indices_byte_offset = 0.0, - component_type = 0.0; - ParseNumberProperty(&indices_buffer_view, err, indices_obj, "bufferView", - true); - ParseNumberProperty(&indices_byte_offset, err, indices_obj, "byteOffset", - true); - ParseNumberProperty(&component_type, err, indices_obj, "componentType", true); - - double values_buffer_view = 0.0, values_byte_offset = 0.0; - ParseNumberProperty(&values_buffer_view, err, values_obj, "bufferView", true); - ParseNumberProperty(&values_byte_offset, err, values_obj, "byteOffset", true); - - accessor->sparse.count = static_cast(count); - accessor->sparse.indices.bufferView = static_cast(indices_buffer_view); - accessor->sparse.indices.byteOffset = static_cast(indices_byte_offset); - accessor->sparse.indices.componentType = static_cast(component_type); - accessor->sparse.values.bufferView = static_cast(values_buffer_view); - accessor->sparse.values.byteOffset = static_cast(values_byte_offset); - - // todo check theses values - - return true; - } - - static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o) { - double bufferView = -1.0; - ParseNumberProperty(&bufferView, err, o, "bufferView", false, "Accessor"); - - double byteOffset = 0.0; - ParseNumberProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); - - bool normalized = false; - ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); - - double componentType = 0.0; - if (!ParseNumberProperty(&componentType, err, o, "componentType", true, - "Accessor")) { - return false; - } - - double count = 0.0; - if (!ParseNumberProperty(&count, err, o, "count", true, "Accessor")) { - return false; - } - - std::string type; - if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { - return false; - } - - if (type.compare("SCALAR") == 0) { - accessor->type = TINYGLTF_TYPE_SCALAR; - } - else if (type.compare("VEC2") == 0) { - accessor->type = TINYGLTF_TYPE_VEC2; - } - else if (type.compare("VEC3") == 0) { - accessor->type = TINYGLTF_TYPE_VEC3; - } - else if (type.compare("VEC4") == 0) { - accessor->type = TINYGLTF_TYPE_VEC4; - } - else if (type.compare("MAT2") == 0) { - accessor->type = TINYGLTF_TYPE_MAT2; - } - else if (type.compare("MAT3") == 0) { - accessor->type = TINYGLTF_TYPE_MAT3; - } - else if (type.compare("MAT4") == 0) { - accessor->type = TINYGLTF_TYPE_MAT4; - } - else { - std::stringstream ss; - ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - - ParseStringProperty(&accessor->name, err, o, "name", false); - - accessor->minValues.clear(); - accessor->maxValues.clear(); - ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, - "Accessor"); - - ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, - "Accessor"); - - accessor->count = static_cast(count); - accessor->bufferView = static_cast(bufferView); - accessor->byteOffset = static_cast(byteOffset); - accessor->normalized = normalized; - { - int comp = static_cast(componentType); - if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && - comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { - // OK - accessor->componentType = comp; - } - else { - std::stringstream ss; - ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; - if (err) { - (*err) += ss.str(); - } - return false; - } - } - - ParseExtrasProperty(&(accessor->extras), o); - - // check if accessor has a "sparse" object: - const auto iterator = o.find("sparse"); - if (iterator != o.end()) { - // here this accessor has a "sparse" subobject - return ParseSparseAccessor(accessor, err, *iterator); - } - - return true; - } - -#ifdef TINYGLTF_ENABLE_DRACO - - static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize, - std::vector &outBuffer) { - if (componentSize == 4) { - assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize); - memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0], - outBuffer.size()); - } - else { - size_t faceStride = componentSize * 3; - for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) { - const draco::Mesh::Face &face = mesh->face(f); - if (componentSize == 2) { - uint16_t indices[3] = { (uint16_t)face[0].value(), - (uint16_t)face[1].value(), - (uint16_t)face[2].value() }; - memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], - faceStride); - } - else { - uint8_t indices[3] = { (uint8_t)face[0].value(), - (uint8_t)face[1].value(), - (uint8_t)face[2].value() }; - memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], - faceStride); - } - } - } - } - - template - static bool GetAttributeForAllPoints(draco::Mesh *mesh, - const draco::PointAttribute *pAttribute, - std::vector &outBuffer) { - size_t byteOffset = 0; - T values[4] = { 0, 0, 0, 0 }; - for (draco::PointIndex i(0); i < mesh->num_points(); ++i) { - const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i); - if (!pAttribute->ConvertValue(val_index, pAttribute->num_components(), - values)) - return false; - - memcpy(outBuffer.data() + byteOffset, &values[0], - sizeof(T) * pAttribute->num_components()); - byteOffset += sizeof(T) * pAttribute->num_components(); - } - - return true; - } - - static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, - const draco::PointAttribute *pAttribute, - std::vector &outBuffer) { - bool decodeResult = false; - switch (componentType) { - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_BYTE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_SHORT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_INT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_FLOAT: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - case TINYGLTF_COMPONENT_TYPE_DOUBLE: - decodeResult = - GetAttributeForAllPoints(mesh, pAttribute, outBuffer); - break; - default: - return false; - } - - return decodeResult; - } - - static bool ParseDracoExtension(Primitive *primitive, Model *model, - std::string *err, - const Value &dracoExtensionValue) { - auto bufferViewValue = dracoExtensionValue.Get("bufferView"); - if (!bufferViewValue.IsInt()) return false; - auto attributesValue = dracoExtensionValue.Get("attributes"); - if (!attributesValue.IsObject()) return false; - - auto attributesObject = attributesValue.Get(); - int bufferView = bufferViewValue.Get(); - - BufferView &view = model->bufferViews[bufferView]; - Buffer &buffer = model->buffers[view.buffer]; - // BufferView has already been decoded - if (view.dracoDecoded) return true; - view.dracoDecoded = true; - - const char *bufferViewData = - reinterpret_cast(buffer.data.data() + view.byteOffset); - size_t bufferViewSize = view.byteLength; - - // decode draco - draco::DecoderBuffer decoderBuffer; - decoderBuffer.Init(bufferViewData, bufferViewSize); - draco::Decoder decoder; - auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); - if (!decodeResult.ok()) { - return false; - } - const std::unique_ptr &mesh = decodeResult.value(); - - // create new bufferView for indices - if (primitive->indices >= 0) { - int32_t componentSize = GetComponentSizeInBytes( - model->accessors[primitive->indices].componentType); - Buffer decodedIndexBuffer; - decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize); - - DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data); - - model->buffers.emplace_back(std::move(decodedIndexBuffer)); - - BufferView decodedIndexBufferView; - decodedIndexBufferView.buffer = int(model->buffers.size() - 1); - decodedIndexBufferView.byteLength = - int(mesh->num_faces() * 3 * componentSize); - decodedIndexBufferView.byteOffset = 0; - decodedIndexBufferView.byteStride = 0; - decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; - model->bufferViews.emplace_back(std::move(decodedIndexBufferView)); - - model->accessors[primitive->indices].bufferView = - int(model->bufferViews.size() - 1); - model->accessors[primitive->indices].count = int(mesh->num_faces() * 3); - } - - for (const auto &attribute : attributesObject) { - if (!attribute.second.IsInt()) return false; - auto primitiveAttribute = primitive->attributes.find(attribute.first); - if (primitiveAttribute == primitive->attributes.end()) return false; - - int dracoAttributeIndex = attribute.second.Get(); - const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); - const auto pBuffer = pAttribute->buffer(); - const auto componentType = - model->accessors[primitiveAttribute->second].componentType; - - // Create a new buffer for this decoded buffer - Buffer decodedBuffer; - size_t bufferSize = mesh->num_points() * pAttribute->num_components() * - GetComponentSizeInBytes(componentType); - decodedBuffer.data.resize(bufferSize); - - if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute, - decodedBuffer.data)) - return false; - - model->buffers.emplace_back(std::move(decodedBuffer)); - - BufferView decodedBufferView; - decodedBufferView.buffer = int(model->buffers.size() - 1); - decodedBufferView.byteLength = bufferSize; - decodedBufferView.byteOffset = pAttribute->byte_offset(); - decodedBufferView.byteStride = pAttribute->byte_stride(); - decodedBufferView.target = primitive->indices >= 0 - ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER - : TINYGLTF_TARGET_ARRAY_BUFFER; - model->bufferViews.emplace_back(std::move(decodedBufferView)); - - model->accessors[primitiveAttribute->second].bufferView = - int(model->bufferViews.size() - 1); - model->accessors[primitiveAttribute->second].count = - int(mesh->num_points()); - } - - return true; - } -#endif - - static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, - const json &o) { - double material = -1.0; - ParseNumberProperty(&material, err, o, "material", false); - primitive->material = static_cast(material); - - double mode = static_cast(TINYGLTF_MODE_TRIANGLES); - ParseNumberProperty(&mode, err, o, "mode", false); - - int primMode = static_cast(mode); - primitive->mode = primMode; // Why only triangled were supported ? - - double indices = -1.0; - ParseNumberProperty(&indices, err, o, "indices", false); - primitive->indices = static_cast(indices); - if (!ParseStringIntProperty(&primitive->attributes, err, o, "attributes", - true, "Primitive")) { - return false; - } - - // Look for morph targets - json::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && targetsObject.value().is_array()) { - for (json::const_iterator i = targetsObject.value().begin(); - i != targetsObject.value().end(); i++) { - std::map targetAttribues; - - const json &dict = i.value(); - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt.key()] = static_cast(dictIt.value()); - } - primitive->targets.push_back(targetAttribues); - } - } - - ParseExtrasProperty(&(primitive->extras), o); - - ParseExtensionsProperty(&primitive->extensions, err, o); - -#ifdef TINYGLTF_ENABLE_DRACO - auto dracoExtension = - primitive->extensions.find("KHR_draco_mesh_compression"); - if (dracoExtension != primitive->extensions.end()) { - ParseDracoExtension(primitive, model, err, dracoExtension->second); - } +static std::string MimeToExt(const std::string &mimeType) { + if (mimeType == "image/jpeg") { + return "jpg"; + } else if (mimeType == "image/png") { + return "png"; + } else if (mimeType == "image/bmp") { + return "bmp"; + } else if (mimeType == "image/gif") { + return "gif"; + } + + return ""; +} + +static void UpdateImageObject(Image &image, std::string &baseDir, int index, + bool embedImages, + WriteImageDataFunction *WriteImageData = nullptr, + void *user_data = nullptr) { + std::string filename; + std::string ext; + // If image has uri, use it it as a filename + if (image.uri.size()) { + filename = GetBaseFilename(image.uri); + ext = GetFilePathExtension(filename); + } else if (image.bufferView != -1) { + // If there's no URI and the data exists in a buffer, + // don't change properties or write images + } else if (image.name.size()) { + ext = MimeToExt(image.mimeType); + // Otherwise use name as filename + filename = image.name + "." + ext; + } else { + ext = MimeToExt(image.mimeType); + // Fallback to index of image as filename + filename = std::to_string(index) + "." + ext; + } + + // If callback is set, modify image data object + if (*WriteImageData != nullptr && !filename.empty()) { + std::string uri; + (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data); + } +} + +bool IsDataURI(const std::string &in) { + std::string header = "data:application/octet-stream;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + return true; + } + + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + return true; + } + + return false; +} + +bool DecodeDataURI(std::vector *out, std::string &mime_type, + const std::string &in, size_t reqBytes, bool checkSize) { + std::string header = "data:application/octet-stream;base64,"; + std::string data; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); // cut mime string. + } + + if (data.empty()) { + header = "data:image/jpeg;base64,"; + if (in.find(header) == 0) { + mime_type = "image/jpeg"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/png;base64,"; + if (in.find(header) == 0) { + mime_type = "image/png"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/bmp;base64,"; + if (in.find(header) == 0) { + mime_type = "image/bmp"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:image/gif;base64,"; + if (in.find(header) == 0) { + mime_type = "image/gif"; + data = base64_decode(in.substr(header.size())); // cut mime string. + } + } + + if (data.empty()) { + header = "data:text/plain;base64,"; + if (in.find(header) == 0) { + mime_type = "text/plain"; + data = base64_decode(in.substr(header.size())); + } + } + + if (data.empty()) { + header = "data:application/gltf-buffer;base64,"; + if (in.find(header) == 0) { + data = base64_decode(in.substr(header.size())); + } + } + + // TODO(syoyo): Allow empty buffer? #229 + if (data.empty()) { + return false; + } + + if (checkSize) { + if (data.size() != reqBytes) { + return false; + } + out->resize(reqBytes); + } else { + out->resize(data.size()); + } + std::copy(data.begin(), data.end(), out->begin()); + return true; +} + +namespace { +bool GetInt(const json &o, int &val) { +#ifdef TINYGLTF_USE_RAPIDJSON + if (!o.IsDouble()) { + if (o.IsInt()) { + val = o.GetInt(); + return true; + } else if (o.IsUint()) { + val = static_cast(o.GetUint()); + return true; + } else if (o.IsInt64()) { + val = static_cast(o.GetInt64()); + return true; + } else if (o.IsUint64()) { + val = static_cast(o.GetUint64()); + return true; + } + } + + return false; #else - (void)model; + auto type = o.type(); + + if ((type == json::value_t::number_integer) || + (type == json::value_t::number_unsigned)) { + val = static_cast(o.get()); + return true; + } + + return false; +#endif +} + +#ifdef TINYGLTF_USE_RAPIDJSON +bool GetDouble(const json &o, double &val) { + if (o.IsDouble()) { + val = o.GetDouble(); + return true; + } + + return false; +} #endif - return true; - } - - static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, - const json &o) { - ParseStringProperty(&mesh->name, err, o, "name", false); - - mesh->primitives.clear(); - json::const_iterator primObject = o.find("primitives"); - if ((primObject != o.end()) && primObject.value().is_array()) { - for (json::const_iterator i = primObject.value().begin(); - i != primObject.value().end(); i++) { - Primitive primitive; - if (ParsePrimitive(&primitive, model, err, i.value())) { - // Only add the primitive if the parsing succeeds. - mesh->primitives.push_back(primitive); - } - } - } - - // Look for morph targets - json::const_iterator targetsObject = o.find("targets"); - if ((targetsObject != o.end()) && targetsObject.value().is_array()) { - for (json::const_iterator i = targetsObject.value().begin(); - i != targetsObject.value().end(); i++) { - std::map targetAttribues; - - const json &dict = i.value(); - json::const_iterator dictIt(dict.begin()); - json::const_iterator dictItEnd(dict.end()); - - for (; dictIt != dictItEnd; ++dictIt) { - targetAttribues[dictIt.key()] = static_cast(dictIt.value()); - } - mesh->targets.push_back(targetAttribues); - } - } - - // 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); - - return true; - } - - static bool ParseLight(Light *light, std::string *err, const json &o) { - ParseStringProperty(&light->name, err, o, "name", false); - ParseNumberArrayProperty(&light->color, err, o, "color", false); - ParseStringProperty(&light->type, err, o, "type", false); - return true; - } - - static bool ParseNode(Node *node, std::string *err, const json &o) { - ParseStringProperty(&node->name, err, o, "name", false); - - double skin = -1.0; - ParseNumberProperty(&skin, err, o, "skin", false); - node->skin = static_cast(skin); - - // Matrix and T/R/S are exclusive - if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { - ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); - ParseNumberArrayProperty(&node->scale, err, o, "scale", false); - ParseNumberArrayProperty(&node->translation, err, o, "translation", false); - } - - double camera = -1.0; - ParseNumberProperty(&camera, err, o, "camera", false); - node->camera = static_cast(camera); - - double mesh = -1.0; - ParseNumberProperty(&mesh, err, o, "mesh", false); - node->mesh = int(mesh); - - node->children.clear(); - json::const_iterator childrenObject = o.find("children"); - if ((childrenObject != o.end()) && childrenObject.value().is_array()) { - for (json::const_iterator i = childrenObject.value().begin(); - i != childrenObject.value().end(); i++) { - if (!i.value().is_number()) { - if (err) { - (*err) += "Invalid `children` array.\n"; - } - return false; - } - const int &childrenNode = static_cast(i.value()); - node->children.push_back(childrenNode); - } - } - - ParseExtensionsProperty(&node->extensions, err, o); - ParseExtrasProperty(&(node->extras), o); - - return true; - } - - static bool ParseMaterial(Material *material, std::string *err, const json &o) { - material->values.clear(); - material->extensions.clear(); - material->additionalValues.clear(); - - json::const_iterator it(o.begin()); - json::const_iterator itEnd(o.end()); - - for (; it != itEnd; it++) { - if (it.key() == "pbrMetallicRoughness") { - if (it.value().is_object()) { - const json &values_object = it.value(); - - json::const_iterator itVal(values_object.begin()); - json::const_iterator itValEnd(values_object.end()); - - for (; itVal != itValEnd; itVal++) { - Parameter param; - if (ParseParameterProperty(¶m, err, values_object, itVal.key(), - false)) { - material->values[itVal.key()] = param; - } - } - } - } - else if (it.key() == "extensions" || it.key() == "extras") { - // done later, skip, otherwise poorly parsed contents will be saved in the - // parametermap and serialized again later - } - else { - Parameter param; - if (ParseParameterProperty(¶m, err, o, it.key(), false)) { - material->additionalValues[it.key()] = param; - } - } - } - - ParseExtensionsProperty(&material->extensions, err, o); - ParseExtrasProperty(&(material->extras), o); - - return true; - } - - static bool ParseAnimationChannel(AnimationChannel *channel, std::string *err, - const json &o) { - double samplerIndex = -1.0; - double targetIndex = -1.0; - if (!ParseNumberProperty(&samplerIndex, err, o, "sampler", true, - "AnimationChannel")) { - if (err) { - (*err) += "`sampler` field is missing in animation channels\n"; - } - return false; - } - - json::const_iterator targetIt = o.find("target"); - if ((targetIt != o.end()) && targetIt.value().is_object()) { - const json &target_object = targetIt.value(); - - if (!ParseNumberProperty(&targetIndex, err, target_object, "node", true)) { - if (err) { - (*err) += "`node` field is missing in animation.channels.target\n"; - } - return false; - } - - if (!ParseStringProperty(&channel->target_path, err, target_object, "path", - true)) { - if (err) { - (*err) += "`path` field is missing in animation.channels.target\n"; - } - return false; - } - } - - channel->sampler = static_cast(samplerIndex); - channel->target_node = static_cast(targetIndex); - - ParseExtrasProperty(&(channel->extras), o); - - return true; - } - - static bool ParseAnimation(Animation *animation, std::string *err, - const json &o) { - { - json::const_iterator channelsIt = o.find("channels"); - if ((channelsIt != o.end()) && channelsIt.value().is_array()) { - for (json::const_iterator i = channelsIt.value().begin(); - i != channelsIt.value().end(); i++) { - AnimationChannel channel; - if (ParseAnimationChannel(&channel, err, i.value())) { - // Only add the channel if the parsing succeeds. - animation->channels.push_back(channel); - } - } - } - } - - { - json::const_iterator samplerIt = o.find("samplers"); - if ((samplerIt != o.end()) && samplerIt.value().is_array()) { - const json &sampler_array = samplerIt.value(); - - json::const_iterator it = sampler_array.begin(); - json::const_iterator itEnd = sampler_array.end(); - - for (; it != itEnd; it++) { - const json &s = it->get(); - - AnimationSampler sampler; - double inputIndex = -1.0; - double outputIndex = -1.0; - if (!ParseNumberProperty(&inputIndex, err, s, "input", true)) { - if (err) { - (*err) += "`input` field is missing in animation.sampler\n"; - } - return false; - } - ParseStringProperty(&sampler.interpolation, err, s, "interpolation", - false); - if (!ParseNumberProperty(&outputIndex, err, s, "output", true)) { - if (err) { - (*err) += "`output` field is missing in animation.sampler\n"; - } - return false; - } - sampler.input = static_cast(inputIndex); - sampler.output = static_cast(outputIndex); - ParseExtrasProperty(&(sampler.extras), s); - animation->samplers.push_back(sampler); - } - } - } - - ParseStringProperty(&animation->name, err, o, "name", false); - - ParseExtrasProperty(&(animation->extras), o); - - return true; - } - - static bool ParseSampler(Sampler *sampler, std::string *err, const json &o) { - ParseStringProperty(&sampler->name, err, o, "name", false); - - double minFilter = - static_cast(TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR); - double magFilter = static_cast(TINYGLTF_TEXTURE_FILTER_LINEAR); - double wrapS = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); - double wrapT = static_cast(TINYGLTF_TEXTURE_WRAP_REPEAT); - ParseNumberProperty(&minFilter, err, o, "minFilter", false); - ParseNumberProperty(&magFilter, err, o, "magFilter", false); - ParseNumberProperty(&wrapS, err, o, "wrapS", false); - ParseNumberProperty(&wrapT, err, o, "wrapT", false); - - sampler->minFilter = static_cast(minFilter); - sampler->magFilter = static_cast(magFilter); - sampler->wrapS = static_cast(wrapS); - sampler->wrapT = static_cast(wrapT); - - ParseExtrasProperty(&(sampler->extras), o); - - return true; - } - - static bool ParseSkin(Skin *skin, std::string *err, const json &o) { - ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); - - std::vector joints; - if (!ParseNumberArrayProperty(&joints, err, o, "joints", false, "Skin")) { - return false; - } - - double skeleton = -1.0; - ParseNumberProperty(&skeleton, err, o, "skeleton", false, "Skin"); - skin->skeleton = static_cast(skeleton); - - skin->joints.resize(joints.size()); - for (size_t i = 0; i < joints.size(); i++) { - skin->joints[i] = static_cast(joints[i]); - } - - double invBind = -1.0; - ParseNumberProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); - skin->inverseBindMatrices = static_cast(invBind); - - return true; - } - - static bool ParsePerspectiveCamera(PerspectiveCamera *camera, std::string *err, - const json &o) { - double yfov = 0.0; - if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "PerspectiveCamera")) { - return false; - } - - double aspectRatio = 0.0; // = invalid - ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, - "PerspectiveCamera"); - - double zfar = 0.0; // = invalid - ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); - - camera->aspectRatio = aspectRatio; - camera->zfar = zfar; - camera->yfov = yfov; - camera->znear = znear; - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - // TODO(syoyo): Validate parameter values. - - return true; - } - - static bool ParseOrthographicCamera(OrthographicCamera *camera, - std::string *err, const json &o) { - double xmag = 0.0; - if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { - return false; - } - - double ymag = 0.0; - if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { - return false; - } - - double zfar = 0.0; - if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { - return false; - } - - double znear = 0.0; - if (!ParseNumberProperty(&znear, err, o, "znear", true, - "OrthographicCamera")) { - return false; - } - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - camera->xmag = xmag; - camera->ymag = ymag; - camera->zfar = zfar; - camera->znear = znear; - - // TODO(syoyo): Validate parameter values. - - return true; - } - - static bool ParseCamera(Camera *camera, std::string *err, const json &o) { - if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { - return false; - } - - if (camera->type.compare("orthographic") == 0) { - if (o.find("orthographic") == o.end()) { - if (err) { - std::stringstream ss; - ss << "Orhographic camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json &v = o.find("orthographic").value(); - if (!v.is_object()) { - if (err) { - std::stringstream ss; - ss << "\"orthographic\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParseOrthographicCamera(&camera->orthographic, err, v.get())) { - return false; - } - } - else if (camera->type.compare("perspective") == 0) { - if (o.find("perspective") == o.end()) { - if (err) { - std::stringstream ss; - ss << "Perspective camera description not found." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const json &v = o.find("perspective").value(); - if (!v.is_object()) { - if (err) { - std::stringstream ss; - ss << "\"perspective\" is not a JSON object." << std::endl; - (*err) += ss.str(); - } - return false; - } - - if (!ParsePerspectiveCamera(&camera->perspective, err, v.get())) { - return false; - } - } - else { - if (err) { - std::stringstream ss; - ss << "Invalid camera type: \"" << camera->type - << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; - (*err) += ss.str(); - } - return false; - } - - ParseStringProperty(&camera->name, err, o, "name", false); - - ParseExtensionsProperty(&camera->extensions, err, o); - ParseExtrasProperty(&(camera->extras), o); - - return true; - } - - bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, - const char *str, unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - if (length < 4) { - if (err) { - (*err) = "JSON string too short.\n"; - } - return false; - } - - json v; +bool GetNumber(const json &o, double &val) { +#ifdef TINYGLTF_USE_RAPIDJSON + if (o.IsNumber()) { + val = o.GetDouble(); + return true; + } + + return false; +#else + if (o.is_number()) { + val = o.get(); + return true; + } + + return false; +#endif +} + +bool GetString(const json &o, std::string &val) { +#ifdef TINYGLTF_USE_RAPIDJSON + if (o.IsString()) { + val = o.GetString(); + return true; + } + + return false; +#else + if (o.type() == json::value_t::string) { + val = o.get(); + return true; + } + + return false; +#endif +} + +bool IsArray(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.IsArray(); +#else + return o.is_array(); +#endif +} + +json_const_array_iterator ArrayBegin(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.Begin(); +#else + return o.begin(); +#endif +} + +json_const_array_iterator ArrayEnd(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.End(); +#else + return o.end(); +#endif +} + +bool IsObject(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.IsObject(); +#else + return o.is_object(); +#endif +} + +json_const_iterator ObjectBegin(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.MemberBegin(); +#else + return o.begin(); +#endif +} + +json_const_iterator ObjectEnd(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.MemberEnd(); +#else + return o.end(); +#endif +} + +// Making this a const char* results in a pointer to a temporary when +// TINYGLTF_USE_RAPIDJSON is off. +std::string GetKey(json_const_iterator &it) { +#ifdef TINYGLTF_USE_RAPIDJSON + return it->name.GetString(); +#else + return it.key().c_str(); +#endif +} + +bool FindMember(const json &o, const char *member, json_const_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 +} + +const json &GetValue(json_const_iterator &it) { +#ifdef TINYGLTF_USE_RAPIDJSON + return it->value; +#else + return it.value(); +#endif +} + +std::string JsonToString(const json &o, int spacing = -1) { +#ifdef TINYGLTF_USE_RAPIDJSON + using namespace rapidjson; + StringBuffer buffer; + if (spacing == -1) { + Writer writer(buffer); + o.Accept(writer); + } else { + PrettyWriter writer(buffer); + writer.SetIndent(' ', uint32_t(spacing)); + o.Accept(writer); + } + return buffer.GetString(); +#else + return o.dump(spacing); +#endif +} + +} // namespace + +static bool ParseJsonAsValue(Value *ret, const json &o) { + Value val{}; +#ifdef TINYGLTF_USE_RAPIDJSON + using rapidjson::Type; + switch (o.GetType()) { + case Type::kObjectType: { + Value::Object value_object; + for (auto it = o.MemberBegin(); it != o.MemberEnd(); ++it) { + Value entry; + ParseJsonAsValue(&entry, it->value); + if (entry.Type() != NULL_TYPE) + value_object.emplace(GetKey(it), std::move(entry)); + } + if (value_object.size() > 0) val = Value(std::move(value_object)); + } break; + case Type::kArrayType: { + Value::Array value_array; + value_array.reserve(o.Size()); + for (auto it = o.Begin(); it != o.End(); ++it) { + Value entry; + ParseJsonAsValue(&entry, *it); + if (entry.Type() != NULL_TYPE) + value_array.emplace_back(std::move(entry)); + } + if (value_array.size() > 0) val = Value(std::move(value_array)); + } break; + case Type::kStringType: + val = Value(std::string(o.GetString())); + break; + case Type::kFalseType: + case Type::kTrueType: + val = Value(o.GetBool()); + break; + case Type::kNumberType: + if (!o.IsDouble()) { + int i = 0; + GetInt(o, i); + val = Value(i); + } else { + double d = 0.0; + GetDouble(o, d); + val = Value(d); + } + break; + case Type::kNullType: + break; + // all types are covered, so no `case default` + } +#else + switch (o.type()) { + case json::value_t::object: { + Value::Object value_object; + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) + value_object.emplace(it.key(), std::move(entry)); + } + if (value_object.size() > 0) val = Value(std::move(value_object)); + } break; + case json::value_t::array: { + Value::Array value_array; + value_array.reserve(o.size()); + for (auto it = o.begin(); it != o.end(); it++) { + Value entry; + ParseJsonAsValue(&entry, it.value()); + if (entry.Type() != NULL_TYPE) + value_array.emplace_back(std::move(entry)); + } + if (value_array.size() > 0) val = Value(std::move(value_array)); + } break; + case json::value_t::string: + val = Value(o.get()); + break; + case json::value_t::boolean: + val = Value(o.get()); + break; + case json::value_t::number_integer: + case json::value_t::number_unsigned: + val = Value(static_cast(o.get())); + break; + case json::value_t::number_float: + val = Value(o.get()); + break; + case json::value_t::null: + case json::value_t::discarded: + // default: + break; + } +#endif + if (ret) *ret = std::move(val); + + return val.Type() != NULL_TYPE; +} + +static bool ParseExtrasProperty(Value *ret, const json &o) { + json_const_iterator it; + if (!FindMember(o, "extras", it)) { + return false; + } + + return ParseJsonAsValue(ret, GetValue(it)); +} + +static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + auto &value = GetValue(it); + + bool isBoolean; + bool boolValue = false; +#ifdef TINYGLTF_USE_RAPIDJSON + isBoolean = value.IsBool(); + if (isBoolean) { + boolValue = value.GetBool(); + } +#else + isBoolean = value.is_boolean(); + if (isBoolean) { + boolValue = value.get(); + } +#endif + if (!isBoolean) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a bool type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = boolValue; + } + + return true; +} + +static bool ParseIntegerProperty(int *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + int intValue; + bool isInt = GetInt(GetValue(it), intValue); + if (!isInt) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = intValue; + } + + return true; +} + +static bool ParseUnsignedProperty(size_t *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + auto &value = GetValue(it); + + size_t uValue = 0; + bool isUValue; +#ifdef TINYGLTF_USE_RAPIDJSON + isUValue = false; + if (value.IsUint()) { + uValue = value.GetUint(); + isUValue = true; + } else if (value.IsUint64()) { + uValue = value.GetUint64(); + isUValue = true; + } +#else + isUValue = value.is_number_unsigned(); + if (isUValue) { + uValue = value.get(); + } +#endif + if (!isUValue) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a positive integer.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = uValue; + } + + return true; +} + +static bool ParseNumberProperty(double *ret, std::string *err, const json &o, + const std::string &property, + const bool required, + const std::string &parent_node = "") { + json_const_iterator it; + + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + double numberValue; + bool isNumber = GetNumber(GetValue(it), numberValue); + + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = numberValue; + } + + return true; +} + +static bool ParseNumberArrayProperty(std::vector *ret, std::string *err, + const json &o, const std::string &property, + bool required, + const std::string &parent_node = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!IsArray(GetValue(it))) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + ret->clear(); + auto end = ArrayEnd(GetValue(it)); + for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) { + double numberValue; + const bool isNumber = GetNumber(*i, numberValue); + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a number.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(numberValue); + } + + return true; +} + +static bool ParseIntegerArrayProperty(std::vector *ret, std::string *err, + const json &o, + const std::string &property, + bool required, + const std::string &parent_node = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + if (!IsArray(GetValue(it))) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an array"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + + ret->clear(); + auto end = ArrayEnd(GetValue(it)); + for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) { + int numberValue; + bool isNumber = GetInt(*i, numberValue); + if (!isNumber) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an integer type.\n"; + if (!parent_node.empty()) { + (*err) += " in " + parent_node; + } + (*err) += ".\n"; + } + } + return false; + } + ret->push_back(numberValue); + } + + return true; +} + +static bool ParseStringProperty( + std::string *ret, std::string *err, const json &o, + const std::string &property, bool required, + const std::string &parent_node = std::string()) { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing"; + if (parent_node.empty()) { + (*err) += ".\n"; + } else { + (*err) += " in `" + parent_node + "'.\n"; + } + } + } + return false; + } + + std::string strValue; + if (!GetString(GetValue(it), strValue)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a string type.\n"; + } + } + return false; + } + + if (ret) { + (*ret) = std::move(strValue); + } + + return true; +} + +static bool ParseStringIntegerProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, + bool required, + const std::string &parent = "") { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + if (!parent.empty()) { + (*err) += + "'" + property + "' property is missing in " + parent + ".\n"; + } else { + (*err) += "'" + property + "' property is missing.\n"; + } + } + } + return false; + } + + const json &dict = GetValue(it); + + // Make sure we are dealing with an object / dictionary. + if (!IsObject(dict)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not an object.\n"; + } + } + return false; + } + + ret->clear(); + + json_const_iterator dictIt(ObjectBegin(dict)); + json_const_iterator dictItEnd(ObjectEnd(dict)); + + for (; dictIt != dictItEnd; ++dictIt) { + int intVal; + if (!GetInt(GetValue(dictIt), intVal)) { + if (required) { + if (err) { + (*err) += "'" + property + "' value is not an integer type.\n"; + } + } + return false; + } + + // Insert into the list. + (*ret)[GetKey(dictIt)] = intVal; + } + return true; +} + +static bool ParseJSONProperty(std::map *ret, + std::string *err, const json &o, + const std::string &property, bool required) { + json_const_iterator it; + if (!FindMember(o, property.c_str(), it)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is missing. \n'"; + } + } + return false; + } + + const json &obj = GetValue(it); + + if (!IsObject(obj)) { + if (required) { + if (err) { + (*err) += "'" + property + "' property is not a JSON object.\n"; + } + } + return false; + } + + ret->clear(); + + json_const_iterator it2(ObjectBegin(obj)); + json_const_iterator itEnd(ObjectEnd(obj)); + for (; it2 != itEnd; ++it2) { + double numVal; + if (GetNumber(GetValue(it2), numVal)) + ret->emplace(std::string(GetKey(it2)), numVal); + } + + return true; +} + +static bool ParseParameterProperty(Parameter *param, std::string *err, + const json &o, const std::string &prop, + bool required) { + // A parameter value can either be a string or an array of either a boolean or + // a number. Booleans of any kind aren't supported here. Granted, it + // complicates the Parameter structure and breaks it semantically in the sense + // that the client probably works off the assumption that if the string is + // empty the vector is used, etc. Would a tagged union work? + if (ParseStringProperty(¶m->string_value, err, o, prop, false)) { + // Found string property. + return true; + } else if (ParseNumberArrayProperty(¶m->number_array, err, o, prop, + false)) { + // Found a number array. + return true; + } else if (ParseNumberProperty(¶m->number_value, err, o, prop, false)) { + return param->has_number_value = true; + } else if (ParseJSONProperty(¶m->json_double_value, err, o, prop, + false)) { + return true; + } else if (ParseBooleanProperty(¶m->bool_value, err, o, prop, false)) { + return true; + } else { + if (required) { + if (err) { + (*err) += "parameter must be a string or number / number array.\n"; + } + } + return false; + } +} + +static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err, + const json &o) { + (void)err; + + json_const_iterator it; + if (!FindMember(o, "extensions", it)) { + return false; + } + + auto &obj = GetValue(it); + if (!IsObject(obj)) { + return false; + } + ExtensionMap extensions; + json_const_iterator extIt = ObjectBegin(obj); // it.value().begin(); + json_const_iterator extEnd = ObjectEnd(obj); + for (; extIt != extEnd; ++extIt) { + auto &itObj = GetValue(extIt); + if (!IsObject(itObj)) continue; + std::string key(GetKey(extIt)); + if (!ParseJsonAsValue(&extensions[key], itObj)) { + if (!key.empty()) { + // create empty object so that an extension object is still of type + // object + extensions[key] = Value{Value::Object{}}; + } + } + } + if (ret) { + (*ret) = std::move(extensions); + } + return true; +} + +static bool ParseAsset(Asset *asset, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&asset->version, err, o, "version", true, "Asset"); + ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset"); + ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset"); + ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset"); + + 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) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + asset->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + asset->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseImage(Image *image, const int image_idx, std::string *err, + std::string *warn, const json &o, + bool store_original_json_for_extras_and_extensions, + const std::string &basedir, FsCallbacks *fs, + LoadImageDataFunction *LoadImageData = nullptr, + void *load_image_user_data = nullptr) { + // A glTF image must either reference a bufferView or an image uri + + // schema says oneOf [`bufferView`, `uri`] + // TODO(syoyo): Check the type of each parameters. + json_const_iterator it; + bool hasBufferView = FindMember(o, "bufferView", it); + bool hasURI = FindMember(o, "uri", it); + + ParseStringProperty(&image->name, err, o, "name", false); + + if (hasBufferView && hasURI) { + // Should not both defined. + if (err) { + (*err) += + "Only one of `bufferView` or `uri` should be defined, but both are " + "defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + "\"\n"; + } + return false; + } + + if (!hasBufferView && !hasURI) { + if (err) { + (*err) += "Neither required `bufferView` nor `uri` defined for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + ParseExtensionsProperty(&image->extensions, err, o); + ParseExtrasProperty(&image->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator eit; + if (FindMember(o, "extensions", eit)) { + image->extensions_json_string = JsonToString(GetValue(eit)); + } + } + { + json_const_iterator eit; + if (FindMember(o, "extras", eit)) { + image->extras_json_string = JsonToString(GetValue(eit)); + } + } + } + + if (hasBufferView) { + int bufferView = -1; + if (!ParseIntegerProperty(&bufferView, err, o, "bufferView", true)) { + if (err) { + (*err) += "Failed to parse `bufferView` for image[" + + std::to_string(image_idx) + "] name = \"" + image->name + + "\"\n"; + } + return false; + } + + std::string mime_type; + ParseStringProperty(&mime_type, err, o, "mimeType", false); + + int width = 0; + ParseIntegerProperty(&width, err, o, "width", false); + + int height = 0; + ParseIntegerProperty(&height, err, o, "height", false); + + // Just only save some information here. Loading actual image data from + // bufferView is done after this `ParseImage` function. + image->bufferView = bufferView; + image->mimeType = mime_type; + image->width = width; + image->height = height; + + return true; + } + + // Parse URI & Load image data. + + std::string uri; + std::string tmp_err; + if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) { + if (err) { + (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) + + "] name = \"" + image->name + "\".\n"; + } + return false; + } + + std::vector img; + + if (IsDataURI(uri)) { + if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) { + if (err) { + (*err) += "Failed to decode 'uri' for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "]\n"; + } + return false; + } + } else { + // Assume external file + // Keep texture path (for textures that cannot be decoded) + image->uri = uri; +#ifdef TINYGLTF_NO_EXTERNAL_IMAGE + return true; +#endif + std::string decoded_uri = dlib::urldecode(uri); + if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir, + /* required */ false, /* required bytes */ 0, + /* checksize */ false, fs)) { + if (warn) { + (*warn) += "Failed to load external 'uri' for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "]\n"; + } + // If the image cannot be loaded, keep uri as image->uri. + return true; + } + + if (img.empty()) { + if (warn) { + (*warn) += "Image data is empty for image[" + + std::to_string(image_idx) + "] name = [" + image->name + + "] \n"; + } + return false; + } + } + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(0), + static_cast(img.size()), load_image_user_data); +} + +static bool ParseTexture(Texture *texture, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions, + const std::string &basedir) { + (void)basedir; + int sampler = -1; + int source = -1; + ParseIntegerProperty(&sampler, err, o, "sampler", false); + + ParseIntegerProperty(&source, err, o, "source", false); + + texture->sampler = sampler; + texture->source = source; + + ParseExtensionsProperty(&texture->extensions, err, o); + ParseExtrasProperty(&texture->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + texture->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + texture->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + ParseStringProperty(&texture->name, err, o, "name", false); + + return true; +} + +static bool ParseTextureInfo( + TextureInfo *texinfo, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "TextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + + ParseExtensionsProperty(&texinfo->extensions, err, o); + ParseExtrasProperty(&texinfo->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + texinfo->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + texinfo->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseNormalTextureInfo( + NormalTextureInfo *texinfo, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "NormalTextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + ParseNumberProperty(&texinfo->scale, err, o, "scale", false); + + ParseExtensionsProperty(&texinfo->extensions, err, o); + ParseExtrasProperty(&texinfo->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + texinfo->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + texinfo->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseOcclusionTextureInfo( + OcclusionTextureInfo *texinfo, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (texinfo == nullptr) { + return false; + } + + if (!ParseIntegerProperty(&texinfo->index, err, o, "index", + /* required */ true, "NormalTextureInfo")) { + return false; + } + + ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false); + ParseNumberProperty(&texinfo->strength, err, o, "strength", false); + + ParseExtensionsProperty(&texinfo->extensions, err, o); + ParseExtrasProperty(&texinfo->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + texinfo->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + texinfo->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions, + FsCallbacks *fs, const std::string &basedir, + bool is_binary = false, + const unsigned char *bin_data = nullptr, + size_t bin_size = 0) { + size_t byteLength; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "Buffer")) { + return false; + } + + // In glTF 2.0, uri is not mandatory anymore + buffer->uri.clear(); + ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer"); + + // having an empty uri for a non embedded image should not be valid + if (!is_binary && buffer->uri.empty()) { + if (err) { + (*err) += "'uri' is missing from non binary glTF file buffer.\n"; + } + } + + json_const_iterator type; + if (FindMember(o, "type", type)) { + std::string typeStr; + if (GetString(GetValue(type), typeStr)) { + if (typeStr.compare("arraybuffer") == 0) { + // buffer.type = "arraybuffer"; + } + } + } + + if (is_binary) { + // Still binary glTF accepts external dataURI. + if (!buffer->uri.empty()) { + // First try embedded data URI. + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, + true)) { + if (err) { + (*err) += + "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // External .bin file. + std::string decoded_uri = dlib::urldecode(buffer->uri); + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, + decoded_uri, basedir, /* required */ true, + byteLength, /* checkSize */ true, fs)) { + return false; + } + } + } else { + // load data from (embedded) binary data + + if ((bin_size == 0) || (bin_data == nullptr)) { + if (err) { + (*err) += "Invalid binary data in `Buffer'.\n"; + } + return false; + } + + if (byteLength > bin_size) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteLength'. Must be equal or less than binary size: " + "`byteLength' = " + << byteLength << ", binary size = " << bin_size << std::endl; + (*err) += ss.str(); + } + return false; + } + + // Read buffer data + buffer->data.resize(static_cast(byteLength)); + memcpy(&(buffer->data.at(0)), bin_data, static_cast(byteLength)); + } + + } else { + if (IsDataURI(buffer->uri)) { + std::string mime_type; + if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength, + true)) { + if (err) { + (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n"; + } + return false; + } + } else { + // Assume external .bin file. + std::string decoded_uri = dlib::urldecode(buffer->uri); + if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri, + basedir, /* required */ true, byteLength, + /* checkSize */ true, fs)) { + return false; + } + } + } + + ParseStringProperty(&buffer->name, err, o, "name", false); + + ParseExtensionsProperty(&buffer->extensions, err, o); + ParseExtrasProperty(&buffer->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + buffer->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + buffer->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseBufferView( + BufferView *bufferView, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + int buffer = -1; + if (!ParseIntegerProperty(&buffer, err, o, "buffer", true, "BufferView")) { + return false; + } + + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false); + + size_t byteLength = 1; + if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true, + "BufferView")) { + return false; + } + + size_t byteStride = 0; + if (!ParseUnsignedProperty(&byteStride, err, o, "byteStride", false)) { + // Spec says: When byteStride of referenced bufferView is not defined, it + // means that accessor elements are tightly packed, i.e., effective stride + // equals the size of the element. + // We cannot determine the actual byteStride until Accessor are parsed, thus + // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner) + byteStride = 0; + } + + if ((byteStride > 252) || ((byteStride % 4) != 0)) { + if (err) { + std::stringstream ss; + ss << "Invalid `byteStride' value. `byteStride' must be the multiple of " + "4 : " + << byteStride << std::endl; + + (*err) += ss.str(); + } + return false; + } + + int target = 0; + ParseIntegerProperty(&target, err, o, "target", false); + if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) || + (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { + // OK + } else { + target = 0; + } + bufferView->target = target; + + ParseStringProperty(&bufferView->name, err, o, "name", false); + + ParseExtensionsProperty(&bufferView->extensions, err, o); + ParseExtrasProperty(&bufferView->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + bufferView->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + bufferView->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + bufferView->buffer = buffer; + bufferView->byteOffset = byteOffset; + bufferView->byteLength = byteLength; + bufferView->byteStride = byteStride; + return true; +} + +static bool ParseSparseAccessor(Accessor *accessor, std::string *err, + const json &o) { + accessor->sparse.isSparse = true; + + int count = 0; + ParseIntegerProperty(&count, err, o, "count", true); + + json_const_iterator indices_iterator; + json_const_iterator values_iterator; + if (!FindMember(o, "indices", indices_iterator)) { + (*err) = "the sparse object of this accessor doesn't have indices"; + return false; + } + + if (!FindMember(o, "values", values_iterator)) { + (*err) = "the sparse object ob ths accessor doesn't have values"; + return false; + } + + const json &indices_obj = GetValue(indices_iterator); + const json &values_obj = GetValue(values_iterator); + + int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0; + ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView", + true); + ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset", + true); + ParseIntegerProperty(&component_type, err, indices_obj, "componentType", + true); + + int values_buffer_view = 0, values_byte_offset = 0; + ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView", + true); + ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset", + true); + + 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; + + // todo check theses values + + return true; +} + +static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + int bufferView = -1; + ParseIntegerProperty(&bufferView, err, o, "bufferView", false, "Accessor"); + + size_t byteOffset = 0; + ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false, "Accessor"); + + bool normalized = false; + ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor"); + + size_t componentType = 0; + if (!ParseUnsignedProperty(&componentType, err, o, "componentType", true, + "Accessor")) { + return false; + } + + size_t count = 0; + if (!ParseUnsignedProperty(&count, err, o, "count", true, "Accessor")) { + return false; + } + + std::string type; + if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) { + return false; + } + + if (type.compare("SCALAR") == 0) { + accessor->type = TINYGLTF_TYPE_SCALAR; + } else if (type.compare("VEC2") == 0) { + accessor->type = TINYGLTF_TYPE_VEC2; + } else if (type.compare("VEC3") == 0) { + accessor->type = TINYGLTF_TYPE_VEC3; + } else if (type.compare("VEC4") == 0) { + accessor->type = TINYGLTF_TYPE_VEC4; + } else if (type.compare("MAT2") == 0) { + accessor->type = TINYGLTF_TYPE_MAT2; + } else if (type.compare("MAT3") == 0) { + accessor->type = TINYGLTF_TYPE_MAT3; + } else if (type.compare("MAT4") == 0) { + accessor->type = TINYGLTF_TYPE_MAT4; + } else { + std::stringstream ss; + ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&accessor->name, err, o, "name", false); + + accessor->minValues.clear(); + accessor->maxValues.clear(); + ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false, + "Accessor"); + + ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false, + "Accessor"); + + accessor->count = count; + accessor->bufferView = bufferView; + accessor->byteOffset = byteOffset; + accessor->normalized = normalized; + { + if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE && + componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { + // OK + accessor->componentType = int(componentType); + } else { + std::stringstream ss; + ss << "Invalid `componentType` in accessor. Got " << componentType + << "\n"; + if (err) { + (*err) += ss.str(); + } + return false; + } + } + + ParseExtensionsProperty(&(accessor->extensions), err, o); + ParseExtrasProperty(&(accessor->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + accessor->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + accessor->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + // check if accessor has a "sparse" object: + json_const_iterator iterator; + if (FindMember(o, "sparse", iterator)) { + // here this accessor has a "sparse" subobject + return ParseSparseAccessor(accessor, err, GetValue(iterator)); + } + + return true; +} + +#ifdef TINYGLTF_ENABLE_DRACO + +static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize, + std::vector &outBuffer) { + if (componentSize == 4) { + assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize); + memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0], + outBuffer.size()); + } else { + size_t faceStride = componentSize * 3; + for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) { + const draco::Mesh::Face &face = mesh->face(f); + if (componentSize == 2) { + uint16_t indices[3] = {(uint16_t)face[0].value(), + (uint16_t)face[1].value(), + (uint16_t)face[2].value()}; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } else { + uint8_t indices[3] = {(uint8_t)face[0].value(), + (uint8_t)face[1].value(), + (uint8_t)face[2].value()}; + memcpy(outBuffer.data() + f.value() * faceStride, &indices[0], + faceStride); + } + } + } +} + +template +static bool GetAttributeForAllPoints(draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + size_t byteOffset = 0; + T values[4] = {0, 0, 0, 0}; + for (draco::PointIndex i(0); i < mesh->num_points(); ++i) { + const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i); + if (!pAttribute->ConvertValue(val_index, pAttribute->num_components(), + values)) + return false; + + memcpy(outBuffer.data() + byteOffset, &values[0], + sizeof(T) * pAttribute->num_components()); + byteOffset += sizeof(T) * pAttribute->num_components(); + } + + return true; +} + +static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh, + const draco::PointAttribute *pAttribute, + std::vector &outBuffer) { + bool decodeResult = false; + switch (componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_BYTE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_SHORT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + case TINYGLTF_COMPONENT_TYPE_DOUBLE: + decodeResult = + GetAttributeForAllPoints(mesh, pAttribute, outBuffer); + break; + default: + return false; + } + + return decodeResult; +} + +static bool ParseDracoExtension(Primitive *primitive, Model *model, + std::string *err, + const Value &dracoExtensionValue) { + auto bufferViewValue = dracoExtensionValue.Get("bufferView"); + if (!bufferViewValue.IsInt()) return false; + auto attributesValue = dracoExtensionValue.Get("attributes"); + if (!attributesValue.IsObject()) return false; + + auto attributesObject = attributesValue.Get(); + int bufferView = bufferViewValue.Get(); + + BufferView &view = model->bufferViews[bufferView]; + Buffer &buffer = model->buffers[view.buffer]; + // BufferView has already been decoded + if (view.dracoDecoded) return true; + view.dracoDecoded = true; + + const char *bufferViewData = + reinterpret_cast(buffer.data.data() + view.byteOffset); + size_t bufferViewSize = view.byteLength; + + // decode draco + draco::DecoderBuffer decoderBuffer; + decoderBuffer.Init(bufferViewData, bufferViewSize); + draco::Decoder decoder; + auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); + if (!decodeResult.ok()) { + return false; + } + const std::unique_ptr &mesh = decodeResult.value(); + + // create new bufferView for indices + if (primitive->indices >= 0) { + int32_t componentSize = GetComponentSizeInBytes( + model->accessors[primitive->indices].componentType); + Buffer decodedIndexBuffer; + decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize); + + DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data); + + model->buffers.emplace_back(std::move(decodedIndexBuffer)); + + BufferView decodedIndexBufferView; + decodedIndexBufferView.buffer = int(model->buffers.size() - 1); + decodedIndexBufferView.byteLength = + int(mesh->num_faces() * 3 * componentSize); + decodedIndexBufferView.byteOffset = 0; + decodedIndexBufferView.byteStride = 0; + decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedIndexBufferView)); + + model->accessors[primitive->indices].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitive->indices].count = int(mesh->num_faces() * 3); + } + + for (const auto &attribute : attributesObject) { + if (!attribute.second.IsInt()) return false; + auto primitiveAttribute = primitive->attributes.find(attribute.first); + if (primitiveAttribute == primitive->attributes.end()) return false; + + int dracoAttributeIndex = attribute.second.Get(); + const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex); + const auto pBuffer = pAttribute->buffer(); + const auto componentType = + model->accessors[primitiveAttribute->second].componentType; + + // Create a new buffer for this decoded buffer + Buffer decodedBuffer; + size_t bufferSize = mesh->num_points() * pAttribute->num_components() * + GetComponentSizeInBytes(componentType); + decodedBuffer.data.resize(bufferSize); + + if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute, + decodedBuffer.data)) + return false; + + model->buffers.emplace_back(std::move(decodedBuffer)); + + BufferView decodedBufferView; + decodedBufferView.buffer = int(model->buffers.size() - 1); + decodedBufferView.byteLength = bufferSize; + decodedBufferView.byteOffset = pAttribute->byte_offset(); + decodedBufferView.byteStride = pAttribute->byte_stride(); + decodedBufferView.target = primitive->indices >= 0 + ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER + : TINYGLTF_TARGET_ARRAY_BUFFER; + model->bufferViews.emplace_back(std::move(decodedBufferView)); + + model->accessors[primitiveAttribute->second].bufferView = + int(model->bufferViews.size() - 1); + model->accessors[primitiveAttribute->second].count = + int(mesh->num_points()); + } + + return true; +} +#endif + +static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err, + const json &o, + bool store_original_json_for_extras_and_extensions) { + int material = -1; + ParseIntegerProperty(&material, err, o, "material", false); + primitive->material = material; + + int mode = TINYGLTF_MODE_TRIANGLES; + ParseIntegerProperty(&mode, err, o, "mode", false); + primitive->mode = mode; // Why only triangled were supported ? + + int indices = -1; + ParseIntegerProperty(&indices, err, o, "indices", false); + primitive->indices = indices; + if (!ParseStringIntegerProperty(&primitive->attributes, err, o, "attributes", + true, "Primitive")) { + return false; + } + + // Look for morph targets + json_const_iterator targetsObject; + if (FindMember(o, "targets", targetsObject) && + IsArray(GetValue(targetsObject))) { + auto targetsObjectEnd = ArrayEnd(GetValue(targetsObject)); + for (json_const_array_iterator i = ArrayBegin(GetValue(targetsObject)); + i != targetsObjectEnd; ++i) { + std::map targetAttribues; + + const json &dict = *i; + if (IsObject(dict)) { + json_const_iterator dictIt(ObjectBegin(dict)); + json_const_iterator dictItEnd(ObjectEnd(dict)); + + for (; dictIt != dictItEnd; ++dictIt) { + int iVal; + if (GetInt(GetValue(dictIt), iVal)) + targetAttribues[GetKey(dictIt)] = iVal; + } + primitive->targets.emplace_back(std::move(targetAttribues)); + } + } + } + + ParseExtrasProperty(&(primitive->extras), o); + ParseExtensionsProperty(&primitive->extensions, err, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + primitive->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + primitive->extras_json_string = JsonToString(GetValue(it)); + } + } + } + +#ifdef TINYGLTF_ENABLE_DRACO + auto dracoExtension = + primitive->extensions.find("KHR_draco_mesh_compression"); + if (dracoExtension != primitive->extensions.end()) { + ParseDracoExtension(primitive, model, err, dracoExtension->second); + } +#else + (void)model; +#endif + + return true; +} + +static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&mesh->name, err, o, "name", false); + + mesh->primitives.clear(); + json_const_iterator primObject; + if (FindMember(o, "primitives", primObject) && + IsArray(GetValue(primObject))) { + json_const_array_iterator primEnd = ArrayEnd(GetValue(primObject)); + for (json_const_array_iterator i = ArrayBegin(GetValue(primObject)); + i != primEnd; ++i) { + Primitive primitive; + if (ParsePrimitive(&primitive, model, err, *i, + store_original_json_for_extras_and_extensions)) { + // Only add the primitive if the parsing succeeds. + mesh->primitives.emplace_back(std::move(primitive)); + } + } + } + + // Should probably check if has targets and if dimensions fit + ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false); + + ParseExtensionsProperty(&mesh->extensions, err, o); + ParseExtrasProperty(&(mesh->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + mesh->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + mesh->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseNode(Node *node, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&node->name, err, o, "name", false); + + int skin = -1; + ParseIntegerProperty(&skin, err, o, "skin", false); + node->skin = skin; + + // Matrix and T/R/S are exclusive + if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) { + ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false); + ParseNumberArrayProperty(&node->scale, err, o, "scale", false); + ParseNumberArrayProperty(&node->translation, err, o, "translation", false); + } + + int camera = -1; + ParseIntegerProperty(&camera, err, o, "camera", false); + node->camera = camera; + + int mesh = -1; + ParseIntegerProperty(&mesh, err, o, "mesh", false); + node->mesh = mesh; + + node->children.clear(); + ParseIntegerArrayProperty(&node->children, err, o, "children", false); + + ParseNumberArrayProperty(&node->weights, err, o, "weights", false); + + ParseExtensionsProperty(&node->extensions, err, o); + ParseExtrasProperty(&(node->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + node->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + node->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParsePbrMetallicRoughness( + PbrMetallicRoughness *pbr, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (pbr == nullptr) { + return false; + } + + std::vector baseColorFactor; + if (ParseNumberArrayProperty(&baseColorFactor, err, o, "baseColorFactor", + /* required */ false)) { + if (baseColorFactor.size() != 4) { + if (err) { + (*err) += + "Array length of `baseColorFactor` parameter in " + "pbrMetallicRoughness must be 4, but got " + + std::to_string(baseColorFactor.size()) + "\n"; + } + return false; + } + pbr->baseColorFactor = baseColorFactor; + } + + { + json_const_iterator it; + if (FindMember(o, "baseColorTexture", it)) { + ParseTextureInfo(&pbr->baseColorTexture, err, GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + json_const_iterator it; + if (FindMember(o, "metallicRoughnessTexture", it)) { + ParseTextureInfo(&pbr->metallicRoughnessTexture, err, GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + ParseNumberProperty(&pbr->metallicFactor, err, o, "metallicFactor", false); + ParseNumberProperty(&pbr->roughnessFactor, err, o, "roughnessFactor", false); + + ParseExtensionsProperty(&pbr->extensions, err, o); + ParseExtrasProperty(&pbr->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + pbr->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + pbr->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseMaterial(Material *material, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&material->name, err, o, "name", /* required */ false); + + if (ParseNumberArrayProperty(&material->emissiveFactor, err, o, + "emissiveFactor", + /* required */ false)) { + if (material->emissiveFactor.size() != 3) { + if (err) { + (*err) += + "Array length of `emissiveFactor` parameter in " + "material must be 3, but got " + + std::to_string(material->emissiveFactor.size()) + "\n"; + } + return false; + } + } else { + // fill with default values + material->emissiveFactor = {0.0, 0.0, 0.0}; + } + + ParseStringProperty(&material->alphaMode, err, o, "alphaMode", + /* required */ false); + ParseNumberProperty(&material->alphaCutoff, err, o, "alphaCutoff", + /* required */ false); + ParseBooleanProperty(&material->doubleSided, err, o, "doubleSided", + /* required */ false); + + { + json_const_iterator it; + if (FindMember(o, "pbrMetallicRoughness", it)) { + ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err, + GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + json_const_iterator it; + if (FindMember(o, "normalTexture", it)) { + ParseNormalTextureInfo(&material->normalTexture, err, GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + json_const_iterator it; + if (FindMember(o, "occlusionTexture", it)) { + ParseOcclusionTextureInfo(&material->occlusionTexture, err, GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + { + json_const_iterator it; + if (FindMember(o, "emissiveTexture", it)) { + ParseTextureInfo(&material->emissiveTexture, err, GetValue(it), + store_original_json_for_extras_and_extensions); + } + } + + // Old code path. For backward compatibility, we still store material values + // as Parameter. This will create duplicated information for + // example(pbrMetallicRoughness), but should be neglible in terms of memory + // consumption. + // TODO(syoyo): Remove in the next major release. + material->values.clear(); + material->additionalValues.clear(); + + json_const_iterator it(ObjectBegin(o)); + json_const_iterator itEnd(ObjectEnd(o)); + + for (; it != itEnd; ++it) { + std::string key(GetKey(it)); + if (key == "pbrMetallicRoughness") { + if (IsObject(GetValue(it))) { + const json &values_object = GetValue(it); + + json_const_iterator itVal(ObjectBegin(values_object)); + json_const_iterator itValEnd(ObjectEnd(values_object)); + + for (; itVal != itValEnd; ++itVal) { + Parameter param; + if (ParseParameterProperty(¶m, err, values_object, GetKey(itVal), + false)) { + material->values.emplace(GetKey(itVal), std::move(param)); + } + } + } + } else if (key == "extensions" || key == "extras") { + // done later, skip, otherwise poorly parsed contents will be saved in the + // parametermap and serialized again later + } else { + Parameter param; + if (ParseParameterProperty(¶m, err, o, key, false)) { + // names of materials have already been parsed. Putting it in this map + // doesn't correctly reflext the glTF specification + if (key != "name") + material->additionalValues.emplace(std::move(key), std::move(param)); + } + } + } + + material->extensions.clear(); + ParseExtensionsProperty(&material->extensions, err, o); + ParseExtrasProperty(&(material->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator eit; + if (FindMember(o, "extensions", eit)) { + material->extensions_json_string = JsonToString(GetValue(eit)); + } + } + { + json_const_iterator eit; + if (FindMember(o, "extras", eit)) { + material->extras_json_string = JsonToString(GetValue(eit)); + } + } + } + + return true; +} + +static bool ParseAnimationChannel( + AnimationChannel *channel, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + int samplerIndex = -1; + int targetIndex = -1; + if (!ParseIntegerProperty(&samplerIndex, err, o, "sampler", true, + "AnimationChannel")) { + if (err) { + (*err) += "`sampler` field is missing in animation channels\n"; + } + return false; + } + + json_const_iterator targetIt; + if (FindMember(o, "target", targetIt) && IsObject(GetValue(targetIt))) { + const json &target_object = GetValue(targetIt); + + if (!ParseIntegerProperty(&targetIndex, err, target_object, "node", true)) { + if (err) { + (*err) += "`node` field is missing in animation.channels.target\n"; + } + return false; + } + + if (!ParseStringProperty(&channel->target_path, err, target_object, "path", + true)) { + if (err) { + (*err) += "`path` field is missing in animation.channels.target\n"; + } + return false; + } + ParseExtensionsProperty(&channel->target_extensions, err, target_object); + if (store_original_json_for_extras_and_extensions) { + json_const_iterator it; + if (FindMember(target_object, "extensions", it)) { + channel->target_extensions_json_string = JsonToString(GetValue(it)); + } + } + } + + channel->sampler = samplerIndex; + channel->target_node = targetIndex; + + ParseExtensionsProperty(&channel->extensions, err, o); + ParseExtrasProperty(&(channel->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + channel->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + channel->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseAnimation(Animation *animation, std::string *err, + const json &o, + bool store_original_json_for_extras_and_extensions) { + { + json_const_iterator channelsIt; + if (FindMember(o, "channels", channelsIt) && + IsArray(GetValue(channelsIt))) { + json_const_array_iterator channelEnd = ArrayEnd(GetValue(channelsIt)); + for (json_const_array_iterator i = ArrayBegin(GetValue(channelsIt)); + i != channelEnd; ++i) { + AnimationChannel channel; + if (ParseAnimationChannel( + &channel, err, *i, + store_original_json_for_extras_and_extensions)) { + // Only add the channel if the parsing succeeds. + animation->channels.emplace_back(std::move(channel)); + } + } + } + } + + { + json_const_iterator samplerIt; + if (FindMember(o, "samplers", samplerIt) && IsArray(GetValue(samplerIt))) { + const json &sampler_array = GetValue(samplerIt); + + json_const_array_iterator it = ArrayBegin(sampler_array); + json_const_array_iterator itEnd = ArrayEnd(sampler_array); + + for (; it != itEnd; ++it) { + const json &s = *it; + + AnimationSampler sampler; + int inputIndex = -1; + int outputIndex = -1; + if (!ParseIntegerProperty(&inputIndex, err, s, "input", true)) { + if (err) { + (*err) += "`input` field is missing in animation.sampler\n"; + } + return false; + } + ParseStringProperty(&sampler.interpolation, err, s, "interpolation", + false); + if (!ParseIntegerProperty(&outputIndex, err, s, "output", true)) { + if (err) { + (*err) += "`output` field is missing in animation.sampler\n"; + } + return false; + } + sampler.input = inputIndex; + sampler.output = outputIndex; + ParseExtensionsProperty(&(sampler.extensions), err, o); + ParseExtrasProperty(&(sampler.extras), s); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator eit; + if (FindMember(o, "extensions", eit)) { + sampler.extensions_json_string = JsonToString(GetValue(eit)); + } + } + { + json_const_iterator eit; + if (FindMember(o, "extras", eit)) { + sampler.extras_json_string = JsonToString(GetValue(eit)); + } + } + } + + animation->samplers.emplace_back(std::move(sampler)); + } + } + } + + ParseStringProperty(&animation->name, err, o, "name", false); + + ParseExtensionsProperty(&animation->extensions, err, o); + ParseExtrasProperty(&(animation->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + animation->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + animation->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseSampler(Sampler *sampler, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&sampler->name, err, o, "name", false); + + int minFilter = -1; + int magFilter = -1; + int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT; + int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT; + //int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT; + ParseIntegerProperty(&minFilter, err, o, "minFilter", false); + ParseIntegerProperty(&magFilter, err, o, "magFilter", false); + ParseIntegerProperty(&wrapS, err, o, "wrapS", false); + ParseIntegerProperty(&wrapT, err, o, "wrapT", false); + //ParseIntegerProperty(&wrapR, err, o, "wrapR", false); // tinygltf extension + + // TODO(syoyo): Check the value is alloed one. + // (e.g. we allow 9728(NEAREST), but don't allow 9727) + + sampler->minFilter = minFilter; + sampler->magFilter = magFilter; + sampler->wrapS = wrapS; + sampler->wrapT = wrapT; + //sampler->wrapR = wrapR; + + ParseExtensionsProperty(&(sampler->extensions), err, o); + ParseExtrasProperty(&(sampler->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + sampler->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + sampler->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseSkin(Skin *skin, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseStringProperty(&skin->name, err, o, "name", false, "Skin"); + + std::vector joints; + if (!ParseIntegerArrayProperty(&joints, err, o, "joints", false, "Skin")) { + return false; + } + skin->joints = std::move(joints); + + int skeleton = -1; + ParseIntegerProperty(&skeleton, err, o, "skeleton", false, "Skin"); + skin->skeleton = skeleton; + + int invBind = -1; + ParseIntegerProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin"); + skin->inverseBindMatrices = invBind; + + ParseExtensionsProperty(&(skin->extensions), err, o); + ParseExtrasProperty(&(skin->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + skin->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + skin->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParsePerspectiveCamera( + PerspectiveCamera *camera, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + double yfov = 0.0; + if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "PerspectiveCamera")) { + return false; + } + + double aspectRatio = 0.0; // = invalid + ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false, + "PerspectiveCamera"); + + double zfar = 0.0; // = invalid + ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera"); + + camera->aspectRatio = aspectRatio; + camera->zfar = zfar; + camera->yfov = yfov; + camera->znear = znear; + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + camera->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + camera->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseSpotLight(SpotLight *light, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + ParseNumberProperty(&light->innerConeAngle, err, o, "innerConeAngle", false); + ParseNumberProperty(&light->outerConeAngle, err, o, "outerConeAngle", false); + + ParseExtensionsProperty(&light->extensions, err, o); + ParseExtrasProperty(&light->extras, o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + light->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + light->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseOrthographicCamera( + OrthographicCamera *camera, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + double xmag = 0.0; + if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) { + return false; + } + + double ymag = 0.0; + if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) { + return false; + } + + double zfar = 0.0; + if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) { + return false; + } + + double znear = 0.0; + if (!ParseNumberProperty(&znear, err, o, "znear", true, + "OrthographicCamera")) { + return false; + } + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + camera->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + camera->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + camera->xmag = xmag; + camera->ymag = ymag; + camera->zfar = zfar; + camera->znear = znear; + + // TODO(syoyo): Validate parameter values. + + return true; +} + +static bool ParseCamera(Camera *camera, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) { + return false; + } + + if (camera->type.compare("orthographic") == 0) { + json_const_iterator orthoIt; + if (!FindMember(o, "orthographic", orthoIt)) { + if (err) { + std::stringstream ss; + ss << "Orhographic camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = GetValue(orthoIt); + if (!IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"orthographic\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseOrthographicCamera( + &camera->orthographic, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } else if (camera->type.compare("perspective") == 0) { + json_const_iterator perspIt; + if (!FindMember(o, "perspective", perspIt)) { + if (err) { + std::stringstream ss; + ss << "Perspective camera description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = GetValue(perspIt); + if (!IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"perspective\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParsePerspectiveCamera( + &camera->perspective, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } else { + if (err) { + std::stringstream ss; + ss << "Invalid camera type: \"" << camera->type + << "\". Must be \"perspective\" or \"orthographic\"" << std::endl; + (*err) += ss.str(); + } + return false; + } + + ParseStringProperty(&camera->name, err, o, "name", false); + + ParseExtensionsProperty(&camera->extensions, err, o); + ParseExtrasProperty(&(camera->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + camera->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + camera->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +static bool ParseLight(Light *light, std::string *err, const json &o, + bool store_original_json_for_extras_and_extensions) { + if (!ParseStringProperty(&light->type, err, o, "type", true)) { + return false; + } + + if (light->type == "spot") { + json_const_iterator spotIt; + if (!FindMember(o, "spot", spotIt)) { + if (err) { + std::stringstream ss; + ss << "Spot light description not found." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const json &v = GetValue(spotIt); + if (!IsObject(v)) { + if (err) { + std::stringstream ss; + ss << "\"spot\" is not a JSON object." << std::endl; + (*err) += ss.str(); + } + return false; + } + + if (!ParseSpotLight(&light->spot, err, v, + store_original_json_for_extras_and_extensions)) { + return false; + } + } + + ParseStringProperty(&light->name, err, o, "name", false); + ParseNumberArrayProperty(&light->color, err, o, "color", false); + ParseNumberProperty(&light->range, err, o, "range", false); + ParseNumberProperty(&light->intensity, err, o, "intensity", false); + ParseExtensionsProperty(&light->extensions, err, o); + ParseExtrasProperty(&(light->extras), o); + + if (store_original_json_for_extras_and_extensions) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + light->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + light->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + return true; +} + +bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, + const char *json_str, + unsigned int json_str_length, + const std::string &base_dir, + unsigned int check_sections) { + if (json_str_length < 4) { + if (err) { + (*err) = "JSON string too short.\n"; + } + return false; + } + + JsonDocument v; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ defined(_CPPUNWIND)) && \ - not defined(TINYGLTF_NOEXCEPTION) - try { - v = json::parse(str, str + length); + !defined(TINYGLTF_NOEXCEPTION) + try { + JsonParse(v, json_str, json_str_length, true); - } - catch (const std::exception &e) { - if (err) { - (*err) = e.what(); - } - return false; - } + } catch (const std::exception &e) { + if (err) { + (*err) = e.what(); + } + return false; + } #else - { - v = json::parse(str, str + length, nullptr, /* exception */ false); + { + JsonParse(v, json_str, json_str_length); - if (!v.is_object()) { - // Assume parsing was failed. - if (err) { - (*err) = "Failed to parse JSON object\n"; - } - return false; - } - } + if (!IsObject(v)) { + // Assume parsing was failed. + if (err) { + (*err) = "Failed to parse JSON object\n"; + } + return false; + } + } #endif - if (!v.is_object()) { - // root is not an object. - if (err) { - (*err) = "Root element is not a JSON object\n"; - } - return false; - } - - // scene is not mandatory. - // FIXME Maybe a better way to handle it than removing the code - - { - json::const_iterator it = v.find("scenes"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } - else if (check_sections & REQUIRE_SCENES) { - if (err) { - (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("nodes"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } - else if (check_sections & REQUIRE_NODES) { - if (err) { - (*err) += "\"nodes\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("accessors"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } - else if (check_sections & REQUIRE_ACCESSORS) { - if (err) { - (*err) += "\"accessors\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("buffers"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } - else if (check_sections & REQUIRE_BUFFERS) { - if (err) { - (*err) += "\"buffers\" object not found in .gltf\n"; - } - return false; - } - } - - { - json::const_iterator it = v.find("bufferViews"); - if ((it != v.end()) && it.value().is_array()) { - // OK - } - else if (check_sections & REQUIRE_BUFFER_VIEWS) { - if (err) { - (*err) += "\"bufferViews\" object not found in .gltf\n"; - } - return false; - } - } - - model->buffers.clear(); - model->bufferViews.clear(); - model->accessors.clear(); - model->meshes.clear(); - model->cameras.clear(); - model->nodes.clear(); - model->extensionsUsed.clear(); - model->extensionsRequired.clear(); - model->extensions.clear(); - model->defaultScene = -1; - - // 1. Parse Asset - { - json::const_iterator it = v.find("asset"); - if ((it != v.end()) && it.value().is_object()) { - const json &root = it.value(); - - ParseAsset(&model->asset, err, root); - } - } - - // 2. Parse extensionUsed - { - json::const_iterator it = v.find("extensionsUsed"); - if ((it != v.end()) && it.value().is_array()) { - const json &root = it.value(); - for (unsigned int i = 0; i < root.size(); ++i) { - model->extensionsUsed.push_back(root[i].get()); - } - } - } - - { - json::const_iterator it = v.find("extensionsRequired"); - if ((it != v.end()) && it.value().is_array()) { - const json &root = it.value(); - for (unsigned int i = 0; i < root.size(); ++i) { - model->extensionsRequired.push_back(root[i].get()); - } - } - } - - // 3. Parse Buffer - { - json::const_iterator rootIt = v.find("buffers"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`buffers' does not contain an JSON object."; - } - return false; - } - Buffer buffer; - if (!ParseBuffer(&buffer, err, it->get(), &fs, base_dir, - is_binary_, bin_data_, bin_size_)) { - return false; - } - - model->buffers.push_back(buffer); - } - } - } - - // 4. Parse BufferView - { - json::const_iterator rootIt = v.find("bufferViews"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`bufferViews' does not contain an JSON object."; - } - return false; - } - BufferView bufferView; - if (!ParseBufferView(&bufferView, err, it->get())) { - return false; - } - - model->bufferViews.push_back(bufferView); - } - } - } - - // 5. Parse Accessor - { - json::const_iterator rootIt = v.find("accessors"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`accessors' does not contain an JSON object."; - } - return false; - } - Accessor accessor; - if (!ParseAccessor(&accessor, err, it->get())) { - return false; - } - - model->accessors.push_back(accessor); - } - } - } - - // 6. Parse Mesh - { - json::const_iterator rootIt = v.find("meshes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`meshes' does not contain an JSON object."; - } - return false; - } - Mesh mesh; - if (!ParseMesh(&mesh, model, err, it->get())) { - return false; - } - - model->meshes.push_back(mesh); - } - } - } - - // Assign missing bufferView target types - // - Look for missing Mesh indices - // - Look for missing bufferView targets - for (auto &mesh : model->meshes) { - for (auto &primitive : mesh.primitives) { - if (primitive.indices > - -1) // has indices from parsing step, must be Element Array Buffer - { - model->bufferViews[model->accessors[primitive.indices].bufferView] - .target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; - // we could optionally check if acessors' bufferView type is Scalar, as - // it should be - } - } - } - // find any missing targets, must be an array buffer type if not fulfilled - // from previous check - for (auto &bufferView : model->bufferViews) { - if (bufferView.target == 0) // missing target type - { - bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; - } - } - - // 7. Parse Node - { - json::const_iterator rootIt = v.find("nodes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`nodes' does not contain an JSON object."; - } - return false; - } - Node node; - if (!ParseNode(&node, err, it->get())) { - return false; - } - - model->nodes.push_back(node); - } - } - } - - // 8. Parse scenes. - { - json::const_iterator rootIt = v.find("scenes"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!(it.value().is_object())) { - if (err) { - (*err) += "`scenes' does not contain an JSON object."; - } - return false; - } - const json &o = it->get(); - std::vector nodes; - if (!ParseNumberArrayProperty(&nodes, err, o, "nodes", false)) { - return false; - } - - Scene scene; - ParseStringProperty(&scene.name, err, o, "name", false); - std::vector nodesIds; - for (size_t i = 0; i < nodes.size(); i++) { - nodesIds.push_back(static_cast(nodes[i])); - } - scene.nodes = nodesIds; - - ParseExtensionsProperty(&scene.extensions, err, o); - ParseExtrasProperty(&scene.extras, o); - - model->scenes.push_back(scene); - } - } - } - - // 9. Parse default scenes. - { - json::const_iterator rootIt = v.find("scene"); - if ((rootIt != v.end()) && rootIt.value().is_number()) { - const int defaultScene = rootIt.value(); - - model->defaultScene = static_cast(defaultScene); - } - } - - // 10. Parse Material - { - json::const_iterator rootIt = v.find("materials"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`materials' does not contain an JSON object."; - } - return false; - } - json jsonMaterial = it->get(); - - Material material; - ParseStringProperty(&material.name, err, jsonMaterial, "name", false); - - if (!ParseMaterial(&material, err, jsonMaterial)) { - return false; - } - - model->materials.push_back(material); - } - } - } - - // 11. Parse Image - { - json::const_iterator rootIt = v.find("images"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - int idx = 0; - for (; it != itEnd; it++, idx++) { - if (!it.value().is_object()) { - if (err) { - (*err) += - "image[" + std::to_string(idx) + "] is not a JSON object."; - } - return false; - } - Image image; - if (!ParseImage(&image, idx, err, warn, it.value(), base_dir, &fs, - &this->LoadImageData, load_image_user_data_)) { - return false; - } - - if (image.bufferView != -1) { - // Load image from the buffer view. - if (size_t(image.bufferView) >= model->bufferViews.size()) { - if (err) { - std::stringstream ss; - ss << "image[" << idx << "] bufferView \"" << image.bufferView - << "\" not found in the scene." << std::endl; - (*err) += ss.str(); - } - return false; - } - - const BufferView &bufferView = - model->bufferViews[size_t(image.bufferView)]; - const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; - - if (*LoadImageData == nullptr) { - if (err) { - (*err) += "No LoadImageData callback specified.\n"; - } - return false; - } - bool ret = LoadImageData( - &image, idx, err, warn, image.width, image.height, - &buffer.data[bufferView.byteOffset], - static_cast(bufferView.byteLength), load_image_user_data_); - if (!ret) { - return false; - } - } - - model->images.push_back(image); - } - } - } - - // 12. Parse Texture - { - json::const_iterator rootIt = v.find("textures"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; it++) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`textures' does not contain an JSON object."; - } - return false; - } - Texture texture; - if (!ParseTexture(&texture, err, it->get(), base_dir)) { - return false; - } - - model->textures.push_back(texture); - } - } - } - - // 13. Parse Animation - { - json::const_iterator rootIt = v.find("animations"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`animations' does not contain an JSON object."; - } - return false; - } - Animation animation; - if (!ParseAnimation(&animation, err, it->get())) { - return false; - } - - model->animations.push_back(animation); - } - } - } - - // 14. Parse Skin - { - json::const_iterator rootIt = v.find("skins"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`skins' does not contain an JSON object."; - } - return false; - } - Skin skin; - if (!ParseSkin(&skin, err, it->get())) { - return false; - } - - model->skins.push_back(skin); - } - } - } - - // 15. Parse Sampler - { - json::const_iterator rootIt = v.find("samplers"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`samplers' does not contain an JSON object."; - } - return false; - } - Sampler sampler; - if (!ParseSampler(&sampler, err, it->get())) { - return false; - } - - model->samplers.push_back(sampler); - } - } - } - - // 16. Parse Camera - { - json::const_iterator rootIt = v.find("cameras"); - if ((rootIt != v.end()) && rootIt.value().is_array()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - if (!it.value().is_object()) { - if (err) { - (*err) += "`cameras' does not contain an JSON object."; - } - return false; - } - Camera camera; - if (!ParseCamera(&camera, err, it->get())) { - return false; - } - - model->cameras.push_back(camera); - } - } - } - - // 17. Parse Extensions - ParseExtensionsProperty(&model->extensions, err, v); - - // 18. Specific extension implementations - { - json::const_iterator rootIt = v.find("extensions"); - if ((rootIt != v.end()) && rootIt.value().is_object()) { - const json &root = rootIt.value(); - - json::const_iterator it(root.begin()); - json::const_iterator itEnd(root.end()); - for (; it != itEnd; ++it) { - // parse KHR_lights_cmn extension - if ((it.key().compare("KHR_lights_cmn") == 0) && - it.value().is_object()) { - const json &object = it.value(); - json::const_iterator itLight(object.find("lights")); - json::const_iterator itLightEnd(object.end()); - if (itLight == itLightEnd) { - continue; - } - - if (!itLight.value().is_array()) { - continue; - } - - const json &lights = itLight.value(); - json::const_iterator arrayIt(lights.begin()); - json::const_iterator arrayItEnd(lights.end()); - for (; arrayIt != arrayItEnd; ++arrayIt) { - Light light; - if (!ParseLight(&light, err, arrayIt.value())) { - return false; - } - model->lights.push_back(light); - } - } - } - } - } - - // 19. Parse Extras - ParseExtrasProperty(&model->extras, v); - - return true; - } - - bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, - std::string *warn, const char *str, - unsigned int length, - const std::string &base_dir, - unsigned int check_sections) { - is_binary_ = false; - bin_data_ = nullptr; - bin_size_ = 0; - - return LoadFromString(model, err, warn, str, length, base_dir, - check_sections); - } - - bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, - std::string *warn, const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - size_t sz = data.size(); - if (sz == 0) { - if (err) { - (*err) = "Empty file."; - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadASCIIFromString( - model, err, warn, reinterpret_cast(&data.at(0)), - static_cast(data.size()), basedir, check_sections); - - return ret; - } - - bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, - std::string *warn, - const unsigned char *bytes, - unsigned int size, - const std::string &base_dir, - unsigned int check_sections) { - if (size < 20) { - if (err) { - (*err) = "Too short data size for glTF Binary."; - } - return false; - } - - if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && - bytes[3] == 'F') { - // ok - } - else { - if (err) { - (*err) = "Invalid magic."; - } - return false; - } - - unsigned int version; // 4 bytes - unsigned int length; // 4 bytes - unsigned int model_length; // 4 bytes - unsigned int model_format; // 4 bytes; - - // @todo { Endian swap for big endian machine. } - memcpy(&version, bytes + 4, 4); - swap4(&version); - memcpy(&length, bytes + 8, 4); - swap4(&length); - memcpy(&model_length, bytes + 12, 4); - swap4(&model_length); - memcpy(&model_format, bytes + 16, 4); - swap4(&model_format); - - // In case the Bin buffer is not present, the size is exactly 20 + size of - // JSON contents, - // so use "greater than" operator. - if ((20 + model_length > size) || (model_length < 1) || - (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. - if (err) { - (*err) = "Invalid glTF binary."; - } - return false; - } - - // Extract JSON string. - std::string jsonString(reinterpret_cast(&bytes[20]), - model_length); - - is_binary_ = true; - bin_data_ = bytes + 20 + model_length + - 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) - bin_size_ = - length - (20 + model_length); // extract header + JSON scene data. - - bool ret = LoadFromString(model, err, warn, - reinterpret_cast(&bytes[20]), - model_length, base_dir, check_sections); - if (!ret) { - return ret; - } - - return true; - } - - bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, - std::string *warn, - const std::string &filename, - unsigned int check_sections) { - std::stringstream ss; - - if (fs.ReadWholeFile == nullptr) { - // Programmer error, assert() ? - ss << "Failed to read file: " << filename - << ": one or more FS callback not set" << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::vector data; - std::string fileerr; - bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); - if (!fileread) { - ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; - if (err) { - (*err) = ss.str(); - } - return false; - } - - std::string basedir = GetBaseDir(filename); - - bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), - static_cast(data.size()), - basedir, check_sections); - - return ret; - } - - /////////////////////// - // GLTF Serialization - /////////////////////// - - // typedef std::pair json_object_pair; - - template - static void SerializeNumberProperty(const std::string &key, T number, - json &obj) { - // obj.insert( - // json_object_pair(key, json(static_cast(number)))); - // obj[key] = static_cast(number); - obj[key] = number; - } - - template - static void SerializeNumberArrayProperty(const std::string &key, - const std::vector &value, - json &obj) { - json o; - json vals; - - for (unsigned int i = 0; i < value.size(); ++i) { - vals.push_back(static_cast(value[i])); - } - if (!vals.is_null()) { - obj[key] = vals; - } - } - - static void SerializeStringProperty(const std::string &key, - const std::string &value, json &obj) { - obj[key] = value; - } - - static void SerializeStringArrayProperty(const std::string &key, - const std::vector &value, - json &obj) { - json o; - json vals; - - for (unsigned int i = 0; i < value.size(); ++i) { - vals.push_back(value[i]); - } - - obj[key] = vals; - } - - static bool ValueToJson(const Value &value, json *ret) { - json obj; - switch (value.Type()) { - case NUMBER_TYPE: - obj = json(value.Get()); - break; - case INT_TYPE: - obj = json(value.Get()); - break; - case BOOL_TYPE: - obj = json(value.Get()); - break; - case STRING_TYPE: - obj = json(value.Get()); - break; - case ARRAY_TYPE: { - for (unsigned int i = 0; i < value.ArrayLen(); ++i) { - Value elementValue = value.Get(int(i)); - json elementJson; - if (ValueToJson(value.Get(int(i)), &elementJson)) - obj.push_back(elementJson); - } - break; - } - case BINARY_TYPE: - // TODO - // obj = json(value.Get>()); - return false; - break; - case OBJECT_TYPE: { - Value::Object objMap = value.Get(); - for (auto &it : objMap) { - json elementJson; - if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; - } - break; - } - case NULL_TYPE: - default: - return false; - } - if (ret) *ret = obj; - return true; - } - - static void SerializeValue(const std::string &key, const Value &value, - json &obj) { - json ret; - if (ValueToJson(value, &ret)) obj[key] = ret; - } - - static void SerializeGltfBufferData(const std::vector &data, - json &o) { - std::string header = "data:application/octet-stream;base64,"; - std::string encodedData = - base64_encode(&data[0], static_cast(data.size())); - SerializeStringProperty("uri", header + encodedData, o); - } - - static bool SerializeGltfBufferData(const std::vector &data, - const std::string &binFilename) { - std::ofstream output(binFilename.c_str(), std::ofstream::binary); - if (!output.is_open()) return false; - output.write(reinterpret_cast(&data[0]), - std::streamsize(data.size())); - output.close(); - return true; - } - - static void SerializeParameterMap(ParameterMap ¶m, json &o) { - for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); - ++paramIt) { - if (paramIt->second.number_array.size()) { - SerializeNumberArrayProperty(paramIt->first, - paramIt->second.number_array, o); - } - else if (paramIt->second.json_double_value.size()) { - json json_double_value; - for (std::map::iterator it = - paramIt->second.json_double_value.begin(); - it != paramIt->second.json_double_value.end(); ++it) { - if (it->first == "index") { - json_double_value[it->first] = paramIt->second.TextureIndex(); - } - else { - json_double_value[it->first] = it->second; - } - } - - o[paramIt->first] = json_double_value; - } - else if (!paramIt->second.string_value.empty()) { - SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); - } - else if (paramIt->second.has_number_value) { - o[paramIt->first] = paramIt->second.number_value; - } - else { - o[paramIt->first] = paramIt->second.bool_value; - } - } - } - - static void SerializeExtensionMap(ExtensionMap &extensions, json &o) { - if (!extensions.size()) return; - - json extMap; - for (ExtensionMap::iterator extIt = extensions.begin(); - extIt != extensions.end(); ++extIt) { - json extension_values; - - // Allow an empty object for extension(#97) - json ret; - if (ValueToJson(extIt->second, &ret)) { - extMap[extIt->first] = ret; - } - if (ret.is_null()) { - if (!(extIt->first.empty())) { // name should not be empty, but for sure - // create empty object so that an extension name is still included in - // json. - extMap[extIt->first] = json({}); - } - } - } - o["extensions"] = extMap; - } - - static void SerializeGltfAccessor(Accessor &accessor, json &o) { - SerializeNumberProperty("bufferView", accessor.bufferView, o); - - if (accessor.byteOffset != 0.0) - SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); - - SerializeNumberProperty("componentType", accessor.componentType, o); - SerializeNumberProperty("count", accessor.count, o); - SerializeNumberArrayProperty("min", accessor.minValues, o); - SerializeNumberArrayProperty("max", accessor.maxValues, o); - std::string type; - switch (accessor.type) { - case TINYGLTF_TYPE_SCALAR: - type = "SCALAR"; - break; - case TINYGLTF_TYPE_VEC2: - type = "VEC2"; - break; - case TINYGLTF_TYPE_VEC3: - type = "VEC3"; - break; - case TINYGLTF_TYPE_VEC4: - type = "VEC4"; - break; - case TINYGLTF_TYPE_MAT2: - type = "MAT2"; - break; - case TINYGLTF_TYPE_MAT3: - type = "MAT3"; - break; - case TINYGLTF_TYPE_MAT4: - type = "MAT4"; - break; - } - - SerializeStringProperty("type", type, o); - if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); - - if (accessor.extras.Type() != NULL_TYPE) { - SerializeValue("extras", accessor.extras, o); - } - } - - static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) { - SerializeNumberProperty("sampler", channel.sampler, o); - json target; - SerializeNumberProperty("node", channel.target_node, target); - SerializeStringProperty("path", channel.target_path, target); - - o["target"] = target; - - if (channel.extras.Type() != NULL_TYPE) { - SerializeValue("extras", channel.extras, o); - } - } - - static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) { - SerializeNumberProperty("input", sampler.input, o); - SerializeNumberProperty("output", sampler.output, o); - SerializeStringProperty("interpolation", sampler.interpolation, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } - } - - static void SerializeGltfAnimation(Animation &animation, json &o) { - if (!animation.name.empty()) - SerializeStringProperty("name", animation.name, o); - json channels; - for (unsigned int i = 0; i < animation.channels.size(); ++i) { - json channel; - AnimationChannel gltfChannel = animation.channels[i]; - SerializeGltfAnimationChannel(gltfChannel, channel); - channels.push_back(channel); - } - o["channels"] = channels; - - json samplers; - for (unsigned int i = 0; i < animation.samplers.size(); ++i) { - json sampler; - AnimationSampler gltfSampler = animation.samplers[i]; - SerializeGltfAnimationSampler(gltfSampler, sampler); - samplers.push_back(sampler); - } - - o["samplers"] = samplers; - - if (animation.extras.Type() != NULL_TYPE) { - SerializeValue("extras", animation.extras, o); - } - } - - static void SerializeGltfAsset(Asset &asset, json &o) { - if (!asset.generator.empty()) { - SerializeStringProperty("generator", asset.generator, o); - } - - if (!asset.version.empty()) { - SerializeStringProperty("version", asset.version, o); - } - - if (asset.extras.Keys().size()) { - SerializeValue("extras", asset.extras, o); - } - - SerializeExtensionMap(asset.extensions, o); - } - - static void SerializeGltfBuffer(Buffer &buffer, json &o) { - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeGltfBufferData(buffer.data, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } - } - - static bool SerializeGltfBuffer(Buffer &buffer, json &o, - const std::string &binFilename, - const std::string &binBaseFilename) { - if (!SerializeGltfBufferData(buffer.data, binFilename)) return false; - SerializeNumberProperty("byteLength", buffer.data.size(), o); - SerializeStringProperty("uri", binBaseFilename, o); - - if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); - - if (buffer.extras.Type() != NULL_TYPE) { - SerializeValue("extras", buffer.extras, o); - } - return true; - } - - static void SerializeGltfBufferView(BufferView &bufferView, json &o) { - SerializeNumberProperty("buffer", bufferView.buffer, o); - SerializeNumberProperty("byteLength", bufferView.byteLength, o); - - // byteStride is optional, minimum allowed is 4 - if (bufferView.byteStride >= 4) { - SerializeNumberProperty("byteStride", bufferView.byteStride, o); - } - // byteOffset is optional, default is 0 - if (bufferView.byteOffset > 0) { - SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); - } - // Target is optional, check if it contains a valid value - if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || - bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { - SerializeNumberProperty("target", bufferView.target, o); - } - if (bufferView.name.size()) { - SerializeStringProperty("name", bufferView.name, o); - } - - if (bufferView.extras.Type() != NULL_TYPE) { - SerializeValue("extras", bufferView.extras, o); - } - } - - static void SerializeGltfImage(Image &image, json &o) { - SerializeStringProperty("uri", image.uri, o); - - if (image.name.size()) { - SerializeStringProperty("name", image.name, o); - } - - if (image.extras.Type() != NULL_TYPE) { - SerializeValue("extras", image.extras, o); - } - - SerializeExtensionMap(image.extensions, o); - } - - static void SerializeGltfMaterial(Material &material, json &o) { - if (material.extras.Size()) SerializeValue("extras", material.extras, o); - SerializeExtensionMap(material.extensions, o); - - if (material.values.size()) { - json pbrMetallicRoughness; - SerializeParameterMap(material.values, pbrMetallicRoughness); - o["pbrMetallicRoughness"] = pbrMetallicRoughness; - } - - SerializeParameterMap(material.additionalValues, o); - - if (material.name.size()) { - SerializeStringProperty("name", material.name, o); - } - - if (material.extras.Type() != NULL_TYPE) { - SerializeValue("extras", material.extras, o); - } - } - - static void SerializeGltfMesh(Mesh &mesh, json &o) { - json primitives; - for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { - json primitive; - json attributes; - Primitive gltfPrimitive = mesh.primitives[i]; - for (std::map::iterator attrIt = - gltfPrimitive.attributes.begin(); - attrIt != gltfPrimitive.attributes.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, attributes); - } - - primitive["attributes"] = attributes; - - // Indicies is optional - if (gltfPrimitive.indices > -1) { - SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); - } - // Material is optional - if (gltfPrimitive.material > -1) { - SerializeNumberProperty("material", gltfPrimitive.material, - primitive); - } - SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); - - // Morph targets - if (gltfPrimitive.targets.size()) { - json targets; - for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { - json targetAttributes; - std::map targetData = gltfPrimitive.targets[k]; - for (std::map::iterator attrIt = targetData.begin(); - attrIt != targetData.end(); ++attrIt) { - SerializeNumberProperty(attrIt->first, attrIt->second, - targetAttributes); - } - - targets.push_back(targetAttributes); - } - primitive["targets"] = targets; - } - - if (gltfPrimitive.extras.Type() != NULL_TYPE) { - SerializeValue("extras", gltfPrimitive.extras, primitive); - } - - primitives.push_back(primitive); - } - - o["primitives"] = primitives; - if (mesh.weights.size()) { - SerializeNumberArrayProperty("weights", mesh.weights, o); - } - - if (mesh.name.size()) { - SerializeStringProperty("name", mesh.name, o); - } - - if (mesh.extras.Type() != NULL_TYPE) { - SerializeValue("extras", mesh.extras, o); - } - } - - static void SerializeGltfLight(Light &light, json &o) { - if (!light.name.empty()) SerializeStringProperty("name", light.name, o); - SerializeNumberArrayProperty("color", light.color, o); - SerializeStringProperty("type", light.type, o); - } - - static void SerializeGltfNode(Node &node, json &o) { - if (node.translation.size() > 0) { - SerializeNumberArrayProperty("translation", node.translation, o); - } - if (node.rotation.size() > 0) { - SerializeNumberArrayProperty("rotation", node.rotation, o); - } - if (node.scale.size() > 0) { - SerializeNumberArrayProperty("scale", node.scale, o); - } - if (node.matrix.size() > 0) { - SerializeNumberArrayProperty("matrix", node.matrix, o); - } - if (node.mesh != -1) { - SerializeNumberProperty("mesh", node.mesh, o); - } - - if (node.skin != -1) { - SerializeNumberProperty("skin", node.skin, o); - } - - if (node.camera != -1) { - SerializeNumberProperty("camera", node.camera, o); - } - - if (node.extras.Type() != NULL_TYPE) { - SerializeValue("extras", node.extras, o); - } - - SerializeExtensionMap(node.extensions, o); - if (!node.name.empty()) SerializeStringProperty("name", node.name, o); - SerializeNumberArrayProperty("children", node.children, o); - } - - static void SerializeGltfSampler(Sampler &sampler, json &o) { - SerializeNumberProperty("magFilter", sampler.magFilter, o); - SerializeNumberProperty("minFilter", sampler.minFilter, o); - SerializeNumberProperty("wrapR", sampler.wrapR, o); - SerializeNumberProperty("wrapS", sampler.wrapS, o); - SerializeNumberProperty("wrapT", sampler.wrapT, o); - - if (sampler.extras.Type() != NULL_TYPE) { - SerializeValue("extras", sampler.extras, o); - } - } - - static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, - json &o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - SerializeNumberProperty("xmag", camera.xmag, o); - SerializeNumberProperty("ymag", camera.ymag, o); - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - } - - static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, - json &o) { - SerializeNumberProperty("zfar", camera.zfar, o); - SerializeNumberProperty("znear", camera.znear, o); - if (camera.aspectRatio > 0) { - SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); - } - - if (camera.yfov > 0) { - SerializeNumberProperty("yfov", camera.yfov, o); - } - - if (camera.extras.Type() != NULL_TYPE) { - SerializeValue("extras", camera.extras, o); - } - } - - static void SerializeGltfCamera(const Camera &camera, json &o) { - SerializeStringProperty("type", camera.type, o); - if (!camera.name.empty()) { - SerializeStringProperty("name", camera.name, o); - } - - if (camera.type.compare("orthographic") == 0) { - json orthographic; - SerializeGltfOrthographicCamera(camera.orthographic, orthographic); - o["orthographic"] = orthographic; - } - else if (camera.type.compare("perspective") == 0) { - json perspective; - SerializeGltfPerspectiveCamera(camera.perspective, perspective); - o["perspective"] = perspective; - } - else { - // ??? - } - } - - static void SerializeGltfScene(Scene &scene, json &o) { - SerializeNumberArrayProperty("nodes", scene.nodes, o); - - if (scene.name.size()) { - SerializeStringProperty("name", scene.name, o); - } - if (scene.extras.Type() != NULL_TYPE) { - SerializeValue("extras", scene.extras, o); - } - SerializeExtensionMap(scene.extensions, o); - } - - static void SerializeGltfSkin(Skin &skin, json &o) { - if (skin.inverseBindMatrices != -1) - SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); - - SerializeNumberArrayProperty("joints", skin.joints, o); - SerializeNumberProperty("skeleton", skin.skeleton, o); - if (skin.name.size()) { - SerializeStringProperty("name", skin.name, o); - } - } - - static void SerializeGltfTexture(Texture &texture, json &o) { - if (texture.sampler > -1) { - SerializeNumberProperty("sampler", texture.sampler, o); - } - if (texture.source > -1) { - SerializeNumberProperty("source", texture.source, o); - } - if (texture.extras.Type() != NULL_TYPE) { - SerializeValue("extras", texture.extras, o); - } - SerializeExtensionMap(texture.extensions, o); - } - - static bool WriteGltfFile(const std::string &output, - const std::string &content) { - std::ofstream gltfFile(output.c_str()); - if (!gltfFile.is_open()) return false; - gltfFile << content << std::endl; - return true; - } - - static void WriteBinaryGltfFile(const std::string &output, - const std::string &content) { - std::ofstream gltfFile(output.c_str(), std::ios::binary); - - const std::string header = "glTF"; - const int version = 2; - const int padding_size = content.size() % 4; - - // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info, - // padding - const int length = 12 + 8 + int(content.size()) + padding_size; - - gltfFile.write(header.c_str(), header.size()); - gltfFile.write(reinterpret_cast(&version), sizeof(version)); - gltfFile.write(reinterpret_cast(&length), sizeof(length)); - - // JSON chunk info, then JSON data - const int model_length = int(content.size()) + padding_size; - const int model_format = 0x4E4F534A; - gltfFile.write(reinterpret_cast(&model_length), - sizeof(model_length)); - gltfFile.write(reinterpret_cast(&model_format), - sizeof(model_format)); - gltfFile.write(content.c_str(), content.size()); - - // Chunk must be multiplies of 4, so pad with spaces - if (padding_size > 0) { - const std::string padding = std::string(padding_size, ' '); - gltfFile.write(padding.c_str(), padding.size()); - } - } - - bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, - bool embedImages = false, - bool embedBuffers = false, - bool prettyPrint = true, - bool writeBinary = false) { - json output; - - // ACCESSORS - json accessors; - for (unsigned int i = 0; i < model->accessors.size(); ++i) { - json accessor; - SerializeGltfAccessor(model->accessors[i], accessor); - accessors.push_back(accessor); - } - output["accessors"] = accessors; - - // ANIMATIONS - if (model->animations.size()) { - json animations; - for (unsigned int i = 0; i < model->animations.size(); ++i) { - if (model->animations[i].channels.size()) { - json animation; - SerializeGltfAnimation(model->animations[i], animation); - animations.push_back(animation); - } - } - output["animations"] = animations; - } - - // ASSET - json asset; - SerializeGltfAsset(model->asset, asset); - output["asset"] = asset; - - std::string defaultBinFilename = GetBaseFilename(filename); - std::string defaultBinFileExt = ".bin"; - std::string::size_type pos = - defaultBinFilename.rfind('.', defaultBinFilename.length()); - - if (pos != std::string::npos) { - defaultBinFilename = defaultBinFilename.substr(0, pos); - } - std::string baseDir = GetBaseDir(filename); - if (baseDir.empty()) { - baseDir = "./"; - } - - // BUFFERS - std::vector usedUris; - json buffers; - for (unsigned int i = 0; i < model->buffers.size(); ++i) { - json buffer; - if (embedBuffers) { - SerializeGltfBuffer(model->buffers[i], buffer); - } - else { - std::string binSavePath; - std::string binUri; - if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) { - binUri = model->buffers[i].uri; - } - else { - binUri = defaultBinFilename + defaultBinFileExt; - bool inUse = true; - int numUsed = 0; - while (inUse) { - inUse = false; - for (const std::string &usedName : usedUris) { - if (binUri.compare(usedName) != 0) continue; - inUse = true; - binUri = defaultBinFilename + std::to_string(numUsed++) + - defaultBinFileExt; - break; - } - } - } - usedUris.push_back(binUri); - binSavePath = JoinPath(baseDir, binUri); - if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, - binUri)) { - return false; - } - } - buffers.push_back(buffer); - } - output["buffers"] = buffers; - - // BUFFERVIEWS - json bufferViews; - for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { - json bufferView; - SerializeGltfBufferView(model->bufferViews[i], bufferView); - bufferViews.push_back(bufferView); - } - output["bufferViews"] = bufferViews; - - // Extensions used - if (model->extensionsUsed.size()) { - SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, - output); - } - - // Extensions required - if (model->extensionsRequired.size()) { - SerializeStringArrayProperty("extensionsRequired", - model->extensionsRequired, output); - } - - // IMAGES - if (model->images.size()) { - json images; - for (unsigned int i = 0; i < model->images.size(); ++i) { - json image; - - UpdateImageObject(model->images[i], baseDir, int(i), embedImages, - &this->WriteImageData, this->write_image_user_data_); - SerializeGltfImage(model->images[i], image); - images.push_back(image); - } - output["images"] = images; - } - - // MATERIALS - if (model->materials.size()) { - json materials; - for (unsigned int i = 0; i < model->materials.size(); ++i) { - json material; - SerializeGltfMaterial(model->materials[i], material); - materials.push_back(material); - } - output["materials"] = materials; - } - - // MESHES - if (model->meshes.size()) { - json meshes; - for (unsigned int i = 0; i < model->meshes.size(); ++i) { - json mesh; - SerializeGltfMesh(model->meshes[i], mesh); - meshes.push_back(mesh); - } - output["meshes"] = meshes; - } - - // NODES - if (model->nodes.size()) { - json nodes; - for (unsigned int i = 0; i < model->nodes.size(); ++i) { - json node; - SerializeGltfNode(model->nodes[i], node); - nodes.push_back(node); - } - output["nodes"] = nodes; - } - - // SCENE - if (model->defaultScene > -1) { - SerializeNumberProperty("scene", model->defaultScene, output); - } - - // SCENES - if (model->scenes.size()) { - json scenes; - for (unsigned int i = 0; i < model->scenes.size(); ++i) { - json currentScene; - SerializeGltfScene(model->scenes[i], currentScene); - scenes.push_back(currentScene); - } - output["scenes"] = scenes; - } - - // SKINS - if (model->skins.size()) { - json skins; - for (unsigned int i = 0; i < model->skins.size(); ++i) { - json skin; - SerializeGltfSkin(model->skins[i], skin); - skins.push_back(skin); - } - output["skins"] = skins; - } - - // TEXTURES - if (model->textures.size()) { - json textures; - for (unsigned int i = 0; i < model->textures.size(); ++i) { - json texture; - SerializeGltfTexture(model->textures[i], texture); - textures.push_back(texture); - } - output["textures"] = textures; - } - - // SAMPLERS - if (model->samplers.size()) { - json samplers; - for (unsigned int i = 0; i < model->samplers.size(); ++i) { - json sampler; - SerializeGltfSampler(model->samplers[i], sampler); - samplers.push_back(sampler); - } - output["samplers"] = samplers; - } - - // CAMERAS - if (model->cameras.size()) { - json cameras; - for (unsigned int i = 0; i < model->cameras.size(); ++i) { - json camera; - SerializeGltfCamera(model->cameras[i], camera); - cameras.push_back(camera); - } - output["cameras"] = cameras; - } - - // EXTENSIONS - SerializeExtensionMap(model->extensions, output); - - // LIGHTS as KHR_lights_cmn - if (model->lights.size()) { - json lights; - for (unsigned int i = 0; i < model->lights.size(); ++i) { - json light; - SerializeGltfLight(model->lights[i], light); - lights.push_back(light); - } - json khr_lights_cmn; - khr_lights_cmn["lights"] = lights; - json ext_j; - - if (output.find("extensions") != output.end()) { - ext_j = output["extensions"]; - } - - ext_j["KHR_lights_cmn"] = khr_lights_cmn; - - output["extensions"] = ext_j; - } - - // EXTRAS - if (model->extras.Type() != NULL_TYPE) { - SerializeValue("extras", model->extras, output); - } - - if (writeBinary) { - WriteBinaryGltfFile(filename, output.dump()); - } - else { - WriteGltfFile(filename, output.dump(prettyPrint ? 2 : -1)); - } - - return true; - } + if (!IsObject(v)) { + // root is not an object. + if (err) { + (*err) = "Root element is not a JSON object\n"; + } + return false; + } + + { + bool version_found = false; + json_const_iterator it; + if (FindMember(v, "asset", it) && IsObject(GetValue(it))) { + auto &itObj = GetValue(it); + json_const_iterator version_it; + std::string versionStr; + if (FindMember(itObj, "version", version_it) && + GetString(GetValue(version_it), versionStr)) { + version_found = true; + } + } + if (version_found) { + // OK + } else if (check_sections & REQUIRE_VERSION) { + if (err) { + (*err) += "\"asset\" object not found in .gltf or not an object type\n"; + } + return false; + } + } + + // scene is not mandatory. + // FIXME Maybe a better way to handle it than removing the code + + auto IsArrayMemberPresent = [](const json &_v, const char *name) -> bool { + json_const_iterator it; + return FindMember(_v, name, it) && IsArray(GetValue(it)); + }; + + { + if ((check_sections & REQUIRE_SCENES) && + !IsArrayMemberPresent(v, "scenes")) { + if (err) { + (*err) += "\"scenes\" object not found in .gltf or not an array type\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) { + if (err) { + (*err) += "\"nodes\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_ACCESSORS) && + !IsArrayMemberPresent(v, "accessors")) { + if (err) { + (*err) += "\"accessors\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_BUFFERS) && + !IsArrayMemberPresent(v, "buffers")) { + if (err) { + (*err) += "\"buffers\" object not found in .gltf\n"; + } + return false; + } + } + + { + if ((check_sections & REQUIRE_BUFFER_VIEWS) && + !IsArrayMemberPresent(v, "bufferViews")) { + if (err) { + (*err) += "\"bufferViews\" object not found in .gltf\n"; + } + return false; + } + } + + model->buffers.clear(); + model->bufferViews.clear(); + model->accessors.clear(); + model->meshes.clear(); + model->cameras.clear(); + model->nodes.clear(); + model->extensionsUsed.clear(); + model->extensionsRequired.clear(); + model->extensions.clear(); + model->defaultScene = -1; + + // 1. Parse Asset + { + json_const_iterator it; + if (FindMember(v, "asset", it) && IsObject(GetValue(it))) { + const json &root = GetValue(it); + + ParseAsset(&model->asset, err, root, + store_original_json_for_extras_and_extensions_); + } + } + +#ifdef TINYGLTF_USE_CPP14 + auto ForEachInArray = [](const json &_v, const char *member, + const auto &cb) -> bool +#else + // The std::function<> implementation can be less efficient because it will + // allocate heap when the size of the captured lambda is above 16 bytes with + // clang and gcc, but it does not require C++14. + auto ForEachInArray = [](const json &_v, const char *member, + const std::function &cb) -> bool +#endif + { + json_const_iterator itm; + if (FindMember(_v, member, itm) && IsArray(GetValue(itm))) { + const json &root = GetValue(itm); + auto it = ArrayBegin(root); + auto end = ArrayEnd(root); + for (; it != end; ++it) { + if (!cb(*it)) return false; + } + } + return true; + }; + + // 2. Parse extensionUsed + { + ForEachInArray(v, "extensionsUsed", [&](const json &o) { + std::string str; + GetString(o, str); + model->extensionsUsed.emplace_back(std::move(str)); + return true; + }); + } + + { + ForEachInArray(v, "extensionsRequired", [&](const json &o) { + std::string str; + GetString(o, str); + model->extensionsRequired.emplace_back(std::move(str)); + return true; + }); + } + + // 3. Parse Buffer + { + bool success = ForEachInArray(v, "buffers", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`buffers' does not contain an JSON object."; + } + return false; + } + Buffer buffer; + if (!ParseBuffer(&buffer, err, o, + store_original_json_for_extras_and_extensions_, &fs, + base_dir, is_binary_, bin_data_, bin_size_)) { + return false; + } + + model->buffers.emplace_back(std::move(buffer)); + return true; + }); + + if (!success) { + return false; + } + } + // 4. Parse BufferView + { + bool success = ForEachInArray(v, "bufferViews", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`bufferViews' does not contain an JSON object."; + } + return false; + } + BufferView bufferView; + if (!ParseBufferView(&bufferView, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->bufferViews.emplace_back(std::move(bufferView)); + return true; + }); + + if (!success) { + return false; + } + } + + // 5. Parse Accessor + { + bool success = ForEachInArray(v, "accessors", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`accessors' does not contain an JSON object."; + } + return false; + } + Accessor accessor; + if (!ParseAccessor(&accessor, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->accessors.emplace_back(std::move(accessor)); + return true; + }); + + if (!success) { + return false; + } + } + + // 6. Parse Mesh + { + bool success = ForEachInArray(v, "meshes", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`meshes' does not contain an JSON object."; + } + return false; + } + Mesh mesh; + if (!ParseMesh(&mesh, model, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->meshes.emplace_back(std::move(mesh)); + return true; + }); + + if (!success) { + return false; + } + } + + // Assign missing bufferView target types + // - Look for missing Mesh indices + // - Look for missing Mesh attributes + for (auto &mesh : model->meshes) { + for (auto &primitive : mesh.primitives) { + if (primitive.indices > + -1) // has indices from parsing step, must be Element Array Buffer + { + if (size_t(primitive.indices) >= model->accessors.size()) { + if (err) { + (*err) += "primitive indices accessor out of bounds"; + } + return false; + } + + auto bufferView = + model->accessors[size_t(primitive.indices)].bufferView; + if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) { + if (err) { + (*err) += "accessor[" + std::to_string(primitive.indices) + + "] invalid bufferView"; + } + return false; + } + + model->bufferViews[size_t(bufferView)].target = + TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER; + // we could optionally check if acessors' bufferView type is Scalar, as + // it should be + } + + for (auto &attribute : primitive.attributes) { + model + ->bufferViews[size_t( + model->accessors[size_t(attribute.second)].bufferView)] + .target = TINYGLTF_TARGET_ARRAY_BUFFER; + } + + for (auto &target : primitive.targets) { + for (auto &attribute : target) { + auto bufferView = + model->accessors[size_t(attribute.second)].bufferView; + // bufferView could be null(-1) for sparse morph target + if (bufferView >= 0) { + model->bufferViews[size_t(bufferView)].target = + TINYGLTF_TARGET_ARRAY_BUFFER; + } + } + } + } + } + + // 7. Parse Node + { + bool success = ForEachInArray(v, "nodes", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`nodes' does not contain an JSON object."; + } + return false; + } + Node node; + if (!ParseNode(&node, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->nodes.emplace_back(std::move(node)); + return true; + }); + + if (!success) { + return false; + } + } + + // 8. Parse scenes. + { + bool success = ForEachInArray(v, "scenes", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`scenes' does not contain an JSON object."; + } + return false; + } + std::vector nodes; + ParseIntegerArrayProperty(&nodes, err, o, "nodes", false); + + Scene scene; + scene.nodes = std::move(nodes); + + ParseStringProperty(&scene.name, err, o, "name", false); + + ParseExtensionsProperty(&scene.extensions, err, o); + ParseExtrasProperty(&scene.extras, o); + + if (store_original_json_for_extras_and_extensions_) { + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + model->extensions_json_string = JsonToString(GetValue(it)); + } + } + { + json_const_iterator it; + if (FindMember(o, "extras", it)) { + model->extras_json_string = JsonToString(GetValue(it)); + } + } + } + + model->scenes.emplace_back(std::move(scene)); + return true; + }); + + if (!success) { + return false; + } + } + + // 9. Parse default scenes. + { + json_const_iterator rootIt; + int iVal; + if (FindMember(v, "scene", rootIt) && GetInt(GetValue(rootIt), iVal)) { + model->defaultScene = iVal; + } + } + + // 10. Parse Material + { + bool success = ForEachInArray(v, "materials", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`materials' does not contain an JSON object."; + } + return false; + } + Material material; + ParseStringProperty(&material.name, err, o, "name", false); + + if (!ParseMaterial(&material, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->materials.emplace_back(std::move(material)); + return true; + }); + + if (!success) { + return false; + } + } + + // 11. Parse Image + void *load_image_user_data{nullptr}; + + LoadImageDataOption load_image_option; + + if (user_image_loader_) { + // Use user supplied pointer + load_image_user_data = load_image_user_data_; + } else { + load_image_option.preserve_channels = preserve_image_channels_; + load_image_user_data = reinterpret_cast(&load_image_option); + } + + { + int idx = 0; + bool success = ForEachInArray(v, "images", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "image[" + std::to_string(idx) + "] is not a JSON object."; + } + return false; + } + Image image; + if (!ParseImage(&image, idx, err, warn, o, + store_original_json_for_extras_and_extensions_, base_dir, + &fs, &this->LoadImageData, load_image_user_data)) { + return false; + } + + if (image.bufferView != -1) { + // Load image from the buffer view. + if (size_t(image.bufferView) >= model->bufferViews.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] bufferView \"" << image.bufferView + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + + const BufferView &bufferView = + model->bufferViews[size_t(image.bufferView)]; + if (size_t(bufferView.buffer) >= model->buffers.size()) { + if (err) { + std::stringstream ss; + ss << "image[" << idx << "] buffer \"" << bufferView.buffer + << "\" not found in the scene." << std::endl; + (*err) += ss.str(); + } + return false; + } + const Buffer &buffer = model->buffers[size_t(bufferView.buffer)]; + + if (*LoadImageData == nullptr) { + if (err) { + (*err) += "No LoadImageData callback specified.\n"; + } + return false; + } + bool ret = LoadImageData( + &image, idx, err, warn, image.width, image.height, + &buffer.data[bufferView.byteOffset], + static_cast(bufferView.byteLength), load_image_user_data); + if (!ret) { + return false; + } + } + + model->images.emplace_back(std::move(image)); + ++idx; + return true; + }); + + if (!success) { + return false; + } + } + + // 12. Parse Texture + { + bool success = ForEachInArray(v, "textures", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`textures' does not contain an JSON object."; + } + return false; + } + Texture texture; + if (!ParseTexture(&texture, err, o, + store_original_json_for_extras_and_extensions_, + base_dir)) { + return false; + } + + model->textures.emplace_back(std::move(texture)); + return true; + }); + + if (!success) { + return false; + } + } + + // 13. Parse Animation + { + bool success = ForEachInArray(v, "animations", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`animations' does not contain an JSON object."; + } + return false; + } + Animation animation; + if (!ParseAnimation(&animation, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->animations.emplace_back(std::move(animation)); + return true; + }); + + if (!success) { + return false; + } + } + + // 14. Parse Skin + { + bool success = ForEachInArray(v, "skins", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`skins' does not contain an JSON object."; + } + return false; + } + Skin skin; + if (!ParseSkin(&skin, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->skins.emplace_back(std::move(skin)); + return true; + }); + + if (!success) { + return false; + } + } + + // 15. Parse Sampler + { + bool success = ForEachInArray(v, "samplers", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`samplers' does not contain an JSON object."; + } + return false; + } + Sampler sampler; + if (!ParseSampler(&sampler, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->samplers.emplace_back(std::move(sampler)); + return true; + }); + + if (!success) { + return false; + } + } + + // 16. Parse Camera + { + bool success = ForEachInArray(v, "cameras", [&](const json &o) { + if (!IsObject(o)) { + if (err) { + (*err) += "`cameras' does not contain an JSON object."; + } + return false; + } + Camera camera; + if (!ParseCamera(&camera, err, o, + store_original_json_for_extras_and_extensions_)) { + return false; + } + + model->cameras.emplace_back(std::move(camera)); + return true; + }); + + if (!success) { + return false; + } + } + + // 17. Parse Extensions + ParseExtensionsProperty(&model->extensions, err, v); + + // 18. Specific extension implementations + { + json_const_iterator rootIt; + if (FindMember(v, "extensions", rootIt) && IsObject(GetValue(rootIt))) { + const json &root = GetValue(rootIt); + + json_const_iterator it(ObjectBegin(root)); + json_const_iterator itEnd(ObjectEnd(root)); + for (; it != itEnd; ++it) { + // parse KHR_lights_punctual extension + std::string key(GetKey(it)); + if ((key == "KHR_lights_punctual") && IsObject(GetValue(it))) { + const json &object = GetValue(it); + json_const_iterator itLight; + if (FindMember(object, "lights", itLight)) { + const json &lights = GetValue(itLight); + if (!IsArray(lights)) { + continue; + } + + auto arrayIt(ArrayBegin(lights)); + auto arrayItEnd(ArrayEnd(lights)); + for (; arrayIt != arrayItEnd; ++arrayIt) { + Light light; + if (!ParseLight(&light, err, *arrayIt, + store_original_json_for_extras_and_extensions_)) { + return false; + } + model->lights.emplace_back(std::move(light)); + } + } + } + } + } + } + + // 19. Parse Extras + ParseExtrasProperty(&model->extras, v); + + if (store_original_json_for_extras_and_extensions_) { + model->extras_json_string = JsonToString(v["extras"]); + model->extensions_json_string = JsonToString(v["extensions"]); + } + + return true; +} + +bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, + std::string *warn, const char *str, + unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + is_binary_ = false; + bin_data_ = nullptr; + bin_size_ = 0; + + return LoadFromString(model, err, warn, str, length, base_dir, + check_sections); +} + +bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, + std::string *warn, const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + size_t sz = data.size(); + if (sz == 0) { + if (err) { + (*err) = "Empty file."; + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadASCIIFromString( + model, err, warn, reinterpret_cast(&data.at(0)), + static_cast(data.size()), basedir, check_sections); + + return ret; +} + +bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, + std::string *warn, + const unsigned char *bytes, + unsigned int size, + const std::string &base_dir, + unsigned int check_sections) { + if (size < 20) { + if (err) { + (*err) = "Too short data size for glTF Binary."; + } + return false; + } + + if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' && + bytes[3] == 'F') { + // ok + } else { + if (err) { + (*err) = "Invalid magic."; + } + return false; + } + + unsigned int version; // 4 bytes + unsigned int length; // 4 bytes + unsigned int model_length; // 4 bytes + unsigned int model_format; // 4 bytes; + + // @todo { Endian swap for big endian machine. } + memcpy(&version, bytes + 4, 4); + swap4(&version); + memcpy(&length, bytes + 8, 4); + swap4(&length); + memcpy(&model_length, bytes + 12, 4); + swap4(&model_length); + memcpy(&model_format, bytes + 16, 4); + swap4(&model_format); + + // In case the Bin buffer is not present, the size is exactly 20 + size of + // JSON contents, + // so use "greater than" operator. + if ((20 + model_length > size) || (model_length < 1) || (length > size) || + (20 + model_length > length) || + (model_format != 0x4E4F534A)) { // 0x4E4F534A = JSON format. + if (err) { + (*err) = "Invalid glTF binary."; + } + return false; + } + + // Extract JSON string. + std::string jsonString(reinterpret_cast(&bytes[20]), + model_length); + + is_binary_ = true; + bin_data_ = bytes + 20 + model_length + + 8; // 4 bytes (buffer_length) + 4 bytes(buffer_format) + bin_size_ = + length - (20 + model_length); // extract header + JSON scene data. + + bool ret = LoadFromString(model, err, warn, + reinterpret_cast(&bytes[20]), + model_length, base_dir, check_sections); + if (!ret) { + return ret; + } + + return true; +} + +bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, + std::string *warn, + const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0), + static_cast(data.size()), + basedir, check_sections); + + return ret; +} + +/////////////////////// +// GLTF Serialization +/////////////////////// +namespace { +json JsonFromString(const char *s) { +#ifdef TINYGLTF_USE_RAPIDJSON + return json(s, GetAllocator()); +#else + return json(s); +#endif +} + +void JsonAssign(json &dest, const json &src) { +#ifdef TINYGLTF_USE_RAPIDJSON + dest.CopyFrom(src, GetAllocator()); +#else + dest = src; +#endif +} + +void JsonAddMember(json &o, const char *key, json &&value) { +#ifdef TINYGLTF_USE_RAPIDJSON + if (!o.IsObject()) { + o.SetObject(); + } + o.AddMember(json(key, GetAllocator()), std::move(value), GetAllocator()); +#else + o[key] = std::move(value); +#endif +} + +void JsonPushBack(json &o, json &&value) { +#ifdef TINYGLTF_USE_RAPIDJSON + o.PushBack(std::move(value), GetAllocator()); +#else + o.push_back(std::move(value)); +#endif +} + +bool JsonIsNull(const json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + return o.IsNull(); +#else + return o.is_null(); +#endif +} + +void JsonSetObject(json &o) { +#ifdef TINYGLTF_USE_RAPIDJSON + o.SetObject(); +#else + o = o.object({}); +#endif +} + +void JsonReserveArray(json &o, size_t s) { +#ifdef TINYGLTF_USE_RAPIDJSON + o.SetArray(); + o.Reserve(static_cast(s), GetAllocator()); +#endif + (void)(o); + (void)(s); +} +} // namespace + +// typedef std::pair json_object_pair; + +template +static void SerializeNumberProperty(const std::string &key, T number, + json &obj) { + // obj.insert( + // json_object_pair(key, json(static_cast(number)))); + // obj[key] = static_cast(number); + JsonAddMember(obj, key.c_str(), json(number)); +} + +#ifdef TINYGLTF_USE_RAPIDJSON +template <> +void SerializeNumberProperty(const std::string &key, size_t number, json &obj) { + JsonAddMember(obj, key.c_str(), json(static_cast(number))); +} +#endif + +template +static void SerializeNumberArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + if (value.empty()) return; + + json ary; + JsonReserveArray(ary, value.size()); + for (const auto &s : value) { + JsonPushBack(ary, json(s)); + } + JsonAddMember(obj, key.c_str(), std::move(ary)); +} + +static void SerializeStringProperty(const std::string &key, + const std::string &value, json &obj) { + JsonAddMember(obj, key.c_str(), JsonFromString(value.c_str())); +} + +static void SerializeStringArrayProperty(const std::string &key, + const std::vector &value, + json &obj) { + json ary; + JsonReserveArray(ary, value.size()); + for (auto &s : value) { + JsonPushBack(ary, JsonFromString(s.c_str())); + } + JsonAddMember(obj, key.c_str(), std::move(ary)); +} + +static bool ValueToJson(const Value &value, json *ret) { + json obj; +#ifdef TINYGLTF_USE_RAPIDJSON + switch (value.Type()) { + case REAL_TYPE: + obj.SetDouble(value.Get()); + break; + case INT_TYPE: + obj.SetInt(value.Get()); + break; + case BOOL_TYPE: + obj.SetBool(value.Get()); + break; + case STRING_TYPE: + obj.SetString(value.Get().c_str(), GetAllocator()); + break; + case ARRAY_TYPE: { + obj.SetArray(); + obj.Reserve(static_cast(value.ArrayLen()), + GetAllocator()); + for (unsigned int i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(int(i)); + json elementJson; + if (ValueToJson(value.Get(int(i)), &elementJson)) + obj.PushBack(std::move(elementJson), GetAllocator()); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + obj.SetObject(); + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + json elementJson; + if (ValueToJson(it.second, &elementJson)) { + obj.AddMember(json(it.first.c_str(), GetAllocator()), + std::move(elementJson), GetAllocator()); + } + } + break; + } + case NULL_TYPE: + default: + return false; + } +#else + switch (value.Type()) { + case REAL_TYPE: + obj = json(value.Get()); + break; + case INT_TYPE: + obj = json(value.Get()); + break; + case BOOL_TYPE: + obj = json(value.Get()); + break; + case STRING_TYPE: + obj = json(value.Get()); + break; + case ARRAY_TYPE: { + for (unsigned int i = 0; i < value.ArrayLen(); ++i) { + Value elementValue = value.Get(int(i)); + json elementJson; + if (ValueToJson(value.Get(int(i)), &elementJson)) + obj.push_back(elementJson); + } + break; + } + case BINARY_TYPE: + // TODO + // obj = json(value.Get>()); + return false; + break; + case OBJECT_TYPE: { + Value::Object objMap = value.Get(); + for (auto &it : objMap) { + json elementJson; + if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson; + } + break; + } + case NULL_TYPE: + default: + return false; + } +#endif + if (ret) *ret = std::move(obj); + return true; +} + +static void SerializeValue(const std::string &key, const Value &value, + json &obj) { + json ret; + if (ValueToJson(value, &ret)) { + JsonAddMember(obj, key.c_str(), std::move(ret)); + } +} + +static void SerializeGltfBufferData(const std::vector &data, + json &o) { + std::string header = "data:application/octet-stream;base64,"; + if (data.size() > 0) { + std::string encodedData = + base64_encode(&data[0], static_cast(data.size())); + SerializeStringProperty("uri", header + encodedData, o); + } else { + // Issue #229 + // size 0 is allowd. Just emit mime header. + SerializeStringProperty("uri", header, o); + } +} + +static bool SerializeGltfBufferData(const std::vector &data, + const std::string &binFilename) { +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = _wopen(UTF8ToWchar(binFilename).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream output(&wfile_buf); + if (!wfile_buf.is_open()) return false; +#elif defined(_MSC_VER) + std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary); + if (!output.is_open()) return false; +#else + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + if (!output.is_open()) return false; +#endif +#else + std::ofstream output(binFilename.c_str(), std::ofstream::binary); + if (!output.is_open()) return false; +#endif + if (data.size() > 0) { + output.write(reinterpret_cast(&data[0]), + std::streamsize(data.size())); + } else { + // Issue #229 + // size 0 will be still valid buffer data. + // write empty file. + } + return true; +} + +#if 0 // FIXME(syoyo): not used. will be removed in the future release. +static void SerializeParameterMap(ParameterMap ¶m, json &o) { + for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end(); + ++paramIt) { + if (paramIt->second.number_array.size()) { + SerializeNumberArrayProperty(paramIt->first, + paramIt->second.number_array, o); + } else if (paramIt->second.json_double_value.size()) { + json json_double_value; + for (std::map::iterator it = + paramIt->second.json_double_value.begin(); + it != paramIt->second.json_double_value.end(); ++it) { + if (it->first == "index") { + json_double_value[it->first] = paramIt->second.TextureIndex(); + } else { + json_double_value[it->first] = it->second; + } + } + + o[paramIt->first] = json_double_value; + } else if (!paramIt->second.string_value.empty()) { + SerializeStringProperty(paramIt->first, paramIt->second.string_value, o); + } else if (paramIt->second.has_number_value) { + o[paramIt->first] = paramIt->second.number_value; + } else { + o[paramIt->first] = paramIt->second.bool_value; + } + } +} +#endif + +static void SerializeExtensionMap(const ExtensionMap &extensions, json &o) { + if (!extensions.size()) return; + + json extMap; + for (ExtensionMap::const_iterator extIt = extensions.begin(); + extIt != extensions.end(); ++extIt) { + // Allow an empty object for extension(#97) + json ret; + bool isNull = true; + if (ValueToJson(extIt->second, &ret)) { + isNull = JsonIsNull(ret); + JsonAddMember(extMap, extIt->first.c_str(), std::move(ret)); + } + if (isNull) { + if (!(extIt->first.empty())) { // name should not be empty, but for sure + // create empty object so that an extension name is still included in + // json. + json empty; + JsonSetObject(empty); + JsonAddMember(extMap, extIt->first.c_str(), std::move(empty)); + } + } + } + JsonAddMember(o, "extensions", std::move(extMap)); +} + +static void SerializeGltfAccessor(Accessor &accessor, json &o) { + if (accessor.bufferView >= 0) + SerializeNumberProperty("bufferView", accessor.bufferView, o); + + if (accessor.byteOffset != 0) + SerializeNumberProperty("byteOffset", int(accessor.byteOffset), o); + + SerializeNumberProperty("componentType", accessor.componentType, o); + SerializeNumberProperty("count", accessor.count, o); + SerializeNumberArrayProperty("min", accessor.minValues, o); + SerializeNumberArrayProperty("max", accessor.maxValues, o); + if (accessor.normalized) + SerializeValue("normalized", Value(accessor.normalized), o); + std::string type; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + type = "SCALAR"; + break; + case TINYGLTF_TYPE_VEC2: + type = "VEC2"; + break; + case TINYGLTF_TYPE_VEC3: + type = "VEC3"; + break; + case TINYGLTF_TYPE_VEC4: + type = "VEC4"; + break; + case TINYGLTF_TYPE_MAT2: + type = "MAT2"; + break; + case TINYGLTF_TYPE_MAT3: + type = "MAT3"; + break; + case TINYGLTF_TYPE_MAT4: + type = "MAT4"; + break; + } + + SerializeStringProperty("type", type, o); + if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o); + + if (accessor.extras.Type() != NULL_TYPE) { + SerializeValue("extras", accessor.extras, o); + } +} + +static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) { + SerializeNumberProperty("sampler", channel.sampler, o); + { + json target; + SerializeNumberProperty("node", channel.target_node, target); + SerializeStringProperty("path", channel.target_path, target); + + SerializeExtensionMap(channel.target_extensions, target); + + JsonAddMember(o, "target", std::move(target)); + } + + if (channel.extras.Type() != NULL_TYPE) { + SerializeValue("extras", channel.extras, o); + } + + SerializeExtensionMap(channel.extensions, o); +} + +static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) { + SerializeNumberProperty("input", sampler.input, o); + SerializeNumberProperty("output", sampler.output, o); + SerializeStringProperty("interpolation", sampler.interpolation, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } +} + +static void SerializeGltfAnimation(Animation &animation, json &o) { + if (!animation.name.empty()) + SerializeStringProperty("name", animation.name, o); + + { + json channels; + JsonReserveArray(channels, animation.channels.size()); + for (unsigned int i = 0; i < animation.channels.size(); ++i) { + json channel; + AnimationChannel gltfChannel = animation.channels[i]; + SerializeGltfAnimationChannel(gltfChannel, channel); + JsonPushBack(channels, std::move(channel)); + } + + JsonAddMember(o, "channels", std::move(channels)); + } + + { + json samplers; + JsonReserveArray(samplers, animation.samplers.size()); + for (unsigned int i = 0; i < animation.samplers.size(); ++i) { + json sampler; + AnimationSampler gltfSampler = animation.samplers[i]; + SerializeGltfAnimationSampler(gltfSampler, sampler); + JsonPushBack(samplers, std::move(sampler)); + } + JsonAddMember(o, "samplers", std::move(samplers)); + } + + if (animation.extras.Type() != NULL_TYPE) { + SerializeValue("extras", animation.extras, o); + } + + SerializeExtensionMap(animation.extensions, o); +} + +static void SerializeGltfAsset(Asset &asset, json &o) { + if (!asset.generator.empty()) { + SerializeStringProperty("generator", asset.generator, o); + } + + if (!asset.copyright.empty()) { + SerializeStringProperty("copyright", asset.copyright, o); + } + + if (!asset.version.empty()) { + SerializeStringProperty("version", asset.version, o); + } + + if (asset.extras.Keys().size()) { + SerializeValue("extras", asset.extras, o); + } + + SerializeExtensionMap(asset.extensions, o); +} + +static void SerializeGltfBufferBin(Buffer &buffer, json &o, + std::vector &binBuffer) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + binBuffer = buffer.data; + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } +} + +static void SerializeGltfBuffer(Buffer &buffer, json &o) { + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeGltfBufferData(buffer.data, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } +} + +static bool SerializeGltfBuffer(Buffer &buffer, json &o, + const std::string &binFilename, + const std::string &binBaseFilename) { + if (!SerializeGltfBufferData(buffer.data, binFilename)) return false; + SerializeNumberProperty("byteLength", buffer.data.size(), o); + SerializeStringProperty("uri", binBaseFilename, o); + + if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o); + + if (buffer.extras.Type() != NULL_TYPE) { + SerializeValue("extras", buffer.extras, o); + } + return true; +} + +static void SerializeGltfBufferView(BufferView &bufferView, json &o) { + SerializeNumberProperty("buffer", bufferView.buffer, o); + SerializeNumberProperty("byteLength", bufferView.byteLength, o); + + // byteStride is optional, minimum allowed is 4 + if (bufferView.byteStride >= 4) { + SerializeNumberProperty("byteStride", bufferView.byteStride, o); + } + // byteOffset is optional, default is 0 + if (bufferView.byteOffset > 0) { + SerializeNumberProperty("byteOffset", bufferView.byteOffset, o); + } + // Target is optional, check if it contains a valid value + if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER || + bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) { + SerializeNumberProperty("target", bufferView.target, o); + } + if (bufferView.name.size()) { + SerializeStringProperty("name", bufferView.name, o); + } + + if (bufferView.extras.Type() != NULL_TYPE) { + SerializeValue("extras", bufferView.extras, o); + } +} + +static void SerializeGltfImage(Image &image, json &o) { + // if uri empty, the mimeType and bufferview should be set + if (image.uri.empty()) { + SerializeStringProperty("mimeType", image.mimeType, o); + SerializeNumberProperty("bufferView", image.bufferView, o); + } else { + // TODO(syoyo): dlib::urilencode? + SerializeStringProperty("uri", image.uri, o); + } + + if (image.name.size()) { + SerializeStringProperty("name", image.name, o); + } + + if (image.extras.Type() != NULL_TYPE) { + SerializeValue("extras", image.extras, o); + } + + SerializeExtensionMap(image.extensions, o); +} + +static void SerializeGltfTextureInfo(TextureInfo &texinfo, json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + if (texinfo.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texinfo.extras, o); + } + + SerializeExtensionMap(texinfo.extensions, o); +} + +static void SerializeGltfNormalTextureInfo(NormalTextureInfo &texinfo, + json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(texinfo.scale, 1.0)) { + SerializeNumberProperty("scale", texinfo.scale, o); + } + + if (texinfo.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texinfo.extras, o); + } + + SerializeExtensionMap(texinfo.extensions, o); +} + +static void SerializeGltfOcclusionTextureInfo(OcclusionTextureInfo &texinfo, + json &o) { + SerializeNumberProperty("index", texinfo.index, o); + + if (texinfo.texCoord != 0) { + SerializeNumberProperty("texCoord", texinfo.texCoord, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(texinfo.strength, 1.0)) { + SerializeNumberProperty("strength", texinfo.strength, o); + } + + if (texinfo.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texinfo.extras, o); + } + + SerializeExtensionMap(texinfo.extensions, o); +} + +static void SerializeGltfPbrMetallicRoughness(PbrMetallicRoughness &pbr, + json &o) { + std::vector default_baseColorFactor = {1.0, 1.0, 1.0, 1.0}; + if (!Equals(pbr.baseColorFactor, default_baseColorFactor)) { + SerializeNumberArrayProperty("baseColorFactor", pbr.baseColorFactor, + o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(pbr.metallicFactor, 1.0)) { + SerializeNumberProperty("metallicFactor", pbr.metallicFactor, o); + } + + if (!TINYGLTF_DOUBLE_EQUAL(pbr.roughnessFactor, 1.0)) { + SerializeNumberProperty("roughnessFactor", pbr.roughnessFactor, o); + } + + if (pbr.baseColorTexture.index > -1) { + json texinfo; + SerializeGltfTextureInfo(pbr.baseColorTexture, texinfo); + JsonAddMember(o, "baseColorTexture", std::move(texinfo)); + } + + if (pbr.metallicRoughnessTexture.index > -1) { + json texinfo; + SerializeGltfTextureInfo(pbr.metallicRoughnessTexture, texinfo); + JsonAddMember(o, "metallicRoughnessTexture", std::move(texinfo)); + } + + SerializeExtensionMap(pbr.extensions, o); + + if (pbr.extras.Type() != NULL_TYPE) { + SerializeValue("extras", pbr.extras, o); + } +} + +static void SerializeGltfMaterial(Material &material, json &o) { + if (material.name.size()) { + SerializeStringProperty("name", material.name, o); + } + + // QUESTION(syoyo): Write material parameters regardless of its default value? + + if (!TINYGLTF_DOUBLE_EQUAL(material.alphaCutoff, 0.5)) { + SerializeNumberProperty("alphaCutoff", material.alphaCutoff, o); + } + + if (material.alphaMode.compare("OPAQUE") != 0) { + SerializeStringProperty("alphaMode", material.alphaMode, o); + } + + if (material.doubleSided != false) + JsonAddMember(o, "doubleSided", json(material.doubleSided)); + + if (material.normalTexture.index > -1) { + json texinfo; + SerializeGltfNormalTextureInfo(material.normalTexture, texinfo); + JsonAddMember(o, "normalTexture", std::move(texinfo)); + } + + if (material.occlusionTexture.index > -1) { + json texinfo; + SerializeGltfOcclusionTextureInfo(material.occlusionTexture, texinfo); + JsonAddMember(o, "occlusionTexture", std::move(texinfo)); + } + + if (material.emissiveTexture.index > -1) { + json texinfo; + SerializeGltfTextureInfo(material.emissiveTexture, texinfo); + JsonAddMember(o, "emissiveTexture", std::move(texinfo)); + } + + std::vector default_emissiveFactor = {0.0, 0.0, 0.0}; + if (!Equals(material.emissiveFactor, default_emissiveFactor)) { + SerializeNumberArrayProperty("emissiveFactor", + material.emissiveFactor, o); + } + + { + json pbrMetallicRoughness; + SerializeGltfPbrMetallicRoughness(material.pbrMetallicRoughness, + pbrMetallicRoughness); + // Issue 204 + // Do not serialize `pbrMetallicRoughness` if pbrMetallicRoughness has all + // default values(json is null). Otherwise it will serialize to + // `pbrMetallicRoughness : null`, which cannot be read by other glTF + // importers(and validators). + // + if (!JsonIsNull(pbrMetallicRoughness)) { + JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); + } + } + +#if 0 // legacy way. just for the record. + if (material.values.size()) { + json pbrMetallicRoughness; + SerializeParameterMap(material.values, pbrMetallicRoughness); + JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness)); + } + + SerializeParameterMap(material.additionalValues, o); +#else + +#endif + + SerializeExtensionMap(material.extensions, o); + + if (material.extras.Type() != NULL_TYPE) { + SerializeValue("extras", material.extras, o); + } +} + +static void SerializeGltfMesh(Mesh &mesh, json &o) { + json primitives; + JsonReserveArray(primitives, mesh.primitives.size()); + for (unsigned int i = 0; i < mesh.primitives.size(); ++i) { + json primitive; + const Primitive &gltfPrimitive = mesh.primitives[i]; // don't make a copy + { + json attributes; + for (auto attrIt = gltfPrimitive.attributes.begin(); + attrIt != gltfPrimitive.attributes.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, attributes); + } + + JsonAddMember(primitive, "attributes", std::move(attributes)); + } + + // Indicies is optional + if (gltfPrimitive.indices > -1) { + SerializeNumberProperty("indices", gltfPrimitive.indices, primitive); + } + // Material is optional + if (gltfPrimitive.material > -1) { + SerializeNumberProperty("material", gltfPrimitive.material, + primitive); + } + SerializeNumberProperty("mode", gltfPrimitive.mode, primitive); + + // Morph targets + if (gltfPrimitive.targets.size()) { + json targets; + JsonReserveArray(targets, gltfPrimitive.targets.size()); + for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) { + json targetAttributes; + std::map targetData = gltfPrimitive.targets[k]; + for (std::map::iterator attrIt = targetData.begin(); + attrIt != targetData.end(); ++attrIt) { + SerializeNumberProperty(attrIt->first, attrIt->second, + targetAttributes); + } + JsonPushBack(targets, std::move(targetAttributes)); + } + JsonAddMember(primitive, "targets", std::move(targets)); + } + + SerializeExtensionMap(gltfPrimitive.extensions, primitive); + + if (gltfPrimitive.extras.Type() != NULL_TYPE) { + SerializeValue("extras", gltfPrimitive.extras, primitive); + } + + JsonPushBack(primitives, std::move(primitive)); + } + + JsonAddMember(o, "primitives", std::move(primitives)); + + if (mesh.weights.size()) { + SerializeNumberArrayProperty("weights", mesh.weights, o); + } + + if (mesh.name.size()) { + SerializeStringProperty("name", mesh.name, o); + } + + SerializeExtensionMap(mesh.extensions, o); + if (mesh.extras.Type() != NULL_TYPE) { + SerializeValue("extras", mesh.extras, o); + } +} + +static void SerializeSpotLight(SpotLight &spot, json &o) { + SerializeNumberProperty("innerConeAngle", spot.innerConeAngle, o); + SerializeNumberProperty("outerConeAngle", spot.outerConeAngle, o); + SerializeExtensionMap(spot.extensions, o); + if (spot.extras.Type() != NULL_TYPE) { + SerializeValue("extras", spot.extras, o); + } +} + +static void SerializeGltfLight(Light &light, json &o) { + if (!light.name.empty()) SerializeStringProperty("name", light.name, o); + SerializeNumberProperty("intensity", light.intensity, o); + if (light.range > 0.0) { + SerializeNumberProperty("range", light.range, o); + } + SerializeNumberArrayProperty("color", light.color, o); + SerializeStringProperty("type", light.type, o); + if (light.type == "spot") { + json spot; + SerializeSpotLight(light.spot, spot); + JsonAddMember(o, "spot", std::move(spot)); + } + SerializeExtensionMap(light.extensions, o); + if (light.extras.Type() != NULL_TYPE) { + SerializeValue("extras", light.extras, o); + } +} + +static void SerializeGltfNode(Node &node, json &o) { + if (node.translation.size() > 0) { + SerializeNumberArrayProperty("translation", node.translation, o); + } + if (node.rotation.size() > 0) { + SerializeNumberArrayProperty("rotation", node.rotation, o); + } + if (node.scale.size() > 0) { + SerializeNumberArrayProperty("scale", node.scale, o); + } + if (node.matrix.size() > 0) { + SerializeNumberArrayProperty("matrix", node.matrix, o); + } + if (node.mesh != -1) { + SerializeNumberProperty("mesh", node.mesh, o); + } + + if (node.skin != -1) { + SerializeNumberProperty("skin", node.skin, o); + } + + if (node.camera != -1) { + SerializeNumberProperty("camera", node.camera, o); + } + + if (node.weights.size() > 0) { + SerializeNumberArrayProperty("weights", node.weights, o); + } + + if (node.extras.Type() != NULL_TYPE) { + SerializeValue("extras", node.extras, o); + } + + SerializeExtensionMap(node.extensions, o); + if (!node.name.empty()) SerializeStringProperty("name", node.name, o); + SerializeNumberArrayProperty("children", node.children, o); +} + +static void SerializeGltfSampler(Sampler &sampler, json &o) { + if (sampler.magFilter != -1) { + SerializeNumberProperty("magFilter", sampler.magFilter, o); + } + if (sampler.minFilter != -1) { + SerializeNumberProperty("minFilter", sampler.minFilter, o); + } + //SerializeNumberProperty("wrapR", sampler.wrapR, o); + SerializeNumberProperty("wrapS", sampler.wrapS, o); + SerializeNumberProperty("wrapT", sampler.wrapT, o); + + if (sampler.extras.Type() != NULL_TYPE) { + SerializeValue("extras", sampler.extras, o); + } +} + +static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + SerializeNumberProperty("xmag", camera.xmag, o); + SerializeNumberProperty("ymag", camera.ymag, o); + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } +} + +static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera, + json &o) { + SerializeNumberProperty("zfar", camera.zfar, o); + SerializeNumberProperty("znear", camera.znear, o); + if (camera.aspectRatio > 0) { + SerializeNumberProperty("aspectRatio", camera.aspectRatio, o); + } + + if (camera.yfov > 0) { + SerializeNumberProperty("yfov", camera.yfov, o); + } + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } +} + +static void SerializeGltfCamera(const Camera &camera, json &o) { + SerializeStringProperty("type", camera.type, o); + if (!camera.name.empty()) { + SerializeStringProperty("name", camera.name, o); + } + + if (camera.type.compare("orthographic") == 0) { + json orthographic; + SerializeGltfOrthographicCamera(camera.orthographic, orthographic); + JsonAddMember(o, "orthographic", std::move(orthographic)); + } else if (camera.type.compare("perspective") == 0) { + json perspective; + SerializeGltfPerspectiveCamera(camera.perspective, perspective); + JsonAddMember(o, "perspective", std::move(perspective)); + } else { + // ??? + } + + if (camera.extras.Type() != NULL_TYPE) { + SerializeValue("extras", camera.extras, o); + } + SerializeExtensionMap(camera.extensions, o); +} + +static void SerializeGltfScene(Scene &scene, json &o) { + SerializeNumberArrayProperty("nodes", scene.nodes, o); + + if (scene.name.size()) { + SerializeStringProperty("name", scene.name, o); + } + if (scene.extras.Type() != NULL_TYPE) { + SerializeValue("extras", scene.extras, o); + } + SerializeExtensionMap(scene.extensions, o); +} + +static void SerializeGltfSkin(Skin &skin, json &o) { + if (skin.inverseBindMatrices != -1) + SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o); + + SerializeNumberArrayProperty("joints", skin.joints, o); + SerializeNumberProperty("skeleton", skin.skeleton, o); + if (skin.name.size()) { + SerializeStringProperty("name", skin.name, o); + } +} + +static void SerializeGltfTexture(Texture &texture, json &o) { + if (texture.sampler > -1) { + SerializeNumberProperty("sampler", texture.sampler, o); + } + if (texture.source > -1) { + SerializeNumberProperty("source", texture.source, o); + } + if (texture.name.size()) { + SerializeStringProperty("name", texture.name, o); + } + if (texture.extras.Type() != NULL_TYPE) { + SerializeValue("extras", texture.extras, o); + } + SerializeExtensionMap(texture.extensions, o); +} + +/// +/// Serialize all properties except buffers and images. +/// +static void SerializeGltfModel(Model *model, json &o) { + // ACCESSORS + if (model->accessors.size()) { + json accessors; + JsonReserveArray(accessors, model->accessors.size()); + for (unsigned int i = 0; i < model->accessors.size(); ++i) { + json accessor; + SerializeGltfAccessor(model->accessors[i], accessor); + JsonPushBack(accessors, std::move(accessor)); + } + JsonAddMember(o, "accessors", std::move(accessors)); + } + + // ANIMATIONS + if (model->animations.size()) { + json animations; + JsonReserveArray(animations, model->animations.size()); + for (unsigned int i = 0; i < model->animations.size(); ++i) { + if (model->animations[i].channels.size()) { + json animation; + SerializeGltfAnimation(model->animations[i], animation); + JsonPushBack(animations, std::move(animation)); + } + } + + JsonAddMember(o, "animations", std::move(animations)); + } + + // ASSET + json asset; + SerializeGltfAsset(model->asset, asset); + JsonAddMember(o, "asset", std::move(asset)); + + // BUFFERVIEWS + if (model->bufferViews.size()) { + json bufferViews; + JsonReserveArray(bufferViews, model->bufferViews.size()); + for (unsigned int i = 0; i < model->bufferViews.size(); ++i) { + json bufferView; + SerializeGltfBufferView(model->bufferViews[i], bufferView); + JsonPushBack(bufferViews, std::move(bufferView)); + } + JsonAddMember(o, "bufferViews", std::move(bufferViews)); + } + + // Extensions required + if (model->extensionsRequired.size()) { + SerializeStringArrayProperty("extensionsRequired", + model->extensionsRequired, o); + } + + // MATERIALS + if (model->materials.size()) { + json materials; + JsonReserveArray(materials, model->materials.size()); + for (unsigned int i = 0; i < model->materials.size(); ++i) { + json material; + SerializeGltfMaterial(model->materials[i], material); + + if (JsonIsNull(material)) { + // Issue 294. + // `material` does not have any required parameters + // so the result may be null(unmodified) when all material parameters have default value. + // + // null is not allowed thus we create an empty JSON object. + JsonSetObject(material); + } + JsonPushBack(materials, std::move(material)); + } + JsonAddMember(o, "materials", std::move(materials)); + } + + // MESHES + if (model->meshes.size()) { + json meshes; + JsonReserveArray(meshes, model->meshes.size()); + for (unsigned int i = 0; i < model->meshes.size(); ++i) { + json mesh; + SerializeGltfMesh(model->meshes[i], mesh); + JsonPushBack(meshes, std::move(mesh)); + } + JsonAddMember(o, "meshes", std::move(meshes)); + } + + // NODES + if (model->nodes.size()) { + json nodes; + JsonReserveArray(nodes, model->nodes.size()); + for (unsigned int i = 0; i < model->nodes.size(); ++i) { + json node; + SerializeGltfNode(model->nodes[i], node); + JsonPushBack(nodes, std::move(node)); + } + JsonAddMember(o, "nodes", std::move(nodes)); + } + + // SCENE + if (model->defaultScene > -1) { + SerializeNumberProperty("scene", model->defaultScene, o); + } + + // SCENES + if (model->scenes.size()) { + json scenes; + JsonReserveArray(scenes, model->scenes.size()); + for (unsigned int i = 0; i < model->scenes.size(); ++i) { + json currentScene; + SerializeGltfScene(model->scenes[i], currentScene); + JsonPushBack(scenes, std::move(currentScene)); + } + JsonAddMember(o, "scenes", std::move(scenes)); + } + + // SKINS + if (model->skins.size()) { + json skins; + JsonReserveArray(skins, model->skins.size()); + for (unsigned int i = 0; i < model->skins.size(); ++i) { + json skin; + SerializeGltfSkin(model->skins[i], skin); + JsonPushBack(skins, std::move(skin)); + } + JsonAddMember(o, "skins", std::move(skins)); + } + + // TEXTURES + if (model->textures.size()) { + json textures; + JsonReserveArray(textures, model->textures.size()); + for (unsigned int i = 0; i < model->textures.size(); ++i) { + json texture; + SerializeGltfTexture(model->textures[i], texture); + JsonPushBack(textures, std::move(texture)); + } + JsonAddMember(o, "textures", std::move(textures)); + } + + // SAMPLERS + if (model->samplers.size()) { + json samplers; + JsonReserveArray(samplers, model->samplers.size()); + for (unsigned int i = 0; i < model->samplers.size(); ++i) { + json sampler; + SerializeGltfSampler(model->samplers[i], sampler); + JsonPushBack(samplers, std::move(sampler)); + } + JsonAddMember(o, "samplers", std::move(samplers)); + } + + // CAMERAS + if (model->cameras.size()) { + json cameras; + JsonReserveArray(cameras, model->cameras.size()); + for (unsigned int i = 0; i < model->cameras.size(); ++i) { + json camera; + SerializeGltfCamera(model->cameras[i], camera); + JsonPushBack(cameras, std::move(camera)); + } + JsonAddMember(o, "cameras", std::move(cameras)); + } + + // EXTENSIONS + SerializeExtensionMap(model->extensions, o); + + auto extensionsUsed = model->extensionsUsed; + + // LIGHTS as KHR_lights_punctual + if (model->lights.size()) { + json lights; + JsonReserveArray(lights, model->lights.size()); + for (unsigned int i = 0; i < model->lights.size(); ++i) { + json light; + SerializeGltfLight(model->lights[i], light); + JsonPushBack(lights, std::move(light)); + } + json khr_lights_cmn; + JsonAddMember(khr_lights_cmn, "lights", std::move(lights)); + json ext_j; + + { + json_const_iterator it; + if (FindMember(o, "extensions", it)) { + JsonAssign(ext_j, GetValue(it)); + } + } + + JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn)); + + JsonAddMember(o, "extensions", std::move(ext_j)); + + // Also add "KHR_lights_punctual" to `extensionsUsed` + { + auto has_khr_lights_punctual = + std::find_if(extensionsUsed.begin(), extensionsUsed.end(), + [](const std::string &s) { + return (s.compare("KHR_lights_punctual") == 0); + }); + + if (has_khr_lights_punctual == extensionsUsed.end()) { + extensionsUsed.push_back("KHR_lights_punctual"); + } + } + } + + // Extensions used + if (model->extensionsUsed.size()) { + SerializeStringArrayProperty("extensionsUsed", extensionsUsed, o); + } + + // EXTRAS + if (model->extras.Type() != NULL_TYPE) { + SerializeValue("extras", model->extras, o); + } +} + +static bool WriteGltfStream(std::ostream &stream, const std::string &content) { + stream << content << std::endl; + return true; +} + +static bool WriteGltfFile(const std::string &output, + const std::string &content) { +#ifdef _WIN32 +#if defined(_MSC_VER) + std::ofstream gltfFile(UTF8ToWchar(output).c_str()); +#elif defined(__GLIBCXX__) + int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream gltfFile(&wfile_buf); + if (!wfile_buf.is_open()) return false; +#else + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; +#endif +#else + std::ofstream gltfFile(output.c_str()); + if (!gltfFile.is_open()) return false; +#endif + return WriteGltfStream(gltfFile, content); +} + +static void WriteBinaryGltfStream(std::ostream &stream, + const std::string &content, + const std::vector &binBuffer) { + const std::string header = "glTF"; + const int version = 2; + + // https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number + auto roundUp = [](uint32_t numToRound, uint32_t multiple) { + if (multiple == 0) return numToRound; + + uint32_t remainder = numToRound % multiple; + if (remainder == 0) return numToRound; + + return numToRound + multiple - remainder; + }; + + const uint32_t padding_size = + roundUp(uint32_t(content.size()), 4) - uint32_t(content.size()); + + // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info. + // Chunk data must be located at 4-byte boundary. + const uint32_t length = + 12 + 8 + roundUp(uint32_t(content.size()), 4) + + (binBuffer.size() ? (8 + roundUp(uint32_t(binBuffer.size()), 4)) : 0); + + stream.write(header.c_str(), std::streamsize(header.size())); + stream.write(reinterpret_cast(&version), sizeof(version)); + stream.write(reinterpret_cast(&length), sizeof(length)); + + // JSON chunk info, then JSON data + const uint32_t model_length = uint32_t(content.size()) + padding_size; + const uint32_t model_format = 0x4E4F534A; + stream.write(reinterpret_cast(&model_length), + sizeof(model_length)); + stream.write(reinterpret_cast(&model_format), + sizeof(model_format)); + stream.write(content.c_str(), std::streamsize(content.size())); + + // Chunk must be multiplies of 4, so pad with spaces + if (padding_size > 0) { + const std::string padding = std::string(size_t(padding_size), ' '); + stream.write(padding.c_str(), std::streamsize(padding.size())); + } + if (binBuffer.size() > 0) { + const uint32_t bin_padding_size = + roundUp(uint32_t(binBuffer.size()), 4) - uint32_t(binBuffer.size()); + // BIN chunk info, then BIN data + const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size; + const uint32_t bin_format = 0x004e4942; + stream.write(reinterpret_cast(&bin_length), + sizeof(bin_length)); + stream.write(reinterpret_cast(&bin_format), + sizeof(bin_format)); + stream.write(reinterpret_cast(binBuffer.data()), + std::streamsize(binBuffer.size())); + // Chunksize must be multiplies of 4, so pad with zeroes + if (bin_padding_size > 0) { + const std::vector padding = + std::vector(size_t(bin_padding_size), 0); + stream.write(reinterpret_cast(padding.data()), + std::streamsize(padding.size())); + } + } +} + +static void WriteBinaryGltfFile(const std::string &output, + const std::string &content, + const std::vector &binBuffer) { +#ifdef _WIN32 +#if defined(_MSC_VER) + std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary); +#elif defined(__GLIBCXX__) + int file_descriptor = _wopen(UTF8ToWchar(output).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream gltfFile(&wfile_buf); +#else + std::ofstream gltfFile(output.c_str(), std::ios::binary); +#endif +#else + std::ofstream gltfFile(output.c_str(), std::ios::binary); +#endif + WriteBinaryGltfStream(gltfFile, content, binBuffer); +} + +bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream, + bool prettyPrint = true, + bool writeBinary = false) { + JsonDocument output; + + /// Serialize all properties except buffers and images. + SerializeGltfModel(model, output); + + // BUFFERS + std::vector binBuffer; + if (model->buffers.size()) { + json buffers; + JsonReserveArray(buffers, model->buffers.size()); + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + json buffer; + if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { + SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); + } else { + SerializeGltfBuffer(model->buffers[i], buffer); + } + JsonPushBack(buffers, std::move(buffer)); + } + JsonAddMember(output, "buffers", std::move(buffers)); + } + + // IMAGES + if (model->images.size()) { + json images; + JsonReserveArray(images, model->images.size()); + for (unsigned int i = 0; i < model->images.size(); ++i) { + json image; + + std::string dummystring = ""; + // UpdateImageObject need baseDir but only uses it if embeddedImages is + // enabled, since we won't write separate images when writing to a stream + // we + UpdateImageObject(model->images[i], dummystring, int(i), false, + &this->WriteImageData, this->write_image_user_data_); + SerializeGltfImage(model->images[i], image); + JsonPushBack(images, std::move(image)); + } + JsonAddMember(output, "images", std::move(images)); + } + + if (writeBinary) { + WriteBinaryGltfStream(stream, JsonToString(output), binBuffer); + } else { + WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1)); + } + + return true; +} + +bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename, + bool embedImages = false, + bool embedBuffers = false, + bool prettyPrint = true, + bool writeBinary = false) { + JsonDocument output; + std::string defaultBinFilename = GetBaseFilename(filename); + std::string defaultBinFileExt = ".bin"; + std::string::size_type pos = + defaultBinFilename.rfind('.', defaultBinFilename.length()); + + if (pos != std::string::npos) { + defaultBinFilename = defaultBinFilename.substr(0, pos); + } + std::string baseDir = GetBaseDir(filename); + if (baseDir.empty()) { + baseDir = "./"; + } + /// Serialize all properties except buffers and images. + SerializeGltfModel(model, output); + + // BUFFERS + std::vector usedUris; + std::vector binBuffer; + if (model->buffers.size()) { + json buffers; + JsonReserveArray(buffers, model->buffers.size()); + for (unsigned int i = 0; i < model->buffers.size(); ++i) { + json buffer; + if (writeBinary && i == 0 && model->buffers[i].uri.empty()) { + SerializeGltfBufferBin(model->buffers[i], buffer, binBuffer); + } else if (embedBuffers) { + SerializeGltfBuffer(model->buffers[i], buffer); + } else { + std::string binSavePath; + std::string binUri; + if (!model->buffers[i].uri.empty() && + !IsDataURI(model->buffers[i].uri)) { + binUri = model->buffers[i].uri; + } else { + binUri = defaultBinFilename + defaultBinFileExt; + bool inUse = true; + int numUsed = 0; + while (inUse) { + inUse = false; + for (const std::string &usedName : usedUris) { + if (binUri.compare(usedName) != 0) continue; + inUse = true; + binUri = defaultBinFilename + std::to_string(numUsed++) + + defaultBinFileExt; + break; + } + } + } + usedUris.push_back(binUri); + binSavePath = JoinPath(baseDir, binUri); + if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath, + binUri)) { + return false; + } + } + JsonPushBack(buffers, std::move(buffer)); + } + JsonAddMember(output, "buffers", std::move(buffers)); + } + + // IMAGES + if (model->images.size()) { + json images; + JsonReserveArray(images, model->images.size()); + for (unsigned int i = 0; i < model->images.size(); ++i) { + json image; + + UpdateImageObject(model->images[i], baseDir, int(i), embedImages, + &this->WriteImageData, this->write_image_user_data_); + SerializeGltfImage(model->images[i], image); + JsonPushBack(images, std::move(image)); + } + JsonAddMember(output, "images", std::move(images)); + } + + if (writeBinary) { + WriteBinaryGltfFile(filename, JsonToString(output), binBuffer); + } else { + WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1))); + } + + return true; +} } // namespace tinygltf @@ -5378,4 +7715,4 @@ namespace tinygltf { #pragma clang diagnostic pop #endif -#endif // TINYGLTF_IMPLEMENTATION \ No newline at end of file +#endif // TINYGLTF_IMPLEMENTATION diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt index 7ee18a30e..5f6879f71 100644 --- a/WickedEngine/ArchiveVersionHistory.txt +++ b/WickedEngine/ArchiveVersionHistory.txt @@ -1,5 +1,6 @@ This file contains changelog of wiArchive versions +56: serialized Material specularColor (specular-glossiness workflow) 55: removed area lights 54: MaterialComponent::subsurfaceScattering color instead of profile 53: Support Morph Target diff --git a/WickedEngine/RenderPath3D.cpp b/WickedEngine/RenderPath3D.cpp index fdbd381ae..ab94f482d 100644 --- a/WickedEngine/RenderPath3D.cpp +++ b/WickedEngine/RenderPath3D.cpp @@ -614,7 +614,6 @@ void RenderPath3D::Render() const wiJobSystem::Execute(ctx, [this, cmd](wiJobArgs args) { GraphicsDevice* device = wiRenderer::GetDevice(); - device->EventBegin("Opaque Z-prepass", cmd); wiRenderer::UpdateCameraCB( *camera, @@ -625,6 +624,7 @@ void RenderPath3D::Render() const device->RenderPassBegin(&renderpass_depthprepass, cmd); + device->EventBegin("Opaque Z-prepass", cmd); auto range = wiProfiler::BeginRangeGPU("Z-Prepass", cmd); Viewport vp; @@ -634,6 +634,7 @@ void RenderPath3D::Render() const wiRenderer::DrawScene(visibility_main, RENDERPASS_DEPTHONLY, cmd, drawscene_flags); wiProfiler::EndRange(range); + device->EventEnd(cmd); wiRenderer::OcclusionCulling_Render(*camera, visibility_main, cmd); @@ -696,7 +697,6 @@ void RenderPath3D::Render() const ); } - device->EventEnd(cmd); }); // Planar reflections depth prepass + Light culling: @@ -704,10 +704,8 @@ void RenderPath3D::Render() const { cmd = device->BeginCommandList(); wiJobSystem::Execute(ctx, [cmd, this](wiJobArgs args) { - auto range = wiProfiler::BeginRangeGPU("Planar Reflections Z-Prepass", cmd); GraphicsDevice* device = wiRenderer::GetDevice(); - device->EventBegin("Planar reflections Z-Prepass", cmd); wiRenderer::UpdateCameraCB( camera_reflection, @@ -716,6 +714,9 @@ void RenderPath3D::Render() const cmd ); + device->EventBegin("Planar reflections Z-Prepass", cmd); + auto range = wiProfiler::BeginRangeGPU("Planar Reflections Z-Prepass", cmd); + Viewport vp; vp.Width = (float)depthBuffer_Reflection.GetDesc().Width; vp.Height = (float)depthBuffer_Reflection.GetDesc().Height; @@ -727,8 +728,10 @@ void RenderPath3D::Render() const device->RenderPassEnd(cmd); + wiProfiler::EndRange(range); // Planar Reflections + device->EventEnd(cmd); + wiRenderer::ComputeTiledLightCulling( - *camera, depthBuffer_Reflection, tileFrustums, entityTiles_Opaque, @@ -737,8 +740,6 @@ void RenderPath3D::Render() const cmd ); - device->EventEnd(cmd); - wiProfiler::EndRange(range); // Planar Reflections }); } @@ -775,10 +776,8 @@ void RenderPath3D::Render() const { cmd = device->BeginCommandList(); wiJobSystem::Execute(ctx, [cmd, this](wiJobArgs args) { - auto range = wiProfiler::BeginRangeGPU("Planar Reflections", cmd); GraphicsDevice* device = wiRenderer::GetDevice(); - device->EventBegin("Planar reflections", cmd); wiRenderer::UpdateCameraCB( camera_reflection, @@ -787,6 +786,9 @@ void RenderPath3D::Render() const cmd ); + device->EventBegin("Planar reflections", cmd); + auto range = wiProfiler::BeginRangeGPU("Planar Reflections", cmd); + Viewport vp; vp.Width = (float)depthBuffer_Reflection.GetDesc().Width; vp.Height = (float)depthBuffer_Reflection.GetDesc().Height; @@ -809,8 +811,8 @@ void RenderPath3D::Render() const device->RenderPassEnd(cmd); - device->EventEnd(cmd); wiProfiler::EndRange(range); // Planar Reflections + device->EventEnd(cmd); }); } @@ -833,7 +835,6 @@ void RenderPath3D::Render() const { auto range = wiProfiler::BeginRangeGPU("Entity Culling", cmd); wiRenderer::ComputeTiledLightCulling( - *camera, depthBuffer_Copy, tileFrustums, entityTiles_Opaque, diff --git a/WickedEngine/ShaderInterop_Renderer.h b/WickedEngine/ShaderInterop_Renderer.h index 236d5a2a2..9a9d613ce 100644 --- a/WickedEngine/ShaderInterop_Renderer.h +++ b/WickedEngine/ShaderInterop_Renderer.h @@ -12,6 +12,7 @@ static const uint SHADERMATERIAL_OPTION_BIT_USE_WIND = 1 << 4; struct ShaderMaterial { float4 baseColor; + float4 specularColor; float4 emissiveColor; float4 subsurfaceScattering; float4 subsurfaceScattering_inv; diff --git a/WickedEngine/brdf.hlsli b/WickedEngine/brdf.hlsli index f63beab4b..b8a4e2bdb 100644 --- a/WickedEngine/brdf.hlsli +++ b/WickedEngine/brdf.hlsli @@ -14,27 +14,19 @@ float3 F_Fresnel(float3 SpecularColor, float VoH) return 0.5 * sqr((g - VoH) / (g + VoH)) * (1 + sqr(((g + VoH) * VoH - 1) / ((g - VoH) * VoH + 1))); } -float3 ComputeAlbedo(in float4 baseColor, in float reflectance, in float metalness) -{ - return lerp(lerp(baseColor.rgb, float3(0, 0, 0), reflectance), float3(0, 0, 0), metalness); -} -float3 ComputeF0(in float4 baseColor, in float reflectance, in float metalness) -{ - return lerp(lerp(float3(0, 0, 0), float3(1, 1, 1), reflectance), baseColor.rgb, metalness); -} - struct Surface { + // Fill these yourself: float3 P; // world space position float3 N; // world space normal float3 V; // world space view vector - float4 baseColor; // base color [0 -> 1] (rgba) + + float3 albedo; // diffuse light absorbtion value (rgb) + float3 f0; // fresnel value (rgb) (reflectance at incidence angle, also known as specular color) float roughness; // roughness: [0:smooth -> 1:rough] (perceptual) float occlusion; // occlusion [0 -> 1] - float metalness; // metalness [0:dielectric -> 1:metal] - float reflectance; // reflectivity [0:diffuse -> 1:specular] float4 emissiveColor; // light emission [0 -> 1] float4 refraction; // refraction color (rgb), refraction amount (a) float2 pixel; // pixel coordinate (used for randomization effects) @@ -46,33 +38,80 @@ struct Surface float4 sss_inv; // 1 / (1 + sss) uint layerMask; + // These will be computed when calling Update(): float alphaRoughness; // roughness remapped from perceptual to a "more linear change in roughness" float alphaRoughnessSq; // roughness input to brdf functions float NdotV; // cos(angle between normal and view vector) - float3 f0; // fresnel value (rgb) (reflectance at incidence angle) float f90; // reflectance at grazing angle - float3 albedo; // diffuse light absorbtion value (rgb) float3 R; // reflection vector float3 F; // fresnel term computed from NdotV - - // Aniso params: float TdotV; float BdotV; float at; float ab; - inline void Update() + inline void init() + { + albedo = 1; + f0 = 0; + roughness = 1; + occlusion = 1; + emissiveColor = 0; + refraction = 0; + pixel = 0; + screenUV = 0; + T = 0; + B = 0; + anisotropy = 0; + sss = 0; + sss_inv = 1; + layerMask = ~0; + } + + inline void create( + in ShaderMaterial material, + in float4 baseColor, + in float4 surfaceMap + ) + { + init(); + + roughness = material.roughness; + float metalness = material.metalness; + float reflectance = material.reflectance; + f0 = material.specularColor.rgb * material.specularColor.a; + + if (material.IsUsingSpecularGlossinessWorkflow()) + { + // Specular-glossiness workflow: + roughness *= saturate(1 - surfaceMap.a); + f0 *= DEGAMMA(surfaceMap.rgb); + albedo = baseColor.rgb; + } + else + { + // Metallic-roughness workflow: + if (material.IsOcclusionEnabled_Primary()) + { + occlusion = surfaceMap.r; + } + roughness *= surfaceMap.g; + metalness *= surfaceMap.b; + reflectance *= surfaceMap.a; + albedo = lerp(lerp(baseColor.rgb, float3(0, 0, 0), reflectance), float3(0, 0, 0), metalness); + f0 = lerp(lerp(float3(0, 0, 0), float3(1, 1, 1), reflectance), baseColor.rgb, metalness); + } + } + + inline void update() { alphaRoughness = roughness * roughness; alphaRoughnessSq = alphaRoughness * alphaRoughness; NdotV = abs(dot(N, V)) + 1e-5; - albedo = ComputeAlbedo(baseColor, reflectance, metalness); - f0 = ComputeF0(baseColor, reflectance, metalness); - - R = -reflect(V, N); f90 = saturate(50.0 * dot(f0, 0.33)); + R = -reflect(V, N); F = F_Schlick(f0, f90, NdotV); TdotV = dot(T, V); @@ -85,45 +124,6 @@ struct Surface #endif // BRDF_CARTOON } }; -inline Surface CreateSurface( - in float3 P, - in float3 N, - in float3 V, - in float4 baseColor, - in float roughness, - in float occlusion, - in float metalness, - in float reflectance, - in float4 emissiveColor = 0, - in float anisotropy = 0, - in float3 T = 0, - in float3 B = 0) -{ - Surface surface; - - surface.P = P; - surface.N = N; - surface.V = V; - surface.baseColor = baseColor; - surface.roughness = roughness; - surface.occlusion = occlusion; - surface.metalness = metalness; - surface.reflectance = reflectance; - surface.emissiveColor = emissiveColor; - surface.refraction = 0; - surface.pixel = 0; - surface.screenUV = 0; - surface.anisotropy = anisotropy; - surface.sss = 0; - surface.sss_inv = 1; - surface.T = T; - surface.B = B; - surface.layerMask = ~0; - - surface.Update(); - - return surface; -} struct SurfaceToLight { @@ -142,49 +142,46 @@ struct SurfaceToLight float BdotL; float TdotH; float BdotH; -}; -inline SurfaceToLight CreateSurfaceToLight(in Surface surface, in float3 L) -{ - SurfaceToLight surfaceToLight; - surfaceToLight.L = L; - surfaceToLight.H = normalize(L + surface.V); + inline void create(in Surface surface, in float3 Lnormalized) + { + L = Lnormalized; + H = normalize(L + surface.V); - surfaceToLight.NdotL = dot(surfaceToLight.L, surface.N); + NdotL = dot(L, surface.N); - surfaceToLight.NdotL_sss = (surfaceToLight.NdotL + surface.sss.rgb) * surface.sss_inv.rgb; + NdotL_sss = (NdotL + surface.sss.rgb) * surface.sss_inv.rgb; - surfaceToLight.NdotV = saturate(dot(surface.N, surface.V)); - surfaceToLight.NdotH = saturate(dot(surface.N, surfaceToLight.H)); - surfaceToLight.LdotH = saturate(dot(surfaceToLight.L, surfaceToLight.H)); - surfaceToLight.VdotH = saturate(dot(surface.V, surfaceToLight.H)); + NdotV = saturate(dot(surface.N, surface.V)); + NdotH = saturate(dot(surface.N, H)); + LdotH = saturate(dot(L, H)); + VdotH = saturate(dot(surface.V, H)); - surfaceToLight.F = F_Schlick(surface.f0, surface.f90, surfaceToLight.VdotH); + F = F_Schlick(surface.f0, surface.f90, VdotH); - surfaceToLight.TdotL = dot(surface.T, L); - surfaceToLight.BdotL = dot(surface.B, L); - surfaceToLight.TdotH = dot(surface.T, surfaceToLight.H); - surfaceToLight.BdotH = dot(surface.B, surfaceToLight.H); + TdotL = dot(surface.T, L); + BdotL = dot(surface.B, L); + TdotH = dot(surface.T, H); + BdotH = dot(surface.B, H); #ifdef BRDF_CARTOON - // SSS is handled differently in cartoon shader: - // 1) The diffuse wraparound is monochrome at first to avoid banding with smoothstep() - surfaceToLight.NdotL_sss = (surfaceToLight.NdotL + surface.sss.a) * surface.sss_inv.a; + // SSS is handled differently in cartoon shader: + // 1) The diffuse wraparound is monochrome at first to avoid banding with smoothstep() + NdotL_sss = (NdotL + surface.sss.a) * surface.sss_inv.a; - surfaceToLight.NdotL = smoothstep(0.005, 0.05, surfaceToLight.NdotL); - surfaceToLight.NdotL_sss = smoothstep(0.005, 0.05, surfaceToLight.NdotL_sss); - surfaceToLight.NdotH = smoothstep(0.98, 0.99, surfaceToLight.NdotH); + NdotL = smoothstep(0.005, 0.05, NdotL); + NdotL_sss = smoothstep(0.005, 0.05, NdotL_sss); + NdotH = smoothstep(0.98, 0.99, NdotH); - // SSS is handled differently in cartoon shader: - // 2) The diffuse wraparound is tinted after smoothstep - surfaceToLight.NdotL_sss = (surfaceToLight.NdotL_sss + surface.sss.rgb) * surface.sss_inv.rgb; + // SSS is handled differently in cartoon shader: + // 2) The diffuse wraparound is tinted after smoothstep + NdotL_sss = (NdotL_sss + surface.sss.rgb) * surface.sss_inv.rgb; #endif // BRDF_CARTOON - surfaceToLight.NdotL = saturate(surfaceToLight.NdotL); - surfaceToLight.NdotL_sss = saturate(surfaceToLight.NdotL_sss); - - return surfaceToLight; -} + NdotL = saturate(NdotL); + NdotL_sss = saturate(NdotL_sss); + } +}; // Smith Joint GGX // Note: Vis = G / (4 * NdotL * NdotV) diff --git a/WickedEngine/captureImpostorPS_surface.hlsl b/WickedEngine/captureImpostorPS_surface.hlsl index 6c367a4d3..ab5c7a72c 100644 --- a/WickedEngine/captureImpostorPS_surface.hlsl +++ b/WickedEngine/captureImpostorPS_surface.hlsl @@ -11,7 +11,7 @@ float4 main(PixelInputType input) : SV_Target0 if (g_xMaterial.IsUsingSpecularGlossinessWorkflow()) { - ConvertToSpecularGlossiness(surface_occlusion_roughness_metallic_reflectance); + //ConvertToSpecularGlossiness(surface_occlusion_roughness_metallic_reflectance); } } else diff --git a/WickedEngine/cubeShadowVS.hlsl b/WickedEngine/cubeShadowVS.hlsl index efcd309a1..cc7b21e97 100644 --- a/WickedEngine/cubeShadowVS.hlsl +++ b/WickedEngine/cubeShadowVS.hlsl @@ -16,7 +16,9 @@ VertexOut main(Input_Object_POS input) VertexOut output; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); uint frustum_index = input.inst.userdata.y; output.RTIndex = xCubemapRenderCams[frustum_index].properties.x; diff --git a/WickedEngine/cubeShadowVS_alphatest.hlsl b/WickedEngine/cubeShadowVS_alphatest.hlsl index a9a1d72d2..eacc8a135 100644 --- a/WickedEngine/cubeShadowVS_alphatest.hlsl +++ b/WickedEngine/cubeShadowVS_alphatest.hlsl @@ -17,7 +17,9 @@ VertexOut main(Input_Object_POS_TEX input) VertexOut output; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); uint frustum_index = input.inst.userdata.y; output.RTIndex = xCubemapRenderCams[frustum_index].properties.x; diff --git a/WickedEngine/emittedparticlePS_soft.hlsl b/WickedEngine/emittedparticlePS_soft.hlsl index 9431b4e6b..d7f2cc35f 100644 --- a/WickedEngine/emittedparticlePS_soft.hlsl +++ b/WickedEngine/emittedparticlePS_soft.hlsl @@ -49,11 +49,18 @@ float4 main(VertextoPixel input) : SV_TARGET N = mul((float3x3)g_xCamera_InvV, N); N = normalize(N); - Lighting lighting = CreateLighting(0, 0, GetAmbient(N), 0); - Surface surface = CreateSurface(input.P, N, 0, color, 1, 1, 0, 0); + Lighting lighting; + lighting.create(0, 0, GetAmbient(N), 0); + + Surface surface; + surface.create(g_xMaterial, color, 0); + surface.P = input.P; + surface.N = N; + surface.V = 0; surface.pixel = pixel; surface.sss = g_xMaterial.subsurfaceScattering; surface.sss_inv = g_xMaterial.subsurfaceScattering_inv; + surface.update(); TiledLighting(surface, lighting); diff --git a/WickedEngine/envMapVS.hlsl b/WickedEngine/envMapVS.hlsl index 13e1c23a6..8b0ec93f6 100644 --- a/WickedEngine/envMapVS.hlsl +++ b/WickedEngine/envMapVS.hlsl @@ -7,7 +7,9 @@ PSIn_EnvmapRendering main(Input_Object_ALL input) PSIn_EnvmapRendering output; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.normal = normalize(mul((float3x3)WORLD, surface.normal)); surface.tangent.xyz = normalize(mul((float3x3)WORLD, surface.tangent.xyz)); diff --git a/WickedEngine/globals.hlsli b/WickedEngine/globals.hlsli index 7e645ab8b..08b4f5584 100644 --- a/WickedEngine/globals.hlsli +++ b/WickedEngine/globals.hlsli @@ -65,13 +65,6 @@ inline float2 GetInternalResolution() { return g_xFrame_InternalResolution; } inline float GetTime() { return g_xFrame_Time; } inline uint2 GetTemporalAASampleRotation() { return uint2((g_xFrame_TemporalAASampleRotation >> 0) & 0x000000FF, (g_xFrame_TemporalAASampleRotation >> 8) & 0x000000FF); } inline bool IsStaticSky() { return g_xFrame_StaticSkyGamma > 0.0; } -inline void ConvertToSpecularGlossiness(inout float4 surface_occlusion_roughness_metallic_reflectance) -{ - surface_occlusion_roughness_metallic_reflectance.r = 1; - surface_occlusion_roughness_metallic_reflectance.g = 1 - surface_occlusion_roughness_metallic_reflectance.a; - surface_occlusion_roughness_metallic_reflectance.b = max(surface_occlusion_roughness_metallic_reflectance.r, max(surface_occlusion_roughness_metallic_reflectance.g, surface_occlusion_roughness_metallic_reflectance.b)); - surface_occlusion_roughness_metallic_reflectance.a = 0.02; -} inline float GetFogAmount(float dist) { diff --git a/WickedEngine/hairparticlePS.hlsl b/WickedEngine/hairparticlePS.hlsl index 5edfa3734..9136e0d48 100644 --- a/WickedEngine/hairparticlePS.hlsl +++ b/WickedEngine/hairparticlePS.hlsl @@ -17,9 +17,18 @@ GBUFFEROutputType main(VertexToPixel input) float dist = length(V); V /= dist; float emissive = 0; - Surface surface = CreateSurface(input.pos3D, input.nor, V, color, 1, 1, 0, 0); - Lighting lighting = CreateLighting(0, 0, GetAmbient(surface.N), 0); + + Surface surface; + surface.create(g_xMaterial, color, 0); + surface.P = input.pos3D; + surface.N = input.nor; + surface.V = V; surface.pixel = input.pos.xy; + surface.update(); + + Lighting lighting; + lighting.create(0, 0, GetAmbient(surface.N), 0); + float depth = input.pos.z; float3 reflection = 0; diff --git a/WickedEngine/impostorPS.hlsl b/WickedEngine/impostorPS.hlsl index 9d7f46e10..0c4daffb6 100644 --- a/WickedEngine/impostorPS.hlsl +++ b/WickedEngine/impostorPS.hlsl @@ -23,9 +23,20 @@ GBUFFEROutputType main(VSOut input) float3 V = g_xCamera_CamPos - input.pos3D; float dist = length(V); V /= dist; - Surface surface = CreateSurface(input.pos3D, N, V, color, roughness, ao, metalness, reflectance); - Lighting lighting = CreateLighting(0, 0, GetAmbient(surface.N), 0); + + Surface surface; + surface.init(); + surface.roughness = roughness; + surface.albedo = lerp(lerp(color.rgb, float3(0, 0, 0), reflectance), float3(0, 0, 0), metalness); + surface.f0 = lerp(lerp(float3(0, 0, 0), float3(1, 1, 1), reflectance), color.rgb, metalness); + surface.P = input.pos3D; + surface.N = N; + surface.V = V; surface.pixel = input.pos.xy; + surface.update(); + + Lighting lighting; + lighting.create(0, 0, GetAmbient(surface.N), 0); float2 ScreenCoord = surface.pixel * g_xFrame_InternalResolution_rcp; float2 pos2D = ScreenCoord * 2 - 1; diff --git a/WickedEngine/lightingHF.hlsli b/WickedEngine/lightingHF.hlsli index 20d21525f..3a4de2f14 100644 --- a/WickedEngine/lightingHF.hlsli +++ b/WickedEngine/lightingHF.hlsli @@ -19,21 +19,20 @@ struct Lighting { LightingPart direct; LightingPart indirect; -}; -inline Lighting CreateLighting( - in float3 diffuse_direct, - in float3 specular_direct, - in float3 diffuse_indirect, - in float3 specular_indirect) -{ - Lighting lighting; - lighting.direct.diffuse = diffuse_direct; - lighting.direct.specular = specular_direct; - lighting.indirect.diffuse = diffuse_indirect; - lighting.indirect.specular = specular_indirect; - return lighting; -} + inline void create( + in float3 diffuse_direct, + in float3 specular_direct, + in float3 diffuse_indirect, + in float3 specular_indirect + ) + { + direct.diffuse = diffuse_direct; + direct.specular = specular_direct; + indirect.diffuse = diffuse_indirect; + indirect.specular = specular_indirect; + } +}; // Combine the direct and indirect lighting into final contribution inline LightingPart CombineLighting(in Surface surface, in Lighting lighting) @@ -151,7 +150,9 @@ inline float shadowTrace(in Surface surface, in float3 L, in float dist) inline void DirectionalLight(in ShaderEntity light, in Surface surface, inout Lighting lighting) { float3 L = light.GetDirection().xyz; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); [branch] if (any(surfaceToLight.NdotL_sss)) @@ -239,7 +240,8 @@ inline void PointLight(in ShaderEntity light, in Surface surface, inout Lighting const float dist = sqrt(dist2); L /= dist; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); [branch] if (any(surfaceToLight.NdotL_sss)) @@ -290,7 +292,8 @@ inline void SpotLight(in ShaderEntity light, in Surface surface, inout Lighting const float dist = sqrt(dist2); L /= dist; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); [branch] if (any(surfaceToLight.NdotL_sss)) @@ -486,7 +489,8 @@ inline void SphereLight(in ShaderEntity light, in Surface surface, inout Lightin float3 lightColor = light.GetColor().rgb * light.GetEnergy() * fLight; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.Create(surface, L); lighting.direct.specular += max(0, lightColor * BRDF_GetSpecular(surface, surfaceToLight)); lighting.direct.diffuse += max(0, lightColor / PI); @@ -550,7 +554,8 @@ inline void DiscLight(in ShaderEntity light, in Surface surface, inout Lighting float3 lightColor = light.GetColor().rgb * light.GetEnergy() * fLight; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.Create(surface, L); lighting.direct.specular += max(0, specularAttenuation * lightColor * BRDF_GetSpecular(surface, surfaceToLight)); lighting.direct.diffuse += max(0, lightColor / PI); @@ -675,7 +680,8 @@ inline void RectangleLight(in ShaderEntity light, in Surface surface, inout Ligh float3 lightColor = light.GetColor().rgb * light.GetEnergy() * fLight; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.Create(surface, L); lighting.direct.specular += max(0, specularAttenuation * lightColor * BRDF_GetSpecular(surface, surfaceToLight)); lighting.direct.diffuse += max(0, lightColor / PI); @@ -775,7 +781,8 @@ inline void TubeLight(in ShaderEntity light, in Surface surface, inout Lighting float3 lightColor = light.GetColor().rgb * light.GetEnergy() * fLight; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.Create(surface, L); lighting.direct.specular += max(0, lightColor * BRDF_GetSpecular(surface, surfaceToLight)); lighting.direct.diffuse += max(0, lightColor / PI); diff --git a/WickedEngine/objectHF.hlsli b/WickedEngine/objectHF.hlsli index 043d86d62..4c6a89c14 100644 --- a/WickedEngine/objectHF.hlsli +++ b/WickedEngine/objectHF.hlsli @@ -721,7 +721,7 @@ GBUFFEROutputType main(PIXELINPUT input) ParallaxOcclusionMapping(input.uvsets, surface.V, TBN); #endif // POM - float4 color; + float4 color = 1; [branch] if (g_xMaterial.uvset_baseColorMap >= 0 && (g_xFrame_Options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { @@ -729,10 +729,6 @@ GBUFFEROutputType main(PIXELINPUT input) color = texture_basecolormap.Sample(sampler_objectshader, UV_baseColorMap); color.rgb = DEGAMMA(color.rgb); } - else - { - color = 1; - } color *= input.color; #ifndef DISABLE_ALPHATEST @@ -758,76 +754,63 @@ GBUFFEROutputType main(PIXELINPUT input) #endif // SIMPLE_INPUT // Surface map: - float4 surface_occlusion_roughness_metallic_reflectance; + float4 surfaceMap = 1; [branch] if (g_xMaterial.uvset_surfaceMap >= 0) { const float2 UV_surfaceMap = g_xMaterial.uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; - surface_occlusion_roughness_metallic_reflectance = texture_surfacemap.Sample(sampler_objectshader, UV_surfaceMap); - if (!g_xMaterial.IsOcclusionEnabled_Primary()) - { - surface_occlusion_roughness_metallic_reflectance.r = 1; - } - if (g_xMaterial.IsUsingSpecularGlossinessWorkflow()) - { - ConvertToSpecularGlossiness(surface_occlusion_roughness_metallic_reflectance); - } + surfaceMap = texture_surfacemap.Sample(sampler_objectshader, UV_surfaceMap); } - else - { - surface_occlusion_roughness_metallic_reflectance = 1; - } - surface_occlusion_roughness_metallic_reflectance *= float4(1, g_xMaterial.roughness, g_xMaterial.metalness, g_xMaterial.reflectance); + + surface.create(g_xMaterial, color, surfaceMap); // Emissive map: - float4 emissiveColor = g_xMaterial.emissiveColor; + surface.emissiveColor = g_xMaterial.emissiveColor; [branch] - if (emissiveColor.a > 0 && g_xMaterial.uvset_emissiveMap >= 0) + if (surface.emissiveColor.a > 0 && g_xMaterial.uvset_emissiveMap >= 0) { const float2 UV_emissiveMap = g_xMaterial.uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; float4 emissiveMap = texture_emissivemap.Sample(sampler_objectshader, UV_emissiveMap); emissiveMap.rgb = DEGAMMA(emissiveMap.rgb); - emissiveColor *= emissiveMap; + surface.emissiveColor *= emissiveMap; } - #ifdef TERRAIN - color = 0; - surface_occlusion_roughness_metallic_reflectance = 0; - emissiveColor = 0; + surface.N = 0; + surface.albedo = 0; + surface.f0 = 0; + surface.roughness = 0; + surface.occlusion = 0; + surface.emissiveColor = 0; + + float4 sam; float4 blend_weights = input.color; blend_weights /= blend_weights.x + blend_weights.y + blend_weights.z + blend_weights.w; - float4 sam; float3 baseN = normalize(input.nor); - surface.N = 0; [branch] if (blend_weights.x > 0) { + float4 color2 = 1; [branch] if (g_xMaterial.uvset_baseColorMap >= 0 && (g_xFrame_Options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { float2 uv = g_xMaterial.uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_basecolormap.Sample(sampler_objectshader, uv); - sam.rgb = DEGAMMA(sam.rgb); + color2 = texture_basecolormap.Sample(sampler_objectshader, uv); + color2.rgb = DEGAMMA(color2.rgb); } - else - { - sam = 1; - } - color += sam * g_xMaterial.baseColor * blend_weights.x; + float4 surfaceMap2 = 1; [branch] if (g_xMaterial.uvset_surfaceMap >= 0) { float2 uv = g_xMaterial.uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_surfacemap.Sample(sampler_objectshader, uv); + surfaceMap2 = texture_surfacemap.Sample(sampler_objectshader, uv); } - else - { - sam = 1; - } - surface_occlusion_roughness_metallic_reflectance += sam * float4(1, g_xMaterial.roughness, g_xMaterial.metalness, g_xMaterial.reflectance) * blend_weights.x; + + Surface surface2; + surface2.N = baseN; + surface2.create(g_xMaterial, color2, surfaceMap2); [branch] if (g_xMaterial.normalMapStrength > 0 && g_xMaterial.uvset_normalMap >= 0) @@ -835,46 +818,50 @@ GBUFFEROutputType main(PIXELINPUT input) float2 uv = g_xMaterial.uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam.rgb = texture_normalmap.Sample(sampler_objectshader, uv); sam.rgb = sam.rgb * 2 - 1; - surface.N += lerp(baseN, mul(sam.rgb, TBN), g_xMaterial.normalMapStrength) * blend_weights.x; + surface2.N = lerp(baseN, mul(sam.rgb, TBN), g_xMaterial.normalMapStrength); } + surface2.emissiveColor = g_xMaterial.emissiveColor; [branch] if (g_xMaterial.uvset_emissiveMap >= 0 && any(g_xMaterial.emissiveColor)) { float2 uv = g_xMaterial.uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam = texture_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); - emissiveColor += sam * g_xMaterial.emissiveColor * blend_weights.x; + surface2.emissiveColor *= sam; } + + surface.N += surface2.N * blend_weights.x; + surface.albedo += surface2.albedo * blend_weights.x; + surface.f0 += surface2.f0 * blend_weights.x; + surface.roughness += surface2.roughness * blend_weights.x; + surface.occlusion += surface2.occlusion * blend_weights.x; + surface.emissiveColor += surface2.emissiveColor * blend_weights.x; } [branch] if (blend_weights.y > 0) { + float4 color2 = 1; [branch] if (g_xMaterial_blend1.uvset_baseColorMap >= 0 && (g_xFrame_Options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { float2 uv = g_xMaterial_blend1.uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend1_basecolormap.Sample(sampler_objectshader, uv); - sam.rgb = DEGAMMA(sam.rgb); + color2 = texture_blend1_basecolormap.Sample(sampler_objectshader, uv); + color2.rgb = DEGAMMA(color2.rgb); } - else - { - sam = 1; - } - color += sam * g_xMaterial_blend1.baseColor * blend_weights.y; + float4 surfaceMap2 = 1; [branch] if (g_xMaterial_blend1.uvset_surfaceMap >= 0) { float2 uv = g_xMaterial_blend1.uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend1_surfacemap.Sample(sampler_objectshader, uv); + surfaceMap2 = texture_blend1_surfacemap.Sample(sampler_objectshader, uv); } - else - { - sam = 1; - } - surface_occlusion_roughness_metallic_reflectance += sam * float4(1, g_xMaterial_blend1.roughness, g_xMaterial_blend1.metalness, g_xMaterial_blend1.reflectance) * blend_weights.y; + + Surface surface2; + surface2.N = baseN; + surface2.create(g_xMaterial_blend1, color2, surfaceMap2); [branch] if (g_xMaterial_blend1.normalMapStrength > 0 && g_xMaterial_blend1.uvset_normalMap >= 0) @@ -882,46 +869,50 @@ GBUFFEROutputType main(PIXELINPUT input) float2 uv = g_xMaterial_blend1.uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam.rgb = texture_blend1_normalmap.Sample(sampler_objectshader, uv); sam.rgb = sam.rgb * 2 - 1; - surface.N += lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend1.normalMapStrength) * blend_weights.y; + surface2.N = lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend1.normalMapStrength); } + surface2.emissiveColor = g_xMaterial_blend1.emissiveColor; [branch] - if (g_xMaterial_blend1.uvset_emissiveMap >= 0 && any(g_xMaterial_blend1.emissiveColor)) + if (g_xMaterial_blend1.uvset_emissiveMap >= 0 && any(g_xMaterial.emissiveColor)) { float2 uv = g_xMaterial_blend1.uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam = texture_blend1_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); - emissiveColor += sam * g_xMaterial_blend1.emissiveColor * blend_weights.y; + surface2.emissiveColor *= sam; } + + surface.N += surface2.N * blend_weights.y; + surface.albedo += surface2.albedo * blend_weights.y; + surface.f0 += surface2.f0 * blend_weights.y; + surface.roughness += surface2.roughness * blend_weights.y; + surface.occlusion += surface2.occlusion * blend_weights.y; + surface.emissiveColor += surface2.emissiveColor * blend_weights.y; } [branch] if (blend_weights.z > 0) { + float4 color2 = 1; [branch] if (g_xMaterial_blend2.uvset_baseColorMap >= 0 && (g_xFrame_Options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { float2 uv = g_xMaterial_blend2.uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend2_basecolormap.Sample(sampler_objectshader, uv); - sam.rgb = DEGAMMA(sam.rgb); + color2 = texture_blend2_basecolormap.Sample(sampler_objectshader, uv); + color2.rgb = DEGAMMA(color2.rgb); } - else - { - sam = 1; - } - color += sam * g_xMaterial_blend2.baseColor * blend_weights.z; + float4 surfaceMap2 = 1; [branch] if (g_xMaterial_blend2.uvset_surfaceMap >= 0) { float2 uv = g_xMaterial_blend2.uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend2_surfacemap.Sample(sampler_objectshader, uv); + surfaceMap2 = texture_blend2_surfacemap.Sample(sampler_objectshader, uv); } - else - { - sam = 1; - } - surface_occlusion_roughness_metallic_reflectance += sam * float4(1, g_xMaterial_blend2.roughness, g_xMaterial_blend2.metalness, g_xMaterial_blend2.reflectance) * blend_weights.z; + + Surface surface2; + surface2.N = baseN; + surface2.create(g_xMaterial_blend2, color2, surfaceMap2); [branch] if (g_xMaterial_blend2.normalMapStrength > 0 && g_xMaterial_blend2.uvset_normalMap >= 0) @@ -929,46 +920,50 @@ GBUFFEROutputType main(PIXELINPUT input) float2 uv = g_xMaterial_blend2.uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam.rgb = texture_blend2_normalmap.Sample(sampler_objectshader, uv); sam.rgb = sam.rgb * 2 - 1; - surface.N += lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend2.normalMapStrength) * blend_weights.z; + surface2.N = lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend2.normalMapStrength); } + surface2.emissiveColor = g_xMaterial_blend2.emissiveColor; [branch] if (g_xMaterial_blend2.uvset_emissiveMap >= 0 && any(g_xMaterial_blend2.emissiveColor)) { float2 uv = g_xMaterial_blend2.uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam = texture_blend2_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); - emissiveColor += sam * g_xMaterial_blend2.emissiveColor * blend_weights.z; + surface2.emissiveColor *= sam; } + + surface.N += surface2.N * blend_weights.z; + surface.albedo += surface2.albedo * blend_weights.z; + surface.f0 += surface2.f0 * blend_weights.z; + surface.roughness += surface2.roughness * blend_weights.z; + surface.occlusion += surface2.occlusion * blend_weights.z; + surface.emissiveColor += surface2.emissiveColor * blend_weights.z; } [branch] if (blend_weights.w > 0) { + float4 color2 = 1; [branch] if (g_xMaterial_blend3.uvset_baseColorMap >= 0 && (g_xFrame_Options & OPTION_BIT_DISABLE_ALBEDO_MAPS) == 0) { float2 uv = g_xMaterial_blend3.uvset_baseColorMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend3_basecolormap.Sample(sampler_objectshader, uv); - sam.rgb = DEGAMMA(sam.rgb); + color2 = texture_blend3_basecolormap.Sample(sampler_objectshader, uv); + color2.rgb = DEGAMMA(color2.rgb); } - else - { - sam = 1; - } - color += sam * g_xMaterial_blend3.baseColor * blend_weights.w; + float4 surfaceMap2 = 1; [branch] if (g_xMaterial_blend3.uvset_surfaceMap >= 0) { float2 uv = g_xMaterial_blend3.uvset_surfaceMap == 0 ? input.uvsets.xy : input.uvsets.zw; - sam = texture_blend3_surfacemap.Sample(sampler_objectshader, uv); + surfaceMap2 = texture_blend3_surfacemap.Sample(sampler_objectshader, uv); } - else - { - sam = 1; - } - surface_occlusion_roughness_metallic_reflectance += sam * float4(1, g_xMaterial_blend3.roughness, g_xMaterial_blend3.metalness, g_xMaterial_blend3.reflectance) * blend_weights.w; + + Surface surface2; + surface2.N = baseN; + surface2.create(g_xMaterial_blend3, color2, surfaceMap2); [branch] if (g_xMaterial_blend3.normalMapStrength > 0 && g_xMaterial_blend3.uvset_normalMap >= 0) @@ -976,20 +971,27 @@ GBUFFEROutputType main(PIXELINPUT input) float2 uv = g_xMaterial_blend3.uvset_normalMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam.rgb = texture_blend3_normalmap.Sample(sampler_objectshader, uv); sam.rgb = sam.rgb * 2 - 1; - surface.N += lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend3.normalMapStrength) * blend_weights.w; + surface2.N = lerp(baseN, mul(sam.rgb, TBN), g_xMaterial_blend3.normalMapStrength); } + surface2.emissiveColor = g_xMaterial_blend3.emissiveColor; [branch] if (g_xMaterial_blend3.uvset_emissiveMap >= 0 && any(g_xMaterial_blend3.emissiveColor)) { float2 uv = g_xMaterial_blend3.uvset_emissiveMap == 0 ? input.uvsets.xy : input.uvsets.zw; sam = texture_blend3_emissivemap.Sample(sampler_objectshader, uv); sam.rgb = DEGAMMA(sam.rgb); - emissiveColor += sam * g_xMaterial_blend3.emissiveColor * blend_weights.w; + surface2.emissiveColor *= sam; } + + surface.N += surface2.N * blend_weights.w; + surface.albedo += surface2.albedo * blend_weights.w; + surface.f0 += surface2.f0 * blend_weights.w; + surface.roughness += surface2.roughness * blend_weights.w; + surface.occlusion += surface2.occlusion * blend_weights.w; + surface.emissiveColor += surface2.emissiveColor * blend_weights.w; } - color.a = 1; surface.N = normalize(surface.N); #endif // TERRAIN @@ -999,33 +1001,22 @@ GBUFFEROutputType main(PIXELINPUT input) if (g_xMaterial.IsOcclusionEnabled_Secondary() && g_xMaterial.uvset_occlusionMap >= 0) { const float2 UV_occlusionMap = g_xMaterial.uvset_occlusionMap == 0 ? input.uvsets.xy : input.uvsets.zw; - surface_occlusion_roughness_metallic_reflectance.r *= texture_occlusionmap.Sample(sampler_objectshader, UV_occlusionMap).r; + surface.occlusion *= texture_occlusionmap.Sample(sampler_objectshader, UV_occlusionMap).r; } #ifndef SIMPLE_INPUT #ifndef ENVMAPRENDERING #ifndef TRANSPARENT - surface_occlusion_roughness_metallic_reflectance.r *= texture_ao.SampleLevel(sampler_linear_clamp, ScreenCoord, 0).r; + surface.occlusion *= texture_ao.SampleLevel(sampler_linear_clamp, ScreenCoord, 0).r; #endif // TRANSPARENT #endif // ENVMAPRENDERING #endif // SIMPLE_INPUT - surface = CreateSurface( - surface.P, - surface.N, - surface.V, - color, - surface_occlusion_roughness_metallic_reflectance.g, - surface_occlusion_roughness_metallic_reflectance.r, - surface_occlusion_roughness_metallic_reflectance.b, - surface_occlusion_roughness_metallic_reflectance.a, - emissiveColor #ifdef BRDF_ANISOTROPIC - , g_xMaterial.parallaxOcclusionMapping, - tangent.xyz, - normalize(cross(tangent.xyz, surface.N) * tangent.w) // Compute bitangent again after normal mapping + surface.anisotropy = g_xMaterial.parallaxOcclusionMapping; + surface.T = tangent.xyz; + surface.B = normalize(cross(tangent.xyz, surface.N) * tangent.w); // Compute bitangent again after normal mapping #endif // BRDF_ANISOTROPIC - ); surface.sss = g_xMaterial.subsurfaceScattering; surface.sss_inv = g_xMaterial.subsurfaceScattering_inv; @@ -1035,10 +1026,13 @@ GBUFFEROutputType main(PIXELINPUT input) surface.layerMask = g_xMaterial.layerMask; + surface.update(); + float3 ambient = GetAmbient(surface.N); ambient = lerp(ambient, ambient * surface.sss.rgb, saturate(surface.sss.a)); - Lighting lighting = CreateLighting(0, 0, ambient, 0); + Lighting lighting; + lighting.create(0, 0, ambient, 0); #ifndef SIMPLE_INPUT @@ -1115,7 +1109,7 @@ GBUFFEROutputType main(PIXELINPUT input) depth_difference = sampled_lineardepth - lineardepth; } // WATER FOG: - surface.refraction.a = 1 - saturate(surface.baseColor.a * 0.1 * depth_difference); + surface.refraction.a = 1 - saturate(color.a * 0.1 * depth_difference); #endif // WATER #ifdef UNLIT diff --git a/WickedEngine/objectInputLayoutHF.hlsli b/WickedEngine/objectInputLayoutHF.hlsli index db61a2a80..ba64fa1a6 100644 --- a/WickedEngine/objectInputLayoutHF.hlsli +++ b/WickedEngine/objectInputLayoutHF.hlsli @@ -75,99 +75,89 @@ struct VertexSurface float3 normal; float4 tangent; float4 prevPos; + + inline void create(in ShaderMaterial material, in Input_Object_POS input) + { + position = float4(input.pos.xyz, 1); + + color = material.baseColor * unpack_rgba(input.inst.userdata.x); + + uint normal_wind = asuint(input.pos.w); + normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; + + if (material.IsUsingWind()) + { + const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; + const float waveoffset = dot(position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (position.x + position.y + position.z) * g_xFrame_WindRandomness; + const float3 wavedir = g_xFrame_WindDirection * windweight; + const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; + const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; + position.xyz += wind; + prevPos.xyz += windPrev; + } + } + inline void create(in ShaderMaterial material, in Input_Object_POS_TEX input) + { + position = float4(input.pos.xyz, 1); + + color = material.baseColor * unpack_rgba(input.inst.userdata.x); + + uint normal_wind = asuint(input.pos.w); + normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; + + if (material.IsUsingWind()) + { + const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; + const float waveoffset = dot(position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (position.x + position.y + position.z) * g_xFrame_WindRandomness; + const float3 wavedir = g_xFrame_WindDirection * windweight; + const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; + const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; + position.xyz += wind; + prevPos.xyz += windPrev; + } + + uvsets = float4(input.uv0 * material.texMulAdd.xy + material.texMulAdd.zw, input.uv1); + + } + inline void create(in ShaderMaterial material, in Input_Object_ALL input) + { + position = float4(input.pos.xyz, 1); + + color = material.baseColor * unpack_rgba(input.inst.userdata.x); + + if (material.IsUsingVertexColors()) + { + color *= input.col; + } + + uint normal_wind = asuint(input.pos.w); + normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; + normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; + + tangent = input.tan * 2 - 1; + + if (material.IsUsingWind()) + { + const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; + const float waveoffset = dot(position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (position.x + position.y + position.z) * g_xFrame_WindRandomness; + const float3 wavedir = g_xFrame_WindDirection * windweight; + const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; + const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; + position.xyz += wind; + prevPos.xyz += windPrev; + } + + uvsets = float4(input.uv0 * material.texMulAdd.xy + material.texMulAdd.zw, input.uv1); + + atlas = input.atl * input.instAtlas.atlasMulAdd.xy + input.instAtlas.atlasMulAdd.zw; + + prevPos = float4(input.pre.xyz, 1); + } }; -inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_POS input) -{ - VertexSurface surface; - surface.position = float4(input.pos.xyz, 1); - - surface.color = g_xMaterial.baseColor * unpack_rgba(input.inst.userdata.x); - - uint normal_wind = asuint(input.pos.w); - surface.normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; - - if (g_xMaterial.IsUsingWind()) - { - const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; - const float waveoffset = dot(surface.position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (surface.position.x + surface.position.y + surface.position.z) * g_xFrame_WindRandomness; - const float3 wavedir = g_xFrame_WindDirection * windweight; - const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; - const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; - surface.position.xyz += wind; - surface.prevPos.xyz += windPrev; - } - - return surface; -} -inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_POS_TEX input) -{ - VertexSurface surface; - - surface.position = float4(input.pos.xyz, 1); - - surface.color = g_xMaterial.baseColor * unpack_rgba(input.inst.userdata.x); - - uint normal_wind = asuint(input.pos.w); - surface.normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; - - if (g_xMaterial.IsUsingWind()) - { - const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; - const float waveoffset = dot(surface.position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (surface.position.x + surface.position.y + surface.position.z) * g_xFrame_WindRandomness; - const float3 wavedir = g_xFrame_WindDirection * windweight; - const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; - const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; - surface.position.xyz += wind; - surface.prevPos.xyz += windPrev; - } - - surface.uvsets = float4(input.uv0 * g_xMaterial.texMulAdd.xy + g_xMaterial.texMulAdd.zw, input.uv1); - - return surface; -} -inline VertexSurface MakeVertexSurfaceFromInput(Input_Object_ALL input) -{ - VertexSurface surface; - - surface.position = float4(input.pos.xyz, 1); - - surface.color = g_xMaterial.baseColor * unpack_rgba(input.inst.userdata.x); - - if (g_xMaterial.IsUsingVertexColors()) - { - surface.color *= input.col; - } - - uint normal_wind = asuint(input.pos.w); - surface.normal.x = (float)((normal_wind >> 0) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.y = (float)((normal_wind >> 8) & 0xFF) / 255.0f * 2.0f - 1.0f; - surface.normal.z = (float)((normal_wind >> 16) & 0xFF) / 255.0f * 2.0f - 1.0f; - - surface.tangent = input.tan * 2 - 1; - - if (g_xMaterial.IsUsingWind()) - { - const float windweight = ((normal_wind >> 24) & 0xFF) / 255.0f; - const float waveoffset = dot(surface.position.xyz, g_xFrame_WindDirection) * g_xFrame_WindWaveSize + (surface.position.x + surface.position.y + surface.position.z) * g_xFrame_WindRandomness; - const float3 wavedir = g_xFrame_WindDirection * windweight; - const float3 wind = sin(g_xFrame_Time * g_xFrame_WindSpeed + waveoffset) * wavedir; - const float3 windPrev = sin(g_xFrame_TimePrev * g_xFrame_WindSpeed + waveoffset) * wavedir; - surface.position.xyz += wind; - surface.prevPos.xyz += windPrev; - } - - surface.uvsets = float4(input.uv0 * g_xMaterial.texMulAdd.xy + g_xMaterial.texMulAdd.zw, input.uv1); - - surface.atlas = input.atl * input.instAtlas.atlasMulAdd.xy + input.instAtlas.atlasMulAdd.zw; - - surface.prevPos = float4(input.pre.xyz, 1); - - return surface; -} - -#endif // WI_MESH_INPUT_LAYOUT_HF \ No newline at end of file +#endif // WI_MESH_INPUT_LAYOUT_HF diff --git a/WickedEngine/objectPS_voxelizer.hlsl b/WickedEngine/objectPS_voxelizer.hlsl index 76846a63b..147955b85 100644 --- a/WickedEngine/objectPS_voxelizer.hlsl +++ b/WickedEngine/objectPS_voxelizer.hlsl @@ -168,7 +168,8 @@ void main(PSInput input) #endif // TERRAIN - Lighting lighting = CreateLighting(0, 0, 0, 0); + Lighting lighting; + lighting.create(0, 0, 0, 0); [branch] if (any(xForwardLightMask)) diff --git a/WickedEngine/objectVS_common.hlsl b/WickedEngine/objectVS_common.hlsl index a325b32a1..312cee83c 100644 --- a/WickedEngine/objectVS_common.hlsl +++ b/WickedEngine/objectVS_common.hlsl @@ -8,7 +8,9 @@ PixelInputType main(Input_Object_ALL input) float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); float4x4 WORLDPREV = MakeWorldMatrixFromInstance(input.instPrev); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); surface.prevPos = mul(WORLDPREV, surface.prevPos); diff --git a/WickedEngine/objectVS_common_tessellation.hlsl b/WickedEngine/objectVS_common_tessellation.hlsl index 1567f9efe..ed2cf0a4f 100644 --- a/WickedEngine/objectVS_common_tessellation.hlsl +++ b/WickedEngine/objectVS_common_tessellation.hlsl @@ -19,7 +19,9 @@ HullInputType main(Input_Object_ALL input) float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); float4x4 WORLDPREV = MakeWorldMatrixFromInstance(input.instPrev); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); surface.prevPos = mul(WORLDPREV, surface.prevPos); @@ -35,4 +37,4 @@ HullInputType main(Input_Object_ALL input) Out.posPrev = surface.prevPos; return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/objectVS_positionstream.hlsl b/WickedEngine/objectVS_positionstream.hlsl index ba7746802..741dc9832 100644 --- a/WickedEngine/objectVS_positionstream.hlsl +++ b/WickedEngine/objectVS_positionstream.hlsl @@ -11,7 +11,9 @@ VSOut main(Input_Object_POS input) VSOut Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); diff --git a/WickedEngine/objectVS_simple.hlsl b/WickedEngine/objectVS_simple.hlsl index 09222b261..86c59f840 100644 --- a/WickedEngine/objectVS_simple.hlsl +++ b/WickedEngine/objectVS_simple.hlsl @@ -5,7 +5,9 @@ PixelInputType_Simple main(Input_Object_POS_TEX input) PixelInputType_Simple Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); diff --git a/WickedEngine/objectVS_simple_tessellation.hlsl b/WickedEngine/objectVS_simple_tessellation.hlsl index 68be6942c..50bebcb25 100644 --- a/WickedEngine/objectVS_simple_tessellation.hlsl +++ b/WickedEngine/objectVS_simple_tessellation.hlsl @@ -17,7 +17,9 @@ HullInputType main(Input_Object_ALL input) HullInputType Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); surface.normal = normalize(mul((float3x3)WORLD, surface.normal)); @@ -32,4 +34,4 @@ HullInputType main(Input_Object_ALL input) Out.posPrev = 0; return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/objectVS_voxelizer.hlsl b/WickedEngine/objectVS_voxelizer.hlsl index 2b6d8631c..be301ce3d 100644 --- a/WickedEngine/objectVS_voxelizer.hlsl +++ b/WickedEngine/objectVS_voxelizer.hlsl @@ -13,7 +13,9 @@ VSOut main(Input_Object_ALL input) VSOut Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); Out.pos = mul(WORLD, surface.position); Out.color = surface.color; @@ -21,4 +23,4 @@ VSOut main(Input_Object_ALL input) Out.nor = normalize(mul((float3x3)WORLD, surface.normal)); return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/oceanSurfacePS.hlsl b/WickedEngine/oceanSurfacePS.hlsl index b63761ffa..e6406a36b 100644 --- a/WickedEngine/oceanSurfacePS.hlsl +++ b/WickedEngine/oceanSurfacePS.hlsl @@ -19,8 +19,20 @@ float4 main(PSIn input) : SV_TARGET float dist = length(V); V /= dist; float emissive = 0; - Surface surface = CreateSurface(input.pos3D, normalize(float3(gradient.x, xOceanTexelLength * 2, gradient.y)), V, color, 0.1, 1, 0, 0.02); - Lighting lighting = CreateLighting(0, 0, GetAmbient(surface.N), 0); + + Surface surface; + surface.init(); + surface.albedo = color.rgb; + surface.f0 = 0.02; + surface.roughness = 0.1; + surface.P = input.pos3D; + surface.N = normalize(float3(gradient.x, xOceanTexelLength * 2, gradient.y)); + surface.V = V; + surface.update(); + + Lighting lighting; + lighting.create(0, 0, GetAmbient(surface.N), 0); + surface.pixel = input.pos.xy; float depth = input.pos.z; @@ -49,7 +61,7 @@ float4 main(PSIn input) : SV_TARGET depth_difference = sampled_lineardepth - lineardepth; } // WATER FOG: - surface.refraction.a = 1 - saturate(surface.baseColor.a * 0.1f * depth_difference); + surface.refraction.a = 1 - saturate(color.a * 0.1f * depth_difference); ApplyLighting(surface, lighting, color); diff --git a/WickedEngine/raytrace_shadeCS.hlsl b/WickedEngine/raytrace_shadeCS.hlsl index bb34fea04..f8626409f 100644 --- a/WickedEngine/raytrace_shadeCS.hlsl +++ b/WickedEngine/raytrace_shadeCS.hlsl @@ -55,37 +55,28 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) } baseColor *= color; - float4 surface_occlusion_roughness_metallic_reflectance; + float4 surfaceMap = 1; [branch] if (material.uvset_surfaceMap >= 0) { const float2 UV_surfaceMap = material.uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; - surface_occlusion_roughness_metallic_reflectance = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * material.surfaceMapAtlasMulAdd.xy + material.surfaceMapAtlasMulAdd.zw, 0); - if (material.IsUsingSpecularGlossinessWorkflow()) - { - ConvertToSpecularGlossiness(surface_occlusion_roughness_metallic_reflectance); - } - } - else - { - surface_occlusion_roughness_metallic_reflectance = 1; + surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * material.surfaceMapAtlasMulAdd.xy + material.surfaceMapAtlasMulAdd.zw, 0); } - float roughness = material.roughness * surface_occlusion_roughness_metallic_reflectance.g; - float metalness = material.metalness * surface_occlusion_roughness_metallic_reflectance.b; - float reflectance = material.reflectance * surface_occlusion_roughness_metallic_reflectance.a; - roughness = sqr(roughness); // convert linear roughness to cone aperture - float4 emissiveColor = material.emissiveColor; + Surface surface; + surface.create(material, baseColor, surfaceMap); + + surface.emissiveColor = material.emissiveColor; [branch] - if (material.emissiveColor.a > 0 && material.uvset_emissiveMap >= 0) + if (surface.emissiveColor.a > 0 && material.uvset_emissiveMap >= 0) { const float2 UV_emissiveMap = material.uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; float4 emissiveMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_emissiveMap * material.emissiveMapAtlasMulAdd.xy + material.emissiveMapAtlasMulAdd.zw, 0); emissiveMap.rgb = DEGAMMA(emissiveMap.rgb); - emissiveColor *= emissiveMap; + surface.emissiveColor *= emissiveMap; } - bounceResult += emissiveColor.rgb * emissiveColor.a; + bounceResult += surface.emissiveColor.rgb * surface.emissiveColor.a; [branch] if (material.uvset_normalMap >= 0) @@ -100,13 +91,16 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) // Calculate chances of reflection types: const float refractChance = 1 - baseColor.a; + // Roughness to cone aperture: + float alphaRoughness = surface.roughness * surface.roughness; + // Roulette-select the ray's path float roulette = rand(seed, uv); if (roulette < refractChance) { // Refraction const float3 R = refract(ray.direction, N, 1 - material.refractionIndex); - ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), roughness); + ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), alphaRoughness); ray.energy *= lerp(baseColor.rgb, 1, refractChance); // The ray penetrates the surface, so push DOWN along normal to avoid self-intersection: @@ -115,9 +109,7 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) else { // Calculate chances of reflection types: - const float3 albedo = ComputeAlbedo(baseColor, reflectance, metalness); - const float3 f0 = ComputeF0(baseColor, reflectance, metalness); - const float3 F = F_Fresnel(f0, saturate(dot(-ray.direction, N))); + const float3 F = F_Fresnel(surface.f0, saturate(dot(-ray.direction, N))); const float specChance = dot(F, 0.333); roulette = rand(seed, uv); @@ -125,14 +117,14 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) { // Specular reflection const float3 R = reflect(ray.direction, N); - ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), roughness); + ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), alphaRoughness); ray.energy *= F / specChance; } else { // Diffuse reflection ray.direction = SampleHemisphere_cos(N, seed, uv); - ray.energy *= albedo / (1 - specChance); + ray.energy *= surface.albedo / (1 - specChance); } // Ray reflects from surface, so push UP along normal to avoid self-intersection: @@ -146,13 +138,18 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) // Light sampling: float3 P = ray.origin; float3 V = normalize(g_xCamera_CamPos - P); - Surface surface = CreateSurface(P, N, V, baseColor, roughness, 1, metalness, reflectance); + surface.P = P; + surface.N = N; + surface.V = V; + surface.update(); [loop] for (uint iterator = 0; iterator < g_xFrame_LightArrayCount; iterator++) { ShaderEntity light = EntityArray[g_xFrame_LightArrayOffset + iterator]; - Lighting lighting = CreateLighting(0, 0, 0, 0); + + Lighting lighting; + lighting.create(0, 0, 0, 0); float3 L = 0; float dist = 0; @@ -165,7 +162,10 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) dist = FLT_MAX; L = light.GetDirection().xyz; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); + NdotL = surfaceToLight.NdotL; [branch] @@ -197,7 +197,9 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) dist = sqrt(dist2); L /= dist; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); + NdotL = surfaceToLight.NdotL; [branch] @@ -230,7 +232,9 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) dist = sqrt(dist2); L /= dist; - SurfaceToLight surfaceToLight = CreateSurfaceToLight(surface, L); + SurfaceToLight surfaceToLight; + surfaceToLight.create(surface, L); + NdotL = surfaceToLight.NdotL; [branch] diff --git a/WickedEngine/renderlightmapPS.hlsl b/WickedEngine/renderlightmapPS.hlsl index 13984cc87..4831d2589 100644 --- a/WickedEngine/renderlightmapPS.hlsl +++ b/WickedEngine/renderlightmapPS.hlsl @@ -29,7 +29,9 @@ float4 main(Input input) : SV_TARGET for (uint iterator = 0; iterator < g_xFrame_LightArrayCount; iterator++) { ShaderEntity light = EntityArray[g_xFrame_LightArrayOffset + iterator]; - Lighting lighting = CreateLighting(0, 0, 0, 0); + + Lighting lighting; + lighting.create(0, 0, 0, 0); if (!(light.GetFlags() & ENTITY_FLAG_LIGHT_STATIC)) { @@ -206,37 +208,28 @@ float4 main(Input input) : SV_TARGET } baseColor *= color; - float4 surface_occlusion_roughness_metallic_reflectance; + float4 surfaceMap = 1; [branch] if (material.uvset_surfaceMap >= 0) { const float2 UV_surfaceMap = material.uvset_surfaceMap == 0 ? uvsets.xy : uvsets.zw; - surface_occlusion_roughness_metallic_reflectance = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * material.surfaceMapAtlasMulAdd.xy + material.surfaceMapAtlasMulAdd.zw, 0); - if (material.IsUsingSpecularGlossinessWorkflow()) - { - ConvertToSpecularGlossiness(surface_occlusion_roughness_metallic_reflectance); - } - } - else - { - surface_occlusion_roughness_metallic_reflectance = 1; + surfaceMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_surfaceMap * material.surfaceMapAtlasMulAdd.xy + material.surfaceMapAtlasMulAdd.zw, 0); } - float roughness = material.roughness * surface_occlusion_roughness_metallic_reflectance.g; - float metalness = material.metalness * surface_occlusion_roughness_metallic_reflectance.b; - float reflectance = material.reflectance * surface_occlusion_roughness_metallic_reflectance.a; - roughness = sqr(roughness); // convert linear roughness to cone aperture - float4 emissiveColor = material.emissiveColor; + Surface surface; + surface.create(material, baseColor, surfaceMap); + + surface.emissiveColor = material.emissiveColor; [branch] if (material.emissiveColor.a > 0 && material.uvset_emissiveMap >= 0) { const float2 UV_emissiveMap = material.uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; float4 emissiveMap = materialTextureAtlas.SampleLevel(sampler_linear_clamp, UV_emissiveMap * material.emissiveMapAtlasMulAdd.xy + material.emissiveMapAtlasMulAdd.zw, 0); emissiveMap.rgb = DEGAMMA(emissiveMap.rgb); - emissiveColor *= emissiveMap; + surface.emissiveColor *= emissiveMap; } - ray.color += max(0, ray.energy * emissiveColor.rgb * emissiveColor.a); + ray.color += max(0, ray.energy * surface.emissiveColor.rgb * surface.emissiveColor.a); [branch] if (material.uvset_normalMap >= 0) @@ -251,13 +244,16 @@ float4 main(Input input) : SV_TARGET // Calculate chances of reflection types: const float refractChance = 1 - baseColor.a; + // Roughness to cone aperture: + float alphaRoughness = surface.roughness * surface.roughness; + // Roulette-select the ray's path float roulette = rand(seed, uv); if (roulette < refractChance) { // Refraction const float3 R = refract(ray.direction, N, 1 - material.refractionIndex); - ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), roughness); + ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), alphaRoughness); ray.energy *= lerp(baseColor.rgb, 1, refractChance); // The ray penetrates the surface, so push DOWN along normal to avoid self-intersection: @@ -266,9 +262,7 @@ float4 main(Input input) : SV_TARGET else { // Calculate chances of reflection types: - const float3 albedo = ComputeAlbedo(baseColor, reflectance, metalness); - const float3 f0 = ComputeF0(baseColor, reflectance, metalness); - const float3 F = F_Fresnel(f0, saturate(dot(-ray.direction, N))); + const float3 F = F_Fresnel(surface.f0, saturate(dot(-ray.direction, N))); const float specChance = dot(F, 0.333f); roulette = rand(seed, uv); @@ -276,14 +270,14 @@ float4 main(Input input) : SV_TARGET { // Specular reflection const float3 R = reflect(ray.direction, N); - ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), roughness); + ray.direction = lerp(R, SampleHemisphere_cos(R, seed, uv), alphaRoughness); ray.energy *= F / specChance; } else { // Diffuse reflection ray.direction = SampleHemisphere_cos(N, seed, uv); - ray.energy *= albedo / (1 - specChance); + ray.energy *= surface.albedo / (1 - specChance); } // Ray reflects from surface, so push UP along normal to avoid self-intersection: diff --git a/WickedEngine/rtreflectionLIB.hlsl b/WickedEngine/rtreflectionLIB.hlsl index 343671c48..00aeda551 100644 --- a/WickedEngine/rtreflectionLIB.hlsl +++ b/WickedEngine/rtreflectionLIB.hlsl @@ -146,23 +146,29 @@ void RTReflection_ClosestHit(inout RayPayload payload, in MyAttributes attr) baseColor = 1; } baseColor *= material.baseColor; - float4 color = baseColor; - float4 emissiveColor = material.emissiveColor; + + Surface surface; + surface.create(material, baseColor, 1); + + surface.emissiveColor = material.emissiveColor; [branch] if (material.uvset_emissiveMap >= 0) { const float2 UV_emissiveMap = material.uvset_emissiveMap == 0 ? uvsets.xy : uvsets.zw; float4 emissiveMap = subsets_textures[descriptorIndex * MATERIAL_TEXTURE_SLOT_DESCRIPTOR_COUNT + MATERIAL_TEXTURE_SLOT_DESCRIPTOR_EMISSIVE].SampleLevel(sampler_linear_wrap, UV_emissiveMap, 2); emissiveMap.rgb = DEGAMMA(emissiveMap.rgb); - emissiveColor *= emissiveMap; + surface.emissiveColor *= emissiveMap; } // Light sampling: - float3 P = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); - float3 V = -WorldRayDirection(); - Surface surface = CreateSurface(P, N, V, baseColor, material.roughness, 1, material.metalness, material.reflectance); - Lighting lighting = CreateLighting(0, 0, GetAmbient(surface.N), 0); + surface.P = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); + surface.V = -WorldRayDirection(); + surface.N = N; + surface.update(); + + Lighting lighting; + lighting.create(0, 0, GetAmbient(surface.N), 0); [loop] for (uint iterator = 0; iterator < g_xFrame_LightArrayCount; iterator++) @@ -195,7 +201,7 @@ void RTReflection_ClosestHit(inout RayPayload payload, in MyAttributes attr) } LightingPart combined_lighting = CombineLighting(surface, lighting); - payload.color = baseColor.rgb * combined_lighting.diffuse + combined_lighting.specular + emissiveColor.rgb * emissiveColor.a; + payload.color = surface.albedo * combined_lighting.diffuse + combined_lighting.specular + surface.emissiveColor.rgb * surface.emissiveColor.a; } diff --git a/WickedEngine/shadowVS.hlsl b/WickedEngine/shadowVS.hlsl index 291ec860d..149732726 100644 --- a/WickedEngine/shadowVS.hlsl +++ b/WickedEngine/shadowVS.hlsl @@ -12,10 +12,12 @@ VertexOut main(Input_Object_POS input) VertexOut Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); Out.pos = mul(WORLD, surface.position); Out.pos = mul(g_xCamera_VP, Out.pos); return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/shadowVS_alphatest.hlsl b/WickedEngine/shadowVS_alphatest.hlsl index 63992d978..02094dda1 100644 --- a/WickedEngine/shadowVS_alphatest.hlsl +++ b/WickedEngine/shadowVS_alphatest.hlsl @@ -13,7 +13,9 @@ VertexOut main(Input_Object_POS_TEX input) VertexOut Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); Out.pos = mul(WORLD, surface.position); @@ -21,4 +23,4 @@ VertexOut main(Input_Object_POS_TEX input) Out.uv = g_xMaterial.uvset_baseColorMap == 0 ? surface.uvsets.xy : surface.uvsets.zw; return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/shadowVS_transparent.hlsl b/WickedEngine/shadowVS_transparent.hlsl index cb675d541..a6d072e27 100644 --- a/WickedEngine/shadowVS_transparent.hlsl +++ b/WickedEngine/shadowVS_transparent.hlsl @@ -12,7 +12,9 @@ VertexOut main(Input_Object_POS_TEX input) VertexOut Out; float4x4 WORLD = MakeWorldMatrixFromInstance(input.inst); - VertexSurface surface = MakeVertexSurfaceFromInput(input); + + VertexSurface surface; + surface.create(g_xMaterial, input); surface.position = mul(WORLD, surface.position); @@ -21,4 +23,4 @@ VertexOut main(Input_Object_POS_TEX input) Out.uvsets = surface.uvsets; return Out; -} \ No newline at end of file +} diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp index 41d2ed40f..411be1c75 100644 --- a/WickedEngine/wiArchive.cpp +++ b/WickedEngine/wiArchive.cpp @@ -6,7 +6,7 @@ using namespace std; // this should always be only INCREMENTED and only if a new serialization is implemeted somewhere! -uint64_t __archiveVersion = 55; +uint64_t __archiveVersion = 56; // this is the version number of which below the archive is not compatible with the current version uint64_t __archiveVersionBarrier = 22; diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 082f60833..29d038010 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -7110,7 +7110,6 @@ void VoxelRadiance(const Visibility& vis, CommandList cmd) void ComputeTiledLightCulling( - const CameraComponent& camera, const Texture& depthbuffer, const GPUBuffer& tileFrustums, const GPUBuffer& entityTiles_Opaque, diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index 52a642876..a1a635453 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -247,7 +247,6 @@ namespace wiRenderer void VoxelRadiance(const Visibility& vis, wiGraphics::CommandList cmd); // Compute light grid tiles void ComputeTiledLightCulling( - const wiScene::CameraComponent& camera, const wiGraphics::Texture& depthbuffer, const wiGraphics::GPUBuffer& tileFrustums, const wiGraphics::GPUBuffer& entityTiles_Opaque, diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 68a660304..9e00ac148 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -275,6 +275,7 @@ namespace wiScene void MaterialComponent::WriteShaderMaterial(ShaderMaterial* dest) const { dest->baseColor = baseColor; + dest->specularColor = specularColor; dest->emissiveColor = emissiveColor; dest->texMulAdd = texMulAdd; dest->roughness = roughness; @@ -610,9 +611,12 @@ namespace wiScene device->CreateBuffer(&bd, nullptr, &streamoutBuffer_POS); device->SetName(&streamoutBuffer_POS, "streamoutBuffer_POS"); - bd.ByteWidth = (uint32_t)(sizeof(Vertex_TAN) * vertex_tangents.size()); - device->CreateBuffer(&bd, nullptr, &streamoutBuffer_TAN); - device->SetName(&streamoutBuffer_TAN, "streamoutBuffer_TAN"); + if (!vertex_tangents.empty()) + { + bd.ByteWidth = (uint32_t)(sizeof(Vertex_TAN) * vertex_tangents.size()); + device->CreateBuffer(&bd, nullptr, &streamoutBuffer_TAN); + device->SetName(&streamoutBuffer_TAN, "streamoutBuffer_TAN"); + } } // vertexBuffer - UV SET 0 diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index a2c25d712..3bda829ee 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -146,6 +146,7 @@ namespace wiScene BLENDMODE userBlendMode = BLENDMODE_OPAQUE; XMFLOAT4 baseColor = XMFLOAT4(1, 1, 1, 1); + XMFLOAT4 specularColor = XMFLOAT4(1, 1, 1, 1); XMFLOAT4 emissiveColor = XMFLOAT4(1, 1, 1, 0); XMFLOAT4 subsurfaceScattering = XMFLOAT4(1, 1, 1, 0); XMFLOAT4 texMulAdd = XMFLOAT4(1, 1, 0, 0); @@ -229,6 +230,7 @@ namespace wiScene inline bool IsCustomShader() const { return customShaderID >= 0; } inline void SetBaseColor(const XMFLOAT4& value) { SetDirty(); baseColor = value; } + inline void SetSpecularColor(const XMFLOAT4& value) { SetDirty(); specularColor = value; } inline void SetEmissiveColor(const XMFLOAT4& value) { SetDirty(); emissiveColor = value; } inline void SetRoughness(float value) { SetDirty(); roughness = value; } inline void SetReflectance(float value) { SetDirty(); reflectance = value; } diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index 8f8d8fb15..f34475388 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -174,6 +174,11 @@ namespace wiScene archive >> subsurfaceScattering; } + if (archive.GetVersion() >= 56) + { + archive >> specularColor; + } + wiJobSystem::Execute(seri.ctx, [&](wiJobArgs args) { CreateRenderData(dir); }); @@ -283,6 +288,11 @@ namespace wiScene { archive << subsurfaceScattering; } + + if (archive.GetVersion() >= 56) + { + archive << specularColor; + } } } void MeshComponent::Serialize(wiArchive& archive, EntitySerializer& seri) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index b16038638..346a86940 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wiVersion // minor features, major updates, breaking compatibility changes const int minor = 51; // minor bug fixes, alterations, refactors, updates - const int revision = 23; + const int revision = 24; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);