diff --git a/doc/changelog.dox b/doc/changelog.dox index 3705cb8226..f86b8a614f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -72,6 +72,21 @@ See also: - Added @ref SceneGraph::Object::move() +@subsubsection changelog-latest-new-trade Trade library + +- A new, redesigned @ref Trade::MaterialData class allowing to store custom + material attributes as well as more material types together in a single + instance; plus new @ref Trade::FlatMaterialData, + @ref Trade::PbrMetallicRoughnessMaterialData, + @ref Trade::PbrSpecularGlossinessMaterialData and + @ref Trade::PbrClearCoatMaterialData convenience accessor APIs similar to + @ref Trade::PhongMaterialData. See [mosra/magnum#459](https://github.com/mosra/magnum/pull/459). +- Added @ref Trade::PhongMaterialData::hasSpecularTexture(), + @ref Trade::PhongMaterialData::specularTextureSwizzle(), + @ref Trade::PhongMaterialData::normalTextureScale() and + @ref Trade::PhongMaterialData::normalTextureSwizzle() to make new features + added for PBR materials recognizable also in classic Phong workflows. + @subsection changelog-latest-changes Changes and improvements @subsubsection changelog-latest-changes-gl GL library @@ -84,10 +99,23 @@ See also: - Added a `--bounds` option to @ref magnum-sceneconverter "magnum-sceneconverter", showing data ranges of known attributes +- @ref magnum-sceneconverter "magnum-sceneconverter" now lists also materials + and textures in `--info` @subsubsection changelog-latest-changes-trade Trade library - Recognizing TIFF file header magic in @ref Trade::AnyImageImporter "AnyImageImporter" +- Added @ref Trade::PhongMaterialData::hasCommonTextureTransformation(), + @ref Trade::PhongMaterialData::ambientTextureMatrix(), + @ref Trade::PhongMaterialData::diffuseTextureMatrix(), + @ref Trade::PhongMaterialData::specularTextureMatrix() and + @ref Trade::PhongMaterialData::normalTextureMatrix() exposing also + per-texture coordinate transformation, similarly to per-texture coordinate + sets added in 2020.06 +- Added @ref Trade::PhongMaterialData::hasCommonTextureCoordinates() and + @ref Trade::PhongMaterialData::commonTextureCoordinates() exposing a + common texture coordinate set as a complement to a per-texture property + added in 2020.06 @subsection changelog-latest-buildsystem Build system @@ -111,6 +139,41 @@ See also: coordinates), @ref MeshTools::compile() should be using only the first set but it wasn't. +@subsection changelog-latest-deprecated Deprecated APIs + +- @cpp Trade::AbstractMaterialData @ce as well as its containing header + `Magnum/Trade/AbstractMaterialData.h` is now a deprecated alias to the new + and more flexible @ref Trade::MaterialData. +- @cpp Trade::PhongMaterialData::ambientCoordinateSet() @ce, + @cpp diffuseCoordinateSet() @ce, @cpp specularCoordinateSet() @ce and + @cpp normalCoordinateSet() @ce are deprecated in favor of more consistently + named @ref Trade::PhongMaterialData::ambientTextureCoordinates(), + @ref Trade::PhongMaterialData::diffuseTextureCoordinates() "diffuseTextureCoordinates()", + @ref Trade::PhongMaterialData::specularTextureCoordinates() "specularTextureCoordinates()" + and @ref Trade::PhongMaterialData::normalTextureCoordinates() "normalTextureCoordinates()" +- @ref Trade::PhongMaterialData constructor is deprecated as the designated + way is to populate the @ref Trade::MaterialData class directly instead. +- @cpp Trade::PhongMaterialData::textureMatrix() @ce is deprecated in favor + of @ref Trade::PhongMaterialData::hasCommonTextureTransformation() and + @ref Trade::PhongMaterialData::commonTextureMatrix(), which do proper + checking and fallback in case a per-texture transformation is present +- @cpp Trade::MaterialData::type() @ce (coming from the original + `AbstractMaterialData` class) is deprecated in favor of + @ref Trade::MaterialData::types(), as a material data can now contain + attributes for multiple different material types at once +- @cpp Trade::MaterialData::flags() @ce (coming from the original + `AbstractMaterialData` class), @cpp Trade::PhongMaterialData::flags() @ce + and related enum (sets) are deprecated. The flags are no longer stored + directly but rather generated on-the-fly from attribute data, which makes + them less efficient than calling @ref Trade::MaterialData::hasAttribute() + etc. +- @ref Trade::AbstractImporter::material() now returns + @ref Corrade::Containers::Optional instead of a @ref Corrade::Containers::Pointer, + as the new @ref Trade::MaterialData class isn't polymorphic anymore. If + @ref MAGNUM_BUILD_DEPRECATED is enabled, the returned type acts as a + @ref Corrade::Containers::Optional but has (deprecated) implicit conversion + to a @ref Corrade::Containers::Pointer to avoid breaking user code. + @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs - Removed remaining APIs deprecated in version 2018.10, in particular: @@ -144,11 +207,12 @@ See also: - @ref Trade::CameraData constructor not taking an explicit type enum, use @ref Trade::CameraData::CameraData(CameraType, Rad, Float, Float, Float, const void*) instead - - @ref Trade::AbstractMaterialData constructor taking just two parameters - and @ref Trade::PhongMaterialData constructor taking three parameters, - use @ref Trade::AbstractMaterialData::AbstractMaterialData(MaterialType, Flags, MaterialAlphaMode, Float, const void*) - and @ref Trade::PhongMaterialData::PhongMaterialData(Flags, MaterialAlphaMode, Float, Float, const void*) - instead + - @cpp Trade::AbstractMaterialData @ce constructor taking just two + parameters and @ref Trade::PhongMaterialData constructor taking three + parameters, use either the (also deprecated) + @cpp Trade::AbstractMaterialData::AbstractMaterialData(MaterialType, Flags, MaterialAlphaMode, Float, const void*) @ce + and @cpp Trade::PhongMaterialData::PhongMaterialData(Flags, MaterialAlphaMode, Float, Float, const void*) @ce + constructors or directly the new @ref Trade::MaterialData class - All includes of @ref Corrade/Containers/PointerStl.h and @ref Corrade/Containers/ArrayViewStl.h that were added in 2019.01 for preserving backwards compatibility after the move from @ref std::unique_ptr @@ -162,6 +226,21 @@ See also: afterwards. This can cause compilation breakages in case the type constructor has the parent parameter non-optional, pass the parent explicitly in that case. +- Mutable access to @ref Trade::PhongMaterialData color and texture + information, deprecated in 2020.06, is now removed, as it's impossible to + implement through the redesigned @ref Trade::MaterialData APIs. However + these APIs were mainly used to populate the contents in asset importers, so + this shouldn't cause any breakages in user code. +- @ref Trade::MaterialData::flags() now calculates the output on-the-fly + based on what attributes are present. This means that arbitrary extra bits + passed to the constructor from @cpp AbstractMaterialData @ce subclasses are + now discarded, and thus subclasses have to override the + @ref Trade::MaterialData::flags() "flags()" function to keep the same + behavior. +- @ref Trade::MaterialData, which the deprecated + @cpp Trade::AbstractMaterialData @ce aliases to, doesn't have a + @cpp virtual @ce destructor as subclasses with extra data members aren't a + desired use case anymore. @section changelog-2020-06 2020.06 @@ -821,6 +900,10 @@ Released 2020-06-27, tagged as deprecated on the 3D variant, use separate @ref Shaders::MeshVisualizer3D::setTransformationMatrix() and @ref Shaders::MeshVisualizer3D::setProjectionMatrix() instead +- Mutable access to @ref Trade::PhongMaterialData color and texture + information is deprecated. This was mainly used to populate the contents + in asset importers, but the class was redesigned and this is no longer + needed. - The unprefixed and alien-looking @cpp FLOAT_EQUALITY_PRECISION @ce, @cpp DOUBLE_EQUALITY_PRECISION @ce and @cpp LONG_DOUBLE_EQUALITY_PRECISION @ce macros are no longer used by any code and thus deprecated in favor of using @@ -2461,10 +2544,10 @@ Released 2018-10-23, tagged as - `Shaders::VertexColor::Color` is deprecated, use the direct @ref Shaders::VertexColor::Color3 or @ref Shaders::VertexColor::Color4 alternatives instead -- @ref Trade::AbstractMaterialData constructor taking just two parameters +- @cpp Trade::AbstractMaterialData @ce constructor taking just two parameters and @ref Trade::PhongMaterialData constructor taking three parameters are - deprecated, use @ref Trade::AbstractMaterialData::AbstractMaterialData(MaterialType, Flags, MaterialAlphaMode, Float, const void*) - and @ref Trade::PhongMaterialData::PhongMaterialData(Flags, MaterialAlphaMode, Float, Float, const void*) + deprecated, use @cpp Trade::AbstractMaterialData::AbstractMaterialData(MaterialType, Flags, MaterialAlphaMode, Float, const void*) @ce + and @cpp Trade::PhongMaterialData::PhongMaterialData(Flags, MaterialAlphaMode, Float, Float, const void*) @ce instead - @ref Trade::CameraData constructor not taking an explicit type enum is deprecated, use @ref Trade::CameraData::CameraData(CameraType, Rad, Float, Float, Float, const void*) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 57dcb68e17..4baf08cd9c 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -33,15 +33,20 @@ #include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" #include "Magnum/Animation/Player.h" +#include "Magnum/Math/Color.h" #include "Magnum/Math/Swizzle.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" -#include "Magnum/Trade/ObjectData3D.h" +#include "Magnum/Trade/MeshObjectData3D.h" +#include "Magnum/Trade/PbrClearCoatMaterialData.h" +#include "Magnum/Trade/PbrSpecularGlossinessMaterialData.h" +#include "Magnum/Trade/PbrMetallicRoughnessMaterialData.h" #include "Magnum/Trade/PhongMaterialData.h" #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" @@ -60,6 +65,8 @@ #include "Magnum/Trade/MeshData3D.h" #endif +#define DOXYGEN_IGNORE(...) __VA_ARGS__ + using namespace Magnum; using namespace Magnum::Math::Literals; @@ -115,17 +122,17 @@ importer->openFile("scene.gltf"); // memory-maps all files { Containers::Pointer importer; -Float shininess; +Int materialIndex; /* [AbstractImporter-usage-cast] */ -Containers::Pointer data = importer->material(12); -if(data && data->type() == Trade::MaterialType::Phong) { - auto& phong = static_cast(*data); +Containers::Pointer data = importer->object3D(12); +if(data && data->instanceType() == Trade::ObjectInstanceType3D::Mesh) { + auto& mesh = static_cast(*data); - shininess = phong.shininess(); + materialIndex = mesh.material(); // ... } /* [AbstractImporter-usage-cast] */ -static_cast(shininess); +static_cast(materialIndex); } { @@ -247,6 +254,208 @@ for(auto&& row: data.mutablePixels()) /* [ImageData-usage-mutable] */ } +{ +/* [MaterialAttributeData-name] */ +Trade::MaterialAttributeData a{ + Trade::MaterialAttribute::DiffuseColor, 0x3bd267ff_rgbaf}; +Trade::MaterialAttributeData b{"DiffuseColor", 0x3bd267ff_rgbaf}; +/* [MaterialAttributeData-name] */ +} + +{ +/* [MaterialData-usage] */ +Trade::MaterialData data = DOXYGEN_IGNORE(Trade::MaterialData{{}, {}}); + +// Assumes the attribute exists +Float roughness = data.attribute(Trade::MaterialAttribute::Roughness); + +// Optional access +Color4 color = data.attributeOr(Trade::MaterialAttribute::BaseColor, + 0x3bd267ff_rgbaf); +if(Containers::Optional texture = + data.tryAttribute(Trade::MaterialAttribute::BaseColorTexture)) +{ + // ... +} +/* [MaterialData-usage] */ +static_cast(roughness); +static_cast(color); +} + +{ +Trade::MaterialData data{{}, {}}; +/* [MaterialData-usage-types] */ +/* Prefer a specular/roughness workflow, if present */ +if(data.types() & Trade::MaterialType::PbrSpecularGlossiness) { + const auto& pbr = data.as(); + + Color4 diffuse = pbr.diffuseColor(); + Color4 specular = pbr.specularColor(); + Float glossiness = pbr.glossiness(); + + DOXYGEN_IGNORE(static_cast(diffuse), static_cast(specular), static_cast(glossiness);) + +/* Otherwise use metallic/roughness (or defaults if no attributes present) */ +} else { + const auto& pbr = data.as(); + + Color4 base = pbr.baseColor(); + Float metalness = pbr.metalness(); + Float roughness = pbr.roughness(); + + DOXYGEN_IGNORE(static_cast(base), static_cast(metalness), static_cast(roughness);) +} +/* [MaterialData-usage-types] */ +} + +{ +/* [MaterialData-usage-texture-complexity] */ +Trade::PbrSpecularGlossinessMaterialData data = DOXYGEN_IGNORE(Trade::PbrSpecularGlossinessMaterialData{{}, {}}); + +/* Simple case for diffuse + packed specular/glossiness texture, the default + coordinate set and a common coordinate transformation for all textures */ +if(data.hasAttribute(Trade::MaterialAttribute::DiffuseTexture) && + data.hasSpecularGlossinessTexture() && + data.hasCommonTextureTransformation() && !data.hasTextureCoordinates()) +{ + UnsignedInt diffuse = data.diffuseTexture(); + UnsignedInt specularGlossiness = data.specularTexture(); + Matrix3 textureMatrix = data.commonTextureMatrix(); + + DOXYGEN_IGNORE(static_cast(diffuse), static_cast(specularGlossiness), static_cast(textureMatrix);) + +/* Extra work needed when using a non-default texture coordinate set */ +} else if(data.hasTextureCoordinates() && data.hasCommonTextureCoordinates()) { + DOXYGEN_IGNORE() + +/* Etc... */ +} else Fatal{} << "Material too complex, giving up"; +/* [MaterialData-usage-texture-complexity] */ +} + +{ +Trade::MaterialData data{{}, {}}; +/* [MaterialData-usage-layers] */ +if(data.hasLayer(Trade::MaterialLayer::ClearCoat)) { + Float clearCoatFactor = data.attributeOr(Trade::MaterialLayer::ClearCoat, + Trade::MaterialAttribute::LayerFactor, 1.0f); + Float clearCoatRoughness = data.attributeOr(Trade::MaterialLayer::ClearCoat, + Trade::MaterialAttribute::Roughness, 0.0f); + + DOXYGEN_IGNORE(static_cast(clearCoatFactor), static_cast(clearCoatRoughness);) +} +/* [MaterialData-usage-layers] */ +} + +{ +Trade::MaterialData data{{}, {}}; +/* [MaterialData-usage-layers-types] */ +if(data.types() & Trade::MaterialType::PbrClearCoat) { + const auto& clearCoat = data.as(); + + Float clearCoatFactor = clearCoat.layerFactor(); + Float clearCoatRoughness = clearCoat.roughness(); + + DOXYGEN_IGNORE(static_cast(clearCoatFactor), static_cast(clearCoatRoughness);) +} +/* [MaterialData-usage-layers-types] */ +} + +{ +/* [MaterialData-populating] */ +Trade::MaterialData data{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::DoubleSided, true}, + {Trade::MaterialAttribute::BaseColor, 0x3bd267ff_srgbaf}, + {Trade::MaterialAttribute::BaseColorTexture, 17u}, + {Trade::MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})} +}}; +/* [MaterialData-populating] */ +} + +{ +/* [MaterialData-populating-non-owned] */ +using namespace Containers::Literals; + +constexpr Trade::MaterialAttributeData attributes[]{ + {"DoubleSided"_s, true}, + {"BaseColor"_s, Color4{0.043735f, 0.64448f, 0.135633f, 1.0f}}, + {"BaseColorTexture"_s, 5u}, + {"TextureMatrix"_s, Matrix3{{0.5f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}}} +}; + +Trade::MaterialData data{Trade::MaterialType::Phong, {}, attributes}; +/* [MaterialData-populating-non-owned] */ +} + +#ifdef MAGNUM_TARGET_GL +{ +GL::Texture2D baseColorTexture; +/* [MaterialData-populating-custom] */ +Trade::MaterialData data{Trade::MaterialType::PbrMetallicRoughness, { + {Trade::MaterialAttribute::BaseColor, 0x3bd267ff_srgbaf}, + {Trade::MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {"baseColorTexturePointer", &baseColorTexture}, + {"highlightColor", 0x00ffff_srgbf}, + {"name", "Canary Green Plastic, really ugly"} +}}; + +// Retrieving the texture pointer +GL::Texture2D* texture = data.attribute("baseColorTexturePointer"); +/* [MaterialData-populating-custom] */ +static_cast(texture); +} +#endif + +{ +/* [MaterialData-populating-layers] */ +Trade::MaterialData data{ + Trade::MaterialType::PbrMetallicRoughness|Trade::MaterialType::PbrClearCoat, + { + {Trade::MaterialAttribute::BaseColor, 0xffcc33_srgbf}, + {Trade::MaterialAttribute::NoneRoughnessMetallicTexture, 0u}, + + {Trade::MaterialLayer::ClearCoat}, + {Trade::MaterialAttribute::LayerFactorTexture, 1u}, + {Trade::MaterialAttribute::RoughnessTexture, 1u}, + {Trade::MaterialAttribute::RoughnessTextureSwizzle, + Trade::MaterialTextureSwizzle::G} + }, + + {2, 6} +}; +/* [MaterialData-populating-layers] */ +} + +{ +UnsignedInt a, b, c, d, sandTile, grassTile, rockTile; +/* [MaterialData-populating-layers-custom] */ +Trade::MaterialData proceduralLandscape{ + Trade::MaterialTypes{}, // Doesn't match any builtin material type + { + // Rock layer + {Trade::MaterialAttribute::LayerFactorTexture, a}, + {Trade::MaterialAttribute::BaseColorTexture, rockTile}, + + // Sand layer + {Trade::MaterialAttribute::LayerFactorTexture, b}, + {"blendType", "mix"}, + {Trade::MaterialAttribute::BaseColorTexture, sandTile}, + + // Grass layer + {Trade::MaterialAttribute::LayerFactorTexture, c}, + {"blendType", "overlay"}, + {"strandLengthTexture", d}, + {Trade::MaterialAttribute::BaseColorTexture, grassTile}, + }, + + // There's no base material, everything is in layers + {0, 2, 5, 9} +}; +/* [MaterialData-populating-layers-custom] */ +} + { /* [MeshIndexData-usage] */ Containers::ArrayView indices; diff --git a/doc/snippets/MagnumTrade.glsl b/doc/snippets/MagnumTrade.glsl new file mode 100644 index 0000000000..d1cef7a37a --- /dev/null +++ b/doc/snippets/MagnumTrade.glsl @@ -0,0 +1,38 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* [unpackTwoChannelNormal] */ +/* Assumes (1, y, 1, x) or (x, y, , 1) */ +vec3 unpackTwoChannelNormal(vec4 packed) { + packed.x *= packed.w; + + /* reconstruct Z */ + vec3 normal; + normal.xy = packed.xy*2.0 - 1.0; + normal.z = sqrt(max(0.0, 1.0 - dot(normal.xy, + normal.xy))); + return normal; +} +/* [unpackTwoChannelNormal] */ diff --git a/src/Magnum/MeshTools/sceneconverter.cpp b/src/Magnum/MeshTools/sceneconverter.cpp index 768349a560..aa25e19b25 100644 --- a/src/Magnum/MeshTools/sceneconverter.cpp +++ b/src/Magnum/MeshTools/sceneconverter.cpp @@ -39,8 +39,10 @@ #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshObjectData3D.h" +#include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Trade/Implementation/converterUtilities.h" @@ -268,6 +270,20 @@ save its output; if no --converter is specified, AnySceneConverter is used.)") return 0; } + struct MaterialInfo { + UnsignedInt material; + UnsignedInt references; + Trade::MaterialData data{{}, {}}; + std::string name; + }; + + struct TextureInfo { + UnsignedInt texture; + UnsignedInt references; + Trade::TextureData data{{}, {}, {}, {}, {}, {}}; + std::string name; + }; + struct MeshAttributeInfo { std::size_t offset; UnsignedInt stride, arraySize; @@ -290,17 +306,59 @@ save its output; if no --converter is specified, AnySceneConverter is used.)") /* Parse everything first to avoid errors interleaved with output */ - /* Scene properties. Currently just counting how much is each mesh - shared. */ + /* Scene properties. Currently just counting how much is each mesh / + material shared. Texture reference count is calculated when parsing + materials. */ + Containers::Array materialReferenceCount{importer->materialCount()}; Containers::Array meshReferenceCount{importer->meshCount()}; for(UnsignedInt i = 0; i != importer->object3DCount(); ++i) { Containers::Pointer object = importer->object3D(i); - if(object && object->instanceType() == Trade::ObjectInstanceType3D::Mesh && std::size_t(object->instance()) < meshReferenceCount.size()) - ++meshReferenceCount[object->instance()]; + if(object && object->instanceType() == Trade::ObjectInstanceType3D::Mesh) { + auto& meshObject = static_cast(*object); + if(std::size_t(meshObject.instance()) < meshReferenceCount.size()) + ++meshReferenceCount[meshObject.instance()]; + if(std::size_t(meshObject.material()) < materialReferenceCount.size()) + ++materialReferenceCount[meshObject.material()]; + } } - /* Mesh properties */ + /* Material properties */ bool error = false; + Containers::Array materialInfos; + Containers::Array textureReferenceCount{importer->textureCount()}; + for(UnsignedInt i = 0; i != importer->materialCount(); ++i) { + Containers::Optional material; + { + Duration d{importTime}; + if(!(material = importer->material(i))) { + error = true; + continue; + } + } + + /* Calculate texture reference count for all properties that + look like a texture */ + for(UnsignedInt j = 0; j != material->layerCount(); ++j) { + for(UnsignedInt k = 0; k != material->attributeCount(j); ++k) { + if(material->attributeType(j, k) != Trade::MaterialAttributeType::UnsignedInt || !Utility::String::endsWith(material->attributeName(j, k), "Texture")) + continue; + + const UnsignedInt texture = material->attribute(j, k); + if(texture < textureReferenceCount.size()) + ++textureReferenceCount[texture]; + } + } + + MaterialInfo info{}; + info.material = i; + info.name = importer->materialName(i); + info.references = materialReferenceCount[i]; + info.data = *std::move(material); + + arrayAppend(materialInfos, std::move(info)); + } + + /* Mesh properties */ Containers::Array meshInfos; for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { for(UnsignedInt j = 0; j != importer->meshLevelCount(i); ++j) { @@ -384,12 +442,115 @@ save its output; if no --converter is specified, AnySceneConverter is used.)") } } + /* Texture properties */ + Containers::Array textureInfos; + for(UnsignedInt i = 0; i != importer->textureCount(); ++i) { + Containers::Optional texture; + { + Duration d{importTime}; + if(!(texture = importer->texture(i))) { + error = true; + continue; + } + } + + TextureInfo info{}; + info.texture = i; + info.name = importer->textureName(i); + info.references = textureReferenceCount[i]; + info.data = *std::move(texture); + + arrayAppend(textureInfos, std::move(info)); + } + /* In case the images have all just a single level and no names, write them in a compact way without listing levels. */ bool compactImages = false; Containers::Array imageInfos = Trade::Implementation::imageInfo(*importer, error, compactImages); + for(const MaterialInfo& info: materialInfos) { + Debug d; + d << "Material" << info.material; + /* Print reference count only if there actually is a scene, + otherwise this information is useless */ + if(importer->object3DCount()) + d << Utility::formatString("(referenced by {} objects)", info.references); + d << Debug::nospace << ":"; + if(!info.name.empty()) d << info.name; + + d << Debug::newline << " Type:" << info.data.types(); + + for(UnsignedInt i = 0; i != info.data.layerCount(); ++i) { + /* Print extra layers with extra indent */ + const char* indent; + if(info.data.layerCount() != 1 && i != 0) { + d << Debug::newline << " Layer" << i << Debug::nospace << ":"; + if(!info.data.layerName(i).isEmpty()) + d << info.data.layerName(i); + indent = " "; + } else indent = " "; + + for(UnsignedInt j = 0; j != info.data.attributeCount(i); ++j) { + /* Ignore layer name (which is always first) unless it's in + the base material, in which case we print it as it + wouldn't otherwise be shown anywhere */ + if(i && !j && info.data.attributeName(i, j) == "$LayerName") + continue; + + d << Debug::newline << indent + << info.data.attributeName(i, j) << "@" + << info.data.attributeType(i, j) << Debug::nospace + << ":"; + switch(info.data.attributeType(i, j)) { + case Trade::MaterialAttributeType::Bool: + d << info.data.attribute(i, j); + break; + #define _c(type) case Trade::MaterialAttributeType::type: \ + d << info.data.attribute(i, j); \ + break; + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + #undef _c + case Trade::MaterialAttributeType::Pointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::MutablePointer: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::String: + d << info.data.attribute(i, j); + break; + case Trade::MaterialAttributeType::TextureSwizzle: + d << info.data.attribute(i, j); + break; + } + } + } + } + for(const MeshInfo& info: meshInfos) { Debug d; if(info.level == 0) { @@ -427,6 +588,24 @@ save its output; if no --converter is specified, AnySceneConverter is used.)") d << Debug::newline << " bounds:" << attribute.bounds; } } + + for(const TextureInfo& info: textureInfos) { + Debug d; + d << "Texture" << info.texture; + /* Print reference count only if there actually are some + materials, otherwise this information is useless */ + if(importer->materialCount()) + d << Utility::formatString("(referenced by {} materials)", info.references); + d << Debug::nospace << ":"; + if(!info.name.empty()) d << info.name; + d << Debug::newline; + d << " Type:" << info.data.type(); + d << "\n Minification:" << info.data.minificationFilter() << info.data.mipmapFilter(); + d << "\n Magnification:" << info.data.magnificationFilter(); + d << "\n Wrapping:" << info.data.wrapping(); + d << "\n Image:" << info.data.image(); + } + for(const Trade::Implementation::ImageInfo& info: imageInfos) { Debug d; if(info.level == 0) { diff --git a/src/Magnum/Platform/AbstractXApplication.h b/src/Magnum/Platform/AbstractXApplication.h index 1231ccc64a..6eb9cb9cb5 100644 --- a/src/Magnum/Platform/AbstractXApplication.h +++ b/src/Magnum/Platform/AbstractXApplication.h @@ -40,6 +40,7 @@ #include /* undef Xlib nonsense to avoid conflicts */ #undef Always +#undef Bool #undef Complex #undef Convex #undef None @@ -51,6 +52,11 @@ #undef Button4 #undef Button5 +#ifndef DOXYGEN_GENERATING_OUTPUT +/* Unfortunately Xlib *needs* the Bool type, so provide a typedef instead */ +typedef int Bool; +#endif + #include "Magnum/Magnum.h" #include "Magnum/Tags.h" #include "Magnum/GL/GL.h" diff --git a/src/Magnum/Platform/WindowlessEglApplication.h b/src/Magnum/Platform/WindowlessEglApplication.h index dd250f2b4f..44c5ec3b6d 100644 --- a/src/Magnum/Platform/WindowlessEglApplication.h +++ b/src/Magnum/Platform/WindowlessEglApplication.h @@ -37,12 +37,18 @@ #include /* undef Xlib nonsense to avoid conflicts */ #undef Always +#undef Bool #undef Complex #undef None #undef Status #include #include +#ifndef DOXYGEN_GENERATING_OUTPUT +/* Unfortunately Xlib *needs* the Bool type, so provide a typedef instead */ +typedef int Bool; +#endif + #include "Magnum/Magnum.h" #include "Magnum/GL/OpenGL.h" #include "Magnum/Tags.h" diff --git a/src/Magnum/Platform/WindowlessGlxApplication.h b/src/Magnum/Platform/WindowlessGlxApplication.h index 6f3fcf1d2c..7fb71f1987 100644 --- a/src/Magnum/Platform/WindowlessGlxApplication.h +++ b/src/Magnum/Platform/WindowlessGlxApplication.h @@ -46,6 +46,7 @@ #include /* undef Xlib nonsense to avoid conflicts */ #undef Always +#undef Bool #undef Complex #undef Convex #undef None @@ -57,6 +58,11 @@ #undef Button4 #undef Button5 +#ifndef DOXYGEN_GENERATING_OUTPUT +/* Unfortunately Xlib *needs* the Bool type, so provide a typedef instead */ +typedef int Bool; +#endif + namespace Magnum { namespace Platform { /** diff --git a/src/Magnum/Shaders/Phong.cpp b/src/Magnum/Shaders/Phong.cpp index 03709088e4..dfdd6f915c 100644 --- a/src/Magnum/Shaders/Phong.cpp +++ b/src/Magnum/Shaders/Phong.cpp @@ -208,7 +208,7 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l setProjectionMatrix({}); if(lightCount) { setDiffuseColor(Magnum::Color4{1.0f}); - setSpecularColor(Magnum::Color4{1.0f}); + setSpecularColor(Magnum::Color4{1.0f, 0.0f}); setShininess(80.0f); setLightColors(Containers::Array{Containers::DirectInit, lightCount, Magnum::Color4{1.0f}}); /* Light position is zero by default */ diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 6e2edf8237..43a05e5712 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -93,7 +93,7 @@ layout(location = 6) #endif uniform lowp vec4 specularColor #ifndef GL_ES - = vec4(1.0) + = vec4(1.0, 1.0, 1.0, 0.0) #endif ; diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index ab895989e0..fcd5b55573 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -80,6 +80,11 @@ working on the framebuffer, you need to enable @ref GL::Renderer::Feature::Blending and set up the blending function. See @ref GL::Renderer::setBlendFunction() for details. +To avoid specular highlights on transparent areas, specular alpha should be +always set to @cpp 0.0f @ce. On the other hand, non-zero specular alpha can be +for example used to render transparent materials which are still expected to +have specular highlights such as glass or soap bubbles. + An alternative is to enable @ref Flag::AlphaMask and tune @ref setAlphaMask() for simple binary alpha-masked drawing that doesn't require depth sorting or blending enabled. Note that this feature is implemented using the GLSL @@ -525,10 +530,10 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { * @brief Set specular color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffffff_rgbaf @ce. Color will be multiplied + * Initial value is @cpp 0xffffff00_rgbaf @ce. Color will be multiplied * with specular texture if @ref Flag::SpecularTexture is set. If you * want to have a fully diffuse material, set specular color to - * @cpp 0x000000ff_rgbaf @ce. If @ref lightCount() is zero, this + * @cpp 0x00000000_rgbaf @ce. If @ref lightCount() is zero, this * function is a no-op, as specular color doesn't contribute to the * output in that case. * @see @ref bindSpecularTexture() diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 1425b23779..0fda5dbd88 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -33,12 +33,12 @@ #include #include "Magnum/FileCallback.h" -#include "Magnum/Trade/AbstractMaterialData.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ArrayAllocator.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" @@ -59,7 +59,7 @@ namespace Magnum { namespace Trade { std::string AbstractImporter::pluginInterface() { - return "cz.mosra.magnum.Trade.AbstractImporter/0.3.1"; + return "cz.mosra.magnum.Trade.AbstractImporter/0.3.2"; } #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT @@ -671,17 +671,39 @@ std::string AbstractImporter::materialName(const UnsignedInt id) { std::string AbstractImporter::doMaterialName(UnsignedInt) { return {}; } -Containers::Pointer AbstractImporter::material(const UnsignedInt id) { +#if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) +Containers::Optional +#else +OptionalButAlsoPointer +#endif +AbstractImporter::material(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::material(): no file opened", {}); CORRADE_ASSERT(id < doMaterialCount(), "Trade::AbstractImporter::material(): index" << id << "out of range for" << doMaterialCount() << "entries", {}); - return doMaterial(id); + + Containers::Optional material = doMaterial(id); + CORRADE_ASSERT(!material || ( + (!material->_data.deleter() || material->_data.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter)) && + (!material->_layerOffsets.deleter() || material->_layerOffsets.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::material(): implementation is not allowed to use a custom Array deleter", {}); + + /* GCC 4.8 and clang-cl needs an explicit conversion here */ + #ifdef MAGNUM_BUILD_DEPRECATED + return OptionalButAlsoPointer{std::move(material)}; + #else + return material; + #endif } -Containers::Pointer AbstractImporter::doMaterial(UnsignedInt) { +Containers::Optional AbstractImporter::doMaterial(UnsignedInt) { CORRADE_ASSERT_UNREACHABLE("Trade::AbstractImporter::material(): not implemented", {}); } -Containers::Pointer AbstractImporter::material(const std::string& name) { +#if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) +Containers::Optional +#else +OptionalButAlsoPointer +#endif +AbstractImporter::material(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::material(): no file opened", {}); const Int id = doMaterialForName(name); if(id == -1) { diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 30e5adee4f..794503df95 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -129,6 +129,18 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ImporterFlag value); */ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ImporterFlags value); +#if defined(MAGNUM_BUILD_DEPRECATED) && !defined(DOXYGEN_GENERATING_OUTPUT) +/* Could be a concrete type as only MaterialData need this, but that would + mean I'd need to include MaterialData here */ +template struct OptionalButAlsoPointer: Containers::Optional { + /*implicit*/ OptionalButAlsoPointer() = default; + /*implicit*/ OptionalButAlsoPointer(Containers::Optional&& optional): Containers::Optional{std::move(optional)} {} + CORRADE_DEPRECATED("use Containers::Optional instead of Containers::Pointer for a MaterialData") /*implicit*/ operator Containers::Pointer() && { + return Containers::Pointer{new T{std::move(**this)}}; + } +}; +#endif + /** @brief Base for importer plugins @@ -186,8 +198,8 @@ expose internal state through various accessors: - @ref importerState() can expose a pointer to the global importer state for currently opened file -- @ref AbstractMaterialData::importerState() can expose importer state for - given material imported by @ref material() +- @ref MaterialData::importerState() can expose importer state for given + material imported by @ref material() - @ref AnimationData::importerState() can expose importer state for given animation imported by @ref animation() - @ref CameraData::importerState() can expose importer state for a camera @@ -325,7 +337,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @brief Plugin interface * * @code{.cpp} - * "cz.mosra.magnum.Trade.AbstractImporter/0.3.1" + * "cz.mosra.magnum.Trade.AbstractImporter/0.3.2" * @endcode */ static std::string pluginInterface(); @@ -1020,11 +1032,16 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @brief Material * @param id Material ID, from range [0, @ref materialCount()). * - * Returns given material or @cpp nullptr @ce if importing failed. - * Expects that a file is opened. + * Returns given material or @ref Containers::NullOpt if importing + * failed. Expects that a file is opened. * @see @ref material(const std::string&) */ - Containers::Pointer material(UnsignedInt id); + #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) + Containers::Optional + #else + OptionalButAlsoPointer + #endif + material(UnsignedInt id); /** * @brief Material for given name @@ -1036,7 +1053,12 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @ref Containers::NullOpt, otherwise propagates the result from * @ref material(UnsignedInt). Expects that a file is opened. */ - Containers::Pointer material(const std::string& name); + #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) + Containers::Optional + #else + OptionalButAlsoPointer + #endif + material(const std::string& name); /** * @brief Texture count @@ -1695,7 +1717,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi virtual std::string doMaterialName(UnsignedInt id); /** @brief Implementation for @ref material() */ - virtual Containers::Pointer doMaterial(UnsignedInt id); + virtual Containers::Optional doMaterial(UnsignedInt id); /** * @brief Implementation for @ref textureCount() diff --git a/src/Magnum/Trade/AbstractMaterialData.cpp b/src/Magnum/Trade/AbstractMaterialData.cpp deleted file mode 100644 index aeec370421..0000000000 --- a/src/Magnum/Trade/AbstractMaterialData.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - This file is part of Magnum. - - Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -#include "AbstractMaterialData.h" - -#include -#include - -namespace Magnum { namespace Trade { - -AbstractMaterialData::AbstractMaterialData(AbstractMaterialData&&) noexcept = default; - -AbstractMaterialData& AbstractMaterialData::operator=(AbstractMaterialData&&) noexcept = default; - -AbstractMaterialData::AbstractMaterialData(const MaterialType type, const Flags flags, const MaterialAlphaMode alphaMode, const Float alphaMask, const void* const importerState) noexcept: _type{type}, _alphaMode{alphaMode}, _flags{flags}, _alphaMask{alphaMask}, _importerState{importerState} {} - -AbstractMaterialData::~AbstractMaterialData() = default; - -Debug& operator<<(Debug& debug, const MaterialType value) { - debug << "Trade::MaterialType" << Debug::nospace; - - switch(value) { - /* LCOV_EXCL_START */ - #define _c(value) case MaterialType::value: return debug << "::" #value; - _c(Phong) - #undef _c - /* LCOV_EXCL_STOP */ - } - - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; -} - -Debug& operator<<(Debug& debug, const AbstractMaterialData::Flag value) { - debug << "Trade::AbstractMaterialData::Flag" << Debug::nospace; - - switch(value) { - /* LCOV_EXCL_START */ - #define _c(value) case AbstractMaterialData::Flag::value: return debug << "::" #value; - _c(DoubleSided) - #undef _c - /* LCOV_EXCL_STOP */ - } - - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; -} - -Debug& operator<<(Debug& debug, const AbstractMaterialData::Flags value) { - return Containers::enumSetDebugOutput(debug, value, "Trade::AbstractMaterialData::Flags{}", { - AbstractMaterialData::Flag::DoubleSided - }); -} - -Debug& operator<<(Debug& debug, const MaterialAlphaMode value) { - debug << "Trade::MaterialAlphaMode" << Debug::nospace; - - switch(value) { - /* LCOV_EXCL_START */ - #define _c(value) case MaterialAlphaMode::value: return debug << "::" #value; - _c(Opaque) - _c(Mask) - _c(Blend) - #undef _c - /* LCOV_EXCL_STOP */ - } - - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; -} - -}} diff --git a/src/Magnum/Trade/AbstractMaterialData.h b/src/Magnum/Trade/AbstractMaterialData.h index 37bc0ae280..d0dedb11ea 100644 --- a/src/Magnum/Trade/AbstractMaterialData.h +++ b/src/Magnum/Trade/AbstractMaterialData.h @@ -25,158 +25,34 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::AbstractMaterialData, enum @ref Magnum::Trade::MaterialType + * @m_deprecated_since_latest Use @ref Magnum/Trade/MaterialData.h and the + * @ref Magnum::Trade::MaterialData "MaterialData" class instead. */ +#endif -#include - -#include "Magnum/Magnum.h" -#include "Magnum/Trade/visibility.h" - -namespace Magnum { namespace Trade { - -/** -@brief Material type - -@see @ref AbstractMaterialData::type() -*/ -enum class MaterialType: UnsignedByte { - Phong /**< Phong shading (see @ref PhongMaterialData) */ -}; - -/** -@brief Material alpha mode +#include "Magnum/configure.h" -@see @ref AbstractMaterialData::alphaMode(), - @ref AbstractMaterialData::alphaMask() -*/ -enum class MaterialAlphaMode: UnsignedByte { - /** Alpha value is ignored and the rendered output is fully opaque. */ - Opaque, +#ifdef MAGNUM_BUILD_DEPRECATED +#include "Magnum/Trade/MaterialData.h" - /** - * The rendered output is either fully transparent or fully opaque, - * depending on the alpha value and specified - * @ref AbstractMaterialData::alphaMask() value. - */ - Mask, +#ifndef _MAGNUM_NO_DEPRECATED_MESHDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MaterialData.h and the MaterialData class instead") +#endif - /** - * The alpha value is used to combine source and destination colors using - * additive blending. - */ - Blend -}; +namespace Magnum { namespace Trade { /** -@brief Base for material data - -Subclasses provide access to parameters for given material type. +@brief @copybrief MaterialData +@m_deprecated_since_latest Use @ref MaterialData instead. */ -class MAGNUM_TRADE_EXPORT AbstractMaterialData { - public: - /** - * @brief Material flag - * - * This enum is extended in subclasses. - * @see @ref Flags, @ref flags() - */ - enum class Flag: UnsignedShort { - /** - * The material is double-sided. Back faces should not be culled - * away but rendered as well, with normals flipped for correct - * lighting. - */ - DoubleSided = 1 << 0 - }; - - /** - * @brief Material flags - * - * This enum is extended in subclasses. - * @see @ref flags() - */ - typedef Containers::EnumSet Flags; - - virtual ~AbstractMaterialData(); - - /** @brief Copying is not allowed */ - AbstractMaterialData(const AbstractMaterialData&) = delete; - - /** @brief Move constructor */ - AbstractMaterialData(AbstractMaterialData&&) noexcept; - - /** @brief Copying is not allowed */ - AbstractMaterialData& operator=(const AbstractMaterialData&) = delete; - - /** @brief Move assignment */ - AbstractMaterialData& operator=(AbstractMaterialData&&) noexcept; - - /** @brief Material type */ - MaterialType type() const { return _type; } - - /** - * @brief Material flags - * - * Not all bits returned might be defiend by @ref Flag, subclasses - * define extra values. - */ - Flags flags() const { return _flags; } - - /** @brief Alpha mode */ - MaterialAlphaMode alphaMode() const { return _alphaMode; } - - /** - * @brief Alpha mask - * - * If @ref alphaMode() is @ref MaterialAlphaMode::Mask, alpha values - * below this value are rendered as fully transparent and alpha values - * above this value as fully opaque. If @ref alphaMode() is not - * @ref MaterialAlphaMode::Mask, this value is ignored. - */ - Float alphaMask() const { return _alphaMask; } - - /** - * @brief Importer-specific state - * - * See @ref AbstractImporter::importerState() for more information. - */ - const void* importerState() const { return _importerState; } - - protected: - /** - * @brief Constructor - * @param type Material type - * @param flags Untyped material flags - * @param alphaMode Alpha mode. Use - * @ref MaterialAlphaMode::Opaque for a default value. - * @param alphaMask Alpha mask value. Use @cpp 0.5f @ce for a - * default value. - * @param importerState Importer-specific state - */ - explicit AbstractMaterialData(MaterialType type, Flags flags, MaterialAlphaMode alphaMode, Float alphaMask, const void* importerState = nullptr) noexcept; - - private: - MaterialType _type; - MaterialAlphaMode _alphaMode; - Flags _flags; - Float _alphaMask; - const void* _importerState; -}; - -/** @debugoperatorenum{MaterialType} */ -MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialType value); - -/** @debugoperatorenum{MaterialAlphaMode} */ -MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialAlphaMode value); - -/** @debugoperatorclassenum{AbstractMaterialData,AbstractMaterialData::Flag} */ -MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AbstractMaterialData::Flag value); - -/** @debugoperatorclassenum{AbstractMaterialData,AbstractMaterialData::Flags} */ -MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, AbstractMaterialData::Flags value); +typedef CORRADE_DEPRECATED("use MaterialData instead") MaterialData AbstractMaterialData; }} +#else +#error use Magnum/Trade/MaterialData.h and the MaterialData class instead +#endif #endif diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index f27b368201..d15b58eb86 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -26,7 +26,6 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS - AbstractMaterialData.cpp ArrayAllocator.cpp Data.cpp LightData.cpp @@ -41,28 +40,38 @@ set(MagnumTrade_GracefulAssert_SRCS AbstractSceneConverter.cpp AnimationData.cpp CameraData.cpp + FlatMaterialData.cpp ImageData.cpp + MaterialData.cpp MeshData.cpp ObjectData2D.cpp ObjectData3D.cpp + PbrClearCoatMaterialData.cpp + PbrMetallicRoughnessMaterialData.cpp + PbrSpecularGlossinessMaterialData.cpp PhongMaterialData.cpp) set(MagnumTrade_HEADERS AbstractImporter.h AbstractImageConverter.h - AbstractMaterialData.h AbstractSceneConverter.h AnimationData.h ArrayAllocator.h CameraData.h Data.h + FlatMaterialData.h ImageData.h LightData.h + MaterialData.h + MaterialLayerData.h MeshData.h MeshObjectData2D.h MeshObjectData3D.h ObjectData2D.h ObjectData3D.h + PbrClearCoatMaterialData.h + PbrMetallicRoughnessMaterialData.h + PbrSpecularGlossinessMaterialData.h PhongMaterialData.h SceneData.h TextureData.h @@ -72,7 +81,8 @@ set(MagnumTrade_HEADERS set(MagnumTrade_PRIVATE_HEADERS Implementation/arrayUtilities.h - Implementation/converterUtilities.h) + Implementation/converterUtilities.h + Implementation/materialAttributeProperties.hpp) if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumTrade_GracefulAssert_SRCS @@ -86,6 +96,8 @@ if(MAGNUM_BUILD_DEPRECATED) MeshData2D.cpp MeshData3D.cpp) list(APPEND MagnumTrade_HEADERS + AbstractMaterialData.h + MeshData2D.h MeshData3D.h) endif() diff --git a/src/Magnum/Trade/FlatMaterialData.cpp b/src/Magnum/Trade/FlatMaterialData.cpp new file mode 100644 index 0000000000..b405f5e68e --- /dev/null +++ b/src/Magnum/Trade/FlatMaterialData.cpp @@ -0,0 +1,107 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "FlatMaterialData.h" + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" + +namespace Magnum { namespace Trade { + +using namespace Math::Literals; + +bool FlatMaterialData::hasTexture() const { + return hasAttribute(MaterialAttribute::BaseColorTexture) || + hasAttribute(MaterialAttribute::DiffuseTexture); +} + +bool FlatMaterialData::hasTextureTransformation() const { + return (hasAttribute(MaterialAttribute::BaseColorTexture) && hasAttribute(MaterialAttribute::BaseColorTextureMatrix)) || + (hasAttribute(MaterialAttribute::DiffuseTexture) && hasAttribute(MaterialAttribute::DiffuseTextureMatrix)) || + hasAttribute(MaterialAttribute::TextureMatrix); +} + +bool FlatMaterialData::hasTextureCoordinates() const { + return (hasAttribute(MaterialAttribute::BaseColorTexture) && hasAttribute(MaterialAttribute::BaseColorTextureCoordinates)) || + (hasAttribute(MaterialAttribute::DiffuseTexture) && hasAttribute(MaterialAttribute::DiffuseTextureCoordinates)) || + hasAttribute(MaterialAttribute::TextureCoordinates); +} + +Color4 FlatMaterialData::color() const { + /* If the material has a texture, return the color that matches it */ + if(hasAttribute(MaterialAttribute::BaseColorTexture)) + return attributeOr(MaterialAttribute::BaseColor, 0xffffffff_srgbaf); + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + return attributeOr(MaterialAttribute::DiffuseColor, 0xffffffff_srgbaf); + + /* If there's no texture, return whatever is present */ + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColor)) + return *value; + return attributeOr(MaterialAttribute::DiffuseColor, 0xffffffff_srgbaf); +} + +UnsignedInt FlatMaterialData::texture() const { + /* Explicit assertion because printing that DiffuseTexture isn't found + would be misleading as it can be also BaseColorTexture */ + CORRADE_ASSERT(hasTexture(), + "Trade::FlatMaterialData::texture(): the material doesn't have a texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColorTexture)) + return *value; + return attribute(MaterialAttribute::DiffuseTexture); +} + +Matrix3 FlatMaterialData::textureMatrix() const { + if(hasAttribute(MaterialAttribute::BaseColorTexture)) { + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColorTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); + } + + if(hasAttribute(MaterialAttribute::DiffuseTexture)) { + if(Containers::Optional value = tryAttribute(MaterialAttribute::DiffuseTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); + } + + CORRADE_ASSERT_UNREACHABLE("Trade::FlatMaterialData::textureMatrix(): the material doesn't have a texture", {}); +} + +UnsignedInt FlatMaterialData::textureCoordinates() const { + if(hasAttribute(MaterialAttribute::BaseColorTexture)) { + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColorTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); + } + + if(hasAttribute(MaterialAttribute::DiffuseTexture)) { + if(Containers::Optional value = tryAttribute(MaterialAttribute::DiffuseTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); + } + + CORRADE_ASSERT_UNREACHABLE("Trade::FlatMaterialData::textureCoordinates(): the material doesn't have a texture", {}); +} + +}} diff --git a/src/Magnum/Trade/FlatMaterialData.h b/src/Magnum/Trade/FlatMaterialData.h new file mode 100644 index 0000000000..9e52a41291 --- /dev/null +++ b/src/Magnum/Trade/FlatMaterialData.h @@ -0,0 +1,160 @@ +#ifndef Magnum_Trade_FlatMaterialData_h +#define Magnum_Trade_FlatMaterialData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::FlatMaterialData + * @m_since_latest + */ + +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace Trade { + +/** +@brief Flat material data +@m_since_latest + +See @ref Trade-MaterialData-usage-types for more information about how to use +this class. +@see @ref AbstractImporter::material(), @ref PhongMaterialData, + @ref PbrMetallicRoughnessMaterialData, + @ref PbrSpecularGlossinessMaterialData, @ref PbrClearCoatMaterialData, + @ref MaterialType::Flat +*/ +class MAGNUM_TRADE_EXPORT FlatMaterialData: public MaterialData { + public: + #ifndef DOXYGEN_GENERATING_OUTPUT + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing). It's + however hidden from the docs as constructing instances this way + isn't really common and it would add a lot of noise. */ + using MaterialData::MaterialData; + #endif + + /** + * @brief Whether the material has a specular texture + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::BaseColorTexture or + * @ref MaterialAttribute::DiffuseTexture attributes is present, + * @cpp false @ce otherwise. + */ + bool hasTexture() const; + + /** + * @brief Whether the material has texture transformation + * + * Returns @cpp true @ce if the material is textured and a + * @ref MaterialAttribute::BaseColorTextureMatrix, + * @ref MaterialAttribute::DiffuseTextureMatrix or + * @ref MaterialAttribute::TextureMatrix attribute matching the texture + * is present, @cpp false @ce otherwise. In particular, if there's for + * example a @ref MaterialAttribute::BaseColorTexture but only a + * @ref MaterialAttribute::DiffuseTextureMatrix, + * returns @cpp false @ce. + */ + bool hasTextureTransformation() const; + + /* Since there's just one texture, no need for any + hasCommonTextureTransformation(), hasCommonTextureCoordinates(), + commonTextureMatrix() or commonTextureCoordinates() APIs */ + + /** + * @brief Whether the material uses extra texture coordinate sets + * + * Returns @cpp true @ce if the material is textured an a + * @ref MaterialAttribute::BaseColorTextureCoordinates, + * @ref MaterialAttribute::DiffuseTextureCoordinates or + * @ref MaterialAttribute::TextureCoordinates attribute matching the + * texture is present and has a non-zero value, @cpp false @ce + * otherwise. In particular, if there's for example a + * @ref MaterialAttribute::BaseColorTexture but + * only a @ref MaterialAttribute::DiffuseTextureCoordinates, returns + * @cpp false @ce. + */ + bool hasTextureCoordinates() const; + + /** + * @brief Color + * + * Convenience access to the @ref MaterialAttribute::BaseColor / + * @ref MaterialAttribute::DiffuseColor attributes. If neither of them + * is present, the default is @cpp 0xffffffff_srgbaf @ce. + * + * If the material has a texture, the color attribute matching the + * texture is picked (instead of combining e.g. a + * @ref MaterialAttribute::BaseColor with @ref MaterialAttribute::DiffuseTexture). The color and texture is meant to + * be multiplied together. + * @see @ref hasAttribute(), @ref hasTexture() + */ + Color4 color() const; + + /** + * @brief Texture ID + * + * Available only if either @ref MaterialAttribute::BaseColorTexture or + * @ref MaterialAttribute::DiffuseTexture is present. Meant to be + * multiplied with @ref color(). + * @see @ref hasTexture(), @ref AbstractImporter::texture() + */ + UnsignedInt texture() const; + + /** + * @brief Texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::DiffuseTextureMatrix + * / @ref MaterialAttribute::BaseColorTextureMatrix / + * @ref MaterialAttribute::TextureMatrix attributes, picking the one + * matching the texture (instead of combining e.g. a + * @ref MaterialAttribute::BaseColorTexture with + * @ref MaterialAttribute::DiffuseTextureMatrix). If no matching + * attribute is present, the default is an identity matrix. Available + * only if the material has a texture. + * @see @ref hasTexture() + */ + Matrix3 textureMatrix() const; + + /** + * @brief Texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::DiffuseTextureCoordinates + * / @ref MaterialAttribute::BaseColorTextureCoordinates / + * @ref MaterialAttribute::TextureCoordinates attributes, picking the + * one matching the texture (instead of combining e.g. a + * @ref MaterialAttribute::BaseColorTexture with + * @ref MaterialAttribute::DiffuseTextureCoordinates). If no matching + * attribute is present, the default is @cpp 0 @ce. Available only if + * the material has a texture. + * @see @ref hasTexture() + */ + UnsignedInt textureCoordinates() const; +}; + +}} + +#endif diff --git a/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp new file mode 100644 index 0000000000..9cfd2b932a --- /dev/null +++ b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp @@ -0,0 +1,88 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* See Magnum/Trade/MaterialData.cpp and Magnum/Trade/Test/MaterialDataTest.cpp */ +#ifdef _c +_cnt(LayerName,"$LayerName",String,Containers::StringView) +_c(AlphaMask,Float) +_ct(AlphaBlend,Bool,bool) +_ct(DoubleSided,Bool,bool) +_c(AmbientColor,Vector4) +_c(AmbientTexture,UnsignedInt) +_c(AmbientTextureMatrix,Matrix3x3) +_c(AmbientTextureCoordinates,UnsignedInt) +_c(DiffuseColor,Vector4) +_c(DiffuseTexture,UnsignedInt) +_c(DiffuseTextureMatrix,Matrix3x3) +_c(DiffuseTextureCoordinates,UnsignedInt) +_c(SpecularColor,Vector4) +_c(SpecularTexture,UnsignedInt) +_ct(SpecularTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(SpecularTextureMatrix,Matrix3x3) +_c(SpecularTextureCoordinates,UnsignedInt) +_c(Shininess,Float) +_c(BaseColor,Vector4) +_c(BaseColorTexture,UnsignedInt) +_c(BaseColorTextureMatrix,Matrix3x3) +_c(BaseColorTextureCoordinates,UnsignedInt) +_c(Metalness,Float) +_c(MetalnessTexture,UnsignedInt) +_ct(MetalnessTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(MetalnessTextureMatrix,Matrix3x3) +_c(MetalnessTextureCoordinates,UnsignedInt) +_c(Roughness,Float) +_c(RoughnessTexture,UnsignedInt) +_ct(RoughnessTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(RoughnessTextureMatrix,Matrix3x3) +_c(RoughnessTextureCoordinates,UnsignedInt) +_c(NoneRoughnessMetallicTexture,UnsignedInt) +_c(Glossiness,Float) +_c(GlossinessTexture,UnsignedInt) +_ct(GlossinessTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(GlossinessTextureMatrix,Matrix3x3) +_c(GlossinessTextureCoordinates,UnsignedInt) +_c(SpecularGlossinessTexture,UnsignedInt) +_c(NormalTexture,UnsignedInt) +_c(NormalTextureScale,Float) +_ct(NormalTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(NormalTextureMatrix,Matrix3x3) +_c(NormalTextureCoordinates,UnsignedInt) +_c(OcclusionTexture,UnsignedInt) +_c(OcclusionTextureStrength,Float) +_ct(OcclusionTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(OcclusionTextureMatrix,Matrix3x3) +_c(OcclusionTextureCoordinates,UnsignedInt) +_c(EmissiveColor,Vector3) +_c(EmissiveTexture,UnsignedInt) +_c(EmissiveTextureMatrix,Matrix3x3) +_c(EmissiveTextureCoordinates,UnsignedInt) +_c(LayerFactor,Float) +_c(LayerFactorTexture,UnsignedInt) +_ct(LayerFactorTextureSwizzle,TextureSwizzle,MaterialTextureSwizzle) +_c(LayerFactorTextureMatrix,Matrix3x3) +_c(LayerFactorTextureCoordinates,UnsignedInt) +_c(TextureMatrix,Matrix3x3) +_c(TextureCoordinates,UnsignedInt) +#endif diff --git a/src/Magnum/Trade/Implementation/materialLayerProperties.hpp b/src/Magnum/Trade/Implementation/materialLayerProperties.hpp new file mode 100644 index 0000000000..2541a090e9 --- /dev/null +++ b/src/Magnum/Trade/Implementation/materialLayerProperties.hpp @@ -0,0 +1,29 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* See Magnum/Trade/MaterialData.cpp and Magnum/Trade/Test/MaterialDataTest.cpp */ +#ifdef _c +_c(ClearCoat) +#endif diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp new file mode 100644 index 0000000000..a888e2d27e --- /dev/null +++ b/src/Magnum/Trade/MaterialData.cpp @@ -0,0 +1,976 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "MaterialData.h" + +#include +#include +#include +#include + +#include "Magnum/Math/Vector4.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Trade/Data.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" + +namespace Magnum { namespace Trade { + +namespace { + +using namespace Containers::Literals; + +#ifndef DOXYGEN_GENERATING_OUTPUT /* It gets *really* confused */ +constexpr Containers::StringView LayerMap[]{ + #define _c(name) #name ## _s, + #include "Magnum/Trade/Implementation/materialLayerProperties.hpp" + #undef _c +}; + +constexpr struct { + Containers::StringView name; + MaterialAttributeType type; + UnsignedByte size; +} AttributeMap[]{ + #define _c(name, type) {#name ## _s, MaterialAttributeType::type, sizeof(type)}, + #define _ct(name, typeName, type) {#name ## _s, MaterialAttributeType::typeName, sizeof(type)}, + #define _cnt(name, string, typeName, type) {string ## _s, MaterialAttributeType::typeName, 255}, + #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" + #undef _c + #undef _ct + #undef _cnt +}; +#endif + +} + +UnsignedInt materialTextureSwizzleComponentCount(const MaterialTextureSwizzle swizzle) { + return (UnsignedInt(swizzle) & 0xff000000u ? 1 : 0) + + (UnsignedInt(swizzle) & 0x00ff0000u ? 1 : 0) + + (UnsignedInt(swizzle) & 0x0000ff00u ? 1 : 0) + + (UnsignedInt(swizzle) & 0x000000ffu ? 1 : 0); +} + +std::size_t materialAttributeTypeSize(const MaterialAttributeType type) { + switch(type) { + case MaterialAttributeType::Bool: + return 1; + + case MaterialAttributeType::Float: + case MaterialAttributeType::Deg: + case MaterialAttributeType::Rad: + case MaterialAttributeType::UnsignedInt: + case MaterialAttributeType::Int: + case MaterialAttributeType::TextureSwizzle: + return 4; + + case MaterialAttributeType::UnsignedLong: + case MaterialAttributeType::Long: + case MaterialAttributeType::Vector2: + case MaterialAttributeType::Vector2ui: + case MaterialAttributeType::Vector2i: + return 8; + + case MaterialAttributeType::Vector3: + case MaterialAttributeType::Vector3ui: + case MaterialAttributeType::Vector3i: + return 12; + + case MaterialAttributeType::Vector4: + case MaterialAttributeType::Vector4ui: + case MaterialAttributeType::Vector4i: + case MaterialAttributeType::Matrix2x2: + return 16; + + case MaterialAttributeType::Matrix2x3: + case MaterialAttributeType::Matrix3x2: + return 24; + + case MaterialAttributeType::Matrix2x4: + case MaterialAttributeType::Matrix4x2: + return 32; + + case MaterialAttributeType::Matrix3x3: + return 36; + + case MaterialAttributeType::Matrix3x4: + case MaterialAttributeType::Matrix4x3: + return 48; + + case MaterialAttributeType::Pointer: + case MaterialAttributeType::MutablePointer: + return sizeof(void*); + + case MaterialAttributeType::String: + CORRADE_ASSERT_UNREACHABLE("Trade::materialAttributeTypeSize(): string size is unknown", {}); + } + + CORRADE_ASSERT_UNREACHABLE("Trade::materialAttributeTypeSize(): invalid type" << type, {}); +} + +MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const MaterialAttributeType type, const std::size_t size, const void* const value) noexcept: _data{} /* zero-initialized */ { + /* Special handling for strings */ + if(type == MaterialAttributeType::String) { + const auto& stringValue = *static_cast(value); + /* The 4 extra bytes are for a null byte after both the name and value, + a type and a string size */ + CORRADE_ASSERT(name.size() + stringValue.size() + 4 <= Implementation::MaterialAttributeDataSize, + "Trade::MaterialAttributeData: name" << name << "and value" << stringValue << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << name.size() + stringValue.size(), ); + _data.type = MaterialAttributeType::String; + std::memcpy(_data.data + 1, name.data(), name.size()); + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - stringValue.size() - 2, stringValue.data(), stringValue.size()); + _data.data[Implementation::MaterialAttributeDataSize - 1] = stringValue.size(); + return; + } + + /* The 2 extra bytes are for a null byte after the name and a type */ + CORRADE_ASSERT(name.size() + size + 2 <= Implementation::MaterialAttributeDataSize, + "Trade::MaterialAttributeData: name" << name << "too long, expected at most" << Implementation::MaterialAttributeDataSize - size - 2 << "bytes for" << type << "but got" << name.size(), ); + _data.type = type; + std::memcpy(_data.data + 1, name.data(), name.size()); + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - size, value, size); +} + +MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const MaterialAttributeType type, const void* const value) noexcept: MaterialAttributeData{name, type, type == MaterialAttributeType::String ? ~std::size_t{} : materialAttributeTypeSize(type), value} {} + +MaterialAttributeData::MaterialAttributeData(const MaterialAttribute name, const MaterialAttributeType type, const void* const value) noexcept { + CORRADE_ASSERT(UnsignedInt(name) - 1 < Containers::arraySize(AttributeMap), + "Trade::MaterialAttributeData: invalid name" << name, ); + CORRADE_ASSERT(AttributeMap[UnsignedInt(name) - 1].type == type, + "Trade::MaterialAttributeData: expected" << AttributeMap[UnsignedInt(name) - 1].type << "for" << name << "but got" << type, ); + + _data.type = type; + const Containers::StringView stringName = AttributeMap[UnsignedInt(name) - 1].name; + std::memcpy(_data.data + 1, stringName.data(), stringName.size()); + + /* Special handling for strings, in that case it's sot known in advance + that we fit and has to be checked */ + if(type == MaterialAttributeType::String) { + const auto& stringValue = *static_cast(value); + /* The 4 extra bytes are for a null byte after both the name and value, + a type and a string size */ + CORRADE_ASSERT(stringName.size() + stringValue.size() + 4 <= Implementation::MaterialAttributeDataSize, + "Trade::MaterialAttributeData: name" << stringName << "and value" << stringValue << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << stringName.size() + stringValue.size(), ); + _data.type = MaterialAttributeType::String; + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - stringValue.size() - 2, stringValue.data(), stringValue.size()); + _data.data[Implementation::MaterialAttributeDataSize - 1] = stringValue.size(); + return; + } else { + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - AttributeMap[UnsignedInt(name) - 1].size, value, AttributeMap[UnsignedInt(name) - 1].size); + } +} + +/* Interestingly enough, [[ is an invalid syntax in C++11? */ +MaterialAttributeData::MaterialAttributeData(const MaterialLayer layerName) noexcept: MaterialAttributeData{MaterialAttribute::LayerName, LayerMap[([](const MaterialLayer layerName){ + CORRADE_ASSERT(UnsignedInt(layerName) - 1 < Containers::arraySize(LayerMap), "Trade::MaterialAttributeData: invalid name" << layerName, UnsignedInt{}); + return UnsignedInt(layerName) - 1; +}(layerName))]} {} + +const void* MaterialAttributeData::value() const { + if(_data.type == MaterialAttributeType::String) + return _data.s.nameValue + Implementation::MaterialAttributeDataSize - _data.s.size - 3; + return _data.data + Implementation::MaterialAttributeDataSize - materialAttributeTypeSize(_data.type); +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +/* On Windows (MSVC, clang-cl and MinGw) it needs an explicit export otherwise + the symbol doesn't get exported. */ +template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialAttributeData::value() const { + CORRADE_ASSERT(_data.type == MaterialAttributeType::String, + "Trade::MaterialAttributeData::value():" << (_data.data + 1) << "of" << _data.type << "can't be retrieved as a string", {}); + return {_data.s.nameValue + Implementation::MaterialAttributeDataSize - _data.s.size - 3, _data.s.size, Containers::StringViewFlag::NullTerminated}; +} +#endif + +MaterialData::MaterialData(const MaterialTypes types, Containers::Array&& attributeData, Containers::Array&& layerData, const void* const importerState) noexcept: _data{std::move(attributeData)}, _layerOffsets{std::move(layerData)}, _types{types}, _importerState{importerState} { + #ifndef CORRADE_NO_ASSERT + /* Not checking what's already done in MaterialAttributeData constructor. + Done before sorting so the index refers to the actual input index. */ + for(std::size_t i = 0; i != _data.size(); ++i) + CORRADE_ASSERT(_data[i]._data.type != MaterialAttributeType{}, + "Trade::MaterialData: attribute" << i << "doesn't specify anything", ); + #endif + + /* Go through all layers and sort each independently */ + const UnsignedInt implicitLayerData[]{UnsignedInt(_data.size())}; + const Containers::ArrayView layerOffsets = + _layerOffsets ? _layerOffsets : Containers::arrayView(implicitLayerData); + UnsignedInt begin = 0; + for(std::size_t i = 0; i != layerOffsets.size(); ++i) { + const UnsignedInt end = layerOffsets[i]; + CORRADE_ASSERT(begin <= end && end <= _data.size(), + "Trade::MaterialData: invalid range (" << Debug::nospace << begin << Debug::nospace << "," << end << Debug::nospace <<") for layer" << i << "with" << _data.size() << "attributes in total", ); + + /* Check if attributes are sorted already. If not, sort them. Can't + just call std::sort() unconditionally because if the data would be + immutable (for example when acquiring release()d immutable data from + another instance) it could cause crashes. (I expected not, but + apparently ASan blows up on that.) */ + if(end - begin > 1) for(std::size_t i = begin + 1; i != end; ++i) { + if(_data[i - 1].name() < _data[i].name()) continue; + + std::sort(_data + begin, _data + end, [](const MaterialAttributeData& a, const MaterialAttributeData& b) { + /* Need to check here (instead of in the outer for loop) as we + exit the loop right after the sort and thus duplicates that + occur after the first non-sorted pair wouldn't get detected. */ + CORRADE_ASSERT(a.name() != b.name(), + "Trade::MaterialData: duplicate attribute" << a.name(), false); + return a.name() < b.name(); + }); + break; + } + + begin = end; + } +} + +MaterialData::MaterialData(const MaterialTypes types, const std::initializer_list attributeData, const std::initializer_list layerData, const void* const importerState): MaterialData{types, Implementation::initializerListToArrayWithDefaultDeleter(attributeData), Implementation::initializerListToArrayWithDefaultDeleter(layerData), importerState} {} + +MaterialData::MaterialData(const MaterialTypes types, DataFlags, const Containers::ArrayView attributeData, DataFlags, Containers::ArrayView layerData, const void* const importerState) noexcept: _data{Containers::Array{const_cast(attributeData.data()), attributeData.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}}, _layerOffsets{Containers::Array{const_cast(layerData.data()), layerData.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}}, _types{types}, _importerState{importerState} { + #ifndef CORRADE_NO_ASSERT + /* Not checking what's already done in MaterialAttributeData constructor */ + for(std::size_t i = 0; i != _data.size(); ++i) + CORRADE_ASSERT(_data[i]._data.type != MaterialAttributeType{}, + "Trade::MaterialData: attribute" << i << "doesn't specify anything", ); + + /* Go through all layers and verify that each is independently sorted */ + const UnsignedInt implicitLayerData[]{UnsignedInt(_data.size())}; + const Containers::ArrayView layerOffsets = + _layerOffsets ? _layerOffsets : Containers::arrayView(implicitLayerData); + UnsignedInt begin = 0; + for(std::size_t i = 0; i != layerOffsets.size(); ++i) { + const UnsignedInt end = layerOffsets[i]; + CORRADE_ASSERT(begin <= end && end <= _data.size(), + "Trade::MaterialData: invalid range (" << Debug::nospace << begin << Debug::nospace << "," << end << Debug::nospace <<") for layer" << i << "with" << _data.size() << "attributes in total", ); + + if(end - begin > 1) for(std::size_t i = begin + 1; i != end; ++i) { + CORRADE_ASSERT(_data[i - 1].name() != _data[i].name(), + "Trade::MaterialData: duplicate attribute" << _data[i].name(), ); + CORRADE_ASSERT(_data[i - 1].name() < _data[i].name(), + "Trade::MaterialData:" << _data[i].name() << "has to be sorted before" << _data[i - 1].name() << "if passing non-owned data", ); + } + + begin = end; + } + #endif +} + +MaterialData::MaterialData(MaterialData&&) noexcept = default; + +MaterialData::~MaterialData() = default; + +MaterialData& MaterialData::operator=(MaterialData&&) noexcept = default; + +Containers::StringView MaterialData::layerString(const MaterialLayer name) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(name) - 1 >= Containers::arraySize(LayerMap)) + return nullptr; + #endif + return LayerMap[UnsignedInt(name) - 1]; +} + +Containers::StringView MaterialData::attributeString(const MaterialAttribute name) { + #ifndef CORRADE_NO_ASSERT + if(UnsignedInt(name) - 1 >= Containers::arraySize(AttributeMap)) + return nullptr; + #endif + return AttributeMap[UnsignedInt(name) - 1].name; +} + +UnsignedInt MaterialData::layerFor(const Containers::StringView layer) const { + for(std::size_t i = 1; i < _layerOffsets.size(); ++i) { + if(_layerOffsets[i] > _layerOffsets[i - 1] && + _data[_layerOffsets[i - 1]].name() == "$LayerName"_s && + _data[_layerOffsets[i - 1]].value() == layer) + return i; + } + return ~UnsignedInt{}; +} + +bool MaterialData::hasLayer(const Containers::StringView layer) const { + return layerFor(layer) != ~UnsignedInt{}; +} + +bool MaterialData::hasLayer(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasLayer(): invalid name" << layer, {}); + return hasLayer(string); +} + +UnsignedInt MaterialData::layerId(const Containers::StringView layer) const { + const UnsignedInt id = layerFor(layer); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::layerId(): layer" << layer << "not found", {}); + return id; +} + +UnsignedInt MaterialData::layerId(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerId(): invalid name" << layer, {}); + return layerId(string); +} + +Containers::StringView MaterialData::layerName(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerName(): index" << layer << "out of range for" << layerCount() << "layers", {}); + /* Deliberately ignore this attribute in the base material */ + if(layer && _layerOffsets[layer] > _layerOffsets[layer - 1] && _data[_layerOffsets[layer - 1]].name() == "$LayerName") + return _data[_layerOffsets[layer - 1]].value(); + return {}; +} + +Float MaterialData::layerFactor(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerFactor(): index" << layer << "out of range for" << layerCount() << "layers", {}); + return attributeOr(layer, MaterialAttribute::LayerFactor, 1.0f); +} + +Float MaterialData::layerFactor(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::layerFactor(): layer" << layer << "not found", {}); + return layerFactor(layerId); +} + +Float MaterialData::layerFactor(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerFactor(): invalid name" << layer, {}); + return layerFactor(string); +} + +UnsignedInt MaterialData::layerFactorTexture(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerFactorTexture(): index" << layer << "out of range for" << layerCount() << "layers", {}); + return attribute(layer, MaterialAttribute::LayerFactorTexture); +} + +UnsignedInt MaterialData::layerFactorTexture(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::layerFactorTexture(): layer" << layer << "not found", {}); + /* Not delegating into layerFactorTexture() in order to have layer name + caught in the assert */ + return attribute(layer, MaterialAttribute::LayerFactorTexture); +} + +UnsignedInt MaterialData::layerFactorTexture(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerFactorTexture(): invalid name" << layer, {}); + return layerFactorTexture(string); +} + +MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerFactorTextureSwizzle(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(hasAttribute(layer, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureSwizzle(): layer" << layer << "doesn't have a factor texture", {}); + return attributeOr(layer, MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::R); +} + +MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::layerFactorTextureSwizzle(): layer" << layer << "not found", {}); + CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureSwizzle(): layer" << layer << "doesn't have a factor texture", {}); + /* Not delegating into layerFactorTextureSwizzle() because we have a + different variant of the assert here */ + return attributeOr(layer, MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::R); +} + +MaterialTextureSwizzle MaterialData::layerFactorTextureSwizzle(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerFactorTextureSwizzle(): invalid name" << layer, {}); + return layerFactorTextureSwizzle(string); +} + +Matrix3 MaterialData::layerFactorTextureMatrix(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerFactorTextureMatrix(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(hasAttribute(layer, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureMatrix(): layer" << layer << "doesn't have a factor texture", {}); + if(Containers::Optional value = tryAttribute(layer, MaterialAttribute::LayerFactorTextureMatrix)) + return *value; + if(Containers::Optional value = tryAttribute(layer, MaterialAttribute::TextureMatrix)) + return *value; + return attributeOr(0, MaterialAttribute::TextureMatrix, Matrix3{}); +} + +Matrix3 MaterialData::layerFactorTextureMatrix(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::layerFactorTextureMatrix(): layer" << layer << "not found", {}); + CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureMatrix(): layer" << layer << "doesn't have a factor texture", {}); + /* Not delegating into layerFactorTextureMatrix() because we have a + different variant of the assert here */ + if(Containers::Optional value = tryAttribute(layerId, MaterialAttribute::LayerFactorTextureMatrix)) + return *value; + if(Containers::Optional value = tryAttribute(layerId, MaterialAttribute::TextureMatrix)) + return *value; + return attributeOr(0, MaterialAttribute::TextureMatrix, Matrix3{}); +} + +Matrix3 MaterialData::layerFactorTextureMatrix(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerFactorTextureMatrix(): invalid name" << layer, {}); + return layerFactorTextureMatrix(string); +} + +UnsignedInt MaterialData::layerFactorTextureCoordinates(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::layerFactorTextureCoordinates(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(hasAttribute(layer, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureCoordinates(): layer" << layer << "doesn't have a factor texture", {}); + if(Containers::Optional value = tryAttribute(layer, MaterialAttribute::LayerFactorTextureCoordinates)) + return *value; + if(Containers::Optional value = tryAttribute(layer, MaterialAttribute::TextureCoordinates)) + return *value; + return attributeOr(0, MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt MaterialData::layerFactorTextureCoordinates(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::layerFactorTextureCoordinates(): layer" << layer << "not found", {}); + CORRADE_ASSERT(hasAttribute(layerId, MaterialAttribute::LayerFactorTexture), + "Trade::MaterialData::layerFactorTextureCoordinates(): layer" << layer << "doesn't have a factor texture", {}); + /* Not delegating into layerFactorTextureCoordinates() because we have a + different variant of the assert here */ + if(Containers::Optional value = tryAttribute(layerId, MaterialAttribute::LayerFactorTextureCoordinates)) + return *value; + if(Containers::Optional value = tryAttribute(layerId, MaterialAttribute::TextureCoordinates)) + return *value; + return attributeOr(0, MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt MaterialData::layerFactorTextureCoordinates(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::layerFactorTextureCoordinates(): invalid name" << layer, {}); + return layerFactorTextureCoordinates(string); +} + +UnsignedInt MaterialData::attributeCount(const UnsignedInt layer) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeCount(): index" << layer << "out of range for" << layerCount() << "layers", {}); + if(!_layerOffsets) return _data.size(); + if(!layer) return _layerOffsets[0]; + return _layerOffsets[layer] - _layerOffsets[layer - 1]; +} + +UnsignedInt MaterialData::attributeCount(const Containers::StringView layer) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeCount(): layer" << layer << "not found", {}); + return attributeCount(layerId); +} + +UnsignedInt MaterialData::attributeCount(const MaterialLayer layer) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeCount(): invalid name" << layer, {}); + return attributeCount(string); +} + +UnsignedInt MaterialData::attributeFor(const UnsignedInt layer, const Containers::StringView name) const { + const MaterialAttributeData* begin = _data.begin() + + (layer && _layerOffsets ? _layerOffsets[layer - 1] : 0); + const MaterialAttributeData* end = + (_layerOffsets ? _data.begin() + _layerOffsets[layer] : _data.end()); + const MaterialAttributeData* const found = std::lower_bound(begin, end, name, [](const MaterialAttributeData& a, const Containers::StringView& b) { + return a.name() < b; + }); + if(found == end || found->name() != name) return ~UnsignedInt{}; + return found - begin; +} + +bool MaterialData::hasAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::hasAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + return attributeFor(layer, name) != ~UnsignedInt{}; +} + +bool MaterialData::hasAttribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); + return hasAttribute(layer, string); +} + +bool MaterialData::hasAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::hasAttribute(): layer" << layer << "not found", {}); + return hasAttribute(layerId, name); +} + +bool MaterialData::hasAttribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); + return hasAttribute(layer, string); +} + +bool MaterialData::hasAttribute(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); + return hasAttribute(string, name); +} + +bool MaterialData::hasAttribute(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << layer, {}); + return hasAttribute(string, name); +} + +UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeId(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); + return id; +} + +UnsignedInt MaterialData::attributeId(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << name, {}); + return attributeId(layer, string); +} + +UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): attribute" << name << "not found in layer" << layer, {}); + return id; +} + +UnsignedInt MaterialData::attributeId(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << name, {}); + return attributeId(layer, string); +} + +UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << layer, {}); + return attributeId(string, name); +} + +UnsignedInt MaterialData::attributeId(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << layer, {}); + return attributeId(string, name); +} + +Containers::StringView MaterialData::attributeName(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeName(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeName(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.data + 1; +} + +Containers::StringView MaterialData::attributeName(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeName(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeName(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.data + 1; +} + +Containers::StringView MaterialData::attributeName(const MaterialLayer layer, const UnsignedInt id) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeName(): invalid name" << layer, {}); + return attributeName(string, id); +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeType(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeType(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layer) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << name, {}); + return attributeType(layer, string); +} + +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attributeType(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layerId) + id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << name, {}); + return attributeType(layer, string); +} + +MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const UnsignedInt id) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << layer, {}); + return attributeType(string, id); +} + +MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << layer, {}); + return attributeType(string, name); +} + +MaterialAttributeType MaterialData::attributeType(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << layer, {}); + return attributeType(string, name); +} + +const void* MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layer) + id].value(); +} + +const void* MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layer) + id].value(); +} + +const void* MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return _data[layerOffset(layerId) + id].value(); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return _data[layerOffset(layerId) + id].value(); +} + +const void* MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); +} + +const void* MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, id); +} + +const void* MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, name); +} + +const void* MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, name); +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +/* On Windows (MSVC, clang-cl and MinGw) it needs an explicit export otherwise + the symbol doesn't get exported. */ +template<> MAGNUM_TRADE_EXPORT Containers::StringView MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { + /* Can't delegate to attribute() returning const void* because that doesn't + include the size */ + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + const Trade::MaterialAttributeData& data = _data[layerOffset(layer) + id]; + CORRADE_ASSERT(data._data.type == MaterialAttributeType::String, + "Trade::MaterialData::attribute():" << (data._data.data + 1) << "of" << data._data.type << "can't be retrieved as a string", {}); + return {data._data.s.nameValue + Implementation::MaterialAttributeDataSize - data._data.s.size - 3, data._data.s.size, Containers::StringViewFlag::NullTerminated}; +} + +const void* MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + if(id == ~UnsignedInt{}) return nullptr; + return _data[layerOffset(layer) + id].value(); +} + +const void* MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); +} +#endif + +const void* MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + if(id == ~UnsignedInt{}) return nullptr; + return _data[layerOffset(layerId) + id].value(); +} + +const void* MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); +} + +const void* MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); + return tryAttribute(string, name); +} + +const void* MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); + return tryAttribute(string, name); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +MaterialData::Flags MaterialData::flags() const { + Flags flags; + if(isDoubleSided()) + flags |= Flag::DoubleSided; + return flags; +} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +bool MaterialData::isDoubleSided() const { + return attributeOr(MaterialAttribute::DoubleSided, false); +} + +MaterialAlphaMode MaterialData::alphaMode() const { + if(attributeOr(MaterialAttribute::AlphaBlend, false)) + return MaterialAlphaMode::Blend; + if(hasAttribute(MaterialAttribute::AlphaMask)) + return MaterialAlphaMode::Mask; + return MaterialAlphaMode::Opaque; +} + +Float MaterialData::alphaMask() const { + return attributeOr(MaterialAttribute::AlphaMask, 0.5f); +} + +Containers::Array MaterialData::releaseLayerData() { + return std::move(_layerOffsets); +} + +Containers::Array MaterialData::releaseAttributeData() { + return std::move(_data); +} + +Debug& operator<<(Debug& debug, const MaterialLayer value) { + debug << "Trade::MaterialLayer" << Debug::nospace; + + if(UnsignedInt(value) - 1 >= Containers::arraySize(LayerMap)) + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; + + return debug << "::" << Debug::nospace << LayerMap[UnsignedInt(value) - 1]; +} + +Debug& operator<<(Debug& debug, const MaterialAttribute value) { + debug << "Trade::MaterialAttribute" << Debug::nospace; + + if(UnsignedInt(value) - 1 >= Containers::arraySize(AttributeMap)) + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; + + /* LayerName is prefixed with $, drop that */ + Containers::StringView string = AttributeMap[UnsignedInt(value) - 1].name; + if(string[0] == '$') string = string.suffix(1); + + return debug << "::" << Debug::nospace << string; +} + +Debug& operator<<(Debug& debug, const MaterialTextureSwizzle value) { + /* The swizzle is encoded as a fourCC, so just print the numerical value as + a char. Worst case this will print nothing or four garbage letters. + Sorry in that case. GCC 4.8 doesn't understand just {}, wants to have + a full MaterialTextureSwizzle{} here. */ + MaterialTextureSwizzle values[]{value, MaterialTextureSwizzle{}}; + return debug << "Trade::MaterialTextureSwizzle::" << Debug::nospace << reinterpret_cast(values); +} + +Debug& operator<<(Debug& debug, const MaterialAttributeType value) { + debug << "Trade::MaterialAttributeType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MaterialAttributeType::value: return debug << "::" #value; + _c(Bool) + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + _c(Pointer) + _c(MutablePointer) + _c(String) + _c(TextureSwizzle) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const MaterialType value) { + debug << "Trade::MaterialType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MaterialType::value: return debug << "::" #value; + _c(Flat) + _c(Phong) + _c(PbrMetallicRoughness) + _c(PbrSpecularGlossiness) + _c(PbrClearCoat) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const MaterialTypes value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::MaterialTypes{}", { + MaterialType::Flat, + MaterialType::Phong, + MaterialType::PbrMetallicRoughness, + MaterialType::PbrSpecularGlossiness, + MaterialType::PbrClearCoat + }); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +Debug& operator<<(Debug& debug, const MaterialData::Flag value) { + debug << "Trade::MaterialData::Flag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MaterialData::Flag::value: return debug << "::" #value; + _c(DoubleSided) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const MaterialData::Flags value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::MaterialData::Flags{}", { + MaterialData::Flag::DoubleSided + }); +} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +Debug& operator<<(Debug& debug, const MaterialAlphaMode value) { + debug << "Trade::MaterialAlphaMode" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MaterialAlphaMode::value: return debug << "::" #value; + _c(Opaque) + _c(Mask) + _c(Blend) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h new file mode 100644 index 0000000000..bdc1873e65 --- /dev/null +++ b/src/Magnum/Trade/MaterialData.h @@ -0,0 +1,2857 @@ +#ifndef Magnum_Trade_MaterialData_h +#define Magnum_Trade_MaterialData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::MaterialData, @ref Magnum::Trade::MaterialAttributeData, enum @ref Magnum::Trade::MaterialLayer, @ref Magnum::Trade::MaterialAttribute, @ref Magnum::Trade::MaterialTextureSwizzle, @ref Magnum::Trade::MaterialAttributeType + * @m_since_latest + */ + +#include +#include +#include +#include +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Math/RectangularMatrix.h" +#include "Magnum/Trade/Trade.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Material layer name +@m_since_latest + +Convenience aliases to actual layer name strings. The alias is in the same form +and capitalization --- so for example @ref MaterialLayer::ClearCoat is an alias +for @cpp "ClearCoat" @ce. Each layer is expected to contain (a subset of) the +@ref MaterialAttribute::LayerName, @ref MaterialAttribute::LayerFactor, +@ref MaterialAttribute::LayerFactorTexture, +@ref MaterialAttribute::LayerFactorTextureSwizzle, +@ref MaterialAttribute::LayerFactorTextureMatrix, +@ref MaterialAttribute::LayerFactorTextureCoordinates attributes in addition to +what's specified for a particular named layer. +@see @ref MaterialData, @ref MaterialData::layerName(), @ref MaterialLayerData +*/ +enum class MaterialLayer: UnsignedInt { + /* Zero used for an invalid value */ + + /** + * Clear coat material layer. + * + * Expected to contain (a subset of) the + * @ref MaterialAttribute::Roughness, + * @ref MaterialAttribute::RoughnessTexture, + * @ref MaterialAttribute::RoughnessTextureSwizzle, + * @ref MaterialAttribute::RoughnessTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureCoordinates, + * @ref MaterialAttribute::NormalTexture, + * @ref MaterialAttribute::NormalTextureSwizzle, + * @ref MaterialAttribute::NormalTextureMatrix and + * @ref MaterialAttribute::NormalTextureCoordinates attributes. + * @see @ref PbrClearCoatMaterialData + */ + ClearCoat = 1, +}; + +/** +@debugoperatorenum{MaterialLayer} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialLayer value); + +/** +@brief Material attribute name +@m_since_latest + +Convenience aliases to actual attribute name strings. In most cases the alias +is in the same form and capitalization --- so for example +@ref MaterialAttribute::DoubleSided is an alias for @cpp "DoubleSided" @ce, the +only exception is @ref MaterialAttribute::LayerName which is @cpp "$LayerName" @ce. + +When this enum si used in +@ref MaterialAttributeData constructors, the data are additionally checked for +type compatibility. Other than that, there is no difference to the string +variants. +@see @ref MaterialAttributeData, @ref MaterialData +*/ +enum class MaterialAttribute: UnsignedInt { + /* Zero used for an invalid value */ + + /** + * Layer name, @ref MaterialAttributeType::String. + * + * Unlike other attributes where string name matches the enum name, in this + * case the corresponding string is @cpp "$LayerName" @ce, done in order to + * have the layer name attribute appear first in each layer and thus + * simplify layer implementation. + * @see @ref MaterialData::layerName() + */ + LayerName = 1, + + /** + * Alpha mask, @ref MaterialAttributeType::Float. + * + * If set together with @ref MaterialAttribute::AlphaBlend, blending is + * preferred, however renderers can fall back to alpha-masked rendering. + * Alpha values below this value are meant to be rendered as fully + * transparent and alpha values above this value as fully opaque. + * @see @ref MaterialAlphaMode, @ref MaterialData::alphaMode(), + * @ref MaterialData::alphaMask() + */ + AlphaMask, + + /** + * Alpha blending, @ref MaterialAttributeType::Bool. + * + * If @cpp true @ce, the material is expected to be rendered with blending + * enabled and in correct depth order. If @cpp false @ce or not present, + * the material should be treated as opaque. If set together with + * @ref MaterialAttribute::AlphaMask, blending is preferred, however + * renderers can fall back to alpha-masked rendering. + * @see @ref MaterialAlphaMode, @ref MaterialData::alphaMode() + */ + AlphaBlend, + + /** + * Double sided, @ref MaterialAttributeType::Bool. + * + * If not present, the default value is @cpp false @ce. + * @see @ref MaterialData::isDoubleSided() + */ + DoubleSided, + + /** + * Ambient color for Phong materials, @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::AmbientTexture is present as well, these two + * are multiplied together. + * @see @ref PhongMaterialData::ambientColor() + */ + AmbientColor, + + /** + * Ambient texture index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::AmbientColor is present as well, these two + * are multiplied together. + * @see @ref PhongMaterialData::ambientTexture() + */ + AmbientTexture, + + /** + * Ambient texture transformation matrix for Phong materials, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PhongMaterialData::ambientTextureMatrix() + */ + AmbientTextureMatrix, + + /** + * Ambient texture coordinate set index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PhongMaterialData::ambientTextureCoordinates() + */ + AmbientTextureCoordinates, + + /** + * Diffuse color for Phong or PBR specular/glossiness materials, + * @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::DiffuseTexture is present as well, these two + * are multiplied together. + * @see @ref FlatMaterialData::color(), + * @ref PhongMaterialData::diffuseColor(), + * @ref PbrSpecularGlossinessMaterialData::diffuseColor() + */ + DiffuseColor, + + /** + * Diffuse texture index for Phong or PBR specular/glossiness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::DiffuseColor is present as well, these two + * are multiplied together. + * @see @ref FlatMaterialData::texture(), + * @ref PhongMaterialData::diffuseTexture(), + * @ref PbrSpecularGlossinessMaterialData::diffuseTexture() + */ + DiffuseTexture, + + /** + * Diffuse texture transformation matrix for Phong or PBR + * specular/glossiness materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref FlatMaterialData::textureMatrix(), + * @ref PhongMaterialData::diffuseTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::diffuseTextureMatrix() + */ + DiffuseTextureMatrix, + + /** + * Diffuse texture coordinate set index for Phong or PBR + * specular/glossiness materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref FlatMaterialData::textureCoordinates(), + * @ref PhongMaterialData::diffuseTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::diffuseTextureCoordinates() + */ + DiffuseTextureCoordinates, + + /** + * Specular color for Phong or PBR specular/glossiness materials, + * @ref MaterialAttributeType::Vector4. Alpha is commonly zero to not + * interfere with alpha-masked objects, non-zero alpha can be for example + * used to render transparent material which are still expected to have + * specular highlights such as glass or soap bubbles. + * + * If @ref MaterialAttribute::SpecularTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture is present as well, + * these two are multiplied together. + * @see @ref PhongMaterialData::specularColor(), + * @ref PbrSpecularGlossinessMaterialData::specularColor() + */ + SpecularColor, + + /** + * Specular texture index for Phong or PBR specular/glossiness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::SpecularColor is present as well, these two + * are multiplied together. Can be alternatively supplied as a packed + * @ref MaterialAttribute::SpecularGlossinessTexture. + * @see @ref PhongMaterialData::hasSpecularTexture(), + * @ref PhongMaterialData::specularTexture(), + * @ref PbrSpecularGlossinessMaterialData::hasSpecularTexture(), + * @ref PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture(), + * @ref PbrSpecularGlossinessMaterialData::specularTexture() + */ + SpecularTexture, + + /** + * Specular texture swizzle for Phong or PBR specular/glossiness materials, + * @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to describe whether the alpha channel of a + * @ref MaterialAttribute::SpecularTexture is used or not. Either + * @ref MaterialTextureSwizzle::RGBA or @ref MaterialTextureSwizzle::RGB + * (which is the default) is expected. Does not apply to + * @ref MaterialAttribute::SpecularGlossinessTexture --- in that case, + * the specular texture is always three-channel, regardless of this + * attribute. + * @see @ref PbrSpecularGlossinessMaterialData::specularTextureSwizzle() + */ + SpecularTextureSwizzle, + + /** + * Specular texture transformation matrix for Phong or PBR + * specular/glossiness materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PhongMaterialData::specularTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::glossinessTextureMatrix() + */ + SpecularTextureMatrix, + + /** + * Specular texture coordinate set index for Phong or PBR + * specular/glossiness materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PhongMaterialData::specularTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::specularTextureCoordinates() + */ + SpecularTextureCoordinates, + + /** + * Shininess value for Phong materials, @ref MaterialAttributeType::Float. + * + * @see @ref PhongMaterialData::shininess() + */ + Shininess, + + /** + * Base color for PBR metallic/roughness materials, + * @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::BaseColorTexture is present as well, these + * two are multiplied together. + * @see @ref FlatMaterialData::color(), + * @ref PbrMetallicRoughnessMaterialData::baseColor() + */ + BaseColor, + + /** + * Base color texture index for PBR metallic/roughness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::BaseColor is present as well, these two are + * multiplied together. + * @see @ref FlatMaterialData::texture(), + * @ref PbrMetallicRoughnessMaterialData::baseColorTexture() + */ + BaseColorTexture, + + /** + * Base color texture transformation matrix for PBR metallic/roughness + * materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref FlatMaterialData::textureMatrix(), + * @ref PbrMetallicRoughnessMaterialData::baseColorTextureMatrix() + */ + BaseColorTextureMatrix, + + /** + * Base color texture coordinate set index for PBR metallic/roughness + * materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref FlatMaterialData::textureCoordinates(), + * @ref PbrMetallicRoughnessMaterialData::baseColorTextureCoordinates() + */ + BaseColorTextureCoordinates, + + /** + * Metalness for PBR metallic/roughness materials, + * @ref MaterialAttributeType::Float. + * + * If @ref MaterialAttribute::MetalnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture is present as well, + * these two are multiplied together. + * @see @ref PbrMetallicRoughnessMaterialData::metalness() + */ + Metalness, + + /** + * Metalness texture index for PBR metallic/roughness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::Metalness is present as well, these two are + * multiplied together. Can be alternatively supplied as a packed + * @ref MaterialAttribute::NoneRoughnessMetallicTexture. + * @see @ref PbrMetallicRoughnessMaterialData::hasMetalnessTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() + * @ref PbrMetallicRoughnessMaterialData::metalnessTexture() + */ + MetalnessTexture, + + /** + * Metalness texture swizzle for PBR metallic/roughness materials, + * @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing of + * @ref MaterialAttribute::MetalnessTexture together with other maps in a + * single texture. A single-channel swizzle value is expected. If not + * present, @ref MaterialTextureSwizzle::R is assumed. Does not apply to + * @ref MaterialAttribute::NoneRoughnessMetallicTexture --- in that case, + * the metalness is implicitly in the red channel regardless of this + * attribute. + * @see @ref PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() + * @ref PbrMetallicRoughnessMaterialData::metalnessTextureSwizzle() + */ + MetalnessTextureSwizzle, + + /** + * Metalness texture transformation matrix for PBR metallic/roughness + * materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PbrMetallicRoughnessMaterialData::metalnessTextureMatrix() + */ + MetalnessTextureMatrix, + + /** + * Metalness texture coordinate set index for PBR metallic/roughness + * materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PbrMetallicRoughnessMaterialData::metalnessTextureCoordinates() + */ + MetalnessTextureCoordinates, + + /** + * Roughness for PBR metallic/roughness materials, + * @ref MaterialAttributeType::Float. + * + * If @ref MaterialAttribute::RoughnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture is present as well, + * these two are multiplied together. + * @see @ref PbrMetallicRoughnessMaterialData::roughness() + */ + Roughness, + + /** + * Roughness texture index for PBR metallic/roughness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::Roughness is present as well, these two are + * multiplied together. Can be alternatively supplied as a packed + * @ref MaterialAttribute::NoneRoughnessMetallicTexture. + * @see @ref PbrMetallicRoughnessMaterialData::hasRoughnessTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() + * @ref PbrMetallicRoughnessMaterialData::roughnessTexture() + */ + RoughnessTexture, + + /** + * Roughness texture swizzle for PBR metallic/roughness materials, + * @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing of + * @ref MaterialAttribute::RoughnessTexture together with other maps in a + * single texture. A single-channel swizzle value is expected. If not + * present, @ref MaterialTextureSwizzle::R is assumed. Does not apply to + * @ref MaterialAttribute::NoneRoughnessMetallicTexture --- in that case, + * the metalness is implicitly in the green channel regardless of this + * attribute. + * @see @ref PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() + * @ref PbrMetallicRoughnessMaterialData::roughnessTextureSwizzle() + */ + RoughnessTextureSwizzle, + + /** + * Roughness texture transformation matrix for PBR metallic/roughness + * materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PbrMetallicRoughnessMaterialData::roughnessTextureMatrix() + */ + RoughnessTextureMatrix, + + /** + * Roughness texture coordinate set index for PBR metallic/roughness + * materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PbrMetallicRoughnessMaterialData::roughnessTextureCoordinates() + */ + RoughnessTextureCoordinates, + + /** + * Combined roughness/metallic texture index for PBR metallic/roughness + * materials with metalness in the blue channel and roughness in the green + * channel, @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::Metalness / @ref MaterialAttribute::Roughness + * is present as well, these two are multiplied together. + * + * This is a convenience alias to simplify representation of glTF and UE4 + * materials, which is where this packing is used ([rationale](https://github.com/KhronosGroup/glTF/issues/857)). + * This packing (and other variants) can be alternatively specified as a + * pair of @ref MaterialAttribute::RoughnessTexture / + * @ref MaterialAttribute::MetalnessTexture attributes together with + * @ref MaterialAttribute::RoughnessTextureSwizzle set to + * @ref MaterialTextureSwizzle::G + * and @ref MaterialAttribute::MetalnessTextureSwizzle set to + * @ref MaterialTextureSwizzle::B. Texture transformation and coordinate + * set, if needed, have to be specified either using the global + * @ref MaterialAttribute::TextureMatrix and + * @ref MaterialAttribute::TextureCoordinates attributes or the per-texture + * @ref MaterialAttribute::RoughnessTextureMatrix, + * @ref MaterialAttribute::MetalnessTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureCoordinates and + * @ref MaterialAttribute::MetalnessTextureCoordinates variants. + * @see @ref PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() + * @ref PbrMetallicRoughnessMaterialData::metalnessTexture(), + * @ref PbrMetallicRoughnessMaterialData::roughnessTexture() + */ + NoneRoughnessMetallicTexture, + + /* DiffuseColor, DiffuseTexture, DiffuseTextureMatrix, + DiffuseTextureCoordinates, SpecularColor, SpecularTexture, + SpecularTextureSwizzle, SpecularTextureMatrix, + SpecularTextureCoordinates specified above for Phong already */ + + /** + * Glossiness for PBR specular/glossiness materials, + * @ref MaterialAttributeType::Float. + * + * If @ref MaterialAttribute::GlossinessTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture is present as well, + * these two are multiplied together. + * @see @ref PbrSpecularGlossinessMaterialData::glossiness() + */ + Glossiness, + + /** + * Glossiness texture index for PBR specular/glossiness materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::Glossiness is present as well, these two are + * multiplied together. Can be alternatively supplied as a packed + * @ref MaterialAttribute::SpecularGlossinessTexture. + * @see @ref PbrSpecularGlossinessMaterialData::hasGlossinessTexture(), + * @ref PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture(), + * @ref PbrSpecularGlossinessMaterialData::glossinessTexture() + */ + GlossinessTexture, + + /** + * Glossiness texture swizzle for PBR specular/glossiness materials, + * @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing of + * @ref MaterialAttribute::GlossinessTexture together with other maps in a + * single texture. A single-channel swizzle value is expected. If not + * present, @ref MaterialTextureSwizzle::R is assumed. Does not apply to + * @ref MaterialAttribute::SpecularGlossinessTexture --- in that case, + * the glossiness is implicitly in the alpha channel regardless of this + * attribute. + * @see @ref PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture(), + * @ref PbrSpecularGlossinessMaterialData::glossinessTextureSwizzle() + */ + GlossinessTextureSwizzle, + + /** + * Glossiness texture transformation matrix for PBR specular/glossiness + * materials, @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PbrSpecularGlossinessMaterialData::glossinessTextureMatrix() + */ + GlossinessTextureMatrix, + + /** + * Glossiness texture coordinate set index for PBR specular/glossiness + * materials, @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PbrSpecularGlossinessMaterialData::glossinessTextureCoordinates() + */ + GlossinessTextureCoordinates, + + /** + * Combined specular/glossiness texture index for PBR specular/glossiness + * materials with specular color in the RGB channels and glossiness in + * alpha, @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::SpecularColor / @ref MaterialAttribute::Glossiness + * is present as well, these two are multiplied together. Can be + * alternatively specified as a pair of @ref MaterialAttribute::SpecularTexture + * / @ref MaterialAttribute::GlossinessTexture attributes together with + * @ref MaterialAttribute::GlossinessTextureSwizzle set to + * @ref MaterialTextureSwizzle::A. Texture transformation and coordinate + * set, if needed, have to be specified either using the global + * @ref MaterialAttribute::TextureMatrix and + * @ref MaterialAttribute::TextureCoordinates attributes or the per-texture + * @ref MaterialAttribute::SpecularTextureMatrix, + * @ref MaterialAttribute::GlossinessTextureMatrix, + * @ref MaterialAttribute::SpecularTextureCoordinates and + * @ref MaterialAttribute::GlossinessTextureCoordinates variants. + * @see @ref PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture(), + * @ref PbrSpecularGlossinessMaterialData::specularTexture(), + * @ref PbrSpecularGlossinessMaterialData::glossinessTexture() + */ + SpecularGlossinessTexture, + + /** + * Tangent-space normal map texture index, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::NormalTextureScale is present as well, these + * two are multiplied together. + * @see @ref PhongMaterialData::normalTexture(), + * @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::normalTexture(), + * @ref PbrSpecularGlossinessMaterialData::normalTexture() + */ + NormalTexture, + + /** + * Normal texture scale, @ref MaterialAttributeType::Float. + * + * Scales the texture defined by @ref MaterialAttribute::NormalTexture. + * @see @ref PhongMaterialData::normalTextureScale(), + * @ref PbrMetallicRoughnessMaterialData::normalTextureScale(), + * @ref PbrSpecularGlossinessMaterialData::normalTextureScale(), + * @ref PbrClearCoatMaterialData::normalTextureScale() + */ + NormalTextureScale, + + /** + * Normal texture swizzle, @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing together with other maps in a + * single texture. A two- or three-channel swizzle value is expected. If + * not present, @ref MaterialTextureSwizzle::RGB is assumed. + * + * If the texture is just two-component, the remaining component is + * implicit and calculated as @f$ z = \sqrt{1 - x^2 - y^2} @f$. In order to + * account for numeric issues and avoid negative values under the square + * root, it's commonly done as @f$ z = \sqrt{\max(0, 1 - x^2 - y^2)} @f$. + * Additionally, to mitigate artifacts when storing normal texture in a + * compressed format, @ref MaterialTextureSwizzle::GA may get used instead + * of @ref MaterialTextureSwizzle::RG. + * + * Shader code that is able to reconstruct a XYZ normal from both RG and GA + * variants assuming constant values in other channels ([source](https://github.com/KhronosGroup/glTF/issues/1682#issuecomment-557880407)): + * + * @snippet MagnumTrade.glsl unpackTwoChannelNormal + * + * @see @ref PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::normalTextureSwizzle(), + * @ref PbrSpecularGlossinessMaterialData::normalTextureSwizzle(), + * @ref materialTextureSwizzleComponentCount() + */ + NormalTextureSwizzle, + + /** + * Normal texture transformation matrix, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PhongMaterialData::normalTextureMatrix(), + * @ref PbrMetallicRoughnessMaterialData::normalTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::normalTextureMatrix() + */ + NormalTextureMatrix, + + /** + * Normal texture coordinate set index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PhongMaterialData::normalTextureCoordinates(), + * @ref PbrMetallicRoughnessMaterialData::normalTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::normalTextureCoordinates() + */ + NormalTextureCoordinates, + + /** + * Occlusion texture index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Single-channel texture that multiplies the resulting material color + * @f$ \boldsymbol{c} @f$: @f[ + * \boldsymbol{c}_o = o \boldsymbol{c} + * @f] + * + * If @ref MaterialAttribute::OcclusionTextureStrength is present as well, + * it's used as an interpolation factor @f$ \color{m-success} s @f$ between + * material color @f$ \boldsymbol{c} @f$ and color with occlusion applied + * @f$ o \boldsymbol{c} @f$: @f[ + * \boldsymbol{c}_o = \operatorname{lerp}(\boldsymbol{c}, o \boldsymbol{c}, {\color{m-success} s}) = + * \boldsymbol{c} (1 - {\color{m-success} s}) + o \boldsymbol{c} {\color{m-success} s} + * @f] + * @see @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::occlusionTexture(), + * @ref PbrSpecularGlossinessMaterialData::occlusionTexture(), + * @ref Math::lerp() + */ + OcclusionTexture, + + /** + * Occlusion texture strength, @ref MaterialAttributeType::Float. + * + * Affects the texture defined by @ref MaterialAttribute::OcclusionTexture, + * see above for details. + * @see @ref PbrMetallicRoughnessMaterialData::occlusionTextureStrength(), + * @ref PbrSpecularGlossinessMaterialData::occlusionTextureStrength() + */ + OcclusionTextureStrength, + + /** + * Occlusion texture swizzle, @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing together with other maps in a + * single texture. A single-channel swizzle value is expected. If + * not present, @ref MaterialTextureSwizzle::R is assumed. + * @see @ref PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture(), + * @ref PbrMetallicRoughnessMaterialData::occlusionTextureSwizzle(), + * @ref PbrSpecularGlossinessMaterialData::occlusionTextureSwizzle() + */ + OcclusionTextureSwizzle, + + /** + * Occlusion texture transformation matrix, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PbrMetallicRoughnessMaterialData::occlusionTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::occlusionTextureSwizzle() + */ + OcclusionTextureMatrix, + + /** + * Occlusion texture coordinate set index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PbrMetallicRoughnessMaterialData::occlusionTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::occlusionTextureCoordinates() + */ + OcclusionTextureCoordinates, + + /** + * Emissive color, + * @ref MaterialAttributeType::Vector3. + * + * If @ref MaterialAttribute::EmissiveTexture is present as well, these two + * are multiplied together. + * @see @ref PbrMetallicRoughnessMaterialData::emissiveColor(), + * @ref PbrSpecularGlossinessMaterialData::emissiveColor() + */ + EmissiveColor, + + /** + * Emissive texture index, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::EmissiveColor is present as well, these two + * are multiplied together. + * @see @ref PbrMetallicRoughnessMaterialData::emissiveTexture(), + * @ref PbrSpecularGlossinessMaterialData::emissiveTexture() + */ + EmissiveTexture, + + /** @todo EmissiveTextureSwizzle? It's a color and I'm not aware of any + existing packing schemes, so probably safe to assume it's always RGB */ + + /** + * Emissive texture transformation matrix, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref PbrMetallicRoughnessMaterialData::emissiveTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::emissiveTextureMatrix(), + */ + EmissiveTextureMatrix, + + /** + * Emissive texture coordinate set index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref PbrMetallicRoughnessMaterialData::emissiveTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::emissiveTextureCoordinates() + */ + EmissiveTextureCoordinates, + + /** + * Layer intensity. @ref MaterialAttributeType::Float. + * + * Expected to be contained in additional layers, not the base material. + * The exact semantic of this attribute is layer-specific. If + * @ref MaterialAttribute::LayerFactorTexture is present as well, these two + * are multiplied together. + * @see @ref MaterialData::layerFactor() + */ + LayerFactor, + + /** + * Layer intensity texture, @ref MaterialAttributeType::UnsignedInt. + * + * Expected to be contained in additional layers, not the base material. + * The exact semantic of this attribute is layer-specific. If + * @ref MaterialAttribute::LayerFactor is present as well, these two are + * multiplied together. + * @see @ref MaterialData::layerFactorTexture() + */ + LayerFactorTexture, + + /** + * Layer intensity texture swizzle, @ref MaterialAttributeType::TextureSwizzle. + * + * Can be used to express arbitrary packing together with other maps in a + * single texture. A single-channel swizzle value is expected. If not + * present, @ref MaterialTextureSwizzle::R is assumed. + * @see @ref MaterialData::layerFactorTextureSwizzle() + */ + LayerFactorTextureSwizzle, + + /** + * Layer intensity texture transformation matrix, + * @ref MaterialAttributeType::Matrix3x3. + * + * Expected to be contained in additional layers, not the base material. + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + * @see @ref MaterialData::layerFactorTextureMatrix() + */ + LayerFactorTextureMatrix, + + /** + * Layer intensity texture coordinate set index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Expected to be contained in additional layers, not the base material. + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + * @see @ref MaterialData::layerFactorTextureCoordinates() + */ + LayerFactorTextureCoordinates, + + /** + * Common texture transformation matrix for all textures, + * @ref MaterialAttributeType::Matrix3x3. + * + * @ref MaterialAttribute::AmbientTextureMatrix / + * @ref MaterialAttribute::DiffuseTextureMatrix / + * @ref MaterialAttribute::SpecularTextureMatrix / + * @ref MaterialAttribute::MetalnessTextureMatrix / + * @ref MaterialAttribute::RoughnessTextureMatrix / + * @ref MaterialAttribute::GlossinessTextureMatrix / + * @ref MaterialAttribute::NormalTextureMatrix / + * @ref MaterialAttribute::OcclusionTextureMatrix / + * @ref MaterialAttribute::EmissiveTextureMatrix / + * @ref MaterialAttribute::LayerFactorTextureMatrix have a precedence over + * this attribute for given texture, if present. + * @see @ref PhongMaterialData::hasCommonTextureTransformation(), + * @ref PbrMetallicRoughnessMaterialData::hasCommonTextureTransformation(), + * @ref PbrSpecularGlossinessMaterialData::hasCommonTextureTransformation(), + * @ref PbrClearCoatMaterialData::hasCommonTextureTransformation(), + * @ref PhongMaterialData::commonTextureMatrix(), + * @ref PbrMetallicRoughnessMaterialData::commonTextureMatrix(), + * @ref PbrSpecularGlossinessMaterialData::commonTextureMatrix(), + * @ref PbrClearCoatMaterialData::commonTextureMatrix(), + * @ref FlatMaterialData::textureMatrix() + */ + TextureMatrix, + + /** + * Common texture coordinate set index for all textures, + * @ref MaterialAttributeType::UnsignedInt. + * + * @ref MaterialAttribute::AmbientTextureCoordinates / + * @ref MaterialAttribute::DiffuseTextureCoordinates / + * @ref MaterialAttribute::SpecularTextureCoordinates / + * @ref MaterialAttribute::MetalnessTextureCoordinates / + * @ref MaterialAttribute::RoughnessTextureCoordinates / + * @ref MaterialAttribute::GlossinessTextureCoordinates / + * @ref MaterialAttribute::NormalTextureCoordinates / + * @ref MaterialAttribute::OcclusionTextureCoordinates / + * @ref MaterialAttribute::EmissiveTextureCoordinates / + * @ref MaterialAttribute::LayerFactorTextureCoordinates have a precedence + * over this attribute for given texture, if present. + * @see @ref PhongMaterialData::hasCommonTextureCoordinates(), + * @ref PbrMetallicRoughnessMaterialData::hasCommonTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::hasCommonTextureCoordinates(), + * @ref PbrClearCoatMaterialData::hasCommonTextureCoordinates(), + * @ref PhongMaterialData::commonTextureCoordinates(), + * @ref PbrMetallicRoughnessMaterialData::commonTextureCoordinates(), + * @ref PbrSpecularGlossinessMaterialData::commonTextureCoordinates(), + * @ref PbrClearCoatMaterialData::commonTextureCoordinates(), + * @ref FlatMaterialData::textureCoordinates() + */ + TextureCoordinates, +}; + +/** +@debugoperatorenum{MaterialAttribute} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialAttribute value); + +/** +@brief Material texture swizzle +@m_since_latest + +See @ref MaterialData for more information. +@see @ref materialTextureSwizzleComponentCount() +*/ +enum class MaterialTextureSwizzle: UnsignedInt { + /** Red component */ + R = Utility::Endianness::fourCC('R', '\0', '\0', '\0'), + + /** Green component */ + G = Utility::Endianness::fourCC('G', '\0', '\0', '\0'), + + /** Blue component */ + B = Utility::Endianness::fourCC('B', '\0', '\0', '\0'), + + /** Alpha component */ + A = Utility::Endianness::fourCC('A', '\0', '\0', '\0'), + + /** Red and green component */ + RG = Utility::Endianness::fourCC('R', 'G', '\0', '\0'), + + /** Green and blue component */ + GB = Utility::Endianness::fourCC('G', 'B', '\0', '\0'), + + /** + * Green and alpha component. May get used to mitigate artifacts when + * storing two independent channels (such as two-channel normal maps) in + * compressed texture formats --- these commonly have separately compressed + * RGB and alpha, with green channel having the most precision of the RGB + * triplet. + * @see @ref MaterialAttribute::NormalTextureSwizzle + */ + GA = Utility::Endianness::fourCC('G', 'A', '\0', '\0'), + + /** Blue and alpha component */ + BA = Utility::Endianness::fourCC('B', 'A', '\0', '\0'), + + /** RGB components */ + RGB = Utility::Endianness::fourCC('R', 'G', 'B', '\0'), + + /** GBA components */ + GBA = Utility::Endianness::fourCC('G', 'B', 'A', '\0'), + + /** RGBA components */ + RGBA = Utility::Endianness::fourCC('R', 'G', 'B', 'A'), +}; + +/** +@brief Component count in a material texture swizzle +@m_since_latest + +Returns for example @cpp 2 @ce for @ref MaterialTextureSwizzle::GA. +*/ +MAGNUM_TRADE_EXPORT UnsignedInt materialTextureSwizzleComponentCount(MaterialTextureSwizzle swizzle); + +/** +@debugoperatorenum{MaterialTextureSwizzle} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialTextureSwizzle value); + +/** +@brief Material attribute type +@m_since_latest + +See @ref MaterialData for more information. +@see @ref MaterialAttribute, @ref MaterialAttributeData, + @ref materialAttributeTypeSize() +*/ +enum class MaterialAttributeType: UnsignedByte { + /* Zero used for an invalid value */ + + Bool = 1, /**< @cpp bool @ce */ + + Float, /**< @ref Magnum::Float "Float" */ + Deg, /**< @ref Magnum::Deg "Deg" */ + Rad, /**< @ref Magnum::Rad "Rad" */ + UnsignedInt, /**< @ref Magnum::UnsignedInt "UnsignedInt" */ + Int, /**< @ref Magnum::Int "Int" */ + UnsignedLong, /**< @ref Magnum::UnsignedLong "UnsignedLong" */ + Long, /**< @ref Magnum::Long "Long" */ + + Vector2, /**< @ref Magnum::Vector2 "Vector2" */ + Vector2ui, /**< @ref Magnum::Vector2ui "Vector2ui" */ + Vector2i, /**< @ref Magnum::Vector2i "Vector2i" */ + + Vector3, /**< @ref Magnum::Vector3 "Vector3" */ + Vector3ui, /**< @ref Magnum::Vector3ui "Vector3ui" */ + Vector3i, /**< @ref Magnum::Vector3i "Vector3i" */ + + Vector4, /**< @ref Magnum::Vector4 "Vector4" */ + Vector4ui, /**< @ref Magnum::Vector4ui "Vector4ui" */ + Vector4i, /**< @ref Magnum::Vector4i "Vector4i" */ + + Matrix2x2, /**< @ref Magnum::Matrix2x2 "Matrix2x2" */ + Matrix2x3, /**< @ref Magnum::Matrix2x3 "Matrix2x3" */ + Matrix2x4, /**< @ref Magnum::Matrix2x4 "Matrix2x4" */ + + Matrix3x2, /**< @ref Magnum::Matrix3x2 "Matrix3x2" */ + Matrix3x3, /**< @ref Magnum::Matrix3x3 "Matrix3x3" */ + Matrix3x4, /**< @ref Magnum::Matrix3x4 "Matrix3x4" */ + + Matrix4x2, /**< @ref Magnum::Matrix4x2 "Matrix4x2" */ + Matrix4x3, /**< @ref Magnum::Matrix4x3 "Matrix4x3" */ + + /* Matrix4x4 not present */ + + /** + * @cpp const void* @ce, type is not preserved. For convenience it's + * possible to retrieve the value by calling @cpp attribute() @ce + * with an arbitrary `T` but the user has to ensure the type is correct. + */ + Pointer, + + /** + * @cpp void* @ce, type is not preserved. For convenience it's possible to + * retrieve the value by calling @cpp attribute() @ce with an arbitrary + * `T` but the user has to ensure the type is correct. + */ + MutablePointer, + + /** + * Null-terminated string. Can be stored using any type convertible to + * @ref Corrade::Containers::StringView, retrieval has to be done using + * @ref Corrade::Containers::StringView. + */ + String, + + /** One of the values from @ref MaterialTextureSwizzle */ + TextureSwizzle +}; + +/** +@brief Byte size of a material attribute type +@m_since_latest + +Can't be used with @ref MaterialAttributeType::String, as the size varies +depending on the value. +*/ +MAGNUM_TRADE_EXPORT std::size_t materialAttributeTypeSize(MaterialAttributeType type); + +/** +@debugoperatorenum{MaterialAttributeType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialAttributeType value); + +namespace Implementation { + template struct MaterialAttributeTypeFor; + enum: std::size_t { MaterialAttributeDataSize = 64 }; +} + +/** +@brief Material attribute data +@m_since_latest + +See @ref MaterialData for more information. +*/ +class MAGNUM_TRADE_EXPORT MaterialAttributeData { + public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the attribute array for @ref MaterialData, + * expected to be replaced with concrete values later. + */ + constexpr explicit MaterialAttributeData() noexcept: _data{} {} + + /** + * @brief Construct with a string name + * @param name Attribute name + * @param value Attribute value + * + * The @p name together with @p value is expected to fit into 62 bytes. + * @ref MaterialAttributeType is inferred from the type passed. + * + * This function is useful in @cpp constexpr @ce contexts and for + * creating custom material attributes. For known attributes prefer to + * use @ref MaterialAttributeData(MaterialAttribute, const T&) if you + * don't need @cpp constexpr @ce, as it additionally checks that given + * attribute has the expected type. + */ + template::value>::type + #endif + > constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, const T& value) noexcept; + + /** + * @brief Construct with a string name and string value + * @param name Attribute name + * @param value Attribute value + * + * The combined length of @p name and @p value is expected to fit into + * 61 bytes. Type is set to @ref MaterialAttributeType::String. + * + * This function is useful in @cpp constexpr @ce contexts and for + * creating custom material attributes. For known attributes prefer to + * use @ref MaterialAttributeData(MaterialAttribute, const T&) if you + * don't need @cpp constexpr @ce, as it additionally checks that given + * attribute has the expected type. + */ + constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, Containers::StringView value) noexcept; + + #ifndef DOXYGEN_GENERATING_OUTPUT + /* "Sure can't be constexpr" overloads to avoid going through the + *insane* overload puzzle when not needed */ + template::value>::type> /*implicit*/ MaterialAttributeData(const char* name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor::type(), sizeof(T), &value} {} + /*implicit*/ MaterialAttributeData(const char* name, Containers::StringView value) noexcept: MaterialAttributeData{name, MaterialAttributeType::String, 0, &value} {} + #endif + + /** + * @brief Construct with a predefined name + * @param name Attribute name + * @param value Attribute value + * + * Compared to @ref MaterialAttributeData(Containers::StringView, const T&) + * checks that the attribute is in expected type. The + * @ref MaterialAttribute gets converted to a corresponding string + * name. Apart from the type check, the following two instances are + * equivalent: + * + * @snippet MagnumTrade.cpp MaterialAttributeData-name + */ + template::value>::type + #endif + > /*implicit*/ MaterialAttributeData(MaterialAttribute name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor::type(), &value} {} + + /** + * @brief Construct with a predefined name and string value + * @param name Attribute name + * @param value Attribute value + * + * Compared to @ref MaterialAttributeData(Containers::StringView, Containers::StringView) + * checks that the attribute is in expected type. The + * @ref MaterialAttribute gets converted to a corresponding string + * name. Apart from the type check, the following two instances are + * equivalent: + * + * @snippet MagnumTrade.cpp MaterialAttributeData-name + */ + /*implicit*/ MaterialAttributeData(MaterialAttribute name, Containers::StringView value) noexcept: MaterialAttributeData{name, MaterialAttributeType::String, &value} {} + + /** + * @brief Construct from a type-erased value + * @param name Attribute name + * @param type Attribute type + * @param value Type-erased value + * + * In case @p type is not @ref MaterialAttributeType::String, copies a + * number of bytes according to @ref materialAttributeTypeSize() from + * @p value. The @p name together with @p value is expected to fit into + * 62 bytes. + * + * In case @p type is @ref MaterialAttributeType::String, @p value is + * expected to point to a @ref Containers::StringView. The combined + * length of @p name and @p value strings is expected to fit into 61 + * bytes. + */ + /*implicit*/ MaterialAttributeData(Containers::StringView name, MaterialAttributeType type, const void* value) noexcept; + + /** + * @brief Construct with a predefined name + * @param name Attribute name + * @param type Attribute type + * @param value Attribute value + * + * Compared to @ref MaterialAttributeData(Containers::StringView, MaterialAttributeType, const void*) + * checks that the attribute is in expected type. The + * @ref MaterialAttribute gets converted to a corresponding string + * name. + */ + /*implicit*/ MaterialAttributeData(MaterialAttribute name, MaterialAttributeType type, const void* value) noexcept; + + /** + * @brief Construct a layer name attribute + * @param layerName Material layer name + * + * Equivalent to calling @ref MaterialAttributeData(MaterialAttribute, Containers::StringView) + * with @ref MaterialAttribute::LayerName and a string corresponding to + * @p layerName. + */ + /*implicit*/ MaterialAttributeData(MaterialLayer layerName) noexcept; + + /** @brief Attribute type */ + MaterialAttributeType type() const { return _data.type; } + + /** + * @brief Attribute name + * + * The returned view always has + * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + */ + Containers::StringView name() const { return _data.data + 1; } + + /** + * @brief Type-erased attribute value + * + * Cast the pointer to a concrete type based on @ref type(). Note that + * in case of a @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * + * In case of a @ref MaterialAttributeType::String, returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus prefer + * to use typed access in that case. + */ + const void* value() const; + + /** + * @brief Attribute value + * + * Expects that @p T corresponds to @ref type(). + */ + template T value() const; + + private: + friend MaterialData; + + explicit MaterialAttributeData(Containers::StringView name, const MaterialAttributeType type, std::size_t typeSize, const void* value) noexcept; + + /* Most of this is needed only for the constexpr constructor (yay C++), + the actual data layout is + + |------------- x B ------------| + + +------+------- .. -----+------+ + | type | name .. \0 | data | + | 1 B | (x - n - 2) B | n B | + +------+------- .. -----+------+ + + where + + - `x` is Implementation::MaterialAttributeDataSize, + - `type` is an 8-bit MaterialAttributeType, + - `data` is of size matching `type`, at the offset of + `(x - materialAttributeTypeSize(type))` B, + - `name` is a null-terminated string filling the rest. + + This way the name is always at the same offset to make binary search + lookup fast and efficient, and data being at the end (instead of + right after the null-terminated string) makes them accessible in O(1) + as well. */ + struct StringData { + template constexpr explicit StringData(MaterialAttributeType type, Containers::StringView name, Containers::StringView value, Math::Implementation::Sequence): type{type}, nameValue{(sequence < name.size() ? name[sequence] : (sequence - (Implementation::MaterialAttributeDataSize - value.size() - 3) < value.size() ? value[sequence - (Implementation::MaterialAttributeDataSize - value.size() - 3)] : '\0'))...}, size{UnsignedByte(value.size())} {} + constexpr explicit StringData(MaterialAttributeType type, Containers::StringView name, Containers::StringView value): StringData{type, name, value, typename Math::Implementation::GenerateSequence::Type{}} {} + + MaterialAttributeType type; + char nameValue[Implementation::MaterialAttributeDataSize - 2]; + UnsignedByte size; + }; + union ErasedScalar { + constexpr explicit ErasedScalar(Float value): f{value} {} + constexpr explicit ErasedScalar(Deg value): f{Float(value)} {} + constexpr explicit ErasedScalar(Rad value): f{Float(value)} {} + constexpr explicit ErasedScalar(UnsignedInt value): u{value} {} + constexpr explicit ErasedScalar(Int value): i{value} {} + constexpr explicit ErasedScalar(MaterialTextureSwizzle value): u{UnsignedInt(value)} {} + + Float f; + UnsignedInt u; + Int i; + }; + union ErasedLongScalar { + constexpr explicit ErasedLongScalar(UnsignedLong value): u{value} {} + constexpr explicit ErasedLongScalar(Long value): i{value} {} + + UnsignedLong u; + Long i; + }; + template union ErasedVector { + constexpr explicit ErasedVector(const Math::Vector& value): f{value} {} + constexpr explicit ErasedVector(const Math::Vector& value): u{value} {} + constexpr explicit ErasedVector(const Math::Vector& value): i{value} {} + + Math::Vector f; + Math::Vector u; + Math::Vector i; + }; + template union ErasedMatrix { + constexpr explicit ErasedMatrix(const Math::RectangularMatrix& value): a{value} {} + constexpr explicit ErasedMatrix(const Math::RectangularMatrix& value): b{value} {} + + Math::RectangularMatrix a; + Math::RectangularMatrix b; + }; + template struct Data { + template constexpr explicit Data(MaterialAttributeType type, Containers::StringView name, const U& value, Math::Implementation::Sequence): type{type}, name{(sequence < name.size() ? name[sequence] : '\0')...}, value{value} {} + template constexpr explicit Data(MaterialAttributeType type, Containers::StringView name, const U& value): Data{type, name, value, typename Math::Implementation::GenerateSequence<63 - sizeof(T)>::Type{}} {} + + MaterialAttributeType type; + char name[Implementation::MaterialAttributeDataSize - sizeof(MaterialAttributeType) - sizeof(T)]; + T value; + }; + union CORRADE_ALIGNAS(8) Storage { + constexpr explicit Storage() noexcept: data{} {} + + constexpr explicit Storage(Containers::StringView name, Containers::StringView value) noexcept: s{MaterialAttributeType::String, name, value} {} + + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _1{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _4{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::value && !std::is_pointer::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _8{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::value && !std::is_pointer::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _8v{type, name, value} {} + constexpr explicit Storage(MaterialAttributeType type, Containers::StringView name, const void* value) noexcept: p{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _12{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _16{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::value, MaterialAttributeType>::type type, Containers::StringView name, const T& value) noexcept: _16m{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _24{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _32{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _36{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _48{type, name, value} {} + + MaterialAttributeType type; + char data[Implementation::MaterialAttributeDataSize]; + StringData s; + Data _1; + Data p; + Data _4; + Data _8; + Data> _8v; + Data> _12; + Data> _16; + Data> _16m; + Data> _24; + Data> _32; + Data> _36; + Data> _48; + } _data; + + static_assert(sizeof(Storage) == Implementation::MaterialAttributeDataSize, "something is off, huh"); +}; + +/** +@brief Material type + +@see @ref MaterialTypes, @ref MaterialData::types() +*/ +enum class MaterialType: UnsignedInt { + /** + * Flat. Use @ref FlatMaterialData for convenience attribute access. + * Materials of this type are generally not combined with any other types. + */ + Flat = 1 << 0, + + /** + * Phong. Use @ref PhongMaterialData for convenience attribute access. + */ + Phong = 1 << 1, + + /** + * PBR metallic/roughness. Use @ref PbrMetallicRoughnessMaterialData for + * convenience attribute access. + */ + PbrMetallicRoughness = 1 << 2, + + /** + * PBR specular/glossiness. Use @ref PbrSpecularGlossinessMaterialData for + * convenience attribute access. + */ + PbrSpecularGlossiness = 1 << 3, + + /** + * PBR clear coat layer. Use @ref PbrClearCoatMaterialData for convenience + * attribute access. + */ + PbrClearCoat = 1 << 4 +}; + +/** @debugoperatorenum{MaterialType} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialType value); + +/** +@brief Material types +@m_since_latest + +@see @ref MaterialData::types() +*/ +typedef Containers::EnumSet MaterialTypes; + +CORRADE_ENUMSET_OPERATORS(MaterialTypes) + +/** @debugoperatorenum{MaterialTypes} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialTypes value); + +/** +@brief Material alpha mode + +Convenience access to @ref MaterialAttribute::AlphaBlend and +@ref MaterialAttribute::AlphaMask attributes. +@see @ref MaterialData::alphaMode(), @ref MaterialData::alphaMask() +*/ +enum class MaterialAlphaMode: UnsignedByte { + /** Alpha value is ignored and the rendered output is fully opaque. */ + Opaque, + + /** + * The rendered output is either fully transparent or fully opaque, + * depending on the alpha value and specified + * @ref MaterialData::alphaMask() value. + */ + Mask, + + /** + * The alpha value is used to combine source and destination colors using + * additive blending. + */ + Blend +}; + +/** @debugoperatorenum{MaterialAlphaMode} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialAlphaMode value); + +/** +@brief Material data +@m_since_latest + +Key-value store for builtin as well as custom material attributes, with an +ability to define additional layers further affecting the base material. +Populated instances of this class are returned from +@ref AbstractImporter::material(). + +@section Trade-MaterialData-usage Usage + +The simplest usage is through templated @ref attribute() functions, which take +a string attribute name or one of the pre-defined @ref MaterialAttribute +values. You're expected to check for attribute presence first with +@ref hasAttribute(), and the requested type has to match @ref attributeType(). +To make things easier, each of the attributes defined in @ref MaterialAttribute +has a strictly defined type, so you can safely assume the type when requesting +those. In addition there's @ref tryAttribute() and @ref attributeOr() that +return a @ref Containers::NullOpt or a default value in case given attribute is +not found. + +@snippet MagnumTrade.cpp MaterialData-usage + +It's also possible to iterate through all attributes using @ref attributeName(), +@ref attributeType() and @ref attribute() taking indices instead of names, with +@ref attributeCount() returning the total attribute count. + +@subsection Trade-MaterialData-usage-types Material types and cnvenience accessors + +A material usually consists of a set of attributes for a particular rendering +workflow --- PBR metallic/roughness, Phong or for example flat-shaded +materials. To hint what the material contains, @ref types() returns a set of +@ref MaterialType values. It's not just a single value as the data can define +attributes for more than one material type (for example both metallic/roughness +and specular/glossiness PBR workflow), allowing the application to pick the +best type for a particular use case. + +Because retrieving everything through the @ref attribute() APIs can get verbose +and complex, the @ref Trade library provides a set of accessor APIs for common +material types such as @ref FlatMaterialData, @ref PhongMaterialData, +@ref PbrMetallicRoughnessMaterialData or @ref PbrSpecularGlossinessMaterialData. +Using @ref as() you can convert any @ref MaterialData instance to a reference +to one of those. These convenience APIs then take care of default values when +an attribute isn't present or handle fallbacks when an attribute can be defined +in multiple ways: + +@snippet MagnumTrade.cpp MaterialData-usage-types + +Each @ref MaterialAttribute is exposed through one or more of those convenience +APIs, see the documentation of of a particular enum value for more information. + +@subsection Trade-MaterialData-usage-texture-complexity Texture packing, coordinate transformation and coordinate sets + +The material APIs allow for a lot of flexibility regarding --- texture maps may +be arbitrarily packed together to efficiently use all four channels, each +texture can be using a different set of texture coordinates and there can be +a different coordinate transformation for each texture. + +In most cases, however, real-world textures fit into a few well-known packing +schemes and usually have a common transformation and coordinate sets for all. +Checking for all corner cases on the application side would be a headache, so +there are queries like @ref PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture() or +@ref PbrSpecularGlossinessMaterialData::hasCommonTextureTransformation() to +help narrowing the options down: + +@snippet MagnumTrade.cpp MaterialData-usage-texture-complexity + +@subsection Trade-MaterialData-usage-layers Material layers + +In addition to the base material, there can be material layers. While a +material attribute can be specified only once for a particular layer, multiple +layers can use the same attribute name for different purpose. Layers are +commonly used in PBR workflows to describe lacquered wood, metallic paint or +for example a thin film on leather surfaces. You can enumerate and query layers +using @ref layerCount(), @ref layerName() and @ref hasLayer(), layer-specific +attributes are retrieved by passing layer ID or name as the first parameter to +the @ref attribute() family of APIs. + +For each layer there can be predefined @ref layerFactor(), +@ref layerFactorTexture() and other texture-related attributes which define how +much the layer affects the underlying material, but the exact semantics of how +the factor is applied is left to the layer implementation. + +Here's an example showing retrieval of a clear coat layer parameters, if +present: + +@snippet MagnumTrade.cpp MaterialData-usage-layers + +Like with base material attributes, builtin layers also have convenience +accessor APIs. The above can be written in a more compact way using +@link PbrClearCoatMaterialData @endlink: + +@snippet MagnumTrade.cpp MaterialData-usage-layers-types + +@section Trade-MaterialData-populating Populating an instance + +A @ref MaterialData instance by default takes over ownership of an +@ref Corrade::Containers::Array containing @ref MaterialAttributeData +instances, together with @ref MaterialTypes suggesting available material types +(or an empty set, in case of a fully custom material). Attribute values can be +in one of the types from @ref MaterialAttributeType, and the type is in most +cases inferred implicitly. The class internally uses strings for attribute +names, but you're encouraged to use the predefined names from +@ref MaterialAttribute --- with those, the attribute gets checked additionally +that it's in an expected type. Attribute order doesn't matter, the array gets +internally sorted by name to allow a @f$ \mathcal{O}(\log n) @f$ lookup. + +@snippet MagnumTrade.cpp MaterialData-populating + +In addition to passing ownership of an array it's also possible to have the +@ref MaterialData instance refer to external data (for example in a +memory-mapped file, constant memory etc.). The additional second argument is +@ref DataFlags that's used only for safely disambiguating from the owning +constructor, and you'll pass a @ref Corrade::Containers::ArrayView instead of +an @ref Corrade::Containers::Array. Note that in this case, since the attribute +data is treated as immutable, you *have to* ensure the list is sorted by name. + +@snippet MagnumTrade.cpp MaterialData-populating-non-owned + + + +@m_class{m-note m-info} + +@par + Additionally, as shown above, in order to create a @cpp constexpr @ce + @ref MaterialAttributeData array, you need to use + @ref Corrade::Containers::StringView literals instead of plain C strings + or the @ref MaterialAttribute enum, and be sure to call only + @cpp constexpr @ce-enabled constructors of stored data types. + +@subsection Trade-MaterialData-populating-custom Custom material attributes + +While attribute names beginning with uppercase letters are reserved for builtin +Magnum attributes, anything beginning with a lowercase letter or a non-letter +can be a custom attribute. For greater flexibility, custom attributes can be +also strings or pointers, allowing you to store arbitrary properties or direct +texture pointers instead of IDs: + +@snippet MagnumTrade.cpp MaterialData-populating-custom + +@subsection Trade-MaterialData-populating-layers Adding material layers + +Material layers are internally represented as ranges of the attribute array and +by default the whole attribute array is treated as a base material. The actual +split into layers can be described using an additionaly offset array passed to +@ref MaterialData(MaterialTypes, Containers::Array&&, Containers::Array&&, const void*), +where entry *i* specifies the end offset of layer *i* --- in the following +snippet we have two layers (one base material and one clear coat layer), the +base material being the first two attributes and the clear coat layer being +attributes in range @cpp 2 @ce to @cpp 6 @ce (thus four attributes): + +@snippet MagnumTrade.cpp MaterialData-populating-layers + +Like with just a base material, the attributes get sorted for a +@f$ \mathcal{O}(\log n) @f$ lookup --- but not as a whole, each layer +separately. In contrary, because layer order matters, those are not reordered +(and thus their lookup is @f$ \mathcal{O}(n) @f$, however it isn't expected to +have that many layers for this to matter). The layers can be named by supplying +a @ref MaterialAttribute::LayerName attribute (or, like shown above, by using +the convenience @ref MaterialAttributeData::MaterialAttributeData(MaterialLayer) +constructor) but don't have to --- if a layer doesn't have a name, it can be +only looked up by its index, not by a name. + +Apart from builtin layers, there's no limit in what the layers can be used for +--- the data can for example describe a completely custom landscape from a set +of authored `rockTile`, `sandTile`, `grassTile` textures and procedurally +generated blend factors `a`, `b`, `c`, `d`: + +@snippet MagnumTrade.cpp MaterialData-populating-layers-custom + +@section Trade-MaterialData-representation Internal representation + +The attributes are stored sorted by the key in a contiguous array, with each +@ref MaterialAttributeData item occupying 64 bytes. The item contains a 1-byte +type identifier, the actual value and the rest is occupied with null-terminated +name. This means the name length can vary from 14 bytes for +@ref Magnum::Matrix3x4 "Matrix3x4" / @ref Magnum::Matrix4x3 "Matrix4x3" to 61 +bytes for @cpp bool @ce (excluding null terminator). As each item has a fixed +size anyway, there's no value in supporting space-efficient 8-, 16- or +half-float types. Conversely, @ref Magnum::Double "Double" types are currently +not supported either as there isn't currently seen any need for extended +precision. + +@m_class{m-block m-warning} + +@par Max representable data size + With the current design, @ref MaterialAttributeData is 64 bytes and in + order to fit a type identifier and a string attribute name of a reasonable + length, the maximum data size is capped to 48 bytes. This means + @ref Magnum::Matrix4x4 "Matrix4x4" isn't listed among supported types, but + it shouldn't be a problem in practice --- ever an arbitrary color + correction matrix is just 3x4 values with the bottom row being always + @f$ \begin{pmatrix} 0 & 0 & 0 & 1 \end{pmatrix} @f$. This restriction might + get lifted eventually. +*/ +class MAGNUM_TRADE_EXPORT MaterialData { + public: + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Material flag + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided() etc. + * + * This enum is further extended in subclasses. + * @see @ref Flags, @ref flags() + */ + enum class CORRADE_DEPRECATED_ENUM("use hasAttribute() etc. instead") Flag: UnsignedInt { + /** + * The material is double-sided. Back faces should not be culled + * away but rendered as well, with normals flipped for correct + * lighting. + */ + DoubleSided = 1 << 0 + }; + + /** + * @brief Material flags + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided() etc. + * + * This enum is extended in subclasses. + * @see @ref flags() + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flag, ugh */ + typedef CORRADE_DEPRECATED("use hasAttribute() etc. instead") Containers::EnumSet Flags; + CORRADE_IGNORE_DEPRECATED_POP + #endif + + /** + * @brief Construct + * @param types Which material types are described by this + * data. Can be an empty set. + * @param attributeData Attribute data + * @param importerState Importer-specific state + * + * The @p attributeData gets sorted by name internally, expecting no + * duplicates. + */ + explicit MaterialData(MaterialTypes types, Containers::Array&& attributeData, const void* importerState = nullptr) noexcept: MaterialData{types, std::move(attributeData), nullptr, importerState} {} + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MaterialData(MaterialTypes types, std::initializer_list attributeData, const void* importerState = nullptr): MaterialData{types, attributeData, {}, importerState} {} + + /** + * @brief Construct a non-owned material data + * @param types Which material types are described by + * this data. Can be an empty set. + * @param attributeDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param attributeData Attribute data + * @param importerState Importer-specific state + * + * The @p attributeData is expected to be already sorted by name, + * without duplicates. + */ + explicit MaterialData(MaterialTypes types, DataFlags attributeDataFlags, Containers::ArrayView attributeData, const void* importerState = nullptr) noexcept: MaterialData{types, attributeDataFlags, attributeData, {}, nullptr, importerState} {} + + /** + * @brief Construct with layers + * @param types Which material types are described by this + * data. Can be an empty set. + * @param attributeData Attribute data + * @param layerData Layer offset data + * @param importerState Importer-specific state + * + * The @p attributeData gets sorted by name internally, expecting no + * duplicates inside each layer. The @p layerData is expected to be + * either empty or a monotonically non-decreasing sequence of offsets + * not larger than @p attributeData size, with *i*-th item specifying + * end offset of *i*-th layer. + */ + explicit MaterialData(MaterialTypes types, Containers::Array&& attributeData, Containers::Array&& layerData, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MaterialData(MaterialTypes types, std::initializer_list attributeData, std::initializer_list layerData, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned material data with layers + * @param types Which material types are described by + * this data. Can be an empty set. + * @param attributeDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param attributeData Attribute data + * @param layerDataFlags Ignored. Used only for a safer + * distinction from the owning constructor. + * @param layerData Layer offset data + * @param importerState Importer-specific state + * + * The @p data is expected to be already sorted by name, without + * duplicates inside each layer. The @p layerData is expected to be + * either empty or a monotonically non-decreasing sequence of offsets + * not larger than @p attributeData size, with *i*-th item specifying + * end offset of *i*-th layer. + */ + /* The second (ignored) DataFlags is present in order to make it ready + for a possible extension where only one of the data is non-owned. + But so far I didn't see a need. */ + explicit MaterialData(MaterialTypes types, DataFlags attributeDataFlags, Containers::ArrayView attributeData, DataFlags layerDataFlags, Containers::ArrayView layerData, const void* importerState = nullptr) noexcept; + + ~MaterialData(); + + /** @brief Copying is not allowed */ + MaterialData(const MaterialData&) = delete; + + /** @brief Move constructor */ + MaterialData(MaterialData&&) noexcept; + + /** @brief Copying is not allowed */ + MaterialData& operator=(const MaterialData&) = delete; + + /** @brief Move assignment */ + MaterialData& operator=(MaterialData&&) noexcept; + + /** + * @brief Material types + * + * Each type indicates that the material data can be interpreted as + * given type. For custom materials the set can also be empty. + */ + MaterialTypes types() const { return _types; } + + /** + * @brief Interpret as a material data of concrete type + * + * Returns a reference to @cpp *this @ce cast to given type. @p T is + * expected to be a subclass of the same size such as + * @ref PbrMetallicRoughnessMaterialData, + * @ref PbrSpecularGlossinessMaterialData or @ref PhongMaterialData. + */ + /* MSVC needs the & here, otherwise it complains that "cannot overload + a member function with ref-qualifier with a member function without + ref-qualifier". Clang or GCC doesn't. */ + template const T& as() const & { + static_assert(std::is_base_of::value && sizeof(T) == sizeof(MaterialData), "expected a trivial subclass of MaterialData"); + return static_cast(*this); + } + + /** + * @brief Interpret a rvalue as a material data of concrete type + * + * Compared to the above, returns a value and not a reference. The + * original instance then behaves the same as a moved-from instance. + */ + template T as() && { + return T{std::move(const_cast(as()))}; + } + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Material type + * @m_deprecated_since_latest Use @ref types() instead. + */ + CORRADE_DEPRECATED("use types() instead") MaterialType type() const { + return MaterialType(UnsignedInt(_types & MaterialType::Phong)); + } + #endif + + /** + * @brief Raw layer offset data + * + * May return @cpp nullptr @ce if the material doesn't have any extra + * layers. + * @see @ref releaseLayerData() + */ + Containers::ArrayView layerData() const { return _layerOffsets; } + + /** + * @brief Raw attribute data + * + * Returns @cpp nullptr @ce if the material has no attributes. + * @see @ref releaseAttributeData() + */ + Containers::ArrayView attributeData() const { return _data; } + + /** + * @brief Layer count + * + * There's always at least the base material, so this function returns + * at least @cpp 1 @ce. + */ + UnsignedInt layerCount() const { + return _layerOffsets.empty() ? 1 : _layerOffsets.size(); + } + + /** + * @brief Whether a material has given named layer + * + * Layers with no name assigned are skipped. The base material (layer + * @cpp 0 @ce is skipped as well) to avoid confusing base material with + * a layer. If you want to create a material consisting of just a + * layer, use @cpp 0 @ce for the first layer offset in the constructor. + * @see @ref hasAttribute() + */ + bool hasLayer(Containers::StringView layer) const; + bool hasLayer(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief ID of a named layer + * + * The @p layer is expected to exist. + * @see @ref hasLayer() + */ + UnsignedInt layerId(Containers::StringView layer) const; + UnsignedInt layerId(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Layer name + * + * Retrieves a @ref MaterialAttribute::LayerName attribute from given + * layer, if present. Returns a @cpp nullptr @ce view if the layer + * has no name, and an empty non-null view if the layer name is empty. + * The @p layer is expected to be smaller than @ref layerCount() const. + * + * The name, if present, is ignored for the base material (layer + * @cpp 0 @ce) to avoid confsing base material with a layer. If you + * want to create a material consisting of just a layer, use @cpp 0 @ce + * for the first layer offset in the constructor. + */ + Containers::StringView layerName(UnsignedInt layer) const; + + /** + * @brief Factor of given layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactor + * attribute. If not present, the default is @cpp 1.0f @ce. The + * @p layer is expected to be smaller than @ref layerCount() const. + * + * If the layer has @ref MaterialAttribute::LayerFactorTexture, the + * factor and texture is meant to be multiplied together. + */ + Float layerFactor(UnsignedInt layer) const; + + /** + * @brief Factor of a named layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactor + * attribute. If not present, the default is @cpp 1.0f @ce. The + * @p layer is expected to exist. + * + * If the layer has @ref MaterialAttribute::LayerFactorTexture, the + * factor and texture is meant to be multiplied together. + * @see @ref hasLayer() + */ + Float layerFactor(Containers::StringView layer) const; + Float layerFactor(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Factor texture ID for given layer + * + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. Meant to be multiplied with @ref layerFactor(). + * The @p layer is expected to be smaller than @ref layerCount(). + * @see @ref hasAttribute() + */ + UnsignedInt layerFactorTexture(UnsignedInt layer) const; + + /** + * @brief Factor texture ID for a named layer + * + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. Meant to be multiplied with @ref layerFactor(). + * The @p layer is expected to exist. + * @see @ref hasLayer(), @ref hasAttribute() + */ + UnsignedInt layerFactorTexture(Containers::StringView layer) const; + UnsignedInt layerFactorTexture(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Factor texture swizzle for given layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureSwizzle + * attribute. If not present, the default is @ref MaterialTextureSwizzle::R. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to be smaller than + * @ref layerCount(). + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle layerFactorTextureSwizzle(UnsignedInt layer) const; + + /** + * @brief Factor texture swizzle for a named layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureSwizzle + * attribute. If not present, the default is @ref MaterialTextureSwizzle::R. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to exist. + * @see @ref hasLayer(), @ref hasAttribute() + */ + MaterialTextureSwizzle layerFactorTextureSwizzle(Containers::StringView layer) const; + MaterialTextureSwizzle layerFactorTextureSwizzle(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Factor texture coordinate transformation matrix for given layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes in given layer or + * a @ref MaterialAttribute::TextureMatrix attribute in the base + * material. If neither is present, the default is an identity matrix. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to be smaller than + * @ref layerCount(). + * @see @ref hasAttribute() + */ + Matrix3 layerFactorTextureMatrix(UnsignedInt layer) const; + + /** + * @brief Factor texture coordinate transformation matrix for a named layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes in given layer or + * a @ref MaterialAttribute::TextureMatrix attribute in the base + * material. If neither is present, the default is an identity matrix. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to exist. + * @see @ref hasLayer(), @ref hasAttribute() + */ + Matrix3 layerFactorTextureMatrix(Containers::StringView layer) const; + Matrix3 layerFactorTextureMatrix(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Factor texture coordinate set for given layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes in given + * layer or a @ref MaterialAttribute::TextureCoordinates attribute in + * the base material. If neither is present, the default is @cpp 0 @ce. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to be smaller than + * @ref layerCount(). + * @see @ref hasAttribute() + */ + UnsignedInt layerFactorTextureCoordinates(UnsignedInt layer) const; + + /** + * @brief Factor texture coordinate set for a named layer + * + * Convenience access to the @ref MaterialAttribute::LayerFactorTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes in given + * layer or a @ref MaterialAttribute::TextureCoordinates attribute in + * the base material. If not present, the default is @cpp 0 @ce. + * Available only if the @ref MaterialAttribute::LayerFactorTexture + * attribute is present. The @p layer is expected to exist. + * @see @ref hasLayer(), @ref hasAttribute() + */ + UnsignedInt layerFactorTextureCoordinates(Containers::StringView layer) const; + UnsignedInt layerFactorTextureCoordinates(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Attribute count in given layer + * + * The @p layer is expected to be smaller than @ref layerCount() const. + */ + UnsignedInt attributeCount(UnsignedInt layer) const; + + /** + * @brief Attribute count in a named layer + * + * The @p layer is expected to exist. + * @see @ref hasLayer() + */ + UnsignedInt attributeCount(Containers::StringView layer) const; + UnsignedInt attributeCount(MaterialLayer layer) const; /**< @overload */ + + /** + * @brief Attribute count in the base material + * + * Equivalent to calling @ref attributeCount(UnsignedInt) const with + * @p layer set to @cpp 0 @ce. + */ + UnsignedInt attributeCount() const { return attributeCount(0); } + + /** + * @brief Whether a material layer has given attribute + * + * The @p layer is expected to be smaller than @ref layerCount() const. + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + */ + bool hasAttribute(UnsignedInt layer, Containers::StringView name) const; + bool hasAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Whether a named material layer has given attribute + * + * The @p layer is expected to exist. + * @see @ref tryAttribute(), @ref attributeOr(), @ref hasLayer() + */ + bool hasAttribute(Containers::StringView layer, Containers::StringView name) const; + bool hasAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + bool hasAttribute(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + bool hasAttribute(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Whether the base material has given attribute + * + * Equivalent to calling @ref hasAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + * @see @ref tryAttribute(), @ref attributeOr() + */ + bool hasAttribute(Containers::StringView name) const { + return hasAttribute(0, name); + } + bool hasAttribute(MaterialAttribute name) const { + return hasAttribute(0, name); + } /**< @overload */ + + /** + * @brief ID of a named attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. + * @see @ref hasAttribute() + */ + UnsignedInt attributeId(UnsignedInt layer, Containers::StringView name) const; + UnsignedInt attributeId(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief ID of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. + * @see @ref hasLayer(), @ref hasAttribute() + */ + UnsignedInt attributeId(Containers::StringView layer, Containers::StringView name) const; + UnsignedInt attributeId(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + UnsignedInt attributeId(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + UnsignedInt attributeId(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief ID of a named attribute in the base material + * + * Equivalent to calling @ref attributeId(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + UnsignedInt attributeId(Containers::StringView name) const { + return attributeId(0, name); + } + UnsignedInt attributeId(MaterialAttribute name) const { + return attributeId(0, name); + } /**< @overload */ + + /** + * @brief Name of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. The returned view always has + * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @see @ref attributeType() + */ + Containers::StringView attributeName(UnsignedInt layer, UnsignedInt id) const; + + /** + * @brief Name of an attribute in a named material layer + * + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. + * @see @ref hasLayer() + */ + Containers::StringView attributeName(Containers::StringView layer, UnsignedInt id) const; + Containers::StringView attributeName(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ + + /** + * @brief Name of an attribute in the base material + * + * Equivalent to calling @ref attributeName(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + Containers::StringView attributeName(UnsignedInt id) const { + return attributeName(0, id); + } + + /** + * @brief Type of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. + * @see @ref attributeName() + */ + MaterialAttributeType attributeType(UnsignedInt layer, UnsignedInt id) const; + + /** + * @brief Type of a named attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. + * @see @ref hasAttribute() + */ + MaterialAttributeType attributeType(UnsignedInt layer, Containers::StringView name) const; + /** @overload */ + MaterialAttributeType attributeType(UnsignedInt layer, MaterialAttribute name) const; + + /** + * @brief Type of an attribute in a named material layer + * + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. + * @see @ref hasLayer() + */ + MaterialAttributeType attributeType(Containers::StringView layer, UnsignedInt id) const; + MaterialAttributeType attributeType(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ + + /** + * @brief Type of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. + * @see @ref hasLayer(), @ref hasAttribute() + */ + MaterialAttributeType attributeType(Containers::StringView layer, Containers::StringView name) const; + MaterialAttributeType attributeType(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + MaterialAttributeType attributeType(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + MaterialAttributeType attributeType(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type of an attribute in the base material + * + * Equivalent to calling @ref attributeType(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + MaterialAttributeType attributeType(UnsignedInt id) const { + return attributeType(0, id); + } + + /** + * @brief Type of a named attribute in the base material + * + * Equivalent to calling @ref attributeType(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + MaterialAttributeType attributeType(Containers::StringView name) const { + return attributeType(0, name); + } + MaterialAttributeType attributeType(MaterialAttribute name) const { + return attributeType(0, name); + } /**< @overload */ + + /** + * @brief Type-erased value of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. Cast the pointer to a concrete type based on + * @ref type(). + * + * - In case of a @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * - In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus + * prefer to use typed access in that case. + */ + const void* attribute(UnsignedInt layer, UnsignedInt id) const; + + /** + * @brief Type-erased value of a named attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. Cast the pointer to + * a concrete type based on @ref attributeType(). + * + * - In case of a @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * - In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus prefer + * to use typed access in that case. + * + * @see @ref hasAttribute(), @ref tryAttribute(), @ref attributeOr() + */ + const void* attribute(UnsignedInt layer, Containers::StringView name) const; + const void* attribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased value of an attribute in a named material layer + * + * The @p layer is expected to exist and the @p id is expected to be smaller than @ref attributeCount(UnsignedInt) const + * in that layer. Cast the pointer to a concrete type based on + * @ref attributeType(). + * + * - In case of a @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * - In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus prefer + * to use typed access in that case. + * + * @see @ref hasLayer() + */ + const void* attribute(Containers::StringView layer, UnsignedInt id) const; + const void* attribute(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ + + /** + * @brief Type-erased value of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. Cast the pointer to a concrete type based on + * @ref attributeType(). + * + * - In case of a @ref MaterialAttributeType::Pointer or a + * @ref MaterialAttributeType::MutablePointer, returns a + * *pointer to a pointer*, not the pointer value itself. + * - In case of a @ref MaterialAttributeType::String returns a + * null-terminated @cpp const char* @ce (not a pointer to + * @ref Containers::StringView). This doesn't preserve the actual + * string size in case the string data contain zero bytes, thus + * prefer to use typed access in that case. + * + * @see @ref hasLayer(), @ref hasAttribute() + */ + const void* attribute(Containers::StringView layer, Containers::StringView name) const; + const void* attribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + const void* attribute(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + const void* attribute(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased value of an attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + const void* attribute(UnsignedInt id) const { + return attribute(0, id); + } + + /** + * @brief Type-erased value of a named attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + const void* attribute(Containers::StringView name) const { + return attribute(0, name); + } + const void* attribute(MaterialAttribute name) const { + return attribute(0, name); + } /**< @overload */ + + /** + * @brief Value of an attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p id is expected to be smaller than + * @ref attributeCount(UnsignedInt) const in that layer. Expects that + * @p T corresponds to @ref attributeType(UnsignedInt, UnsignedInt) const + * for given @p layer and @p id. In case of a string, the returned view + * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * set. + */ + template T attribute(UnsignedInt layer, UnsignedInt id) const; + + /** + * @brief Value of a named attribute in given material layer + * + * The @p layer is expected to be smaller than @ref layerCount() const + * and @p name is expected to exist in that layer. Expects that @p T + * corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const + * for given @p layer and @p name. In case of a string, the returned + * view always has + * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @see @ref hasLayer(), @ref hasAttribute() + */ + template T attribute(UnsignedInt layer, Containers::StringView name) const; + template T attribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of an attribute in a named material layer + * + * The @p layer is expected to exist and @p id is expected to be + * smaller than @ref attributeCount(UnsignedInt) const in that layer. + * Expects that @p T corresponds to + * @ref attributeType(Containers::StringView, UnsignedInt) const + * for given @p layer and @p id. In case of a string, the returned view + * always has @ref Corrade::Containers::StringViewFlag::NullTerminated + * set. + * @see @ref hasLayer() + */ + template T attribute(Containers::StringView layer, UnsignedInt id) const; + template T attribute(MaterialLayer layer, UnsignedInt id) const; /**< @overload */ + + /** + * @brief Value of a named attribute in a named material layer + * + * The @p layer is expected to exist and @p name is expected to exist + * in that layer. Expects that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. In case of a string, the returned + * view always has + * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @see @ref hasLayer(), @ref hasAttribute() + */ + template T attribute(Containers::StringView layer, Containers::StringView name) const; + template T attribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + template T attribute(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + template T attribute(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of an attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, UnsignedInt) const + * with @p layer set to @cpp 0 @ce. + */ + template T attribute(UnsignedInt id) const { + return attribute(0, id); + } + + /** + * @brief Value of a named attribute in the base material + * + * Equivalent to calling @ref attribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + template T attribute(Containers::StringView name) const { + return attribute(0, name); + } + template T attribute(MaterialAttribute name) const { + return attribute(0, name); + } /**< @overload */ + + /** + * @brief Type-erased attribute value in given material layer, if exists + * + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @cpp nullptr @ce instead of + * asserting. Expects that @p layer is smaller than @ref layerCount() const. + * Cast the pointer to a concrete type based on @ref attributeType(). + * @see @ref hasAttribute(), @ref attributeOr() + */ + const void* tryAttribute(UnsignedInt layer, Containers::StringView name) const; + const void* tryAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased attribute value in a named material layer, if exists + * + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @cpp nullptr @ce instead of + * asserting. Expects that @p layer exists. Cast the pointer to a + * concrete type based on @ref attributeType(). + * @see @ref hasLayer(), @ref hasAttribute(), @ref attributeOr() + */ + const void* tryAttribute(Containers::StringView layer, Containers::StringView name) const; + const void* tryAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + const void* tryAttribute(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + const void* tryAttribute(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of a named attribute in given material layer, if exists + * + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt + * instead of asserting. Expects that @p layer is smaller than + * @ref layerCount() const and that @p T corresponds to + * @ref attributeType(UnsignedInt, Containers::StringView) const for + * given @p layer and @p name. + */ + template Containers::Optional tryAttribute(UnsignedInt layer, Containers::StringView name) const; + template Containers::Optional tryAttribute(UnsignedInt layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Value of a named attribute in a named material layer, if exists + * + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @ref Corrade::Containers::NullOpt + * instead of asserting. Expects that @p layer exists and that @p T + * corresponds to @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. + * @see @ref hasLayer() + */ + template Containers::Optional tryAttribute(Containers::StringView layer, Containers::StringView name) const; + template Containers::Optional tryAttribute(Containers::StringView layer, MaterialAttribute name) const; /**< @overload */ + template Containers::Optional tryAttribute(MaterialLayer layer, Containers::StringView name) const; /**< @overload */ + template Containers::Optional tryAttribute(MaterialLayer layer, MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Type-erased attribute value in the base material, if exists + * + * Equivalent to calling @ref tryAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + const void* tryAttribute(Containers::StringView name) const { + return tryAttribute(0, name); + } + const void* tryAttribute(MaterialAttribute name) const { + return tryAttribute(0, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in the base material, if exists + * + * Equivalent to calling @ref tryAttribute(UnsignedInt, Containers::StringView) const + * with @p layer set to @cpp 0 @ce. + */ + template Containers::Optional tryAttribute(Containers::StringView name) const { + return tryAttribute(0, name); + } + template Containers::Optional tryAttribute(MaterialAttribute name) const { + return tryAttribute(0, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in given layer or a default + * + * Compared to @ref attribute(UnsignedInt, Containers::StringView name) const, + * if @p name doesn't exist, returns @p defaultValue instead of + * asserting. Expects that @p layer is smaller than @ref layerCount() + * const + * and that @p T corresponds to @ref attributeType(UnsignedInt, Containers::StringView) const + * for given @p layer and @p name. + */ + template T attributeOr(UnsignedInt layer, Containers::StringView name, const T& defaultValue) const; + template T attributeOr(UnsignedInt layer, MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + + /** + * @brief Value of a named attribute in a named layer or a default + * + * Compared to @ref attribute(Containers::StringView, Containers::StringView name) const, + * if @p name doesn't exist, returns @p defaultValue instead of + * asserting. Expects that @p layer exists and that @p T corresponds to + * @ref attributeType(Containers::StringView, Containers::StringView) const + * for given @p layer and @p name. + * @see @ref hasLayer() + */ + template T attributeOr(Containers::StringView layer, Containers::StringView name, const T& defaultValue) const; + template T attributeOr(Containers::StringView layer, MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + template T attributeOr(MaterialLayer layer, Containers::StringView name, const T& defaultValue) const; /**< @overload */ + template T attributeOr(MaterialLayer layer, MaterialAttribute name, const T& defaultValue) const; /**< @overload */ + + /** + * @brief Value of a named attribute in the base material or a default + * + * Equivalent to calling @ref attributeOr(UnsignedInt, Containers::StringView, const T&) const + * with @p layer set to @cpp 0 @ce. + */ + template T attributeOr(Containers::StringView name, const T& defaultValue) const { + return attributeOr(0, name, defaultValue); + } + template T attributeOr(MaterialAttribute name, const T& defaultValue) const { + return attributeOr(0, name, defaultValue); + }/**< @overload */ + + /** + * @brief Whether a material is double-sided + * + * Convenience access to the @ref MaterialAttribute::DoubleSided + * attribute. If not present, the default is @cpp false @ce. + */ + bool isDoubleSided() const; + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Material flags + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided() etc. + * + * Not all bits returned might be defined by @ref Flag, subclasses may + * define extra values. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + CORRADE_DEPRECATED("use hasAttribute() instead") Flags flags() const; + CORRADE_IGNORE_DEPRECATED_POP + #endif + + /** + * @brief Alpha mode + * + * Convenience access to @ref MaterialAttribute::AlphaBlend and + * @ref MaterialAttribute::AlphaMask attributes. If neither is present, + * the default is @ref MaterialAlphaMode::Opaque. + */ + MaterialAlphaMode alphaMode() const; + + /** + * @brief Alpha mask + * + * Convenience access to the @ref MaterialAttribute::AlphaMask + * attribute. If not present, the default is @cpp 0.5f @ce. + */ + Float alphaMask() const; + + /** + * @brief Release layer data storage + * + * Releases the ownership of the layer offset array and resets internal + * layer-related state to default. The material then behaves like if it + * has no layers. Note that the returned array has a custom no-op + * deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not. + * + * @attention Querying attributes after calling @ref releaseLayerData() + * has undefined behavior and might lead to crashes. This is done + * intentionally in order to simplify the interaction between this + * function and @ref releaseAttributeData(). + * @see @ref layerData() + */ + Containers::Array releaseLayerData(); + + /** + * @brief Release attribute data storage + * + * Releases the ownership of the attribute array and resets internal + * attribute-related state to default. The material then behaves like + * if it has no attributes. Note that the returned array has a custom + * no-op deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not. + * + * @attention Querying layers after calling @ref releaseAttributeData() + * has undefined behavior and might lead to crashes. This is done + * intentionally in order to simplify the interaction between this + * function and @ref releaseLayerData(). + * @see @ref attributeData() + */ + Containers::Array releaseAttributeData(); + + /** + * @brief Importer-specific state + * + * See @ref AbstractImporter::importerState() for more information. + */ + const void* importerState() const { return _importerState; } + + private: + /* For custom deleter checks. Not done in the constructors here because + the restriction is pointless when used outside of plugin + implementations. */ + friend AbstractImporter; + + static Containers::StringView layerString(MaterialLayer name); + static Containers::StringView attributeString(MaterialAttribute name); + /* Internal helpers that don't assert, unlike layerId() / attributeId() */ + UnsignedInt layerFor(Containers::StringView layer) const; + UnsignedInt layerOffset(UnsignedInt layer) const { + return layer && _layerOffsets ? _layerOffsets[layer - 1] : 0; + } + UnsignedInt attributeFor(UnsignedInt layer, Containers::StringView name) const; + + Containers::Array _data; + Containers::Array _layerOffsets; + MaterialTypes _types; + const void* _importerState; +}; + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_ENUMSET_OPERATORS(MaterialData::Flags) + +/** +@debugoperatorclassenum{MaterialData,MaterialData::Flag} +@m_deprecated_since_latest The flags are no longer stored directly but + generated on-the-fly from attribute data, which makes them less efficient + than calling @ref MaterialData::hasAttribute(), + @ref MaterialData::isDoubleSided() etc. +*/ +/* Not marked with CORRADE_DEPRECATED() as there's enough warnings already */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialData::Flag value); + +/** +@debugoperatorclassenum{MaterialData,MaterialData::Flags} +@m_deprecated_since_latest The flags are no longer stored directly but + generated on-the-fly from attribute data, which makes them less efficient + than calling @ref MaterialData::hasAttribute(), + @ref MaterialData::isDoubleSided() etc. +*/ +/* Not marked with CORRADE_DEPRECATED() as there's enough warnings already */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialData::Flags value); +CORRADE_IGNORE_DEPRECATED_POP +#endif + +namespace Implementation { + /* LCOV_EXCL_START */ + template struct MaterialAttributeTypeFor { + /* C++ why there isn't an obvious way to do such a thing?! */ + static_assert(sizeof(T) == 0, "unsupported attribute type"); + }; + template<> struct MaterialAttributeTypeFor { + constexpr static MaterialAttributeType type() { + return MaterialAttributeType::Bool; + } + }; + template struct MaterialAttributeTypeFor { + constexpr static MaterialAttributeType type() { + return MaterialAttributeType::Pointer; + } + }; + template struct MaterialAttributeTypeFor { + constexpr static MaterialAttributeType type() { + return MaterialAttributeType::MutablePointer; + } + }; + /* No specialization for StringView as this type trait should not be used + in that case */ + template<> struct MaterialAttributeTypeFor { + constexpr static MaterialAttributeType type() { + return MaterialAttributeType::TextureSwizzle; + } + }; + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(type_) template<> struct MaterialAttributeTypeFor { \ + constexpr static MaterialAttributeType type() { \ + return MaterialAttributeType::type_; \ + } \ + }; + _c(Float) + _c(Deg) + _c(Rad) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x3) + _c(Matrix2x4) + _c(Matrix3x2) + _c(Matrix3x3) + _c(Matrix3x4) + _c(Matrix4x2) + _c(Matrix4x3) + #undef _c + #endif + template<> struct MaterialAttributeTypeFor: MaterialAttributeTypeFor {}; + template<> struct MaterialAttributeTypeFor: MaterialAttributeTypeFor {}; + template<> struct MaterialAttributeTypeFor: MaterialAttributeTypeFor {}; + /* LCOV_EXCL_STOP */ +} + +/* The 2 extra bytes are for a null byte after the name and a type */ +template constexpr MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const T& value) noexcept: + _data{Implementation::MaterialAttributeTypeFor::type(), + /* MSVC 2015 complains about "error C2065: 'T': undeclared identifier" + in the lambda inside this macro. Sorry, the assert will be less + useful on that stupid thing. */ + #ifndef CORRADE_MSVC2015_COMPATIBILITY + (CORRADE_CONSTEXPR_ASSERT(name.size() + sizeof(T) + 2 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "too long, expected at most" << Implementation::MaterialAttributeDataSize - sizeof(T) - 2 << "bytes for" << Implementation::MaterialAttributeTypeFor::type() << "but got" << name.size()), name) + #else + (CORRADE_CONSTEXPR_ASSERT(name.size() + sizeof(T) + 2 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "too long, got" << name.size() << "bytes"), name) + #endif + , value} {} + +/* The 4 extra bytes are for a null byte after both the name and value, a type + and a string size */ +constexpr MaterialAttributeData::MaterialAttributeData(const Containers::StringView name, const Containers::StringView value) noexcept: _data{(CORRADE_CONSTEXPR_ASSERT(name.size() + value.size() + 4 <= Implementation::MaterialAttributeDataSize, "Trade::MaterialAttributeData: name" << name << "and value" << value << "too long, expected at most" << Implementation::MaterialAttributeDataSize - 4 << "bytes in total but got" << name.size() + value.size()), name), value} {} + +template T MaterialAttributeData::value() const { + CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor::type() == _data.type, + "Trade::MaterialAttributeData::value(): improper type requested for" << (_data.data + 1) << "of" << _data.type, {}); + return *reinterpret_cast(value()); +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +template<> Containers::StringView MaterialAttributeData::value() const; +#endif + +template T MaterialData::attribute(const UnsignedInt layer, const UnsignedInt id) const { + const void* const value = attribute(layer, id); + #ifdef CORRADE_GRACEFUL_ASSERT + if(!value) return {}; + #endif + const Trade::MaterialAttributeData& data = _data[layerOffset(layer) + id]; + CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor::type() == data._data.type, + "Trade::MaterialData::attribute(): improper type requested for" << (data._data.data + 1) << "of" << data._data.type, {}); + return *reinterpret_cast(value); +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +template<> Containers::StringView MaterialData::attribute(UnsignedInt, UnsignedInt) const; +#endif + +template T MaterialData::attribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return attribute(layer, id); +} + +template T MaterialData::attribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); +} + +template T MaterialData::attribute(const Containers::StringView layer, const UnsignedInt id) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + CORRADE_ASSERT(id < attributeCount(layer), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << attributeCount(layer) << "attributes in layer" << layer, {}); + return attribute(layerId, id); +} + +template T MaterialData::attribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): layer" << layer << "not found", {}); + const UnsignedInt id = attributeFor(layerId, name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found in layer" << layer, {}); + return attribute(layerId, id); +} + +template T MaterialData::attribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(layer, string); +} + +template T MaterialData::attribute(const MaterialLayer layer, const UnsignedInt id) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, id); +} + +template T MaterialData::attribute(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, name); +} + +template T MaterialData::attribute(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << layer, {}); + return attribute(string, name); +} + +template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const Containers::StringView name) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::tryAttribute(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + if(id == ~UnsignedInt{}) return {}; + return attribute(layer, id); +} + +template Containers::Optional MaterialData::tryAttribute(const UnsignedInt layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); +} + +template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const Containers::StringView name) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::tryAttribute(): layer" << layer << "not found", {}); + return tryAttribute(layerId, name); +} + +template Containers::Optional MaterialData::tryAttribute(const Containers::StringView layer, const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << name, {}); + return tryAttribute(layer, string); +} + +template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const Containers::StringView name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); + return tryAttribute(string, name); +} + +template Containers::Optional MaterialData::tryAttribute(const MaterialLayer layer, const MaterialAttribute name) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::tryAttribute(): invalid name" << layer, {}); + return tryAttribute(string, name); +} + +template T MaterialData::attributeOr(const UnsignedInt layer, const Containers::StringView name, const T& defaultValue) const { + CORRADE_ASSERT(layer < layerCount(), + "Trade::MaterialData::attributeOr(): index" << layer << "out of range for" << layerCount() << "layers", {}); + const UnsignedInt id = attributeFor(layer, name); + if(id == ~UnsignedInt{}) return defaultValue; + return attribute(layer, id); +} + +template T MaterialData::attributeOr(const UnsignedInt layer, const MaterialAttribute name, const T& defaultValue) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); + return attributeOr(layer, string, defaultValue); +} + +template T MaterialData::attributeOr(const Containers::StringView layer, const Containers::StringView name, const T& defaultValue) const { + const UnsignedInt layerId = layerFor(layer); + CORRADE_ASSERT(layerId != ~UnsignedInt{}, + "Trade::MaterialData::attributeOr(): layer" << layer << "not found", {}); + return attributeOr(layerId, name, defaultValue); +} + +template T MaterialData::attributeOr(const Containers::StringView layer, const MaterialAttribute name, const T& defaultValue) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << name, {}); + return attributeOr(layer, string, defaultValue); +} + +template T MaterialData::attributeOr(const MaterialLayer layer, const Containers::StringView name, const T& defaultValue) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); + return attributeOr(string, name, defaultValue); +} + +template T MaterialData::attributeOr(const MaterialLayer layer, const MaterialAttribute name, const T& defaultValue) const { + const Containers::StringView string = layerString(layer); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeOr(): invalid name" << layer, {}); + return attributeOr(string, name, defaultValue); +} + +}} + +#endif diff --git a/src/Magnum/Trade/MaterialLayerData.h b/src/Magnum/Trade/MaterialLayerData.h new file mode 100644 index 0000000000..d1e6db023e --- /dev/null +++ b/src/Magnum/Trade/MaterialLayerData.h @@ -0,0 +1,467 @@ +#ifndef Magnum_Trade_MaterialLayerData_h +#define Magnum_Trade_MaterialLayerData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::MaterialLayerData + * @m_since_latest + */ + +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace Trade { + +/** +@brief Material layer data +@m_since_latest + +Convenience wrapper that re-routes all @ref MaterialData base material layer +and attribute accessors APIS from to a layer specified in the @p layer template +parameter. All APIs expect that given layer exists. See +@ref Trade-MaterialData-usage-layers for more information. +*/ +template class MaterialLayerData: public MaterialData { + public: + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing) */ + using MaterialData::MaterialData; + + /** + * @brief Layer name + * + * Same as calling @ref MaterialData::layerName() with @ref layerId() + * for @p layer. + */ + Containers::StringView layerName() const { + return MaterialData::layerName(layerId(layer)); + } + + /** + * @brief Layer factor + * + * Same as calling @ref MaterialData::layerFactor() with @p layer. + */ + Float layerFactor() const { + return MaterialData::layerFactor(layer); + } + + /** + * @brief Layer factor texture ID + * + * Same as calling @ref MaterialData::layerFactorTexture() with + * @p layer. + */ + UnsignedInt layerFactorTexture() const { + return MaterialData::layerFactorTexture(layer); + } + + /** + * @brief Layer factor texture swizzle + * + * Same as calling @ref MaterialData::layerFactorTextureSwizzle() with + * @p layer. + */ + MaterialTextureSwizzle layerFactorTextureSwizzle() const { + return MaterialData::layerFactorTextureSwizzle(layer); + } + + /** + * @brief Layer factor texture coordinate transformation matrix + * + * Same as calling @ref MaterialData::layerFactorTextureMatrix() with + * @p layer. + */ + Matrix3 layerFactorTextureMatrix() const { + return MaterialData::layerFactorTextureMatrix(layer); + } + + /** + * @brief Layer factor texture coordinate set + * + * Same as calling @ref MaterialData::layerFactorTextureCoordinates() + * with @p layer. + */ + UnsignedInt layerFactorTextureCoordinates() const { + return MaterialData::layerFactorTextureCoordinates(layer); + } + + /** + * @brief Attribute count in this layer + * + * Same as calling @ref MaterialData::attributeCount() with @p layer. + */ + UnsignedInt attributeCount() const { + return MaterialData::attributeCount(layer); + } + + /** + * @brief Whether this layer has given attribute + * + * Same as calling @ref MaterialData::hasAttribute() with @p layer. + */ + bool hasAttribute(Containers::StringView name) const { + return MaterialData::hasAttribute(layer, name); + } + bool hasAttribute(MaterialAttribute name) const { + return MaterialData::hasAttribute(layer, name); + } /**< @overload */ + + /** + * @brief ID of a named attribute in this layer + * + * Same as calling @ref MaterialData::attributeId() with @p layer. + */ + UnsignedInt attributeId(Containers::StringView name) const { + return MaterialData::attributeId(layer, name); + } + UnsignedInt attributeId(MaterialAttribute name) const { + return MaterialData::attributeId(layer, name); + } /**< @overload */ + + /** + * @brief Name of an attribute in this layer + * + * Same as calling @ref MaterialData::attributeName() with @p layer. + */ + Containers::StringView attributeName(UnsignedInt id) const { + return MaterialData::attributeName(layer, id); + } + + /** + * @brief Type of an attribute in this layer + * + * Same as calling @ref MaterialData::attributeType() with @p layer. + */ + MaterialAttributeType attributeType(UnsignedInt id) const { + return MaterialData::attributeType(layer, id); + } + MaterialAttributeType attributeType(Containers::StringView name) const { + return MaterialData::attributeType(layer, name); + } /**< @overload */ + MaterialAttributeType attributeType(MaterialAttribute name) const { + return MaterialData::attributeType(layer, name); + } /**< @overload */ + + /** + * @brief Type-erased value of an attribute in this layer + * + * Same as calling @ref MaterialData::attribute() with @p layer. + */ + const void* attribute(UnsignedInt id) const { + return MaterialData::attribute(layer, id); + } + const void* attribute(Containers::StringView name) const { + return MaterialData::attribute(layer, name); + } /**< @overload */ + const void* attribute(MaterialAttribute name) const { + return MaterialData::attribute(layer, name); + } /**< @overload */ + + /** + * @brief Value of an attribute in this layer + * + * Same as calling @ref MaterialData::attribute() with @p layer. + */ + template T attribute(UnsignedInt id) const { + return MaterialData::attribute(layer, id); + } + template T attribute(Containers::StringView name) const { + return MaterialData::attribute(layer, name); + } /**< @overload */ + template T attribute(MaterialAttribute name) const { + return MaterialData::attribute(layer, name); + } /**< @overload */ + + /** + * @brief Type-erased attribute value in this layer, if exists + * + * Same as calling @ref MaterialData::tryAttribute() with @p layer. + */ + const void* tryAttribute(Containers::StringView name) const { + return MaterialData::tryAttribute(layer, name); + } + const void* tryAttribute(MaterialAttribute name) const { + return MaterialData::tryAttribute(layer, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in this layer, if exists + * + * Same as calling @ref MaterialData::tryAttribute() with @p layer. + */ + template Containers::Optional tryAttribute(Containers::StringView name) const { + return MaterialData::tryAttribute(layer, name); + } + template Containers::Optional tryAttribute(MaterialAttribute name) const { + return MaterialData::tryAttribute(layer, name); + } /**< @overload */ + + /** + * @brief Value of a named attribute in this layer or a default + * + * Same as calling @ref MaterialData::attributeOr() with @p layer. + */ + template T attributeOr(Containers::StringView name, const T& defaultValue) const { + return MaterialData::attributeOr(layer, name, defaultValue); + } + template T attributeOr(MaterialAttribute name, const T& defaultValue) const { + return MaterialData::attributeOr(layer, name, defaultValue); + } /**< @overload */ + + /* Bring in all other overloads as well and override just the ones + with implicit layers */ + using MaterialData::layerName; + using MaterialData::layerFactor; + using MaterialData::layerFactorTexture; + using MaterialData::layerFactorTextureSwizzle; + using MaterialData::layerFactorTextureMatrix; + using MaterialData::layerFactorTextureCoordinates; + /* MSVC is so damn unbelievably stupid it's setting my ass on fire. + https://en.cppreference.com/w/cpp/language/using_declaration says + that "If the derived class already has a member with the same name, + parameter list, and qualifications, the derived class member hides + or overrides (doesn't conflict with) the member that is introduced + from the base class." and EVERY COMPILER ON EARTH EXCEPT THIS DAMN + POOL OF GOO implements that correctly. So instead of a bunch of sane + using declarations I have to enumerate EACH AND EVERY overload and + explicitly pass them through LIKE A FUCKING SAVAGE. FFS. */ + #if !defined(CORRADE_TARGET_MSVC) || defined(CORRADE_TARGET_CLANG_CL) + using MaterialData::attributeCount; + using MaterialData::hasAttribute; + using MaterialData::attributeId; + using MaterialData::attributeName; + using MaterialData::attributeType; + using MaterialData::attribute; + using MaterialData::tryAttribute; + using MaterialData::attributeOr; + #else + UnsignedInt attributeCount(UnsignedInt layer_) const { + return MaterialData::attributeCount(layer_); + } + UnsignedInt attributeCount(Containers::StringView layer_) const { + return MaterialData::attributeCount(layer_); + } + UnsignedInt attributeCount(MaterialLayer layer_) const { + return MaterialData::attributeCount(layer_); + } + + bool hasAttribute(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::hasAttribute(layer_, name); + } + bool hasAttribute(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::hasAttribute(layer_, name); + } + bool hasAttribute(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::hasAttribute(layer_, name); + } + bool hasAttribute(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::hasAttribute(layer_, name); + } + bool hasAttribute(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::hasAttribute(layer_, name); + } + bool hasAttribute(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::hasAttribute(layer_, name); + } + + UnsignedInt attributeId(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::attributeId(layer_, name); + } + UnsignedInt attributeId(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::attributeId(layer_, name); + } + UnsignedInt attributeId(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::attributeId(layer_, name); + } + UnsignedInt attributeId(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::attributeId(layer_, name); + } + UnsignedInt attributeId(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::attributeId(layer_, name); + } + UnsignedInt attributeId(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::attributeId(layer_, name); + } + + Containers::StringView attributeName(UnsignedInt layer_, UnsignedInt id) const { + return MaterialData::attributeName(layer_, id); + } + Containers::StringView attributeName(Containers::StringView layer_, UnsignedInt id) const { + return MaterialData::attributeName(layer_, id); + } + Containers::StringView attributeName(MaterialLayer layer_, UnsignedInt id) const { + return MaterialData::attributeName(layer_, id); + } + + MaterialAttributeType attributeType(UnsignedInt layer_, UnsignedInt id) const { + return MaterialData::attributeType(layer_, id); + } + MaterialAttributeType attributeType(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::attributeType(layer_, name); + } + MaterialAttributeType attributeType(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::attributeType(layer_, name); + } + MaterialAttributeType attributeType(Containers::StringView layer_, UnsignedInt id) const { + return MaterialData::attributeType(layer_, id); + } + MaterialAttributeType attributeType(MaterialLayer layer_, UnsignedInt id) const { + return MaterialData::attributeType(layer_, id); + } + MaterialAttributeType attributeType(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::attributeType(layer_, name); + } + MaterialAttributeType attributeType(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::attributeType(layer_, name); + } + MaterialAttributeType attributeType(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::attributeType(layer_, name); + } + MaterialAttributeType attributeType(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::attributeType(layer_, name); + } + + const void* attribute(UnsignedInt layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + const void* attribute(Containers::StringView layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + const void* attribute(MaterialLayer layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + const void* attribute(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + const void* attribute(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + const void* attribute(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + const void* attribute(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + const void* attribute(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + const void* attribute(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + + template T attribute(UnsignedInt layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + template T attribute(Containers::StringView layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + template T attribute(MaterialLayer layer_, UnsignedInt id) const { + return MaterialData::attribute(layer_, id); + } + template T attribute(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + template T attribute(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + template T attribute(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + template T attribute(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + template T attribute(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::attribute(layer_, name); + } + template T attribute(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::attribute(layer_, name); + } + + const void* tryAttribute(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + const void* tryAttribute(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + const void* tryAttribute(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + const void* tryAttribute(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + const void* tryAttribute(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + const void* tryAttribute(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + + template Containers::Optional tryAttribute(UnsignedInt layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + template Containers::Optional tryAttribute(UnsignedInt layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + + template Containers::Optional tryAttribute(Containers::StringView layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + template Containers::Optional tryAttribute(Containers::StringView layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + template Containers::Optional tryAttribute(MaterialLayer layer_, Containers::StringView name) const { + return MaterialData::tryAttribute(layer_, name); + } + template Containers::Optional tryAttribute(MaterialLayer layer_, MaterialAttribute name) const { + return MaterialData::tryAttribute(layer_, name); + } + + template T attributeOr(UnsignedInt layer_, Containers::StringView name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + template T attributeOr(UnsignedInt layer_, MaterialAttribute name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + template T attributeOr(Containers::StringView layer_, Containers::StringView name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + template T attributeOr(Containers::StringView layer_, MaterialAttribute name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + template T attributeOr(MaterialLayer layer_, Containers::StringView name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + template T attributeOr(MaterialLayer layer_, MaterialAttribute name, const T& defaultValue) const { + return MaterialData::attributeOr(layer_, name, defaultValue); + } + #endif +}; + +}} + +#endif diff --git a/src/Magnum/Trade/PbrClearCoatMaterialData.cpp b/src/Magnum/Trade/PbrClearCoatMaterialData.cpp new file mode 100644 index 0000000000..73bd1bd881 --- /dev/null +++ b/src/Magnum/Trade/PbrClearCoatMaterialData.cpp @@ -0,0 +1,196 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "PbrClearCoatMaterialData.h" + +namespace Magnum { namespace Trade { + +bool PbrClearCoatMaterialData::hasLayerFactorRoughnessTexture() const { + return hasAttribute(MaterialAttribute::LayerFactorTexture) && + hasAttribute(MaterialAttribute::RoughnessTexture) && + attribute(MaterialAttribute::LayerFactorTexture) == attribute(MaterialAttribute::RoughnessTexture) && + layerFactorTextureSwizzle() == MaterialTextureSwizzle::R && + roughnessTextureSwizzle() == MaterialTextureSwizzle::G && + layerFactorTextureMatrix() == roughnessTextureMatrix() && + layerFactorTextureCoordinates() == roughnessTextureCoordinates(); +} + +bool PbrClearCoatMaterialData::hasTextureTransformation() const { + return hasAttribute(MaterialAttribute::LayerFactorTextureMatrix) || + hasAttribute(MaterialAttribute::RoughnessTextureMatrix) || + hasAttribute(MaterialAttribute::NormalTextureMatrix) || + hasAttribute(MaterialAttribute::TextureMatrix) || + hasAttribute(0, MaterialAttribute::TextureMatrix); +} + +bool PbrClearCoatMaterialData::hasCommonTextureTransformation() const { + auto check = [](Containers::Optional& transformation, Matrix3 current) { + if(!transformation) { + transformation = current; + return true; + } + return transformation == current; + }; + + Containers::Optional transformation; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::LayerFactorTexture) && !check(transformation, layerFactorTextureMatrix())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasAttribute(MaterialAttribute::RoughnessTexture) && !check(transformation, roughnessTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(transformation, normalTextureMatrix())) + return false; + + return true; +} + +bool PbrClearCoatMaterialData::hasTextureCoordinates() const { + return hasAttribute(MaterialAttribute::LayerFactorTextureCoordinates) || + hasAttribute(MaterialAttribute::RoughnessTextureCoordinates) || + hasAttribute(MaterialAttribute::NormalTextureCoordinates) || + hasAttribute(MaterialAttribute::TextureCoordinates) || + hasAttribute(0, MaterialAttribute::TextureCoordinates); +} + +bool PbrClearCoatMaterialData::hasCommonTextureCoordinates() const { + auto check = [](Containers::Optional& coordinates, UnsignedInt current) { + if(!coordinates) { + coordinates = current; + return true; + } + return coordinates == current; + }; + + Containers::Optional coordinates; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::LayerFactorTexture) && !check(coordinates, layerFactorTextureCoordinates())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasAttribute(MaterialAttribute::RoughnessTexture) && !check(coordinates, roughnessTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(coordinates, normalTextureCoordinates())) + return false; + + return true; +} + +Float PbrClearCoatMaterialData::roughness() const { + return attributeOr(MaterialAttribute::Roughness, 0.0f); +} + +UnsignedInt PbrClearCoatMaterialData::roughnessTexture() const { + return attribute(MaterialAttribute::RoughnessTexture); +} + +MaterialTextureSwizzle PbrClearCoatMaterialData::roughnessTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::RoughnessTexture), + "Trade::PbrClearCoatMaterialData::roughnessTextureSwizzle(): the layer doesn't have a roughness texture", {}); + return attributeOr(MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrClearCoatMaterialData::roughnessTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::RoughnessTexture), + "Trade::PbrClearCoatMaterialData::roughnessTextureMatrix(): the layer doesn't have a roughness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::RoughnessTextureMatrix)) + return *value; + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureMatrix)) + return *value; + return attributeOr(0, MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrClearCoatMaterialData::roughnessTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::RoughnessTexture), + "Trade::PbrClearCoatMaterialData::roughnessTextureCoordinates(): the layer doesn't have a roughness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::RoughnessTextureCoordinates)) + return *value; + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureCoordinates)) + return *value; + return attributeOr(0, MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt PbrClearCoatMaterialData::normalTexture() const { + return attribute(MaterialAttribute::NormalTexture); +} + +Float PbrClearCoatMaterialData::normalTextureScale() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrClearCoatMaterialData::normalTextureScale(): the layer doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureScale, 1.0f); +} + +MaterialTextureSwizzle PbrClearCoatMaterialData::normalTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrClearCoatMaterialData::normalTextureSwizzle(): the layer doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RGB); +} + +Matrix3 PbrClearCoatMaterialData::normalTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrClearCoatMaterialData::normalTextureMatrix(): the layer doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureMatrix)) + return *value; + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureMatrix)) + return *value; + return attributeOr(0, MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrClearCoatMaterialData::normalTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrClearCoatMaterialData::normalTextureCoordinates(): the layer doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureCoordinates)) + return *value; + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureCoordinates)) + return *value; + return attributeOr(0, MaterialAttribute::TextureCoordinates, 0u); +} + +Matrix3 PbrClearCoatMaterialData::commonTextureMatrix() const { + CORRADE_ASSERT(hasCommonTextureTransformation(), + "Trade::PbrClearCoatMaterialData::commonTextureMatrix(): the layer doesn't have a common texture coordinate transformation", {}); + if(hasAttribute(MaterialAttribute::LayerFactorTexture)) + return layerFactorTextureMatrix(); + if(hasAttribute(MaterialAttribute::RoughnessTexture)) + return roughnessTextureMatrix(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureMatrix(); + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureMatrix)) + return *value; + return attributeOr(0, MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrClearCoatMaterialData::commonTextureCoordinates() const { + CORRADE_ASSERT(hasCommonTextureCoordinates(), + "Trade::PbrClearCoatMaterialData::commonTextureCoordinates(): the layer doesn't have a common texture coordinate set", {}); + if(hasAttribute(MaterialAttribute::LayerFactorTexture)) + return layerFactorTextureCoordinates(); + if(hasAttribute(MaterialAttribute::RoughnessTexture)) + return roughnessTextureCoordinates(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureCoordinates(); + if(Containers::Optional value = tryAttribute(MaterialAttribute::TextureCoordinates)) + return *value; + return attributeOr(0, MaterialAttribute::TextureCoordinates, 0u); +} + +}} diff --git a/src/Magnum/Trade/PbrClearCoatMaterialData.h b/src/Magnum/Trade/PbrClearCoatMaterialData.h new file mode 100644 index 0000000000..9622599ce2 --- /dev/null +++ b/src/Magnum/Trade/PbrClearCoatMaterialData.h @@ -0,0 +1,283 @@ +#ifndef Magnum_Trade_PbrClearCoatMaterialData_h +#define Magnum_Trade_PbrClearCoatMaterialData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::PbrClearCoatMaterialData + * @m_since_latest + */ + +#include "Magnum/Trade/MaterialLayerData.h" + +namespace Magnum { namespace Trade { + +/** +@brief Clear coat material layer data +@m_since_latest + +Exposes properties of a @ref MaterialLayer::ClearCoat layer. All APIs expect +that the layer is present in the material. See @ref Trade-MaterialData-usage-types +for more information about how to use this class. +@see @ref AbstractImporter::material(), @ref FlatMaterialData, + @ref PhongMaterialData, @ref PbrMetallicRoughnessMaterialData, + @ref PbrSpecularGlossinessMaterialData, @ref MaterialType::PbrClearCoat +*/ +class MAGNUM_TRADE_EXPORT PbrClearCoatMaterialData: public MaterialLayerData { + public: + #ifndef DOXYGEN_GENERATING_OUTPUT + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing). It's + however hidden from the docs as constructing instances this way + isn't really common and it would add a lot of noise. */ + using MaterialLayerData::MaterialLayerData; + #endif + + /** + * @brief Whether the material has a combined layer factor / roughness texture + * + * Returns @cpp true @ce if both + * @ref MaterialAttribute::LayerFactorTexture and + * @ref MaterialAttribute::RoughnessTexture attributes are present, + * point to the same texture ID, + * @ref MaterialAttribute::LayerFactorTextureSwizzle is either not + * present or set to @ref MaterialTextureSwizzle::R and + * @ref MaterialAttribute::RoughnessTextureSwizzle is set to + * @ref MaterialTextureSwizzle::G, and additionally + * @ref MaterialAttribute::LayerFactorTextureMatrix and + * @ref MaterialAttribute::RoughnessTextureMatrix are both either not + * present or have the same value, and + * @ref MaterialAttribute::LayerFactorTextureCoordinates and + * @ref MaterialAttribute::RoughnessTextureCoordinates are both either + * not present or have the smae value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref layerFactorTexture(), @ref layerFactorTextureMatrix() and + * @ref layerFactorTextureCoordinates() return values common for both + * layer factor and roughness texture, and the two are packed together + * with layer factor occupying the R channel and roughness the G + * channel. This check is present in order to provide support for the + * [KHR_materials_clearcoat](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md) + * glTF extension. + * @see @ref hasAttribute() + */ + bool hasLayerFactorRoughnessTexture() const; + + /** + * @brief Whether the material has texture transformation + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::LayerFactorTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureMatrix, + * @ref MaterialAttribute::NormalTextureMatrix or + * @ref MaterialAttribute::TextureMatrix attributes are present in this + * layer or if @ref MaterialAttribute::TextureMatrix is present in the + * base material, @cpp false @ce otherwise + */ + bool hasTextureTransformation() const; + + /** + * @brief Whether the material has a common transformation for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref layerFactorTextureMatrix(), @ref roughnessTextureMatrix() and + * @ref normalTextureMatrix() have the same value, @cpp false @ce + * otherwise. In particular, returns @cpp true @ce also if there's no + * texture transformation at all. Use @ref hasTextureTransformation() + * to distinguish that case. + */ + bool hasCommonTextureTransformation() const; + + /** + * @brief Whether the material uses extra texture coordinate sets + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::LayerFactorTextureCoordinates, + * @ref MaterialAttribute::RoughnessTextureCoordinates, + * @ref MaterialAttribute::NormalTextureCoordinates or + * @ref MaterialAttribute::TextureCoordinates attributes are present in + * this material or if @ref MaterialAttribute::TextureCoordinates is + * present in the base material, @cpp false @ce otherwise. + */ + bool hasTextureCoordinates() const; + + /** + * @brief Whether the material has a common coordinate set for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref layerFactorTextureCoordinates(), + * @ref roughnessTextureCoordinates() and + * @ref normalTextureCoordinates() have the same value, @cpp false @ce + * otherwise. In particular, returns @cpp true @ce also if there's no + * extra texture coordinate set used at all. Use + * @ref hasTextureCoordinates() to distinguish that case. + */ + bool hasCommonTextureCoordinates() const; + + /** + * @brief Roughness factor + * + * Convenience access to the @ref MaterialAttribute::Roughness + * attribute in this layer. If not present, the default is @cpp 1.0f @ce. + * + * If the layer has a @ref MaterialAttribute::RoughnessTexture, the + * factor and texture is meant to be multiplied together. + */ + Float roughness() const; + + /** + * @brief Roughness texture ID + * + * Available only if @ref MaterialAttribute::RoughnessTexture is + * present in this layer. Meant to be multiplied with @ref roughness(). + * @see @ref AbstractImporter::texture() + */ + UnsignedInt roughnessTexture() const; + + /** + * @brief Roughness texture swizzle + * + * Convenience access to the @ref MaterialAttribute::RoughnessTextureSwizzle + * attribute in this layer. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::RoughnessTexture is + * present in this layer. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle roughnessTextureSwizzle() const; + + /** + * @brief Roughness texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::RoughnessTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes in this layer or + * a @ref MaterialAttribute::TextureMatrix attribute in the base + * material. If neither is present, the default is an identity matrix. + * Available only if @ref MaterialAttribute::RoughnessTexture is + * present in this layer. + * @see @ref hasAttribute() + */ + Matrix3 roughnessTextureMatrix() const; + + /** + * @brief Roughness texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::RoughnessTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes in this + * layer or a @ref MaterialAttribute::TextureCoordinates attribute in + * the base material. If neither is present, the default is @cpp 0 @ce. + * Available only if @ref MaterialAttribute::RoughnessTexture is + * present in this layer. + * @see @ref hasAttribute() + */ + UnsignedInt roughnessTextureCoordinates() const; + + /** + * @brief Normal texture ID + * + * Available only if @ref MaterialAttribute::NormalTexture is present + * in this layer. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt normalTexture() const; + + /** + * @brief Normal texture scale + * + * Convenience access to the @ref MaterialAttribute::NormalTextureScale + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + Float normalTextureScale() const; + + /** + * @brief Normal texture swizzle + * + * Convenience access to the + * @ref MaterialAttribute::NormalTextureSwizzle attribute in this + * layer. If not present, the default is + * @ref MaterialTextureSwizzle::RGB. Available only if + * @ref MaterialAttribute::NormalTexture is present in this layer. + * + * The texture can be also just two-component, in which case the + * remaining component is implicit and calculated as + * @f$ z = \sqrt{1 - x^2 - y^2} @f$. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle normalTextureSwizzle() const; + + /** + * @brief Normal texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::NormalTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes in this layer or + * a @ref MaterialAttribute::TextureMatrix attribute in the base + * material. If neither is present, the default is an identity matrix. + * Available only if @ref MaterialAttribute::NormalTexture is present + * in this layer. + * @see @ref hasAttribute() + */ + Matrix3 normalTextureMatrix() const; + + /** + * @brief Normal texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::NormalTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes in this + * layer or a @ref MaterialAttribute::TextureCoordinates attribute in + * the base material. If neither is present, the default is @cpp 0 @ce. + * Available only if @ref MaterialAttribute::NormalTexture is present + * in this layer. + * @see @ref hasAttribute() + */ + UnsignedInt normalTextureCoordinates() const; + + /** + * @brief Common texture coordinate transformation matrix for all textures + * + * Expects that @ref hasCommonTextureTransformation() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref layerFactorTextureMatrix(), @ref roughnessTextureMatrix() and + * @ref normalTextureMatrix() where a texture is present. If no texture + * is present, returns an identity matrix. + */ + Matrix3 commonTextureMatrix() const; + + /** + * @brief Common texture coordinate set index for all textures + * + * Expects that @ref hasCommonTextureCoordinates() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref layerFactorTextureCoordinates(), @ref roughnessTextureCoordinates() + * and @ref normalTextureCoordinates() where a texture is present. If + * no texture is present, returns @cpp 0 @ce. + */ + UnsignedInt commonTextureCoordinates() const; +}; + +}} + +#endif diff --git a/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.cpp b/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.cpp new file mode 100644 index 0000000000..08898f3b30 --- /dev/null +++ b/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.cpp @@ -0,0 +1,420 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "PbrMetallicRoughnessMaterialData.h" + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" + +namespace Magnum { namespace Trade { + +using namespace Math::Literals; + +bool PbrMetallicRoughnessMaterialData::hasMetalnessTexture() const { + return hasAttribute(MaterialAttribute::MetalnessTexture) || + hasAttribute(MaterialAttribute::NoneRoughnessMetallicTexture); +} + +bool PbrMetallicRoughnessMaterialData::hasRoughnessTexture() const { + return hasAttribute(MaterialAttribute::RoughnessTexture) || + hasAttribute(MaterialAttribute::NoneRoughnessMetallicTexture); +} + +bool PbrMetallicRoughnessMaterialData::hasNoneRoughnessMetallicTexture() const { + return (hasAttribute(MaterialAttribute::NoneRoughnessMetallicTexture) || + (hasAttribute(MaterialAttribute::MetalnessTexture) && + hasAttribute(MaterialAttribute::RoughnessTexture) && + attribute(MaterialAttribute::MetalnessTexture) == attribute(MaterialAttribute::RoughnessTexture) && + roughnessTextureSwizzle() == MaterialTextureSwizzle::G && + metalnessTextureSwizzle() == MaterialTextureSwizzle::B)) && + roughnessTextureMatrix() == metalnessTextureMatrix() && + roughnessTextureCoordinates() == metalnessTextureCoordinates(); +} + +bool PbrMetallicRoughnessMaterialData::hasRoughnessMetallicOcclusionTexture() const { + if(!hasAttribute(MaterialAttribute::RoughnessTexture) || + !hasAttribute(MaterialAttribute::MetalnessTexture) || + !hasAttribute(MaterialAttribute::OcclusionTexture)) + return false; + + const UnsignedInt roughnessTexture = attribute(MaterialAttribute::RoughnessTexture); + if(attribute(MaterialAttribute::MetalnessTexture) != roughnessTexture || + attribute(MaterialAttribute::OcclusionTexture) != roughnessTexture || + roughnessTextureSwizzle() != MaterialTextureSwizzle::R || + metalnessTextureSwizzle() != MaterialTextureSwizzle::G || + occlusionTextureSwizzle() != MaterialTextureSwizzle::B) + return false; + + const Matrix3 roughnessTextureMatrix = this->roughnessTextureMatrix(); + const UnsignedInt roughnessTextureCoordinates = this->roughnessTextureCoordinates(); + return metalnessTextureMatrix() == roughnessTextureMatrix && + occlusionTextureMatrix() == roughnessTextureMatrix && + metalnessTextureCoordinates() == roughnessTextureCoordinates && + occlusionTextureCoordinates() == roughnessTextureCoordinates; +} + +bool PbrMetallicRoughnessMaterialData::hasOcclusionRoughnessMetallicTexture() const { + if(!hasAttribute(MaterialAttribute::OcclusionTexture) || + !hasAttribute(MaterialAttribute::RoughnessTexture) || + !hasAttribute(MaterialAttribute::MetalnessTexture)) + return false; + + const UnsignedInt occlusionTexture = attribute(MaterialAttribute::OcclusionTexture); + if(attribute(MaterialAttribute::RoughnessTexture) != occlusionTexture || + attribute(MaterialAttribute::MetalnessTexture) != occlusionTexture || + occlusionTextureSwizzle() != MaterialTextureSwizzle::R || + roughnessTextureSwizzle() != MaterialTextureSwizzle::G || + metalnessTextureSwizzle() != MaterialTextureSwizzle::B) + return false; + + const Matrix3 occlusionTextureMatrix = this->occlusionTextureMatrix(); + const UnsignedInt occlusionTextureCoordinates = this->occlusionTextureCoordinates(); + return roughnessTextureMatrix() == occlusionTextureMatrix && + metalnessTextureMatrix() == occlusionTextureMatrix && + roughnessTextureCoordinates() == occlusionTextureCoordinates && + metalnessTextureCoordinates() == occlusionTextureCoordinates; +} + +bool PbrMetallicRoughnessMaterialData::hasNormalRoughnessMetallicTexture() const { + if(!hasAttribute(MaterialAttribute::NormalTexture) || + !hasAttribute(MaterialAttribute::RoughnessTexture) || + !hasAttribute(MaterialAttribute::MetalnessTexture)) + return false; + + const UnsignedInt normalTexture = attribute(MaterialAttribute::NormalTexture); + if(attribute(MaterialAttribute::RoughnessTexture) != normalTexture || + attribute(MaterialAttribute::MetalnessTexture) != normalTexture || + normalTextureSwizzle() != MaterialTextureSwizzle::RG || + roughnessTextureSwizzle() != MaterialTextureSwizzle::B || + metalnessTextureSwizzle() != MaterialTextureSwizzle::A) + return false; + + const Matrix3 normalTextureMatrix = this->normalTextureMatrix(); + const UnsignedInt normalTextureCoordinates = this->normalTextureCoordinates(); + return roughnessTextureMatrix() == normalTextureMatrix && + metalnessTextureMatrix() == normalTextureMatrix && + roughnessTextureCoordinates() == normalTextureCoordinates && + metalnessTextureCoordinates() == normalTextureCoordinates; +} + +bool PbrMetallicRoughnessMaterialData::hasTextureTransformation() const { + return hasAttribute(MaterialAttribute::TextureMatrix) || + hasAttribute(MaterialAttribute::BaseColorTextureMatrix) || + hasAttribute(MaterialAttribute::MetalnessTextureMatrix) || + hasAttribute(MaterialAttribute::RoughnessTextureMatrix) || + hasAttribute(MaterialAttribute::NormalTextureMatrix) || + hasAttribute(MaterialAttribute::OcclusionTextureMatrix) || + hasAttribute(MaterialAttribute::EmissiveTextureMatrix);; +} + +bool PbrMetallicRoughnessMaterialData::hasCommonTextureTransformation() const { + auto check = [](Containers::Optional& transformation, Matrix3 current) { + if(!transformation) { + transformation = current; + return true; + } + return transformation == current; + }; + + Containers::Optional transformation; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::BaseColorTexture) && !check(transformation, baseColorTextureMatrix())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasMetalnessTexture() && !check(transformation, metalnessTextureMatrix())) + return false; + if(hasRoughnessTexture() && !check(transformation, roughnessTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(transformation, normalTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::OcclusionTexture) && !check(transformation, occlusionTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::EmissiveTexture) && !check(transformation, emissiveTextureMatrix())) + return false; + + return true; +} + +bool PbrMetallicRoughnessMaterialData::hasTextureCoordinates() const { + return hasAttribute(MaterialAttribute::TextureCoordinates) || + hasAttribute(MaterialAttribute::BaseColorTextureCoordinates) || + hasAttribute(MaterialAttribute::MetalnessTextureCoordinates) || + hasAttribute(MaterialAttribute::RoughnessTextureCoordinates) || + hasAttribute(MaterialAttribute::NormalTextureCoordinates) || + hasAttribute(MaterialAttribute::OcclusionTextureCoordinates) || + hasAttribute(MaterialAttribute::EmissiveTextureCoordinates); +} + +bool PbrMetallicRoughnessMaterialData::hasCommonTextureCoordinates() const { + auto check = [](Containers::Optional& coordinates, UnsignedInt current) { + if(!coordinates) { + coordinates = current; + return true; + } + return coordinates == current; + }; + + Containers::Optional coordinates; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::BaseColorTexture) && !check(coordinates, baseColorTextureCoordinates())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasMetalnessTexture() && !check(coordinates, metalnessTextureCoordinates())) + return false; + if(hasRoughnessTexture() && !check(coordinates, roughnessTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(coordinates, normalTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::OcclusionTexture) && !check(coordinates, occlusionTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::EmissiveTexture) && !check(coordinates, emissiveTextureCoordinates())) + return false; + + return true; +} + +Color4 PbrMetallicRoughnessMaterialData::baseColor() const { + return attributeOr(MaterialAttribute::BaseColor, 0xffffffff_rgbaf); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::baseColorTexture() const { + return attribute(MaterialAttribute::BaseColorTexture); +} + +Matrix3 PbrMetallicRoughnessMaterialData::baseColorTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::BaseColorTexture), + "Trade::PbrMetallicRoughnessMaterialData::baseColorTextureMatrix(): the material doesn't have a base color texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColorTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::baseColorTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::BaseColorTexture), + "Trade::PbrMetallicRoughnessMaterialData::baseColorTextureCoordinates(): the material doesn't have a base color texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::BaseColorTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Float PbrMetallicRoughnessMaterialData::metalness() const { + return attributeOr(MaterialAttribute::Metalness, 1.0f); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::metalnessTexture() const { + /* Explicit assertion because printing that MetalnessTexture isn't found + would be misleading as it can be also MetallicRoughnessTexture */ + CORRADE_ASSERT(hasMetalnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::metalnessTexture(): the material doesn't have a metalness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NoneRoughnessMetallicTexture)) + return *value; + return attribute(MaterialAttribute::MetalnessTexture); +} + +MaterialTextureSwizzle PbrMetallicRoughnessMaterialData::metalnessTextureSwizzle() const { + CORRADE_ASSERT(hasMetalnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureSwizzle(): the material doesn't have a metalness texture", {}); + if(hasAttribute(MaterialAttribute::NoneRoughnessMetallicTexture)) + return MaterialTextureSwizzle::B; + return attributeOr(MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrMetallicRoughnessMaterialData::metalnessTextureMatrix() const { + CORRADE_ASSERT(hasMetalnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureMatrix(): the material doesn't have a metalness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::MetalnessTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::metalnessTextureCoordinates() const { + CORRADE_ASSERT(hasMetalnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureCoordinates(): the material doesn't have a metalness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::MetalnessTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Float PbrMetallicRoughnessMaterialData::roughness() const { + return attributeOr(MaterialAttribute::Roughness, 1.0f); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::roughnessTexture() const { + /* Explicit assertion because printing that RoughnessTexture isn't found + would be misleading as it can be also MetallicRoughnessTexture */ + CORRADE_ASSERT(hasRoughnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::roughnessTexture(): the material doesn't have a roughness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NoneRoughnessMetallicTexture)) + return *value; + return attribute(MaterialAttribute::RoughnessTexture); +} + +MaterialTextureSwizzle PbrMetallicRoughnessMaterialData::roughnessTextureSwizzle() const { + CORRADE_ASSERT(hasRoughnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureSwizzle(): the material doesn't have a roughness texture", {}); + if(hasAttribute(MaterialAttribute::NoneRoughnessMetallicTexture)) + return MaterialTextureSwizzle::G; + return attributeOr(MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrMetallicRoughnessMaterialData::roughnessTextureMatrix() const { + CORRADE_ASSERT(hasRoughnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureMatrix(): the material doesn't have a roughness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::RoughnessTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::roughnessTextureCoordinates() const { + CORRADE_ASSERT(hasRoughnessTexture(), + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureCoordinates(): the material doesn't have a roughness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::RoughnessTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::normalTexture() const { + return attribute(MaterialAttribute::NormalTexture); +} + +Float PbrMetallicRoughnessMaterialData::normalTextureScale() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrMetallicRoughnessMaterialData::normalTextureScale(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureScale, 1.0f); +} + +MaterialTextureSwizzle PbrMetallicRoughnessMaterialData::normalTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrMetallicRoughnessMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RGB); +} + +Matrix3 PbrMetallicRoughnessMaterialData::normalTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrMetallicRoughnessMaterialData::normalTextureMatrix(): the material doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::normalTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrMetallicRoughnessMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::occlusionTexture() const { + return attribute(MaterialAttribute::OcclusionTexture); +} + +Float PbrMetallicRoughnessMaterialData::occlusionTextureStrength() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureStrength(): the material doesn't have an occlusion texture", {}); + return attributeOr(MaterialAttribute::OcclusionTextureStrength, 1.0f); +} + +MaterialTextureSwizzle PbrMetallicRoughnessMaterialData::occlusionTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureSwizzle(): the material doesn't have an occlusion texture", {}); + return attributeOr(MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrMetallicRoughnessMaterialData::occlusionTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureMatrix(): the material doesn't have an occlusion texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::OcclusionTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::occlusionTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureCoordinates(): the material doesn't have an occlusion texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::OcclusionTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Color3 PbrMetallicRoughnessMaterialData::emissiveColor() const { + return attributeOr(MaterialAttribute::EmissiveColor, 0x000000_srgbf); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::emissiveTexture() const { + return attribute(MaterialAttribute::EmissiveTexture); +} + +Matrix3 PbrMetallicRoughnessMaterialData::emissiveTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::EmissiveTexture), + "Trade::PbrMetallicRoughnessMaterialData::emissiveTextureMatrix(): the material doesn't have an emissive texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::EmissiveTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::emissiveTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::EmissiveTexture), + "Trade::PbrMetallicRoughnessMaterialData::emissiveTextureCoordinates(): the material doesn't have an emissive texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::EmissiveTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Matrix3 PbrMetallicRoughnessMaterialData::commonTextureMatrix() const { + CORRADE_ASSERT(hasCommonTextureTransformation(), + "Trade::PbrMetallicRoughnessMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation", {}); + if(hasAttribute(MaterialAttribute::BaseColorTexture)) + return baseColorTextureMatrix(); + if(hasMetalnessTexture()) + return metalnessTextureMatrix(); + if(hasRoughnessTexture()) + return roughnessTextureMatrix(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureMatrix(); + if(hasAttribute(MaterialAttribute::OcclusionTexture)) + return occlusionTextureMatrix(); + if(hasAttribute(MaterialAttribute::EmissiveTexture)) + return emissiveTextureMatrix(); + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrMetallicRoughnessMaterialData::commonTextureCoordinates() const { + CORRADE_ASSERT(hasCommonTextureCoordinates(), + "Trade::PbrMetallicRoughnessMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set", {}); + if(hasAttribute(MaterialAttribute::BaseColorTexture)) + return baseColorTextureCoordinates(); + if(hasMetalnessTexture()) + return metalnessTextureCoordinates(); + if(hasRoughnessTexture()) + return roughnessTextureCoordinates(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureCoordinates(); + if(hasAttribute(MaterialAttribute::OcclusionTexture)) + return occlusionTextureCoordinates(); + if(hasAttribute(MaterialAttribute::EmissiveTexture)) + return emissiveTextureCoordinates(); + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +}} diff --git a/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.h b/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.h new file mode 100644 index 0000000000..328212ed32 --- /dev/null +++ b/src/Magnum/Trade/PbrMetallicRoughnessMaterialData.h @@ -0,0 +1,628 @@ +#ifndef Magnum_Trade_PbrMetallicRoughnessMaterialData_h +#define Magnum_Trade_PbrMetallicRoughnessMaterialData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::PbrMetallicRoughnessMaterialData + * @m_since_latest + */ + +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace Trade { + +/** +@brief PBR metallic/roughness material data +@m_since_latest + +See @ref Trade-MaterialData-usage-types for more information about how to use +this class. +@see @ref AbstractImporter::material(), @ref FlatMaterialData, + @ref PhongMaterialData, @ref PbrSpecularGlossinessMaterialData, + @ref PbrClearCoatMaterialData, @ref MaterialType::PbrMetallicRoughness +*/ +class MAGNUM_TRADE_EXPORT PbrMetallicRoughnessMaterialData: public MaterialData { + public: + #ifndef DOXYGEN_GENERATING_OUTPUT + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing). It's + however hidden from the docs as constructing instances this way + isn't really common and it would add a lot of noise. */ + using MaterialData::MaterialData; + #endif + + /** + * @brief Whether the material has a metalness texture + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::MetalnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture attributes is + * present, @cpp false @ce otherwise. + * @see @ref hasRoughnessTexture(), @ref hasNoneRoughnessMetallicTexture() + */ + bool hasMetalnessTexture() const; + + /** + * @brief Whether the material has a roughness texture + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::RoughnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture attributes is + * present, @cpp false @ce otherwise. + * @see @ref hasMetalnessTexture(), @ref hasNoneRoughnessMetallicTexture() + */ + bool hasRoughnessTexture() const; + + /** + * @brief Whether the material has a combined roughness/metallic texture + * + * Returns @cpp true @ce if either the + * @ref MaterialAttribute::NoneRoughnessMetallicTexture attribute is + * present or both @ref MaterialAttribute::RoughnessTexture and + * @ref MaterialAttribute::MetalnessTexture are present, point to the + * same texture ID, @ref MaterialAttribute::RoughnessTextureSwizzle is + * set to @ref MaterialTextureSwizzle::G and + * @ref MaterialAttribute::MetalnessTextureSwizzle is set to + * @ref MaterialTextureSwizzle::B, and ddditionally + * @ref MaterialAttribute::RoughnessTextureMatrix and + * @ref MaterialAttribute::MetalnessTextureMatrix are both either not + * present or have the same value, and + * @ref MaterialAttribute::RoughnessTextureCoordinates and + * @ref MaterialAttribute::MetalnessTextureCoordinates are both either + * not present or have the same value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref roughnessTexture(), @ref roughnessTextureMatrix() and + * @ref roughnessTextureCoordinates() return values common for both + * metalness and roughness texture, and the two are packed together + * with roughness occupying the G channel and metalness the B channel. + * This packing is common in glTF metallic/roughness materials, which + * in turn is compatible with how UE4 assets are usually packed. + * Original rationale for this [can be seen here](https://github.com/KhronosGroup/glTF/issues/857). + * + * The red and alpha channels are ignored and can be repurposed for + * other maps such as occlusion or transmission. This check is a subset + * of @ref hasOcclusionRoughnessMetallicTexture() --- if that function + * returns @cpp true @ce, this will return @cpp true @ce as well. + * @see @ref hasMetalnessTexture(), @ref hasRoughnessTexture(), + * @ref hasRoughnessMetallicOcclusionTexture(), + * @ref hasNormalRoughnessMetallicTexture() + */ + bool hasNoneRoughnessMetallicTexture() const; + + /** + * @brief Whether the material has a combined occlusion/roughness/metallic texture + * + * Returns @cpp true @ce if @ref MaterialAttribute::OcclusionTexture, + * @ref MaterialAttribute::RoughnessTexture and + * @ref MaterialAttribute::MetalnessTexture are all present, point to + * the same texture ID, @ref MaterialAttribute::OcclusionTextureSwizzle + * is set to @ref MaterialTextureSwizzle::R (or omitted, in which case + * it's the default), @ref MaterialAttribute::RoughnessTextureSwizzle + * is set to @ref MaterialTextureSwizzle::G and + * @ref MaterialAttribute::MetalnessTextureSwizzle is set to + * @ref MaterialTextureSwizzle::B, and additionally + * @ref MaterialAttribute::OcclusionTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureMatrix and + * @ref MaterialAttribute::MetalnessTextureMatrix are all other not + * present or have the same value, and + * @ref MaterialAttribute::OcclusionTextureCoordinates, + * @ref MaterialAttribute::RoughnessTextureCoordinates and + * @ref MaterialAttribute::MetalnessTextureCoordinates are all either + * not present or have the same value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref occlusionTexture(), @ref occlusionTextureMatrix() and + * @ref occlusionTextureCoordinates() return values common for + * occlusion, roughness and metalness textures, and the three are + * packed together with occlusion occupying the R channel, roughness + * the G channel and metalness the B channel. This packing is common in + * glTF metallic/roughness materials, which in turn is compatible with + * how UE4 assets are usually packed. Original rationale for this [can + * be seen here](https://github.com/KhronosGroup/glTF/issues/857), + * there's also a [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic/README.md) + * glTF extension using this packing in a more explicit way. + * + * The alpha channel is ignored and can be repurposed for other maps + * such as transmission. This check is a superset of + * @ref hasNoneRoughnessMetallicTexture() --- if this function returns + * @cpp true @ce, that one will return @cpp true @ce as well. + * @see @ref hasMetalnessTexture(), @ref hasRoughnessTexture(), + * @ref hasNoneRoughnessMetallicTexture(), + * @ref hasRoughnessMetallicOcclusionTexture(), + * @ref hasNormalRoughnessMetallicTexture() + */ + bool hasOcclusionRoughnessMetallicTexture() const; + + /** + * @brief Whether the material has a combined roughness/metallic/occlusion texture + * + * Returns @cpp true @ce if @ref MaterialAttribute::RoughnessTexture, + * @ref MaterialAttribute::MetalnessTexture and + * @ref MaterialAttribute::OcclusionTexture are all present, point to + * the same texture ID, @ref MaterialAttribute::RoughnessTextureSwizzle + * is set to @ref MaterialTextureSwizzle::R (or omitted, in which case + * it's the default), @ref MaterialAttribute::MetalnessTextureSwizzle + * is set to @ref MaterialTextureSwizzle::G and + * @ref MaterialAttribute::OcclusionTextureSwizzle is set to + * @ref MaterialTextureSwizzle::B, and additionally + * @ref MaterialAttribute::RoughnessTextureMatrix, + * @ref MaterialAttribute::MetalnessTextureMatrix and + * @ref MaterialAttribute::OcclusionTextureMatrix are all other not + * present or have the same value, and + * @ref MaterialAttribute::RoughnessTextureCoordinates, + * @ref MaterialAttribute::MetalnessTextureCoordinates and + * @ref MaterialAttribute::OcclusionTextureCoordinates are all either + * not present or have the same value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref roughnessTexture(), @ref roughnessTextureMatrix() and + * @ref roughnessTextureCoordinates() return values common for + * roughness, metalness and occlusion textures, and the three are + * packed together with roughness occupying the R channel, metalness + * the G channel and occlusion the B channel. This check is present in + * order to provide support for the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic/README.md) + * glTF extension. + * + * The alpha channel is ignored and can be repurposed for other maps + * such as transmission. + * @see @ref hasMetalnessTexture(), @ref hasRoughnessTexture(), + * @ref hasNoneRoughnessMetallicTexture(), + * @ref hasOcclusionRoughnessMetallicTexture(), + * @ref hasNormalRoughnessMetallicTexture() + */ + bool hasRoughnessMetallicOcclusionTexture() const; + + /** + * @brief Whether the material has a combined normal/roughness/metallic texture + * + * Returns @cpp true @ce if @ref MaterialAttribute::NormalTexture, + * @ref MaterialAttribute::RoughnessTexture and + * @ref MaterialAttribute::MetalnessTexture are all present, point to + * the same texture ID, @ref MaterialAttribute::NormalTextureSwizzle + * is set to @ref MaterialTextureSwizzle::RG (with the third channel + * implicit), @ref MaterialAttribute::RoughnessTextureSwizzle + * is set to @ref MaterialTextureSwizzle::B and + * @ref MaterialAttribute::MetalnessTextureSwizzle is set to + * @ref MaterialTextureSwizzle::A, and additionally + * @ref MaterialAttribute::NormalTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureMatrix and + * @ref MaterialAttribute::MetalnessTextureMatrix are all other not + * present or have the same value, and + * @ref MaterialAttribute::NormalTextureCoordinates, + * @ref MaterialAttribute::RoughnessTextureCoordinates and + * @ref MaterialAttribute::MetalnessTextureCoordinates are all either + * not present or have the same value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref normalTexture(), @ref normalTextureMatrix() and + * @ref normalTextureCoordinates() return values common for normal, + * roughness and metalness textures, and the three are packed together + * with normals occupying the RG channel, roughness the B channel and + * metalness the A channel. This check is present in order to provide + * support for the [MSFT_packing_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic/README.md) + * glTF extension. + * + * @see @ref hasMetalnessTexture(), @ref hasRoughnessTexture(), + * @ref hasNoneRoughnessMetallicTexture(), + * @ref hasRoughnessMetallicOcclusionTexture(), + * @ref hasOcclusionRoughnessMetallicTexture() + */ + bool hasNormalRoughnessMetallicTexture() const; + + /** + * @brief Whether the material has texture transformation + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::BaseColorTextureMatrix, + * @ref MaterialAttribute::MetalnessTextureMatrix, + * @ref MaterialAttribute::RoughnessTextureMatrix, + * @ref MaterialAttribute::NormalTextureMatrix, + * @ref MaterialAttribute::OcclusionTextureMatrix, + * @ref MaterialAttribute::EmissiveTextureMatrix or + * @ref MaterialAttribute::TextureMatrix attributes is present, + * @cpp false @ce otherwise. + * @see @ref hasCommonTextureTransformation() + */ + bool hasTextureTransformation() const; + + /** + * @brief Whether the material has a common transformation for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref baseColorTextureMatrix(), @ref metalnessTextureMatrix(), + * @ref roughnessTextureMatrix(), @ref normalTextureMatrix(), + * @ref occlusionTextureMatrix() and @ref emissiveTextureMatrix() have + * the same value, @cpp false @ce otherwise. In particular, returns + * @cpp true @ce also if there's no texture transformation at all. Use + * @ref hasTextureTransformation() to distinguish that case. + */ + bool hasCommonTextureTransformation() const; + + /** + * @brief Whether the material uses extra texture coordinate sets + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::BaseColorTextureCoordinates, + * @ref MaterialAttribute::MetalnessTextureCoordinates, + * @ref MaterialAttribute::RoughnessTextureCoordinates, + * @ref MaterialAttribute::NormalTextureCoordinates, + * @ref MaterialAttribute::OcclusionTextureCoordinates, + * @ref MaterialAttribute::EmissiveTextureCoordinates or + * @ref MaterialAttribute::TextureCoordinates attributes is present and + * has a non-zero value, @cpp false @ce otherwise. + * @see @ref hasCommonTextureCoordinates() + */ + bool hasTextureCoordinates() const; + + /** + * @brief Whether the material has a common coordinate set for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref baseColorTextureCoordinates(), @ref metalnessTextureCoordinates(), + * @ref roughnessTextureCoordinates(), @ref normalTextureCoordinates(), + * @ref occlusionTextureCoordinates() and @ref emissiveTextureCoordinates() + * have the same value, @cpp false @ce otherwise. In particular, + * returns @cpp true @ce also if there's no extra texture coordinate + * set used at all. Use @ref hasTextureCoordinates() to distinguish + * that case. + */ + bool hasCommonTextureCoordinates() const; + + /** + * @brief Base color + * + * Convenience access to the @ref MaterialAttribute::BaseColor + * attribute. If not present, the default is @cpp 0xffffffff_srgbaf @ce. + * + * If the material has @ref MaterialAttribute::BaseColorTexture, the + * color and texture is meant to be multiplied together. + */ + Color4 baseColor() const; + + /** + * @brief Base color texture ID + * + * Available only if @ref MaterialAttribute::BaseColorTexture is + * present. Meant to be multiplied with @ref baseColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt baseColorTexture() const; + + /** + * @brief Base color texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::BaseColorTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::BaseColorTexture. + * @see @ref hasAttribute() + */ + Matrix3 baseColorTextureMatrix() const; + + /** + * @brief Base color texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::BaseColorTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::BaseColorTexture. + * @see @ref hasAttribute() + */ + UnsignedInt baseColorTextureCoordinates() const; + + /** + * @brief Metalness factor + * + * Convenience access to the @ref MaterialAttribute::Metalness + * attribute. If not present, the default is @cpp 1.0f @ce. + * + * If the material has a metalness texture, the factor and texture is + * meant to be multiplied together. + * @see @ref hasMetalnessTexture() + */ + Float metalness() const; + + /** + * @brief Metalness texture ID + * + * Available only if either @ref MaterialAttribute::MetalnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture is present. + * Meant to be multiplied with @ref metalness(). + * @see @ref hasMetalnessTexture(), @ref AbstractImporter::texture() + */ + UnsignedInt metalnessTexture() const; + + /** + * @brief Metalness texture swizzle + * + * If @ref MaterialAttribute::NoneRoughnessMetallicTexture is present, + * returns always @ref MaterialTextureSwizzle::B. Otherwise returns the + * @ref MaterialAttribute::MetalnessTextureSwizzle attribute, or + * @ref MaterialTextureSwizzle::R, if it's not present. Available only + * if the material has a metalness texture. + * @see @ref hasMetalnessTexture() + */ + MaterialTextureSwizzle metalnessTextureSwizzle() const; + + /** + * @brief Metalness texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::MetalnessTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has a metalness texture. + * @see @ref hasMetalnessTexture() + */ + Matrix3 metalnessTextureMatrix() const; + + /** + * @brief Metalness texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::MetalnessTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has a metalness texture. + * @see @ref hasMetalnessTexture() + */ + UnsignedInt metalnessTextureCoordinates() const; + + /** + * @brief Roughness factor + * + * Convenience access to the @ref MaterialAttribute::Roughness + * attribute. If not present, the default is @cpp 1.0f @ce. + * + * If the material has a roughness texture, the factor and texture is + * meant to be multiplied together. + * @see @ref hasRoughnessTexture() + */ + Float roughness() const; + + /** + * @brief Roughness texture ID + * + * Available only if either @ref MaterialAttribute::RoughnessTexture or + * @ref MaterialAttribute::NoneRoughnessMetallicTexture is present. Meant + * to be multiplied with @ref roughness(). + * @see @ref hasRoughnessTexture(), @ref AbstractImporter::texture() + */ + UnsignedInt roughnessTexture() const; + + /** + * @brief Roughness texture swizzle + * + * If @ref MaterialAttribute::NoneRoughnessMetallicTexture is present, + * returns always @ref MaterialTextureSwizzle::G. Otherwise returns the + * @ref MaterialAttribute::RoughnessTextureSwizzle attribute, or + * @ref MaterialTextureSwizzle::R, if it's not present. Available only + * if the material has a roughness texture. + * @see @ref hasRoughnessTexture() + */ + MaterialTextureSwizzle roughnessTextureSwizzle() const; + + /** + * @brief Roughness texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::RoughnessTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has a roughness texture. + * @see @ref hasRoughnessTexture() + */ + Matrix3 roughnessTextureMatrix() const; + + /** + * @brief Roughness texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::RoughnessTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has a roughness texture. + * @see @ref hasRoughnessTexture() + */ + UnsignedInt roughnessTextureCoordinates() const; + + /** + * @brief Normal texture ID + * + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt normalTexture() const; + + /** + * @brief Normal texture scale + * + * Convenience access to the @ref MaterialAttribute::NormalTextureScale + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + Float normalTextureScale() const; + + /** + * @brief Normal texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::NormalTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + Matrix3 normalTextureMatrix() const; + + /** + * @brief Normal texture swizzle + * @m_since_latest + * + * Convenience access to the + * @ref MaterialAttribute::NormalTextureSwizzle attribute. If not + * present, the default is @ref MaterialTextureSwizzle::RGB. Available + * only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle normalTextureSwizzle() const; + + /** + * @brief Normal texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::NormalTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt normalTextureCoordinates() const; + + /** + * @brief Occlusion texture ID + * + * Available only if @ref MaterialAttribute::OcclusionTexture is present. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt occlusionTexture() const; + + /** + * @brief Occlusion texture strength + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureStrength + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::OcclusionTexture is + * present. + * @see @ref hasAttribute() + */ + Float occlusionTextureStrength() const; + + /** + * @brief Occlusion texture swizzle + * + * Convenience access to the + * @ref MaterialAttribute::OcclusionTextureSwizzle attribute. If not + * present, the default is @ref MaterialTextureSwizzle::R. Available + * only if @ref MaterialAttribute::OcclusionTexture is present. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle occlusionTextureSwizzle() const; + + /** + * @brief Occlusion texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::OcclusionTexture. + * @see @ref hasAttribute() + */ + Matrix3 occlusionTextureMatrix() const; + + /** + * @brief Occlusion texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::OcclusionTexture. + * @see @ref hasAttribute() + */ + UnsignedInt occlusionTextureCoordinates() const; + + /** + * @brief Emissive color + * + * Convenience access to the @ref MaterialAttribute::EmissiveColor + * attribute. If not present, the default is @cpp 0x000000_srgbf @ce + * (i.e, no emission). + * + * If the material has @ref MaterialAttribute::EmissiveTexture, the + * color and texture is meant to be multiplied together. + */ + Color3 emissiveColor() const; + + /** + * @brief Emissive texture ID + * + * Available only if @ref MaterialAttribute::EmissiveTexture is present. + * Meant to be multiplied with @ref emissiveColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt emissiveTexture() const; + + /* No EmissiveTextureSwizzle attribute right now (implicitly RGB) */ + + /** + * @brief Emissive texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::EmissiveTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::EmissiveTexture. + * @see @ref hasAttribute() + */ + Matrix3 emissiveTextureMatrix() const; + + /** + * @brief Emissive texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::EmissiveTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::EmissiveTexture. + * @see @ref hasAttribute() + */ + UnsignedInt emissiveTextureCoordinates() const; + + /** + * @brief Common texture coordinate transformation matrix for all textures + * + * Expects that @ref hasCommonTextureTransformation() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref baseColorTextureMatrix(), @ref metalnessTextureMatrix(), + * @ref roughnessTextureMatrix(), @ref normalTextureMatrix(), + * @ref occlusionTextureMatrix() and @ref emissiveTextureMatrix() where + * a texture is present. If no texture is present, returns an identity + * matrix. + */ + Matrix3 commonTextureMatrix() const; + + /** + * @brief Common texture coordinate set index for all textures + * + * Expects that @ref hasCommonTextureCoordinates() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref baseColorTextureCoordinates(), @ref metalnessTextureCoordinates(), + * @ref roughnessTextureCoordinates(), @ref normalTextureCoordinates(), + * @ref occlusionTextureCoordinates() and @ref emissiveTextureCoordinates() + * where a texture is present. If no texture is present, returns + * @cpp 0 @ce. + */ + UnsignedInt commonTextureCoordinates() const; +}; + +}} + +#endif diff --git a/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.cpp b/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.cpp new file mode 100644 index 0000000000..11fd16b71d --- /dev/null +++ b/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.cpp @@ -0,0 +1,354 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "PbrSpecularGlossinessMaterialData.h" + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" + +namespace Magnum { namespace Trade { + +using namespace Math::Literals; + +bool PbrSpecularGlossinessMaterialData::hasSpecularTexture() const { + return hasAttribute(MaterialAttribute::SpecularTexture) || + hasAttribute(MaterialAttribute::SpecularGlossinessTexture); +} + +bool PbrSpecularGlossinessMaterialData::hasGlossinessTexture() const { + return hasAttribute(MaterialAttribute::GlossinessTexture) || + hasAttribute(MaterialAttribute::SpecularGlossinessTexture); +} + +bool PbrSpecularGlossinessMaterialData::hasSpecularGlossinessTexture() const { + return (hasAttribute(MaterialAttribute::SpecularGlossinessTexture) || + (hasAttribute(MaterialAttribute::SpecularTexture) && + hasAttribute(MaterialAttribute::GlossinessTexture) && + attribute(MaterialAttribute::SpecularTexture) == attribute(MaterialAttribute::GlossinessTexture) && + specularTextureSwizzle() == MaterialTextureSwizzle::RGB && + glossinessTextureSwizzle() == MaterialTextureSwizzle::A)) && + specularTextureMatrix() == glossinessTextureMatrix() && + specularTextureCoordinates() == glossinessTextureCoordinates(); +} + +bool PbrSpecularGlossinessMaterialData::hasTextureTransformation() const { + return hasAttribute(MaterialAttribute::TextureMatrix) || + hasAttribute(MaterialAttribute::DiffuseTextureMatrix) || + hasAttribute(MaterialAttribute::SpecularTextureMatrix) || + hasAttribute(MaterialAttribute::GlossinessTextureMatrix) || + hasAttribute(MaterialAttribute::NormalTextureMatrix) || + hasAttribute(MaterialAttribute::OcclusionTextureMatrix) || + hasAttribute(MaterialAttribute::EmissiveTextureMatrix); +} + +bool PbrSpecularGlossinessMaterialData::hasCommonTextureTransformation() const { + auto check = [](Containers::Optional& transformation, Matrix3 current) { + if(!transformation) { + transformation = current; + return true; + } + return transformation == current; + }; + + Containers::Optional transformation; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::DiffuseTexture) && !check(transformation, diffuseTextureMatrix())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasSpecularTexture() && !check(transformation, specularTextureMatrix())) + return false; + if(hasGlossinessTexture() && !check(transformation, glossinessTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(transformation, normalTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::OcclusionTexture) && !check(transformation, occlusionTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::EmissiveTexture) && !check(transformation, emissiveTextureMatrix())) + return false; + + return true; +} + +bool PbrSpecularGlossinessMaterialData::hasTextureCoordinates() const { + return hasAttribute(MaterialAttribute::TextureCoordinates) || + hasAttribute(MaterialAttribute::DiffuseTextureCoordinates) || + hasAttribute(MaterialAttribute::SpecularTextureCoordinates) || + hasAttribute(MaterialAttribute::GlossinessTextureCoordinates) || + hasAttribute(MaterialAttribute::NormalTextureCoordinates) || + hasAttribute(MaterialAttribute::OcclusionTextureCoordinates) || + hasAttribute(MaterialAttribute::EmissiveTextureCoordinates); +} + +bool PbrSpecularGlossinessMaterialData::hasCommonTextureCoordinates() const { + auto check = [](Containers::Optional& coordinates, UnsignedInt current) { + if(!coordinates) { + coordinates = current; + return true; + } + return coordinates == current; + }; + + Containers::Optional coordinates; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::DiffuseTexture) && !check(coordinates, diffuseTextureCoordinates())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasSpecularTexture() && !check(coordinates, specularTextureCoordinates())) + return false; + if(hasGlossinessTexture() && !check(coordinates, glossinessTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(coordinates, normalTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::OcclusionTexture) && !check(coordinates, occlusionTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::EmissiveTexture) && !check(coordinates, emissiveTextureCoordinates())) + return false; + + return true; +} + +Color4 PbrSpecularGlossinessMaterialData::diffuseColor() const { + return attributeOr(MaterialAttribute::DiffuseColor, 0xffffffff_srgbaf); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::diffuseTexture() const { + return attribute(MaterialAttribute::DiffuseTexture); +} + +Matrix3 PbrSpecularGlossinessMaterialData::diffuseTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::DiffuseTexture), + "Trade::PbrSpecularGlossinessMaterialData::diffuseTextureMatrix(): the material doesn't have a diffuse texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::DiffuseTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::diffuseTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::DiffuseTexture), + "Trade::PbrSpecularGlossinessMaterialData::diffuseTextureCoordinates(): the material doesn't have a diffuse texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::DiffuseTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Color4 PbrSpecularGlossinessMaterialData::specularColor() const { + return attributeOr(MaterialAttribute::SpecularColor, 0xffffff00_srgbaf); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::specularTexture() const { + /* Explicit assertion because printing that SpecularTexture isn't found + would be misleading as it can be also SpecularGlossinessTexture */ + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PbrSpecularGlossinessMaterialData::specularTexture(): the material doesn't have a specular texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return *value; + return attribute(MaterialAttribute::SpecularTexture); +} + +MaterialTextureSwizzle PbrSpecularGlossinessMaterialData::specularTextureSwizzle() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PbrSpecularGlossinessMaterialData::specularTextureSwizzle(): the material doesn't have a specular texture", {}); + if(hasAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return MaterialTextureSwizzle::RGB; + return attributeOr(MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGB); +} + +Matrix3 PbrSpecularGlossinessMaterialData::specularTextureMatrix() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PbrSpecularGlossinessMaterialData::specularTextureMatrix(): the material doesn't have a specular texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::SpecularTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::specularTextureCoordinates() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PbrSpecularGlossinessMaterialData::specularTextureCoordinates(): the material doesn't have a specular texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::SpecularTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Float PbrSpecularGlossinessMaterialData::glossiness() const { + return attributeOr(MaterialAttribute::Glossiness, 1.0f); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::glossinessTexture() const { + /* Explicit assertion because printing that GlossinessTexture isn't found + would be misleading as it can be also SpecularGlossinessTexture */ + CORRADE_ASSERT(hasGlossinessTexture(), + "Trade::PbrSpecularGlossinessMaterialData::glossinessTexture(): the material doesn't have a glossiness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return *value; + return attribute(MaterialAttribute::GlossinessTexture); +} + +MaterialTextureSwizzle PbrSpecularGlossinessMaterialData::glossinessTextureSwizzle() const { + CORRADE_ASSERT(hasGlossinessTexture(), + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureSwizzle(): the material doesn't have a glossiness texture", {}); + if(hasAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return MaterialTextureSwizzle::A; + return attributeOr(MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrSpecularGlossinessMaterialData::glossinessTextureMatrix() const { + CORRADE_ASSERT(hasGlossinessTexture(), + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureMatrix(): the material doesn't have a glossiness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::GlossinessTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::glossinessTextureCoordinates() const { + CORRADE_ASSERT(hasGlossinessTexture(), + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureCoordinates(): the material doesn't have a glossiness texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::GlossinessTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::normalTexture() const { + return attribute(MaterialAttribute::NormalTexture); +} + +Float PbrSpecularGlossinessMaterialData::normalTextureScale() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrSpecularGlossinessMaterialData::normalTextureScale(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureScale, 1.0f); +} + +MaterialTextureSwizzle PbrSpecularGlossinessMaterialData::normalTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrSpecularGlossinessMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RGB); +} + +Matrix3 PbrSpecularGlossinessMaterialData::normalTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrSpecularGlossinessMaterialData::normalTextureMatrix(): the material doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::normalTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PbrSpecularGlossinessMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::NormalTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::occlusionTexture() const { + return attribute(MaterialAttribute::OcclusionTexture); +} + +Float PbrSpecularGlossinessMaterialData::occlusionTextureStrength() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureStrength(): the material doesn't have an occlusion texture", {}); + return attributeOr(MaterialAttribute::OcclusionTextureStrength, 1.0f); +} + +MaterialTextureSwizzle PbrSpecularGlossinessMaterialData::occlusionTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureSwizzle(): the material doesn't have an occlusion texture", {}); + return attributeOr(MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::R); +} + +Matrix3 PbrSpecularGlossinessMaterialData::occlusionTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureMatrix(): the material doesn't have an occlusion texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::OcclusionTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::occlusionTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::OcclusionTexture), + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureCoordinates(): the material doesn't have an occlusion texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::OcclusionTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Color3 PbrSpecularGlossinessMaterialData::emissiveColor() const { + return attributeOr(MaterialAttribute::EmissiveColor, 0x000000_srgbf); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::emissiveTexture() const { + return attribute(MaterialAttribute::EmissiveTexture); +} + +Matrix3 PbrSpecularGlossinessMaterialData::emissiveTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::EmissiveTexture), + "Trade::PbrSpecularGlossinessMaterialData::emissiveTextureMatrix(): the material doesn't have an emissive texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::EmissiveTextureMatrix)) + return *value; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::emissiveTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::EmissiveTexture), + "Trade::PbrSpecularGlossinessMaterialData::emissiveTextureCoordinates(): the material doesn't have an emissive texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::EmissiveTextureCoordinates)) + return *value; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Matrix3 PbrSpecularGlossinessMaterialData::commonTextureMatrix() const { + CORRADE_ASSERT(hasCommonTextureTransformation(), + "Trade::PbrSpecularGlossinessMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation", {}); + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + return diffuseTextureMatrix(); + if(hasSpecularTexture()) + return specularTextureMatrix(); + if(hasGlossinessTexture()) + return glossinessTextureMatrix(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureMatrix(); + if(hasAttribute(MaterialAttribute::OcclusionTexture)) + return occlusionTextureMatrix(); + if(hasAttribute(MaterialAttribute::EmissiveTexture)) + return emissiveTextureMatrix(); + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PbrSpecularGlossinessMaterialData::commonTextureCoordinates() const { + CORRADE_ASSERT(hasCommonTextureCoordinates(), + "Trade::PbrSpecularGlossinessMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set", {}); + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + return diffuseTextureCoordinates(); + if(hasSpecularTexture()) + return specularTextureCoordinates(); + if(hasGlossinessTexture()) + return glossinessTextureCoordinates(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureCoordinates(); + if(hasAttribute(MaterialAttribute::OcclusionTexture)) + return occlusionTextureCoordinates(); + if(hasAttribute(MaterialAttribute::EmissiveTexture)) + return emissiveTextureCoordinates(); + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +}} diff --git a/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.h b/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.h new file mode 100644 index 0000000000..39b6ec6f3f --- /dev/null +++ b/src/Magnum/Trade/PbrSpecularGlossinessMaterialData.h @@ -0,0 +1,496 @@ +#ifndef Magnum_Trade_PbrSpecularGlossinessMaterialData_h +#define Magnum_Trade_PbrSpecularGlossinessMaterialData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::PbrSpecularGlossinessMaterialData + * @m_since_latest + */ + +#include "Magnum/Trade/MaterialData.h" + +namespace Magnum { namespace Trade { + +/** +@brief PBR specular/glossiness material data +@m_since_latest + +See @ref Trade-MaterialData-usage-types for more information about how to use +this class. +@see @ref AbstractImporter::material(), @ref PhongMaterialData, + @ref FlatMaterialData, @ref PbrMetallicRoughnessMaterialData, + @ref PbrClearCoatMaterialData, @ref MaterialType::PbrSpecularGlossiness +*/ +class MAGNUM_TRADE_EXPORT PbrSpecularGlossinessMaterialData: public MaterialData { + public: + #ifndef DOXYGEN_GENERATING_OUTPUT + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing). It's + however hidden from the docs as constructing instances this way + isn't really common and it would add a lot of noise. */ + using MaterialData::MaterialData; + #endif + + /** + * @brief Whether the material has a specular texture + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::SpecularTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture attributes is + * present, @cpp false @ce otherwise. + * @see @ref hasGlossinessTexture(), + * @ref hasSpecularGlossinessTexture() + */ + bool hasSpecularTexture() const; + + /** + * @brief Whether the material has a glossiness texture + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::GlossinessTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture attributes is + * present, @cpp false @ce otherwise. + * @see @ref hasSpecularTexture(), + * @ref hasSpecularGlossinessTexture() + */ + bool hasGlossinessTexture() const; + + /** + * @brief Whether the material has a combined specular/glossiness texture + * + * Returns @cpp true @ce if either the + * @ref MaterialAttribute::SpecularGlossinessTexture attribute is + * present or both @ref MaterialAttribute::SpecularTexture and + * @ref MaterialAttribute::GlossinessTexture are present, point to + * the same texture ID and @ref MaterialAttribute::GlossinessTextureSwizzle + * is set to @ref MaterialTextureSwizzle::A, and ddditionally + * @ref MaterialAttribute::SpecularTextureMatrix and + * @ref MaterialAttribute::GlossinessTextureMatrix are both either not + * present or have the same value, and + * @ref MaterialAttribute::SpecularTextureCoordinates and + * @ref MaterialAttribute::GlossinessTextureCoordinates are both either + * not present or have the same value; @cpp false @ce otherwise. + * + * In other words, if this function returns @cpp true @ce, + * @ref specularTexture(), @ref specularTextureMatrix() and + * @ref specularTextureCoordinates() return values common for both + * specular and glossiness texture, and the two are packed together + * with specular occupying the RGB channels and glossiness the alpha. + * @see @ref hasSpecularTexture(), @ref hasGlossinessTexture() + */ + bool hasSpecularGlossinessTexture() const; + + /** + * @brief Whether the material has texture transformation + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::DiffuseTextureMatrix, + * @ref MaterialAttribute::SpecularTextureMatrix, + * @ref MaterialAttribute::GlossinessTextureMatrix, + * @ref MaterialAttribute::NormalTextureMatrix, + * @ref MaterialAttribute::OcclusionTextureMatrix, + * @ref MaterialAttribute::EmissiveTextureMatrix or + * @ref MaterialAttribute::TextureMatrix attributes is present, + * @cpp false @ce otherwise. + * @see @ref hasCommonTextureTransformation() + */ + bool hasTextureTransformation() const; + + /** + * @brief Whether the material has a common transformation for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref diffuseTextureMatrix(), @ref specularTextureMatrix(), + * @ref glossinessTextureMatrix(), @ref normalTextureMatrix(), + * @ref occlusionTextureMatrix() and @ref emissiveTextureMatrix() have + * the same value, @cpp false @ce otherwise. In particular, returns + * @cpp true @ce also if there's no texture transformation at all. Use + * @ref hasTextureTransformation() to distinguish that case. + */ + bool hasCommonTextureTransformation() const; + + /** + * @brief Whether the material uses extra texture coordinate sets + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::DiffuseTextureCoordinates, + * @ref MaterialAttribute::SpecularTextureCoordinates, + * @ref MaterialAttribute::GlossinessTextureCoordinates, + * @ref MaterialAttribute::NormalTextureCoordinates, + * @ref MaterialAttribute::OcclusionTextureCoordinates, + * @ref MaterialAttribute::EmissiveTextureCoordinates or + * @ref MaterialAttribute::TextureCoordinates attributes is present and + * has a non-zero value, @cpp false @ce otherwise. + */ + bool hasTextureCoordinates() const; + + /** + * @brief Whether the material has a common coordinate set for all textures + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref diffuseTextureCoordinates(), @ref specularTextureCoordinates(), + * @ref glossinessTextureCoordinates(), @ref normalTextureCoordinates(), + * @ref occlusionTextureCoordinates() and @ref emissiveTextureCoordinates() + * have the same value, @cpp false @ce otherwise. In particular, + * returns @cpp true @ce also if there's no extra texture coordinate + * set used at all. Use @ref hasTextureCoordinates() to distinguish + * that case. + */ + bool hasCommonTextureCoordinates() const; + + /** + * @brief Base color + * + * Convenience access to the @ref MaterialAttribute::DiffuseColor + * attribute. If not present, the default is @cpp 0xffffffff_srgbaf @ce. + * + * If the material has @ref MaterialAttribute::DiffuseTexture, the + * color and texture is meant to be multiplied together. + */ + Color4 diffuseColor() const; + + /** + * @brief Base color texture ID + * + * Available only if @ref MaterialAttribute::DiffuseTexture is + * present. Meant to be multiplied with @ref diffuseColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt diffuseTexture() const; + + /** + * @brief Base color texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::DiffuseTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::DiffuseTexture. + * @see @ref hasAttribute() + */ + Matrix3 diffuseTextureMatrix() const; + + /** + * @brief Base color texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::DiffuseTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has @ref MaterialAttribute::DiffuseTexture. + * @see @ref hasAttribute() + */ + UnsignedInt diffuseTextureCoordinates() const; + + /** + * @brief Specular color + * + * Convenience access to the @ref MaterialAttribute::SpecularColor + * attribute. If not present, the default is @cpp 0xffffff00_srgbaf @ce. + * + * If the material has a specular texture, the color and texture is + * meant to be multiplied together. + * @see @ref hasSpecularTexture() + */ + Color4 specularColor() const; + + /** + * @brief Specular texture ID + * + * Available only if either @ref MaterialAttribute::SpecularTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture is present. Meant + * to be multiplied with @ref specularColor(). + * @see @ref hasSpecularTexture(), @ref AbstractImporter::texture() + */ + UnsignedInt specularTexture() const; + + /** + * @brief Specular texture swizzle + * + * If @ref MaterialAttribute::SpecularGlossinessTexture is present, + * returns always @ref MaterialTextureSwizzle::RGB. Otherwise returns + * the @ref MaterialAttribute::SpecularTextureSwizzle attribute, or + * @ref MaterialTextureSwizzle::RGB if it's not present. Available only + * if the material has a specular texture. + * @see @ref hasSpecularTexture() + */ + MaterialTextureSwizzle specularTextureSwizzle() const; + + /** + * @brief Specular texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::SpecularTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has a specular texture. + * @see @ref hasSpecularTexture() + */ + Matrix3 specularTextureMatrix() const; + + /** + * @brief Specular texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::SpecularTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has a specular texture. + * @see @ref hasSpecularTexture() + */ + UnsignedInt specularTextureCoordinates() const; + + /** + * @brief Glossiness factor + * + * Convenience access to the @ref MaterialAttribute::Glossiness + * attribute. If not present, the default is @cpp 1.0f @ce. + * + * If the material has a glossiness texture, the factor and texture is + * meant to be multiplied together. + * @see @ref hasGlossinessTexture() + */ + Float glossiness() const; + + /** + * @brief Glossiness texture ID + * + * Available only if either @ref MaterialAttribute::GlossinessTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture is present. Meant + * to be multiplied with @ref glossiness(). + * @see @ref hasGlossinessTexture(), @ref AbstractImporter::texture() + */ + UnsignedInt glossinessTexture() const; + + /** + * @brief Glossiness texture swizzle + * + * If @ref MaterialAttribute::SpecularGlossinessTexture is present, + * returns always @ref MaterialTextureSwizzle::A. Otherwise returns the + * @ref MaterialAttribute::GlossinessTextureSwizzle attribute, or + * @ref MaterialTextureSwizzle::R if it's not present. Available only + * if the material has a glossiness texture. + * @see @ref hasGlossinessTexture() + */ + MaterialTextureSwizzle glossinessTextureSwizzle() const; + + /** + * @brief Glossiness texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::GlossinessTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has a glossiness texture. + * @see @ref hasGlossinessTexture() + */ + Matrix3 glossinessTextureMatrix() const; + + /** + * @brief Glossiness texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::GlossinessTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has a glossiness texture. + * @see @ref hasGlossinessTexture() + */ + UnsignedInt glossinessTextureCoordinates() const; + + /** + * @brief Normal texture ID + * + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt normalTexture() const; + + /** + * @brief Normal texture scale + * + * Convenience access to the @ref MaterialAttribute::NormalTextureScale + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + Float normalTextureScale() const; + + /** + * @brief Normal texture swizzle + * + * Convenience access to the + * @ref MaterialAttribute::NormalTextureSwizzle attribute. If not + * present, the default is @ref MaterialTextureSwizzle::RGB. Available + * only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle normalTextureSwizzle() const; + + /** + * @brief Normal texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::NormalTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute() + */ + Matrix3 normalTextureMatrix() const; + + /** + * @brief Normal texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::NormalTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute() + */ + UnsignedInt normalTextureCoordinates() const; + + /** + * @brief Occlusion texture ID + * + * Available only if @ref MaterialAttribute::OcclusionTexture is present. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt occlusionTexture() const; + + /** + * @brief Occlusion texture strength + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureStrength + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::OcclusionTexture is + * present. + * @see @ref hasAttribute() + */ + Float occlusionTextureStrength() const; + + /** + * @brief Occlusion texture swizzle + * + * Convenience access to the + * @ref MaterialAttribute::OcclusionTextureSwizzle attribute. If not + * present, the default is @ref MaterialTextureSwizzle::R. Available + * only if @ref MaterialAttribute::OcclusionTexture is present. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle occlusionTextureSwizzle() const; + + /** + * @brief Occlusion texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::OcclusionTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + Matrix3 occlusionTextureMatrix() const; + + /** + * @brief Occlusion texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::OcclusionTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither is + * present, the default is @cpp 0 @ce. Available only if the material + * has @ref MaterialAttribute::OcclusionTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt occlusionTextureCoordinates() const; + + /** + * @brief Emissive color + * + * Convenience access to the @ref MaterialAttribute::EmissiveColor + * attribute. If not present, the default is @cpp 0x000000_srgbf @ce + * (i.e, no emission). + * + * If the material has @ref MaterialAttribute::EmissiveTexture, the + * color and texture is meant to be multiplied together. + */ + Color3 emissiveColor() const; + + /** + * @brief Emissive texture ID + * + * Available only if @ref MaterialAttribute::EmissiveTexture is present. + * Meant to be multiplied with @ref emissiveColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt emissiveTexture() const; + + /* No EmissiveTextureSwizzle attribute right now (implicitly RGB) */ + + /** + * @brief Emissive texture coordinate transformation matrix + * + * Convenience access to the @ref MaterialAttribute::EmissiveTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::EmissiveTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + Matrix3 emissiveTextureMatrix() const; + + /** + * @brief Emissive texture coordinate set + * + * Convenience access to the @ref MaterialAttribute::EmissiveTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::EmissiveTexture. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() + */ + UnsignedInt emissiveTextureCoordinates() const; + + /** + * @brief Common texture coordinate transformation matrix for all textures + * + * Expects that @ref hasCommonTextureTransformation() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref diffuseTextureMatrix(), @ref specularTextureMatrix(), + * @ref glossinessTextureMatrix(), @ref normalTextureMatrix(), + * @ref occlusionTextureMatrix() and @ref emissiveTextureMatrix() where + * a texture is present. If no texture is present, returns an identity + * matrix. + */ + Matrix3 commonTextureMatrix() const; + + /** + * @brief Common texture coordinate set index for all textures + * + * Expects that @ref hasCommonTextureCoordinates() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref diffuseTextureCoordinates(), @ref specularTextureCoordinates(), + * @ref glossinessTextureCoordinates(), @ref normalTextureCoordinates(), + * @ref occlusionTextureCoordinates() and @ref emissiveTextureCoordinates() + * where a texture is present. If no texture is present, returns + * @cpp 0 @ce. + */ + UnsignedInt commonTextureCoordinates() const; +}; + +}} + +#endif diff --git a/src/Magnum/Trade/PhongMaterialData.cpp b/src/Magnum/Trade/PhongMaterialData.cpp index c0e667dead..1a8b9150e1 100644 --- a/src/Magnum/Trade/PhongMaterialData.cpp +++ b/src/Magnum/Trade/PhongMaterialData.cpp @@ -27,110 +27,335 @@ #include "PhongMaterialData.h" #include +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#endif + +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" namespace Magnum { namespace Trade { using namespace Math::Literals; -PhongMaterialData::PhongMaterialData(const Flags flags, const Color4& ambientColor, const UnsignedInt ambientTexture, const UnsignedInt ambientCoordinateSet, const Color4& diffuseColor, const UnsignedInt diffuseTexture, const UnsignedInt diffuseCoordinateSet, const Color4& specularColor, const UnsignedInt specularTexture, const UnsignedInt specularCoordinateSet, const UnsignedInt normalTexture, const UnsignedInt normalCoordinateSet, const Matrix3& textureMatrix, const MaterialAlphaMode alphaMode, const Float alphaMask, const Float shininess, const void* const importerState) noexcept: AbstractMaterialData{MaterialType::Phong, AbstractMaterialData::Flag(UnsignedShort(flags)), alphaMode, alphaMask, importerState}, _ambientColor{ambientColor}, _diffuseColor{diffuseColor}, _specularColor{specularColor}, _shininess{shininess} { +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +PhongMaterialData::PhongMaterialData(const Flags flags, const Color4& ambientColor, const UnsignedInt ambientTexture, const UnsignedInt ambientTextureCoordinates, const Color4& diffuseColor, const UnsignedInt diffuseTexture, const UnsignedInt diffuseTextureCoordinates, const Color4& specularColor, const UnsignedInt specularTexture, const UnsignedInt specularTextureCoordinates, const UnsignedInt normalTexture, const UnsignedInt normalTextureCoordinates, const Matrix3& textureMatrix, const MaterialAlphaMode alphaMode, const Float alphaMask, const Float shininess, const void* const importerState) noexcept: MaterialData{MaterialType::Phong, [&](){ + Containers::Array data; + + if(flags & Flag::DoubleSided) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DoubleSided, true); + if(alphaMode == MaterialAlphaMode::Blend) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AlphaBlend, true); + /* Include a mask also if it has a non-default value to stay compatible + with existing behavior */ + if(alphaMode == MaterialAlphaMode::Mask || alphaMask != 0.0f) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AlphaMask, alphaMask); + CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)), - "Trade::PhongMaterialData: texture transformation enabled but the material has no textures", ); + "Trade::PhongMaterialData: texture transformation enabled but the material has no textures", data); CORRADE_ASSERT((flags & Flag::TextureTransformation) || textureMatrix == Matrix3{}, - "PhongMaterialData::PhongMaterialData: non-default texture matrix requires Flag::TextureTransformation to be enabled", ); - CORRADE_ASSERT((flags & Flag::TextureCoordinateSets) || (ambientCoordinateSet == 0 && diffuseCoordinateSet == 0 && specularCoordinateSet == 0 && normalCoordinateSet == 0), - "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinateSets to be enabled", ); + "PhongMaterialData::PhongMaterialData: non-default texture matrix requires Flag::TextureTransformation to be enabled", data); + CORRADE_ASSERT((flags & Flag::TextureCoordinateSets) || (ambientTextureCoordinates == 0 && diffuseTextureCoordinates == 0 && specularTextureCoordinates == 0 && normalTextureCoordinates == 0), + "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinates to be enabled", data); + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientColor, ambientColor); if(flags & Flag::AmbientTexture) { - _ambientTexture = ambientTexture; - _ambientCoordinateSet = ambientCoordinateSet; + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientTexture, ambientTexture); + if(ambientTextureCoordinates) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::AmbientTextureCoordinates, ambientTextureCoordinates); } + + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseColor, diffuseColor); if(flags & Flag::DiffuseTexture) { - _diffuseTexture = diffuseTexture; - _diffuseCoordinateSet = diffuseCoordinateSet; + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseTexture, diffuseTexture); + if(diffuseTextureCoordinates) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::DiffuseTextureCoordinates, diffuseTextureCoordinates); } + + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularColor, specularColor); if(flags & Flag::SpecularTexture) { - _specularTexture = specularTexture; - _specularCoordinateSet = specularCoordinateSet; + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularTexture, specularTexture); + if(specularTextureCoordinates) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::SpecularTextureCoordinates, specularTextureCoordinates); } + if(flags & Flag::NormalTexture) { - _normalTexture = normalTexture; - _normalCoordinateSet = normalCoordinateSet; + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::NormalTexture, normalTexture); + if(normalTextureCoordinates) + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::NormalTextureCoordinates, normalTextureCoordinates); } + if(flags & Flag::TextureTransformation) - _textureMatrix = textureMatrix; + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::TextureMatrix, textureMatrix); + + arrayAppend(data, Containers::InPlaceInit, MaterialAttribute::Shininess, shininess); + + /* Convert back to a non-growable Array as importers don't allow custom + deleters in plugin implementations */ + arrayShrink(data, Containers::DefaultInit); + return data; +}(), importerState} { + /* The data can't be filled here because it won't be sorted correctly */ } PhongMaterialData::PhongMaterialData(const Flags flags, const Color4& ambientColor, const UnsignedInt ambientTexture, const Color4& diffuseColor, const UnsignedInt diffuseTexture, const Color4& specularColor, const UnsignedInt specularTexture, const UnsignedInt normalTexture, const Matrix3& textureMatrix, const MaterialAlphaMode alphaMode, const Float alphaMask, const Float shininess, const void* const importerState) noexcept: PhongMaterialData{flags, ambientColor, ambientTexture, 0, diffuseColor, diffuseTexture, 0, specularColor, specularTexture, 0, normalTexture, 0, textureMatrix, alphaMode, alphaMask, shininess, importerState} {} -#ifdef MAGNUM_BUILD_DEPRECATED PhongMaterialData::PhongMaterialData(const Flags flags, const MaterialAlphaMode alphaMode, const Float alphaMask, const Float shininess, const void* const importerState) noexcept: PhongMaterialData{flags, 0x000000ff_rgbaf, {}, 0xffffffff_rgbaf, {}, 0xffffffff_rgbaf, {}, {}, {}, alphaMode, alphaMask, shininess, importerState} {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +PhongMaterialData::Flags PhongMaterialData::flags() const { + /* "Append" to flags recognized by the base class */ + Flags flags{Flag(UnsignedInt(MaterialData::flags()))}; + + if(hasAttribute(MaterialAttribute::AmbientTexture)) + flags |= Flag::AmbientTexture; + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + flags |= Flag::DiffuseTexture; + if(hasSpecularTexture()) + flags |= Flag::SpecularTexture; + if(hasAttribute(MaterialAttribute::NormalTexture)) + flags |= Flag::NormalTexture; + if(hasTextureTransformation()) + flags |= Flag::TextureTransformation; + if(hasTextureCoordinates()) + flags |= Flag::TextureCoordinates; + + return flags; +} +CORRADE_IGNORE_DEPRECATED_POP #endif -PhongMaterialData::PhongMaterialData(PhongMaterialData&& other) noexcept = default; +bool PhongMaterialData::hasSpecularTexture() const { + return hasAttribute(MaterialAttribute::SpecularTexture) || + hasAttribute(MaterialAttribute::SpecularGlossinessTexture); +} + +bool PhongMaterialData::hasTextureTransformation() const { + return hasAttribute(MaterialAttribute::AmbientTextureMatrix) || + hasAttribute(MaterialAttribute::DiffuseTextureMatrix) || + hasAttribute(MaterialAttribute::SpecularTextureMatrix) || + hasAttribute(MaterialAttribute::NormalTextureMatrix) || + hasAttribute(MaterialAttribute::TextureMatrix); +} + +bool PhongMaterialData::hasCommonTextureTransformation() const { + auto check = [](Containers::Optional& transformation, Matrix3 current) { + if(!transformation) { + transformation = current; + return true; + } + return transformation == current; + }; + + Containers::Optional transformation; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::AmbientTexture) && !check(transformation, ambientTextureMatrix())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasAttribute(MaterialAttribute::DiffuseTexture) && !check(transformation, diffuseTextureMatrix())) + return false; + if(hasSpecularTexture() && !check(transformation, specularTextureMatrix())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(transformation, normalTextureMatrix())) + return false; -PhongMaterialData& PhongMaterialData::operator=(PhongMaterialData&& other) noexcept = default; + return true; +} + +bool PhongMaterialData::hasTextureCoordinates() const { + return attributeOr(MaterialAttribute::AmbientTextureCoordinates, 0u) || + attributeOr(MaterialAttribute::DiffuseTextureCoordinates, 0u) || + attributeOr(MaterialAttribute::SpecularTextureCoordinates, 0u) || + attributeOr(MaterialAttribute::NormalTextureCoordinates, 0u) || + attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +bool PhongMaterialData::hasCommonTextureCoordinates() const { + auto check = [](Containers::Optional& coordinates, UnsignedInt current) { + if(!coordinates) { + coordinates = current; + return true; + } + return coordinates == current; + }; + + Containers::Optional coordinates; + /* First one can't fail */ + if(hasAttribute(MaterialAttribute::AmbientTexture) && !check(coordinates, ambientTextureCoordinates())) + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + if(hasAttribute(MaterialAttribute::DiffuseTexture) && !check(coordinates, diffuseTextureCoordinates())) + return false; + if(hasSpecularTexture() && !check(coordinates, specularTextureCoordinates())) + return false; + if(hasAttribute(MaterialAttribute::NormalTexture) && !check(coordinates, normalTextureCoordinates())) + return false; + + return true; +} + +Color4 PhongMaterialData::ambientColor() const { + return attributeOr(MaterialAttribute::AmbientColor, + hasAttribute(MaterialAttribute::AmbientTexture) ? 0xffffffff_rgbaf : 0x000000ff_rgbaf); +} UnsignedInt PhongMaterialData::ambientTexture() const { - CORRADE_ASSERT(flags() & Flag::AmbientTexture, "Trade::PhongMaterialData::ambientTexture(): the material doesn't have an ambient texture", {}); - return _ambientTexture; + return attribute(MaterialAttribute::AmbientTexture); } -#ifdef MAGNUM_BUILD_DEPRECATED -UnsignedInt& PhongMaterialData::ambientTexture() { - CORRADE_ASSERT(flags() & Flag::AmbientTexture, "Trade::PhongMaterialData::ambientTexture(): the material doesn't have an ambient texture", _ambientTexture); - return _ambientTexture; +Matrix3 PhongMaterialData::ambientTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::AmbientTexture), + "Trade::PhongMaterialData::ambientTextureMatrix(): the material doesn't have an ambient texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::AmbientTextureMatrix)) + return *set; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PhongMaterialData::ambientTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::AmbientTexture), + "Trade::PhongMaterialData::ambientTextureCoordinates(): the material doesn't have an ambient texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::AmbientTextureCoordinates)) + return *set; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); } -#endif -UnsignedInt PhongMaterialData::ambientCoordinateSet() const { - CORRADE_ASSERT(flags() & Flag::AmbientTexture, "Trade::PhongMaterialData::ambientCoordinateSet(): the material doesn't have an ambient texture", {}); - return _ambientCoordinateSet; +Color4 PhongMaterialData::diffuseColor() const { + return attributeOr(MaterialAttribute::DiffuseColor, 0xffffffff_rgbaf); } UnsignedInt PhongMaterialData::diffuseTexture() const { - CORRADE_ASSERT(flags() & Flag::DiffuseTexture, "Trade::PhongMaterialData::diffuseTexture(): the material doesn't have a diffuse texture", {}); - return _diffuseTexture; + return attribute(MaterialAttribute::DiffuseTexture); } -#ifdef MAGNUM_BUILD_DEPRECATED -UnsignedInt& PhongMaterialData::diffuseTexture() { - CORRADE_ASSERT(flags() & Flag::DiffuseTexture, "Trade::PhongMaterialData::diffuseTexture(): the material doesn't have a diffuse texture", _diffuseTexture); - return _diffuseTexture; +Matrix3 PhongMaterialData::diffuseTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::DiffuseTexture), + "Trade::PhongMaterialData::diffuseTextureMatrix(): the material doesn't have a diffuse texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::DiffuseTextureMatrix)) + return *set; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PhongMaterialData::diffuseTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::DiffuseTexture), + "Trade::PhongMaterialData::diffuseTextureCoordinates(): the material doesn't have a diffuse texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::DiffuseTextureCoordinates)) + return *set; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); } -#endif -UnsignedInt PhongMaterialData::diffuseCoordinateSet() const { - CORRADE_ASSERT(flags() & Flag::DiffuseTexture, "Trade::PhongMaterialData::diffuseCoordinateSet(): the material doesn't have a diffuse texture", {}); - return _diffuseCoordinateSet; +Color4 PhongMaterialData::specularColor() const { + return attributeOr(MaterialAttribute::SpecularColor, 0xffffff00_rgbaf); } UnsignedInt PhongMaterialData::specularTexture() const { - CORRADE_ASSERT(flags() & Flag::SpecularTexture, "Trade::PhongMaterialData::specularTexture(): the material doesn't have a specular texture", {}); - return _specularTexture; + /* Explicit assertion because printing that SpecularTexture isn't found + would be misleading as it can be also SpecularGlossinessTexture */ + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PhongMaterialData::specularTexture(): the material doesn't have a specular texture", {}); + if(Containers::Optional value = tryAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return *value; + return attribute(MaterialAttribute::SpecularTexture); } -#ifdef MAGNUM_BUILD_DEPRECATED -UnsignedInt& PhongMaterialData::specularTexture() { - CORRADE_ASSERT(flags() & Flag::SpecularTexture, "Trade::PhongMaterialData::specularTexture(): the material doesn't have a specular texture", _specularTexture); - return _specularTexture; +MaterialTextureSwizzle PhongMaterialData::specularTextureSwizzle() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PhongMaterialData::specularTextureSwizzle(): the material doesn't have a specular texture", {}); + if(hasAttribute(MaterialAttribute::SpecularGlossinessTexture)) + return MaterialTextureSwizzle::RGB; + return attributeOr(MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGB); +} + +Matrix3 PhongMaterialData::specularTextureMatrix() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PhongMaterialData::specularTextureMatrix(): the material doesn't have a specular texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::SpecularTextureMatrix)) + return *set; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); } -#endif -UnsignedInt PhongMaterialData::specularCoordinateSet() const { - CORRADE_ASSERT(flags() & Flag::SpecularTexture, "Trade::PhongMaterialData::specularCoordinateSet(): the material doesn't have a specular texture", {}); - return _specularCoordinateSet; +UnsignedInt PhongMaterialData::specularTextureCoordinates() const { + CORRADE_ASSERT(hasSpecularTexture(), + "Trade::PhongMaterialData::specularTextureCoordinates(): the material doesn't have a specular texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::SpecularTextureCoordinates)) + return *set; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); } UnsignedInt PhongMaterialData::normalTexture() const { - CORRADE_ASSERT(flags() & Flag::NormalTexture, "Trade::PhongMaterialData::normalTexture(): the material doesn't have a normal texture", {}); - return _normalTexture; + return attribute(MaterialAttribute::NormalTexture); +} + +Float PhongMaterialData::normalTextureScale() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PhongMaterialData::normalTextureScale(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureScale, 1.0f); } -UnsignedInt PhongMaterialData::normalCoordinateSet() const { - CORRADE_ASSERT(flags() & Flag::NormalTexture, "Trade::PhongMaterialData::normalCoordinateSet(): the material doesn't have a normal texture", {}); - return _normalCoordinateSet; +MaterialTextureSwizzle PhongMaterialData::normalTextureSwizzle() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PhongMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture", {}); + return attributeOr(MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RGB); } +Matrix3 PhongMaterialData::normalTextureMatrix() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PhongMaterialData::normalTextureMatrix(): the material doesn't have a normal texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::NormalTextureMatrix)) + return *set; + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +UnsignedInt PhongMaterialData::normalTextureCoordinates() const { + CORRADE_ASSERT(hasAttribute(MaterialAttribute::NormalTexture), + "Trade::PhongMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture", {}); + if(Containers::Optional set = tryAttribute(MaterialAttribute::NormalTextureCoordinates)) + return *set; + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Matrix3 PhongMaterialData::commonTextureMatrix() const { + CORRADE_ASSERT(hasCommonTextureTransformation(), + "Trade::PhongMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation", {}); + if(hasAttribute(MaterialAttribute::AmbientTexture)) + return ambientTextureMatrix(); + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + return diffuseTextureMatrix(); + if(hasSpecularTexture()) + return specularTextureMatrix(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureMatrix(); + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +Matrix3 PhongMaterialData::textureMatrix() const { + if(hasCommonTextureTransformation()) return commonTextureMatrix(); + return attributeOr(MaterialAttribute::TextureMatrix, Matrix3{}); +} +#endif + +UnsignedInt PhongMaterialData::commonTextureCoordinates() const { + CORRADE_ASSERT(hasCommonTextureCoordinates(), + "Trade::PhongMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set", {}); + if(hasAttribute(MaterialAttribute::AmbientTexture)) + return ambientTextureCoordinates(); + if(hasAttribute(MaterialAttribute::DiffuseTexture)) + return diffuseTextureCoordinates(); + if(hasSpecularTexture()) + return specularTextureCoordinates(); + if(hasAttribute(MaterialAttribute::NormalTexture)) + return normalTextureCoordinates(); + return attributeOr(MaterialAttribute::TextureCoordinates, 0u); +} + +Float PhongMaterialData::shininess() const { + return attributeOr(MaterialAttribute::Shininess, 80.0f); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH Debug& operator<<(Debug& debug, const PhongMaterialData::Flag value) { debug << "Trade::PhongMaterialData::Flag" << Debug::nospace; @@ -143,7 +368,7 @@ Debug& operator<<(Debug& debug, const PhongMaterialData::Flag value) { _c(SpecularTexture) _c(NormalTexture) _c(TextureTransformation) - _c(TextureCoordinateSets) + _c(TextureCoordinates) #undef _c /* LCOV_EXCL_STOP */ } @@ -159,7 +384,9 @@ Debug& operator<<(Debug& debug, const PhongMaterialData::Flags value) { PhongMaterialData::Flag::SpecularTexture, PhongMaterialData::Flag::NormalTexture, PhongMaterialData::Flag::TextureTransformation, - PhongMaterialData::Flag::TextureCoordinateSets}); + PhongMaterialData::Flag::TextureCoordinates}); } +CORRADE_IGNORE_DEPRECATED_POP +#endif }} diff --git a/src/Magnum/Trade/PhongMaterialData.h b/src/Magnum/Trade/PhongMaterialData.h index 3565b8892d..0d265908fe 100644 --- a/src/Magnum/Trade/PhongMaterialData.h +++ b/src/Magnum/Trade/PhongMaterialData.h @@ -30,28 +30,35 @@ * @brief Class @ref Magnum::Trade::PhongMaterialData */ -#include "Magnum/Magnum.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Math/Matrix3.h" -#include "Magnum/Trade/AbstractMaterialData.h" +#include "Magnum/Trade/MaterialData.h" namespace Magnum { namespace Trade { /** @brief Phong material data -@see @ref AbstractImporter::material() +See @ref Trade-MaterialData-usage-types for more information about how to use +this class. +@see @ref AbstractImporter::material(), @ref FlatMaterialData, + @ref PbrMetallicRoughnessMaterialData, + @ref PbrSpecularGlossinessMaterialData, @ref PbrClearCoatMaterialData */ -class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { +class MAGNUM_TRADE_EXPORT PhongMaterialData: public MaterialData { public: + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Material flag + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided(), @ref hasTextureTransformation(), + * @ref hasTextureCoordinates() etc. * - * A superset of @ref AbstractMaterialData::Flag. + * A superset of @ref MaterialData::Flag. * @see @ref Flags, @ref flags() */ - enum class Flag: UnsignedShort { - /** @copydoc AbstractMaterialData::Flag::DoubleSided */ + enum class CORRADE_DEPRECATED_ENUM("use hasAttribute() etc. instead") Flag: UnsignedInt { + /** @copydoc MaterialData::Flag::DoubleSided */ DoubleSided = 1 << 0, /** The material has an ambient texture */ @@ -77,19 +84,46 @@ class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { /** * The material uses non-default texture coordinate sets - * @m_since{2020,06} + * @m_since_latest */ - TextureCoordinateSets = 1 << 6 + TextureCoordinates = 1 << 6, + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * The material uses non-default texture coordinate sets + * @m_deprecated_since_latest Use @ref Flag::TextureCoordinates + * instead. + */ + TextureCoordinateSets CORRADE_DEPRECATED_ENUM("use Flag::TextureCoordinates instead") = TextureCoordinates + #endif }; /** * @brief Material flags + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided(), @ref hasTextureTransformation(), + * @ref hasTextureCoordinates() etc. * - * A superset of @ref AbstractMaterialData::Flags. + * A superset of @ref MaterialData::Flags. * @see @ref flags() */ - typedef Containers::EnumSet Flags; + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + typedef CORRADE_DEPRECATED("use hasAttribute() etc. instead") Containers::EnumSet Flags; + CORRADE_IGNORE_DEPRECATED_POP + #endif + + #ifndef DOXYGEN_GENERATING_OUTPUT + /* Allow constructing subclasses directly. While not used in the + general Importer workflow, it allows users to create instances with + desired convenience APIs easier (and simplifies testing). It's + however hidden from the docs as constructing instances this way + isn't really common and it would add a lot of noise. */ + using MaterialData::MaterialData; + #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Constructor * @param flags Material flags @@ -98,14 +132,14 @@ class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { * non-textured material and @cpp 0xffffffff_rgbaf @ce for a * default value for a textured material. * @param ambientTexture Ambient texture ID. Ignored if @p flags - * doesn't have @ref Flag::AmbientTexture + * doesn't have @ref Flag::AmbientTexture. * @param diffuseColor Diffuse color. Use * @cpp 0xffffffff_rgbaf @ce for a default value for both a * non-textured and a textured material. * @param diffuseTexture Diffuse texture ID. Ignored if @p flags - * doesn't have @ref Flag::DiffuseTexture + * doesn't have @ref Flag::DiffuseTexture. * @param specularColor Specular color. Use - * @cpp 0xffffffff_rgbaf @ce for a default value for both a + * @cpp 0xffffff00_rgbaf @ce for a default value for both a * non-textured and a textured material. * @param specularTexture Specular texture ID. Ignored if @p flags * doesn't have @ref Flag::SpecularTexture. @@ -119,13 +153,19 @@ class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { * @param shininess Shininess. Use @cpp 80.0f @ce for a default * value. * @param importerState Importer-specific state - * @m_since{2020,06} * - * All `*CoordinateSet` accessors are implicitly zero with this + * All `*TextureCoordinates()` accessors are implicitly zero with this * constructor. If @p textureMatrix is not default-constructed, expects * @ref Flag::TextureTransformation to be enabled as well. + * + * @m_deprecated_since_latest Populate a @ref MaterialData instance + * using @ref MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) + * instead. This class is not meant to be constructed directly + * anymore. */ - explicit PhongMaterialData(Flags flags, const Color4& ambientColor, UnsignedInt ambientTexture, const Color4& diffuseColor, UnsignedInt diffuseTexture, const Color4& specularColor, UnsignedInt specularTexture, UnsignedInt normalTexture, const Matrix3& textureMatrix, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + explicit CORRADE_DEPRECATED("use MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) instead") PhongMaterialData(Flags flags, const Color4& ambientColor, UnsignedInt ambientTexture, const Color4& diffuseColor, UnsignedInt diffuseTexture, const Color4& specularColor, UnsignedInt specularTexture, UnsignedInt normalTexture, const Matrix3& textureMatrix, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Construct with non-zero texture coordinate sets @@ -136,26 +176,26 @@ class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { * default value for a textured material. * @param ambientTexture Ambient texture ID. Ignored if @p flags * doesn't have @ref Flag::AmbientTexture. - * @param ambientCoordinateSet Ambient texture coordinate set. Ignored - * if @p flags doesn't have @ref Flag::AmbientTexture + * @param ambientTextureCoordinates Ambient texture coordinate set. + * Ignored if @p flags doesn't have @ref Flag::AmbientTexture * @param diffuseColor Diffuse color. Use * @cpp 0xffffffff_rgbaf @ce for a default value for both a * non-textured and a textured material. * @param diffuseTexture Diffuse texture ID. Ignored if @p flags * doesn't have @ref Flag::DiffuseTexture. - * @param diffuseCoordinateSet Diffuse texture coordinate set. Ignored - * if @p flags doesn't have @ref Flag::DiffuseTexture + * @param diffuseTextureCoordinates Diffuse texture coordinate set. + * Ignored if @p flags doesn't have @ref Flag::DiffuseTexture * @param specularColor Specular color. Use - * @cpp 0xffffffff_rgbaf @ce for a default value for both a + * @cpp 0xffffff00_rgbaf @ce for a default value for both a * non-textured and a textured material. * @param specularTexture Specular texture ID. Ignored if * @p flags doesn't have @ref Flag::SpecularTexture. - * @param specularCoordinateSet Specular texture coordinate set. + * @param specularTextureCoordinates Specular texture coordinate set. * Ignored if @p flags doesn't have @ref Flag::SpecularTexture. * @param normalTexture Normal texture ID. Ignored if @p flags * doesn't have @ref Flag::NormalTexture. - * @param normalCoordinateSet Normal texture coordinate set. Ignored - * if @p flags doesn't have @ref Flag::NormalTexture. + * @param normalTextureCoordinates Normal texture coordinate set. + * Ignored if @p flags doesn't have @ref Flag::NormalTexture. * @param textureMatrix Texture coordinate transformation * @param alphaMode Alpha mode. Use * @ref MaterialAlphaMode::Opaque for a default value. @@ -164,239 +204,445 @@ class MAGNUM_TRADE_EXPORT PhongMaterialData: public AbstractMaterialData { * @param shininess Shininess. Use @cpp 80.0f @ce for a * default value. * @param importerState Importer-specific state - * @m_since{2020,06} * * If @p textureMatrix is not default-constructed, expects * @ref Flag::TextureTransformation to be enabled as well. If any - * `*CoordinateSet` is non-zero, expects @ref Flag::TextureCoordinateSets - * to be enabled as well. + * `*Coordinates` parameter is non-zero, expects + * @ref Flag::TextureCoordinates to be enabled as well. + * + * @m_deprecated_since_latest Populate a @ref MaterialData instance + * using @ref MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) + * instead. This class is not meant to be constructed directly + * anymore. */ - explicit PhongMaterialData(Flags flags, const Color4& ambientColor, UnsignedInt ambientTexture, UnsignedInt ambientCoordinateSet, const Color4& diffuseColor, UnsignedInt diffuseTexture, UnsignedInt diffuseCoordinateSet, const Color4& specularColor, UnsignedInt specularTexture, UnsignedInt specularCoordinateSet, UnsignedInt normalTexture, UnsignedInt normalCoordinateSet, const Matrix3& textureMatrix, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + explicit CORRADE_DEPRECATED("use MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) instead") PhongMaterialData(Flags flags, const Color4& ambientColor, UnsignedInt ambientTexture, UnsignedInt ambientTextureCoordinates, const Color4& diffuseColor, UnsignedInt diffuseTexture, UnsignedInt diffuseTextureCoordinates, const Color4& specularColor, UnsignedInt specularTexture, UnsignedInt specularTextureCoordinates, UnsignedInt normalTexture, UnsignedInt normalTextureCoordinates, const Matrix3& textureMatrix, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_POP - #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Constructor - * @m_deprecated_since{2020,06} Use @ref PhongMaterialData(Flags, const Color4&, UnsignedInt, const Color4&, UnsignedInt, const Color4&, UnsignedInt, UnsignedInt, const Matrix3&, MaterialAlphaMode, Float, Float, const void*) - * instead. + * @m_deprecated_since{2020,06} Populate a @ref MaterialData instance + * using @ref MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) + * instead. This class is not meant to be constructed directly + * anymore. */ - explicit CORRADE_DEPRECATED("use PhongMaterialData(Flags, const Color4&, UnsignedInt, const Color4&, UnsignedInt, const Color4&, UnsignedInt, UnsignedInt, const Matrix3&, MaterialAlphaMode, Float, Float, const void*) instead") PhongMaterialData(Flags flags, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + explicit CORRADE_DEPRECATED("use MaterialData::MaterialData(MaterialTypes, Containers::Array&&, const void*) instead") PhongMaterialData(Flags flags, MaterialAlphaMode alphaMode, Float alphaMask, Float shininess, const void* importerState = nullptr) noexcept; + CORRADE_IGNORE_DEPRECATED_POP #endif - /** @brief Copying is not allowed */ - PhongMaterialData(const PhongMaterialData&) = delete; - - /** @brief Move constructor */ - PhongMaterialData(PhongMaterialData&& other) noexcept; + /** + * @brief Whether the material has a specular texture + * @m_since_latest + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::SpecularTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture attributes is + * present, @cpp false @ce otherwise. + */ + bool hasSpecularTexture() const; - /** @brief Copying is not allowed */ - PhongMaterialData& operator=(const PhongMaterialData&) = delete; + /** + * @brief Whether the material has texture transformation + * @m_since_latest + * + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::AmbientTextureMatrix, + * @ref MaterialAttribute::DiffuseTextureMatrix, + * @ref MaterialAttribute::SpecularTextureMatrix, + * @ref MaterialAttribute::NormalTextureMatrix or + * @ref MaterialAttribute::TextureMatrix attributes is present, + * @cpp false @ce otherwise. + * @see @ref hasCommonTextureTransformation() + */ + bool hasTextureTransformation() const; - /** @brief Move assignment */ - PhongMaterialData& operator=(PhongMaterialData&& other) noexcept; + /** + * @brief Whether the material has a common transformation for all textures + * @m_since_latest + * + * Returns @cpp true @ce if, for each texture that is present, + * @ref ambientTextureMatrix(), @ref diffuseTextureMatrix(), + * @ref specularTextureMatrix() and @ref normalTextureMatrix() have the + * same value, @cpp false @ce otherwise. In particular, returns + * @cpp true @ce also if there's no texture transformation at all. Use + * @ref hasTextureTransformation() to distinguish that case. + */ + bool hasCommonTextureTransformation() const; /** - * @brief Material flags + * @brief Whether the material uses extra texture coordinate sets + * @m_since_latest * - * A superset of @ref AbstractMaterialData::flags(). + * Returns @cpp true @ce if any of the + * @ref MaterialAttribute::AmbientTextureCoordinates, + * @ref MaterialAttribute::DiffuseTextureCoordinates, + * @ref MaterialAttribute::SpecularTextureCoordinates, + * @ref MaterialAttribute::NormalTextureCoordinates or + * @ref MaterialAttribute::TextureCoordinates attributes is present and + * has a non-zero value, @cpp false @ce otherwise. + * @see @ref hasCommonTextureCoordinates() */ - Flags flags() const { - return Flag(UnsignedShort(AbstractMaterialData::flags())); - } + bool hasTextureCoordinates() const; /** - * @brief Ambient color + * @brief Whether the material has a common coordinate set for all textures + * @m_since_latest * - * If the material has @ref Flag::AmbientTexture, the color and texture - * is multiplied together. - * @see @ref flags() + * Returns @cpp true @ce if, for each texture that is present, + * @ref ambientTextureCoordinates(), @ref diffuseTextureCoordinates(), + * @ref specularTextureCoordinates() and @ref normalTextureCoordinates() + * have the same value, @cpp false @ce otherwise. In particular, + * returns @cpp true @ce also if there's no extra texture coordinate + * set used at all. Use @ref hasTextureCoordinates() to distinguish + * that case. */ - Color4 ambientColor() const { return _ambientColor; } + bool hasCommonTextureCoordinates() const; #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead. + * @brief Material flags + * @m_deprecated_since_latest The flags are no longer stored directly + * but generated on-the-fly from attribute data, which makes them + * less efficient than calling @ref hasAttribute(), + * @ref isDoubleSided(), @ref hasTextureTransformation(), + * @ref hasTextureCoordinates() etc. + * + * A superset of @ref MaterialData::flags(). */ - /* Not marked with CORRADE_DEPRECATED() because the compiler picks this - overload over the const one for a non-const instance and there's no - way to selectively enable deprecation warnings only if the code - attempts to modify the value. */ - Color4& ambientColor() { return _ambientColor; } + CORRADE_IGNORE_DEPRECATED_PUSH /* GCC warns about Flags, ugh */ + CORRADE_DEPRECATED("use hasAttribute() instead") Flags flags() const; + CORRADE_IGNORE_DEPRECATED_POP #endif /** - * @brief Ambient texture ID + * @brief Ambient color + * + * Convenience access to the @ref MaterialAttribute::AmbientColor + * attribute. If not present, the default is either + * @cpp 0xffffffff_rgbaf @ce if there's + * @ref MaterialAttribute::AmbientTexture and @cpp 0x000000ff_rgbaf @ce + * otherwise. * - * Available only if the material has @ref Flag::AmbientTexture. - * Multiplied with @ref ambientColor(). - * @see @ref flags(), @ref AbstractImporter::texture() + * If the material has @ref MaterialAttribute::AmbientTexture, the + * color and texture is meant to be multiplied together. + * @see @ref hasAttribute() */ - UnsignedInt ambientTexture() const; + Color4 ambientColor() const; - #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead of modifying the instance afterwards. + * @brief Ambient texture ID + * + * Available only if @ref MaterialAttribute::AmbientTexture is + * present. Meant to be multiplied with @ref ambientColor(). + * @see @ref hasAttribute() */ - /* Not using CORRADE_DEPRECATED(), see why above */ - UnsignedInt& ambientTexture(); - #endif + UnsignedInt ambientTexture() const; /** - * @brief Ambient texture coordinate set - * @m_since{2020,06} + * @brief Ambient texture coordinate transformation matrix + * @m_since_latest * - * Available only if the material has @ref Flag::AmbientTexture. - * @see @ref flags(), @ref AbstractImporter::texture() + * Convenience access to the @ref MaterialAttribute::AmbientTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::AmbientTexture. + * @see @ref hasAttribute() */ - UnsignedInt ambientCoordinateSet() const; + Matrix3 ambientTextureMatrix() const; /** - * @brief Diffuse color + * @brief Ambient texture coordinate set + * @m_since_latest * - * If the material has @ref Flag::DiffuseTexture, the color and texture - * is multiplied together. - * @see @ref flags() + * Convenience access to the @ref MaterialAttribute::AmbientTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::AmbientTexture. + * @see @ref hasAttribute() */ - Color4 diffuseColor() const { return _diffuseColor; } + UnsignedInt ambientTextureCoordinates() const; #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead of modifying the instance afterwards. + * @brief @copybrief ambientTextureCoordinates() + * @m_deprecated_since_latest Use @ref ambientTextureCoordinates() + * instead. */ - /* Not using CORRADE_DEPRECATED(), see why above */ - Color4& diffuseColor() { return _diffuseColor; } + CORRADE_DEPRECATED("use ambientTextureCoordinates() instead") UnsignedInt ambientCoordinateSet() const { + return ambientTextureCoordinates(); + } #endif /** - * @brief Diffuse texture ID + * @brief Diffuse color + * + * Convenience access to the @ref MaterialAttribute::DiffuseColor + * attribute. If not present, the default is @cpp 0xffffffff_rgbaf @ce. * - * Available only if the material has @ref Flag::DiffuseTexture. - * Multiplied with @ref diffuseColor(). - * @see @ref flags(), @ref AbstractImporter::texture() + * If the material has @ref MaterialAttribute::DiffuseTexture, the + * color and texture is meant to be multiplied together. + * @see @ref hasAttribute() */ - UnsignedInt diffuseTexture() const; + Color4 diffuseColor() const; - #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead of modifying the instance afterwards. + * @brief Diffuse texture ID + * + * Available only if @ref MaterialAttribute::DiffuseTexture is + * present. Meant to be multiplied with @ref diffuseColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() */ - /* Not using CORRADE_DEPRECATED(), see why above */ - UnsignedInt& diffuseTexture(); - #endif + UnsignedInt diffuseTexture() const; /** - * @brief Diffuse texture coordinate set - * @m_since{2020,06} + * @brief Diffuse texture coordinate transformation matrix + * @m_since_latest * - * Available only if the material has @ref Flag::DiffuseTexture. - * @see @ref flags(), @ref AbstractImporter::texture() + * Convenience access to the @ref MaterialAttribute::DiffuseTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::DiffuseTexture. + * @see @ref hasAttribute() */ - UnsignedInt diffuseCoordinateSet() const; + Matrix3 diffuseTextureMatrix() const; /** - * @brief Specular color + * @brief Diffuse texture coordinate set + * @m_since_latest * - * If the material has @ref Flag::SpecularTexture, the color and - * texture is multiplied together. - * @see @ref flags() + * Convenience access to the @ref MaterialAttribute::DiffuseTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::DiffuseTexture. + * @see @ref hasAttribute() */ - Color4 specularColor() const { return _specularColor; } + UnsignedInt diffuseTextureCoordinates() const; #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead of modifying the instance afterwards. + * @brief @copybrief diffuseTextureCoordinates() + * @m_deprecated_since_latest Use @ref diffuseTextureCoordinates() + * instead. */ - /* Not using CORRADE_DEPRECATED(), see why above */ - Color4& specularColor() { return _specularColor; } + CORRADE_DEPRECATED("use diffuseTextureCoordinates() instead") UnsignedInt diffuseCoordinateSet() const { + return diffuseTextureCoordinates(); + } #endif + /** + * @brief Specular color + * + * Convenience access to the @ref MaterialAttribute::SpecularColor + * attribute. If not present, the default is @cpp 0xffffff00_rgbaf @ce. + * + * If the material has a specular texture, the color and texture is + * meant to be multiplied together. + * @see @ref hasSpecularTexture() + */ + Color4 specularColor() const; + /** * @brief Specular texture ID * - * Available only if the material has @ref Flag::SpecularTexture. - * Multiplied with @ref specularColor(). - * @see @ref flags(), @ref AbstractImporter::texture() + * Available only if either @ref MaterialAttribute::SpecularTexture or + * @ref MaterialAttribute::SpecularGlossinessTexture is present. Meant + * to be multiplied with @ref specularColor(). + * @see @ref hasAttribute(), @ref AbstractImporter::texture() */ UnsignedInt specularTexture() const; - #ifdef MAGNUM_BUILD_DEPRECATED /** - * @m_deprecated_since{2020,06} Use the constructor to populate all - * values instead of modifying the instance afterwards. + * @brief Specular texture swizzle + * @m_since_latest + * + * If @ref MaterialAttribute::SpecularGlossinessTexture is present, + * returns always @ref MaterialTextureSwizzle::RGB. Otherwise returns + * the @ref MaterialAttribute::SpecularTextureSwizzle attribute, or + * @ref MaterialTextureSwizzle::RGB if it's not present. Available only + * if the material has a specular texture. + * @see @ref hasSpecularTexture() */ - /* Not using CORRADE_DEPRECATED(), see why above */ - UnsignedInt& specularTexture(); - #endif + MaterialTextureSwizzle specularTextureSwizzle() const; + + /** + * @brief Specular texture coordinate transformation matrix + * @m_since_latest + * + * Convenience access to the @ref MaterialAttribute::SpecularTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has a specular texture. + * @see @ref hasSpecularTexture() + */ + Matrix3 specularTextureMatrix() const; /** * @brief Specular texture coordinate set - * @m_since{2020,06} + * @m_since_latest * - * Available only if the material has @ref Flag::SpecularTexture. - * @see @ref flags(), @ref AbstractImporter::texture() + * Convenience access to the @ref MaterialAttribute::SpecularTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has a specular texture. + * @see @ref hasSpecularTexture() + */ + UnsignedInt specularTextureCoordinates() const; + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief @copybrief specularTextureCoordinates() + * @m_deprecated_since_latest Use @ref specularTextureCoordinates() + * instead. */ - UnsignedInt specularCoordinateSet() const; + CORRADE_DEPRECATED("use specularTextureCoordinates() instead") UnsignedInt specularCoordinateSet() const { + return specularTextureCoordinates(); + } + #endif /** * @brief Normal texture ID * @m_since{2020,06} * - * Available only if the material has @ref Flag::NormalTexture. - * @see @ref flags(), @ref AbstractImporter::texture() + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute(), @ref AbstractImporter::texture() */ UnsignedInt normalTexture() const; + /** + * @brief Normal texture scale + * @m_since_latest + * + * Convenience access to the @ref MaterialAttribute::NormalTextureScale + * attribute. If not present, the default is @cpp 1.0f @ce. + * Available only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + Float normalTextureScale() const; + + /** + * @brief Normal texture swizzle + * @m_since_latest + * + * Convenience access to the + * @ref MaterialAttribute::NormalTextureSwizzle attribute. If not + * present, the default is @ref MaterialTextureSwizzle::RGB. Available + * only if @ref MaterialAttribute::NormalTexture is present. + * @see @ref hasAttribute() + */ + MaterialTextureSwizzle normalTextureSwizzle() const; + + /** + * @brief Normal texture coordinate transformation matrix + * @m_since_latest + * + * Convenience access to the @ref MaterialAttribute::NormalTextureMatrix + * / @ref MaterialAttribute::TextureMatrix attributes. If neither is + * present, the default is an identity matrix. Available only if the + * material has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute() + */ + Matrix3 normalTextureMatrix() const; + /** * @brief Normal texture coordinate set - * @m_since{2020,06} + * @m_since_latest * - * Available only if the material has @ref Flag::NormalTexture. - * @see @ref flags(), @ref AbstractImporter::texture() + * Convenience access to the @ref MaterialAttribute::NormalTextureCoordinates + * / @ref MaterialAttribute::TextureCoordinates attributes. If neither + * is present, the default is @cpp 0 @ce. Available only if the + * material has @ref MaterialAttribute::NormalTexture. + * @see @ref hasAttribute() */ - UnsignedInt normalCoordinateSet() const; + UnsignedInt normalTextureCoordinates() const; + #ifdef MAGNUM_BUILD_DEPRECATED /** - * @brief Texture coordinate transformation matrix - * @m_since{2020,06} + * @brief @copybrief normalTextureCoordinates() + * @m_deprecated_since_latest Use @ref normalTextureCoordinates() + * instead. + */ + CORRADE_DEPRECATED("use normalTextureCoordinates() instead") UnsignedInt normalCoordinateSet() const { + return normalTextureCoordinates(); + } + #endif + + /** + * @brief Common texture coordinate transformation matrix for all textures + * @m_since_latest * - * If the material doesn't have @ref Flag::TextureTransformation, - * returns an identity matrix. - * @see @ref flags() + * Expects that @ref hasCommonTextureTransformation() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref ambientTextureMatrix(), @ref diffuseTextureMatrix(), + * @ref specularTextureMatrix() and @ref normalTextureMatrix() where a + * texture is present. If no texture is present, returns an identity + * matrix. + */ + Matrix3 commonTextureMatrix() const; + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Common texture coordinate transformation matrix for all textures + * @m_deprecated_since_latest Because the material may now also define + * per-texture transformations, which take precedence over the + * common one, this value is misleading. Use either + * @ref commonTextureMatrix() or separate + * @ref ambientTextureMatrix(), @ref diffuseTextureMatrix(), + * @ref specularTextureMatrix() and @ref normalTextureMatrix() + * accessors instead. + */ + CORRADE_DEPRECATED("use commonTextureMatrix() or per-texture accessors instead") Matrix3 textureMatrix() const; + #endif + + /** + * @brief Common texture coordinate set index for all textures + * @m_since_latest + * + * Expects that @ref hasCommonTextureCoordinates() is @cpp true @ce; + * returns a coordinate set index that's the same for all of + * @ref ambientTextureCoordinates(), @ref diffuseTextureCoordinates(), + * @ref specularTextureCoordinates() and @ref normalTextureCoordinates() + * where a texture is present. If no texture is present, returns + * @cpp 0 @ce. + */ + UnsignedInt commonTextureCoordinates() const; + + /** + * @brief Shininess + * + * Convenience access to the @ref MaterialAttribute::Shininess + * attribute. If not present, the default is @cpp 80.0f @ce. */ - Matrix3 textureMatrix() const { return _textureMatrix; } - - /** @brief Shininess */ - Float shininess() const { return _shininess; } - - private: - /* Initializing texture IDs to insane values to make accidents worse - and thus better noticeable */ - Color4 _ambientColor; - UnsignedInt _ambientTexture{~UnsignedInt{}}; - UnsignedInt _ambientCoordinateSet; - Color4 _diffuseColor; - UnsignedInt _diffuseTexture{~UnsignedInt{}}; - UnsignedInt _diffuseCoordinateSet; - Color4 _specularColor; - UnsignedInt _specularTexture{~UnsignedInt{}}; - UnsignedInt _specularCoordinateSet; - UnsignedInt _normalTexture{~UnsignedInt{}}; - UnsignedInt _normalCoordinateSet; - Matrix3 _textureMatrix; - Float _shininess; + Float shininess() const; }; +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ENUMSET_OPERATORS(PhongMaterialData::Flags) -/** @debugoperatorclassenum{PhongMaterialData,PhongMaterialData::Flag} */ +/** +@debugoperatorclassenum{PhongMaterialData,PhongMaterialData::Flag} +@m_deprecated_since_latest The flags are no longer stored directly but + generated on-the-fly from attribute data, which makes them less efficient + than calling @ref MaterialData::hasAttribute(), + @ref MaterialData::isDoubleSided(), + @ref PhongMaterialData::hasTextureTransformation(), + @ref PhongMaterialData::hasTextureCoordinates() etc. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, PhongMaterialData::Flag value); -/** @debugoperatorclassenum{PhongMaterialData,PhongMaterialData::Flags} */ +/** +@debugoperatorclassenum{PhongMaterialData,PhongMaterialData::Flags} +@m_deprecated_since_latest The flags are no longer stored directly but + generated on-the-fly from attribute data, which makes them less efficient + than calling @ref MaterialData::hasAttribute(), + @ref MaterialData::isDoubleSided(), + @ref PhongMaterialData::hasTextureTransformation(), + @ref PhongMaterialData::hasTextureCoordinates() etc. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, PhongMaterialData::Flags value); +CORRADE_IGNORE_DEPRECATED_POP +#endif }} diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index d71d542109..4a69917d1e 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -189,10 +190,16 @@ struct AbstractImporterTest: TestSuite::Tester { #endif void material(); + #ifdef MAGNUM_BUILD_DEPRECATED + void materialDeprecatedFallback(); + #endif void materialNameNotImplemented(); void materialNameOutOfRange(); void materialNotImplemented(); void materialOutOfRange(); + void materialNonOwningDeleters(); + void materialCustomAttributeDataDeleter(); + void materialCustomLayerDataDeleter(); void texture(); void textureNameNotImplemented(); @@ -391,10 +398,16 @@ AbstractImporterTest::AbstractImporterTest() { #endif &AbstractImporterTest::material, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImporterTest::materialDeprecatedFallback, + #endif &AbstractImporterTest::materialNameNotImplemented, &AbstractImporterTest::materialNameOutOfRange, &AbstractImporterTest::materialNotImplemented, &AbstractImporterTest::materialOutOfRange, + &AbstractImporterTest::materialNonOwningDeleters, + &AbstractImporterTest::materialCustomAttributeDataDeleter, + &AbstractImporterTest::materialCustomLayerDataDeleter, &AbstractImporterTest::texture, &AbstractImporterTest::textureNameNotImplemented, @@ -2989,12 +3002,8 @@ void AbstractImporterTest::material() { if(id == 7) return "eighth"; else return {}; } - Containers::Pointer doMaterial(UnsignedInt id) override { - if(id == 7) return Containers::pointer(new PhongMaterialData{{}, - {}, {}, - {}, {}, - {}, {}, {}, {}, - {}, {}, {}, &state}); + Containers::Optional doMaterial(UnsignedInt id) override { + if(id == 7) return Containers::optional(MaterialTypes{}, nullptr, &state); else return {}; } } importer; @@ -3013,6 +3022,49 @@ void AbstractImporterTest::material() { CORRADE_COMPARE(data->importerState(), &state); } } + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImporterTest::materialDeprecatedFallback() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 8; } + Int doMaterialForName(const std::string&) override { return 0; } + /* Using a deprecated PhongMaterialData constructor to verify that + propagating such instance works as well (array deleters etc.) */ + Containers::Optional doMaterial(UnsignedInt) override { + CORRADE_IGNORE_DEPRECATED_PUSH + return Containers::Optional{std::move(PhongMaterialData{{}, + {}, {}, + {}, {}, + {}, {}, + {}, + {}, {}, {}, {}, &state + })}; + CORRADE_IGNORE_DEPRECATED_POP + } + } importer; + + { + CORRADE_IGNORE_DEPRECATED_PUSH + Containers::Pointer data = importer.material(0); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_VERIFY(data); + CORRADE_COMPARE_AS(data->attributeCount(), 0, TestSuite::Compare::Greater); + CORRADE_COMPARE(data->importerState(), &state); + } { + CORRADE_IGNORE_DEPRECATED_PUSH + Containers::Pointer data = importer.material(""); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_VERIFY(data); + CORRADE_COMPARE_AS(data->attributeCount(), 0, TestSuite::Compare::Greater); + CORRADE_COMPARE(data->importerState(), &state); + } +} +#endif + void AbstractImporterTest::materialNameNotImplemented() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3085,6 +3137,89 @@ void AbstractImporterTest::materialOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::material(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::materialNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 1; } + Containers::Optional doMaterial(UnsignedInt) override { + return MaterialData{{}, {}, attributeData, {}, layerData}; + } + + UnsignedInt layerData[1]{}; + MaterialAttributeData attributeData[1]{ + {MaterialAttribute::DiffuseColor, Color4{}} + }; + } importer; + + auto data = importer.material(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->attributeData()), importer.attributeData); + CORRADE_COMPARE(static_cast(data->layerData()), importer.layerData); +} + +void AbstractImporterTest::materialCustomAttributeDataDeleter() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 1; } + Int doMaterialForName(const std::string&) override { return 0; } + Containers::Optional doMaterial(UnsignedInt) override { + return MaterialData{{}, Containers::Array{attributeData, 1, [](MaterialAttributeData*, std::size_t) {}}}; + } + + MaterialAttributeData attributeData[1]{ + {MaterialAttribute::DiffuseColor, Color4{}} + }; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.material(0); + importer.material(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::material(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::material(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::materialCustomLayerDataDeleter() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMaterialCount() const override { return 1; } + Int doMaterialForName(const std::string&) override { return 0; } + Containers::Optional doMaterial(UnsignedInt) override { + return MaterialData{{}, nullptr, Containers::Array{layerData, 1, [](UnsignedInt*, std::size_t) {}}}; + } + + UnsignedInt layerData[1]{}; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.material(0); + importer.material(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::material(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::material(): implementation is not allowed to use a custom Array deleter\n"); +} + void AbstractImporterTest::texture() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 38b91579f2..d04cd597b4 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -60,6 +60,7 @@ corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) set_property(TARGET TradeAnimationDataTest + TradeMaterialDataTest TradeMeshDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") diff --git a/src/Magnum/Trade/Test/MaterialDataTest.cpp b/src/Magnum/Trade/Test/MaterialDataTest.cpp index 26ecfd31c6..56cb0485bc 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -23,10 +23,22 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include +#include #include +#include #include +#include +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Trade/FlatMaterialData.h" +#include "Magnum/Trade/MaterialData.h" +#include "Magnum/Trade/PbrClearCoatMaterialData.h" +#include "Magnum/Trade/PbrMetallicRoughnessMaterialData.h" +#include "Magnum/Trade/PbrSpecularGlossinessMaterialData.h" #include "Magnum/Trade/PhongMaterialData.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -35,332 +47,4943 @@ class MaterialDataTest: public TestSuite::Tester { public: explicit MaterialDataTest(); - void constructPhong(); - void constructPhongTextured(); - void constructPhongTexturedTextureTransform(); - void constructPhongTexturedCoordinateSets(); - void constructPhongTextureTransformNoTextures(); - void constructPhongNoTextureTransformationFlag(); - void constructPhongNoTextureCoordinateSetsFlag(); + void textureSwizzleComponentCount(); + void attributeTypeSize(); + void attributeTypeSizeInvalid(); + + void attributeMap(); + void layerMap(); + + void constructAttributeDefault(); + void constructAttributeString(); + void constructAttributeName(); + void constructAttributeTypeErasedString(); + void constructAttributeTypeErasedName(); + + template void constructAttributeStringConstexpr(); + + void constructAttributePointer(); + void constructAttributeMutablePointer(); + void constructAttributeStringNameStringValue(); + void constructAttributeNameStringValue(); + void constructAttributeTextureSwizzle(); + void constructAttributeLayer(); + + void constructAttributeInvalidName(); + void constructAttributeInvalidLayerName(); + void constructAttributeWrongTypeForName(); + void constructAttributeInvalidType(); + void constructAttributeTooLarge(); + void constructAttributeTooLargeString(); + void constructAttributeTooLargeNameString(); + void constructAttributeWrongAccessType(); + void constructAttributeWrongAccessPointerType(); + void constructAttributeWrongAccessTypeString(); + + void construct(); + void constructEmptyAttribute(); + void constructDuplicateAttribute(); + void constructFromImmutableSortedArray(); + + void constructLayers(); + void constructLayersNotMonotonic(); + void constructLayersOffsetOutOfBounds(); + + void constructNonOwned(); + void constructNonOwnedLayers(); + void constructNonOwnedEmptyAttribute(); + void constructNonOwnedNotSorted(); + void constructNonOwnedDuplicateAttribute(); + void constructNonOwnedLayersNotMonotonic(); + void constructNonOwnedLayersOffsetOutOfBounds(); + void constructCopy(); - void constructMovePhong(); + void constructMove(); + + void as(); + void asRvalue(); + + void access(); + void accessPointer(); + void accessString(); + void accessTextureSwizzle(); + void accessOptional(); + void accessOutOfBounds(); + void accessNotFound(); + void accessInvalidAttributeName(); + void accessWrongType(); + void accessWrongPointerType(); + void accessWrongTypeString(); + + void accessLayers(); + void accessLayersDefaults(); + void accessLayersTextured(); + void accessLayersTexturedDefault(); + void accessLayersTexturedSingleMatrixCoordinates(); + void accessLayersTexturedBaseMaterialMatrixCoordinates(); + void accessLayersInvalidTextures(); + + void accessLayerLayerNameInBaseMaterial(); + void accessLayerEmptyLayer(); + void accessLayerIndexOptional(); + void accessLayerNameOptional(); + void accessLayerStringOptional(); + void accessLayerOutOfBounds(); + void accessLayerNotFound(); + void accessInvalidLayerName(); + void accessOutOfBoundsInLayerIndex(); + void accessOutOfBoundsInLayerString(); + void accessNotFoundInLayerIndex(); + void accessNotFoundInLayerString(); + + void releaseAttributes(); + void releaseLayers(); + + #ifdef MAGNUM_BUILD_DEPRECATED + void constructPhongDeprecated(); + void constructPhongDeprecatedTextured(); + void constructPhongDeprecatedTexturedTextureTransform(); + void constructPhongDeprecatedTexturedCoordinates(); + void constructPhongDeprecatedTextureTransformNoTextures(); + void constructPhongDeprecatedNoTextureTransformationFlag(); + void constructPhongDeprecatedNoTextureCoordinatesFlag(); + #endif + + void pbrMetallicRoughnessAccess(); + void pbrMetallicRoughnessAccessDefaults(); + void pbrMetallicRoughnessAccessTextured(); + void pbrMetallicRoughnessAccessTexturedDefaults(); + void pbrMetallicRoughnessAccessTexturedImplicitPackedMetallicRoughness(); + void pbrMetallicRoughnessAccessTexturedExplicitPackedMetallicRoughness(); + void pbrMetallicRoughnessAccessTexturedExplicitPackedRoughnessMetallicOcclusion(); + void pbrMetallicRoughnessAccessTexturedExplicitPackedOcclusionRoughnessMetallic(); + void pbrMetallicRoughnessAccessTexturedExplicitPackedNormalRoughnessMetallic(); + void pbrMetallicRoughnessAccessTexturedSingleMatrixCoordinates(); + void pbrMetallicRoughnessAccessInvalidTextures(); + void pbrMetallicRoughnessAccessCommonTransformationCoordinatesNoTextures(); + void pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneTexture(); + void pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneDifferentTexture(); + void pbrMetallicRoughnessAccessNoCommonTransformationCoordinates(); - void accessInvalidTextures(); + void pbrSpecularGlossinessAccess(); + void pbrSpecularGlossinessAccessDefaults(); + void pbrSpecularGlossinessAccessTextured(); + void pbrSpecularGlossinessAccessTexturedDefaults(); + void pbrSpecularGlossinessAccessTexturedImplicitPackedSpecularGlossiness(); + void pbrSpecularGlossinessAccessTexturedExplicitPackedSpecularGlossiness(); + void pbrSpecularGlossinessAccessTexturedSingleMatrixCoordinates(); + void pbrSpecularGlossinessAccessInvalidTextures(); + void pbrSpecularGlossinessAccessCommonTransformationCoordinatesNoTextures(); + void pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneTexture(); + void pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneDifferentTexture(); + void pbrSpecularGlossinessAccessNoCommonTransformationCoordinates(); + + void phongAccess(); + void phongAccessDefaults(); + void phongAccessTextured(); + void phongAccessTexturedDefaults(); + void phongAccessTexturedSingleMatrixCoordinates(); + void phongAccessTexturedImplicitPackedSpecularGlossiness(); + void phongAccessInvalidTextures(); + void phongAccessCommonTransformationCoordinatesNoTextures(); + void phongAccessCommonTransformationCoordinatesOneTexture(); + void phongAccessCommonTransformationCoordinatesOneDifferentTexture(); + void phongAccessNoCommonTransformationCoordinates(); + + void flatAccessBaseColor(); + void flatAccessDiffuseColor(); + void flatAccessDefaults(); + void flatAccessTexturedBaseColor(); + void flatAccessTexturedDiffuseColor(); + void flatAccessTexturedDefaults(); + void flatAccessTexturedBaseColorSingleMatrixCoordinates(); + void flatAccessTexturedDiffuseColorSingleMatrixCoordinates(); + void flatAccessTexturedMismatchedMatrixCoordinates(); + void flatAccessInvalidTextures(); + + void templateLayerAccess(); + + void pbrClearCoatAccess(); + void pbrClearCoatAccessDefaults(); + void pbrClearCoatAccessTextured(); + void pbrClearCoatAccessTexturedDefaults(); + void pbrClearCoatAccessTexturedExplicitPackedLayerFactorRoughness(); + void pbrClearCoatAccessTexturedSingleMatrixCoordinates(); + void pbrClearCoatAccessTexturedBaseMaterialMatrixCoordinates(); + void pbrClearCoatAccessInvalidTextures(); + void pbrClearCoatAccessCommonTransformationCoordinatesNoTextures(); + void pbrClearCoatAccessCommonTransformationCoordinatesOneTexture(); + void pbrClearCoatAccessCommonTransformationCoordinatesOneDifferentTexture(); + void pbrClearCoatAccessNoCommonTransformationCoordinates(); + + void debugLayer(); + void debugAttribute(); + void debugTextureSwizzle(); + void debugAttributeType(); void debugType(); + void debugTypes(); + #ifdef MAGNUM_BUILD_DEPRECATED void debugFlag(); void debugFlags(); + #endif void debugAlphaMode(); + #ifdef MAGNUM_BUILD_DEPRECATED void debugPhongFlag(); void debugPhongFlags(); + #endif +}; + +const Containers::StringView PbrMetallicRoughnessTextureData[] { + "BaseColorTexture", + "MetalnessTexture", + "RoughnessTexture", + "NormalTexture", + "OcclusionTexture", + "EmissiveTexture" +}; + +const Containers::StringView PbrSpecularGlossinessTextureData[] { + "DiffuseTexture", + "SpecularTexture", + "GlossinessTexture", + "NormalTexture", + "OcclusionTexture", + "EmissiveTexture" +}; + +const Containers::StringView PhongTextureData[] { + "AmbientTexture", + "DiffuseTexture", + "SpecularTexture", + "NormalTexture" +}; + +const Containers::StringView PbrClearCoatTextureData[] { + "LayerFactorTexture", + "RoughnessTexture", + "NormalTexture" }; MaterialDataTest::MaterialDataTest() { - addTests({&MaterialDataTest::constructPhong, - &MaterialDataTest::constructPhongTextured, - &MaterialDataTest::constructPhongTexturedTextureTransform, - &MaterialDataTest::constructPhongTexturedCoordinateSets, - &MaterialDataTest::constructPhongTextureTransformNoTextures, - &MaterialDataTest::constructPhongNoTextureTransformationFlag, - &MaterialDataTest::constructPhongNoTextureCoordinateSetsFlag, + addTests({&MaterialDataTest::textureSwizzleComponentCount, + + &MaterialDataTest::attributeTypeSize, + &MaterialDataTest::attributeTypeSizeInvalid, + &MaterialDataTest::attributeMap, + &MaterialDataTest::layerMap, + + &MaterialDataTest::constructAttributeDefault, + &MaterialDataTest::constructAttributeString, + &MaterialDataTest::constructAttributeName, + &MaterialDataTest::constructAttributeTypeErasedString, + &MaterialDataTest::constructAttributeTypeErasedName, + + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + &MaterialDataTest::constructAttributeStringConstexpr, + + &MaterialDataTest::constructAttributePointer, + &MaterialDataTest::constructAttributeMutablePointer, + &MaterialDataTest::constructAttributeStringNameStringValue, + &MaterialDataTest::constructAttributeNameStringValue, + &MaterialDataTest::constructAttributeTextureSwizzle, + &MaterialDataTest::constructAttributeLayer, + + &MaterialDataTest::constructAttributeInvalidName, + &MaterialDataTest::constructAttributeInvalidLayerName, + &MaterialDataTest::constructAttributeWrongTypeForName, + &MaterialDataTest::constructAttributeInvalidType, + &MaterialDataTest::constructAttributeTooLarge, + &MaterialDataTest::constructAttributeTooLargeString, + &MaterialDataTest::constructAttributeTooLargeNameString, + &MaterialDataTest::constructAttributeWrongAccessType, + &MaterialDataTest::constructAttributeWrongAccessPointerType, + &MaterialDataTest::constructAttributeWrongAccessTypeString, + + &MaterialDataTest::construct, + &MaterialDataTest::constructEmptyAttribute}); + + addRepeatedTests({&MaterialDataTest::constructDuplicateAttribute}, + 5*4*3*2); + + addTests({&MaterialDataTest::constructFromImmutableSortedArray, + + &MaterialDataTest::constructLayers, + &MaterialDataTest::constructLayersNotMonotonic, + &MaterialDataTest::constructLayersOffsetOutOfBounds, + + &MaterialDataTest::constructNonOwned, + &MaterialDataTest::constructNonOwnedLayers, + &MaterialDataTest::constructNonOwnedEmptyAttribute, + &MaterialDataTest::constructNonOwnedNotSorted, + &MaterialDataTest::constructNonOwnedDuplicateAttribute, + &MaterialDataTest::constructNonOwnedLayersNotMonotonic, + &MaterialDataTest::constructNonOwnedLayersOffsetOutOfBounds, + &MaterialDataTest::constructCopy, - &MaterialDataTest::constructMovePhong, + &MaterialDataTest::constructMove, + + &MaterialDataTest::as, + &MaterialDataTest::asRvalue, + + &MaterialDataTest::access, + &MaterialDataTest::accessPointer, + &MaterialDataTest::accessString, + &MaterialDataTest::accessTextureSwizzle, + &MaterialDataTest::accessOptional, + &MaterialDataTest::accessOutOfBounds, + &MaterialDataTest::accessNotFound, + &MaterialDataTest::accessInvalidAttributeName, + &MaterialDataTest::accessWrongType, + &MaterialDataTest::accessWrongPointerType, + &MaterialDataTest::accessWrongTypeString, + + &MaterialDataTest::accessLayers, + &MaterialDataTest::accessLayersDefaults, + &MaterialDataTest::accessLayersTextured, + &MaterialDataTest::accessLayersTexturedDefault, + &MaterialDataTest::accessLayersTexturedSingleMatrixCoordinates, + &MaterialDataTest::accessLayersTexturedBaseMaterialMatrixCoordinates, + &MaterialDataTest::accessLayersInvalidTextures, + + &MaterialDataTest::accessLayerLayerNameInBaseMaterial, + &MaterialDataTest::accessLayerEmptyLayer, + &MaterialDataTest::accessLayerIndexOptional, + &MaterialDataTest::accessLayerNameOptional, + &MaterialDataTest::accessLayerStringOptional, + &MaterialDataTest::accessLayerOutOfBounds, + &MaterialDataTest::accessLayerNotFound, + &MaterialDataTest::accessInvalidLayerName, + &MaterialDataTest::accessOutOfBoundsInLayerIndex, + &MaterialDataTest::accessOutOfBoundsInLayerString, + &MaterialDataTest::accessNotFoundInLayerIndex, + &MaterialDataTest::accessNotFoundInLayerString, + + &MaterialDataTest::releaseAttributes, + &MaterialDataTest::releaseLayers, + + #ifdef MAGNUM_BUILD_DEPRECATED + &MaterialDataTest::constructPhongDeprecated, + &MaterialDataTest::constructPhongDeprecatedTextured, + &MaterialDataTest::constructPhongDeprecatedTexturedTextureTransform, + &MaterialDataTest::constructPhongDeprecatedTexturedCoordinates, + &MaterialDataTest::constructPhongDeprecatedTextureTransformNoTextures, + &MaterialDataTest::constructPhongDeprecatedNoTextureTransformationFlag, + &MaterialDataTest::constructPhongDeprecatedNoTextureCoordinatesFlag, + #endif + + &MaterialDataTest::pbrMetallicRoughnessAccess, + &MaterialDataTest::pbrMetallicRoughnessAccessDefaults, + &MaterialDataTest::pbrMetallicRoughnessAccessTextured, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedDefaults, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedImplicitPackedMetallicRoughness, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedMetallicRoughness, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedRoughnessMetallicOcclusion, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedOcclusionRoughnessMetallic, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedNormalRoughnessMetallic, + &MaterialDataTest::pbrMetallicRoughnessAccessTexturedSingleMatrixCoordinates, + &MaterialDataTest::pbrMetallicRoughnessAccessInvalidTextures, + &MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesNoTextures}); + + addInstancedTests({ + &MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneTexture, + &MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneDifferentTexture}, + Containers::arraySize(PbrMetallicRoughnessTextureData)); - &MaterialDataTest::accessInvalidTextures, + addTests({&MaterialDataTest::pbrMetallicRoughnessAccessNoCommonTransformationCoordinates, + + &MaterialDataTest::pbrSpecularGlossinessAccess, + &MaterialDataTest::pbrSpecularGlossinessAccessDefaults, + &MaterialDataTest::pbrSpecularGlossinessAccessTextured, + &MaterialDataTest::pbrSpecularGlossinessAccessTexturedDefaults, + &MaterialDataTest::pbrSpecularGlossinessAccessTexturedImplicitPackedSpecularGlossiness, + &MaterialDataTest::pbrSpecularGlossinessAccessTexturedExplicitPackedSpecularGlossiness, + &MaterialDataTest::pbrSpecularGlossinessAccessTexturedSingleMatrixCoordinates, + &MaterialDataTest::pbrSpecularGlossinessAccessInvalidTextures, + &MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesNoTextures}); + + addInstancedTests({ + &MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneTexture, + &MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneDifferentTexture}, + Containers::arraySize(PbrSpecularGlossinessTextureData)); + + addTests({&MaterialDataTest::pbrSpecularGlossinessAccessNoCommonTransformationCoordinates, + + &MaterialDataTest::phongAccess, + &MaterialDataTest::phongAccessDefaults, + &MaterialDataTest::phongAccessTextured, + &MaterialDataTest::phongAccessTexturedDefaults, + &MaterialDataTest::phongAccessTexturedSingleMatrixCoordinates, + &MaterialDataTest::phongAccessTexturedImplicitPackedSpecularGlossiness, + &MaterialDataTest::phongAccessInvalidTextures, + &MaterialDataTest::phongAccessCommonTransformationCoordinatesNoTextures}); + + addInstancedTests({ + &MaterialDataTest::phongAccessCommonTransformationCoordinatesOneTexture, + &MaterialDataTest::phongAccessCommonTransformationCoordinatesOneDifferentTexture}, + Containers::arraySize(PhongTextureData)); + + addTests({&MaterialDataTest::phongAccessNoCommonTransformationCoordinates, + + &MaterialDataTest::flatAccessBaseColor, + &MaterialDataTest::flatAccessDiffuseColor, + &MaterialDataTest::flatAccessDefaults, + &MaterialDataTest::flatAccessTexturedBaseColor, + &MaterialDataTest::flatAccessTexturedDiffuseColor, + &MaterialDataTest::flatAccessTexturedDefaults, + &MaterialDataTest::flatAccessTexturedBaseColorSingleMatrixCoordinates, + &MaterialDataTest::flatAccessTexturedDiffuseColorSingleMatrixCoordinates, + &MaterialDataTest::flatAccessTexturedMismatchedMatrixCoordinates, + &MaterialDataTest::flatAccessInvalidTextures, + + &MaterialDataTest::templateLayerAccess, + + &MaterialDataTest::pbrClearCoatAccess, + &MaterialDataTest::pbrClearCoatAccessDefaults, + &MaterialDataTest::pbrClearCoatAccessTextured, + &MaterialDataTest::pbrClearCoatAccessTexturedDefaults, + &MaterialDataTest::pbrClearCoatAccessTexturedExplicitPackedLayerFactorRoughness, + &MaterialDataTest::pbrClearCoatAccessTexturedSingleMatrixCoordinates, + &MaterialDataTest::pbrClearCoatAccessTexturedBaseMaterialMatrixCoordinates, + &MaterialDataTest::pbrClearCoatAccessInvalidTextures, + &MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesNoTextures}); + + addInstancedTests({ + &MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesOneTexture, + &MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesOneDifferentTexture}, + Containers::arraySize(PbrClearCoatTextureData)); + + addTests({&MaterialDataTest::pbrClearCoatAccessNoCommonTransformationCoordinates, + + &MaterialDataTest::debugLayer, + &MaterialDataTest::debugAttribute, + &MaterialDataTest::debugTextureSwizzle, + &MaterialDataTest::debugAttributeType, &MaterialDataTest::debugType, + &MaterialDataTest::debugTypes, + #ifdef MAGNUM_BUILD_DEPRECATED &MaterialDataTest::debugFlag, &MaterialDataTest::debugFlags, + #endif &MaterialDataTest::debugAlphaMode, + #ifdef MAGNUM_BUILD_DEPRECATED &MaterialDataTest::debugPhongFlag, - &MaterialDataTest::debugPhongFlags}); + &MaterialDataTest::debugPhongFlags + #endif + }); } -void MaterialDataTest::constructPhong() { - using namespace Math::Literals; +using namespace Containers::Literals; +using namespace Math::Literals; - const int a{}; - PhongMaterialData data{{}, - 0xccffbb_rgbf, {}, - 0xebefbf_rgbf, {}, - 0xacabad_rgbf, {}, {}, {}, - MaterialAlphaMode::Mask, 0.3f, 80.0f, &a}; +void MaterialDataTest::textureSwizzleComponentCount() { + CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::B), 1); + CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::RG), 2); + CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::GA), 2); + CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::GBA), 3); + CORRADE_COMPARE(materialTextureSwizzleComponentCount(MaterialTextureSwizzle::RGBA), 4); +} - /** @todo use data directly once deprecated mutable access is gone */ - const PhongMaterialData& cdata = data; - CORRADE_COMPARE(cdata.type(), MaterialType::Phong); - CORRADE_COMPARE(cdata.flags(), PhongMaterialData::Flags{}); - CORRADE_COMPARE(cdata.ambientColor(), 0xccffbb_rgbf); - CORRADE_COMPARE(cdata.diffuseColor(), 0xebefbf_rgbf); - CORRADE_COMPARE(cdata.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(cdata.textureMatrix(), Matrix3{}); - CORRADE_COMPARE(cdata.alphaMode(), MaterialAlphaMode::Mask); - CORRADE_COMPARE(cdata.alphaMask(), 0.3f); - CORRADE_COMPARE(cdata.shininess(), 80.0f); - CORRADE_COMPARE(cdata.importerState(), &a); +void MaterialDataTest::attributeTypeSize() { + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Bool), 1); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Deg), 4); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Vector2i), 8); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Vector3), 12); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Vector4ui), 16); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Matrix2x3), 24); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Matrix4x2), 32); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Matrix3x3), 36); + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::Matrix3x4), 48); } -void MaterialDataTest::constructPhongTextured() { - using namespace Math::Literals; +void MaterialDataTest::attributeTypeSizeInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif - const int a{}; - PhongMaterialData data{ - PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture, - 0x111111_rgbf, 42, - 0xeebbff_rgbf, {}, - 0xacabad_rgbf, 17, {}, {}, - MaterialAlphaMode::Blend, 0.37f, 96.0f, &a}; + std::ostringstream out; + Error redirectError{&out}; + materialAttributeTypeSize(MaterialAttributeType(0x0)); + materialAttributeTypeSize(MaterialAttributeType(0xfe)); + materialAttributeTypeSize(MaterialAttributeType::String); + CORRADE_COMPARE(out.str(), + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0x0)\n" + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n" + "Trade::materialAttributeTypeSize(): string size is unknown\n"); +} - /** @todo use data directly once deprecated mutable access is gone */ - const PhongMaterialData& cdata = data; - CORRADE_COMPARE(cdata.type(), MaterialType::Phong); - CORRADE_COMPARE(cdata.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture); - CORRADE_COMPARE(cdata.ambientColor(), 0x111111_rgbf); - CORRADE_COMPARE(cdata.ambientTexture(), 42); - CORRADE_COMPARE(cdata.ambientCoordinateSet(), 0); - CORRADE_COMPARE(cdata.diffuseColor(), 0xeebbff_rgbf); - CORRADE_COMPARE(cdata.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(cdata.specularTexture(), 17); - CORRADE_COMPARE(cdata.specularCoordinateSet(), 0); - CORRADE_COMPARE(cdata.textureMatrix(), Matrix3{}); - CORRADE_COMPARE(cdata.alphaMode(), MaterialAlphaMode::Blend); - CORRADE_COMPARE(cdata.alphaMask(), 0.37f); - CORRADE_COMPARE(cdata.shininess(), 96.0f); - CORRADE_COMPARE(cdata.importerState(), &a); -} - -void MaterialDataTest::constructPhongTexturedTextureTransform() { - using namespace Math::Literals; +void MaterialDataTest::attributeMap() { + /* Ensure all attribute names are: + - present in the map, + - that their translated string name corresponds to the enum value name, + - that the calculated type size corresponds to the actual type, + - and that the name together with the type fits. + This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + /* Attribute 0 reserved for an invalid value */ - const int a{}; - PhongMaterialData data{ - PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation, - 0x111111_rgbf, {}, - 0xeebbff_rgbf, 42, - 0xacabad_rgbf, {}, 17, - Matrix3::rotation(90.0_degf), - MaterialAlphaMode::Opaque, 0.5f, 96.0f, &a}; - - /** @todo use data directly once deprecated mutable access is gone */ - const PhongMaterialData& cdata = data; - CORRADE_COMPARE(cdata.type(), MaterialType::Phong); - CORRADE_COMPARE(cdata.flags(), PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation); - CORRADE_COMPARE(cdata.ambientColor(), 0x111111_rgbf); - CORRADE_COMPARE(cdata.diffuseColor(), 0xeebbff_rgbf); - CORRADE_COMPARE(cdata.diffuseTexture(), 42); - CORRADE_COMPARE(cdata.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(cdata.normalTexture(), 17); - CORRADE_COMPARE(cdata.textureMatrix(), Matrix3::rotation(90.0_degf)); - CORRADE_COMPARE(cdata.alphaMode(), MaterialAlphaMode::Opaque); - CORRADE_COMPARE(cdata.alphaMask(), 0.5f); - CORRADE_COMPARE(cdata.shininess(), 96.0f); - CORRADE_COMPARE(cdata.importerState(), &a); -} - -void MaterialDataTest::constructPhongTexturedCoordinateSets() { - using namespace Math::Literals; + const auto attribute = MaterialAttribute(i); + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(attribute) { + #define _c(name_, type) \ + case MaterialAttribute::name_: \ + CORRADE_COMPARE((MaterialAttributeData{MaterialAttribute::name_, type{}}.name()), #name_); \ + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::type), sizeof(type)); \ + CORRADE_COMPARE_AS(sizeof(type) + Containers::arraySize(#name_) + sizeof(MaterialAttributeType), sizeof(MaterialAttributeData), TestSuite::Compare::LessOrEqual); \ + break; + #define _ct(name_, typeName, type) \ + case MaterialAttribute::name_: \ + CORRADE_COMPARE((MaterialAttributeData{MaterialAttribute::name_, type{}}.name()), #name_); \ + CORRADE_COMPARE(materialAttributeTypeSize(MaterialAttributeType::typeName), sizeof(type)); \ + CORRADE_COMPARE_AS(sizeof(type) + Containers::arraySize(#name_) + sizeof(MaterialAttributeType), sizeof(MaterialAttributeData), TestSuite::Compare::LessOrEqual); \ + break; + #define _cnt(name_, string, typeName, type) \ + case MaterialAttribute::name_: \ + CORRADE_COMPARE((MaterialAttributeData{MaterialAttribute::name_, type{}}.name()), string); \ + break; + #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" + #undef _c + #undef _ct + #undef _cnt + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } +} - const int a{}; - PhongMaterialData data{ - PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::TextureCoordinateSets, - 0x111111_rgbf, 42, 3, - 0xeebbff_rgbf, {}, 0, - 0xacabad_rgbf, 17, 1, - {}, 0, {}, - MaterialAlphaMode::Blend, 0.37f, 96.0f, &a}; +void MaterialDataTest::layerMap() { + /* Ensure all layer names are: + - present in the map, + - and that their translated string name corresponds to the enum value + name + This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + /* Attribute 0 reserved for an invalid value */ + + const auto attribute = MaterialLayer(i); + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(attribute) { + #define _c(name_) \ + case MaterialLayer::name_: \ + CORRADE_COMPARE((MaterialAttributeData{MaterialLayer::name_}.value()), #name_); \ + break; + #include "Magnum/Trade/Implementation/materialLayerProperties.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } +} + +void MaterialDataTest::constructAttributeDefault() { + MaterialAttributeData attribute; + CORRADE_COMPARE(attribute.name(), ""); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType{}); + + constexpr MaterialAttributeData cattribute; + CORRADE_COMPARE(cattribute.name(), ""); + CORRADE_COMPARE(cattribute.type(), MaterialAttributeType{}); +} + +void MaterialDataTest::constructAttributeString() { + MaterialAttributeData attribute{"colorTransform", Matrix3::scaling({2.0f, 0.3f})}; + CORRADE_COMPARE(attribute.name(), "colorTransform"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(attribute.value(), Matrix3::scaling({2.0f, 0.3f})); + CORRADE_COMPARE(*reinterpret_cast(attribute.value()), Matrix3::scaling({2.0f, 0.3f})); +} + +void MaterialDataTest::constructAttributeName() { + MaterialAttributeData attribute{MaterialAttribute::DiffuseColor, 0xff3366aa_rgbaf}; + CORRADE_COMPARE(attribute.name(), "DiffuseColor"_s); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::Vector4); + CORRADE_COMPARE(attribute.value(), 0xff3366aa_rgbaf); + CORRADE_COMPARE(*reinterpret_cast(attribute.value()), 0xff3366aa_rgbaf); +} + +void MaterialDataTest::constructAttributeTypeErasedString() { + const Vector2i data{37, -458}; + MaterialAttributeData attribute{"millibitsOfInformation", MaterialAttributeType::Vector2i, &data}; + CORRADE_COMPARE(attribute.name(), "millibitsOfInformation"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::Vector2i); + CORRADE_COMPARE(attribute.value(), (Vector2i{37, -458})); +} + +void MaterialDataTest::constructAttributeTypeErasedName() { + const Float data = 85.1f; + MaterialAttributeData attribute{MaterialAttribute::Shininess, MaterialAttributeType::Float, &data}; + CORRADE_COMPARE(attribute.name(), "Shininess"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::Float); + CORRADE_COMPARE(attribute.value(), 85.1f); +} + +template struct TypeName { + static const char* name() { return Math::TypeTraits::name(); } +}; +template<> struct TypeName { + static const char* name() { return "bool"; } +}; +#define _c(type) template<> struct TypeName { \ + static const char* name() { return #type; } \ +}; +_c(Deg) +_c(Rad) +_c(Vector2) +_c(Vector2i) +_c(Vector2ui) +_c(Vector3) +_c(Vector3i) +_c(Vector3ui) +_c(Vector4) +_c(Vector4i) +_c(Vector4ui) +_c(Matrix2x2) +_c(Matrix2x3) +_c(Matrix2x4) +_c(Matrix3x2) +_c(Matrix3x3) +_c(Matrix3x4) +_c(Matrix4x2) +_c(Matrix4x3) +#undef _c + +template void MaterialDataTest::constructAttributeStringConstexpr() { + setTestCaseTemplateName(TypeName::name()); + + /* "templateAttrib" is 14 chars, which is the maximum for 48-bit types */ + #ifndef CORRADE_MSVC2015_COMPATIBILITY + /* Unfortunately this doesn't always work, for example Vector3. Disabling + unconditionally, might revisit later. */ + constexpr + #endif + MaterialAttributeData attribute{"templateAttrib"_s, T(15)}; + CORRADE_COMPARE(attribute.name(), "templateAttrib"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.value(), T(15)); +} + +constexpr Int SomeData = 3; + +void MaterialDataTest::constructAttributePointer() { + MaterialAttributeData attribute{"pointer!", &SomeData}; + CORRADE_COMPARE(attribute.name(), "pointer!"); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::Pointer); + CORRADE_COMPARE(*static_cast(attribute.value()), &SomeData); + CORRADE_COMPARE(attribute.value(), &SomeData); + /* Any type works */ + CORRADE_COMPARE(attribute.value(), &SomeData); + + constexpr MaterialAttributeData cattribute{"pointer!"_s, &SomeData}; + CORRADE_COMPARE(cattribute.name(), "pointer!"); + CORRADE_COMPARE(cattribute.type(), MaterialAttributeType::Pointer); + CORRADE_COMPARE(*static_cast(cattribute.value()), &SomeData); + CORRADE_COMPARE(cattribute.value(), &SomeData); + + /* Type-erased variant */ + const Int* pointer = &SomeData; + MaterialAttributeData typeErased{"pointer!", MaterialAttributeType::Pointer, &pointer}; + CORRADE_COMPARE(typeErased.name(), "pointer!"); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::Pointer); + CORRADE_COMPARE(typeErased.value(), &SomeData); + /* Any type works */ + CORRADE_COMPARE(typeErased.value(), &SomeData); +} + +void MaterialDataTest::constructAttributeMutablePointer() { + Float data = 85.1f; + + MaterialAttributeData attribute{"pointer!", &data}; + CORRADE_COMPARE(attribute.name(), "pointer!"); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::MutablePointer); + CORRADE_COMPARE(*static_cast(attribute.value()), &data); + CORRADE_COMPARE(attribute.value(), &data); + /* Any type works */ + CORRADE_COMPARE(attribute.value(), &data); + + /* Type-erased variant */ + Float* pointer = &data; + MaterialAttributeData typeErased{"pointer!", MaterialAttributeType::MutablePointer, &pointer}; + CORRADE_COMPARE(typeErased.name(), "pointer!"); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::MutablePointer); + CORRADE_COMPARE(typeErased.value(), &data); + /* Any type works */ + CORRADE_COMPARE(typeErased.value(), &data); +} + +void MaterialDataTest::constructAttributeStringNameStringValue() { + /* Explicitly using a non-null-terminated view on input to check the null + byte isn't read by accident*/ + MaterialAttributeData attribute{"name that's long", "and a value\0that's also long but still fits!!"_s.except(1)}; + CORRADE_COMPARE(attribute.name(), "name that's long"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::String); + /* Pointer access will stop at the first null byte, but typed access won't */ + CORRADE_COMPARE(static_cast(attribute.value()), "and a value"_s); + CORRADE_COMPARE(attribute.value(), "and a value\0that's also long but still fits!"_s); + CORRADE_COMPARE(attribute.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.value()[attribute.value().size()], '\0'); + + constexpr MaterialAttributeData cattribute{"name that's long"_s, "and a value\0that's also long but still fits!!"_s.except(1)}; + CORRADE_COMPARE(cattribute.name(), "name that's long"); + CORRADE_COMPARE(cattribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(cattribute.name()[cattribute.name().size()], '\0'); + CORRADE_COMPARE(cattribute.type(), MaterialAttributeType::String); + CORRADE_COMPARE(cattribute.value(), "and a value\0that's also long but still fits!"_s); + CORRADE_COMPARE(cattribute.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(cattribute.value()[cattribute.value().size()], '\0'); + + /* Type-erased variant */ + const Containers::StringView value = "and a value\0that's also long but still fits!!"_s.except(1); + MaterialAttributeData typeErased{"name that's long", MaterialAttributeType::String, &value}; + CORRADE_COMPARE(typeErased.name(), "name that's long"); + CORRADE_COMPARE(typeErased.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.name()[typeErased.name().size()], '\0'); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::String); + CORRADE_COMPARE(typeErased.value(), "and a value\0that's also long but still fits!"_s); + CORRADE_COMPARE(typeErased.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.value()[typeErased.value().size()], '\0'); +} + +void MaterialDataTest::constructAttributeNameStringValue() { + /* Explicitly using a non-null-terminated view on input to check the null + byte isn't read by accident*/ - /** @todo use data directly once deprecated mutable access is gone */ - const PhongMaterialData& cdata = data; - CORRADE_COMPARE(cdata.type(), MaterialType::Phong); - CORRADE_COMPARE(cdata.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::TextureCoordinateSets); - CORRADE_COMPARE(cdata.ambientColor(), 0x111111_rgbf); - CORRADE_COMPARE(cdata.ambientTexture(), 42); - CORRADE_COMPARE(cdata.ambientCoordinateSet(), 3); - CORRADE_COMPARE(cdata.diffuseColor(), 0xeebbff_rgbf); - CORRADE_COMPARE(cdata.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(cdata.specularTexture(), 17); - CORRADE_COMPARE(cdata.specularCoordinateSet(), 1); - CORRADE_COMPARE(cdata.textureMatrix(), Matrix3{}); - CORRADE_COMPARE(cdata.alphaMode(), MaterialAlphaMode::Blend); - CORRADE_COMPARE(cdata.alphaMask(), 0.37f); - CORRADE_COMPARE(cdata.shininess(), 96.0f); - CORRADE_COMPARE(cdata.importerState(), &a); + MaterialAttributeData attribute{MaterialAttribute::LayerName, "a value\0that's long but still fits!!"_s.except(1)}; + CORRADE_COMPARE(attribute.name(), "$LayerName"); + CORRADE_COMPARE(attribute.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.name()[attribute.name().size()], '\0'); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::String); + /* Pointer access will stop at the first null byte, but typed access won't */ + CORRADE_COMPARE(static_cast(attribute.value()), "a value"_s); + CORRADE_COMPARE(attribute.value(), "a value\0that's long but still fits!"_s); + CORRADE_COMPARE(attribute.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(attribute.value()[attribute.value().size()], '\0'); + + /* Type-erased variant */ + const Containers::StringView value = "a value\0that's long but still fits!!"_s.except(1); + MaterialAttributeData typeErased{MaterialAttribute::LayerName, MaterialAttributeType::String, &value}; + CORRADE_COMPARE(typeErased.name(), "$LayerName"); + CORRADE_COMPARE(typeErased.name().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.name()[typeErased.name().size()], '\0'); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::String); + CORRADE_COMPARE(typeErased.value(), "a value\0that's long but still fits!"_s); + CORRADE_COMPARE(typeErased.value().flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(typeErased.value()[typeErased.value().size()], '\0'); +} + +void MaterialDataTest::constructAttributeTextureSwizzle() { + MaterialAttributeData attribute{"swizzle", MaterialTextureSwizzle::GBA}; + CORRADE_COMPARE(attribute.name(), "swizzle"); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(*static_cast(attribute.value()), MaterialTextureSwizzle::GBA); + CORRADE_COMPARE(attribute.value(), MaterialTextureSwizzle::GBA); + + constexpr MaterialAttributeData cattribute{"swizzle"_s, MaterialTextureSwizzle::GBA}; + CORRADE_COMPARE(cattribute.name(), "swizzle"); + CORRADE_COMPARE(cattribute.type(), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(*static_cast(cattribute.value()), MaterialTextureSwizzle::GBA); + CORRADE_COMPARE(cattribute.value(), MaterialTextureSwizzle::GBA); + + /* Type-erased variant */ + const MaterialTextureSwizzle swizzle = MaterialTextureSwizzle::GBA; + MaterialAttributeData typeErased{"swizzle", MaterialAttributeType::TextureSwizzle, &swizzle}; + CORRADE_COMPARE(typeErased.name(), "swizzle"); + CORRADE_COMPARE(typeErased.type(), MaterialAttributeType::TextureSwizzle); + CORRADE_COMPARE(typeErased.value(), MaterialTextureSwizzle::GBA); +} + +void MaterialDataTest::constructAttributeLayer() { + MaterialAttributeData attribute{MaterialLayer::ClearCoat}; + CORRADE_COMPARE(attribute.name(), "$LayerName"); + CORRADE_COMPARE(attribute.type(), MaterialAttributeType::String); + CORRADE_COMPARE(attribute.value(), "ClearCoat"); } -void MaterialDataTest::constructPhongTextureTransformNoTextures() { +void MaterialDataTest::constructAttributeInvalidName() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif std::ostringstream out; Error redirectError{&out}; - PhongMaterialData a{PhongMaterialData::Flag::TextureTransformation, - {}, {}, - {}, {}, - {}, {}, {}, {}, - {}, 0.5f, 80.0f}; + MaterialAttributeData{MaterialAttribute(0x0), 5}; + MaterialAttributeData{MaterialAttribute(0xfefe), 5}; CORRADE_COMPARE(out.str(), - "Trade::PhongMaterialData: texture transformation enabled but the material has no textures\n"); + "Trade::MaterialAttributeData: invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialAttributeData: invalid name Trade::MaterialAttribute(0xfefe)\n"); } -void MaterialDataTest::constructPhongNoTextureTransformationFlag() { - using namespace Math::Literals; +void MaterialDataTest::constructAttributeInvalidLayerName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{MaterialLayer(0x0)}; + MaterialAttributeData{MaterialLayer(0xfefe)}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialAttributeData: invalid name Trade::MaterialLayer(0xfefe)\n"); +} +void MaterialDataTest::constructAttributeWrongTypeForName() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif std::ostringstream out; Error redirectError{&out}; - PhongMaterialData a{{}, - {}, {}, - {}, {}, - {}, {}, {}, Matrix3::rotation(90.0_degf), - {}, 0.5f, 80.0f}; + MaterialAttributeData{MaterialAttribute::DiffuseColor, Vector3ui{255, 16, 24}}; CORRADE_COMPARE(out.str(), - "PhongMaterialData::PhongMaterialData: non-default texture matrix requires Flag::TextureTransformation to be enabled\n"); + "Trade::MaterialAttributeData: expected Trade::MaterialAttributeType::Vector4 for Trade::MaterialAttribute::DiffuseColor but got Trade::MaterialAttributeType::Vector3ui\n"); } -void MaterialDataTest::constructPhongNoTextureCoordinateSetsFlag() { +void MaterialDataTest::constructAttributeInvalidType() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif std::ostringstream out; Error redirectError{&out}; - PhongMaterialData a{{}, - {}, {}, 1, - {}, {}, 2, - {}, {}, 3, {}, 4, {}, - {}, 0.5f, 80.0f}; + MaterialAttributeData{"bla", MaterialAttributeType(0x0), nullptr}; + MaterialAttributeData{"bla", MaterialAttributeType(0xfe), nullptr}; CORRADE_COMPARE(out.str(), - "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinateSets to be enabled\n"); + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0x0)\n" + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n"); } -void MaterialDataTest::constructCopy() { - CORRADE_VERIFY(!(std::is_constructible{})); - CORRADE_VERIFY(!(std::is_constructible{})); - CORRADE_VERIFY(!(std::is_assignable{})); - CORRADE_VERIFY(!(std::is_assignable{})); +void MaterialDataTest::constructAttributeTooLarge() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{"attributeIsLong", Matrix3x4{}}; + /* Constexpr variant has the same assert, but in the header. It should have + the same output. Except on MSVC 2015, which is a crap thing and gets + lost when encountering T in there, so the assert is less useful. */ + /*constexpr*/ MaterialAttributeData{"attributeIsLong"_s, Matrix3x4{}}; + #ifndef CORRADE_MSVC2015_COMPATIBILITY + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: name attributeIsLong too long, expected at most 14 bytes for Trade::MaterialAttributeType::Matrix3x4 but got 15\n" + "Trade::MaterialAttributeData: name attributeIsLong too long, expected at most 14 bytes for Trade::MaterialAttributeType::Matrix3x4 but got 15\n"); + #else + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: name attributeIsLong too long, expected at most 14 bytes for Trade::MaterialAttributeType::Matrix3x4 but got 15\n" + "Trade::MaterialAttributeData: name attributeIsLong too long, got 15 bytes\n"); + #endif } -void MaterialDataTest::constructMovePhong() { - using namespace Math::Literals; +void MaterialDataTest::constructAttributeTooLargeString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif - const int a{}; - PhongMaterialData data{ - PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation|PhongMaterialData::Flag::TextureCoordinateSets, - 0x111111_rgbf, 1, 0, - 0xeebbff_rgbf, 42, 1, - 0xacabad_rgbf, 24, 2, 17, 3, - Matrix3::rotation(90.0_degf), - MaterialAlphaMode::Blend, 0.55f, 13.0f, &a}; - - PhongMaterialData b{std::move(data)}; - CORRADE_COMPARE(b.type(), MaterialType::Phong); - CORRADE_COMPARE(b.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation|PhongMaterialData::Flag::TextureCoordinateSets); - CORRADE_COMPARE(b.ambientColor(), 0x111111_rgbf); - CORRADE_COMPARE(b.ambientTexture(), 1); - CORRADE_COMPARE(b.ambientCoordinateSet(), 0); - CORRADE_COMPARE(b.diffuseColor(), 0xeebbff_rgbf); - CORRADE_COMPARE(b.diffuseTexture(), 42); - CORRADE_COMPARE(b.diffuseCoordinateSet(), 1); - CORRADE_COMPARE(b.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(b.specularTexture(), 24); - CORRADE_COMPARE(b.specularCoordinateSet(), 2); - CORRADE_COMPARE(b.normalTexture(), 17); - CORRADE_COMPARE(b.normalCoordinateSet(), 3); - CORRADE_COMPARE(b.textureMatrix(), Matrix3::rotation(90.0_degf)); - CORRADE_COMPARE(b.alphaMode(), MaterialAlphaMode::Blend); - CORRADE_COMPARE(b.alphaMask(), 0.55f); - CORRADE_COMPARE(b.shininess(), 13.0f); - CORRADE_COMPARE(b.importerState(), &a); - - const int c{}; - PhongMaterialData d{{}, - 0xccffbb_rgbf, {}, - 0xebefbf_rgbf, {}, - 0xacabad_rgbf, {}, {}, {}, - MaterialAlphaMode::Mask, 0.3f, 80.0f, &c}; - d = std::move(b); + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{"attribute is long", "This is a problem, got a long piece of text!"}; + /* Constexpr variant has the same assert, but in the header. It should have + the same output. */ + /*constexpr*/ MaterialAttributeData{"attribute is long"_s, "This is a problem, got a long piece of text!"_s}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: name attribute is long and value This is a problem, got a long piece of text! too long, expected at most 60 bytes in total but got 61\n" + "Trade::MaterialAttributeData: name attribute is long and value This is a problem, got a long piece of text! too long, expected at most 60 bytes in total but got 61\n"); +} - CORRADE_COMPARE(d.type(), MaterialType::Phong); - CORRADE_COMPARE(d.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation|PhongMaterialData::Flag::TextureCoordinateSets); - CORRADE_COMPARE(d.ambientColor(), 0x111111_rgbf); - CORRADE_COMPARE(d.ambientTexture(), 1); - CORRADE_COMPARE(d.ambientCoordinateSet(), 0); - CORRADE_COMPARE(d.diffuseColor(), 0xeebbff_rgbf); - CORRADE_COMPARE(d.diffuseTexture(), 42); - CORRADE_COMPARE(d.diffuseCoordinateSet(), 1); - CORRADE_COMPARE(d.specularColor(), 0xacabad_rgbf); - CORRADE_COMPARE(d.specularTexture(), 24); - CORRADE_COMPARE(d.specularCoordinateSet(), 2); - CORRADE_COMPARE(d.normalTexture(), 17); - CORRADE_COMPARE(d.normalCoordinateSet(), 3); - CORRADE_COMPARE(d.textureMatrix(), Matrix3::rotation(90.0_degf)); - CORRADE_COMPARE(d.alphaMode(), MaterialAlphaMode::Blend); - CORRADE_COMPARE(d.alphaMask(), 0.55f); - CORRADE_COMPARE(d.shininess(), 13.0f); - CORRADE_COMPARE(d.importerState(), &a); +void MaterialDataTest::constructAttributeTooLargeNameString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif - CORRADE_VERIFY(std::is_nothrow_move_constructible::value); - CORRADE_VERIFY(std::is_nothrow_move_assignable::value); + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{MaterialAttribute::LayerName, "This is a problem, got a huge, yuuge value to store"}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: name $LayerName and value This is a problem, got a huge, yuuge value to store too long, expected at most 60 bytes in total but got 61\n"); } -void MaterialDataTest::accessInvalidTextures() { +void MaterialDataTest::constructAttributeWrongAccessType() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - PhongMaterialData a{{}, - {}, {}, - {}, {}, - {}, {}, {}, {}, - {}, 0.5f, 80.0f}; + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{"thing3", Matrix4x3{}}.value(); + CORRADE_COMPARE(out.str(), "Trade::MaterialAttributeData::value(): improper type requested for thing3 of Trade::MaterialAttributeType::Matrix4x3\n"); +} + +void MaterialDataTest::constructAttributeWrongAccessPointerType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Int a = 3; + const Float b = 57.0f; std::ostringstream out; Error redirectError{&out}; - a.ambientTexture(); - a.ambientCoordinateSet(); - a.diffuseTexture(); - a.diffuseCoordinateSet(); - a.specularTexture(); - a.specularCoordinateSet(); - a.normalTexture(); - a.normalCoordinateSet(); + MaterialAttributeData{"thing3", &a}.value(); + MaterialAttributeData{"boom", &b}.value(); CORRADE_COMPARE(out.str(), - "Trade::PhongMaterialData::ambientTexture(): the material doesn't have an ambient texture\n" - "Trade::PhongMaterialData::ambientCoordinateSet(): the material doesn't have an ambient texture\n" - "Trade::PhongMaterialData::diffuseTexture(): the material doesn't have a diffuse texture\n" - "Trade::PhongMaterialData::diffuseCoordinateSet(): the material doesn't have a diffuse texture\n" - "Trade::PhongMaterialData::specularTexture(): the material doesn't have a specular texture\n" - "Trade::PhongMaterialData::specularCoordinateSet(): the material doesn't have a specular texture\n" - "Trade::PhongMaterialData::normalTexture(): the material doesn't have a normal texture\n" - "Trade::PhongMaterialData::normalCoordinateSet(): the material doesn't have a normal texture\n"); + "Trade::MaterialAttributeData::value(): improper type requested for thing3 of Trade::MaterialAttributeType::MutablePointer\n" + "Trade::MaterialAttributeData::value(): improper type requested for boom of Trade::MaterialAttributeType::Pointer\n"); } -void MaterialDataTest::debugType() { +void MaterialDataTest::constructAttributeWrongAccessTypeString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{"thing3", Matrix4x3{}}.value(); + CORRADE_COMPARE(out.str(), "Trade::MaterialAttributeData::value(): thing3 of Trade::MaterialAttributeType::Matrix4x3 can't be retrieved as a string\n"); +} - Debug(&out) << MaterialType::Phong << MaterialType(0xbe); - CORRADE_COMPARE(out.str(), "Trade::MaterialType::Phong Trade::MaterialType(0xbe)\n"); +void MaterialDataTest::construct() { + int state; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {"highlightColor", 0x335566ff_rgbaf}, + {MaterialAttribute::AmbientTextureMatrix, Matrix3::scaling({0.5f, 1.0f})} + }, &state}; + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(!data.layerData()); + CORRADE_COMPARE(data.attributeCount(), 4); + CORRADE_COMPARE(data.attributeData().size(), 4); + CORRADE_COMPARE(data.importerState(), &state); + + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_VERIFY(!data.hasLayer("")); + + /* Verify sorting */ + CORRADE_COMPARE(data.attributeName(0), "AmbientTextureMatrix"); + CORRADE_COMPARE(data.attributeName(1), "DiffuseTextureCoordinates"); + CORRADE_COMPARE(data.attributeName(2), "DoubleSided"); + CORRADE_COMPARE(data.attributeName(3), "highlightColor"); + + /* Access by ID */ + CORRADE_COMPARE(data.attributeType(0), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(data.attributeType(1), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(2), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(3), MaterialAttributeType::Vector4); + + CORRADE_COMPARE(data.attribute(0), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.attribute(1), 5); + CORRADE_COMPARE(data.attribute(2), true); + CORRADE_COMPARE(data.attribute(3), 0x335566ff_rgbaf); + + CORRADE_COMPARE(*static_cast(data.attribute(0)), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(*static_cast(data.attribute(1)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(2)), true); + CORRADE_COMPARE(*static_cast(data.attribute(3)), 0x335566ff_rgbaf); + + /* Access by name */ + CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::DoubleSided)); + CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::AmbientTextureMatrix)); + CORRADE_VERIFY(!data.hasAttribute(MaterialAttribute::TextureMatrix)); + + CORRADE_COMPARE(data.attributeId(MaterialAttribute::DoubleSided), 2); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::AmbientTextureMatrix), 0); + CORRADE_COMPARE(data.attributeId(MaterialAttribute::DiffuseTextureCoordinates), 1); + + CORRADE_COMPARE(data.attributeType(MaterialAttribute::AmbientTextureMatrix), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(data.attributeType(MaterialAttribute::DiffuseTextureCoordinates), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(MaterialAttribute::DoubleSided), MaterialAttributeType::Bool); + + CORRADE_COMPARE(data.attribute(MaterialAttribute::AmbientTextureMatrix), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.attribute(MaterialAttribute::DiffuseTextureCoordinates), 5); + CORRADE_COMPARE(data.attribute(MaterialAttribute::DoubleSided), true); + + CORRADE_COMPARE(*static_cast(data.attribute(MaterialAttribute::AmbientTextureMatrix)), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(*static_cast(data.attribute(MaterialAttribute::DiffuseTextureCoordinates)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(MaterialAttribute::DoubleSided)), true); + + /* Access by string */ + CORRADE_VERIFY(data.hasAttribute("DoubleSided")); + CORRADE_VERIFY(data.hasAttribute("highlightColor")); + CORRADE_VERIFY(!data.hasAttribute("TextureMatrix")); + + CORRADE_COMPARE(data.attributeId("DoubleSided"), 2); + CORRADE_COMPARE(data.attributeId("AmbientTextureMatrix"), 0); + CORRADE_COMPARE(data.attributeId("DiffuseTextureCoordinates"), 1); + CORRADE_COMPARE(data.attributeId("highlightColor"), 3); + + CORRADE_COMPARE(data.attributeType("AmbientTextureMatrix"), MaterialAttributeType::Matrix3x3); + CORRADE_COMPARE(data.attributeType("DiffuseTextureCoordinates"), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType("DoubleSided"), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType("highlightColor"), MaterialAttributeType::Vector4); + + CORRADE_COMPARE(data.attribute("AmbientTextureMatrix"), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.attribute("DiffuseTextureCoordinates"), 5); + CORRADE_COMPARE(data.attribute("DoubleSided"), true); + CORRADE_COMPARE(data.attribute("highlightColor"), 0x335566ff_rgbaf); + + CORRADE_COMPARE(*static_cast(data.attribute("AmbientTextureMatrix")), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(*static_cast(data.attribute("DiffuseTextureCoordinates")), 5); + CORRADE_COMPARE(*static_cast(data.attribute("DoubleSided")), true); + CORRADE_COMPARE(*static_cast(data.attribute("highlightColor")), 0x335566ff_rgbaf); +} + +void MaterialDataTest::constructEmptyAttribute() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialData{{}, { + {"DiffuseTexture"_s, 12u}, + MaterialAttributeData{} + }}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: attribute 1 doesn't specify anything\n"); +} + +void MaterialDataTest::constructDuplicateAttribute() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + auto attributes = Containers::Array{Containers::InPlaceInit, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {"highlightColor", 0x335566ff_rgbaf}, + {MaterialAttribute::AmbientTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u} + }}; + + /* Testing that it asserts in all input permutations */ + for(std::size_t i = 0; i != testCaseRepeatId(); ++i) + std::next_permutation(attributes.begin(), attributes.end(), [](const MaterialAttributeData& a, const MaterialAttributeData& b) { + return a.name() < b.name(); + }); + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{{}, std::move(attributes)}; + /* Because with graceful asserts it doesn't exit on error, the assertion + might get printed multiple times */ + CORRADE_COMPARE(Utility::String::partition(out.str(), '\n')[0], "Trade::MaterialData: duplicate attribute DiffuseTextureCoordinates"); +} + +void MaterialDataTest::constructFromImmutableSortedArray() { + constexpr MaterialAttributeData attributes[]{ + {"hello this is first"_s, 1}, + {"yay this is last"_s, Vector4{0.2f, 0.6f, 0.4f, 1.0f}} + }; + + MaterialData data{{}, Containers::Array{const_cast(attributes), Containers::arraySize(attributes), [](MaterialAttributeData*, std::size_t) {}}}; + + CORRADE_COMPARE(data.attributeCount(), 2); + CORRADE_COMPARE(data.attributeName(0), "hello this is first"); + CORRADE_COMPARE(data.attributeName(1), "yay this is last"); +} + +void MaterialDataTest::constructLayers() { + int state; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + + /* Layer name gets sorted first by the constructor */ + {"highlightColor", 0x335566ff_rgbaf}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialLayer::ClearCoat}, + + /* Empty layer here */ + + /* Unnamed but nonempty layer */ + {"thickness", 0.015f}, + {MaterialAttribute::NormalTexture, 3u} + }, { + 2, 5, 5, 7 + }, &state}; + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.importerState(), &state); + + CORRADE_COMPARE(data.layerCount(), 4); + CORRADE_COMPARE(data.layerData().size(), 4); + + CORRADE_COMPARE(data.attributeData().size(), 7); + CORRADE_COMPARE(data.attributeCount(0), 2); + CORRADE_COMPARE(data.attributeCount(1), 3); + CORRADE_COMPARE(data.attributeCount(2), 0); + CORRADE_COMPARE(data.attributeCount(3), 2); + CORRADE_COMPARE(data.attributeCount("ClearCoat"), 3); + CORRADE_COMPARE(data.attributeCount(MaterialLayer::ClearCoat), 3); + + /* Layer access */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), "ClearCoat"); + CORRADE_COMPARE(data.layerName(2), ""); + CORRADE_COMPARE(data.layerName(3), ""); + + CORRADE_VERIFY(data.hasLayer("ClearCoat")); + CORRADE_VERIFY(data.hasLayer(MaterialLayer::ClearCoat)); + CORRADE_VERIFY(!data.hasLayer("")); + CORRADE_VERIFY(!data.hasLayer("DoubleSided")); + + CORRADE_COMPARE(data.layerId("ClearCoat"), 1); + CORRADE_COMPARE(data.layerId(MaterialLayer::ClearCoat), 1); + + /* Verify sorting in each layer */ + CORRADE_COMPARE(data.attributeName(0, 0), "DiffuseTextureCoordinates"); + CORRADE_COMPARE(data.attributeName(0, 1), "DoubleSided"); + + CORRADE_COMPARE(data.attributeName(1, 0), "$LayerName"); + CORRADE_COMPARE(data.attributeName(1, 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName(1, 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeName(3, 0), "NormalTexture"); + CORRADE_COMPARE(data.attributeName(3, 1), "thickness"); + + /* Access by layer ID and attribute ID */ + CORRADE_COMPARE(data.attributeType(0, 0), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(1, 2), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType(3, 1), MaterialAttributeType::Float); + + CORRADE_COMPARE(data.attribute(0, 0), 5); + CORRADE_COMPARE(data.attribute(1, 2), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute(3, 1), 0.015f); + + CORRADE_COMPARE(*static_cast(data.attribute(0, 0)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(1, 2)), 0x335566ff_rgbaf); + CORRADE_COMPARE(*static_cast(data.attribute(3, 1)), 0.015f); + + /* Access by layer ID and attribute name */ + CORRADE_VERIFY(data.hasAttribute(0, MaterialAttribute::DiffuseTextureCoordinates)); + CORRADE_VERIFY(!data.hasAttribute(0, MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute(1, MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute(1, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::LayerName)); + CORRADE_VERIFY(!data.hasAttribute(2, MaterialAttribute::NormalTexture)); + CORRADE_VERIFY(data.hasAttribute(3, MaterialAttribute::NormalTexture)); + + CORRADE_COMPARE(data.attributeId(0, MaterialAttribute::DiffuseTextureCoordinates), 0); + CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.attributeId(1, MaterialAttribute::LayerName), 0); + CORRADE_COMPARE(data.attributeId(3, MaterialAttribute::NormalTexture), 0); + + CORRADE_COMPARE(data.attributeType(0, MaterialAttribute::DiffuseTextureCoordinates), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(1, MaterialAttribute::AlphaBlend), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(1, MaterialAttribute::LayerName), MaterialAttributeType::String); + CORRADE_COMPARE(data.attributeType(3, MaterialAttribute::NormalTexture), MaterialAttributeType::UnsignedInt); + + CORRADE_COMPARE(data.attribute(0, MaterialAttribute::DiffuseTextureCoordinates), 5); + CORRADE_COMPARE(data.attribute(1, MaterialAttribute::AlphaBlend), true); + CORRADE_COMPARE(data.attribute(1, MaterialAttribute::LayerName), "ClearCoat"); + CORRADE_COMPARE(data.attribute(3, MaterialAttribute::NormalTexture), 3); + + CORRADE_COMPARE(*static_cast(data.attribute(0, MaterialAttribute::DiffuseTextureCoordinates)), 5); + CORRADE_COMPARE(*static_cast(data.attribute(1, MaterialAttribute::AlphaBlend)), true); + CORRADE_COMPARE(static_cast(data.attribute(1, MaterialAttribute::LayerName)), "ClearCoat"_s); + CORRADE_COMPARE(*static_cast(data.attribute(3, MaterialAttribute::NormalTexture)), 3); + + /* Access by layer ID and attribute string */ + CORRADE_VERIFY(data.hasAttribute(0, "DoubleSided")); + CORRADE_VERIFY(!data.hasAttribute(0, "highlightColor")); + CORRADE_VERIFY(data.hasAttribute(1, "highlightColor")); + CORRADE_VERIFY(data.hasAttribute(1, "$LayerName")); + CORRADE_VERIFY(!data.hasAttribute(2, "$LayerName")); + CORRADE_VERIFY(!data.hasAttribute(2, "NormalTexture")); + CORRADE_VERIFY(data.hasAttribute(3, "NormalTexture")); + + CORRADE_COMPARE(data.attributeId(0, "DoubleSided"), 1); + CORRADE_COMPARE(data.attributeId(1, "highlightColor"), 2); + CORRADE_COMPARE(data.attributeId(1, "$LayerName"), 0); + CORRADE_COMPARE(data.attributeId(3, "NormalTexture"), 0); + + CORRADE_COMPARE(data.attributeType(0, "DoubleSided"), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(1, "highlightColor"), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType(1, "$LayerName"), MaterialAttributeType::String); + CORRADE_COMPARE(data.attributeType(3, "NormalTexture"), MaterialAttributeType::UnsignedInt); + + CORRADE_COMPARE(data.attribute(0, "DoubleSided"), true); + CORRADE_COMPARE(data.attribute(1, "highlightColor"), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute(1, "$LayerName"), "ClearCoat"); + CORRADE_COMPARE(data.attribute(3, "NormalTexture"), 3); + + CORRADE_COMPARE(*static_cast(data.attribute(0, "DoubleSided")), true); + CORRADE_COMPARE(*static_cast(data.attribute(1, "highlightColor")), 0x335566ff_rgbaf); + CORRADE_COMPARE(static_cast(data.attribute(1, "$LayerName")), "ClearCoat"_s); + CORRADE_COMPARE(*static_cast(data.attribute(3, "NormalTexture")), 3); + + /* Access by layer name and attribute ID */ + CORRADE_COMPARE(data.attributeName(MaterialLayer::ClearCoat, 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName(MaterialLayer::ClearCoat, 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, 1), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, 2), MaterialAttributeType::Vector4); + + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, 1), true); + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, 2), 0x335566ff_rgbaf); + + CORRADE_COMPARE(*static_cast(data.attribute(MaterialLayer::ClearCoat, 1)), true); + CORRADE_COMPARE(*static_cast(data.attribute(MaterialLayer::ClearCoat, 2)), 0x335566ff_rgbaf); + + /* Access by layer name and attribute name */ + CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, MaterialAttribute::LayerName)); + + CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), 0); + + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend), true); + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, MaterialAttribute::LayerName), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute(MaterialLayer::ClearCoat, MaterialAttribute::AlphaBlend)), true); + CORRADE_COMPARE(static_cast(data.attribute(MaterialLayer::ClearCoat, MaterialAttribute::LayerName)), "ClearCoat"_s); + + /* Access by layer name and attribute string */ + CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, "highlightColor")); + CORRADE_VERIFY(data.hasAttribute(MaterialLayer::ClearCoat, "$LayerName")); + + CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, "highlightColor"), 2); + CORRADE_COMPARE(data.attributeId(MaterialLayer::ClearCoat, "$LayerName"), 0); + + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, "highlightColor"), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType(MaterialLayer::ClearCoat, "$LayerName"), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, "highlightColor"), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute(MaterialLayer::ClearCoat, "$LayerName"), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute(MaterialLayer::ClearCoat, "highlightColor")), 0x335566ff_rgbaf); + CORRADE_COMPARE(static_cast(data.attribute(MaterialLayer::ClearCoat, "$LayerName")), "ClearCoat"_s); + + /* Access by layer string and attribute ID */ + CORRADE_COMPARE(data.attributeName("ClearCoat", 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName("ClearCoat", 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeType("ClearCoat", 1), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType("ClearCoat", 2), MaterialAttributeType::Vector4); + + CORRADE_COMPARE(data.attribute("ClearCoat", 1), true); + CORRADE_COMPARE(data.attribute("ClearCoat", 2), 0x335566ff_rgbaf); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", 1)), true); + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", 2)), 0x335566ff_rgbaf); + + /* Access by layer string and attribute name */ + CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::AlphaBlend)); + CORRADE_VERIFY(data.hasAttribute("ClearCoat", MaterialAttribute::LayerName)); + + CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::AlphaBlend), 1); + CORRADE_COMPARE(data.attributeId("ClearCoat", MaterialAttribute::LayerName), 0); + + CORRADE_COMPARE(data.attributeType("ClearCoat", MaterialAttribute::AlphaBlend), MaterialAttributeType::Bool); + CORRADE_COMPARE(data.attributeType("ClearCoat", MaterialAttribute::LayerName), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute("ClearCoat", MaterialAttribute::AlphaBlend), true); + CORRADE_COMPARE(data.attribute("ClearCoat", MaterialAttribute::LayerName), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", MaterialAttribute::AlphaBlend)), true); + CORRADE_COMPARE(static_cast(data.attribute("ClearCoat", MaterialAttribute::LayerName)), "ClearCoat"_s); + + /* Access by layer string and attribute string */ + CORRADE_VERIFY(data.hasAttribute("ClearCoat", "highlightColor")); + CORRADE_VERIFY(data.hasAttribute("ClearCoat", "$LayerName")); + + CORRADE_COMPARE(data.attributeId("ClearCoat", "highlightColor"), 2); + CORRADE_COMPARE(data.attributeId("ClearCoat", "$LayerName"), 0); + + CORRADE_COMPARE(data.attributeType("ClearCoat", "highlightColor"), MaterialAttributeType::Vector4); + CORRADE_COMPARE(data.attributeType("ClearCoat", "$LayerName"), MaterialAttributeType::String); + + CORRADE_COMPARE(data.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); + CORRADE_COMPARE(data.attribute("ClearCoat", "$LayerName"), "ClearCoat"); + + CORRADE_COMPARE(*static_cast(data.attribute("ClearCoat", "highlightColor")), 0x335566ff_rgbaf); + CORRADE_COMPARE(static_cast(data.attribute("ClearCoat", "$LayerName")), "ClearCoat"_s); +} + +void MaterialDataTest::constructLayersNotMonotonic() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::NormalTexture, 3u} + }, {2, 5, 4, 5}}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (5, 4) for layer 2 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructLayersOffsetOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::NormalTexture, 3u} + }, {2, 6}}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (2, 6) for layer 1 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructNonOwned() { + constexpr MaterialAttributeData attributes[]{ + {"AmbientTextureMatrix"_s, Matrix3{{0.5f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 1.0f}}}, + {"DiffuseTextureCoordinates"_s, 5u}, + {"DoubleSided"_s, true}, + {"highlightColor"_s, Vector4{0.2f, 0.6f, 0.4f, 1.0f}} + }; + + int state; + MaterialData data{MaterialType::Phong, {}, attributes, &state}; + + /* Expecting the same output as in construct() */ + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(!data.layerData()); + CORRADE_COMPARE(data.attributeCount(), 4); + CORRADE_COMPARE(data.attributeData().size(), 4); + CORRADE_COMPARE(data.attributeData().data(), attributes); + CORRADE_COMPARE(data.importerState(), &state); + + /* We sorted the input already */ + CORRADE_COMPARE(data.attributeName(0), "AmbientTextureMatrix"); + CORRADE_COMPARE(data.attributeName(1), "DiffuseTextureCoordinates"); + CORRADE_COMPARE(data.attributeName(2), "DoubleSided"); + CORRADE_COMPARE(data.attributeName(3), "highlightColor"); + + /* No need to verify the contents as there's no difference in access in + owned vs non-owned */ +} + +void MaterialDataTest::constructNonOwnedLayers() { + constexpr MaterialAttributeData attributes[]{ + {"DiffuseCoordinateSet"_s, 5u}, + {"DoubleSided"_s, true}, + + {"$LayerName"_s, "ClearCoat"_s}, + {"AlphaBlend"_s, true}, + {"highlightColor"_s, Vector4{0.2f, 0.6f, 0.4f, 1.0f}}, + + /* Empty layer here */ + + /* Unnamed but nonempty layer */ + {"NormalTexture"_s, 3u}, + {"thickness"_s, 0.015f} + }; + + constexpr UnsignedInt layers[]{ + 2, 5, 5, 7 + }; + + int state; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers, &state}; + + /* Expecting the same output as in constructLayers() */ + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_COMPARE(data.importerState(), &state); + + CORRADE_COMPARE(data.layerCount(), 4); + CORRADE_COMPARE(data.layerData().size(), 4); + CORRADE_COMPARE(data.layerData().data(), layers); + + CORRADE_COMPARE(data.attributeData().size(), 7); + CORRADE_COMPARE(data.attributeData().data(), attributes); + CORRADE_COMPARE(data.attributeCount(0), 2); + CORRADE_COMPARE(data.attributeCount(1), 3); + CORRADE_COMPARE(data.attributeCount(2), 0); + CORRADE_COMPARE(data.attributeCount(3), 2); + + /* Layer access */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), "ClearCoat"); + CORRADE_COMPARE(data.layerName(2), ""); + CORRADE_COMPARE(data.layerName(3), ""); + + /* We sorted the input already */ + CORRADE_COMPARE(data.attributeName(0, 0), "DiffuseCoordinateSet"); + CORRADE_COMPARE(data.attributeName(0, 1), "DoubleSided"); + + CORRADE_COMPARE(data.attributeName(1, 0), "$LayerName"); + CORRADE_COMPARE(data.attributeName(1, 1), "AlphaBlend"); + CORRADE_COMPARE(data.attributeName(1, 2), "highlightColor"); + + CORRADE_COMPARE(data.attributeName(3, 0), "NormalTexture"); + CORRADE_COMPARE(data.attributeName(3, 1), "thickness"); + + /* No need to verify the contents as there's no difference in access in + owned vs non-owned */ +} + +void MaterialDataTest::constructNonOwnedEmptyAttribute() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {"DiffuseTexture"_s, 12u}, + MaterialAttributeData{} + }; + + std::ostringstream out; + Error redirectError{&out}; + /* nullptr to avoid attributes interpreted as importerState */ + MaterialData{{}, {}, attributes, nullptr}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: attribute 1 doesn't specify anything\n"); +} + +void MaterialDataTest::constructNonOwnedNotSorted() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {"DiffuseTextureCoordinates"_s, 5u}, + {"DiffuseTexture"_s, 12u} + }; + + std::ostringstream out; + Error redirectError{&out}; + /* nullptr to avoid attributes interpreted as importerState */ + MaterialData{{}, {}, attributes, nullptr}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: DiffuseTexture has to be sorted before DiffuseTextureCoordinates if passing non-owned data\n"); +} + +void MaterialDataTest::constructNonOwnedDuplicateAttribute() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {"DiffuseTexture"_s, 35u}, + {"DiffuseTextureCoordinates"_s, 5u}, + {"DiffuseTextureCoordinates"_s, 12u} + }; + + std::ostringstream out; + Error redirectError{&out}; + /* nullptr to avoid attributes interpreted as importerState */ + MaterialData{{}, {}, attributes, nullptr}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: duplicate attribute DiffuseTextureCoordinates\n"); +} + +void MaterialDataTest::constructNonOwnedLayersNotMonotonic() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::NormalTexture, 3u} + }; + + UnsignedInt layers[]{ + 2, 5, 4, 5 + }; + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (5, 4) for layer 2 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructNonOwnedLayersOffsetOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialAttributeData attributes[]{ + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::NormalTexture, 3u} + }; + + UnsignedInt layers[]{ + 2, 6 + }; + + std::ostringstream out; + Error redirectError{&out}; + MaterialData data{MaterialType::Phong, + {}, attributes, + {}, layers}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData: invalid range (2, 6) for layer 1 with 5 attributes in total\n"); +} + +void MaterialDataTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void MaterialDataTest::constructMove() { + int state; + MaterialData a{MaterialType::Phong, { + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::AlphaBlend, true}, + {"boredomFactor", 5} + }, { + 1, 1, 3 + }, &state}; + + MaterialData b{std::move(a)}; + CORRADE_COMPARE(a.layerCount(), 1); + CORRADE_COMPARE(a.attributeCount(), 0); + CORRADE_COMPARE(b.types(), MaterialType::Phong); + CORRADE_COMPARE(b.layerCount(), 3); + CORRADE_COMPARE(b.attributeCount(2), 2); + CORRADE_COMPARE(b.attributeName(2, 0), "AlphaBlend"); + CORRADE_COMPARE(b.importerState(), &state); + + MaterialData c{MaterialTypes{}, { + {MaterialAttribute::AlphaMask, 0.5f} + }, {1}}; + c = std::move(b); + CORRADE_COMPARE(b.attributeCount(), 1); + CORRADE_COMPARE(b.layerCount(), 1); + CORRADE_COMPARE(c.types(), MaterialType::Phong); + CORRADE_COMPARE(c.layerCount(), 3); + CORRADE_COMPARE(c.attributeCount(2), 2); + CORRADE_COMPARE(c.attributeName(2, 0), "AlphaBlend"); + CORRADE_COMPARE(c.importerState(), &state); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void MaterialDataTest::as() { + int state; + MaterialData data{MaterialType::Phong|MaterialType::PbrSpecularGlossiness, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::SpecularColor, 0x33556600_rgbaf}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {"highlightColor", 0x335566ff_rgbaf} + }, { + 2, 4 + }, &state}; + + auto& phong = data.as(); + CORRADE_COMPARE(phong.importerState(), &state); + CORRADE_COMPARE(phong.layerCount(), 2); + CORRADE_COMPARE(phong.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(phong.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); + + auto& specularGlossiness = data.as(); + CORRADE_COMPARE(specularGlossiness.importerState(), &state); + CORRADE_COMPARE(specularGlossiness.layerCount(), 2); + CORRADE_COMPARE(specularGlossiness.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(specularGlossiness.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); +} + +void MaterialDataTest::asRvalue() { + int state; + MaterialData data{MaterialType::Phong|MaterialType::PbrSpecularGlossiness, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::SpecularColor, 0x33556600_rgbaf}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {"highlightColor", 0x335566ff_rgbaf} + }, { + 2, 4 + }, &state}; + + auto phong = std::move(data).as(); + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_COMPARE(phong.layerCount(), 2); + CORRADE_COMPARE(phong.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(phong.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); + + auto specularGlossiness = std::move(phong).as(); + CORRADE_COMPARE(phong.layerCount(), 1); + CORRADE_COMPARE(specularGlossiness.layerCount(), 2); + CORRADE_COMPARE(specularGlossiness.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(specularGlossiness.attribute("ClearCoat", "highlightColor"), 0x335566ff_rgbaf); +} + +void MaterialDataTest::access() { + MaterialData a{{}, { + {MaterialAttribute::DoubleSided, false}, + {MaterialAttribute::AlphaBlend, true}, + {MaterialAttribute::AlphaMask, 0.9f} + }}; + CORRADE_VERIFY(!a.isDoubleSided()); + CORRADE_COMPARE(a.alphaMode(), MaterialAlphaMode::Blend); + CORRADE_COMPARE(a.alphaMask(), 0.9f); + + MaterialData b{{}, { + {MaterialAttribute::AlphaBlend, false}, + {MaterialAttribute::AlphaMask, 0.3f} + }}; + CORRADE_VERIFY(!b.isDoubleSided()); + CORRADE_COMPARE(b.alphaMode(), MaterialAlphaMode::Mask); + CORRADE_COMPARE(b.alphaMask(), 0.3f); + + MaterialData c{{}, { + {MaterialAttribute::DoubleSided, true}, + }}; + CORRADE_VERIFY(c.isDoubleSided()); + CORRADE_COMPARE(c.alphaMode(), MaterialAlphaMode::Opaque); + CORRADE_COMPARE(c.alphaMask(), 0.5f); +} + +void MaterialDataTest::accessPointer() { + const Float a = 3.0f; + Long b = -4; + + MaterialData data{{}, { + {"pointer", &a}, + {"mutable", &b} + }}; + CORRADE_COMPARE(data.attributeType("pointer"), MaterialAttributeType::Pointer); + CORRADE_COMPARE(data.attributeType("mutable"), MaterialAttributeType::MutablePointer); + + CORRADE_COMPARE(*static_cast(data.attribute("pointer")), &a); + CORRADE_COMPARE(*static_cast(data.attribute("mutable")), &b); + CORRADE_COMPARE(data.attribute("pointer"), &a); + CORRADE_COMPARE(data.attribute("mutable"), &b); +} + +void MaterialDataTest::accessString() { + MaterialData data{{}, { + {"name?", "THIS IS\0WHO I AM!"_s} + }}; + CORRADE_COMPARE(data.attributeType("name?"), MaterialAttributeType::String); + + /* Pointer access will stop at the first null byte, but typed access won't */ + CORRADE_COMPARE(static_cast(data.attribute(0)), "THIS IS"_s); + CORRADE_COMPARE(data.attribute(0), "THIS IS\0WHO I AM!"_s); + CORRADE_COMPARE(data.attribute(0).flags(), Containers::StringViewFlag::NullTerminated); + CORRADE_COMPARE(data.attribute(0)[data.attribute(0).size()], '\0'); +} + +void MaterialDataTest::accessTextureSwizzle() { + MaterialData data{{}, { + {"normalSwizzle", MaterialTextureSwizzle::BA} + }}; + CORRADE_COMPARE(data.attributeType("normalSwizzle"), MaterialAttributeType::TextureSwizzle); + + /* Pointer access will stop at the first null byte, printing the string + value */ + CORRADE_COMPARE(static_cast(data.attribute(0)), "BA"_s); + CORRADE_COMPARE(*static_cast(data.attribute(0)), MaterialTextureSwizzle::BA); + CORRADE_COMPARE(data.attribute(0), MaterialTextureSwizzle::BA); +} + +void MaterialDataTest::accessOptional() { + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute("SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute(MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute("SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute(MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute("SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute(MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr("SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr(MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute("DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute("DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr("DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr(MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(2); + data.attributeType(2); + data.attribute(2); + data.attribute(2); + data.attribute(2); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 0\n"); +} + +void MaterialDataTest::accessNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf} + }}; + + CORRADE_VERIFY(!data.hasAttribute("DiffuseColour")); + + std::ostringstream out; + Error redirectError{&out}; + data.attributeId("DiffuseColour"); + data.attributeType("DiffuseColour"); + data.attribute("DiffuseColour"); + data.attribute("DiffuseColour"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 0\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 0\n"); +} + +void MaterialDataTest::accessWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(0); + data.attribute(MaterialAttribute::DiffuseColor); + data.attribute("DiffuseColor"); + data.tryAttribute(MaterialAttribute::DiffuseColor); + data.tryAttribute("DiffuseColor"); + data.attributeOr(MaterialAttribute::DiffuseColor, Color3{1.0f}); + data.attributeOr("DiffuseColor", Color3{1.0f}); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + /* tryAttribute() and attributeOr() delegate to attribute() so the + assert is the same */ + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n" + "Trade::MaterialData::attribute(): improper type requested for DiffuseColor of Trade::MaterialAttributeType::Vector4\n"); +} + +void MaterialDataTest::accessWrongPointerType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Int a = 3; + const Double b = 57.0; + + MaterialData data{{}, { + {"mutablePointer", &a}, + {"pointer", &b} + }}; + + /* These are fine (type is not checked) */ + data.attribute("mutablePointer"); + data.attribute("pointer"); + + std::ostringstream out; + Error redirectError{&out}; + data.attribute("mutablePointer"); + data.attribute("pointer"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): improper type requested for mutablePointer of Trade::MaterialAttributeType::MutablePointer\n" + "Trade::MaterialData::attribute(): improper type requested for pointer of Trade::MaterialAttributeType::Pointer\n"); +} + +void MaterialDataTest::accessWrongTypeString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {"Shininess", 0.0f} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(0); + data.attribute(MaterialAttribute::Shininess); + data.attribute("Shininess"); + data.tryAttribute(MaterialAttribute::Shininess); + data.tryAttribute("Shininess"); + data.attributeOr(MaterialAttribute::Shininess, Containers::StringView{}); + data.attributeOr("Shininess", Containers::StringView{}); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + /* tryAttribute() and attributeOr() delegate to attribute() so the + assert is the same */ + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n" + "Trade::MaterialData::attribute(): Shininess of Trade::MaterialAttributeType::Float can't be retrieved as a string\n"); +} + +void MaterialDataTest::accessLayers() { + MaterialData data{{}, { + {MaterialAttribute::LayerName, "decals"}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::LayerFactor, 0.5f}, + }, { + 0, 1, 3 + }}; + + CORRADE_COMPARE(data.layerCount(), 3); + + CORRADE_COMPARE(data.layerFactor(2), 0.5f); + CORRADE_COMPARE(data.layerFactor("ClearCoat"), 0.5f); + CORRADE_COMPARE(data.layerFactor(MaterialLayer::ClearCoat), 0.5f); +} + +void MaterialDataTest::accessLayersDefaults() { + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + }, { + 0, 1 + }}; + + CORRADE_COMPARE(data.layerCount(), 2); + + CORRADE_COMPARE(data.layerFactor(1), 1.0f); + CORRADE_COMPARE(data.layerFactor("ClearCoat"), 1.0f); + CORRADE_COMPARE(data.layerFactor(MaterialLayer::ClearCoat), 1.0f); +} + +void MaterialDataTest::accessLayersTextured() { + MaterialData data{{}, { + {MaterialAttribute::LayerName, "decals"}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::LayerFactor, 0.5f}, + {MaterialAttribute::LayerFactorTexture, 4u}, + {MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::LayerFactorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::LayerFactorTextureCoordinates, 2u}, + }, { + 0, 1, 7 + }}; + + CORRADE_COMPARE(data.layerCount(), 3); + + CORRADE_COMPARE(data.layerFactor(2), 0.5f); + CORRADE_COMPARE(data.layerFactor("ClearCoat"), 0.5f); + CORRADE_COMPARE(data.layerFactor(MaterialLayer::ClearCoat), 0.5f); + + CORRADE_COMPARE(data.layerFactorTexture(2), 4u); + CORRADE_COMPARE(data.layerFactorTexture("ClearCoat"), 4u); + CORRADE_COMPARE(data.layerFactorTexture(MaterialLayer::ClearCoat), 4u); + + CORRADE_COMPARE(data.layerFactorTextureSwizzle(2), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.layerFactorTextureSwizzle("ClearCoat"), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.layerFactorTextureSwizzle(MaterialLayer::ClearCoat), MaterialTextureSwizzle::A); + + CORRADE_COMPARE(data.layerFactorTextureMatrix(2), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix("ClearCoat"), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix(MaterialLayer::ClearCoat), Matrix3::scaling({0.5f, 1.0f})); + + CORRADE_COMPARE(data.layerFactorTextureCoordinates(2), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates("ClearCoat"), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(MaterialLayer::ClearCoat), 2u); +} + +void MaterialDataTest::accessLayersTexturedDefault() { + MaterialData data{{}, { + {MaterialAttribute::LayerName, "decals"}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::LayerFactorTexture, 3u}, + }, { + 0, 1, 3 + }}; + + CORRADE_COMPARE(data.layerCount(), 3); + + CORRADE_COMPARE(data.layerFactor(2), 1.0f); + CORRADE_COMPARE(data.layerFactor("ClearCoat"), 1.0f); + CORRADE_COMPARE(data.layerFactor(MaterialLayer::ClearCoat), 1.0f); + + CORRADE_COMPARE(data.layerFactorTexture(2), 3u); + CORRADE_COMPARE(data.layerFactorTexture("ClearCoat"), 3u); + CORRADE_COMPARE(data.layerFactorTexture(MaterialLayer::ClearCoat), 3u); + + CORRADE_COMPARE(data.layerFactorTextureSwizzle(2), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.layerFactorTextureSwizzle("ClearCoat"), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.layerFactorTextureSwizzle(MaterialLayer::ClearCoat), MaterialTextureSwizzle::R); + + CORRADE_COMPARE(data.layerFactorTextureMatrix(2), Matrix3{}); + CORRADE_COMPARE(data.layerFactorTextureMatrix("ClearCoat"), Matrix3{}); + CORRADE_COMPARE(data.layerFactorTextureMatrix(MaterialLayer::ClearCoat), Matrix3{}); + + CORRADE_COMPARE(data.layerFactorTextureCoordinates(2), 0); + CORRADE_COMPARE(data.layerFactorTextureCoordinates("ClearCoat"), 0); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(MaterialLayer::ClearCoat), 0); +} + +void MaterialDataTest::accessLayersTexturedSingleMatrixCoordinates() { + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::LayerFactorTexture, 4u}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::TextureCoordinates, 2u}, + }, { + 0, 4 + }}; + + CORRADE_COMPARE(data.layerFactorTextureMatrix(1), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix("ClearCoat"), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix(MaterialLayer::ClearCoat), Matrix3::scaling({0.5f, 1.0f})); + + CORRADE_COMPARE(data.layerFactorTextureCoordinates(1), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates("ClearCoat"), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(MaterialLayer::ClearCoat), 2u); +} + +void MaterialDataTest::accessLayersTexturedBaseMaterialMatrixCoordinates() { + MaterialData data{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::TextureCoordinates, 2u}, + + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::LayerFactorTexture, 4u}, + }, { + 2, 4 + }}; + + CORRADE_COMPARE(data.layerFactorTextureMatrix(1), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix("ClearCoat"), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureMatrix(MaterialLayer::ClearCoat), Matrix3::scaling({0.5f, 1.0f})); + + CORRADE_COMPARE(data.layerFactorTextureCoordinates(1), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates("ClearCoat"), 2u); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(MaterialLayer::ClearCoat), 2u); +} + +void MaterialDataTest::accessLayersInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + }, {0, 1}}; + + std::ostringstream out; + Error redirectError{&out}; + data.layerFactorTexture(1); + data.layerFactorTexture("ClearCoat"); + data.layerFactorTexture(MaterialLayer::ClearCoat); + data.layerFactorTextureSwizzle(1); + data.layerFactorTextureSwizzle("ClearCoat"); + data.layerFactorTextureSwizzle(MaterialLayer::ClearCoat); + data.layerFactorTextureMatrix(1); + data.layerFactorTextureMatrix("ClearCoat"); + data.layerFactorTextureMatrix(MaterialLayer::ClearCoat); + data.layerFactorTextureCoordinates(1); + data.layerFactorTextureCoordinates("ClearCoat"); + data.layerFactorTextureCoordinates(MaterialLayer::ClearCoat); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): attribute LayerFactorTexture not found in layer 1\n" + "Trade::MaterialData::attribute(): attribute LayerFactorTexture not found in layer ClearCoat\n" + "Trade::MaterialData::attribute(): attribute LayerFactorTexture not found in layer ClearCoat\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): layer 1 doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): layer ClearCoat doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): layer ClearCoat doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureMatrix(): layer 1 doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureMatrix(): layer ClearCoat doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureMatrix(): layer ClearCoat doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): layer 1 doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): layer ClearCoat doesn't have a factor texture\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): layer ClearCoat doesn't have a factor texture\n"); +} + +void MaterialDataTest::accessLayerLayerNameInBaseMaterial() { + MaterialData data{{}, { + {MaterialAttribute::Shininess, 50.0f}, + {MaterialAttribute::LayerName, "base material name"} + }}; + + /* To avoid confusing the base material with a layer, LayerName is ignored + for the base material. */ + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_VERIFY(!data.hasLayer("base material name")); +} + +void MaterialDataTest::accessLayerEmptyLayer() { + /* If a layer is empty, its contents shouldn't leak into upper layers */ + MaterialData data{{}, { + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::LayerName, "crumples"} + }, {0, 0, 2}}; + + CORRADE_COMPARE(data.layerName(0), ""); + CORRADE_COMPARE(data.layerName(1), ""); + CORRADE_COMPARE(data.layerName(2), "crumples"); + CORRADE_COMPARE(data.attributeCount(0), 0); + CORRADE_COMPARE(data.attributeCount(1), 0); + CORRADE_COMPARE(data.attributeCount(2), 2); + CORRADE_COMPARE(data.layerId("crumples"), 2); + CORRADE_COMPARE(data.attribute("crumples", MaterialAttribute::NormalTexture), 3u); +} + +void MaterialDataTest::accessLayerIndexOptional() { + MaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0x335566ff_rgbaf}, + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {1, 3}}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute(1, "SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute(1, MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute(1, "SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute(1, MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute(1, "SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute(1, MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr(1, "SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr(1, MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute(1, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(1, MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute(1, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(1, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr(1, "DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr(1, MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessLayerNameOptional() { + MaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0x335566ff_rgbaf}, + {MaterialLayer::ClearCoat}, + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {1, 4}}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute(MaterialLayer::ClearCoat, "SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute(MaterialLayer::ClearCoat, MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute(MaterialLayer::ClearCoat, "SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute(MaterialLayer::ClearCoat, MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute(MaterialLayer::ClearCoat, "SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute(MaterialLayer::ClearCoat, MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr(MaterialLayer::ClearCoat, "SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr(MaterialLayer::ClearCoat, MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute(MaterialLayer::ClearCoat, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialLayer::ClearCoat, MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute(MaterialLayer::ClearCoat, "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute(MaterialLayer::ClearCoat, MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr(MaterialLayer::ClearCoat, "DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr(MaterialLayer::ClearCoat, MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessLayerStringOptional() { + MaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0x335566ff_rgbaf}, + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {1, 4}}; + + /* This exists */ + CORRADE_VERIFY(data.tryAttribute("ClearCoat", "SpecularTexture")); + CORRADE_VERIFY(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture)); + CORRADE_COMPARE(*static_cast(data.tryAttribute("ClearCoat", "SpecularTexture")), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture)), 3); + CORRADE_COMPARE(data.tryAttribute("ClearCoat", "SpecularTexture"), 3); + CORRADE_COMPARE(data.tryAttribute("ClearCoat", MaterialAttribute::SpecularTexture), 3); + CORRADE_COMPARE(data.attributeOr("ClearCoat", "SpecularTexture", 5u), 3); + CORRADE_COMPARE(data.attributeOr("ClearCoat", MaterialAttribute::SpecularTexture, 5u), 3); + + /* This doesn't */ + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", MaterialAttribute::DiffuseTexture)); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", "DiffuseTexture")); + CORRADE_VERIFY(!data.tryAttribute("ClearCoat", MaterialAttribute::DiffuseTexture)); + CORRADE_COMPARE(data.attributeOr("ClearCoat", "DiffuseTexture", 5u), 5); + CORRADE_COMPARE(data.attributeOr("ClearCoat", MaterialAttribute::DiffuseTexture, 5u), 5); +} + +void MaterialDataTest::accessLayerOutOfBounds() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.layerName(2); + data.layerFactor(2); + data.layerFactorTexture(2); + data.layerFactorTextureSwizzle(2); + data.layerFactorTextureMatrix(2); + data.layerFactorTextureCoordinates(2); + data.attributeCount(2); + data.hasAttribute(2, "AlphaMask"); + data.hasAttribute(2, MaterialAttribute::AlphaMask); + data.attributeId(2, "AlphaMask"); + data.attributeId(2, MaterialAttribute::AlphaMask); + data.attributeName(2, 0); + data.attributeType(2, 0); + data.attributeType(2, "AlphaMask"); + data.attributeType(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.attribute(2, "AlphaMask"); + data.attribute(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.attribute(2, "AlphaMask"); + data.attribute(2, MaterialAttribute::AlphaMask); + data.attribute(2, 0); + data.tryAttribute(2, "AlphaMask"); + data.tryAttribute(2, MaterialAttribute::AlphaMask); + data.tryAttribute(2, "AlphaMask"); + data.tryAttribute(2, MaterialAttribute::AlphaMask); + data.attributeOr(2, "AlphaMask", false); + data.attributeOr(2, MaterialAttribute::AlphaMask, false); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::layerName(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::layerFactor(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::layerFactorTexture(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::layerFactorTextureMatrix(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeCount(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::hasAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeId(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeName(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::tryAttribute(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeOr(): index 2 out of range for 2 layers\n" + "Trade::MaterialData::attributeOr(): index 2 out of range for 2 layers\n"); +} + +void MaterialDataTest::accessLayerNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "clearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f}, + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.layerId("ClearCoat"); + data.layerFactor("ClearCoat"); + data.layerFactorTexture("ClearCoat"); + data.layerFactorTextureSwizzle("ClearCoat"); + data.layerFactorTextureMatrix("ClearCoat"); + data.layerFactorTextureCoordinates("ClearCoat"); + data.attributeCount("ClearCoat"); + data.hasAttribute("ClearCoat", "AlphaMask"); + data.hasAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeId("ClearCoat", "AlphaMask"); + data.attributeId("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeName("ClearCoat", 0); + data.attributeType("ClearCoat", 0); + data.attributeType("ClearCoat", "AlphaMask"); + data.attributeType("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.attribute("ClearCoat", "AlphaMask"); + data.attribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.attribute("ClearCoat", "AlphaMask"); + data.attribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attribute("ClearCoat", 0); + data.tryAttribute("ClearCoat", "AlphaMask"); + data.tryAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.tryAttribute("ClearCoat", "AlphaMask"); + data.tryAttribute("ClearCoat", MaterialAttribute::AlphaMask); + data.attributeOr("ClearCoat", "AlphaMask", false); + data.attributeOr("ClearCoat", MaterialAttribute::AlphaMask, false); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::layerId(): layer ClearCoat not found\n" + "Trade::MaterialData::layerFactor(): layer ClearCoat not found\n" + "Trade::MaterialData::layerFactorTexture(): layer ClearCoat not found\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): layer ClearCoat not found\n" + "Trade::MaterialData::layerFactorTextureMatrix(): layer ClearCoat not found\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeCount(): layer ClearCoat not found\n" + "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::hasAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeId(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeName(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeType(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::tryAttribute(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeOr(): layer ClearCoat not found\n" + "Trade::MaterialData::attributeOr(): layer ClearCoat not found\n"); +} + +void MaterialDataTest::accessInvalidLayerName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, {}}; + + std::ostringstream out; + Error redirectError{&out}; + data.layerId(MaterialLayer(0x0)); + data.layerId(MaterialLayer(0xfefe)); + data.layerFactor(MaterialLayer(0xfefe)); + data.layerFactorTexture(MaterialLayer(0xfefe)); + data.layerFactorTextureSwizzle(MaterialLayer(0xfefe)); + data.layerFactorTextureMatrix(MaterialLayer(0xfefe)); + data.layerFactorTextureCoordinates(MaterialLayer(0xfefe)); + data.attributeCount(MaterialLayer(0xfefe)); + data.hasAttribute(MaterialLayer(0xfefe), "AlphaMask"); + data.hasAttribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attributeId(MaterialLayer(0xfefe), "AlphaMask"); + data.attributeId(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attributeName(MaterialLayer(0xfefe), 0); + data.attributeType(MaterialLayer(0xfefe), 0); + data.attributeType(MaterialLayer(0xfefe), "AlphaMask"); + data.attributeType(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attribute(MaterialLayer(0xfefe), 0); + data.attribute(MaterialLayer(0xfefe), "AlphaMask"); + data.attribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attribute(MaterialLayer(0xfefe), 0); + data.attribute(MaterialLayer(0xfefe), "AlphaMask"); + data.attribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attribute(MaterialLayer(0xfefe), 0); + data.tryAttribute(MaterialLayer(0xfefe), "AlphaMask"); + data.tryAttribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.tryAttribute(MaterialLayer(0xfefe), "AlphaMask"); + data.tryAttribute(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask); + data.attributeOr(MaterialLayer(0xfefe), "AlphaMask", false); + data.attributeOr(MaterialLayer(0xfefe), MaterialAttribute::AlphaMask, false); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0x0)\n" + "Trade::MaterialData::layerId(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::layerFactor(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::layerFactorTexture(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::layerFactorTextureSwizzle(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::layerFactorTextureMatrix(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::layerFactorTextureCoordinates(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeCount(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeName(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialLayer(0xfefe)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialLayer(0xfefe)\n"); +} + +void MaterialDataTest::accessOutOfBoundsInLayerIndex() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::AlphaMask, 0.5f}, + {MaterialAttribute::SpecularTexture, 3u} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(1, 2); + data.attributeType(1, 2); + data.attribute(1, 2); + data.attribute(1, 2); + data.attribute(1, 2); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer 1\n"); +} + +void MaterialDataTest::accessOutOfBoundsInLayerString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + {MaterialAttribute::AlphaMask, 0.5f} + }, {0, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName("ClearCoat", 2); + data.attributeType("ClearCoat", 2); + data.attribute("ClearCoat", 2); + data.attribute("ClearCoat", 2); + data.attribute("ClearCoat", 2); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes in layer ClearCoat\n"); +} + +void MaterialDataTest::accessNotFoundInLayerIndex() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf} + }, {0, 1}}; + + CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + + std::ostringstream out; + Error redirectError{&out}; + data.attributeId(1, "DiffuseColour"); + data.attributeType(1, "DiffuseColour"); + data.attribute(1, "DiffuseColour"); + data.attribute(1, "DiffuseColour"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 1\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer 1\n"); +} + +void MaterialDataTest::accessNotFoundInLayerString() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, { + {MaterialAttribute::LayerName, "ClearCoat"}, + {"DiffuseColor", 0xff3366aa_rgbaf} + }, {0, 1}}; + + CORRADE_VERIFY(!data.hasAttribute(1, "DiffuseColour")); + + std::ostringstream out; + Error redirectError{&out}; + data.attributeId("ClearCoat", "DiffuseColour"); + data.attributeType("ClearCoat", "DiffuseColour"); + data.attribute("ClearCoat", "DiffuseColour"); + data.attribute("ClearCoat", "DiffuseColour"); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeId(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer ClearCoat\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found in layer ClearCoat\n"); +} + +void MaterialDataTest::accessInvalidAttributeName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{{}, {}}; + + /* The name should be converted to a string first and foremost and only + then delegated to another overload. Which means all asserts should + print the leaf function name. */ + std::ostringstream out; + Error redirectError{&out}; + data.hasAttribute(0, MaterialAttribute(0x0)); + data.hasAttribute("Layer", MaterialAttribute(0xfefe)); + data.attributeId(0, MaterialAttribute(0x0)); + data.attributeId("Layer", MaterialAttribute(0xfefe)); + data.attributeType(0, MaterialAttribute(0x0)); + data.attributeType("Layer", MaterialAttribute(0xfefe)); + data.attribute(0, MaterialAttribute(0x0)); + data.attribute("Layer", MaterialAttribute(0xfefe)); + data.attribute(0, MaterialAttribute(0x0)); + data.attribute("Layer", MaterialAttribute(0xfefe)); + data.tryAttribute(0, MaterialAttribute(0x0)); + data.tryAttribute("Layer", MaterialAttribute(0xfefe)); + data.tryAttribute(0, MaterialAttribute(0x0)); + data.tryAttribute("Layer", MaterialAttribute(0xfefe)); + data.attributeOr(0, MaterialAttribute(0x0), 42); + data.attributeOr("Layer", MaterialAttribute(0xfefe), 42); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::hasAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeId(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeType(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::tryAttribute(): invalid name Trade::MaterialAttribute(0xfefe)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialData::attributeOr(): invalid name Trade::MaterialAttribute(0xfefe)\n"); +} + +void MaterialDataTest::releaseAttributes() { + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf}, + {MaterialAttribute::NormalTexture, 0u} + }, {1, 2}}; + + const void* pointer = data.attributeData().data(); + + Containers::Array released = data.releaseAttributeData(); + CORRADE_COMPARE(released.data(), pointer); + CORRADE_COMPARE(released.size(), 2); + CORRADE_VERIFY(data.layerData()); + CORRADE_COMPARE(data.layerCount(), 2); + CORRADE_VERIFY(!data.attributeData()); + /* This is based on the layer offsets, not an actual attribute count, so + it's inconsistent, yes */ + CORRADE_COMPARE(data.attributeCount(), 1); +} + +void MaterialDataTest::releaseLayers() { + MaterialData data{{}, { + {"DiffuseColor", 0xff3366aa_rgbaf}, + {MaterialAttribute::NormalTexture, 0u} + }, {1, 2}}; + + const void* pointer = data.layerData().data(); + + Containers::Array released = data.releaseLayerData(); + CORRADE_COMPARE(released.data(), pointer); + CORRADE_COMPARE(released.size(), 2); + CORRADE_VERIFY(!data.layerData()); + /* Returns always at least 1 (now it sees no layer data and thus thinks + there's just the implicit base material) */ + CORRADE_COMPARE(data.layerCount(), 1); + CORRADE_VERIFY(data.attributeData()); + /* No layer offsets anymore, so this is the total attribute count instead + of the base material attribute count. It's inconsistent, yes. */ + CORRADE_COMPARE(data.attributeCount(), 2); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void MaterialDataTest::constructPhongDeprecated() { + const int a{}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData data{PhongMaterialData::Flag::DoubleSided, + 0xccffbb_rgbf, {}, + 0xebefbf_rgbf, {}, + 0xacabad_rgbf, {}, {}, {}, + MaterialAlphaMode::Mask, 0.3f, 80.0f, &a}; + CORRADE_IGNORE_DEPRECATED_POP + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.type(), MaterialType::Phong); + CORRADE_COMPARE(data.flags(), PhongMaterialData::Flag::DoubleSided); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.ambientColor(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.diffuseColor(), 0xebefbf_rgbf); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.alphaMode(), MaterialAlphaMode::Mask); + CORRADE_COMPARE(data.alphaMask(), 0.3f); + CORRADE_COMPARE(data.shininess(), 80.0f); + CORRADE_COMPARE(data.importerState(), &a); +} + +void MaterialDataTest::constructPhongDeprecatedTextured() { + const int a{}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData data{ + PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture, + 0x111111_rgbf, 42, + 0xeebbff_rgbf, {}, + 0xacabad_rgbf, 17, {}, {}, + MaterialAlphaMode::Blend, 0.37f, 96.0f, &a}; + CORRADE_IGNORE_DEPRECATED_POP + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.type(), MaterialType::Phong); + CORRADE_COMPARE(data.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::SpecularTexture); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.ambientColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.ambientTexture(), 42); + CORRADE_COMPARE(data.ambientTextureCoordinates(), 0); + CORRADE_COMPARE(data.diffuseColor(), 0xeebbff_rgbf); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.specularTexture(), 17); + CORRADE_COMPARE(data.specularTextureCoordinates(), 0); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.alphaMode(), MaterialAlphaMode::Blend); + CORRADE_COMPARE(data.alphaMask(), 0.37f); + CORRADE_COMPARE(data.shininess(), 96.0f); + CORRADE_COMPARE(data.importerState(), &a); +} + +void MaterialDataTest::constructPhongDeprecatedTexturedTextureTransform() { + const int a{}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData data{ + PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation, + 0x111111_rgbf, {}, + 0xeebbff_rgbf, 42, + 0xacabad_rgbf, {}, 17, + Matrix3::rotation(90.0_degf), + MaterialAlphaMode::Mask, 0.5f, 96.0f, &a}; + CORRADE_IGNORE_DEPRECATED_POP + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.type(), MaterialType::Phong); + CORRADE_COMPARE(data.flags(), PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureTransformation); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.ambientColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.diffuseColor(), 0xeebbff_rgbf); + CORRADE_COMPARE(data.diffuseTexture(), 42); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.normalTexture(), 17); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3::rotation(90.0_degf)); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.alphaMode(), MaterialAlphaMode::Mask); + CORRADE_COMPARE(data.alphaMask(), 0.5f); + CORRADE_COMPARE(data.shininess(), 96.0f); + CORRADE_COMPARE(data.importerState(), &a); +} + +void MaterialDataTest::constructPhongDeprecatedTexturedCoordinates() { + const int a{}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData data{ + PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureCoordinates, + 0x111111_rgbf, 42, 3, + 0xeebbff_rgbf, {}, 6, + 0xacabad_rgbf, 17, 1, + 0, 8, {}, + MaterialAlphaMode::Blend, 0.37f, 96.0f, &a}; + CORRADE_IGNORE_DEPRECATED_POP + + CORRADE_COMPARE(data.types(), MaterialType::Phong); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.type(), MaterialType::Phong); + CORRADE_COMPARE(data.flags(), PhongMaterialData::Flag::AmbientTexture|PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::NormalTexture|PhongMaterialData::Flag::TextureCoordinates); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.ambientColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.ambientTexture(), 42); + CORRADE_COMPARE(data.ambientTextureCoordinates(), 3); + CORRADE_COMPARE(data.diffuseColor(), 0xeebbff_rgbf); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 6); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.specularTexture(), 17); + CORRADE_COMPARE(data.specularTextureCoordinates(), 1); + CORRADE_COMPARE(data.normalTexture(), 0); + CORRADE_COMPARE(data.normalTextureCoordinates(), 8); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(data.ambientTextureCoordinates(), 3); + CORRADE_COMPARE(data.alphaMode(), MaterialAlphaMode::Blend); + CORRADE_COMPARE(data.alphaMask(), 0.37f); + CORRADE_COMPARE(data.shininess(), 96.0f); + CORRADE_COMPARE(data.importerState(), &a); +} + +void MaterialDataTest::constructPhongDeprecatedTextureTransformNoTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData a{PhongMaterialData::Flag::TextureTransformation, + {}, {}, + {}, {}, + {}, {}, {}, {}, + {}, 0.5f, 80.0f}; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "Trade::PhongMaterialData: texture transformation enabled but the material has no textures\n"); +} + +void MaterialDataTest::constructPhongDeprecatedNoTextureTransformationFlag() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData a{{}, + {}, {}, + {}, {}, + {}, {}, {}, Matrix3::rotation(90.0_degf), + {}, 0.5f, 80.0f}; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "PhongMaterialData::PhongMaterialData: non-default texture matrix requires Flag::TextureTransformation to be enabled\n"); +} + +void MaterialDataTest::constructPhongDeprecatedNoTextureCoordinatesFlag() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + PhongMaterialData a{{}, + {}, {}, 1, + {}, {}, 2, + {}, {}, 3, {}, 4, {}, + {}, 0.5f, 80.0f}; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinates to be enabled\n"); +} +#endif + +void MaterialDataTest::pbrMetallicRoughnessAccess() { + MaterialData base{MaterialType::PbrMetallicRoughness, { + {MaterialAttribute::BaseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::Metalness, 0.5f}, + {MaterialAttribute::Roughness, 0.79f}, + {MaterialAttribute::EmissiveColor, 0x111111_rgbf} + }}; + + CORRADE_COMPARE(base.types(), MaterialType::PbrMetallicRoughness); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasMetalnessTexture()); + CORRADE_VERIFY(!data.hasRoughnessTexture()); + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.baseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(data.metalness(), 0.5f); + CORRADE_COMPARE(data.roughness(), 0.79f); + CORRADE_COMPARE(data.emissiveColor(), 0x111111_rgbf); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessDefaults() { + MaterialData base{{}, {}}; + + CORRADE_COMPARE(base.types(), MaterialTypes{}); + /* Casting is fine even if the type doesn't include PbrMetallicRoughness */ + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasMetalnessTexture()); + CORRADE_VERIFY(!data.hasRoughnessTexture()); + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.baseColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.metalness(), 1.0f); + CORRADE_COMPARE(data.roughness(), 1.0f); + CORRADE_COMPARE(data.emissiveColor(), 0x000000_rgbf); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTextured() { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::BaseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::BaseColorTexture, 0u}, + {MaterialAttribute::BaseColorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::BaseColorTextureCoordinates, 2u}, + {MaterialAttribute::Metalness, 0.5f}, + {MaterialAttribute::MetalnessTexture, 1u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u}, + {MaterialAttribute::Roughness, 0.79f}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({1.0f, 1.0f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 4u}, + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::NormalTextureScale, 0.35f}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::BA}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::scaling({1.0f, 0.5f})}, + {MaterialAttribute::NormalTextureCoordinates, 5u}, + {MaterialAttribute::OcclusionTexture, 4u}, + {MaterialAttribute::OcclusionTextureStrength, 0.66f}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({1.0f, 0.75f})}, + {MaterialAttribute::OcclusionTextureCoordinates, 6u}, + {MaterialAttribute::EmissiveColor, 0x111111_rgbf}, + {MaterialAttribute::EmissiveTexture, 5u}, + {MaterialAttribute::EmissiveTextureMatrix, Matrix3::scaling({0.75f, 0.5f})}, + {MaterialAttribute::EmissiveTextureCoordinates, 7u} + }}; + + CORRADE_VERIFY(data.hasMetalnessTexture()); + CORRADE_VERIFY(data.hasRoughnessTexture()); + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.baseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(data.baseColorTexture(), 0); + CORRADE_COMPARE(data.baseColorTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.baseColorTextureCoordinates(), 2); + CORRADE_COMPARE(data.metalness(), 0.5f); + CORRADE_COMPARE(data.metalnessTexture(), 1); + CORRADE_COMPARE(data.metalnessTextureSwizzle(), MaterialTextureSwizzle::G); + CORRADE_COMPARE(data.metalnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.metalnessTextureCoordinates(), 3); + CORRADE_COMPARE(data.roughness(), 0.79f); + CORRADE_COMPARE(data.roughnessTexture(), 2); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::scaling({1.0f, 1.0f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 4); + CORRADE_COMPARE(data.normalTexture(), 3); + CORRADE_COMPARE(data.normalTextureScale(), 0.35f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::BA); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::scaling({1.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 5); + CORRADE_COMPARE(data.occlusionTexture(), 4); + CORRADE_COMPARE(data.occlusionTextureStrength(), 0.66f); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3::scaling({1.0f, 0.75f})); + CORRADE_COMPARE(data.occlusionTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 6); + CORRADE_COMPARE(data.emissiveColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3::scaling({0.75f, 0.5f})); + CORRADE_COMPARE(data.emissiveTexture(), 5); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedDefaults() { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::BaseColorTexture, 1u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 3u}, + {MaterialAttribute::NormalTexture, 4u}, + {MaterialAttribute::OcclusionTexture, 5u}, + {MaterialAttribute::EmissiveTexture, 6u} + }}; + + CORRADE_VERIFY(data.hasMetalnessTexture()); + CORRADE_VERIFY(data.hasRoughnessTexture()); + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.baseColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.baseColorTexture(), 1); + CORRADE_COMPARE(data.baseColorTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.baseColorTextureCoordinates(), 0); + CORRADE_COMPARE(data.metalness(), 1.0f); + CORRADE_COMPARE(data.metalnessTexture(), 2); + CORRADE_COMPARE(data.metalnessTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.metalnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.metalnessTextureCoordinates(), 0); + CORRADE_COMPARE(data.roughness(), 1.0f); + CORRADE_COMPARE(data.roughnessTexture(), 3); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 0); + CORRADE_COMPARE(data.normalTexture(), 4); + CORRADE_COMPARE(data.normalTextureScale(), 1.0f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.normalTextureCoordinates(), 0); + CORRADE_COMPARE(data.occlusionTexture(), 5); + CORRADE_COMPARE(data.occlusionTextureStrength(), 1.0f); + CORRADE_COMPARE(data.occlusionTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 0); + CORRADE_COMPARE(data.emissiveColor(), 0x000000_rgbf); + CORRADE_COMPARE(data.emissiveTexture(), 6); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 0); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedSingleMatrixCoordinates() { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::BaseColorTexture, 1u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 3u}, + {MaterialAttribute::NormalTexture, 4u}, + {MaterialAttribute::OcclusionTexture, 5u}, + {MaterialAttribute::EmissiveTexture, 6u}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u} + }}; + + CORRADE_COMPARE(data.baseColorTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.baseColorTextureCoordinates(), 7); + CORRADE_COMPARE(data.metalnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.metalnessTextureCoordinates(), 7); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 7); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 7); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 7); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedImplicitPackedMetallicRoughness() { + /* Just the texture ID, the rest is implicit */ + { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NoneRoughnessMetallicTexture, 2u}, + }}; + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + CORRADE_COMPARE(data.roughnessTexture(), 2); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::G); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 0); + CORRADE_COMPARE(data.metalnessTexture(), 2); + CORRADE_COMPARE(data.metalnessTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.metalnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.metalnessTextureCoordinates(), 0); + + /* Explicit parameters for everything, but all the same */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NoneRoughnessMetallicTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + CORRADE_COMPARE(data.roughnessTexture(), 2); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::G); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 3); + CORRADE_COMPARE(data.metalnessTexture(), 2); + CORRADE_COMPARE(data.metalnessTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.metalnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.metalnessTextureCoordinates(), 3); + + /* Swizzle is ignored when the combined texture is specified, so this is + fine */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NoneRoughnessMetallicTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + }}; + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + + /* Unexpected texture matrix */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NoneRoughnessMetallicTexture, 2u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Unexpected texture coordinates */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NoneRoughnessMetallicTexture, 2u}, + {MaterialAttribute::RoughnessTextureCoordinates, 1u}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + } +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedMetallicRoughness() { + /* Just the texture IDs and swizzles, the rest is implicit */ + { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B} + }}; + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + + /* Explicit parameters for everything, but all the same */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + + /* Different texture ID */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 3u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B} + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* One texture missing */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Unexpected swizzle */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::R}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Unexpected texture matrix */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Unexpected texture coordinates */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureCoordinates, 1u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + } +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedRoughnessMetallicOcclusion() { + /* Just the texture IDs and swizzles, the rest is implicit */ + { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(data.hasRoughnessMetallicOcclusionTexture()); + /* This isn't a superset */ + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Explicit parameters for everything, but all the same */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::R}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::OcclusionTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasRoughnessMetallicOcclusionTexture()); + /* This isn't a superset */ + CORRADE_VERIFY(!data.hasNoneRoughnessMetallicTexture()); + + /* Different texture ID */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 3u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + + /* One texture missing */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + + /* Unexpected swizzle */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + + /* Unexpected texture matrix */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + }}; + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + + /* Unexpected texture coordinates */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureCoordinates, 1u}, + }}; + CORRADE_VERIFY(!data.hasRoughnessMetallicOcclusionTexture()); + } +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedOcclusionRoughnessMetallic() { + /* Just the texture IDs and swizzles, the rest is implicit */ + { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(data.hasOcclusionRoughnessMetallicTexture()); + /* This is a superset */ + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + + /* Explicit parameters for everything, but all the same */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::R}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::OcclusionTextureCoordinates, 3u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasOcclusionRoughnessMetallicTexture()); + /* This is a superset */ + CORRADE_VERIFY(data.hasNoneRoughnessMetallicTexture()); + + /* Different texture ID */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 3u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + + /* One texture missing */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + + /* Unexpected swizzle */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + + /* Unexpected texture matrix */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + + /* Unexpected texture coordinates */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::OcclusionTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTextureCoordinates, 1u}, + }}; + CORRADE_VERIFY(!data.hasOcclusionRoughnessMetallicTexture()); + } +} + +void MaterialDataTest::pbrMetallicRoughnessAccessTexturedExplicitPackedNormalRoughnessMetallic() { + /* Just the texture IDs and swizzles, the rest is implicit */ + { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RG}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(data.hasNormalRoughnessMetallicTexture()); + + /* Explicit parameters for everything, but all the same */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RG}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::NormalTextureCoordinates, 3u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::MetalnessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasNormalRoughnessMetallicTexture()); + + /* Different texture ID */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RG}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 3u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + + /* One texture missing */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A}, + }}; + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + + /* Unexpected swizzle */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RGB}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A}, + }}; + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + + /* Unexpected texture matrix */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RG}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A}, + }}; + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + + /* Unexpected texture coordinates */ + } { + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::NormalTexture, 2u}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::RG}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::MetalnessTexture, 2u}, + {MaterialAttribute::MetalnessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::MetalnessTextureCoordinates, 1u}, + }}; + CORRADE_VERIFY(!data.hasNormalRoughnessMetallicTexture()); + } +} + +void MaterialDataTest::pbrMetallicRoughnessAccessInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrMetallicRoughnessMaterialData data{{}, {}}; + + std::ostringstream out; + Error redirectError{&out}; + data.baseColorTexture(); + data.baseColorTextureMatrix(); + data.baseColorTextureCoordinates(); + data.metalnessTexture(); + data.metalnessTextureSwizzle(); + data.metalnessTextureMatrix(); + data.metalnessTextureCoordinates(); + data.roughnessTexture(); + data.roughnessTextureSwizzle(); + data.roughnessTextureMatrix(); + data.roughnessTextureCoordinates(); + data.normalTexture(); + data.normalTextureScale(); + data.normalTextureSwizzle(); + data.normalTextureMatrix(); + data.normalTextureCoordinates(); + data.occlusionTexture(); + data.occlusionTextureStrength(); + data.occlusionTextureSwizzle(); + data.occlusionTextureMatrix(); + data.occlusionTextureCoordinates(); + data.emissiveTexture(); + data.emissiveTextureMatrix(); + data.emissiveTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): attribute BaseColorTexture not found in layer 0\n" + "Trade::PbrMetallicRoughnessMaterialData::baseColorTextureMatrix(): the material doesn't have a base color texture\n" + "Trade::PbrMetallicRoughnessMaterialData::baseColorTextureCoordinates(): the material doesn't have a base color texture\n" + "Trade::PbrMetallicRoughnessMaterialData::metalnessTexture(): the material doesn't have a metalness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureSwizzle(): the material doesn't have a metalness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureMatrix(): the material doesn't have a metalness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::metalnessTextureCoordinates(): the material doesn't have a metalness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::roughnessTexture(): the material doesn't have a roughness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureSwizzle(): the material doesn't have a roughness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureMatrix(): the material doesn't have a roughness texture\n" + "Trade::PbrMetallicRoughnessMaterialData::roughnessTextureCoordinates(): the material doesn't have a roughness texture\n" + "Trade::MaterialData::attribute(): attribute NormalTexture not found in layer 0\n" + "Trade::PbrMetallicRoughnessMaterialData::normalTextureScale(): the material doesn't have a normal texture\n" + "Trade::PbrMetallicRoughnessMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture\n" + "Trade::PbrMetallicRoughnessMaterialData::normalTextureMatrix(): the material doesn't have a normal texture\n" + "Trade::PbrMetallicRoughnessMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture\n" + "Trade::MaterialData::attribute(): attribute OcclusionTexture not found in layer 0\n" + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureStrength(): the material doesn't have an occlusion texture\n" + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureSwizzle(): the material doesn't have an occlusion texture\n" + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureMatrix(): the material doesn't have an occlusion texture\n" + "Trade::PbrMetallicRoughnessMaterialData::occlusionTextureCoordinates(): the material doesn't have an occlusion texture\n" + "Trade::MaterialData::attribute(): attribute EmissiveTexture not found in layer 0\n" + "Trade::PbrMetallicRoughnessMaterialData::emissiveTextureMatrix(): the material doesn't have an emissive texture\n" + "Trade::PbrMetallicRoughnessMaterialData::emissiveTextureCoordinates(): the material doesn't have an emissive texture\n"); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesNoTextures() { + PbrMetallicRoughnessMaterialData a{{}, {}}; + CORRADE_VERIFY(a.hasCommonTextureTransformation()); + CORRADE_VERIFY(a.hasCommonTextureCoordinates()); + CORRADE_COMPARE(a.commonTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(a.commonTextureCoordinates(), 0); + + PbrMetallicRoughnessMaterialData b{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u} + }}; + CORRADE_VERIFY(b.hasCommonTextureTransformation()); + CORRADE_VERIFY(b.hasCommonTextureCoordinates()); + CORRADE_COMPARE(b.commonTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(b.commonTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneTexture() { + Containers::StringView textureName = PbrMetallicRoughnessTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrMetallicRoughnessMaterialData data{{}, { + {textureName, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These shouldn't affect the above */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasCommonTextureTransformation()); + CORRADE_COMPARE(data.commonTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_VERIFY(data.hasCommonTextureCoordinates()); + CORRADE_COMPARE(data.commonTextureCoordinates(), 17u); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessCommonTransformationCoordinatesOneDifferentTexture() { + Containers::StringView textureName = PbrMetallicRoughnessTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::BaseColorTexture, 2u}, + {MaterialAttribute::MetalnessTexture, 3u}, + {MaterialAttribute::RoughnessTexture, 4u}, + {MaterialAttribute::NormalTexture, 5u}, + {MaterialAttribute::OcclusionTexture, 6u}, + {MaterialAttribute::EmissiveTexture, 7u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These are used by all textures except the one above, failing the + check */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); +} + +void MaterialDataTest::pbrMetallicRoughnessAccessNoCommonTransformationCoordinates() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrMetallicRoughnessMaterialData data{{}, { + {MaterialAttribute::BaseColorTexture, 3u}, + {MaterialAttribute::BaseColorTextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::BaseColorTextureCoordinates, 3u}, + {MaterialAttribute::MetalnessTexture, 4u}, + {MaterialAttribute::MetalnessTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::RoughnessTexture, 5u}, + {MaterialAttribute::RoughnessTextureCoordinates, 17u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); + + std::ostringstream out; + Error redirectError{&out}; + data.commonTextureMatrix(); + data.commonTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::PbrMetallicRoughnessMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation\n" + "Trade::PbrMetallicRoughnessMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set\n"); +} + +void MaterialDataTest::pbrSpecularGlossinessAccess() { + MaterialData base{MaterialType::PbrSpecularGlossiness, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::SpecularColor, 0xff336600_rgbaf}, + {MaterialAttribute::Glossiness, 0.79f}, + {MaterialAttribute::EmissiveColor, 0x111111_rgbf} + }}; + + CORRADE_COMPARE(base.types(), MaterialType::PbrSpecularGlossiness); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasSpecularTexture()); + CORRADE_VERIFY(!data.hasGlossinessTexture()); + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(data.specularColor(), 0xff336600_rgbaf); + CORRADE_COMPARE(data.glossiness(), 0.79f); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessDefaults() { + MaterialData base{{}, {}}; + + CORRADE_COMPARE(base.types(), MaterialTypes{}); + /* Casting is fine even if the type doesn't include PbrMetallicRoughness */ + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasSpecularTexture()); + CORRADE_VERIFY(!data.hasGlossinessTexture()); + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.diffuseColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.specularColor(), 0xffffff00_rgbaf); + CORRADE_COMPARE(data.glossiness(), 1.0f); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessTextured() { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::DiffuseTexture, 0u}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 2u}, + {MaterialAttribute::SpecularColor, 0x33556600_rgbaf}, + {MaterialAttribute::SpecularTexture, 1u}, + {MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGBA}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::SpecularTextureCoordinates, 3u}, + {MaterialAttribute::Glossiness, 0.79f}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::GlossinessTextureMatrix, Matrix3::scaling({1.0f, 1.0f})}, + {MaterialAttribute::GlossinessTextureCoordinates, 4u}, + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::NormalTextureScale, 0.35f}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::BA}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::scaling({1.0f, 0.5f})}, + {MaterialAttribute::NormalTextureCoordinates, 5u}, + {MaterialAttribute::OcclusionTexture, 4u}, + {MaterialAttribute::OcclusionTextureStrength, 0.66f}, + {MaterialAttribute::OcclusionTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::OcclusionTextureMatrix, Matrix3::scaling({1.0f, 0.75f})}, + {MaterialAttribute::OcclusionTextureCoordinates, 6u}, + {MaterialAttribute::EmissiveColor, 0x111111_rgbf}, + {MaterialAttribute::EmissiveTexture, 5u}, + {MaterialAttribute::EmissiveTextureMatrix, Matrix3::scaling({0.75f, 0.5f})}, + {MaterialAttribute::EmissiveTextureCoordinates, 7u} + }}; + + CORRADE_VERIFY(data.hasSpecularTexture()); + CORRADE_VERIFY(data.hasGlossinessTexture()); + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.diffuseColor(), 0xccffbbff_rgbaf); + CORRADE_COMPARE(data.diffuseTexture(), 0); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 2); + CORRADE_COMPARE(data.specularColor(), 0x33556600_rgbaf); + CORRADE_COMPARE(data.specularTexture(), 1); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGBA); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 3); + CORRADE_COMPARE(data.glossiness(), 0.79f); + CORRADE_COMPARE(data.glossinessTexture(), 2); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3::scaling({1.0f, 1.0f})); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 4); + CORRADE_COMPARE(data.normalTexture(), 3); + CORRADE_COMPARE(data.normalTextureScale(), 0.35f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::BA); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::scaling({1.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 5); + CORRADE_COMPARE(data.occlusionTexture(), 4); + CORRADE_COMPARE(data.occlusionTextureStrength(), 0.66f); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3::scaling({1.0f, 0.75f})); + CORRADE_COMPARE(data.occlusionTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 6); + CORRADE_COMPARE(data.emissiveColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3::scaling({0.75f, 0.5f})); + CORRADE_COMPARE(data.emissiveTexture(), 5); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessTexturedDefaults() { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 1u}, + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 3u}, + {MaterialAttribute::NormalTexture, 4u}, + {MaterialAttribute::OcclusionTexture, 5u}, + {MaterialAttribute::EmissiveTexture, 6u} + }}; + + CORRADE_VERIFY(data.hasSpecularTexture()); + CORRADE_VERIFY(data.hasGlossinessTexture()); + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.diffuseColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.diffuseTexture(), 1); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 0); + CORRADE_COMPARE(data.specularColor(), 0xffffff00_rgbaf); + CORRADE_COMPARE(data.specularTexture(), 2); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.specularTextureCoordinates(), 0); + CORRADE_COMPARE(data.glossiness(), 1.0f); + CORRADE_COMPARE(data.glossinessTexture(), 3); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 0); + CORRADE_COMPARE(data.normalTexture(), 4); + CORRADE_COMPARE(data.normalTextureScale(), 1.0f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.normalTextureCoordinates(), 0); + CORRADE_COMPARE(data.occlusionTexture(), 5); + CORRADE_COMPARE(data.occlusionTextureStrength(), 1.0f); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.occlusionTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 0); + CORRADE_COMPARE(data.emissiveColor(), 0x000000_rgbf); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.emissiveTexture(), 6); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 0); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessTexturedSingleMatrixCoordinates() { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 1u}, + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 3u}, + {MaterialAttribute::NormalTexture, 4u}, + {MaterialAttribute::OcclusionTexture, 5u}, + {MaterialAttribute::EmissiveTexture, 6u}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u} + }}; + + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 7); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 7); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 7); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 7); + CORRADE_COMPARE(data.occlusionTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.occlusionTextureCoordinates(), 7); + CORRADE_COMPARE(data.emissiveTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.emissiveTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessTexturedImplicitPackedSpecularGlossiness() { + /* Just the texture ID, the rest is implicit */ + { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularGlossinessTexture, 2u}, + }}; + CORRADE_VERIFY(data.hasSpecularGlossinessTexture()); + CORRADE_COMPARE(data.specularTexture(), 2); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.specularTextureCoordinates(), 0); + CORRADE_COMPARE(data.glossinessTexture(), 2); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 0); + + /* Explicit parameters for everything, but all the same */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularGlossinessTexture, 2u}, + {MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGB}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::SpecularTextureCoordinates, 3u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::GlossinessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::GlossinessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasSpecularGlossinessTexture()); + CORRADE_COMPARE(data.specularTexture(), 2); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 3); + CORRADE_COMPARE(data.glossinessTexture(), 2); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 3); + + /* Swizzle is ignored when the combined texture is specified, so this is + fine. */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularGlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::B}, + }}; + CORRADE_VERIFY(data.hasSpecularGlossinessTexture()); + + /* Unexpected texture matrix */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularGlossinessTexture, 2u}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + + /* Unexpected texture coordinates */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularGlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureCoordinates, 1u}, + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + } +} + +void MaterialDataTest::pbrSpecularGlossinessAccessTexturedExplicitPackedSpecularGlossiness() { + /* Just the texture ID and swizzles, the rest is implicit */ + { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(data.hasSpecularGlossinessTexture()); + CORRADE_COMPARE(data.specularTexture(), 2); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.specularTextureCoordinates(), 0); + CORRADE_COMPARE(data.glossinessTexture(), 2); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 0); + + /* Explicit parameters for everything, but all the same */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGB}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::SpecularTextureCoordinates, 3u}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::GlossinessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::GlossinessTextureCoordinates, 3u} + }}; + CORRADE_VERIFY(data.hasSpecularGlossinessTexture()); + CORRADE_COMPARE(data.specularTexture(), 2); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 3); + CORRADE_COMPARE(data.glossinessTexture(), 2); + CORRADE_COMPARE(data.glossinessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.glossinessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.glossinessTextureCoordinates(), 3); + + /* Different texture ID */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 3u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + + /* Unexpected swizzle 1 */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGBA}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + + /* Unexpected swizzle 2 */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::B} + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + + /* Unexpected texture matrix */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A} + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + + /* Unexpected texture coordinates */ + } { + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::SpecularTexture, 2u}, + {MaterialAttribute::GlossinessTexture, 2u}, + {MaterialAttribute::GlossinessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::GlossinessTextureCoordinates, 1u} + }}; + CORRADE_VERIFY(!data.hasSpecularGlossinessTexture()); + } +} + +void MaterialDataTest::pbrSpecularGlossinessAccessInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrSpecularGlossinessMaterialData data{{}, {}}; + + std::ostringstream out; + Error redirectError{&out}; + data.diffuseTexture(); + data.diffuseTextureMatrix(); + data.diffuseTextureCoordinates(); + data.specularTexture(); + data.specularTextureSwizzle(); + data.specularTextureMatrix(); + data.specularTextureCoordinates(); + data.glossinessTexture(); + data.glossinessTextureSwizzle(); + data.glossinessTextureMatrix(); + data.glossinessTextureCoordinates(); + data.normalTexture(); + data.normalTextureScale(); + data.normalTextureSwizzle(); + data.normalTextureMatrix(); + data.normalTextureCoordinates(); + data.occlusionTexture(); + data.occlusionTextureStrength(); + data.occlusionTextureSwizzle(); + data.occlusionTextureMatrix(); + data.occlusionTextureCoordinates(); + data.emissiveTexture(); + data.emissiveTextureMatrix(); + data.emissiveTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): attribute DiffuseTexture not found in layer 0\n" + "Trade::PbrSpecularGlossinessMaterialData::diffuseTextureMatrix(): the material doesn't have a diffuse texture\n" + "Trade::PbrSpecularGlossinessMaterialData::diffuseTextureCoordinates(): the material doesn't have a diffuse texture\n" + "Trade::PbrSpecularGlossinessMaterialData::specularTexture(): the material doesn't have a specular texture\n" + "Trade::PbrSpecularGlossinessMaterialData::specularTextureSwizzle(): the material doesn't have a specular texture\n" + "Trade::PbrSpecularGlossinessMaterialData::specularTextureMatrix(): the material doesn't have a specular texture\n" + "Trade::PbrSpecularGlossinessMaterialData::specularTextureCoordinates(): the material doesn't have a specular texture\n" + "Trade::PbrSpecularGlossinessMaterialData::glossinessTexture(): the material doesn't have a glossiness texture\n" + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureSwizzle(): the material doesn't have a glossiness texture\n" + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureMatrix(): the material doesn't have a glossiness texture\n" + "Trade::PbrSpecularGlossinessMaterialData::glossinessTextureCoordinates(): the material doesn't have a glossiness texture\n" + "Trade::MaterialData::attribute(): attribute NormalTexture not found in layer 0\n" + "Trade::PbrSpecularGlossinessMaterialData::normalTextureScale(): the material doesn't have a normal texture\n" + "Trade::PbrSpecularGlossinessMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture\n" + "Trade::PbrSpecularGlossinessMaterialData::normalTextureMatrix(): the material doesn't have a normal texture\n" + "Trade::PbrSpecularGlossinessMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture\n" + "Trade::MaterialData::attribute(): attribute OcclusionTexture not found in layer 0\n" + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureStrength(): the material doesn't have an occlusion texture\n" + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureSwizzle(): the material doesn't have an occlusion texture\n" + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureMatrix(): the material doesn't have an occlusion texture\n" + "Trade::PbrSpecularGlossinessMaterialData::occlusionTextureCoordinates(): the material doesn't have an occlusion texture\n" + "Trade::MaterialData::attribute(): attribute EmissiveTexture not found in layer 0\n" + "Trade::PbrSpecularGlossinessMaterialData::emissiveTextureMatrix(): the material doesn't have an emissive texture\n" + "Trade::PbrSpecularGlossinessMaterialData::emissiveTextureCoordinates(): the material doesn't have an emissive texture\n"); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesNoTextures() { + PbrSpecularGlossinessMaterialData a{{}, {}}; + CORRADE_VERIFY(a.hasCommonTextureTransformation()); + CORRADE_VERIFY(a.hasCommonTextureCoordinates()); + CORRADE_COMPARE(a.commonTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(a.commonTextureCoordinates(), 0); + + PbrSpecularGlossinessMaterialData b{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u} + }}; + CORRADE_VERIFY(b.hasCommonTextureTransformation()); + CORRADE_VERIFY(b.hasCommonTextureCoordinates()); + CORRADE_COMPARE(b.commonTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(b.commonTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneTexture() { + Containers::StringView textureName = PbrSpecularGlossinessTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrSpecularGlossinessMaterialData data{{}, { + {textureName, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These shouldn't affect the above */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasCommonTextureTransformation()); + CORRADE_COMPARE(data.commonTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_VERIFY(data.hasCommonTextureCoordinates()); + CORRADE_COMPARE(data.commonTextureCoordinates(), 17u); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessCommonTransformationCoordinatesOneDifferentTexture() { + Containers::StringView textureName = PbrSpecularGlossinessTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 2u}, + {MaterialAttribute::SpecularTexture, 3u}, + {MaterialAttribute::GlossinessTexture, 4u}, + {MaterialAttribute::NormalTexture, 5u}, + {MaterialAttribute::OcclusionTexture, 6u}, + {MaterialAttribute::EmissiveTexture, 7u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These are used by all textures except the one above, failing the + check */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); +} + +void MaterialDataTest::pbrSpecularGlossinessAccessNoCommonTransformationCoordinates() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrSpecularGlossinessMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 3u}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 3u}, + {MaterialAttribute::SpecularTexture, 4u}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::OcclusionTexture, 5u}, + {MaterialAttribute::OcclusionTextureCoordinates, 17u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); + + std::ostringstream out; + Error redirectError{&out}; + data.commonTextureMatrix(); + data.commonTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::PbrSpecularGlossinessMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation\n" + "Trade::PbrSpecularGlossinessMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set\n"); +} + +void MaterialDataTest::phongAccess() { + MaterialData base{MaterialType::Phong, { + {MaterialAttribute::AmbientColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::DiffuseColor, 0xebefbfff_rgbaf}, + {MaterialAttribute::SpecularColor, 0xacabadff_rgbaf}, + {MaterialAttribute::Shininess, 96.0f} + }}; + + CORRADE_COMPARE(base.types(), MaterialType::Phong); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasSpecularTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.ambientColor(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.diffuseColor(), 0xebefbf_rgbf); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.shininess(), 96.0f); +} + +void MaterialDataTest::phongAccessDefaults() { + MaterialData base{{}, {}}; + + CORRADE_COMPARE(base.types(), MaterialTypes{}); + /* Casting is fine even if the type doesn't include Phong */ + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.ambientColor(), 0x000000_rgbf); + CORRADE_COMPARE(data.diffuseColor(), 0xffffff_rgbf); + CORRADE_COMPARE(data.specularColor(), 0xffffff00_rgbaf); + CORRADE_COMPARE(data.shininess(), 80.0f); +} + +void MaterialDataTest::phongAccessTextured() { + PhongMaterialData data{{}, { + {MaterialAttribute::AmbientColor, 0x111111ff_rgbaf}, + {MaterialAttribute::AmbientTexture, 42u}, + {MaterialAttribute::AmbientTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::AmbientTextureCoordinates, 2u}, + {MaterialAttribute::DiffuseTexture, 33u}, + {MaterialAttribute::DiffuseColor, 0xeebbffff_rgbaf}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 3u}, + {MaterialAttribute::SpecularColor, 0xacabadff_rgbaf}, + {MaterialAttribute::SpecularTexture, 17u}, + {MaterialAttribute::SpecularTextureSwizzle, MaterialTextureSwizzle::RGBA}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({1.0f, 1.0f})}, + {MaterialAttribute::SpecularTextureCoordinates, 4u}, + {MaterialAttribute::NormalTexture, 0u}, + {MaterialAttribute::NormalTextureScale, 0.5f}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::GB}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::scaling({1.0f, 0.5f})}, + {MaterialAttribute::NormalTextureCoordinates, 5u} + }}; + + CORRADE_VERIFY(data.hasSpecularTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.ambientColor(), 0x111111_rgbf); + CORRADE_COMPARE(data.ambientTexture(), 42); + CORRADE_COMPARE(data.ambientTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.ambientTextureCoordinates(), 2); + CORRADE_COMPARE(data.diffuseColor(), 0xeebbff_rgbf); + CORRADE_COMPARE(data.diffuseTexture(), 33); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 3); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.specularTexture(), 17); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGBA); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({1.0f, 1.0f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 4); + CORRADE_COMPARE(data.normalTexture(), 0); + CORRADE_COMPARE(data.normalTextureScale(), 0.5f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::GB); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::scaling({1.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 5); +} + +void MaterialDataTest::phongAccessTexturedDefaults() { + PhongMaterialData data{{}, { + {MaterialAttribute::AmbientTexture, 42u}, + {MaterialAttribute::DiffuseTexture, 33u}, + {MaterialAttribute::SpecularTexture, 17u}, + {MaterialAttribute::NormalTexture, 1u} + }}; + + CORRADE_VERIFY(data.hasSpecularTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.ambientColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.ambientTexture(), 42); + CORRADE_COMPARE(data.ambientTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.ambientTextureCoordinates(), 0); + CORRADE_COMPARE(data.diffuseColor(), 0xffffffff_rgbaf); + CORRADE_COMPARE(data.diffuseTexture(), 33); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 0); + CORRADE_COMPARE(data.specularColor(), 0xffffff00_rgbaf); + CORRADE_COMPARE(data.specularTexture(), 17); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.specularTextureCoordinates(), 0); + CORRADE_COMPARE(data.normalTexture(), 1); + CORRADE_COMPARE(data.normalTextureScale(), 1.0f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.normalTextureCoordinates(), 0); +} + +void MaterialDataTest::phongAccessTexturedSingleMatrixCoordinates() { + PhongMaterialData data{{}, { + {MaterialAttribute::AmbientTexture, 42u}, + {MaterialAttribute::DiffuseTexture, 33u}, + {MaterialAttribute::SpecularTexture, 17u}, + {MaterialAttribute::NormalTexture, 0u}, + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 1.0f})}, + {MaterialAttribute::TextureCoordinates, 2u} + }}; + + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.ambientTextureMatrix(), Matrix3::translation({0.5f, 1.0f})); + CORRADE_COMPARE(data.ambientTextureCoordinates(), 2); + CORRADE_COMPARE(data.diffuseTextureMatrix(), Matrix3::translation({0.5f, 1.0f})); + CORRADE_COMPARE(data.diffuseTextureCoordinates(), 2); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::translation({0.5f, 1.0f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 2); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::translation({0.5f, 1.0f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 2); +} + +void MaterialDataTest::phongAccessTexturedImplicitPackedSpecularGlossiness() { + PhongMaterialData data{{}, { + {MaterialAttribute::SpecularColor, 0xacabadff_rgbaf}, + {MaterialAttribute::SpecularGlossinessTexture, 17u}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({1.0f, 1.0f})}, + {MaterialAttribute::SpecularTextureCoordinates, 4u}, + }}; + + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.flags(), PhongMaterialData::Flag::SpecularTexture|PhongMaterialData::Flag::TextureCoordinates|PhongMaterialData::Flag::TextureTransformation); + CORRADE_IGNORE_DEPRECATED_POP + #endif + CORRADE_VERIFY(data.hasSpecularTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.specularColor(), 0xacabad_rgbf); + CORRADE_COMPARE(data.specularTexture(), 17); + CORRADE_COMPARE(data.specularTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.specularTextureMatrix(), Matrix3::scaling({1.0f, 1.0f})); + CORRADE_COMPARE(data.specularTextureCoordinates(), 4); +} + +void MaterialDataTest::phongAccessInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PhongMaterialData data{{}, {}}; + + std::ostringstream out; + Error redirectError{&out}; + data.ambientTexture(); + data.ambientTextureMatrix(); + data.ambientTextureCoordinates(); + data.diffuseTexture(); + data.diffuseTextureMatrix(); + data.diffuseTextureCoordinates(); + data.specularTexture(); + data.specularTextureSwizzle(); + data.specularTextureMatrix(); + data.specularTextureCoordinates(); + data.normalTexture(); + data.normalTextureScale(); + data.normalTextureSwizzle(); + data.normalTextureMatrix(); + data.normalTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): attribute AmbientTexture not found in layer 0\n" + "Trade::PhongMaterialData::ambientTextureMatrix(): the material doesn't have an ambient texture\n" + "Trade::PhongMaterialData::ambientTextureCoordinates(): the material doesn't have an ambient texture\n" + "Trade::MaterialData::attribute(): attribute DiffuseTexture not found in layer 0\n" + "Trade::PhongMaterialData::diffuseTextureMatrix(): the material doesn't have a diffuse texture\n" + "Trade::PhongMaterialData::diffuseTextureCoordinates(): the material doesn't have a diffuse texture\n" + "Trade::PhongMaterialData::specularTexture(): the material doesn't have a specular texture\n" + "Trade::PhongMaterialData::specularTextureSwizzle(): the material doesn't have a specular texture\n" + "Trade::PhongMaterialData::specularTextureMatrix(): the material doesn't have a specular texture\n" + "Trade::PhongMaterialData::specularTextureCoordinates(): the material doesn't have a specular texture\n" + "Trade::MaterialData::attribute(): attribute NormalTexture not found in layer 0\n" + "Trade::PhongMaterialData::normalTextureScale(): the material doesn't have a normal texture\n" + "Trade::PhongMaterialData::normalTextureSwizzle(): the material doesn't have a normal texture\n" + "Trade::PhongMaterialData::normalTextureMatrix(): the material doesn't have a normal texture\n" + "Trade::PhongMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture\n"); +} + +void MaterialDataTest::phongAccessCommonTransformationCoordinatesNoTextures() { + PhongMaterialData a{{}, {}}; + CORRADE_VERIFY(a.hasCommonTextureTransformation()); + CORRADE_VERIFY(a.hasCommonTextureCoordinates()); + CORRADE_COMPARE(a.commonTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(a.commonTextureCoordinates(), 0); + + #ifdef MAGNUM_BUILD_DEPRECATED + /* textureMatrix() should return the common matrix, if possible, and + fall back to the global one if not */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(a.textureMatrix(), Matrix3{}); + CORRADE_IGNORE_DEPRECATED_POP + #endif + + PhongMaterialData b{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u} + }}; + CORRADE_VERIFY(b.hasCommonTextureTransformation()); + CORRADE_VERIFY(b.hasCommonTextureCoordinates()); + CORRADE_COMPARE(b.commonTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(b.commonTextureCoordinates(), 7); + + #ifdef MAGNUM_BUILD_DEPRECATED + /* textureMatrix() should return the common matrix, if possible, and + fall back to the global one if not */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(b.textureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_IGNORE_DEPRECATED_POP + #endif +} + +void MaterialDataTest::phongAccessCommonTransformationCoordinatesOneTexture() { + Containers::StringView textureName = PhongTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PhongMaterialData data{{}, { + {textureName, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These shouldn't affect the above */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasCommonTextureTransformation()); + CORRADE_COMPARE(data.commonTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_VERIFY(data.hasCommonTextureCoordinates()); + CORRADE_COMPARE(data.commonTextureCoordinates(), 17u); + + #ifdef MAGNUM_BUILD_DEPRECATED + /* textureMatrix() should return the common matrix, if possible, and + fall back to the global one if not */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_IGNORE_DEPRECATED_POP + #endif +} + +void MaterialDataTest::phongAccessCommonTransformationCoordinatesOneDifferentTexture() { + Containers::StringView textureName = PhongTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PhongMaterialData data{{}, { + {MaterialAttribute::AmbientTexture, 2u}, + {MaterialAttribute::DiffuseTexture, 3u}, + {MaterialAttribute::SpecularTexture, 4u}, + {MaterialAttribute::NormalTexture, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + + /* These are used by all textures except the one above, failing the + check */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); + + #ifdef MAGNUM_BUILD_DEPRECATED + /* textureMatrix() should return the common matrix, if possible, and + fall back to the global one if not */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(data.textureMatrix(), Matrix3::translation({0.5f, 0.0f})); + CORRADE_IGNORE_DEPRECATED_POP + #endif +} + +void MaterialDataTest::phongAccessNoCommonTransformationCoordinates() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PhongMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 3u}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 3u}, + {MaterialAttribute::SpecularTexture, 4u}, + {MaterialAttribute::SpecularTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::NormalTexture, 5u}, + {MaterialAttribute::NormalTextureCoordinates, 17u} + }}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); + + std::ostringstream out; + Error redirectError{&out}; + data.commonTextureMatrix(); + data.commonTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::PhongMaterialData::commonTextureMatrix(): the material doesn't have a common texture coordinate transformation\n" + "Trade::PhongMaterialData::commonTextureCoordinates(): the material doesn't have a common texture coordinate set\n"); +} + +void MaterialDataTest::flatAccessBaseColor() { + MaterialData base{MaterialType::Flat, { + {MaterialAttribute::BaseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::DiffuseColor, 0x33556600_rgbaf}, /* Ignored */ + }}; + + CORRADE_COMPARE(base.types(), MaterialType::Flat); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); +} + +void MaterialDataTest::flatAccessDiffuseColor() { + MaterialData base{MaterialType::Flat, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + }}; + + CORRADE_COMPARE(base.types(), MaterialType::Flat); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); +} + +void MaterialDataTest::flatAccessDefaults() { + MaterialData base{{}, {}}; + + CORRADE_COMPARE(base.types(), MaterialTypes{}); + /* Casting is fine even if the type doesn't include Flat */ + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xffffff_rgbf); +} + +void MaterialDataTest::flatAccessTexturedBaseColor() { + FlatMaterialData data{{}, { + {MaterialAttribute::BaseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::BaseColorTexture, 5u}, + {MaterialAttribute::BaseColorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::BaseColorTextureCoordinates, 2u}, + + /* All this is ignored */ + {MaterialAttribute::DiffuseColor, 0x33556600_rgbaf}, + {MaterialAttribute::DiffuseTexture, 6u}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::translation({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.textureCoordinates(), 2); +} + +void MaterialDataTest::flatAccessTexturedDiffuseColor() { + FlatMaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::DiffuseTexture, 5u}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 2u}, + + /* Ignored, as we have a diffuse texture */ + {MaterialAttribute::BaseColor, 0x33556600_rgbaf} + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.textureCoordinates(), 2); +} + +void MaterialDataTest::flatAccessTexturedDefaults() { + FlatMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 5u} + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xffffff_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.textureCoordinates(), 0); +} + +void MaterialDataTest::flatAccessTexturedBaseColorSingleMatrixCoordinates() { + FlatMaterialData data{{}, { + {MaterialAttribute::BaseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::BaseColorTexture, 5u}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::TextureCoordinates, 2u}, + + /* This is ignored because it doesn't match the texture */ + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::translation({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.textureCoordinates(), 2); +} + +void MaterialDataTest::flatAccessTexturedDiffuseColorSingleMatrixCoordinates() { + FlatMaterialData data{{}, { + {MaterialAttribute::DiffuseColor, 0xccffbbff_rgbaf}, + {MaterialAttribute::DiffuseTexture, 5u}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::TextureCoordinates, 2u}, + + /* This is ignored because it doesn't match the texture */ + {MaterialAttribute::BaseColorTextureMatrix, Matrix3::translation({0.5f, 1.0f})}, + {MaterialAttribute::BaseColorTextureCoordinates, 3u} + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xccffbb_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.textureCoordinates(), 2); +} + +void MaterialDataTest::flatAccessTexturedMismatchedMatrixCoordinates() { + { + FlatMaterialData data{{}, { + {MaterialAttribute::BaseColorTexture, 5u}, + + /* This is ignored because it doesn't match the texture */ + {MaterialAttribute::DiffuseColor, 0x33556600_rgbaf}, + {MaterialAttribute::DiffuseTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::DiffuseTextureCoordinates, 2u}, + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xffffff_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.textureCoordinates(), 0); + } { + FlatMaterialData data{{}, { + {MaterialAttribute::DiffuseTexture, 5u}, + + /* This is ignored because it doesn't match the texture */ + {MaterialAttribute::BaseColor, 0x33556600_rgbaf}, + {MaterialAttribute::BaseColorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::BaseColorTextureCoordinates, 2u}, + }}; + + CORRADE_VERIFY(data.hasTexture()); + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.color(), 0xffffff_rgbf); + CORRADE_COMPARE(data.texture(), 5); + CORRADE_COMPARE(data.textureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.textureCoordinates(), 0); + } +} + +void MaterialDataTest::flatAccessInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + FlatMaterialData data{{}, {}}; + + std::ostringstream out; + Error redirectError{&out}; + data.texture(); + data.textureMatrix(); + data.textureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::FlatMaterialData::texture(): the material doesn't have a texture\n" + "Trade::FlatMaterialData::textureMatrix(): the material doesn't have a texture\n" + "Trade::FlatMaterialData::textureCoordinates(): the material doesn't have a texture\n"); +} + +void MaterialDataTest::templateLayerAccess() { + MaterialLayerData data{{}, { + {MaterialAttribute::BaseColor, 0x335566ff_rgbaf}, + + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactor, 0.35f}, + {MaterialAttribute::LayerFactorTexture, 3u}, + {MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::LayerFactorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::LayerFactorTextureCoordinates, 4u}, + }, {1, 7}}; + + CORRADE_COMPARE(data.layerName(), "ClearCoat"); + CORRADE_COMPARE(data.layerFactor(), 0.35f); + CORRADE_COMPARE(data.layerFactorTexture(), 3u); + CORRADE_COMPARE(data.layerFactorTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.layerFactorTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(), 4u); + + CORRADE_COMPARE(data.attributeCount(), 6); + CORRADE_VERIFY(data.hasAttribute(MaterialAttribute::LayerFactor)); + CORRADE_VERIFY(data.hasAttribute("LayerFactorTexture")); + CORRADE_VERIFY(!data.hasAttribute(MaterialAttribute::BaseColor)); + CORRADE_VERIFY(!data.hasAttribute("BaseColor")); + CORRADE_VERIFY(data.hasAttribute(0, MaterialAttribute::BaseColor)); + CORRADE_VERIFY(data.hasAttribute(0, "BaseColor")); + + CORRADE_COMPARE(data.attributeId(MaterialAttribute::LayerFactorTexture), 2); + CORRADE_COMPARE(data.attributeId("LayerFactorTexture"), 2); + + CORRADE_COMPARE(data.attributeName(2), "LayerFactorTexture"); + + CORRADE_COMPARE(data.attributeType(2), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType(MaterialAttribute::LayerFactorTexture), MaterialAttributeType::UnsignedInt); + CORRADE_COMPARE(data.attributeType("LayerFactorTexture"), MaterialAttributeType::UnsignedInt); + + CORRADE_COMPARE(*static_cast(data.attribute(2)), 3); + CORRADE_COMPARE(*static_cast(data.attribute(MaterialAttribute::LayerFactorTexture)), 3); + CORRADE_COMPARE(*static_cast(data.attribute("LayerFactorTexture")), 3); + + CORRADE_COMPARE(data.attribute(2), 3); + CORRADE_COMPARE(data.attribute(MaterialAttribute::LayerFactorTexture), 3); + CORRADE_COMPARE(data.attribute("LayerFactorTexture"), 3); + + CORRADE_COMPARE(*static_cast(data.tryAttribute(MaterialAttribute::LayerFactorTexture)), 3); + CORRADE_COMPARE(*static_cast(data.tryAttribute("LayerFactorTexture")), 3); + + CORRADE_COMPARE(data.tryAttribute(MaterialAttribute::LayerFactorTexture), 3); + CORRADE_COMPARE(data.tryAttribute("LayerFactorTexture"), 3); + + CORRADE_COMPARE(data.attributeOr(MaterialAttribute::LayerFactorTexture, 5u), 3); + CORRADE_COMPARE(data.attributeOr("LayerFactorTexture", 5u), 3); +} + +void MaterialDataTest::pbrClearCoatAccess() { + MaterialData base{MaterialType::PbrClearCoat, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::Roughness, 0.7f} + }, {0, 2}}; + + CORRADE_COMPARE(base.types(), MaterialType::PbrClearCoat); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.roughness(), 0.7f); +} + +void MaterialDataTest::pbrClearCoatAccessDefaults() { + MaterialData base{{}, { + /* Needs to have at least the layer name, otherwise the queries will + blow up */ + {MaterialLayer::ClearCoat} + }, {0, 1}}; + + CORRADE_COMPARE(base.types(), MaterialTypes{}); + const auto& data = base.as(); + + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.layerFactor(), 1.0f); + CORRADE_COMPARE(data.roughness(), 0.0f); +} + +void MaterialDataTest::pbrClearCoatAccessTextured() { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::Roughness, 0.7f}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::A}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::translation({2.0f, 1.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 6u}, + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::NormalTextureScale, 0.5f}, + {MaterialAttribute::NormalTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::NormalTextureMatrix, Matrix3::translation({0.0f, 0.5f})}, + {MaterialAttribute::NormalTextureCoordinates, 7u}, + }, {0, 11}}; + + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.roughness(), 0.7f); + CORRADE_COMPARE(data.roughnessTexture(), 2u); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::A); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::translation({2.0f, 1.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 6u); + CORRADE_COMPARE(data.normalTexture(), 3u); + CORRADE_COMPARE(data.normalTextureScale(), 0.5f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::B); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 7u); +} + +void MaterialDataTest::pbrClearCoatAccessTexturedDefaults() { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::NormalTexture, 3u}, + }, {0, 3}}; + + CORRADE_VERIFY(!data.hasTextureTransformation()); + CORRADE_VERIFY(!data.hasTextureCoordinates()); + CORRADE_COMPARE(data.roughness(), 0.0f); + CORRADE_COMPARE(data.roughnessTexture(), 2u); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::R); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 0u); + CORRADE_COMPARE(data.normalTexture(), 3u); + CORRADE_COMPARE(data.normalTextureScale(), 1.0f); + CORRADE_COMPARE(data.normalTextureSwizzle(), MaterialTextureSwizzle::RGB); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.normalTextureCoordinates(), 0u); +} + +void MaterialDataTest::pbrClearCoatAccessTexturedExplicitPackedLayerFactorRoughness() { + /* Just the texture ID and swizzles, the rest is implicit */ + { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G} + }, {0, 4}}; + CORRADE_VERIFY(data.hasLayerFactorRoughnessTexture()); + CORRADE_COMPARE(data.layerFactorTexture(), 2); + CORRADE_COMPARE(data.layerFactorTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(), 0); + CORRADE_COMPARE(data.roughnessTexture(), 2); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::G); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 0); + + /* Explicit parameters for everything, but all the same */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::R}, + {MaterialAttribute::LayerFactorTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::LayerFactorTextureCoordinates, 3u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::RoughnessTextureCoordinates, 3u} + }, {0, 9}}; + CORRADE_VERIFY(data.hasLayerFactorRoughnessTexture()); + CORRADE_COMPARE(data.layerFactorTexture(), 2); + CORRADE_COMPARE(data.layerFactorTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.layerFactorTextureCoordinates(), 3); + CORRADE_COMPARE(data.roughnessTexture(), 2); + CORRADE_COMPARE(data.roughnessTextureSwizzle(), MaterialTextureSwizzle::G); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 3); + + /* Different texture ID */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 3u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + }, {0, 4}}; + CORRADE_VERIFY(!data.hasLayerFactorRoughnessTexture()); + + /* Unexpected swizzle 1 */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::LayerFactorTextureSwizzle, MaterialTextureSwizzle::B}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + }, {0, 5}}; + CORRADE_VERIFY(!data.hasLayerFactorRoughnessTexture()); + + /* Unexpected swizzle 2 */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::B} + }, {0, 4}}; + CORRADE_VERIFY(!data.hasLayerFactorRoughnessTexture()); + + /* Unexpected texture matrix */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::LayerFactorTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G} + }, {0, 5}}; + CORRADE_VERIFY(!data.hasLayerFactorRoughnessTexture()); + + /* Unexpected texture coordinates */ + } { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::RoughnessTextureSwizzle, MaterialTextureSwizzle::G}, + {MaterialAttribute::RoughnessTextureCoordinates, 1u} + }, {0, 5}}; + CORRADE_VERIFY(!data.hasLayerFactorRoughnessTexture()); + } +} + +void MaterialDataTest::pbrClearCoatAccessTexturedSingleMatrixCoordinates() { + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::NormalTexture, 3u}, + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.0f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u}, + }, {0, 5}}; + + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 7u); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 7u); +} + +void MaterialDataTest::pbrClearCoatAccessTexturedBaseMaterialMatrixCoordinates() { + PbrClearCoatMaterialData data{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.0f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u}, + + {MaterialLayer::ClearCoat}, + {MaterialAttribute::RoughnessTexture, 2u}, + {MaterialAttribute::NormalTexture, 3u}, + }, {2, 5}}; + + CORRADE_VERIFY(data.hasTextureTransformation()); + CORRADE_VERIFY(data.hasTextureCoordinates()); + CORRADE_COMPARE(data.roughnessTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.roughnessTextureCoordinates(), 7u); + CORRADE_COMPARE(data.normalTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.normalTextureCoordinates(), 7u); + + CORRADE_VERIFY(data.hasCommonTextureTransformation()); + CORRADE_VERIFY(data.hasCommonTextureCoordinates()); + CORRADE_COMPARE(data.commonTextureMatrix(), Matrix3::translation({0.0f, 0.5f})); + CORRADE_COMPARE(data.commonTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrClearCoatAccessInvalidTextures() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + }, {0, 1}}; + + std::ostringstream out; + Error redirectError{&out}; + data.roughnessTexture(); + data.roughnessTextureSwizzle(); + data.roughnessTextureMatrix(); + data.roughnessTextureCoordinates(); + data.normalTexture(); + data.normalTextureScale(); + data.normalTextureSwizzle(); + data.normalTextureMatrix(); + data.normalTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attribute(): attribute RoughnessTexture not found in layer ClearCoat\n" + "Trade::PbrClearCoatMaterialData::roughnessTextureSwizzle(): the layer doesn't have a roughness texture\n" + "Trade::PbrClearCoatMaterialData::roughnessTextureMatrix(): the layer doesn't have a roughness texture\n" + "Trade::PbrClearCoatMaterialData::roughnessTextureCoordinates(): the layer doesn't have a roughness texture\n" + "Trade::MaterialData::attribute(): attribute NormalTexture not found in layer ClearCoat\n" + "Trade::PbrClearCoatMaterialData::normalTextureScale(): the layer doesn't have a normal texture\n" + "Trade::PbrClearCoatMaterialData::normalTextureSwizzle(): the layer doesn't have a normal texture\n" + "Trade::PbrClearCoatMaterialData::normalTextureMatrix(): the layer doesn't have a normal texture\n" + "Trade::PbrClearCoatMaterialData::normalTextureCoordinates(): the layer doesn't have a normal texture\n"); +} + +void MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesNoTextures() { + PbrClearCoatMaterialData a{{}, { + {MaterialLayer::ClearCoat}, + }, {0, 1}}; + CORRADE_VERIFY(a.hasCommonTextureTransformation()); + CORRADE_VERIFY(a.hasCommonTextureCoordinates()); + CORRADE_COMPARE(a.commonTextureMatrix(), Matrix3{}); + CORRADE_COMPARE(a.commonTextureCoordinates(), 0); + + PbrClearCoatMaterialData b{{}, { + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u}, + + {MaterialLayer::ClearCoat} + }, {2, 3}}; + CORRADE_VERIFY(b.hasCommonTextureTransformation()); + CORRADE_VERIFY(b.hasCommonTextureCoordinates()); + CORRADE_COMPARE(b.commonTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(b.commonTextureCoordinates(), 7); + + PbrClearCoatMaterialData c{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::TextureMatrix, Matrix3::scaling({0.5f, 0.5f})}, + {MaterialAttribute::TextureCoordinates, 7u}, + }, {0, 3}}; + CORRADE_VERIFY(c.hasCommonTextureTransformation()); + CORRADE_VERIFY(c.hasCommonTextureCoordinates()); + CORRADE_COMPARE(c.commonTextureMatrix(), Matrix3::scaling({0.5f, 0.5f})); + CORRADE_COMPARE(c.commonTextureCoordinates(), 7); +} + +void MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesOneTexture() { + Containers::StringView textureName = PbrClearCoatTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrClearCoatMaterialData data{{}, { + /* These shouldn't affect the below */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u}, + + {MaterialLayer::ClearCoat}, + {textureName, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u}, + }, {2, 6}}; + + CORRADE_VERIFY(data.hasCommonTextureTransformation()); + CORRADE_COMPARE(data.commonTextureMatrix(), Matrix3::scaling({0.5f, 1.0f})); + CORRADE_VERIFY(data.hasCommonTextureCoordinates()); + CORRADE_COMPARE(data.commonTextureCoordinates(), 17u); +} + +void MaterialDataTest::pbrClearCoatAccessCommonTransformationCoordinatesOneDifferentTexture() { + Containers::StringView textureName = PbrClearCoatTextureData[testCaseInstanceId()]; + setTestCaseDescription(textureName); + + PbrClearCoatMaterialData data{{}, { + /* These are used by all textures except the one below, failing the + check */ + {MaterialAttribute::TextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::TextureCoordinates, 3u}, + + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 2u}, + {MaterialAttribute::RoughnessTexture, 3u}, + {MaterialAttribute::NormalTexture, 5u}, + {std::string{textureName} + "Matrix", Matrix3::scaling({0.5f, 1.0f})}, + {std::string{textureName} + "Coordinates", 17u} + }, {2, 8}}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); +} + +void MaterialDataTest::pbrClearCoatAccessNoCommonTransformationCoordinates() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + PbrClearCoatMaterialData data{{}, { + {MaterialLayer::ClearCoat}, + {MaterialAttribute::LayerFactorTexture, 3u}, + {MaterialAttribute::LayerFactorTextureMatrix, Matrix3::translation({0.5f, 0.0f})}, + {MaterialAttribute::LayerFactorTextureCoordinates, 3u}, + {MaterialAttribute::RoughnessTexture, 4u}, + {MaterialAttribute::RoughnessTextureMatrix, Matrix3::scaling({0.5f, 1.0f})}, + {MaterialAttribute::NormalTexture, 5u}, + {MaterialAttribute::NormalTextureCoordinates, 17u} + }, {0, 8}}; + + CORRADE_VERIFY(!data.hasCommonTextureTransformation()); + CORRADE_VERIFY(!data.hasCommonTextureCoordinates()); + + std::ostringstream out; + Error redirectError{&out}; + data.commonTextureMatrix(); + data.commonTextureCoordinates(); + CORRADE_COMPARE(out.str(), + "Trade::PbrClearCoatMaterialData::commonTextureMatrix(): the layer doesn't have a common texture coordinate transformation\n" + "Trade::PbrClearCoatMaterialData::commonTextureCoordinates(): the layer doesn't have a common texture coordinate set\n"); +} + +void MaterialDataTest::debugLayer() { + std::ostringstream out; + + Debug{&out} << MaterialLayer::ClearCoat << MaterialLayer(0xfefe) << MaterialLayer{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialLayer::ClearCoat Trade::MaterialLayer(0xfefe) Trade::MaterialLayer(0x0)\n"); +} + +void MaterialDataTest::debugAttribute() { + std::ostringstream out; + + Debug{&out} << MaterialAttribute::DiffuseTextureCoordinates << MaterialAttribute::LayerName << MaterialAttribute(0xfefe) << MaterialAttribute{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialAttribute::DiffuseTextureCoordinates Trade::MaterialAttribute::LayerName Trade::MaterialAttribute(0xfefe) Trade::MaterialAttribute(0x0)\n"); +} + +void MaterialDataTest::debugTextureSwizzle() { + std::ostringstream out; + + /* The swizzle is encoded as a fourCC, so it just prints the numerical + value as a char. Worst case this will print nothing or four garbage + letters. Sorry in that case. */ + Debug{&out} << MaterialTextureSwizzle::BA << MaterialTextureSwizzle{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialTextureSwizzle::BA Trade::MaterialTextureSwizzle::\n"); +} + +void MaterialDataTest::debugAttributeType() { + std::ostringstream out; + + Debug{&out} << MaterialAttributeType::Matrix3x2 << MaterialAttributeType(0xfe); + CORRADE_COMPARE(out.str(), "Trade::MaterialAttributeType::Matrix3x2 Trade::MaterialAttributeType(0xfe)\n"); +} + +void MaterialDataTest::debugType() { + std::ostringstream out; + + Debug(&out) << MaterialType::Phong << MaterialType(0xbe); + CORRADE_COMPARE(out.str(), "Trade::MaterialType::Phong Trade::MaterialType(0xbe)\n"); +} + +void MaterialDataTest::debugTypes() { + std::ostringstream out; + + Debug{&out} << (MaterialType::Phong|MaterialType(0xe0)) << MaterialTypes{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialType::Phong|Trade::MaterialType(0xe0) Trade::MaterialTypes{}\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void MaterialDataTest::debugFlag() { std::ostringstream out; - Debug{&out} << AbstractMaterialData::Flag::DoubleSided << AbstractMaterialData::Flag(0xf0); - CORRADE_COMPARE(out.str(), "Trade::AbstractMaterialData::Flag::DoubleSided Trade::AbstractMaterialData::Flag(0xf0)\n"); + Debug{&out} << MaterialData::Flag::DoubleSided << MaterialData::Flag(0xf0); + CORRADE_COMPARE(out.str(), "Trade::MaterialData::Flag::DoubleSided Trade::MaterialData::Flag(0xf0)\n"); } void MaterialDataTest::debugFlags() { std::ostringstream out; - Debug{&out} << AbstractMaterialData::Flag::DoubleSided << AbstractMaterialData::Flags{}; - CORRADE_COMPARE(out.str(), "Trade::AbstractMaterialData::Flag::DoubleSided Trade::AbstractMaterialData::Flags{}\n"); + Debug{&out} << MaterialData::Flag::DoubleSided << MaterialData::Flags{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialData::Flag::DoubleSided Trade::MaterialData::Flags{}\n"); } +CORRADE_IGNORE_DEPRECATED_POP +#endif void MaterialDataTest::debugAlphaMode() { std::ostringstream out; @@ -369,6 +4992,8 @@ void MaterialDataTest::debugAlphaMode() { CORRADE_COMPARE(out.str(), "Trade::MaterialAlphaMode::Opaque Trade::MaterialAlphaMode(0xee)\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void MaterialDataTest::debugPhongFlag() { std::ostringstream out; @@ -382,6 +5007,8 @@ void MaterialDataTest::debugPhongFlags() { Debug{&out} << (PhongMaterialData::Flag::DiffuseTexture|PhongMaterialData::Flag::SpecularTexture) << PhongMaterialData::Flags{}; CORRADE_COMPARE(out.str(), "Trade::PhongMaterialData::Flag::DiffuseTexture|Trade::PhongMaterialData::Flag::SpecularTexture Trade::PhongMaterialData::Flags{}\n"); } +CORRADE_IGNORE_DEPRECATED_POP +#endif }}}} diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 93c9f8c818..dac3bb7389 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -48,9 +48,17 @@ class AbstractSceneConverter; typedef CORRADE_DEPRECATED("use InputFileCallbackPolicy instead") InputFileCallbackPolicy ImporterFileCallbackPolicy; #endif -enum class MaterialType: UnsignedByte; +enum class MaterialAttribute: UnsignedInt; +enum class MaterialTextureSwizzle: UnsignedInt; +enum class MaterialAttributeType: UnsignedByte; +enum class MaterialLayer: UnsignedInt; +enum class MaterialType: UnsignedInt; enum class MaterialAlphaMode: UnsignedByte; -class AbstractMaterialData; +class MaterialAttributeData; +class MaterialData; +#ifdef MAGNUM_BUILD_DEPRECATED +typedef CORRADE_DEPRECATED("use MaterialData instead") MaterialData AbstractMaterialData; +#endif enum class AnimationTrackTargetType: UnsignedByte; enum class AnimationTrackType: UnsignedByte; @@ -63,6 +71,8 @@ class CameraData; enum class DataFlag: UnsignedByte; typedef Containers::EnumSet DataFlags; +class FlatMaterialData; + template class ImageData; typedef ImageData<1> ImageData1D; typedef ImageData<2> ImageData2D; @@ -83,6 +93,9 @@ class MeshObjectData2D; class MeshObjectData3D; class ObjectData2D; class ObjectData3D; +class PbrClearCoatMaterialData; +class PbrMetallicRoughnessMaterialData; +class PbrSpecularGlossinessMaterialData; class PhongMaterialData; class TextureData; class SceneData; diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index 635cc99233..b2a789627f 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -239,4 +239,4 @@ Containers::Optional AnyImageImporter::doImage2D(const UnsignedInt }} CORRADE_PLUGIN_REGISTER(AnyImageImporter, Magnum::Trade::AnyImageImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.1") + "cz.mosra.magnum.Trade.AbstractImporter/0.3.2") diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 64043c433b..25048581b8 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -32,11 +32,11 @@ #include #include -#include "Magnum/Trade/AbstractMaterialData.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" @@ -217,7 +217,7 @@ CORRADE_IGNORE_DEPRECATED_POP UnsignedInt AnySceneImporter::doMaterialCount() const { return _in->materialCount(); } Int AnySceneImporter::doMaterialForName(const std::string& name) { return _in->materialForName(name); } std::string AnySceneImporter::doMaterialName(const UnsignedInt id) { return _in->materialName(id); } -Containers::Pointer AnySceneImporter::doMaterial(const UnsignedInt id) { return _in->material(id); } +Containers::Optional AnySceneImporter::doMaterial(const UnsignedInt id) { return _in->material(id); } UnsignedInt AnySceneImporter::doTextureCount() const { return _in->textureCount(); } Int AnySceneImporter::doTextureForName(const std::string& name) { return _in->textureForName(name); } @@ -245,4 +245,4 @@ Containers::Optional AnySceneImporter::doImage3D(const UnsignedInt }} CORRADE_PLUGIN_REGISTER(AnySceneImporter, Magnum::Trade::AnySceneImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.1") + "cz.mosra.magnum.Trade.AbstractImporter/0.3.2") diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index d267442899..6a68182a60 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -203,7 +203,7 @@ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMaterialCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMaterialForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMaterialName(UnsignedInt id) override; - MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Pointer doMaterial(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMaterial(UnsignedInt id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doTextureCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doTextureForName(const std::string& name) override; diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 1a609a2234..f6ceb3b35e 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -486,4 +486,4 @@ Containers::Optional ObjImporter::doMesh(UnsignedInt id, UnsignedInt) }} CORRADE_PLUGIN_REGISTER(ObjImporter, Magnum::Trade::ObjImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.1") + "cz.mosra.magnum.Trade.AbstractImporter/0.3.2") diff --git a/src/MagnumPlugins/TgaImporter/TgaImporter.cpp b/src/MagnumPlugins/TgaImporter/TgaImporter.cpp index 2f9e90a868..f37a236dee 100644 --- a/src/MagnumPlugins/TgaImporter/TgaImporter.cpp +++ b/src/MagnumPlugins/TgaImporter/TgaImporter.cpp @@ -206,4 +206,4 @@ Containers::Optional TgaImporter::doImage2D(UnsignedInt, UnsignedIn }} CORRADE_PLUGIN_REGISTER(TgaImporter, Magnum::Trade::TgaImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.1") + "cz.mosra.magnum.Trade.AbstractImporter/0.3.2")