diff --git a/doc/changelog.dox b/doc/changelog.dox index 75474d74d..c9f746158 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -195,8 +195,9 @@ See also: @subsubsection changelog-latest-new-primitives Primitives library - @ref Primitives::capsule3DSolid(), @ref Primitives::circle3DSolid(), - @ref Primitives::coneSolid(), @ref Primitives::cylinderSolid() and - @ref Primitives::uvSphereSolid() can now have tangents as well + @ref Primitives::coneSolid(), @ref Primitives::cylinderSolid(), + @ref Primitives::grid3DSolid() and @ref Primitives::uvSphereSolid() can now + have tangents as well @subsubsection changelog-latest-new-scenegraph SceneGraph library diff --git a/src/Magnum/Primitives/Grid.cpp b/src/Magnum/Primitives/Grid.cpp index 0fc766fdb..f02c913e3 100644 --- a/src/Magnum/Primitives/Grid.cpp +++ b/src/Magnum/Primitives/Grid.cpp @@ -56,16 +56,20 @@ Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) } } - /* Allocate interleaved array for all vertex data */ + /* Calculate attribute count and vertex size */ std::size_t stride = sizeof(Vector3); std::size_t attributeCount = 1; if(flags & GridFlag::Normals) { - ++attributeCount; stride += sizeof(Vector3); + ++attributeCount; } - if(flags & GridFlag::TextureCoordinates) { + if(flags & GridFlag::Tangents) { + stride += sizeof(Vector4); ++attributeCount; + } + if(flags & GridFlag::TextureCoordinates) { stride += sizeof(Vector2); + ++attributeCount; } Containers::Array vertexData{stride*vertexCount.product()}; Containers::Array attributes{attributeCount}; @@ -86,8 +90,7 @@ Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) positions[i++] = {(Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f}; } - /* Fill normals, if any. It's always the second attribute, right after - positions. */ + /* Fill normals and tangents, if any. Those are the same for all. */ if(flags & GridFlag::Normals) { Containers::StridedArrayView1D normals{vertexData, reinterpret_cast(vertexData.begin() + attributeOffset), @@ -97,6 +100,15 @@ Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) attributeOffset += sizeof(Vector3); for(auto&& i: normals) i = Vector3::zAxis(1.0f); } + if(flags & GridFlag::Tangents) { + Containers::StridedArrayView1D tangents{vertexData, + reinterpret_cast(vertexData.begin() + attributeOffset), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, tangents}; + attributeOffset += sizeof(Vector4); + for(auto&& i: tangents) i = {1.0f, 0.0f, 0.0f, 1.0f}; + } if(flags & GridFlag::TextureCoordinates) { Containers::StridedArrayView1D textureCoords{vertexData, @@ -109,8 +121,6 @@ Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; } - /* Not using a compile-time attribute array because there's way too many - combinations */ return Trade::MeshData{MeshPrimitive::Triangles, std::move(indexData), Trade::MeshIndexData{indices}, std::move(vertexData), std::move(attributes)}; diff --git a/src/Magnum/Primitives/Grid.h b/src/Magnum/Primitives/Grid.h index 4dba3112b..b5846b2f0 100644 --- a/src/Magnum/Primitives/Grid.h +++ b/src/Magnum/Primitives/Grid.h @@ -70,8 +70,16 @@ enum class GridFlag: UnsignedByte { * Generate normals in positive Z direction. * @m_deprecated_since_latest Use @ref GridFlag::Normals instead. */ - GenerateNormals CORRADE_DEPRECATED_ENUM("use Normals instead") = Normals + GenerateNormals CORRADE_DEPRECATED_ENUM("use Normals instead") = Normals, #endif + + /** + * 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 << 2 }; /** @@ -89,8 +97,8 @@ CORRADE_ENUMSET_OPERATORS(GridFlags) 2x2 grid in the XY plane with normals in positive Z direction. @ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 positions, optional -@ref VertexFormat::Vector3 normals and @ref VertexFormat::Vector2 texture -coordinates. +@ref VertexFormat::Vector3 normals, optional @ref VertexFormat::Vector4 +tangents and optional @ref VertexFormat::Vector2 texture coordinates. @image html primitives-grid3dsolid.png width=256px diff --git a/src/Magnum/Primitives/Test/GridTest.cpp b/src/Magnum/Primitives/Test/GridTest.cpp index 1db60af6c..8696fc7f9 100644 --- a/src/Magnum/Primitives/Test/GridTest.cpp +++ b/src/Magnum/Primitives/Test/GridTest.cpp @@ -26,7 +26,7 @@ #include #include -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/Grid.h" #include "Magnum/Trade/MeshData.h" @@ -35,103 +35,37 @@ namespace Magnum { namespace Primitives { namespace Test { namespace { struct GridTest: TestSuite::Tester { explicit GridTest(); - void solid3DWithoutAnything(); - void solid3DWithNormalsAndTextureCoords(); + void solid3D(); void wireframe3D(); }; -GridTest::GridTest() { - addTests({&GridTest::solid3DWithoutAnything, - &GridTest::solid3DWithNormalsAndTextureCoords, - &GridTest::wireframe3D}); -} - -void GridTest::solid3DWithoutAnything() { - Trade::MeshData grid = grid3DSolid({5, 3}, {}); - - CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); - CORRADE_VERIFY(grid.isIndexed()); - CORRADE_COMPARE(grid.attributeCount(), 1); - - CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ - {-1.0f, -1.0f, 0.0f}, - {-0.666667f, -1.0f, 0.0f}, - {-0.333333f, -1.0f, 0.0f}, - {0.0f, -1.0f, 0.0f}, - {0.333333f, -1.0f, 0.0f}, - {0.666667f, -1.0f, 0.0f}, - {1.0f, -1.0f, 0.0f}, - - {-1.0f, -0.5f, 0.0f}, - {-0.666667f, -0.5f, 0.0f}, - {-0.333333f, -0.5f, 0.0f}, - {0.0f, -0.5f, 0.0f}, - {0.333333f, -0.5f, 0.0f}, - {0.666667f, -0.5f, 0.0f}, - {1.0f, -0.5f, 0.0f}, - - {-1.0f, 0.0f, 0.0f}, - {-0.666667f, 0.0f, 0.0f}, - {-0.333333f, 0.0f, 0.0f}, - {0.0f, 0.0f, 0.0f}, - {0.333333f, 0.0f, 0.0f}, - {0.666667f, 0.0f, 0.0f}, - {1.0f, 0.0f, 0.0f}, - - {-1.0f, 0.5f, 0.0f}, - {-0.666667f, 0.5f, 0.0f}, - {-0.333333f, 0.5f, 0.0f}, - {0.0f, 0.5f, 0.0f}, - {0.333333f, 0.5f, 0.0f}, - {0.666667f, 0.5f, 0.0f}, - {1.0f, 0.5f, 0.0f}, - - {-1.0f, 1.0f, 0.0f}, - {-0.666667f, 1.0f, 0.0f}, - {-0.333333f, 1.0f, 0.0f}, - {0.0f, 1.0f, 0.0f}, - {0.333333f, 1.0f, 0.0f}, - {0.666667f, 1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f} - }), TestSuite::Compare::Container); - - CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ - 0, 8, 7, 0, 1, 8, - 1, 9, 8, 1, 2, 9, - 2, 10, 9, 2, 3, 10, - 3, 11, 10, 3, 4, 11, - 4, 12, 11, 4, 5, 12, - 5, 13, 12, 5, 6, 13, - - 7, 15, 14, 7, 8, 15, - 8, 16, 15, 8, 9, 16, - 9, 17, 16, 9, 10, 17, - 10, 18, 17, 10, 11, 18, - 11, 19, 18, 11, 12, 19, - 12, 20, 19, 12, 13, 20, +constexpr struct { + const char* name; + GridFlags flags; +} Solid3DData[] { + {"", GridFlags{}}, + {"normals", GridFlag::Normals}, + {"texture coordinates", GridFlag::TextureCoordinates}, + {"tangents", GridFlag::Tangents}, + {"normals + tangents", GridFlag::Normals|GridFlag::Tangents}, + {"all", GridFlag::TextureCoordinates|GridFlag::Normals|GridFlag::Tangents} +}; - 14, 22, 21, 14, 15, 22, - 15, 23, 22, 15, 16, 23, - 16, 24, 23, 16, 17, 24, - 17, 25, 24, 17, 18, 25, - 18, 26, 25, 18, 19, 26, - 19, 27, 26, 19, 20, 27, +GridTest::GridTest() { + addInstancedTests({&GridTest::solid3D}, + Containers::arraySize(Solid3DData)); - 21, 29, 28, 21, 22, 29, - 22, 30, 29, 22, 23, 30, - 23, 31, 30, 23, 24, 31, - 24, 32, 31, 24, 25, 32, - 25, 33, 32, 25, 26, 33, - 26, 34, 33, 26, 27, 34 - }), TestSuite::Compare::Container); + addTests({&GridTest::wireframe3D}); } -void GridTest::solid3DWithNormalsAndTextureCoords() { - Trade::MeshData grid = grid3DSolid({5, 3}, GridFlag::Normals|GridFlag::TextureCoordinates); +void GridTest::solid3D() { + auto&& data = Solid3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData grid = grid3DSolid({5, 3}, data.flags); CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(grid.isIndexed()); - CORRADE_COMPARE(grid.attributeCount(), 3); CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, @@ -175,89 +109,150 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - }), TestSuite::Compare::Container); - - CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.0f, 0.0f}, - {0.166667f, 0.0f}, - {0.333333f, 0.0f}, - {0.5f, 0.0f}, - {0.666667f, 0.0f}, - {0.833333f, 0.0f}, - {1.0f, 0.0f}, - - {0.0f, 0.25f}, - {0.166667f, 0.25f}, - {0.333333f, 0.25f}, - {0.5f, 0.25f}, - {0.666667f, 0.25f}, - {0.833333f, 0.25f}, - {1.0f, 0.25f}, - - {0.0f, 0.5f}, - {0.166667f, 0.5f}, - {0.333333f, 0.5f}, - {0.5f, 0.5f}, - {0.666667f, 0.5f}, - {0.833333f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 0.75f}, - {0.166667f, 0.75f}, - {0.333333f, 0.75f}, - {0.5f, 0.75f}, - {0.666667f, 0.75f}, - {0.833333f, 0.75f}, - {1.0f, 0.75f}, - - {0.0f, 1.0f}, - {0.166667f, 1.0f}, - {0.333333f, 1.0f}, - {0.5f, 1.0f}, - {0.666667f, 1.0f}, - {0.833333f, 1.0f}, - {1.0f, 1.0f} - }), TestSuite::Compare::Container); + if(data.flags & GridFlag::Tangents) { + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Tangent), Containers::arrayView({ + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!grid.hasAttribute(Trade::MeshAttribute::Tangent)); + + if(data.flags & GridFlag::Normals) { + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!grid.hasAttribute(Trade::MeshAttribute::Normal)); + + if(data.flags & GridFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.0f, 0.0f}, + {0.166667f, 0.0f}, + {0.333333f, 0.0f}, + {0.5f, 0.0f}, + {0.666667f, 0.0f}, + {0.833333f, 0.0f}, + {1.0f, 0.0f}, + + {0.0f, 0.25f}, + {0.166667f, 0.25f}, + {0.333333f, 0.25f}, + {0.5f, 0.25f}, + {0.666667f, 0.25f}, + {0.833333f, 0.25f}, + {1.0f, 0.25f}, + + {0.0f, 0.5f}, + {0.166667f, 0.5f}, + {0.333333f, 0.5f}, + {0.5f, 0.5f}, + {0.666667f, 0.5f}, + {0.833333f, 0.5f}, + {1.0f, 0.5f}, + + {0.0f, 0.75f}, + {0.166667f, 0.75f}, + {0.333333f, 0.75f}, + {0.5f, 0.75f}, + {0.666667f, 0.75f}, + {0.833333f, 0.75f}, + {1.0f, 0.75f}, + + {0.0f, 1.0f}, + {0.166667f, 1.0f}, + {0.333333f, 1.0f}, + {0.5f, 1.0f}, + {0.666667f, 1.0f}, + {0.833333f, 1.0f}, + {1.0f, 1.0f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!grid.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags >= (GridFlag::Tangents|GridFlag::Normals)) { + auto tangents = grid.attribute(Trade::MeshAttribute::Tangent); + auto normals = grid.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(grid.indices(), Containers::arrayView({ 0, 8, 7, 0, 1, 8,