feat(physics): wire physx sdk into build

This commit is contained in:
2026-04-15 12:22:15 +08:00
parent 5bf258df6d
commit 31f40e2cbb
2044 changed files with 752623 additions and 1 deletions

View File

@@ -0,0 +1,48 @@
// 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 "ExtBVH.h"
#include "ExtUtilities.h"
using namespace physx;
using namespace Ext;
using namespace Gu;
void BVHDesc::query(const PxBounds3& bounds, PxArray<PxI32>& items)
{
items.clear();
IntersectionCollectingTraversalController traversalController(bounds, items);
traverseBVH(tree.begin(), traversalController, 0);
}
void BVHBuilder::build(BVHDesc& bvh, const PxBounds3* items, PxI32 n)
{
AABBTreeBounds boxes;
boxes.init(n);
for (PxI32 i = 0; i < n; ++i)
boxes.getBounds()[i] = items[i];
Gu::buildAABBTree(n, boxes, bvh.tree);
}

View File

@@ -0,0 +1,53 @@
// 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.
#ifndef EXT_BVH_H
#define EXT_BVH_H
#include "foundation/PxBounds3.h"
#include "foundation/PxArray.h"
#include "GuAABBTreeNode.h"
namespace physx
{
namespace Ext
{
struct BVHDesc
{
PxArray<Gu::BVHNode> tree;
void query(const PxBounds3& bounds, PxArray<PxI32>& items);
};
class BVHBuilder
{
public:
static void build(BVHDesc& bvh, const PxBounds3* items, PxI32 numItems);
};
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
// 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.
#ifndef EXT_DELAUNAY_BOUNDARY_INSERTER_H
#define EXT_DELAUNAY_BOUNDARY_INSERTER_H
#include "foundation/PxHashSet.h"
#include "foundation/PxVec3.h"
#include "foundation/PxMat33.h"
#include "foundation/PxHashMap.h"
#include "foundation/PxArray.h"
#include "common/PxCoreUtilityTypes.h"
#include "extensions/PxTriangleMeshAnalysisResult.h"
#include "extensions/PxTetrahedronMeshAnalysisResult.h"
#include "ExtDelaunayTetrahedralizer.h"
namespace physx
{
namespace Ext
{
//Generates a tetmesh that matches the specified triangle mesh. All triangle mesh points are present in the tetmesh, additional points on edges and faces might be present.
void generateTetmesh(const PxBoundedData& inputPoints, const PxBoundedData& inputTriangles, const bool has16bitIndices, PxArray<PxVec3>& tetPoints, PxArray<PxU32>& finalTets);
//Generates a tetmesh that that matches the surface of the input tetmesh approximately but creats very regular shaped tetrahedra.
void generateVoxelTetmesh(const PxBoundedData& inputPoints, const PxBoundedData& inputTets, PxU32 numVoxelsX, PxU32 numVoxelsY, PxU32 numVoxelsZ,
PxArray<PxVec3>& voxelPoints, PxArray<PxU32>& voxelTets, PxI32* intputPointToOutputTetIndex, const PxU32* anchorNodeIndices = NULL, PxU32 numTetsPerVoxel = 5,
bool avoidVoxelDuplication = false);
//Generates a tetmesh that that matches the surface of the input tetmesh approximately but creats very regular shaped tetrahedra.
void generateVoxelTetmesh(const PxBoundedData& inputPoints, const PxBoundedData& inputTets, PxReal voxelEdgeLength,
PxArray<PxVec3>& voxelPoints, PxArray<PxU32>& voxelTets, PxI32* intputPointToOutputTetIndex, const PxU32* anchorNodeIndices = NULL, PxU32 numTetsPerVoxel = 5,
bool avoidVoxelDuplication = false);
//Generates a tetmesh that that matches the surface of the input tetmesh approximately but creats very regular shaped tetrahedra.
void generateVoxelTetmesh(const PxBoundedData& inputPoints, const PxBoundedData& inputTets, PxU32 numVoxelsAlongLongestBoundingBoxAxis,
PxArray<PxVec3>& voxelPoints, PxArray<PxU32>& voxelTets, PxI32* intputPointToOutputTetIndex, const PxU32* anchorNodeIndices = NULL, PxU32 numTetsPerVoxel = 5,
bool avoidVoxelDuplication = false);
//Extracts the surface triangles from the specified tetrahedra
void extractTetmeshSurface(const PxArray<PxI32>& tets, PxArray<PxI32>& triangles);
//Computes the lumped mass per vertex for the specified tetmesh
void pointMasses(const PxArray<PxVec3>& tetVerts, const PxArray<PxU32>& tets, PxF32 density, PxArray<PxF32>& mass);
//Computes a rest pose matrix for every tetrahedron in the specified tetmesh
void restPoses(const PxArray<PxVec3>& tetVerts, const PxArray<PxU32>& tets, PxArray<PxMat33>& restPoses);
//Computes a fiber direction for every tetrahedron in the specified tetmesh. Currently just returns dummy values.
void tetFibers(const PxArray<PxVec3>& tetVerts, const PxArray<PxU32>& tets, PxArray<PxVec3>& tetFibers);
//Analyzes the triangle mesh to get a report about deficiencies. Some deficiencies can be handled by the tetmesher, others cannot.
PxTriangleMeshAnalysisResults validateTriangleMesh(const PxBoundedData& points, const PxBoundedData& triangles, const bool has16BitIndices, const PxReal minVolumeThreshold = 1e-6f, const PxReal minTriangleAngleRadians = 10.0f*3.1415926535898f / 180.0f);
//Analyzes the tetrahedron mesh to get a report about deficiencies. Some deficiencies can be handled by the softbody cooker, others cannot.
PxTetrahedronMeshAnalysisResults validateTetrahedronMesh(const PxBoundedData& points, const PxBoundedData& tetrahedra, const bool has16BitIndices, const PxReal minTetVolumeThreshold = 1e-8f);
PxU32 removeDisconnectedIslands(PxI32* finalTets, PxU32 numTets);
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,362 @@
// 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.
#ifndef EXT_DELAUNAY_TETRAHEDRALIZER_H
#define EXT_DELAUNAY_TETRAHEDRALIZER_H
#include "foundation/PxArray.h"
#include "foundation/PxVec3.h"
#include "foundation/PxHashSet.h"
#include "GuTetrahedron.h"
#include "ExtVec3.h"
namespace physx
{
namespace Ext
{
using Edge = PxPair<PxI32, PxI32>;
using Tetrahedron = Gu::TetrahedronT<PxI32>;
using Tetrahedron16 = Gu::TetrahedronT<PxI16>;
void buildNeighborhood(const PxArray<Tetrahedron>& tets, PxArray<PxI32>& result);
void buildNeighborhood(const PxI32* tets, PxU32 numTets, PxArray<PxI32>& result);
PX_FORCE_INLINE PxF64 tetVolume(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
return (-1.0 / 6.0) * (a - d).dot((b - d).cross(c - d));
}
PX_FORCE_INLINE PxF64 tetVolume(const Tetrahedron& tet, const PxArray<PxVec3d>& points)
{
return tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
}
//Returns the intersection (set operation) of two sorted lists
PX_FORCE_INLINE void intersectionOfSortedLists(const PxArray<PxI32>& sorted1, const PxArray<PxI32>& sorted2, PxArray<PxI32>& result)
{
PxU32 a = 0;
PxU32 b = 0;
result.clear();
while (a < sorted1.size() && b < sorted2.size())
{
if (sorted1[a] == sorted2[b])
{
result.pushBack(sorted1[a]);
++a;
++b;
}
else if (sorted1[a] > sorted2[b])
++b;
else
++a;
}
}
PX_FORCE_INLINE bool intersectionOfSortedListsContainsElements(const PxArray<PxI32>& sorted1, const PxArray<PxI32>& sorted2)
{
PxU32 a = 0;
PxU32 b = 0;
while (a < sorted1.size() && b < sorted2.size())
{
if (sorted1[a] == sorted2[b])
return true;
else if (sorted1[a] > sorted2[b])
++b;
else
++a;
}
return false;
}
class BaseTetAnalyzer
{
public:
virtual PxF64 quality(const PxArray<PxI32> tetIndices) const = 0;
virtual PxF64 quality(const PxArray<Tetrahedron> tetrahedraToCheck) const = 0;
virtual bool improved(PxF64 previousQuality, PxF64 newQuality) const = 0;
virtual ~BaseTetAnalyzer() {}
};
class MinimizeMaxAmipsEnergy : public BaseTetAnalyzer
{
const PxArray<PxVec3d>& points;
const PxArray<Tetrahedron>& tetrahedra;
public:
MinimizeMaxAmipsEnergy(const PxArray<PxVec3d>& points_, const PxArray<Tetrahedron>& tetrahedra_) : points(points_), tetrahedra(tetrahedra_)
{}
PxF64 quality(const PxArray<PxI32> tetIndices) const;
PxF64 quality(const PxArray<Tetrahedron> tetrahedraToCheck) const;
bool improved(PxF64 previousQuality, PxF64 newQuality) const;
virtual ~MinimizeMaxAmipsEnergy() {}
private:
PX_NOCOPY(MinimizeMaxAmipsEnergy)
};
class MaximizeMinTetVolume : public BaseTetAnalyzer
{
const PxArray<PxVec3d>& points;
const PxArray<Tetrahedron>& tetrahedra;
public:
MaximizeMinTetVolume(const PxArray<PxVec3d>& points_, const PxArray<Tetrahedron>& tetrahedra_) : points(points_), tetrahedra(tetrahedra_)
{}
PxF64 quality(const PxArray<PxI32> tetIndices) const;
PxF64 quality(const PxArray<Tetrahedron> tetrahedraToCheck) const;
bool improved(PxF64 previousQuality, PxF64 newQuality) const;
virtual ~MaximizeMinTetVolume() {}
private:
PX_NOCOPY(MaximizeMinTetVolume)
};
//Helper class to extract surface triangles from a tetmesh
struct SortedTriangle
{
public:
PxI32 A;
PxI32 B;
PxI32 C;
bool Flipped;
PX_FORCE_INLINE SortedTriangle(PxI32 a, PxI32 b, PxI32 c)
{
A = a; B = b; C = c; Flipped = false;
if (A > B) { PxSwap(A, B); Flipped = !Flipped; }
if (B > C) { PxSwap(B, C); Flipped = !Flipped; }
if (A > B) { PxSwap(A, B); Flipped = !Flipped; }
}
};
struct TriangleHash
{
PX_FORCE_INLINE std::size_t operator()(const SortedTriangle& k) const
{
return k.A ^ k.B ^ k.C;
}
PX_FORCE_INLINE bool equal(const SortedTriangle& first, const SortedTriangle& second) const
{
return first.A == second.A && first.B == second.B && first.C == second.C;
}
};
struct RecoverEdgeMemoryCache
{
PxArray<PxI32> facesStart;
PxHashSet<PxI32> tetsDoneStart;
PxArray<PxI32> resultStart;
PxArray<PxI32> facesEnd;
PxHashSet<PxI32> tetsDoneEnd;
PxArray<PxI32> resultEnd;
};
class StackMemory
{
public:
void clear()
{
faces.forceSize_Unsafe(0);
hashSet.clear();
}
PxArray<PxI32> faces;
PxHashSet<PxI32> hashSet;
};
//Incremental delaunay tetrahedralizer
class DelaunayTetrahedralizer
{
public:
//The bounds specified must contain all points that will get inserted by calling insertPoints.
DelaunayTetrahedralizer(const PxVec3d& min, const PxVec3d& max);
DelaunayTetrahedralizer(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets);
void initialize(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets);
//Inserts a bunch of new points into the tetrahedralization and keeps the delaunay condition satisfied. The new result will
//get stored in the tetrahedra array. Points to insert must already be present in inPoints, the indices of the points to insert
//can be controlled with start and end index (end index is exclusive, start index is inclusive)
void insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end, PxArray<Tetrahedron>& tetrahedra);
bool insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end);
void exportTetrahedra(PxArray<Tetrahedron>& tetrahedra);
bool canCollapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove, PxF64 volumeChangeThreshold = 0.1, BaseTetAnalyzer* tetAnalyzer = NULL);
bool canCollapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB,
PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold = 0.1, BaseTetAnalyzer* tetAnalyzer = NULL);
void collapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove);
void collapseEdge(PxI32 edgeVertexAToKeep, PxI32 edgeVertexBToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB);
void collectTetsConnectedToVertex(PxI32 vertexIndex, PxArray<PxI32>& tetIds);
void collectTetsConnectedToVertex(PxArray<PxI32>& faces, PxHashSet<PxI32>& tetsDone, PxI32 vertexIndex, PxArray<PxI32>& tetIds);
void collectTetsConnectedToEdge(PxI32 edgeStart, PxI32 edgeEnd, PxArray<PxI32>& tetIds);
PX_FORCE_INLINE const PxVec3d& point(PxI32 index) const { return centeredNormalizedPoints[index]; }
PX_FORCE_INLINE PxU32 numPoints() const { return centeredNormalizedPoints.size(); }
PX_FORCE_INLINE PxArray<PxVec3d>& points() { return centeredNormalizedPoints; }
PX_FORCE_INLINE const PxArray<PxVec3d>& points() const { return centeredNormalizedPoints; }
PxU32 addPoint(const PxVec3d& p)
{
centeredNormalizedPoints.pushBack(p);
vertexToTet.pushBack(-1);
return centeredNormalizedPoints.size() - 1;
}
PX_FORCE_INLINE const Tetrahedron& tetrahedron(PxI32 index) const { return result[index]; }
PX_FORCE_INLINE PxU32 numTetrahedra() const { return result.size(); }
PX_FORCE_INLINE PxArray<Tetrahedron>& tetrahedra() { return result; }
PX_FORCE_INLINE const PxArray<Tetrahedron>& tetrahedra() const { return result; }
void copyInternalPointsTo(PxArray<PxVec3d>& points) { points = centeredNormalizedPoints; }
bool optimizeByFlipping(PxArray<PxI32>& affectedFaces, const BaseTetAnalyzer& qualityAnalyzer);
void insertPointIntoEdge(PxI32 newPointIndex, PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& affectedTets, BaseTetAnalyzer* qualityAnalyzer = NULL);
bool removeEdgeByFlip(PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& tetIndices, BaseTetAnalyzer* qualityAnalyzer = NULL);
void addLockedEdges(const PxArray<Gu::IndexedTriangleT<PxI32>>& triangles);
void addLockedTriangles(const PxArray<Gu::IndexedTriangleT<PxI32>>& triangles);
void clearLockedEdges() { lockedEdges.clear(); }
void clearLockedTriangles() { lockedTriangles.clear(); }
bool recoverEdgeByFlip(PxI32 eStart, PxI32 eEnd, RecoverEdgeMemoryCache& cache);
void generateTetmeshEnforcingEdges(const PxArray<PxVec3d>& trianglePoints, const PxArray<Gu::IndexedTriangleT<PxI32>>& triangles, PxArray<PxArray<PxI32>>& allEdges,
PxArray<PxArray<PxI32>>& pointToOriginalTriangle,
PxArray<PxVec3d>& points, PxArray<Tetrahedron>& finalTets);
private:
PxArray<PxVec3d> centeredNormalizedPoints;
PxArray<PxI32> neighbors;
PxArray<PxI32> unusedTets;
PxArray<PxI32> vertexToTet;
PxArray<Tetrahedron> result;
PxI32 numAdditionalPointsAtBeginning = 4;
PxHashSet<SortedTriangle, TriangleHash> lockedTriangles;
PxHashSet<PxU64> lockedEdges;
StackMemory stackMemory;
PX_NOCOPY(DelaunayTetrahedralizer)
};
struct EdgeWithLength
{
PxI32 A;
PxI32 B;
PxF64 Length;
EdgeWithLength(PxI32 a_, PxI32 b_, PxF64 length_)
{
A = a_;
B = b_;
Length = length_;
}
};
PX_FORCE_INLINE bool operator <(const EdgeWithLength& lhs, const EdgeWithLength& rhs)
{
return lhs.Length < rhs.Length;
}
struct SplitEdge
{
PxI32 A;
PxI32 B;
PxF64 Q;
PxF64 L;
bool InteriorEdge;
SplitEdge(PxI32 a, PxI32 b, PxF64 q, PxF64 l, bool interiorEdge)
{
A = a;
B = b;
Q = q;
L = l;
InteriorEdge = interiorEdge;
}
};
PX_FORCE_INLINE bool operator >(const SplitEdge& lhs, const SplitEdge& rhs)
{
if (lhs.Q == rhs.Q)
return lhs.L > rhs.L;
return lhs.Q > rhs.Q;
}
bool optimizeByCollapsing(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints, BaseTetAnalyzer* qualityAnalyzer = NULL);
bool optimizeBySwapping(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
const PxArray<PxArray<PxI32>>& pointToOriginalTriangle, BaseTetAnalyzer* qualityAnalyzer);
bool optimizeBySplitting(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges, const PxArray<PxArray<PxI32>>& pointToOriginalTriangle,
PxI32 maxPointsToInsert = -1, bool sortByQuality = false, BaseTetAnalyzer* qualityAnalyzer = NULL, PxF64 qualityThreshold = 10);
//Modified tetmesh quality improvement implementation of the method described in https://cs.nyu.edu/~yixinhu/tetwild.pdf Section 3.2 Mesh Improvement
void optimize(DelaunayTetrahedralizer& del, PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints,
PxArray<PxVec3d>& optimizedPoints, PxArray<Tetrahedron>& optimizedTets, PxI32 numPasses = 10);
}
}
#endif

View File

@@ -0,0 +1,53 @@
// 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 "ExtFastWindingNumber.h"
#include "foundation/PxSort.h"
#include "foundation/PxMath.h"
#include "ExtUtilities.h"
//The following paper explains all the techniques used in this file
//http://www.dgp.toronto.edu/projects/fast-winding-numbers/fast-winding-numbers-for-soups-and-clouds-siggraph-2018-barill-et-al.pdf
namespace physx
{
namespace Ext
{
PxF64 computeWindingNumber(const PxArray<Gu::BVHNode>& tree, const PxVec3d& q, PxF64 beta, const PxHashMap<PxU32, ClusterApproximationF64>& clusters,
const PxArray<Triangle>& triangles, const PxArray<PxVec3d>& points)
{
return Gu::computeWindingNumber<PxF64, PxVec3d>(tree.begin(), q, beta, clusters, reinterpret_cast<const PxU32*>(triangles.begin()), points.begin());
}
void precomputeClusterInformation(PxArray<Gu::BVHNode>& tree, const PxArray<Triangle>& triangles,
const PxArray<PxVec3d>& points, PxHashMap<PxU32, ClusterApproximationF64>& result, PxI32 rootNodeIndex)
{
Gu::precomputeClusterInformation<PxF64, PxVec3d>(tree.begin(), reinterpret_cast<const PxU32*>(triangles.begin()), triangles.size(), points.begin(), result, rootNodeIndex);
}
}
}

View File

@@ -0,0 +1,53 @@
// 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.
#ifndef EXT_FAST_WINDING_NUMBER_H
#define EXT_FAST_WINDING_NUMBER_H
#include "ExtVec3.h"
#include "GuWindingNumberT.h"
namespace physx
{
namespace Ext
{
using Triangle = Gu::IndexedTriangleT<PxI32>;
using Triangle16 = Gu::IndexedTriangleT<PxI16>;
typedef Gu::ClusterApproximationT<PxF64, PxVec3d> ClusterApproximationF64;
typedef Gu::SecondOrderClusterApproximationT<PxF64, PxVec3d> SecondOrderClusterApproximationF64;
PxF64 computeWindingNumber(const PxArray<Gu::BVHNode>& tree, const PxVec3d& q, PxF64 beta, const PxHashMap<PxU32, ClusterApproximationF64>& clusters,
const PxArray<Triangle>& triangles, const PxArray<PxVec3d>& points);
void precomputeClusterInformation(PxArray<Gu::BVHNode>& tree, const PxArray<Triangle>& triangles,
const PxArray<PxVec3d>& points, PxHashMap<PxU32, ClusterApproximationF64>& result, PxI32 rootNodeIndex = 0);
}
}
#endif

View File

