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

2176 lines
74 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 "ExtDelaunayTetrahedralizer.h"
#include "foundation/PxSort.h"
#include "foundation/PxHashMap.h"
#include "ExtUtilities.h"
using namespace physx;
using namespace Ext;
//http://tizian.cs.uni-bonn.de/publications/BaudsonKlein.pdf Page 44
static const PxI32 neighborFaces[4][3] = { { 0, 1, 2 }, { 0, 3, 1 }, { 0, 2, 3 }, { 1, 3, 2 } };
static const PxI32 tetTip[4] = { 3, 2, 1, 0 };
namespace
{
struct Plane
{
PxVec3d normal;
PxF64 planeD;
PX_FORCE_INLINE Plane(const PxVec3d& n, PxF64 d) : normal(n), planeD(d)
{ }
PX_FORCE_INLINE Plane(const PxVec3d& n, const PxVec3d& pointOnPlane) : normal(n)
{
planeD = -pointOnPlane.dot(normal);
}
PX_FORCE_INLINE Plane(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c)
{
normal = (b - a).cross(c - a);
PxF64 norm = normal.magnitude();
normal /= norm;
planeD = -a.dot(normal);
}
PX_FORCE_INLINE PxF64 signedDistance(const PxVec3d& p)
{
return normal.dot(p) + planeD;
}
PX_FORCE_INLINE PxF64 unsignedDistance(const PxVec3d& p)
{
return PxAbs(signedDistance(p));
}
};
}
DelaunayTetrahedralizer::DelaunayTetrahedralizer(const PxVec3d& min, const PxVec3d& max)
{
for(PxI32 i = 0; i < 4; ++i)
neighbors.pushBack(-1);
PxF64 radius = 1.1 * 0.5 * (max - min).magnitude();
PxF64 scaledRadius = 6 * radius;
centeredNormalizedPoints.pushBack(PxVec3d(-scaledRadius, -scaledRadius, -scaledRadius));
centeredNormalizedPoints.pushBack(PxVec3d(scaledRadius, scaledRadius, -scaledRadius));
centeredNormalizedPoints.pushBack(PxVec3d(-scaledRadius, scaledRadius, scaledRadius));
centeredNormalizedPoints.pushBack(PxVec3d(scaledRadius, -scaledRadius, scaledRadius));
numAdditionalPointsAtBeginning = 4;
result.pushBack(Tetrahedron(0, 1, 2, 3));
for (PxI32 i = 0; i < 4; ++i)
vertexToTet.pushBack(0);
}
void physx::Ext::buildNeighborhood(const PxI32* tets, PxU32 numTets, PxArray<PxI32>& result)
{
PxU32 l = 4 * numTets;
result.clear();
result.resize(l, -1);
PxHashMap<SortedTriangle, PxI32, TriangleHash> faces;
for (PxU32 i = 0; i < numTets; ++i)
{
const PxI32* tet = &tets[4 * i];
if (tet[0] < 0)
continue;
for (PxI32 j = 0; j < 4; ++j)
{
SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
if (const PxPair<const SortedTriangle, PxI32>* ptr = faces.find(tri))
{
if (ptr->second < 0)
{
//PX_ASSERT(false); //Invalid tetmesh since a face is shared by more than 2 tetrahedra
continue;
}
result[4 * i + j] = ptr->second;
result[ptr->second] = 4 * i + j;
faces[tri] = -1;
}
else
faces.insert(tri, 4 * i + j);
}
}
}
void physx::Ext::buildNeighborhood(const PxArray<Tetrahedron>& tets, PxArray<PxI32>& result)
{
buildNeighborhood(reinterpret_cast<const PxI32*>(tets.begin()), tets.size(), result);
}
static bool shareFace(const Tetrahedron& t1, const Tetrahedron& t2)
{
PxI32 counter = 0;
if (t1.contains(t2[0])) ++counter;
if (t1.contains(t2[1])) ++counter;
if (t1.contains(t2[2])) ++counter;
if (t1.contains(t2[3])) ++counter;
if (counter == 4)
PX_ASSERT(false);
return counter == 3;
}
// PT: this function is currently unused
//Keep for debugging & verification
/*static*/ void validateNeighborhood(const PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors)
{
PxI32 borderCounter = 0;
PX_UNUSED(borderCounter);
for (PxU32 i = 0; i < neighbors.size(); ++i)
if (neighbors[i] == -1)
++borderCounter;
//if (borderCounter != 4)
// throw new Exception();
PxArray<PxI32> n;
buildNeighborhood(tets, n);
for (PxU32 i = 0; i < n.size(); ++i)
{
if (n[i] < 0)
continue;
if (n[i] != neighbors[i])
PX_ASSERT(false);
}
for (PxU32 i = 0; i < neighbors.size(); ++i)
{
if (neighbors[i] < 0)
continue;
Tetrahedron tet1 = tets[i >> 2];
Tetrahedron tet2 = tets[neighbors[i] >> 2];
if (!shareFace(tet1, tet2))
PX_ASSERT(false);
PxI32 face1 = i & 3;
SortedTriangle tri1(tet1[neighborFaces[face1][0]], tet1[neighborFaces[face1][1]], tet1[neighborFaces[face1][2]]);
PxI32 face2 = neighbors[i] & 3;
SortedTriangle tri2(tet2[neighborFaces[face2][0]], tet2[neighborFaces[face2][1]], tet2[neighborFaces[face2][2]]);
if (tri1.A != tri2.A || tri1.B != tri2.B || tri1.C != tri2.C)
PX_ASSERT(false);
}
}
static void buildVertexToTet(const PxArray<Tetrahedron>& tets, PxI32 numPoints, PxArray<PxI32>& result)
{
result.clear();
result.resize(numPoints, -1);
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
result[tet[0]] = i;
result[tet[1]] = i;
result[tet[2]] = i;
result[tet[3]] = i;
}
}
DelaunayTetrahedralizer::DelaunayTetrahedralizer(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets)
{
initialize(points, tets);
}
void DelaunayTetrahedralizer::initialize(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets)
{
clearLockedEdges();
clearLockedTriangles();
centeredNormalizedPoints = points;
result = tets;
numAdditionalPointsAtBeginning = 0;
buildNeighborhood(tets, neighbors);
buildVertexToTet(tets, points.size(), vertexToTet);
for (PxU32 i = 0; i < tets.size(); ++i)
if (tets[i][0] < 0)
unusedTets.pushBack(i);
}
static PX_FORCE_INLINE PxF64 inSphere(const PxVec3d& pa, const PxVec3d& pb, const PxVec3d& pc, const PxVec3d& pd, const PxVec3d& candidiate)
{
PxF64 aex = pa.x - candidiate.x;
PxF64 bex = pb.x - candidiate.x;
PxF64 cex = pc.x - candidiate.x;
PxF64 dex = pd.x - candidiate.x;
PxF64 aey = pa.y - candidiate.y;
PxF64 bey = pb.y - candidiate.y;
PxF64 cey = pc.y - candidiate.y;
PxF64 dey = pd.y - candidiate.y;
PxF64 aez = pa.z - candidiate.z;
PxF64 bez = pb.z - candidiate.z;
PxF64 cez = pc.z - candidiate.z;
PxF64 dez = pd.z - candidiate.z;
PxF64 ab = aex * bey - bex * aey;
PxF64 bc = bex * cey - cex * bey;
PxF64 cd = cex * dey - dex * cey;
PxF64 da = dex * aey - aex * dey;
PxF64 ac = aex * cey - cex * aey;
PxF64 bd = bex * dey - dex * bey;
PxF64 abc = aez * bc - bez * ac + cez * ab;
PxF64 bcd = bez * cd - cez * bd + dez * bc;
PxF64 cda = cez * da + dez * ac + aez * cd;
PxF64 dab = dez * ab + aez * bd + bez * da;
PxF64 alift = aex * aex + aey * aey + aez * aez;
PxF64 blift = bex * bex + bey * bey + bez * bez;
PxF64 clift = cex * cex + cey * cey + cez * cez;
PxF64 dlift = dex * dex + dey * dey + dez * dez;
return (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
}
static PX_FORCE_INLINE bool isDelaunay(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& neighbors, PxI32 faceId)
{
PxI32 neighborPointer = neighbors[faceId];
if (neighborPointer < 0)
return true; //Border faces are always delaunay
PxI32 tet1Id = faceId >> 2;
PxI32 tet2Id = neighborPointer >> 2;
const PxI32* localTriangle1 = neighborFaces[faceId & 3];
PxI32 localTip1 = tetTip[faceId & 3];
PxI32 localTip2 = tetTip[neighborPointer & 3];
Tetrahedron tet1 = tets[tet1Id];
Tetrahedron tet2 = tets[tet2Id];
return inSphere(points[tet1[localTriangle1[0]]], points[tet1[localTriangle1[1]]], points[tet1[localTriangle1[2]]], points[tet1[localTip1]], points[tet2[localTip2]]) > 0;
}
static PX_FORCE_INLINE PxI32 storeNewTet(PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, const Tetrahedron& tet, PxArray<PxI32>& unusedTets)
{
if (unusedTets.size() == 0)
{
PxU32 tetId = tets.size();
tets.pushBack(tet);
for (PxI32 i = 0; i < 4; ++i)
neighbors.pushBack(-1);
return tetId;
}
else
{
PxI32 tetId = unusedTets[unusedTets.size() - 1];
unusedTets.remove(unusedTets.size() - 1);
tets[tetId] = tet;
return tetId;
}
}
static PX_FORCE_INLINE PxI32 localFaceId(PxI32 localA, PxI32 localB, PxI32 localC)
{
PxI32 result = localA + localB + localC - 3;
PX_ASSERT(result >= 0 || result <= 3);
return result;
}
static PX_FORCE_INLINE PxI32 localFaceId(const Tetrahedron& tet, PxI32 globalA, PxI32 globalB, PxI32 globalC)
{
PxI32 result = localFaceId(tet.indexOf(globalA), tet.indexOf(globalB), tet.indexOf(globalC));
return result;
}
static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighborTet, PxI32 newNeighborFace)
{
neighbors[4 * tetId + faceId] = 4 * newNeighborTet + newNeighborFace;
neighbors[4 * newNeighborTet + newNeighborFace] = 4 * tetId + faceId;
}
static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighbor)
{
neighbors[4 * tetId + faceId] = newNeighbor;
if (newNeighbor >= 0)
neighbors[newNeighbor] = 4 * tetId + faceId;
}
static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& affectedFaces, PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighbor)
{
setFaceNeighbor(neighbors, tetId, faceId, newNeighbor);
affectedFaces.pushBack(newNeighbor);
}
static void flip1to4(PxI32 pointToInsert, PxI32 tetId, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces)
{
const Tetrahedron origTet = tets[tetId];
Tetrahedron tet(origTet[0], origTet[1], origTet[2], pointToInsert);
tets[tetId] = tet;
Tetrahedron tet2(origTet[0], origTet[3], origTet[1], pointToInsert);
Tetrahedron tet3(origTet[0], origTet[2], origTet[3], pointToInsert);
Tetrahedron tet4(origTet[1], origTet[3], origTet[2], pointToInsert);
PxI32 tet2Id = storeNewTet(tets, neighbors, tet2, unusedTets);
PxI32 tet3Id = storeNewTet(tets, neighbors, tet3, unusedTets);
PxI32 tet4Id = storeNewTet(tets, neighbors, tet4, unusedTets);
PxI32 n1 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[1], origTet[2])];
PxI32 n2 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[1], origTet[3])];
PxI32 n3 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[2], origTet[3])];
PxI32 n4 = neighbors[4 * tetId + localFaceId(origTet, origTet[1], origTet[2], origTet[3])];
setFaceNeighbor(affectedFaces, neighbors, tetId, localFaceId(tet, origTet[0], origTet[1], origTet[2]), n1);
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, origTet[0], origTet[1], origTet[3]), n2);
setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, origTet[0], origTet[2], origTet[3]), n3);
setFaceNeighbor(affectedFaces, neighbors, tet4Id, localFaceId(tet4, origTet[1], origTet[2], origTet[3]), n4);
setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[0], origTet[1]), tet2Id, localFaceId(tet2, pointToInsert, origTet[0], origTet[1]));
setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[0], origTet[2]), tet3Id, localFaceId(tet3, pointToInsert, origTet[0], origTet[2]));
setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[1], origTet[2]), tet4Id, localFaceId(tet4, pointToInsert, origTet[1], origTet[2]));
setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, pointToInsert, origTet[0], origTet[3]), tet3Id, localFaceId(tet3, pointToInsert, origTet[0], origTet[3]));
setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, pointToInsert, origTet[1], origTet[3]), tet4Id, localFaceId(tet4, pointToInsert, origTet[1], origTet[3]));
setFaceNeighbor(neighbors, tet3Id, localFaceId(tet3, pointToInsert, origTet[2], origTet[3]), tet4Id, localFaceId(tet4, pointToInsert, origTet[2], origTet[3]));
vertexToTet[tet[0]] = tetId; vertexToTet[tet[1]] = tetId; vertexToTet[tet[2]] = tetId; vertexToTet[tet[3]] = tetId;
vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
vertexToTet[tet3[0]] = tet3Id; vertexToTet[tet3[1]] = tet3Id; vertexToTet[tet3[2]] = tet3Id; vertexToTet[tet3[3]] = tet3Id;
vertexToTet[tet4[0]] = tet4Id; vertexToTet[tet4[1]] = tet4Id; vertexToTet[tet4[2]] = tet4Id; vertexToTet[tet4[3]] = tet4Id;
}
static bool flip2to3(PxI32 tet1Id, PxI32 tet2Id,
PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
PxI32 tip1, PxI32 tip2, const Triangle& tri)
{
// 2->3 flip
Tetrahedron tet1(tip2, tip1, tri[0], tri[1]);
Tetrahedron tet2(tip2, tip1, tri[1], tri[2]);
Tetrahedron tet3(tip2, tip1, tri[2], tri[0]);
PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[0], tri[1], tip1)];
PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[0], tri[1], tip2)];
if (n1 >= 0 && (n1 >> 2) == (n2 >> 2))
{
if (Tetrahedron::identical(tet1, tets[n1 >> 2]))
return false;
}
PxI32 n3 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[1], tri[2], tip1)];
PxI32 n4 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[1], tri[2], tip2)];
if (n3 >= 0 && (n3 >> 2) == (n4 >> 2))
{
if (Tetrahedron::identical(tet2, tets[n3 >> 2]))
return false;
}
PxI32 n5 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[2], tri[0], tip1)];
PxI32 n6 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[2], tri[0], tip2)];
if (n5 >= 0 && (n5 >> 2) == (n6 >> 2))
{
if (Tetrahedron::identical(tet3, tets[n5 >> 2]))
return false;
}
PxI32 tet3Id = storeNewTet(tets, neighbors, tet3, unusedTets);
setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, tri[0], tri[1], tip1), n1);
setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, tri[0], tri[1], tip2), n2);
setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, tri[0], tip1, tip2), tet3Id, localFaceId(tet3, tri[0], tip1, tip2)); //Interior face
setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, tri[1], tip1, tip2), tet2Id, localFaceId(tet2, tri[1], tip1, tip2)); //Interior face
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, tri[1], tri[2], tip1), n3);
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, tri[1], tri[2], tip2), n4);
setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, tri[2], tip1, tip2), tet3Id, localFaceId(tet3, tri[2], tip1, tip2)); //Interior face
setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, tri[2], tri[0], tip1), n5);
setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, tri[2], tri[0], tip2), n6);
tets[tet1Id] = tet1;
tets[tet2Id] = tet2;
vertexToTet[tet1[0]] = tet1Id; vertexToTet[tet1[1]] = tet1Id; vertexToTet[tet1[2]] = tet1Id; vertexToTet[tet1[3]] = tet1Id;
vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
vertexToTet[tet3[0]] = tet3Id; vertexToTet[tet3[1]] = tet3Id; vertexToTet[tet3[2]] = tet3Id; vertexToTet[tet3[3]] = tet3Id;
return true;
}
static bool flip3to2(PxI32 tet1Id, PxI32 tet2Id, PxI32 tet3Id,
PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
PxI32 tip1, PxI32 tip2, PxI32 reflexEdgeA, PxI32 reflexEdgeB, PxI32 nonReflexTrianglePoint)
{
// 3->2 flip
Tetrahedron tet1(tip1, tip2, reflexEdgeA, nonReflexTrianglePoint);
Tetrahedron tet2(tip2, tip1, reflexEdgeB, nonReflexTrianglePoint);
PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], reflexEdgeA, tip1, nonReflexTrianglePoint)];
PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], reflexEdgeA, tip2, nonReflexTrianglePoint)];
PxI32 n3 = neighbors[4 * tet3Id + localFaceId(tets[tet3Id], reflexEdgeA, tip1, tip2)];
if (n1 >= 0 && n2 >= 0 && (n1 >> 2) == (n2 >> 2) && Tetrahedron::identical(tet1, tets[n1 >> 2]))
return false;
if (n1 >= 0 && n3 >= 0 && (n1 >> 2) == (n3 >> 2) && Tetrahedron::identical(tet1, tets[n1 >> 2]))
return false;
if (n2 >= 0 && n3 >= 0 && (n2 >> 2) == (n3 >> 2) && Tetrahedron::identical(tet1, tets[n2 >> 2]))
return false;
PxI32 n4 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], reflexEdgeB, tip1, nonReflexTrianglePoint)];
PxI32 n5 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], reflexEdgeB, tip2, nonReflexTrianglePoint)];
PxI32 n6 = neighbors[4 * tet3Id + localFaceId(tets[tet3Id], reflexEdgeB, tip1, tip2)];
if (n4 >= 0 && n5 >= 0 && (n4 >> 2) == (n5 >> 2) && Tetrahedron::identical(tet2, tets[n4 >> 2]))
return false;
if (n4 >= 0 && n6 >= 0 && (n4 >> 2) == (n6 >> 2) && Tetrahedron::identical(tet2, tets[n4 >> 2]))
return false;
if (n5 >= 0 && n6 >= 0 && (n5 >> 2) == (n6 >> 2) && Tetrahedron::identical(tet2, tets[n5 >> 2]))
return false;
setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip1, nonReflexTrianglePoint), n1);
setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip2, nonReflexTrianglePoint), n2);
setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip1, tip2), n3);
setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, nonReflexTrianglePoint, tip1, tip2), tet2Id, localFaceId(tet2, nonReflexTrianglePoint, tip1, tip2)); //Interior face // Marker (*)
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip1, nonReflexTrianglePoint), n4);
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip2, nonReflexTrianglePoint), n5);
setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip1, tip2), n6);
tets[tet1Id] = tet1;
tets[tet2Id] = tet2;
tets[tet3Id] = Tetrahedron(-1, -1, -1, -1);
for (PxI32 i = 0; i < 4; ++i)
neighbors[4 * tet3Id + i] = -2;
unusedTets.pushBack(tet3Id);
vertexToTet[tet1[0]] = tet1Id; vertexToTet[tet1[1]] = tet1Id; vertexToTet[tet1[2]] = tet1Id; vertexToTet[tet1[3]] = tet1Id;
vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
return true;
}
static PX_FORCE_INLINE PxF64 orient3D(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
return (a - d).dot((b - d).cross(c - d));
}
static PX_FORCE_INLINE bool edgeIsLocked(const PxHashSet<PxU64>& lockedEdges, int edgeA, int edgeB)
{
return lockedEdges.contains(key(edgeA, edgeB));
}
static PX_FORCE_INLINE bool faceIsLocked(const PxHashSet<SortedTriangle, TriangleHash>& lockedTriangles, int triA, int triB, int triC)
{
return lockedTriangles.contains(SortedTriangle(triA, triB, triC));
}
static void flip(const PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxI32 faceId, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
PxI32 neighborPointer = neighbors[faceId];
if (neighborPointer < 0)
return;
PxI32 tet1Id = faceId >> 2;
const PxI32* localTriangle1 = neighborFaces[faceId & 3];
Tetrahedron tet1 = tets[tet1Id];
Triangle tri(tet1[localTriangle1[0]], tet1[localTriangle1[1]], tet1[localTriangle1[2]]);
PxI32 localTip1 = tetTip[faceId & 3];
PxI32 localTip2 = tetTip[neighborPointer & 3];
PxI32 tip1 = tet1[localTip1];
PxI32 tet2Id = neighborPointer >> 2;
Tetrahedron tet2 = tets[tet2Id];
PxI32 tip2 = tet2[localTip2];
PX_ASSERT(tet2.contains(tri[0]) && tet2.contains(tri[1]) && tet2.contains(tri[2]));
PxI32 face1 = -1;
PxI32 face2 = -1;
PxI32 numReflexEdges = 0;
PxI32 reflexEdgeA = -1;
PxI32 reflexEdgeB = -1;
PxI32 nonReflexTrianglePoint = -1;
PxF64 ab = orient3D(points[tri[0]], points[tri[1]], points[tip1], points[tip2]);
if (ab < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[0], localTriangle1[1], localTip1); reflexEdgeA = tri[0]; reflexEdgeB = tri[1]; nonReflexTrianglePoint = tri[2]; face2 = localFaceId(tet2, tri[0], tri[1], tip2); }
PxF64 bc = orient3D(points[tri[1]], points[tri[2]], points[tip1], points[tip2]);
if (bc < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[1], localTriangle1[2], localTip1); reflexEdgeA = tri[1]; reflexEdgeB = tri[2]; nonReflexTrianglePoint = tri[0]; face2 = localFaceId(tet2, tri[1], tri[2], tip2); }
PxF64 ca = orient3D(points[tri[2]], points[tri[0]], points[tip1], points[tip2]);
if (ca < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[2], localTriangle1[0], localTip1); reflexEdgeA = tri[2]; reflexEdgeB = tri[0]; nonReflexTrianglePoint = tri[1]; face2 = localFaceId(tet2, tri[2], tri[0], tip2); }
if (numReflexEdges == 0)
{
if (!faceIsLocked(lockedFaces, tri[0], tri[1], tri[2]))
flip2to3(tet1Id, tet2Id, neighbors, vertexToTet, tets, unusedTets, affectedFaces, tip1, tip2, tri);
}
else if (numReflexEdges == 1)
{
PxI32 candidate1 = neighbors[4 * tet1Id + face1] >> 2;
PxI32 candidate2 = neighbors[4 * tet2Id + face2] >> 2;
if (candidate1 == candidate2 && candidate1 >= 0)
{
if (!edgeIsLocked(lockedEdges, reflexEdgeA, reflexEdgeB) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip1) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip2))
flip3to2(tet1Id, tet2Id, candidate1, neighbors, vertexToTet, tets, unusedTets, affectedFaces, tip1, tip2, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint);
}
}
else if (numReflexEdges == 2)
{
//Cannot do anything
}
else if (numReflexEdges == 3)
{
//Something is wrong if we end up here - maybe there are degenerate tetrahedra or issues due to numerical rounding
}
}
static void flip(PxArray<PxI32>& faces, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, const PxArray<PxVec3d>& points,
PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
while (faces.size() > 0)
{
PxI32 faceId = faces.popBack();
if (faceId < 0)
continue;
if (isDelaunay(points, tets, neighbors, faceId))
continue;
affectedFaces.clear();
flip(points, tets, neighbors, vertexToTet, faceId, unusedTets, affectedFaces, lockedFaces, lockedEdges);
for (PxU32 j = 0; j < affectedFaces.size(); ++j)
if (faces.find(affectedFaces[j]) == faces.end())
faces.pushBack(affectedFaces[j]);
}
}
static void insertAndFlip(PxI32 pointToInsert, PxI32 tetId, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, const PxArray<PxVec3d>& points,
PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
flip1to4(pointToInsert, tetId, neighbors, vertexToTet, tets, unusedTets, affectedFaces);
PxArray<PxI32> stack;
for (PxU32 j = 0; j < affectedFaces.size(); ++j)
if (stack.find(affectedFaces[j]) == stack.end())
stack.pushBack(affectedFaces[j]);
flip(stack, neighbors, vertexToTet, points, tets, unusedTets, affectedFaces, lockedFaces, lockedEdges);
}
static PxI32 searchAll(const PxVec3d& p, const PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points)
{
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
PxI32 j = 0;
for (; j < 4; j++)
{
Plane plane(points[tet[neighborFaces[j][0]]], points[tet[neighborFaces[j][1]]], points[tet[neighborFaces[j][2]]]);
PxF64 distP = plane.signedDistance(p);
if (distP < 0)
break;
}
if (j == 4)
return i;
}
return -1;
}
static bool runDelaunay(const PxArray<PxVec3d>& points, PxI32 start, PxI32 end, PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
PxI32 tetId = 0;
PxArray<PxI32> affectedFaces;
for (PxI32 i = start; i < end; ++i)
{
const PxVec3d p = points[i];
if (!PxIsFinite(p.x) || !PxIsFinite(p.y) || !PxIsFinite(p.z))
continue;
if (tetId < 0 || unusedTets.find(tetId) != unusedTets.end())
tetId = 0;
while (unusedTets.find(tetId) != unusedTets.end())
++tetId;
PxU32 counter = 0;
bool tetLocated = false;
while (!tetLocated)
{
const Tetrahedron& tet = tets[tetId];
const PxVec3d center = (points[tet[0]] + points[tet[1]] + points[tet[2]] + points[tet[3]]) * 0.25;
PxF64 minDist = DBL_MAX;
PxI32 minFaceNr = -1;
for (PxI32 j = 0; j < 4; j++)
{
Plane plane(points[tet[neighborFaces[j][0]]], points[tet[neighborFaces[j][1]]], points[tet[neighborFaces[j][2]]]);
PxF64 distP = plane.signedDistance(p);
PxF64 distCenter = plane.signedDistance(center);
PxF64 delta = distP - distCenter;
if (delta == 0.0)
continue;
delta = -distCenter / delta;
if (delta >= 0.0 && delta < minDist)
{
minDist = delta;
minFaceNr = j;
}
}
if (minDist >= 1.0)
tetLocated = true;
else
{
tetId = neighbors[4 * tetId + minFaceNr] >> 2;
if (tetId < 0)
{
tetId = searchAll(p, tets, points);
tetLocated = true;
}
}
++counter;
if (counter > tets.size())
{
tetId = searchAll(p, tets, points);
if (tetId < 0)
return false;
tetLocated = true;
}
}
insertAndFlip(i, tetId, neighbors, vertexToTet, points, tets, unusedTets, affectedFaces, lockedFaces, lockedEdges);
}
return true;
}
bool DelaunayTetrahedralizer::insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end)
{
for (PxI32 i = start; i < end; ++i)
{
centeredNormalizedPoints.pushBack(inPoints[i]);
vertexToTet.pushBack(-1);
}
if (!runDelaunay(centeredNormalizedPoints, start + numAdditionalPointsAtBeginning, end + numAdditionalPointsAtBeginning, result, neighbors, vertexToTet, unusedTets, lockedTriangles, lockedEdges))
return false;
return true;
}
void DelaunayTetrahedralizer::exportTetrahedra(PxArray<Tetrahedron>& tetrahedra)
{
tetrahedra.clear();
for (PxU32 i = 0; i < result.size(); ++i)
{
const Tetrahedron& tet = result[i];
if (tet[0] >= numAdditionalPointsAtBeginning && tet[1] >= numAdditionalPointsAtBeginning && tet[2] >= numAdditionalPointsAtBeginning && tet[3] >= numAdditionalPointsAtBeginning)
tetrahedra.pushBack(Tetrahedron(tet[0] - numAdditionalPointsAtBeginning, tet[1] - numAdditionalPointsAtBeginning, tet[2] - numAdditionalPointsAtBeginning, tet[3] - numAdditionalPointsAtBeginning));
}
}
void DelaunayTetrahedralizer::insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end, PxArray<Tetrahedron>& tetrahedra)
{
insertPoints(inPoints, start, end);
exportTetrahedra(tetrahedra);
}
//Code below this line is for tetmesh manipulation and not directly required to generate a Delaunay tetrahedron mesh. It is used to impmrove the quality of a tetrahedral mesh.
//https://cs.nyu.edu/~panozzo/papers/SLIM-2016.pdf
static PxF64 evaluateAmipsEnergyPow3(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
PxF64 x1 = a.x + d.x - 2.0 * b.x;
PxF64 x2 = a.x + b.x + d.x - 3.0 * c.x;
PxF64 y1 = a.y + d.y - 2.0 * b.y;
PxF64 y2 = a.y + b.y + d.y - 3.0 * c.y;
PxF64 z1 = a.z + d.z - 2.0 * b.z;
PxF64 z2 = a.z + b.z + d.z - 3.0 * c.z;
PxF64 f = (1.0 / (PxSqrt(3.0) * PxSqrt(6.0))) *
((a.x - d.x) * (y1 * z2 - y2 * z1) -
(a.y - d.y) * (z2 * x1 - x2 * z1) +
(a.z - d.z) * (y2 * x1 - y1 * x2));
if (f >= 0)
return 0.25 * DBL_MAX;
PxF64 g = a.x * (b.x + c.x + d.x - 3.0 * a.x) +
a.y * (b.y + c.y + d.y - 3.0 * a.y) +
a.z * (b.z + c.z + d.z - 3.0 * a.z) +
b.x * (a.x + c.x + d.x - 3.0 * b.x) +
b.y * (a.y + c.y + d.y - 3.0 * b.y) +
b.z * (a.z + c.z + d.z - 3.0 * b.z) +
c.x * x2 +
c.y * y2 +
c.z * z2 +
d.x * (a.x + b.x + c.x - 3.0 * d.x) +
d.y * (a.y + b.y + c.y - 3.0 * d.y) +
d.z * (a.z + b.z + c.z - 3.0 * d.z);
return -0.125 * (g * g * g) / (f * f);
//return -0.5 * PxPow(f * f, -1.0 / 3.0) * g;
}
static PX_FORCE_INLINE PxF64 _pow(const PxF64 a, const PxF64 b)
{
return PxF64(PxPow(PxF32(a), PxF32(b)));
}
static PX_FORCE_INLINE PxF64 evaluateAmipsEnergy(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
return _pow(evaluateAmipsEnergyPow3(a, b, c, d), 1.0 / 3.0);
}
static PxF64 maxEnergyPow3(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
PxF64 maxEnergy = 0;
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
PxF64 e = evaluateAmipsEnergyPow3(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
if (e > maxEnergy)
maxEnergy = e;
}
return maxEnergy;
}
static PX_FORCE_INLINE PxF64 maxEnergy(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
return pow(maxEnergyPow3(points, tets), 1.0 / 3.0);
}
static PxF64 maxEnergyPow3(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& tetIds)
{
PxF64 maxEnergy = 0;
for (PxU32 i = 0; i < tetIds.size(); ++i)
{
const Tetrahedron& tet = tets[tetIds[i]];
if (tet[0] < 0)
continue;
PxF64 e = evaluateAmipsEnergyPow3(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
if (e > maxEnergy)
maxEnergy = e;
}
return maxEnergy;
}
static PxF64 minAbsTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
PxF64 minVol = DBL_MAX;
for (PxU32 i = 0; i < tets.size(); ++i) {
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
PxF64 vol = PxAbs(tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]));
if (vol < minVol)
minVol = vol;
}
return minVol;
}
static PxF64 minTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
PxF64 minVol = DBL_MAX;
for (PxU32 i = 0; i < tets.size(); ++i) {
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
PxF64 vol = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
if (vol < minVol)
minVol = vol;
}
return minVol;
}
static PxF64 minTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& indices)
{
PxF64 minVol = DBL_MAX;
for (PxU32 i = 0; i < indices.size(); ++i) {
const Tetrahedron& tet = tets[indices[i]];
if (tet[0] < 0)
continue;
PxF64 vol = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
if (vol < minVol)
minVol = vol;
}
return minVol;
}
PxF64 MinimizeMaxAmipsEnergy::quality(const PxArray<PxI32> tetIndices) const
{
return maxEnergyPow3(points, tetrahedra, tetIndices);
}
PxF64 MinimizeMaxAmipsEnergy::quality(const PxArray<Tetrahedron> tetrahedraToCheck) const
{
return maxEnergyPow3(points, tetrahedraToCheck);
}
bool MinimizeMaxAmipsEnergy::improved(PxF64 previousQuality, PxF64 newQuality) const
{
return newQuality < previousQuality; //Minimize quality for Amips energy
}
PxF64 MaximizeMinTetVolume::quality(const PxArray<PxI32> tetIndices) const
{
return minTetVolume(points, tetrahedra, tetIndices);
}
PxF64 MaximizeMinTetVolume::quality(const PxArray<Tetrahedron> tetrahedraToCheck) const
{
return minTetVolume(points, tetrahedraToCheck);
}
bool MaximizeMinTetVolume::improved(PxF64 previousQuality, PxF64 newQuality) const
{
return newQuality > previousQuality; //Maximize quality for min volume
}
static void fixVertexToTet(PxArray<PxI32>& vertexToTet, const PxArray<PxI32>& newTetIds, const PxArray<Tetrahedron>& tets)
{
for (PxU32 i = 0; i < newTetIds.size(); ++i)
{
PxI32 id = newTetIds[i];
const Tetrahedron& tet = tets[id];
if (tet[0] < 0)
continue;
while (PxU32(tet[0]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
while (PxU32(tet[1]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
while (PxU32(tet[2]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
while (PxU32(tet[3]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
vertexToTet[tet[0]] = id; vertexToTet[tet[1]] = id; vertexToTet[tet[2]] = id; vertexToTet[tet[3]] = id;
}
}
static void fixNeighborhoodLocally(const PxArray<PxI32>& removedTets, const PxArray<PxI32>& tetIndices, const PxArray<Tetrahedron>& tets,
PxArray<PxI32>& neighborhood, PxArray<PxI32>* affectedFaces = NULL)
{
for (PxU32 k = 0; k < removedTets.size(); ++k)
{
PxI32 i = removedTets[k];
for (PxI32 j = 0; j < 4; ++j)
{
PxI32 faceId = 4 * i + j;
neighborhood[faceId] = -1;
if (affectedFaces != NULL && affectedFaces->find(faceId) == affectedFaces->end())
affectedFaces->pushBack(faceId);
}
}
PxHashMap<SortedTriangle, PxI32, TriangleHash> faces;
for(PxU32 k = 0; k< tetIndices.size(); ++k)
{
PxI32 i = tetIndices[k];
if (i < 0)
continue;
const Tetrahedron& tet = tets[i];
if (tet[0] < 0)
continue;
for (PxI32 j = 0; j < 4; ++j)
{
SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
if (const PxPair<const SortedTriangle, PxI32>* ptr = faces.find(tri))
{
neighborhood[4 * i + j] = ptr->second;
neighborhood[ptr->second] = 4 * i + j;
}
else
faces.insert(tri, 4 * i + j);
}
}
//validateNeighborhood(tets, neighborhood);
}
static void collectTetsConnectedToVertex(PxArray<PxI32>& faces, PxHashSet<PxI32>& tetsDone, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& tetNeighbors, const PxArray<PxI32>& vertexToTet,
PxI32 vertexIndex, PxArray<PxI32>& tetIds, PxI32 secondVertexId = -1, PxI32 thirdVertexId = -1)
{
tetIds.clear();
int tetIndex = vertexToTet[vertexIndex];
if (tetIndex < 0)
return; //Free floating point
if ((secondVertexId < 0 || tets[tetIndex].contains(secondVertexId)) && (thirdVertexId < 0 || tets[tetIndex].contains(thirdVertexId)))
tetIds.pushBack(tetIndex);
faces.clear();
tetsDone.clear();
for (int i = 0; i < 4; ++i)
{
if (tetNeighbors[4 * tetIndex + i] >= 0)
faces.pushBack(4 * tetIndex + i);
}
tetsDone.insert(tetIndex);
while (faces.size() > 0)
{
PxI32 faceId = faces.popBack();
int tetId = tetNeighbors[faceId] >> 2;
if (tetId < 0 || tetIds.find(tetId) != tetIds.end())
continue;
const Tetrahedron& tet = tets[tetId];
int id = tet.indexOf(vertexIndex);
if (id >= 0)
{
if ((secondVertexId < 0 || tets[tetId].contains(secondVertexId)) && (thirdVertexId < 0 || tets[tetId].contains(thirdVertexId)))
tetIds.pushBack(tetId);
const PxI32* f = neighborFaces[id];
for (int i = 0; i < 3; ++i)
{
int candidate = 4 * tetId + f[i];
if (tetsDone.insert(tetNeighbors[candidate] >> 2))
{
int otherTetId = tetNeighbors[candidate] >> 2;
if (otherTetId >= 0 && tets[otherTetId].contains(vertexIndex))
faces.pushBack(candidate);
}
}
}
}
}
void DelaunayTetrahedralizer::collectTetsConnectedToVertex(PxI32 vertexIndex, PxArray<PxI32>& tetIds)
{
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, vertexIndex, tetIds, -1, -1);
}
void DelaunayTetrahedralizer::collectTetsConnectedToVertex(PxArray<PxI32>& faces, PxHashSet<PxI32>& tetsDone, PxI32 vertexIndex, PxArray<PxI32>& tetIds)
{
faces.clear();
tetsDone.clear();
::collectTetsConnectedToVertex(faces, tetsDone, result, neighbors, vertexToTet, vertexIndex, tetIds);
}
void DelaunayTetrahedralizer::collectTetsConnectedToEdge(PxI32 edgeStart, PxI32 edgeEnd, PxArray<PxI32>& tetIds)
{
if (edgeStart < edgeEnd)
PxSwap(edgeStart, edgeEnd);
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeStart, tetIds, edgeEnd);
}
static PX_FORCE_INLINE bool containsDuplicates(PxI32 a, PxI32 b, PxI32 c, PxI32 d)
{
return a == b || a == c || a == d || b == c || b == d || c == d;
}
//Returns true if the volume of a tet would become negative if a vertex it contains would get replaced by another one
static bool tetFlipped(const Tetrahedron& tet, PxI32 vertexToReplace, PxI32 replacement, const PxArray<PxVec3d>& points, PxF64 volumeChangeThreshold = 0.1)
{
PxF64 volumeBefore = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
PxI32 a = tet[0] == vertexToReplace ? replacement : tet[0];
PxI32 b = tet[1] == vertexToReplace ? replacement : tet[1];
PxI32 c = tet[2] == vertexToReplace ? replacement : tet[2];
PxI32 d = tet[3] == vertexToReplace ? replacement : tet[3];
if (containsDuplicates(a, b, c, d))
return false;
PxF64 volume = tetVolume(points[a], points[b], points[c], points[d]);
if (volume < 0)
return true;
if (PxAbs(volume / volumeBefore) < volumeChangeThreshold)
return true;
return false;
}
static PX_FORCE_INLINE Tetrahedron getTet(const Tetrahedron& tet, PxI32 vertexToReplace, PxI32 replacement)
{
return Tetrahedron(tet[0] == vertexToReplace ? replacement : tet[0],
tet[1] == vertexToReplace ? replacement : tet[1],
tet[2] == vertexToReplace ? replacement : tet[2],
tet[3] == vertexToReplace ? replacement : tet[3]);
}
static PX_FORCE_INLINE bool containsDuplicates(const Tetrahedron& tet)
{
return tet[0] == tet[1] || tet[0] == tet[2] || tet[0] == tet[3] || tet[1] == tet[2] || tet[1] == tet[3] || tet[2] == tet[3];
}
//Returns true if a tetmesh's edge, defined by vertex indices keep and remove, can be collapsed without leading to an invalid tetmesh topology
static bool canCollapseEdge(PxI32 keep, PxI32 remove, const PxArray<PxVec3d>& points,
const PxArray<PxI32>& tetsConnectedToKeepVertex, const PxArray<PxI32>& tetsConnectedToRemoveVertex, const PxArray<Tetrahedron>& allTet,
PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold = 0.1, BaseTetAnalyzer* tetAnalyzer = NULL)
{
const PxArray<PxI32>& tets = tetsConnectedToRemoveVertex;
//If a tet would get a negative volume due to the edge collapse, then this edge is not collapsible
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = allTet[tets[i]];
if (tet[0] < 0)
return false;
if (tetFlipped(tet, remove, keep, points, volumeChangeThreshold))
return false;
}
PxHashMap<SortedTriangle, PxI32, TriangleHash> triangles;
PxHashSet<PxI32> candidates;
for (PxU32 i = 0; i < tets.size(); ++i)
candidates.insert(tets[i]);
const PxArray<PxI32>& tets2 = tetsConnectedToKeepVertex;
for (PxU32 i = 0; i < tets2.size(); ++i)
if (allTet[tets2[i]][0] >= 0)
candidates.insert(tets2[i]);
PxArray<PxI32> affectedTets;
affectedTets.reserve(candidates.size());
PxArray<Tetrahedron> remainingTets;
remainingTets.reserve(candidates.size());
//If a tet-face would get referenced by more than 2 tetrahedra due to the edge collapse, then this edge is not collapsible
for (PxHashSet<PxI32>::Iterator iter = candidates.getIterator(); !iter.done(); ++iter)
{
PxI32 tetRef = *iter;
affectedTets.pushBack(tetRef);
Tetrahedron tet = getTet(allTet[tetRef], remove, keep);
if (containsDuplicates(tet))
continue;
remainingTets.pushBack(tet);
for (PxU32 j = 0; j < 4; ++j)
{
SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
if (const PxPair<const SortedTriangle, PxI32>* ptr = triangles.find(tri))
triangles[tri] = ptr->second + 1;
else
triangles.insert(tri, 1);
}
}
for (PxHashMap<SortedTriangle, PxI32, TriangleHash>::Iterator iter = triangles.getIterator(); !iter.done(); ++iter)
if (iter->second > 2)
return false;
if (tetAnalyzer == NULL)
return true;
qualityAfterCollapse = tetAnalyzer->quality(remainingTets);
return tetAnalyzer->improved(tetAnalyzer->quality(affectedTets), qualityAfterCollapse);
}
bool DelaunayTetrahedralizer::canCollapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove, PxF64 volumeChangeThreshold, BaseTetAnalyzer* tetAnalyzer)
{
PxF64 qualityAfterCollapse;
PxArray<PxI32> tetsA, tetsB;
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToKeep, tetsA);
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToRemove, tetsB);
return canCollapseEdge(edgeVertexToKeep, edgeVertexToRemove, tetsA, tetsB, qualityAfterCollapse, volumeChangeThreshold, tetAnalyzer);
}
bool DelaunayTetrahedralizer::canCollapseEdge(PxI32 edgeVertexAToKeep, PxI32 edgeVertexBToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB,
PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold, BaseTetAnalyzer* tetAnalyzer)
{
return ::canCollapseEdge(edgeVertexAToKeep, edgeVertexBToRemove, centeredNormalizedPoints,
tetsConnectedToA, //physx::Ext::collectTetsConnectedToVertex(result, neighbors, vertexToTet, edgeVertexAToKeep),
tetsConnectedToB, //physx::Ext::collectTetsConnectedToVertex(result, neighbors, vertexToTet, edgeVertexBToRemove),
result, qualityAfterCollapse, volumeChangeThreshold, tetAnalyzer);
}
static void collapseEdge(PxI32 keep, PxI32 remove, const PxArray<PxI32>& tetsConnectedToKeepVertex, const PxArray<PxI32>& tetsConnectedToRemoveVertex,
PxArray<Tetrahedron>& allTets, PxArray<PxI32>& neighborhood, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets, PxArray<PxI32>& changedTets)
{
PxArray<PxI32> removedTets;
changedTets.clear();
const PxArray<PxI32>& tets = tetsConnectedToRemoveVertex;
for (PxI32 i = tets.size() - 1; i >= 0; --i)
{
PxI32 tetId = tets[i];
Tetrahedron tet = allTets[tetId];
tet.replace(remove, keep);
if (containsDuplicates(tet))
{
for (PxU32 j = 0; j < 4; ++j)
{
if (vertexToTet[tet[j]] == tetId) vertexToTet[tet[j]] = -1;
tet[j] = -1;
}
removedTets.pushBack(tetId);
unusedTets.pushBack(tetId);
}
else
{
changedTets.pushBack(tetId);
}
allTets[tetId] = tet;
}
for (PxU32 i = 0; i < tetsConnectedToKeepVertex.size(); ++i)
{
PxI32 id = tetsConnectedToKeepVertex[i];
if (removedTets.find(id) == removedTets.end())
changedTets.pushBack(id);
}
for (PxU32 i = 0; i < removedTets.size(); ++i)
{
PxI32 id = removedTets[i];
for (PxI32 j = 0; j < 4; ++j)
{
PxI32 n = neighborhood[4 * id + j];
if (n >= 0)
neighborhood[n] = -1;
}
}
fixNeighborhoodLocally(removedTets, changedTets, allTets, neighborhood);
fixVertexToTet(vertexToTet, changedTets, allTets);
vertexToTet[remove] = -1;
}
void DelaunayTetrahedralizer::collapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove)
{
PxArray<PxI32> changedTets;
PxArray<PxI32> keepTetIds;
PxArray<PxI32> removeTetIds;
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToKeep, keepTetIds);
stackMemory.clear();
::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToRemove, removeTetIds);
::collapseEdge(edgeVertexToKeep, edgeVertexToRemove,
keepTetIds,
removeTetIds,
result, neighbors, vertexToTet, unusedTets, changedTets);
}
void DelaunayTetrahedralizer::collapseEdge(PxI32 edgeVertexAToKeep, PxI32 edgeVertexBToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB)
{
PxArray<PxI32> changedTets;
::collapseEdge(edgeVertexAToKeep, edgeVertexBToRemove,
tetsConnectedToA, tetsConnectedToB,
result, neighbors, vertexToTet, unusedTets, changedTets);
}
static bool buildRing(const PxArray<Edge>& edges, PxArray<PxI32>& result)
{
result.reserve(edges.size() + 1);
const Edge& first = edges[0];
result.pushBack(first.first);
result.pushBack(first.second);
PxU32 currentEdge = 0;
PxI32 connector = first.second;
for (PxU32 j = 1; j < edges.size(); ++j)
{
for (PxU32 i = 1; i < edges.size(); ++i)
{
if (i == currentEdge)
continue;
const Edge& e = edges[i];
if (e.first == connector)
{
currentEdge = i;
result.pushBack(e.second);
connector = e.second;
}
else if (e.second == connector)
{
currentEdge = i;
result.pushBack(e.first);
connector = e.first;
}
if (connector == result[0])
{
result.remove(result.size() - 1);
if (result.size() != edges.size())
{
result.clear();
return false; //Multiple segments - unexpected input
}
return true; //Cyclic
}
}
}
result.clear();
return false;
}
static void addRange(PxHashSet<PxI32>& set, const PxArray<PxI32>& list)
{
for (PxU32 i = 0; i < list.size(); ++i)
set.insert(list[i]);
}
static Edge createEdge(PxI32 x, PxI32 y, bool swap)
{
if (swap)
return Edge(y, x);
else
return Edge(x, y);
}
static Edge getOtherEdge(const Tetrahedron& tet, PxI32 x, PxI32 y)
{
x = tet.indexOf(x);
y = tet.indexOf(y);
bool swap = x > y;
if (swap)
PxSwap(x, y);
if (x == 0 && y == 1) return createEdge(tet[2], tet[3], swap);
if (x == 0 && y == 2) return createEdge(tet[3], tet[1], swap);
if (x == 0 && y == 3) return createEdge(tet[1], tet[2], swap);
if (x == 1 && y == 2) return createEdge(tet[0], tet[3], swap);
if (x == 1 && y == 3) return createEdge(tet[2], tet[0], swap);
if (x == 2 && y == 3) return createEdge(tet[0], tet[1], swap);
return Edge(-1, -1);
}
static bool removeEdgeByFlip(PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& faces, PxHashSet<PxI32>& hashset, PxArray<PxI32>& tetIndices,
PxArray<Tetrahedron>& tets, PxArray<PxVec3d>& pts, PxArray<PxI32>& unusedTets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet,
BaseTetAnalyzer* qualityAnalyzer = NULL)
{
//validateNeighborhood(tets, neighbors);
PxArray<Edge> ringEdges;
ringEdges.reserve(tetIndices.size());
for (PxU32 i = 0; i < tetIndices.size(); ++i)
ringEdges.pushBack(getOtherEdge(tets[tetIndices[i]], edgeA, edgeB));
PxArray<PxI32> ring;
buildRing(ringEdges, ring);
if (ring.size() == 0)
return false;
PxArray<PxI32> ringCopy(ring);
const PxVec3d& a = pts[edgeA];
const PxVec3d& b = pts[edgeB];
PxArray<Tetrahedron> newTets;
newTets.reserve(ring.size() * 2);
PxArray<Triangle> newFaces;
while (ring.size() >= 3)
{
PxF64 shortestDist = DBL_MAX;
PxI32 id = -1;
for (PxI32 i = 0; i < PxI32(ring.size()); ++i)
{
const PxVec3d& s = pts[ring[(i - 1 + ring.size()) % ring.size()]];
const PxVec3d& middle = pts[ring[i]];
const PxVec3d& e = pts[ring[(i + 1) % ring.size()]];
const PxF64 d = (s - e).magnitudeSquared();
if (d < shortestDist)
{
const PxF64 vol1 = tetVolume(s, middle, e, b);
const PxF64 vol2 = tetVolume(a, s, middle, e);
if (vol1 > 0 && vol2 > 0)
{
shortestDist = d;
id = i;
}
}
}
if (id < 0)
return false;
{
const PxI32& s = ring[(id - 1 + ring.size()) % ring.size()];
const PxI32& middle = ring[id];
const PxI32& e = ring[(id + 1) % ring.size()];
newTets.pushBack(Tetrahedron(s, middle, e, edgeB));
newTets.pushBack(Tetrahedron(edgeA, s, middle, e));
newFaces.pushBack(Triangle(s, middle, e));
if (ring.size() > 3)
{
newFaces.pushBack(Triangle(s, e, edgeA));
newFaces.pushBack(Triangle(s, e, edgeB));
}
}
ring.remove(id);
}
//Analyze and decide if operation should be done
if (qualityAnalyzer != NULL && !qualityAnalyzer->improved(qualityAnalyzer->quality(tetIndices), qualityAnalyzer->quality(newTets)))
return false;
PxArray<PxI32> newTetIds;
for (PxU32 i = 0; i < tetIndices.size(); ++i)
{
for (PxI32 j = 0; j < 4; ++j)
{
PxI32 id = 4 * tetIndices[i] + j;
PxI32 neighborTet = neighbors[id] >> 2;
if (neighborTet >= 0 && tetIndices.find(neighborTet) == tetIndices.end() && newTetIds.find(neighborTet) == newTetIds.end())
newTetIds.pushBack(neighborTet);
}
}
PxHashSet<PxI32> set;
PxArray<PxI32> tetIds;
collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, edgeA, tetIds);
addRange(set, tetIds);
faces.forceSize_Unsafe(0);
hashset.clear();
tetIds.forceSize_Unsafe(0);
collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, edgeB, tetIds);
addRange(set, tetIds);
for (PxU32 i = 0; i < ringCopy.size(); ++i)
{
faces.forceSize_Unsafe(0);
hashset.clear();
tetIds.forceSize_Unsafe(0);
collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, ringCopy[i], tetIds);
addRange(set, tetIds);
}
for (PxHashSet<PxI32>::Iterator iter = set.getIterator(); !iter.done(); ++iter)
{
PxI32 i = *iter;
const Tetrahedron& neighborTet = tets[i];
for (PxU32 j = 0; j < newFaces.size(); ++j)
if (neighborTet.containsFace(newFaces[j]))
return false;
for (PxU32 j = 0; j < newTets.size(); ++j)
{
const Tetrahedron& t = newTets[j];
if (Tetrahedron::identical(t, neighborTet))
return false;
}
}
for (PxU32 i = 0; i < tetIndices.size(); ++i)
{
tets[tetIndices[i]] = Tetrahedron(-1, -1, -1, -1);
unusedTets.pushBack(tetIndices[i]);
}
PxU32 l = newTetIds.size();
for (PxU32 i = 0; i < newTets.size(); ++i)
newTetIds.pushBack(storeNewTet(tets, neighbors, newTets[i], unusedTets));
fixNeighborhoodLocally(tetIndices, newTetIds, tets, neighbors);
tetIndices.clear();
for (PxU32 i = l; i < newTetIds.size(); ++i)
tetIndices.pushBack(newTetIds[i]);
fixVertexToTet(vertexToTet, tetIndices, tets);
return true;
}
bool DelaunayTetrahedralizer::removeEdgeByFlip(PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& tetIndices, BaseTetAnalyzer* qualityAnalyzer)
{
stackMemory.clear();
return ::removeEdgeByFlip(edgeA, edgeB, stackMemory.faces, stackMemory.hashSet, tetIndices, result, centeredNormalizedPoints, unusedTets, neighbors, vertexToTet, qualityAnalyzer);
}
void DelaunayTetrahedralizer::addLockedEdges(const PxArray<Triangle>& triangles)
{
for (PxU32 i = 0; i < triangles.size(); ++i)
{
const Triangle& t = triangles[i];
lockedEdges.insert(key(t[0] + numAdditionalPointsAtBeginning, t[1] + numAdditionalPointsAtBeginning));
lockedEdges.insert(key(t[0] + numAdditionalPointsAtBeginning, t[2] + numAdditionalPointsAtBeginning));
lockedEdges.insert(key(t[1] + numAdditionalPointsAtBeginning, t[2] + numAdditionalPointsAtBeginning));
}
}
void DelaunayTetrahedralizer::addLockedTriangles(const PxArray<Triangle>& triangles)
{
for (PxU32 i = 0; i < triangles.size(); ++i)
{
const Triangle& tri = triangles[i];
lockedTriangles.insert(SortedTriangle(tri[0] + numAdditionalPointsAtBeginning, tri[1] + numAdditionalPointsAtBeginning, tri[2] + numAdditionalPointsAtBeginning));
}
}
static void previewFlip3to2(PxI32 tip1, PxI32 tip2, PxI32 reflexEdgeA, PxI32 reflexEdgeB, PxI32 nonReflexTrianglePoint, Tetrahedron& tet1, Tetrahedron& tet2)
{
// 3->2 flip
tet1 = Tetrahedron(tip1, tip2, reflexEdgeA, nonReflexTrianglePoint);
tet2 = Tetrahedron(tip2, tip1, reflexEdgeB, nonReflexTrianglePoint);
}
static bool previewFlip2to3(PxI32 tet1Id, PxI32 tet2Id, const PxArray<PxI32>& neighbors, const PxArray<Tetrahedron>& tets,
PxI32 tip1, PxI32 tip2, Triangle tri, Tetrahedron& tet1, Tetrahedron& tet2, Tetrahedron& tet3)
{
// 2->3 flip
tet1 = Tetrahedron(tip2, tip1, tri[0], tri[1]);
tet2 = Tetrahedron(tip2, tip1, tri[1], tri[2]);
tet3 = Tetrahedron(tip2, tip1, tri[2], tri[0]);
PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[0], tri[1], tip1)];
PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[0], tri[1], tip2)];
if (n1 >= 0 && (n1 >> 2) == (n2 >> 2))
{
if (Tetrahedron::identical(tet1, tets[n1 >> 2]))
return false;
}
PxI32 n3 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[1], tri[2], tip1)];
PxI32 n4 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[1], tri[2], tip2)];
if (n3 >= 0 && (n3 >> 2) == (n4 >> 2))
{
if (Tetrahedron::identical(tet2, tets[n3 >> 2]))
return false;
}
PxI32 n5 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[2], tri[0], tip1)];
PxI32 n6 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[2], tri[0], tip2)];
if (n5 >= 0 && (n5 >> 2) == (n6 >> 2))
{
if (Tetrahedron::identical(tet3, tets[n5 >> 2]))
return false;
}
return true;
}
static bool previewFlip(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& neighbors, PxI32 faceId,
PxArray<Tetrahedron>& newTets, PxArray<PxI32>& removedTets,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
newTets.clear();
removedTets.clear();
PxI32 neighborPointer = neighbors[faceId];
if (neighborPointer < 0)
return false;
PxI32 tet1Id = faceId >> 2;
const PxI32* localTriangle1 = neighborFaces[faceId & 3];
Tetrahedron tet1 = tets[tet1Id];
Triangle tri(tet1[localTriangle1[0]], tet1[localTriangle1[1]], tet1[localTriangle1[2]]);
PxI32 localTip1 = tetTip[faceId & 3];
PxI32 localTip2 = tetTip[neighborPointer & 3];
PxI32 tip1 = tet1[localTip1];
PxI32 tet2Id = neighborPointer >> 2;
Tetrahedron tet2 = tets[tet2Id];
PxI32 tip2 = tet2[localTip2];
if (!tet2.contains(tri[0]) || !tet2.contains(tri[1]) || !tet2.contains(tri[2]))
{
return false;
}
PxI32 face1 = -1;
PxI32 face2 = -1;
PxI32 numReflexEdges = 0;
PxI32 reflexEdgeA = -1;
PxI32 reflexEdgeB = -1;
PxI32 nonReflexTrianglePoint = -1;
PxF64 ab = orient3D(points[tri[0]], points[tri[1]], points[tip1], points[tip2]);
if (ab < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[0], localTriangle1[1], localTip1); reflexEdgeA = tri[0]; reflexEdgeB = tri[1]; nonReflexTrianglePoint = tri[2]; face2 = localFaceId(tet2, tri[0], tri[1], tip2); }
PxF64 bc = orient3D(points[tri[1]], points[tri[2]], points[tip1], points[tip2]);
if (bc < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[1], localTriangle1[2], localTip1); reflexEdgeA = tri[1]; reflexEdgeB = tri[2]; nonReflexTrianglePoint = tri[0]; face2 = localFaceId(tet2, tri[1], tri[2], tip2); }
PxF64 ca = orient3D(points[tri[2]], points[tri[0]], points[tip1], points[tip2]);
if (ca < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[2], localTriangle1[0], localTip1); reflexEdgeA = tri[2]; reflexEdgeB = tri[0]; nonReflexTrianglePoint = tri[1]; face2 = localFaceId(tet2, tri[2], tri[0], tip2); }
if (numReflexEdges == 0)
{
if (!faceIsLocked(lockedFaces, tri[0], tri[1], tri[2]))
{
Tetrahedron tet3;
if (previewFlip2to3(tet1Id, tet2Id, neighbors, tets, tip1, tip2, tri, tet1, tet2, tet3))
{
newTets.pushBack(tet1); newTets.pushBack(tet2); newTets.pushBack(tet3);
removedTets.pushBack(tet1Id); removedTets.pushBack(tet2Id);
return true;
}
else return true;
}
}
else if (numReflexEdges == 1)
{
PxI32 candidate1 = neighbors[4 * tet1Id + face1] >> 2;
PxI32 candidate2 = neighbors[4 * tet2Id + face2] >> 2;
if (candidate1 == candidate2 && candidate1 >= 0)
{
if (!edgeIsLocked(lockedEdges, reflexEdgeA, reflexEdgeB) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip1) &&
!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip2))
{
previewFlip3to2(tip1, tip2, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint, tet1, tet2);
newTets.pushBack(tet1); newTets.pushBack(tet2);
removedTets.pushBack(tet1Id); removedTets.pushBack(tet2Id); removedTets.pushBack(candidate1);
return true;
}
}
}
else if (numReflexEdges == 2)
{
//Cannot do anything
}
else if (numReflexEdges == 3)
{
}
return true;
}
static void collectCommonFaces(const PxArray<PxI32>& a, const PxArray<PxI32>& b, const PxArray<PxI32>& neighbors, PxArray<PxI32>& result)
{
result.clear();
for (PxU32 i = 0; i < a.size(); ++i)
{
PxI32 tetId = a[i];
for (int j = 0; j < 4; ++j)
{
int id = 4 * tetId + j;
if (b.find(neighbors[id] >> 2) != b.end())
{
if (result.find(id) == result.end())
result.pushBack(id);
}
}
}
}
static void previewFlip2to3(PxI32 tip1, PxI32 tip2, const Triangle& tri, Tetrahedron& tet1, Tetrahedron& tet2, Tetrahedron& tet3)
{
// 2->3 flip
tet1 = Tetrahedron(tip2, tip1, tri[0], tri[1]);
tet2 = Tetrahedron(tip2, tip1, tri[1], tri[2]);
tet3 = Tetrahedron(tip2, tip1, tri[2], tri[0]);
}
bool DelaunayTetrahedralizer::recoverEdgeByFlip(PxI32 eStart, PxI32 eEnd, RecoverEdgeMemoryCache& cache)
{
PxArray<PxI32>& tetsStart = cache.resultStart;
PxArray<PxI32>& tetsEnd = cache.resultEnd;
collectTetsConnectedToVertex(cache.facesStart, cache.tetsDoneStart, eStart, tetsStart);
collectTetsConnectedToVertex(cache.facesEnd, cache.tetsDoneEnd, eEnd, tetsEnd);
for (PxU32 i = 0; i < tetsEnd.size(); ++i)
{
const Tetrahedron& tet = result[tetsEnd[i]];
if (tet.contains(eStart))
return true;
}
PxArray<PxI32> commonFaces;
collectCommonFaces(tetsStart, tetsEnd, neighbors, commonFaces);
if (commonFaces.size() > 0)
{
for (PxU32 i = 0; i < commonFaces.size(); ++i)
{
PxI32 faceId = commonFaces[i];
PxI32 tetId = faceId >> 2;
const Tetrahedron& tet = result[tetId];
const PxI32* local = neighborFaces[faceId & 3];
Triangle tri(tet[local[0]], tet[local[1]], tet[local[2]]);
if (tri.contains(eStart) || tri.contains(eEnd))
continue;
int n = neighbors[faceId];
int tetId2 = n >> 2;
int tip1 = tet[tetTip[faceId & 3]];
int tip2 = result[tetId2][tetTip[n & 3]];
Tetrahedron tet1, tet2, tet3;
previewFlip2to3(tip1, tip2, tri, tet1, tet2, tet3);
if (tetVolume(tet1, centeredNormalizedPoints) > 0 &&
tetVolume(tet2, centeredNormalizedPoints) > 0 &&
tetVolume(tet3, centeredNormalizedPoints) > 0)
{
PxArray<PxI32> affectedFaces;
if (flip2to3(tetId, tetId2, neighbors, vertexToTet, result, unusedTets, affectedFaces, tip1, tip2, tri))
return true;
}
}
}
return false;
}
static void insert(PxArray<int>& list, int a, int b, int id)
{
for (PxI32 i = 0; i < PxI32(list.size()); ++i)
if (list[i] == a || list[i] == b)
{
list.pushBack(-1);
for (PxI32 j = list.size() - 2; j > i; --j)
{
list[j + 1] = list[j];
}
list[i + 1] = id;
return;
}
PX_ASSERT(false);
}
void DelaunayTetrahedralizer::generateTetmeshEnforcingEdges(const PxArray<PxVec3d>& trianglePoints, const PxArray<Triangle>& triangles, PxArray<PxArray<PxI32>>& allEdges,
PxArray<PxArray<PxI32>>& pointToOriginalTriangle,
PxArray<PxVec3d>& points, PxArray<Tetrahedron>& finalTets)
{
clearLockedEdges();
clearLockedTriangles();
addLockedEdges(triangles);
addLockedTriangles(triangles);
points.resize(trianglePoints.size());
for (PxU32 i = 0; i < trianglePoints.size(); ++i)
points[i] = trianglePoints[i];
insertPoints(points, 0, points.size());
allEdges.clear();
allEdges.reserve(lockedEdges.size());
PxHashMap<PxU64, PxU32> edgeToIndex;
PxArray<PxI32> list;
for (PxHashSet<PxU64>::Iterator iter = lockedEdges.getIterator(); !iter.done(); ++iter)
{
edgeToIndex.insert(*iter, allEdges.size());
list.forceSize_Unsafe(0);
list.pushBack(PxI32((*iter) >> 32));
list.pushBack(PxI32((*iter)));
allEdges.pushBack(list);
}
pointToOriginalTriangle.clear();
for (PxU32 i = 0; i < points.size(); ++i)
pointToOriginalTriangle.pushBack(PxArray<PxI32>());
for (PxU32 i = 0; i < triangles.size(); ++i)
{
const Triangle& tri = triangles[i];
pointToOriginalTriangle[tri[0]].pushBack(i);
pointToOriginalTriangle[tri[1]].pushBack(i);
pointToOriginalTriangle[tri[2]].pushBack(i);
}
for (PxU32 i = 0;i < points.size(); ++i) {
PxArray<PxI32>& tempList = pointToOriginalTriangle[i];
PxSort(tempList.begin(), tempList.size());
}
PxArray<PxI32> intersection;
RecoverEdgeMemoryCache cache;
while (true) {
PxArray<PxU64> copy;
copy.reserve(lockedEdges.size());
for (PxHashSet<PxU64>::Iterator e = lockedEdges.getIterator(); !e.done(); ++e)
copy.pushBack(*e);
PxI32 missing = 0;
for (PxU32 k = 0; k < copy.size(); ++k)
{
PxU64 e = copy[k];
PxI32 a = PxI32(e >> 32);
PxI32 b = PxI32(e);
if (!recoverEdgeByFlip(a, b, cache))
{
PxU32 id = centeredNormalizedPoints.size();
PxU32 i = edgeToIndex[e];
if (allEdges[i].size() < 10)
{
PxVec3d p = (centeredNormalizedPoints[a] + centeredNormalizedPoints[b]) * 0.5;
points.pushBack(p);
insertPoints(points, points.size() - 1, points.size());
intersection.forceSize_Unsafe(0);
intersectionOfSortedLists(pointToOriginalTriangle[a - numAdditionalPointsAtBeginning], pointToOriginalTriangle[b - numAdditionalPointsAtBeginning], intersection);
pointToOriginalTriangle.pushBack(intersection);
insert(allEdges[i], a, b, id);
edgeToIndex.insert(key(a, id), i);
edgeToIndex.insert(key(b, id), i);
edgeToIndex.erase(e);
lockedEdges.erase(e);
lockedEdges.insert(key(a, id));
lockedEdges.insert(key(b, id));
++missing;
}
}
}
if (missing == 0)
break;
}
for (PxU32 i = 0; i < allEdges.size(); ++i)
{
PxArray<PxI32>& tempList = allEdges[i];
for (PxU32 j = 0; j < tempList.size(); ++j)
tempList[j] -= numAdditionalPointsAtBeginning;
}
exportTetrahedra(finalTets);
}
static PxI32 optimizeByFlipping(PxArray<PxI32>& faces, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet,
const PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, const BaseTetAnalyzer& qualityAnalyzer,
PxArray<PxI32>& affectedFaces,
const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
PxI32 counter = 0;
while (faces.size() > 0)
{
PxI32 faceId = faces.popBack();
if (faceId < 0)
continue;
PxArray<Tetrahedron> newTets;
PxArray<PxI32> removedTets;
if (!previewFlip(points, tets, neighbors, faceId, newTets, removedTets, lockedFaces, lockedEdges))
continue;
if (!qualityAnalyzer.improved(qualityAnalyzer.quality(removedTets), qualityAnalyzer.quality(newTets)))
continue;
affectedFaces.clear();
flip(points, tets, neighbors, vertexToTet, faceId, unusedTets, affectedFaces, lockedFaces, lockedEdges);
for (PxU32 j = 0; j < affectedFaces.size(); ++j)
if (faces.find(affectedFaces[j]) == faces.end())
faces.pushBack(affectedFaces[j]);
++counter;
}
return counter;
}
bool DelaunayTetrahedralizer::optimizeByFlipping(PxArray<PxI32>& affectedFaces, const BaseTetAnalyzer& qualityAnalyzer)
{
PxArray<PxI32> stack;
for (PxU32 i = 0; i < affectedFaces.size(); ++i)
stack.pushBack(affectedFaces[i]);
PxI32 counter = ::optimizeByFlipping(stack, neighbors, vertexToTet, centeredNormalizedPoints, result, unusedTets, qualityAnalyzer, affectedFaces, lockedTriangles, lockedEdges);
return counter > 0;
}
static void insertPointIntoEdge(PxI32 newPointIndex, PxI32 edgeA, PxI32 edgeB, PxArray<Tetrahedron>& tets,
PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets, PxArray<PxI32>* affectedFaces, PxArray<PxI32>& affectedTets)
{
PxU32 l = affectedTets.size();
if (l == 0)
return;
PxArray<PxI32> removedTets;
for (PxU32 i = 0; i < affectedTets.size(); ++i)
removedTets.pushBack(affectedTets[i]);
PxArray<PxI32> newTets;
for (PxU32 i = 0; i < affectedTets.size(); ++i)
newTets.pushBack(affectedTets[i]);
for (PxU32 i = 0; i < l; ++i)
{
for (PxI32 j = 0; j < 4; ++j)
{
PxI32 id = 4 * affectedTets[i] + j;
PxI32 neighborTet = neighbors[id] >> 2;
if (neighborTet >= 0 && affectedTets.find(neighborTet) == affectedTets.end())
affectedTets.pushBack(neighborTet);
}
}
PxU32 l2 = affectedTets.size();
for (PxU32 i = 0; i < l; ++i)
{
PxI32 id = affectedTets[i];
const Tetrahedron& tet = tets[id];
Edge oppositeEdge = getOtherEdge(tet, edgeA, edgeB);
tets[id] = Tetrahedron(oppositeEdge.first, oppositeEdge.second, edgeA, newPointIndex);
PxI32 j = storeNewTet(tets, neighbors, Tetrahedron(oppositeEdge.first, oppositeEdge.second, newPointIndex, edgeB), unusedTets);
affectedTets.pushBack(j);
newTets.pushBack(j);
}
fixNeighborhoodLocally(removedTets, affectedTets, tets, neighbors, affectedFaces);
fixVertexToTet(vertexToTet, newTets, tets);
affectedTets.removeRange(l, l2 - l);
}
void DelaunayTetrahedralizer::insertPointIntoEdge(PxI32 newPointIndex, PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& affectedTets, BaseTetAnalyzer* qualityAnalyzer)
{
PxArray<PxI32> affectedFaces;
::insertPointIntoEdge(newPointIndex, edgeA, edgeB, result, neighbors, vertexToTet, unusedTets, &affectedFaces, affectedTets);
if (qualityAnalyzer != NULL)
optimizeByFlipping(affectedFaces, *qualityAnalyzer);
}
static bool containsAll(const PxArray<PxI32>& list, const PxArray<PxI32>& itemsToBePresentInList)
{
for (PxU32 i = 0; i < itemsToBePresentInList.size(); ++i)
if (list.find(itemsToBePresentInList[i]) == list.end())
return false;
return true;
}
bool physx::Ext::optimizeByCollapsing(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints, BaseTetAnalyzer* qualityAnalyzer)
{
PxI32 l = PxI32(pointToOriginalTriangle.size());
bool success = false;
PxArray<PxI32> tetsA;
PxArray<PxI32> tetsB;
for (PxU32 i = 0; i < edges.size(); ++i)
{
const EdgeWithLength& e = edges[i];
tetsA.forceSize_Unsafe(0);
del.collectTetsConnectedToVertex(e.A, tetsA);
if (tetsA.empty()) continue;
bool edgeExists = false;
for (PxU32 j = 0; j < tetsA.size(); ++j)
{
const Tetrahedron& tet = del.tetrahedron(tetsA[j]);
if (tet.contains(e.B))
{
edgeExists = true;
break;
}
}
if (!edgeExists)
continue;
tetsB.forceSize_Unsafe(0);
del.collectTetsConnectedToVertex(e.B, tetsB);
if (tetsB.empty()) continue;
PxF64 firstQuality = 0, secondQuality = 0;
bool firstOptionValid = e.B >= numFixPoints && (e.B >= l || (e.A < l && containsAll(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]))) &&
del.canCollapseEdge(e.A, e.B, tetsA, tetsB, firstQuality, 0, qualityAnalyzer);
bool secondOptionValid = e.A >= numFixPoints && (e.A >= l || (e.B < l && containsAll(pointToOriginalTriangle[e.B], pointToOriginalTriangle[e.A]))) &&
del.canCollapseEdge(e.B, e.A, tetsB, tetsA, secondQuality, 0, qualityAnalyzer);
if (qualityAnalyzer != NULL && firstOptionValid && secondOptionValid)
{
if (qualityAnalyzer->improved(firstQuality, secondQuality))
firstOptionValid = false;
else
secondOptionValid = false;
}
if (firstOptionValid)
{
if (e.B >= numFixPoints)
{
del.collapseEdge(e.A, e.B, tetsA, tetsB);
if (e.B < l)
pointToOriginalTriangle[e.B].clear();
success = true;
}
}
else if (secondOptionValid)
{
if (e.A >= numFixPoints)
{
del.collapseEdge(e.B, e.A, tetsB, tetsA);
if (e.A < l)
pointToOriginalTriangle[e.A].clear();
success = true;
}
}
}
return success;
}
bool physx::Ext::optimizeBySwapping(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
const PxArray<PxArray<PxI32>>& pointToOriginalTriangle, BaseTetAnalyzer* qualityAnalyzer)
{
PxI32 l = PxI32(pointToOriginalTriangle.size());
bool success = false;
PxArray<PxI32> tetIds;
for (PxU32 i = 0; i < edges.size(); ++i)
{
const EdgeWithLength& e = edges[i];
const bool isInteriorEdge = e.A >= l || e.B >= l || !intersectionOfSortedListsContainsElements(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]);
if (isInteriorEdge)
{
tetIds.forceSize_Unsafe(0);
del.collectTetsConnectedToEdge(e.A, e.B, tetIds);
if (tetIds.size() <= 2)
continue; //This would mean we have a surface edge
if (del.removeEdgeByFlip(e.A, e.B, tetIds, qualityAnalyzer))
success = true;
}
}
return success;
}
static void patternSearchOptimize(PxI32 pointToModify, PxF64 step, PxArray<PxVec3d>& points, BaseTetAnalyzer& score, const PxArray<PxI32>& tetrahedra, PxF64 minStep)
{
const PxVec3d dir[] = { PxVec3d(1.0, 0.0, 0.0), PxVec3d(-1.0, 0.0, 0.0), PxVec3d(0.0, 1.0, 0.0), PxVec3d(0.0, -1.0, 0.0), PxVec3d(0.0, 0.0, 1.0), PxVec3d(0.0, 0.0, -1.0), };
const PxI32 oppositeDir[] = { 1, 0, 3, 2, 5, 4 };
PxF64 currentScore = score.quality(tetrahedra); // score();
PxVec3d p = points[pointToModify];
PxI32 skip = -1;
PxI32 maxIter = 100;
PxI32 iter = 0;
while (step > minStep && iter < maxIter)
{
PxF64 best = currentScore;
PxI32 id = -1;
for (PxI32 i = 0; i < 6; ++i)
{
if (i == skip)
continue; //That point was the best before current iteration - it cannot be the best anymore
points[pointToModify] = p + dir[i] * step;
PxF64 s = score.quality(tetrahedra); // score();
if (score.improved(best, s))
{
best = s;
id = i;
}
}
if (id >= 0)
{
p = p + dir[id] * step;
points[pointToModify] = p;
currentScore = best;
skip = oppositeDir[id];
}
else
{
points[pointToModify] = p;
step = step * 0.5;
skip = -1;
}
++iter;
}
}
bool physx::Ext::optimizeBySplitting(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges, const PxArray<PxArray<PxI32>>& pointToOriginalTriangle,
PxI32 maxPointsToInsert, bool sortByQuality, BaseTetAnalyzer* qualityAnalyzer, PxF64 qualityThreshold)
{
const PxF64 qualityThresholdPow3 = qualityThreshold * qualityThreshold * qualityThreshold;
PxArray<PxF64> tetQualityBuffer;
tetQualityBuffer.resize(del.numTetrahedra());
for (PxU32 i = 0; i < del.numTetrahedra(); ++i) {
const Tetrahedron& tet = del.tetrahedron(i);
if (tet[0] >= 0)
tetQualityBuffer[i] = evaluateAmipsEnergyPow3(del.point(tet[0]), del.point(tet[1]), del.point(tet[2]), del.point(tet[3]));
}
PxI32 l = PxI32(pointToOriginalTriangle.size());
PxArray<SplitEdge> edgesToSplit;
PxArray<PxI32> tetIds;
for (PxI32 i = edges.size() - 1; i >= 0; --i)
{
const EdgeWithLength& e = edges[i];
const bool isInteriorEdge = e.A >= l || e.B >= l || !intersectionOfSortedListsContainsElements(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]);
if (isInteriorEdge)
{
tetIds.forceSize_Unsafe(0);
del.collectTetsConnectedToEdge(e.A, e.B, tetIds);
if (tetIds.size() <= 2)
continue; //This would mean we got a surface edge...
PxF64 max = 0;
for (PxU32 j = 0; j < tetIds.size(); ++j)
{
if (tetQualityBuffer[tetIds[j]] > max)
max = tetQualityBuffer[tetIds[j]];
}
if (max > qualityThresholdPow3)
edgesToSplit.pushBack(SplitEdge(e.A, e.B, max, (del.point(e.A) - del.point(e.B)).magnitudeSquared(), isInteriorEdge));
}
}
if (sortByQuality)
{
PxSort(edgesToSplit.begin(), edgesToSplit.size(), PxGreater<SplitEdge>());
}
PxI32 counter = 0;
PxArray<PxI32> tetsConnectedToEdge;
PxArray<PxI32> tetsConnectedToVertex;
for (PxU32 i = 0; i < edgesToSplit.size(); ++i)
{
const SplitEdge& e = edgesToSplit[i];
if (i > 0 && edgesToSplit[i - 1].Q == e.Q)
continue;
if (counter == maxPointsToInsert)
break;
PxU32 id = del.addPoint((del.point(e.A) + del.point(e.B)) * 0.5);
tetsConnectedToEdge.forceSize_Unsafe(0);
del.collectTetsConnectedToEdge(e.A, e.B, tetsConnectedToEdge);
del.insertPointIntoEdge(id, e.A, e.B, tetsConnectedToEdge, qualityAnalyzer);
if (e.InteriorEdge && qualityAnalyzer)
{
tetsConnectedToVertex.forceSize_Unsafe(0);
del.collectTetsConnectedToVertex(id, tetsConnectedToVertex);
patternSearchOptimize(id, e.L, del.points(), *qualityAnalyzer, tetsConnectedToVertex, 0.01 * e.L);
}
++counter;
}
return edgesToSplit.size() > 0;
}
static void updateEdges(PxArray<EdgeWithLength>& edges, PxHashSet<PxU64>& tetEdges, const PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points)
{
edges.clear();
tetEdges.clear();
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = tets[i];
if (tet[0] < 0) continue;
tetEdges.insert(key(tet[0], tet[1]));
tetEdges.insert(key(tet[0], tet[2]));
tetEdges.insert(key(tet[0], tet[3]));
tetEdges.insert(key(tet[1], tet[2]));
tetEdges.insert(key(tet[1], tet[3]));
tetEdges.insert(key(tet[2], tet[3]));
}
for (PxHashSet<PxU64>::Iterator iter = tetEdges.getIterator(); !iter.done(); ++iter)
{
PxU64 e = *iter;
PxI32 a = PxI32(e >> 32);
PxI32 b = PxI32(e);
edges.pushBack(EdgeWithLength(a, b, (points[a] - points[b]).magnitudeSquared()));
}
PxSort(edges.begin(), edges.size(), PxLess<EdgeWithLength>());
}
void physx::Ext::optimize(DelaunayTetrahedralizer& del, PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints,
PxArray<PxVec3d>& optimizedPoints, PxArray<Tetrahedron>& optimizedTets, PxI32 numPasses)
{
PxHashSet<PxU64> tetEdges;
PxArray<EdgeWithLength> edges;
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
MinimizeMaxAmipsEnergy qualityAnalyzer(del.points(), del.tetrahedra());
//MaximizeMinTetVolume qualityAnalyzer(del.points(), del.tetrahedra());
optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
optimizedPoints = PxArray<PxVec3d>(del.points());
PxF64 minVolBefore = minAbsTetVolume(del.points(), del.tetrahedra());
PxF64 maxEnergyBefore = maxEnergy(del.points(), del.tetrahedra());
PxI32 numPointsToInsertPerPass = PxMax(20, PxI32(0.05 * del.numPoints()));
for (PxI32 j = 0; j < numPasses; ++j)
{
//(1) Splitting
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
optimizeBySplitting(del, edges, pointToOriginalTriangle, numPointsToInsertPerPass, true, &qualityAnalyzer);
//(3) Swapping
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
optimizeBySwapping(del, edges, pointToOriginalTriangle, &qualityAnalyzer);
//(2) Collapsing
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
optimizeByCollapsing(del, edges, pointToOriginalTriangle, numFixPoints, &qualityAnalyzer);
//(4) Smoothing
//Note done here - seems not to improve the quality that much
PxF64 energy = maxEnergy(del.points(), del.tetrahedra());
PxF64 minVol = minAbsTetVolume(del.points(), del.tetrahedra());
if (energy / minVol >= maxEnergyBefore / minVolBefore/*minVol <= minVolBefore*/)
{
del.initialize(optimizedPoints, optimizedTets);
bool swapped = true, collapsed = true;
PxI32 maxReductionLoops = 30;
PxI32 counter = 0;
while (swapped || collapsed)
{
//(3) Swapping
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
swapped = optimizeBySwapping(del, edges, pointToOriginalTriangle, &qualityAnalyzer);
//(2) Collapsing
updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
collapsed = optimizeByCollapsing(del, edges, pointToOriginalTriangle, numFixPoints, &qualityAnalyzer);
if (counter >= maxReductionLoops)
break;
++counter;
}
optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
optimizedPoints = PxArray<PxVec3d>(del.points());
return;
}
optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
optimizedPoints = PxArray<PxVec3d>(del.points());
minVolBefore = minVol;
maxEnergyBefore = energy;
}
}