/** @file MeshAlg.cpp @maintainer Morgan McGuire, http://graphics.cs.williams.edu @created 2003-09-14 @edited 2008-09-03 Copyright 2000-2009, Morgan McGuire. All rights reserved. */ #include "G3D/MeshAlg.h" #include "G3D/Table.h" #include "G3D/Set.h" #include "G3D/Box.h" #include "G3D/Sphere.h" #include "G3D/vectorMath.h" #include "G3D/AABox.h" #include "G3D/Image1.h" #include namespace G3D { const int MeshAlg::Face::NONE = INT_MIN; void MeshAlg::generateGrid( Array& vertex, Array& texCoord, Array& index, int wCells, int hCells, const Vector2& textureScale, bool spaceCentered, bool twoSided, const CoordinateFrame& xform, const Image1::Ref& height) { vertex.fastClear(); texCoord.fastClear(); index.fastClear(); // Generate vertices for (int z = 0; z <= hCells; ++z) { for (int x = 0; x <= wCells; ++x) { Vector3 v(x / (float)wCells, 0, z / (float)hCells); Vector2 t = v.xz() * textureScale; texCoord.append(t); if (height) { v.y = height->nearest(v.x * (height->width() - 1), v.z * (height->height() - 1)).value; } if (spaceCentered) { v -= Vector3(0.5f, 0, 0.5f); } v = xform.pointToWorldSpace(v); vertex.append(v); } } // Generate indices for (int z = 0; z < hCells; ++z) { for (int x = 0; x < wCells; ++x) { int A = x + z * (wCells + 1); int B = A + 1; int C = A + (wCells + 1); int D = C + 1; // A B // *-----* // | \ | // | \ | // *-----* // C D index.append(A, D, B); index.append(A, C, D); } } if (twoSided) { // The index array needs to have reversed winding for the bottom // and offset by the original number of vertices Array ti = index; ti.reverse(); for (int i = 0; i < ti.size(); ++i) { ti[i] += vertex.size(); } index.append(ti); // Duplicate the arrays vertex.append(Array(vertex)); texCoord.append(Array(texCoord)); } } MeshAlg::Face::Face() { for (int i = 0; i < 3; ++i) { edgeIndex[i] = 0; vertexIndex[i] = 0; } } MeshAlg::Edge::Edge() { for (int i = 0; i < 2; ++i) { vertexIndex[i] = 0; // Negative face indices are faces that don't exist faceIndex[i] = -1; } } MeshAlg::Geometry& MeshAlg::Geometry::operator=(const MeshAlg::Geometry& src) { vertexArray.resize(src.vertexArray.size()); normalArray.resize(src.vertexArray.size()); System::memcpy(vertexArray.getCArray(), src.vertexArray.getCArray(), sizeof(Vector3)*vertexArray.size()); System::memcpy(normalArray.getCArray(), src.normalArray.getCArray(), sizeof(Vector3)*normalArray.size()); return *this; } void MeshAlg::computeNormals( Geometry& geometry, const Array& indexArray) { Array faceArray; Array vertexArray; Array edgeArray; Array faceNormalArray; computeAdjacency(geometry.vertexArray, indexArray, faceArray, edgeArray, vertexArray); computeNormals(geometry.vertexArray, faceArray, vertexArray, geometry.normalArray, faceNormalArray); } void MeshAlg::computeNormals( const Array& vertexGeometry, const Array& faceArray, const Array< Array >& adjacentFaceArray, Array& vertexNormalArray, Array& faceNormalArray) { // Construct a fake vertex array for backwards compatibility Array fakeVertexArray; fakeVertexArray.resize(adjacentFaceArray.size()); for (int v = 0; v < adjacentFaceArray.size(); ++v) { fakeVertexArray[v].faceIndex.resize(adjacentFaceArray[v].size()); for (int i = 0; i < fakeVertexArray[v].faceIndex.size(); ++i) { fakeVertexArray[v].faceIndex[i] = adjacentFaceArray[v][i]; } // We leave out the edges because they aren't used to compute normals } computeNormals(vertexGeometry, faceArray, fakeVertexArray, vertexNormalArray, faceNormalArray); } void MeshAlg::computeNormals( const Array& vertexGeometry, const Array& faceArray, const Array& vertexArray, Array& vertexNormalArray, Array& faceNormalArray) { // Face normals (not unit length) faceNormalArray.resize(faceArray.size()); for (int f = 0; f < faceArray.size(); ++f) { const Face& face = faceArray[f]; Vector3 vertex[3]; for (int j = 0; j < 3; ++j) { vertex[j] = vertexGeometry[face.vertexIndex[j]]; debugAssert(vertex[j].isFinite()); } faceNormalArray[f] = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[0]); # ifdef G3D_DEBUG const Vector3& N = faceNormalArray[f]; debugAssert(N.isFinite()); # endif } // Per-vertex normals, computed by averaging vertexNormalArray.resize(vertexGeometry.size()); for (int v = 0; v < vertexNormalArray.size(); ++v) { Vector3 sum = Vector3::zero(); for (int k = 0; k < vertexArray[v].faceIndex.size(); ++k) { const int f = vertexArray[v].faceIndex[k]; sum += faceNormalArray[f]; } vertexNormalArray[v] = sum.directionOrZero(); # ifdef G3D_DEBUG const Vector3& N = vertexNormalArray[v]; debugAssert(N.isUnit() || N.isZero()); # endif } for (int f = 0; f < faceArray.size(); ++f) { faceNormalArray[f] = faceNormalArray[f].directionOrZero(); # ifdef G3D_DEBUG const Vector3& N = faceNormalArray[f]; debugAssert(N.isUnit() || N.isZero()); # endif } } void MeshAlg::computeFaceNormals( const Array& vertexArray, const Array& faceArray, Array& faceNormals, bool normalize) { faceNormals.resize(faceArray.size()); for (int f = 0; f < faceArray.size(); ++f) { const MeshAlg::Face& face = faceArray[f]; const Vector3& v0 = vertexArray[face.vertexIndex[0]]; const Vector3& v1 = vertexArray[face.vertexIndex[1]]; const Vector3& v2 = vertexArray[face.vertexIndex[2]]; faceNormals[f] = (v1 - v0).cross(v2 - v0); } if (normalize) { for (int f = 0; f < faceArray.size(); ++f) { faceNormals[f] = faceNormals[f].direction(); } } } void MeshAlg::identifyBackfaces( const Array& vertexArray, const Array& faceArray, const Vector4& HP, Array& backface) { Vector3 P = HP.xyz(); backface.resize(faceArray.size()); if (fuzzyEq(HP.w, 0.0f)) { // Infinite case for (int f = faceArray.size() - 1; f >= 0; --f) { const MeshAlg::Face& face = faceArray[f]; const Vector3& v0 = vertexArray[face.vertexIndex[0]]; const Vector3& v1 = vertexArray[face.vertexIndex[1]]; const Vector3& v2 = vertexArray[face.vertexIndex[2]]; const Vector3 N = (v1 - v0).cross(v2 - v0); backface[f] = N.dot(P) < 0; } } else { // Finite case for (int f = faceArray.size() - 1; f >= 0; --f) { const MeshAlg::Face& face = faceArray[f]; const Vector3& v0 = vertexArray[face.vertexIndex[0]]; const Vector3& v1 = vertexArray[face.vertexIndex[1]]; const Vector3& v2 = vertexArray[face.vertexIndex[2]]; const Vector3 N = (v1 - v0).cross(v2 - v0); backface[f] = N.dot(P - v0) < 0; } } } void MeshAlg::identifyBackfaces( const Array& vertexArray, const Array& faceArray, const Vector4& HP, Array& backface, const Array& faceNormals) { Vector3 P = HP.xyz(); backface.resize(faceArray.size()); if (fuzzyEq(HP.w, 0.0f)) { // Infinite case for (int f = faceArray.size() - 1; f >= 0; --f) { const Vector3& N = faceNormals[f]; backface[f] = N.dot(P) < 0; } } else { // Finite case for (int f = faceArray.size() - 1; f >= 0; --f) { const MeshAlg::Face& face = faceArray[f]; const Vector3& v0 = vertexArray[face.vertexIndex[0]]; const Vector3& N = faceNormals[f]; backface[f] = N.dot(P - v0) < 0; } } } void MeshAlg::createIndexArray(int n, Array& array, int start, int run, int skip) { debugAssert(skip >= 0); debugAssert(run >= 0); array.resize(n); if (skip == 0) { for (int i = 0; i < n; ++i) { array[i] = start + i; } } else { int rcount = 0; int j = start; for (int i = 0; i < n; ++i) { array[i] = j; ++j; ++rcount; if (rcount == run) { rcount = 0; j += skip; } } } } void MeshAlg::computeAreaStatistics( const Array& vertexArray, const Array& indexArray, double& minEdgeLength, double& meanEdgeLength, double& medianEdgeLength, double& maxEdgeLength, double& minFaceArea, double& meanFaceArea, double& medianFaceArea, double& maxFaceArea) { debugAssert(indexArray.size() % 3 == 0); Array area; area.resize(indexArray.size() / 3); Array magnitude; magnitude.resize(indexArray.size()); for (int i = 0; i < indexArray.size(); i += 3) { const Vector3& v0 = vertexArray[indexArray[i]]; const Vector3& v1 = vertexArray[indexArray[i + 1]]; const Vector3& v2 = vertexArray[indexArray[i + 2]]; area[i / 3] = (v1 - v0).cross(v2 - v0).magnitude() / 2.0; magnitude[i] = (v1 - v0).magnitude(); magnitude[i + 1] = (v2 - v1).magnitude(); magnitude[i + 2] = (v0 - v2).magnitude(); } area.sort(); magnitude.sort(); minEdgeLength = max(0.0, magnitude[0]); maxEdgeLength = max(0.0, magnitude.last()); medianEdgeLength = max(0.0, magnitude[magnitude.size() / 2]); meanEdgeLength = 0; for (int i = 0; i < magnitude.size(); ++i) { meanEdgeLength += magnitude[i]; } meanEdgeLength /= magnitude.size(); minFaceArea = max(0.0, area[0]); maxFaceArea = max(0.0, area.last()); medianFaceArea = max(0.0, area[area.size() / 2]); meanFaceArea = 0; for (int i = 0; i < area.size(); ++i) { meanFaceArea += area[i]; } meanFaceArea /= area.size(); // Make sure round-off hasn't pushed values less than zero meanFaceArea = max(0.0, meanFaceArea); meanEdgeLength = max(0.0, meanEdgeLength); } int MeshAlg::countBoundaryEdges(const Array& edgeArray) { int b = 0; for (int i = 0; i < edgeArray.size(); ++i) { if ((edgeArray[i].faceIndex[0] == MeshAlg::Face::NONE) != (edgeArray[i].faceIndex[1] == MeshAlg::Face::NONE)) { ++b; } } return b; } void MeshAlg::computeBounds( const Array& vertexArray, const Array& indexArray, AABox& box, Sphere& sphere) { // Makes a copy so as to re-use the existing computebounds code Array newArray; newArray.resize(indexArray.size()); for (int i = 0; i < indexArray.size(); ++i) { newArray[i] = vertexArray[indexArray[i]]; } computeBounds(newArray, box, sphere); } void MeshAlg::computeBounds( const Array& vertexArray, AABox& box, Sphere& sphere) { Vector3 xmin, xmax, ymin, ymax, zmin, zmax; // FIRST PASS: find 6 minima/maxima points xmin.x = ymin.y = zmin.z = finf(); xmax.x = ymax.y = zmax.z = -finf(); for (int v = 0; v < vertexArray.size(); ++v) { const Vector3& vertex = vertexArray[v]; if (vertex.x < xmin.x) { xmin = vertex; } if (vertex.x > xmax.x) { xmax = vertex; } if (vertex.y < ymin.y) { ymin = vertex; } if (vertex.y > ymax.y) { ymax = vertex; } if (vertex.z < zmin.z) { zmin = vertex; } if (vertex.z > zmax.z) { zmax = vertex; } } // Set points dia1 & dia2 to the maximally separated pair Vector3 dia1 = xmin; Vector3 dia2 = xmax; { // Set xspan = distance between the 2 points xmin & xmax (squared) float xspan = (xmax - xmin).squaredMagnitude(); // Same for y & z spans float yspan = (ymax - ymin).squaredMagnitude(); float zspan = (zmax - zmin).squaredMagnitude(); float maxspan = xspan; if (yspan > maxspan) { maxspan = yspan; dia1 = ymin; dia2 = ymax; } if (zspan > maxspan) { maxspan = zspan; dia1 = zmin; dia2 = zmax; } } // dia1, dia2 is a diameter of initial sphere // calc initial center Vector3 center = (dia1 + dia2) / 2.0; // calculate initial radius^2 and radius Vector3 d = dia2 - sphere.center; float radSq = d.squaredMagnitude(); float rad = sqrt(radSq); // SECOND PASS: increment current sphere float old_to_p, old_to_new; for (int v = 0; v < vertexArray.size(); ++v) { const Vector3& vertex = vertexArray[v]; d = vertex - center; float old_to_p_sq = d.squaredMagnitude(); // do r^2 test first if (old_to_p_sq > radSq) { // this point is outside of current sphere old_to_p = sqrt(old_to_p_sq); // calc radius of new sphere rad = (rad + old_to_p) / 2.0f; // for next r^2 compare radSq = rad * rad; old_to_new = old_to_p - rad; // calc center of new sphere center = (rad * center + old_to_new * vertex) / old_to_p; } } const Vector3 min(xmin.x, ymin.y, zmin.z); const Vector3 max(xmax.x, ymax.y, zmax.z); box = AABox(min, max); const float boxRadSq = (max - min).squaredMagnitude() * 0.25f; if (boxRadSq >= radSq){ if (isNaN(center.x) || ! isFinite(rad)) { sphere = Sphere(Vector3::zero(), finf()); } else { sphere = Sphere(center, rad); } } else { sphere = Sphere((max + min) * 0.5f, sqrt(boxRadSq)); } } void MeshAlg::computeTangentSpaceBasis( const Array& vertexArray, const Array& texCoordArray, const Array& vertexNormalArray, const Array& faceArray, Array& tangent, Array& binormal) { debugAssertM(faceArray.size() != 0, "Unable to calculate valid tangent space without faces."); tangent.resize(vertexArray.size()); binormal.resize(vertexArray.size()); // Zero the output arrays. System::memset(tangent.getCArray(), 0, sizeof(Vector3) * tangent.size()); System::memset(binormal.getCArray(), 0, sizeof(Vector3) * binormal.size()); // Iterate over faces, computing the tangent vectors for each // vertex. Accumulate those into the tangent and binormal arrays // and then orthonormalize at the end. for (int f = 0; f < faceArray.size(); ++f) { const Face& face = faceArray[f]; const int i0 = face.vertexIndex[0]; const int i1 = face.vertexIndex[1]; const int i2 = face.vertexIndex[2]; const Vector3& v0 = vertexArray[i0]; const Vector3& v1 = vertexArray[i1]; const Vector3& v2 = vertexArray[i2]; const Vector2& t0 = texCoordArray[i0]; const Vector2& t1 = texCoordArray[i1]; const Vector2& t2 = texCoordArray[i2]; // See http://www.terathon.com/code/tangent.html for a derivation of the following code // vertex edges Vector3 ve1 = v1 - v0; Vector3 ve2 = v2 - v0; // texture edges Vector2 te1 = t1 - t0; Vector2 te2 = t2 - t0; Vector3 n(ve1.cross(ve2).direction()); Vector3 t, b; float r = te1.x * te2.y - te1.y * te2.x; if (r == 0.0) { // degenerate case if (! n.isFinite() || n.isZero()) { n = Vector3::unitY(); } n.getTangents(t, b); } else { r = 1.0f / r; t = (te2.y * ve1 - te1.y * ve2) * r; b = (te2.x * ve1 - te1.x * ve2) * r; } for (int v = 0; v < 3; ++v) { int i = face.vertexIndex[v]; tangent[i] += t; binormal[i] += b; } } // Normalize the basis vectors for (int v = 0; v < vertexArray.size(); ++v) { // Remove the component parallel to the normal const Vector3& N = vertexNormalArray[v]; Vector3& T = tangent[v]; Vector3& B = binormal[v]; debugAssertM(N.isUnit() || N.isZero(), "Input normals must have unit length"); T -= T.dot(N) * N; B -= B.dot(N) * N; // Normalize T = T.directionOrZero(); B = B.directionOrZero(); } } } // G3D namespace