// 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& result) { PxU32 l = 4 * numTets; result.clear(); result.resize(l, -1); PxHashMap 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* 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& tets, PxArray& result) { buildNeighborhood(reinterpret_cast(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& tets, PxArray& 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 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& tets, PxI32 numPoints, PxArray& 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& points, PxArray& tets) { initialize(points, tets); } void DelaunayTetrahedralizer::initialize(PxArray& points, PxArray& 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& points, const PxArray& tets, const PxArray& 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& tets, PxArray& neighbors, const Tetrahedron& tet, PxArray& 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& 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& 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& affectedFaces, PxArray& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighbor) { setFaceNeighbor(neighbors, tetId, faceId, newNeighbor); affectedFaces.pushBack(newNeighbor); } static void flip1to4(PxI32 pointToInsert, PxI32 tetId, PxArray& neighbors, PxArray& vertexToTet, PxArray& tets, PxArray& unusedTets, PxArray& 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& neighbors, PxArray& vertexToTet, PxArray& tets, PxArray& unusedTets, PxArray& 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& neighbors, PxArray& vertexToTet, PxArray& tets, PxArray& unusedTets, PxArray& 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& lockedEdges, int edgeA, int edgeB) { return lockedEdges.contains(key(edgeA, edgeB)); } static PX_FORCE_INLINE bool faceIsLocked(const PxHashSet& lockedTriangles, int triA, int triB, int triC) { return lockedTriangles.contains(SortedTriangle(triA, triB, triC)); } static void flip(const PxArray& points, PxArray& tets, PxArray& neighbors, PxArray& vertexToTet, PxI32 faceId, PxArray& unusedTets, PxArray& affectedFaces, const PxHashSet& lockedFaces, const PxHashSet& 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& faces, PxArray& neighbors, PxArray& vertexToTet, const PxArray& points, PxArray& tets, PxArray& unusedTets, PxArray& affectedFaces, const PxHashSet& lockedFaces, const PxHashSet& 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& neighbors, PxArray& vertexToTet, const PxArray& points, PxArray& tets, PxArray& unusedTets, PxArray& affectedFaces, const PxHashSet& lockedFaces, const PxHashSet& lockedEdges) { flip1to4(pointToInsert, tetId, neighbors, vertexToTet, tets, unusedTets, affectedFaces); PxArray 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& tets, const PxArray& 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& points, PxI32 start, PxI32 end, PxArray& tets, PxArray& neighbors, PxArray& vertexToTet, PxArray& unusedTets, const PxHashSet& lockedFaces, const PxHashSet& lockedEdges) { PxI32 tetId = 0; PxArray 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& 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& 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& inPoints, PxI32 start, PxI32 end, PxArray& 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& points, const PxArray& 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& points, const PxArray& tets) { return pow(maxEnergyPow3(points, tets), 1.0 / 3.0); } static PxF64 maxEnergyPow3(const PxArray& points, const PxArray& tets, const PxArray& 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& points, const PxArray& 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& points, const PxArray& 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& points, const PxArray& tets, const PxArray& 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 tetIndices) const { return maxEnergyPow3(points, tetrahedra, tetIndices); } PxF64 MinimizeMaxAmipsEnergy::quality(const PxArray 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 tetIndices) const { return minTetVolume(points, tetrahedra, tetIndices); } PxF64 MaximizeMinTetVolume::quality(const PxArray 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& vertexToTet, const PxArray& newTetIds, const PxArray& 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& removedTets, const PxArray& tetIndices, const PxArray& tets, PxArray& neighborhood, PxArray* 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 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* 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& faces, PxHashSet& tetsDone, const PxArray& tets, const PxArray& tetNeighbors, const PxArray& vertexToTet, PxI32 vertexIndex, PxArray& 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& tetIds) { stackMemory.clear(); ::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, vertexIndex, tetIds, -1, -1); } void DelaunayTetrahedralizer::collectTetsConnectedToVertex(PxArray& faces, PxHashSet& tetsDone, PxI32 vertexIndex, PxArray& tetIds) { faces.clear(); tetsDone.clear(); ::collectTetsConnectedToVertex(faces, tetsDone, result, neighbors, vertexToTet, vertexIndex, tetIds); } void DelaunayTetrahedralizer::collectTetsConnectedToEdge(PxI32 edgeStart, PxI32 edgeEnd, PxArray& 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& 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& points, const PxArray& tetsConnectedToKeepVertex, const PxArray& tetsConnectedToRemoveVertex, const PxArray& allTet, PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold = 0.1, BaseTetAnalyzer* tetAnalyzer = NULL) { const PxArray& 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 triangles; PxHashSet candidates; for (PxU32 i = 0; i < tets.size(); ++i) candidates.insert(tets[i]); const PxArray& tets2 = tetsConnectedToKeepVertex; for (PxU32 i = 0; i < tets2.size(); ++i) if (allTet[tets2[i]][0] >= 0) candidates.insert(tets2[i]); PxArray affectedTets; affectedTets.reserve(candidates.size()); PxArray 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::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* ptr = triangles.find(tri)) triangles[tri] = ptr->second + 1; else triangles.insert(tri, 1); } } for (PxHashMap::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 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& tetsConnectedToA, const PxArray& 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& tetsConnectedToKeepVertex, const PxArray& tetsConnectedToRemoveVertex, PxArray& allTets, PxArray& neighborhood, PxArray& vertexToTet, PxArray& unusedTets, PxArray& changedTets) { PxArray removedTets; changedTets.clear(); const PxArray& 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 changedTets; PxArray keepTetIds; PxArray 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& tetsConnectedToA, const PxArray& tetsConnectedToB) { PxArray changedTets; ::collapseEdge(edgeVertexAToKeep, edgeVertexBToRemove, tetsConnectedToA, tetsConnectedToB, result, neighbors, vertexToTet, unusedTets, changedTets); } static bool buildRing(const PxArray& edges, PxArray& 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& set, const PxArray& 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& faces, PxHashSet& hashset, PxArray& tetIndices, PxArray& tets, PxArray& pts, PxArray& unusedTets, PxArray& neighbors, PxArray& vertexToTet, BaseTetAnalyzer* qualityAnalyzer = NULL) { //validateNeighborhood(tets, neighbors); PxArray ringEdges; ringEdges.reserve(tetIndices.size()); for (PxU32 i = 0; i < tetIndices.size(); ++i) ringEdges.pushBack(getOtherEdge(tets[tetIndices[i]], edgeA, edgeB)); PxArray ring; buildRing(ringEdges, ring); if (ring.size() == 0) return false; PxArray ringCopy(ring); const PxVec3d& a = pts[edgeA]; const PxVec3d& b = pts[edgeB]; PxArray newTets; newTets.reserve(ring.size() * 2); PxArray 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 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 set; PxArray 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::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& 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& 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& 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& neighbors, const PxArray& 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& points, const PxArray& tets, const PxArray& neighbors, PxI32 faceId, PxArray& newTets, PxArray& removedTets, const PxHashSet& lockedFaces, const PxHashSet& 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& a, const PxArray& b, const PxArray& neighbors, PxArray& 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& tetsStart = cache.resultStart; PxArray& 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 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 affectedFaces; if (flip2to3(tetId, tetId2, neighbors, vertexToTet, result, unusedTets, affectedFaces, tip1, tip2, tri)) return true; } } } return false; } static void insert(PxArray& 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& trianglePoints, const PxArray& triangles, PxArray>& allEdges, PxArray>& pointToOriginalTriangle, PxArray& points, PxArray& 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 edgeToIndex; PxArray list; for (PxHashSet::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()); 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& tempList = pointToOriginalTriangle[i]; PxSort(tempList.begin(), tempList.size()); } PxArray intersection; RecoverEdgeMemoryCache cache; while (true) { PxArray copy; copy.reserve(lockedEdges.size()); for (PxHashSet::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& tempList = allEdges[i]; for (PxU32 j = 0; j < tempList.size(); ++j) tempList[j] -= numAdditionalPointsAtBeginning; } exportTetrahedra(finalTets); } static PxI32 optimizeByFlipping(PxArray& faces, PxArray& neighbors, PxArray& vertexToTet, const PxArray& points, PxArray& tets, PxArray& unusedTets, const BaseTetAnalyzer& qualityAnalyzer, PxArray& affectedFaces, const PxHashSet& lockedFaces, const PxHashSet& lockedEdges) { PxI32 counter = 0; while (faces.size() > 0) { PxI32 faceId = faces.popBack(); if (faceId < 0) continue; PxArray newTets; PxArray 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& affectedFaces, const BaseTetAnalyzer& qualityAnalyzer) { PxArray 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& tets, PxArray& neighbors, PxArray& vertexToTet, PxArray& unusedTets, PxArray* affectedFaces, PxArray& affectedTets) { PxU32 l = affectedTets.size(); if (l == 0) return; PxArray removedTets; for (PxU32 i = 0; i < affectedTets.size(); ++i) removedTets.pushBack(affectedTets[i]); PxArray 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& affectedTets, BaseTetAnalyzer* qualityAnalyzer) { PxArray affectedFaces; ::insertPointIntoEdge(newPointIndex, edgeA, edgeB, result, neighbors, vertexToTet, unusedTets, &affectedFaces, affectedTets); if (qualityAnalyzer != NULL) optimizeByFlipping(affectedFaces, *qualityAnalyzer); } static bool containsAll(const PxArray& list, const PxArray& 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& edges, PxArray>& pointToOriginalTriangle, PxI32 numFixPoints, BaseTetAnalyzer* qualityAnalyzer) { PxI32 l = PxI32(pointToOriginalTriangle.size()); bool success = false; PxArray tetsA; PxArray 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& edges, const PxArray>& pointToOriginalTriangle, BaseTetAnalyzer* qualityAnalyzer) { PxI32 l = PxI32(pointToOriginalTriangle.size()); bool success = false; PxArray 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& points, BaseTetAnalyzer& score, const PxArray& 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& edges, const PxArray>& pointToOriginalTriangle, PxI32 maxPointsToInsert, bool sortByQuality, BaseTetAnalyzer* qualityAnalyzer, PxF64 qualityThreshold) { const PxF64 qualityThresholdPow3 = qualityThreshold * qualityThreshold * qualityThreshold; PxArray 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 edgesToSplit; PxArray 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()); } PxI32 counter = 0; PxArray tetsConnectedToEdge; PxArray 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& edges, PxHashSet& tetEdges, const PxArray& tets, const PxArray& 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::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()); } void physx::Ext::optimize(DelaunayTetrahedralizer& del, PxArray>& pointToOriginalTriangle, PxI32 numFixPoints, PxArray& optimizedPoints, PxArray& optimizedTets, PxI32 numPasses) { PxHashSet tetEdges; PxArray edges; updateEdges(edges, tetEdges, del.tetrahedra(), del.points()); MinimizeMaxAmipsEnergy qualityAnalyzer(del.points(), del.tetrahedra()); //MaximizeMinTetVolume qualityAnalyzer(del.points(), del.tetrahedra()); optimizedTets = PxArray(del.tetrahedra()); optimizedPoints = PxArray(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(del.tetrahedra()); optimizedPoints = PxArray(del.points()); return; } optimizedTets = PxArray(del.tetrahedra()); optimizedPoints = PxArray(del.points()); minVolBefore = minVol; maxEnergyBefore = energy; } }