mxw_wotlk_azerothcore/deps/g3dlite/source/MeshAlg.cpp

642 lines
18 KiB
C++
Raw Permalink Normal View History

2020-10-30 23:45:46 -04:00
/**
@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 <climits>
namespace G3D {
const int MeshAlg::Face::NONE = INT_MIN;
void MeshAlg::generateGrid(
Array<Vector3>& vertex,
Array<Vector2>& texCoord,
Array<int>& 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<int> 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<Vector3>(vertex));
texCoord.append(Array<Vector2>(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<int>& indexArray) {
Array<Face> faceArray;
Array<Vertex> vertexArray;
Array<Edge> edgeArray;
Array<Vector3> faceNormalArray;
computeAdjacency(geometry.vertexArray, indexArray, faceArray, edgeArray, vertexArray);
computeNormals(geometry.vertexArray, faceArray, vertexArray,
geometry.normalArray, faceNormalArray);
}
void MeshAlg::computeNormals(
const Array<Vector3>& vertexGeometry,
const Array<Face>& faceArray,
const Array< Array<int> >& adjacentFaceArray,
Array<Vector3>& vertexNormalArray,
Array<Vector3>& faceNormalArray) {
// Construct a fake vertex array for backwards compatibility
Array<Vertex> 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<Vector3>& vertexGeometry,
const Array<Face>& faceArray,
const Array<Vertex>& vertexArray,
Array<Vector3>& vertexNormalArray,
Array<Vector3>& 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<Vector3>& vertexArray,
const Array<MeshAlg::Face>& faceArray,
Array<Vector3>& 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<Vector3>& vertexArray,
const Array<MeshAlg::Face>& faceArray,
const Vector4& HP,
Array<bool>& 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<Vector3>& vertexArray,
const Array<MeshAlg::Face>& faceArray,
const Vector4& HP,
Array<bool>& backface,
const Array<Vector3>& 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<int>& 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<Vector3>& vertexArray,
const Array<int>& indexArray,
double& minEdgeLength,
double& meanEdgeLength,
double& medianEdgeLength,
double& maxEdgeLength,
double& minFaceArea,
double& meanFaceArea,
double& medianFaceArea,
double& maxFaceArea) {
debugAssert(indexArray.size() % 3 == 0);
Array<double> area;
area.resize(indexArray.size() / 3);
Array<double> 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<MeshAlg::Edge>& 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<Vector3>& vertexArray,
const Array<int>& indexArray,
AABox& box,
Sphere& sphere) {
// Makes a copy so as to re-use the existing computebounds code
Array<Vector3> 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<Vector3>& 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<Vector3>& vertexArray,
const Array<Vector2>& texCoordArray,
const Array<Vector3>& vertexNormalArray,
const Array<Face>& faceArray,
Array<Vector3>& tangent,
Array<Vector3>& 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