Browse Source

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.
pull/371/head
Vladimír Vondruš 7 years ago
parent
commit
ecbe5718b4
  1. 3
      doc/changelog.dox
  2. 65
      doc/snippets/MagnumTrade.cpp
  3. 10
      src/Magnum/Shaders/Generic.h
  4. 8
      src/Magnum/Trade/CMakeLists.txt
  5. 50
      src/Magnum/Trade/Implementation/arrayUtilities.h
  6. 357
      src/Magnum/Trade/MeshData.cpp
  7. 793
      src/Magnum/Trade/MeshData.h
  8. 2
      src/Magnum/Trade/Test/CMakeLists.txt
  9. 969
      src/Magnum/Trade/Test/MeshDataTest.cpp
  10. 6
      src/Magnum/Trade/Trade.h
  11. 2
      src/Magnum/VertexFormat.h

3
doc/changelog.dox

@ -160,6 +160,9 @@ See also:
@subsubsection changelog-latest-new-trade Trade library @subsubsection changelog-latest-new-trade Trade library
- A new, redesigned @ref Trade::MeshData class that allows much more flexible
access to vertex/index data without unnecessary allocations and data
conversions or copies
- Ability to import image mip levels via an additional parameter in - Ability to import image mip levels via an additional parameter in
@ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2D(),
@ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D

65
doc/snippets/MagnumTrade.cpp

