From f2a8d48f24cdadf8e793f8f9b6a73ed604103355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 4 Nov 2019 20:28:20 +0100 Subject: [PATCH] Trade: a reworked MeshData class. With API analogous to the (relatively) new AnimationData -- with one buffer containing all index data and one buffer containing all vertex data, both meant to be uploaded as-is to the GPU. This will eventually replace MeshData2D and MeshData3D, backwards compatibility and wiring up to other APIs will be done in follow-up commits. --- doc/snippets/MagnumTrade.cpp | 64 +++ src/Magnum/Trade/CMakeLists.txt | 2 + src/Magnum/Trade/MeshData.cpp | 307 ++++++++++ src/Magnum/Trade/MeshData.h | 634 +++++++++++++++++++++ src/Magnum/Trade/Test/CMakeLists.txt | 2 + src/Magnum/Trade/Test/MeshDataTest.cpp | 753 +++++++++++++++++++++++++ src/Magnum/Trade/Trade.h | 6 + 7 files changed, 1768 insertions(+) create mode 100644 src/Magnum/Trade/MeshData.cpp create mode 100644 src/Magnum/Trade/MeshData.h create mode 100644 src/Magnum/Trade/Test/MeshDataTest.cpp diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 23c4caa94c..0be6742408 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -30,12 +30,15 @@ #include "Magnum/FileCallback.h" #include "Magnum/ImageView.h" +#include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" +#include "Magnum/MeshTools/Interleave.h" #include "Magnum/Animation/Player.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" @@ -43,6 +46,8 @@ #include "Magnum/Trade/PhongMaterialData.h" #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/Shaders/Phong.h" #endif using namespace Magnum; @@ -201,6 +206,65 @@ else } #endif +#ifdef MAGNUM_TARGET_GL +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage] */ +/* Check that we have at least positions and normals */ +GL::Mesh mesh{data.primitive()}; +if(!data.hasAttribute(Trade::MeshAttributeName::Positions) || + !data.hasAttribute(Trade::MeshAttributeName::Normals)) + Fatal{} << "Oh well"; + +/* Interleave vertex data */ +GL::Buffer vertices; +vertices.setData(MeshTools::interleave(data.positions3D(), data.normals())); +mesh.addVertexBuffer(std::move(vertices), 0, + Shaders::Phong::Position{}, Shaders::Phong::Normal{}); + +/* Set up an index buffer, if the mesh is indexed*/ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indices()); + mesh.setIndexBuffer(std::move(indices), 0, MeshIndexType::UnsignedInt) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage] */ +} + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +GL::Mesh mesh{data.primitive()}; +/* [MeshData-usage-advanced] */ +/* Upload the original packed vertex data */ +GL::Buffer vertices; +vertices.setData(data.vertexData()); + +/* Set up the position attribute */ +Shaders::Phong::Position position; +auto positionType = data.attributeType(Trade::MeshAttributeName::Positions); +if(positionType == MeshAttributeType::Vector2) + position = {Shaders::Phong::Position::Components::Two}; +else if(positionType == MeshAttributeType::Vector3) + position = {Shaders::Phong::Position::Components::Three}; +else Fatal{} << "Huh?"; +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttributeName::Positions), + data.attributeStride(Trade::MeshAttributeName::Positions), position); + +// Set up other attributes ... + +/* Upload the original packed index data */ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indexData()); + mesh.setIndexBuffer(std::move(indices), 0, data.indexType()) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage-advanced] */ +} +#endif + { Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 89d5163136..e7376fac36 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -41,6 +41,7 @@ set(MagnumTrade_GracefulAssert_SRCS AnimationData.cpp CameraData.cpp ImageData.cpp + MeshData.cpp ObjectData2D.cpp ObjectData3D.cpp PhongMaterialData.cpp) @@ -53,6 +54,7 @@ set(MagnumTrade_HEADERS CameraData.h ImageData.h LightData.h + MeshData.h MeshData2D.h MeshData3D.h MeshObjectData2D.h diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp new file mode 100644 index 0000000000..f890a5f02a --- /dev/null +++ b/src/Magnum/Trade/MeshData.cpp @@ -0,0 +1,307 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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 "MeshData.h" + +#include "Magnum/Math/Color.h" + +namespace Magnum { namespace Trade { + +MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: type{type}, data{reinterpret_cast&>(data)} { + CORRADE_ASSERT(!data.empty(), + "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead", ); + CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, + "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); +} + +MeshAttributeData::MeshAttributeData(const MeshAttributeName name, const MeshAttributeType type, const Containers::StridedArrayView1D& data) noexcept: name{name}, type{type}, data{data} { + /** @todo support zero / negative stride? would be hard to transfer to GL */ + CORRADE_ASSERT(data.empty() || std::ptrdiff_t(meshAttributeTypeSize(type)) <= data.stride(), + "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << type, ); + CORRADE_ASSERT( + (name == MeshAttributeName::Positions && + (type == MeshAttributeType::Vector2 || + type == MeshAttributeType::Vector3)) || + (name == MeshAttributeName::Normals && + (type == MeshAttributeType::Vector3)) || + (name == MeshAttributeName::Colors && + (type == MeshAttributeType::Vector3 || + type == MeshAttributeType::Vector4)) || + (name == MeshAttributeName::TextureCoordinates && + (type == MeshAttributeType::Vector2)) || + name == MeshAttributeName::Custom /* can be any type */, + "Trade::MeshAttributeData:" << type << "is not a valid type for" << name, ); +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data}, _indexType{indices.type}, _primitive{primitive}, _importerState{importerState} { + /* Save vertex count. It's a strided array view, so the size is not + depending on type. */ + if(_attributes.empty()) { + CORRADE_ASSERT(indices.type != MeshIndexType{}, + "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); + /** @todo some better value? attributeless indexed with defined vertex count? */ + _vertexCount = 0; + } else _vertexCount = _attributes[0].data.size(); + + CORRADE_ASSERT(!_indices.empty() || !_indexData, + "Trade::MeshData: indexData passed for a non-indexed mesh", ); + CORRADE_ASSERT(_indices.empty() || (_indices.begin() >= _indexData.begin() && _indices.end() <= _indexData.end()), + "Trade::MeshData: indices are not contained in passed indexData array", ); + CORRADE_ASSERT(!_attributes.empty() || !_vertexData, + "Trade::MeshData: vertexData passed for an attribute-less mesh", ); + CORRADE_ASSERT(_vertexCount || !_vertexData, + "Trade::MeshData: vertexData passed for a mesh with zero vertices", ); + + #ifndef CORRADE_NO_ASSERT + /* Not checking what's already checked in MeshIndexData / MeshAttributeData + constructors */ + for(std::size_t i = 0; i != _attributes.size(); ++i) { + const MeshAttributeData& attribute = _attributes[i]; + CORRADE_ASSERT(attribute.data.size() == _vertexCount, + "Trade::MeshData: attribute" << i << "has" << attribute.data.size() << "vertices but" << _vertexCount << "expected", ); + CORRADE_ASSERT(attribute.data.empty() || (&attribute.data.front() >= _vertexData.begin() && &attribute.data.back() + meshAttributeTypeSize(attribute.type) <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "is not contained in passed vertexData array", ); + } + #endif +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Containers::Array{Containers::InPlaceInit, attributes}, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {} + +MeshData::MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState): MeshData{primitive, std::move(vertexData), Containers::Array{Containers::InPlaceInit, attributes}, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _importerState{importerState} {} + +MeshData::~MeshData() = default; + +MeshData::MeshData(MeshData&&) noexcept = default; + +MeshData& MeshData::operator=(MeshData&&) noexcept = default; + +UnsignedInt MeshData::indexCount() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexCount(): the mesh is not indexed", {}); + return _indices.size()/meshIndexTypeSize(_indexType); +} + +MeshIndexType MeshData::indexType() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexType(): the mesh is not indexed", {}); + return _indexType; +} + +MeshAttributeName MeshData::attributeName(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].name; +} + +MeshAttributeType MeshData::attributeType(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeType(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].type; +} + +std::size_t MeshData::attributeOffset(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return static_cast(_attributes[id].data.data()) - _vertexData.data(); +} + +UnsignedInt MeshData::attributeStride(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].data.stride(); +} + +UnsignedInt MeshData::attributeCount(const MeshAttributeName name) const { + UnsignedInt count = 0; + for(const MeshAttributeData& attribute: _attributes) + if(attribute.name == name) ++count; + return count; +} + +UnsignedInt MeshData::attributeFor(const MeshAttributeName name, UnsignedInt id) const { + for(std::size_t i = 0; i != _attributes.size(); ++i) { + if(_attributes[i].name != name) continue; + if(id-- == 0) return i; + } + + #ifdef CORRADE_NO_ASSERT + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + #else + return ~UnsignedInt{}; + #endif +} + +MeshAttributeType MeshData::attributeType(MeshAttributeName name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeType(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeType(attributeId); +} + +std::size_t MeshData::attributeOffset(MeshAttributeName name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeOffset(attributeId); +} + +UnsignedInt MeshData::attributeStride(MeshAttributeName name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeStride(attributeId); +} + +namespace { + template Containers::Array convertIndices(const Containers::ArrayView data) { + const auto input = Containers::arrayCast(data); + Containers::Array output{input.size()}; + for(std::size_t i = 0; i != input.size(); ++i) output[i] = input[i]; + return output; + } +} + +Containers::Array MeshData::indices() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indices(): the mesh is not indexed", {}); + + switch(_indexType) { + case MeshIndexType::UnsignedByte: return convertIndices(_indices); + case MeshIndexType::UnsignedShort: return convertIndices(_indices); + case MeshIndexType::UnsignedInt: return convertIndices(_indices); + } + + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +namespace { + +template Containers::Array copyAsArray(MeshAttributeType type, const Containers::StridedArrayView1D& data) { + CORRADE_INTERNAL_ASSERT(type == Implementation::meshAttributeTypeFor()); + const auto input = Containers::arrayCast(data); + Containers::Array output{input.size()}; + for(std::size_t i = 0, max = input.size(); i != max; ++i) + output[i] = input[i]; + return output; +} + +} + +Containers::Array MeshData::positions2D(const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttributeName::Positions, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2D(): index" << id << "out of range for" << attributeCount(MeshAttributeName::Positions) << "position attributes", {}); + const MeshAttributeData& attribute = _attributes[attributeId]; + + /* Copy 2D positions as-is, for 3D positions ignore Z */ + if(attribute.type == MeshAttributeType::Vector2) + return copyAsArray(attribute.type, attribute.data); + else if(attribute.type == MeshAttributeType::Vector3) + return copyAsArray(MeshAttributeType::Vector2, attribute.data); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::positions3D(const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttributeName::Positions, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3D(): index" << id << "out of range for" << attributeCount(MeshAttributeName::Positions) << "position attributes", {}); + const MeshAttributeData& attribute = _attributes[attributeId]; + + /* For 2D positions set Z to zero, copy 3D positions as-is */ + if(attribute.type == MeshAttributeType::Vector2) { + Containers::Array output{attribute.data.size()}; + const auto input = Containers::arrayCast(attribute.data); + for(std::size_t i = 0, max = input.size(); i != max; ++i) + output[i] = Vector3{input[i], 0.0f}; + return output; + } else if(attribute.type == MeshAttributeType::Vector3) { + return copyAsArray(attribute.type, attribute.data); + } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::normals(const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttributeName::Normals, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normals(): index" << id << "out of range for" << attributeCount(MeshAttributeName::Normals) << "normal attributes", {}); + const MeshAttributeData& attribute = _attributes[attributeId]; + + return copyAsArray(attribute.type, attribute.data); +} + +Containers::Array MeshData::textureCoordinates2D(const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttributeName::TextureCoordinates, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2D(): index" << id << "out of range for" << attributeCount(MeshAttributeName::TextureCoordinates) << "texture coordinate attributes", {}); + const MeshAttributeData& attribute = _attributes[attributeId]; + + return copyAsArray(attribute.type, attribute.data); +} + +Containers::Array MeshData::colors(const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttributeName::Colors, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colors(): index" << id << "out of range for" << attributeCount(MeshAttributeName::Colors) << "color attributes", {}); + const MeshAttributeData& attribute = _attributes[attributeId]; + + if(_attributes[attributeId].type == MeshAttributeType::Vector3) + return copyAsArray(attribute.type, attribute.data); + else if(_attributes[attributeId].type == MeshAttributeType::Vector4) + return copyAsArray(attribute.type, attribute.data); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::releaseIndexData() { + _indexType = MeshIndexType{}; /* so isIndexed() returns false */ + _indices = nullptr; + return std::move(_indexData); +} + +Containers::Array MeshData::releaseVertexData() { + _attributes = nullptr; + return std::move(_vertexData); +} + +Debug& operator<<(Debug& debug, const MeshAttributeName value) { + debug << "Trade::MeshAttributeName" << Debug::nospace; + + if(UnsignedByte(value) >= UnsignedByte(MeshAttributeName::Custom)) + return debug << "::Custom(" << Debug::nospace << (UnsignedByte(value) - UnsignedByte(MeshAttributeName::Custom)) << Debug::nospace << ")"; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MeshAttributeName::value: return debug << "::" << Debug::nospace << #value; + _c(Positions) + _c(Normals) + _c(TextureCoordinates) + _c(Colors) + #undef _c + /* LCOV_EXCL_STOP */ + + /* To silence compiler warning about unhandled values */ + case MeshAttributeName::Custom: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h new file mode 100644 index 0000000000..fdca505bc5 --- /dev/null +++ b/src/Magnum/Trade/MeshData.h @@ -0,0 +1,634 @@ +#ifndef Magnum_Trade_MeshData_h +#define Magnum_Trade_MeshData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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::MeshData, @ref Magnum::Trade::MeshIndexData, @ref Magnum::Trade::MeshAttributeData, enum @ref Magnum::Trade::MeshAttributeName + */ + +#include +#include + +#include "Magnum/Mesh.h" +#include "Magnum/Trade/Trade.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Mesh attribute name + +@see @ref MeshData, @ref MeshAttributeData, @ref MeshAttributeType +*/ +enum class MeshAttributeName: UnsignedByte { + /** + * Positions. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and + * @ref Magnum::Vector3 "Vector3" for 3D. + * @see @ref MeshAttributeType::Vector2, @ref MeshAttributeType::Vector3, + * @ref MeshData::positions2D(), @ref MeshData::positions3D() + */ + Positions, + + /** + * Normals. Type is usually @ref Magnum::Vector3 "Vector3". + * @see @ref MeshAttributeType::Vector3, @ref MeshData::normals() + */ + Normals, + + /** + * Texture coordinates. Type is usually @ref Magnum::Vector2 "Vector2" for + * 2D coordinates. + * @see @ref MeshAttributeType::Vector2, + * @ref MeshData::textureCoordinates2D() + */ + TextureCoordinates, + + /** + * Vertex colors. Type is usually @ref Magnum::Vector3 "Vector3" or + * @ref Magnum::Vector4 "Vector4" (or @ref Color3 / @ref Color4). + * @see @ref MeshAttributeType::Vector3, + * @ref MeshAttributeType::Vector4, + * @ref MeshData::colors() + */ + Colors, + + /** + * This and all higher values are for importer-specific attributes. Can be + * of any type. See documentation of a particular importer for details. + */ + Custom = 128, +}; + +/** @debugoperatorenum{MeshAttributeName} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MeshAttributeName value); + +/** +@brief Mesh index data + +Convenience type for populating @ref MeshData. Has no accessors, as the data +are then accessible through @ref MeshData APIs. +@see @ref MeshAttributeData +*/ +class MAGNUM_TRADE_EXPORT MeshIndexData { + public: + /** @brief Construct for a non-indexed mesh */ + explicit MeshIndexData() noexcept: type{} {} + + /** + * @brief Construct with a runtime-specified index type + * @param type Mesh index type + * @param data Index data + * + * The @p data size is expected to correspond to given @p type (e.g., + * for @ref MeshIndexType::UnsignedInt the @p data array size should + * be divisible by 4). If you know the @p type at compile time, you can + * use one of the @ref MeshIndexData(Containers::ArrayView), + * @ref MeshIndexData(Containers::ArrayView) or + * @ref MeshIndexData(Containers::ArrayView) + * constructors, which infer the index type automatically. + */ + explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; + + /** @brief Construct with unsigned byte indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data} {} + + /** @brief Construct with unsigned short indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data} {} + + /** @brief Construct with unsigned int indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data} {} + + private: + /* Not prefixed with _ because we use them like public in MeshData */ + friend MeshData; + MeshIndexType type; + Containers::ArrayView data; +}; + +/** +@brief Mesh attribute data + +Convenience type for populating @ref MeshData. Has no accessors, as the data +are then accessible through @ref MeshData APIs. +*/ +class MAGNUM_TRADE_EXPORT MeshAttributeData { + public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the attribute array for @ref MeshData, expected to + * be replaced with concrete values later. + */ + explicit MeshAttributeData() noexcept: name{}, type{}, data{} {} + + /** + * @brief Type-erased constructor + * @param name Attribute name + * @param type Attribute type + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type and that + * @p type corresponds to @p name. + */ + explicit MeshAttributeData(MeshAttributeName name, MeshAttributeType type, const Containers::StridedArrayView1D& data) noexcept; + + /** + * @brief Constructor + * @param name Attribute name + * @param data Attribute data + * + * Detectes @ref MeshAttributeType based on @p T and calls + * @ref MeshAttributeData(MeshAttributeName, MeshAttributeType, const Containers::StridedArrayView1D&). + */ + template explicit MeshAttributeData(MeshAttributeName name, const Containers::StridedArrayView1D& data) noexcept; + + /** @overload */ + template explicit MeshAttributeData(MeshAttributeName name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + + private: + /* Not prefixed with _ because we use them like public in MeshData */ + friend MeshData; + MeshAttributeName name; + MeshAttributeType type; + Containers::StridedArrayView1D data; +}; + +/** +@brief Mesh data + +Provides access to mesh vertex and index data, together with additional +information such as primitive type. Populated instances of this class are +returned from @ref AbstractImporter::mesh() and from particular functions in +the @ref Primitives library. + +@section Trade-MeshData-usage Basic usage + +The simplest usage is through the convenience functions @ref positions2D(), +@ref positions3D(), @ref normals(), @ref textureCoordinates2D() and +@ref colors(). Each of these takes an index (as there can be multiple sets of +texture coordinates, for example) and you're expected to check for attribute +presence first with either @ref hasAttribute() or +@ref attributeCount(MeshAttributeName) const: + +@snippet MagnumTrade.cpp MeshData-usage + +@section Trade-MeshData-usage-advanced Advanced usage + +The @ref positions2D(), ... functions shown above always return a +newly-allocated @ref Corrade::Containers::Array instance with a clearly defined +type that's large enough to represent most data. While that's fine for many use +cases, sometimes you may want to minimize the import time of a large model or +the imported data may be already in a well-optimized layout and format that you +want to preserve. The @ref MeshData class internally stores a contiguous blob +of data, which you can directly upload, and then use provided metadata to let +the GPU know of the format and layout: + +@snippet MagnumTrade.cpp MeshData-usage-advanced + +@see @ref AbstractImporter::mesh() +*/ +class MAGNUM_TRADE_EXPORT MeshData { + public: + /** + * @brief Construct an indexed mesh data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * The @p indices are expected to point to a sub-range of @p indexData. + * The @p attributes are expected to reference (sparse) sub-ranges of + * @p vertexData. + * + * If the mesh has no attributes, the @p indices are expected to be + * valid and non-empty. If you want to create an index-less + * attribute-less mesh, use @ref MeshData(MeshPrimitive, UnsignedInt, const void*) + * to specify desired vertex count. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct an non-indexed mesh data + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * with default-constructed @p indexData and @p indices arguments. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct an attribute-less indexed mesh data + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * with default-constructed @p vertexData and @p attributes arguments. + * + * The @p indices are expected to be valid and non-empty. If you want + * to create an index-less attribute-less mesh, use + * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify + * desired vertex count. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an index-less attribute-less mesh data + * @param primitive Primitive + * @param vertexCount Desired count of vertices to draw + * @param importerState Importer-specific state + * + * Useful in case the drawing is fully driven by a shader. + */ + explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; + + ~MeshData(); + + /** @brief Copying is not allowed */ + MeshData(const MeshData&) = delete; + + /** @brief Move constructor */ + MeshData(MeshData&&) noexcept; + + /** @brief Copying is not allowed */ + MeshData& operator=(const MeshData&) = delete; + + /** @brief Move assignment */ + MeshData& operator=(MeshData&&) noexcept; + + /** @brief Primitive */ + MeshPrimitive primitive() const { return _primitive; } + + /** + * @brief Raw index data + * + * Returns @cpp nullptr @ce if the mesh is not indexed. + * @see @ref isIndexed(), @ref indexCount(), @ref indexType(), + * @ref indices(), @ref releaseIndexData() + */ + Containers::ArrayView indexData() const & { return _indexData; } + Containers::ArrayView indexData() const && = delete; /**< @overload */ + + /** + * @brief Raw vertex data + * + * Contains data for all vertex attributes. Returns @cpp nullptr @ce if + * the mesh has no attributes. + * @see @ref attributeCount(), @ref attributeName(), + * @ref attributeType(), @ref attribute(), @ref releaseVertexData() + */ + Containers::ArrayView vertexData() const & { return _vertexData; } + Containers::ArrayView vertexData() const && = delete; /**< @overload */ + + /** @brief Whether the mesh is indexed */ + bool isIndexed() const { return _indexType != MeshIndexType{}; } + + /** + * @brief Index count + * + * Count of elements in the @ref indices() array. Expects that the + * mesh is indexed; returned value is always non-zero. See also + * @ref vertexCount() which returns count of elements in every + * @ref attribute() array, and @ref attributeCount() which returns + * count of different per-vertex attribute arrays. + * @see @ref isIndexed(), @ref indexType() + */ + UnsignedInt indexCount() const; + + /** + * @brief Index type + * + * Expects that the mesh is indexed. + * @see @ref isIndexed() + */ + MeshIndexType indexType() const; + + /** + * @brief Mesh indices + * + * Expects that the mesh is indexed and that @p T corresponds to + * @ref indexType(). You can also use the non-templated @ref indices() + * accessor to get indices converted to 32-bit, but note that such + * operation involves extra allocation and data conversion. + * @see @ref isIndexed(), @ref attribute() + */ + template Containers::ArrayView indices() const; + + /** + * @brief Mesh vertex count + * + * Count of elements in every attribute array returned by + * @ref attribute() (or, in case of an attribute-less mesh, the + * desired vertex count). See also @ref indexCount() which returns + * count of elements in the @ref indices() array, and + * @ref attributeCount() which returns count of different per-vertex + * attribute arrays. + */ + UnsignedInt vertexCount() const { return _vertexCount; } + + /** + * @brief Attribute array count + * + * Count of different per-vertex attribute arrays, or @cpp 0 @ce for an + * attribute-less mesh. See also @ref indexCount() which returns count + * of elements in the @ref indices() array and @ref vertexCount() which + * returns count of elements in every @ref attribute() array. + * @see @ref attributeCount(MeshAttributeName) const + */ + UnsignedInt attributeCount() const { return _attributes.size(); } + + /** + * @brief Attribute name + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * @see @ref attributeType() + */ + MeshAttributeName attributeName(UnsignedInt id) const; + + /** + * @brief Attribute type + * + * The @p is expected to be smaller than @ref attributeCount() const. + * You can also use @ref attributeType(MeshAttributeName, UnsignedInt) const + * to directly get a type of given named attribute. + * @see @ref attributeName() + */ + MeshAttributeType attributeType(UnsignedInt id) const; + + /** + * @brief Attribute offset + * + * Byte offset of the first element of given attribute from the + * beginning of the @ref vertexData() array. The @p is expected to be + * smaller than @ref attributeCount() const. You can also use + * @ref attributeOffset(MeshAttributeName, UnsignedInt) const to + * directly get an offset of given named attribute. + */ + std::size_t attributeOffset(UnsignedInt id) const; + + /** + * @brief Attribute stride + * + * Stride between consecutive elements of given attribute in the + * @ref vertexData() array. The @p is expected to be smaller + * than @ref attributeCount() const. You can also use + * @ref attributeStride(MeshAttributeName, UnsignedInt) const to + * directly get a stride of given named attribute. + */ + UnsignedInt attributeStride(UnsignedInt id) const; + + /** + * @brief Whether the mesh has given attribute + * + * @see @ref attributeCount(MeshAttributeName) const + */ + bool hasAttribute(MeshAttributeName name) const { + return attributeCount(name); + } + + /** + * @brief Count of given named attribute + * + * Unlike @ref attributeCount() const this returns count for given + * attribute name --- for example a mesh can have more than one set of + * texture coordinates. + * @see @ref hasAttribute() + */ + UnsignedInt attributeCount(MeshAttributeName name) const; + + /** + * @brief Type of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttributeName) const. + * @see @ref attributeType(UnsignedInt) const + */ + MeshAttributeType attributeType(MeshAttributeName name, UnsignedInt id = 0) const; + + /** + * @brief Offset of a named attribute + * + * Byte offset of the first element of given named attribute from the + * beginning of the @ref vertexData() array. The @p id is expected to + * be smaller than @ref attributeCount(MeshAttributeName) const. + * @see @ref attributeOffset(UnsignedInt) const + */ + std::size_t attributeOffset(MeshAttributeName name, UnsignedInt id = 0) const; + + /** + * @brief Stride of a named attribute + * + * Stride between consecutive elements of given named attribute in the + * @ref vertexData() array. The @p id is expected to be smaller than + * @ref attributeCount(MeshAttributeName) const. + * @see @ref attributeStride(UnsignedInt) const + */ + UnsignedInt attributeStride(MeshAttributeName name, UnsignedInt id = 0) const; + + /** + * @brief Data for given attribute array + * + * The @p id is expected to be smaller than @ref attributeCount() const + * and @p T is expected to correspond to + * @ref attributeType(UnsignedInt) const. You can also use the + * non-templated @ref positions2D(), @ref positions3D(), @ref normals(), + * @ref textureCoordinates2D() and @ref colors() accessors to get + * common attributes converted to usual types, but note that these + * operations involve extra allocation and data conversion. + * @see @ref attribute(MeshAttributeName, UnsignedInt) const + */ + template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + + /** + * @brief Data for given named attribute array + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttributeName) const and @p T is expected to + * correspond to @ref attributeType(MeshAttributeName, UnsignedInt) const. + * You can also use the non-templated @ref positions2D(), + * @ref positions3D(), @ref normals(), @ref textureCoordinates2D() and + * @ref colors() accessors to get common attributes converted to usual + * types, but note that these operations involve extra data conversion + * and an allocation. + * @see @ref attribute(UnsignedInt) const + */ + template Containers::StridedArrayView1D attribute(MeshAttributeName name, UnsignedInt id = 0) const; + + /** + * @brief Indices as 32-bit integers + * + * Convenience alternative to the templated @ref indices(). Converts + * the index array from an arbitrary underlying type and returns it in + * a newly-allocated array. + */ + Containers::Array indices() const; + + /** + * @brief Positions as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttributeName, UnsignedInt) const + * with @ref MeshAttributeName::Positions as the first argument. + * Converts the position array from an arbitrary underlying type and + * returns it in a newly-allocated array. If the underlying type is + * three-component, the last component is dropped. + */ + Containers::Array positions2D(UnsignedInt id = 0) const; + + /** + * @brief Positions as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttributeName, UnsignedInt) const + * with @ref MeshAttributeName::Positions as the first argument. + * Converts the position array from an arbitrary underlying type and + * returns it in a newly-allocated array. If the underlying type is + * two-component, the Z component is set to @cpp 0.0f @ce. + */ + Containers::Array positions3D(UnsignedInt id = 0) const; + + /** + * @brief Normals as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttributeName, UnsignedInt) const + * with @ref MeshAttributeName::Normals as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. + */ + Containers::Array normals(UnsignedInt id = 0) const; + + /** + * @brief Texture coordinates as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttributeName, UnsignedInt) const + * with @ref MeshAttributeName::TextureCoordinates as the first + * argument. Converts the position array from an arbitrary underlying + * type and returns it in a newly-allocated array. + */ + Containers::Array textureCoordinates2D(UnsignedInt id = 0) const; + + /** + * @brief Colors as RGBA floats + * + * Convenience alternative to @ref attribute(MeshAttributeName, UnsignedInt) const + * with @ref MeshAttributeName::Colors as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. If the underlying type is + * three-component, the alpha component is set to @cpp 1.0f @ce. + */ + Containers::Array colors(UnsignedInt id = 0) const; + + /** + * @brief Release index data storage + * + * Releases the ownership of the index data array and resets internal + * index-related state to default. The mesh then behaves like + * non-indexed. + * @see @ref indexData() + */ + Containers::Array releaseIndexData(); + + /** + * @brief Release vertex data storage + * + * Releases the ownership of the index data array and resets internal + * attribute-related state to default. The mesh then behaves like if + * it has no attributes. + * @see @ref vertexData() + */ + Containers::Array releaseVertexData(); + + /** + * @brief Importer-specific state + * + * See @ref AbstractImporter::importerState() for more information. + */ + const void* importerState() const { return _importerState; } + + private: + UnsignedInt attributeFor(MeshAttributeName name, UnsignedInt id) const; + + Containers::Array _indexData, _vertexData; + Containers::Array _attributes; + /* MeshIndexData are "unpacked" in order to avoid excessive padding */ + Containers::ArrayView _indices; + UnsignedInt _vertexCount; + MeshIndexType _indexType; + MeshPrimitive _primitive; + + const void* _importerState; +}; + +#if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT) +namespace Implementation { + /* LCOV_EXCL_START */ + template constexpr MeshIndexType meshIndexTypeFor(); + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedByte; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedShort; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedInt; } + + template constexpr MeshAttributeType meshAttributeTypeFor(); + template<> constexpr MeshAttributeType meshAttributeTypeFor() { return MeshAttributeType::Vector2; } + template<> constexpr MeshAttributeType meshAttributeTypeFor() { return MeshAttributeType::Vector3; } + template<> constexpr MeshAttributeType meshAttributeTypeFor() { return MeshAttributeType::Vector3; } + template<> constexpr MeshAttributeType meshAttributeTypeFor() { return MeshAttributeType::Vector4; } + template<> constexpr MeshAttributeType meshAttributeTypeFor() { return MeshAttributeType::Vector4; } + /* LCOV_EXCL_STOP */ +} +#endif + +template MeshAttributeData::MeshAttributeData(MeshAttributeName name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::meshAttributeTypeFor(), Containers::arrayCast(data)} {} + +template Containers::ArrayView MeshData::indices() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indices(): the mesh is not indexed", {}); + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::indices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast(_indices); +} + +template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + CORRADE_ASSERT(Implementation::meshAttributeTypeFor() == _attributes[id].type, + "Trade::MeshData::attribute(): improper type requested for" << _attributes[id].name << "of type" << _attributes[id].type, nullptr); + return Containers::arrayCast(_attributes[id].data); +} + +template Containers::StridedArrayView1D MeshData::attribute(MeshAttributeName name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attribute(attributeId); +} + +}} + +#endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 1ed9a94545..fc781c5c1c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -47,6 +47,7 @@ corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTes corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) +corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) @@ -56,6 +57,7 @@ corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) set_property(TARGET TradeAnimationDataTest + TradeMeshDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp new file mode 100644 index 0000000000..42bacab379 --- /dev/null +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -0,0 +1,753 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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 +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct MeshDataTest: TestSuite::Tester { + explicit MeshDataTest(); + + void constructIndex(); + void constructIndexZeroCount(); + void constructIndexTypeErased(); + void constructIndexTypeErasedWrongSize(); + + void constructAttribute(); + void constructAttributeWrongType(); + void constructAttributeTypeErased(); + void constructAttributeTypeErasedWrongStride(); + + void construct(); + void constructIndexless(); + void constructIndexlessZeroVertices(); + void constructAttributeless(); + void constructIndexlessAttributeless(); + void constructIndexlessAttributelessZeroVertices(); + + void constructIndexDataButNotIndexed(); + void constructVertexDataButNoAttributes(); + void constructVertexDataButNoVertices(); + void constructAttributelessInvalidIndices(); + void constructIndicesNotContained(); + void constructAttributeNotContained(); + void constructInconsitentVertexCount(); + + template void indicesAsArray(); + template void positions2DAsArray(); + template void positions3DAsArray(); + template void normalsAsArray(); + template void textureCoordinates2DAsArray(); + template void colorsAsArray(); + + void indicesNotIndexed(); + void indicesWrongType(); + + void attributeNotFound(); + void attributeWrongType(); + + void releaseIndexData(); + void releaseVertexData(); + + void debugAttributeName(); +}; + +MeshDataTest::MeshDataTest() { + addTests({&MeshDataTest::constructIndex, + &MeshDataTest::constructIndexZeroCount, + &MeshDataTest::constructIndexTypeErased, + &MeshDataTest::constructIndexTypeErasedWrongSize, + + &MeshDataTest::constructAttribute, + &MeshDataTest::constructAttributeWrongType, + &MeshDataTest::constructAttributeTypeErased, + &MeshDataTest::constructAttributeTypeErasedWrongStride, + + &MeshDataTest::construct, + &MeshDataTest::constructIndexless, + &MeshDataTest::constructIndexlessZeroVertices, + &MeshDataTest::constructAttributeless, + &MeshDataTest::constructIndexlessAttributeless, + &MeshDataTest::constructIndexlessAttributelessZeroVertices, + + &MeshDataTest::constructIndexDataButNotIndexed, + &MeshDataTest::constructVertexDataButNoAttributes, + &MeshDataTest::constructVertexDataButNoVertices, + &MeshDataTest::constructAttributelessInvalidIndices, + &MeshDataTest::constructIndicesNotContained, + &MeshDataTest::constructAttributeNotContained, + &MeshDataTest::constructInconsitentVertexCount, + + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::normalsAsArray, + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + + &MeshDataTest::indicesNotIndexed, + &MeshDataTest::indicesWrongType, + + &MeshDataTest::attributeNotFound, + &MeshDataTest::attributeWrongType, + + &MeshDataTest::releaseIndexData, + &MeshDataTest::releaseVertexData, + + &MeshDataTest::debugAttributeName}); +} + +using namespace Math::Literals; + +void MeshDataTest::constructIndex() { + { + Containers::Array indexData{3*1}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 25; + indexView[1] = 132; + indexView[2] = 3; + + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } { + Containers::Array indexData{3*2}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 2575; + indexView[1] = 13224; + indexView[2] = 3; + + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } { + Containers::Array indexData{3*4}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 2110122; + indexView[1] = 132257; + indexView[2] = 3; + MeshIndexData indices{indexView}; + + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } +} + +void MeshDataTest::constructIndexZeroCount() { + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedInt, nullptr}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead\n"); +} + +void MeshDataTest::constructIndexTypeErased() { + Containers::Array indexData{3*2}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 2575; + indexView[1] = 13224; + indexView[2] = 3; + + MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); +} + +void MeshDataTest::constructIndexTypeErasedWrongSize() { + Containers::Array indexData{3*2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedInt, indexData}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); +} + +void MeshDataTest::constructAttribute() { + Containers::Array positionData{3*sizeof(Vector2)}; + auto positionView = Containers::arrayCast(positionData); + positionView[0] = {1.2f, 0.2f}; + positionView[1] = {2.2f, 1.1f}; + positionView[2] = {-0.2f, 7.2f}; + + MeshAttributeData positions{MeshAttributeName::Positions, positionView}; + MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttributeName::Positions); + CORRADE_COMPARE(data.attributeType(0), MeshAttributeType::Vector2); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + positionView.data()); +} + +void MeshDataTest::constructAttributeWrongType() { + Containers::Array positionData{3*sizeof(Vector2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttributeName::Colors, Containers::arrayCast(positionData)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: MeshAttributeType::Vector2 is not a valid type for Trade::MeshAttributeName::Colors\n"); +} + +void MeshDataTest::constructAttributeTypeErased() { + Containers::Array positionData{3*sizeof(Vector3)}; + auto positionView = Containers::arrayCast(positionData); + positionView[0] = {1.2f, 0.2f, 0.1f}; + positionView[1] = {2.2f, 1.1f, 1.2f}; + positionView[2] = {-0.2f, 7.2f, 0.0f}; + + MeshAttributeData positions{MeshAttributeName::Positions, MeshAttributeType::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionView))}; + MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttributeName::Positions); + CORRADE_COMPARE(data.attributeType(0), MeshAttributeType::Vector3); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + positionView.data()); +} + +void MeshDataTest::constructAttributeTypeErasedWrongStride() { + Containers::Array positionData{3*sizeof(Vector3)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttributeName::Positions, MeshAttributeType::Vector3, Containers::arrayCast(positionData)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain MeshAttributeType::Vector3\n"); +} + +void MeshDataTest::construct() { + struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinate; + }; + + Containers::Array indexData{6*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + Containers::Array vertexData{3*sizeof(Vertex)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0].position = {0.1f, 0.2f, 0.3f}; + vertexView[1].position = {0.4f, 0.5f, 0.6f}; + vertexView[2].position = {0.7f, 0.8f, 0.9f}; + vertexView[0].normal = Vector3::xAxis(); + vertexView[1].normal = Vector3::yAxis(); + vertexView[2].normal = Vector3::zAxis(); + vertexView[0].textureCoordinate = {0.000f, 0.125f}; + vertexView[1].textureCoordinate = {0.250f, 0.375f}; + vertexView[2].textureCoordinate = {0.500f, 0.625f}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttributeName::Positions, + Containers::StridedArrayView1D{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData normals{MeshAttributeName::Normals, + Containers::StridedArrayView1D{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData textureCoordinates{MeshAttributeName::TextureCoordinates, + Containers::StridedArrayView1D{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + /* Texture coordinates deliberately twice (though aliased) */ + std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates}, &importerState}; + + /* Basics */ + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + /* Index access */ + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + /* Attribute access by ID */ + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 4); + CORRADE_COMPARE(data.attributeName(0), MeshAttributeName::Positions); + CORRADE_COMPARE(data.attributeName(1), MeshAttributeName::TextureCoordinates); + CORRADE_COMPARE(data.attributeName(2), MeshAttributeName::Normals); + CORRADE_COMPARE(data.attributeName(3), MeshAttributeName::TextureCoordinates); + CORRADE_COMPARE(data.attributeType(0), MeshAttributeType::Vector3); + CORRADE_COMPARE(data.attributeType(1), MeshAttributeType::Vector2); + CORRADE_COMPARE(data.attributeType(2), MeshAttributeType::Vector3); + CORRADE_COMPARE(data.attributeType(3), MeshAttributeType::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(2), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(3), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); + + /* Attribute access by name */ + CORRADE_VERIFY(data.hasAttribute(MeshAttributeName::Positions)); + CORRADE_VERIFY(data.hasAttribute(MeshAttributeName::Normals)); + CORRADE_VERIFY(data.hasAttribute(MeshAttributeName::TextureCoordinates)); + CORRADE_VERIFY(!data.hasAttribute(MeshAttributeName::Colors)); + CORRADE_VERIFY(!data.hasAttribute(MeshAttributeName::Custom)); + CORRADE_COMPARE(data.attributeCount(MeshAttributeName::Positions), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttributeName::Normals), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttributeName::TextureCoordinates), 2); + CORRADE_COMPARE(data.attributeCount(MeshAttributeName::Colors), 0); + CORRADE_COMPARE(data.attributeCount(MeshAttributeName::Custom), 0); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::Positions), + MeshAttributeType::Vector3); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::Normals), + MeshAttributeType::Vector3); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::TextureCoordinates, 0), + MeshAttributeType::Vector2); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::TextureCoordinates, 1), + MeshAttributeType::Vector2); + CORRADE_COMPARE(data.attributeOffset(MeshAttributeName::Positions), 0); + CORRADE_COMPARE(data.attributeOffset(MeshAttributeName::Normals), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttributeName::TextureCoordinates, 0), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttributeName::TextureCoordinates, 1), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeStride(MeshAttributeName::Positions), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttributeName::Normals), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttributeName::TextureCoordinates, 0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttributeName::TextureCoordinates, 1), sizeof(Vertex)); + CORRADE_COMPARE(data.attribute(MeshAttributeName::Positions)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(MeshAttributeName::Normals)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(MeshAttributeName::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(MeshAttributeName::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); +} + +void MeshDataTest::constructIndexless() { + Containers::Array vertexData{3*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + vertexView[2] = {0.7f, 0.8f}; + + int importerState; + MeshAttributeData positions{MeshAttributeName::Positions, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::Positions), MeshAttributeType::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttributeName::Positions)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructIndexlessZeroVertices() { + MeshAttributeData positions{MeshAttributeName::Positions, MeshAttributeType::Vector2, nullptr}; + MeshData data{MeshPrimitive::LineLoop, nullptr, {positions}}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeType(MeshAttributeName::Positions), MeshAttributeType::Vector2); +} + +void MeshDataTest::constructAttributeless() { + Containers::Array indexData{6*sizeof(UnsignedInt)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + int importerState; + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributeless() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 37); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributelessZeroVertices() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 0, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexDataButNotIndexed() { + Containers::Array indexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData positions{MeshAttributeName::Positions, MeshAttributeType::Vector2, nullptr}; + MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{}, nullptr, {positions}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: indexData passed for a non-indexed mesh\n"); +} + +void MeshDataTest::constructVertexDataButNoAttributes() { + Containers::Array indexData{6}; + Containers::Array vertexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}, std::move(vertexData), {}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for an attribute-less mesh\n"); +} + +void MeshDataTest::constructVertexDataButNoVertices() { + Containers::Array vertexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData positions{MeshAttributeName::Positions, MeshAttributeType::Vector2, nullptr}; + MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {positions}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for a mesh with zero vertices\n"); +} + +void MeshDataTest::constructAttributelessInvalidIndices() { + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Points, nullptr, MeshIndexData{}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly\n"); +} + +void MeshDataTest::constructIndicesNotContained() { + Containers::Array indexData{6}; + UnsignedShort indexData2[3]; + MeshIndexData indices{indexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(indexData), indices}; + MeshData{MeshPrimitive::Triangles, nullptr, indices}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: indices are not contained in passed indexData array\n" + "Trade::MeshData: indices are not contained in passed indexData array\n"); +} + +void MeshDataTest::constructAttributeNotContained() { + Containers::Array vertexData{24}; + Vector2 vertexData2[3]; + MeshAttributeData positions{MeshAttributeName::Positions, Containers::arrayCast(vertexData)}; + MeshAttributeData positions2{MeshAttributeName::Positions, Containers::arrayView(vertexData2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 1 is not contained in passed vertexData array\n" + "Trade::MeshData: attribute 0 is not contained in passed vertexData array\n"); +} + +void MeshDataTest::constructInconsitentVertexCount() { + Containers::Array vertexData{24}; + MeshAttributeData positions{MeshAttributeName::Positions, Containers::arrayCast(vertexData)}; + MeshAttributeData positions2{MeshAttributeName::Positions, Containers::arrayCast(vertexData).prefix(2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); +} + +template struct NameTraits; +#define _c(type) template<> struct NameTraits { \ + static const char* name() { return #type; } \ + }; +_c(Vector2) +_c(Vector3) +_c(Vector4) +_c(Color3) +_c(Color4) +#undef _c + +template void MeshDataTest::indicesAsArray() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array indexData{3*sizeof(T)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 75; + indexView[1] = 131; + indexView[2] = 240; + + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + CORRADE_COMPARE_AS(data.indices(), + (Containers::Array{Containers::InPlaceInit, {75, 131, 240}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Vector2{2.0f, 1.0f}); + positionsView[1] = T::pad(Vector2{0.0f, -1.0f}); + positionsView[2] = T::pad(Vector2{-2.0f, 3.0f}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttributeName::Positions, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2D(), (Containers::Array{ + Containers::InPlaceInit, {{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Vector3{2.0f, 1.0f, 0.3f}); + positionsView[1] = T::pad(Vector3{0.0f, -1.0f, 1.1f}); + positionsView[2] = T::pad(Vector3{-2.0f, 3.0f, 2.2f}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttributeName::Positions, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3D(), (Containers::Array{ + Containers::InPlaceInit, { + Vector3::pad(T::pad(Vector3{2.0f, 1.0f, 0.3f})), + Vector3::pad(T::pad(Vector3{0.0f, -1.0f, 1.1f})), + Vector3::pad(T::pad(Vector3{-2.0f, 3.0f, 2.2f}))}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::normalsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto normalsView = Containers::arrayCast(vertexData); + normalsView[0] = {2.0f, 1.0f, 0.3f}; + normalsView[1] = {0.0f, -1.0f, 1.1f}; + normalsView[2] = {-2.0f, 3.0f, 2.2f}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttributeName::Normals, normalsView}}}; + CORRADE_COMPARE_AS(data.normals(), (Containers::Array{ + Containers::InPlaceInit, {{2.0f, 1.0f, 0.3f}, {0.0f, -1.0f, 1.1f}, {-2.0f, 3.0f, 2.2f}}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2.0f, 1.0f}; + textureCoordinatesView[1] = {0.0f, -1.0f}; + textureCoordinatesView[2] = {-2.0f, 3.0f}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttributeName::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2D(), (Containers::Array{ + Containers::InPlaceInit, {{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::colorsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto colorsView = Containers::arrayCast(vertexData); + colorsView[0] = 0xff3366_rgbf; + colorsView[1] = 0x99aacc_rgbf; + colorsView[2] = 0x3377ff_rgbf; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttributeName::Colors, colorsView}}}; + CORRADE_COMPARE_AS(data.colors(), (Containers::Array{ + Containers::InPlaceInit, {0xff3366_rgbf, 0x99aacc_rgbf, 0x3377ff_rgbf}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::indicesNotIndexed() { + MeshData data{MeshPrimitive::Triangles, 37}; + + std::ostringstream out; + Error redirectError{&out}; + data.indexCount(); + data.indexType(); + data.indices(); + data.indices(); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::indexCount(): the mesh is not indexed\n" + "Trade::MeshData::indexType(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n"); +} + +void MeshDataTest::indicesWrongType() { + Containers::Array indexData{sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 57616; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + + std::ostringstream out; + Error redirectError{&out}; + data.indices(); + CORRADE_COMPARE(out.str(), "Trade::MeshData::indices(): improper type requested for MeshIndexType::UnsignedShort\n"); +} + +void MeshDataTest::attributeNotFound() { + MeshAttributeData colors1{MeshAttributeName::Colors, MeshAttributeType::Vector3, nullptr}; + MeshAttributeData colors2{MeshAttributeName::Colors, MeshAttributeType::Vector4, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {colors1, colors2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(2); + data.attributeType(2); + data.attributeOffset(2); + data.attributeStride(2); + data.attribute(2); + data.attributeType(MeshAttributeName::Positions); + data.attributeType(MeshAttributeName::Colors, 2); + data.attributeOffset(MeshAttributeName::Positions); + data.attributeOffset(MeshAttributeName::Colors, 2); + data.attributeStride(MeshAttributeName::Positions); + data.attributeStride(MeshAttributeName::Colors, 2); + data.attribute(MeshAttributeName::Positions); + data.attribute(MeshAttributeName::Colors, 2); + data.positions2D(); + data.positions3D(); + data.normals(); + data.textureCoordinates2D(); + data.colors(2); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attributeName(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeType(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeType(): index 0 out of range for 0 Trade::MeshAttributeName::Positions attributes\n" + "Trade::MeshData::attributeType(): index 2 out of range for 2 Trade::MeshAttributeName::Colors attributes\n" + "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttributeName::Positions attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttributeName::Colors attributes\n" + "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttributeName::Positions attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttributeName::Colors attributes\n" + "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttributeName::Positions attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttributeName::Colors attributes\n" + "Trade::MeshData::positions2D(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::positions3D(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::normals(): index 0 out of range for 0 normal attributes\n" + "Trade::MeshData::textureCoordinates2D(): index 0 out of range for 0 texture coordinate attributes\n" + "Trade::MeshData::colors(): index 2 out of range for 2 color attributes\n"); +} + +void MeshDataTest::attributeWrongType() { + MeshAttributeData positions{MeshAttributeName::Positions, MeshAttributeType::Vector3, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {positions}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttributeName::Positions); + CORRADE_COMPARE(out.str(), "Trade::MeshData::attribute(): improper type requested for Trade::MeshAttributeName::Positions of type MeshAttributeType::Vector3\n"); +} + +void MeshDataTest::releaseIndexData() { + Containers::Array indexData{6}; + auto indexView = Containers::arrayCast(indexData); + + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}}; + CORRADE_VERIFY(data.isIndexed()); + + Containers::Array released = data.releaseIndexData(); + CORRADE_COMPARE(static_cast(released.data()), indexView.data()); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_VERIFY(!data.isIndexed()); +} + +void MeshDataTest::releaseVertexData() { + Containers::Array vertexData{16}; + auto vertexView = Containers::arrayCast(vertexData); + + MeshAttributeData positions{MeshAttributeName::Positions, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; + CORRADE_COMPARE(data.attributeCount(), 2); + + Containers::Array released = data.releaseVertexData(); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::debugAttributeName() { + std::ostringstream out; + Debug{&out} << MeshAttributeName::Positions << MeshAttributeName(UnsignedByte(MeshAttributeName::Custom) + 73) << MeshAttributeName(0x73); + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeName::Positions Trade::MeshAttributeName::Custom(73) Trade::MeshAttributeName(0x73)\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshDataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 06c28ca496..593f275253 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -65,6 +65,12 @@ typedef ImageData<2> ImageData2D; typedef ImageData<3> ImageData3D; class LightData; + +enum class MeshAttributeName: UnsignedByte; +class MeshIndexData; +class MeshAttributeData; +class MeshData; + class MeshData2D; class MeshData3D; class MeshObjectData2D;