@@ -0,0 +1,192 @@
// 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 "ExtInsideTester.h"
#include "foundation/PxBounds3.h"
using namespace physx;
using namespace Ext;
// ------------------------------------------------------------------------
void InsideTester::init(const PxVec3 *vertices, PxI32 numVertices, const PxI32 *triIndices, PxI32 numTris)
{
PxArray<PxI32> newIds(numVertices, -1);
mVertices.clear();
mIndices.clear();
for (PxI32 i = 0; i < 3 * numTris; i++)
{
PxI32 id = triIndices[i];
if (newIds[id] < 0)
{
newIds[id] = PxI32(mVertices.size());
mVertices.pushBack(vertices[id]);
}
mIndices.pushBack(newIds[id]);
}
mGrids[0].init(0, mVertices, mIndices);
mGrids[1].init(1, mVertices, mIndices);
mGrids[2].init(2, mVertices, mIndices);
}
// ------------------------------------------------------------------------
bool InsideTester::isInside(const PxVec3 &pos)
{
PxI32 vote = 0;
vote += mGrids[0].numInside(pos, mVertices, mIndices);
vote += mGrids[1].numInside(pos, mVertices, mIndices);
vote += mGrids[2].numInside(pos, mVertices, mIndices);
return (vote > 3);
}
// ------------------------------------------------------------------------
void InsideTester::Grid2d::init(PxI32 _dim0, const PxArray<PxVec3> &vertices, const PxArray<PxI32> &indices)
{
first.clear();
tris.clear();
next.clear();
num1 = num2 = 0;
this->dim0 = _dim0;
PxI32 dim1 = (_dim0 + 1) % 3;
PxI32 dim2 = (_dim0 + 2) % 3;
PxI32 numTris = PxI32(indices.size()) / 3;
if (numTris == 0)
return;
PxBounds3 bounds, triBounds;
bounds.setEmpty();
PxReal avgSize = 0.0f;
for (PxI32 i = 0; i < numTris; i++)
{
triBounds.setEmpty();
triBounds.include(vertices[indices[3 * i]]);
triBounds.include(vertices[indices[3 * i + 1]]);
triBounds.include(vertices[indices[3 * i + 2]]);
triBounds.minimum[dim0] = 0.0f;
triBounds.maximum[dim0] = 0.0f;
avgSize += triBounds.getDimensions().magnitude();
bounds.include(triBounds);
}
if (bounds.isEmpty())
return;
avgSize /= PxReal(numTris);
orig = bounds.minimum;
spacing = avgSize;
num1 = PxI32((bounds.maximum[dim1] - orig[dim1]) / spacing) + 2;
num2 = PxI32((bounds.maximum[dim2] - orig[dim2]) / spacing) + 2;
first.clear();
first.resize(num1 * num2, -1);
for (PxI32 i = 0; i < numTris; i++)
{
triBounds.setEmpty();
triBounds.include(vertices[indices[3 * i]] - orig);
triBounds.include(vertices[indices[3 * i + 1]] - orig);
triBounds.include(vertices[indices[3 * i + 2]] - orig);
PxI32 min1 = PxI32(triBounds.minimum[dim1] / spacing);
PxI32 min2 = PxI32(triBounds.minimum[dim2] / spacing);
PxI32 max1 = PxI32(triBounds.maximum[dim1] / spacing);
PxI32 max2 = PxI32(triBounds.maximum[dim2] / spacing);
for (PxI32 i1 = min1; i1 <= max1; i1++)
{
for (PxI32 i2 = min2; i2 <= max2; i2++)
{
PxI32 nr = i1 * num2 + i2;
next.pushBack(first[nr]);
first[nr] = PxI32(tris.size());
tris.pushBack(i);
}
}
}
}
// ------------------------------------------------------------------------
PxI32 InsideTester::Grid2d::numInside(const PxVec3 &pos, const PxArray<PxVec3> &vertices, const PxArray<PxI32> &indices)
{
if (first.empty())
return 0;
PxI32 dim1 = (dim0 + 1) % 3;
PxI32 dim2 = (dim0 + 2) % 3;
PxReal r = 1e-5f;
PxVec3 p = pos;
p[dim1] = pos[dim1] + rnd.rand(0.0f, r);
p[dim2] = pos[dim2] + rnd.rand(0.0f, r);
PxI32 i1 = PxI32((p[dim1] - orig[dim1]) / spacing);
PxI32 i2 = PxI32((p[dim2] - orig[dim2]) / spacing);
if (i1 < 0 || i1 >= num1 || i2 < 0 || i2 >= num2)
return false;
PxI32 count1 = 0;
PxI32 count2 = 0;
PxI32 nr = first[i1 * num2 + i2];
while (nr >= 0)
{
PxI32 triNr = tris[nr];
nr = next[nr];
const PxVec3 &p0 = vertices[indices[3 * triNr]];
const PxVec3 &p1 = vertices[indices[3 * triNr + 1]];
const PxVec3 &p2 = vertices[indices[3 * triNr + 2]];
bool side0 = (p1 - p0).cross(p - p0)[dim0] > 0.0f;
bool side1 = (p2 - p1).cross(p - p1)[dim0] > 0.0f;
bool side2 = (p0 - p2).cross(p - p2)[dim0] > 0.0f;
if (side0 != side1 || side1 != side2)
continue;
// ray triangle intersection
PxVec3 n = (p1 - p0).cross(p2 - p0);
if (n[dim0] == 0.0f)
continue;
PxReal t = (p0 - p).dot(n) / n[dim0];
if (t > 0.0f)
count1++;
else if (t < 0.0f)
count2++;
}
PxI32 num = 0;
if ((count1 % 2) == 1)
num++;
if ((count2 % 2) == 1)
num++;
return num;
}

View File

@@ -0,0 +1,76 @@
// 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.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef EXT_INSIDE_TESTER_H
#define EXT_INSIDE_TESTER_H
// MM: tester whether a point is inside a triangle mesh
// all faces are projected onto the 3 canonical planes and hashed
// for fast ray mesh intersections
#include "foundation/PxVec3.h"
#include "foundation/PxArray.h"
#include "foundation/PxQuat.h"
#include "CmRandom.h"
namespace physx
{
namespace Ext
{
// ----------------------------------------------------------
class InsideTester
{
public:
void init(const PxVec3 *vertices, PxI32 numVertices, const PxI32 *triIndices, PxI32 numTris);
bool isInside(const PxVec3& pos);
private:
PxArray<PxVec3> mVertices;
PxArray<PxI32> mIndices;
struct Grid2d
{
void init(PxI32 dim0, const PxArray<PxVec3> &vertices, const PxArray<PxI32> &indices);
PxI32 numInside(const PxVec3&pos, const PxArray<PxVec3> &vertices, const PxArray<PxI32> &indices);
PxI32 dim0;
PxVec3 orig;
PxI32 num1, num2;
float spacing;
PxArray<PxI32> first;
PxArray<PxI32> tris;
PxArray<int> next;
Cm::RandomR250 rnd = Cm::RandomR250(0);
};
Grid2d mGrids[3];
};
}
}
#endif

View File

@@ -0,0 +1,174 @@
// 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.
#ifndef EXT_MARCHING_CUBES_TABLE_H
#define EXT_MARCHING_CUBES_TABLE_H
namespace physx
{
namespace Ext
{
// point numbering
// 7-----------6
// /| /|
// / | / |
// / | / |
// 4-----------5 |
// | | | |
// | 3-------|---2
// | / | /
// | / | /
// |/ |/
// 0-----------1
// edge numbering
// *-----6-----*
// /| /|
// 7 | 5 |
// / 11 / 10
// *-----4-----* |
// | | | |
// | *-----2-|---*
// 8 / 9 /
// | 3 | 1
// |/ |/
// *-----0-----*
// z
// | y
// | /
// |/
// 0---- x
int marchingCubeCorners[8][3] = { {0,0,0}, {1,0,0},{1,1,0},{0,1,0}, {0,0,1}, {1,0,1},{1,1,1},{0,1,1} };
int marchingCubeEdges[12][2] = { {0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7} };
int firstMarchingCubesId[257] = {
0, 0, 3, 6, 12, 15, 21, 27, 36, 39, 45, 51, 60, 66, 75, 84, 90, 93, 99, 105, 114,
120, 129, 138, 150, 156, 165, 174, 186, 195, 207, 219, 228, 231, 237, 243, 252, 258, 267, 276, 288,
294, 303, 312, 324, 333, 345, 357, 366, 372, 381, 390, 396, 405, 417, 429, 438, 447, 459, 471, 480,
492, 507, 522, 528, 531, 537, 543, 552, 558, 567, 576, 588, 594, 603, 612, 624, 633, 645, 657, 666,
672, 681, 690, 702, 711, 723, 735, 750, 759, 771, 783, 798, 810, 825, 840, 852, 858, 867, 876, 888,
897, 909, 915, 924, 933, 945, 957, 972, 984, 999, 1008, 1014, 1023, 1035, 1047, 1056, 1068, 1083, 1092, 1098,
1110, 1125, 1140, 1152, 1167, 1173, 1185, 1188, 1191, 1197, 1203, 1212, 1218, 1227, 1236, 1248, 1254, 1263, 1272, 1284,
1293, 1305, 1317, 1326, 1332, 1341, 1350, 1362, 1371, 1383, 1395, 1410, 1419, 1425, 1437, 1446, 1458, 1467, 1482, 1488,
1494, 1503, 1512, 1524, 1533, 1545, 1557, 1572, 1581, 1593, 1605, 1620, 1632, 1647, 1662, 1674, 1683, 1695, 1707, 1716,
1728, 1743, 1758, 1770, 1782, 1791, 1806, 1812, 1827, 1839, 1845, 1848, 1854, 1863, 1872, 1884, 1893, 1905, 1917, 1932,
1941, 1953, 1965, 1980, 1986, 1995, 2004, 2010, 2019, 2031, 2043, 2058, 2070, 2085, 2100, 2106, 2118, 2127, 2142, 2154,
2163, 2169, 2181, 2184, 2193, 2205, 2217, 2232, 2244, 2259, 2268, 2280, 2292, 2307, 2322, 2328, 2337, 2349, 2355, 2358,
2364, 2373, 2382, 2388, 2397, 2409, 2415, 2418, 2427, 2433, 2445, 2448, 2454, 2457, 2460, 2460 };
int marchingCubesIds[2460] = {
0, 8, 3, 0, 1, 9, 1, 8, 3, 9, 8, 1, 1, 2, 10, 0, 8, 3, 1, 2, 10, 9, 2, 10, 0, 2, 9, 2, 8, 3, 2,
10, 8, 10, 9, 8, 3, 11, 2, 0, 11, 2, 8, 11, 0, 1, 9, 0, 2, 3, 11, 1, 11, 2, 1, 9, 11, 9, 8, 11, 3,
10, 1, 11, 10, 3, 0, 10, 1, 0, 8, 10, 8, 11, 10, 3, 9, 0, 3, 11, 9, 11, 10, 9, 9, 8, 10, 10, 8, 11, 4,
7, 8, 4, 3, 0, 7, 3, 4, 0, 1, 9, 8, 4, 7, 4, 1, 9, 4, 7, 1, 7, 3, 1, 1, 2, 10, 8, 4, 7, 3,
4, 7, 3, 0, 4, 1, 2, 10, 9, 2, 10, 9, 0, 2, 8, 4, 7, 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, 8,
4, 7, 3, 11, 2, 11, 4, 7, 11, 2, 4, 2, 0, 4, 9, 0, 1, 8, 4, 7, 2, 3, 11, 4, 7, 11, 9, 4, 11, 9,
11, 2, 9, 2, 1, 3, 10, 1, 3, 11, 10, 7, 8, 4, 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, 4, 7, 8, 9,
0, 11, 9, 11, 10, 11, 0, 3, 4, 7, 11, 4, 11, 9, 9, 11, 10, 9, 5, 4, 9, 5, 4, 0, 8, 3, 0, 5, 4, 1,
5, 0, 8, 5, 4, 8, 3, 5, 3, 1, 5, 1, 2, 10, 9, 5, 4, 3, 0, 8, 1, 2, 10, 4, 9, 5, 5, 2, 10, 5,
4, 2, 4, 0, 2, 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, 9, 5, 4, 2, 3, 11, 0, 11, 2, 0, 8, 11, 4,
9, 5, 0, 5, 4, 0, 1, 5, 2, 3, 11, 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, 10, 3, 11, 10, 1, 3, 9,
5, 4, 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, 5, 4, 8, 5,
8, 10, 10, 8, 11, 9, 7, 8, 5, 7, 9, 9, 3, 0, 9, 5, 3, 5, 7, 3, 0, 7, 8, 0, 1, 7, 1, 5, 7, 1,
5, 3, 3, 5, 7, 9, 7, 8, 9, 5, 7, 10, 1, 2, 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, 8, 0, 2, 8,
2, 5, 8, 5, 7, 10, 5, 2, 2, 10, 5, 2, 5, 3, 3, 5, 7, 7, 9, 5, 7, 8, 9, 3, 11, 2, 9, 5, 7, 9,
7, 2, 9, 2, 0, 2, 7, 11, 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, 11, 2, 1, 11, 1, 7, 7, 1, 5, 9,
5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, 11, 10, 0, 11,
0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, 11, 10, 5, 7, 11, 5, 10, 6, 5, 0, 8, 3, 5, 10, 6, 9, 0, 1, 5,
10, 6, 1, 8, 3, 1, 9, 8, 5, 10, 6, 1, 6, 5, 2, 6, 1, 1, 6, 5, 1, 2, 6, 3, 0, 8, 9, 6, 5, 9,
0, 6, 0, 2, 6, 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, 2, 3, 11, 10, 6, 5, 11, 0, 8, 11, 2, 0, 10,
6, 5, 0, 1, 9, 2, 3, 11, 5, 10, 6, 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, 6, 3, 11, 6, 5, 3, 5,
1, 3, 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, 6, 5, 9, 6,
9, 11, 11, 9, 8, 5, 10, 6, 4, 7, 8, 4, 3, 0, 4, 7, 3, 6, 5, 10, 1, 9, 0, 5, 10, 6, 8, 4, 7, 10,
6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, 6, 1, 2, 6, 5, 1, 4, 7, 8, 1, 2, 5, 5, 2, 6, 3, 0, 4, 3,
4, 7, 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, 3,
11, 2, 7, 8, 4, 10, 6, 5, 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, 0, 1, 9, 4, 7, 8, 2, 3, 11, 5,
10, 6, 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, 5,
1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, 6,
5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, 10, 4, 9, 6, 4, 10, 4, 10, 6, 4, 9, 10, 0, 8, 3, 10, 0, 1, 10,
6, 0, 6, 4, 0, 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, 1, 4, 9, 1, 2, 4, 2, 6, 4, 3, 0, 8, 1,
2, 9, 2, 4, 9, 2, 6, 4, 0, 2, 4, 4, 2, 6, 8, 3, 2, 8, 2, 4, 4, 2, 6, 10, 4, 9, 10, 6, 4, 11,
2, 3, 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, 6, 4, 1, 6,
1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, 8, 11, 1, 8, 1, 0, 11,
6, 1, 9, 1, 4, 6, 4, 1, 3, 11, 6, 3, 6, 0, 0, 6, 4, 6, 4, 8, 11, 6, 8, 7, 10, 6, 7, 8, 10, 8,
9, 10, 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, 10, 6, 7, 10,
7, 1, 1, 7, 3, 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7,
3, 9, 7, 8, 0, 7, 0, 6, 6, 0, 2, 7, 3, 2, 6, 7, 2, 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, 2,
0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, 11,
2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, 0, 9, 1, 11,
6, 7, 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, 7, 11, 6, 7, 6, 11, 3, 0, 8, 11, 7, 6, 0, 1, 9, 11,
7, 6, 8, 1, 9, 8, 3, 1, 11, 7, 6, 10, 1, 2, 6, 11, 7, 1, 2, 10, 3, 0, 8, 6, 11, 7, 2, 9, 0, 2,
10, 9, 6, 11, 7, 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, 7, 2, 3, 6, 2, 7, 7, 0, 8, 7, 6, 0, 6,
2, 0, 2, 7, 6, 2, 3, 7, 0, 1, 9, 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, 10, 7, 6, 10, 1, 7, 1,
3, 7, 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, 7, 6, 10, 7,
10, 8, 8, 10, 9, 6, 8, 4, 11, 8, 6, 3, 6, 11, 3, 0, 6, 0, 4, 6, 8, 6, 11, 8, 4, 6, 9, 0, 1, 9,
4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, 6, 8, 4, 6, 11, 8, 2, 10, 1, 1, 2, 10, 3, 0, 11, 0, 6, 11, 0,
4, 6, 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, 8,
2, 3, 8, 4, 2, 4, 6, 2, 0, 4, 2, 4, 6, 2, 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, 1, 9, 4, 1,
4, 2, 2, 4, 6, 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, 10, 1, 0, 10, 0, 6, 6, 0, 4, 4, 6, 3, 4,
3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, 10, 9, 4, 6, 10, 4, 4, 9, 5, 7, 6, 11, 0, 8, 3, 4, 9, 5, 11,
7, 6, 5, 0, 1, 5, 4, 0, 7, 6, 11, 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, 9, 5, 4, 10, 1, 2, 7,
6, 11, 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, 3, 4, 8, 3,
5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, 7, 2, 3, 7, 6, 2, 5, 4, 9, 9, 5, 4, 0, 8, 6, 0, 6, 2, 6,
8, 7, 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, 9,
5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, 4, 0, 10, 4,
10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, 6, 9, 5, 6, 11, 9, 11,
8, 9, 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, 6, 11, 3, 6,
3, 5, 5, 3, 1, 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1,
2, 10, 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, 5,
8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, 9, 5, 6, 9, 6, 0, 0, 6, 2, 1, 5, 8, 1, 8, 0, 5, 6, 8, 3,
8, 2, 6, 2, 8, 1, 5, 6, 2, 1, 6, 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, 10, 1, 0, 10,
0, 6, 9, 5, 0, 5, 6, 0, 0, 3, 8, 5, 6, 10, 10, 5, 6, 11, 5, 10, 7, 5, 11, 11, 5, 10, 11, 7, 5, 8,
3, 0, 5, 11, 7, 5, 10, 11, 1, 9, 0, 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, 11, 1, 2, 11, 7, 1, 7,
5, 1, 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, 7, 5, 2, 7,
2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, 2, 5, 10, 2, 3, 5, 3, 7, 5, 8, 2, 0, 8, 5, 2, 8, 7, 5, 10,
2, 5, 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, 1,
3, 5, 3, 7, 5, 0, 8, 7, 0, 7, 1, 1, 7, 5, 9, 0, 3, 9, 3, 5, 5, 3, 7, 9, 8, 7, 5, 9, 7, 5,
8, 4, 5, 10, 8, 10, 11, 8, 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, 0, 1, 9, 8, 4, 10, 8, 10, 11, 10,
4, 5, 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, 0,
4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, 9,
4, 5, 2, 11, 3, 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, 5, 10, 2, 5, 2, 4, 4, 2, 0, 3, 10, 2, 3,
5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, 8, 4, 5, 8, 5, 3, 3,
5, 1, 0, 4, 5, 1, 0, 5, 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, 9, 4, 5, 4, 11, 7, 4, 9, 11, 9,
10, 11, 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, 3, 1, 4, 3,
4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, 9, 7, 4, 9, 11, 7, 9,
1, 11, 2, 11, 1, 0, 8, 3, 11, 7, 4, 11, 4, 2, 2, 4, 0, 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, 2,
9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, 3, 7, 10, 3,
10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, 1, 10, 2, 8, 7, 4, 4, 9, 1, 4, 1, 7, 7, 1, 3, 4, 9, 1, 4,
1, 7, 0, 8, 1, 8, 7, 1, 4, 0, 3, 7, 4, 3, 4, 8, 7, 9, 10, 8, 10, 11, 8, 3, 0, 9, 3, 9, 11, 11,
9, 10, 0, 1, 10, 0, 10, 8, 8, 10, 11, 3, 1, 10, 11, 3, 10, 1, 2, 11, 1, 11, 9, 9, 11, 8, 3, 0, 9, 3,
9, 11, 1, 2, 9, 2, 11, 9, 0, 2, 11, 8, 0, 11, 3, 2, 11, 2, 3, 8, 2, 8, 10, 10, 8, 9, 9, 10, 2, 0,
9, 2, 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, 1, 10, 2, 1, 3, 8, 9, 1, 8, 0, 9, 1, 0, 3, 8 };
}
}
#endif

View File

