Skip to content

Commit

Permalink
Primitives: implement tangent generation in spheroid primitives.
Browse files Browse the repository at this point in the history
  • Loading branch information
mosra committed Mar 22, 2020
1 parent ca5beb4 commit a099a57
Show file tree
Hide file tree
Showing 13 changed files with 600 additions and 214 deletions.
6 changes: 6 additions & 0 deletions doc/changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions src/Magnum/Primitives/Capsule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/**
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Magnum/Primitives/Cone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 14 additions & 5 deletions src/Magnum/Primitives/Cone.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};

/**
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Magnum/Primitives/Cylinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 13 additions & 4 deletions src/Magnum/Primitives/Cylinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};

/**
Expand All @@ -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
Expand Down
72 changes: 57 additions & 15 deletions src/Magnum/Primitives/Implementation/Spheroid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,35 @@
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);
++_attributeCount;
} 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<Trade::ArrayAllocator>(_vertexData,
Containers::arrayCast<const char>(Containers::arrayView(&position, 1)));
Containers::arrayAppend<Trade::ArrayAllocator>(_vertexData,
Containers::arrayCast<const char>(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<Trade::ArrayAllocator>(_vertexData,
Containers::arrayView(empty));
}
if(_flags & Flag::TextureCoordinates) {
/** @todo make arrayGrow() a public API instead */
constexpr const char empty[sizeof(Vector2)]{};
Containers::arrayAppend<Trade::ArrayAllocator>(_vertexData,
Containers::arrayCast<const char>(Containers::arrayView(&textureCoords, 1)));
Containers::arrayView(empty));
}
}

Expand All @@ -62,12 +76,18 @@ Vector3 Spheroid::lastVertexNormal(const std::size_t offsetFromEnd) {
return Containers::arrayCast<Vector3>(_vertexData.slice<sizeof(Vector3)>(_vertexData.size() - _stride*offsetFromEnd + sizeof(Vector3)))[0];
}

Vector4& Spheroid::lastVertexTangent(const std::size_t offsetFromEnd) {
return Containers::arrayCast<Vector4>(_vertexData.slice<sizeof(Vector4)>(_vertexData.size() - _stride*offsetFromEnd + _tangentOffset))[0];
}

Vector2& Spheroid::lastVertexTextureCoords(const std::size_t offsetFromEnd) {
return Containers::arrayCast<Vector2>(_vertexData.slice<sizeof(Vector2)>(_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};
}
Expand All @@ -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};
}
}
}
Expand All @@ -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;
Expand All @@ -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 */
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -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 */
Expand All @@ -193,14 +223,20 @@ void Spheroid::capVertexRing(Float y, Float textureCoordsV, const Vector3& norma
const std::pair<Float, Float> 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};
}
}

Expand All @@ -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,
Expand Down
11 changes: 7 additions & 4 deletions src/Magnum/Primitives/Implementation/Spheroid.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@
#include <Corrade/Containers/StridedArrayView.h>

#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 {

class Spheroid {
public:
enum class Flag: UnsignedByte {
TextureCoordinates = 1 << 0
TextureCoordinates = 1 << 0,
Tangents = 1 << 1
};

typedef Containers::EnumSet<Flag> Flags;
Expand All @@ -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<UnsignedInt> _indexData;
Containers::Array<char> _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);
};

Expand Down
Loading

0 comments on commit a099a57

Please sign in to comment.