/**
@file MeshAlg.h
Indexed Mesh algorithms.
@maintainer Morgan McGuire, http://graphics.cs.williams.edu
@created 2003-09-14
@edited 2010-01-18
*/
#ifndef G3D_MeshAlg_h
#define G3D_MeshAlg_h
#include "G3D/platform.h"
#include "G3D/Array.h"
#include "G3D/Vector3.h"
#include "G3D/CoordinateFrame.h"
#include "G3D/SmallArray.h"
#include "G3D/constants.h"
#include "G3D/Image1.h"
#ifdef G3D_WINDOWS
// Turn off "conditional expression is constant" warning; MSVC generates this
// for debug assertions in inlined methods.
#pragma warning( push )
#pragma warning (disable : 4127)
#endif
namespace G3D {
/**
Indexed mesh algorithms. You have to build your own mesh class.
No mesh class is provided with G3D because there isn't an "ideal"
mesh format-- one application needs keyframed animation, another
skeletal animation, a third texture coordinates, a fourth
cannot precompute information, etc. Instead of compromising, this
class implements the hard parts of mesh computation and you can write
your own ideal mesh class on top of it.
\sa G3D::ArticulatedModel, G3D::IFSModel
*/
class MeshAlg {
public:
/** \deprecated */
typedef PrimitiveType Primitive;
/** Adjacency information for a vertex.
Does not contain the vertex position or normal,
which are stored in the MeshAlg::Geometry object.
Vertex
s must be stored in an array
parallel to (indexed in the same way as)
MeshAlg::Geometry::vertexArray.
*/
class Vertex {
public:
Vertex() {}
/**
Array of edges adjacent to this vertex.
Let e = edgeIndex[i].
edge[(e >= 0) ? e : ~e].vertexIndex[0] == this
vertex index.
Edges may be listed multiple times if they are
degenerate.
*/
SmallArray edgeIndex;
/**
Returns true if e or ~e is in the edgeIndex list.
*/
inline bool inEdge(int e) const {
return edgeIndex.contains(~e) || edgeIndex.contains(e);
}
/**
Array of faces containing this vertex. Faces
may be listed multiple times if they are degenerate.
*/
SmallArray faceIndex;
inline bool inFace(int f) const {
debugAssert(f >= 0);
return faceIndex.contains(f);
}
};
/**
Oriented, indexed triangle.
*/
class Face {
public:
Face();
/**
Used by Edge::faceIndex to indicate a missing face.
This is a large negative value.
*/
static const int NONE;
/**
Vertices in the face in counter-clockwise order.
Degenerate faces may include the same vertex multiple times.
*/
int vertexIndex[3];
inline bool containsVertex(int v) const {
return contains(vertexIndex, 3, v);
}
/**
Edge indices in counter-clockwise order. Edges are
undirected, so it is important to know which way
each edge is pointing in a face. This is encoded
using negative indices.
If edgeIndex[i] >= 0
then this face
contains the directed edge
between vertex indices
edgeArray[face.edgeIndex[i]].vertexIndex[0]
and
edgeArray[face.edgeIndex[i]].vertexIndex[1]
.
If edgeIndex[i] < 0
then
~edgeIndex[i]
(i.e. the two's
complement of) is used and this face contains the directed
edge between vertex indices
edgeArray[~face.edgeIndex[i]].vertexIndex[0]
and
edgeArray[~face.edgeIndex[i]].vertexIndex[1]
.
Degenerate faces may include the same edge multiple times.
*/
// Temporarily takes on the value Face::NONE during adjacency
// computation to indicate an edge that has not yet been assigned.
int edgeIndex[3];
inline bool containsEdge(int e) const {
if (e < 0) {
e = ~e;
}
return contains(edgeIndex, 3, e) || contains(edgeIndex, 3, ~e);
}
/** Contains the forward edge e if e >= 0 and the backward edge
~e otherwise. */
inline bool containsDirectedEdge(int e) const {
return contains(edgeIndex, 3, e);
}
};
/** Oriented, indexed edge */
class Edge {
public:
Edge();
/** Degenerate edges may include the same vertex times. */
int vertexIndex[2];
inline bool containsVertex(int v) const {
return contains(vertexIndex, 2, v);
}
/**
The edge is directed forward in face 0
backward in face 1. Face index of MeshAlg::Face::NONE
indicates a boundary (a.k.a. crack, broken) edge.
*/
int faceIndex[2];
/** Returns true if f is contained in the faceIndex array in either slot.
To see if it is forward in that face, just check edge.faceIndex[0] == f.*/
inline bool inFace(int f) const {
return contains(faceIndex, 2, f);
}
/**
Returns true if either faceIndex is NONE.
*/
inline bool boundary() const {
return (faceIndex[0] == Face::NONE) ||
(faceIndex[1] == Face::NONE);
}
/**
Returns the reversed edge.
*/
inline Edge reverse() const {
Edge e;
e.vertexIndex[0] = vertexIndex[1];
e.vertexIndex[1] = vertexIndex[0];
e.faceIndex[0] = faceIndex[1];
e.faceIndex[1] = faceIndex[0];
return e;
}
};
/**
Convenient for passing around the per-vertex data that changes under
animation. The faces and edges are needed to interpret
these values.
*/
class Geometry {
public:
/** Vertex positions */
Array vertexArray;
/** Vertex normals */
Array normalArray;
/**
Assignment is optimized using SSE.
*/
Geometry& operator=(const Geometry& src);
void clear() {
vertexArray.clear();
normalArray.clear();
}
};
/**
Given a set of vertices and a set of indices for traversing them
to create triangles, computes other mesh properties.
Colocated vertices are treated as separate. To have
colocated vertices collapsed (necessary for many algorithms,
like shadowing), weld the mesh before computing adjacency.
Recent change: In version 6.00, colocated vertices were automatically
welded by this routine and degenerate faces and edges were removed. That
is no longer the case.
Where two faces meet, there are two opposite directed edges. These
are collapsed into a single bidirectional edge in the edgeArray.
If four faces meet exactly at the same edge, that edge will appear
twice in the array, and so on. If an edge is a boundary of the mesh
(i.e. if the edge has only one adjacent face) it will appear in the
array with one face index set to MeshAlg::Face::NONE.
@param vertexGeometry %Vertex positions to use when deciding colocation.
@param indexArray Order to traverse vertices to make triangles
@param faceArray Output
@param edgeArray Output. Sorted so that boundary edges are at the end of the array.
@param vertexArray Output
*/
static void computeAdjacency(
const Array& vertexGeometry,
const Array& indexArray,
Array& faceArray,
Array& edgeArray,
Array& vertexArray);
/**
@deprecated Use the other version of computeAdjacency, which takes Array.
@param facesAdjacentToVertex Output adjacentFaceArray[v] is an array of
indices for faces touching vertex index v
*/
static void computeAdjacency(
const Array& vertexArray,
const Array& indexArray,
Array& faceArray,
Array& edgeArray,
Array< Array >& facesAdjacentToVertex);
/**
Computes some basic mesh statistics including: min, max mean and median,
edge lengths; and min, mean, median, and max face area.
@param vertexArray %Vertex positions to use when deciding colocation.
@param indexArray Order to traverse vertices to make triangles
@param minEdgeLength Minimum edge length
@param meanEdgeLength Mean edge length
@param medianEdgeLength Median edge length
@param maxEdgeLength Max edge length
@param minFaceArea Minimum face area
@param meanFaceArea Mean face area
@param medianFaceArea Median face area
@param maxFaceArea Max face area
*/
static void computeAreaStatistics(
const Array& vertexArray,
const Array& indexArray,
double& minEdgeLength,
double& meanEdgeLength,
double& medianEdgeLength,
double& maxEdgeLength,
double& minFaceArea,
double& meanFaceArea,
double& medianFaceArea,
double& maxFaceArea);
private:
/** Helper for weldAdjacency */
static void weldBoundaryEdges(
Array& faceArray,
Array& edgeArray,
Array& vertexArray);
public:
/**
Computes tangent and binormal vectors,
which provide a (mostly) consistent
parameterization over the surface for
effects like bump mapping. In the resulting coordinate frame,
T = x (varies with texture s coordinate), B = y (varies with negative texture t coordinate),
and N = z for a right-handed coordinate frame. If a billboard is vertical on the screen
in view of the camera, the tangent space matches the camera's coordinate frame.
The vertex, texCoord, tangent, and binormal
arrays are parallel arrays.
The resulting tangent and binormal might not be exactly
perpendicular to each other. They are guaranteed to
be perpendicular to the normal.
@cite Max McGuire
*/
static void computeTangentSpaceBasis(
const Array& vertexArray,
const Array& texCoordArray,
const Array& vertexNormalArray,
const Array& faceArray,
Array& tangent,
Array& binormal);
/** @deprecated */
static void computeNormals(
const Array& vertexArray,
const Array& faceArray,
const Array< Array >& adjacentFaceArray,
Array& vertexNormalArray,
Array& faceNormalArray);
/**
Vertex normals are weighted by the area of adjacent faces.
Nelson Max showed this is superior to uniform weighting for
general meshes in jgt.
@param vertexNormalArray Output. Unit length
@param faceNormalArray Output. Degenerate faces produce zero magnitude normals. Unit length
@see weld
*/
static void computeNormals(
const Array& vertexGeometry,
const Array& faceArray,
const Array& vertexArray,
Array& vertexNormalArray,
Array& faceNormalArray);
/** Computes unit length normals in place using the other computeNormals methods.
If you already have a face array use another method; it will be faster.
@see weld*/
static void computeNormals(
Geometry& geometry,
const Array& indexArray);
/**
Computes face normals only. Significantly faster (especially if
normalize is false) than computeNormals.
@see weld
*/
static void computeFaceNormals(
const Array& vertexArray,
const Array& faceArray,
Array& faceNormals,
bool normalize = true);
/**
Classifies each face as a backface or a front face relative
to the observer point P (which is at infinity when P.w = 0).
A face with normal exactly perpendicular to the observer vector
may be classified as either a front or a back face arbitrarily.
*/
static void identifyBackfaces(
const Array& vertexArray,
const Array& faceArray,
const Vector4& P,
Array& backface);
/** A faster version of identifyBackfaces for the case where
face normals have already been computed */
static void identifyBackfaces(
const Array& vertexArray,
const Array& faceArray,
const Vector4& P,
Array& backface,
const Array& faceNormals);
/**
Welds nearby and colocated elements of the oldVertexArray together so that
newVertexArray contains no vertices within radius of one another.
Every vertex in newVertexPositions also appears in oldVertexPositions.
This is useful for downsampling meshes and welding cracks created by artist errors
or numerical imprecision.
The two integer arrays map indices back and forth between the arrays according to:
oldVertexArray[toOld[ni]] == newVertexArray[ni]
oldVertexArray[oi] == newVertexArray[toNew[ni]]
Note that newVertexPositions is never longer than oldVertexPositions
and is shorter when vertices are welded.
Welding with a large radius will effectively compute a lower level of detail for
the mesh.
The welding method runs in roughly linear time in the length of oldVertexArray--
a uniform spatial grid is used to achieve nearly constant time vertex collapses
for uniformly distributed vertices.
It is sometimes desirable to keep the original vertex ordering but
identify the unique vertices. The following code computes
array canonical s.t. canonical[v] = first occurance of
a vertex near oldVertexPositions[v] in oldVertexPositions.
Array canonical(oldVertexPositions.size()), toNew, toOld;
computeWeld(oldVertexPositions, Array(), toNew, toOld, radius);
for (int v = 0; v < canonical.size(); ++v) {
canonical[v] = toOld[toNew[v]];
}
See also G3D::MeshAlg::weldAdjacency.
@cite The method is that described as the 'Grouper' in Baum, Mann, Smith, and Winget,
Making Radiosity Usable: Automatic Preprocessing and Meshing Techniques for
the Generation of Accurate Radiosity Solutions, Computer Graphics vol 25, no 4, July 1991.
@deprecated Use weld.
*/
static void computeWeld(
const Array& oldVertexPositions,
Array& newVertexPositions,
Array& toNew,
Array& toOld,
float radius = fuzzyEpsilon32);
/**
Modifies the face, edge, and vertex arrays in place so that
colocated (within radius) vertices are treated as identical.
Note that the vertexArray and corresponding geometry will
contain elements that are no longer used. In the vertexArray,
these elements are initialized to MeshAlg::Vertex() but not
removed (because removal would change the indexing).
This is a good preprocessing step for algorithms that are only
concerned with the shape of a mesh (e.g. cartoon rendering, fur, shadows)
and not the indexing of the vertices.
Use this method when you have already computed adjacency information
and want to collapse colocated vertices within that data without
disturbing the actual mesh vertices or indexing scheme.
If you have not computed adjacency already, use MeshAlg::computeWeld
instead and compute adjacency information after welding.
@deprecated Use weld.
@param faceArray Mutated in place. Size is maintained (degenerate
faces are not removed).
@param edgeArray Mutated in place. May shrink if boundary edges
are welded together.
@param vertexArray Mutated in place. Size is maintained (duplicate
vertices contain no adjacency info).
*/
static void weldAdjacency(
const Array& originalGeometry,
Array& faceArray,
Array& edgeArray,
Array& vertexArray,
float radius = fuzzyEpsilon32);
/**
Counts the number of edges (in an edge array returned from
MeshAlg::computeAdjacency) that have only one adjacent face.
*/
static int countBoundaryEdges(const Array& edgeArray);
/**
Generates an array of integers from start to start + n - 1 that have run numbers
in series then omit the next skip before the next run. Useful for turning
a triangle list into an indexed face set.
Example:
createIndexArray(10, x);
// x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
createIndexArray(5, x, 2);
// x = [2, 3, 4, 5, 6, 7]
createIndexArray(6, x, 0, 2, 1);
// x = [0, 1, 3, 4, 6, 7]
*/
static void createIndexArray(
int n,
Array& array,
int start = 0,
int run = 1,
int skip = 0);
/**
Computes a conservative, near-optimal axis aligned bounding box and sphere.
@cite The bounding sphere uses the method from J. Ritter. An effcient bounding sphere. In Andrew S. Glassner, editor, Graphics Gems. Academic Press, Boston, MA, 1990.
*/
static void computeBounds(const Array& vertex, class AABox& box, class Sphere& sphere);
/** Computes bounds for a subset of the vertices. It is ok if vertices appear more than once in the index array. */
static void computeBounds(const Array& vertex, const Array& index, class AABox& box, class Sphere& sphere);
/**
In debug mode, asserts that the adjacency references between the
face, edge, and vertex array are consistent.
*/
static void debugCheckConsistency(
const Array& faceArray,
const Array& edgeArray,
const Array& vertexArray);
/**
Generates a unit square in the X-Z plane composed of a grid of wCells x hCells
squares on the unit interval and then transforms it by xform.
@param vertex Output vertices
@param texCoord Output texture coordinates
@param index Output triangle list indices
@param textureScale Lower-right texture coordinate
@param spaceCentered If true, the coordinates generated are centered at the origin before the transformation.
@param twoSided If true, matching top and bottom planes are generated.
\param elevation If non-NULL, values from this image are used as elevations. Apply an \a xform to adjust the scale
*/
static void generateGrid
(Array& vertex,
Array& texCoord,
Array& index,
int wCells = 10,
int hCells = 10,
const Vector2& textureScale = Vector2(1,1),
bool spaceCentered = true,
bool twoSided = true,
const CoordinateFrame& xform = CoordinateFrame(),
const Image1::Ref& elevation = Image1::Ref());
/** Converts quadlist (QUADS),
triangle fan (TRIANGLE_FAN),
tristrip(TRIANGLE_STRIP), and quadstrip (QUAD_STRIP) indices into
triangle list (TRIANGLES) indices and appends them to outIndices. */
template
static void toIndexedTriList(
const Array& inIndices,
MeshAlg::Primitive inType,
Array& outIndices) {
debugAssert(
inType == PrimitiveType::TRIANGLE_STRIP ||
inType == PrimitiveType::TRIANGLE_FAN ||
inType == PrimitiveType::QUADS ||
inType == PrimitiveType::QUAD_STRIP);
const int inSize = inIndices.size();
switch(inType) {
case PrimitiveType::TRIANGLE_FAN:
{
debugAssert(inSize >= 3);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
for (IndexType i = 1, outIndex = N; i <= (inSize - 2); ++i, outIndex += 3) {
outIndices[outIndex] = inIndices[0];
outIndices[outIndex + 1] = inIndices[i];
outIndices[outIndex + 2] = inIndices[i + 1];
}
break;
}
case PrimitiveType::TRIANGLE_STRIP:
{
debugAssert(inSize >= 3);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
bool atEven = false;
for (IndexType i = 0, outIndex = N; i < (inSize - 2); ++i, outIndex += 3) {
if (atEven) {
outIndices[outIndex] = inIndices[i + 1];
outIndices[outIndex + 1] = inIndices[i];
outIndices[outIndex + 2] = inIndices[i + 2];
atEven = false;
} else {
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 2];
atEven = true;
}
}
break;
}
case PrimitiveType::QUADS:
{
debugAssert(inIndices.size() >= 4);
int N = outIndices.size();
outIndices.resize(N + (inSize / 4) * 3);
for (IndexType i = 0, outIndex = N; i <= (inSize - 4); i += 4, outIndex += 6) {
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 3];
outIndices[outIndex + 3] = inIndices[i + 1];
outIndices[outIndex + 4] = inIndices[i + 2];
outIndices[outIndex + 5] = inIndices[i + 3];
}
break;
}
case PrimitiveType::QUAD_STRIP:
{
debugAssert(inIndices.size() >= 4);
int N = outIndices.size();
outIndices.resize(N + (inSize - 2) * 3);
for (IndexType i = 0, outIndex = N; i <= (inSize - 2); i += 2, outIndex += 6) {
outIndices[outIndex] = inIndices[i];
outIndices[outIndex + 1] = inIndices[i + 1];
outIndices[outIndex + 2] = inIndices[i + 2];
outIndices[outIndex + 3] = inIndices[i + 2];
outIndices[outIndex + 4] = inIndices[i + 1];
outIndices[outIndex + 5] = inIndices[i + 3];
}
break;
}
default:
alwaysAssertM(false, "Illegal argument");
}
}
protected:
/**
Helper for computeAdjacency. If a directed edge with index e already
exists from i0 to i1 then e is returned. If a directed edge with index e
already exists from i1 to i0, ~e is returned (the complement) and
edgeArray[e] is set to f. Otherwise, a new edge is created from i0 to i1
with first face index f and its index is returned.
@param vertexArray Vertex positions to use when deciding colocation.
@param area Area of face f. When multiple edges of the same direction
are found between the same vertices (usually because of degenerate edges)
the face with larger area is kept in the edge table.
*/
static int findEdgeIndex(
const Array& vertexArray,
Array& geometricEdgeArray,
int i0, int i1, int f, double area);
};
}
#ifdef G3D_WINDOWS
#pragma warning( pop )
#endif
#endif