@@ -0,0 +1,726 @@
// 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 "ExtMeshSimplificator.h"
#include "foundation/PxSort.h"
using namespace physx;
using namespace Ext;
// -------------------------------------------------------------------------------------
MeshSimplificator::MeshSimplificator()
{
currentVertMark = 0;
numMeshTris = 0;
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::findTriNeighbors()
{
PxI32 numTris = PxI32(triIds.size()) / 3;
triNeighbors.clear();
triNeighbors.resize(3 * numTris, -1);
struct Edge
{
PX_FORCE_INLINE void init(PxI32 _id0, PxI32 _id1, PxI32 _triNr, PxI32 _edgeNr)
{
this->id0 = PxMin(_id0, _id1);
this->id1 = PxMax(_id0, _id1);
this->triNr = _triNr;
this->edgeNr = _edgeNr;
}
PX_FORCE_INLINE bool operator < (const Edge& e) const
{
return (id0 < e.id0 || (id0 == e.id0 && id1 < e.id1));
}
PX_FORCE_INLINE bool operator == (const Edge& e) const
{
return id0 == e.id0 && id1 == e.id1;
}
PxI32 id0, id1, triNr, edgeNr;
};
PxArray<Edge> edges(PxI32(triIds.size()));
for (PxI32 i = 0; i < numTris; i++)
{
for (PxI32 j = 0; j < 3; j++)
{
PxI32 id0 = triIds[3 * i + j];
PxI32 id1 = triIds[3 * i + (j + 1) % 3];
edges[3 * i + j].init(id0, id1, i, j);
}
}
PxSort(edges.begin(), edges.size());
PxI32 nr = 0;
while (nr < PxI32(edges.size()))
{
Edge& e0 = edges[nr];
nr++;
while (nr < PxI32(edges.size()) && edges[nr] == e0)
{
Edge& e1 = edges[nr];
triNeighbors[3 * e0.triNr + e0.edgeNr] = e1.triNr;
triNeighbors[3 * e1.triNr + e1.edgeNr] = e0.triNr;
nr++;
}
}
}
// -------------------------------------------------------------------------------------
bool MeshSimplificator::getAdjTris(PxI32 triNr, PxI32 vertNr, PxI32& valence, bool& open, PxArray<PxI32>* tris) const
{
open = false;
if (tris)
tris->clear();
PxI32 cnt = 0;
valence = 0;
PxI32 nr = triNr;
// counter clock
do
{
if (tris)
tris->pushBack(nr);
valence++;
if (triIds[3 * nr] == vertNr)
nr = triNeighbors[3 * nr + 2];
else if (triIds[3 * nr + 1] == vertNr)
nr = triNeighbors[3 * nr];
else
nr = triNeighbors[3 * nr + 1];
cnt++;
}
while (nr >= 0 && nr != triNr && cnt < 100);
if (cnt >= 100)
{
valence = 0;
return false;
}
cnt = 0;
if (nr < 0)
{ // open: search clockwise too
open = true;
nr = triNr;
do
{
if (nr != triNr)
{
if (tris)
tris->pushBack(nr);
valence++;
}
if (triIds[3 * nr] == vertNr)
nr = triNeighbors[3 * nr];
else if (triIds[3 * nr + 1] == vertNr)
nr = triNeighbors[3 * nr + 1];
else
nr = triNeighbors[3 * nr + 2];
cnt++;
}
while (nr >= 0 && nr != triNr && cnt < 100);
valence++; // num tris + 1 if open
if (cnt > 100)
{
valence = 0;
return false;
}
}
return true;
}
// -------------------------------------------------------------------------------------
bool MeshSimplificator::getAdjTris(PxI32 triNr, PxI32 vertNr, PxArray<PxI32>& tris) const
{
PxI32 valence;
bool open;
return getAdjTris(triNr, vertNr, valence, open, &tris);
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::replaceNeighbor(PxI32 triNr, PxI32 oldNeighbor, PxI32 newNeighbor)
{
if (triNr < 0)
return;
for (PxI32 i = 0; i < 3; i++)
{
if (triNeighbors[3 * triNr + i] == oldNeighbor)
triNeighbors[3 * triNr + i] = newNeighbor;
}
}
// -------------------------------------------------------------------------------------
PxI32 MeshSimplificator::getEdgeId(PxI32 triNr, PxI32 edgeNr)
{
PxI32 n = triNeighbors[3 * triNr + edgeNr];
if (n < 0 || triNr < n)
return 3 * triNr + edgeNr;
else
{
for (PxI32 i = 0; i < 3; i++)
{
if (triNeighbors[3 * n + i] == triNr)
return 3 * n + i;
}
}
return 0;
}
inline PxVec3Ex MeshSimplificator::projectPoint(const PxVec3& p)
{
PxU32 triangleId;
PxVec3 pos = projector->projectPoint(p, triangleId);
return PxVec3Ex(pos, triangleId);
}
// -------------------------------------------------------------------------------------
PxVec3Ex MeshSimplificator::evalEdgeCost(PxI32 triNr, PxI32 edgeNr, PxReal &cost)
{
const PxI32 numSteps = 10;
cost = -1.0f;
PxI32 id0 = triIds[3 * triNr + edgeNr];
PxI32 id1 = triIds[3 * triNr + (edgeNr + 1) % 3];
PxReal minCost = FLT_MAX;
PxReal maxCost = -FLT_MAX;
Quadric q; q = quadrics[id0] + quadrics[id1];
PxVec3Ex pos;
PxReal edgeLength = (vertices[id0].p - vertices[id1].p).magnitude();
for (PxI32 i = 0; i <= numSteps; i++)
{
float r = 1.0f / numSteps * i;
pos.p = vertices[id0].p * (1.0f - r) + vertices[id1].p * r;
if (projector)
pos = projectPoint(pos.p);
float c = q.outerProduct(pos.p);
c += edgeLengthCostWeight * edgeLength;
if (cost < 0.0f || c < cost)
{
cost = c;
}
if (cost > maxCost)
maxCost = cost;
if (cost < minCost)
minCost = cost;
}
if (maxCost - minCost < flatnessDetectionThreshold)
{
float r = 0.5f;
pos.p = vertices[id0].p * (1.0f - r) + vertices[id1].p * r;
if (projector)
pos = projectPoint(pos.p);
cost = q.outerProduct(pos.p) + edgeLengthCostWeight * edgeLength;
}
return pos;
}
// -------------------------------------------------------------------------------------
bool MeshSimplificator::collapseEdge(PxI32 triNr, PxI32 edgeNr)
{
if (triIds[3 * triNr] == triIds[3 * triNr + 1]) // the triangle deleted
return false;
PxI32 id0 = triIds[3 * triNr + edgeNr];
PxI32 id1 = triIds[3 * triNr + (edgeNr + 1) % 3];
PxI32 id2 = triIds[3 * triNr + (edgeNr + 2) % 3];
PxI32 id3 = -1;
PxI32 n = triNeighbors[3 * triNr + edgeNr];
PxI32 nEdgeNr = 0;
if (n >= 0)
{
if (triNeighbors[3 * n] == triNr) nEdgeNr = 0;
else if (triNeighbors[3 * n + 1] == triNr) nEdgeNr = 1;
else if (triNeighbors[3 * n + 2] == triNr) nEdgeNr = 2;
id3 = triIds[3 * n + (nEdgeNr + 2) % 3];
}
// not legal if there exists id != id0,id1 with (id0,id) and (id1,id) edges
// but (id,id0,id1) is not a triangle
bool OK = getAdjTris(triNr, id0, adjTris);
currentVertMark++;
for (PxI32 i = 0; i < PxI32(adjTris.size()); i++)
{
PxI32 adj = adjTris[i];
for (PxI32 j = 0; j < 3; j++)
vertMarks[triIds[3 * adj + j]] = currentVertMark;
}
OK = OK && getAdjTris(triNr, id1, adjTris);
if (!OK)
return false;
for (PxI32 i = 0; i < PxI32(adjTris.size()); i++)
{
PxI32 adj = adjTris[i];
for (PxI32 j = 0; j < 3; j++)
{
PxI32 id = triIds[3 * adj + j];
if (vertMarks[id] == currentVertMark &&
id != id0 && id != id1 && id != id2 && id != id3)
return false;
}
}
// new center pos
PxReal cost;
PxVec3Ex newPos = evalEdgeCost(triNr, edgeNr, cost);
//PxVec3 newPos = vertices[id0] * (1.0f - ratio) + vertices[id1] * ratio;
// any triangle flips?
for (PxI32 side = 0; side < 2; side++)
{
getAdjTris(triNr, side == 0 ? id0 : id1, adjTris);
PxI32 other = side == 0 ? id1 : id0;
for (PxU32 i = 0; i < adjTris.size(); i++)
{
PxI32 adj = adjTris[i];
PxVec3 p[3], q[3];
bool deleted = false;
for (PxI32 j = 0; j < 3; j++)
{
PxI32 id = triIds[3 * adj + j];
if (id == other)
deleted = true;
p[j] = vertices[id].p;
q[j] = (id == id0 || id == id1) ? newPos.p : p[j];
}
if (!deleted)
{
PxVec3 n0 = (p[1] - p[0]).cross(p[2] - p[0]);
PxVec3 n1 = (q[1] - q[0]).cross(q[2] - q[0]);
if (n0.dot(n1) <= 0.0f)
return false;
}
}
}
// remove adjacent edges from heap
for (PxI32 side = 0; side < 2; side++)
{
PxI32 id = side == 0 ? id0 : id1;
getAdjTris(triNr, id, adjTris);
for (PxU32 i = 0; i < adjTris.size(); i++)
{
PxI32 adj = adjTris[i];
for (PxI32 j = 0; j < 3; j++)
{
PxI32 adj0 = triIds[3 * adj + j];
PxI32 adj1 = triIds[3 * adj + (j + 1) % 3];
if (adj0 == id0 || adj0 == id1 || adj1 == id0 || adj1 == id1)
{
PxI32 edgeId = getEdgeId(adj, j);
heap.remove(edgeId);
}
}
}
}
// move vertex
if (id0 > id1) {
int id = id0; id0 = id1; id1 = id;
}
vertices[id0] = newPos;
quadrics[id0] += quadrics[id1];
// collapse edge
getAdjTris(triNr, id1, adjTris);
for (PxU32 i = 0; i < adjTris.size(); i++)
{
PxI32 adj = adjTris[i];
for (PxI32 j = 0; j < 3; j++) {
PxI32& id = triIds[3 * adj + j];
if (id == id1)
id = id0;
}
}
simplificationMap[id1] = id0;
// mark triangles as deleted (duplicate indices, can still be rendered)
triIds[3 * triNr + 2] = triIds[3 * triNr + 1] = triIds[3 * triNr];
numMeshTris--;
if (n >= 0)
{
triIds[3 * n + 2] = triIds[3 * n + 1] = triIds[3 * n];
numMeshTris--;
}
// update neighbors
PxI32 right = triNeighbors[3 * triNr + (edgeNr + 1) % 3];
PxI32 left = triNeighbors[3 * triNr + (edgeNr + 2) % 3];
replaceNeighbor(right, triNr, left);
replaceNeighbor(left, triNr, right);
PxI32 startTriNr = PxMax(right, left);
if (n >= 0)
{
right = triNeighbors[3 * n + (nEdgeNr + 1) % 3];
left = triNeighbors[3 * n + (nEdgeNr + 2) % 3];
replaceNeighbor(right, n, left);
replaceNeighbor(left, n, right);
startTriNr = PxMax(startTriNr, PxMax(right, left));
}
// add new edges to heap
if (startTriNr >= 0)
{
getAdjTris(startTriNr, id0, adjTris);
for (PxU32 i = 0; i < adjTris.size(); i++)
{
PxI32 adj = adjTris[i];
for (PxI32 j = 0; j < 3; j++)
{
PxI32 adj0 = triIds[3 * adj + j];
PxI32 adj1 = triIds[3 * adj + (j + 1) % 3];
if (adj0 == id0 || adj1 == id0)
{
evalEdgeCost(adj, j, cost);
PxI32 id = getEdgeId(adj, j);
heap.insert(HeapElem(adj, j, cost), id);
}
}
}
}
return true;
}
#if PX_LINUX
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmisleading-indentation"
#endif
static void minMax(const PxArray<PxVec3Ex>& points, PxVec3& min, PxVec3& max)
{
min = PxVec3(FLT_MAX, FLT_MAX, FLT_MAX);
max = PxVec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (PxU32 i = 0; i < points.size(); ++i)
{
const PxVec3& p = points[i].p;
if (p.x > max.x) max.x = p.x; if (p.y > max.y) max.y = p.y; if (p.z > max.z) max.z = p.z;
if (p.x < min.x) min.x = p.x; if (p.y < min.y) min.y = p.y; if (p.z < min.z) min.z = p.z;
}
}
#if PX_LINUX
#pragma GCC diagnostic pop
#endif
void MeshSimplificator::transformPointsToUnitBox(PxArray<PxVec3Ex>& points)
{
PxVec3 min, max;
minMax(points, min, max);
origin = min;
PxVec3 size = max - min;
scaling = 1.0f / PxMax(PxMax(1e-6f, size.x), PxMax(size.y, size.z));
for (PxU32 i = 0; i < points.size(); ++i)
points[i].p = (points[i].p - min) * scaling;
}
void MeshSimplificator::transformPointsToOriginalPosition(PxArray<PxVec3>& points)
{
PxReal s = 1.0f / scaling;
for (PxU32 i = 0; i < points.size(); ++i)
points[i] = points[i] * s + origin;
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::init(const PxSimpleTriangleMesh& inputMesh, PxReal edgeLengthCostWeight_,
PxReal flatnessDetectionThreshold_, bool projectSimplifiedPointsOnInputMeshSurface)
{
edgeLengthCostWeight = edgeLengthCostWeight_;
flatnessDetectionThreshold = flatnessDetectionThreshold_;
vertices.resize(inputMesh.points.count);
for (PxU32 i = 0; i < inputMesh.points.count; i++)
vertices[i] = PxVec3Ex(inputMesh.points.at<PxVec3>(i));
transformPointsToUnitBox(vertices);
PxI32 numIndices = 3 * inputMesh.triangles.count;
triIds.resize(numIndices);
if (inputMesh.flags & PxMeshFlag::e16_BIT_INDICES)
{
for (PxI32 i = 0; i < numIndices; i++)
triIds[i] = PxI32(inputMesh.triangles.at<PxU16>(i));
}
else
{
for (PxI32 i = 0; i < numIndices; i++)
triIds[i] = PxI32(inputMesh.triangles.at<PxU32>(i));
}
for (PxU32 i = 0; i < triIds.size(); i++)
vertices[triIds[i]].i = i / 3;
if (projectSimplifiedPointsOnInputMeshSurface)
{
originalTriIds.resize(triIds.size());
for (PxU32 i = 0; i < triIds.size(); ++i)
originalTriIds[i] = triIds[i];
scaledOriginalVertices.resize(inputMesh.points.count);
for (PxU32 i = 0; i < inputMesh.points.count; i++)
scaledOriginalVertices[i] = vertices[i].p;
projector = Gu::PxCreatePointOntoTriangleMeshProjector(scaledOriginalVertices.begin(), originalTriIds.begin(), inputMesh.triangles.count);
}
else
projector = NULL;
init();
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::init(const PxArray<PxVec3> &inputVertices, const PxArray<PxU32> &inputTriIds, PxReal edgeLengthCostWeight_,
PxReal flatnessDetectionThreshold_, bool projectSimplifiedPointsOnInputMeshSurface)
{
edgeLengthCostWeight = edgeLengthCostWeight_;
flatnessDetectionThreshold = flatnessDetectionThreshold_;
vertices.resize(inputVertices.size());
for (PxU32 i = 0; i < inputVertices.size(); i++)
vertices[i] = PxVec3Ex(inputVertices[i]);
for (PxU32 i = 0; i < inputTriIds.size(); i++)
vertices[inputTriIds[i]].i = i / 3;
transformPointsToUnitBox(vertices);
triIds.resize(inputTriIds.size());
for (PxU32 i = 0; i < inputTriIds.size(); i++)
triIds[i] = PxI32(inputTriIds[i]);
if (projectSimplifiedPointsOnInputMeshSurface)
{
scaledOriginalVertices.resize(inputVertices.size());
for (PxU32 i = 0; i < inputVertices.size(); i++)
scaledOriginalVertices[i] = vertices[i].p;
projector = Gu::PxCreatePointOntoTriangleMeshProjector(scaledOriginalVertices.begin(), inputTriIds.begin(), inputTriIds.size() / 3);
}
else
projector = NULL;
init();
}
MeshSimplificator::~MeshSimplificator()
{
if (projector)
{
PX_RELEASE(projector)
}
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::init()
{
vertMarks.clear();
vertMarks.resize(vertices.size(), 0);
currentVertMark = 0;
findTriNeighbors();
// init vertex quadrics
quadrics.resize(vertices.size());
for (PxU32 i = 0; i < vertices.size(); i++)
quadrics[i].zero();
Quadric q;
PxI32 numTris = PxI32(triIds.size()) / 3;
for (PxI32 i = 0; i < numTris; i++)
{
PxI32 id0 = triIds[3 * i];
PxI32 id1 = triIds[3 * i + 1];
PxI32 id2 = triIds[3 * i + 2];
q.setFromPlane(vertices[id0].p, vertices[id1].p, vertices[id2].p);
quadrics[id0] += q;
quadrics[id1] += q;
quadrics[id2] += q;
}
// init heap
heap.clear();
for (PxI32 i = 0; i < numTris; i++)
{
for (PxI32 j = 0; j < 3; j++)
{
PxI32 n = triNeighbors[3 * i + j];
if (n < 0 || i < n)
{
PxReal cost;
evalEdgeCost(i, j, cost);
heap.insert(HeapElem(i, j, cost), getEdgeId(i, j));
}
}
}
numMeshTris = numTris;
// init simplification map
simplificationMap.resize(vertices.size());
for (PxI32 i = 0; i < PxI32(vertices.size()); i++)
simplificationMap[i] = i; // each vertex is a root
}
// -------------------------------------------------------------------------------------
bool MeshSimplificator::step(PxF32 maximalEdgeLength)
{
int heapMinSize = 20;
if (heap.size() < heapMinSize)
return false;
while (heap.size() > heapMinSize)
{
HeapElem e = heap.deleteMin();
PxI32 id0 = triIds[3 * e.triNr + e.edgeNr];
PxI32 id1 = triIds[3 * e.triNr + (e.edgeNr + 1) % 3];
PxF32 length = (vertices[id0].p - vertices[id1].p).magnitude();
if (maximalEdgeLength == 0.0f || length < maximalEdgeLength)
{
collapseEdge(e.triNr, e.edgeNr);
return true;
}
}
return false;
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::decimateByRatio(PxF32 relativeOutputMeshSize, PxF32 maximalEdgeLength)
{
relativeOutputMeshSize = PxClamp(relativeOutputMeshSize, 0.1f, 0.99f);
PxI32 numSteps = PxI32(PxFloor(PxF32(heap.size()) * (1.0f - relativeOutputMeshSize)));
for (PxI32 i = 0; i < numSteps; i++)
{
if (!step(maximalEdgeLength))
break;
}
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::decimateBySize(PxI32 targetTriangleCount, PxF32 maximalEdgeLength)
{
while (numMeshTris > targetTriangleCount)
{
if (!step(maximalEdgeLength))
break;
}
}
// -------------------------------------------------------------------------------------
void MeshSimplificator::readBack(PxArray<PxVec3>& outVertices, PxArray<PxU32>& outTriIds, PxArray<PxU32> *vertexMap, PxArray<PxU32> *outputVertexToInputTriangle)
{
outVertices.clear();
outTriIds.clear();
PxArray<PxI32> idMap(vertices.size(), -1);
PxI32 numTris = PxI32(triIds.size()) / 3;
for (PxI32 i = 0; i < numTris; i++)
{
if (triIds[3 * i] == triIds[3 * i + 1]) // deleted
continue;
for (PxI32 j = 0; j < 3; j++)
{
PxI32 id = triIds[3 * i + j];
if (idMap[id] < 0)
{
idMap[id] = outVertices.size();
outVertices.pushBack(vertices[id].p);
if(outputVertexToInputTriangle && projector)
outputVertexToInputTriangle->pushBack(vertices[id].i);
}
outTriIds.pushBack(PxU32(idMap[id]));
}
}
transformPointsToOriginalPosition(outVertices);
if (vertexMap)
{
for (PxU32 i = 0; i < simplificationMap.size(); ++i)
{
PxI32 id = i;
while (id != simplificationMap[id])
{
id = simplificationMap[id];
}
const PxI32 finalLink = id;
id = i;
simplificationMap[i] = finalLink;
while (id != simplificationMap[id])
{
PxI32 oldId = id;
id = simplificationMap[id];
simplificationMap[oldId] = finalLink;
}
}
vertexMap->resize(vertices.size());
for (PxU32 i = 0; i < simplificationMap.size(); ++i)
{
PxI32 mapped = idMap[simplificationMap[i]];
(*vertexMap)[i] = mapped;
}
}
}

View File

@@ -0,0 +1,135 @@
// 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.
#ifndef EXT_MESH_SIMPLIFICATOR_H
#define EXT_MESH_SIMPLIFICATOR_H
#include "foundation/PxBounds3.h"
#include "foundation/PxArray.h"
#include "geometry/PxSimpleTriangleMesh.h"
#include "ExtQuadric.h"
#include "ExtRandomAccessHeap.h"
#include "GuSDF.h"
// ------------------------------------------------------------------------------
// MM: implementation of paper Garland and Heckbert: "Surface Simplification Using Quadric Error Metrics"
namespace physx
{
namespace Ext
{
struct PxVec3Ex
{
PxVec3 p;
PxU32 i = 0xFFFFFFFF;
explicit PxVec3Ex() : p(0.0f), i(0xFFFFFFFF)
{
}
explicit PxVec3Ex(PxVec3 point, PxU32 sourceTriangleIndex = 0xFFFFFFFF) : p(point), i(sourceTriangleIndex)
{
}
};
class MeshSimplificator
{
public:
MeshSimplificator();
void init(const PxSimpleTriangleMesh& inputMesh, PxReal edgeLengthCostWeight_ = 1e-1f, PxReal flatnessDetectionThreshold_ = 1e-2f, bool projectSimplifiedPointsOnInputMeshSurface = false);
void init(const PxArray<PxVec3> &vertices, const PxArray<PxU32> &triIds, PxReal edgeLengthCostWeight_ = 1e-1f, PxReal flatnessDetectionThreshold_ = 1e-2f, bool projectSimplifiedPointsOnInputMeshSurface = false);
void decimateByRatio(PxF32 relativeOutputMeshSize = 0.5f, PxF32 maximalEdgeLength = 0.0f);
void decimateBySize(PxI32 targetTriangleCount, PxF32 maximalEdgeLength = 0.0f);
void readBack(PxArray<PxVec3>& vertices, PxArray<PxU32>& triIds, PxArray<PxU32> *vertexMap = NULL, PxArray<PxU32> *outputVertexToInputTriangle = NULL);
~MeshSimplificator();
private:
PxArray<PxVec3Ex> vertices;
PxArray<PxI32> triIds;
PxArray<PxVec3> scaledOriginalVertices;
PxArray<PxU32> originalTriIds;
Gu::PxPointOntoTriangleMeshProjector* projector;
void init();
bool step(PxF32 maximalEdgeLength);
bool getAdjTris(PxI32 triNr, PxI32 vertNr, PxI32& valence, bool& open,
PxArray<PxI32>* tris) const;
bool getAdjTris(PxI32 triNr, PxI32 vertNr, PxArray<PxI32>& tris) const;
void replaceNeighbor(PxI32 triNr, PxI32 oldNeighbor, PxI32 newNeighbor);
PxI32 getEdgeId(PxI32 triNr, PxI32 edgeNr);
bool collapseEdge(PxI32 triNr, PxI32 edgeNr);
PxVec3Ex evalEdgeCost(PxI32 triNr, PxI32 edgeNr, PxReal& costt);
PxVec3Ex projectPoint(const PxVec3& p);
void findTriNeighbors();
void transformPointsToUnitBox(PxArray<PxVec3Ex>& points);
void transformPointsToOriginalPosition(PxArray<PxVec3>& points);
PxI32 numMeshTris;
PxArray<Quadric> quadrics;
PxArray<PxI32> vertMarks;
PxArray<PxI32> adjTris;
PxI32 currentVertMark;
PxArray<PxI32> triNeighbors;
//Scale input points into 0...1 unit-box
PxReal scaling;
PxVec3 origin;
PxReal edgeLengthCostWeight;
PxReal flatnessDetectionThreshold;
PxArray<PxI32> simplificationMap;
struct HeapElem
{
HeapElem() : triNr(0), edgeNr(0), cost(0.0f) {}
HeapElem(PxI32 triNr_, PxI32 edgeNr_, float cost_) :
triNr(triNr_), edgeNr(edgeNr_), cost(cost_) {}
PxI32 triNr, edgeNr;
float cost;
PX_FORCE_INLINE bool operator < (const HeapElem& e) const
{
return cost < e.cost;
}
};
RandomAccessHeap<HeapElem> heap;
};
}
}
#endif

View File

@@ -0,0 +1,276 @@
// 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.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef EXT_MULTI_LIST_H
#define EXT_MULTI_LIST_H
// MM: Multiple linked lists in a common array with a free list
#include "foundation/PxArray.h"
namespace physx
{
namespace Ext
{
//-----------------------------------------------------------------------------
template <class T>
class MultiList {
public:
MultiList(PxI32 maxId = 0) {
firstFree = -1;
if (maxId > 0)
first.reserve(maxId + 1);
}
void reserve(int maxId) {
first.reserve(maxId + 1);
}
void clear();
PxI32 add(PxI32 id, const T &item);
bool addUnique(PxI32 id, const T &item);
bool exists(PxI32 id, const T &item) const;
void remove(PxI32 id, const T &item);
void removeAll(PxI32 id);
PxI32 size(PxI32 id) const;
PxI32 getPairNr(PxI32 id, const T &item) const;
void replace(PxI32 id, const T &before, const T &after);
void getItems(PxI32 id) const;
mutable PxArray<T> queryItems;
void initIteration(PxI32 id, PxI32& iterator);
bool iterate(T& item, PxI32& iterator);
void getPointers(PxI32 id);
mutable PxArray<T*> queryPointers;
private:
PxArray<PxI32> first;
PxArray<T> items;
PxArray<PxI32> next;
PxI32 firstFree;
};
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::clear()
{
first.clear();
next.clear();
items.clear();
queryItems.clear();
queryPointers.clear();
firstFree = -1;
}
//-----------------------------------------------------------------------------
template <class T>
PxI32 MultiList<T>::add(PxI32 id, const T &item)
{
if (id >= PxI32(first.size()))
first.resize(id + 1, -1);
PxI32 pos = firstFree;
if (pos >= 0)
firstFree = next[firstFree];
else
{
pos = PxI32(items.size());
items.resize(items.size() + 1);
next.resize(items.size() + 1);
}
next[pos] = first[id];
first[id] = pos;
items[pos] = item;
return pos;
}
//-----------------------------------------------------------------------------
template <class T>
bool MultiList<T>::addUnique(PxI32 id, const T &item)
{
if (exists(id, item))
return false;
add(id, item);
return true;
}
//-----------------------------------------------------------------------------
template <class T>
bool MultiList<T>::exists(PxI32 id, const T &item) const
{
return getPairNr(id, item) >= 0;
}
//-----------------------------------------------------------------------------
template <class T>
PxI32 MultiList<T>::size(PxI32 id) const
{
if (id >= PxI32(first.size()))
return 0;
PxI32 num = 0;
PxI32 nr = first[id];
while (nr >= 0)
{
num++;
nr = next[nr];
}
return num;
}
//-----------------------------------------------------------------------------
template <class T>
PxI32 MultiList<T>::getPairNr(PxI32 id, const T &item) const
{
if (id < 0 || id >= PxI32(first.size()))
return -1;
PxI32 nr = first[id];
while (nr >= 0)
{
if (items[nr] == item)
return nr;
nr = next[nr];
}
return -1;
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::remove(PxI32 id, const T &itemNr)
{
PxI32 nr = first[id];
PxI32 prev = -1;
while (nr >= 0 && items[nr] != itemNr)
{
prev = nr;
nr = next[nr];
}
if (nr < 0)
return;
if (prev >= 0)
next[prev] = next[nr];
else
first[id] = next[nr];
next[nr] = firstFree;
firstFree = nr;
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::replace(PxI32 id, const T &before, const T &after)
{
PxI32 nr = first[id];
while (nr >= 0)
{
if (items[nr] == before)
items[nr] = after;
nr = next[nr];
}
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::removeAll(PxI32 id)
{
if (id >= PxI32(first.size()))
return;
PxI32 nr = first[id];
if (nr < 0)
return;
PxI32 prev = -1;
while (nr >= 0)
{
prev = nr;
nr = next[nr];
}
next[prev] = firstFree;
firstFree = first[id];
first[id] = -1;
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::getItems(PxI32 id) const
{
queryItems.clear();
if (id >= PxI32(first.size()))
return;
PxI32 nr = first[id];
while (nr >= 0)
{
queryItems.push_back(items[nr]);
nr = next[nr];
}
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::initIteration(PxI32 id, PxI32& iterator)
{
if (id >= PxI32(first.size()))
iterator = -1;
else
iterator = first[id];
}
//-----------------------------------------------------------------------------
template <class T>
bool MultiList<T>::iterate(T& item, PxI32& iterator)
{
if (iterator >= 0)
{
item = items[iterator];
iterator = next[iterator];
return true;
}
return false;
}
//-----------------------------------------------------------------------------
template <class T>
void MultiList<T>::getPointers(PxI32 id)
{
queryPointers.clear();
if (id >= PxI32(first.size()))
return;
PxI32 nr = first[id];
while (nr >= 0)
{
queryPointers.push_back(&items[nr]);
nr = next[nr];
}
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,169 @@
// 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.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef EXT_OCTREE_TETRAHEDRALIZER_H
#define EXT_OCTREE_TETRAHEDRALIZER_H
#include "ExtMultiList.h"
#include "ExtVec3.h"
#include "foundation/PxVec3.h"
#include "ExtInsideTester.h"
namespace physx
{
namespace Ext
{
class InsideTester;
// ------------------------------------------------------------------------------
class OctreeTetrahedralizer
{
public:
OctreeTetrahedralizer();
void clear();
void createTetMesh(const PxArray<PxVec3> &verts, const PxArray<PxU32> &triIds,
bool includeOctreeNodes = true, PxI32 maxVertsPerCell = 20, PxI32 maxTreeDepth = 5);
void readBack(PxArray<PxVec3> &tetVertices, PxArray<PxU32> &tetIndices);
private:
// input mesh
PxArray<PxVec3> surfaceVerts;
PxArray<PxI32> surfaceTriIds;
// octree
PxI32 maxVertsPerCell;
PxI32 maxTreeDepth;
struct Cell
{
void init()
{
firstChild = -1;
orig = PxVec3d(0.0, 0.0, 0.0);
size = 0.0;
numVerts = 0;
closestTetNr = -1;
depth = 0;
}
PxI32 getChildNr(const PxVec3d& p);
PX_FORCE_INLINE PxI32 getChildNr(const PxVec3& p)
{
return getChildNr(PxVec3d(PxF64(p.x), PxF64(p.y), PxF64(p.z)));
}
PxI32 firstChild;
PxI32 firstCellVert;
PxI32 firstCellTet;
PxVec3d orig;
double size;
PxI32 numVerts;
PxI32 closestTetNr;
PxI32 depth;
};
PxArray<Cell> cells;
MultiList<PxI32> vertsOfCell;
// tet mesh
PxArray<PxVec3d> tetVerts;
PxArray<PxI32> tetIds;
PxArray<PxI32> tetNeighbors;
PxArray<PxI32> tetMarks;
PxI32 currentTetMark;
PxArray<PxI32> stack;
PxArray<PxI32> violatingTets;
PxI32 firstABBVert;
struct Edge
{
PxI32 id0, id1;
PxI32 faceNr, tetNr;
void init(PxI32 _id0, PxI32 _id1, PxI32 _tetNr, PxI32 _faceNr)
{
this->id0 = _id0 < _id1 ? _id0 : _id1;
this->id1 = _id0 > _id1 ? _id0 : _id1;
this->tetNr = _tetNr;
this->faceNr = _faceNr;
}
PX_FORCE_INLINE bool operator < (Edge e) const
{
if (id0 < e.id0) return true;
if (id0 > e.id0) return false;
return id1 < e.id1;
}
PX_FORCE_INLINE bool operator == (Edge e)
{
return id0 == e.id0 && id1 == e.id1;
}
};
PxArray<Edge> edges;
void clearTets();
void createTree();
void treeInsertVert(PxI32 cellNr, PxI32 vertNr);
void createTetVerts(bool includeOctreeNodes);
bool findSurroundingTet(const PxVec3d& p, PxI32 startTetNr, PxI32& tetNr);
bool findSurroundingTet(const PxVec3d& p, PxI32& tetNr);
void treeInsertTet(PxI32 tetNr);
void treeRemoveTet(PxI32 tetNr);
PxI32 firstFreeTet;
PxI32 getNewTetNr();
void removeTetNr(PxI32 tetNr);
PxVec3d getTetCenter(PxI32 tetNr) const;
bool meshInsertTetVert(PxI32 vertNr);
InsideTester insideTester;
void pruneTets();
mutable float prevClip;
mutable float prevScale;
mutable PxArray<PxVec3> renderVerts;
mutable PxArray<PxVec3> renderNormals;
mutable PxArray<PxI32> renderTriIds;
};
}
}
#endif

View File

@@ -0,0 +1,93 @@
// 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.
#ifndef EXT_QUADRIC_H
#define EXT_QUADRIC_H
#include "foundation/PxVec3.h"
// MM: used in ExtMeshSimplificator
// see paper Garland and Heckbert: "Surface Simplification Using Quadric Error Metrics"
namespace physx
{
namespace Ext
{
class Quadric {
public:
PX_FORCE_INLINE void zero()
{
a00 = 0.0f; a01 = 0.0f; a02 = 0.0f; a03 = 0.0f;
a11 = 0.0f; a12 = 0.0f; a13 = 0.0f;
a22 = 0.0f; a23 = 0.0f;
a33 = 0.0f;
}
PX_FORCE_INLINE void setFromPlane(const PxVec3& v0, const PxVec3& v1, const PxVec3& v2)
{
PxVec3 n = (v1 - v0).cross(v2 - v0); n.normalize();
float d = -n.dot(v0);
a00 = n.x * n.x; a01 = n.x * n.y; a02 = n.x * n.z; a03 = n.x * d;
a11 = n.y * n.y; a12 = n.y * n.z; a13 = n.y * d;
a22 = n.z * n.z; a23 = n.z * d;
a33 = d * d;
}
PX_FORCE_INLINE Quadric operator +(const Quadric& q) const
{
Quadric sum;
sum.a00 = a00 + q.a00; sum.a01 = a01 + q.a01; sum.a02 = a02 + q.a02; sum.a03 = a03 + q.a03;
sum.a11 = a11 + q.a11; sum.a12 = a12 + q.a12; sum.a13 = a13 + q.a13;
sum.a22 = a22 + q.a22; sum.a23 = a23 + q.a23;
sum.a33 = a33 + q.a33;
return sum;
}
void operator +=(const Quadric& q)
{
a00 += q.a00; a01 += q.a01; a02 += q.a02; a03 += q.a03;
a11 += q.a11; a12 += q.a12; a13 += q.a13;
a22 += q.a22; a23 += q.a23;
a33 += q.a33;
}
PxF32 outerProduct(const PxVec3& v)
{
return a00 * v.x * v.x + 2.0f * a01 * v.x * v.y + 2.0f * a02 * v.x * v.z + 2.0f * a03 * v.x +
a11 * v.y * v.y + 2.0f * a12 * v.y * v.z + 2.0f * a13 * v.y +
a22 * v.z * v.z + 2.0f * a23 * v.z + a33;
}
private:
PxF32 a00, a01, a02, a03;
PxF32 a11, a12, a13;
PxF32 a22, a23;
PxF32 a33;
};
}
}
#endif

View File

@@ -0,0 +1,218 @@
// 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.
#ifndef EXT_RANDOM_ACCESS_HEAP_H
#define EXT_RANDOM_ACCESS_HEAP_H
#include "foundation/PxArray.h"
// MM: heap which allows the modification of the priorities of entries stored anywhere in the tree
// for this, every entry gets an id via which it can be accessed
// used in ExtMeshSimplificator to sort edges w.r.t. their error metric
// ------------------------------------------------------------------------------
namespace physx
{
namespace Ext
{
template <class T> class RandomAccessHeap
{
public:
RandomAccessHeap() {
heap.resize(1); // dummy such that root is at 1 for faster parent child computation
ids.resize(1);
nextId = 0;
}
void resizeFast(PxArray<PxI32>& arr, PxU32 newSize, PxI32 value = 0)
{
if (newSize < arr.size())
arr.removeRange(newSize, arr.size() - newSize);
else
{
while (arr.size() < newSize)
arr.pushBack(value);
}
}
PxI32 insert(T elem, PxI32 id = -1) // id for ability to alter the entry later or to identify duplicates
{
if (id < 0) {
id = nextId;
nextId++;
}
else if (id >= nextId)
nextId = id + 1;
if (id >= 0 && id < PxI32(posOfId.size()) && posOfId[id] >= 0)
return id; // already in heap
heap.pushBack(elem);
ids.pushBack(id);
if (id >= PxI32(posOfId.size()))
resizeFast(posOfId, id + 1, -1);
posOfId[id] = heap.size() - 1;
percolate(PxI32(heap.size()) - 1);
return id;
}
bool remove(PxI32 id)
{
PxI32 i = posOfId[id];
if (i < 0)
return false;
posOfId[id] = -1;
T prev = heap[i];
heap[i] = heap.back();
heap.popBack();
ids[i] = ids.back();
ids.popBack();
if (i < PxI32(heap.size()))
{
posOfId[ids[i]] = i;
if (heap.size() > 1)
{
if (heap[i] < prev)
percolate(i);
else
siftDown(i);
}
}
return true;
}
T deleteMin()
{
T min(-1, -1, 0.0f);
if (heap.size() > 1)
{
min = heap[1];
posOfId[ids[1]] = -1;
heap[1] = heap.back();
heap.popBack();
ids[1] = ids.back();
ids.popBack();
posOfId[ids[1]] = 1;
siftDown(1);
}
return min;
}
void makeHeap(const PxArray<T> &elems) // O(n) instead of inserting one after the other O(n log n)
{
heap.resize(elems.size() + 1);
ids.resize(elems.size() + 1);
posOfId.resize(elems.size());
for (PxU32 i = 0; i < elems.size(); i++)
{
heap[i + 1] = elems[i];
ids[i + 1] = i;
posOfId[ids[i + 1]] = i + 1;
}
PxI32 n = (heap.size() - 1) >> 1;
for (PxI32 i = n; i >= 1; i--)
siftDown(i);
}
void clear()
{
heap.capacity() == 0 ? heap.resize(1) : heap.forceSize_Unsafe(1);
ids.capacity() == 0 ? ids.resize(1) : ids.forceSize_Unsafe(1);
posOfId.forceSize_Unsafe(0);
nextId = 0;
}
PX_FORCE_INLINE PxI32 size() { return heap.size() - 1; }
PX_FORCE_INLINE bool empty() { return heap.size() <= 1; }
private:
void siftDown(PxI32 i)
{
PxI32 n = PxI32(heap.size()) - 1;
PxI32 k = i;
PxI32 j;
do
{
j = k;
if (2 * j < n && heap[2 * j] < heap[k])
k = 2 * j;
if (2 * j < n && heap[2 * j + 1] < heap[k])
k = 2 * j + 1;
T temp = heap[j]; heap[j] = heap[k]; heap[k] = temp;
PxI32 id = ids[j]; ids[j] = ids[k]; ids[k] = id;
posOfId[ids[j]] = j;
posOfId[ids[k]] = k;
}
while (j != k);
}
void percolate(PxI32 i)
{
PxI32 k = i;
PxI32 j;
do
{
j = k;
if (j > 1 && !(heap[j >> 1] < heap[k]))
k = j >> 1;
T temp = heap[j]; heap[j] = heap[k]; heap[k] = temp;
PxI32 id = ids[j]; ids[j] = ids[k]; ids[k] = id;
posOfId[ids[j]] = j;
posOfId[ids[k]] = k;
}
while (j != k);
}
PxArray<T> heap;
PxArray<PxI32> ids;
PxArray<PxI32> posOfId;
PxI32 nextId;
};
}
}
#endif

View File

@@ -0,0 +1,656 @@
// 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 "ExtRemesher.h"
#include "ExtBVH.h"
#include "ExtMarchingCubesTable.h"
#include "foundation/PxFPU.h"
#include "foundation/PxSort.h"
#include "GuIntersectionTriangleBox.h"
#include "GuBox.h"
using namespace physx;
using namespace Ext;
// -------------------------------------------------------------------------------------
void Remesher::clear()
{
cells.clear();
vertices.clear();
normals.clear();
triIds.clear();
triNeighbors.clear();
}
#define HASH_SIZE 170111
// -------------------------------------------------------------------------------------
PX_FORCE_INLINE static PxU32 hash(PxI32 xi, PxI32 yi, PxI32 zi)
{
PxU32 h = (xi * 92837111) ^ (yi * 689287499) ^ (zi * 283923481);
return h % HASH_SIZE;
}
// -------------------------------------------------------------------------------------
void Remesher::addCell(PxI32 xi, PxI32 yi, PxI32 zi)
{
Cell c;
c.init(xi, yi, zi);
PxU32 h = hash(xi, yi, zi);
c.next = firstCell[h];
firstCell[h] = PxI32(cells.size());
cells.pushBack(c);
}
// -------------------------------------------------------------------------------------
PxI32 Remesher::getCellNr(PxI32 xi, PxI32 yi, PxI32 zi) const
{
PxU32 h = hash(xi, yi, zi);
PxI32 nr = firstCell[h];
while (nr >= 0)
{
const Cell& c = cells[nr];
if (c.xi == xi && c.yi == yi && c.zi == zi)
return nr;
nr = c.next;
}
return -1;
}
// -------------------------------------------------------------------------------------
PX_FORCE_INLINE bool Remesher::cellExists(PxI32 xi, PxI32 yi, PxI32 zi) const
{
return getCellNr(xi, yi, zi) >= 0;
}
// -------------------------------------------------------------------------------------
void Remesher::remesh(const PxArray<PxVec3>& inputVerts, const PxArray<PxU32>& inputTriIds,
PxU32 resolution, PxArray<PxU32> *vertexMap)
{
PX_SIMD_GUARD
remesh(inputVerts.begin(), inputVerts.size(), inputTriIds.begin(), inputTriIds.size(), resolution, vertexMap);
}
void Remesher::remesh(const PxVec3* inputVerts, PxU32 nbVertices, const PxU32* inputTriIds, PxU32 nbTriangleIndices, PxU32 resolution, PxArray<PxU32> *vertexMap)
{
clear();
PxBounds3 meshBounds;
meshBounds.setEmpty();
for (PxU32 i = 0; i < nbVertices; i++)
meshBounds.include(inputVerts[i]);
PxVec3 dims = meshBounds.getDimensions();
float spacing = PxMax(dims.x, PxMax(dims.y, dims.z)) / resolution;
meshBounds.fattenFast(3.0f * spacing);
PxU32 numTris = nbTriangleIndices / 3;
PxBounds3 triBounds, cellBounds;
Gu::BoxPadded box;
box.rot = PxMat33(PxIdentity);
firstCell.clear();
firstCell.resize(HASH_SIZE, -1);
// create sparse overlapping cells
for (PxU32 i = 0; i < numTris; i++)
{
const PxVec3& p0 = inputVerts[inputTriIds[3 * i]];
const PxVec3& p1 = inputVerts[inputTriIds[3 * i + 1]];
const PxVec3& p2 = inputVerts[inputTriIds[3 * i + 2]];
triBounds.setEmpty();
triBounds.include(p0);
triBounds.include(p1);
triBounds.include(p2);
PxI32 x0 = PxI32(PxFloor((triBounds.minimum.x - meshBounds.minimum.x) / spacing));
PxI32 y0 = PxI32(PxFloor((triBounds.minimum.y - meshBounds.minimum.y) / spacing));
PxI32 z0 = PxI32(PxFloor((triBounds.minimum.z - meshBounds.minimum.z) / spacing));
PxI32 x1 = PxI32(PxFloor((triBounds.maximum.x - meshBounds.minimum.x) / spacing)) + 1;
PxI32 y1 = PxI32(PxFloor((triBounds.maximum.y - meshBounds.minimum.y) / spacing)) + 1;
PxI32 z1 = PxI32(PxFloor((triBounds.maximum.z - meshBounds.minimum.z) / spacing)) + 1;
for (PxI32 xi = x0; xi <= x1; xi++)
{
for (PxI32 yi = y0; yi <= y1; yi++)
{
for (PxI32 zi = z0; zi <= z1; zi++)
{
cellBounds.minimum.x = meshBounds.minimum.x + xi * spacing;
cellBounds.minimum.y = meshBounds.minimum.y + yi * spacing;
cellBounds.minimum.z = meshBounds.minimum.z + zi * spacing;
cellBounds.maximum = cellBounds.minimum + PxVec3(spacing, spacing, spacing);
cellBounds.fattenFast(1e-5f);
box.center = cellBounds.getCenter();
box.extents = cellBounds.getExtents();
if (!Gu::intersectTriangleBox(box, p0, p1, p2))
continue;
if (!cellExists(xi, yi, zi))
addCell(xi, yi, zi);
}
}
}
}
// using marching cubes to create boundaries
vertices.clear();
cellOfVertex.clear();
triIds.clear();
PxI32 edgeVertId[12];
PxVec3 cornerPos[8];
int cornerVoxelNr[8];
for (PxI32 i = 0; i < PxI32(cells.size()); i++)
{
Cell& c = cells[i];
// we need to handle a 2 x 2 x 2 block of cells to cover the boundary
for (PxI32 dx = 0; dx < 2; dx++)
{
for (PxI32 dy = 0; dy < 2; dy++)
{
for (PxI32 dz = 0; dz < 2; dz++)
{
PxI32 xi = c.xi + dx;
PxI32 yi = c.yi + dy;
PxI32 zi = c.zi + dz;
// are we responsible for this cell?
PxI32 maxCellNr = i;
for (PxI32 rx = xi - 1; rx <= xi; rx++)
for (PxI32 ry = yi - 1; ry <= yi; ry++)
for (PxI32 rz = zi - 1; rz <= zi; rz++)
maxCellNr = PxMax(maxCellNr, getCellNr(rx, ry, rz));
if (maxCellNr != i)
continue;
PxI32 code = 0;
for (PxI32 j = 0; j < 8; j++)
{
PxI32 mx = xi - 1 + marchingCubeCorners[j][0];
PxI32 my = yi - 1 + marchingCubeCorners[j][1];
PxI32 mz = zi - 1 + marchingCubeCorners[j][2];
cornerVoxelNr[j] = getCellNr(mx, my, mz);
if (cornerVoxelNr[j] >= 0)
code |= (1 << j);
cornerPos[j].x = meshBounds.minimum.x + (mx + 0.5f) * spacing;
cornerPos[j].y = meshBounds.minimum.y + (my + 0.5f) * spacing;
cornerPos[j].z = meshBounds.minimum.z + (mz + 0.5f) * spacing;
}
PxI32 first = firstMarchingCubesId[code];
PxI32 num = (firstMarchingCubesId[code + 1] - first);
// create vertices and tris
for (PxI32 j = 0; j < 12; j++)
edgeVertId[j] = -1;
for (PxI32 j = num - 1; j >= 0; j--)
{
PxI32 edgeId = marchingCubesIds[first + j];
if (edgeVertId[edgeId] < 0)
{
PxI32 id0 = marchingCubeEdges[edgeId][0];
PxI32 id1 = marchingCubeEdges[edgeId][1];
PxVec3& p0 = cornerPos[id0];
PxVec3& p1 = cornerPos[id1];
edgeVertId[edgeId] = vertices.size();
vertices.pushBack((p0 + p1) * 0.5f);
cellOfVertex.pushBack(PxMax(cornerVoxelNr[id0], cornerVoxelNr[id1]));
}
triIds.pushBack(edgeVertId[edgeId]);
}
}
}
}
}
removeDuplicateVertices();
pruneInternalSurfaces();
project(inputVerts, inputTriIds, nbTriangleIndices, 2.0f * spacing, 0.1f * spacing);
if (vertexMap)
createVertexMap(inputVerts, nbVertices, meshBounds.minimum, spacing, *vertexMap);
computeNormals();
}
// -------------------------------------------------------------------------------------
void Remesher::removeDuplicateVertices()
{
PxF32 eps = 1e-5f;
struct Ref
{
PxF32 d;
PxI32 id;
bool operator < (const Ref& r) const
{
return d < r.d;
}
};
PxI32 numVerts = PxI32(vertices.size());
PxArray<Ref> refs(numVerts);
for (PxI32 i = 0; i < numVerts; i++)
{
PxVec3& p = vertices[i];
refs[i].d = p.x + 0.3f * p.y + 0.1f * p.z;
refs[i].id = i;
}
PxSort(refs.begin(), refs.size());
PxArray<PxI32> idMap(vertices.size(), -1);
PxArray<PxVec3> oldVerts = vertices;
PxArray<PxI32> oldCellOfVertex = cellOfVertex;
vertices.clear();
cellOfVertex.clear();
PxI32 nr = 0;
while (nr < numVerts)
{
Ref& r = refs[nr];
nr++;
if (idMap[r.id] >= 0)
continue;
idMap[r.id] = vertices.size();
vertices.pushBack(oldVerts[r.id]);
cellOfVertex.pushBack(oldCellOfVertex[r.id]);
PxI32 i = nr;
while (i < numVerts && fabsf(refs[i].d - r.d) < eps)
{
PxI32 id = refs[i].id;
if ((oldVerts[r.id] - oldVerts[id]).magnitudeSquared() < eps * eps)
idMap[id] = idMap[r.id];
i++;
}
}
for (PxI32 i = 0; i < PxI32(triIds.size()); i++)
triIds[i] = idMap[triIds[i]];
}
// -------------------------------------------------------------------------------------
void Remesher::findTriNeighbors()
{
PxI32 numTris = PxI32(triIds.size()) / 3;
triNeighbors.clear();
triNeighbors.resize(3 * numTris, -1);
struct Edge
{
void init(PxI32 _id0, PxI32 _id1, PxI32 _triNr, PxI32 _edgeNr)
{
this->id0 = PxMin(_id0, _id1);
this->id1 = PxMax(_id0, _id1);
this->triNr = _triNr;
this->edgeNr = _edgeNr;
}
bool operator < (const Edge& e) const
{
if (id0 < e.id0) return true;
if (id0 > e.id0) return false;
return id1 < e.id1;
}
bool operator == (const Edge& e) const
{
return id0 == e.id0 && id1 == e.id1;
}
PxI32 id0, id1, triNr, edgeNr;
};
PxArray<Edge> edges(triIds.size());
for (PxI32 i = 0; i < numTris; i++)
{
for (PxI32 j = 0; j < 3; j++) {
PxI32 id0 = triIds[3 * i + j];
PxI32 id1 = triIds[3 * i + (j + 1) % 3];
edges[3 * i + j].init(id0, id1, i, j);
}
}
PxSort(edges.begin(), edges.size());
PxI32 nr = 0;
while (nr < PxI32(edges.size()))
{
Edge& e0 = edges[nr];
nr++;
while (nr < PxI32(edges.size()) && edges[nr] == e0) {
Edge& e1 = edges[nr];
triNeighbors[3 * e0.triNr + e0.edgeNr] = e1.triNr;
triNeighbors[3 * e1.triNr + e1.edgeNr] = e0.triNr;
nr++;
}
}
}
// -------------------------------------------------------------------------------------
void Remesher::pruneInternalSurfaces()
{
// flood islands, if the enclosed volume is negative remove it
findTriNeighbors();
PxI32 numTris = PxI32(triIds.size()) / 3;
PxArray<PxI32> oldTriIds = triIds;
triIds.clear();
PxArray<bool> visited(numTris, false);
PxArray<PxI32> stack;
for (PxI32 i = 0; i < numTris; i++)
{
if (visited[i])
continue;
stack.clear();
stack.pushBack(i);
PxI32 islandStart = PxI32(triIds.size());
float vol = 0.0f;
while (!stack.empty())
{
PxI32 triNr = stack.back();
stack.popBack();
if (visited[triNr])
continue;
visited[triNr] = true;
for (PxI32 j = 0; j < 3; j++)
triIds.pushBack(oldTriIds[3 * triNr + j]);
const PxVec3& p0 = vertices[oldTriIds[3 * triNr]];
const PxVec3& p1 = vertices[oldTriIds[3 * triNr + 1]];
const PxVec3& p2 = vertices[oldTriIds[3 * triNr + 2]];
vol += p0.cross(p1).dot(p2);
for (PxI32 j = 0; j < 3; j++)
{
PxI32 n = triNeighbors[3 * triNr + j];
if (n >= 0 && !visited[n])
stack.pushBack(n);
}
}
if (vol <= 0.0f)
triIds.resize(islandStart);
}
// remove unreferenced vertices
PxArray<PxI32> idMap(vertices.size(), -1);
PxArray<PxVec3> oldVerts = vertices;
PxArray<PxI32> oldCellOfVertex = cellOfVertex;
vertices.clear();
cellOfVertex.clear();
for (int i = 0; i < PxI32(triIds.size()); i++)
{
PxI32 id = triIds[i];
if (idMap[id] < 0)
{
idMap[id] = vertices.size();
vertices.pushBack(oldVerts[id]);
cellOfVertex.pushBack(oldCellOfVertex[id]);
}
triIds[i] = idMap[id];
}
}
// -----------------------------------------------------------------------------------
// PT: TODO: refactor with other implementation
static void getClosestPointOnTriangle(const PxVec3& pos, const PxVec3& p0, const PxVec3& p1, const PxVec3& p2,
PxVec3& closest, PxVec3& bary)
{
PxVec3 e0 = p1 - p0;
PxVec3 e1 = p2 - p0;
PxVec3 tmp = p0 - pos;
float a = e0.dot(e0);
float b = e0.dot(e1);
float c = e1.dot(e1);
float d = e0.dot(tmp);
float e = e1.dot(tmp);
PxVec3 coords, clampedCoords;
coords.x = b * e - c * d; // s * det
coords.y = b * d - a * e; // t * det
coords.z = a * c - b * b; // det
clampedCoords = PxVec3(PxZero);
if (coords.x <= 0.0f)
{
if (c != 0.0f)
clampedCoords.y = -e / c;
}
else if (coords.y <= 0.0f)
{
if (a != 0.0f)
clampedCoords.x = -d / a;
}
else if (coords.x + coords.y > coords.z)
{
float denominator = a + c - b - b;
float numerator = c + e - b - d;
if (denominator != 0.0f)
{
clampedCoords.x = numerator / denominator;
clampedCoords.y = 1.0f - clampedCoords.x;
}
}
else
{ // all inside
if (coords.z != 0.0f) {
clampedCoords.x = coords.x / coords.z;
clampedCoords.y = coords.y / coords.z;
}
}
bary.y = PxMin(PxMax(clampedCoords.x, 0.0f), 1.0f);
bary.z = PxMin(PxMax(clampedCoords.y, 0.0f), 1.0f);
bary.x = 1.0f - bary.y - bary.z;
closest = p0 * bary.x + p1 * bary.y + p2 * bary.z;
}
// -------------------------------------------------------------------------------------
void Remesher::project(const PxVec3* inputVerts, const PxU32* inputTriIds, PxU32 nbTriangleIndices,
float searchDist, float surfaceDist)
{
// build a bvh for the input mesh
PxI32 numInputTris = PxI32(nbTriangleIndices) / 3;
if (numInputTris == 0)
return;
bvhBounds.resize(numInputTris);
bvhTris.clear();
for (PxI32 i = 0; i < numInputTris; i++) {
PxBounds3& b = bvhBounds[i];
b.setEmpty();
b.include(inputVerts[inputTriIds[3 * i]]);
b.include(inputVerts[inputTriIds[3 * i + 1]]);
b.include(inputVerts[inputTriIds[3 * i + 2]]);
}
BVHDesc bvh;
BVHBuilder::build(bvh, &bvhBounds[0], bvhBounds.size());
// project vertices to closest point on surface
PxBounds3 pb;
for (PxU32 i = 0; i < vertices.size(); i++)
{
PxVec3& p = vertices[i];
pb.setEmpty();
pb.include(p);
pb.fattenFast(searchDist);
bvh.query(pb, bvhTris);
float minDist2 = PX_MAX_F32;
PxVec3 closest(PxZero);
for (PxU32 j = 0; j < bvhTris.size(); j++)
{
PxI32 triNr = bvhTris[j];
const PxVec3& p0 = inputVerts[inputTriIds[3 * triNr]];
const PxVec3& p1 = inputVerts[inputTriIds[3 * triNr + 1]];
const PxVec3& p2 = inputVerts[inputTriIds[3 * triNr + 2]];
PxVec3 c, bary;
getClosestPointOnTriangle(p, p0, p1, p2, c, bary);
float dist2 = (c - p).magnitudeSquared();
if (dist2 < minDist2) {
minDist2 = dist2;
closest = c;
}
}
if (minDist2 < PX_MAX_F32) {
PxVec3 n = p - closest;
n.normalize();
p = closest + n * surfaceDist;
}
}
}
static const int cellNeighbors[6][3] = { { -1,0,0 }, {1,0,0},{0,-1,0},{0,1,0},{0,0,-1},{0,0,1} };
// -------------------------------------------------------------------------------------
void Remesher::createVertexMap(const PxVec3* inputVerts, PxU32 nbVertices, const PxVec3 &gridOrigin, PxF32 &gridSpacing,
PxArray<PxU32> &vertexMap)
{
PxArray<PxI32> vertexOfCell(cells.size(), -1);
PxArray<PxI32 > front[2];
PxI32 frontNr = 0;
// compute inverse links
for (PxI32 i = 0; i < PxI32(vertices.size()); i++)
{
PxI32 cellNr = cellOfVertex[i];
if (cellNr >= 0)
{
if (vertexOfCell[cellNr] < 0) {
vertexOfCell[cellNr] = i;
front[frontNr].pushBack(cellNr);
}
}
}
// propagate cell->vertex links through the voxel mesh
while (!front[frontNr].empty())
{
front[1 - frontNr].clear();
for (PxI32 i = 0; i < PxI32(front[frontNr].size()); i++)
{
int cellNr = front[frontNr][i];
Cell& c = cells[cellNr];
for (PxI32 j = 0; j < 6; j++)
{
PxI32 n = getCellNr(c.xi + cellNeighbors[j][0],
c.yi + cellNeighbors[j][1],
c.zi + cellNeighbors[j][2]);
if (n >= 0 && vertexOfCell[n] < 0) {
vertexOfCell[n] = vertexOfCell[cellNr];
front[1 - frontNr].pushBack(n);
}
}
}
frontNr = 1 - frontNr;
}
// create the map
vertexMap.clear();
vertexMap.resize(nbVertices, 0);
for (PxU32 i = 0; i < nbVertices; i++)
{
const PxVec3& p = inputVerts[i];
PxI32 xi = PxI32(PxFloor((p.x - gridOrigin.x) / gridSpacing));
PxI32 yi = PxI32(PxFloor((p.y - gridOrigin.y) / gridSpacing));
PxI32 zi = PxI32(PxFloor((p.z - gridOrigin.z) / gridSpacing));
PxI32 cellNr = getCellNr(xi, yi, zi);
vertexMap[i] = cellNr >= 0 ? vertexOfCell[cellNr] : 0;
}
}
// -------------------------------------------------------------------------------------
void Remesher::computeNormals()
{
normals.clear();
normals.resize(vertices.size(), PxVec3(PxZero));
for (PxI32 i = 0; i < PxI32(triIds.size()); i += 3)
{
PxI32* ids = &triIds[i];
PxVec3& p0 = vertices[ids[0]];
PxVec3& p1 = vertices[ids[1]];
PxVec3& p2 = vertices[ids[2]];
PxVec3 n = (p1 - p0).cross(p2 - p0);
normals[ids[0]] += n;
normals[ids[1]] += n;
normals[ids[2]] += n;
}
for (PxI32 i = 0; i < PxI32(normals.size()); i++)
normals[i].normalize();
}
// -------------------------------------------------------------------------------------
void Remesher::readBack(PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputTriIds)
{
outputVertices = vertices;
outputTriIds.resize(triIds.size());
for (PxU32 i = 0; i < triIds.size(); i++)
outputTriIds[i] = PxU32(triIds[i]);
}

View File

@@ -0,0 +1,96 @@
// 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.
#ifndef EXT_REMESHER_H
#define EXT_REMESHER_H
#include "foundation/PxBounds3.h"
#include "foundation/PxArray.h"
namespace physx
{
namespace Ext
{
// ------------------------------------------------------------------------------
class Remesher {
public:
Remesher() {}
~Remesher() {}
void remesh(const PxVec3* verts, PxU32 nbVertices, const PxU32* triIds, PxU32 nbTriangleIndices, PxU32 resolution = 100, PxArray<PxU32> *vertexMap = nullptr);
void remesh(const PxArray<PxVec3>& verts, const PxArray<PxU32>& triIds, PxU32 resolution = 100, PxArray<PxU32> *vertexMap = nullptr);
void clear();
void readBack(PxArray<PxVec3>& vertices, PxArray<PxU32>& triIds);
private:
PxArray<PxVec3> vertices;
PxArray<PxI32> triIds;
void addCell(PxI32 xi, PxI32 yi, PxI32 zi);
PxI32 getCellNr(PxI32 xi, PxI32 yi, PxI32 zi) const;
bool cellExists(PxI32 xi, PxI32 yi, PxI32 zi) const;
void removeDuplicateVertices();
void pruneInternalSurfaces();
void computeNormals();
void findTriNeighbors();
void project(const PxVec3* inputVerts, const PxU32* inputTriIds, PxU32 nbTriangleIndices,
float searchDist, float surfaceDist);
void createVertexMap(const PxVec3* verts, PxU32 nbVertices, const PxVec3 &gridOrigin, PxF32 &gridSpacing,
PxArray<PxU32> &vertexMap);
// -------------------------------------------------------------------------------------
struct Cell
{
void init(PxI32 _xi, PxI32 _yi, PxI32 _zi) {
this->xi = _xi; this->yi = _yi; this->zi = _zi;
this->next = -1;
}
PxI32 xi, yi, zi;
PxI32 next;
};
PxArray<Cell> cells;
PxArray<PxI32> firstCell;
PxArray<PxVec3> normals;
PxArray<PxI32> triNeighbors;
PxArray<PxI32> cellOfVertex;
PxArray<PxBounds3> bvhBounds;
PxArray<PxI32> bvhTris;
};
}
}
#endif

View File

@@ -0,0 +1,806 @@
// 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 "ExtTetSplitting.h"
#include "ExtUtilities.h"
#include "foundation/PxBasicTemplates.h"
using namespace physx;
using namespace Ext;
//Last four bits are used
//Last bit set stands for A
//Second last bit stands for B
//Third last bit stands for C
//Fourth last bit stands for D
typedef PxU32 TetCorner;
typedef PxU32 TetEdge;
static const TetCorner None = 0x00000000;
static const TetCorner A = 0x00000001;
static const TetCorner B = 0x00000002;
static const TetCorner C = 0x00000004;
static const TetCorner D = 0x00000008;
static const TetEdge AB = 0x00000003;
static const TetEdge AC = 0x00000005;
static const TetEdge AD = 0x00000009;
static const TetEdge BC = 0x00000006;
static const TetEdge BD = 0x0000000A;
static const TetEdge CD = 0x0000000C;
static const TetCorner tetCorners[4] = { A, B, C, D };
namespace
{
//Specifies which edges of a tetrahedron should get a point inserted (=split)
struct TetSubdivisionInfo
{
//If an index has value -1 then this means that this edge won't get split
PxI32 ab;
PxI32 ac;
PxI32 ad;
PxI32 bc;
PxI32 bd;
PxI32 cd;
Tetrahedron tet;
PxI32 id;
PX_FORCE_INLINE TetSubdivisionInfo() : ab(-1), ac(-1), ad(-1), bc(-1), bd(-1), cd(-1), tet(Tetrahedron(-1, -1, -1, -1)), id(-1) {}
PX_FORCE_INLINE TetSubdivisionInfo(Tetrahedron tet_, PxI32 aBPointInsertIndex, PxI32 aCPointInsertIndex, PxI32 aDPointInsertIndex,
PxI32 bCPointInsertIndex, PxI32 bDPointInsertIndex, PxI32 cDPointInsertIndex, PxI32 id_ = -1) :
ab(aBPointInsertIndex), ac(aCPointInsertIndex), ad(aDPointInsertIndex), bc(bCPointInsertIndex), bd(bDPointInsertIndex), cd(cDPointInsertIndex), tet(tet_), id(id_)
{ }
PX_FORCE_INLINE void swapInternal(TetCorner corner1, TetCorner corner2, TetCorner& cornerToProcess)
{
if (cornerToProcess == corner1)
cornerToProcess = corner2;
else if (cornerToProcess == corner2)
cornerToProcess = corner1;
}
// PT: this function seems unused
//Helper method for sorting
/*template<typename T>
void swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}*/
//Helper method for sorting
bool swap(TetCorner corner1, TetCorner corner2, TetCorner& additionalCornerToProcess1, TetCorner& additionalCornerToProcess2)
{
swapInternal(corner1, corner2, additionalCornerToProcess1);
swapInternal(corner1, corner2, additionalCornerToProcess2);
return swap(corner1, corner2);
}
//Helper method for sorting
bool swap(TetCorner corner1, TetCorner corner2, TetCorner& additionalCornerToProcess)
{
swapInternal(corner1, corner2, additionalCornerToProcess);
return swap(corner1, corner2);
}
//Helper method for sorting
bool swap(TetCorner corner1, TetCorner corner2)
{
if (corner1 == corner2)
return false;
if (corner1 > corner2)
{
TetCorner tmp = corner1;
corner1 = corner2;
corner2 = tmp;
}
if (corner1 == A && corner2 == B)
{
PxSwap(ad, bd);
PxSwap(ac, bc);
PxSwap(tet[0], tet[1]);
}
else if (corner1 == A && corner2 == C)
{
PxSwap(ad, cd);
PxSwap(ab, bc);
PxSwap(tet[0], tet[2]);
}
else if (corner1 == A && corner2 == D)
{
PxSwap(ac, cd);
PxSwap(ab, bd);
PxSwap(tet[0], tet[3]);
}
else if (corner1 == B && corner2 == C)
{
PxSwap(ac, ab);
PxSwap(cd, bd);
PxSwap(tet[1], tet[2]);
}
else if (corner1 == B && corner2 == D)
{
PxSwap(ab, ad);
PxSwap(bc, cd);
PxSwap(tet[1], tet[3]);
}
else if (corner1 == C && corner2 == D)
{
PxSwap(ac, ad);
PxSwap(bc, bd);
PxSwap(tet[2], tet[3]);
}
return true;
}
//Allows to sort the information such that edges of neighboring tets have the same orientation
PxI32 sort()
{
PxI32 counter = 0;
if (tet[0] > tet[1]) { swap(A, B); ++counter; }
if (tet[0] > tet[2]) { swap(A, C); ++counter; }
if (tet[0] > tet[3]) { swap(A, D); ++counter; }
if (tet[1] > tet[2]) { swap(B, C); ++counter; }
if (tet[1] > tet[3]) { swap(B, D); ++counter; }
if (tet[2] > tet[3]) { swap(C, D); ++counter; }
return counter;
}
};
}
//Returns true if the edge is adjacent to the specified corner
static PX_FORCE_INLINE bool edgeContainsCorner(TetEdge edge, TetCorner corner)
{
return (edge & corner) != 0;
}
//Returns the common point of two edges, will be None if there is no common point
static PX_FORCE_INLINE TetCorner getCommonPoint(TetEdge edge1, TetEdge edge2)
{
return edge1 & edge2;
}
//Extracts the global indices from a tet given a local edge
static Edge getTetEdge(const Tetrahedron& tet, TetEdge edge)
{
switch (edge)
{
case AB:
return Edge(tet[0], tet[1]);
case AC:
return Edge(tet[0], tet[2]);
case AD:
return Edge(tet[0], tet[3]);
case BC:
return Edge(tet[1], tet[2]);
case BD:
return Edge(tet[1], tet[3]);
case CD:
return Edge(tet[2], tet[3]);
}
return Edge(-1, -1);
}
static TetCorner getStart(TetEdge e)
{
switch (e)
{
case AB:
return A;
case AC:
return A;
case AD:
return A;
case BC:
return B;
case BD:
return B;
case CD:
return C;
}
return None;
}
static TetCorner getEnd(TetEdge e)
{
switch (e)
{
case AB:
return B;
case AC:
return C;
case AD:
return D;
case BC:
return C;
case BD:
return D;
case CD:
return D;
}
return None;
}
static PX_FORCE_INLINE TetEdge getOppositeEdge(TetEdge edge)
{
return 0x0000000F ^ edge;
}
//Finds the index of the first instance of value in list
static PxI32 getIndexOfFirstValue(PxI32 list[4], PxI32 value = 0, PxI32 startAt = 0)
{
for (PxI32 i = startAt; i < 4; ++i)
if (list[i] == value)
return i;
PX_ASSERT(false); // we should never reach this line
return 0;
}
//Counts how many times every corner is referenced by the specified set of edges - useful for corner classification
static void getCornerAccessCounter(TetEdge edges[6], PxI32 edgesLength, PxI32 cornerAccessCounter[4])
{
for (PxI32 i = 0; i < 4; ++i)
cornerAccessCounter[i] = 0;
for (PxI32 j = 0; j < edgesLength; ++j)
{
switch (edges[j])
{
case AB:
++cornerAccessCounter[0];
++cornerAccessCounter[1];
break;
case AC:
++cornerAccessCounter[0];
++cornerAccessCounter[2];
break;
case AD:
++cornerAccessCounter[0];
++cornerAccessCounter[3];
break;
case BC:
++cornerAccessCounter[1];
++cornerAccessCounter[2];
break;
case BD:
++cornerAccessCounter[1];
++cornerAccessCounter[3];
break;
case CD:
++cornerAccessCounter[2];
++cornerAccessCounter[3];
break;
}
}
}
// PT: using static exposed the fact that this function was not used. Expected?
//Returns the tet's edge that does not contain corner1 and neither corner2
/*static Edge getRemainingEdge(const Tetrahedron& tet, PxI32 corner1, PxI32 corner2)
{
PxI32 indexer = 0;
Edge result(-1, -1);
for (PxU32 i = 0; i < 4; ++i)
{
if (tet[i] != corner1 && tet[i] != corner2)
{
if (indexer == 0)
result.first = tet[i];
else if (indexer == 1)
result.second = tet[i];
++indexer;
}
}
return result;
}*/
static PX_FORCE_INLINE TetCorner getOtherCorner(TetEdge edge, TetCorner corner)
{
return edge ^ corner;
}
static PX_FORCE_INLINE Tetrahedron flip(bool doFlip, Tetrahedron t)
{
if (doFlip) PxSwap(t[2], t[3]);
return t;
}
//Splits all tets according to the specification in tetSubdivisionInfos. The resulting mesh will be watertight if the tetSubdivisionInfos are specified such
//that all tets sharing and edge will get the same point inserted on their corresponding edge
static void split(PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points, const PxArray<TetSubdivisionInfo>& tetSubdivisionInfos)
{
PxU32 originalNumTets = tets.size();
for (PxU32 i = 0; i < originalNumTets; ++i)
{
TetSubdivisionInfo info = tetSubdivisionInfos[i];
PxI32 counter = info.sort();
TetEdge splitEdges[6];
PxI32 splitEdgesLength = 0;
TetEdge nonSplitEdges[6];
PxI32 nonSplitEdgesLength = 0;
PxI32 insertionIndices[6];
PxI32 insertionIndicesLength = 0;
if (info.ab >= 0) { splitEdges[splitEdgesLength++] = AB; insertionIndices[insertionIndicesLength++] = info.ab; }
else nonSplitEdges[nonSplitEdgesLength++] = AB;
if (info.ac >= 0) { splitEdges[splitEdgesLength++] = AC; insertionIndices[insertionIndicesLength++] = info.ac; }
else nonSplitEdges[nonSplitEdgesLength++] = AC;
if (info.ad >= 0) { splitEdges[splitEdgesLength++] = AD; insertionIndices[insertionIndicesLength++] = info.ad; }
else nonSplitEdges[nonSplitEdgesLength++] = AD;
if (info.bc >= 0) { splitEdges[splitEdgesLength++] = BC; insertionIndices[insertionIndicesLength++] = info.bc; }
else nonSplitEdges[nonSplitEdgesLength++] = BC;
if (info.bd >= 0) { splitEdges[splitEdgesLength++] = BD; insertionIndices[insertionIndicesLength++] = info.bd; }
else nonSplitEdges[nonSplitEdgesLength++] = BD;
if (info.cd >= 0) { splitEdges[splitEdgesLength++] = CD; insertionIndices[insertionIndicesLength++] = info.cd; }
else nonSplitEdges[nonSplitEdgesLength++] = CD;
//Depending on how many tet edges get a point inserted, a different topology results.
//The created topology will make sure all neighboring tet faces will be tessellated identically to keep the mesh watertight
switch (splitEdgesLength)
{
case 0:
//Nothing to do here
break;
case 1:
{
PxI32 pointIndex = insertionIndices[0];
Edge splitEdge = getTetEdge(info.tet, splitEdges[0]);
Edge oppositeEdge = getTetEdge(info.tet, getOppositeEdge(splitEdges[0]));
tets[i] = Tetrahedron(oppositeEdge.first, oppositeEdge.second, splitEdge.first, pointIndex);
tets.pushBack(Tetrahedron(oppositeEdge.first, oppositeEdge.second, pointIndex, splitEdge.second));
break;
}
case 2:
{
TetCorner corner = getCommonPoint(splitEdges[0], splitEdges[1]);
if (corner != None)
{
//edges have a common point
//Rearrange such that common corner is a and first edge is from a to b while second edge is from a to c
TetCorner p1 = getOtherCorner(splitEdges[0], corner);
TetCorner p2 = getOtherCorner(splitEdges[1], corner);
if (info.swap(corner, A, p1, p2)) ++counter;
if (info.swap(p1, B, p2)) ++counter;
if (info.swap(p2, C)) ++counter;
if (info.tet[1] > info.tet[2])
{
if (info.swap(B, C)) ++counter;
}
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[0], info.tet[3], info.ab, info.ac));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.tet[1], info.ab, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.tet[2], info.tet[1], info.ac)));
}
else
{
//Edges don't have a common point (opposite edges)
TetEdge edge1 = splitEdges[0];
//TetEdge edge2 = splitEdges[1];
//Permute the tetrahedron such that edge1 becomes the edge AB
if (info.swap(getStart(edge1), A)) ++counter;
if (info.swap(getEnd(edge1), B)) ++counter;
if (info.tet[0] > info.tet[1])
{
if (info.swap(A, B)) ++counter;
}
if (info.tet[2] > info.tet[3])
{
if (info.swap(C, D)) ++counter;
}
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[0], info.ab, info.tet[2], info.cd));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.ab, info.cd, info.tet[2])));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ab, info.cd, info.tet[3])));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.ab, info.tet[3], info.cd)));
}
break;
}
case 3:
{
//There are three sub cases called a, b and c
TetCorner commonPoint01 = getCommonPoint(splitEdges[0], splitEdges[1]);
TetCorner commonPoint02 = getCommonPoint(splitEdges[0], splitEdges[2]);
TetCorner commonPoint12 = getCommonPoint(splitEdges[1], splitEdges[2]);
if (commonPoint01 == None || commonPoint02 == None || commonPoint12 == None)
{
//The three edges form a non closed strip
//The strip's end points are connected by a tet edge - map this edge such that it becomes edge AB
//Then sort AB
PxI32 cnt[4];
getCornerAccessCounter(splitEdges, splitEdgesLength, cnt);
PxI32 index = getIndexOfFirstValue(cnt, 1);
TetCorner refStart = tetCorners[index];
TetCorner refEnd = tetCorners[getIndexOfFirstValue(cnt, 1, index + 1)];
TetCorner cornerToMapOntoC = None;
if (edgeContainsCorner(splitEdges[0], refEnd))
{
cornerToMapOntoC = getOtherCorner(splitEdges[0], refEnd);
}
else if (edgeContainsCorner(splitEdges[1], refEnd))
{
cornerToMapOntoC = getOtherCorner(splitEdges[1], refEnd);
}
else if (edgeContainsCorner(splitEdges[2], refEnd))
{
cornerToMapOntoC = getOtherCorner(splitEdges[2], refEnd);
}
if (info.swap(refStart, A, refEnd, cornerToMapOntoC)) ++counter;
if (info.swap(refEnd, B, cornerToMapOntoC)) ++counter;
if (info.swap(cornerToMapOntoC, C)) ++counter;
if (info.tet[0] > info.tet[1])
{
if (info.swap(A, B)) ++counter;
if (info.swap(C, D)) ++counter;
}
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[0], info.tet[1], info.bc, info.ad));
if (info.tet[0] > info.tet[2])
{
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.cd, info.ad, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[2], info.ad, info.bc)));
}
else
{
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[2], info.cd, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ad, info.bc, info.cd)));
}
if (info.tet[1] > info.tet[3])
{
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.bc, info.ad, info.cd)));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.tet[1], info.ad, info.bc)));
}
else
{
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.tet[3], info.cd, info.ad)));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.bc, info.ad, info.cd)));
}
}
else if (edgeContainsCorner(splitEdges[2], commonPoint01))
{
//All three edges share one common point
//Permute tetrahedron such that the common tip point is a
if (info.swap(commonPoint01, A)) ++counter;
//Sort the remaining values
if (info.tet[1] > info.tet[2])
{
if (info.swap(B, C)) ++counter;
}
if (info.tet[2] > info.tet[3])
{
if (info.swap(C, D)) ++counter;
}
if (info.tet[1] > info.tet[2])
{
if (info.swap(B, C)) ++counter;
}
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[0], info.ab, info.ac, info.ad));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.ab, info.ad, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.tet[1], info.ad, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.tet[2], info.ad, info.tet[3])));
}
else
{
//Edges form a triangle
//Rearrange such that point opposite of triangle loop is point d
//Triangle loop is a, b and c, make sure they're sorted
if (!(commonPoint01 == A || commonPoint02 == A || commonPoint12 == A))
{
if (info.swap(A, D)) ++counter;
}
else if (!(commonPoint01 == B || commonPoint02 == B || commonPoint12 == B))
{
if (info.swap(B, D)) ++counter;
}
else if (!(commonPoint01 == C || commonPoint02 == C || commonPoint12 == C))
{
if (info.swap(C, D)) ++counter;
}
else if (!(commonPoint01 == D || commonPoint02 == D || commonPoint12 == D))
{
if (info.swap(D, D)) ++counter;
}
//Sort a,b and c
if (info.tet[0] > info.tet[1])
if (info.swap(A, B)) ++counter;
if (info.tet[1] > info.tet[2])
if (info.swap(B, C)) ++counter;
if (info.tet[0] > info.tet[1])
if (info.swap(A, B)) ++counter;
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[3], info.ab, info.ac, info.bc));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.ab, info.ac, info.tet[0])));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.ab, info.bc, info.tet[1])));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.ac, info.bc, info.tet[2])));
}
break;
}
case 4:
{
TetCorner commonPoint = getCommonPoint(nonSplitEdges[0], nonSplitEdges[1]);
if (commonPoint != None)
{
//Three edges form a triangle and the two edges that are not split share a common point
TetCorner p1 = getOtherCorner(nonSplitEdges[0], commonPoint);
TetCorner p2 = getOtherCorner(nonSplitEdges[1], commonPoint);
if (info.swap(commonPoint, A, p1, p2)) ++counter;
if (info.swap(p1, B, p2)) ++counter;
if (info.swap(p2, C)) ++counter;
if (info.tet[1] > info.tet[2])
if (info.swap(B, C)) ++counter;
const bool f = counter % 2 == 0;
//Tip
tets[i] = flip(f, Tetrahedron(info.tet[3], info.ad, info.bd, info.cd));
//Center
tets.pushBack(flip(f, Tetrahedron(info.bd, info.ad, info.bc, info.cd)));
if (info.tet[0] < info.tet[1] && info.tet[0] < info.tet[2])
{
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[1], info.bd, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[2], info.bc, info.cd)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.bc, info.bd, info.ad)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.bc, info.ad, info.cd)));
}
else if (info.tet[0] > info.tet[1] && info.tet[0] < info.tet[2])
{
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[2], info.bc, info.cd)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.bc, info.ad, info.cd)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[1], info.ad, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.bc, info.bd, info.ad)));
}
else
{
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[1], info.ad, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[1], info.bc, info.bd, info.ad)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.tet[0], info.ad, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.bc, info.ad, info.cd)));
}
}
else
{
//All four edges form a loop
TetEdge edge1 = nonSplitEdges[0];
//Permute the tetrahedron such that edge1 becomes the edge AB
TetCorner end = getEnd(edge1);
if (info.swap(getStart(edge1), A, end)) ++counter;
if (info.swap(end, B)) ++counter;
//Sort
if (info.tet[0] > info.tet[1])
if (info.swap(A, B)) ++counter;
if (info.tet[2] > info.tet[3])
if (info.swap(C, D)) ++counter;
const bool f = counter % 2 == 1;
tets[i] = flip(f, Tetrahedron(info.tet[0], info.tet[1], info.bc, info.bd));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.tet[3], info.ad, info.bd)));
PxF64 dist1 = (points[info.ad] - points[info.bc]).magnitudeSquared();
PxF64 dist2 = (points[info.ac] - points[info.bd]).magnitudeSquared();
if (dist1 < dist2)
{
//Diagonal from AD to BC
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ad, info.bc, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ad, info.bd, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.ad, info.ac, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.ad, info.bc, info.bd)));
}
else
{
//Diagonal from AC to BD
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ad, info.bd, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ac, info.bd, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.bc, info.bd, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[2], info.bd, info.ad, info.ac)));
}
}
break;
}
case 5:
{
//There is only one edge that does not get split
//First create 2 small tetrahedra in every corner that is not an end point of the unsplit edge
TetEdge nonSplitEdge;
if (info.ab < 0)
nonSplitEdge = AB;
else if (info.ac < 0)
nonSplitEdge = AC;
else if (info.ad < 0)
nonSplitEdge = AD;
else if (info.bc < 0)
nonSplitEdge = BC;
else if (info.bd < 0)
nonSplitEdge = BD;
else //if (info.CDPointInsertIndex < 0)
nonSplitEdge = CD;
TetCorner end = getEnd(nonSplitEdge);
if (info.swap(getStart(nonSplitEdge), A, end)) ++counter;
if (info.swap(end, B)) ++counter;
if (info.tet[0] > info.tet[1])
if (info.swap(A, B)) ++counter;
if (info.tet[2] > info.tet[3])
if (info.swap(C, D)) ++counter;
const bool f = counter % 2 == 1;
//Two corner tets at corner C and corner D
tets[i] = flip(f, Tetrahedron(info.tet[2], info.ac, info.bc, info.cd));
tets.pushBack(flip(f, Tetrahedron(info.tet[3], info.ad, info.cd, info.bd)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.tet[1], info.bc, info.bd)));
//There are two possible diagonals -> take the shorter
PxF64 dist1 = (points[info.ac] - points[info.bd]).magnitudeSquared();
PxF64 dist2 = (points[info.ad] - points[info.bc]).magnitudeSquared();
if (dist1 < dist2)
{
//Diagonal from AC to BD
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ad, info.bd, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ac, info.bd, info.bc)));
//Tip pyramid
tets.pushBack(flip(f, Tetrahedron(info.cd, info.bc, info.bd, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.cd, info.bd, info.ad, info.ac)));
}
else
{
//Diagonal from AD to BC
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.ac, info.ad, info.bc)));
tets.pushBack(flip(f, Tetrahedron(info.tet[0], info.bd, info.bc, info.ad)));
//Tip pyramid
tets.pushBack(flip(f, Tetrahedron(info.cd, info.bc, info.ad, info.ac)));
tets.pushBack(flip(f, Tetrahedron(info.cd, info.bd, info.ad, info.bc)));
}
break;
}
case 6:
{
//First create a small tetrahedron in every corner
const bool f = counter % 2 == 1;
if (f)
info.swap(A, B);
tets[i] = Tetrahedron(info.tet[0], info.ab, info.ac, info.ad);
tets.pushBack(Tetrahedron(info.tet[1], info.ab, info.bd, info.bc));
tets.pushBack(Tetrahedron(info.tet[2], info.ac, info.bc, info.cd));
tets.pushBack(Tetrahedron(info.tet[3], info.ad, info.cd, info.bd));
//Now fill the remaining octahedron in the middle
//An octahedron can be constructed using 4 tetrahedra
//There are three diagonal candidates -> pick the shortest diagonal
PxF64 dist1 = (points[info.ab] - points[info.cd]).magnitudeSquared();
PxF64 dist2 = (points[info.ac] - points[info.bd]).magnitudeSquared();
PxF64 dist3 = (points[info.ad] - points[info.bc]).magnitudeSquared();
if (dist1 <= dist2 && dist1 <= dist3)
{
tets.pushBack(Tetrahedron(info.ab, info.cd, info.ad, info.bd));
tets.pushBack(Tetrahedron(info.ab, info.cd, info.bd, info.bc));
tets.pushBack(Tetrahedron(info.ab, info.cd, info.bc, info.ac));
tets.pushBack(Tetrahedron(info.ab, info.cd, info.ac, info.ad));
}
else if (dist2 <= dist1 && dist2 <= dist3)
{
tets.pushBack(Tetrahedron(info.ac, info.bd, info.cd, info.ad));
tets.pushBack(Tetrahedron(info.ac, info.bd, info.ad, info.ab));
tets.pushBack(Tetrahedron(info.ac, info.bd, info.ab, info.bc));
tets.pushBack(Tetrahedron(info.ac, info.bd, info.bc, info.cd));
}
else
{
tets.pushBack(Tetrahedron(info.ad, info.bc, info.bd, info.ab));
tets.pushBack(Tetrahedron(info.ad, info.bc, info.cd, info.bd));
tets.pushBack(Tetrahedron(info.ad, info.bc, info.ac, info.cd));
tets.pushBack(Tetrahedron(info.ad, info.bc, info.ab, info.ac));
}
break;
}
}
}
}
void physx::Ext::split(PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points, const PxHashMap<PxU64, PxI32>& edgesToSplit)
{
PxArray<TetSubdivisionInfo> subdivisionInfos;
subdivisionInfos.resize(tets.size());
for (PxU32 i = 0; i < tets.size(); ++i)
{
const Tetrahedron& tet = tets[i];
TetSubdivisionInfo info(tet, -1, -1, -1, -1, -1, -1, i);
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[0], tet[1])))
info.ab = ptr->second;
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[0], tet[2])))
info.ac = ptr->second;
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[0], tet[3])))
info.ad = ptr->second;
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[1], tet[2])))
info.bc = ptr->second;
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[1], tet[3])))
info.bd = ptr->second;
if (const PxPair<const PxU64, PxI32>* ptr = edgesToSplit.find(key(tet[2], tet[3])))
info.cd = ptr->second;
subdivisionInfos[i] = info;
}
::split(tets, points, subdivisionInfos);
}

