You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
682 lines
26 KiB
682 lines
26 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <Mathematics/Matrix.h>
|
|
#include <Mathematics/IndexAttribute.h>
|
|
#include <Mathematics/VertexAttribute.h>
|
|
#include <Mathematics/Vector2.h>
|
|
#include <Mathematics/Vector3.h>
|
|
|
|
// The Mesh class is designed to support triangulations of surfaces of a small
|
|
// number of topologies. See the documents
|
|
// https://www.geometrictools.com/MeshDifferentialGeometry.pdf
|
|
// https://www.geometrictools.com/MeshFactory.pdf
|
|
// for details.
|
|
//
|
|
// You must set the vertex attribute sources before calling Update().
|
|
//
|
|
// The semantic "position" is required and its source must be an array
|
|
// of Real with at least 3 channels so that positions are computed as
|
|
// Vector3<Real>.
|
|
//
|
|
// The positions are assumed to be parameterized by texture coordinates
|
|
// (u,v); the position is thought of as a function P(u,v). If texture
|
|
// coordinates are provided, the semantic must be "tcoord". If texture
|
|
// coordinates are not provided, default texture coordinates are computed
|
|
// internally as described in the mesh factory document.
|
|
//
|
|
// The frame for the tangent space is optional. All vectors in the frame
|
|
// must have sources that are arrays of Real with at least 3 channels per
|
|
// attribute. If normal vectors are provided, the semantic must be
|
|
// "normal".
|
|
//
|
|
// Two options are supported for tangent vectors. The first option is
|
|
// that the tangents are surface derivatives dP/du and dP/dv, which are
|
|
// not necessarily unit length or orthogonal. The semantics must be
|
|
// "dpdu" and "dpdv". The second option is that the tangents are unit
|
|
// length and orthogonal, with the infrequent possibility that a vertex
|
|
// is degenerate in that dP/du and dP/dv are linearly dependent. The
|
|
// semantics must be "tangent" and "bitangent".
|
|
//
|
|
// For each provided vertex attribute, a derived class can initialize
|
|
// that attribute by overriding one of the Initialize*() functions whose
|
|
// stubs are defined in this class.
|
|
|
|
namespace gte
|
|
{
|
|
enum class MeshTopology
|
|
{
|
|
ARBITRARY,
|
|
RECTANGLE,
|
|
CYLINDER,
|
|
TORUS,
|
|
DISK,
|
|
SPHERE
|
|
};
|
|
|
|
class MeshDescription
|
|
{
|
|
public:
|
|
// Constructor for MeshTopology::ARBITRARY. The members topology,
|
|
// numVertices, and numTriangles are set in the obvious manner. The
|
|
// members numRows and numCols are set to zero. The remaining members
|
|
// must be set explicitly by the client.
|
|
MeshDescription(uint32_t inNumVertices, uint32_t inNumTriangles)
|
|
:
|
|
topology(MeshTopology::ARBITRARY),
|
|
numVertices(inNumVertices),
|
|
numTriangles(inNumTriangles),
|
|
wantDynamicTangentSpaceUpdate(false),
|
|
wantCCW(true),
|
|
hasTangentSpaceVectors(false),
|
|
allowUpdateFrame(false),
|
|
numRows(0),
|
|
numCols(0),
|
|
rMax(0),
|
|
cMax(0),
|
|
rIncrement(0),
|
|
constructed(false)
|
|
{
|
|
LogAssert(numVertices >= 3 && numTriangles >= 1, "Invalid input.");
|
|
}
|
|
|
|
// Constructor for topologies other than MeshTopology::ARBITRARY.
|
|
// Compute the number of vertices and triangles for the mesh based on
|
|
// the requested number of rows and columns. If the number of rows or
|
|
// columns is invalid for the specified topology, they are modified to
|
|
// be valid, in which case inNumRows/numRows and inNumCols/numCols can
|
|
// differ. If the input topology is MeshTopology::ARBITRARY, then
|
|
// inNumRows and inNumCols are assigned to numVertices and
|
|
// numTriangles, respectively, and numRows and numCols are set to
|
|
// zero. The remaining members must be set explicitly by the client.
|
|
MeshDescription(MeshTopology inTopology, uint32_t inNumRows, uint32_t inNumCols)
|
|
:
|
|
topology(inTopology),
|
|
wantDynamicTangentSpaceUpdate(false),
|
|
wantCCW(true),
|
|
hasTangentSpaceVectors(false),
|
|
allowUpdateFrame(false),
|
|
constructed(false)
|
|
{
|
|
switch (topology)
|
|
{
|
|
case MeshTopology::ARBITRARY:
|
|
numVertices = inNumRows;
|
|
numTriangles = inNumCols;
|
|
numRows = 0;
|
|
numCols = 0;
|
|
rMax = 0;
|
|
cMax = 0;
|
|
rIncrement = 0;
|
|
break;
|
|
|
|
case MeshTopology::RECTANGLE:
|
|
numRows = std::max(inNumRows, 2u);
|
|
numCols = std::max(inNumCols, 2u);
|
|
rMax = numRows - 1;
|
|
cMax = numCols - 1;
|
|
rIncrement = numCols;
|
|
numVertices = (rMax + 1) * (cMax + 1);
|
|
numTriangles = 2 * rMax * cMax;
|
|
break;
|
|
|
|
case MeshTopology::CYLINDER:
|
|
numRows = std::max(inNumRows, 2u);
|
|
numCols = std::max(inNumCols, 3u);
|
|
rMax = numRows - 1;
|
|
cMax = numCols;
|
|
rIncrement = numCols + 1;
|
|
numVertices = (rMax + 1) * (cMax + 1);
|
|
numTriangles = 2 * rMax * cMax;
|
|
break;
|
|
|
|
case MeshTopology::TORUS:
|
|
numRows = std::max(inNumRows, 2u);
|
|
numCols = std::max(inNumCols, 3u);
|
|
rMax = numRows;
|
|
cMax = numCols;
|
|
rIncrement = numCols + 1;
|
|
numVertices = (rMax + 1) * (cMax + 1);
|
|
numTriangles = 2 * rMax * cMax;
|
|
break;
|
|
|
|
case MeshTopology::DISK:
|
|
numRows = std::max(inNumRows, 1u);
|
|
numCols = std::max(inNumCols, 3u);
|
|
rMax = numRows - 1;
|
|
cMax = numCols;
|
|
rIncrement = numCols + 1;
|
|
numVertices = (rMax + 1) * (cMax + 1) + 1;
|
|
numTriangles = 2 * rMax * cMax + numCols;
|
|
break;
|
|
|
|
case MeshTopology::SPHERE:
|
|
numRows = std::max(inNumRows, 1u);
|
|
numCols = std::max(inNumCols, 3u);
|
|
rMax = numRows - 1;
|
|
cMax = numCols;
|
|
rIncrement = numCols + 1;
|
|
numVertices = (rMax + 1) * (cMax + 1) + 2;
|
|
numTriangles = 2 * rMax * cMax + 2 * numCols;
|
|
break;
|
|
}
|
|
}
|
|
|
|
MeshTopology topology;
|
|
uint32_t numVertices;
|
|
uint32_t numTriangles;
|
|
std::vector<VertexAttribute> vertexAttributes;
|
|
IndexAttribute indexAttribute;
|
|
bool wantDynamicTangentSpaceUpdate; // default: false
|
|
bool wantCCW; // default: true
|
|
|
|
// For internal use only.
|
|
bool hasTangentSpaceVectors;
|
|
bool allowUpdateFrame;
|
|
uint32_t numRows, numCols;
|
|
uint32_t rMax, cMax, rIncrement;
|
|
|
|
// After an attempt to construct a Mesh or Mesh-derived object,
|
|
// examine this value to determine whether the construction was
|
|
// successful.
|
|
bool constructed;
|
|
};
|
|
|
|
template <typename Real>
|
|
class Mesh
|
|
{
|
|
public:
|
|
// Construction and destruction. This constructor is for ARBITRARY
|
|
// topology. The vertices and indices must already be assigned by the
|
|
// client. Derived classes use the protected constructor, but
|
|
// assignment of vertices and indices occurs in the derived-class
|
|
// constructors.
|
|
Mesh(MeshDescription const& description, std::vector<MeshTopology> const& validTopologies)
|
|
:
|
|
mDescription(description),
|
|
mPositions(nullptr),
|
|
mNormals(nullptr),
|
|
mTangents(nullptr),
|
|
mBitangents(nullptr),
|
|
mDPDUs(nullptr),
|
|
mDPDVs(nullptr),
|
|
mTCoords(nullptr),
|
|
mPositionStride(0),
|
|
mNormalStride(0),
|
|
mTangentStride(0),
|
|
mBitangentStride(0),
|
|
mDPDUStride(0),
|
|
mDPDVStride(0),
|
|
mTCoordStride(0)
|
|
{
|
|
mDescription.constructed = false;
|
|
for (auto const& topology : validTopologies)
|
|
{
|
|
if (mDescription.topology == topology)
|
|
{
|
|
mDescription.constructed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
LogAssert(mDescription.indexAttribute.source != nullptr,
|
|
"The mesh needs triangles/indices in Mesh constructor.");
|
|
|
|
// Set sources for the requested vertex attributes.
|
|
mDescription.hasTangentSpaceVectors = false;
|
|
mDescription.allowUpdateFrame = mDescription.wantDynamicTangentSpaceUpdate;
|
|
for (auto const& attribute : mDescription.vertexAttributes)
|
|
{
|
|
if (attribute.source != nullptr && attribute.stride > 0)
|
|
{
|
|
if (attribute.semantic == "position")
|
|
{
|
|
mPositions = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mPositionStride = attribute.stride;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "normal")
|
|
{
|
|
mNormals = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mNormalStride = attribute.stride;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "tangent")
|
|
{
|
|
mTangents = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mTangentStride = attribute.stride;
|
|
mDescription.hasTangentSpaceVectors = true;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "bitangent")
|
|
{
|
|
mBitangents = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mBitangentStride = attribute.stride;
|
|
mDescription.hasTangentSpaceVectors = true;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "dpdu")
|
|
{
|
|
mDPDUs = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mDPDUStride = attribute.stride;
|
|
mDescription.hasTangentSpaceVectors = true;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "dpdv")
|
|
{
|
|
mDPDVs = reinterpret_cast<Vector3<Real>*>(attribute.source);
|
|
mDPDVStride = attribute.stride;
|
|
mDescription.hasTangentSpaceVectors = true;
|
|
continue;
|
|
}
|
|
|
|
if (attribute.semantic == "tcoord")
|
|
{
|
|
mTCoords = reinterpret_cast<Vector2<Real>*>(attribute.source);
|
|
mTCoordStride = attribute.stride;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
LogAssert(mPositions != nullptr, "The mesh needs positions in Mesh constructor.");
|
|
|
|
// The initial value of allowUpdateFrame is the client request
|
|
// about wanting dynamic tangent-space updates. If the vertex
|
|
// attributes do not include tangent-space vectors, then dynamic
|
|
// updates are not necessary. If tangent-space vectors are
|
|
// present, the update algorithm requires texture coordinates
|
|
// (mTCoords must be nonnull) or must compute local coordinates
|
|
// (mNormals must be nonnull).
|
|
if (mDescription.allowUpdateFrame)
|
|
{
|
|
if (!mDescription.hasTangentSpaceVectors)
|
|
{
|
|
mDescription.allowUpdateFrame = false;
|
|
}
|
|
|
|
if (!mTCoords && !mNormals)
|
|
{
|
|
mDescription.allowUpdateFrame = false;
|
|
}
|
|
}
|
|
|
|
if (mDescription.allowUpdateFrame)
|
|
{
|
|
mUTU.resize(mDescription.numVertices);
|
|
mDTU.resize(mDescription.numVertices);
|
|
}
|
|
}
|
|
|
|
virtual ~Mesh()
|
|
{
|
|
}
|
|
|
|
// No copying or assignment is allowed.
|
|
Mesh(Mesh const&) = delete;
|
|
Mesh& operator=(Mesh const&) = delete;
|
|
|
|
// Member accessors.
|
|
inline MeshDescription const& GetDescription() const
|
|
{
|
|
return mDescription;
|
|
}
|
|
|
|
// If the underlying geometric data varies dynamically, call this
|
|
// function to update whatever vertex attributes are specified by
|
|
// the vertex pool.
|
|
void Update()
|
|
{
|
|
LogAssert(mDescription.constructed, "The Mesh object failed the construction.");
|
|
|
|
UpdatePositions();
|
|
|
|
if (mDescription.allowUpdateFrame)
|
|
{
|
|
UpdateFrame();
|
|
}
|
|
else if (mNormals)
|
|
{
|
|
UpdateNormals();
|
|
}
|
|
// else: The mesh has no frame data, so there is nothing to do.
|
|
}
|
|
|
|
protected:
|
|
// Access the vertex attributes.
|
|
inline Vector3<Real>& Position(uint32_t i)
|
|
{
|
|
char* positions = reinterpret_cast<char*>(mPositions);
|
|
return *reinterpret_cast<Vector3<Real>*>(positions + i * mPositionStride);
|
|
}
|
|
|
|
inline Vector3<Real>& Normal(uint32_t i)
|
|
{
|
|
char* normals = reinterpret_cast<char*>(mNormals);
|
|
return *reinterpret_cast<Vector3<Real>*>(normals + i * mNormalStride);
|
|
}
|
|
|
|
inline Vector3<Real>& Tangent(uint32_t i)
|
|
{
|
|
char* tangents = reinterpret_cast<char*>(mTangents);
|
|
return *reinterpret_cast<Vector3<Real>*>(tangents + i * mTangentStride);
|
|
}
|
|
|
|
inline Vector3<Real>& Bitangent(uint32_t i)
|
|
{
|
|
char* bitangents = reinterpret_cast<char*>(mBitangents);
|
|
return *reinterpret_cast<Vector3<Real>*>(bitangents + i * mBitangentStride);
|
|
}
|
|
|
|
inline Vector3<Real>& DPDU(uint32_t i)
|
|
{
|
|
char* dpdus = reinterpret_cast<char*>(mDPDUs);
|
|
return *reinterpret_cast<Vector3<Real>*>(dpdus + i * mDPDUStride);
|
|
}
|
|
|
|
inline Vector3<Real>& DPDV(uint32_t i)
|
|
{
|
|
char* dpdvs = reinterpret_cast<char*>(mDPDVs);
|
|
return *reinterpret_cast<Vector3<Real>*>(dpdvs + i * mDPDVStride);
|
|
}
|
|
|
|
inline Vector2<Real>& TCoord(uint32_t i)
|
|
{
|
|
char* tcoords = reinterpret_cast<char*>(mTCoords);
|
|
return *reinterpret_cast<Vector2<Real>*>(tcoords + i * mTCoordStride);
|
|
}
|
|
|
|
// Compute the indices for non-arbitrary topologies. This function is
|
|
// called by derived classes.
|
|
void ComputeIndices()
|
|
{
|
|
uint32_t t = 0;
|
|
for (uint32_t r = 0, i = 0; r < mDescription.rMax; ++r)
|
|
{
|
|
uint32_t v0 = i, v1 = v0 + 1;
|
|
i += mDescription.rIncrement;
|
|
uint32_t v2 = i, v3 = v2 + 1;
|
|
for (uint32_t c = 0; c < mDescription.cMax; ++c, ++v0, ++v1, ++v2, ++v3)
|
|
{
|
|
if (mDescription.wantCCW)
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2);
|
|
mDescription.indexAttribute.SetTriangle(t++, v1, v3, v2);
|
|
}
|
|
else
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1);
|
|
mDescription.indexAttribute.SetTriangle(t++, v1, v2, v3);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDescription.topology == MeshTopology::DISK)
|
|
{
|
|
uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 1;
|
|
for (unsigned int c = 0; c < mDescription.numCols; ++c, ++v0, ++v1)
|
|
{
|
|
if (mDescription.wantCCW)
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1);
|
|
}
|
|
else
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2);
|
|
}
|
|
}
|
|
}
|
|
else if (mDescription.topology == MeshTopology::SPHERE)
|
|
{
|
|
uint32_t v0 = 0, v1 = 1, v2 = mDescription.numVertices - 2;
|
|
for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1)
|
|
{
|
|
if (mDescription.wantCCW)
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1);
|
|
}
|
|
else
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2);
|
|
}
|
|
}
|
|
|
|
v0 = (mDescription.numRows - 1) * mDescription.numCols;
|
|
v1 = v0 + 1;
|
|
v2 = mDescription.numVertices - 1;
|
|
for (uint32_t c = 0; c < mDescription.numCols; ++c, ++v0, ++v1)
|
|
{
|
|
if (mDescription.wantCCW)
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v2, v1);
|
|
}
|
|
else
|
|
{
|
|
mDescription.indexAttribute.SetTriangle(t++, v0, v1, v2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The Update() function allows derived classes to use algorithms
|
|
// different from least-squares fitting to compute the normals (when
|
|
// no tangent-space information is requested) or to compute the frame
|
|
// (normals and tangent space). The UpdatePositions() is a stub; the
|
|
// base-class has no knowledge about how positions should be modified.
|
|
// A derived class, however, might choose to use dynamic updating
|
|
// and override UpdatePositions(). The base-class UpdateNormals()
|
|
// computes vertex normals as averages of area-weighted triangle
|
|
// normals (nonparametric approach). The base-class UpdateFrame()
|
|
// uses a least-squares algorithm for estimating the tangent space
|
|
// (parametric approach).
|
|
virtual void UpdatePositions()
|
|
{
|
|
}
|
|
|
|
virtual void UpdateNormals()
|
|
{
|
|
// Compute normal vector as normalized weighted averages of triangle
|
|
// normal vectors.
|
|
|
|
// Set the normals to zero to allow accumulation of triangle normals.
|
|
Vector3<Real> zero{ (Real)0, (Real)0, (Real)0 };
|
|
for (uint32_t i = 0; i < mDescription.numVertices; ++i)
|
|
{
|
|
Normal(i) = zero;
|
|
}
|
|
|
|
// Accumulate the triangle normals.
|
|
for (uint32_t t = 0; t < mDescription.numTriangles; ++t)
|
|
{
|
|
// Get the positions for the triangle.
|
|
uint32_t v0, v1, v2;
|
|
mDescription.indexAttribute.GetTriangle(t, v0, v1, v2);
|
|
Vector3<Real> P0 = Position(v0);
|
|
Vector3<Real> P1 = Position(v1);
|
|
Vector3<Real> P2 = Position(v2);
|
|
|
|
// Get the edge vectors.
|
|
Vector3<Real> E1 = P1 - P0;
|
|
Vector3<Real> E2 = P2 - P0;
|
|
|
|
// Compute a triangle normal show length is twice the area of the
|
|
// triangle.
|
|
Vector3<Real> triangleNormal = Cross(E1, E2);
|
|
|
|
// Accumulate the triangle normals.
|
|
Normal(v0) += triangleNormal;
|
|
Normal(v1) += triangleNormal;
|
|
Normal(v2) += triangleNormal;
|
|
}
|
|
|
|
// Normalize the normals.
|
|
for (uint32_t i = 0; i < mDescription.numVertices; ++i)
|
|
{
|
|
Normalize(Normal(i), true);
|
|
}
|
|
}
|
|
|
|
virtual void UpdateFrame()
|
|
{
|
|
if (!mTCoords)
|
|
{
|
|
// We need to compute vertex normals first in order to compute
|
|
// local texture coordinates. The vertex normals are recomputed
|
|
// later based on estimated tangent vectors.
|
|
UpdateNormals();
|
|
}
|
|
|
|
// Use the least-squares algorithm to estimate the tangent-space vectors
|
|
// and, if requested, normal vectors.
|
|
Matrix<2, 2, Real> zero2x2; // initialized to zero
|
|
Matrix<3, 2, Real> zero3x2; // initialized to zero
|
|
std::fill(mUTU.begin(), mUTU.end(), zero2x2);
|
|
std::fill(mDTU.begin(), mDTU.end(), zero3x2);
|
|
for (uint32_t t = 0; t < mDescription.numTriangles; ++t)
|
|
{
|
|
// Get the positions and differences for the triangle.
|
|
uint32_t v0, v1, v2;
|
|
mDescription.indexAttribute.GetTriangle(t, v0, v1, v2);
|
|
Vector3<Real> P0 = Position(v0);
|
|
Vector3<Real> P1 = Position(v1);
|
|
Vector3<Real> P2 = Position(v2);
|
|
Vector3<Real> D10 = P1 - P0;
|
|
Vector3<Real> D20 = P2 - P0;
|
|
Vector3<Real> D21 = P2 - P1;
|
|
|
|
if (mTCoords)
|
|
{
|
|
// Get the texture coordinates and differences for the triangle.
|
|
Vector2<Real> C0 = TCoord(v0);
|
|
Vector2<Real> C1 = TCoord(v1);
|
|
Vector2<Real> C2 = TCoord(v2);
|
|
Vector2<Real> U10 = C1 - C0;
|
|
Vector2<Real> U20 = C2 - C0;
|
|
Vector2<Real> U21 = C2 - C1;
|
|
|
|
// Compute the outer products.
|
|
Matrix<2, 2, Real> outerU10 = OuterProduct(U10, U10);
|
|
Matrix<2, 2, Real> outerU20 = OuterProduct(U20, U20);
|
|
Matrix<2, 2, Real> outerU21 = OuterProduct(U21, U21);
|
|
Matrix<3, 2, Real> outerD10 = OuterProduct(D10, U10);
|
|
Matrix<3, 2, Real> outerD20 = OuterProduct(D20, U20);
|
|
Matrix<3, 2, Real> outerD21 = OuterProduct(D21, U21);
|
|
|
|
// Keep a running sum of U^T*U and D^T*U.
|
|
mUTU[v0] += outerU10 + outerU20;
|
|
mUTU[v1] += outerU10 + outerU21;
|
|
mUTU[v2] += outerU20 + outerU21;
|
|
mDTU[v0] += outerD10 + outerD20;
|
|
mDTU[v1] += outerD10 + outerD21;
|
|
mDTU[v2] += outerD20 + outerD21;
|
|
}
|
|
else
|
|
{
|
|
// Compute local coordinates and differences for the triangle.
|
|
Vector3<Real> basis[3];
|
|
|
|
basis[0] = Normal(v0);
|
|
ComputeOrthogonalComplement(1, basis, true);
|
|
Vector2<Real> U10{ Dot(basis[1], D10), Dot(basis[2], D10) };
|
|
Vector2<Real> U20{ Dot(basis[1], D20), Dot(basis[2], D20) };
|
|
mUTU[v0] += OuterProduct(U10, U10) + OuterProduct(U20, U20);
|
|
mDTU[v0] += OuterProduct(D10, U10) + OuterProduct(D20, U20);
|
|
|
|
basis[0] = Normal(v1);
|
|
ComputeOrthogonalComplement(1, basis, true);
|
|
Vector2<Real> U01{ Dot(basis[1], D10), Dot(basis[2], D10) };
|
|
Vector2<Real> U21{ Dot(basis[1], D21), Dot(basis[2], D21) };
|
|
mUTU[v1] += OuterProduct(U01, U01) + OuterProduct(U21, U21);
|
|
mDTU[v1] += OuterProduct(D10, U01) + OuterProduct(D21, U21);
|
|
|
|
basis[0] = Normal(v2);
|
|
ComputeOrthogonalComplement(1, basis, true);
|
|
Vector2<Real> U02{ Dot(basis[1], D20), Dot(basis[2], D20) };
|
|
Vector2<Real> U12{ Dot(basis[1], D21), Dot(basis[2], D21) };
|
|
mUTU[v2] += OuterProduct(U02, U02) + OuterProduct(U12, U12);
|
|
mDTU[v2] += OuterProduct(D20, U02) + OuterProduct(D21, U12);
|
|
}
|
|
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mDescription.numVertices; ++i)
|
|
{
|
|
Matrix<3, 2, Real> jacobian = mDTU[i] * Inverse(mUTU[i]);
|
|
|
|
Vector3<Real> basis[3];
|
|
basis[0] = { jacobian(0, 0), jacobian(1, 0), jacobian(2, 0) };
|
|
basis[1] = { jacobian(0, 1), jacobian(1, 1), jacobian(2, 1) };
|
|
|
|
if (mDPDUs)
|
|
{
|
|
DPDU(i) = basis[0];
|
|
}
|
|
if (mDPDVs)
|
|
{
|
|
DPDV(i) = basis[1];
|
|
}
|
|
|
|
ComputeOrthogonalComplement(2, basis, true);
|
|
|
|
if (mNormals)
|
|
{
|
|
Normal(i) = basis[2];
|
|
}
|
|
if (mTangents)
|
|
{
|
|
Tangent(i) = basis[0];
|
|
}
|
|
if (mBitangents)
|
|
{
|
|
Bitangent(i) = basis[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Constructor inputs.
|
|
// The client requests this via the constructor; however, if it is
|
|
// requested and the vertex attributes do not contain entries for
|
|
// "tangent", "bitangent", "dpdu", or "dpdv", then this member is
|
|
// set to false.
|
|
MeshDescription mDescription;
|
|
|
|
// Copied from mVertexAttributes when available.
|
|
Vector3<Real>* mPositions;
|
|
Vector3<Real>* mNormals;
|
|
Vector3<Real>* mTangents;
|
|
Vector3<Real>* mBitangents;
|
|
Vector3<Real>* mDPDUs;
|
|
Vector3<Real>* mDPDVs;
|
|
Vector2<Real>* mTCoords;
|
|
size_t mPositionStride;
|
|
size_t mNormalStride;
|
|
size_t mTangentStride;
|
|
size_t mBitangentStride;
|
|
size_t mDPDUStride;
|
|
size_t mDPDVStride;
|
|
size_t mTCoordStride;
|
|
|
|
// When dynamic tangent-space updates are requested, the update algorithm
|
|
// requires texture coordinates (user-specified or non-local). It is
|
|
// possible to create a vertex-adjacent set (with indices into the
|
|
// vertex array) for each mesh vertex; however, instead we rely on a
|
|
// triangle iteration and incrementally store the information needed for
|
|
// the estimation of the tangent space. Each vertex has associated
|
|
// matrices D and U, but we need to store only U^T*U and D^T*U. See the
|
|
// PDF for details.
|
|
std::vector<Matrix<2, 2, Real>> mUTU;
|
|
std::vector<Matrix<3, 2, Real>> mDTU;
|
|
};
|
|
}
|
|
|