summaryrefslogtreecommitdiff
path: root/indra/llprimitive
diff options
context:
space:
mode:
authorCosmic Linden <cosmic@lindenlab.com>2023-02-09 15:04:46 -0800
committerCosmic Linden <cosmic@lindenlab.com>2023-02-09 15:05:19 -0800
commitd6841c07983a46ff805ed23a7318efbf9cca3b24 (patch)
treec6b8432f722f939f1ed088681bcb2dada1bf5587 /indra/llprimitive
parent627e3d51c61778e07e350689ce68ede24afe61ab (diff)
SL-19080: Update GLTF Material asset upload to v1.1, with stricter GLTF compliance and removal of unsupported features
Diffstat (limited to 'indra/llprimitive')
-rw-r--r--indra/llprimitive/CMakeLists.txt2
-rw-r--r--indra/llprimitive/llgltfmaterial.cpp151
-rw-r--r--indra/llprimitive/llgltfmaterial.h53
-rw-r--r--indra/llprimitive/tests/llgltfmaterial_test.cpp255
4 files changed, 368 insertions, 93 deletions
diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index 328b22f900..dd25c19713 100644
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -99,6 +99,8 @@ if (LL_TESTS)
SET(llprimitive_TEST_SOURCE_FILES
llmediaentry.cpp
llprimitive.cpp
+ llgltfmaterial.cpp
)
+
LL_ADD_PROJECT_UNIT_TESTS(llprimitive "${llprimitive_TEST_SOURCE_FILES}")
endif (LL_TESTS)
diff --git a/indra/llprimitive/llgltfmaterial.cpp b/indra/llprimitive/llgltfmaterial.cpp
index a8dad89292..2e920aa44e 100644
--- a/indra/llprimitive/llgltfmaterial.cpp
+++ b/indra/llprimitive/llgltfmaterial.cpp
@@ -31,6 +31,10 @@
// NOTE -- this should be the one and only place tiny_gltf.h is included
#include "tinygltf/tiny_gltf.h"
+const char* LLGLTFMaterial::ASSET_VERSION = "1.1";
+const char* LLGLTFMaterial::ASSET_TYPE = "GLTF 2.0";
+const std::array<char*, 2> LLGLTFMaterial::ACCEPTED_ASSET_VERSIONS = { "1.0", "1.1" };
+
const char* GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform";
const char* GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale";
const char* GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset";
@@ -73,16 +77,14 @@ LLGLTFMaterial::LLGLTFMaterial(const LLGLTFMaterial& rhs)
LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)
{
- LL_PROFILE_ZONE_SCOPED;
- //have to do a manual operator= because of LLRefCount
- mBaseColorId = rhs.mBaseColorId;
- mNormalId = rhs.mNormalId;
- mMetallicRoughnessId = rhs.mMetallicRoughnessId;
- mEmissiveId = rhs.mEmissiveId;
+ //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;
@@ -90,8 +92,6 @@ LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)
mDoubleSided = rhs.mDoubleSided;
mAlphaMode = rhs.mAlphaMode;
- mTextureTransform = rhs.mTextureTransform;
-
mOverrideDoubleSided = rhs.mOverrideDoubleSided;
mOverrideAlphaMode = rhs.mOverrideAlphaMode;
@@ -100,10 +100,9 @@ LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)
bool LLGLTFMaterial::operator==(const LLGLTFMaterial& rhs) const
{
- return mBaseColorId == rhs.mBaseColorId &&
- mNormalId == rhs.mNormalId &&
- mMetallicRoughnessId == rhs.mMetallicRoughnessId &&
- mEmissiveId == rhs.mEmissiveId &&
+ return mTextureId == rhs.mTextureId &&
+
+ mTextureTransform == rhs.mTextureTransform &&
mBaseColor == rhs.mBaseColor &&
mEmissiveColor == rhs.mEmissiveColor &&
@@ -115,8 +114,6 @@ bool LLGLTFMaterial::operator==(const LLGLTFMaterial& rhs) const
mDoubleSided == rhs.mDoubleSided &&
mAlphaMode == rhs.mAlphaMode &&
- mTextureTransform == rhs.mTextureTransform &&
-
mOverrideDoubleSided == rhs.mOverrideDoubleSided &&
mOverrideAlphaMode == rhs.mOverrideAlphaMode;
}
@@ -148,6 +145,8 @@ std::string LLGLTFMaterial::asJSON(bool prettyprint) const
writeToModel(model_out, 0);
+ // To ensure consistency in asset upload, this should be the only reference
+ // to WriteGltfSceneToStream in the viewer.
gltf.WriteGltfSceneToStream(&model_out, str, prettyprint, false);
return str.str();
@@ -164,13 +163,13 @@ void LLGLTFMaterial::setFromModel(const tinygltf::Model& model, S32 mat_index)
const tinygltf::Material& material_in = model.materials[mat_index];
// Apply base color texture
- setFromTexture(model, material_in.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR, mBaseColorId);
+ setFromTexture(model, material_in.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR);
// Apply normal map
- setFromTexture(model, material_in.normalTexture, GLTF_TEXTURE_INFO_NORMAL, mNormalId);
+ setFromTexture(model, material_in.normalTexture, GLTF_TEXTURE_INFO_NORMAL);
// Apply metallic-roughness texture
- setFromTexture(model, material_in.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, mMetallicRoughnessId);
+ setFromTexture(model, material_in.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS);
// Apply emissive texture
- setFromTexture(model, material_in.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE, mEmissiveId);
+ setFromTexture(model, material_in.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE);
setAlphaMode(material_in.alphaMode);
mAlphaCutoff = llclamp((F32)material_in.alphaCutoff, 0.f, 1.f);
@@ -264,11 +263,11 @@ std::string gltf_get_texture_image(const tinygltf::Model& model, const T& textur
// *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, LLUUID& texture_id_out)
+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);
- texture_id_out.set(uri);
+ 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);
@@ -297,21 +296,24 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
tinygltf::Material& material_out = model.materials[mat_index];
// set base color texture
- writeToTexture(model, material_out.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR, mBaseColorId);
+ writeToTexture(model, material_out.pbrMetallicRoughness.baseColorTexture, GLTF_TEXTURE_INFO_BASE_COLOR);
// set normal texture
- writeToTexture(model, material_out.normalTexture, GLTF_TEXTURE_INFO_NORMAL, mNormalId);
+ writeToTexture(model, material_out.normalTexture, GLTF_TEXTURE_INFO_NORMAL);
// set metallic-roughness texture
- writeToTexture(model, material_out.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, mMetallicRoughnessId);
+ writeToTexture(model, material_out.pbrMetallicRoughness.metallicRoughnessTexture, GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS);
// set emissive texture
- writeToTexture(model, material_out.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE, mEmissiveId);
+ writeToTexture(model, material_out.emissiveTexture, GLTF_TEXTURE_INFO_EMISSIVE);
+ // set occlusion texture
+ // *NOTE: This is required for ORM materials for GLTF compliance.
+ // See: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#_material_occlusiontexture
+ writeToTexture(model, material_out.occlusionTexture, GLTF_TEXTURE_INFO_OCCLUSION);
+
material_out.alphaMode = getAlphaMode();
material_out.alphaCutoff = mAlphaCutoff;
-
+
mBaseColor.write(material_out.pbrMetallicRoughness.baseColorFactor);
- material_out.emissiveFactor.resize(3); // 0 size by default
-
if (mEmissiveColor != LLGLTFMaterial::getDefaultEmissiveColor())
{
material_out.emissiveFactor.resize(3);
@@ -323,7 +325,6 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
material_out.doubleSided = mDoubleSided;
-
// generate "extras" string
tinygltf::Value::Object extras;
bool write_extras = false;
@@ -364,28 +365,43 @@ void gltf_allocate_texture_image(tinygltf::Model& model, T& texture_info, const
}
template<typename T>
-void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, const LLUUID& texture_id) const
+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];
- if (texture_id.isNull() && transform == sDefault.mTextureTransform[0])
+ 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());
- 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);
+ 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;
}
bool LLGLTFMaterial::setBaseMaterial()
@@ -419,40 +435,33 @@ void LLGLTFMaterial::hackOverrideUUID(LLUUID& id)
}
}
-void LLGLTFMaterial::setBaseColorId(const LLUUID& id, bool for_override)
+void LLGLTFMaterial::setTextureId(TextureInfo texture_info, const LLUUID& id, bool for_override)
{
- mBaseColorId = id;
+ mTextureId[texture_info] = id;
if (for_override)
{
- hackOverrideUUID(mBaseColorId);
+ hackOverrideUUID(mTextureId[texture_info]);
}
}
+void LLGLTFMaterial::setBaseColorId(const LLUUID& id, bool for_override)
+{
+ setTextureId(GLTF_TEXTURE_INFO_BASE_COLOR, id, for_override);
+}
+
void LLGLTFMaterial::setNormalId(const LLUUID& id, bool for_override)
{
- mNormalId = id;
- if (for_override)
- {
- hackOverrideUUID(mNormalId);
- }
+ setTextureId(GLTF_TEXTURE_INFO_NORMAL, id, for_override);
}
-void LLGLTFMaterial::setMetallicRoughnessId(const LLUUID& id, bool for_override)
+void LLGLTFMaterial::setOcclusionRoughnessMetallicId(const LLUUID& id, bool for_override)
{
- mMetallicRoughnessId = id;
- if (for_override)
- {
- hackOverrideUUID(mMetallicRoughnessId);
- }
+ setTextureId(GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, id, for_override);
}
void LLGLTFMaterial::setEmissiveId(const LLUUID& id, bool for_override)
{
- mEmissiveId = id;
- if (for_override)
- {
- hackOverrideUUID(mEmissiveId);
- }
+ setTextureId(GLTF_TEXTURE_INFO_EMISSIVE, id, for_override);
}
void LLGLTFMaterial::setBaseColorFactor(const LLColor4& baseColor, bool for_override)
@@ -533,10 +542,7 @@ const char* LLGLTFMaterial::getAlphaMode() const
void LLGLTFMaterial::setAlphaMode(S32 mode, bool for_override)
{
mAlphaMode = (AlphaMode) llclamp(mode, (S32) ALPHA_MODE_OPAQUE, (S32) ALPHA_MODE_MASK);
- if (for_override)
- {
- mOverrideAlphaMode = true;
- }
+ mOverrideAlphaMode = for_override && mAlphaMode == getDefaultAlphaMode();
}
void LLGLTFMaterial::setDoubleSided(bool double_sided, bool for_override)
@@ -544,10 +550,7 @@ void LLGLTFMaterial::setDoubleSided(bool double_sided, bool for_override)
// sure, no clamping will ever be needed for a bool, but include the
// setter for consistency with the clamping API
mDoubleSided = double_sided;
- if (for_override)
- {
- mOverrideDoubleSided = true;
- }
+ mOverrideDoubleSided = for_override && mDoubleSided == getDefaultDoubleSided();
}
void LLGLTFMaterial::setTextureOffset(TextureInfo texture_info, const LLVector2& offset)
@@ -640,10 +643,12 @@ void LLGLTFMaterial::applyOverride(const LLGLTFMaterial& override_mat)
{
LL_PROFILE_ZONE_SCOPED;
- applyOverrideUUID(mBaseColorId, override_mat.mBaseColorId);
- applyOverrideUUID(mNormalId, override_mat.mNormalId);
- applyOverrideUUID(mMetallicRoughnessId, override_mat.mMetallicRoughnessId);
- applyOverrideUUID(mEmissiveId, override_mat.mEmissiveId);
+ for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
+ {
+ LLUUID& texture_id = mTextureId[i];
+ const LLUUID& override_texture_id = override_mat.mTextureId[i];
+ applyOverrideUUID(texture_id, override_texture_id);
+ }
if (override_mat.mBaseColor != getDefaultBaseColor())
{
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index dec7b696f8..fd26e7563c 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -49,6 +49,11 @@ public:
// default material for reference
static const LLGLTFMaterial sDefault;
+ static const char* ASSET_VERSION;
+ static const char* ASSET_TYPE;
+ static const std::array<char*, 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(); }
+
struct TextureTransform
{
LLVector2 mOffset = { 0.f, 0.f };
@@ -74,10 +79,25 @@ public:
bool operator==(const LLGLTFMaterial& rhs) const;
bool operator!=(const LLGLTFMaterial& rhs) const { return !(*this == rhs); }
- LLUUID mBaseColorId;
- LLUUID mNormalId;
- LLUUID mMetallicRoughnessId;
- LLUUID mEmissiveId;
+ enum TextureInfo : U32
+ {
+ GLTF_TEXTURE_INFO_BASE_COLOR,
+ GLTF_TEXTURE_INFO_NORMAL,
+ GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS,
+ // *NOTE: GLTF_TEXTURE_INFO_OCCLUSION is currently ignored, in favor of
+ // the values specified with GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS.
+ // Currently, only ORM materials are supported (materials which define
+ // occlusion, roughness, and metallic in the same texture).
+ // -Cosmic,2023-01-26
+ GLTF_TEXTURE_INFO_OCCLUSION = GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS,
+ GLTF_TEXTURE_INFO_EMISSIVE,
+
+ GLTF_TEXTURE_INFO_COUNT
+ };
+
+ 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
LLColor4 mBaseColor = LLColor4(1, 1, 1, 1);
@@ -106,24 +126,14 @@ public:
return id;
}
- enum TextureInfo : U32
- {
- GLTF_TEXTURE_INFO_BASE_COLOR,
- GLTF_TEXTURE_INFO_NORMAL,
- GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS,
- GLTF_TEXTURE_INFO_EMISSIVE,
-
- GLTF_TEXTURE_INFO_COUNT
- };
-
- std::array<TextureTransform, GLTF_TEXTURE_INFO_COUNT> mTextureTransform;
-
//setters for various members (will clamp to acceptable ranges)
// for_override - set to true if this value is being set as part of an override (important for handling override to default value)
+ void setTextureId(TextureInfo texture_info, const LLUUID& id, bool for_override = false);
+
void setBaseColorId(const LLUUID& id, bool for_override = false);
void setNormalId(const LLUUID& id, bool for_override = false);
- void setMetallicRoughnessId(const LLUUID& id, bool for_override = false);
+ void setOcclusionRoughnessMetallicId(const LLUUID& id, bool for_override = false);
void setEmissiveId(const LLUUID& id, bool for_override = false);
void setBaseColorFactor(const LLColor4& baseColor, bool for_override = false);
@@ -182,6 +192,10 @@ public:
void applyOverride(const LLGLTFMaterial& override_mat);
+ // For base materials only (i.e. assets). Clears transforms to
+ // default since they're not supported in assets yet.
+ void sanitizeAssetMaterial();
+
// For material overrides only. Clears most properties to
// default/fallthrough, but preserves the transforms.
bool setBaseMaterial();
@@ -189,12 +203,11 @@ public:
bool isClearedForBaseMaterial();
private:
-
template<typename T>
- void setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id, LLUUID& texture_id_out);
+ void setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id);
template<typename T>
- void writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, const LLUUID& texture_id) const;
+ void writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write = false) const;
void setBaseMaterial(const LLGLTFMaterial& old_override_mat);
};
diff --git a/indra/llprimitive/tests/llgltfmaterial_test.cpp b/indra/llprimitive/tests/llgltfmaterial_test.cpp
new file mode 100644
index 0000000000..4f58f4e567
--- /dev/null
+++ b/indra/llprimitive/tests/llgltfmaterial_test.cpp
@@ -0,0 +1,255 @@
+/**
+ * @file llgltfmaterial_test.cpp
+ *
+ * $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 "linden_common.h"
+#include "lltut.h"
+
+#include "../llgltfmaterial.h"
+#include "lluuid.cpp"
+
+// Import & define single-header gltf import/export lib
+#define TINYGLTF_IMPLEMENTATION
+#define TINYGLTF_USE_CPP14 // default is C++ 11
+
+// tinygltf by default loads image files using STB
+#define STB_IMAGE_IMPLEMENTATION
+// to use our own image loading:
+// 1. replace this definition with TINYGLTF_NO_STB_IMAGE
+// 2. provide image loader callback with TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)
+
+// tinygltf saves image files using STB
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+// similarly, can override with TINYGLTF_NO_STB_IMAGE_WRITE and TinyGLTF::SetImageWriter(fxn, data)
+
+// Disable reading external images to prevent warnings and speed up the tests.
+// We don't need this for the tests, but still need the filesystem
+// implementation to be defined in order for llprimitive to link correctly.
+#define TINYGLTF_NO_EXTERNAL_IMAGE 1
+
+#include "tinygltf/tiny_gltf.h"
+
+namespace tut
+{
+ struct llgltfmaterial
+ {
+ };
+ typedef test_group<llgltfmaterial> llgltfmaterial_t;
+ typedef llgltfmaterial_t::object llgltfmaterial_object_t;
+ tut::llgltfmaterial_t tut_llgltfmaterial("llgltfmaterial");
+
+ // A positive 32-bit float with a long string representation
+ constexpr F32 test_fraction = 1.09045365e-32;
+ // A larger positive 32-bit float for values that get zeroed if below a threshold
+ constexpr F32 test_fraction_big = 0.109045;
+
+ void apply_test_material_texture_ids(LLGLTFMaterial& material)
+ {
+ material.setBaseColorId(LLUUID::generateNewID());
+ material.setNormalId(LLUUID::generateNewID());
+ material.setOcclusionRoughnessMetallicId(LLUUID::generateNewID());
+ material.setEmissiveId(LLUUID::generateNewID());
+ }
+
+ void apply_test_material_texture_transforms(LLGLTFMaterial& material)
+ {
+ LLGLTFMaterial::TextureTransform test_transform;
+ test_transform.mOffset.mV[VX] = test_fraction;
+ test_transform.mOffset.mV[VY] = test_fraction;
+ test_transform.mScale.mV[VX] = test_fraction;
+ test_transform.mScale.mV[VY] = test_fraction;
+ test_transform.mRotation = test_fraction;
+ for (LLGLTFMaterial::TextureInfo i = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; i = LLGLTFMaterial::TextureInfo((U32)i + 1))
+ {
+ material.setTextureOffset(i, test_transform.mOffset);
+ material.setTextureScale(i, test_transform.mScale);
+ material.setTextureRotation(i, test_transform.mRotation);
+ }
+ }
+
+ void apply_test_material_factors(LLGLTFMaterial& material)
+ {
+ material.setBaseColorFactor(LLColor4(test_fraction_big, test_fraction_big, test_fraction_big, test_fraction_big));
+ material.setEmissiveColorFactor(LLColor3(test_fraction_big, test_fraction_big, test_fraction_big));
+ material.setMetallicFactor(test_fraction);
+ material.setRoughnessFactor(test_fraction);
+ }
+
+ LLGLTFMaterial create_test_material()
+ {
+ LLGLTFMaterial material;
+
+ apply_test_material_texture_ids(material);
+
+ apply_test_material_texture_transforms(material);
+
+ apply_test_material_factors(material);
+
+ material.setAlphaCutoff(test_fraction);
+ // Because this is the default value, it should append to the extras field to mark it as an override
+ material.setAlphaMode(LLGLTFMaterial::ALPHA_MODE_OPAQUE);
+ // Because this is the default value, it should append to the extras field to mark it as an override
+ material.setDoubleSided(false);
+
+ return material;
+ }
+
+ void ensure_gltf_material_serialize(const std::string& ensure_suffix, const LLGLTFMaterial& material_in)
+ {
+ const std::string json_in = material_in.asJSON();
+ LLGLTFMaterial material_out;
+ std::string warn_msg;
+ std::string error_msg;
+ bool serialize_success = material_out.fromJSON(json_in, warn_msg, error_msg);
+ ensure_equals("LLGLTFMaterial serialization has no warnings: " + ensure_suffix, "", warn_msg);
+ ensure_equals("LLGLTFMaterial serialization has no errors: " + ensure_suffix, "", error_msg);
+ ensure("LLGLTFMaterial serializes successfully: " + ensure_suffix, serialize_success);
+ ensure("LLGLTFMaterial is preserved when deserialized: " + ensure_suffix, material_in == material_out);
+ const std::string json_out = material_out.asJSON();
+ ensure_equals("LLGLTFMaterial is preserved when serialized: " + ensure_suffix, json_in, json_out);
+ }
+
+ void ensure_gltf_material_trimmed(const std::string& material_json, const std::string& must_not_contain)
+ {
+ ensure("LLGLTFMaterial serialization trims property '" + must_not_contain + "'", material_json.find(must_not_contain) == std::string::npos);
+ }
+
+ // Test that GLTF material fields have not changed since these tests were written
+ template<> template<>
+ void llgltfmaterial_object_t::test<1>()
+ {
+ if (sizeof(void*) > 4) // Don't bother running this test for 32-bit systems
+ {
+ // If any fields are added/changed, these tests should be updated (consider also updating ASSET_VERSION in LLGLTFMaterial)
+ ensure_equals("fields supported for GLTF (sizeof check)", sizeof(LLGLTFMaterial), 216);
+ }
+ ensure_equals("LLGLTFMaterial texture info count", (U32)LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT, 4);
+ }
+
+ // Test that occlusion and metallicRoughness are the same (They are different for asset validation. See lluploadmaterial.cpp)
+ template<> template<>
+ void llgltfmaterial_object_t::test<2>()
+ {
+ ensure_equals("LLGLTFMaterial occlusion does not differ from metallic roughness", LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS, LLGLTFMaterial::GLTF_TEXTURE_INFO_OCCLUSION);
+ }
+
+ // Ensure double sided and alpha mode overrides serialize as expected
+ template<> template<>
+ void llgltfmaterial_object_t::test<3>()
+ {
+ const bool doubleSideds[] { false, true };
+ const LLGLTFMaterial::AlphaMode alphaModes[] { LLGLTFMaterial::ALPHA_MODE_OPAQUE, LLGLTFMaterial::ALPHA_MODE_BLEND, LLGLTFMaterial::ALPHA_MODE_MASK };
+ const bool forOverrides[] { false, true };
+
+ for (bool doubleSided : doubleSideds)
+ {
+ for (bool forOverride : forOverrides)
+ {
+ LLGLTFMaterial material;
+ material.setDoubleSided(doubleSided, forOverride);
+ const bool overrideBit = (doubleSided == false) && forOverride;
+ ensure_equals("LLGLTFMaterial: double sided = " + std::to_string(doubleSided) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideDoubleSided, overrideBit);
+ ensure_gltf_material_serialize("double sided = " + std::to_string(doubleSided), material);
+ }
+ }
+
+ for (LLGLTFMaterial::AlphaMode alphaMode : alphaModes)
+ {
+ for (bool forOverride : forOverrides)
+ {
+ LLGLTFMaterial material;
+ material.setAlphaMode(alphaMode, forOverride);
+ const bool overrideBit = (alphaMode == LLGLTFMaterial::ALPHA_MODE_OPAQUE) && forOverride;
+ ensure_equals("LLGLTFMaterial: alpha mode = " + std::to_string(alphaMode) + " override bit when forOverride = " + std::to_string(forOverride), material.mOverrideAlphaMode, overrideBit);
+ ensure_gltf_material_serialize("alpha mode = " + std::to_string(alphaMode), material);
+ }
+ }
+ }
+
+ // Test that a GLTF material's transform components serialize as expected
+ template<> template<>
+ void llgltfmaterial_object_t::test<4>()
+ {
+ LLGLTFMaterial material;
+ LLGLTFMaterial::TextureTransform& transform = material.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR];
+ transform.mOffset[VX] = 1.f;
+ transform.mOffset[VY] = 2.f;
+ transform.mScale[VX] = 0.05f;
+ transform.mScale[VY] = 100.f;
+ transform.mRotation = 1.571f;
+ ensure_gltf_material_serialize("material with transform", material);
+ }
+
+ // Test that a GLTF material avoids serializing a material unnecessarily
+ template<> template<>
+ void llgltfmaterial_object_t::test<5>()
+ {
+ {
+ const LLGLTFMaterial material;
+ const std::string material_json = material.asJSON();
+ ensure_gltf_material_trimmed(material_json, "pbrMetallicRoughness");
+ ensure_gltf_material_trimmed(material_json, "normalTexture");
+ ensure_gltf_material_trimmed(material_json, "emissiveTexture");
+ ensure_gltf_material_trimmed(material_json, "occlusionTexture");
+ }
+
+ {
+ LLGLTFMaterial metallic_factor_material;
+ metallic_factor_material.setMetallicFactor(0.5);
+ const std::string metallic_factor_material_json = metallic_factor_material.asJSON();
+ ensure_gltf_material_trimmed(metallic_factor_material_json, "baseColorTexture");
+ ensure_gltf_material_trimmed(metallic_factor_material_json, "metallicRoughnessTexture");
+ }
+ }
+
+ // Test that a GLTF material preserves values on serialization
+ template<> template<>
+ void llgltfmaterial_object_t::test<6>()
+ {
+ {
+ const LLGLTFMaterial full_material = create_test_material();
+ ensure_gltf_material_serialize("full material", full_material);
+ }
+
+ {
+ LLGLTFMaterial texture_ids_only_material;
+ apply_test_material_texture_ids(texture_ids_only_material);
+ ensure_gltf_material_serialize("material with texture IDs only", texture_ids_only_material);
+ }
+
+ {
+ LLGLTFMaterial texture_transforms_only_material;
+ apply_test_material_texture_ids(texture_transforms_only_material);
+ ensure_gltf_material_serialize("material with texture transforms only", texture_transforms_only_material);
+ }
+
+ {
+ LLGLTFMaterial factors_only_material;
+ apply_test_material_factors(factors_only_material);
+ ensure_gltf_material_serialize("material with scaling/tint factors only", factors_only_material);
+ }
+ }
+}