diff --git a/doc/changelog.dox b/doc/changelog.dox index e4fb6ed8f..7d830da93 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -72,6 +72,12 @@ 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. See [mosra/magnum#459](https://github.com/mosra/magnum/pull/459). + @subsection changelog-latest-changes Changes and improvements @subsubsection changelog-latest-changes-gl GL library diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index f27b36820..0ce26fe34 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -42,6 +42,7 @@ set(MagnumTrade_GracefulAssert_SRCS AnimationData.cpp CameraData.cpp ImageData.cpp + MaterialData.cpp MeshData.cpp ObjectData2D.cpp ObjectData3D.cpp @@ -58,6 +59,7 @@ set(MagnumTrade_HEADERS Data.h ImageData.h LightData.h + MaterialData.h MeshData.h MeshObjectData2D.h MeshObjectData3D.h @@ -72,7 +74,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 diff --git a/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp new file mode 100644 index 000000000..037219da0 --- /dev/null +++ b/src/Magnum/Trade/Implementation/materialAttributeProperties.hpp @@ -0,0 +1,49 @@ +/* + 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(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) +_c(SpecularTextureMatrix,Matrix3x3) +_c(SpecularTextureCoordinates,UnsignedInt) +_c(Shininess,Float) +_c(NormalTexture,UnsignedInt) +_c(NormalTextureMatrix,Matrix3x3) +_c(NormalTextureCoordinates,UnsignedInt) +_c(TextureMatrix,Matrix3x3) +_c(TextureCoordinates,UnsignedInt) +#endif diff --git a/src/Magnum/Trade/MaterialData.cpp b/src/Magnum/Trade/MaterialData.cpp new file mode 100644 index 000000000..b2dc17f9a --- /dev/null +++ b/src/Magnum/Trade/MaterialData.cpp @@ -0,0 +1,314 @@ +/* + 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 "Magnum/Math/Vector4.h" +#include "Magnum/Math/Matrix.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 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)}, + #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" + #undef _c + #undef _ct +}; +#endif + +} + +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: + return 4; + + 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; + } + + 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 */ { + /* 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, 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; + std::memcpy(_data.data + 1, AttributeMap[UnsignedInt(name) - 1].name.data(), AttributeMap[UnsignedInt(name) - 1].name.size()); + std::memcpy(_data.data + Implementation::MaterialAttributeDataSize - AttributeMap[UnsignedInt(name) - 1].size, value, AttributeMap[UnsignedInt(name) - 1].size); +} + +const void* MaterialAttributeData::value() const { + return _data.data + Implementation::MaterialAttributeDataSize - materialAttributeTypeSize(_data.type); +} + +MaterialData::MaterialData(Containers::Array&& data, const void* const importerState) noexcept: _data{std::move(data)}, _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 + + /* 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(_data.size() > 1) for(std::size_t i = 1; i != _data.size(); ++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; + } +} + +MaterialData::MaterialData(const std::initializer_list data, const void* const importerState): MaterialData{Implementation::initializerListToArrayWithDefaultDeleter(data), importerState} {} + +MaterialData::MaterialData(DataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: _data{Containers::Array{const_cast(data.data()), data.size(), [](MaterialAttributeData*, std::size_t){}}}, _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", ); + + if(_data.size() > 1) for(std::size_t i = 1; i != _data.size(); ++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", ); + } + #endif +} + +MaterialData::MaterialData(MaterialData&&) noexcept = default; + +MaterialData::~MaterialData() = default; + +MaterialData& MaterialData::operator=(MaterialData&&) noexcept = default; + +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::attributeFor(const Containers::StringView name) const { + const MaterialAttributeData* const found = std::lower_bound(_data.begin(), _data.end(), name, [](const MaterialAttributeData& a, const Containers::StringView& b) { + return a.name() < b; + }); + if(found == _data.end() || found->name() != name) return ~UnsignedInt{}; + return found - _data.begin(); +} + +bool MaterialData::hasAttribute(const Containers::StringView name) const { + return attributeFor(name) != ~UnsignedInt{}; +} + +bool MaterialData::hasAttribute(const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::hasAttribute(): invalid name" << name, {}); + return hasAttribute(string); +} + +UnsignedInt MaterialData::attributeId(const Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeId(): attribute" << name << "not found", {}); + return id; +} + +UnsignedInt MaterialData::attributeId(const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeId(): invalid name" << name, {}); + return attributeId(string); +} + +Containers::StringView MaterialData::attributeName(const UnsignedInt id) const { + CORRADE_ASSERT(id < _data.size(), + "Trade::MaterialData::attributeName(): index" << id << "out of range for" << _data.size() << "attributes", {}); + return _data[id]._data.data + 1; +} + +MaterialAttributeType MaterialData::attributeType(const UnsignedInt id) const { + CORRADE_ASSERT(id < _data.size(), + "Trade::MaterialData::attributeType(): index" << id << "out of range for" << _data.size() << "attributes", {}); + return _data[id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attributeType(): attribute" << name << "not found", {}); + return _data[id]._data.type; +} + +MaterialAttributeType MaterialData::attributeType(const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attributeType(): invalid name" << name, {}); + return attributeType(string); +} + +const void* MaterialData::attribute(const UnsignedInt id) const { + CORRADE_ASSERT(id < _data.size(), + "Trade::MaterialData::attribute(): index" << id << "out of range for" << _data.size() << "attributes", {}); + return _data[id].value(); +} + +const void* MaterialData::attribute(const Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found", {}); + return _data[id].value(); +} + +const void* MaterialData::attribute(const MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(string); +} + +Containers::Array MaterialData::release() { + return std::move(_data); +} + +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 << ")"; + + return debug << "::" << Debug::nospace << AttributeMap[UnsignedInt(value) - 1].name; +} + +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(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 + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/MaterialData.h b/src/Magnum/Trade/MaterialData.h new file mode 100644 index 000000000..92fcfc554 --- /dev/null +++ b/src/Magnum/Trade/MaterialData.h @@ -0,0 +1,779 @@ +#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::MaterialAttribute, @ref Magnum::Trade::MaterialAttributeType + * @m_since_latest + */ + +#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 attribute name +@m_since_latest + +Convenience aliases to actual attribute name strings, in the same form and +capitalization --- so for example @ref MaterialAttribute::DoubleSided is an +alias for @cpp "DoubleSided" @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 */ + + /** + * 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. + */ + AlphaMask = 1, + + /** + * 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. + */ + AlphaBlend, + + /** + * Double sided, @ref MaterialAttributeType::Bool. + * + * If not present, the default value is @cpp false @ce. + */ + DoubleSided, + + /** + * Ambient color for Phong materials, @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::AmbientTexture is present as well, these two + * are multiplied together. + */ + AmbientColor, + + /** + * Ambient texture index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::AmbientColor is present as well, these two + * are multiplied together. + */ + AmbientTexture, + + /** + * Ambient texture transformation matrix for Phong materials, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + */ + AmbientTextureMatrix, + + /** + * Ambient texture coordinate set index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + */ + AmbientTextureCoordinates, + + /** + * Diffuse color for Phong materials, @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::DiffuseTexture is present as well, these two + * are multiplied together. + */ + DiffuseColor, + + /** + * Diffuse texture index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::DiffuseColor is present as well, these two + * are multiplied together. + */ + DiffuseTexture, + + /** + * Diffuse texture transformation matrix for Phong materials, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + */ + DiffuseTextureMatrix, + + /** + * Diffuse texture coordinate set index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + */ + DiffuseTextureCoordinates, + + /** + * Specular color for Phong materials, @ref MaterialAttributeType::Vector4. + * + * If @ref MaterialAttribute::SpecularTexture is present as well, these two + * are multiplied together. + */ + SpecularColor, + + /** + * Specular texture index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * If @ref MaterialAttribute::SpecularColor is present as well, these two + * are multiplied together. + */ + SpecularTexture, + + /** + * Specular texture transformation matrix for Phong materials, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + */ + SpecularTextureMatrix, + + /** + * Specular texture coordinate set index for Phong materials, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + */ + SpecularTextureCoordinates, + + /** + * Shininess value for Phong materials, @ref MaterialAttributeType::Float. + */ + Shininess, + + /** + * Tangent-space normal map texture index, + * @ref MaterialAttributeType::UnsignedInt. + */ + NormalTexture, + + /** + * Normal texture transformation matrix, + * @ref MaterialAttributeType::Matrix3x3. + * + * Has a precedence over @ref MaterialAttribute::TextureMatrix if both are + * present. + */ + NormalTextureMatrix, + + /** + * Normal texture coordinate set index, + * @ref MaterialAttributeType::UnsignedInt. + * + * Has a precedence over @ref MaterialAttribute::TextureCoordinates if both + * are present. + */ + NormalTextureCoordinates, + + /** + * Common texture transformation matrix for all textures, + * @ref MaterialAttributeType::Matrix3x3. + * + * @ref MaterialAttribute::AmbientTextureMatrix / + * @ref MaterialAttribute::DiffuseTextureMatrix / + * @ref MaterialAttribute::SpecularTextureMatrix / + * @ref MaterialAttribute::NormalTextureMatrix have a precedence over this + * attribute for given texture, if present. + */ + TextureMatrix, + + /** + * Common texture coordinate set index for all textures, + * @ref MaterialAttributeType::UnsignedInt. + * + * @ref MaterialAttribute::AmbientTextureCoordinates / + * @ref MaterialAttribute::DiffuseTextureCoordinates / + * @ref MaterialAttribute::SpecularTextureCoordinates / + * @ref MaterialAttribute::NormalTextureCoordinates have a precedence over + * this attribute for given texture, if present. + */ + TextureCoordinates, +}; + +/** +@debugoperatorenum{MaterialAttribute} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MaterialAttribute 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" */ + + 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 */ +}; + +/** +@brief Byte size of a material attribute type +@m_since_latest +*/ +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 constexpr /*implicit*/ MaterialAttributeData(Containers::StringView name, const T& value) noexcept; + #ifndef DOXYGEN_GENERATING_OUTPUT + /* "Sure can't be constexpr" overload to avoid going through the + *insane* overload puzzle when not needed */ + template /*implicit*/ MaterialAttributeData(const char* name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor::type(), sizeof(T), &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 /*implicit*/ MaterialAttributeData(MaterialAttribute name, const T& value) noexcept: MaterialAttributeData{name, Implementation::MaterialAttributeTypeFor::type(), &value} {} + + /** + * @brief Construct from a type-erased value + * @param name Attribute name + * @param type Attribute type + * @param value Type-erased value + * + * 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. + */ + /*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 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(). + */ + 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. */ + 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} {} + + Float f; + UnsignedInt u; + Int 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{} {} + + 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::type type, Containers::StringView name, const T& value) noexcept: _4{type, name, value} {} + template constexpr explicit Storage(typename std::enable_if::type type, Containers::StringView name, const T& value) noexcept: _8{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]; + Data _1; + Data _4; + Data> _8; + 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 data +@m_since_latest + +Key-value store for material attributes in one of the types defined by +@ref MaterialAttributeType. + +@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: + /** + * @brief Construct + * @param data Attribute data + * @param importerState Importer-specific state + * + * The @p data gets sorted by name internally, expecting no duplicates. + */ + explicit MaterialData(Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MaterialData(std::initializer_list data, const void* importerState = nullptr); + + /** + * @brief Construct a non-owned material data + * @param dataFlags Ignored. Used only for a safer distinction + * from the owning constructor. + * @param data Attribute data + * @param importerState Importer-specific state + * + * The @p data is expected to be already sorted by name, without + * duplicates. + */ + explicit MaterialData(DataFlags dataFlags, Containers::ArrayView data, 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 Raw attribute data + * + * Returns @cpp nullptr @ce if the material has no attributes. + * @see @ref release() + */ + Containers::ArrayView data() const { return _data; } + + /** @brief Attribute count */ + UnsignedInt attributeCount() const { return _data.size(); } + + /** @brief Whether the material has given attribute */ + bool hasAttribute(Containers::StringView name) const; + bool hasAttribute(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief ID of a named attribute + * + * The @p name is expected to exist. + */ + UnsignedInt attributeId(Containers::StringView name) const; + UnsignedInt attributeId(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Attribute name + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * The returned view always has + * @ref Corrade::Containers::StringViewFlag::NullTerminated set. + * @see @ref attributeType() + */ + Containers::StringView attributeName(UnsignedInt id) const; + + /** + * @brief Attribute type + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * @see @ref attributeName() + */ + MaterialAttributeType attributeType(UnsignedInt id) const; + + /** + * @brief Type of a named attribute + * + * The @p name is expected to exist. + * @see @ref hasAttribute() + */ + MaterialAttributeType attributeType(Containers::StringView name) const; + /** @overload */ + MaterialAttributeType attributeType(MaterialAttribute name) const; + + /** + * @brief Type-erased attribute value + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * Cast the pointer to a concrete type based on @ref type(). + */ + const void* attribute(UnsignedInt id) const; + + /** + * @brief Type-erased value of a named attribute + * + * The @p name is expected to exist. Cast the pointer to a concrete + * type based on @ref attributeType(). + * @see @ref hasAttribute() + */ + const void* attribute(Containers::StringView name) const; + const void* attribute(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Attribute value + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * Expects that @p T corresponds to @ref attributeType(UnsignedInt) const + * for given @p id. + */ + template T attribute(UnsignedInt id) const; + + /** + * @brief Value of a named attribute + * + * The @p name is expected to exist. Expects that @p T corresponds to + * @ref attributeType(Containers::StringView) const for given @p name. + * @see @ref hasAttribute() + */ + template T attribute(Containers::StringView name) const; + template T attribute(MaterialAttribute name) const; /**< @overload */ + + /** + * @brief Release data storage + * + * Releases the ownership of the attribute array and resets internal + * 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. + * @see @ref data() + */ + Containers::Array release(); + + /** + * @brief Importer-specific state + * + * See @ref AbstractImporter::importerState() for more information. + */ + const void* importerState() const { return _importerState; } + + private: + static Containers::StringView attributeString(MaterialAttribute name); + /* Internal helper that doesn't assert, unlike attributeId() */ + UnsignedInt attributeFor(Containers::StringView name) const; + + Containers::Array _data; + const void* _importerState; +}; + +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; + } + }; + #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(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(), (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), 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()); +} + +template T MaterialData::attribute(UnsignedInt id) const { + const void* const value = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT + if(!value) return {}; + #endif + CORRADE_ASSERT(Implementation::MaterialAttributeTypeFor::type() == _data[id]._data.type, + "Trade::MaterialData::attribute(): improper type requested for" << (_data[id]._data.data + 1) << "of" << _data[id]._data.type, {}); + return *reinterpret_cast(value); +} + +template T MaterialData::attribute(Containers::StringView name) const { + const UnsignedInt id = attributeFor(name); + CORRADE_ASSERT(id != ~UnsignedInt{}, + "Trade::MaterialData::attribute(): attribute" << name << "not found", {}); + return attribute(id); +} + +template T MaterialData::attribute(MaterialAttribute name) const { + const Containers::StringView string = attributeString(name); + CORRADE_ASSERT(string.data(), "Trade::MaterialData::attribute(): invalid name" << name, {}); + return attribute(string); +} + +}} + +#endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 38b91579f..d04cd597b 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 acbc60fa3..ea9461868 100644 --- a/src/Magnum/Trade/Test/MaterialDataTest.cpp +++ b/src/Magnum/Trade/Test/MaterialDataTest.cpp @@ -23,10 +23,16 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include +#include #include +#include +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/PhongMaterialData.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -35,6 +41,45 @@ class MaterialDataTest: public TestSuite::Tester { public: explicit MaterialDataTest(); + void attributeTypeSize(); + void attributeTypeSizeInvalid(); + + void attributeMap(); + + void constructAttributeDefault(); + void constructAttributeString(); + void constructAttributeName(); + void constructAttributeTypeErasedString(); + void constructAttributeTypeErasedName(); + + template void constructAttributeStringConstexpr(); + + void constructAttributeInvalidName(); + void constructAttributeWrongTypeForName(); + void constructAttributeInvalidType(); + void constructAttributeTooLarge(); + void constructAttributeWrongAccessType(); + + void construct(); + void constructEmptyAttribute(); + void constructDuplicateAttribute(); + void constructFromImmutableSortedArray(); + + void constructNonOwned(); + void constructNonOwnedEmptyAttribute(); + void constructNonOwnedNotSorted(); + void constructNonOwnedDuplicateAttribute(); + + void constructCopy(); + void constructMove(); + + void accessOutOfBounds(); + void accessInvalidAttributeName(); + void accessNotFound(); + void accessWrongType(); + + void release(); + void constructPhong(); void constructPhongTextured(); void constructPhongTexturedTextureTransform(); @@ -42,11 +87,14 @@ class MaterialDataTest: public TestSuite::Tester { void constructPhongTextureTransformNoTextures(); void constructPhongNoTextureTransformationFlag(); void constructPhongNoTextureCoordinatesFlag(); - void constructCopy(); - void constructMovePhong(); + void constructPhongCopy(); + void constructPhongMove(); void accessInvalidTextures(); + void debugAttribute(); + void debugAttributeType(); + void debugType(); void debugFlag(); void debugFlags(); @@ -57,18 +105,84 @@ class MaterialDataTest: public TestSuite::Tester { }; MaterialDataTest::MaterialDataTest() { - addTests({&MaterialDataTest::constructPhong, + addTests({&MaterialDataTest::attributeTypeSize, + &MaterialDataTest::attributeTypeSizeInvalid, + &MaterialDataTest::attributeMap, + + &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::constructAttributeInvalidName, + &MaterialDataTest::constructAttributeWrongTypeForName, + &MaterialDataTest::constructAttributeInvalidType, + &MaterialDataTest::constructAttributeTooLarge, + &MaterialDataTest::constructAttributeWrongAccessType, + + &MaterialDataTest::construct, + &MaterialDataTest::constructEmptyAttribute}); + + addRepeatedTests({&MaterialDataTest::constructDuplicateAttribute}, + 5*4*3*2); + + addTests({&MaterialDataTest::constructFromImmutableSortedArray, + + &MaterialDataTest::constructNonOwned, + &MaterialDataTest::constructNonOwnedEmptyAttribute, + &MaterialDataTest::constructNonOwnedNotSorted, + &MaterialDataTest::constructNonOwnedDuplicateAttribute, + + &MaterialDataTest::constructCopy, + &MaterialDataTest::constructMove, + + &MaterialDataTest::accessOutOfBounds, + &MaterialDataTest::accessInvalidAttributeName, + &MaterialDataTest::accessNotFound, + &MaterialDataTest::accessWrongType, + + &MaterialDataTest::release, + + &MaterialDataTest::constructPhong, &MaterialDataTest::constructPhongTextured, &MaterialDataTest::constructPhongTexturedTextureTransform, &MaterialDataTest::constructPhongTexturedCoordinates, &MaterialDataTest::constructPhongTextureTransformNoTextures, &MaterialDataTest::constructPhongNoTextureTransformationFlag, &MaterialDataTest::constructPhongNoTextureCoordinatesFlag, - &MaterialDataTest::constructCopy, - &MaterialDataTest::constructMovePhong, + &MaterialDataTest::constructPhongCopy, + &MaterialDataTest::constructPhongMove, &MaterialDataTest::accessInvalidTextures, + &MaterialDataTest::debugAttribute, + &MaterialDataTest::debugAttributeType, + &MaterialDataTest::debugType, &MaterialDataTest::debugFlag, &MaterialDataTest::debugFlags, @@ -78,6 +192,594 @@ MaterialDataTest::MaterialDataTest() { &MaterialDataTest::debugPhongFlags}); } +using namespace Containers::Literals; +using namespace Math::Literals; + +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::attributeTypeSizeInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + materialAttributeTypeSize(MaterialAttributeType(0x0)); + materialAttributeTypeSize(MaterialAttributeType(0xfe)); + CORRADE_COMPARE(out.str(), + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0x0)\n" + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n"); +} + +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 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; + #include "Magnum/Trade/Implementation/materialAttributeProperties.hpp" + #undef _c + #undef _ct + } + #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 */ + constexpr 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)); +} + +void MaterialDataTest::constructAttributeInvalidName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{MaterialAttribute(0x0), 5}; + MaterialAttributeData{MaterialAttribute(0xfefe), 5}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: invalid name Trade::MaterialAttribute(0x0)\n" + "Trade::MaterialAttributeData: invalid name Trade::MaterialAttribute(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}; + MaterialAttributeData{MaterialAttribute::DiffuseColor, Vector3ui{255, 16, 24}}; + CORRADE_COMPARE(out.str(), + "Trade::MaterialAttributeData: expected Trade::MaterialAttributeType::Vector4 for Trade::MaterialAttribute::DiffuseColor but got Trade::MaterialAttributeType::Vector3ui\n"); +} + +void MaterialDataTest::constructAttributeInvalidType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MaterialAttributeData{"bla", MaterialAttributeType(0x0), nullptr}; + MaterialAttributeData{"bla", MaterialAttributeType(0xfe), nullptr}; + CORRADE_COMPARE(out.str(), + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0x0)\n" + "Trade::materialAttributeTypeSize(): invalid type Trade::MaterialAttributeType(0xfe)\n"); +} + +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. */ + /*constexpr*/ MaterialAttributeData{"attributeIsLong"_s, Matrix3x4{}}; + 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"); +} + +void MaterialDataTest::constructAttributeWrongAccessType() { + #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(): improper type requested for thing3 of Trade::MaterialAttributeType::Matrix4x3\n"); +} + +void MaterialDataTest::construct() { + int state; + MaterialData data{{ + {MaterialAttribute::DoubleSided, true}, + {MaterialAttribute::DiffuseTextureCoordinates, 5u}, + {"highlightColor", 0x335566ff_rgbaf}, + {MaterialAttribute::AmbientTextureMatrix, Matrix3::scaling({0.5f, 1.0f})} + }, &state}; + + CORRADE_COMPARE(data.attributeCount(), 4); + CORRADE_COMPARE(data.data().size(), 4); + CORRADE_COMPARE(data.importerState(), &state); + + /* 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::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{{}, attributes, &state}; + + /* Expecting the same output as in construct() */ + CORRADE_COMPARE(data.attributeCount(), 4); + CORRADE_COMPARE(data.data().size(), 4); + CORRADE_COMPARE(data.data().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::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::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void MaterialDataTest::constructMove() { + int state; + MaterialData a{{ + {MaterialAttribute::DoubleSided, true}, + {"boredomFactor", 5} + }, &state}; + + MaterialData b{std::move(a)}; + CORRADE_COMPARE(a.attributeCount(), 0); + CORRADE_COMPARE(b.attributeCount(), 2); + CORRADE_COMPARE(b.attributeName(0), "DoubleSided"); + CORRADE_COMPARE(b.importerState(), &state); + + MaterialData c{{ + {MaterialAttribute::AlphaMask, 0.5f} + }}; + c = std::move(b); + CORRADE_COMPARE(b.attributeCount(), 1); + CORRADE_COMPARE(c.attributeCount(), 2); + CORRADE_COMPARE(c.attributeName(0), "DoubleSided"); + CORRADE_COMPARE(c.importerState(), &state); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +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); + CORRADE_COMPARE(out.str(), + "Trade::MaterialData::attributeName(): index 2 out of range for 2 attributes\n" + "Trade::MaterialData::attributeType(): index 2 out of range for 2 attributes\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MaterialData::attribute(): index 2 out of range for 2 attributes\n"); +} + +void MaterialDataTest::accessInvalidAttributeName() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MaterialData data{}; + + std::ostringstream out; + Error redirectError{&out}; + data.hasAttribute(MaterialAttribute(0x0)); + data.hasAttribute(MaterialAttribute(0xfefe)); + data.attributeId(MaterialAttribute(0x0)); + data.attributeId(MaterialAttribute(0xfefe)); + data.attributeType(MaterialAttribute(0x0)); + data.attributeType(MaterialAttribute(0xfefe)); + data.attribute(MaterialAttribute(0x0)); + data.attribute(MaterialAttribute(0xfefe)); + data.attribute(MaterialAttribute(0x0)); + data.attribute(MaterialAttribute(0xfefe)); + 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"); +} + +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\n" + "Trade::MaterialData::attributeType(): attribute DiffuseColour not found\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found\n" + "Trade::MaterialData::attribute(): attribute DiffuseColour not found\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"); + 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"); +} + +void MaterialDataTest::release() { + MaterialData data{{ + {"DiffuseColor", 0xff3366aa_rgbaf}, + {MaterialAttribute::NormalTexture, 0u} + }}; + + const void* pointer = data.data().data(); + + Containers::Array released = data.release(); + CORRADE_COMPARE(released.data(), pointer); + CORRADE_COMPARE(released.size(), 2); + CORRADE_VERIFY(!data.data()); + CORRADE_COMPARE(data.attributeCount(), 0); +} + void MaterialDataTest::constructPhong() { using namespace Math::Literals; @@ -198,8 +900,6 @@ void MaterialDataTest::constructPhongTextureTransformNoTextures() { } void MaterialDataTest::constructPhongNoTextureTransformationFlag() { - using namespace Math::Literals; - #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -231,14 +931,14 @@ void MaterialDataTest::constructPhongNoTextureCoordinatesFlag() { "PhongMaterialData::PhongMaterialData: non-zero texture coordinate sets require Flag::TextureCoordinates to be enabled\n"); } -void MaterialDataTest::constructCopy() { +void MaterialDataTest::constructPhongCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); CORRADE_VERIFY(!(std::is_assignable{})); } -void MaterialDataTest::constructMovePhong() { +void MaterialDataTest::constructPhongMove() { using namespace Math::Literals; const int a{}; @@ -333,6 +1033,20 @@ void MaterialDataTest::accessInvalidTextures() { "Trade::PhongMaterialData::normalTextureCoordinates(): the material doesn't have a normal texture\n"); } +void MaterialDataTest::debugAttribute() { + std::ostringstream out; + + Debug{&out} << MaterialAttribute::DiffuseTextureCoordinates << MaterialAttribute(0xfefe) << MaterialAttribute{}; + CORRADE_COMPARE(out.str(), "Trade::MaterialAttribute::DiffuseTextureCoordinates Trade::MaterialAttribute(0xfefe) Trade::MaterialAttribute(0x0)\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; diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 93c9f8c81..164ae036a 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -48,9 +48,13 @@ class AbstractSceneConverter; typedef CORRADE_DEPRECATED("use InputFileCallbackPolicy instead") InputFileCallbackPolicy ImporterFileCallbackPolicy; #endif +enum class MaterialAttribute: UnsignedInt; +enum class MaterialAttributeType: UnsignedByte; enum class MaterialType: UnsignedByte; enum class MaterialAlphaMode: UnsignedByte; class AbstractMaterialData; +class MaterialAttributeData; +class MaterialData; enum class AnimationTrackTargetType: UnsignedByte; enum class AnimationTrackType: UnsignedByte;