diff options
| author | cosmic-linden <111533034+cosmic-linden@users.noreply.github.com> | 2023-10-09 16:18:06 -0700 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-09 16:18:06 -0700 | 
| commit | 87b34fb74290e9b3c7d7f4f908ac2ba39b85cd48 (patch) | |
| tree | e605ca9bb00695871a39134f3384ffd32fb4e7da | |
| parent | eab9396579842ae0202bfa3318ffc527e7ee6d1a (diff) | |
| parent | d22ea319a51707bdcc0a8cb946143208d8c3f553 (diff) | |
Merge pull request #354 from secondlife/SL-20225
SL-20225: Update LLGLTFMaterial
| -rw-r--r-- | indra/llprimitive/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | indra/llprimitive/llgltfmaterial.cpp | 161 | ||||
| -rw-r--r-- | indra/llprimitive/llgltfmaterial.h | 38 | ||||
| -rw-r--r-- | indra/llprimitive/llgltfmaterial_templates.h | 142 | 
4 files changed, 197 insertions, 145 deletions
| diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index 76d261ab3e..2bd1edaacc 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -34,6 +34,7 @@ set(llprimitive_HEADER_FILES      lldaeloader.h      llgltfloader.h      llgltfmaterial.h +    llgltfmaterial_templates.h      legacy_object_types.h      llmaterial.h      llmaterialid.h diff --git a/indra/llprimitive/llgltfmaterial.cpp b/indra/llprimitive/llgltfmaterial.cpp index 19b7413934..f42c11ee21 100644 --- a/indra/llprimitive/llgltfmaterial.cpp +++ b/indra/llprimitive/llgltfmaterial.cpp @@ -24,25 +24,28 @@   * $/LicenseInfo$   */ +  #include "linden_common.h"  #include "llgltfmaterial.h" +  #include "llsdserialize.h"  // NOTE -- this should be the one and only place tiny_gltf.h is included  #include "tinygltf/tiny_gltf.h" +#include "llgltfmaterial_templates.h"  const char* const LLGLTFMaterial::ASSET_VERSION = "1.1";  const char* const LLGLTFMaterial::ASSET_TYPE = "GLTF 2.0";  const std::array<std::string, 2> LLGLTFMaterial::ACCEPTED_ASSET_VERSIONS = { "1.0", "1.1" }; -const char* const GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform"; -const char* const GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale"; -const char* const GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset"; -const char* const GLTF_FILE_EXTENSION_TRANSFORM_ROTATION = "rotation"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset"; +const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_ROTATION = "rotation";  // special UUID that indicates a null UUID in override data -static const LLUUID GLTF_OVERRIDE_NULL_UUID = LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff"); +const LLUUID LLGLTFMaterial::GLTF_OVERRIDE_NULL_UUID = LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff");  void LLGLTFMaterial::TextureTransform::getPacked(F32 (&packed)[8]) const  { @@ -68,14 +71,14 @@ LLGLTFMaterial::LLGLTFMaterial(const LLGLTFMaterial& rhs)  LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)  { -    //have to do a manual operator= because of LLRefCount  +    //have to do a manual operator= because of LLRefCount      mTextureId = rhs.mTextureId;      mTextureTransform = rhs.mTextureTransform;      mBaseColor = rhs.mBaseColor;      mEmissiveColor = rhs.mEmissiveColor; -     +      mMetallicFactor = rhs.mMetallicFactor;      mRoughnessFactor = rhs.mRoughnessFactor;      mAlphaCutoff = rhs.mAlphaCutoff; @@ -97,7 +100,7 @@ bool LLGLTFMaterial::operator==(const LLGLTFMaterial& rhs) const          mBaseColor == rhs.mBaseColor &&          mEmissiveColor == rhs.mEmissiveColor && -         +          mMetallicFactor == rhs.mMetallicFactor &&          mRoughnessFactor == rhs.mRoughnessFactor &&          mAlphaCutoff == rhs.mAlphaCutoff && @@ -122,6 +125,7 @@ bool LLGLTFMaterial::fromJSON(const std::string& json, std::string& warn_msg, st          return true;      } +      return false;  } @@ -190,7 +194,8 @@ void LLGLTFMaterial::setFromModel(const tinygltf::Model& model, S32 mat_index)      }  } -LLVector2 vec2_from_json(const tinygltf::Value::Object& object, const char* key, const LLVector2& default_value) +// static +LLVector2 LLGLTFMaterial::vec2FromJson(const tinygltf::Value::Object& object, const char* key, const LLVector2& default_value)  {      const auto it = object.find(key);      if (it == object.end()) @@ -215,7 +220,8 @@ LLVector2 vec2_from_json(const tinygltf::Value::Object& object, const char* key,      return value;  } -F32 float_from_json(const tinygltf::Value::Object& object, const char* key, const F32 default_value) +// static +F32 LLGLTFMaterial::floatFromJson(const tinygltf::Value::Object& object, const char* key, const F32 default_value)  {      const auto it = object.find(key);      if (it == object.end()) @@ -230,52 +236,6 @@ F32 float_from_json(const tinygltf::Value::Object& object, const char* key, cons      return (F32)real_json.GetNumberAsDouble();  } -template<typename T> -std::string gltf_get_texture_image(const tinygltf::Model& model, const T& texture_info) -{ -    const S32 texture_idx = texture_info.index; -    if (texture_idx < 0 || texture_idx >= model.textures.size()) -    { -        return ""; -    } -    const tinygltf::Texture& texture = model.textures[texture_idx]; - -    // Ignore texture.sampler for now - -    const S32 image_idx = texture.source; -    if (image_idx < 0 || image_idx >= model.images.size()) -    { -        return ""; -    } -    const tinygltf::Image& image = model.images[image_idx]; - -    return image.uri; -} - -// *NOTE: Use template here as workaround for the different similar texture info classes -template<typename T> -void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id) -{ -    LL_PROFILE_ZONE_SCOPED; -    const std::string uri = gltf_get_texture_image(model, texture_info); -    mTextureId[texture_info_id].set(uri); - -    const tinygltf::Value::Object& extensions_object = texture_info.extensions; -    const auto transform_it = extensions_object.find(GLTF_FILE_EXTENSION_TRANSFORM); -    if (transform_it != extensions_object.end()) -    { -        const tinygltf::Value& transform_json = std::get<1>(*transform_it); -        if (transform_json.IsObject()) -        { -            const tinygltf::Value::Object& transform_object = transform_json.Get<tinygltf::Value::Object>(); -            TextureTransform& transform = mTextureTransform[texture_info_id]; -            transform.mOffset = vec2_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_OFFSET, getDefaultTextureOffset()); -            transform.mScale = vec2_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_SCALE, getDefaultTextureScale()); -            transform.mRotation = float_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_ROTATION, getDefaultTextureRotation()); -        } -    } -} -  void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const  {      LL_PROFILE_ZONE_SCOPED; @@ -302,7 +262,7 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const      material_out.alphaMode = getAlphaMode();      material_out.alphaCutoff = mAlphaCutoff; -     +      mBaseColor.write(material_out.pbrMetallicRoughness.baseColorFactor);      if (mEmissiveColor != LLGLTFMaterial::getDefaultEmissiveColor()) @@ -320,7 +280,7 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const      tinygltf::Value::Object extras;      bool write_extras = false;      if (mOverrideAlphaMode && mAlphaMode == getDefaultAlphaMode()) -    {  +    {          extras["override_alpha_mode"] = tinygltf::Value(mOverrideAlphaMode);          write_extras = true;      } @@ -339,57 +299,6 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const      model.asset.version = "2.0";  } -template<typename T> -void gltf_allocate_texture_image(tinygltf::Model& model, T& texture_info, const std::string& uri) -{ -    const S32 image_idx = model.images.size(); -    model.images.emplace_back(); -    model.images[image_idx].uri = uri; - -    // The texture, not to be confused with the texture info -    const S32 texture_idx = model.textures.size(); -    model.textures.emplace_back(); -    tinygltf::Texture& texture = model.textures[texture_idx]; -    texture.source = image_idx; - -    texture_info.index = texture_idx; -} - -template<typename T> -void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write) const -{ -    LL_PROFILE_ZONE_SCOPED; -    const LLUUID& texture_id = mTextureId[texture_info_id]; -    const TextureTransform& transform = mTextureTransform[texture_info_id]; -    const bool is_blank_transform = transform == sDefault.mTextureTransform[0]; -    // Check if this material matches all the fallback values, and if so, then -    // skip including it to reduce material size -    if (!force_write && texture_id.isNull() && is_blank_transform) -    { -        return; -    } - -    // tinygltf will discard this texture info if there is no valid texture, -    // causing potential loss of information for overrides, so ensure one is -    // defined. -Cosmic,2023-01-30 -    gltf_allocate_texture_image(model, texture_info, texture_id.asString()); - -    if (!is_blank_transform) -    { -        tinygltf::Value::Object transform_map; -        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_OFFSET] = tinygltf::Value(tinygltf::Value::Array({ -            tinygltf::Value(transform.mOffset.mV[VX]), -            tinygltf::Value(transform.mOffset.mV[VY]) -        })); -        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_SCALE] = tinygltf::Value(tinygltf::Value::Array({ -            tinygltf::Value(transform.mScale.mV[VX]), -            tinygltf::Value(transform.mScale.mV[VY]) -        })); -        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_ROTATION] = tinygltf::Value(transform.mRotation); -        texture_info.extensions[GLTF_FILE_EXTENSION_TRANSFORM] = tinygltf::Value(transform_map); -    } -} -  void LLGLTFMaterial::sanitizeAssetMaterial()  {      mTextureTransform = sDefault.mTextureTransform; @@ -403,19 +312,19 @@ bool LLGLTFMaterial::setBaseMaterial()      return *this != old_override;  } -bool LLGLTFMaterial::isClearedForBaseMaterial() -{ -    LLGLTFMaterial cleared_override = sDefault; -    cleared_override.setBaseMaterial(*this); -    return *this == cleared_override; -} -  // For material overrides only. Copies transforms from the old override.  void LLGLTFMaterial::setBaseMaterial(const LLGLTFMaterial& old_override_mat)  {      mTextureTransform = old_override_mat.mTextureTransform;  } +bool LLGLTFMaterial::isClearedForBaseMaterial() const +{ +    LLGLTFMaterial cleared_override = sDefault; +    cleared_override.setBaseMaterial(*this); +    return *this == cleared_override; +} +  // static  void LLGLTFMaterial::hackOverrideUUID(LLUUID& id) @@ -516,7 +425,7 @@ void LLGLTFMaterial::setAlphaMode(const std::string& mode, bool for_override)      {          m = ALPHA_MODE_BLEND;      } -     +      setAlphaMode(m, for_override);  } @@ -709,7 +618,6 @@ void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& d          {              data["tex"][i] = LLSD::UUID(override_texture_id);          } -      }      if (override_mat.mBaseColor != getDefaultBaseColor()) @@ -764,23 +672,6 @@ void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& d              data["ti"][i]["r"] = override_mat.mTextureTransform[i].mRotation;          }      } - -#if 0 -    { -        std::ostringstream ostr; -        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_NOTATION); -        std::string param_str(ostr.str()); -        LL_INFOS() << param_str << LL_ENDL; -        LL_INFOS() << "Notation size: " << param_str.size() << LL_ENDL; -    } - -    { -        std::ostringstream ostr; -        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_BINARY); -        std::string param_str(ostr.str()); -        LL_INFOS() << "Binary size: " << param_str.size() << LL_ENDL; -    } -#endif  } diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h index ca27507707..a078a530a4 100644 --- a/indra/llprimitive/llgltfmaterial.h +++ b/indra/llprimitive/llgltfmaterial.h @@ -35,10 +35,13 @@  #include "hbxxh.h"  #include <string> +#include <map>  namespace tinygltf  {      class Model; +    struct TextureInfo; +    class Value;  }  class LLTextureEntry; @@ -52,6 +55,9 @@ public:      static const char* const ASSET_VERSION;      static const char* const ASSET_TYPE; +    // Max allowed size of a GLTF material asset or override, when serialized +    // as a minified JSON string +    static constexpr size_t MAX_ASSET_LENGTH = 2048;      static const std::array<std::string, 2> ACCEPTED_ASSET_VERSIONS;      static bool isAcceptedVersion(const std::string& version) { return std::find(ACCEPTED_ASSET_VERSIONS.cbegin(), ACCEPTED_ASSET_VERSIONS.cend(), version) != ACCEPTED_ASSET_VERSIONS.cend(); } @@ -64,6 +70,7 @@ public:          void getPacked(F32 (&packed)[8]) const;          bool operator==(const TextureTransform& other) const; +        bool operator!=(const TextureTransform& other) const { return !(*this == other); }      };      enum AlphaMode @@ -96,8 +103,13 @@ public:          GLTF_TEXTURE_INFO_COUNT      }; -    std::array<LLUUID, GLTF_TEXTURE_INFO_COUNT> mTextureId; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_SCALE; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_OFFSET; +    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_ROTATION; +    static const LLUUID GLTF_OVERRIDE_NULL_UUID; +    std::array<LLUUID, GLTF_TEXTURE_INFO_COUNT> mTextureId;      std::array<TextureTransform, GLTF_TEXTURE_INFO_COUNT> mTextureTransform;      // NOTE: initialize values to defaults according to the GLTF spec @@ -137,7 +149,7 @@ public:      void setAlphaMode(S32 mode, bool for_override = false);      void setDoubleSided(bool double_sided, bool for_override = false); -    //NOTE: texture offsets only exist in overrides, so "for_override" is not needed +    // *NOTE: texture offsets only exist in overrides, so "for_override" is not needed      void setTextureOffset(TextureInfo texture_info, const LLVector2& offset);      void setTextureScale(TextureInfo texture_info, const LLVector2& scale); @@ -155,7 +167,6 @@ public:      static LLVector2 getDefaultTextureScale();      static F32 getDefaultTextureRotation(); -      static void hackOverrideUUID(LLUUID& id);      static void applyOverrideUUID(LLUUID& dst_id, const LLUUID& override_id); @@ -164,7 +175,7 @@ public:      void setAlphaMode(const std::string& mode, bool for_override = false);      const char* getAlphaMode() const; -     +      // set the contents of this LLGLTFMaterial from the given json      // returns true if successful      // if unsuccessful, the contents of this LLGLTFMaterial should be left unchanged and false is returned @@ -176,7 +187,6 @@ public:      // get the contents of this LLGLTFMaterial as a json string      std::string asJSON(bool prettyprint = false) const; -      // initialize from given tinygltf::Model      // model - the model to reference      // mat_index - index of material in model's material array @@ -202,21 +212,29 @@ public:      // For material overrides only. Clears most properties to      // default/fallthrough, but preserves the transforms.      bool setBaseMaterial(); +    void setBaseMaterial(const LLGLTFMaterial& old_override_mat);      // True if setBaseMaterial() was just called -    bool isClearedForBaseMaterial(); +    bool isClearedForBaseMaterial() const;      // For local materials, they have to keep track of where      // they are assigned to for full updates      virtual void addTextureEntry(LLTextureEntry* te) {};      virtual void removeTextureEntry(LLTextureEntry* te) {}; -private: +protected: +    static LLVector2 vec2FromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const LLVector2& default_value); +    static F32 floatFromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const F32 default_value); + +    template<typename T> +    static void allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri); +      template<typename T>      void setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id); +    template<typename T> +    static void setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform);      template<typename T>      void writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write = false) const; - -    void setBaseMaterial(const LLGLTFMaterial& old_override_mat); +    template<typename T> +    static void writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write = false);  }; - diff --git a/indra/llprimitive/llgltfmaterial_templates.h b/indra/llprimitive/llgltfmaterial_templates.h new file mode 100644 index 0000000000..f607dfe967 --- /dev/null +++ b/indra/llprimitive/llgltfmaterial_templates.h @@ -0,0 +1,142 @@ +/** + * @file llgltfmaterial_templates.h + * @brief Material template definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + *  + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + *  + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + *  + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + *  + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "llgltfmaterial.h" + +// Use templates here as workaround for the different similar texture info classes in tinygltf +// Includer must first include tiny_gltf.h with the desired flags + +template<typename T> +std::string gltf_get_texture_image(const tinygltf::Model& model, const T& texture_info) +{ +    const S32 texture_idx = texture_info.index; +    if (texture_idx < 0 || texture_idx >= model.textures.size()) +    { +        return ""; +    } +    const tinygltf::Texture& texture = model.textures[texture_idx]; + +    // Ignore texture.sampler for now + +    const S32 image_idx = texture.source; +    if (image_idx < 0 || image_idx >= model.images.size()) +    { +        return ""; +    } +    const tinygltf::Image& image = model.images[image_idx]; + +    return image.uri; +} + +template<typename T> +void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id) +{ +    setFromTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id]); +    const std::string uri = gltf_get_texture_image(model, texture_info); +} + +// static +template<typename T> +void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform) +{ +    LL_PROFILE_ZONE_SCOPED; +    const std::string uri = gltf_get_texture_image(model, texture_info); +    texture_id.set(uri); + +    const tinygltf::Value::Object& extensions_object = texture_info.extensions; +    const auto transform_it = extensions_object.find(GLTF_FILE_EXTENSION_TRANSFORM); +    if (transform_it != extensions_object.end()) +    { +        const tinygltf::Value& transform_json = std::get<1>(*transform_it); +        if (transform_json.IsObject()) +        { +            const tinygltf::Value::Object& transform_object = transform_json.Get<tinygltf::Value::Object>(); +            transform.mOffset = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_OFFSET, getDefaultTextureOffset()); +            transform.mScale = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_SCALE, getDefaultTextureScale()); +            transform.mRotation = floatFromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_ROTATION, getDefaultTextureRotation()); +        } +    } +} + +// static +template<typename T> +void LLGLTFMaterial::allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri) +{ +    const S32 image_idx = model.images.size(); +    model.images.emplace_back(); +    model.images[image_idx].uri = uri; + +    // The texture, not to be confused with the texture info +    const S32 texture_idx = model.textures.size(); +    model.textures.emplace_back(); +    tinygltf::Texture& texture = model.textures[texture_idx]; +    texture.source = image_idx; + +    texture_info.index = texture_idx; +} + +// static +template<typename T> +void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write) const +{ +    writeToTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id], force_write); +} + +// static +template<typename T> +void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write) +{ +    LL_PROFILE_ZONE_SCOPED; +    const bool is_blank_transform = transform == sDefault.mTextureTransform[0]; +    // Check if this material matches all the fallback values, and if so, then +    // skip including it to reduce material size +    if (!force_write && texture_id.isNull() && is_blank_transform) +    { +        return; +    } + +    // tinygltf will discard this texture info if there is no valid texture, +    // causing potential loss of information for overrides, so ensure one is +    // defined. -Cosmic,2023-01-30 +    allocateTextureImage(model, texture_info, texture_id.asString()); + +    if (!is_blank_transform) +    { +        tinygltf::Value::Object transform_map; +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_OFFSET] = tinygltf::Value(tinygltf::Value::Array({ +            tinygltf::Value(transform.mOffset.mV[VX]), +            tinygltf::Value(transform.mOffset.mV[VY]) +        })); +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_SCALE] = tinygltf::Value(tinygltf::Value::Array({ +            tinygltf::Value(transform.mScale.mV[VX]), +            tinygltf::Value(transform.mScale.mV[VY]) +        })); +        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_ROTATION] = tinygltf::Value(transform.mRotation); +        texture_info.extensions[GLTF_FILE_EXTENSION_TRANSFORM] = tinygltf::Value(transform_map); +    } +} | 
