384 lines
14 KiB
C++
384 lines
14 KiB
C++
|
|
// Redistribution and use in source and binary forms, with or without
|
||
|
|
// modification, are permitted provided that the following conditions
|
||
|
|
// are met:
|
||
|
|
// * Redistributions of source code must retain the above copyright
|
||
|
|
// notice, this list of conditions and the following disclaimer.
|
||
|
|
// * Redistributions in binary form must reproduce the above copyright
|
||
|
|
// notice, this list of conditions and the following disclaimer in the
|
||
|
|
// documentation and/or other materials provided with the distribution.
|
||
|
|
// * Neither the name of NVIDIA CORPORATION nor the names of its
|
||
|
|
// contributors may be used to endorse or promote products derived
|
||
|
|
// from this software without specific prior written permission.
|
||
|
|
//
|
||
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
|
||
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||
|
|
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
//
|
||
|
|
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
|
||
|
|
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
|
||
|
|
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
|
||
|
|
|
||
|
|
#include "tet/ExtDelaunayBoundaryInserter.h"
|
||
|
|
#include "extensions/PxTetMakerExt.h"
|
||
|
|
#include "cooking/PxTetrahedronMeshDesc.h"
|
||
|
|
#include "geometry/PxTriangleMesh.h"
|
||
|
|
#include "tet/ExtMeshSimplificator.h"
|
||
|
|
#include "tet/ExtRemesher.h"
|
||
|
|
#include "tet/ExtOctreeTetrahedralizer.h"
|
||
|
|
#include "tet/ExtVoxelTetrahedralizer.h"
|
||
|
|
#include "foundation/PxMat33.h"
|
||
|
|
#include <stdio.h>
|
||
|
|
|
||
|
|
using namespace physx;
|
||
|
|
|
||
|
|
static PX_FORCE_INLINE PxReal computeTetrahedronVolume(const PxVec3& x0, const PxVec3& x1, const PxVec3& x2, const PxVec3& x3)
|
||
|
|
{
|
||
|
|
const PxVec3 u1 = x1 - x0;
|
||
|
|
const PxVec3 u2 = x2 - x0;
|
||
|
|
const PxVec3 u3 = x3 - x0;
|
||
|
|
|
||
|
|
PxMat33 edgeMatrix = PxMat33(u1, u2, u3);
|
||
|
|
|
||
|
|
const PxReal det = edgeMatrix.getDeterminant();
|
||
|
|
|
||
|
|
const PxReal volume = det / 6.0f;
|
||
|
|
return volume;
|
||
|
|
}
|
||
|
|
|
||
|
|
//Remove tets with small volume
|
||
|
|
static void removeSmallVolumeTetrahedra(PxArray<::physx::PxVec3>& vertices, PxArray<PxU32>& indices, PxReal volumeThreshold = 1e-8f)
|
||
|
|
{
|
||
|
|
uint32_t indexer = 0;
|
||
|
|
|
||
|
|
for (uint32_t i = 0; i < indices.size(); i += 4)
|
||
|
|
{
|
||
|
|
for (uint32_t j = 0; j < 4; ++j)
|
||
|
|
{
|
||
|
|
indices[indexer + j] = indices[i + j];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (computeTetrahedronVolume(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]], vertices[indices[i + 3]]) >= volumeThreshold)
|
||
|
|
{
|
||
|
|
indexer += 4;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (indexer < indices.size())
|
||
|
|
{
|
||
|
|
indices.removeRange(indexer, indices.size() - indexer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//Removes vertices not referenced by any tetrahedron and maps the tet's indices to match the compacted vertex list
|
||
|
|
static void removeUnusedVertices(PxArray<::physx::PxVec3>& vertices, PxArray<PxU32>& tets, PxU32 numPointsToKeepAtBeginning = 0)
|
||
|
|
{
|
||
|
|
PxArray<PxI32> compressorMap;
|
||
|
|
compressorMap.resize(vertices.size());
|
||
|
|
|
||
|
|
for (PxU32 i = 0; i < numPointsToKeepAtBeginning; ++i)
|
||
|
|
compressorMap[i] = 0;
|
||
|
|
for (PxU32 i = numPointsToKeepAtBeginning; i < compressorMap.size(); ++i)
|
||
|
|
compressorMap[i] = -1;
|
||
|
|
|
||
|
|
for (PxU32 i = 0; i < tets.size(); i += 4)
|
||
|
|
{
|
||
|
|
const PxU32* tet = &tets[i];
|
||
|
|
if (tet[0] == 0xFFFFFFFFu)
|
||
|
|
continue;
|
||
|
|
compressorMap[tet[0]] = 0;
|
||
|
|
compressorMap[tet[1]] = 0;
|
||
|
|
compressorMap[tet[2]] = 0;
|
||
|
|
compressorMap[tet[3]] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
PxU32 indexer = 0;
|
||
|
|
for (PxU32 i = 0; i < compressorMap.size(); ++i)
|
||
|
|
{
|
||
|
|
if (compressorMap[i] >= 0)
|
||
|
|
{
|
||
|
|
compressorMap[i] = indexer;
|
||
|
|
vertices[indexer] = vertices[i];
|
||
|
|
indexer++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (PxU32 i = 0; i < tets.size(); i += 4)
|
||
|
|
{
|
||
|
|
PxU32* tet = &tets[i];
|
||
|
|
if (tet[0] == 0xFFFFFFFFu)
|
||
|
|
continue;
|
||
|
|
tet[0] = compressorMap[tet[0]];
|
||
|
|
tet[1] = compressorMap[tet[1]];
|
||
|
|
tet[2] = compressorMap[tet[2]];
|
||
|
|
tet[3] = compressorMap[tet[3]];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (indexer < vertices.size())
|
||
|
|
vertices.removeRange(indexer, vertices.size() - indexer);
|
||
|
|
}
|
||
|
|
|
||
|
|
static PX_FORCE_INLINE PxU64 buildKey(PxI32 a, PxI32 b)
|
||
|
|
{
|
||
|
|
if (a < b)
|
||
|
|
return ((PxU64(a)) << 32) | (PxU64(b));
|
||
|
|
else
|
||
|
|
return ((PxU64(b)) << 32) | (PxU64(a));
|
||
|
|
}
|
||
|
|
|
||
|
|
static const PxI32 neighborEdgeList[3][2] = { { 0, 1 }, { 0, 2 }, { 1, 2 } };
|
||
|
|
|
||
|
|
static void buildTriangleNeighborhood(const PxI32* tris, PxU32 numTris, PxArray<PxI32>& result)
|
||
|
|
{
|
||
|
|
PxU32 l = 4 * numTris; //Waste one element in neighborhood info but allow bit shift access instead
|
||
|
|
result.clear();
|
||
|
|
result.resize(l, -1);
|
||
|
|
|
||
|
|
PxHashMap<PxU64, PxI32> faces;
|
||
|
|
for (PxU32 i = 0; i < numTris; ++i)
|
||
|
|
{
|
||
|
|
const PxI32* tri = &tris[3 * i];
|
||
|
|
if (tris[0] < 0)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
for (PxI32 j = 0; j < 3; ++j)
|
||
|
|
{
|
||
|
|
PxU64 key = buildKey(tri[neighborEdgeList[j][0]], tri[neighborEdgeList[j][1]]);
|
||
|
|
if (const PxPair<const PxU64, PxI32>* ptr = faces.find(key))
|
||
|
|
{
|
||
|
|
if (ptr->second < 0)
|
||
|
|
{
|
||
|
|
//PX_ASSERT(false); //Invalid tetmesh since a face is shared by more than 2 tetrahedra
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
result[4 * i + j] = ptr->second;
|
||
|
|
result[ptr->second] = 4 * i + j;
|
||
|
|
|
||
|
|
faces[key] = -1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
faces.insert(key, 4 * i + j);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::detectTriangleIslands(const PxI32* triangles, PxU32 numTriangles, PxArray<PxU32>& islandIndexPerTriangle)
|
||
|
|
{
|
||
|
|
//Detect islands
|
||
|
|
PxArray<PxI32> neighborhood;
|
||
|
|
buildTriangleNeighborhood(triangles, numTriangles, neighborhood);
|
||
|
|
const PxU32 noIslandAssignedMarker = 0xFFFFFFFF;
|
||
|
|
islandIndexPerTriangle.resize(numTriangles, noIslandAssignedMarker);
|
||
|
|
PxU32 start = 0;
|
||
|
|
PxI32 color = -1;
|
||
|
|
PxArray<PxI32> stack;
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
stack.clear();
|
||
|
|
while (start < islandIndexPerTriangle.size())
|
||
|
|
{
|
||
|
|
if (islandIndexPerTriangle[start] == noIslandAssignedMarker)
|
||
|
|
{
|
||
|
|
stack.pushBack(start);
|
||
|
|
++color;
|
||
|
|
islandIndexPerTriangle[start] = color;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
++start;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (start == islandIndexPerTriangle.size())
|
||
|
|
break;
|
||
|
|
|
||
|
|
while (stack.size() > 0)
|
||
|
|
{
|
||
|
|
PxI32 id = stack.popBack();
|
||
|
|
for (PxI32 i = 0; i < 3; ++i)
|
||
|
|
{
|
||
|
|
PxI32 a = neighborhood[4 * id + i];
|
||
|
|
PxI32 tetId = a >> 2;
|
||
|
|
if (tetId >= 0 && islandIndexPerTriangle[tetId] == noIslandAssignedMarker)
|
||
|
|
{
|
||
|
|
stack.pushBack(tetId);
|
||
|
|
islandIndexPerTriangle[tetId] = color;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
PxU32 PxTetMaker::findLargestIslandId(const PxU32* islandIndexPerTriangle, PxU32 numTriangles)
|
||
|
|
{
|
||
|
|
PxU32 numIslands = 0;
|
||
|
|
for (PxU32 i = 0; i < numTriangles; ++i)
|
||
|
|
numIslands = PxMax(numIslands, islandIndexPerTriangle[i]);
|
||
|
|
++numIslands;
|
||
|
|
|
||
|
|
PxArray<PxU32> numEntriesPerColor;
|
||
|
|
numEntriesPerColor.resize(numIslands, 0);
|
||
|
|
for (PxU32 i = 0; i < numTriangles; ++i)
|
||
|
|
numEntriesPerColor[islandIndexPerTriangle[i]] += 1;
|
||
|
|
|
||
|
|
PxU32 colorWithHighestTetCount = 0;
|
||
|
|
for (PxU32 i = 1; i < numEntriesPerColor.size(); ++i)
|
||
|
|
if (numEntriesPerColor[i] > numEntriesPerColor[colorWithHighestTetCount])
|
||
|
|
colorWithHighestTetCount = i;
|
||
|
|
|
||
|
|
return colorWithHighestTetCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool PxTetMaker::createConformingTetrahedronMesh(const PxSimpleTriangleMesh& triangleMesh,
|
||
|
|
physx::PxArray<physx::PxVec3>& outVertices, physx::PxArray<physx::PxU32>& outTetIndices, const bool validate, PxReal volumeThreshold)
|
||
|
|
{
|
||
|
|
if (validate)
|
||
|
|
{
|
||
|
|
PxTriangleMeshAnalysisResults result = PxTetMaker::validateTriangleMesh(triangleMesh);
|
||
|
|
if (result & PxTriangleMeshAnalysisResult::eMESH_IS_INVALID)
|
||
|
|
{
|
||
|
|
PxGetFoundation().error(PxErrorCode::eDEBUG_INFO, PX_FL, "createConformingTetrahedronMesh(): Input triangle mesh is not suited to create a tetmesh due to deficiencies. Please call PxTetMaker::validateTriangleMesh(triangleMesh) for more details.");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ext::generateTetmesh(triangleMesh.points, triangleMesh.triangles, triangleMesh.flags & PxMeshFlag::e16_BIT_INDICES, outVertices, outTetIndices);
|
||
|
|
|
||
|
|
if (volumeThreshold > 0.0f)
|
||
|
|
removeSmallVolumeTetrahedra(outVertices, outTetIndices, volumeThreshold);
|
||
|
|
|
||
|
|
PxU32 numRemoveAtEnd = Ext::removeDisconnectedIslands(reinterpret_cast<PxI32*>(outTetIndices.begin()), outTetIndices.size() / 4);
|
||
|
|
if (numRemoveAtEnd > 0)
|
||
|
|
outTetIndices.removeRange(outTetIndices.size() - 4 * numRemoveAtEnd, 4 * numRemoveAtEnd);
|
||
|
|
|
||
|
|
removeUnusedVertices(outVertices, outTetIndices, triangleMesh.points.count);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool PxTetMaker::createVoxelTetrahedronMesh(const PxTetrahedronMeshDesc& tetMesh,
|
||
|
|
const PxU32 numVoxelsAlongLongestBoundingBoxAxis, physx::PxArray<physx::PxVec3>& outVertices, physx::PxArray<physx::PxU32>& outTetIndices,
|
||
|
|
PxI32* intputPointToOutputTetIndex, const PxU32* anchorNodeIndices, PxU32 numTetsPerVoxel)
|
||
|
|
{
|
||
|
|
//numTetsPerVoxel has only two valid values.
|
||
|
|
if (numTetsPerVoxel != 5 && numTetsPerVoxel != 6)
|
||
|
|
numTetsPerVoxel = 5;
|
||
|
|
Ext::generateVoxelTetmesh(tetMesh.points, tetMesh.tetrahedrons, numVoxelsAlongLongestBoundingBoxAxis, outVertices, outTetIndices, intputPointToOutputTetIndex, anchorNodeIndices, numTetsPerVoxel);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool PxTetMaker::createVoxelTetrahedronMeshFromEdgeLength(const PxTetrahedronMeshDesc& tetMesh,
|
||
|
|
const PxReal voxelEdgeLength, physx::PxArray<physx::PxVec3>& outVertices, physx::PxArray<physx::PxU32>& outTetIndices,
|
||
|
|
PxI32* intputPointToOutputTetIndex, const PxU32* anchorNodeIndices, PxU32 numTetsPerVoxel)
|
||
|
|
{
|
||
|
|
//numTetsPerVoxel has only two valid values.
|
||
|
|
if (numTetsPerVoxel != 5 && numTetsPerVoxel != 6)
|
||
|
|
numTetsPerVoxel = 5;
|
||
|
|
Ext::generateVoxelTetmesh(tetMesh.points, tetMesh.tetrahedrons, voxelEdgeLength, outVertices, outTetIndices, intputPointToOutputTetIndex, anchorNodeIndices, numTetsPerVoxel);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
PxTriangleMeshAnalysisResults PxTetMaker::validateTriangleMesh(const PxSimpleTriangleMesh& triangleMesh, const PxReal minVolumeThreshold, const PxReal minTriangleAngleRadians)
|
||
|
|
{
|
||
|
|
return Ext::validateTriangleMesh(triangleMesh.points, triangleMesh.triangles, triangleMesh.flags & PxMeshFlag::e16_BIT_INDICES, minVolumeThreshold, minTriangleAngleRadians);
|
||
|
|
}
|
||
|
|
|
||
|
|
PxTetrahedronMeshAnalysisResults PxTetMaker::validateTetrahedronMesh(const PxBoundedData& points, const PxBoundedData& tetrahedra, const PxReal minTetVolumeThreshold)
|
||
|
|
{
|
||
|
|
return Ext::validateTetrahedronMesh(points, tetrahedra, false, minTetVolumeThreshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::simplifyTriangleMesh(const PxArray<PxVec3>& inputVertices, const PxArray<PxU32>&inputIndices, int targetTriangleCount, PxF32 maximalEdgeLength,
|
||
|
|
PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputIndices,
|
||
|
|
PxArray<PxU32> *vertexMap, PxReal edgeLengthCostWeight, PxReal flatnessDetectionThreshold,
|
||
|
|
bool projectSimplifiedPointsOnInputMeshSurface, PxArray<PxU32>* outputVertexToInputTriangle, bool removeDisconnectedPatches)
|
||
|
|
{
|
||
|
|
Ext::MeshSimplificator ms;
|
||
|
|
|
||
|
|
PxArray<PxU32> indexMapToFullTriangleSet;
|
||
|
|
if (removeDisconnectedPatches)
|
||
|
|
{
|
||
|
|
PxU32 numTriangles = inputIndices.size() / 3;
|
||
|
|
PxArray<PxU32> islandIndexPerTriangle;
|
||
|
|
PxTetMaker::detectTriangleIslands(reinterpret_cast<const PxI32*>(inputIndices.begin()), numTriangles, islandIndexPerTriangle);
|
||
|
|
|
||
|
|
PxU32 biggestIslandIndex = PxTetMaker::findLargestIslandId(islandIndexPerTriangle.begin(), islandIndexPerTriangle.size());
|
||
|
|
|
||
|
|
PxArray<PxU32> connectedTriangleSet;
|
||
|
|
for (PxU32 i = 0; i < numTriangles; ++i)
|
||
|
|
{
|
||
|
|
if (islandIndexPerTriangle[i] == biggestIslandIndex)
|
||
|
|
{
|
||
|
|
for (PxU32 j = 0; j < 3; ++j)
|
||
|
|
connectedTriangleSet.pushBack(inputIndices[3 * i + j]);
|
||
|
|
indexMapToFullTriangleSet.pushBack(i);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ms.init(inputVertices, connectedTriangleSet, edgeLengthCostWeight, flatnessDetectionThreshold, projectSimplifiedPointsOnInputMeshSurface);
|
||
|
|
ms.decimateBySize(targetTriangleCount, maximalEdgeLength);
|
||
|
|
ms.readBack(outputVertices, outputIndices, vertexMap, outputVertexToInputTriangle);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
ms.init(inputVertices, inputIndices, edgeLengthCostWeight, flatnessDetectionThreshold, projectSimplifiedPointsOnInputMeshSurface);
|
||
|
|
ms.decimateBySize(targetTriangleCount, maximalEdgeLength);
|
||
|
|
ms.readBack(outputVertices, outputIndices, vertexMap, outputVertexToInputTriangle);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (removeDisconnectedPatches && projectSimplifiedPointsOnInputMeshSurface && outputVertexToInputTriangle)
|
||
|
|
{
|
||
|
|
for (PxU32 i = 0; i < outputVertexToInputTriangle->size(); ++i)
|
||
|
|
(*outputVertexToInputTriangle)[i] = indexMapToFullTriangleSet[(*outputVertexToInputTriangle)[i]];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::remeshTriangleMesh(const PxArray<PxVec3>& inputVertices, const PxArray<PxU32>&inputIndices, PxU32 gridResolution,
|
||
|
|
PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputIndices, PxArray<PxU32> *vertexMap)
|
||
|
|
{
|
||
|
|
Ext::Remesher rm;
|
||
|
|
rm.remesh(inputVertices, inputIndices, gridResolution, vertexMap);
|
||
|
|
rm.readBack(outputVertices, outputIndices);
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::remeshTriangleMesh(const PxVec3* inputVertices, PxU32 nbVertices, const PxU32* inputIndices, PxU32 nbIndices, PxU32 gridResolution,
|
||
|
|
PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputIndices, PxArray<PxU32> *vertexMap)
|
||
|
|
{
|
||
|
|
Ext::Remesher rm;
|
||
|
|
rm.remesh(inputVertices, nbVertices, inputIndices, nbIndices, gridResolution, vertexMap);
|
||
|
|
rm.readBack(outputVertices, outputIndices);
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::createTreeBasedTetrahedralMesh(const PxArray<PxVec3>& inputVertices, const PxArray<PxU32>&inputIndices,
|
||
|
|
bool useTreeNodes, PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputIndices, PxReal volumeThreshold)
|
||
|
|
{
|
||
|
|
Ext::OctreeTetrahedralizer ot;
|
||
|
|
ot.createTetMesh(inputVertices, inputIndices, useTreeNodes);
|
||
|
|
ot.readBack(outputVertices, outputIndices);
|
||
|
|
|
||
|
|
if (volumeThreshold > 0.0f)
|
||
|
|
removeSmallVolumeTetrahedra(outputVertices, outputIndices, volumeThreshold);
|
||
|
|
|
||
|
|
PxU32 numRemoveAtEnd = Ext::removeDisconnectedIslands(reinterpret_cast<PxI32*>(outputIndices.begin()), outputIndices.size() / 4);
|
||
|
|
if (numRemoveAtEnd > 0)
|
||
|
|
outputIndices.removeRange(outputIndices.size() - 4 * numRemoveAtEnd, 4 * numRemoveAtEnd);
|
||
|
|
|
||
|
|
removeUnusedVertices(outputVertices, outputIndices, inputVertices.size());
|
||
|
|
}
|
||
|
|
|
||
|
|
void PxTetMaker::createRelaxedVoxelTetrahedralMesh(const PxArray<PxVec3>& inputVertices, const PxArray<PxU32>&inputIndices,
|
||
|
|
PxArray<PxVec3>& outputVertices, PxArray<PxU32>& outputIndices,
|
||
|
|
PxI32 resolution, PxI32 numRelaxationIters, PxF32 relMinTetVolume)
|
||
|
|
{
|
||
|
|
Ext::VoxelTetrahedralizer vt;
|
||
|
|
vt.createTetMesh(inputVertices, inputIndices, resolution, numRelaxationIters, relMinTetVolume);
|
||
|
|
vt.readBack(outputVertices, outputIndices);
|
||
|
|
}
|
||
|
|
|