aboutsummaryrefslogtreecommitdiff
path: root/APEX_1.4/common/src/ApexTetrahedralizer.cpp
diff options
context:
space:
mode:
authorgit perforce import user <a@b>2016-10-25 12:29:14 -0600
committerSheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees>2016-10-25 18:56:37 -0500
commit3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch)
treefa6485c169e50d7415a651bf838f5bcd0fd3bfbd /APEX_1.4/common/src/ApexTetrahedralizer.cpp
downloadphysx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz
physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip
Initial commit:
PhysX 3.4.0 Update @ 21294896 APEX 1.4.0 Update @ 21275617 [CL 21300167]
Diffstat (limited to 'APEX_1.4/common/src/ApexTetrahedralizer.cpp')
-rw-r--r--APEX_1.4/common/src/ApexTetrahedralizer.cpp1266
1 files changed, 1266 insertions, 0 deletions
diff --git a/APEX_1.4/common/src/ApexTetrahedralizer.cpp b/APEX_1.4/common/src/ApexTetrahedralizer.cpp
new file mode 100644
index 00000000..b1e8fda0
--- /dev/null
+++ b/APEX_1.4/common/src/ApexTetrahedralizer.cpp
@@ -0,0 +1,1266 @@
+/*
+ * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * NVIDIA CORPORATION and its licensors retain all intellectual property
+ * and proprietary rights in and to this software, related documentation
+ * and any modifications thereto. Any use, reproduction, disclosure or
+ * distribution of this software and related documentation without an express
+ * license agreement from NVIDIA CORPORATION is strictly prohibited.
+ */
+
+
+#include "ApexTetrahedralizer.h"
+
+#include "ApexSDKIntl.h"
+
+#include "ApexCollision.h"
+#include "ApexMeshHash.h"
+#include "ApexSharedUtils.h"
+
+#include "PsSort.h"
+
+namespace nvidia
+{
+namespace apex
+{
+
+// check for sanity in between algorithms
+#define TETRALIZER_SANITY_CHECKS 0
+
+const uint32_t ApexTetrahedralizer::FullTetrahedron::sideIndices[4][3] = {{1, 2, 3}, {2, 0, 3}, {0, 1, 3}, {2, 1, 0}};
+
+void ApexTetrahedralizer::TetraEdgeList::sort()
+{
+ shdfnd::sort(mEdges.begin(), mEdges.size(), TetraEdge());
+}
+
+
+ApexTetrahedralizer::ApexTetrahedralizer(uint32_t subdivision) :
+ mMeshHash(NULL),
+ mSubdivision(subdivision),
+ mBoundDiagonal(0),
+ mFirstFarVertex(0),
+ mLastFarVertex(0)
+{
+ mBound.setEmpty();
+}
+
+
+
+ApexTetrahedralizer::~ApexTetrahedralizer()
+{
+ if (mMeshHash != NULL)
+ {
+ delete mMeshHash;
+ mMeshHash = NULL;
+ }
+}
+
+
+
+void ApexTetrahedralizer::registerVertex(const PxVec3& pos)
+{
+ TetraVertex v;
+ v.init(pos, 0);
+ mVertices.pushBack(v);
+ mBound.include(pos);
+}
+
+
+
+void ApexTetrahedralizer::registerTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
+{
+ mIndices.pushBack(i0);
+ mIndices.pushBack(i1);
+ mIndices.pushBack(i2);
+}
+
+
+
+void ApexTetrahedralizer::endRegistration(IProgressListener* progressListener)
+{
+
+ // hash mesh triangles for faster access
+ mBoundDiagonal = (mBound.minimum - mBound.maximum).magnitude();
+
+ HierarchicalProgressListener progress(100, progressListener);
+
+ if (mMeshHash == NULL)
+ {
+ mMeshHash = PX_NEW(ApexMeshHash)();
+ }
+
+ // clears the hash if it's not empty
+ mMeshHash->setGridSpacing(0.1f * mBoundDiagonal);
+
+ weldVertices();
+
+ for (uint32_t i = 0; i < mIndices.size(); i += 3)
+ {
+ PxBounds3 triangleBound;
+ triangleBound.minimum = triangleBound.maximum = mVertices[mIndices[i]].pos;
+ triangleBound.include(mVertices[mIndices[i + 1]].pos);
+ triangleBound.include(mVertices[mIndices[i + 2]].pos);
+ mMeshHash->add(triangleBound, i);
+ }
+
+
+ progress.setSubtaskWork(10, "Delaunay Tetrahedralization");
+ delaunayTetrahedralization(&progress);
+ progress.completeSubtask();
+
+ // neighbor info is gone from this point on!
+ compressTetrahedra(true);
+
+#if 1 // do we really need this?
+ uint32_t tetStart = 0;
+ uint32_t oldNumTets = mTetras.size();
+ uint32_t numSwapped = 0;
+ uint32_t lastNumSwapped = mTetras.size() * 10; // just a number that is way too big
+ uint32_t iterations = 0; // this one is pure safety
+ int32_t fraction = 50;
+ do
+ {
+ fraction /= 2;
+ progress.setSubtaskWork(fraction, "Swapping Tetrahedra");
+ lastNumSwapped = numSwapped;
+ numSwapped = swapTetrahedra(tetStart, &progress);
+ tetStart = oldNumTets;
+ oldNumTets = mTetras.size();
+ progress.completeSubtask();
+ }
+ while (numSwapped > 0 && (numSwapped < lastNumSwapped) && (iterations++ < 20));
+
+
+ // just to fill the entire fraction up
+ progress.setSubtaskWork(fraction, "Swapping Tetrahedra");
+ progress.completeSubtask();
+#endif
+
+
+
+ progress.setSubtaskWork(-1, "Removing outer tetrahedra");
+ removeOuterTetrahedra(&progress);
+ progress.completeSubtask();
+
+ compressTetrahedra(true);
+ compressVertices();
+
+ // release the indices
+ mIndices.clear();
+}
+
+
+
+void ApexTetrahedralizer::getVertices(PxVec3* data)
+{
+ for (uint32_t i = 0; i < mVertices.size(); i++)
+ {
+ data[i] = mVertices[i].pos;
+ }
+}
+
+
+
+void ApexTetrahedralizer::getIndices(uint32_t* data)
+{
+ for (uint32_t i = 0; i < mTetras.size(); i++)
+ {
+ for (uint32_t j = 0; j < 4; j++)
+ {
+ data[i * 4 + j] = (uint32_t)mTetras[i].vertexNr[j];
+ }
+ }
+}
+
+
+
+void ApexTetrahedralizer::weldVertices()
+{
+ if (mSubdivision < 1)
+ {
+ return;
+ }
+
+ const float relativeThreshold = 0.3f;
+
+ const float d = mBoundDiagonal / mSubdivision * relativeThreshold;
+ const float d2 = d * d;
+
+ uint32_t readIndex = 0;
+ uint32_t writeIndex = 0;
+ physx::Array<int32_t> old2new(mVertices.size(), -1);
+ while (readIndex < mVertices.size())
+ {
+ for (uint32_t i = 0; i < writeIndex; i++)
+ {
+ if ((mVertices[i].pos - mVertices[readIndex].pos).magnitudeSquared() < d2)
+ {
+ old2new[readIndex] = (int32_t)i;
+ break;
+ }
+ }
+
+ if (old2new[readIndex] == -1)
+ {
+ old2new[readIndex] = (int32_t)writeIndex;
+ mVertices[writeIndex] = mVertices[readIndex];
+ writeIndex++;
+ }
+ readIndex++;
+ }
+
+ if (readIndex == writeIndex)
+ {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mIndices.size(); i++)
+ {
+ PX_ASSERT(old2new[mIndices[i]] != -1);
+ mIndices[i] = (uint32_t)old2new[mIndices[i]];
+ }
+}
+
+
+void ApexTetrahedralizer::delaunayTetrahedralization(IProgressListener* progress)
+{
+ physx::Array<TetraEdge> edges;
+ TetraEdge edge;
+
+ // start with huge tetrahedron
+ mTetras.clear();
+
+ const float a = 3.0f * mBoundDiagonal;
+ const float x = 0.5f * a;
+ const float y0 = x / PxSqrt(3.0f);
+ const float y1 = x * PxSqrt(3.0f) - y0;
+ const float z0 = 0.25f * PxSqrt(6.0f) * a;
+ const float z1 = a * PxSqrt(6.0f) / 3.0f - z0;
+
+ PxVec3 center = mBound.getCenter();
+
+ mFirstFarVertex = mVertices.size();
+ PxVec3 p0(-x, -y0, -z1);
+ registerVertex(center + p0);
+ PxVec3 p1(x, -y0, -z1);
+ registerVertex(center + p1);
+ PxVec3 p2(0, y1, -z1);
+ registerVertex(center + p2);
+ PxVec3 p3(0, 0, z0);
+ registerVertex(center + p3);
+ mLastFarVertex = mVertices.size() - 1;
+
+ // do not update it as these vertices are irrelevant
+ //mBoundDiagonal = mBound.minimum.distance(mBound.maximum);
+
+ FullTetrahedron tetra;
+ tetra.set((int32_t)mFirstFarVertex, (int32_t)mFirstFarVertex + 1, (int32_t)mFirstFarVertex + 2, (int32_t)mFirstFarVertex + 3);
+ mTetras.pushBack(tetra);
+
+ // build Delaunay triangulation iteratively
+
+ for (uint32_t i = 0; i < mFirstFarVertex; i++)
+ {
+
+ if (progress != NULL && (i & 0x7f) == 0)
+ {
+ const int32_t percent = (int32_t)(100 * i / mFirstFarVertex);
+ progress->setProgress(percent);
+ }
+
+ PxVec3& v = mVertices[i].pos;
+ /// \todo vertex reordering could speed up this search via warm start
+ int tNr = findSurroundingTetra(mTetras.size() - 1, v); // fast walk
+ if (tNr == -1)
+ {
+ continue;
+ }
+ PX_ASSERT(tNr >= 0);
+
+ const uint32_t newTetraNr = mTetras.size();
+ const float volumeDiff = retriangulate((uint32_t)tNr, i);
+
+#if TETRALIZER_SANITY_CHECKS
+ if (PxAbs(volumeDiff) > 0.001f)
+ {
+ APEX_INTERNAL_ERROR("Volume added: %f", volumeDiff);
+ }
+#else
+ PX_UNUSED(volumeDiff);
+#endif
+
+ edges.clear();
+ for (uint32_t j = newTetraNr; j < mTetras.size(); j++)
+ {
+ FullTetrahedron& newTet = mTetras[j];
+ edge.init(newTet.vertexNr[2], newTet.vertexNr[3], (int32_t)j, 1);
+ edges.pushBack(edge);
+ edge.init(newTet.vertexNr[3], newTet.vertexNr[1], (int32_t)j, 2);
+ edges.pushBack(edge);
+ edge.init(newTet.vertexNr[1], newTet.vertexNr[2], (int32_t)j, 3);
+ edges.pushBack(edge);
+ }
+
+ shdfnd::sort(edges.begin(), edges.size(), TetraEdge());
+
+ for (uint32_t j = 0; j < edges.size(); j += 2)
+ {
+ TetraEdge& edge0 = edges[j];
+ TetraEdge& edge1 = edges[j + 1];
+ PX_ASSERT(edge0 == edge1);
+ mTetras[edge0.tetraNr].neighborNr[(uint32_t)edge0.neighborNr] = (int32_t)edge1.tetraNr;
+ mTetras[edge1.tetraNr].neighborNr[(uint32_t)edge1.neighborNr] = (int32_t)edge0.tetraNr;
+ }
+ }
+}
+
+
+
+
+
+int32_t ApexTetrahedralizer::findSurroundingTetra(uint32_t startTetra, const PxVec3& p) const
+{
+ // find the tetrahedra which contains the vertex
+ // by walking through mesh O(n ^ (1/3))
+
+ if (mTetras.empty())
+ {
+ return -1;
+ }
+
+ PX_ASSERT(mTetras[startTetra].bDeleted == 0);
+
+
+ uint32_t iterationCounter = 0;
+ const uint32_t iterationCounterMax = PxMin(1000u, mTetras.size());
+
+ int32_t tetNr = (int32_t)startTetra;
+ while (tetNr >= 0 && iterationCounter++ < iterationCounterMax)
+ {
+ const FullTetrahedron& tetra = mTetras[(uint32_t)tetNr];
+ PX_ASSERT(tetra.bDeleted == 0);
+
+ const PxVec3& p0 = mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ PxVec3 q = p - p0;
+ PxVec3 q0 = mVertices[(uint32_t)tetra.vertexNr[1]].pos - p0;
+ PxVec3 q1 = mVertices[(uint32_t)tetra.vertexNr[2]].pos - p0;
+ PxVec3 q2 = mVertices[(uint32_t)tetra.vertexNr[3]].pos - p0;
+ PxMat33 m(q0,q1,q2);
+ const float det = m.getDeterminant();
+ m.column0 = q;
+ const float x = m.getDeterminant();
+ if (x < 0.0f && tetra.neighborNr[1] >= 0)
+ {
+ tetNr = tetra.neighborNr[1];
+ continue;
+ }
+
+ m.column0 = q0;
+ m.column1 = q;
+ const float y = m.getDeterminant();
+ if (y < 0.0f && tetra.neighborNr[2] >= 0)
+ {
+ tetNr = tetra.neighborNr[2];
+ continue;
+ }
+
+ m.column1 = q1;
+ m.column2 = q;
+ const float z = m.getDeterminant();
+
+ if (z < 0.0f && tetra.neighborNr[3] >= 0)
+ {
+ tetNr = tetra.neighborNr[3];
+ continue;
+ }
+
+ if (x + y + z > det && tetra.neighborNr[0] >= 0)
+ {
+ tetNr = tetra.neighborNr[0];
+ continue;
+ }
+
+#if TETRALIZER_SANITY_CHECKS
+ static uint32_t maxIterationCounter = 0;
+ maxIterationCounter = PxMax(maxIterationCounter, iterationCounter);
+#endif
+ return tetNr;
+ }
+ if (iterationCounter == iterationCounterMax + 1)
+ {
+ // search all vertices
+ for (uint32_t i = 0; i < mVertices.size(); i++)
+ {
+ if (mVertices[i].pos == p)
+ {
+ PX_ASSERT(!mVertices[i].isDeleted());
+ return -1;
+ }
+ }
+
+ uint32_t nbDeleted = 0;
+ for (uint32_t i = 0; i < mTetras.size(); i++)
+ {
+ const FullTetrahedron& tetra = mTetras[i];
+ if (tetra.bDeleted == 0)
+ {
+ if (pointInTetra(tetra, p))
+ {
+ if (tetra.bDeleted == 0)
+ {
+ return (int32_t)i;
+ }
+ nbDeleted++;
+ }
+ }
+ }
+#if defined(_DEBUG) || TETRALIZER_SANITY_CHECKS
+ // PH: Do not give out this warning/error when running in release mode
+ APEX_INTERNAL_ERROR("Failed to find tetra for hull vertex");
+ //debugPoints.pushBack(p);
+#endif
+ }
+ return -1;
+}
+
+
+
+float ApexTetrahedralizer::retriangulate(const uint32_t tetraNr, uint32_t vertexNr)
+{
+ PX_ASSERT(tetraNr < mTetras.size());
+ PX_ASSERT(vertexNr < mVertices.size());
+
+ if (mTetras[tetraNr].bDeleted == 1)
+ {
+ return 0;
+ }
+
+#if TETRALIZER_SANITY_CHECKS
+ const float volumeDeleted = getTetraVolume(mTetras[tetraNr].vertexNr[0], mTetras[tetraNr].vertexNr[1], mTetras[tetraNr].vertexNr[2], mTetras[tetraNr].vertexNr[3]);;
+#endif
+ float volumeCreated = 0;
+
+ mTetras[tetraNr].bDeleted = 1;
+
+ PxVec3& v = mVertices[vertexNr].pos;
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ int n = mTetras[tetraNr].neighborNr[i];
+ if (n >= 0 && mTetras[(uint32_t)n].bDeleted == 1)
+ {
+ continue;
+ }
+
+ if (n >= 0 && pointInCircumSphere(mTetras[(uint32_t)n], v))
+ {
+ volumeCreated += retriangulate((uint32_t)n, vertexNr);
+ }
+ else
+ {
+ FullTetrahedron& tetra = mTetras[tetraNr];
+ FullTetrahedron tNew;
+ tNew.set((int32_t)vertexNr,
+ tetra.vertexNr[FullTetrahedron::sideIndices[i][0]],
+ tetra.vertexNr[FullTetrahedron::sideIndices[i][1]],
+ tetra.vertexNr[FullTetrahedron::sideIndices[i][2]]
+ );
+
+#if TETRALIZER_SANITY_CHECKS
+ volumeCreated += getTetraVolume(tNew.vertexNr[0], tNew.vertexNr[1], tNew.vertexNr[2], tNew.vertexNr[3]);
+#endif
+
+ tNew.neighborNr[0] = n;
+
+ if (n >= 0)
+ {
+ PX_ASSERT(mTetras[(uint32_t)n].neighborOf(tNew.vertexNr[1], tNew.vertexNr[2], tNew.vertexNr[3]) == (int32_t)tetraNr);
+ mTetras[(uint32_t)n].neighborOf(tNew.vertexNr[1], tNew.vertexNr[2], tNew.vertexNr[3]) = (int32_t)mTetras.size();
+ }
+ mTetras.pushBack(tNew);
+ }
+ }
+#if TETRALIZER_SANITY_CHECKS
+ return volumeCreated - volumeDeleted;
+#else
+ return 0;
+#endif
+}
+
+
+
+uint32_t ApexTetrahedralizer::swapTetrahedra(uint32_t startTet, IProgressListener* progress)
+{
+ PX_ASSERT(mMeshHash != NULL);
+
+ const float threshold = 0.05f * mBoundDiagonal / mSubdivision;
+
+ TetraEdgeList edges;
+ edges.mEdges.reserve(mTetras.size() * 6);
+ for (uint32_t i = startTet; i < mTetras.size(); i++)
+ {
+ FullTetrahedron& t = mTetras[i];
+ if (t.bDeleted == 1)
+ {
+ continue;
+ }
+
+ TetraEdge edge;
+ edge.init(t.vertexNr[0], t.vertexNr[1], (int32_t)i);
+ edges.add(edge);
+ edge.init(t.vertexNr[1], t.vertexNr[2], (int32_t)i);
+ edges.add(edge);
+ edge.init(t.vertexNr[2], t.vertexNr[0], (int32_t)i);
+ edges.add(edge);
+ edge.init(t.vertexNr[0], t.vertexNr[3], (int32_t)i);
+ edges.add(edge);
+ edge.init(t.vertexNr[1], t.vertexNr[3], (int32_t)i);
+ edges.add(edge);
+ edge.init(t.vertexNr[2], t.vertexNr[3], (int32_t)i);
+ edges.add(edge);
+ }
+ edges.sort();
+
+
+ uint32_t index = 0;
+ uint32_t progressCounter = 0;
+ uint32_t numEdgesSwapped = 0;
+ while (index < edges.numEdges())
+ {
+ if (progress != NULL && ((progressCounter++ & 0xf) == 0))
+ {
+ const int32_t percent = (int32_t)(100 * index / edges.numEdges());
+ progress->setProgress(percent);
+ }
+ TetraEdge edge = edges[index];
+ uint32_t vNr[2] = { edge.vNr0, edge.vNr1 };
+
+ index++;
+ while (index < edges.numEdges() && edges[index] == edge)
+ {
+ index++;
+ }
+
+ if (isFarVertex(vNr[0]) || isFarVertex(vNr[1]))
+ {
+ continue;
+ }
+
+ const PxVec3 v0 = mVertices[vNr[0]].pos;
+ const PxVec3 v1 = mVertices[vNr[1]].pos;
+ PxBounds3 edgeBounds;
+ edgeBounds.setEmpty();
+ edgeBounds.include(v0);
+ edgeBounds.include(v1);
+ mMeshHash->queryUnique(edgeBounds, mTempItemIndices);
+ if (mTempItemIndices.size() == 0)
+ {
+ continue;
+ }
+
+ bool cut = false;
+ for (uint32_t k = 0; k < mTempItemIndices.size(); k++)
+ {
+ uint32_t triNr = mTempItemIndices[k];
+ PX_ASSERT(triNr % 3 == 0);
+ uint32_t* triangle = &mIndices[triNr];
+
+ // if one vertex is part of the iso surface
+ if (triangleContainsVertexNr(triangle, vNr, 2))
+ {
+ continue;
+ }
+
+ const PxVec3 p0 = mVertices[triangle[0]].pos;
+ const PxVec3 p1 = mVertices[triangle[1]].pos;
+ const PxVec3 p2 = mVertices[triangle[2]].pos;
+
+ PxBounds3 triBounds;
+ triBounds.minimum = triBounds.maximum = p0;
+ triBounds.include(p1);
+ triBounds.include(p2);
+
+ if (!triBounds.intersects(edgeBounds))
+ {
+ continue;
+ }
+
+ PxVec3 normal = (p1 - p0).cross(p2 - p0);
+ normal.normalize();
+
+ if (PxAbs(normal.dot(v0 - p0)) < threshold)
+ {
+ continue;
+ }
+ if (PxAbs(normal.dot(v1 - p0)) < threshold)
+ {
+ continue;
+ }
+
+ float t, u, v;
+ if (!APEX_RayTriangleIntersect(v0, v1 - v0, p0, p1, p2, t, u, v))
+ {
+ continue;
+ }
+
+ // PH: I guess we don't need to cut when edge does not intersect triangle
+ if (t < 0 || t > 1)
+ {
+ continue;
+ }
+
+ cut = true;
+ break;
+ }
+ if (cut)
+ {
+#if TETRAHEDRALIZER_DEBUG_RENDERING
+ debugLines.pushBack(mVertices[vNr[0]].pos);
+ debugLines.pushBack(mVertices[vNr[1]].pos);
+#endif
+ numEdgesSwapped += swapEdge(vNr[0], vNr[1]) ? 1 : 0;
+ }
+ }
+
+ return numEdgesSwapped;
+}
+
+
+
+bool ApexTetrahedralizer::swapEdge(uint32_t v0, uint32_t v1)
+{
+ physx::Array<int32_t> borderEdges;
+ physx::Array<FullTetrahedron> newTetras;
+
+ uint32_t nbDeleted = 0;
+
+ mTempItemIndices.clear();
+
+ for (uint32_t i = 0; i < mTetras.size(); i++)
+ {
+ FullTetrahedron& t = mTetras[i];
+
+ if (t.bDeleted == 1)
+ {
+ continue;
+ }
+
+ if (!t.containsVertex((int32_t)v0) || !t.containsVertex((int32_t)v1))
+ {
+ continue;
+ }
+
+ //t.bDeleted = 1;
+ mTempItemIndices.pushBack(i);
+ nbDeleted++;
+ int32_t v2, v3;
+ t.get2OppositeVertices((int32_t)v0, (int32_t)v1, v2, v3);
+ if (getTetraVolume((int32_t)v0, (int32_t)v1, v2, v3) >= 0.0f)
+ {
+ borderEdges.pushBack(v2);
+ borderEdges.pushBack(v3);
+ }
+ else
+ {
+ borderEdges.pushBack(v3);
+ borderEdges.pushBack(v2);
+ }
+ }
+
+ if (borderEdges.size() < 6)
+ {
+ return false;
+ }
+
+ // start with first edge
+ physx::Array<int32_t> borderVertices;
+ physx::Array<float> borderQuality;
+ borderVertices.pushBack(borderEdges[borderEdges.size() - 2]);
+ borderVertices.pushBack(borderEdges[borderEdges.size() - 1]);
+ borderEdges.popBack();
+ borderEdges.popBack();
+
+ // construct border
+ int vEnd = borderVertices[1];
+ while (borderEdges.size() > 0)
+ {
+ uint32_t i = 0;
+ uint32_t num = borderEdges.size();
+ for (; i < num - 1; i += 2)
+ {
+ if (borderEdges[i] == vEnd)
+ {
+ vEnd = borderEdges[i + 1];
+ break;
+ }
+ }
+ // not connected
+ if (i >= num)
+ {
+ return false;
+ }
+
+ borderVertices.pushBack(vEnd);
+ borderEdges[i] = borderEdges[num - 2];
+ borderEdges[i + 1] = borderEdges[num - 1];
+ borderEdges.popBack();
+ borderEdges.popBack();
+ }
+
+ // not circular
+ if (borderVertices[0] != borderVertices[borderVertices.size() - 1])
+ {
+ return false;
+ }
+
+ borderVertices.popBack();
+
+ if (borderVertices.size() < 3)
+ {
+ return false;
+ }
+
+ // generate tetrahedra
+ FullTetrahedron tetra0, tetra1;
+ borderQuality.resize(borderVertices.size());
+ while (borderVertices.size() > 3)
+ {
+ uint32_t num = borderVertices.size();
+ uint32_t i0, i1, i2;
+ for (i0 = 0; i0 < num; i0++)
+ {
+ i1 = (i0 + 1) % num;
+ i2 = (i1 + 1) % num;
+ tetra0.set(borderVertices[i0], borderVertices[i1], borderVertices[i2], (int32_t)v1);
+ borderQuality[i1] = getTetraQuality(tetra0);
+ }
+
+ float maxQ = 0.0f;
+ int32_t maxI0 = -1;
+ for (i0 = 0; i0 < num; i0++)
+ {
+ i1 = (i0 + 1) % num;
+ i2 = (i1 + 1) % num;
+ tetra0.set(borderVertices[i0], borderVertices[i1], borderVertices[i2], (int32_t)v1);
+ tetra1.set(borderVertices[i2], borderVertices[i1], borderVertices[i0], (int32_t)v0);
+ if (getTetraVolume(tetra0) < 0.0f || getTetraVolume(tetra1) < 0.0f)
+ {
+ continue;
+ }
+
+ bool ear = true;
+ for (uint32_t i = 0; i < num; i++)
+ {
+ if (i == i0 || i == i1 || i == i2)
+ {
+ continue;
+ }
+
+ PxVec3& pos = mVertices[(uint32_t)borderVertices[i]].pos;
+ if (pointInTetra(tetra0, pos) || pointInTetra(tetra1, pos))
+ {
+ ear = false;
+ }
+ }
+
+ if (!ear)
+ {
+ continue;
+ }
+
+ float q = (1.0f - borderQuality[i0]) + borderQuality[i1] + (1.0f - borderQuality[i2]);
+
+ if (maxI0 < 0 || q > maxQ)
+ {
+ maxQ = q;
+ maxI0 = (int32_t)i0;
+ }
+ }
+ if (maxI0 < 0)
+ {
+ return false;
+ }
+
+ i0 = (uint32_t)maxI0;
+ i1 = (i0 + 1) % num;
+ i2 = (i1 + 1) % num;
+ tetra0.set(borderVertices[i0], borderVertices[i1], borderVertices[i2], (int32_t)v1);
+ tetra1.set(borderVertices[i2], borderVertices[i1], borderVertices[i0], (int32_t)v0);
+
+ // add tetras, remove vertex;
+ newTetras.pushBack(tetra0);
+ newTetras.pushBack(tetra1);
+ for (uint32_t i = i1; i < num - 1; i++)
+ {
+ borderVertices[i] = borderVertices[i + 1];
+ }
+ borderVertices.popBack();
+ }
+ tetra0.set(borderVertices[0], borderVertices[1], borderVertices[2], (int32_t)v1);
+ tetra1.set(borderVertices[2], borderVertices[1], borderVertices[0], (int32_t)v0);
+
+ if (getTetraVolume(tetra0) < 0.0f || getTetraVolume(tetra1) < 0.0f)
+ {
+ return false;
+ }
+
+ newTetras.pushBack(tetra0);
+ newTetras.pushBack(tetra1);
+
+ PX_ASSERT(nbDeleted <= newTetras.size() + 1);
+
+ for (uint32_t i = 0; i < mTempItemIndices.size(); i++)
+ {
+ mTetras[mTempItemIndices[i]].bDeleted = 1;
+ }
+
+ // add new tetras;
+ for (uint32_t i = 0; i < newTetras.size(); i++)
+ {
+ mTetras.pushBack(newTetras[i]);
+ }
+
+ return true;
+}
+
+
+class F32Less
+{
+public:
+ PX_INLINE bool operator()(float v1, float v2) const
+ {
+ return v1 < v2;
+ }
+};
+
+
+
+bool ApexTetrahedralizer::removeOuterTetrahedra(IProgressListener* progress)
+{
+ const float EPSILON = 1e-5f;
+ PX_ASSERT((float)(mBoundDiagonal + EPSILON) > mBoundDiagonal);
+
+#if TETRAHEDRALIZER_DEBUG_RENDERING && TETRALIZER_SANITY_CHECKS
+ static uint32_t interesting = 0xffffffff;
+ debugBounds.clear();
+#endif
+
+ physx::Array<float> raycastHitTimes;
+
+ for (uint32_t i = 0; i < mTetras.size(); i++)
+ {
+ if (progress != NULL && (i & 0x7) == 0)
+ {
+ const int32_t percent = (int32_t)(100 * i / mTetras.size());
+ progress->setProgress(percent);
+ }
+ FullTetrahedron& tetra = mTetras[i];
+
+ if (isFarVertex((uint32_t)tetra.vertexNr[0]) || isFarVertex((uint32_t)tetra.vertexNr[1])
+ || isFarVertex((uint32_t)tetra.vertexNr[2]) || isFarVertex((uint32_t)tetra.vertexNr[3]))
+ {
+ tetra.bDeleted = 1;
+ }
+ else if (tetra.bDeleted == 0)
+ {
+ PxVec3 orig, dir;
+ orig = mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ orig += mVertices[(uint32_t)tetra.vertexNr[1]].pos;
+ orig += mVertices[(uint32_t)tetra.vertexNr[2]].pos;
+ orig += mVertices[(uint32_t)tetra.vertexNr[3]].pos;
+ orig *= 0.25f;
+
+ uint32_t numInside = 0; // test 6 standard rays, majority vote
+ for (uint32_t j = 0; j < 3; j++)
+ {
+ PxBounds3 rayBounds(orig, orig);
+ switch (j)
+ {
+ case 0:
+ {
+ rayBounds.maximum.x = mBound.maximum.x + EPSILON;
+ rayBounds.minimum.x = mBound.minimum.x - EPSILON;
+ dir = PxVec3(1.0f, 0.0f, 0.0f);
+ }
+ break;
+ case 1:
+ {
+ rayBounds.maximum.y = mBound.maximum.y + EPSILON;
+ rayBounds.minimum.y = mBound.minimum.y - EPSILON;
+ dir = PxVec3(0.0f, 1.0f, 0.0f);
+ }
+ break;
+ case 2:
+ {
+ rayBounds.maximum.z = mBound.maximum.z + EPSILON;
+ rayBounds.minimum.z = mBound.minimum.z - EPSILON;
+ dir = PxVec3(0.0f, 0.0f, 1.0f);
+ }
+ break;
+ }
+ mMeshHash->queryUnique(rayBounds, mTempItemIndices);
+
+ raycastHitTimes.clear();
+
+#if TETRAHEDRALIZER_DEBUG_RENDERING && TETRALIZER_SANITY_CHECKS
+ if (i == interesting)
+ {
+ debugBounds.pushBack(rayBounds.minimum);
+ debugBounds.pushBack(rayBounds.maximum);
+ }
+#endif
+
+ for (uint32_t k = 0; k < mTempItemIndices.size(); k++)
+ {
+ uint32_t indexNr = mTempItemIndices[k];
+ PX_ASSERT(indexNr % 3 == 0);
+
+ const PxVec3 p0 = mVertices[mIndices[indexNr + 0]].pos;
+ const PxVec3 p1 = mVertices[mIndices[indexNr + 1]].pos;
+ const PxVec3 p2 = mVertices[mIndices[indexNr + 2]].pos;
+ PxBounds3 triBounds;
+ triBounds.minimum = triBounds.maximum = p0;
+ triBounds.include(p1);
+ triBounds.include(p2);
+
+ if (!rayBounds.intersects(triBounds))
+ {
+ continue;
+ }
+
+#if TETRAHEDRALIZER_DEBUG_RENDERING && TETRALIZER_SANITY_CHECKS
+ if (i == interesting)
+ {
+ //debugTriangles.pushBack(p0);
+ //debugTriangles.pushBack(p1);
+ //debugTriangles.pushBack(p2);
+ debugBounds.pushBack(triBounds.minimum);
+ debugBounds.pushBack(triBounds.maximum);
+ }
+#endif
+
+
+ float t, u, v;
+ if (!APEX_RayTriangleIntersect(orig, dir, p0, p1, p2, t, u, v))
+ {
+ continue;
+ }
+
+ raycastHitTimes.pushBack(t);
+ }
+
+ if (!raycastHitTimes.empty())
+ {
+ shdfnd::sort(raycastHitTimes.begin(), raycastHitTimes.size(), F32Less());
+
+ uint32_t negative = 0, positive = 0;
+#if TETRALIZER_SANITY_CHECKS
+ bool report = false;//0 <= i && i < 1033;
+#endif
+ for (uint32_t k = 0; k < raycastHitTimes.size(); k++)
+ {
+ if (k > 0 && PxAbs(raycastHitTimes[k] - raycastHitTimes[k - 1]) < EPSILON)
+ {
+#if TETRALIZER_SANITY_CHECKS
+ report = true;
+#endif
+ // PH: This proved to not be working right, or not making any difference
+ //continue;
+ }
+ if (raycastHitTimes[k] < 0)
+ {
+ negative++;
+ }
+ else
+ {
+ positive++;
+ }
+ }
+ numInside += (positive & 0x1) + (negative & 0x1);
+#if TETRAHEDRALIZER_DEBUG_RENDERING && TETRALIZER_SANITY_CHECKS
+ if (report)
+ {
+ float scale = 1.01f;
+ debugLines.pushBack(worldRay.orig - worldRay.dir * scale);
+ debugLines.pushBack(worldRay.orig + worldRay.dir * scale);
+ }
+#endif
+ }
+ }
+ if (numInside < 3)
+ {
+ tetra.bDeleted = 1;
+ }
+#if TETRAHEDRALIZER_DEBUG_RENDERING && TETRALIZER_SANITY_CHECKS
+ if (numInside > 0 && numInside < 6)
+ {
+ debugTetras.pushBack(mVertices[mTetras[i].vertexNr[0]].pos);
+ debugTetras.pushBack(mVertices[mTetras[i].vertexNr[1]].pos);
+ debugTetras.pushBack(mVertices[mTetras[i].vertexNr[2]].pos);
+ debugTetras.pushBack(mVertices[mTetras[i].vertexNr[3]].pos);
+ }
+#endif
+
+ // remove degenerated tetrahedra (slivers)
+ float quality = getTetraQuality(tetra);
+ PX_ASSERT(quality >= 0);
+ PX_ASSERT(quality <= 1);
+ tetra.quality = (uint32_t)(quality * 1023.0f);
+ if (quality < 0.001f)
+ {
+ tetra.bDeleted = 1;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+
+void ApexTetrahedralizer::updateCircumSphere(FullTetrahedron& tetra) const
+{
+ if (tetra.bCircumSphereDirty == 0)
+ {
+ return;
+ }
+
+ const PxVec3 p0 = mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3 b = mVertices[(uint32_t)tetra.vertexNr[1]].pos - p0;
+ const PxVec3 c = mVertices[(uint32_t)tetra.vertexNr[2]].pos - p0;
+ const PxVec3 d = mVertices[(uint32_t)tetra.vertexNr[3]].pos - p0;
+
+ float det = b.x * (c.y * d.z - c.z * d.y) - b.y * (c.x * d.z - c.z * d.x) + b.z * (c.x * d.y - c.y * d.x);
+
+ if (det == 0.0f)
+ {
+ tetra.center = p0;
+ tetra.radiusSquared = 0.0f;
+ return; // singular case
+ }
+
+ det *= 2.0f;
+ PxVec3 v = c.cross(d) * b.dot(b) + d.cross(b) * c.dot(c) + b.cross(c) * d.dot(d);
+ v /= det;
+
+ tetra.radiusSquared = v.magnitudeSquared();
+ tetra.center = p0 + v;
+ tetra.bCircumSphereDirty = 0;
+}
+
+
+
+bool ApexTetrahedralizer::pointInCircumSphere(FullTetrahedron& tetra, const PxVec3& p) const
+{
+ updateCircumSphere(tetra);
+ return (tetra.center - p).magnitudeSquared() < tetra.radiusSquared;
+}
+
+
+
+bool ApexTetrahedralizer::pointInTetra(const FullTetrahedron& tetra, const PxVec3& p) const
+{
+ const PxVec3 q = p - mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3 q0 = mVertices[(uint32_t)tetra.vertexNr[1]].pos - mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3 q1 = mVertices[(uint32_t)tetra.vertexNr[2]].pos - mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3 q2 = mVertices[(uint32_t)tetra.vertexNr[3]].pos - mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+
+ PxMat33 m(q0,q1,q2);
+ float det = m.getDeterminant();
+ m.column0 = q;
+ float x = m.getDeterminant();
+ m.column0 = q0;
+ m.column1 = q;
+ float y = m.getDeterminant();
+ m.column1 = q1;
+ m.column2 = q;
+ float z = m.getDeterminant();
+ if (det < 0.0f)
+ {
+ x = -x;
+ y = -y;
+ z = -z;
+ det = -det;
+ }
+
+ if (x < 0.0f || y < 0.0f || z < 0.0f)
+ {
+ return false;
+ }
+
+ return (x + y + z < det);
+}
+
+
+
+float ApexTetrahedralizer::getTetraVolume(const FullTetrahedron& tetra) const
+{
+ const PxVec3 v0 = mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3 v1 = mVertices[(uint32_t)tetra.vertexNr[1]].pos - v0;
+ const PxVec3 v2 = mVertices[(uint32_t)tetra.vertexNr[2]].pos - v0;
+ const PxVec3 v3 = mVertices[(uint32_t)tetra.vertexNr[3]].pos - v0;
+ return v3.dot(v1.cross(v2)) / 6.0f;
+}
+
+
+
+float ApexTetrahedralizer::getTetraVolume(int32_t v0, int32_t v1, int32_t v2, int32_t v3) const
+{
+ FullTetrahedron t;
+ t.set(v0, v1, v2, v3);
+ return getTetraVolume(t);
+}
+
+
+
+float ApexTetrahedralizer::getTetraQuality(const FullTetrahedron& tetra) const
+{
+ const float sqrt2 = 1.4142135623f;
+
+ const float e = getTetraLongestEdge(tetra);
+ if (e == 0.0f)
+ {
+ return 0.0f;
+ }
+
+ // for regular tetrahedron vol * 6 * sqrt(2) = s^3 -> quality = 1.0
+ return PxAbs(getTetraVolume(tetra)) * 6.0f * sqrt2 / (e * e * e);
+}
+
+
+
+float ApexTetrahedralizer::getTetraLongestEdge(const FullTetrahedron& tetra) const
+{
+ const PxVec3& v0 = mVertices[(uint32_t)tetra.vertexNr[0]].pos;
+ const PxVec3& v1 = mVertices[(uint32_t)tetra.vertexNr[1]].pos;
+ const PxVec3& v2 = mVertices[(uint32_t)tetra.vertexNr[2]].pos;
+ const PxVec3& v3 = mVertices[(uint32_t)tetra.vertexNr[3]].pos;
+ float max = (v0 - v1).magnitudeSquared();;
+ max = PxMax(max, (v0 - v2).magnitudeSquared());
+ max = PxMax(max, (v0 - v3).magnitudeSquared());
+ max = PxMax(max, (v1 - v2).magnitudeSquared());
+ max = PxMax(max, (v1 - v3).magnitudeSquared());
+ max = PxMax(max, (v2 - v3).magnitudeSquared());
+ return PxSqrt(max);
+}
+
+
+
+bool ApexTetrahedralizer::triangleContainsVertexNr(uint32_t* triangle, uint32_t* vertexNumber, uint32_t nbVertices)
+{
+ if (triangle != NULL && vertexNumber != NULL && nbVertices > 0)
+ {
+ for (uint32_t i = 0; i < nbVertices; i++)
+ {
+ if (triangle[0] == vertexNumber[i] || triangle[1] == vertexNumber[i] || triangle[2] == vertexNumber[i])
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+
+void ApexTetrahedralizer::compressTetrahedra(bool trashNeighbours)
+{
+ if (trashNeighbours)
+ {
+ uint32_t i = 0;
+ while (i < mTetras.size())
+ {
+ if (mTetras[i].bDeleted == 1)
+ {
+ mTetras.replaceWithLast(i);
+ }
+ else
+ {
+ mTetras[i].neighborNr[0] = mTetras[i].neighborNr[1] = mTetras[i].neighborNr[2] = mTetras[i].neighborNr[3] = -1;
+ i++;
+ }
+ }
+ }
+ else
+ {
+ physx::Array<int32_t> oldToNew(mTetras.size());
+
+ uint32_t i = 0;
+ while (i < mTetras.size())
+ {
+ if (mTetras[i].bDeleted == 1)
+ {
+ oldToNew[i] = -1;
+ if (mTetras[mTetras.size() - 1].bDeleted == 1)
+ {
+ oldToNew[mTetras.size() - 1] = -1;
+ }
+ else
+ {
+ oldToNew[mTetras.size() - 1] = (int32_t)i;
+ mTetras[i] = mTetras[mTetras.size() - 1];
+ i++;
+ }
+ mTetras.popBack();
+ }
+ else
+ {
+ oldToNew[i] = (int32_t)i;
+ i++;
+ }
+ }
+
+ for (i = 0; i < mTetras.size(); i++)
+ {
+ FullTetrahedron& t = mTetras[i];
+ for (uint32_t j = 0; j < 4; j++)
+ {
+ if (t.neighborNr[j] >= 0)
+ {
+ PX_ASSERT((uint32_t)t.neighborNr[j] < oldToNew.size());
+ t.neighborNr[j] = oldToNew[(uint32_t)t.neighborNr[j]];
+ }
+ }
+ }
+ }
+}
+
+
+
+void ApexTetrahedralizer::compressVertices()
+{
+ // remove vertices that are not referenced by any tetrahedra
+ physx::Array<int32_t> oldToNew;
+ physx::Array<TetraVertex> newVertices;
+
+ mBound.setEmpty();
+
+ oldToNew.resize(mVertices.size(), -1);
+
+ for (uint32_t i = 0; i < mTetras.size(); i++)
+ {
+ FullTetrahedron& t = mTetras[i];
+ for (uint32_t j = 0; j < 4; j++)
+ {
+ uint32_t vNr = (uint32_t)t.vertexNr[j];
+ if (oldToNew[vNr] < 0)
+ {
+ oldToNew[vNr] = (int32_t)newVertices.size();
+ newVertices.pushBack(mVertices[vNr]);
+ mBound.include(mVertices[vNr].pos);
+ }
+ t.vertexNr[j] = oldToNew[vNr];
+ }
+ }
+
+ mVertices.clear();
+ mVertices.resize(newVertices.size());
+ for (uint32_t i = 0; i < newVertices.size(); i++)
+ {
+ mVertices[i] = newVertices[i];
+ }
+}
+
+}
+} // end namespace nvidia::apex
+