mirror of https://github.com/mosra/magnum.git
Browse Source
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
11 changed files with 2261 additions and 4 deletions
@ -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 |
||||||
@ -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 << ")"; |
||||||
|
} |
||||||
|
|
||||||
|
}} |
||||||
@ -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 |
||||||
@ -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) |
||||||
Loading…
Reference in new issue