Skip to content

Commit

Permalink
Primitives: tangents in the plane primitive.
Browse files Browse the repository at this point in the history
This turned the primitive from being fully defined at compile time to
being mostly dynamically allocated. Keeping just the positions+normals
case defined at compile time, and splitting the function into two
overloads so the extra code can be DCEd when people call the function
with no flags.
  • Loading branch information
mosra committed Mar 22, 2020
1 parent 1e04dbc commit 2a88a88
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 58 deletions.
4 changes: 2 additions & 2 deletions doc/changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ See also:

- @ref Primitives::capsule3DSolid(), @ref Primitives::circle3DSolid(),
@ref Primitives::coneSolid(), @ref Primitives::cylinderSolid(),
@ref Primitives::grid3DSolid() and @ref Primitives::uvSphereSolid() can now
have tangents as well
@ref Primitives::grid3DSolid(), @ref Primitives::planeSolid() and
@ref Primitives::uvSphereSolid() can now have tangents as well

@subsubsection changelog-latest-new-scenegraph SceneGraph library

Expand Down
110 changes: 79 additions & 31 deletions src/Magnum/Primitives/Plane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include "Plane.h"

#include "Magnum/Mesh.h"
#include "Magnum/Math/Vector3.h"
#include "Magnum/Math/Vector4.h"
#include "Magnum/Trade/MeshData.h"

namespace Magnum { namespace Primitives {
Expand All @@ -42,16 +42,6 @@ constexpr struct VertexSolid {
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
{{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}
};
constexpr struct VertexSolidTextureCoords {
Vector3 position;
Vector3 normal;
Vector2 textureCoords;
} VerticesSolidTextureCoords[] {
{VerticesSolid[0].position, VerticesSolid[0].normal, {1.0f, 0.0f}},
{VerticesSolid[1].position, VerticesSolid[1].normal, {1.0f, 1.0f}},
{VerticesSolid[2].position, VerticesSolid[2].normal, {0.0f, 0.0f}},
{VerticesSolid[3].position, VerticesSolid[3].normal, {0.0f, 1.0f}}
};
constexpr Trade::MeshAttributeData AttributesSolid[]{
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position,
Expand All @@ -60,34 +50,92 @@ constexpr Trade::MeshAttributeData AttributesSolid[]{
Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].normal,
Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}
};
constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
Containers::stridedArrayView(VerticesSolidTextureCoords,
&VerticesSolidTextureCoords[0].position,
Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal,
Containers::stridedArrayView(VerticesSolidTextureCoords,
&VerticesSolidTextureCoords[0].normal,
Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates,
Containers::stridedArrayView(VerticesSolidTextureCoords,
&VerticesSolidTextureCoords[0].textureCoords,
Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))},
};

}

