diff --git a/doc/changelog.dox b/doc/changelog.dox index 7a955aba1..75474d74d 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -194,9 +194,9 @@ See also: @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 +- @ref Primitives::capsule3DSolid(), @ref Primitives::circle3DSolid(), + @ref Primitives::coneSolid(), @ref Primitives::cylinderSolid() and + @ref Primitives::uvSphereSolid() can now have tangents as well @subsubsection changelog-latest-new-scenegraph SceneGraph library diff --git a/src/Magnum/Primitives/Circle.cpp b/src/Magnum/Primitives/Circle.cpp index d4ba1ecb6..f078014b6 100644 --- a/src/Magnum/Primitives/Circle.cpp +++ b/src/Magnum/Primitives/Circle.cpp @@ -115,74 +115,88 @@ Trade::MeshData circle2DWireframe(const UnsignedInt segments) { Trade::meshAttributeDataNonOwningArray(AttributeData2D), UnsignedInt(positions.size())}; } -namespace { - -constexpr Trade::MeshAttributeData AttributeData3D[]{ - Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, - 0, 0, 2*sizeof(Vector3)}, - Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, - sizeof(Vector3), 0, 2*sizeof(Vector3)} -}; - -constexpr Trade::MeshAttributeData AttributeData3DTextureCoords[]{ - Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, - 0, 0, 2*sizeof(Vector3) + sizeof(Vector2)}, - Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, - sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)}, - Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, - 2*sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)} -}; - -constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ - Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, - 0, 0, sizeof(Vector3)} -}; - -} - Trade::MeshData circle3DSolid(const UnsignedInt segments, const Circle3DFlags flags) { CORRADE_ASSERT(segments >= 3, "Primitives::circle3DSolid(): segments must be >= 3", (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); - /* Allocate interleaved array for all vertex data */ - Containers::Array attributes; - if(flags & Circle3DFlag::TextureCoordinates) - attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3DTextureCoords); - else - attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3D); - const std::size_t stride = attributes[0].stride(); - Containers::Array vertexData{stride*(segments + 2)}; + /* Calculate attribute count and vertex size */ + std::size_t stride = sizeof(Vector3) + sizeof(Vector3); + std::size_t attributeCount = 2; + if(flags & Circle3DFlag::Tangents) { + stride += sizeof(Vector4); + ++attributeCount; + } + if(flags & Circle3DFlag::TextureCoordinates) { + stride += sizeof(Vector2); + ++attributeCount; + } + + /* Set up the layout */ + Containers::Array vertexData{Containers::NoInit, (segments + 2)*stride}; + Containers::Array attributeData{attributeCount}; + std::size_t attributeIndex = 0; + std::size_t attributeOffset = 0; - /* Fill positions */ Containers::StridedArrayView1D positions{vertexData, - reinterpret_cast(vertexData.begin()), + reinterpret_cast(vertexData.data() + attributeOffset), segments + 2, std::ptrdiff_t(stride)}; - positions[0] = {}; - /* Points on the circle. The first/last point is here twice to close the - circle properly. */ - const Rad angleIncrement(Constants::tau()/segments); - for(UnsignedInt i = 0; i != segments + 1; ++i) { - const Rad angle(Float(i)*angleIncrement); - const std::pair sincos = Math::sincos(angle); - positions[i + 1] = {sincos.second, sincos.first, 0.0f}; - } + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, positions}; + attributeOffset += sizeof(Vector3); - /* Fill normals */ Containers::StridedArrayView1D normals{vertexData, - reinterpret_cast(vertexData.begin() + sizeof(Vector3)), + reinterpret_cast(vertexData.data() + attributeOffset), segments + 2, std::ptrdiff_t(stride)}; - for(Vector3& normal: normals) normal = Vector3::zAxis(1.0f); + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, normals}; + attributeOffset += sizeof(Vector3); + + Containers::StridedArrayView1D tangents; + if(flags & Circle3DFlag::Tangents) { + tangents = Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + attributeOffset), + segments + 2, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::Tangent, tangents}; + attributeOffset += sizeof(Vector4); + } - /* Fill texture coords, if any */ + Containers::StridedArrayView1D textureCoordinates; if(flags & Circle3DFlag::TextureCoordinates) { - Containers::StridedArrayView1D textureCoords{vertexData, - reinterpret_cast(vertexData.begin() + 2*sizeof(Vector3)), - positions.size(), std::ptrdiff_t(stride)}; - for(std::size_t i = 0; i != positions.size(); ++i) - textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; + textureCoordinates = Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + attributeOffset), + segments + 2, std::ptrdiff_t(stride)}; + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, textureCoordinates}; + attributeOffset += sizeof(Vector2); } - return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes), UnsignedInt(positions.size())}; + CORRADE_INTERNAL_ASSERT(attributeIndex == attributeCount); + CORRADE_INTERNAL_ASSERT(attributeOffset == stride); + + /* Fill the data. First is center, the first/last point on the edge is + twice to close the circle properly. */ + positions[0] = {}; + normals[0] = {0.0f, 0.0f, 1.0f}; + if(flags & Circle3DFlag::Tangents) + tangents[0] = {1.0f, 0.0f, 0.0f, 1.0f}; + if(flags & Circle3DFlag::TextureCoordinates) + textureCoordinates[0] = {0.5f, 0.5f}; + const Rad angleIncrement(Constants::tau()/segments); + for(UnsignedInt i = 1; i != segments + 2; ++i) { + const Rad angle(Float(i - 1)*angleIncrement); + const std::pair sincos = Math::sincos(angle); + + positions[i] = {sincos.second, sincos.first, 0.0f}; + normals[i] = {0.0f, 0.0f, 1.0f}; + if(flags & Circle3DFlag::Tangents) + tangents[i] = {1.0f, 0.0f, 0.0f, 1.0f}; + if(flags & Circle3DFlag::TextureCoordinates) + textureCoordinates[i] = positions[i].xy()*0.5f + Vector2{0.5f}; + } + + return Trade::MeshData{MeshPrimitive::TriangleFan, + std::move(vertexData), std::move(attributeData)}; } #ifdef MAGNUM_BUILD_DEPRECATED @@ -194,6 +208,15 @@ Trade::MeshData circle3DSolid(const UnsignedInt segments, const CircleTextureCoo CORRADE_IGNORE_DEPRECATED_POP #endif +namespace { + +constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + +} + Trade::MeshData circle3DWireframe(const UnsignedInt segments) { CORRADE_ASSERT(segments >= 3, "Primitives::circle3DWireframe(): segments must be >= 3", (Trade::MeshData{MeshPrimitive::LineLoop, 0})); diff --git a/src/Magnum/Primitives/Circle.h b/src/Magnum/Primitives/Circle.h index a12c0accf..9cdc418d0 100644 --- a/src/Magnum/Primitives/Circle.h +++ b/src/Magnum/Primitives/Circle.h @@ -119,7 +119,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle2DWireframe(UnsignedInt segments) @see @ref Circle3DFlags, @ref circle3DSolid() */ enum class Circle3DFlag: 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 }; /** @@ -140,8 +148,9 @@ CORRADE_ENUMSET_OPERATORS(Circle3DFlags) Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::TriangleFan with interleaved @ref VertexFormat::Vector3 -positions, @ref VertexFormat::Vector3 normals in positive Z direction and -optional @ref VertexFormat::Vector2 texture coordinates. +positions, @ref VertexFormat::Vector3 normals in positive Z direction, optional +@ref VertexFormat::Vector4 tangents and optional @ref VertexFormat::Vector2 +texture coordinates. @image html primitives-circle3dsolid.png width=256px diff --git a/src/Magnum/Primitives/Implementation/Spheroid.h b/src/Magnum/Primitives/Implementation/Spheroid.h index c430ba9cb..da30b12fa 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.h +++ b/src/Magnum/Primitives/Implementation/Spheroid.h @@ -29,7 +29,6 @@ #include #include "Magnum/Magnum.h" -#include "Magnum/Math/Vector4.h" #include "Magnum/Trade/Trade.h" namespace Magnum { namespace Primitives { namespace Implementation { diff --git a/src/Magnum/Primitives/Test/CircleTest.cpp b/src/Magnum/Primitives/Test/CircleTest.cpp index be7145aa8..8f9d2ebd7 100644 --- a/src/Magnum/Primitives/Test/CircleTest.cpp +++ b/src/Magnum/Primitives/Test/CircleTest.cpp @@ -27,7 +27,7 @@ #include #include "Magnum/Mesh.h" -#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Vector4.h" #include "Magnum/Primitives/Circle.h" #include "Magnum/Trade/MeshData.h" @@ -37,48 +37,49 @@ struct CircleTest: TestSuite::Tester { explicit CircleTest(); void solid2D(); - void solid2DTextureCoords(); - void solid3D(); - void solid3DTextureCoords(); void wireframe2D(); void wireframe3D(); }; +constexpr struct { + const char* name; + Circle2DFlags flags; +} Solid2DData[] { + {"", Circle2DFlags{}}, + {"texture coordinates", Circle2DFlag::TextureCoordinates} +}; + +constexpr struct { + const char* name; + Circle3DFlags flags; +} Solid3DData[] { + {"", Circle3DFlags{}}, + {"texture coordinates", Circle3DFlag::TextureCoordinates}, + {"tangents", Circle3DFlag::Tangents}, + {"both", Circle3DFlag::TextureCoordinates|Circle3DFlag::Tangents} +}; + CircleTest::CircleTest() { - addTests({&CircleTest::solid2D, - &CircleTest::solid2DTextureCoords, + addInstancedTests({&CircleTest::solid2D}, + Containers::arraySize(Solid2DData)); - &CircleTest::solid3D, - &CircleTest::solid3DTextureCoords, + addInstancedTests({&CircleTest::solid3D}, + Containers::arraySize(Solid3DData)); - &CircleTest::wireframe2D, + addTests({&CircleTest::wireframe2D, &CircleTest::wireframe3D}); } void CircleTest::solid2D() { - Trade::MeshData circle = Primitives::circle2DSolid(8); + auto&& data = Solid2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_VERIFY(!circle.isIndexed()); - CORRADE_COMPARE(circle.attributeCount(), 1); - CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ - { 0.0f, 0.0f}, - { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, - { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, - {-1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, - { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, - { 1.0f, 0.0f} - }), TestSuite::Compare::Container); -} - -void CircleTest::solid2DTextureCoords() { - Trade::MeshData circle = Primitives::circle2DSolid(8, Primitives::Circle2DFlag::TextureCoordinates); + Trade::MeshData circle = Primitives::circle2DSolid(8, data.flags); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); CORRADE_VERIFY(!circle.isIndexed()); - CORRADE_COMPARE(circle.attributeCount(), 2); CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f}, { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, @@ -87,22 +88,27 @@ void CircleTest::solid2DTextureCoords() { { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, { 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.5f}, - {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, - {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, - {0.0f, 0.5f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, - {0.5f, 0.0f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, - {1.0f, 0.5f} - }), TestSuite::Compare::Container); + + if(data.flags & Circle2DFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.5f}, + {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, + {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, + {0.0f, 0.5f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, + {0.5f, 0.0f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, + {1.0f, 0.5f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!circle.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); } void CircleTest::solid3D() { - Trade::MeshData circle = Primitives::circle3DSolid(8); + auto&& data = Solid3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData circle = Primitives::circle3DSolid(8, data.flags); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); CORRADE_VERIFY(!circle.isIndexed()); - CORRADE_COMPARE(circle.attributeCount(), 2); CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, @@ -111,34 +117,22 @@ void CircleTest::solid3D() { { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, { 1.0f, 0.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.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} - }), TestSuite::Compare::Container); -} -void CircleTest::solid3DTextureCoords() { - Trade::MeshData circle = Primitives::circle3DSolid(8, Primitives::Circle3DFlag::TextureCoordinates); + if(data.flags & Circle3DFlag::Tangents) { + CORRADE_COMPARE_AS(circle.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} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!circle.hasAttribute(Trade::MeshAttribute::Tangent)); - CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_VERIFY(!circle.isIndexed()); - CORRADE_COMPARE(circle.attributeCount(), 3); - CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ - { 0.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, - {-1.0f, 0.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f} - }), TestSuite::Compare::Container); CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, @@ -151,14 +145,30 @@ void CircleTest::solid3DTextureCoords() { { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ - {0.5f, 0.5f}, - {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, - {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, - {0.0f, 0.5f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, - {0.5f, 0.0f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, - {1.0f, 0.5f} - }), TestSuite::Compare::Container); + + if(data.flags & Circle3DFlag::TextureCoordinates) { + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ + {0.5f, 0.5f}, + {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, + {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, + {0.0f, 0.5f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, + {0.5f, 0.0f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f - Constants::sqrt2()/4.0f}, + {1.0f, 0.5f} + }), TestSuite::Compare::Container); + } else CORRADE_VERIFY(!circle.hasAttribute(Trade::MeshAttribute::TextureCoordinates)); + + if(data.flags & Circle3DFlag::Tangents) { + auto tangents = circle.attribute(Trade::MeshAttribute::Tangent); + auto normals = circle.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); + } + } } void CircleTest::wireframe2D() {