@ -30,12 +30,15 @@
#include "Magnum/FileCallback.h" #include "Magnum/FileCallback.h"
#include "Magnum/ImageView.h" #include "Magnum/ImageView.h"
#include "Magnum/Mesh.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/MeshTools/Interleave.h"
#include "Magnum/Animation/Player.h" #include "Magnum/Animation/Player.h"
#include "Magnum/MeshTools/Transform.h" #include "Magnum/MeshTools/Transform.h"
#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/AnimationData.h"
#include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/ImageData.h"
#include "Magnum/Trade/MeshData.h"
#include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData2D.h"
#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/MeshData3D.h"
#include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData2D.h"
@ -43,6 +46,8 @@
#include "Magnum/Trade/PhongMaterialData.h" #include "Magnum/Trade/PhongMaterialData.h"
#ifdef MAGNUM_TARGET_GL #ifdef MAGNUM_TARGET_GL
#include "Magnum/GL/Texture.h" #include "Magnum/GL/Texture.h"
#include "Magnum/GL/Mesh.h"
#include "Magnum/Shaders/Phong.h"
#endif #endif
using namespace Magnum; using namespace Magnum;
@ -201,6 +206,66 @@ else
} }
#endif #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::MeshAttribute::Position) ||
!data.hasAttribute(Trade::MeshAttribute::Normal))
Fatal{} << "Oh well";
/* Interleave vertex data */
GL::Buffer vertices;
vertices.setData(MeshTools::interleave(data.positions3DAsArray(),
data.normalsAsArray()));
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.indicesAsArray());
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 positionFormat = data.attributeFormat(Trade::MeshAttribute::Position);
if(positionFormat == VertexFormat::Vector2)
position = {Shaders::Phong::Position::Components::Two};
else if(positionFormat == VertexFormat::Vector3)
position = {Shaders::Phong::Position::Components::Three};
else Fatal{} << "Huh?";
mesh.addVertexBuffer(vertices,
data.attributeOffset(Trade::MeshAttribute::Position),
data.attributeStride(Trade::MeshAttribute::Position), 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& foo();
Trade::MeshData2D& data = foo(); Trade::MeshData2D& data = foo();

10
src/Magnum/Shaders/Generic.h

@ -78,21 +78,23 @@ template<UnsignedInt dimensions> struct Generic {
* @brief Vertex position * @brief Vertex position
* *
* @ref Magnum::Vector2 "Vector2" in 2D and @ref Magnum::Vector3 "Vector3" * @ref Magnum::Vector2 "Vector2" in 2D and @ref Magnum::Vector3 "Vector3"
* in 3D. * in 3D. Corresponds to @ref Trade::MeshAttribute::Position.
*/ */
typedef GL::Attribute<0, T> Position; typedef GL::Attribute<0, T> Position;
/** /**
* @brief 2D texture coordinates * @brief 2D texture coordinates
* *
* @ref Magnum::Vector2 "Vector2". * @ref Magnum::Vector2 "Vector2". Corresponds to
* @ref Trade::MeshAttribute::TextureCoordinates.
*/ */
typedef GL::Attribute<1, Vector2> TextureCoordinates; typedef GL::Attribute<1, Vector2> TextureCoordinates;
/** /**
* @brief Vertex normal * @brief Vertex normal
* *
* @ref Magnum::Vector3 "Vector3", defined only in 3D. * @ref Magnum::Vector3 "Vector3", defined only in 3D. Corresponds to
* @ref Trade::MeshAttribute::Normal.
*/ */
typedef GL::Attribute<2, Vector3> Normal; typedef GL::Attribute<2, Vector3> Normal;
@ -108,6 +110,7 @@ template<UnsignedInt dimensions> struct Generic {
* @brief Three-component vertex color. * @brief Three-component vertex color.
* *
* @ref Magnum::Color3. Use either this or the @ref Color4 attribute. * @ref Magnum::Color3. Use either this or the @ref Color4 attribute.
* Corresponds to @ref Trade::MeshAttribute::Color.
*/ */
typedef GL::Attribute<3, Magnum::Color3> Color3; typedef GL::Attribute<3, Magnum::Color3> Color3;
@ -115,6 +118,7 @@ template<UnsignedInt dimensions> struct Generic {
* @brief Four-component vertex color. * @brief Four-component vertex color.
* *
* @ref Magnum::Color4. Use either this or the @ref Color3 attribute. * @ref Magnum::Color4. Use either this or the @ref Color3 attribute.
* Corresponds to @ref Trade::MeshAttribute::Color.
*/ */
typedef GL::Attribute<3, Magnum::Color4> Color4; typedef GL::Attribute<3, Magnum::Color4> Color4;

8
src/Magnum/Trade/CMakeLists.txt

@ -41,6 +41,7 @@ set(MagnumTrade_GracefulAssert_SRCS
AnimationData.cpp AnimationData.cpp
CameraData.cpp CameraData.cpp
ImageData.cpp ImageData.cpp
MeshData.cpp
ObjectData2D.cpp ObjectData2D.cpp
ObjectData3D.cpp ObjectData3D.cpp
PhongMaterialData.cpp) PhongMaterialData.cpp)
@ -53,6 +54,7 @@ set(MagnumTrade_HEADERS
CameraData.h CameraData.h
ImageData.h ImageData.h
LightData.h LightData.h
MeshData.h
MeshData2D.h MeshData2D.h
MeshData3D.h MeshData3D.h
MeshObjectData2D.h MeshObjectData2D.h
@ -66,6 +68,9 @@ set(MagnumTrade_HEADERS
visibility.h) visibility.h)
set(MagnumTrade_PRIVATE_HEADERS
Implementation/arrayUtilities.h)
if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/configure.h) ${CMAKE_CURRENT_BINARY_DIR}/configure.h)
@ -74,7 +79,8 @@ endif()
# Objects shared between main and test library # Objects shared between main and test library
add_library(MagnumTradeObjects OBJECT add_library(MagnumTradeObjects OBJECT
${MagnumTrade_SRCS} ${MagnumTrade_SRCS}
${MagnumTrade_HEADERS}) ${MagnumTrade_HEADERS}
${MagnumTrade_PRIVATE_HEADERS})
target_include_directories(MagnumTradeObjects PUBLIC target_include_directories(MagnumTradeObjects PUBLIC
$<TARGET_PROPERTY:Magnum,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:Magnum,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:Corrade::PluginManager,INTERFACE_INCLUDE_DIRECTORIES>) $<TARGET_PROPERTY:Corrade::PluginManager,INTERFACE_INCLUDE_DIRECTORIES>)

50
src/Magnum/Trade/Implementation/arrayUtilities.h

@ -0,0 +1,50 @@
#ifndef Magnum_Trade_Implementation_arrayUtilities_h
#define Magnum_Trade_Implementation_arrayUtilities_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <initializer_list>
#include <Corrade/Containers/Array.h>
#include "Magnum/Magnum.h"
namespace Magnum { namespace Trade { namespace Implementation {
/* Can't use InPlaceInit as that uses a custom deleters. Compared to
InPlaceInit it does an an unnecessary default-initialization of all
elements */
/** @todo isn't there some C++56 feature that would allow me to allocate
without calling constructors? */
template<class T> Containers::Array<T> initializerListToArrayWithDefaultDeleter(const std::initializer_list<T> list) {
Containers::Array<T> out{list.size()};
/* FFS why initializer list doesn't have an operator[] */
std::size_t i = 0;
for(auto&& item: list) out[i++] = item;
return out;
}
}}}
#endif

357
src/Magnum/Trade/MeshData.cpp

@ -0,0 +1,357 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <Corrade/Utility/Algorithms.h>
#include "Magnum/Math/Color.h"
#include "Magnum/Trade/Implementation/arrayUtilities.h"
namespace Magnum { namespace Trade {
MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView<const void> data) noexcept: type{type}, data{reinterpret_cast<const Containers::ArrayView<const char>&>(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 MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D<const char>& data) noexcept: name{name}, format{format}, data{data} {
/** @todo support zero / negative stride? would be hard to transfer to GL */
CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(),
"Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, );
CORRADE_ASSERT(
(name == MeshAttribute::Position &&
(format == VertexFormat::Vector2 ||
format == VertexFormat::Vector3)) ||
(name == MeshAttribute::Normal &&
(format == VertexFormat::Vector3)) ||
(name == MeshAttribute::Color &&
(format == VertexFormat::Vector3 ||
format == VertexFormat::Vector4)) ||
(name == MeshAttribute::TextureCoordinates &&
(format == VertexFormat::Vector2)) ||
isMeshAttributeCustom(name) /* can be any format */,
"Trade::MeshAttributeData:" << format << "is not a valid format for" << name, );
}
MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& indexData, const MeshIndexData& indices, Containers::Array<char>&& vertexData, Containers::Array<MeshAttributeData>&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} {
/* 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 [" << Debug::nospace << static_cast<const void*>(_indices.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(_indices.end()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast<const void*>(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(_indexData.end()) << Debug::nospace << "]", );
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() + vertexFormatSize(attribute.format) <= _vertexData.end()),
"Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast<const void*>(&attribute.data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(&attribute.data.back() + vertexFormatSize(attribute.format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast<const void*>(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast<const void*>(_vertexData.end()) << Debug::nospace << "]", );
}
#endif
}
MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& indexData, const MeshIndexData& indices, Containers::Array<char>&& vertexData, std::initializer_list<MeshAttributeData> attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {}
MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& vertexData, Containers::Array<MeshAttributeData>&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {}
MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& vertexData, const std::initializer_list<MeshAttributeData> attributes, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {}
MeshData::MeshData(const MeshPrimitive primitive, Containers::Array<char>&& 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;
}
MeshAttribute 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;
}
VertexFormat MeshData::attributeFormat(UnsignedInt id) const {
CORRADE_ASSERT(id < _attributes.size(),
"Trade::MeshData::attributeFormat(): index" << id << "out of range for" << _attributes.size() << "attributes", {});
return _attributes[id].format;
}
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<const char*>(_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 MeshAttribute name) const {
UnsignedInt count = 0;
for(const MeshAttributeData& attribute: _attributes)
if(attribute.name == name) ++count;
return count;
}
UnsignedInt MeshData::attributeFor(const MeshAttribute 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
}
VertexFormat MeshData::attributeFormat(MeshAttribute name, UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(name, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {});
return attributeFormat(attributeId);
}
std::size_t MeshData::attributeOffset(MeshAttribute 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(MeshAttribute 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<class T> void convertIndices(const Containers::ArrayView<const char> data, const Containers::ArrayView<UnsignedInt> destination) {
const auto input = Containers::arrayCast<const T>(data);
for(std::size_t i = 0; i != input.size(); ++i) destination[i] = input[i];
}
}
void MeshData::indicesInto(const Containers::ArrayView<UnsignedInt> destination) const {
CORRADE_ASSERT(isIndexed(),
"Trade::MeshData::indicesInto(): the mesh is not indexed", );
CORRADE_ASSERT(destination.size() == indexCount(), "Trade::MeshData::indicesInto(): expected a view with" << indexCount() << "elements but got" << destination.size(), );
switch(_indexType) {
case MeshIndexType::UnsignedByte: return convertIndices<UnsignedByte>(_indices, destination);
case MeshIndexType::UnsignedShort: return convertIndices<UnsignedShort>(_indices, destination);
case MeshIndexType::UnsignedInt: return convertIndices<UnsignedInt>(_indices, destination);
}
CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<UnsignedInt> MeshData::indicesAsArray() const {
/* Repeating the assert here because otherwise it would fire in
indexCount() which may be confusing */
CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indicesAsArray(): the mesh is not indexed", {});
Containers::Array<UnsignedInt> output{indexCount()};
indicesInto(output);
return output;
}
void MeshData::positions2DInto(const Containers::StridedArrayView1D<Vector2> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
/* Copy 2D positions as-is, for 3D positions ignore Z */
if(attribute.format == VertexFormat::Vector2 ||
attribute.format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector2>(attribute.data), destination);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<Vector2> MeshData::positions2DAsArray(const UnsignedInt id) const {
Containers::Array<Vector2> out{_vertexCount};
positions2DInto(out, id);
return out;
}
void MeshData::positions3DInto(const Containers::StridedArrayView1D<Vector3> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
/* For 2D positions copy the XY part to the first two components and then
fill the Z with a single value */
if(attribute.format == VertexFormat::Vector2) {
Utility::copy(Containers::arrayCast<const Vector2>(attribute.data),
Containers::arrayCast<Vector2>(destination));
constexpr Float z[1]{0.0f};
Utility::copy(
Containers::stridedArrayView(z).broadcasted<0>(_vertexCount),
Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[2]);
/* Copy 3D positions as-is */
} else if(attribute.format == VertexFormat::Vector3) {
Utility::copy(Containers::arrayCast<const Vector3>(attribute.data), destination);
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<Vector3> MeshData::positions3DAsArray(const UnsignedInt id) const {
Containers::Array<Vector3> out{_vertexCount};
positions3DInto(out, id);
return out;
}
void MeshData::normalsInto(const Containers::StridedArrayView1D<Vector3> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
if(attribute.format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector3>(attribute.data), destination);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<Vector3> MeshData::normalsAsArray(const UnsignedInt id) const {
Containers::Array<Vector3> out{_vertexCount};
normalsInto(out, id);
return out;
}
void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D<Vector2> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::TextureCoordinates, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::textureCoordinates2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
if(attribute.format == VertexFormat::Vector2)
Utility::copy(Containers::arrayCast<const Vector2>(attribute.data), destination);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<Vector2> MeshData::textureCoordinates2DAsArray(const UnsignedInt id) const {
Containers::Array<Vector2> out{_vertexCount};
textureCoordinates2DInto(out, id);
return out;
}
void MeshData::colorsInto(const Containers::StridedArrayView1D<Color4> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::Color, id);
CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", );
CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), );
const MeshAttributeData& attribute = _attributes[attributeId];
/* For three-component colors copy the RGB part to the first three
components and then fill the alpha with a single value */
if(attribute.format == VertexFormat::Vector3) {
Utility::copy(Containers::arrayCast<const Vector3>(attribute.data),
Containers::arrayCast<Vector3>(destination));
constexpr Float alpha[1]{1.0f};
Utility::copy(
Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount),
Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[3]);
/* Copy four-component colors as-is */
} else if(attribute.format == VertexFormat::Vector4) {
Utility::copy(Containers::arrayCast<const Vector4>(attribute.data),
Containers::arrayCast<Vector4>(destination));
} else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
Containers::Array<Color4> MeshData::colorsAsArray(const UnsignedInt id) const {
Containers::Array<Color4> out{_vertexCount};
colorsInto(out, id);
return out;
}
Containers::Array<char> MeshData::releaseIndexData() {
_indexType = MeshIndexType{}; /* so isIndexed() returns false */
_indices = nullptr;
return std::move(_indexData);
}
Containers::Array<char> MeshData::releaseVertexData() {
_attributes = nullptr;
return std::move(_vertexData);
}
Debug& operator<<(Debug& debug, const MeshAttribute value) {
debug << "Trade::MeshAttribute" << Debug::nospace;
if(UnsignedShort(value) >= UnsignedShort(MeshAttribute::Custom))
return debug << "::Custom(" << Debug::nospace << (UnsignedShort(value) - UnsignedShort(MeshAttribute::Custom)) << Debug::nospace << ")";
switch(value) {
/* LCOV_EXCL_START */
#define _c(value) case MeshAttribute::value: return debug << "::" << Debug::nospace << #value;
_c(Position)
_c(Normal)
_c(TextureCoordinates)
_c(Color)
#undef _c
/* LCOV_EXCL_STOP */
/* To silence compiler warning about unhandled values */
case MeshAttribute::Custom: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedShort(value)) << Debug::nospace << ")";
}
}}

793
src/Magnum/Trade/MeshData.h

@ -0,0 +1,793 @@
#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š <mosra@centrum.cz>
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::MeshAttribute, function @ref Magnum::Trade::isMeshAttributeCustom(), @ref Magnum::Trade::meshAttributeCustom()
* @m_since_latest
*/
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Mesh.h"
#include "Magnum/VertexFormat.h"
#include "Magnum/Trade/Trade.h"
#include "Magnum/Trade/visibility.h"
namespace Magnum { namespace Trade {
/**
@brief Mesh attribute name
@m_since_latest
@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat
*/
/* 16 bits because 8 bits is not enough to cover all potential per-edge,
per-face, per-instance and per-meshlet attributes */
enum class MeshAttribute: UnsignedShort {
/**
* Position. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and
* @ref Magnum::Vector3 "Vector3" for 3D. Corresponds to
* @ref Shaders::Generic::Position.
* @see @ref VertexFormat::Vector2, @ref VertexFormat::Vector3,
* @ref MeshData::positions2DAsArray(),
* @ref MeshData::positions3DAsArray()
*/
Position,
/**
* Normal. Type is usually @ref Magnum::Vector3 "Vector3". Corresponds to
* @ref Shaders::Generic::Normal.
* @see @ref VertexFormat::Vector3, @ref MeshData::normalsAsArray()
*/
Normal,
/**
* Texture coordinates. Type is usually @ref Magnum::Vector2 "Vector2" for
* 2D coordinates. Corresponds to @ref Shaders::Generic::TextureCoordinates.
* @see @ref VertexFormat::Vector2,
* @ref MeshData::textureCoordinates2DAsArray()
*/
TextureCoordinates,
/**
* Vertex color. Type is usually @ref Magnum::Vector3 "Vector3" or
* @ref Magnum::Vector4 "Vector4" (or @ref Color3 / @ref Color4).
* Corresponds to @ref Shaders::Generic::Color3 or
* @ref Shaders::Generic::Color4.
* @see @ref VertexFormat::Vector3, @ref VertexFormat::Vector4,
* @ref MeshData::colorsAsArray()
*/
Color,
/**
* This and all higher values are for importer-specific attributes. Can be
* of any type. See documentation of a particular importer for details.
* @see @ref isMeshAttributeCustom(MeshAttribute)
* @ref meshAttributeCustom(MeshAttribute),
* @ref meshAttributeCustom(UnsignedShort)
*/
Custom = 32768
};
/**
@debugoperatorenum{MeshAttribute}
@m_since_latest
*/
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MeshAttribute value);
/**
@brief Whether a mesh attribute is custom
@m_since_latest
Returns @cpp true @ce if @p name has a value larger or equal to
@ref MeshAttribute::Custom, @cpp false @ce otherwise.
@see @ref meshAttributeCustom(UnsignedShort),
@ref meshAttributeCustom(MeshAttribute)
*/
constexpr bool isMeshAttributeCustom(MeshAttribute name) {
return UnsignedShort(name) >= UnsignedShort(MeshAttribute::Custom);
}
/**
@brief Create a custom mesh attribute
@m_since_latest
Returns a custom mesh attribute with index @p id. The index is expected to be
less than the value of @ref MeshAttribute::Custom. Use
@ref meshAttributeCustom(MeshAttribute) to get the index back.
*/
/* Constexpr so it's usable for creating compile-time MeshAttributeData
instances */
constexpr MeshAttribute meshAttributeCustom(UnsignedShort id) {
return CORRADE_CONSTEXPR_ASSERT(id < UnsignedShort(MeshAttribute::Custom),
"Trade::meshAttributeCustom(): index" << id << "too large"),
MeshAttribute(UnsignedShort(MeshAttribute::Custom) + id);
}
/**
@brief Get index of a custom mesh attribute
@m_since_latest
Inverse to @ref meshAttributeCustom(UnsignedShort). Expects that the attribute
is custom.
@see @ref isMeshAttributeCustom()
*/
constexpr UnsignedShort meshAttributeCustom(MeshAttribute name) {
return CORRADE_CONSTEXPR_ASSERT(isMeshAttributeCustom(name),
"Trade::meshAttributeCustom():" << name << "is not custom"),
UnsignedShort(name) - UnsignedShort(MeshAttribute::Custom);
}
/**
@brief Mesh index data
@m_since_latest
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<const UnsignedByte>),
* @ref MeshIndexData(Containers::ArrayView<const UnsignedShort>) or
* @ref MeshIndexData(Containers::ArrayView<const UnsignedInt>)
* constructors, which infer the index type automatically.
*/
explicit MeshIndexData(MeshIndexType type, Containers::ArrayView<const void> data) noexcept;
/** @brief Construct with unsigned byte indices */
explicit MeshIndexData(Containers::ArrayView<const UnsignedByte> data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data} {}
/** @brief Construct with unsigned short indices */
explicit MeshIndexData(Containers::ArrayView<const UnsignedShort> data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data} {}
/** @brief Construct with unsigned int indices */
explicit MeshIndexData(Containers::ArrayView<const UnsignedInt> data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data} {}
private:
/* Not prefixed with _ because we use them like public in MeshData */
friend MeshData;
MeshIndexType type;
Containers::ArrayView<const char> data;
};
/**
@brief Mesh attribute data
@m_since_latest
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{}, format{}, data{} {}
/**
* @brief Type-erased constructor
* @param name Attribute name
* @param format Vertex format
* @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(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D<const char>& data) noexcept;
/**
* @brief Constructor
* @param name Attribute name
* @param data Attribute data
*
* Detects @ref VertexFormat based on @p T and calls
* @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D<const void>&).
*/
template<class T> explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D<T>& data) noexcept;
/** @overload */
template<class T> explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView<T>& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {}
private:
/* Not prefixed with _ because we use them like public in MeshData */
friend MeshData;
MeshAttribute name;
/* Here's some room for flags */
VertexFormat format;
Containers::StridedArrayView1D<const char> data;
};
/**
@brief Mesh data
@m_since_latest
Provides access to mesh vertex and index data, together with additional
information such as primitive type.
@section Trade-MeshData-usage Basic usage
The simplest usage is through the convenience functions @ref positions2DAsArray(),
@ref positions3DAsArray(), @ref normalsAsArray(), @ref textureCoordinates2DAsArray()
and @ref colorsAsArray(). 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(MeshAttribute) const:
@snippet MagnumTrade.cpp MeshData-usage
@section Trade-MeshData-usage-advanced Advanced usage
The @ref positions2DAsArray(), ... 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
*/
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<char>&& indexData, const MeshIndexData& indices, Containers::Array<char>&& vertexData, Containers::Array<MeshAttributeData>&& attributes, const void* importerState = nullptr) noexcept;
/** @overload */
/* Not noexcept because allocation happens inside */
explicit MeshData(MeshPrimitive primitive, Containers::Array<char>&& indexData, const MeshIndexData& indices, Containers::Array<char>&& vertexData, std::initializer_list<MeshAttributeData> attributes, const void* importerState = nullptr);
/**
* @brief Construct a non-indexed mesh data
* @param primitive Primitive
* @param vertexData Vertex data
* @param attributes Description of all vertex attribute data
* @param importerState Importer-specific state
*
* Same as calling @ref MeshData(MeshPrimitive, Containers::Array<char>&&, const MeshIndexData&, Containers::Array<char>&&, Containers::Array<MeshAttributeData>&&, const void*)
* with default-constructed @p indexData and @p indices arguments.
*/
explicit MeshData(MeshPrimitive primitive, Containers::Array<char>&& vertexData, Containers::Array<MeshAttributeData>&& attributes, const void* importerState = nullptr) noexcept;
/** @overload */
/* Not noexcept because allocation happens inside */
explicit MeshData(MeshPrimitive primitive, Containers::Array<char>&& vertexData, std::initializer_list<MeshAttributeData> attributes, const void* importerState = nullptr);
/**
* @brief Construct an attribute-less indexed mesh data
* @param primitive Primitive
* @param indexData Index data
* @param indices Index data description
* @param importerState Importer-specific state
*
* Same as calling @ref MeshData(MeshPrimitive, Containers::Array<char>&&, const MeshIndexData&, Containers::Array<char>&&, Containers::Array<MeshAttributeData>&&, 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<char>&& 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<const char> indexData() const & { return _indexData; }
/** @brief Taking a view to a r-value instance is not allowed */
Containers::ArrayView<const char> indexData() const && = delete;
/**
* @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 attributeFormat(), @ref attribute(),
* @ref releaseVertexData()
*/
Containers::ArrayView<const char> vertexData() const & { return _vertexData; }
/** @brief Taking a view to a r-value instance is not allowed */
Containers::ArrayView<const char> vertexData() const && = delete;
/** @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(), @ref attributeFormat()
*/
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 indicesAsArray() 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<class T> Containers::ArrayView<const T> 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(MeshAttribute) const
*/
UnsignedInt attributeCount() const { return _attributes.size(); }
/**
* @brief Attribute name
*
* The @p id is expected to be smaller than @ref attributeCount() const.
* @see @ref attributeFormat(), @ref isMeshAttributeCustom()
*/
MeshAttribute attributeName(UnsignedInt id) const;
/**
* @brief Attribute format
*
* The @p id is expected to be smaller than @ref attributeCount() const.
* You can also use @ref attributeFormat(MeshAttribute, UnsignedInt) const
* to directly get a type of given named attribute.
* @see @ref attributeName(), @ref indexType()
*/
VertexFormat attributeFormat(UnsignedInt id) const;
/**
* @brief Attribute offset
*
* Byte offset of the first element of given attribute from the
* beginning of the @ref vertexData() array, or a byte difference
* between pointers returned from @ref vertexData() and a particular
* @ref attribute(). The @p id is expected to be smaller than
* @ref attributeCount() const. You can also use
* @ref attributeOffset(MeshAttribute, 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 id is expected to be smaller
* than @ref attributeCount() const. You can also use
* @ref attributeStride(MeshAttribute, 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(MeshAttribute) const
*/
bool hasAttribute(MeshAttribute 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(MeshAttribute name) const;
/**
* @brief Format of a named attribute
*
* The @p id is expected to be smaller than
* @ref attributeCount(MeshAttribute) const.
* @see @ref attributeFormat(UnsignedInt) const
*/
VertexFormat attributeFormat(MeshAttribute 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(MeshAttribute) const.
* @see @ref attributeOffset(UnsignedInt) const
*/
std::size_t attributeOffset(MeshAttribute 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(MeshAttribute) const.
* @see @ref attributeStride(UnsignedInt) const
*/
UnsignedInt attributeStride(MeshAttribute 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 attributeFormat(UnsignedInt) const. You can also use the
* non-templated @ref positions2DAsArray(), @ref positions3DAsArray(),
* @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and
* @ref colorsAsArray() accessors to get common attributes converted to
* usual types, but note that these operations involve extra allocation
* and data conversion.
* @see @ref attribute(MeshAttribute, UnsignedInt) const
*/
template<class T> Containers::StridedArrayView1D<const T> attribute(UnsignedInt id) const;
/**
* @brief Data for given named attribute array
*
* The @p id is expected to be smaller than
* @ref attributeCount(MeshAttribute) const and @p T is expected to
* correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const.
* You can also use the non-templated @ref positions2DAsArray(),
* @ref positions3DAsArray(), @ref normalsAsArray(),
* @ref textureCoordinates2DAsArray() and @ref colorsAsArray()
* 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<class T> Containers::StridedArrayView1D<const T> attribute(MeshAttribute 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.
* @see @ref indicesInto()
*/
Containers::Array<UnsignedInt> indicesAsArray() const;
/**
* @brief Positions as 32-bit integers into a pre-allocated view
*
* Like @ref indicesAsArray(), but puts the result into @p destination
* instead of allocating a new array. Expects that @p destination is
* sized to contain exactly all data.
* @see @ref indexCount()
*/
void indicesInto(Containers::ArrayView<UnsignedInt> destination) const;
/**
* @brief Positions as 2D float vectors
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const
* with @ref MeshAttribute::Position 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.
* @see @ref positions2DInto()
*/
Containers::Array<Vector2> positions2DAsArray(UnsignedInt id = 0) const;
/**
* @brief Positions as 2D float vectors into a pre-allocated view
*
* Like @ref positions2DAsArray(), but puts the result into
* @p destination instead of allocating a new array. Expects that
* @p destination is sized to contain exactly all data.
* @see @ref vertexCount()
*/
void positions2DInto(Containers::StridedArrayView1D<Vector2> destination, UnsignedInt id = 0) const;
/**
* @brief Positions as 3D float vectors
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const
* with @ref MeshAttribute::Position 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.
* @see @ref positions3DInto()
*/
Containers::Array<Vector3> positions3DAsArray(UnsignedInt id = 0) const;
/**
* @brief Positions as 3D float vectors into a pre-allocated view
*
* Like @ref positions3DAsArray(), but puts the result into
* @p destination instead of allocating a new array. Expects that
* @p destination is sized to contain exactly all data.
* @see @ref vertexCount()
*/
void positions3DInto(Containers::StridedArrayView1D<Vector3> destination, UnsignedInt id = 0) const;
/**
* @brief Normals as 3D float vectors
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const
* with @ref MeshAttribute::Normal as the first argument. Converts the
* normal array from an arbitrary underlying type and returns it in a
* newly-allocated array.
* @see @ref normalsInto()
*/
Containers::Array<Vector3> normalsAsArray(UnsignedInt id = 0) const;
/**
* @brief Normals as 3D float vectors into a pre-allocated view
*
* Like @ref normalsAsArray(), but puts the result into @p destination
* instead of allocating a new array. Expects that @p destination is
* sized to contain exactly all data.
* @see @ref vertexCount()
*/
void normalsInto(Containers::StridedArrayView1D<Vector3> destination, UnsignedInt id = 0) const;
/**
* @brief Texture coordinates as 2D float vectors
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const
* with @ref MeshAttribute::TextureCoordinates as the first argument.
* Converts the texture coordinate array from an arbitrary underlying
* type and returns it in a newly-allocated array.
* @see @ref textureCoordinates2DInto()
*/
Containers::Array<Vector2> textureCoordinates2DAsArray(UnsignedInt id = 0) const;
/**
* @brief Texture coordinates as 2D float vectors into a pre-allocated view
*
* Like @ref textureCoordinates2DAsArray(), but puts the result into
* @p destination instead of allocating a new array. Expects that
* @p destination is sized to contain exactly all data.
* @see @ref vertexCount()
*/
void textureCoordinates2DInto(Containers::StridedArrayView1D<Vector2> destination, UnsignedInt id = 0) const;
/**
* @brief Colors as RGBA floats
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const
* with @ref MeshAttribute::Color as the first argument. Converts the
* color 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.
* @see @ref colorsInto()
*/
Containers::Array<Color4> colorsAsArray(UnsignedInt id = 0) const;
/**
* @brief Colors as RGBA floats into a pre-allocated view
*
* Like @ref colorsAsArray(), but puts the result into @p destination
* instead of allocating a new array. Expects that @p destination is
* sized to contain exactly all data.
* @see @ref vertexCount()
*/
void colorsInto(Containers::StridedArrayView1D<Color4> destination, 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<char> 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<char> releaseVertexData();
/**
* @brief Importer-specific state
*
* See @ref AbstractImporter::importerState() for more information.
*/
const void* importerState() const { return _importerState; }
private:
UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const;
UnsignedInt _vertexCount;
MeshIndexType _indexType;
MeshPrimitive _primitive;
const void* _importerState;
Containers::Array<char> _indexData, _vertexData;
Containers::Array<MeshAttributeData> _attributes;
/* MeshIndexData are "unpacked" in order to avoid excessive padding */
Containers::ArrayView<const char> _indices;
};
#if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT)
namespace Implementation {
/* LCOV_EXCL_START */
template<class T> constexpr MeshIndexType meshIndexTypeFor() {
/* C++ why there isn't an obvious way to do such a thing?! */
static_assert(sizeof(T) == 0, "unsupported index type");
return {};
}
template<> constexpr MeshIndexType meshIndexTypeFor<UnsignedByte>() { return MeshIndexType::UnsignedByte; }
template<> constexpr MeshIndexType meshIndexTypeFor<UnsignedShort>() { return MeshIndexType::UnsignedShort; }
template<> constexpr MeshIndexType meshIndexTypeFor<UnsignedInt>() { return MeshIndexType::UnsignedInt; }
template<class T> constexpr VertexFormat vertexFormatFor() {
/* C++ why there isn't an obvious way to do such a thing?! */
static_assert(sizeof(T) == 0, "unsupported attribute type");
return {};
}
#ifndef DOXYGEN_GENERATING_OUTPUT
#define _c(format) \
template<> constexpr VertexFormat vertexFormatFor<format>() { return VertexFormat::format; }
_c(Float)
_c(UnsignedByte)
_c(Byte)
_c(UnsignedShort)
_c(Short)
_c(UnsignedInt)
_c(Int)
_c(Vector2)
_c(Vector3)
_c(Vector4)
#undef _c
#endif
template<> constexpr VertexFormat vertexFormatFor<Color3>() { return VertexFormat::Vector3; }
template<> constexpr VertexFormat vertexFormatFor<Color4>() { return VertexFormat::Vector4; }
/* LCOV_EXCL_STOP */
}
#endif
template<class T> MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D<T>& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor<typename std::remove_const<T>::type>(), Containers::arrayCast<const char>(data)} {}
template<class T> Containers::ArrayView<const T> MeshData::indices() const {
CORRADE_ASSERT(isIndexed(),
"Trade::MeshData::indices(): the mesh is not indexed", {});
CORRADE_ASSERT(Implementation::meshIndexTypeFor<T>() == _indexType,
"Trade::MeshData::indices(): improper type requested for" << _indexType, nullptr);
return Containers::arrayCast<const T>(_indices);
}
template<class T> Containers::StridedArrayView1D<const T> 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::vertexFormatFor<T>() == _attributes[id].format,
"Trade::MeshData::attribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr);
return Containers::arrayCast<const T>(_attributes[id].data);
}
template<class T> Containers::StridedArrayView1D<const T> MeshData::attribute(MeshAttribute 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<T>(attributeId);
}
}}
#endif

