From a099a57464be42e151472442dd62feea52ba0189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 21 Mar 2020 12:51:48 +0100 Subject: [PATCH] Primitives: implement tangent generation in spheroid primitives. --- doc/changelog.dox | 6 + src/Magnum/Primitives/Capsule.h | 16 +- src/Magnum/Primitives/Cone.cpp | 2 +- src/Magnum/Primitives/Cone.h | 19 +- src/Magnum/Primitives/Cylinder.cpp | 2 +- src/Magnum/Primitives/Cylinder.h | 17 +- .../Primitives/Implementation/Spheroid.cpp | 72 ++++-- .../Primitives/Implementation/Spheroid.h | 11 +- src/Magnum/Primitives/Test/CapsuleTest.cpp | 142 ++++++++---- src/Magnum/Primitives/Test/ConeTest.cpp | 196 +++++++++++----- src/Magnum/Primitives/Test/CylinderTest.cpp | 217 +++++++++++++----- src/Magnum/Primitives/Test/UVSphereTest.cpp | 97 ++++++-- src/Magnum/Primitives/UVSphere.h | 17 +- 13 files changed, 600 insertions(+), 214 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index dceb661e30..7a955aba17 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -192,6 +192,12 @@ See also: bootstrap project for using Magnum together with gtkmm (see [mosra/magnum-bootstrap#24](https://github.com/mosra/magnum-bootstrap/pull/24)) +@subsubsection changelog-latest-new-primitives Primitives library + +- @ref Primitives::capsule3DSolid(), @ref Primitives::coneSolid(), + @ref Primitives::cylinderSolid() and @ref Primitives::uvSphereSolid() can + now have tangents as well + @subsubsection changelog-latest-new-scenegraph SceneGraph library - All 2D transformation implementations that support rotation now have a diff --git a/src/Magnum/Primitives/Capsule.h b/src/Magnum/Primitives/Capsule.h index 578558c838..58024796d9 100644 --- a/src/Magnum/Primitives/Capsule.h +++ b/src/Magnum/Primitives/Capsule.h @@ -63,7 +63,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule2DWireframe(UnsignedInt hemisphe @see @ref CapsuleFlags, @ref capsule3DSolid() */ enum class CapsuleFlag: UnsignedByte { - TextureCoordinates = 1 << 0 /**< Generate texture coordinates */ + TextureCoordinates = 1 << 0, /**< Generate texture coordinates */ + + /** + * Generate four-component tangents. The last component can be used to + * reconstruct a bitangent as described in the documentation of + * @ref Trade::MeshAttribute::Tangent. + * @m_since_latest + */ + Tangents = 1 << 1 }; /** @@ -91,9 +99,9 @@ CORRADE_ENUMSET_OPERATORS(CapsuleFlags) Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 -normals and optional @ref VertexFormat::Vector2 texture coordinates. If texture -coordinates are generated, vertices of one segment are duplicated for texture -wrapping. +normals, optional @ref VertexFormat::Vector4 tangents and optional +@ref VertexFormat::Vector2 texture coordinates. If texture coordinates are +generated, vertices of one segment are duplicated for texture wrapping. @image html primitives-capsule3dsolid.png width=256px diff --git a/src/Magnum/Primitives/Cone.cpp b/src/Magnum/Primitives/Cone.cpp index bc5bf01657..f8b48be622 100644 --- a/src/Magnum/Primitives/Cone.cpp +++ b/src/Magnum/Primitives/Cone.cpp @@ -54,7 +54,7 @@ Trade::MeshData coneSolid(const UnsignedInt rings, const UnsignedInt segments, c /* Faces. Account for the extra vertices for caps and texture coords. */ if(flags & ConeFlag::CapEnd) cone.bottomFaceRing(); - if(flags >= (ConeFlag::CapEnd|ConeFlag::TextureCoordinates)) + if(flags & ConeFlag::CapEnd && flags & (ConeFlag::Tangents|ConeFlag::TextureCoordinates)) cone.faceRings(rings, 2 + segments); else if(flags & ConeFlag::CapEnd) cone.faceRings(rings, 1 + segments); diff --git a/src/Magnum/Primitives/Cone.h b/src/Magnum/Primitives/Cone.h index 9d29303c02..614356a97b 100644 --- a/src/Magnum/Primitives/Cone.h +++ b/src/Magnum/Primitives/Cone.h @@ -59,7 +59,15 @@ enum class ConeFlag: UnsignedByte { GenerateTextureCoords CORRADE_DEPRECATED_ENUM("use TextureCoordinates instead") = TextureCoordinates, #endif - CapEnd = 1 << 1 /**< Cap end */ + /** + * Generate four-component tangents. The last component can be used to + * reconstruct a bitangent as described in the documentation of + * @ref Trade::MeshAttribute::Tangent. + * @m_since_latest + */ + Tangents = 1 << 1, + + CapEnd = 1 << 2 /**< Cap end */ }; /** @@ -82,10 +90,11 @@ CORRADE_ENUMSET_OPERATORS(ConeFlags) Cone along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 -positions, @ref VertexFormat::Vector3 normals and optional -@ref VertexFormat::Vector2 texture coordinates. Note that in order to have -properly smooth normals over the whole area, the tip consists of -@cpp segments*2 @ce vertices instead of just one. +positions, @ref VertexFormat::Vector3 normals, optional +@ref VertexFormat::Vector4 tangents and optional @ref VertexFormat::Vector2 +texture coordinates. Note that in order to have properly smooth normals over +the whole area, the tip consists of @cpp segments*2 @ce vertices instead of +just one. @image html primitives-conesolid.png width=256px diff --git a/src/Magnum/Primitives/Cylinder.cpp b/src/Magnum/Primitives/Cylinder.cpp index ffea1ba96c..7ba783c46a 100644 --- a/src/Magnum/Primitives/Cylinder.cpp +++ b/src/Magnum/Primitives/Cylinder.cpp @@ -60,7 +60,7 @@ Trade::MeshData cylinderSolid(const UnsignedInt rings, const UnsignedInt segment /* Faces. Account for the extra vertices for caps and texture coords. */ if(flags & CylinderFlag::CapEnds) cylinder.bottomFaceRing(); - if(flags >= (CylinderFlag::CapEnds|CylinderFlag::TextureCoordinates)) + if(flags & CylinderFlag::CapEnds && flags & (CylinderFlag::TextureCoordinates|CylinderFlag::Tangents)) cylinder.faceRings(rings, 2 + segments); else if(flags & CylinderFlag::CapEnds) cylinder.faceRings(rings, 1 + segments); diff --git a/src/Magnum/Primitives/Cylinder.h b/src/Magnum/Primitives/Cylinder.h index feca7769c3..abc3058313 100644 --- a/src/Magnum/Primitives/Cylinder.h +++ b/src/Magnum/Primitives/Cylinder.h @@ -58,7 +58,15 @@ enum class CylinderFlag: UnsignedByte { GenerateTextureCoords CORRADE_DEPRECATED_ENUM("use TextureCoordinates instead") = TextureCoordinates, #endif - CapEnds = 1 << 1 /**< Cap ends */ + /** + * Generate four-component tangents. The last component can be used to + * reconstruct a bitangent as described in the documentation of + * @ref Trade::MeshAttribute::Tangent. + * @m_since_latest + */ + Tangents = 1 << 1, + + CapEnds = 1 << 2 /**< Cap ends */ }; /** @@ -82,9 +90,10 @@ CORRADE_ENUMSET_OPERATORS(CylinderFlags) Cylinder along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 normals, -optional @ref VertexFormat::Vector2 texture coordinates and optional capped -ends. If texture coordinates are generated, vertices of one segment are -duplicated for texture wrapping. +optional @ref VertexFormat::Vector4 tangents, optional +@ref VertexFormat::Vector2 texture coordinates and optional capped ends. If +texture coordinates are generated, vertices of one segment are duplicated for +texture wrapping. @image html primitives-cylindersolid.png width=256px diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index 09c030aeac..9863fbdd7e 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -36,6 +36,12 @@ namespace Magnum { namespace Primitives { namespace Implementation { Spheroid::Spheroid(UnsignedInt segments, Flags flags): _segments(segments), _flags{flags}, _stride{sizeof(Vector3) + sizeof(Vector3)}, _attributeCount{2} { + if(_flags & Flag::Tangents) { + _tangentOffset = _stride; + _stride += sizeof(Vector4); + ++_attributeCount; + } else _tangentOffset = ~std::size_t{}; + if(_flags & Flag::TextureCoordinates) { _textureCoordinateOffset = _stride; _stride += sizeof(Vector2); @@ -43,14 +49,22 @@ Spheroid::Spheroid(UnsignedInt segments, Flags flags): _segments(segments), _fla } else _textureCoordinateOffset = ~std::size_t{}; } -void Spheroid::append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords) { +void Spheroid::append(const Vector3& position, const Vector3& normal) { Containers::arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(&position, 1))); Containers::arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(&normal, 1))); + if(_flags & Flag::Tangents) { + /** @todo make arrayGrow() a public API instead of this */ + constexpr const char empty[sizeof(Vector4)]{}; + Containers::arrayAppend(_vertexData, + Containers::arrayView(empty)); + } if(_flags & Flag::TextureCoordinates) { + /** @todo make arrayGrow() a public API instead */ + constexpr const char empty[sizeof(Vector2)]{}; Containers::arrayAppend(_vertexData, - Containers::arrayCast(Containers::arrayView(&textureCoords, 1))); + Containers::arrayView(empty)); } } @@ -62,12 +76,18 @@ Vector3 Spheroid::lastVertexNormal(const std::size_t offsetFromEnd) { return Containers::arrayCast(_vertexData.slice(_vertexData.size() - _stride*offsetFromEnd + sizeof(Vector3)))[0]; } +Vector4& Spheroid::lastVertexTangent(const std::size_t offsetFromEnd) { + return Containers::arrayCast(_vertexData.slice(_vertexData.size() - _stride*offsetFromEnd + _tangentOffset))[0]; +} + Vector2& Spheroid::lastVertexTextureCoords(const std::size_t offsetFromEnd) { return Containers::arrayCast(_vertexData.slice(_vertexData.size() - _stride*offsetFromEnd + _textureCoordinateOffset))[0]; } void Spheroid::capVertex(Float y, Float normalY, Float textureCoordsV) { append({0.0f, y, 0.0f}, {0.0f, normalY, 0.0f}); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = {normalY > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f, 1.0f}; if(_flags & Flag::TextureCoordinates) lastVertexTextureCoords(1) = {0.5f, textureCoordsV}; } @@ -87,15 +107,20 @@ void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad start append({x*segmentSinCos.first, centerY+y, z*segmentSinCos.second}, {x*segmentSinCos.first, y, z*segmentSinCos.second}); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = {segmentSinCos.second, 0.0f, -segmentSinCos.first, 1.0f}; if(_flags & Flag::TextureCoordinates) lastVertexTextureCoords(1) = {j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}; } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(_flags & Flag::TextureCoordinates) { - append(lastVertexPosition(_segments), lastVertexNormal(_segments), - {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); + if(_flags & (Flag::TextureCoordinates|Flag::Tangents)) { + append(lastVertexPosition(_segments), lastVertexNormal(_segments)); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = lastVertexTangent(_segments + 1); + if(_flags & Flag::TextureCoordinates) + lastVertexTextureCoords(1) = {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}; } } } @@ -112,14 +137,19 @@ void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, append({base.x()*segmentSinCos.first, base.y(), base.x()*segmentSinCos.second}, {baseNormal.x()*segmentSinCos.first, baseNormal.y(), baseNormal.x()*segmentSinCos.second}); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = {segmentSinCos.second, 0.0f, -segmentSinCos.first, 1.0f}; if(_flags & Flag::TextureCoordinates) lastVertexTextureCoords(1) = {j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}; } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(_flags & Flag::TextureCoordinates) { - append(lastVertexPosition(_segments), lastVertexNormal(_segments), - {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); + if(_flags & (Flag::TextureCoordinates|Flag::Tangents)) { + append(lastVertexPosition(_segments), lastVertexNormal(_segments)); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = lastVertexTangent(_segments + 1); + if(_flags & Flag::TextureCoordinates) + lastVertexTextureCoords(1) = {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}; } base += increment; @@ -133,7 +163,7 @@ void Spheroid::bottomFaceRing() { 0u, /* Top right vertex */ - (j != _segments-1 || _flags & Flag::TextureCoordinates) ? + (j != _segments-1 || _flags & (Flag::TextureCoordinates|Flag::Tangents)) ? j+2 : 1, /* Top left vertex */ @@ -143,12 +173,12 @@ void Spheroid::bottomFaceRing() { } void Spheroid::faceRings(UnsignedInt count, UnsignedInt offset) { - const UnsignedInt vertexSegments = _segments + (_flags & Flag::TextureCoordinates ? 1 : 0); + const UnsignedInt vertexSegments = _segments + (_flags & (Flag::TextureCoordinates|Flag::Tangents) ? 1 : 0); for(UnsignedInt i = 0; i != count; ++i) { for(UnsignedInt j = 0; j != _segments; ++j) { const UnsignedInt bottomLeft = i*vertexSegments+j+offset; - const UnsignedInt bottomRight = ((j != _segments-1 || _flags & Flag::TextureCoordinates) ? + const UnsignedInt bottomRight = ((j != _segments-1 || _flags & (Flag::TextureCoordinates|Flag::Tangents)) ? i*vertexSegments+j+1+offset : i*_segments+offset); const UnsignedInt topLeft = bottomLeft+vertexSegments; const UnsignedInt topRight = bottomRight+vertexSegments; @@ -166,7 +196,7 @@ void Spheroid::faceRings(UnsignedInt count, UnsignedInt offset) { } void Spheroid::topFaceRing() { - const UnsignedInt vertexSegments = _segments + (_flags & Flag::TextureCoordinates ? 1 : 0); + const UnsignedInt vertexSegments = _segments + (_flags & (Flag::TextureCoordinates|Flag::Tangents) ? 1 : 0); const UnsignedInt vertexCount = _vertexData.size()/_stride; @@ -176,7 +206,7 @@ void Spheroid::topFaceRing() { vertexCount - vertexSegments + j - 1, /* Bottom right vertex */ - (j != _segments-1 || _flags & Flag::TextureCoordinates) ? + (j != _segments-1 || _flags & (Flag::TextureCoordinates|Flag::Tangents)) ? vertexCount - vertexSegments + j : vertexCount - _segments - 1, /* Top vertex */ @@ -193,14 +223,20 @@ void Spheroid::capVertexRing(Float y, Float textureCoordsV, const Vector3& norma const std::pair segmentSinCos = Math::sincos(segmentAngle); append({segmentSinCos.first, y, segmentSinCos.second}, normal); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = {segmentSinCos.second, 0.0f, -segmentSinCos.first, 1.0f}; if(_flags & Flag::TextureCoordinates) lastVertexTextureCoords(1) = {i*1.0f/_segments, textureCoordsV}; } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(_flags & Flag::TextureCoordinates) { - append(lastVertexPosition(_segments), normal, {1.0f, textureCoordsV}); + if(_flags & (Flag::TextureCoordinates|Flag::Tangents)) { + append(lastVertexPosition(_segments), normal); + if(_flags & Flag::Tangents) + lastVertexTangent(1) = lastVertexTangent(_segments + 1); + if(_flags & Flag::TextureCoordinates) + lastVertexTextureCoords(1) = {1.0f, textureCoordsV}; } } @@ -220,6 +256,12 @@ Trade::MeshData Spheroid::finalize() { _vertexData.data() + sizeof(Vector3), _vertexData.size()/_stride, std::ptrdiff_t(_stride))}; + if(_flags & Flag::Tangents) + attributes[attributeOffset++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Tangent, VertexFormat::Vector4, + Containers::stridedArrayView(_vertexData, + _vertexData.data() + _tangentOffset, + _vertexData.size()/_stride, std::ptrdiff_t(_stride))}; if(_flags & Flag::TextureCoordinates) attributes[attributeOffset++] = Trade::MeshAttributeData{ Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, diff --git a/src/Magnum/Primitives/Implementation/Spheroid.h b/src/Magnum/Primitives/Implementation/Spheroid.h index 79253bf778..c430ba9cb1 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.h +++ b/src/Magnum/Primitives/Implementation/Spheroid.h @@ -29,7 +29,7 @@ #include #include "Magnum/Magnum.h" -#include "Magnum/Math/Vector2.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Trade/Trade.h" namespace Magnum { namespace Primitives { namespace Implementation { @@ -37,7 +37,8 @@ namespace Magnum { namespace Primitives { namespace Implementation { class Spheroid { public: enum class Flag: UnsignedByte { - TextureCoordinates = 1 << 0 + TextureCoordinates = 1 << 0, + Tangents = 1 << 1 }; typedef Containers::EnumSet Flags; @@ -58,15 +59,17 @@ class Spheroid { UnsignedInt _segments; Flags _flags; std::size_t _stride; - std::size_t _textureCoordinateOffset; + std::size_t _textureCoordinateOffset, + _tangentOffset; std::size_t _attributeCount; Containers::Array _indexData; Containers::Array _vertexData; - void append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords = {}); + void append(const Vector3& position, const Vector3& normal); Vector3 lastVertexPosition(std::size_t offsetFromEnd); Vector3 lastVertexNormal(std::size_t offsetFromEnd); + Vector4& lastVertexTangent(std::size_t offsetFromEnd); Vector2& lastVertexTextureCoords(std::size_t offsetFromEnd); }; diff --git a/src/Magnum/Primitives/Test/CapsuleTest.cpp b/src/Magnum/Primitives/Test/CapsuleTest.cpp index 0c2464bc8b..9ed38a93f4 100644 --- a/src/Magnum/Primitives/Test/CapsuleTest.cpp +++ b/src/Magnum/Primitives/Test/CapsuleTest.cpp @@ -26,7 +26,7 @@ #include #include -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Primitives/Capsule.h" @@ -37,16 +37,28 @@ struct CapsuleTest: TestSuite::Tester { void wireframe2D(); - void solid3DWithoutTextureCoords(); - void solid3DWithTextureCoords(); + void solid3DWithoutTextureCoordinates(); + void solid3DWithTextureCoordinatesOrTangents(); void wireframe3D(); }; +constexpr struct { + const char* name; + CapsuleFlags flags; +} TextureCoordinatesOrTangentsData[] { + {"texture coordinates", CapsuleFlag::TextureCoordinates}, + {"tangents", CapsuleFlag::Tangents}, + {"both", CapsuleFlag::TextureCoordinates|CapsuleFlag::Tangents} +}; + CapsuleTest::CapsuleTest() { addTests({&CapsuleTest::wireframe2D, - &CapsuleTest::solid3DWithoutTextureCoords, - &CapsuleTest::solid3DWithTextureCoords, - &CapsuleTest::wireframe3D}); + &CapsuleTest::solid3DWithoutTextureCoordinates}); + + addInstancedTests({&CapsuleTest::solid3DWithTextureCoordinatesOrTangents}, + Containers::arraySize(TextureCoordinatesOrTangentsData)); + + addTests({&CapsuleTest::wireframe3D}); } void CapsuleTest::wireframe2D() { @@ -97,7 +109,7 @@ void CapsuleTest::wireframe2D() { }), TestSuite::Compare::Container); } -void CapsuleTest::solid3DWithoutTextureCoords() { +void CapsuleTest::solid3DWithoutTextureCoordinates() { Trade::MeshData capsule = capsule3DSolid(2, 4, 3, 0.5f); CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); @@ -184,12 +196,14 @@ void CapsuleTest::solid3DWithoutTextureCoords() { }), TestSuite::Compare::Container); } -void CapsuleTest::solid3DWithTextureCoords() { - Trade::MeshData capsule = capsule3DSolid(2, 2, 3, 0.5f, CapsuleFlag::TextureCoordinates); +void CapsuleTest::solid3DWithTextureCoordinatesOrTangents() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData capsule = capsule3DSolid(2, 2, 3, 0.5f, data.flags); CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(capsule.isIndexed()); - CORRADE_COMPARE(capsule.attributeCount(), 3); CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, @@ -222,36 +236,84 @@ void CapsuleTest::solid3DWithTextureCoords() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.0f}, - - {0.0f, 0.166667f}, - {0.333333f, 0.166667f}, - {0.666667f, 0.166667f}, - {1.0f, 0.166667f}, - - {0.0f, 0.333333f}, - {0.333333f, 0.333333f}, - {0.666667f, 0.333333f}, - {1.0f, 0.333333f}, - - {0.0f, 0.5f}, - {0.333333f, 0.5f}, - {0.666667f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 0.666667f}, - {0.333333f, 0.666667f}, - {0.666667f, 0.666667f}, - {1.0f, 0.666667f}, - - {0.0f, 0.833333f}, - {0.333333f, 0.833333f}, - {0.666667f, 0.833333f}, - {1.0f, 0.833333f}, - - {0.5f, 1.0f} - }), TestSuite::Compare::Container); + if(data.flags & CapsuleFlag::Tangents) { + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {-1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!capsule.hasAttribute(Trade::MeshAttribute::Tangent)); + + if(data.flags & CapsuleFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.0f}, + + {0.0f, 0.166667f}, + {0.333333f, 0.166667f}, + {0.666667f, 0.166667f}, + {1.0f, 0.166667f}, + + {0.0f, 0.333333f}, + {0.333333f, 0.333333f}, + {0.666667f, 0.333333f}, + {1.0f, 0.333333f}, + + {0.0f, 0.5f}, + {0.333333f, 0.5f}, + {0.666667f, 0.5f}, + {1.0f, 0.5f}, + + {0.0f, 0.666667f}, + {0.333333f, 0.666667f}, + {0.666667f, 0.666667f}, + {1.0f, 0.666667f}, + + {0.0f, 0.833333f}, + {0.333333f, 0.833333f}, + {0.666667f, 0.833333f}, + {1.0f, 0.833333f}, + + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!capsule.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & CapsuleFlag::Tangents) { + auto tangents = capsule.attribute(Trade::MeshAttribute::Tangent); + auto normals = capsule.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, diff --git a/src/Magnum/Primitives/Test/ConeTest.cpp b/src/Magnum/Primitives/Test/ConeTest.cpp index f98027cd3f..0baf0c8de8 100644 --- a/src/Magnum/Primitives/Test/ConeTest.cpp +++ b/src/Magnum/Primitives/Test/ConeTest.cpp @@ -26,7 +26,7 @@ #include #include -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/Cone.h" #include "Magnum/Trade/MeshData.h" @@ -37,17 +37,30 @@ struct ConeTest: TestSuite::Tester { void solidWithoutAnything(); void solidWithCaps(); - void solidWithTextureCoords(); - void solidWithTextureCoordsAndCaps(); + void solidWithTextureCoordinatesOrTangents(); + void solidWithTextureCoordinatesOrTangentsAndCaps(); void wireframe(); }; +constexpr struct { + const char* name; + ConeFlags flags; +} TextureCoordinatesOrTangentsData[] { + {"texture coordinates", ConeFlag::TextureCoordinates}, + {"tangents", ConeFlag::Tangents}, + {"both", ConeFlag::TextureCoordinates|ConeFlag::Tangents} +}; + ConeTest::ConeTest() { addTests({&ConeTest::solidWithoutAnything, - &ConeTest::solidWithCaps, - &ConeTest::solidWithTextureCoords, - &ConeTest::solidWithTextureCoordsAndCaps, - &ConeTest::wireframe}); + &ConeTest::solidWithCaps}); + + addInstancedTests({ + &ConeTest::solidWithTextureCoordinatesOrTangents, + &ConeTest::solidWithTextureCoordinatesOrTangentsAndCaps}, + Containers::arraySize(TextureCoordinatesOrTangentsData)); + + addTests({&ConeTest::wireframe}); } void ConeTest::solidWithoutAnything() { @@ -149,12 +162,14 @@ void ConeTest::solidWithCaps() { }), TestSuite::Compare::Container); } -void ConeTest::solidWithTextureCoords() { - Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::TextureCoordinates); +void ConeTest::solidWithTextureCoordinatesOrTangents() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData cone = coneSolid(2, 3, 1.0f, data.flags); CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(cone.isIndexed()); - CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ @@ -175,6 +190,25 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} /* 11 */ }), TestSuite::Compare::Container); + if(data.flags & ConeFlag::Tangents) { + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 0 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 1 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 2 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 3 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 4 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 5 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 6 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 7 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 8 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 9 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 10 */ + {1.0f, 0.0f, 0.0f, 1.0f} /* 11 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cone.hasAttribute(Trade::MeshAttribute::Tangent)); + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.447214f, 0.894427f}, /* 0 */ {0.774597f, 0.447214f, -0.447214f}, /* 1 */ @@ -192,22 +226,37 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 0.447214f, 0.894427f} /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.0f, 0.0f}, /* 0 */ - {0.333333f, 0.0f}, /* 1 */ - {0.666667f, 0.0f}, /* 2 */ - {1.0f, 0.0f}, /* 3 */ - - {0.0f, 0.5f}, /* 4 */ - {0.333333f, 0.5f}, /* 5 */ - {0.666667f, 0.5f}, /* 6 */ - {1.0f, 0.5f}, /* 7 */ - - {0.0f, 1.0f}, /* 8 */ - {0.333333f, 1.0f}, /* 9 */ - {0.666667f, 1.0f}, /* 10 */ - {1.0f, 1.0f}, /* 11 */ - }), TestSuite::Compare::Container); + if(data.flags & ConeFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.0f, 0.0f}, /* 0 */ + {0.333333f, 0.0f}, /* 1 */ + {0.666667f, 0.0f}, /* 2 */ + {1.0f, 0.0f}, /* 3 */ + + {0.0f, 0.5f}, /* 4 */ + {0.333333f, 0.5f}, /* 5 */ + {0.666667f, 0.5f}, /* 6 */ + {1.0f, 0.5f}, /* 7 */ + + {0.0f, 1.0f}, /* 8 */ + {0.333333f, 1.0f}, /* 9 */ + {0.666667f, 1.0f}, /* 10 */ + {1.0f, 1.0f}, /* 11 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cone.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & ConeFlag::Tangents) { + auto tangents = cone.attribute(Trade::MeshAttribute::Tangent); + auto normals = cone.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } /* Each ring has an extra vertex for texture coords */ CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ @@ -216,12 +265,14 @@ void ConeTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); } -void ConeTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::TextureCoordinates|ConeFlag::CapEnd); +void ConeTest::solidWithTextureCoordinatesOrTangentsAndCaps() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData cone = coneSolid(2, 3, 1.0f, data.flags|ConeFlag::CapEnd); CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(cone.isIndexed()); - CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ @@ -249,6 +300,32 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f} /* 16 */ }), TestSuite::Compare::Container); + if(data.flags & ConeFlag::Tangents) { + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* 0 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 1 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 2 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 3 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 4 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 5 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 6 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 7 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 8 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 9 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 10 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 11 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 12 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 13 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 14 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 15 */ + {1.0f, 0.0f, 0.0f, 1.0f} /* 16 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cone.hasAttribute(Trade::MeshAttribute::Tangent)); + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ @@ -273,29 +350,44 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 0.447214f, 0.894427f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.0f}, /* 0 */ - - {0.0f, 0.333333f}, /* 1 */ - {0.333333f, 0.333333f}, /* 2 */ - {0.666667f, 0.333333f}, /* 3 */ - {1.0f, 0.333333f}, /* 4 */ - - {0.0f, 0.333333f}, /* 5 */ - {0.333333f, 0.333333f}, /* 6 */ - {0.666667f, 0.333333f}, /* 7 */ - {1.0f, 0.333333f}, /* 8 */ - - {0.0f, 0.666667f}, /* 9 */ - {0.333333f, 0.666667f}, /* 10 */ - {0.666667f, 0.666667f}, /* 11 */ - {1.0f, 0.666667f}, /* 12 */ - - {0.0f, 1.0f}, /* 13 */ - {0.333333f, 1.0f}, /* 14 */ - {0.666667f, 1.0f}, /* 15 */ - {1.0f, 1.0f}, /* 16 */ - }), TestSuite::Compare::Container); + if(data.flags & ConeFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.0f}, /* 0 */ + + {0.0f, 0.333333f}, /* 1 */ + {0.333333f, 0.333333f}, /* 2 */ + {0.666667f, 0.333333f}, /* 3 */ + {1.0f, 0.333333f}, /* 4 */ + + {0.0f, 0.333333f}, /* 5 */ + {0.333333f, 0.333333f}, /* 6 */ + {0.666667f, 0.333333f}, /* 7 */ + {1.0f, 0.333333f}, /* 8 */ + + {0.0f, 0.666667f}, /* 9 */ + {0.333333f, 0.666667f}, /* 10 */ + {0.666667f, 0.666667f}, /* 11 */ + {1.0f, 0.666667f}, /* 12 */ + + {0.0f, 1.0f}, /* 13 */ + {0.333333f, 1.0f}, /* 14 */ + {0.666667f, 1.0f}, /* 15 */ + {1.0f, 1.0f}, /* 16 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cone.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & ConeFlag::Tangents) { + auto tangents = cone.attribute(Trade::MeshAttribute::Tangent); + auto normals = cone.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ diff --git a/src/Magnum/Primitives/Test/CylinderTest.cpp b/src/Magnum/Primitives/Test/CylinderTest.cpp index 031318cdc7..c3b0f91b73 100644 --- a/src/Magnum/Primitives/Test/CylinderTest.cpp +++ b/src/Magnum/Primitives/Test/CylinderTest.cpp @@ -26,7 +26,7 @@ #include #include -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/Cylinder.h" #include "Magnum/Trade/MeshData.h" @@ -37,17 +37,30 @@ struct CylinderTest: TestSuite::Tester { void solidWithoutAnything(); void solidWithCaps(); - void solidWithTextureCoords(); - void solidWithTextureCoordsAndCaps(); + void solidWithTextureCoordinatesOrTangents(); + void solidWithTextureCoordinatesOrTangentsAndCaps(); void wireframe(); }; +constexpr struct { + const char* name; + CylinderFlags flags; +} TextureCoordinatesOrTangentsData[] { + {"texture coordinates", CylinderFlag::TextureCoordinates}, + {"tangents", CylinderFlag::Tangents}, + {"both", CylinderFlag::TextureCoordinates|CylinderFlag::Tangents} +}; + CylinderTest::CylinderTest() { addTests({&CylinderTest::solidWithoutAnything, - &CylinderTest::solidWithCaps, - &CylinderTest::solidWithTextureCoords, - &CylinderTest::solidWithTextureCoordsAndCaps, - &CylinderTest::wireframe}); + &CylinderTest::solidWithCaps}); + + addInstancedTests({ + &CylinderTest::solidWithTextureCoordinatesOrTangents, + &CylinderTest::solidWithTextureCoordinatesOrTangentsAndCaps}, + Containers::arraySize(TextureCoordinatesOrTangentsData)); + + addTests({&CylinderTest::wireframe}); } void CylinderTest::solidWithoutAnything() { @@ -162,12 +175,14 @@ void CylinderTest::solidWithCaps() { }), TestSuite::Compare::Container); } -void CylinderTest::solidWithTextureCoords() { - Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::TextureCoordinates); +void CylinderTest::solidWithTextureCoordinatesOrTangents() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, data.flags); CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(cylinder.isIndexed()); - CORRADE_COMPARE(cylinder.attributeCount(), 3); /* First vertex of each ring duplicated because it has different texture coordinates */ @@ -188,6 +203,25 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 1.5f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); + if(data.flags & CylinderFlag::Tangents) { + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 0 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 1 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 2 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 3 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 4 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 5 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 6 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 7 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 8 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 9 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 10 */ + {1.0f, 0.0f, 0.0f, 1.0f} /* 11 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cylinder.hasAttribute(Trade::MeshAttribute::Tangent)); + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, /* 0 */ {0.866025f, 0.0f, -0.5f}, /* 1 */ @@ -205,22 +239,37 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 0.0f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.0f, 0.0f}, /* 0 */ - {0.333333f, 0.0f}, /* 1 */ - {0.666667f, 0.0f}, /* 2 */ - {1.0f, 0.0f}, /* 3 */ - - {0.0f, 0.5f}, /* 4 */ - {0.333333f, 0.5f}, /* 5 */ - {0.666667f, 0.5f}, /* 6 */ - {1.0f, 0.5f}, /* 7 */ - - {0.0f, 1.0f}, /* 8 */ - {0.333333f, 1.0f}, /* 9 */ - {0.666667f, 1.0f}, /* 10 */ - {1.0f, 1.0f}, /* 11 */ - }), TestSuite::Compare::Container); + if(data.flags & CylinderFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.0f, 0.0f}, /* 0 */ + {0.333333f, 0.0f}, /* 1 */ + {0.666667f, 0.0f}, /* 2 */ + {1.0f, 0.0f}, /* 3 */ + + {0.0f, 0.5f}, /* 4 */ + {0.333333f, 0.5f}, /* 5 */ + {0.666667f, 0.5f}, /* 6 */ + {1.0f, 0.5f}, /* 7 */ + + {0.0f, 1.0f}, /* 8 */ + {0.333333f, 1.0f}, /* 9 */ + {0.666667f, 1.0f}, /* 10 */ + {1.0f, 1.0f}, /* 11 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cylinder.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & CylinderFlag::Tangents) { + auto tangents = cylinder.attribute(Trade::MeshAttribute::Tangent); + auto normals = cylinder.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } /* Each ring has an extra vertex for texture coords */ CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ @@ -229,12 +278,14 @@ void CylinderTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); } -void CylinderTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::TextureCoordinates|CylinderFlag::CapEnds); +void CylinderTest::solidWithTextureCoordinatesOrTangentsAndCaps() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, data.flags|CylinderFlag::CapEnds); CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(cylinder.isIndexed()); - CORRADE_COMPARE(cylinder.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ @@ -269,6 +320,39 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.5f, 0.0f} /* 21 */ }), TestSuite::Compare::Container); + if(data.flags & CylinderFlag::Tangents) { + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {-1.0f, 0.0f, 0.0f, 1.0f}, /* 0 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 1 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 2 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 3 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 4 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 5 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 6 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 7 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 8 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 9 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 10 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 11 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 12 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 13 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 14 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 15 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 16 */ + + {1.0f, 0.0f, 0.0f, 1.0f}, /* 17 */ + {-0.5f, 0.0f, -0.866025f, 1.0f}, /* 18 */ + {-0.5f, 0.0f, 0.866025f, 1.0f}, /* 19 */ + {1.0f, 0.0f, 0.0f, 1.0f}, /* 20 */ + + {1.0f, 0.0f, 0.0f, 1.0f} /* 21 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cylinder.hasAttribute(Trade::MeshAttribute::Tangent)); + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ @@ -300,36 +384,51 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f}, /* 21 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.0f}, /* 0 */ - - {0.0f, 0.2f}, /* 1 */ - {0.333333f, 0.2f}, /* 2 */ - {0.666667f, 0.2f}, /* 3 */ - {1.0f, 0.2f}, /* 4 */ - - {0.0f, 0.2f}, /* 5 */ - {0.333333f, 0.2f}, /* 6 */ - {0.666667f, 0.2f}, /* 7 */ - {1.0f, 0.2f}, /* 8 */ - - {0.0f, 0.5f}, /* 9 */ - {0.333333f, 0.5f}, /* 10 */ - {0.666667f, 0.5f}, /* 11 */ - {1.0f, 0.5f}, /* 12 */ - - {0.0f, 0.8f}, /* 13 */ - {0.333333f, 0.8f}, /* 14 */ - {0.666667f, 0.8f}, /* 15 */ - {1.0f, 0.8f}, /* 16 */ - - {0.0f, 0.8f}, /* 17 */ - {0.333333f, 0.8f}, /* 18 */ - {0.666667f, 0.8f}, /* 19 */ - {1.0f, 0.8f}, /* 20 */ - - {0.5f, 1.0f} /* 21 */ - }), TestSuite::Compare::Container); + if(data.flags & CylinderFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.0f}, /* 0 */ + + {0.0f, 0.2f}, /* 1 */ + {0.333333f, 0.2f}, /* 2 */ + {0.666667f, 0.2f}, /* 3 */ + {1.0f, 0.2f}, /* 4 */ + + {0.0f, 0.2f}, /* 5 */ + {0.333333f, 0.2f}, /* 6 */ + {0.666667f, 0.2f}, /* 7 */ + {1.0f, 0.2f}, /* 8 */ + + {0.0f, 0.5f}, /* 9 */ + {0.333333f, 0.5f}, /* 10 */ + {0.666667f, 0.5f}, /* 11 */ + {1.0f, 0.5f}, /* 12 */ + + {0.0f, 0.8f}, /* 13 */ + {0.333333f, 0.8f}, /* 14 */ + {0.666667f, 0.8f}, /* 15 */ + {1.0f, 0.8f}, /* 16 */ + + {0.0f, 0.8f}, /* 17 */ + {0.333333f, 0.8f}, /* 18 */ + {0.666667f, 0.8f}, /* 19 */ + {1.0f, 0.8f}, /* 20 */ + + {0.5f, 1.0f} /* 21 */ + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!cylinder.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & CylinderFlag::Tangents) { + auto tangents = cylinder.attribute(Trade::MeshAttribute::Tangent); + auto normals = cylinder.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ diff --git a/src/Magnum/Primitives/Test/UVSphereTest.cpp b/src/Magnum/Primitives/Test/UVSphereTest.cpp index fa1344de68..d4dd76f38c 100644 --- a/src/Magnum/Primitives/Test/UVSphereTest.cpp +++ b/src/Magnum/Primitives/Test/UVSphereTest.cpp @@ -26,7 +26,7 @@ #include #include -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Trade/MeshData.h" @@ -35,18 +35,30 @@ namespace Magnum { namespace Primitives { namespace Test { namespace { struct UVSphereTest: TestSuite::Tester { explicit UVSphereTest(); - void solidWithoutTextureCoords(); - void solidWithTextureCoords(); + void solidWithoutTextureCoordinates(); + void solidWithTextureCoordinatesOrTangents(); void wireframe(); }; +constexpr struct { + const char* name; + UVSphereFlags flags; +} TextureCoordinatesOrTangentsData[] { + {"texture coordinates", UVSphereFlag::TextureCoordinates}, + {"tangents", UVSphereFlag::Tangents}, + {"both", UVSphereFlag::TextureCoordinates|UVSphereFlag::Tangents} +}; + UVSphereTest::UVSphereTest() { - addTests({&UVSphereTest::solidWithoutTextureCoords, - &UVSphereTest::solidWithTextureCoords, - &UVSphereTest::wireframe}); + addTests({&UVSphereTest::solidWithoutTextureCoordinates}); + + addInstancedTests({&UVSphereTest::solidWithTextureCoordinatesOrTangents}, + Containers::arraySize(TextureCoordinatesOrTangentsData)); + + addTests({&UVSphereTest::wireframe}); } -void UVSphereTest::solidWithoutTextureCoords() { +void UVSphereTest::solidWithoutTextureCoordinates() { Trade::MeshData sphere = uvSphereSolid(3, 3); CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); @@ -88,12 +100,14 @@ void UVSphereTest::solidWithoutTextureCoords() { }), TestSuite::Compare::Container); } -void UVSphereTest::solidWithTextureCoords() { - Trade::MeshData sphere = uvSphereSolid(3, 3, UVSphereFlag::TextureCoordinates); +void UVSphereTest::solidWithTextureCoordinatesOrTangents() { + auto&& data = TextureCoordinatesOrTangentsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData sphere = uvSphereSolid(3, 3, data.flags); CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(sphere.isIndexed()); - CORRADE_COMPARE(sphere.attributeCount(), 3); CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, @@ -111,21 +125,54 @@ void UVSphereTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.0f}, - - {0.0f, 0.333333f}, - {0.333333f, 0.333333f}, - {0.666667f, 0.333333f}, - {1.0f, 0.333333f}, - - {0.0f, 0.666667f}, - {0.333333f, 0.666667f}, - {0.666667f, 0.666667f}, - {1.0f, 0.666667f}, - - {0.5f, 1.0f} - }), TestSuite::Compare::Container); + if(data.flags & UVSphereFlag::Tangents) { + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {-1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {-0.5f, 0.0f, -0.866025f, 1.0f}, + {-0.5f, 0.0f, 0.866025f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!sphere.hasAttribute(Trade::MeshAttribute::Tangent)); + + if(data.flags & UVSphereFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.0f}, + + {0.0f, 0.333333f}, + {0.333333f, 0.333333f}, + {0.666667f, 0.333333f}, + {1.0f, 0.333333f}, + + {0.0f, 0.666667f}, + {0.333333f, 0.666667f}, + {0.666667f, 0.666667f}, + {1.0f, 0.666667f}, + + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!sphere.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & UVSphereFlag::Tangents) { + auto tangents = sphere.attribute(Trade::MeshAttribute::Tangent); + auto normals = sphere.attribute(Trade::MeshAttribute::Normal); + for(std::size_t i = 0; i != tangents.size(); ++i) { + CORRADE_ITERATION(i); + CORRADE_ITERATION(tangents[i]); + CORRADE_ITERATION(normals[i]); + CORRADE_VERIFY(tangents[i].xyz().isNormalized()); + CORRADE_VERIFY(normals[i].isNormalized()); + CORRADE_COMPARE(Math::dot(tangents[i].xyz(), normals[i]), 0.0f); + } + } CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, diff --git a/src/Magnum/Primitives/UVSphere.h b/src/Magnum/Primitives/UVSphere.h index 4e4cc4898d..a2b972bc92 100644 --- a/src/Magnum/Primitives/UVSphere.h +++ b/src/Magnum/Primitives/UVSphere.h @@ -44,7 +44,15 @@ namespace Magnum { namespace Primitives { @see @ref UVSphereFlags, @ref uvSphereSolid() */ enum class UVSphereFlag: UnsignedByte { - TextureCoordinates = 1 << 0 /**< Generate texture coordinates */ + TextureCoordinates = 1 << 0, /**< Generate texture coordinates */ + + /** + * Generate four-component tangents. The last component can be used to + * reconstruct a bitangent as described in the documentation of + * @ref Trade::MeshAttribute::Tangent. + * @m_since_latest + */ + Tangents = 1 << 1 }; /** @@ -68,9 +76,10 @@ CORRADE_ENUMSET_OPERATORS(UVSphereFlags) Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 -positions, @ref VertexFormat::Vector3 normals and optional -@ref VertexFormat::Vector2 texture coordinates. If texture coordinates are -generated, vertices of one segment are duplicated for texture wrapping. +positions, @ref VertexFormat::Vector3 normals, optional +@ref VertexFormat::Vector4 tangents and @ref VertexFormat::Vector2 texture +coordinates. If texture coordinates are generated, vertices of one segment are +duplicated for texture wrapping. @image html primitives-uvspheresolid.png width=256px