Browse Source

Primitives: tangent support in the 3D circle primitive.

pull/430/head
Vladimír Vondruš 6 years ago
parent
commit
1b776e13bc
  1. 6
      doc/changelog.dox
  2. 131
      src/Magnum/Primitives/Circle.cpp
  3. 15
      src/Magnum/Primitives/Circle.h
  4. 1
      src/Magnum/Primitives/Implementation/Spheroid.h
  5. 150
      src/Magnum/Primitives/Test/CircleTest.cpp

6
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

131
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<Trade::MeshAttributeData> attributes;
if(flags & Circle3DFlag::TextureCoordinates)
attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3DTextureCoords);
else
attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3D);
const std::size_t stride = attributes[0].stride();
Containers::Array<char> 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<char> vertexData{Containers::NoInit, (segments + 2)*stride};
Containers::Array<Trade::MeshAttributeData> attributeData{attributeCount};
std::size_t attributeIndex = 0;
std::size_t attributeOffset = 0;
/* Fill positions */
Containers::StridedArrayView1D<Vector3> positions{vertexData,
reinterpret_cast<Vector3*>(vertexData.begin()),
reinterpret_cast<Vector3*>(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<Float, Float> 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<Vector3> normals{vertexData,
reinterpret_cast<Vector3*>(vertexData.begin() + sizeof(Vector3)),
reinterpret_cast<Vector3*>(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<Vector4> tangents;
if(flags & Circle3DFlag::Tangents) {
tangents = Containers::StridedArrayView1D<Vector4>{vertexData,
reinterpret_cast<Vector4*>(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<Vector2> textureCoordinates;
if(flags & Circle3DFlag::TextureCoordinates) {
Containers::StridedArrayView1D<Vector2> textureCoords{vertexData,
reinterpret_cast<Vector2*>(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<Vector2>{vertexData,
reinterpret_cast<Vector2*>(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<Float, Float> 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}));

15
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

1
src/Magnum/Primitives/Implementation/Spheroid.h

@ -29,7 +29,6 @@
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Magnum.h"
#include "Magnum/Math/Vector4.h"
#include "Magnum/Trade/Trade.h"
namespace Magnum { namespace Primitives { namespace Implementation {

150
src/Magnum/Primitives/Test/CircleTest.cpp

@ -27,7 +27,7 @@
#include <Corrade/TestSuite/Compare/Container.h>
#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<Vector2>(Trade::MeshAttribute::Position), Containers::arrayView<Vector2>({
{ 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<Vector2>(Trade::MeshAttribute::Position), Containers::arrayView<Vector2>({
{ 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<Vector2>(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView<Vector2>({
{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<Vector2>(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView<Vector2>({
{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<Vector3>(Trade::MeshAttribute::Position), Containers::arrayView<Vector3>({
{ 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<Vector3>(Trade::MeshAttribute::Normal), Containers::arrayView<Vector3>({
{ 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<Vector4>(Trade::MeshAttribute::Tangent), Containers::arrayView<Vector4>({
{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<Vector3>(Trade::MeshAttribute::Position), Containers::arrayView<Vector3>({
{ 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<Vector3>(Trade::MeshAttribute::Normal), Containers::arrayView<Vector3>({
{ 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<Vector2>(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView<Vector2>({
{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<Vector2>(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView<Vector2>({
{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<Vector4>(Trade::MeshAttribute::Tangent);
auto normals = circle.attribute<Vector3>(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() {

Loading…
Cancel
Save