View File

@@ -0,0 +1,49 @@
// 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.
#ifndef EXT_TET_SPLITTING_H
#define EXT_TET_SPLITTING_H
#include "ExtVec3.h"
#include "foundation/PxArray.h"
#include "foundation/PxHashMap.h"
#include "GuTetrahedron.h"
namespace physx
{
namespace Ext
{
using Edge = PxPair<PxI32, PxI32>;
using Tetrahedron = Gu::TetrahedronT<PxI32>;
//Splits all edges specified in edgesToSplit. The tets are modified in place. The poitns referenced by index in the key-value pari in
//edgesToSplit must already pe present in the points array. This functions guarantees that the tetmesh will remain watertight.
PX_C_EXPORT void PX_CALL_CONV split(PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points, const PxHashMap<PxU64, PxI32>& edgesToSplit);
}
}
#endif

View File

@@ -0,0 +1,25 @@
// 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.

View File

@@ -0,0 +1,88 @@
// 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 "ExtTetUnionFind.h"
using namespace physx;
using namespace Ext;
//------------------------------------------------------------------------------------
void UnionFind::init(PxI32 numSets)
{
mEntries.resize(numSets);
for (PxI32 i = 0; i < numSets; i++) {
Entry &e = mEntries[i];
e.parent = i;
e.rank = 0;
e.setNr = i;
}
}
//------------------------------------------------------------------------------------
PxI32 UnionFind::find(PxI32 x)
{
if (mEntries[x].parent != x)
mEntries[x].parent = find(mEntries[x].parent);
return mEntries[x].parent;
}
//------------------------------------------------------------------------------------
void UnionFind::makeSet(PxI32 x, PxI32 y)
{
PxI32 xroot = find(x);
PxI32 yroot = find(y);
if (xroot == yroot)
return;
if (mEntries[xroot].rank < mEntries[yroot].rank)
mEntries[xroot].parent = yroot;
else if (mEntries[xroot].rank > mEntries[yroot].rank)
mEntries[yroot].parent = xroot;
else {
mEntries[yroot].parent = xroot;
mEntries[xroot].rank++;
}
}
//------------------------------------------------------------------------------------
PxI32 UnionFind::computeSetNrs()
{
PxArray<PxI32> oldToNew(mEntries.size(), -1);
PxI32 numSets = 0;
for (PxU32 i = 0; i < mEntries.size(); i++) {
PxI32 nr = find(i);
if (oldToNew[nr] < 0)
oldToNew[nr] = numSets++;
mEntries[i].setNr = oldToNew[nr];
}
return numSets;
}
//------------------------------------------------------------------------------------
PxI32 UnionFind::getSetNr(PxI32 x)
{
return mEntries[x].setNr;
}

