Files
XCEngine/engine/third_party/physx/source/physxextensions/src/tet/ExtOctreeTetrahedralizer.cpp

726 lines
18 KiB
C++

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
#include "ExtOctreeTetrahedralizer.h"
#include "foundation/PxSort.h"
#include "foundation/PxQuat.h"
#include "CmRandom.h"
using namespace physx;
using namespace Ext;
// -------------------------------------------------------------------------------------
static const PxI32 childRelPos[8][3] = { {0,0,0}, {1,0,0},{0,1,0},{1,1,0}, {0,0,1}, {1,0,1},{0,1,1},{1,1,1} };
static const PxI32 cubeCorners[8][3] = { {0,0,0}, {1,0,0},{1,1,0},{0,1,0}, {0,0,1}, {1,0,1},{1,1,1},{0,1,1} };
//static const PxI32 cubeEdges[12][2] = { {0,1}, {1,2},{2,3},{3,0}, {0,4},{1,5},{2,6},{3,7},{4,5},{5,6},{6,7},{7,4} };
static const PxI32 tetFaces[4][3] = { {2,1,0}, {0,1,3}, {1,2,3}, {2,0,3} };
static const PxI32 cubeTets[6][4] = { {0,1,2,5}, {0,4,5,2}, {2,4,5,6}, {4,7,6,3}, {2,6,3,4}, {0,2,3,4} };
static const PxI32 cubeTetNeighbors[6][4] = { {-1,-1,-1,1}, {-1,5,2,0}, {1,4,-1,-1},
{-1,-1,-1,4}, {-1,2,3,5}, {-1,1,4,-1} };
// -------------------------------------------------------------------------------------
OctreeTetrahedralizer::OctreeTetrahedralizer()
{
clear();
}
// -------------------------------------------------------------------------------------
void OctreeTetrahedralizer::clearTets()
{
tetVerts.clear();
tetIds.clear();
firstFreeTet = -1;
currentTetMark = 0;
tetMarks.clear();
tetNeighbors.clear();
renderVerts.clear();
renderTriIds.clear();
firstABBVert = 0;
}
// -------------------------------------------------------------------------------------
void OctreeTetrahedralizer::clear()
{
surfaceVerts.clear();
surfaceTriIds.clear();
tetIds.clear();
tetNeighbors.clear();
cells.clear();
clearTets();
prevClip = -1.0f;
prevScale = -1.0f;
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::createTree()
{
Bounds3 bounds;
bounds.setEmpty();
for (PxI32 i = 0; i < PxI32(surfaceVerts.size()); i++)
{
const PxVec3& v = surfaceVerts[i];
bounds.include(PxVec3d(PxF64(v.x), PxF64(v.y), PxF64(v.z)));
}
bounds.expand(0.01);
PxVec3d dims = bounds.getDimensions();
PxF64 size = PxMax(dims.x, PxMax(dims.y, dims.z));
// create root
cells.resize(1);
cells.front().init();
cells.front().orig = bounds.minimum;
cells.front().size = size;
cells.front().depth = 0;
// insert vertices
PxI32 numVerts = PxI32(surfaceVerts.size());
for (PxI32 i = 0; i < numVerts; i++)
{
treeInsertVert(0, i);
}
}
// -------------------------------------------------------------------------------------
PxI32 OctreeTetrahedralizer::Cell::getChildNr(const PxVec3d& p)
{
if (firstChild < 0)
return -1;
PxI32 nr = 0;
if (p.x > orig.x + 0.5 * size) nr |= 1;
if (p.y > orig.y + 0.5 * size) nr |= 2;
if (p.z > orig.z + 0.5 * size) nr |= 4;
return firstChild + nr;
}
// -------------------------------------------------------------------------------------
void OctreeTetrahedralizer::treeInsertVert(PxI32 cellNr, PxI32 vertNr)
{
// inner node
if (cells[cellNr].firstChild >= 0)
{
treeInsertVert(cells[cellNr].getChildNr(surfaceVerts[vertNr]), vertNr);
return;
}
// add
vertsOfCell.add(cellNr, vertNr);
cells[cellNr].numVerts++;
if (cells[cellNr].numVerts <= maxVertsPerCell ||
cells[cellNr].depth >= maxTreeDepth)
return;
// split
PxI32 firstChild = cells.size();
cells[cellNr].firstChild = firstChild;
cells.resize(cells.size() + 8);
for (PxI32 i = 0; i < 8; i++)
{
Cell& child = cells[firstChild + i];
child.init();
child.depth = cells[cellNr].depth + 1;
child.size = cells[cellNr].size * 0.5;
child.orig = cells[cellNr].orig + PxVec3d(
childRelPos[i][0] * child.size,
childRelPos[i][1] * child.size,
childRelPos[i][2] * child.size);
}
PxI32 iterator, id;
vertsOfCell.initIteration(cellNr, iterator);
while (vertsOfCell.iterate(id, iterator))
treeInsertVert(cells[cellNr].getChildNr(surfaceVerts[id]), id);
vertsOfCell.removeAll(cellNr);
cells[cellNr].numVerts = 0;
}
// -----------------------------------------------------------------------------------
static PxVec3d jitter(const PxVec3d& p, Cm::RandomR250& random)
{
PxF64 eps = 0.001;
return PxVec3d(
p.x - eps + 2.0 * eps * PxF64(random.rand(0.0f, 1.0f)),
p.y - eps + 2.0 * eps * PxF64(random.rand(0.0f, 1.0f)),
p.z - eps + 2.0 * eps * PxF64(random.rand(0.0f, 1.0f)));
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::createTetVerts(bool includeOctreeNodes)
{
tetVerts.clear();
insideTester.init(surfaceVerts.begin(), PxI32(surfaceVerts.size()),
surfaceTriIds.begin(), PxI32(surfaceTriIds.size()) / 3);
for (PxI32 i = 0; i < PxI32(surfaceVerts.size()); i++)
{
const PxVec3& v = surfaceVerts[i];
tetVerts.pushBack(PxVec3d(PxF64(v.x), PxF64(v.y), PxF64(v.z)));
}
if (includeOctreeNodes)
{
PxArray<PxVec3d> treeVerts;
for (PxI32 i = 0; i < PxI32(cells.size()); i++)
{
PxF64 s = cells[i].size;
for (PxI32 j = 0; j < 8; j++) {
PxVec3d p = cells[i].orig + PxVec3d(
s * cubeCorners[j][0],
s * cubeCorners[j][1],
s * cubeCorners[j][2]);
treeVerts.pushBack(p);
}
}
// remove duplicates
PxF64 eps = 1e-8;
struct Ref
{
PxF64 d;
PxI32 vertNr;
bool operator < (const Ref& r) const
{
return d < r.d;
}
};
PxI32 numTreeVerts = PxI32(treeVerts.size());
PxArray<Ref> refs(numTreeVerts);
for (PxI32 i = 0; i < numTreeVerts; i++)
{
PxVec3d& p = treeVerts[i];
refs[i].d = p.x + 0.3 * p.y + 0.1 * p.z;
refs[i].vertNr = i;
}
PxSort(refs.begin(), refs.size());
PxArray<bool> duplicate(numTreeVerts, false);
PxI32 nr = 0;
Cm::RandomR250 random(0);
while (nr < numTreeVerts)
{
Ref& r = refs[nr];
nr++;
if (duplicate[r.vertNr])
continue;
PxVec3d& p = treeVerts[r.vertNr];
PxVec3d v = jitter(p, random);
if (insideTester.isInside(PxVec3(PxReal(v.x), PxReal(v.y), PxReal(v.z))))
tetVerts.pushBack(jitter(p, random));
PxI32 i = nr;
while (i < numTreeVerts && fabs(refs[i].d - r.d) < eps)
{
PxVec3d& q = treeVerts[refs[i].vertNr];
if ((p - q).magnitude() < eps)
duplicate[refs[i].vertNr] = true;
i++;
}
}
}
}
// -----------------------------------------------------------------------------------
PxVec3d OctreeTetrahedralizer::getTetCenter(PxI32 tetNr) const
{
return (tetVerts[tetIds[4 * tetNr]] +
tetVerts[tetIds[4 * tetNr + 1]] +
tetVerts[tetIds[4 * tetNr + 2]] +
tetVerts[tetIds[4 * tetNr + 3]]) * 0.25;
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::treeInsertTet(PxI32 tetNr)
{
PxVec3d center = getTetCenter(tetNr);
PxI32 cellNr = 0;
while (cellNr >= 0)
{
Cell& c = cells[cellNr];
if (c.closestTetNr < 0)
c.closestTetNr = tetNr;
else
{
PxVec3d cellCenter = c.orig + PxVec3d(c.size, c.size, c.size) * 0.5;
PxVec3d closest = getTetCenter(c.closestTetNr);
if ((cellCenter - center).magnitudeSquared() < (cellCenter - closest).magnitudeSquared())
c.closestTetNr = tetNr;
}
cellNr = cells[cellNr].getChildNr(center);
}
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::treeRemoveTet(PxI32 tetNr)
{
PxVec3d center = getTetCenter(tetNr);
PxI32 cellNr = 0;
while (cellNr >= 0)
{
Cell& c = cells[cellNr];
if (c.closestTetNr == tetNr)
c.closestTetNr = -1;
cellNr = cells[cellNr].getChildNr(center);
}
}
static void resizeFast(PxArray<PxI32>& arr, PxU32 newSize, PxI32 value = 0)
{
if (newSize < arr.size())
arr.removeRange(newSize, arr.size() - newSize);
else
{
while (arr.size() < newSize)
arr.pushBack(value);
}
}
// -----------------------------------------------------------------------------------
PxI32 OctreeTetrahedralizer::getNewTetNr()
{
PxI32 newTetNr;
if (firstFreeTet >= 0)
{
// take from free list
newTetNr = firstFreeTet;
firstFreeTet = tetIds[4 * firstFreeTet];
}
else
{
// append
newTetNr = PxI32(tetIds.size()) / 4;
resizeFast(tetIds, tetIds.size() + 4);
resizeFast(tetMarks, newTetNr + 1, 0);
resizeFast(tetNeighbors, tetIds.size(), -1);
}
return newTetNr;
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::removeTetNr(PxI32 tetNr)
{
// add to free list
tetIds[4 * tetNr] = firstFreeTet;
tetIds[4 * tetNr + 1] = -1;
tetIds[4 * tetNr + 2] = -1;
tetIds[4 * tetNr + 3] = -1;
firstFreeTet = tetNr;
}
// -----------------------------------------------------------------------------------
bool OctreeTetrahedralizer::findSurroundingTet(const PxVec3d& p, PxI32 startTetNr, PxI32& tetNr)
{
currentTetMark++;
tetNr = startTetNr;
bool found = false;
while (!found)
{
if (tetNr < 0 || tetMarks[tetNr] == currentTetMark) // circular, something went wrong
break;
tetMarks[tetNr] = currentTetMark;
PxVec3d c = getTetCenter(tetNr);
PxI32* ids = &tetIds[4 * tetNr];
PxF64 minT = DBL_MAX;
PxI32 minFaceNr = -1;
for (PxI32 i = 0; i < 4; i++)
{
const PxVec3d& p0 = tetVerts[ids[tetFaces[i][0]]];
const PxVec3d& p1 = tetVerts[ids[tetFaces[i][1]]];
const PxVec3d& p2 = tetVerts[ids[tetFaces[i][2]]];
PxVec3d n = (p1 - p0).cross(p2 - p0);
n = n.getNormalized();
PxF64 hp = (p - p0).dot(n);
PxF64 hc = (c - p0).dot(n);
PxF64 t = hp - hc;
if (t == 0.0)
continue;
t = -hc / t; // time when c -> p hits the face
if (t >= 0.0 && t < minT) { // in front and new min
minT = t;
minFaceNr = i;
}
}
if (minT >= 1.0)
found = true;
else
tetNr = tetNeighbors[4 * tetNr + minFaceNr];
}
return found;
}
// -----------------------------------------------------------------------------------
bool OctreeTetrahedralizer::findSurroundingTet(const PxVec3d& p, PxI32& tetNr)
{
PxI32 startTet = 0;
PxI32 cellNr = 0;
while (cellNr >= 0)
{
if (cells[cellNr].closestTetNr >= 0)
startTet = cells[cellNr].closestTetNr;
cellNr = cells[cellNr].getChildNr(p);
}
return findSurroundingTet(p, startTet, tetNr);
}
// -----------------------------------------------------------------------------------
static PxVec3d getCircumCenter(PxVec3d& p0, PxVec3d& p1, PxVec3d& p2, PxVec3d& p3)
{
PxVec3d b = p1 - p0;
PxVec3d c = p2 - p0;
PxVec3d d = p3 - p0;
PxF64 det = 2.0 * (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.0)
return p0;
else
{
PxVec3d v = c.cross(d)*b.dot(b) + d.cross(b)*c.dot(c) + b.cross(c)*d.dot(d);
v /= det;
return p0 + v;
}
}
// -----------------------------------------------------------------------------------
bool OctreeTetrahedralizer::meshInsertTetVert(PxI32 vertNr)
{
const PxVec3d& p = tetVerts[vertNr];
PxI32 surroundingTetNr;
if (!findSurroundingTet(p, surroundingTetNr))
return false;
// find violating tets
violatingTets.clear();
stack.clear();
currentTetMark++;
stack.pushBack(surroundingTetNr);
while (!stack.empty())
{
PxI32 tetNr = stack.back();
stack.popBack();
if (tetMarks[tetNr] == currentTetMark)
continue;
tetMarks[tetNr] = currentTetMark;
violatingTets.pushBack(tetNr);
for (PxI32 i = 0; i < 4; i++)
{
PxI32 n = tetNeighbors[4 * tetNr + i];
if (n < 0 || tetMarks[n] == currentTetMark)
continue;
// Delaunay condition test
PxI32* ids = &tetIds[4 * n];
PxVec3d c = getCircumCenter(tetVerts[ids[0]], tetVerts[ids[1]], tetVerts[ids[2]], tetVerts[ids[3]]);
PxF64 r2 = (tetVerts[ids[0]] - c).magnitudeSquared();
if ((p - c).magnitudeSquared() < r2)
stack.pushBack(n);
}
}
// remove old tets, create new ones
edges.clear();
Edge e;
for (PxI32 i = 0; i < PxI32(violatingTets.size()); i++)
{
PxI32 tetNr = violatingTets[i];
// copy information before we delete it
PxI32 ids[4], ns[4];
for (PxI32 j = 0; j < 4; j++) {
ids[j] = tetIds[4 * tetNr + j];
ns[j] = tetNeighbors[4 * tetNr + j];
}
// delete the tetrahedron
treeRemoveTet(tetNr);
removeTetNr(tetNr);
// visit neighbors
for (PxI32 j = 0; j < 4; j++) {
PxI32 n = ns[j];
if (n < 0 || tetMarks[n] != currentTetMark)
{
// no neighbor or neighbor is not-violating -> we are facing the border
// create new tetrahedron
PxI32 newTetNr = getNewTetNr();
PxI32 id0 = ids[tetFaces[j][2]];
PxI32 id1 = ids[tetFaces[j][1]];
PxI32 id2 = ids[tetFaces[j][0]];
tetIds[4 * newTetNr] = id0;
tetIds[4 * newTetNr + 1] = id1;
tetIds[4 * newTetNr + 2] = id2;
tetIds[4 * newTetNr + 3] = vertNr;
treeInsertTet(newTetNr);
tetNeighbors[4 * newTetNr] = n;
if (n >= 0)
{
for (PxI32 k = 0; k < 4; k++)
{
if (tetNeighbors[4 * n + k] == tetNr)
tetNeighbors[4 * n + k] = newTetNr;
}
}
// will set the neighbors among the new tetrahedra later
tetNeighbors[4 * newTetNr + 1] = -1;
tetNeighbors[4 * newTetNr + 2] = -1;
tetNeighbors[4 * newTetNr + 3] = -1;
e.init(id0, id1, newTetNr, 1); edges.pushBack(e);
e.init(id1, id2, newTetNr, 2); edges.pushBack(e);
e.init(id2, id0, newTetNr, 3); edges.pushBack(e);
}
} // next neighbor
} // next violating tetrahedron
// fix neighbors
PxSort(edges.begin(), edges.size());
PxI32 nr = 0;
while (nr < PxI32(edges.size()))
{
Edge& e0 = edges[nr];
nr++;
if (nr < PxI32(edges.size()) && edges[nr] == e0)
{
Edge& e1 = edges[nr];
tetNeighbors[4 * e0.tetNr + e0.faceNr] = e1.tetNr;
tetNeighbors[4 * e1.tetNr + e1.faceNr] = e0.tetNr;
nr++;
}
}
return true;
}
// -----------------------------------------------------------------------------------
static PxF64 tetQuality(const PxVec3d& p0, const PxVec3d& p1, const PxVec3d& p2, const PxVec3d& p3)
{
PxVec3d d0 = p1 - p0;
PxVec3d d1 = p2 - p0;
PxVec3d d2 = p3 - p0;
PxVec3d d3 = p2 - p1;
PxVec3d d4 = p3 - p2;
PxVec3d d5 = p1 - p3;
PxF64 s0 = d0.magnitudeSquared();
PxF64 s1 = d1.magnitudeSquared();
PxF64 s2 = d2.magnitudeSquared();
PxF64 s3 = d3.magnitudeSquared();
PxF64 s4 = d4.magnitudeSquared();
PxF64 s5 = d5.magnitudeSquared();
PxF64 ms = (s0 + s1 + s2 + s3 + s4 + s5) / 6.0;
PxF64 rms = sqrt(ms);
static const PxF64 s = 12.0 / sqrt(2.0);
PxF64 vol = d0.dot(d1.cross(d2)) / 6.0;
return s * vol / (rms * rms * rms); // 1.0 for regular tetrahedron
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::pruneTets()
{
insideTester.init(surfaceVerts.begin(), PxI32(surfaceVerts.size()),
surfaceTriIds.begin(), PxI32(surfaceTriIds.size()) / 3);
static PxF64 minQuality = 0.01;
PxI32 numTets = tetIds.size() / 4;
PxI32 num = 0;
for (PxI32 i = 0; i < numTets; i++)
{
bool remove = false;
PxI32* ids = &tetIds[4 * i];
for (PxI32 j = 0; j < 4; j++)
{
if (ids[j] >= firstABBVert)
remove = true;
}
if (ids[0] < 0 || ids[1] < 0 || ids[2] < 0 || ids[3] < 0)
remove = true;
if (!remove)
{
PxVec3d c = getTetCenter(i);
if (!insideTester.isInside(PxVec3(PxReal(c.x), PxReal(c.y), PxReal(c.z))))
remove = true;
if (tetQuality(tetVerts[ids[0]], tetVerts[ids[1]],
tetVerts[ids[2]], tetVerts[ids[3]]) < minQuality)
continue;
}
if (remove)
continue;
for (PxI32 j = 0; j < 4; j++)
tetIds[4 * num + j] = ids[j];
num++;
}
tetIds.resize(4 * num);
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::createTetMesh(const PxArray<PxVec3>& verts, const PxArray<PxU32>& triIds,
bool includeOctreeNodes, PxI32 _maxVertsPerCell, PxI32 _maxTreeDepth)
{
this->surfaceVerts = verts;
surfaceTriIds.resize(triIds.size());
for (PxU32 i = 0; i < triIds.size(); i++)
this->surfaceTriIds[i] = triIds[i];
this->maxVertsPerCell = _maxVertsPerCell;
this->maxTreeDepth = _maxTreeDepth;
createTree();
clearTets();
if (cells.empty())
return;
createTetVerts(includeOctreeNodes);
if (tetVerts.empty())
return;
for (PxI32 i = 0; i < PxI32(cells.size()); i++)
cells[i].closestTetNr = -1;
// create aabb tets
Bounds3 bounds;
bounds.setEmpty();
for (PxI32 i = 0; i < PxI32(tetVerts.size()); i++)
bounds.include(tetVerts[i]);
bounds.expand(bounds.getDimensions().magnitude() * 0.1);
firstABBVert = PxI32(tetVerts.size());
PxVec3d dims = bounds.getDimensions();
for (PxI32 i = 0; i < 8; i++)
{
tetVerts.pushBack(bounds.minimum + PxVec3d(
cubeCorners[i][0] * dims.x,
cubeCorners[i][1] * dims.y,
cubeCorners[i][2] * dims.z));
}
for (PxI32 i = 0; i < 6; i++)
{
for (PxI32 j = 0; j < 4; j++)
{
tetIds.pushBack(firstABBVert + cubeTets[i][j]);
tetNeighbors.pushBack(cubeTetNeighbors[i][j]);
}
treeInsertTet(i);
}
tetMarks.resize(6, 0);
for (PxI32 i = 0; i < firstABBVert; i++)
{
meshInsertTetVert(i);
}
pruneTets();
renderTriIds.clear();
renderVerts.clear();
}
// -----------------------------------------------------------------------------------
void OctreeTetrahedralizer::readBack(PxArray<PxVec3> &outputTetVerts, PxArray<PxU32> &outputTetIds)
{
outputTetVerts.resize(tetVerts.size());
for (PxU32 i = 0; i < tetVerts.size(); i++)
{
PxVec3d &v = tetVerts[i];
outputTetVerts[i] = PxVec3(PxReal(v.x), PxReal(v.y), PxReal(v.z));
}
outputTetIds.resize(tetIds.size());
for (PxU32 i = 0; i < tetIds.size(); i++)
outputTetIds[i] = PxU32(tetIds[i]);
}