2
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(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib)
corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) 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(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade)
corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib)
@ -56,6 +57,7 @@ corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade)
set_property(TARGET set_property(TARGET
TradeAnimationDataTest TradeAnimationDataTest
TradeMeshDataTest
APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT")
set_target_properties( set_target_properties(

969
src/Magnum/Trade/Test/MeshDataTest.cpp

@ -0,0 +1,969 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
Vladimír Vondruš <mosra@centrum.cz>
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 <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Color.h"
#include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace Trade { namespace Test { namespace {
struct MeshDataTest: TestSuite::Tester {
explicit MeshDataTest();
void customAttributeName();
void customAttributeNameTooLarge();
void customAttributeNameNotCustom();
void debugAttributeName();
void constructIndex();
void constructIndexZeroCount();
void constructIndexTypeErased();
void constructIndexTypeErasedWrongSize();
void constructAttribute();
void constructAttributeCustom();
void constructAttributeWrongFormat();
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();
void constructCopy();
void constructMove();
template<class T> void indicesAsArray();
void indicesIntoArrayInvalidSize();
template<class T> void positions2DAsArray();
void positions2DIntoArrayInvalidSize();
template<class T> void positions3DAsArray();
void positions3DIntoArrayInvalidSize();
template<class T> void normalsAsArray();
void normalsIntoArrayInvalidSize();
template<class T> void textureCoordinates2DAsArray();
void textureCoordinates2DIntoArrayInvalidSize();
template<class T> void colorsAsArray();
void colorsIntoArrayInvalidSize();
void indicesNotIndexed();
void indicesWrongType();
void attributeNotFound();
void attributeWrongType();
void releaseIndexData();
void releaseVertexData();
};
MeshDataTest::MeshDataTest() {
addTests({&MeshDataTest::customAttributeName,
&MeshDataTest::customAttributeNameTooLarge,
&MeshDataTest::customAttributeNameNotCustom,
&MeshDataTest::debugAttributeName,
&MeshDataTest::constructIndex,
&MeshDataTest::constructIndexZeroCount,
&MeshDataTest::constructIndexTypeErased,
&MeshDataTest::constructIndexTypeErasedWrongSize,
&MeshDataTest::constructAttribute,
&MeshDataTest::constructAttributeCustom,
&MeshDataTest::constructAttributeWrongFormat,
&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::constructCopy,
&MeshDataTest::constructMove,
&MeshDataTest::indicesAsArray<UnsignedByte>,
&MeshDataTest::indicesAsArray<UnsignedShort>,
&MeshDataTest::indicesAsArray<UnsignedInt>,
&MeshDataTest::indicesIntoArrayInvalidSize,
&MeshDataTest::positions2DAsArray<Vector2>,
&MeshDataTest::positions2DAsArray<Vector3>,
&MeshDataTest::positions2DIntoArrayInvalidSize,
&MeshDataTest::positions3DAsArray<Vector2>,
&MeshDataTest::positions3DAsArray<Vector3>,
&MeshDataTest::positions3DIntoArrayInvalidSize,
&MeshDataTest::normalsAsArray<Vector3>,
&MeshDataTest::normalsIntoArrayInvalidSize,
&MeshDataTest::textureCoordinates2DAsArray<Vector2>,
&MeshDataTest::textureCoordinates2DIntoArrayInvalidSize,
&MeshDataTest::colorsAsArray<Color3>,
&MeshDataTest::colorsAsArray<Color4>,
&MeshDataTest::colorsIntoArrayInvalidSize,
&MeshDataTest::indicesNotIndexed,
&MeshDataTest::indicesWrongType,
&MeshDataTest::attributeNotFound,
&MeshDataTest::attributeWrongType,
&MeshDataTest::releaseIndexData,
&MeshDataTest::releaseVertexData});
}
void MeshDataTest::customAttributeName() {
CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute::Position));
CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute(32767)));
CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute::Custom));
CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute(65535)));
CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(0)), 32768);
CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(8290)), 41058);
CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(32767)), 65535);
CORRADE_COMPARE(meshAttributeCustom(MeshAttribute::Custom), 0);
CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(41058)), 8290);
CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(65535)), 32767);
constexpr bool is = isMeshAttributeCustom(MeshAttribute(41058));
CORRADE_VERIFY(is);
constexpr MeshAttribute a = meshAttributeCustom(8290);
CORRADE_COMPARE(UnsignedShort(a), 41058);
constexpr UnsignedShort b = meshAttributeCustom(a);
CORRADE_COMPARE(b, 8290);
}
void MeshDataTest::customAttributeNameTooLarge() {
std::ostringstream out;
Error redirectError{&out};
meshAttributeCustom(32768);
CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): index 32768 too large\n");
}
void MeshDataTest::customAttributeNameNotCustom() {
std::ostringstream out;
Error redirectError{&out};
meshAttributeCustom(MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): Trade::MeshAttribute::TextureCoordinates is not custom\n");
}
void MeshDataTest::debugAttributeName() {
std::ostringstream out;
Debug{&out} << MeshAttribute::Position << meshAttributeCustom(73) << MeshAttribute(0x73);
CORRADE_COMPARE(out.str(), "Trade::MeshAttribute::Position Trade::MeshAttribute::Custom(73) Trade::MeshAttribute(0x73)\n");
}
using namespace Math::Literals;
void MeshDataTest::constructIndex() {
{
Containers::Array<char> indexData{3*1};
auto indexView = Containers::arrayCast<UnsignedByte>(indexData);
MeshIndexData indices{indexView};
MeshData data{MeshPrimitive::Points, std::move(indexData), indices};
CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedByte);
CORRADE_COMPARE(static_cast<const void*>(data.indices<UnsignedByte>().data()), indexView.data());
CORRADE_COMPARE(data.indexCount(), 3);
} {
Containers::Array<char> indexData{3*2};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
MeshIndexData indices{indexView};
MeshData data{MeshPrimitive::Points, std::move(indexData), indices};
CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(static_cast<const void*>(data.indices<UnsignedShort>().data()), indexView.data());
CORRADE_COMPARE(data.indexCount(), 3);
} {
Containers::Array<char> indexData{3*4};
auto indexView = Containers::arrayCast<UnsignedInt>(indexData);
MeshIndexData indices{indexView};
MeshData data{MeshPrimitive::Points, std::move(indexData), indices};
CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt);
CORRADE_COMPARE(static_cast<const void*>(data.indices<UnsignedInt>().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<char> indexData{3*2};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
MeshIndexData indices{MeshIndexType::UnsignedShort, indexData};
MeshData data{MeshPrimitive::Points, std::move(indexData), indices};
CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(static_cast<const void*>(data.indices<UnsignedShort>().data()), indexView.data());
CORRADE_COMPARE(data.indexCount(), 3);
}
void MeshDataTest::constructIndexTypeErasedWrongSize() {
Containers::Array<char> 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<char> positionData{3*sizeof(Vector2)};
auto positionView = Containers::arrayCast<Vector2>(positionData);
MeshAttributeData positions{MeshAttribute::Position, positionView};
MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}};
CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2);
CORRADE_COMPARE(static_cast<const void*>(data.attribute<Vector2>(0).data()),
positionView.data());
}
void MeshDataTest::constructAttributeCustom() {
Containers::Array<char> idData{3*sizeof(Short)};
auto idView = Containers::arrayCast<Short>(idData);
MeshAttributeData ids{meshAttributeCustom(13), idView};
MeshData data{MeshPrimitive::Points, std::move(idData), {ids}};
CORRADE_COMPARE(data.attributeName(0), meshAttributeCustom(13));
CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Short);
CORRADE_COMPARE(static_cast<const void*>(data.attribute<Short>(0).data()),
idView.data());
}
void MeshDataTest::constructAttributeWrongFormat() {
Containers::Array<char> positionData{3*sizeof(Vector2)};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Color, Containers::arrayCast<Vector2>(positionData)};
CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n");
}
void MeshDataTest::constructAttributeTypeErased() {
Containers::Array<char> positionData{3*sizeof(Vector3)};
auto positionView = Containers::arrayCast<Vector3>(positionData);
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast<const char>(Containers::stridedArrayView(positionView))};
MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}};
CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3);
CORRADE_COMPARE(static_cast<const void*>(data.attribute<Vector3>(0).data()),
positionView.data());
}
void MeshDataTest::constructAttributeTypeErasedWrongStride() {
Containers::Array<char> positionData{3*sizeof(Vector3)};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast<const char>(positionData)};
CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n");
}
void MeshDataTest::construct() {
struct Vertex {
Vector3 position;
Vector3 normal;
Vector2 textureCoordinate;
Short id;
};
Containers::Array<char> indexData{6*sizeof(UnsignedShort)};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
indexView[0] = 0;
indexView[1] = 1;
indexView[2] = 2;
indexView[3] = 0;
indexView[4] = 2;
indexView[5] = 1;
Containers::Array<char> vertexData{3*sizeof(Vertex)};
auto vertexView = Containers::arrayCast<Vertex>(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};
vertexView[0].id = 15;
vertexView[1].id = -374;
vertexView[2].id = 22;
int importerState;
MeshIndexData indices{indexView};
MeshAttributeData positions{MeshAttribute::Position,
Containers::StridedArrayView1D<Vector3>{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}};
MeshAttributeData normals{MeshAttribute::Normal,
Containers::StridedArrayView1D<Vector3>{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}};
MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates,
Containers::StridedArrayView1D<Vector2>{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}};
MeshAttributeData ids{meshAttributeCustom(13),
Containers::StridedArrayView1D<Short>{vertexData, &vertexView[0].id, 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, ids}, &importerState};
/* Basics */
CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles);
CORRADE_COMPARE(static_cast<const void*>(data.indexData()), indexView.data());
CORRADE_COMPARE(static_cast<const void*>(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<UnsignedShort>()[0], 0);
CORRADE_COMPARE(data.indices<UnsignedShort>()[2], 2);
CORRADE_COMPARE(data.indices<UnsignedShort>()[5], 1);
/* Attribute access by ID */
CORRADE_COMPARE(data.vertexCount(), 3);
CORRADE_COMPARE(data.attributeCount(), 5);
CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(data.attributeName(1), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(data.attributeName(2), MeshAttribute::Normal);
CORRADE_COMPARE(data.attributeName(3), MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(data.attributeName(4), meshAttributeCustom(13));
CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3);
CORRADE_COMPARE(data.attributeFormat(1), VertexFormat::Vector2);
CORRADE_COMPARE(data.attributeFormat(2), VertexFormat::Vector3);
CORRADE_COMPARE(data.attributeFormat(3), VertexFormat::Vector2);
CORRADE_COMPARE(data.attributeFormat(4), VertexFormat::Short);
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.attributeOffset(4), 2*sizeof(Vector3) + sizeof(Vector2));
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<Vector3>(0)[1], (Vector3{0.4f, 0.5f, 0.6f}));
CORRADE_COMPARE(data.attribute<Vector2>(1)[0], (Vector2{0.000f, 0.125f}));
CORRADE_COMPARE(data.attribute<Vector3>(2)[2], Vector3::zAxis());
CORRADE_COMPARE(data.attribute<Vector2>(3)[1], (Vector2{0.250f, 0.375f}));
CORRADE_COMPARE(data.attribute<Short>(4)[1], -374);
/* Attribute access by name */
CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position));
CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal));
CORRADE_VERIFY(data.hasAttribute(MeshAttribute::TextureCoordinates));
CORRADE_VERIFY(data.hasAttribute(meshAttributeCustom(13)));
CORRADE_VERIFY(!data.hasAttribute(MeshAttribute::Color));
CORRADE_VERIFY(!data.hasAttribute(meshAttributeCustom(23)));
CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position), 1);
CORRADE_COMPARE(data.attributeCount(MeshAttribute::Normal), 1);
CORRADE_COMPARE(data.attributeCount(MeshAttribute::TextureCoordinates), 2);
CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13)), 1);
CORRADE_COMPARE(data.attributeCount(MeshAttribute::Color), 0);
CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(23)), 0);
CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position),
VertexFormat::Vector3);
CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Normal),
VertexFormat::Vector3);
CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 0),
VertexFormat::Vector2);
CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 1),
VertexFormat::Vector2);
CORRADE_COMPARE(data.attributeFormat(meshAttributeCustom(13)),
VertexFormat::Short);
CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position), 0);
CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Normal), sizeof(Vector3));
CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 0), 2*sizeof(Vector3));
CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 1), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(meshAttributeCustom(13)), 2*sizeof(Vector3) + sizeof(Vector2));
CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position), sizeof(Vertex));
CORRADE_COMPARE(data.attributeStride(MeshAttribute::Normal), sizeof(Vertex));
CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex));
CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex));
CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex));
CORRADE_COMPARE(data.attribute<Vector3>(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f}));
CORRADE_COMPARE(data.attribute<Vector3>(MeshAttribute::Normal)[2], Vector3::zAxis());
CORRADE_COMPARE(data.attribute<Vector2>(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f}));
CORRADE_COMPARE(data.attribute<Vector2>(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f}));
CORRADE_COMPARE(data.attribute<Short>(meshAttributeCustom(13))[2], 22);
}
void MeshDataTest::constructIndexless() {
Containers::Array<char> vertexData{3*sizeof(Vector2)};
auto vertexView = Containers::arrayCast<Vector2>(vertexData);
vertexView[0] = {0.1f, 0.2f};
vertexView[1] = {0.4f, 0.5f};
vertexView[2] = {0.7f, 0.8f};
int importerState;
MeshAttributeData positions{MeshAttribute::Position, 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.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2);
CORRADE_COMPARE(data.attribute<Vector2>(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f}));
}
void MeshDataTest::constructIndexlessZeroVertices() {
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::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.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2);
}
void MeshDataTest::constructAttributeless() {
Containers::Array<char> indexData{6*sizeof(UnsignedInt)};
auto indexView = Containers::arrayCast<UnsignedInt>(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<UnsignedInt>()[0], 0);
CORRADE_COMPARE(data.indices<UnsignedInt>()[2], 2);
CORRADE_COMPARE(data.indices<UnsignedInt>()[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<char> indexData{6};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::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<char> indexData{6};
Containers::Array<char> vertexData{6};
std::ostringstream out;
Error redirectError{&out};
MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast<UnsignedShort>(indexData)}, std::move(vertexData), {}};
CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for an attribute-less mesh\n");
}
void MeshDataTest::constructVertexDataButNoVertices() {
Containers::Array<char> vertexData{6};
std::ostringstream out;
Error redirectError{&out};
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::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<char> indexData{reinterpret_cast<char*>(0xbadda9), 6, [](char*, std::size_t){}};
Containers::ArrayView<UnsignedShort> indexData2{reinterpret_cast<UnsignedShort*>(0xdead), 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 [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n"
"Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n");
}
void MeshDataTest::constructAttributeNotContained() {
Containers::Array<char> vertexData{reinterpret_cast<char*>(0xbadda9), 24, [](char*, std::size_t){}};
Containers::ArrayView<Vector2> vertexData2{reinterpret_cast<Vector2*>(0xdead), 3};
MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast<Vector2>(vertexData)};
MeshAttributeData positions2{MeshAttribute::Position, 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 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n"
"Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n");
}
void MeshDataTest::constructInconsitentVertexCount() {
Containers::Array<char> vertexData{24};
MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast<Vector2>(vertexData)};
MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayCast<Vector2>(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");
}
void MeshDataTest::constructCopy() {
CORRADE_VERIFY(!(std::is_constructible<MeshData, const MeshData&>{}));
CORRADE_VERIFY(!(std::is_assignable<MeshData, const MeshData&>{}));
}
void MeshDataTest::constructMove() {
Containers::Array<char> indexData{3*sizeof(UnsignedShort)};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
indexView[0] = 0;
indexView[1] = 1;
indexView[2] = 0;
Containers::Array<char> vertexData{2*sizeof(Vector2)};
auto vertexView = Containers::arrayCast<Vector2>(vertexData);
vertexView[0] = {0.1f, 0.2f};
vertexView[1] = {0.4f, 0.5f};
int importerState;
MeshIndexData indices{indexView};
MeshAttributeData positions{MeshAttribute::Position, vertexView};
MeshData a{MeshPrimitive::Triangles, std::move(indexData), indices, std::move(vertexData), {positions}, &importerState};
MeshData b{std::move(a)};
CORRADE_COMPARE(b.primitive(), MeshPrimitive::Triangles);
CORRADE_COMPARE(static_cast<const void*>(b.indexData()), indexView.data());
CORRADE_COMPARE(static_cast<const void*>(b.vertexData()), vertexView.data());
CORRADE_COMPARE(b.importerState(), &importerState);
CORRADE_VERIFY(b.isIndexed());
CORRADE_COMPARE(b.indexCount(), 3);
CORRADE_COMPARE(b.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(b.indices<UnsignedShort>()[1], 1);
CORRADE_COMPARE(b.indices<UnsignedShort>()[2], 0);
CORRADE_COMPARE(b.vertexCount(), 2);
CORRADE_COMPARE(b.attributeCount(), 1);
CORRADE_COMPARE(b.attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(b.attributeFormat(0), VertexFormat::Vector2);
CORRADE_COMPARE(b.attributeOffset(0), 0);
CORRADE_COMPARE(b.attributeStride(0), sizeof(Vector2));
CORRADE_COMPARE(b.attribute<Vector2>(0)[0], (Vector2{0.1f, 0.2f}));
CORRADE_COMPARE(b.attribute<Vector2>(0)[1], (Vector2{0.4f, 0.5f}));
MeshData c{MeshPrimitive::LineLoop, 37};
c = std::move(b);
CORRADE_COMPARE(c.primitive(), MeshPrimitive::Triangles);
CORRADE_COMPARE(static_cast<const void*>(c.indexData()), indexView.data());
CORRADE_COMPARE(static_cast<const void*>(c.vertexData()), vertexView.data());
CORRADE_COMPARE(c.importerState(), &importerState);
CORRADE_VERIFY(c.isIndexed());
CORRADE_COMPARE(c.indexCount(), 3);
CORRADE_COMPARE(c.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(c.indices<UnsignedShort>()[1], 1);
CORRADE_COMPARE(c.indices<UnsignedShort>()[2], 0);
CORRADE_COMPARE(c.vertexCount(), 2);
CORRADE_COMPARE(c.attributeCount(), 1);
CORRADE_COMPARE(c.attributeName(0), MeshAttribute::Position);
CORRADE_COMPARE(c.attributeFormat(0), VertexFormat::Vector2);
CORRADE_COMPARE(c.attributeOffset(0), 0);
CORRADE_COMPARE(c.attributeStride(0), sizeof(Vector2));
CORRADE_COMPARE(c.attribute<Vector2>(0)[0], (Vector2{0.1f, 0.2f}));
CORRADE_COMPARE(c.attribute<Vector2>(0)[1], (Vector2{0.4f, 0.5f}));
CORRADE_VERIFY(std::is_nothrow_move_constructible<MeshData>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<MeshData>::value);
}
template<class> struct NameTraits;
#define _c(format) template<> struct NameTraits<format> { \
static const char* name() { return #format; } \
};
_c(Vector2)
_c(Vector3)
_c(Color3)
_c(Color4)
#undef _c
template<class T> void MeshDataTest::indicesAsArray() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
Containers::Array<char> indexData{3*sizeof(T)};
auto indexView = Containers::arrayCast<T>(indexData);
indexView[0] = 75;
indexView[1] = 131;
indexView[2] = 240;
MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}};
CORRADE_COMPARE_AS(data.indicesAsArray(),
Containers::arrayView<UnsignedInt>({75, 131, 240}),
TestSuite::Compare::Container);
}
void MeshDataTest::indicesIntoArrayInvalidSize() {
Containers::Array<char> indexData{3*sizeof(UnsignedInt)};
MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast<UnsignedInt>(indexData)}};
std::ostringstream out;
Error redirectError{&out};
UnsignedInt destination[2];
data.indicesInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::indicesInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::positions2DAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(T)};
auto positionsView = Containers::arrayCast<T>(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{MeshAttribute::Position, positionsView}}};
CORRADE_COMPARE_AS(data.positions2DAsArray(),
Containers::arrayView<Vector2>({{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}}),
TestSuite::Compare::Container);
}
void MeshDataTest::positions2DIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector2)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast<Vector2>(vertexData)}}};
std::ostringstream out;
Error redirectError{&out};
Vector2 destination[2];
data.positions2DInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::positions2DInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::positions3DAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(T)};
auto positionsView = Containers::arrayCast<T>(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{MeshAttribute::Position, positionsView}}};
CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView<Vector3>({
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);
}
void MeshDataTest::positions3DIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector3)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast<Vector3>(vertexData)}}};
std::ostringstream out;
Error redirectError{&out};
Vector3 destination[2];
data.positions3DInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::positions3DInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::normalsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(T)};
auto normalsView = Containers::arrayCast<T>(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{MeshAttribute::Normal, normalsView}}};
CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView<Vector3>({
{2.0f, 1.0f, 0.3f}, {0.0f, -1.0f, 1.1f}, {-2.0f, 3.0f, 2.2f},
}), TestSuite::Compare::Container);
}
void MeshDataTest::normalsIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector3)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, Containers::arrayCast<Vector3>(vertexData)}}};
std::ostringstream out;
Error redirectError{&out};
Vector3 destination[2];
data.normalsInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::normalsInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::textureCoordinates2DAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(T)};
auto textureCoordinatesView = Containers::arrayCast<T>(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{MeshAttribute::TextureCoordinates, textureCoordinatesView}}};
CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), Containers::arrayView<Vector2>({
{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f},
}), TestSuite::Compare::Container);
}
void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector2)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, Containers::arrayCast<Vector2>(vertexData)}}};
std::ostringstream out;
Error redirectError{&out};
Vector2 destination[2];
data.textureCoordinates2DInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::textureCoordinates2DInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::colorsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(T)};
auto colorsView = Containers::arrayCast<T>(vertexData);
colorsView[0] = 0xff3366_rgbf;
colorsView[1] = 0x99aacc_rgbf;
colorsView[2] = 0x3377ff_rgbf;
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}};
CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView<Color4>({
0xff3366_rgbf, 0x99aacc_rgbf, 0x3377ff_rgbf
}), TestSuite::Compare::Container);
}
void MeshDataTest::colorsIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Color4)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, Containers::arrayCast<Color4>(vertexData)}}};
std::ostringstream out;
Error redirectError{&out};
Color4 destination[2];
data.colorsInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n");
}
void MeshDataTest::indicesNotIndexed() {
MeshData data{MeshPrimitive::Triangles, 37};
std::ostringstream out;
Error redirectError{&out};
data.indexCount();
data.indexType();
data.indices<UnsignedInt>();
data.indicesAsArray();
UnsignedInt a[1];
data.indicesInto(a);
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::indicesAsArray(): the mesh is not indexed\n"
"Trade::MeshData::indicesInto(): the mesh is not indexed\n");
}
void MeshDataTest::indicesWrongType() {
Containers::Array<char> indexData{sizeof(UnsignedShort)};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
indexView[0] = 57616;
MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}};
std::ostringstream out;
Error redirectError{&out};
data.indices<UnsignedByte>();
CORRADE_COMPARE(out.str(), "Trade::MeshData::indices(): improper type requested for MeshIndexType::UnsignedShort\n");
}
void MeshDataTest::attributeNotFound() {
MeshAttributeData colors1{MeshAttribute::Color, VertexFormat::Vector3, nullptr};
MeshAttributeData colors2{MeshAttribute::Color, VertexFormat::Vector4, nullptr};
MeshData data{MeshPrimitive::Points, nullptr, {colors1, colors2}};
std::ostringstream out;
Error redirectError{&out};
data.attributeName(2);
data.attributeFormat(2);
data.attributeOffset(2);
data.attributeStride(2);
data.attribute<Vector2>(2);
data.attributeFormat(MeshAttribute::Position);
data.attributeFormat(MeshAttribute::Color, 2);
data.attributeOffset(MeshAttribute::Position);
data.attributeOffset(MeshAttribute::Color, 2);
data.attributeStride(MeshAttribute::Position);
data.attributeStride(MeshAttribute::Color, 2);
data.attribute<Vector2>(MeshAttribute::Position);
data.attribute<Vector2>(MeshAttribute::Color, 2);
data.positions2DAsArray();
data.positions3DAsArray();
data.normalsAsArray();
data.textureCoordinates2DAsArray();
data.colorsAsArray(2);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::attributeName(): index 2 out of range for 2 attributes\n"
"Trade::MeshData::attributeFormat(): 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::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n"
"Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n"
"Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n"
"Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n"
"Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n"
"Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n"
"Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n"
"Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n"
"Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n"
"Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n"
"Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n"
"Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes\n"
"Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n");
}
void MeshDataTest::attributeWrongType() {
MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr};
MeshData data{MeshPrimitive::Points, nullptr, {positions}};
std::ostringstream out;
Error redirectError{&out};
data.attribute<Vector4>(MeshAttribute::Position);
CORRADE_COMPARE(out.str(), "Trade::MeshData::attribute(): improper type requested for Trade::MeshAttribute::Position of format VertexFormat::Vector3\n");
}
void MeshDataTest::releaseIndexData() {
Containers::Array<char> indexData{6};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}};
CORRADE_VERIFY(data.isIndexed());
Containers::Array<char> released = data.releaseIndexData();
CORRADE_COMPARE(static_cast<void*>(released.data()), indexView.data());
CORRADE_COMPARE(data.indexData(), nullptr);
CORRADE_VERIFY(!data.isIndexed());
}
void MeshDataTest::releaseVertexData() {
Containers::Array<char> vertexData{16};
auto vertexView = Containers::arrayCast<Vector2>(vertexData);
MeshAttributeData positions{MeshAttribute::Position, vertexView};
MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}};
CORRADE_COMPARE(data.attributeCount(), 2);
Containers::Array<char> released = data.releaseVertexData();
CORRADE_COMPARE(data.vertexData(), nullptr);
CORRADE_COMPARE(data.attributeCount(), 0);
}
}}}}
CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshDataTest)

6
src/Magnum/Trade/Trade.h

@ -65,6 +65,12 @@ typedef ImageData<2> ImageData2D;
typedef ImageData<3> ImageData3D; typedef ImageData<3> ImageData3D;
class LightData; class LightData;
enum class MeshAttribute: UnsignedShort;
class MeshIndexData;
class MeshAttributeData;
class MeshData;
class MeshData2D; class MeshData2D;
class MeshData3D; class MeshData3D;
class MeshObjectData2D; class MeshObjectData2D;

2
src/Magnum/VertexFormat.h

@ -42,6 +42,8 @@ namespace Magnum {
Like @ref PixelFormat, but for mesh attributes --- including double-precision Like @ref PixelFormat, but for mesh attributes --- including double-precision
types and matrices. types and matrices.
@see @ref Trade::MeshData, @ref Trade::MeshAttributeData,
@ref Trade::MeshAttribute
*/ */
enum class VertexFormat: UnsignedInt { enum class VertexFormat: UnsignedInt {
/* Zero reserved for an invalid type (but not being a named value) */ /* Zero reserved for an invalid type (but not being a named value) */

Loading…
Cancel
Save