View File

@@ -0,0 +1,59 @@
// 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.
#ifndef UNION_FIND_H
#define UNION_FIND_H
#include "foundation/PxArray.h"
namespace physx
{
namespace Ext
{
class UnionFind {
public:
UnionFind() {}
UnionFind(PxI32 numSets) { init(numSets); }
void init(PxI32 numSets);
PxI32 find(PxI32 x);
void makeSet(PxI32 x, PxI32 y);
PxI32 computeSetNrs();
PxI32 getSetNr(PxI32 x);
private:
struct Entry {
PxI32 parent, rank;
PxI32 setNr;
};
PxArray<Entry> mEntries;
};
}
}
#endif

View File

@@ -0,0 +1,58 @@
// 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 "foundation/PxAssert.h"
#include "ExtUtilities.h"
#include "GuAABBTreeBuildStats.h"
#include "foundation/PxFPU.h"
using namespace physx;
using namespace Ext;
using namespace Gu;
static PxVec3 toFloat(const PxVec3d& p)
{
return PxVec3(PxReal(p.x), PxReal(p.y), PxReal(p.z));
}
void physx::Ext::buildTree(const PxU32* triangles, const PxU32 numTriangles, const PxVec3d* points, PxArray<Gu::BVHNode>& tree, PxF32 enlargement)
{
//Computes a bounding box for every triangle in triangles
AABBTreeBounds boxes;
boxes.init(numTriangles);
for (PxU32 i = 0; i < numTriangles; ++i)
{
const PxU32* tri = &triangles[3 * i];
PxBounds3 box = PxBounds3::empty();
box.include(toFloat(points[tri[0]]));
box.include(toFloat(points[tri[1]]));
box.include(toFloat(points[tri[2]]));
box.fattenFast(enlargement);
boxes.getBounds()[i] = box;
}
Gu::buildAABBTree(numTriangles, boxes, tree);
}