Trade::MeshData planeSolid(const PlaneFlags flags) {
if(flags & PlaneFlag::TextureCoordinates)
return Trade::MeshData{MeshPrimitive::TriangleStrip,
{}, VerticesSolidTextureCoords,
Trade::meshAttributeDataNonOwningArray(AttributesSolidTextureCoords)};

Trade::MeshData planeSolid() {
return Trade::MeshData{MeshPrimitive::TriangleStrip,
{}, VerticesSolid,
Trade::meshAttributeDataNonOwningArray(AttributesSolid)};
}

Trade::MeshData planeSolid(const PlaneFlags flags) {
/* Return the compile-time data if nothing extra is requested */
if(!flags) return planeSolid();

/* Calculate attribute count and vertex size */
std::size_t stride = sizeof(Vector3) + sizeof(Vector3);
std::size_t attributeCount = 2;
if(flags & PlaneFlag::Tangents) {
stride += sizeof(Vector4);
++attributeCount;
}
if(flags & PlaneFlag::TextureCoordinates) {
stride += sizeof(Vector2);
++attributeCount;
}

/* Set up the layout */
Containers::Array<char> vertexData{Containers::NoInit, 4*stride};
Containers::Array<Trade::MeshAttributeData> attributeData{attributeCount};
std::size_t attributeIndex = 0;
std::size_t attributeOffset = 0;

Containers::StridedArrayView1D<Vector3> positions{vertexData,
reinterpret_cast<Vector3*>(vertexData.data() + attributeOffset),
4, std::ptrdiff_t(stride)};
attributeData[attributeIndex++] = Trade::MeshAttributeData{
Trade::MeshAttribute::Position, positions};
attributeOffset += sizeof(Vector3);

Containers::StridedArrayView1D<Vector3> normals{vertexData,
reinterpret_cast<Vector3*>(vertexData.data() + sizeof(Vector3)),
4, std::ptrdiff_t(stride)};
attributeData[attributeIndex++] = Trade::MeshAttributeData{
Trade::MeshAttribute::Normal, normals};
attributeOffset += sizeof(Vector3);

Containers::StridedArrayView1D<Vector4> tangents;
if(flags & PlaneFlag::Tangents) {
tangents = Containers::StridedArrayView1D<Vector4>{vertexData,
reinterpret_cast<Vector4*>(vertexData.data() + attributeOffset),
4, std::ptrdiff_t(stride)};
attributeData[attributeIndex++] = Trade::MeshAttributeData{
Trade::MeshAttribute::Tangent, tangents};
attributeOffset += sizeof(Vector4);
}

Containers::StridedArrayView1D<Vector2> textureCoordinates;
if(flags & PlaneFlag::TextureCoordinates) {
textureCoordinates = Containers::StridedArrayView1D<Vector2>{vertexData,
reinterpret_cast<Vector2*>(vertexData.data() + attributeOffset),
4, std::ptrdiff_t(stride)};
attributeData[attributeIndex++] = Trade::MeshAttributeData{
Trade::MeshAttribute::TextureCoordinates, textureCoordinates};
attributeOffset += sizeof(Vector2);
}

CORRADE_INTERNAL_ASSERT(attributeIndex == attributeCount);
CORRADE_INTERNAL_ASSERT(attributeOffset == stride);

/* Fill the data */
for(std::size_t i = 0; i != 4; ++i) {
positions[i] = VerticesSolid[i].position;
normals[i] = VerticesSolid[i].normal;
if(flags & PlaneFlag::Tangents)
tangents[i] = {1.0f, 0.0f, 0.0f, 1.0f};
}
if(flags & PlaneFlag::TextureCoordinates) {
textureCoordinates[0] = {1.0f, 0.0f};
textureCoordinates[1] = {1.0f, 1.0f};
textureCoordinates[2] = {0.0f, 0.0f};
textureCoordinates[3] = {0.0f, 1.0f};
}

return Trade::MeshData{MeshPrimitive::TriangleStrip,
std::move(vertexData), std::move(attributeData)};
}

#ifdef MAGNUM_BUILD_DEPRECATED
CORRADE_IGNORE_DEPRECATED_PUSH
Trade::MeshData planeSolid(const PlaneTextureCoords textureCoords) {
Expand Down
24 changes: 19 additions & 5 deletions src/Magnum/Primitives/Plane.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ namespace Magnum { namespace Primitives {
*/
enum class PlaneFlag: UnsignedByte {
/** Generate texture coordinates with origin in bottom left corner */
TextureCoordinates = 1 << 0
TextureCoordinates = 1 << 0,

/**
* 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
};

/**
Expand Down Expand Up @@ -77,16 +85,22 @@ enum class CORRADE_DEPRECATED_ENUM("use PlaneFlags instead") PlaneTextureCoords:
@m_since_latest
2x2 plane. Non-indexed @ref MeshPrimitive::TriangleStrip on the XY plane with
@ref VertexFormat::Vector3 positions and @ref VertexFormat::Vector3 normals in
positive Z direction. The returned instance references data stored in constant
memory.
@ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 normals in
positive Z direction, optional @ref VertexFormat::Vector4 tangents and optional
@ref VertexFormat::Vector2 texture coordinates. The returned instance may
reference data stored in constant memory.
@image html primitives-planesolid.png width=256px
@see @ref planeWireframe(), @ref squareSolid(), @ref gradient3D(),
@ref MeshTools::generateTriangleStripIndices()
*/
MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeSolid(PlaneFlags flags = {});
MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeSolid(PlaneFlags flags);

/** @overload */
/* Separate API so apps that don't need texture coordinate / tangents don't
need to drag in the extra code needed to allocate & calculate them */
MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeSolid();

#ifdef MAGNUM_BUILD_DEPRECATED
/**
Expand Down
62 changes: 42 additions & 20 deletions src/Magnum/Primitives/Test/PlaneTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <Corrade/TestSuite/Tester.h>

#include "Magnum/Mesh.h"
#include "Magnum/Math/Vector3.h"
#include "Magnum/Math/Vector4.h"
#include "Magnum/Primitives/Plane.h"
#include "Magnum/Trade/MeshData.h"

Expand All @@ -36,42 +36,64 @@ struct PlaneTest: TestSuite::Tester {
explicit PlaneTest();

void solid();
void solidTextured();
void wireframe();
};

constexpr struct {
const char* name;
PlaneFlags flags;
} SolidData[] {
{"", PlaneFlags{}},
{"texture coordinates", PlaneFlag::TextureCoordinates},
{"tangents", PlaneFlag::Tangents},
{"both", PlaneFlag::TextureCoordinates|PlaneFlag::Tangents}
};

PlaneTest::PlaneTest() {
addTests({&PlaneTest::solid,
&PlaneTest::solidTextured,
&PlaneTest::wireframe});
addInstancedTests({&PlaneTest::solid},
Containers::arraySize(SolidData));

addTests({&PlaneTest::wireframe});
}

void PlaneTest::solid() {
Trade::MeshData plane = Primitives::planeSolid();
auto&& data = SolidData[testCaseInstanceId()];
setTestCaseDescription(data.name);

Trade::MeshData plane = Primitives::planeSolid(data.flags);

CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(!plane.isIndexed());
CORRADE_COMPARE(plane.vertexCount(), 4);
CORRADE_COMPARE(plane.attributeCount(), 2);

CORRADE_COMPARE(plane.attribute<Vector3>(Trade::MeshAttribute::Position)[3],
(Vector3{-1.0f, 1.0f, 0.0f}));
CORRADE_COMPARE(plane.attribute<Vector3>(Trade::MeshAttribute::Normal)[2],
(Vector3{0.0f, 0.0f, 1.0f}));
}

void PlaneTest::solidTextured() {
Trade::MeshData plane = Primitives::planeSolid(Primitives::PlaneFlag::TextureCoordinates);
if(data.flags & PlaneFlag::Tangents)
CORRADE_COMPARE(plane.attribute<Vector4>(Trade::MeshAttribute::Tangent)[1],
(Vector4{1.0f, 0.0f, 0.0f, 1.0f}));
else CORRADE_VERIFY(!plane.hasAttribute(Trade::MeshAttribute::Tangent));

CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(!plane.isIndexed());
CORRADE_COMPARE(plane.vertexCount(), 4);
CORRADE_COMPARE(plane.attributeCount(), 3);
CORRADE_COMPARE(plane.attribute<Vector3>(Trade::MeshAttribute::Position)[3],
(Vector3{-1.0f, 1.0f, 0.0f}));
CORRADE_COMPARE(plane.attribute<Vector3>(Trade::MeshAttribute::Normal)[2],
(Vector3{0.0f, 0.0f, 1.0f}));
CORRADE_COMPARE(plane.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates)[1],
(Vector2{1.0f, 1.0f}));

if(data.flags & PlaneFlag::TextureCoordinates)
CORRADE_COMPARE(plane.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates)[1],
(Vector2{1.0f, 1.0f}));
else CORRADE_VERIFY(!plane.hasAttribute(Trade::MeshAttribute::TextureCoordinates));

if(data.flags & PlaneFlag::Tangents) {
auto tangents = plane.attribute<Vector4>(Trade::MeshAttribute::Tangent);
auto normals = plane.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 PlaneTest::wireframe() {
Expand Down

0 comments on commit 2a88a88

Please sign in to comment.