View File

@@ -0,0 +1,109 @@
// 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.
#ifndef EXT_TET_CPU_BVH_H
#define EXT_TET_CPU_BVH_H
#include "foundation/PxArray.h"
#include "foundation/PxBounds3.h"
#include "GuBVH.h"
#include "GuAABBTree.h"
#include "GuAABBTreeNode.h"
#include "GuAABBTreeBounds.h"
#include "GuAABBTreeQuery.h"
#include "GuTriangle.h"
#include "ExtVec3.h"
namespace physx
{
namespace Ext
{
using Triangle = Gu::IndexedTriangleT<PxI32>;
//Creates an unique 64bit bit key out of two 32bit values, the key is order independent, useful as hash key for edges
//Use this functions to compute the edge keys used in the edgesToSplit parameter of the split function below.
PX_FORCE_INLINE PxU64 key(PxI32 a, PxI32 b)
{
if (a < b)
return ((PxU64(a)) << 32) | (PxU64(b));
else
return ((PxU64(b)) << 32) | (PxU64(a));
}
void buildTree(const PxU32* triangles, const PxU32 numTriangles, const PxVec3d* points, PxArray<Gu::BVHNode>& tree, PxF32 enlargement = 1e-4f);
//Builds a BVH from a set of triangles
PX_FORCE_INLINE void buildTree(const PxArray<Triangle>& triangles, const PxArray<PxVec3d>& points, PxArray<Gu::BVHNode>& tree, PxF32 enlargement = 1e-4f)
{
buildTree(reinterpret_cast<const PxU32*>(triangles.begin()), triangles.size(), points.begin(), tree, enlargement);
}
template<typename T>
void traverseBVH(const PxArray<Gu::BVHNode>& nodes, T& traversalController, PxI32 rootNodeIndex = 0)
{
traverseBVH(nodes.begin(), traversalController, rootNodeIndex);
}
class IntersectionCollectingTraversalController
{
PxBounds3 box;
PxArray<PxI32>& candidateTriangleIndices;
public:
IntersectionCollectingTraversalController(PxArray<PxI32>& candidateTriangleIndices_) :
box(PxBounds3::empty()), candidateTriangleIndices(candidateTriangleIndices_)
{ }
IntersectionCollectingTraversalController(const PxBounds3& box_, PxArray<PxI32>& candidateTriangleIndices_) :
box(box_), candidateTriangleIndices(candidateTriangleIndices_)
{ }
void reset(const PxBounds3& box_)
{
box = box_;
candidateTriangleIndices.clear();
}
Gu::TraversalControl::Enum analyze(const Gu::BVHNode& node, PxI32)
{
if (node.isLeaf())
{
candidateTriangleIndices.pushBack(node.getPrimitiveIndex());
return Gu::TraversalControl::eDontGoDeeper;
}
if (node.mBV.intersects(box))
return Gu::TraversalControl::eGoDeeper;
return Gu::TraversalControl::eDontGoDeeper;
}
private:
PX_NOCOPY(IntersectionCollectingTraversalController)
};
}
}
#endif

View File

@@ -0,0 +1,70 @@
// 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.
#ifndef EXT_VEC3_H
#define EXT_VEC3_H
#include "foundation/PxMath.h"
#include "foundation/PxVec3.h"
namespace physx
{
namespace Ext
{
// ---------------------------------------------------------------------------------
struct Bounds3 {
Bounds3() {}
Bounds3(const PxVec3d &min, const PxVec3d &max) : minimum(min), maximum(max) {}
void setEmpty() {
minimum = PxVec3d(PX_MAX_F64, PX_MAX_F64, PX_MAX_F64);
maximum = PxVec3d(-PX_MAX_F64, -PX_MAX_F64, -PX_MAX_F64);
}
PxVec3d getDimensions() const {
return maximum - minimum;
}
void include(const PxVec3d &p) {
minimum = minimum.minimum(p);
maximum = maximum.maximum(p);
}
void include(const Bounds3 &b) {
minimum = minimum.minimum(b.minimum);
maximum = maximum.maximum(b.maximum);
}
void expand(double d) {
minimum -= PxVec3d(d, d, d);
maximum += PxVec3d(d, d, d);
}
PxVec3d minimum, maximum;
};
}
}
#endif

View File

@@ -0,0 +1,753 @@
// 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 "ExtVoxelTetrahedralizer.h"
#include "CmRandom.h"
#include "ExtTetUnionFind.h"
using namespace physx;
using namespace Ext;
// -------------------------------------------------------------------------------------
static PxI32 cubeNeighbors[6][3] = { { -1,0,0 }, {1,0,0}, {0,-1,0}, {0,1,0}, {0,0,-1}, {0,0,1} };
static const PxI32 cubeCorners[8][3] = { {0,0,0}, {1,0,0},{1,1,0},{0,1,0}, {0,0,1}, {1,0,1},{1,1,1},{0,1,1} };
static const PxI32 cubeFaces[6][4] = { {0,3,7,4},{1,2,6,5},{0,1,5,4},{3,2,6,7},{0,1,2,3},{4,5,6,7} };
static const PxI32 oppNeighbor[6] = { 1,0,3,2,5,4 };
static const PxI32 tetEdges[12][2] = { {0,1},{1,2},{2,0},{0,3},{1,3},{2,3}, {1,0},{2,1},{0,2},{3,0},{3,1},{3,2} };
static PxI32 cubeSixTets[6][4] = {
{ 0, 4, 5, 7 },{ 1, 5, 6, 7 },{ 1, 0, 5, 7 },{ 1, 2, 3, 6 },{ 3, 1, 6, 7 },{ 0, 1, 3, 7 } };
static PxI32 cubeFiveTets[2][5][4] = {
{ { 0, 1, 2, 5 },{ 0, 2, 3, 7 },{ 0, 5, 2, 7 },{ 0, 5, 7, 4 },{ 2, 7, 5, 6 } },
{ { 1, 2, 3, 6 },{ 1, 3, 0, 4 },{ 1, 6, 3, 4 },{ 1, 6, 4, 5 },{ 3, 4, 6, 7 } },
};
static PxI32 cubeSixSubdivTets[12][4] = {
{0,4,5,8}, {0,5,1,8}, {3,2,6,8}, {3,6,7,8},
{0,3,7,8}, {0,7,4,8}, {1,5,6,8}, {1,6,2,8},
{0,1,3,8}, {1,2,3,8}, {5,4,7,8}, {5,7,6,8}
};
static PxI32 cubeFiveSubdivTets[2][12][4] = {
{
{0,1,2,8}, {0,2,3,8}, {4,7,5,8}, {5,7,6,8},
{0,7,4,8}, {0,3,7,8}, {1,5,2,8}, {2,5,6,8},
{0,5,1,8}, {0,4,5,8}, {3,2,7,8}, {2,6,7,8}
},
{
{0,1,3,8}, {1,2,3,8}, {4,7,6,8}, {4,6,5,8},
{0,3,4,8}, {3,7,4,8}, {1,5,6,8}, {1,6,2,8},
{0,4,1,8}, {1,4,5,8}, {3,2,6,8}, {3,6,7,8}
}
};
static const PxI32 volIdOrder[4][3] = { {1, 3, 2}, {0, 2, 3}, {0, 3, 1}, {0, 1, 2} };
// -------------------------------------------------------------------------------------
static bool boxTriangleIntersection(
PxVec3 p0, PxVec3 p1, PxVec3 p2, PxVec3 center, PxVec3 extents);
static void getClosestPointOnTriangle(
PxVec3 p1, PxVec3 p2, PxVec3 p3, PxVec3 p, PxVec3& closest, PxVec3& bary);
// -------------------------------------------------------------------------------------
VoxelTetrahedralizer::VoxelTetrahedralizer()
{
clear();
}
// -------------------------------------------------------------------------------------
void VoxelTetrahedralizer::clear()
{
surfaceVerts.clear();
surfaceTriIds.clear();
surfaceBounds.setEmpty();
tetVerts.clear();
origTetVerts.clear();
isSurfaceVert.clear();
targetVertPos.clear();
tetIds.clear();
voxels.clear();
gridOrigin = PxVec3(PxZero);
gridSpacing = 0.0f;
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::readBack(PxArray<PxVec3>& _tetVertices, PxArray<PxU32>& _tetIndices)
{
_tetVertices = tetVerts;
_tetIndices.resize(tetIds.size());
for (PxU32 i = 0; i < tetIds.size(); i++)
_tetIndices[i] = PxU32(tetIds[i]);
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::createTetMesh(const PxArray<PxVec3>& verts, const PxArray<PxU32>& triIds,
PxI32 resolution, PxI32 numRelaxationIters, PxF32 relMinTetVolume)
{
surfaceVerts = verts;
surfaceTriIds.resize(triIds.size());
for (PxU32 i = 0; i < triIds.size(); i++)
surfaceTriIds[i] = triIds[i];
surfaceBounds.setEmpty();
for (PxU32 i = 0; i < surfaceVerts.size(); i++)
surfaceBounds.include(surfaceVerts[i]);
buildBVH();
voxelize(resolution);
bool subdivBorder = true;
int numTetsPerVoxel = 5; // or 6
createTets(subdivBorder, numTetsPerVoxel);
findTargetPositions(0.2f * gridSpacing);
relax(numRelaxationIters, relMinTetVolume);
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::buildBVH()
{
PxI32 numTris = PxI32(surfaceTriIds.size()) / 3;
if (numTris == 0)
return;
PxArray<PxBounds3> bvhBounds(numTris);
for (PxI32 i = 0; i < numTris; i++) {
PxBounds3& b = bvhBounds[i];
b.setEmpty();
b.include(surfaceVerts[surfaceTriIds[3 * i]]);
b.include(surfaceVerts[surfaceTriIds[3 * i + 1]]);
b.include(surfaceVerts[surfaceTriIds[3 * i + 2]]);
}
BVHBuilder::build(bvh, &bvhBounds[0], bvhBounds.size());
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::voxelize(PxU32 resolution)
{
tetIds.clear();
tetVerts.clear();
PxBounds3 meshBounds;
meshBounds.setEmpty();
for (PxU32 i = 0; i < surfaceVerts.size(); i++)
meshBounds.include(surfaceVerts[i]);
gridSpacing = meshBounds.getDimensions().magnitude() / resolution;
meshBounds.fattenSafe(gridSpacing);
gridOrigin = meshBounds.minimum;
voxels.clear();
PxI32 numX = PxI32((meshBounds.maximum.x - meshBounds.minimum.x) / gridSpacing) + 1;
PxI32 numY = PxI32((meshBounds.maximum.y - meshBounds.minimum.y) / gridSpacing) + 1;
PxI32 numZ = PxI32((meshBounds.maximum.z - meshBounds.minimum.z) / gridSpacing) + 1;
PxI32 numCells = numX * numY * numZ;
PxArray<PxI32> voxelOfCell(numCells, -1);
PxBounds3 voxelBounds, faceBounds;
// create intersected voxels
for (PxI32 i = 0; i < numCells; i++) {
PxI32 zi = i % numZ;
PxI32 yi = (i / numZ) % numY;
PxI32 xi = (i / numZ / numY);
voxelBounds.minimum = meshBounds.minimum + PxVec3(PxF32(xi), PxF32(yi), PxF32(zi)) * gridSpacing;
voxelBounds.maximum = voxelBounds.minimum + PxVec3(gridSpacing);
bvh.query(voxelBounds, queryTris);
for (PxU32 j = 0; j < queryTris.size(); j++) {
PxI32 triNr = queryTris[j];
const PxVec3& p0 = surfaceVerts[surfaceTriIds[3 * triNr]];
const PxVec3& p1 = surfaceVerts[surfaceTriIds[3 * triNr + 1]];
const PxVec3& p2 = surfaceVerts[surfaceTriIds[3 * triNr + 2]];
if (boxTriangleIntersection(p0, p1, p2, voxelBounds.getCenter(), voxelBounds.getExtents())) {
// volume
if (voxelOfCell[i] < 0) {
voxelOfCell[i] = voxels.size();
voxels.resize(voxels.size() + 1);
voxels.back().init(xi, yi, zi);
}
}
}
}
// flood outside
PxArray<PxI32> stack;
stack.pushBack(0);
while (!stack.empty()) {
PxI32 nr = stack.back();
stack.popBack();
if (voxelOfCell[nr] == -1) {
voxelOfCell[nr] = -2; // outside
PxI32 z0 = nr % numZ;
PxI32 y0 = (nr / numZ) % numY;
PxI32 x0 = (nr / numZ / numY);
for (PxI32 i = 0; i < 6; i++) {
PxI32 xi = x0 + cubeNeighbors[i][0];
PxI32 yi = y0 + cubeNeighbors[i][1];
PxI32 zi = z0 + cubeNeighbors[i][2];
if (xi >= 0 && xi < numX && yi >= 0 && yi < numY && zi >= 0 && zi < numZ) {
PxI32 adj = (xi * numY + yi) * numZ + zi;
if (voxelOfCell[adj] == -1)
stack.pushBack(adj);
}
}
}
}
// create voxels for the inside
for (PxI32 i = 0; i < numCells; i++) {
if (voxelOfCell[i] == -1) {
voxelOfCell[i] = voxels.size();
voxels.resize(voxels.size() + 1);
PxI32 zi = i % numZ;
PxI32 yi = (i / numZ) % numY;
PxI32 xi = (i / numZ / numY);
voxels.back().init(xi, yi, zi);
voxels.back().inner = true;
}
}
// find neighbors
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v = voxels[i];
voxelBounds.minimum = meshBounds.minimum + PxVec3(PxF32(v.xi), PxF32(v.yi), PxF32(v.zi)) * gridSpacing;
voxelBounds.maximum = voxelBounds.minimum + PxVec3(gridSpacing);
for (PxI32 j = 0; j < 6; j++) {
PxI32 xi = v.xi + cubeNeighbors[j][0];
PxI32 yi = v.yi + cubeNeighbors[j][1];
PxI32 zi = v.zi + cubeNeighbors[j][2];
if (xi < 0 || xi >= numX || yi < 0 || yi >= numY || zi < 0 || zi >= numZ)
continue;
PxI32 neighbor = voxelOfCell[(xi * numY + yi) * numZ + zi];
if (neighbor < 0)
continue;
if (v.inner || voxels[neighbor].inner) {
v.neighbors[j] = neighbor;
continue;
}
faceBounds = voxelBounds;
PxF32 eps = 1e-4f;
switch (j) {
case 0: faceBounds.maximum.x = faceBounds.minimum.x + eps; break;
case 1: faceBounds.minimum.x = faceBounds.maximum.x - eps; break;
case 2: faceBounds.maximum.y = faceBounds.minimum.y + eps; break;
case 3: faceBounds.minimum.y = faceBounds.maximum.y - eps; break;
case 4: faceBounds.maximum.z = faceBounds.minimum.z + eps; break;
case 5: faceBounds.minimum.z = faceBounds.maximum.z - eps; break;
}
bvh.query(faceBounds, queryTris);
bool intersected = false;
for (PxU32 k = 0; k < queryTris.size(); k++) {
PxI32 triNr = queryTris[k];
const PxVec3& p0 = surfaceVerts[surfaceTriIds[3 * triNr]];
const PxVec3& p1 = surfaceVerts[surfaceTriIds[3 * triNr + 1]];
const PxVec3& p2 = surfaceVerts[surfaceTriIds[3 * triNr + 2]];
if (boxTriangleIntersection(p0, p1, p2, faceBounds.getCenter(), faceBounds.getExtents())) {
intersected = true;
break;
}
}
if (intersected)
v.neighbors[j] = neighbor;
}
}
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::createUniqueTetVertices()
{
// start with each voxel having its own vertices
PxArray<PxVec3> verts;
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v = voxels[i];
for (PxI32 j = 0; j < 8; j++) {
v.ids[j] = verts.size();
verts.pushBack(gridOrigin + PxVec3(
PxF32(v.xi + cubeCorners[j][0]),
PxF32(v.yi + cubeCorners[j][1]),
PxF32(v.zi + cubeCorners[j][2])) * gridSpacing);
}
}
// unify vertices
UnionFind* u = new UnionFind();
u->init(verts.size());
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v0 = voxels[i];
for (PxI32 j = 0; j < 6; j++) {
PxI32 n = v0.neighbors[j];
if (n < 0)
continue;
Voxel& v1 = voxels[n];
for (PxI32 k = 0; k < 4; k++) {
PxI32 id0 = v0.ids[cubeFaces[j][k]];
PxI32 id1 = v1.ids[cubeFaces[oppNeighbor[j]][k]];
u->makeSet(id0, id1);
}
}
}
u->computeSetNrs();
tetVerts.clear();
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v = voxels[i];
for (PxI32 j = 0; j < 8; j++) {
PxI32 setNr = u->getSetNr(v.ids[j]);
if (PxI32(tetVerts.size()) <= setNr)
tetVerts.resize(setNr + 1, PxVec3(PxZero));
tetVerts[setNr] = verts[v.ids[j]];
v.ids[j] = setNr;
}
}
origTetVerts = tetVerts;
delete u;
}
// -------------------------------------------------------------------------------------
void VoxelTetrahedralizer::findTargetPositions(PxF32 surfaceDist)
{
targetVertPos = tetVerts;
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v = voxels[i];
PxBounds3 voxelBounds;
voxelBounds.minimum = gridOrigin + PxVec3(PxF32(v.xi), PxF32(v.yi), PxF32(v.zi)) * gridSpacing;
voxelBounds.maximum = voxelBounds.minimum + PxVec3(gridSpacing);
voxelBounds.fattenFast(0.1f * gridSpacing);
bvh.query(voxelBounds, queryTris);
for (PxI32 j = 0; j < 8; j++) {
PxI32 id = v.ids[j];
if (!isSurfaceVert[id])
continue;
PxVec3& p = tetVerts[id];
PxF32 minDist2 = PX_MAX_F32;
PxVec3 closest(PxZero);
for (PxU32 k = 0; k < queryTris.size(); k++) {
PxI32 triNr = queryTris[k];
const PxVec3& p0 = surfaceVerts[surfaceTriIds[3 * triNr]];
const PxVec3& p1 = surfaceVerts[surfaceTriIds[3 * triNr + 1]];
const PxVec3& p2 = surfaceVerts[surfaceTriIds[3 * triNr + 2]];
PxVec3 c, bary;
getClosestPointOnTriangle(p0, p1, p2, p, c, bary);
PxF32 dist2 = (c - p).magnitudeSquared();
if (dist2 < minDist2) {
minDist2 = dist2;
closest = c;
}
}
if (minDist2 < PX_MAX_F32) {
PxVec3 n = p - closest;
n.normalize();
targetVertPos[id] = closest + n * surfaceDist;
}
}
}
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::createTets(bool subdivBorder, PxU32 numTetsPerVoxel)
{
if (numTetsPerVoxel < 5 || numTetsPerVoxel > 6)
return;
createUniqueTetVertices();
PxArray<Voxel> prevVoxels;
PxArray<PxI32> numVertVoxels(tetVerts.size(), 0);
tetIds.clear();
for (PxU32 i = 0; i < voxels.size(); i++) {
Voxel& v = voxels[i];
for (PxI32 j = 0; j < 8; j++)
numVertVoxels[v.ids[j]]++;
PxI32 parity = (v.xi + v.yi + v.zi) % 2;
if (v.inner || !subdivBorder) {
if (numTetsPerVoxel == 6) {
for (PxI32 j = 0; j < 6; j++) {
tetIds.pushBack(v.ids[cubeSixTets[j][0]]);
tetIds.pushBack(v.ids[cubeSixTets[j][1]]);
tetIds.pushBack(v.ids[cubeSixTets[j][2]]);
tetIds.pushBack(v.ids[cubeSixTets[j][3]]);
}
}
else if (numTetsPerVoxel == 5) {
for (PxI32 j = 0; j < 5; j++) {
tetIds.pushBack(v.ids[cubeFiveTets[parity][j][0]]);
tetIds.pushBack(v.ids[cubeFiveTets[parity][j][1]]);
tetIds.pushBack(v.ids[cubeFiveTets[parity][j][2]]);
tetIds.pushBack(v.ids[cubeFiveTets[parity][j][3]]);
}
}
}
else {
PxVec3 p(PxZero);
for (PxI32 j = 0; j < 8; j++)
p += tetVerts[v.ids[j]];
p /= 8.0;
PxI32 newId = tetVerts.size();
tetVerts.pushBack(p);
origTetVerts.pushBack(p);
numVertVoxels.pushBack(8);
for (PxI32 j = 0; j < 12; j++) {
const int* localIds;
if (numTetsPerVoxel == 6)
localIds = cubeSixSubdivTets[j];
else
localIds = cubeFiveSubdivTets[parity][j];
for (PxI32 k = 0; k < 4; k++) {
PxI32 id = localIds[k] < 8 ? v.ids[localIds[k]] : newId;
tetIds.pushBack(id);
}
}
}
}
isSurfaceVert.resize(tetVerts.size(), false);
for (PxU32 i = 0; i < tetVerts.size(); i++)
isSurfaceVert[i] = numVertVoxels[i] < 8;
// randomize tets
PxU32 numTets = tetIds.size() / 4;
//for (PxU32 i = 0; i < numTets - 1; i++) {
// PxI32 ri = i + rand() % (numTets - i);
// for (PxI32 j = 0; j < 4; j++) {
// PxI32 id = tetIds[4 * i + j]; tetIds[4 * i + j] = tetIds[4 * ri + j]; tetIds[4 * ri + j] = id;
// }
//}
// edges
MultiList<int> adjVerts;
edgeIds.clear();
adjVerts.clear();
adjVerts.reserve(tetVerts.size());
for (PxU32 i = 0; i < numTets; i++) {
for (PxI32 j = 0; j < 6; j++) {
PxI32 id0 = tetIds[4 * i + tetEdges[j][0]];
PxI32 id1 = tetIds[4 * i + tetEdges[j][1]];
if (!adjVerts.exists(id0, id1)) {
edgeIds.pushBack(id0);
edgeIds.pushBack(id1);
adjVerts.addUnique(id0, id1);
adjVerts.addUnique(id1, id0);
}
}
}
}
// -----------------------------------------------------------------------------------
void VoxelTetrahedralizer::conserveVolume(PxF32 relMinVolume)
{
PxVec3 grads[4];
PxU32 numTets = tetIds.size() / 4;
for (PxU32 i = 0; i < numTets; i++) {
PxI32* ids = &tetIds[4 * i];
PxF32 w = 0.0f;
for (PxI32 j = 0; j < 4; j++) {
PxI32 id0 = ids[volIdOrder[j][0]];
PxI32 id1 = ids[volIdOrder[j][1]];
PxI32 id2 = ids[volIdOrder[j][2]];
grads[j] = (tetVerts[id1] - tetVerts[id0]).cross(tetVerts[id2] - tetVerts[id0]);
w += grads[j].magnitudeSquared();
}
if (w == 0.0f)
continue;
PxVec3& p0 = tetVerts[ids[0]];
PxF32 V = (tetVerts[ids[1]] - p0).cross(tetVerts[ids[2]] - p0).dot(tetVerts[ids[3]] - p0);
PxVec3& origP0 = origTetVerts[ids[0]];
PxF32 origV = (origTetVerts[ids[1]] - origP0).cross(origTetVerts[ids[2]] - origP0).dot(origTetVerts[ids[3]] - origP0);
PxF32 minV = relMinVolume * origV;
if (V < minV) {
PxF32 C = V - minV;
PxF32 lambda = -C / w;
for (PxI32 j = 0; j < 4; j++) {
tetVerts[ids[j]] += grads[j] * lambda;
}
}
}
}
// -------------------------------------------------------------------------------------
void VoxelTetrahedralizer::relax(PxI32 numIters, PxF32 relMinVolume)
{
const PxF32 targetScale = 0.3f;
const PxF32 edgeScale = 0.3f;
for (PxI32 iter = 0; iter < numIters; iter++) {
PxU32 numVerts = tetVerts.size();
for (PxU32 i = 0; i < numVerts; i++) {
if (isSurfaceVert[i]) {
PxVec3 offset = (targetVertPos[i] - tetVerts[i]) * targetScale;
tetVerts[i] += offset;
}
}
for (PxU32 i = 0; i < edgeIds.size(); i += 2) {
PxI32 id0 = edgeIds[i];
PxI32 id1 = edgeIds[i + 1];
PxF32 w0 = isSurfaceVert[id0] ? 0.0f : 1.0f;
PxF32 w1 = isSurfaceVert[id1] ? 0.0f : 1.0f;
PxF32 w = w0 + w1;
if (w == 0.0f)
continue;
PxVec3& p0 = tetVerts[id0];
PxVec3& p1 = tetVerts[id1];
PxVec3 e = (p1 - p0) * edgeScale;
if (w == 1.0f)
e *= 0.5f;
p0 += w0 / w * e;
p1 -= w1 / w * e;
}
conserveVolume(relMinVolume);
}
PxI32 volIters = 2;
for (PxI32 volIter = 0; volIter < volIters; volIter++)
conserveVolume(relMinVolume);
}
// -----------------------------------------------------------------------------------
static PxF32 max3(PxF32 f0, PxF32 f1, PxF32 f2) {
return PxMax(f0, PxMax(f1, f2));
}
static PxF32 min3(PxF32 f0, PxF32 f1, PxF32 f2) {
return PxMin(f0, PxMin(f1, f2));
}
static PxF32 minMax(PxF32 f0, PxF32 f1, PxF32 f2) {
return PxMax(-max3(f0, f1, f2), min3(f0, f1, f2));
}
// -----------------------------------------------------------------------------------
// PT: TODO: refactor with other SDK implementation
static bool boxTriangleIntersection(
PxVec3 p0, PxVec3 p1, PxVec3 p2, PxVec3 center, PxVec3 extents)
{
PxVec3 v0 = p0 - center, v1 = p1 - center, v2 = p2 - center;
PxVec3 f0 = p1 - p0, f1 = p2 - p1, f2 = p0 - p2;
PxF32 r;
PxVec3 n = f0.cross(f1);
PxF32 d = n.dot(v0);
r = extents.x * fabsf(n.x) + extents.y * fabsf(n.y) + extents.z * fabsf(n.z);
if (d > r || d < -r)
return false;
if (max3(v0.x, v1.x, v2.x) < -extents.x || min3(v0.x, v1.x, v2.x) > extents.x)
return false;
if (max3(v0.y, v1.y, v2.y) < -extents.y || min3(v0.y, v1.y, v2.y) > extents.y)
return false;
if (max3(v0.z, v1.z, v2.z) < -extents.z || min3(v0.z, v1.z, v2.z) > extents.z)
return false;
PxVec3 a00(0.0f, -f0.z, f0.y);
r = extents.y * fabsf(f0.z) + extents.z * fabsf(f0.y);
if (minMax(v0.dot(a00), v1.dot(a00), v2.dot(a00)) > r)
return false;
PxVec3 a01(0.0f, -f1.z, f1.y);
r = extents.y * fabsf(f1.z) + extents.z * fabsf(f1.y);
if (minMax(v0.dot(a01), v1.dot(a01), v2.dot(a01)) > r)
return false;
PxVec3 a02(0.0f, -f2.z, f2.y);
r = extents.y * fabsf(f2.z) + extents.z * fabsf(f2.y);
if (minMax(v0.dot(a02), v1.dot(a02), v2.dot(a02)) > r)
return false;
PxVec3 a10(f0.z, 0.0f, -f0.x);
r = extents.x * fabsf(f0.z) + extents.z * fabsf(f0.x);
if (minMax(v0.dot(a10), v1.dot(a10), v2.dot(a10)) > r)
return false;
PxVec3 a11(f1.z, 0.0f, -f1.x);
r = extents.x * fabsf(f1.z) + extents.z * fabsf(f1.x);
if (minMax(v0.dot(a11), v1.dot(a11), v2.dot(a11)) > r)
return false;
PxVec3 a12(f2.z, 0.0f, -f2.x);
r = extents.x * fabsf(f2.z) + extents.z * fabsf(f2.x);
if (minMax(v0.dot(a12), v1.dot(a12), v2.dot(a12)) > r)
return false;
PxVec3 a20(-f0.y, f0.x, 0.0f);
r = extents.x * fabsf(f0.y) + extents.y * fabsf(f0.x);
if (minMax(v0.dot(a20), v1.dot(a20), v2.dot(a20)) > r)
return false;
PxVec3 a21(-f1.y, f1.x, 0.0f);
r = extents.x * fabsf(f1.y) + extents.y * fabsf(f1.x);
if (minMax(v0.dot(a21), v1.dot(a21), v2.dot(a21)) > r)
return false;
PxVec3 a22(-f2.y, f2.x, 0.0f);
r = extents.x * fabsf(f2.y) + extents.y * fabsf(f2.x);
if (minMax(v0.dot(a22), v1.dot(a22), v2.dot(a22)) > r)
return false;
return true;
}
// -----------------------------------------------------------------------------------
// PT: TODO: refactor with other implementation
static void getClosestPointOnTriangle(
PxVec3 p1, PxVec3 p2, PxVec3 p3, PxVec3 p, PxVec3& closest, PxVec3& bary)
{
PxVec3 e0 = p2 - p1;
PxVec3 e1 = p3 - p1;
PxVec3 tmp = p1 - p;
PxF32 a = e0.dot(e0);
PxF32 b = e0.dot(e1);
PxF32 c = e1.dot(e1);
PxF32 d = e0.dot(tmp);
PxF32 e = e1.dot(tmp);
PxVec3 coords, clampedCoords;
coords.x = b * e - c * d; // s * det
coords.y = b * d - a * e; // t * det
coords.z = a * c - b * b; // det
clampedCoords = PxVec3(0.0f, 0.0f, 0.0f);
if (coords.x <= 0.0f) {
if (c != 0.0f)
clampedCoords.y = -e / c;
}
else if (coords.y <= 0.0f) {
if (a != 0.0f)
clampedCoords.x = -d / a;
}
else if (coords.x + coords.y > coords.z) {
PxF32 denominator = a + c - b - b;
PxF32 numerator = c + e - b - d;
if (denominator != 0.0f) {
clampedCoords.x = numerator / denominator;
clampedCoords.y = 1.0f - clampedCoords.x;
}
}
else { // all inside
if (coords.z != 0.0f) {
clampedCoords.x = coords.x / coords.z;
clampedCoords.y = coords.y / coords.z;
}
}
clampedCoords.x = PxMax(clampedCoords.x, 0.0f);
clampedCoords.y = PxMax(clampedCoords.y, 0.0f);
clampedCoords.x = PxMin(clampedCoords.x, 1.0f);
clampedCoords.y = PxMin(clampedCoords.y, 1.0f);
closest = p1 + e0 * clampedCoords.x + e1 * clampedCoords.y;
bary.x = 1.0f - clampedCoords.x - clampedCoords.y;
bary.y = clampedCoords.x;
bary.z = clampedCoords.y;
}

View File

@@ -0,0 +1,116 @@
// 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.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef EXT_VOXEL_TETRAHEDRALIZER_H
#define EXT_VOXEL_TETRAHEDRALIZER_H
#include "ExtMultiList.h"
#include "ExtBVH.h"
#include "foundation/PxVec3.h"
#include "foundation/PxBounds3.h"
namespace physx
{
namespace Ext
{
// ------------------------------------------------------------------------------
class VoxelTetrahedralizer
{
public:
VoxelTetrahedralizer();
void clear();
void createTetMesh(const PxArray<PxVec3>& verts, const PxArray<PxU32>& triIds,
PxI32 resolution, PxI32 numRelaxationIters = 5, PxF32 relMinTetVolume = 0.05f);
void readBack(PxArray<PxVec3>& tetVertices, PxArray<PxU32>& tetIndices);
private:
void voxelize(PxU32 resolution);
void createTets(bool subdivBorder, PxU32 numTetsPerVoxel);
void buildBVH();
void createUniqueTetVertices();
void findTargetPositions(PxF32 surfaceDist);
void conserveVolume(PxF32 relMinVolume);
void relax(PxI32 numIters, PxF32 relMinVolume);
// input mesh
PxArray<PxVec3> surfaceVerts;
PxArray<PxI32> surfaceTriIds;
PxBounds3 surfaceBounds;
// voxel grid
struct Voxel {
void init(PxI32 _xi, PxI32 _yi, PxI32 _zi)
{
xi = _xi; yi = _yi; zi = _zi;
for (PxI32 i = 0; i < 6; i++)
neighbors[i] = -1;
for (PxI32 i = 0; i < 8; i++)
ids[i] = -1;
parent = -1;
inner = false;
}
bool isAt(PxI32 _xi, PxI32 _yi, PxI32 _zi) {
return xi == _xi && yi == _yi && zi == _zi;
}
PxI32 xi, yi, zi;
PxI32 neighbors[6];
PxI32 parent;
PxI32 ids[8];
bool inner;
};
PxVec3 gridOrigin;
PxF32 gridSpacing;
PxArray<Voxel> voxels;
BVHDesc bvh;
// tet mesh
PxArray<PxVec3> tetVerts;
PxArray<PxVec3> origTetVerts;
PxArray<PxI32> tetIds;
// relaxation
PxArray<bool> isSurfaceVert;
PxArray<PxVec3> targetVertPos;
PxArray<PxI32> queryTris;
PxArray<PxI32> edgeIds;
};
}
}
#endif