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,75 @@
// 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 "GuCooking.h"
#include "GuBVH.h"
#include "foundation/PxFPU.h"
#include "cooking/PxBVHDesc.h"
#include "common/PxInsertionCallback.h"
using namespace physx;
using namespace Gu;
static bool buildBVH(const PxBVHDesc& desc, BVHData& data, const char* errorMessage)
{
if(!desc.isValid())
return PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, errorMessage);
BVHBuildStrategy bs;
if(desc.buildStrategy==PxBVHBuildStrategy::eFAST)
bs = BVH_SPLATTER_POINTS;
else if(desc.buildStrategy==PxBVHBuildStrategy::eDEFAULT)
bs = BVH_SPLATTER_POINTS_SPLIT_GEOM_CENTER;
else //if(desc.buildStrategy==PxBVHBuildStrategy::eSAH)
bs = BVH_SAH;
return data.build(desc.bounds.count, desc.bounds.data, desc.bounds.stride, desc.enlargement, desc.numPrimsPerLeaf, bs);
}
bool immediateCooking::cookBVH(const PxBVHDesc& desc, PxOutputStream& stream)
{
PX_FPU_GUARD;
BVHData bvhData;
if(!buildBVH(desc, bvhData, "Cooking::cookBVH: user-provided BVH descriptor is invalid!"))
return false;
return bvhData.save(stream, platformMismatch());
}
PxBVH* immediateCooking::createBVH(const PxBVHDesc& desc, PxInsertionCallback& insertionCallback)
{
PX_FPU_GUARD;
BVHData bvhData;
if(!buildBVH(desc, bvhData, "Cooking::createBVH: user-provided BVH descriptor is invalid!"))
return NULL;
return static_cast<PxBVH*>(insertionCallback.buildObjectFromData(PxConcreteType::eBVH, &bvhData));
}

View File

@@ -0,0 +1,354 @@
// 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 "cooking/PxCooking.h"
#include "foundation/PxUserAllocated.h"
#include "foundation/PxUtilities.h"
#include "foundation/PxVecMath.h"
#include "GuConvexMeshData.h"
#include "GuBigConvexData2.h"
#include "GuIntersectionRayPlane.h"
#include "GuCookingBigConvexDataBuilder.h"
#include "GuCookingConvexHullBuilder.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
using namespace physx;
using namespace Gu;
using namespace aos;
static const PxU32 gSupportVersion = 0;
static const PxU32 gVersion = 0;
BigConvexDataBuilder::BigConvexDataBuilder(const Gu::ConvexHullData* hull, BigConvexData* gm, const PxVec3* hullVerts) : mHullVerts(hullVerts)
{
mSVM = gm;
mHull = hull;
}
BigConvexDataBuilder::~BigConvexDataBuilder()
{
}
bool BigConvexDataBuilder::initialize()
{
mSVM->mData.mSamples = PX_ALLOCATE(PxU8, mSVM->mData.mNbSamples*2u, "mData.mSamples");
#if PX_DEBUG
// printf("SVM: %d bytes\n", mNbSamples*sizeof(PxU8)*2);
#endif
return true;
}
bool BigConvexDataBuilder::save(PxOutputStream& stream, bool platformMismatch) const
{
// Export header
if(!Cm::WriteHeader('S', 'U', 'P', 'M', gSupportVersion, platformMismatch, stream))
return false;
// Save base gaussmap
// if(!GaussMapBuilder::Save(stream, platformMismatch)) return false;
// Export header
if(!Cm::WriteHeader('G', 'A', 'U', 'S', gVersion, platformMismatch, stream))
return false;
// Export basic info
// stream.StoreDword(mSubdiv);
writeDword(mSVM->mData.mSubdiv, platformMismatch, stream); // PT: could now write Word here
// stream.StoreDword(mNbSamples);
writeDword(mSVM->mData.mNbSamples, platformMismatch, stream); // PT: could now write Word here
// Save map data
// It's an array of bytes so we don't care about 'PlatformMismatch'
stream.write(mSVM->mData.mSamples, sizeof(PxU8)*mSVM->mData.mNbSamples*2);
if(!saveValencies(stream, platformMismatch))
return false;
return true;
}
//////////////////////////////////////////////////////////////////////////
// compute valencies for each vertex
// we dont compute the edges again here, we have them temporary stored in mHullDataFacesByAllEdges8 structure
bool BigConvexDataBuilder::computeValencies(const ConvexHullBuilder& meshBuilder)
{
// Create valencies
const PxU32 numVertices = meshBuilder.mHull->mNbHullVertices;
mSVM->mData.mNbVerts = numVertices;
// Get ram for valencies and adjacent verts
const PxU32 numAlignedVerts = (numVertices+3)&~3;
const PxU32 TotalSize = sizeof(Gu::Valency)*numAlignedVerts + sizeof(PxU8)*meshBuilder.mHull->mNbEdges*2u;
mSVM->mVBuffer = PX_ALLOC(TotalSize, "BigConvexData data");
mSVM->mData.mValencies = reinterpret_cast<Gu::Valency*>(mSVM->mVBuffer);
mSVM->mData.mAdjacentVerts = (reinterpret_cast<PxU8*>(mSVM->mVBuffer)) + sizeof(Gu::Valency)*numAlignedVerts;
PxMemZero(mSVM->mData.mValencies, numVertices*sizeof(Gu::Valency));
PxU8 vertexMarker[256];
PxMemZero(vertexMarker,numVertices);
// Compute valencies
for (PxU32 i = 0; i < meshBuilder.mHull->mNbPolygons; i++)
{
const PxU32 numVerts = meshBuilder.mHullDataPolygons[i].mNbVerts;
const PxU8* Data = meshBuilder.mHullDataVertexData8 + meshBuilder.mHullDataPolygons[i].mVRef8;
for (PxU32 j = 0; j < numVerts; j++)
{
mSVM->mData.mValencies[Data[j]].mCount++;
PX_ASSERT(mSVM->mData.mValencies[Data[j]].mCount != 0xffff);
}
}
// Create offsets
mSVM->CreateOffsets();
// mNbAdjVerts = mOffsets[mNbVerts-1] + mValencies[mNbVerts-1];
mSVM->mData.mNbAdjVerts = PxU32(mSVM->mData.mValencies[mSVM->mData.mNbVerts - 1].mOffset + mSVM->mData.mValencies[mSVM->mData.mNbVerts - 1].mCount);
PX_ASSERT(mSVM->mData.mNbAdjVerts == PxU32(meshBuilder.mHull->mNbEdges * 2));
// Create adjacent vertices
// parse the polygons and its vertices
for (PxU32 i = 0; i < meshBuilder.mHull->mNbPolygons; i++)
{
PxU32 numVerts = meshBuilder.mHullDataPolygons[i].mNbVerts;
const PxU8* Data = meshBuilder.mHullDataVertexData8 + meshBuilder.mHullDataPolygons[i].mVRef8;
for (PxU32 j = 0; j < numVerts; j++)
{
const PxU8 vertexIndex = Data[j];
PxU8 numAdj = 0;
// if we did not parsed this vertex, traverse to the adjacent face and then
// again to next till we hit back the original polygon
if(vertexMarker[vertexIndex] == 0)
{
PxU8 prevIndex = Data[(j+1)%numVerts];
mSVM->mData.mAdjacentVerts[mSVM->mData.mValencies[vertexIndex].mOffset++] = prevIndex;
numAdj++;
// now traverse the neighbors
const PxU16 edgeIndex = PxU16(meshBuilder.mEdgeData16[meshBuilder.mHullDataPolygons[i].mVRef8 + j]*2);
PxU8 n0 = meshBuilder.mHullDataFacesByEdges8[edgeIndex];
PxU8 n1 = meshBuilder.mHullDataFacesByEdges8[edgeIndex + 1];
PxU32 neighborPolygon = n0 == i ? n1 : n0;
while (neighborPolygon != i)
{
PxU32 numNeighborVerts = meshBuilder.mHullDataPolygons[neighborPolygon].mNbVerts;
const PxU8* neighborData = meshBuilder.mHullDataVertexData8 + meshBuilder.mHullDataPolygons[neighborPolygon].mVRef8;
PxU32 nextEdgeIndex = 0;
// search in the neighbor face for the tested vertex
for (PxU32 k = 0; k < numNeighborVerts; k++)
{
// search the vertexIndex
if(neighborData[k] == vertexIndex)
{
const PxU8 nextIndex = neighborData[(k+1)%numNeighborVerts];
// next index already there, pick the previous
if(nextIndex == prevIndex)
{
prevIndex = k == 0 ? neighborData[numNeighborVerts - 1] : neighborData[k-1];
nextEdgeIndex = k == 0 ? numNeighborVerts - 1 : k-1;
}
else
{
prevIndex = nextIndex;
nextEdgeIndex = k;
}
mSVM->mData.mAdjacentVerts[mSVM->mData.mValencies[vertexIndex].mOffset++] = prevIndex;
numAdj++;
break;
}
}
// now move to next neighbor
const PxU16 edgeIndex2 = PxU16(meshBuilder.mEdgeData16[(meshBuilder.mHullDataPolygons[neighborPolygon].mVRef8 + nextEdgeIndex)]*2);
n0 = meshBuilder.mHullDataFacesByEdges8[edgeIndex2];
n1 = meshBuilder.mHullDataFacesByEdges8[edgeIndex2 + 1];
neighborPolygon = n0 == neighborPolygon ? n1 : n0;
}
vertexMarker[vertexIndex] = numAdj;
}
}
}
// Recreate offsets
mSVM->CreateOffsets();
return true;
}
//////////////////////////////////////////////////////////////////////////
// compute the min dot product from the verts for given dir
void BigConvexDataBuilder::precomputeSample(const PxVec3& dir, PxU8& startIndex_, float negativeDir)
{
PxU8 startIndex = startIndex_;
const PxVec3* verts = mHullVerts;
const Valency* valency = mSVM->mData.mValencies;
const PxU8* adjacentVerts = mSVM->mData.mAdjacentVerts;
// we have only 256 verts
PxU32 smallBitMap[8] = {0,0,0,0,0,0,0,0};
float minimum = negativeDir * verts[startIndex].dot(dir);
PxU32 initialIndex = startIndex;
do
{
initialIndex = startIndex;
const PxU32 numNeighbours = valency[startIndex].mCount;
const PxU32 offset = valency[startIndex].mOffset;
for (PxU32 a = 0; a < numNeighbours; ++a)
{
const PxU8 neighbourIndex = adjacentVerts[offset + a];
const float dist = negativeDir * verts[neighbourIndex].dot(dir);
if (dist < minimum)
{
const PxU32 ind = PxU32(neighbourIndex >> 5);
const PxU32 mask = PxU32(1 << (neighbourIndex & 31));
if ((smallBitMap[ind] & mask) == 0)
{
smallBitMap[ind] |= mask;
minimum = dist;
startIndex = neighbourIndex;
}
}
}
} while (startIndex != initialIndex);
startIndex_ = startIndex;
}
//////////////////////////////////////////////////////////////////////////
// Precompute the min/max vertices for cube directions.
bool BigConvexDataBuilder::precompute(PxU32 subdiv)
{
mSVM->mData.mSubdiv = PxTo16(subdiv);
mSVM->mData.mNbSamples = PxTo16(6 * subdiv*subdiv);
if (!initialize())
return false;
PxU8 startIndex[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
PxU8 startIndex2[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
const float halfSubdiv = float(subdiv - 1) * 0.5f;
for (PxU32 j = 0; j < subdiv; j++)
{
for (PxU32 i = j; i < subdiv; i++)
{
const float iSubDiv = 1.0f - i / halfSubdiv;
const float jSubDiv = 1.0f - j / halfSubdiv;
PxVec3 tempDir(1.0f, iSubDiv, jSubDiv);
// we need to normalize only once, then we permute the components
// as before for each i,j and j,i face direction
tempDir.normalize();
const PxVec3 dirs[12] = {
PxVec3(-tempDir.x, tempDir.y, tempDir.z),
PxVec3(tempDir.x, tempDir.y, tempDir.z),
PxVec3(tempDir.z, -tempDir.x, tempDir.y),
PxVec3(tempDir.z, tempDir.x, tempDir.y),
PxVec3(tempDir.y, tempDir.z, -tempDir.x),
PxVec3(tempDir.y, tempDir.z, tempDir.x),
PxVec3(-tempDir.x, tempDir.z, tempDir.y),
PxVec3(tempDir.x, tempDir.z, tempDir.y),
PxVec3(tempDir.y, -tempDir.x, tempDir.z),
PxVec3(tempDir.y, tempDir.x, tempDir.z),
PxVec3(tempDir.z, tempDir.y, -tempDir.x),
PxVec3(tempDir.z, tempDir.y, tempDir.x)
};
// compute in each direction + negative/positive dot, we have
// then two start indexes, which are used then for hill climbing
for (PxU32 dStep = 0; dStep < 12; dStep++)
{
precomputeSample(dirs[dStep], startIndex[dStep], 1.0f);
precomputeSample(dirs[dStep], startIndex2[dStep], -1.0f);
}
// decompose the vector results into face directions
for (PxU32 k = 0; k < 6; k++)
{
const PxU32 ksub = k*subdiv*subdiv;
const PxU32 offset = j + i*subdiv + ksub;
const PxU32 offset2 = i + j*subdiv + ksub;
PX_ASSERT(offset < mSVM->mData.mNbSamples);
PX_ASSERT(offset2 < mSVM->mData.mNbSamples);
mSVM->mData.mSamples[offset] = startIndex[k];
mSVM->mData.mSamples[offset + mSVM->mData.mNbSamples] = startIndex2[k];
mSVM->mData.mSamples[offset2] = startIndex[k + 6];
mSVM->mData.mSamples[offset2 + mSVM->mData.mNbSamples] = startIndex2[k + 6];
}
}
}
return true;
}
static const PxU32 gValencyVersion = 2;
//////////////////////////////////////////////////////////////////////////
bool BigConvexDataBuilder::saveValencies(PxOutputStream& stream, bool platformMismatch) const
{
// Export header
if(!Cm::WriteHeader('V', 'A', 'L', 'E', gValencyVersion, platformMismatch, stream))
return false;
writeDword(mSVM->mData.mNbVerts, platformMismatch, stream);
writeDword(mSVM->mData.mNbAdjVerts, platformMismatch, stream);
{
PxU16* temp = PX_ALLOCATE(PxU16, mSVM->mData.mNbVerts, "tmp");
for(PxU32 i=0;i<mSVM->mData.mNbVerts;i++)
temp[i] = mSVM->mData.mValencies[i].mCount;
const PxU32 maxIndex = computeMaxIndex(temp, mSVM->mData.mNbVerts);
writeDword(maxIndex, platformMismatch, stream);
Cm::StoreIndices(PxTo16(maxIndex), mSVM->mData.mNbVerts, temp, stream, platformMismatch);
PX_FREE(temp);
}
stream.write(mSVM->mData.mAdjacentVerts, mSVM->mData.mNbAdjVerts);
return true;
}

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.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#ifndef GU_COOKING_BIG_CONVEX_DATA_BUILDER_H
#define GU_COOKING_BIG_CONVEX_DATA_BUILDER_H
#include "foundation/PxMemory.h"
#include "foundation/PxVecMath.h"
namespace physx
{
class BigConvexData;
class ConvexHullBuilder;
class BigConvexDataBuilder : public PxUserAllocated
{
public:
BigConvexDataBuilder(const Gu::ConvexHullData* hull, BigConvexData* gm, const PxVec3* hullVerts);
~BigConvexDataBuilder();
// Support vertex map
bool precompute(PxU32 subdiv);
bool initialize();
bool save(PxOutputStream& stream, bool platformMismatch) const;
bool computeValencies(const ConvexHullBuilder& meshBuilder);
//~Support vertex map
// Valencies
bool saveValencies(PxOutputStream& stream, bool platformMismatch) const;
//~Valencies
protected:
PX_FORCE_INLINE void precomputeSample(const PxVec3& dir, PxU8& startIndex, float negativeDir);
private:
const Gu::ConvexHullData* mHull;
BigConvexData* mSVM;
const PxVec3* mHullVerts;
};
}
#endif // BIG_CONVEX_DATA_BUILDER_H

View File

@@ -0,0 +1,709 @@
// 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 "foundation/PxMemory.h"
#include "cooking/PxCooking.h"
#include "GuEdgeList.h"
#include "GuTriangle.h"
#include "GuConvexMesh.h"
#include "GuMeshCleaner.h"
#include "GuCookingConvexHullBuilder.h"
#include "GuCookingConvexHullLib.h"
#include "foundation/PxArray.h"
#include "foundation/PxVecMath.h"
#include "CmRadixSort.h"
// PT: TODO: refactor/revisit this, looks like it comes from an old ICE file
// 7: added mHullDataFacesByVertices8
// 8: added mEdges
// 9: removed duplicite 'C', 'V', 'H', 'L' header
static const physx::PxU32 gVersion = 9;
using namespace physx;
using namespace Gu;
using namespace Cm;
using namespace aos;
#define USE_PRECOMPUTED_HULL_PROJECTION
///////////////////////////////////////////////////////////////////////////////
PX_IMPLEMENT_OUTPUT_ERROR
///////////////////////////////////////////////////////////////////////////////
// default constructor
ConvexHullBuilder::ConvexHullBuilder(ConvexHullData* hull, const bool buildGRBData) :
mHullDataHullVertices (NULL),
mHullDataPolygons (NULL),
mHullDataVertexData8 (NULL),
mHullDataFacesByEdges8 (NULL),
mHullDataFacesByVertices8 (NULL),
mEdgeData16 (NULL),
mEdges (NULL),
mHull (hull),
mBuildGRBData (buildGRBData)
{
}
//////////////////////////////////////////////////////////////////////////
// default destructor
ConvexHullBuilder::~ConvexHullBuilder()
{
PX_FREE(mEdgeData16);
PX_FREE(mEdges);
PX_FREE(mHullDataHullVertices);
PX_FREE(mHullDataPolygons);
PX_FREE(mHullDataVertexData8);
PX_FREE(mHullDataFacesByEdges8);
PX_FREE(mHullDataFacesByVertices8);
}
//////////////////////////////////////////////////////////////////////////
// initialize the convex hull
// \param nbVerts [in] number of vertices used
// \param verts [in] vertices array
// \param indices [in] indices array
// \param nbPolygons [in] number of polygons
// \param hullPolygons [in] polygons array
// \param doValidation [in] specifies whether we should run the validation code
// \param hullLib [in] if hullLib is provided, we can reuse the hull create data, hulllib is NULL in case of user provided polygons
bool ConvexHullBuilder::init(PxU32 nbVerts, const PxVec3* verts, const PxU32* indices, const PxU32 nbIndices,
const PxU32 nbPolygons, const PxHullPolygon* hullPolygons, bool doValidation, ConvexHullLib* hullLib)
{
PX_ASSERT(indices);
PX_ASSERT(verts);
PX_ASSERT(hullPolygons);
PX_ASSERT(nbVerts);
PX_ASSERT(nbPolygons);
mHullDataHullVertices = NULL;
mHullDataPolygons = NULL;
mHullDataVertexData8 = NULL;
mHullDataFacesByEdges8 = NULL;
mHullDataFacesByVertices8 = NULL;
mEdges = NULL;
mEdgeData16 = NULL;
mHull->mNbHullVertices = PxTo8(nbVerts);
// allocate additional vec3 for V4 safe load in VolumeInteration
mHullDataHullVertices = PX_ALLOCATE(PxVec3, (mHull->mNbHullVertices + 1), "PxVec3");
PxMemCopy(mHullDataHullVertices, verts, mHull->mNbHullVertices*sizeof(PxVec3));
// Cleanup
mHull->mNbPolygons = 0;
PX_FREE(mHullDataVertexData8);
PX_FREE(mHullDataPolygons);
if(nbPolygons>255)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "ConvexHullBuilder::init: convex hull has more than 255 polygons!");
// Precompute hull polygon structures
mHull->mNbPolygons = PxTo8(nbPolygons);
mHullDataPolygons = PX_ALLOCATE(HullPolygonData, mHull->mNbPolygons, "Gu::HullPolygonData");
mHullDataVertexData8 = PX_ALLOCATE(PxU8, nbIndices, "mHullDataVertexData8");
PxU8* dest = mHullDataVertexData8;
for(PxU32 i=0;i<nbPolygons;i++)
{
const PxHullPolygon& inPolygon = hullPolygons[i];
mHullDataPolygons[i].mVRef8 = PxU16(dest - mHullDataVertexData8); // Setup link for current polygon
PxU32 numVerts = inPolygon.mNbVerts;
PX_ASSERT(numVerts>=3); // Else something very wrong happened...
mHullDataPolygons[i].mNbVerts = PxTo8(numVerts);
for (PxU32 j = 0; j < numVerts; j++)
{
dest[j] = PxTo8(indices[inPolygon.mIndexBase + j]);
}
mHullDataPolygons[i].mPlane = PxPlane(inPolygon.mPlane[0],inPolygon.mPlane[1],inPolygon.mPlane[2],inPolygon.mPlane[3]);
// Next one
dest += numVerts;
}
if(!calculateVertexMapTable(nbPolygons, (hullLib != NULL) ? false : true))
return false;
// moved create edge list here from save, copy. This is a part of the validation process and
// we need to create the edge list anyway
if(!hullLib || !hullLib->createEdgeList(nbIndices, mHullDataVertexData8, &mHullDataFacesByEdges8, &mEdgeData16, &mEdges))
{
if (!createEdgeList(doValidation, nbIndices))
return false;
}
else
{
mHull->mNbEdges = PxU16(nbIndices/2);
}
#ifdef USE_PRECOMPUTED_HULL_PROJECTION
// Loop through polygons
for (PxU32 j = 0; j < nbPolygons; j++)
{
// Precompute hull projection along local polygon normal
PxU32 NbVerts = mHull->mNbHullVertices;
const PxVec3* Verts = mHullDataHullVertices;
HullPolygonData& polygon = mHullDataPolygons[j];
PxReal min = PX_MAX_F32;
PxU8 minIndex = 0xff;
for (PxU8 i = 0; i < NbVerts; i++)
{
float dp = (*Verts++).dot(polygon.mPlane.n);
if (dp < min)
{
min = dp;
minIndex = i;
}
}
polygon.mMinIndex = minIndex;
}
#endif
if(doValidation)
return checkHullPolygons();
else
return true;
}
//////////////////////////////////////////////////////////////////////////
// hull polygons check
bool ConvexHullBuilder::checkHullPolygons() const
{
const PxVec3* hullVerts = mHullDataHullVertices;
const PxU8* vertexData = mHullDataVertexData8;
HullPolygonData* hullPolygons = mHullDataPolygons;
// Check hull validity
if(!hullVerts || !hullPolygons)
return false;
if(mHull->mNbPolygons<4)
return false;
PxVec3 max(-FLT_MAX,-FLT_MAX,-FLT_MAX);
PxVec3 hullMax = hullVerts[0];
PxVec3 hullMin = hullVerts[0];
for(PxU32 j=0;j<mHull->mNbHullVertices;j++)
{
const PxVec3& hullVert = hullVerts[j];
if(fabsf(hullVert.x) > max.x)
max.x = fabsf(hullVert.x);
if(fabsf(hullVert.y) > max.y)
max.y = fabsf(hullVert.y);
if(fabsf(hullVert.z) > max.z)
max.z = fabsf(hullVert.z);
if (hullVert.x > hullMax.x)
{
hullMax.x = hullVert.x;
}
else if (hullVert.x < hullMin.x)
{
hullMin.x = hullVert.x;
}
if (hullVert.y > hullMax.y)
{
hullMax.y = hullVert.y;
}
else if (hullVert.y < hullMin.y)
{
hullMin.y = hullVert.y;
}
if (hullVert.z > hullMax.z)
{
hullMax.z = hullVert.z;
}
else if (hullVert.z < hullMin.z)
{
hullMin.z = hullVert.z;
}
}
// compute the test epsilon the same way we construct the hull, verts are considered coplanar within this epsilon
const float planeTolerance = 0.02f;
const float testEpsilon = PxMax(planeTolerance * (PxMax(PxAbs(hullMax.x), PxAbs(hullMin.x)) +
PxMax(PxAbs(hullMax.y), PxAbs(hullMin.y)) +
PxMax(PxAbs(hullMax.z), PxAbs(hullMin.z))), planeTolerance);
max += PxVec3(testEpsilon, testEpsilon, testEpsilon);
PxVec3 testVectors[8];
bool foundPlane[8];
for (PxU32 i = 0; i < 8; i++)
{
foundPlane[i] = false;
}
testVectors[0] = PxVec3(max.x,max.y,max.z);
testVectors[1] = PxVec3(max.x,-max.y,-max.z);
testVectors[2] = PxVec3(max.x,max.y,-max.z);
testVectors[3] = PxVec3(max.x,-max.y,max.z);
testVectors[4] = PxVec3(-max.x,max.y,max.z);
testVectors[5] = PxVec3(-max.x,-max.y,max.z);
testVectors[6] = PxVec3(-max.x,max.y,-max.z);
testVectors[7] = PxVec3(-max.x,-max.y,-max.z);
// Extra convex hull validity check. This is less aggressive than previous convex decomposer!
// Loop through polygons
for(PxU32 i=0;i<mHull->mNbPolygons;i++)
{
const PxPlane& P = hullPolygons[i].mPlane;
for (PxU32 k = 0; k < 8; k++)
{
if(!foundPlane[k])
{
const float d = P.distance(testVectors[k]);
if(d >= 0)
{
foundPlane[k] = true;
}
}
}
// Test hull vertices against polygon plane
for(PxU32 j=0;j<mHull->mNbHullVertices;j++)
{
// Don't test vertex if it belongs to plane (to prevent numerical issues)
PxU32 nb = hullPolygons[i].mNbVerts;
bool discard=false;
for(PxU32 k=0;k<nb;k++)
{
if(vertexData[hullPolygons[i].mVRef8+k]==PxU8(j))
{
discard = true;
break;
}
}
if(!discard)
{
const float d = P.distance(hullVerts[j]);
// if(d>0.0001f)
//if(d>0.02f)
if(d > testEpsilon)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Gu::ConvexMesh::checkHullPolygons: Some hull vertices seems to be too far from hull planes.");
}
}
}
for (PxU32 i = 0; i < 8; i++)
{
if(!foundPlane[i])
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Gu::ConvexMesh::checkHullPolygons: Hull seems to have opened volume or do (some) faces have reversed winding?");
}
return true;
}
//////////////////////////////////////////////////////////////////////////
// hull data store
PX_COMPILE_TIME_ASSERT(sizeof(EdgeDescData)==8);
PX_COMPILE_TIME_ASSERT(sizeof(EdgeData)==8);
bool ConvexHullBuilder::save(PxOutputStream& stream, bool platformMismatch) const
{
// Export header
if(!WriteHeader('C', 'L', 'H', 'L', gVersion, platformMismatch, stream))
return false;
// Export figures
//embed grb flag into mNbEdges
PxU16 hasGRBData = PxU16(mBuildGRBData);
hasGRBData = PxU16(hasGRBData << 15);
PX_ASSERT(mHull->mNbEdges <( (1 << 15) - 1));
const PxU16 nbEdges = PxU16(mHull->mNbEdges | hasGRBData);
writeDword(mHull->mNbHullVertices, platformMismatch, stream);
writeDword(nbEdges, platformMismatch, stream);
writeDword(computeNbPolygons(), platformMismatch, stream); // Use accessor to lazy-build
PxU32 nb=0;
for(PxU32 i=0;i<mHull->mNbPolygons;i++)
nb += mHullDataPolygons[i].mNbVerts;
writeDword(nb, platformMismatch, stream);
// Export triangles
writeFloatBuffer(&mHullDataHullVertices->x, PxU32(mHull->mNbHullVertices*3), platformMismatch, stream);
// Export polygons
// TODO: allow lazy-evaluation
// We can't really store the buffer in one run anymore!
for(PxU32 i=0;i<mHull->mNbPolygons;i++)
{
HullPolygonData tmpCopy = mHullDataPolygons[i];
if(platformMismatch)
flipData(tmpCopy);
stream.write(&tmpCopy, sizeof(HullPolygonData));
}
// PT: why not storeBuffer here?
for(PxU32 i=0;i<nb;i++)
stream.write(&mHullDataVertexData8[i], sizeof(PxU8));
stream.write(mHullDataFacesByEdges8, PxU32(mHull->mNbEdges*2));
stream.write(mHullDataFacesByVertices8, PxU32(mHull->mNbHullVertices*3));
if (mBuildGRBData)
writeWordBuffer(mEdges, PxU32(mHull->mNbEdges * 2), platformMismatch, stream);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool ConvexHullBuilder::copy(ConvexHullData& hullData, PxU32& mNb)
{
// set the numbers
hullData.mNbHullVertices = mHull->mNbHullVertices;
PxU16 hasGRBData = PxU16(mBuildGRBData);
hasGRBData = PxU16(hasGRBData << 15);
PX_ASSERT(mHull->mNbEdges <((1 << 15) - 1));
hullData.mNbEdges = PxU16(mHull->mNbEdges | hasGRBData);
hullData.mNbPolygons = PxTo8(computeNbPolygons());
PxU32 nb = 0;
for (PxU32 i = 0; i < mHull->mNbPolygons; i++)
nb += mHullDataPolygons[i].mNbVerts;
mNb = nb;
PxU32 bytesNeeded = computeBufferSize(hullData, nb);
// allocate the memory first.
void* dataMemory = PX_ALLOC(bytesNeeded, "ConvexHullData data");
PxU8* address = reinterpret_cast<PxU8*>(dataMemory);
// set data pointers
hullData.mPolygons = reinterpret_cast<HullPolygonData*>(address); address += sizeof(HullPolygonData) * hullData.mNbPolygons;
PxVec3* dataHullVertices = reinterpret_cast<PxVec3*>(address); address += sizeof(PxVec3) * hullData.mNbHullVertices;
PxU8* dataFacesByEdges8 = reinterpret_cast<PxU8*>(address); address += sizeof(PxU8) * hullData.mNbEdges * 2;
PxU8* dataFacesByVertices8 = reinterpret_cast<PxU8*>(address); address += sizeof(PxU8) * hullData.mNbHullVertices * 3;
PxU16* dataEdges = reinterpret_cast<PxU16*>(address); address += hullData.mNbEdges.isBitSet() ? sizeof(PxU16) *hullData.mNbEdges * 2 : 0;
PxU8* dataVertexData8 = reinterpret_cast<PxU8*>(address); address += sizeof(PxU8) * nb; // PT: leave that one last, so that we don't need to serialize "Nb"
PX_ASSERT(!(size_t(dataHullVertices) % sizeof(PxReal)));
PX_ASSERT(!(size_t(hullData.mPolygons) % sizeof(PxReal)));
PX_ASSERT(size_t(address) <= size_t(dataMemory) + bytesNeeded);
PX_ASSERT(mHullDataHullVertices);
PX_ASSERT(mHullDataPolygons);
PX_ASSERT(mHullDataVertexData8);
PX_ASSERT(mHullDataFacesByEdges8);
PX_ASSERT(mHullDataFacesByVertices8);
// copy the data
PxMemCopy(dataHullVertices, &mHullDataHullVertices->x, PxU32(mHull->mNbHullVertices * 3)*sizeof(float));
PxMemCopy(hullData.mPolygons, mHullDataPolygons , hullData.mNbPolygons*sizeof(HullPolygonData));
PxMemCopy(dataVertexData8, mHullDataVertexData8, nb);
PxMemCopy(dataFacesByEdges8,mHullDataFacesByEdges8, PxU32(mHull->mNbEdges * 2));
if (mBuildGRBData)
PxMemCopy(dataEdges, mEdges, PxU32(mHull->mNbEdges * 2) * sizeof(PxU16));
PxMemCopy(dataFacesByVertices8, mHullDataFacesByVertices8, PxU32(mHull->mNbHullVertices * 3));
return true;
}
//////////////////////////////////////////////////////////////////////////
// calculate vertex map table
bool ConvexHullBuilder::calculateVertexMapTable(PxU32 nbPolygons, bool userPolygons)
{
mHullDataFacesByVertices8 = PX_ALLOCATE(PxU8, mHull->mNbHullVertices*3u, "mHullDataFacesByVertices8");
PxU8 vertexMarker[256];
PxMemSet(vertexMarker, 0, mHull->mNbHullVertices);
for (PxU32 i = 0; i < nbPolygons; i++)
{
const HullPolygonData& polygon = mHullDataPolygons[i];
for (PxU32 k = 0; k < polygon.mNbVerts; ++k)
{
const PxU8 index = mHullDataVertexData8[polygon.mVRef8 + k];
if (vertexMarker[index] < 3)
{
//Found a polygon
mHullDataFacesByVertices8[index*3 + vertexMarker[index]++] = PxTo8(i);
}
}
}
bool noPlaneShift = false;
for (PxU32 i = 0; i < mHull->mNbHullVertices; ++i)
{
if(vertexMarker[i] != 3)
noPlaneShift = true;
}
if (noPlaneShift)
{
//PCM will use the original shape, which means it will have a huge performance drop
if (!userPolygons)
outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "ConvexHullBuilder: convex hull does not have vertex-to-face info! Try to use different convex mesh cooking settings.");
else
outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "ConvexHullBuilder: convex hull does not have vertex-to-face info! Some of the vertices have less than 3 neighbor polygons. The vertex is most likely inside a polygon or on an edge between 2 polygons, please remove those vertices.");
for (PxU32 i = 0; i < mHull->mNbHullVertices; ++i)
{
mHullDataFacesByVertices8[i * 3 + 0] = 0xFF;
mHullDataFacesByVertices8[i * 3 + 1] = 0xFF;
mHullDataFacesByVertices8[i * 3 + 2] = 0xFF;
}
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
// create edge list
bool ConvexHullBuilder::createEdgeList(bool doValidation, PxU32 nbEdges)
{
// Code below could be greatly simplified if we assume manifold meshes!
//feodorb: ok, let's assume manifold meshes, since the code before this change
//would fail on non-maniflold meshes anyways
// We need the adjacency graph for hull polygons, similar to what we have for triangles.
// - sort the polygon edges and walk them in order
// - each edge should appear exactly twice since a convex is a manifold mesh without boundary edges
// - the polygon index is implicit when we walk the sorted list => get the 2 polygons back and update adjacency graph
//
// Two possible structures:
// - polygon to edges: needed for local search (actually: polygon to polygons)
// - edge to polygons: needed to compute edge normals on-the-fly
// Below is largely copied from the edge-list code
// Polygon to edges:
//
// We're dealing with convex polygons made of N vertices, defining N edges. For each edge we want the edge in
// an edge array.
//
// Edges to polygon:
//
// For each edge in the array, we want two polygon indices - ie an edge.
// 0) Compute the total size needed for "polygon to edges"
const PxU32 nbPolygons = mHull->mNbPolygons;
PxU32 nbEdgesUnshared = nbEdges;
// in a manifold mesh, each edge is repeated exactly twice as it shares exactly 2 faces
if (nbEdgesUnshared % 2 != 0)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: non-manifold mesh cannot be used, invalid mesh!");
// 1) Get some bytes: I need one EdgesRefs for each face, and some temp buffers
// Face indices by edge indices. First face is the one where the edge is ordered from tail to head.
PX_FREE(mHullDataFacesByEdges8);
mHullDataFacesByEdges8 = PX_ALLOCATE(PxU8, nbEdgesUnshared, "mHullDataFacesByEdges8");
PxU32* tempBuffer = PX_ALLOCATE(PxU32, nbEdgesUnshared*8, "tmp"); // Temp storage
PxU32* bufferAdd = tempBuffer;
PxU32* PX_RESTRICT vRefs0 = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* PX_RESTRICT vRefs1 = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* polyIndex = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* vertexIndex = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* polyIndex2 = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* vertexIndex2 = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* edgeIndex = tempBuffer; tempBuffer += nbEdgesUnshared;
PxU32* edgeData = tempBuffer; tempBuffer += nbEdgesUnshared;
// TODO avoroshilov: use the same "tempBuffer"
bool* flippedVRefs = PX_ALLOCATE(bool, nbEdgesUnshared, "tmp"); // Temp storage
PxU32* run0 = vRefs0;
PxU32* run1 = vRefs1;
PxU32* run2 = polyIndex;
PxU32* run3 = vertexIndex;
bool* run4 = flippedVRefs;
// 2) Create a full redundant list of edges
PxU32 edgeCounter = 0;
for(PxU32 i=0;i<nbPolygons;i++)
{
PxU32 nbVerts = mHullDataPolygons[i].mNbVerts;
const PxU8* PX_RESTRICT Data = mHullDataVertexData8 + mHullDataPolygons[i].mVRef8;
// Loop through polygon vertices
for(PxU32 j=0;j<nbVerts;j++)
{
PxU32 vRef0 = Data[j];
PxU32 vRef1 = Data[(j+1)%nbVerts];
bool flipped = vRef0>vRef1;
if (flipped)
physx::PxSwap(vRef0, vRef1);
*run0++ = vRef0;
*run1++ = vRef1;
*run2++ = i;
*run3++ = j;
*run4++ = flipped;
edgeData[edgeCounter] = edgeCounter;
edgeCounter++;
}
}
PX_ASSERT(PxU32(run0-vRefs0)==nbEdgesUnshared);
PX_ASSERT(PxU32(run1-vRefs1)==nbEdgesUnshared);
// 3) Sort the list according to both keys (VRefs0 and VRefs1)
Cm::RadixSortBuffered sorter;
const PxU32* PX_RESTRICT sorted = sorter.Sort(vRefs1, nbEdgesUnshared,Cm::RADIX_UNSIGNED).Sort(vRefs0, nbEdgesUnshared,Cm::RADIX_UNSIGNED).GetRanks();
PX_FREE(mEdges);
// Edges by their tail and head VRefs. NbEdgesUnshared == nbEdges * 2
// mEdges[edgeIdx*2 + 0] = tailVref, mEdges[edgeIdx*2 + 1] = headVref
// Tails and heads should be consistent with face refs, so that the edge is given in the order of
// his first face and opposite to the order of his second face
mEdges = PX_ALLOCATE(PxU16, nbEdgesUnshared, "mEdges");
PX_FREE(mEdgeData16);
// Face to edge mapping
mEdgeData16 = PX_ALLOCATE(PxU16, nbEdgesUnshared, "mEdgeData16");
// TODO avoroshilov: remove this comment
//mHull->mNbEdges = PxTo16(nbEdgesUnshared / 2); // #non-redundant edges
mHull->mNbEdges = 0; // #non-redundant edges
// 4) Loop through all possible edges
// - clean edges list by removing redundant edges
// - create EdgesRef list
// mNbFaces = nbFaces;
// TODO avoroshilov:
PxU32 numFacesPerEdgeVerificationCounter = 0;
PxU16* edgeVertOutput = mEdges;
PxU32 previousRef0 = PX_INVALID_U32;
PxU32 previousRef1 = PX_INVALID_U32;
PxU32 previousPolyId = PX_INVALID_U32;
PxU16 nbHullEdges = 0;
for (PxU32 i = 0; i < nbEdgesUnshared; i++)
{
const PxU32 sortedIndex = sorted[i]; // Between 0 and Nb
const PxU32 polyID = polyIndex[sortedIndex]; // Poly index
const PxU32 vertexID = vertexIndex[sortedIndex]; // Poly index
PxU32 sortedRef0 = vRefs0[sortedIndex]; // (SortedRef0, SortedRef1) is the sorted edge
PxU32 sortedRef1 = vRefs1[sortedIndex];
bool flipped = flippedVRefs[sortedIndex];
if (sortedRef0 != previousRef0 || sortedRef1 != previousRef1)
{
// TODO avoroshilov: remove this?
if (i != 0 && numFacesPerEdgeVerificationCounter != 1)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: non-manifold mesh cannot be used, invalid mesh!");
numFacesPerEdgeVerificationCounter = 0;
// ### TODO: change this in edge list as well
previousRef0 = sortedRef0;
previousRef1 = sortedRef1;
previousPolyId = polyID;
//feodorb:restore the original order of VRefs (tail and head)
if (flipped)
physx::PxSwap(sortedRef0, sortedRef1);
*edgeVertOutput++ = PxTo16(sortedRef0);
*edgeVertOutput++ = PxTo16(sortedRef1);
nbHullEdges++;
}
else
{
mHullDataFacesByEdges8[(nbHullEdges - 1) * 2] = PxTo8(previousPolyId);
mHullDataFacesByEdges8[(nbHullEdges - 1) * 2 + 1] = PxTo8(polyID);
++numFacesPerEdgeVerificationCounter;
}
mEdgeData16[mHullDataPolygons[polyID].mVRef8 + vertexID] = PxTo16(i / 2);
// Create mEdgesRef on the fly
polyIndex2[i] = polyID;
vertexIndex2[i] = vertexID;
edgeIndex[i] = PxU32(nbHullEdges - 1);
}
mHull->mNbEdges = nbHullEdges;
//////////////////////
// 2) Get some bytes: one Pair structure / edge
// create this structure only for validation purpose
// 3) Create Counters, ie compute the #faces sharing each edge
if(doValidation)
{
//
sorted = sorter.Sort(vertexIndex2, nbEdgesUnshared, Cm::RADIX_UNSIGNED).Sort(polyIndex2, nbEdgesUnshared, Cm::RADIX_UNSIGNED).GetRanks();
for (PxU32 i = 0; i < nbEdgesUnshared; i++) edgeData[i] = edgeIndex[sorted[i]];
const PxU16 nbToGo = PxU16(mHull->mNbEdges);
EdgeDescData* edgeToTriangles = PX_ALLOCATE(EdgeDescData, nbToGo, "edgeToTriangles");
PxMemZero(edgeToTriangles, sizeof(EdgeDescData)*nbToGo);
PxU32* data = edgeData;
for(PxU32 i=0;i<nbEdgesUnshared;i++) // <= maybe not the same Nb
{
edgeToTriangles[*data++].Count++;
}
// if we don't have a manifold mesh, this can fail... but the runtime would assert in any case
for (PxU32 i = 0; i < nbToGo; i++)
{
if (edgeToTriangles[i].Count != 2)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: non-manifold mesh cannot be used, invalid mesh!");
}
PX_FREE(edgeToTriangles);
}
// TODO avoroshilov: use the same "tempBuffer"
PX_FREE(flippedVRefs);
// ### free temp ram
PX_FREE(bufferAdd);
return true;
}

View File

@@ -0,0 +1,87 @@
// 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 GU_COOKING_CONVEX_HULL_BUILDER_H
#define GU_COOKING_CONVEX_HULL_BUILDER_H
#include "cooking/PxCooking.h"
#include "GuConvexMeshData.h"
#include "foundation/PxUserAllocated.h"
namespace physx
{
struct PxHullPolygon;
class ConvexHullLib;
namespace Gu
{
struct EdgeDescData;
struct ConvexHullData;
} // namespace Gu
class ConvexHullBuilder : public PxUserAllocated
{
public:
ConvexHullBuilder(Gu::ConvexHullData* hull, const bool buildGRBData);
~ConvexHullBuilder();
bool init(PxU32 nbVerts, const PxVec3* verts, const PxU32* indices, const PxU32 nbIndices, const PxU32 nbPolygons,
const PxHullPolygon* hullPolygons, bool doValidation = true, ConvexHullLib* hullLib = NULL);
bool save(PxOutputStream& stream, bool platformMismatch) const;
bool copy(Gu::ConvexHullData& hullData, PxU32& nb);
bool createEdgeList(bool doValidation, PxU32 nbEdges);
bool checkHullPolygons() const;
bool calculateVertexMapTable(PxU32 nbPolygons, bool userPolygons = false);
PX_INLINE PxU32 computeNbPolygons() const
{
PX_ASSERT(mHull->mNbPolygons);
return mHull->mNbPolygons;
}
PxVec3* mHullDataHullVertices;
Gu::HullPolygonData* mHullDataPolygons;
PxU8* mHullDataVertexData8;
PxU8* mHullDataFacesByEdges8;
PxU8* mHullDataFacesByVertices8;
PxU16* mEdgeData16; //!< Edge indices indexed by hull polygons
PxU16* mEdges; //!< Edge to vertex mapping
Gu::ConvexHullData* mHull;
bool mBuildGRBData;
};
}
#endif

View File

@@ -0,0 +1,285 @@
// 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 "GuCookingConvexHullLib.h"
#include "GuQuantizer.h"
#include "foundation/PxAllocator.h"
#include "foundation/PxBounds3.h"
#include "foundation/PxMemory.h"
using namespace physx;
using namespace Gu;
namespace local
{
//////////////////////////////////////////////////////////////////////////
// constants
static const float DISTANCE_EPSILON = 0.000001f; // close enough to consider two floating point numbers to be 'the same'.
static const float RESIZE_VALUE = 0.01f; // if the provided points AABB is very thin resize it to this size
//////////////////////////////////////////////////////////////////////////
// checks if points form a valid AABB cube, if not construct a default CUBE
static bool checkPointsAABBValidity(PxU32 numPoints, const PxVec3* points, PxU32 stride , float distanceEpsilon,
float resizeValue, PxU32& vcount, PxVec3* vertices, bool fCheck = false)
{
const char* vtx = reinterpret_cast<const char *> (points);
PxBounds3 bounds;
bounds.setEmpty();
// get the bounding box
for (PxU32 i = 0; i < numPoints; i++)
{
const PxVec3& p = *reinterpret_cast<const PxVec3 *> (vtx);
vtx += stride;
bounds.include(p);
vertices[i] = p;
}
PxVec3 dim = bounds.getDimensions();
PxVec3 center = bounds.getCenter();
// special case, the AABB is very thin or user provided us with only input 2 points
// we construct an AABB cube and return it
if ( dim.x < distanceEpsilon || dim.y < distanceEpsilon || dim.z < distanceEpsilon || numPoints < 3 )
{
float len = FLT_MAX;
// pick the shortest size bigger than the distance epsilon
if ( dim.x > distanceEpsilon && dim.x < len )
len = dim.x;
if ( dim.y > distanceEpsilon && dim.y < len )
len = dim.y;
if ( dim.z > distanceEpsilon && dim.z < len )
len = dim.z;
// if the AABB is small in all dimensions, resize it
if ( len == FLT_MAX )
{
dim = PxVec3(resizeValue);
}
// if one edge is small, set to 1/5th the shortest non-zero edge.
else
{
if ( dim.x < distanceEpsilon )
dim.x = PxMin(len * 0.05f, resizeValue);
else
dim.x *= 0.5f;
if ( dim.y < distanceEpsilon )
dim.y = PxMin(len * 0.05f, resizeValue);
else
dim.y *= 0.5f;
if ( dim.z < distanceEpsilon )
dim.z = PxMin(len * 0.05f, resizeValue);
else
dim.z *= 0.5f;
}
// construct the AABB
const PxVec3 extPos = center + dim;
const PxVec3 extNeg = center - dim;
if(fCheck)
vcount = 0;
vertices[vcount++] = extNeg;
vertices[vcount++] = PxVec3(extPos.x,extNeg.y,extNeg.z);
vertices[vcount++] = PxVec3(extPos.x,extPos.y,extNeg.z);
vertices[vcount++] = PxVec3(extNeg.x,extPos.y,extNeg.z);
vertices[vcount++] = PxVec3(extNeg.x,extNeg.y,extPos.z);
vertices[vcount++] = PxVec3(extPos.x,extNeg.y,extPos.z);
vertices[vcount++] = extPos;
vertices[vcount++] = PxVec3(extNeg.x,extPos.y,extPos.z);
return true; // return cube
}
vcount = numPoints;
return false;
}
}
//////////////////////////////////////////////////////////////////////////
// shift vertices around origin and normalize point cloud, remove duplicates!
bool ConvexHullLib::shiftAndcleanupVertices(PxU32 svcount, const PxVec3* svertices, PxU32 stride,
PxU32& vcount, PxVec3* vertices)
{
mShiftedVerts = PX_ALLOCATE(PxVec3, svcount, "PxVec3");
const char* vtx = reinterpret_cast<const char *> (svertices);
PxBounds3 bounds;
bounds.setEmpty();
// get the bounding box
for (PxU32 i = 0; i < svcount; i++)
{
const PxVec3& p = *reinterpret_cast<const PxVec3 *> (vtx);
vtx += stride;
bounds.include(p);
}
mOriginShift = bounds.getCenter();
vtx = reinterpret_cast<const char *> (svertices);
for (PxU32 i = 0; i < svcount; i++)
{
const PxVec3& p = *reinterpret_cast<const PxVec3 *> (vtx);
vtx += stride;
mShiftedVerts[i] = p - mOriginShift;
}
return cleanupVertices(svcount, mShiftedVerts, sizeof(PxVec3), vcount, vertices);
}
//////////////////////////////////////////////////////////////////////////
// Shift verts/planes in the desc back
void ConvexHullLib::shiftConvexMeshDesc(PxConvexMeshDesc& desc)
{
PX_ASSERT(mConvexMeshDesc.flags & PxConvexFlag::eSHIFT_VERTICES);
PxVec3* points = reinterpret_cast<PxVec3*>(const_cast<void*>(desc.points.data));
for(PxU32 i = 0; i < desc.points.count; i++)
{
points[i] = points[i] + mOriginShift;
}
PxHullPolygon* polygons = reinterpret_cast<PxHullPolygon*>(const_cast<void*>(desc.polygons.data));
for(PxU32 i = 0; i < desc.polygons.count; i++)
{
polygons[i].mPlane[3] -= PxVec3(polygons[i].mPlane[0], polygons[i].mPlane[1], polygons[i].mPlane[2]).dot(mOriginShift);
}
}
//////////////////////////////////////////////////////////////////////////
// normalize point cloud, remove duplicates!
bool ConvexHullLib::cleanupVertices(PxU32 svcount, const PxVec3* svertices, PxU32 stride,
PxU32& vcount, PxVec3* vertices)
{
if (svcount == 0)
return false;
const PxVec3* verticesToClean = svertices;
PxU32 numVerticesToClean = svcount;
Quantizer* quantizer = NULL;
// if quantization is enabled, parse the input vertices and produce new qantized vertices,
// that will be then cleaned the same way
if (mConvexMeshDesc.flags & PxConvexFlag::eQUANTIZE_INPUT)
{
quantizer = createQuantizer();
PxU32 vertsOutCount;
const PxVec3* vertsOut = quantizer->kmeansQuantize3D(svcount, svertices, stride,true, mConvexMeshDesc.quantizedCount, vertsOutCount);
if (vertsOut)
{
numVerticesToClean = vertsOutCount;
verticesToClean = vertsOut;
}
}
const float distanceEpsilon = local::DISTANCE_EPSILON * mCookingParams.scale.length;
const float resizeValue = local::RESIZE_VALUE * mCookingParams.scale.length;
vcount = 0;
// check for the AABB from points, if its very tiny return a resized CUBE
if (local::checkPointsAABBValidity(numVerticesToClean, verticesToClean, stride, distanceEpsilon, resizeValue, vcount, vertices, false))
{
if (quantizer)
quantizer->release();
return true;
}
if(vcount < 4)
return PxGetFoundation().error(PxErrorCode::eINTERNAL_ERROR, PX_FL, "ConvexHullLib::cleanupVertices: Less than four valid vertices were found. Provide at least four valid (e.g. each at a different position) vertices.");
if (quantizer)
quantizer->release();
return true;
}
void ConvexHullLib::swapLargestFace(PxConvexMeshDesc& desc)
{
const PxHullPolygon* polygons = reinterpret_cast<const PxHullPolygon*>(desc.polygons.data);
PxHullPolygon* polygonsOut = const_cast<PxHullPolygon*>(polygons);
PxU32 largestFace = 0;
for (PxU32 i = 1; i < desc.polygons.count; i++)
{
if(polygons[largestFace].mNbVerts < polygons[i].mNbVerts)
largestFace = i;
}
// early exit if no swap needs to be done
if(largestFace == 0)
return;
const PxU32* indices = reinterpret_cast<const PxU32*>(desc.indices.data);
mSwappedIndices = PX_ALLOCATE(PxU32, desc.indices.count, "PxU32");
PxHullPolygon replacedPolygon = polygons[0];
PxHullPolygon largestPolygon = polygons[largestFace];
polygonsOut[0] = polygons[largestFace];
polygonsOut[largestFace] = replacedPolygon;
// relocate indices
PxU16 indexBase = 0;
for (PxU32 i = 0; i < desc.polygons.count; i++)
{
if(i == 0)
{
PxMemCopy(mSwappedIndices, &indices[largestPolygon.mIndexBase],sizeof(PxU32)*largestPolygon.mNbVerts);
polygonsOut[0].mIndexBase = indexBase;
indexBase += largestPolygon.mNbVerts;
}
else
{
if(i == largestFace)
{
PxMemCopy(&mSwappedIndices[indexBase], &indices[replacedPolygon.mIndexBase], sizeof(PxU32)*replacedPolygon.mNbVerts);
polygonsOut[i].mIndexBase = indexBase;
indexBase += replacedPolygon.mNbVerts;
}
else
{
PxMemCopy(&mSwappedIndices[indexBase], &indices[polygons[i].mIndexBase], sizeof(PxU32)*polygons[i].mNbVerts);
polygonsOut[i].mIndexBase = indexBase;
indexBase += polygons[i].mNbVerts;
}
}
}
PX_ASSERT(indexBase == desc.indices.count);
desc.indices.data = mSwappedIndices;
}
ConvexHullLib::~ConvexHullLib()
{
PX_FREE(mSwappedIndices);
PX_FREE(mShiftedVerts);
}

View File

@@ -0,0 +1,92 @@
// 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 GU_COOKING_CONVEX_HULL_LIB_H
#define GU_COOKING_CONVEX_HULL_LIB_H
#include "cooking/PxConvexMeshDesc.h"
#include "cooking/PxCooking.h"
namespace physx
{
//////////////////////////////////////////////////////////////////////////
// base class for the convex hull libraries - inflation based and quickhull
class ConvexHullLib
{
PX_NOCOPY(ConvexHullLib)
public:
// functions
ConvexHullLib(const PxConvexMeshDesc& desc, const PxCookingParams& params)
: mConvexMeshDesc(desc), mCookingParams(params), mSwappedIndices(NULL),
mShiftedVerts(NULL)
{
}
virtual ~ConvexHullLib();
// computes the convex hull from provided points
virtual PxConvexMeshCookingResult::Enum createConvexHull() = 0;
// fills the PxConvexMeshDesc with computed hull data
virtual void fillConvexMeshDesc(PxConvexMeshDesc& desc) = 0;
// compute the edge list information if possible
virtual bool createEdgeList(const PxU32 nbIndices, const PxU8* indices, PxU8** hullDataFacesByEdges8, PxU16** edgeData16, PxU16** edges) = 0;
static const PxU32 gpuMaxVertsPerFace = 31;
protected:
// clean input vertices from duplicates, normalize etc.
bool cleanupVertices(PxU32 svcount, // input vertex count
const PxVec3* svertices, // vertices
PxU32 stride, // stride
PxU32& vcount, // output number of vertices
PxVec3* vertices); // location to store the results.
// shift vertices around origin and clean input vertices from duplicates, normalize etc.
bool shiftAndcleanupVertices(PxU32 svcount, // input vertex count
const PxVec3* svertices, // vertices
PxU32 stride, // stride
PxU32& vcount, // output number of vertices
PxVec3* vertices); // location to store the results.
void swapLargestFace(PxConvexMeshDesc& desc);
void shiftConvexMeshDesc(PxConvexMeshDesc& desc);
protected:
const PxConvexMeshDesc& mConvexMeshDesc;
const PxCookingParams& mCookingParams;
PxU32* mSwappedIndices;
PxVec3 mOriginShift;
PxVec3* mShiftedVerts;
};
}
#endif

View File

@@ -0,0 +1,922 @@
// 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 "foundation/PxBounds3.h"
#include "foundation/PxMathUtils.h"
#include "foundation/PxSIMDHelpers.h"
#include "GuCookingConvexHullUtils.h"
#include "GuCookingVolumeIntegration.h"
#include "foundation/PxUtilities.h"
#include "foundation/PxVecMath.h"
#include "GuBox.h"
#include "GuConvexMeshData.h"
using namespace physx;
using namespace aos;
using namespace Gu;
namespace local
{
static const float MIN_ADJACENT_ANGLE = 3.0f; // in degrees - result wont have two adjacent facets within this angle of each other.
static const float MAXDOT_MINANG = cosf(PxDegToRad(MIN_ADJACENT_ANGLE)); // adjacent angle for dot product tests
//////////////////////////////////////////////////////////////////////////
// helper class for ConvexHullCrop
class VertFlag
{
public:
PxU8 planetest;
PxU8 undermap;
PxU8 overmap;
};
//////////////////////////////////////////////////////////////////////////|
// helper class for ConvexHullCrop
class EdgeFlag
{
public:
PxI16 undermap;
};
//////////////////////////////////////////////////////////////////////////|
// helper class for ConvexHullCrop
class Coplanar
{
public:
PxU16 ea;
PxU8 v0;
PxU8 v1;
};
//////////////////////////////////////////////////////////////////////////
// plane test
enum PlaneTestResult
{
eCOPLANAR = 0,
eUNDER = 1 << 0,
eOVER = 1 << 1
};
//////////////////////////////////////////////////////////////////////////
// test where vertex lies in respect to the plane
static PlaneTestResult planeTest(const PxPlane& p, const PxVec3& v, float epsilon)
{
const float a = v.dot(p.n) + p.d;
PlaneTestResult flag = (a > epsilon) ? eOVER : ((a < -epsilon) ? eUNDER : eCOPLANAR);
return flag;
}
// computes the OBB for this set of points relative to this transform matrix. SIMD version
void computeOBBSIMD(PxU32 vcount, const Vec4V* points, Vec4V& sides, const QuatV& rot, Vec4V& trans)
{
PX_ASSERT(vcount);
Vec4V minV = V4Load(PX_MAX_F32);
Vec4V maxV = V4Load(-PX_MAX_F32);
for (PxU32 i = 0; i < vcount; i++)
{
const Vec4V& vertexV = points[i];
const Vec4V t = V4Sub(vertexV, trans);
const Vec4V v = Vec4V_From_Vec3V(QuatRotateInv(rot, Vec3V_From_Vec4V(t)));
minV = V4Min(minV, v);
maxV = V4Max(maxV, v);
}
sides = V4Sub(maxV, minV);
Mat33V tmpMat;
QuatGetMat33V(rot, tmpMat.col0, tmpMat.col1, tmpMat.col2);
const FloatV coe = FLoad(0.5f);
const Vec4V deltaVec = V4Sub(maxV, V4Scale(sides, coe));
const Vec4V t0 = V4Scale(Vec4V_From_Vec3V(tmpMat.col0), V4GetX(deltaVec));
trans = V4Add(trans, t0);
const Vec4V t1 = V4Scale(Vec4V_From_Vec3V(tmpMat.col1), V4GetY(deltaVec));
trans = V4Add(trans, t1);
const Vec4V t2 = V4Scale(Vec4V_From_Vec3V(tmpMat.col2), V4GetZ(deltaVec));
trans = V4Add(trans, t2);
}
}
//////////////////////////////////////////////////////////////////////////
// construct the base cube from given min/max
ConvexHull::ConvexHull(const PxVec3& bmin, const PxVec3& bmax, const PxArray<PxPlane>& inPlanes) : mInputPlanes(inPlanes)
{
// min max verts of the cube - 8 verts
mVertices.pushBack(PxVec3(bmin.x, bmin.y, bmin.z)); // ---
mVertices.pushBack(PxVec3(bmin.x, bmin.y, bmax.z)); // --+
mVertices.pushBack(PxVec3(bmin.x, bmax.y, bmin.z)); // -+-
mVertices.pushBack(PxVec3(bmin.x, bmax.y, bmax.z)); // -++
mVertices.pushBack(PxVec3(bmax.x, bmin.y, bmin.z)); // +--
mVertices.pushBack(PxVec3(bmax.x, bmin.y, bmax.z)); // +-+
mVertices.pushBack(PxVec3(bmax.x, bmax.y, bmin.z)); // ++-
mVertices.pushBack(PxVec3(bmax.x, bmax.y, bmax.z)); // +++
// cube planes - 6 planes
mFacets.pushBack(PxPlane(PxVec3(-1.f, 0, 0), bmin.x)); // 0,1,3,2
mFacets.pushBack(PxPlane(PxVec3(1.f, 0, 0), -bmax.x)); // 6,7,5,4
mFacets.pushBack(PxPlane(PxVec3(0, -1.f, 0), bmin.y)); // 0,4,5,1
mFacets.pushBack(PxPlane(PxVec3(0, 1.f, 0), -bmax.y)); // 3,7,6,2
mFacets.pushBack(PxPlane(PxVec3(0, 0, -1.f), bmin.z)); // 0,2,6,4
mFacets.pushBack(PxPlane(PxVec3(0, 0, 1.f), -bmax.z)); // 1,5,7,3
// cube edges - 24 edges
mEdges.pushBack(HalfEdge(11, 0, 0));
mEdges.pushBack(HalfEdge(23, 1, 0));
mEdges.pushBack(HalfEdge(15, 3, 0));
mEdges.pushBack(HalfEdge(16, 2, 0));
mEdges.pushBack(HalfEdge(13, 6, 1));
mEdges.pushBack(HalfEdge(21, 7, 1));
mEdges.pushBack(HalfEdge(9, 5, 1));
mEdges.pushBack(HalfEdge(18, 4, 1));
mEdges.pushBack(HalfEdge(19, 0, 2));
mEdges.pushBack(HalfEdge(6, 4, 2));
mEdges.pushBack(HalfEdge(20, 5, 2));
mEdges.pushBack(HalfEdge(0, 1, 2));
mEdges.pushBack(HalfEdge(22, 3, 3));
mEdges.pushBack(HalfEdge(4, 7, 3));
mEdges.pushBack(HalfEdge(17, 6, 3));
mEdges.pushBack(HalfEdge(2, 2, 3));
mEdges.pushBack(HalfEdge(3, 0, 4));
mEdges.pushBack(HalfEdge(14, 2, 4));
mEdges.pushBack(HalfEdge(7, 6, 4));
mEdges.pushBack(HalfEdge(8, 4, 4));
mEdges.pushBack(HalfEdge(10, 1, 5));
mEdges.pushBack(HalfEdge(5, 5, 5));
mEdges.pushBack(HalfEdge(12, 7, 5));
mEdges.pushBack(HalfEdge(1, 3, 5));
}
//////////////////////////////////////////////////////////////////////////
// create the initial convex hull from given OBB
ConvexHull::ConvexHull(const PxVec3& extent, const PxTransform& transform, const PxArray<PxPlane>& inPlanes) : mInputPlanes(inPlanes)
{
// get the OBB corner points
PxVec3 extentPoints[8];
const PxMat33Padded rot(transform.q);
Gu::computeOBBPoints(extentPoints, transform.p, extent, rot.column0, rot.column1, rot.column2);
mVertices.pushBack(PxVec3(extentPoints[0].x, extentPoints[0].y, extentPoints[0].z)); // ---
mVertices.pushBack(PxVec3(extentPoints[4].x, extentPoints[4].y, extentPoints[4].z)); // --+
mVertices.pushBack(PxVec3(extentPoints[3].x, extentPoints[3].y, extentPoints[3].z)); // -+-
mVertices.pushBack(PxVec3(extentPoints[7].x, extentPoints[7].y, extentPoints[7].z)); // -++
mVertices.pushBack(PxVec3(extentPoints[1].x, extentPoints[1].y, extentPoints[1].z)); // +--
mVertices.pushBack(PxVec3(extentPoints[5].x, extentPoints[5].y, extentPoints[5].z)); // +-+
mVertices.pushBack(PxVec3(extentPoints[2].x, extentPoints[2].y, extentPoints[2].z)); // ++-
mVertices.pushBack(PxVec3(extentPoints[6].x, extentPoints[6].y, extentPoints[6].z)); // +++
// cube planes - 6 planes
const PxPlane plane0(extentPoints[0], extentPoints[4], extentPoints[7]); // 0,1,3,2
mFacets.pushBack(PxPlane(plane0.n, plane0.d));
const PxPlane plane1(extentPoints[2], extentPoints[6], extentPoints[5]); // 6,7,5,4
mFacets.pushBack(PxPlane(plane1.n, plane1.d));
const PxPlane plane2(extentPoints[0], extentPoints[1], extentPoints[5]); // 0,4,5,1
mFacets.pushBack(PxPlane(plane2.n, plane2.d));
const PxPlane plane3(extentPoints[7], extentPoints[6], extentPoints[2]); // 3,7,6,2
mFacets.pushBack(PxPlane(plane3.n, plane3.d));
const PxPlane plane4(extentPoints[0], extentPoints[3], extentPoints[2]); // 0,2,6,4
mFacets.pushBack(PxPlane(plane4.n, plane4.d));
const PxPlane plane5(extentPoints[4], extentPoints[5], extentPoints[6]); // 1,5,7,3
mFacets.pushBack(PxPlane(plane5.n, plane5.d));
// cube edges - 24 edges
mEdges.pushBack(HalfEdge(11, 0, 0));
mEdges.pushBack(HalfEdge(23, 1, 0));
mEdges.pushBack(HalfEdge(15, 3, 0));
mEdges.pushBack(HalfEdge(16, 2, 0));
mEdges.pushBack(HalfEdge(13, 6, 1));
mEdges.pushBack(HalfEdge(21, 7, 1));
mEdges.pushBack(HalfEdge(9, 5, 1));
mEdges.pushBack(HalfEdge(18, 4, 1));
mEdges.pushBack(HalfEdge(19, 0, 2));
mEdges.pushBack(HalfEdge(6, 4, 2));
mEdges.pushBack(HalfEdge(20, 5, 2));
mEdges.pushBack(HalfEdge(0, 1, 2));
mEdges.pushBack(HalfEdge(22, 3, 3));
mEdges.pushBack(HalfEdge(4, 7, 3));
mEdges.pushBack(HalfEdge(17, 6, 3));
mEdges.pushBack(HalfEdge(2, 2, 3));
mEdges.pushBack(HalfEdge(3, 0, 4));
mEdges.pushBack(HalfEdge(14, 2, 4));
mEdges.pushBack(HalfEdge(7, 6, 4));
mEdges.pushBack(HalfEdge(8, 4, 4));
mEdges.pushBack(HalfEdge(10, 1, 5));
mEdges.pushBack(HalfEdge(5, 5, 5));
mEdges.pushBack(HalfEdge(12, 7, 5));
mEdges.pushBack(HalfEdge(1, 3, 5));
}
//////////////////////////////////////////////////////////////////////////
// finds the candidate plane, returns -1 otherwise
PxI32 ConvexHull::findCandidatePlane(float planeTestEpsilon, float epsilon) const
{
PxI32 p = -1;
float md = 0.0f;
PxU32 i, j;
for (i = 0; i < mInputPlanes.size(); i++)
{
float d = 0.0f;
float dmax = 0.0f;
float dmin = 0.0f;
for (j = 0; j < mVertices.size(); j++)
{
dmax = PxMax(dmax, mVertices[j].dot(mInputPlanes[i].n) + mInputPlanes[i].d);
dmin = PxMin(dmin, mVertices[j].dot(mInputPlanes[i].n) + mInputPlanes[i].d);
}
float dr = dmax - dmin;
if (dr < planeTestEpsilon)
dr = 1.0f; // shouldn't happen.
d = dmax / dr;
// we have a better candidate try another one
if (d <= md)
continue;
// check if we dont have already that plane or if the normals are nearly the same
for (j = 0; j<mFacets.size(); j++)
{
if (mInputPlanes[i] == mFacets[j])
{
d = 0.0f;
continue;
}
if (mInputPlanes[i].n.dot(mFacets[j].n)> local::MAXDOT_MINANG)
{
for (PxU32 k = 0; k < mEdges.size(); k++)
{
if (mEdges[k].p != j)
continue;
if (mVertices[mEdges[k].v].dot(mInputPlanes[i].n) + mInputPlanes[i].d < 0)
{
d = 0; // so this plane wont get selected.
break;
}
}
}
}
if (d>md)
{
p = PxI32(i);
md = d;
}
}
return (md > epsilon) ? p : -1;
}
//////////////////////////////////////////////////////////////////////////
// internal hull check
bool ConvexHull::assertIntact(float epsilon) const
{
PxU32 i;
PxU32 estart = 0;
for (i = 0; i < mEdges.size(); i++)
{
if (mEdges[estart].p != mEdges[i].p)
{
estart = i;
}
PxU32 inext = i + 1;
if (inext >= mEdges.size() || mEdges[inext].p != mEdges[i].p)
{
inext = estart;
}
PX_ASSERT(mEdges[inext].p == mEdges[i].p);
PxI16 nb = mEdges[i].ea;
if (nb == 255 || nb == -1)
return false;
PX_ASSERT(nb != -1);
PX_ASSERT(i == PxU32(mEdges[PxU32(nb)].ea));
// Check that the vertex of the next edge is the vertex of the adjacent half edge.
// Otherwise the two half edges are not really adjacent and we have a hole.
PX_ASSERT(mEdges[PxU32(nb)].v == mEdges[inext].v);
if (!(mEdges[PxU32(nb)].v == mEdges[inext].v))
return false;
}
for (i = 0; i < mEdges.size(); i++)
{
PX_ASSERT(local::eCOPLANAR == local::planeTest(mFacets[mEdges[i].p], mVertices[mEdges[i].v], epsilon));
if (local::eCOPLANAR != local::planeTest(mFacets[mEdges[i].p], mVertices[mEdges[i].v], epsilon))
return false;
if (mEdges[estart].p != mEdges[i].p)
{
estart = i;
}
PxU32 i1 = i + 1;
if (i1 >= mEdges.size() || mEdges[i1].p != mEdges[i].p) {
i1 = estart;
}
PxU32 i2 = i1 + 1;
if (i2 >= mEdges.size() || mEdges[i2].p != mEdges[i].p) {
i2 = estart;
}
if (i == i2)
continue; // i sliced tangent to an edge and created 2 meaningless edges
// check the face normal against the triangle from edges
PxVec3 localNormal = (mVertices[mEdges[i1].v] - mVertices[mEdges[i].v]).cross(mVertices[mEdges[i2].v] - mVertices[mEdges[i1].v]);
const float m = localNormal.magnitude();
if (m == 0.0f)
localNormal = PxVec3(1.f, 0.0f, 0.0f);
localNormal *= (1.0f / m);
if (localNormal.dot(mFacets[mEdges[i].p].n) <= 0.0f)
return false;
}
return true;
}
// returns the maximum number of vertices on a face
PxU32 ConvexHull::maxNumVertsPerFace() const
{
PxU32 maxVerts = 0;
PxU32 currentVerts = 0;
PxU32 estart = 0;
for (PxU32 i = 0; i < mEdges.size(); i++)
{
if (mEdges[estart].p != mEdges[i].p)
{
if(currentVerts > maxVerts)
{
maxVerts = currentVerts + 1;
}
currentVerts = 0;
estart = i;
}
else
{
currentVerts++;
}
}
return maxVerts;
}
//////////////////////////////////////////////////////////////////////////
// slice the input convexHull with the slice plane
ConvexHull* physx::convexHullCrop(const ConvexHull& convex, const PxPlane& slice, float planeTestEpsilon)
{
static const PxU8 invalidIndex = PxU8(-1);
PxU32 i;
PxU32 vertCountUnder = 0; // Running count of the vertices UNDER the slicing plane.
PX_ASSERT(convex.getEdges().size() < 480);
// Arrays of mapping information associated with features in the input convex.
// edgeflag[i].undermap - output index of input edge convex->edges[i]
// vertflag[i].undermap - output index of input vertex convex->vertices[i]
// vertflag[i].planetest - the side-of-plane classification of convex->vertices[i]
// (There are other members but they are unused.)
local::EdgeFlag edgeFlag[512];
local::VertFlag vertFlag[256];
// Lists of output features. Populated during clipping.
// Coplanar edges have one sibling in tmpunderedges and one in coplanaredges.
// coplanaredges holds the sibling that belong to the new polygon created from slicing.
ConvexHull::HalfEdge tmpUnderEdges[512]; // The output edge list.
PxPlane tmpUnderPlanes[128]; // The output plane list.
local::Coplanar coplanarEdges[512]; // The coplanar edge list.
PxU32 coplanarEdgesNum = 0; // Running count of coplanar edges.
// Created vertices on the slicing plane (stored for output after clipping).
PxArray<PxVec3> createdVerts;
// Logical OR of individual vertex flags.
PxU32 convexClipFlags = 0;
// Classify each vertex against the slicing plane as OVER | COPLANAR | UNDER.
// OVER - Vertex is over (outside) the slicing plane. Will not be output.
// COPLANAR - Vertex is on the slicing plane. A copy will be output.
// UNDER - Vertex is under (inside) the slicing plane. Will be output.
// We keep an array of information structures for each vertex in the input convex.
// vertflag[i].undermap - The (computed) index of convex->vertices[i] in the output.
// invalidIndex for OVER vertices - they are not output.
// initially invalidIndex for COPLANAR vertices - set later.
// vertflag[i].overmap - Unused - we don't care about the over part.
// vertflag[i].planetest - The classification (clip flag) of convex->vertices[i].
for (i = 0; i < convex.getVertices().size(); i++)
{
local::PlaneTestResult vertexClipFlag = local::planeTest(slice, convex.getVertices()[i], planeTestEpsilon);
switch (vertexClipFlag)
{
case local::eOVER:
case local::eCOPLANAR:
vertFlag[i].undermap = invalidIndex; // Initially invalid for COPLANAR
vertFlag[i].overmap = invalidIndex;
break;
case local::eUNDER:
vertFlag[i].undermap = PxTo8(vertCountUnder++);
vertFlag[i].overmap = invalidIndex;
break;
}
vertFlag[i].planetest = PxU8(vertexClipFlag);
convexClipFlags |= vertexClipFlag;
}
// Check special case: everything UNDER or COPLANAR.
// This way we know we wont end up with silly faces / edges later on.
if ((convexClipFlags & local::eOVER) == 0)
{
// Just return a copy of the same convex.
ConvexHull* dst = PX_NEW(ConvexHull)(convex);
return dst;
}
PxU16 underEdgeCount = 0; // Running count of output edges.
PxU16 underPlanesCount = 0; // Running count of output planes.
// Clipping Loop
// =============
//
// for each plane
//
// for each edge
//
// if first UNDER & second !UNDER
// output current edge -> tmpunderedges
// if we have done the sibling
// connect current edge to its sibling
// set vout = first vertex of sibling
// else if second is COPLANAR
// if we havent already copied it
// copy second -> createdverts
// set vout = index of created vertex
// else
// generate a new vertex -> createdverts
// set vout = index of created vertex
// if vin is already set and vin != vout (non-trivial edge)
// output coplanar edge -> tmpunderedges (one sibling)
// set coplanaredge to new edge index (for connecting the other sibling)
//
// else if first !UNDER & second UNDER
// if we have done the sibling
// connect current edge to its sibling
// set vin = second vertex of sibling (this is a bit of a pain)
// else if first is COPLANAR
// if we havent already copied it
// copy first -> createdverts
// set vin = index of created vertex
// else
// generate a new vertex -> createdverts
// set vin = index of created vertex
// if vout is already set and vin != vout (non-trivial edge)
// output coplanar edge -> tmpunderedges (one sibling)
// set coplanaredge to new edge index (for connecting the other sibling)
// output current edge -> tmpunderedges
//
// else if first UNDER & second UNDER
// output current edge -> tmpunderedges
//
// next edge
//
// if part of current plane was UNDER
// output current plane -> tmpunderplanes
//
// if coplanaredge is set
// output coplanar edge -> coplanaredges
//
// next plane
//
// Indexing is a bit tricky here:
//
// e0 - index of the current edge
// e1 - index of the next edge
// estart - index of the first edge in the current plane
// currentplane - index of the current plane
// enextface - first edge of next plane
PxU32 e0 = 0;
for (PxU32 currentplane = 0; currentplane < convex.getFacets().size(); currentplane++)
{
PxU32 eStart = e0;
PxU32 eNextFace = 0xffffffff;
PxU32 e1 = e0 + 1;
PxU8 vout = invalidIndex;
PxU8 vin = invalidIndex;
PxU32 coplanarEdge = invalidIndex;
// Logical OR of individual vertex flags in the current plane.
PxU32 planeSide = 0;
do{
// Next edge modulo logic
if (e1 >= convex.getEdges().size() || convex.getEdges()[e1].p != currentplane)
{
eNextFace = e1;
e1 = eStart;
}
const ConvexHull::HalfEdge& edge0 = convex.getEdges()[e0];
const ConvexHull::HalfEdge& edge1 = convex.getEdges()[e1];
const ConvexHull::HalfEdge& edgea = convex.getEdges()[PxU32(edge0.ea)];
planeSide |= vertFlag[edge0.v].planetest;
if (vertFlag[edge0.v].planetest == local::eUNDER && vertFlag[edge1.v].planetest != local::eUNDER)
{
// first is UNDER, second is COPLANAR or OVER
// Output current edge.
edgeFlag[e0].undermap = short(underEdgeCount);
tmpUnderEdges[underEdgeCount].v = vertFlag[edge0.v].undermap;
tmpUnderEdges[underEdgeCount].p = PxU8(underPlanesCount);
PX_ASSERT(tmpUnderEdges[underEdgeCount].v != invalidIndex);
if (PxU32(edge0.ea) < e0)
{
// We have already done the sibling.
// Connect current edge to its sibling.
PX_ASSERT(edgeFlag[edge0.ea].undermap != invalidIndex);
tmpUnderEdges[underEdgeCount].ea = edgeFlag[edge0.ea].undermap;
tmpUnderEdges[edgeFlag[edge0.ea].undermap].ea = short(underEdgeCount);
// Set vout = first vertex of (output, clipped) sibling.
vout = tmpUnderEdges[edgeFlag[edge0.ea].undermap].v;
}
else if (vertFlag[edge1.v].planetest == local::eCOPLANAR)
{
// Boundary case.
// We output coplanar vertices once.
if (vertFlag[edge1.v].undermap == invalidIndex)
{
createdVerts.pushBack(convex.getVertices()[edge1.v]);
// Remember the index so we don't output it again.
vertFlag[edge1.v].undermap = PxTo8(vertCountUnder++);
}
vout = vertFlag[edge1.v].undermap;
}
else
{
// Add new vertex.
const PxPlane& p0 = convex.getFacets()[edge0.p];
const PxPlane& pa = convex.getFacets()[edgea.p];
createdVerts.pushBack(threePlaneIntersection(p0, pa, slice));
vout = PxTo8(vertCountUnder++);
}
// We added an edge, increment the counter
underEdgeCount++;
if (vin != invalidIndex && vin != vout)
{
// We already have vin and a non-trivial edge
// Output coplanar edge
PX_ASSERT(vout != invalidIndex);
coplanarEdge = underEdgeCount;
tmpUnderEdges[underEdgeCount].v = vout;
tmpUnderEdges[underEdgeCount].p = PxU8(underPlanesCount);
tmpUnderEdges[underEdgeCount].ea = invalidIndex;
underEdgeCount++;
}
}
else if (vertFlag[edge0.v].planetest != local::eUNDER && vertFlag[edge1.v].planetest == local::eUNDER)
{
// First is OVER or COPLANAR, second is UNDER.
if (PxU32(edge0.ea) < e0)
{
// We have already done the sibling.
// We need the second vertex of the sibling.
// Which is the vertex of the next edge in the adjacent poly.
int nea = edgeFlag[edge0.ea].undermap + 1;
int p = tmpUnderEdges[edgeFlag[edge0.ea].undermap].p;
if (nea >= underEdgeCount || tmpUnderEdges[nea].p != p)
{
// End of polygon, next edge is first edge
nea -= 2;
while (nea > 0 && tmpUnderEdges[nea - 1].p == p)
nea--;
}
vin = tmpUnderEdges[nea].v;
PX_ASSERT(vin < vertCountUnder);
}
else if (vertFlag[edge0.v].planetest == local::eCOPLANAR)
{
// Boundary case.
// We output coplanar vertices once.
if (vertFlag[edge0.v].undermap == invalidIndex)
{
createdVerts.pushBack(convex.getVertices()[edge0.v]);
// Remember the index so we don't output it again.
vertFlag[edge0.v].undermap = PxTo8(vertCountUnder++);
}
vin = vertFlag[edge0.v].undermap;
}
else
{
// Add new vertex.
const PxPlane& p0 = convex.getFacets()[edge0.p];
const PxPlane& pa = convex.getFacets()[edgea.p];
createdVerts.pushBack(threePlaneIntersection(p0, pa, slice));
vin = PxTo8(vertCountUnder++);
}
if (vout != invalidIndex && vin != vout)
{
// We have been in and out, Add the coplanar edge
coplanarEdge = underEdgeCount;
tmpUnderEdges[underEdgeCount].v = vout;
tmpUnderEdges[underEdgeCount].p = PxTo8(underPlanesCount);
tmpUnderEdges[underEdgeCount].ea = invalidIndex;
underEdgeCount++;
}
// Output current edge.
tmpUnderEdges[underEdgeCount].v = vin;
tmpUnderEdges[underEdgeCount].p = PxTo8(underPlanesCount);
edgeFlag[e0].undermap = short(underEdgeCount);
if (PxU32(edge0.ea) < e0)
{
// We have already done the sibling.
// Connect current edge to its sibling.
PX_ASSERT(edgeFlag[edge0.ea].undermap != invalidIndex);
tmpUnderEdges[underEdgeCount].ea = edgeFlag[edge0.ea].undermap;
tmpUnderEdges[edgeFlag[edge0.ea].undermap].ea = short(underEdgeCount);
}
PX_ASSERT(edgeFlag[e0].undermap == underEdgeCount);
underEdgeCount++;
}
else if (vertFlag[edge0.v].planetest == local::eUNDER && vertFlag[edge1.v].planetest == local::eUNDER)
{
// Both UNDER
// Output current edge.
edgeFlag[e0].undermap = short(underEdgeCount);
tmpUnderEdges[underEdgeCount].v = vertFlag[edge0.v].undermap;
tmpUnderEdges[underEdgeCount].p = PxTo8(underPlanesCount);
if (PxU32(edge0.ea) < e0)
{
// We have already done the sibling.
// Connect current edge to its sibling.
PX_ASSERT(edgeFlag[edge0.ea].undermap != invalidIndex);
tmpUnderEdges[underEdgeCount].ea = edgeFlag[edge0.ea].undermap;
tmpUnderEdges[edgeFlag[edge0.ea].undermap].ea = short(underEdgeCount);
}
underEdgeCount++;
}
e0 = e1;
e1++; // do the modulo at the beginning of the loop
} while (e0 != eStart);
e0 = eNextFace;
if (planeSide & local::eUNDER)
{
// At least part of current plane is UNDER.
// Output current plane.
tmpUnderPlanes[underPlanesCount] = convex.getFacets()[currentplane];
underPlanesCount++;
}
if (coplanarEdge != invalidIndex)
{
// We have a coplanar edge.
// Add to coplanaredges for later processing.
// (One sibling is in place but one is missing)
PX_ASSERT(vin != invalidIndex);
PX_ASSERT(vout != invalidIndex);
PX_ASSERT(coplanarEdge != 511);
coplanarEdges[coplanarEdgesNum].ea = PxU8(coplanarEdge);
coplanarEdges[coplanarEdgesNum].v0 = vin;
coplanarEdges[coplanarEdgesNum].v1 = vout;
coplanarEdgesNum++;
}
// Reset coplanar edge infos for next poly
vin = invalidIndex;
vout = invalidIndex;
coplanarEdge = invalidIndex;
}
// Add the new plane to the mix:
if (coplanarEdgesNum > 0)
{
tmpUnderPlanes[underPlanesCount++] = slice;
}
// Sort the coplanar edges in winding order.
for (i = 0; i < coplanarEdgesNum - 1; i++)
{
if (coplanarEdges[i].v1 != coplanarEdges[i + 1].v0)
{
PxU32 j = 0;
for (j = i + 2; j < coplanarEdgesNum; j++)
{
if (coplanarEdges[i].v1 == coplanarEdges[j].v0)
{
local::Coplanar tmp = coplanarEdges[i + 1];
coplanarEdges[i + 1] = coplanarEdges[j];
coplanarEdges[j] = tmp;
break;
}
}
if (j >= coplanarEdgesNum)
{
// PX_ASSERT(j<coplanaredges_num);
return NULL;
}
}
}
// PT: added this line to fix DE2904
if (!vertCountUnder)
return NULL;
// Create the output convex.
ConvexHull* punder = PX_NEW(ConvexHull)(convex.getInputPlanes());
ConvexHull& under = *punder;
// Copy UNDER vertices
PxU32 k = 0;
for (i = 0; i < convex.getVertices().size(); i++)
{
if (vertFlag[i].planetest == local::eUNDER)
{
under.getVertices().pushBack(convex.getVertices()[i]);
k++;
}
}
// Copy created vertices
i = 0;
while (k < vertCountUnder)
{
under.getVertices().pushBack(createdVerts[i++]);
k++;
}
PX_ASSERT(i == createdVerts.size());
// Copy the output edges and output planes.
under.getEdges().resize(underEdgeCount + coplanarEdgesNum);
under.getFacets().resize(underPlanesCount);
// Add the coplanar edge siblings that belong to the new polygon (coplanaredges).
for (i = 0; i < coplanarEdgesNum; i++)
{
under.getEdges()[underEdgeCount + i].p = PxU8(underPlanesCount - 1);
under.getEdges()[underEdgeCount + i].ea = short(coplanarEdges[i].ea);
tmpUnderEdges[coplanarEdges[i].ea].ea = PxI16(underEdgeCount + i);
under.getEdges()[underEdgeCount + i].v = coplanarEdges[i].v0;
}
PxMemCopy(under.getEdges().begin(), tmpUnderEdges, sizeof(ConvexHull::HalfEdge)*underEdgeCount);
PxMemCopy(under.getFacets().begin(), tmpUnderPlanes, sizeof(PxPlane)*underPlanesCount);
return punder;
}
bool physx::computeOBBFromConvex(const PxConvexMeshDesc& desc, PxVec3& sides, PxTransform& matrix)
{
PxIntegrals integrals;
// using the centroid of the convex for the volume integration solved accuracy issues in cases where the inertia tensor
// ended up close to not being positive definite and after a few further transforms the diagonalized inertia tensor ended
// up with negative values.
const PxVec3* verts = (reinterpret_cast<const PxVec3*>(desc.points.data));
const PxU32* ind = (reinterpret_cast<const PxU32*>(desc.indices.data));
const PxHullPolygon* polygons = (reinterpret_cast<const PxHullPolygon*>(desc.polygons.data));
PxVec3 mean(0.0f);
for (PxU32 i = 0; i < desc.points.count; i++)
mean += verts[i];
mean *= (1.0f / desc.points.count);
PxU8* indices = PX_ALLOCATE(PxU8, desc.indices.count, "PxU8");
for (PxU32 i = 0; i < desc.indices.count; i++)
{
indices[i] = PxTo8(ind[i]);
}
// we need to move the polygon data to internal format
Gu::HullPolygonData* polygonData = PX_ALLOCATE(Gu::HullPolygonData, desc.polygons.count, "Gu::HullPolygonData");
for (PxU32 i = 0; i < desc.polygons.count; i++)
{
polygonData[i].mPlane = PxPlane(polygons[i].mPlane[0], polygons[i].mPlane[1], polygons[i].mPlane[2], polygons[i].mPlane[3]);
polygonData[i].mNbVerts = PxTo8(polygons[i].mNbVerts);
polygonData[i].mVRef8 = polygons[i].mIndexBase;
}
PxConvexMeshDesc inDesc;
inDesc.points.data = desc.points.data;
inDesc.points.count = desc.points.count;
inDesc.polygons.data = polygonData;
inDesc.polygons.count = desc.polygons.count;
inDesc.indices.data = indices;
inDesc.indices.count = desc.indices.count;
// compute volume integrals to get basis axis
if(computeVolumeIntegralsEberly(inDesc, 1.0f, integrals, mean, desc.flags & PxConvexFlag::eFAST_INERTIA_COMPUTATION))
{
Vec4V* pointsV = PX_ALLOCATE(Vec4V, desc.points.count, "Vec4V");
for (PxU32 i = 0; i < desc.points.count; i++)
{
// safe to V4 load, same as volume integration - we allocate one more vector
pointsV[i] = V4LoadU(&verts[i].x);
}
PxMat33 inertia;
integrals.getOriginInertia(inertia);
PxQuat inertiaQuat;
PxDiagonalize(inertia, inertiaQuat);
const PxMat33Padded baseAxis(inertiaQuat);
Vec4V center = V4LoadU(&integrals.COM.x);
const PxU32 numSteps = 20;
const float subStep = PxDegToRad(float(360/numSteps));
float bestVolume = FLT_MAX;
for (PxU32 axis = 0; axis < 3; axis++)
{
for (PxU32 iStep = 0; iStep < numSteps; iStep++)
{
PxQuat quat(iStep*subStep, baseAxis[axis]);
Vec4V transV = center;
Vec4V psidesV;
const QuatV rotV = QuatVLoadU(&quat.x);
local::computeOBBSIMD(desc.points.count, pointsV, psidesV, rotV, transV);
PxVec3 psides;
V3StoreU(Vec3V_From_Vec4V(psidesV), psides);
const float volume = psides[0] * psides[1] * psides[2]; // the volume of the cube
if (volume <= bestVolume)
{
bestVolume = volume;
sides = psides;
V4StoreU(rotV, &matrix.q.x);
V3StoreU(Vec3V_From_Vec4V(transV), matrix.p);
}
}
}
PX_FREE(pointsV);
}
else
{
PX_FREE(indices);
PX_FREE(polygonData);
return false;
}
PX_FREE(indices);
PX_FREE(polygonData);
return true;
}

View File

@@ -0,0 +1,171 @@
// 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 GU_COOKING_CONVEX_HULL_UTILS_H
#define GU_COOKING_CONVEX_HULL_UTILS_H
#include "foundation/PxMemory.h"
#include "foundation/PxPlane.h"
#include "cooking/PxConvexMeshDesc.h"
#include "foundation/PxUserAllocated.h"
#include "foundation/PxArray.h"
namespace physx
{
//////////////////////////////////////////////////////////////////////////
// helper class for hull construction, holds the vertices and planes together
// while cropping the hull with planes
class ConvexHull : public PxUserAllocated
{
public:
// Helper class for halfedge representation
class HalfEdge
{
public:
PxI16 ea; // the other half of the edge (index into edges list)
PxU8 v; // the vertex at the start of this edge (index into vertices list)
PxU8 p; // the facet on which this edge lies (index into facets list)
HalfEdge(){}
HalfEdge(PxI16 _ea, PxU8 _v, PxU8 _p) :ea(_ea), v(_v), p(_p){}
};
ConvexHull& operator = (const ConvexHull&);
// construct the base cube hull from given max/min AABB
ConvexHull(const PxVec3& bmin, const PxVec3& bmax, const PxArray<PxPlane>& inPlanes);
// construct the base cube hull from given OBB
ConvexHull(const PxVec3& extent, const PxTransform& transform, const PxArray<PxPlane>& inPlanes);
// copy constructor
ConvexHull(const ConvexHull& srcHull)
: mInputPlanes(srcHull.getInputPlanes())
{
copyHull(srcHull);
}
// construct plain hull
ConvexHull(const PxArray<PxPlane>& inPlanes)
: mInputPlanes(inPlanes)
{
}
// finds the candidate plane, returns -1 otherwise
PxI32 findCandidatePlane(float planetestepsilon, float epsilon) const;
// internal check of the hull integrity
bool assertIntact(float epsilon) const;
// return vertices
const PxArray<PxVec3>& getVertices() const
{
return mVertices;
}
// return edges
const PxArray<HalfEdge>& getEdges() const
{
return mEdges;
}
// return faces
const PxArray<PxPlane>& getFacets() const
{
return mFacets;
}
// return input planes
const PxArray<PxPlane>& getInputPlanes() const
{
return mInputPlanes;
}
// return vertices
PxArray<PxVec3>& getVertices()
{
return mVertices;
}
// return edges
PxArray<HalfEdge>& getEdges()
{
return mEdges;
}
// return faces
PxArray<PxPlane>& getFacets()
{
return mFacets;
}
// returns the maximum number of vertices on a face
PxU32 maxNumVertsPerFace() const;
// copy the hull from source
void copyHull(const ConvexHull& src)
{
mVertices.resize(src.getVertices().size());
mEdges.resize(src.getEdges().size());
mFacets.resize(src.getFacets().size());
PxMemCopy(mVertices.begin(), src.getVertices().begin(), src.getVertices().size()*sizeof(PxVec3));
PxMemCopy(mEdges.begin(), src.getEdges().begin(), src.getEdges().size()*sizeof(HalfEdge));
PxMemCopy(mFacets.begin(), src.getFacets().begin(), src.getFacets().size()*sizeof(PxPlane));
}
private:
PxArray<PxVec3> mVertices;
PxArray<HalfEdge> mEdges;
PxArray<PxPlane> mFacets;
const PxArray<PxPlane>& mInputPlanes;
};
//////////////////////////////////////////////////////////////////////////|
// Crops the hull with a provided plane and with given epsilon
// returns new hull if succeeded
ConvexHull* convexHullCrop(const ConvexHull& convex, const PxPlane& slice, float planetestepsilon);
//////////////////////////////////////////////////////////////////////////|
// three planes intersection
PX_FORCE_INLINE PxVec3 threePlaneIntersection(const PxPlane& p0, const PxPlane& p1, const PxPlane& p2)
{
PxMat33 mp = (PxMat33(p0.n, p1.n, p2.n)).getTranspose();
PxMat33 mi = (mp).getInverse();
PxVec3 b(p0.d, p1.d, p2.d);
return -mi.transform(b);
}
//////////////////////////////////////////////////////////////////////////
// Compute OBB around given convex hull
bool computeOBBFromConvex(const PxConvexMeshDesc& desc, PxVec3& sides, PxTransform& matrix);
}
#endif

View File

@@ -0,0 +1,254 @@
// 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 "GuCooking.h"
#include "GuCookingConvexMeshBuilder.h"
#include "GuCookingQuickHullConvexHullLib.h"
#include "GuConvexMesh.h"
#include "foundation/PxAlloca.h"
#include "foundation/PxFPU.h"
#include "common/PxInsertionCallback.h"
using namespace physx;
using namespace Gu;
///////////////////////////////////////////////////////////////////////////////
PX_IMPLEMENT_OUTPUT_ERROR
///////////////////////////////////////////////////////////////////////////////
// cook convex mesh from given desc, internal function to be shared between create/cook convex mesh
static bool cookConvexMeshInternal(const PxCookingParams& params, const PxConvexMeshDesc& desc_, ConvexMeshBuilder& meshBuilder, ConvexHullLib* hullLib, PxConvexMeshCookingResult::Enum* condition)
{
if(condition)
*condition = PxConvexMeshCookingResult::eFAILURE;
if(!desc_.isValid())
return outputError<PxErrorCode::eINVALID_PARAMETER>(__LINE__, "Cooking::cookConvexMesh: user-provided convex mesh descriptor is invalid!");
if(params.areaTestEpsilon <= 0.0f)
return outputError<PxErrorCode::eINVALID_PARAMETER>(__LINE__, "Cooking::cookConvexMesh: provided cooking parameter areaTestEpsilon is invalid!");
if(params.planeTolerance < 0.0f)
return outputError<PxErrorCode::eINVALID_PARAMETER>(__LINE__, "Cooking::cookConvexMesh: provided cooking parameter planeTolerance is invalid!");
PxConvexMeshDesc desc = desc_;
bool polygonsLimitReached = false;
// the convex will be cooked from provided points
if(desc_.flags & PxConvexFlag::eCOMPUTE_CONVEX)
{
PX_ASSERT(hullLib);
// clean up the indices information, it could have been set by accident
desc.flags &= ~PxConvexFlag::e16_BIT_INDICES;
desc.indices.count = 0;
desc.indices.data = NULL;
desc.indices.stride = 0;
desc.polygons.count = 0;
desc.polygons.data = NULL;
desc.polygons.stride = 0;
PxConvexMeshCookingResult::Enum res = hullLib->createConvexHull();
if(res == PxConvexMeshCookingResult::eSUCCESS || res == PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED)
{
if(res == PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED)
polygonsLimitReached = true;
hullLib->fillConvexMeshDesc(desc);
}
else
{
if((res == PxConvexMeshCookingResult::eZERO_AREA_TEST_FAILED) && condition)
{
*condition = PxConvexMeshCookingResult::eZERO_AREA_TEST_FAILED;
}
return false;
}
}
if(desc.points.count >= 256)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: user-provided hull must have less than 256 vertices!");
if(desc.polygons.count >= 256)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: user-provided hull must have less than 256 faces!");
if (params.buildGPUData)
{
if (desc.points.count > 64)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: GPU-compatible user-provided hull must have less than 65 vertices!");
if (desc.polygons.count > 64)
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Cooking::cookConvexMesh: GPU-compatible user-provided hull must have less than 65 faces!");
}
if(!meshBuilder.build(desc, params.gaussMapLimit, false, hullLib))
return false;
PxConvexMeshCookingResult::Enum result = PxConvexMeshCookingResult::eSUCCESS;
if (polygonsLimitReached)
result = PxConvexMeshCookingResult::ePOLYGONS_LIMIT_REACHED;
// AD: we check this outside of the actual convex cooking because we can still cook a valid convex hull
// but we won't be able to use it on GPU.
if (params.buildGPUData && !meshBuilder.checkExtentRadiusRatio())
{
result = PxConvexMeshCookingResult::eNON_GPU_COMPATIBLE;
outputError<PxErrorCode::eDEBUG_WARNING>(__LINE__, "Cooking::cookConvexMesh: GPU-compatible convex hull could not be built because of oblong shape. Will fall back to CPU collision, particles and deformables will not collide with this mesh!");
}
if(condition)
*condition = result;
return true;
}
static ConvexHullLib* createHullLib(PxConvexMeshDesc& desc, const PxCookingParams& params)
{
if(desc.flags & PxConvexFlag::eCOMPUTE_CONVEX)
{
const PxU16 gpuMaxVertsLimit = 64;
const PxU16 gpuMaxFacesLimit = 64;
// GRB supports 64 verts max
if(params.buildGPUData)
{
desc.vertexLimit = PxMin(desc.vertexLimit, gpuMaxVertsLimit);
desc.polygonLimit = PxMin(desc.polygonLimit, gpuMaxFacesLimit);
}
return PX_NEW(QuickHullConvexHullLib) (desc, params);
}
return NULL;
}
bool immediateCooking::cookConvexMesh(const PxCookingParams& params, const PxConvexMeshDesc& desc_, PxOutputStream& stream, PxConvexMeshCookingResult::Enum* condition)
{
PX_FPU_GUARD;
// choose cooking library if needed
PxConvexMeshDesc desc = desc_;
ConvexHullLib* hullLib = createHullLib(desc, params);
ConvexMeshBuilder meshBuilder(params.buildGPUData);
if(!cookConvexMeshInternal(params, desc, meshBuilder, hullLib, condition))
{
PX_DELETE(hullLib);
return false;
}
// save the cooked results into stream
if(!meshBuilder.save(stream, platformMismatch()))
{
if(condition)
*condition = PxConvexMeshCookingResult::eFAILURE;
PX_DELETE(hullLib);
return false;
}
PX_DELETE(hullLib);
return true;
}
PxConvexMesh* immediateCooking::createConvexMesh(const PxCookingParams& params, const PxConvexMeshDesc& desc_, PxInsertionCallback& insertionCallback, PxConvexMeshCookingResult::Enum* condition)
{
PX_FPU_GUARD;
// choose cooking library if needed
PxConvexMeshDesc desc = desc_;
ConvexHullLib* hullLib = createHullLib(desc, params);
// cook the mesh
ConvexMeshBuilder meshBuilder(params.buildGPUData);
if(!cookConvexMeshInternal(params, desc, meshBuilder, hullLib, condition))
{
PX_DELETE(hullLib);
return NULL;
}
// copy the constructed data into the new mesh
ConvexHullInitData meshData;
meshBuilder.copy(meshData);
// insert into physics
PxConvexMesh* convexMesh = static_cast<PxConvexMesh*>(insertionCallback.buildObjectFromData(PxConcreteType::eCONVEX_MESH, &meshData));
if(!convexMesh)
{
if(condition)
*condition = PxConvexMeshCookingResult::eFAILURE;
PX_DELETE(hullLib);
return NULL;
}
PX_DELETE(hullLib);
return convexMesh;
}
bool immediateCooking::validateConvexMesh(const PxCookingParams& params, const PxConvexMeshDesc& desc)
{
ConvexMeshBuilder mesh(params.buildGPUData);
return mesh.build(desc, params.gaussMapLimit, true);
}
bool immediateCooking::computeHullPolygons(const PxCookingParams& params, const PxSimpleTriangleMesh& mesh, PxAllocatorCallback& inCallback, PxU32& nbVerts, PxVec3*& vertices,
PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& hullPolygons)
{
PxVec3* geometry = reinterpret_cast<PxVec3*>(PxAlloca(sizeof(PxVec3)*mesh.points.count));
immediateCooking::gatherStrided(mesh.points.data, geometry, mesh.points.count, sizeof(PxVec3), mesh.points.stride);
PxU32* topology = reinterpret_cast<PxU32*>(PxAlloca(sizeof(PxU32)*3*mesh.triangles.count));
if(mesh.flags & PxMeshFlag::e16_BIT_INDICES)
{
// conversion; 16 bit index -> 32 bit index & stride
PxU32* dest = topology;
const PxU32* pastLastDest = topology + 3*mesh.triangles.count;
const PxU8* source = reinterpret_cast<const PxU8*>(mesh.triangles.data);
while (dest < pastLastDest)
{
const PxU16 * trig16 = reinterpret_cast<const PxU16*>(source);
*dest++ = trig16[0];
*dest++ = trig16[1];
*dest++ = trig16[2];
source += mesh.triangles.stride;
}
}
else
{
immediateCooking::gatherStrided(mesh.triangles.data, topology, mesh.triangles.count, sizeof(PxU32) * 3, mesh.triangles.stride);
}
ConvexMeshBuilder meshBuilder(params.buildGPUData);
if(!meshBuilder.computeHullPolygons(mesh.points.count, geometry, mesh.triangles.count, topology, inCallback, nbVerts, vertices, nbIndices, indices, nbPolygons, hullPolygons))
return false;
return true;
}

View File

@@ -0,0 +1,626 @@
// 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 "GuConvexMesh.h"
#include "foundation/PxMathUtils.h"
#include "foundation/PxAlloca.h"
#include "GuCooking.h"
#include "GuBigConvexData2.h"
#include "GuBounds.h"
#include "GuCookingVolumeIntegration.h"
#include "GuCookingConvexMeshBuilder.h"
#include "GuCookingBigConvexDataBuilder.h"
#include "CmUtils.h"
#include "foundation/PxVecMath.h"
#include "GuCookingSDF.h"
using namespace physx;
using namespace Gu;
using namespace aos;
///////////////////////////////////////////////////////////////////////////////
PX_IMPLEMENT_OUTPUT_ERROR
///////////////////////////////////////////////////////////////////////////////
ConvexMeshBuilder::ConvexMeshBuilder(const bool buildGRBData) : hullBuilder(&mHullData, buildGRBData), mSdfData(NULL), mBigConvexData(NULL), mMass(0.0f), mInertia(PxIdentity)
{
}
ConvexMeshBuilder::~ConvexMeshBuilder()
{
PX_DELETE(mSdfData);
PX_DELETE(mBigConvexData);
}
// load the mesh data from given polygons
bool ConvexMeshBuilder::build(const PxConvexMeshDesc& desc, PxU32 gaussMapVertexLimit, bool validateOnly, ConvexHullLib* hullLib)
{
if(!desc.isValid())
return outputError<PxErrorCode::eINVALID_PARAMETER>(__LINE__, "Gu::ConvexMesh::loadFromDesc: desc.isValid() failed!");
if(!loadConvexHull(desc, hullLib))
return false;
// Compute local bounds (*after* hull has been created)
PxBounds3 minMaxBounds;
computeBoundsAroundVertices(minMaxBounds, mHullData.mNbHullVertices, hullBuilder.mHullDataHullVertices);
mHullData.mAABB = CenterExtents(minMaxBounds);
if(mHullData.mNbHullVertices > gaussMapVertexLimit)
{
if(!computeGaussMaps())
{
return false;
}
}
if(validateOnly)
return true;
// TEST_INTERNAL_OBJECTS
computeInternalObjects();
//~TEST_INTERNAL_OBJECTS
if (desc.sdfDesc)
{
computeSDF(desc);
}
return true;
}
PX_COMPILE_TIME_ASSERT(sizeof(PxMaterialTableIndex)==sizeof(PxU16));
bool ConvexMeshBuilder::save(PxOutputStream& stream, bool platformMismatch) const
{
// Export header
if(!writeHeader('C', 'V', 'X', 'M', PX_CONVEX_VERSION, platformMismatch, stream))
return false;
// Export serialization flags
PxU32 serialFlags = 0;
writeDword(serialFlags, platformMismatch, stream);
if(!hullBuilder.save(stream, platformMismatch))
return false;
// Export local bounds
// writeFloat(geomEpsilon, platformMismatch, stream);
writeFloat(0.0f, platformMismatch, stream);
writeFloat(mHullData.mAABB.getMin(0), platformMismatch, stream);
writeFloat(mHullData.mAABB.getMin(1), platformMismatch, stream);
writeFloat(mHullData.mAABB.getMin(2), platformMismatch, stream);
writeFloat(mHullData.mAABB.getMax(0), platformMismatch, stream);
writeFloat(mHullData.mAABB.getMax(1), platformMismatch, stream);
writeFloat(mHullData.mAABB.getMax(2), platformMismatch, stream);
// Export mass info
writeFloat(mMass, platformMismatch, stream);
writeFloatBuffer(reinterpret_cast<const PxF32*>(&mInertia), 9, platformMismatch, stream);
writeFloatBuffer(&mHullData.mCenterOfMass.x, 3, platformMismatch, stream);
// Export gaussmaps
if(mBigConvexData)
{
writeFloat(1.0f, platformMismatch, stream); //gauss map flag true
BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices);
SVMB.save(stream, platformMismatch);
}
else
writeFloat(-1.0f, platformMismatch, stream); //gauss map flag false
if (mSdfData)
{
writeFloat(1.0f, platformMismatch, stream); //sdf flag true
// Export sdf values
writeFloat(mSdfData->mMeshLower.x, platformMismatch, stream);
writeFloat(mSdfData->mMeshLower.y, platformMismatch, stream);
writeFloat(mSdfData->mMeshLower.z, platformMismatch, stream);
writeFloat(mSdfData->mSpacing, platformMismatch, stream);
writeDword(mSdfData->mDims.x, platformMismatch, stream);
writeDword(mSdfData->mDims.y, platformMismatch, stream);
writeDword(mSdfData->mDims.z, platformMismatch, stream);
writeDword(mSdfData->mNumSdfs, platformMismatch, stream);
writeDword(mSdfData->mNumSubgridSdfs, platformMismatch, stream);
writeDword(mSdfData->mNumStartSlots, platformMismatch, stream);
writeDword(mSdfData->mSubgridSize, platformMismatch, stream);
writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.x, platformMismatch, stream);
writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.y, platformMismatch, stream);
writeDword(mSdfData->mSdfSubgrids3DTexBlockDim.z, platformMismatch, stream);
writeFloat(mSdfData->mSubgridsMinSdfValue, platformMismatch, stream);
writeFloat(mSdfData->mSubgridsMaxSdfValue, platformMismatch, stream);
writeDword(mSdfData->mBytesPerSparsePixel, platformMismatch, stream);
writeFloatBuffer(mSdfData->mSdf, mSdfData->mNumSdfs, platformMismatch, stream);
writeByteBuffer(mSdfData->mSubgridSdf, mSdfData->mNumSubgridSdfs, stream);
writeIntBuffer(mSdfData->mSubgridStartSlots, mSdfData->mNumStartSlots, platformMismatch, stream);
}
else
writeFloat(-1.0f, platformMismatch, stream); //sdf flag false
// TEST_INTERNAL_OBJECTS
writeFloat(mHullData.mInternal.mInternalRadius, platformMismatch, stream);
writeFloat(mHullData.mInternal.mInternalExtents.x, platformMismatch, stream);
writeFloat(mHullData.mInternal.mInternalExtents.y, platformMismatch, stream);
writeFloat(mHullData.mInternal.mInternalExtents.z, platformMismatch, stream);
//~TEST_INTERNAL_OBJECTS
return true;
}
//////////////////////////////////////////////////////////////////////////
// instead of saving the data into stream, we copy the mesh data into internal Gu::ConvexMesh.
bool ConvexMeshBuilder::copy(Gu::ConvexHullInitData& hullData)
{
// hull builder data copy
PxU32 nb = 0;
hullBuilder.copy(hullData.mHullData, nb);
hullData.mNb = nb;
hullData.mInertia = mInertia;
hullData.mMass = mMass;
// mass props
hullData.mHullData.mAABB = mHullData.mAABB;
hullData.mHullData.mCenterOfMass = mHullData.mCenterOfMass;
// big convex data
if(mBigConvexData)
{
hullData.mHullData.mBigConvexRawData = &mBigConvexData->mData;
hullData.mBigConvexData = mBigConvexData;
mBigConvexData = NULL;
}
else
{
hullData.mHullData.mBigConvexRawData = NULL;
hullData.mBigConvexData = NULL;
}
if (mSdfData)
{
hullData.mHullData.mSdfData = mSdfData;
hullData.mSdfData = mSdfData;
mSdfData = NULL;
}
else
{
hullData.mHullData.mSdfData = NULL;
hullData.mSdfData = NULL;
}
// internal data
hullData.mHullData.mInternal.mInternalExtents = mHullData.mInternal.mInternalExtents;
hullData.mHullData.mInternal.mInternalRadius = mHullData.mInternal.mInternalRadius;
return true;
}
// compute mass and inertia of the convex mesh
void ConvexMeshBuilder::computeMassInfo(bool lowerPrecision)
{
if(mMass <= 0.0f) //not yet computed.
{
PxIntegrals integrals;
PxConvexMeshDesc meshDesc;
meshDesc.points.count = mHullData.mNbHullVertices;
meshDesc.points.data = hullBuilder.mHullDataHullVertices;
meshDesc.points.stride = sizeof(PxVec3);
meshDesc.polygons.data = hullBuilder.mHullDataPolygons;
meshDesc.polygons.stride = sizeof(Gu::HullPolygonData);
meshDesc.polygons.count = hullBuilder.mHull->mNbPolygons;
meshDesc.indices.data = hullBuilder.mHullDataVertexData8;
// using the centroid of the convex for the volume integration solved accuracy issues in cases where the inertia tensor
// ended up close to not being positive definite and after a few further transforms the diagonalized inertia tensor ended
// up with negative values.
PxVec3 mean(0.0f);
for(PxU32 i=0; i < mHullData.mNbHullVertices; i++)
mean += hullBuilder.mHullDataHullVertices[i];
mean *= (1.0f / mHullData.mNbHullVertices);
if(computeVolumeIntegralsEberly(meshDesc, 1.0f, integrals, mean, lowerPrecision))
{
integrals.getOriginInertia(mInertia);
mHullData.mCenterOfMass = integrals.COM;
//note: the mass will be negative for an inside-out mesh!
if(mInertia.column0.isFinite() && mInertia.column1.isFinite() && mInertia.column2.isFinite()
&& mHullData.mCenterOfMass.isFinite() && PxIsFinite(PxReal(integrals.mass)))
{
if (integrals.mass < 0)
{
outputError<PxErrorCode::eDEBUG_WARNING>(__LINE__, "Gu::ConvexMesh: Mesh has a negative volume! Is it open or do (some) faces have reversed winding? (Taking absolute value.)");
integrals.mass = -integrals.mass;
mInertia = -mInertia;
}
mMass = PxReal(integrals.mass); //set mass to valid value.
return;
}
}
outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Gu::ConvexMesh: Error computing mesh mass properties!\n");
}
}
#if PX_VC
#pragma warning(push)
#pragma warning(disable:4996) // permitting use of gatherStrided until we have a replacement.
#endif
bool ConvexMeshBuilder::loadConvexHull(const PxConvexMeshDesc& desc, ConvexHullLib* hullLib)
{
// gather points
PxVec3* geometry = reinterpret_cast<PxVec3*>(PxAlloca(sizeof(PxVec3)*desc.points.count));
immediateCooking::gatherStrided(desc.points.data, geometry, desc.points.count, sizeof(PxVec3), desc.points.stride);
PxU32* topology = NULL;
// gather indices
// store the indices into topology if we have the polygon data
if(desc.indices.data)
{
topology = reinterpret_cast<PxU32*>(PxAlloca(sizeof(PxU32)*desc.indices.count));
if (desc.flags & PxConvexFlag::e16_BIT_INDICES)
{
// conversion; 16 bit index -> 32 bit index & stride
PxU32* dest = topology;
const PxU32* pastLastDest = topology + desc.indices.count;
const PxU8* source = reinterpret_cast<const PxU8*>(desc.indices.data);
while (dest < pastLastDest)
{
const PxU16 * trig16 = reinterpret_cast<const PxU16*>(source);
*dest++ = *trig16;
source += desc.indices.stride;
}
}
else
{
immediateCooking::gatherStrided(desc.indices.data, topology, desc.indices.count, sizeof(PxU32), desc.indices.stride);
}
}
// gather polygons
PxHullPolygon* hullPolygons = NULL;
if(desc.polygons.data)
{
hullPolygons = reinterpret_cast<PxHullPolygon*>(PxAlloca(sizeof(PxHullPolygon)*desc.polygons.count));
immediateCooking::gatherStrided(desc.polygons.data,hullPolygons,desc.polygons.count,sizeof(PxHullPolygon),desc.polygons.stride);
// if user polygons, make sure the largest one is the first one
if (!hullLib)
{
PxU32 largestPolygon = 0;
for (PxU32 i = 1; i < desc.polygons.count; i++)
{
if(hullPolygons[i].mNbVerts > hullPolygons[largestPolygon].mNbVerts)
largestPolygon = i;
}
if(largestPolygon != 0)
{
PxHullPolygon movedPolygon = hullPolygons[0];
hullPolygons[0] = hullPolygons[largestPolygon];
hullPolygons[largestPolygon] = movedPolygon;
}
}
}
const bool doValidation = desc.flags & PxConvexFlag::eDISABLE_MESH_VALIDATION ? false : true;
if(!hullBuilder.init(desc.points.count, geometry, topology, desc.indices.count, desc.polygons.count, hullPolygons, doValidation, hullLib))
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "Gu::ConvexMesh::loadConvexHull: convex hull init failed!");
computeMassInfo(desc.flags & PxConvexFlag::eFAST_INERTIA_COMPUTATION);
return true;
}
#if PX_VC
#pragma warning(pop)
#endif
// compute polygons from given triangles. This is support function used in extensions. We do not accept triangles as an input for convex mesh desc.
bool ConvexMeshBuilder::computeHullPolygons(const PxU32& nbVerts,const PxVec3* verts, const PxU32& nbTriangles, const PxU32* triangles, PxAllocatorCallback& inAllocator,
PxU32& outNbVerts, PxVec3*& outVertices , PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& polygons)
{
if(!hullBuilder.computeHullPolygons(nbVerts,verts,nbTriangles,triangles))
return outputError<PxErrorCode::eINTERNAL_ERROR>(__LINE__, "ConvexMeshBuilder::computeHullPolygons: compute convex hull polygons failed. Provided triangles dont form a convex hull.");
outNbVerts = hullBuilder.mHull->mNbHullVertices;
nbPolygons = hullBuilder.mHull->mNbPolygons;
outVertices = reinterpret_cast<PxVec3*>(inAllocator.allocate(outNbVerts*sizeof(PxVec3),"PxVec3",__FILE__,__LINE__));
PxMemCopy(outVertices,hullBuilder.mHullDataHullVertices,outNbVerts*sizeof(PxVec3));
nbIndices = 0;
for (PxU32 i = 0; i < nbPolygons; i++)
{
nbIndices += hullBuilder.mHullDataPolygons[i].mNbVerts;
}
indices = reinterpret_cast<PxU32*>(inAllocator.allocate(nbIndices*sizeof(PxU32),"PxU32",__FILE__,__LINE__));
for (PxU32 i = 0; i < nbIndices; i++)
{
indices[i] = hullBuilder.mHullDataVertexData8[i];
}
polygons = reinterpret_cast<PxHullPolygon*>(inAllocator.allocate(nbPolygons*sizeof(PxHullPolygon),"PxHullPolygon",__FILE__,__LINE__));
for (PxU32 i = 0; i < nbPolygons; i++)
{
const Gu::HullPolygonData& polygonData = hullBuilder.mHullDataPolygons[i];
PxHullPolygon& outPolygon = polygons[i];
outPolygon.mPlane[0] = polygonData.mPlane.n.x;
outPolygon.mPlane[1] = polygonData.mPlane.n.y;
outPolygon.mPlane[2] = polygonData.mPlane.n.z;
outPolygon.mPlane[3] = polygonData.mPlane.d;
outPolygon.mNbVerts = polygonData.mNbVerts;
outPolygon.mIndexBase = polygonData.mVRef8;
for (PxU32 j = 0; j < polygonData.mNbVerts; j++)
{
PX_ASSERT(indices[outPolygon.mIndexBase + j] == hullBuilder.mHullDataVertexData8[polygonData.mVRef8+j]);
}
}
return true;
}
// compute big convex data
bool ConvexMeshBuilder::computeGaussMaps()
{
// The number of polygons is limited to 256 because the gaussmap encode 256 polys maximum
PxU32 density = 16;
// density = 64;
// density = 8;
// density = 2;
PX_DELETE(mBigConvexData);
PX_NEW_SERIALIZED(mBigConvexData,BigConvexData);
BigConvexDataBuilder SVMB(&mHullData, mBigConvexData, hullBuilder.mHullDataHullVertices);
// valencies we need to compute first, they are needed for min/max precompute
SVMB.computeValencies(hullBuilder);
SVMB.precompute(density);
return true;
}
// TEST_INTERNAL_OBJECTS
static void ComputeInternalExtent(Gu::ConvexHullData& data, const Gu::HullPolygonData* hullPolys)
{
const PxVec3 e = data.mAABB.getMax() - data.mAABB.getMin();
// PT: For that formula, see %SDKRoot%\InternalDocumentation\Cooking\InternalExtents.png
const float r = data.mInternal.mInternalRadius / sqrtf(3.0f);
const float epsilon = 1E-7f;
const PxU32 largestExtent = PxLargestAxis(e);
PxU32 e0 = PxGetNextIndex3(largestExtent);
PxU32 e1 = PxGetNextIndex3(e0);
if(e[e0] < e[e1])
PxSwap<PxU32>(e0,e1);
PxVec3 internalExtents(FLT_MAX);
// PT: the following code does ray-vs-plane raycasts.
// find the largest box along the largest extent, with given internal radius
for(PxU32 i = 0; i < data.mNbPolygons; i++)
{
// concurrent with search direction
const float d = hullPolys[i].mPlane.n[largestExtent];
if((-epsilon < d && d < epsilon))
continue;
const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(data.mCenterOfMass);
const float denBase = 1.0f/hullPolys[i].mPlane.n[largestExtent];
const float numn0 = r * hullPolys[i].mPlane.n[e0];
const float numn1 = r * hullPolys[i].mPlane.n[e1];
float num = numBase - numn0 - numn1;
float ext = PxMax(fabsf(num*denBase), r);
if(ext < internalExtents[largestExtent])
internalExtents[largestExtent] = ext;
num = numBase - numn0 + numn1;
ext = PxMax(fabsf(num *denBase), r);
if(ext < internalExtents[largestExtent])
internalExtents[largestExtent] = ext;
num = numBase + numn0 + numn1;
ext = PxMax(fabsf(num *denBase), r);
if(ext < internalExtents[largestExtent])
internalExtents[largestExtent] = ext;
num = numBase + numn0 - numn1;
ext = PxMax(fabsf(num *denBase), r);
if(ext < internalExtents[largestExtent])
internalExtents[largestExtent] = ext;
}
// Refine the box along e0,e1
for(PxU32 i = 0; i < data.mNbPolygons; i++)
{
const float denumAdd = hullPolys[i].mPlane.n[e0] + hullPolys[i].mPlane.n[e1];
const float denumSub = hullPolys[i].mPlane.n[e0] - hullPolys[i].mPlane.n[e1];
const float numBase = -hullPolys[i].mPlane.d - hullPolys[i].mPlane.n.dot(data.mCenterOfMass);
const float numn0 = internalExtents[largestExtent] * hullPolys[i].mPlane.n[largestExtent];
if(!(-epsilon < denumAdd && denumAdd < epsilon))
{
float num = numBase - numn0;
float ext = PxMax(fabsf(num/ denumAdd), r);
if(ext < internalExtents[e0])
internalExtents[e0] = ext;
num = numBase + numn0;
ext = PxMax(fabsf(num / denumAdd), r);
if(ext < internalExtents[e0])
internalExtents[e0] = ext;
}
if(!(-epsilon < denumSub && denumSub < epsilon))
{
float num = numBase - numn0;
float ext = PxMax(fabsf(num / denumSub), r);
if(ext < internalExtents[e0])
internalExtents[e0] = ext;
num = numBase + numn0;
ext = PxMax(fabsf(num / denumSub), r);
if(ext < internalExtents[e0])
internalExtents[e0] = ext;
}
}
internalExtents[e1] = internalExtents[e0];
data.mInternal.mInternalExtents = internalExtents;
}
//////////////////////////////////////////////////////////////////////////
// compute internal objects, get the internal extent and radius
void ConvexMeshBuilder::computeInternalObjects()
{
const Gu::HullPolygonData* hullPolys = hullBuilder.mHullDataPolygons;
Gu::ConvexHullData& data = mHullData;
// compute the internal radius
float internalRadius = FLT_MAX;
for(PxU32 i=0;i<data.mNbPolygons;i++)
{
const float dist = fabsf(hullPolys[i].mPlane.distance(data.mCenterOfMass));
if(dist<internalRadius)
internalRadius = dist;
}
data.mInternal.mInternalRadius = internalRadius;
ComputeInternalExtent(data, hullPolys);
PX_ASSERT(mHullData.mInternal.mInternalExtents.isFinite());
PX_ASSERT(mHullData.mInternal.mInternalExtents.x != 0.0f);
PX_ASSERT(mHullData.mInternal.mInternalExtents.y != 0.0f);
PX_ASSERT(mHullData.mInternal.mInternalExtents.z != 0.0f);
}
bool ConvexMeshBuilder::checkExtentRadiusRatio()
{
return mHullData.checkExtentRadiusRatio();
}
void ConvexMeshBuilder::computeSDF(const PxConvexMeshDesc& desc)
{
PX_DELETE(mSdfData);
PX_NEW_SERIALIZED(mSdfData, SDF);
//create triangle mesh from polygons
const PxU32 nbPolygons = mHullData.mNbPolygons;
PxU32 nbVerts = mHullData.mNbHullVertices;
const Gu::HullPolygonData* hullPolys = hullBuilder.mHullDataPolygons;
const PxU8* polygons = hullBuilder.mHullDataVertexData8;
const PxVec3* verts = hullBuilder.mHullDataHullVertices;
//compute total number of triangles
PxU32 numTotalTriangles = 0;
for (PxU32 i = 0; i < nbPolygons; ++i)
{
const Gu::HullPolygonData& polyData = hullPolys[i];
const PxU32 nbTriangles = polyData.mNbVerts - 2;
numTotalTriangles += nbTriangles;
}
PxArray<PxU32> triangleIndice(numTotalTriangles * 3);
PxU32 startIndex = 0;
for (PxU32 i = 0; i < nbPolygons; ++i)
{
const Gu::HullPolygonData& polyData = hullPolys[i];
const PxU32 nbTriangles = polyData.mNbVerts - 2;
const PxU8 vref0 = polygons[polyData.mVRef8];
for (PxU32 j = 0; j < nbTriangles; ++j)
{
const PxU32 index = startIndex + j * 3;
const PxU32 vref1 = polygons[polyData.mVRef8 + 0 + j + 1];
const PxU32 vref2 = polygons[polyData.mVRef8 + 0 + j + 2];
triangleIndice[index + 0] = vref0;
triangleIndice[index + 1] = vref1;
triangleIndice[index + 2] = vref2;
}
startIndex += nbTriangles * 3;
}
PxArray<PxReal> sdfData;
PxArray<PxU8> sdfDataSubgrids;
PxArray<PxU32> sdfSubgridsStartSlots;
PxTriangleMeshDesc triDesc;
triDesc.points.count = nbVerts;
triDesc.points.stride = sizeof(PxVec3);
triDesc.points.data = verts;
triDesc.triangles.count = numTotalTriangles;
triDesc.triangles.stride = sizeof(PxU32) * 3;
triDesc.triangles.data = triangleIndice.begin();
triDesc.flags &= (~PxMeshFlag::e16_BIT_INDICES);
triDesc.sdfDesc = desc.sdfDesc;
buildSDF(triDesc, sdfData, sdfDataSubgrids, sdfSubgridsStartSlots);
PxSDFDesc& sdfDesc = *desc.sdfDesc;
PxReal* sdf = mSdfData->allocateSdfs(sdfDesc.meshLower, sdfDesc.spacing, sdfDesc.dims.x, sdfDesc.dims.y, sdfDesc.dims.z,
sdfDesc.subgridSize, sdfDesc.sdfSubgrids3DTexBlockDim.x, sdfDesc.sdfSubgrids3DTexBlockDim.y, sdfDesc.sdfSubgrids3DTexBlockDim.z,
sdfDesc.subgridsMinSdfValue, sdfDesc.subgridsMaxSdfValue, sdfDesc.bitsPerSubgridPixel);
if (sdfDesc.subgridSize > 0)
{
//Sparse sdf
immediateCooking::gatherStrided(sdfDesc.sdf.data, sdf, sdfDesc.sdf.count, sizeof(PxReal), sdfDesc.sdf.stride);
immediateCooking::gatherStrided(sdfDesc.sdfSubgrids.data, mSdfData->mSubgridSdf,
sdfDesc.sdfSubgrids.count,
sizeof(PxU8), sdfDesc.sdfSubgrids.stride);
immediateCooking::gatherStrided(sdfDesc.sdfStartSlots.data, mSdfData->mSubgridStartSlots, sdfDesc.sdfStartSlots.count, sizeof(PxU32), sdfDesc.sdfStartSlots.stride);
}
else
{
//copy, and compact to get rid of strides:
immediateCooking::gatherStrided(sdfDesc.sdf.data, sdf, sdfDesc.dims.x * sdfDesc.dims.y * sdfDesc.dims.z, sizeof(PxReal), sdfDesc.sdf.stride);
}
}
//~TEST_INTERNAL_OBJECTS

View File

@@ -0,0 +1,101 @@
// 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 GU_COOKING_CONVEX_MESH_BUILDER_H
#define GU_COOKING_CONVEX_MESH_BUILDER_H
#include "cooking/PxCooking.h"
#include "GuConvexMeshData.h"
#include "GuCookingConvexPolygonsBuilder.h"
#include "GuSDF.h"
namespace physx
{
class BigConvexData;
namespace Gu
{
struct ConvexHullInitData;
}
//////////////////////////////////////////////////////////////////////////
// Convex mesh builder, creates the convex mesh from given polygons and creates internal data
class ConvexMeshBuilder
{
public:
ConvexMeshBuilder(const bool buildGRBData);
~ConvexMeshBuilder();
// loads the computed or given convex hull from descriptor.
// the descriptor does contain polygons directly, triangles are not allowed
bool build(const PxConvexMeshDesc&, PxU32 gaussMapVertexLimit, bool validateOnly = false, ConvexHullLib* hullLib = NULL);
// save the convex mesh into stream
bool save(PxOutputStream& stream, bool platformMismatch) const;
// copy the convex mesh into internal convex mesh, which can be directly used then
bool copy(Gu::ConvexHullInitData& convexData);
// loads the convex mesh from given polygons
bool loadConvexHull(const PxConvexMeshDesc&, ConvexHullLib* hullLib);
// computed hull polygons from given triangles
bool computeHullPolygons(const PxU32& nbVerts,const PxVec3* verts, const PxU32& nbTriangles, const PxU32* triangles, PxAllocatorCallback& inAllocator,
PxU32& outNbVerts, PxVec3*& outVertices, PxU32& nbIndices, PxU32*& indices, PxU32& nbPolygons, PxHullPolygon*& polygons);
// compute big convex data
bool computeGaussMaps();
// compute mass, inertia tensor
void computeMassInfo(bool lowerPrecision);
// TEST_INTERNAL_OBJECTS
// internal objects
void computeInternalObjects();
bool checkExtentRadiusRatio();
//~TEST_INTERNAL_OBJECTS
void computeSDF(const PxConvexMeshDesc& desc);
// set big convex data
void setBigConvexData(BigConvexData* data) { mBigConvexData = data; }
mutable ConvexPolygonsBuilder hullBuilder;
protected:
Gu::ConvexHullData mHullData;
Gu::SDF* mSdfData;
BigConvexData* mBigConvexData; //!< optional, only for large meshes! PT: redundant with ptr in chull data? Could also be end of other buffer
PxReal mMass; //this is mass assuming a unit density that can be scaled by instances!
PxMat33 mInertia; //in local space of mesh!
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
// 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 GU_COOKING_CONVEX_POLYGONS_BUILDER_H
#define GU_COOKING_CONVEX_POLYGONS_BUILDER_H
#include "GuCookingConvexHullBuilder.h"
#include "GuTriangle.h"
namespace physx
{
//////////////////////////////////////////////////////////////////////////
// extended convex hull builder for a case where we build polygons from input triangles
class ConvexPolygonsBuilder : public ConvexHullBuilder
{
public:
ConvexPolygonsBuilder(Gu::ConvexHullData* hull, const bool buildGRBData);
~ConvexPolygonsBuilder();
bool computeHullPolygons(const PxU32& nbVerts,const PxVec3* verts, const PxU32& nbTriangles, const PxU32* triangles);
PX_FORCE_INLINE PxU32 getNbFaces()const { return mNbHullFaces; }
PX_FORCE_INLINE const Gu::IndexedTriangle32* getFaces() const { return mFaces; }
private:
bool createPolygonData();
bool createTrianglesFromPolygons();
PxU32 mNbHullFaces; //!< Number of faces in the convex hull
Gu::IndexedTriangle32* mFaces; //!< Triangles.
};
}
#endif

View File

@@ -0,0 +1,301 @@
// 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 GU_COOKING_GRB_TRIANGLE_MESH_H
#define GU_COOKING_GRB_TRIANGLE_MESH_H
#include "foundation/PxPlane.h"
#include "foundation/PxSort.h"
#include "GuMeshData.h"
#include "GuTriangle.h"
#include "GuEdgeList.h"
#include "cooking/PxCooking.h"
#include "CmRadixSort.h"
//#define CHECK_OLD_CODE_VS_NEW_CODE
namespace physx
{
namespace Gu
{
PX_ALIGN_PREFIX(16)
struct uint4
{
unsigned int x, y, z, w;
}
PX_ALIGN_SUFFIX(16);
// TODO avoroshilov: remove duplicate definitions
static const PxU32 BOUNDARY = 0xffffffff;
static const PxU32 NONCONVEX_FLAG = 0x80000000;
#ifdef CHECK_OLD_CODE_VS_NEW_CODE
struct EdgeTriLookup
{
PxU32 edgeId0, edgeId1;
PxU32 triId;
bool operator < (const EdgeTriLookup& edge1) const
{
return edgeId0 < edge1.edgeId0 || (edgeId0 == edge1.edgeId0 && edgeId1 < edge1.edgeId1);
}
bool operator <=(const EdgeTriLookup& edge1) const
{
return edgeId0 < edge1.edgeId0 || (edgeId0 == edge1.edgeId0 && edgeId1 <= edge1.edgeId1);
}
};
static PxU32 binarySearch(const EdgeTriLookup* PX_RESTRICT data, const PxU32 numElements, const EdgeTriLookup& value)
{
PxU32 left = 0;
PxU32 right = numElements;
while ((right - left) > 1)
{
const PxU32 pos = (left + right) / 2;
const EdgeTriLookup& element = data[pos];
if (element <= value)
{
left = pos;
}
else
{
right = pos;
}
}
return left;
}
// slightly different behavior from collide2: boundary edges are filtered out
static PxU32 findAdjacent(const PxVec3* triVertices, const PxVec3* triNormals, const IndexedTriangle32* triIndices,
PxU32 nbTris, PxU32 i0, PxU32 i1, const PxPlane& plane,
EdgeTriLookup* triLookups, PxU32 triangleIndex)
{
PxU32 result = BOUNDARY;
PxReal bestCos = -FLT_MAX;
EdgeTriLookup lookup;
lookup.edgeId0 = PxMin(i0, i1);
lookup.edgeId1 = PxMax(i0, i1);
PxU32 startIndex = binarySearch(triLookups, nbTris * 3, lookup);
for (PxU32 a = startIndex; a > 0; --a)
{
if (triLookups[a - 1].edgeId0 == lookup.edgeId0 && triLookups[a - 1].edgeId1 == lookup.edgeId1)
startIndex = a - 1;
else
break;
}
for (PxU32 a = startIndex; a < nbTris * 3; ++a)
{
const EdgeTriLookup& edgeTri = triLookups[a];
if (edgeTri.edgeId0 != lookup.edgeId0 || edgeTri.edgeId1 != lookup.edgeId1)
break;
if (edgeTri.triId == triangleIndex)
continue;
const IndexedTriangle32& triIdx = triIndices[edgeTri.triId];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
const PxU32 other = vIdx0 + vIdx1 + vIdx2 - (i0 + i1);
const PxReal c = plane.n.dot(triNormals[edgeTri.triId]);
if (plane.distance(triVertices[other]) >= 0 && c > 0.f)
return NONCONVEX_FLAG | edgeTri.triId;
if (c>bestCos)
{
bestCos = c;
result = edgeTri.triId;
}
}
return result;
}
#endif
static PxU32 findAdjacent(const PxVec3* triVertices, const PxVec3* triNormals, const IndexedTriangle32* triIndices, const PxU32* faceByEdge, PxU32 nbTris, PxU32 i0, PxU32 i1, const PxPlane& plane, PxU32 triangleIndex)
{
PxU32 result = BOUNDARY;
PxReal bestCos = -FLT_MAX;
for(PxU32 i=0; i<nbTris; i++)
{
const PxU32 candidateTriIndex = faceByEdge[i];
if(triangleIndex==candidateTriIndex)
continue;
const IndexedTriangle32& triIdx = triIndices[candidateTriIndex];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
const PxU32 other = vIdx0 + vIdx1 + vIdx2 - (i0 + i1);
const PxReal c = plane.n.dot(triNormals[candidateTriIndex]);
if(plane.distance(triVertices[other]) >= 0 && c > 0.f)
return NONCONVEX_FLAG | candidateTriIndex;
if(c>bestCos)
{
bestCos = c;
result = candidateTriIndex;
}
}
return result;
}
static void buildAdjacencies(uint4* triAdjacencies, PxVec3* tempNormalsPerTri_prealloc, const PxVec3* triVertices, const IndexedTriangle32* triIndices, PxU32 nbTris)
{
#ifdef CHECK_OLD_CODE_VS_NEW_CODE
{
EdgeTriLookup* edgeLookups = PX_ALLOCATE(EdgeTriLookup, (nbTris * 3), "edgeLookups");
for (PxU32 i = 0; i < nbTris; i++)
{
const IndexedTriangle32& triIdx = triIndices[i];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
tempNormalsPerTri_prealloc[i] = (triVertices[vIdx1] - triVertices[vIdx0]).cross(triVertices[vIdx2] - triVertices[vIdx0]).getNormalized();
edgeLookups[i * 3].edgeId0 = PxMin(vIdx0, vIdx1);
edgeLookups[i * 3].edgeId1 = PxMax(vIdx0, vIdx1);
edgeLookups[i * 3].triId = i;
edgeLookups[i * 3 + 1].edgeId0 = PxMin(vIdx1, vIdx2);
edgeLookups[i * 3 + 1].edgeId1 = PxMax(vIdx1, vIdx2);
edgeLookups[i * 3 + 1].triId = i;
edgeLookups[i * 3 + 2].edgeId0 = PxMin(vIdx0, vIdx2);
edgeLookups[i * 3 + 2].edgeId1 = PxMax(vIdx0, vIdx2);
edgeLookups[i * 3 + 2].triId = i;
}
PxSort<EdgeTriLookup>(edgeLookups, PxU32(nbTris * 3));
for (PxU32 i = 0; i < nbTris; i++)
{
const IndexedTriangle32& triIdx = triIndices[i];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
const PxPlane triPlane(triVertices[vIdx0], tempNormalsPerTri_prealloc[i]);
uint4 triAdjIdx;
triAdjIdx.x = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, nbTris, vIdx0, vIdx1, triPlane, edgeLookups, i);
triAdjIdx.y = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, nbTris, vIdx1, vIdx2, triPlane, edgeLookups, i);
triAdjIdx.z = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, nbTris, vIdx2, vIdx0, triPlane, edgeLookups, i);
triAdjIdx.w = 0;
triAdjacencies[i] = triAdjIdx;
}
PX_FREE(edgeLookups);
}
#endif
if(1)
{
EDGELISTCREATE create;
create.NbFaces = nbTris;
create.DFaces = triIndices->mRef;
create.WFaces = NULL;
create.FacesToEdges = true;
create.EdgesToFaces = true;
// PT: important: do NOT set the vertices, it triggers computation of edge flags that we don't need
//create.Verts = triVertices;
EdgeList edgeList;
if(edgeList.init(create))
{
for(PxU32 i=0; i<nbTris; i++)
{
const IndexedTriangle32& triIdx = triIndices[i];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
tempNormalsPerTri_prealloc[i] = (triVertices[vIdx1] - triVertices[vIdx0]).cross(triVertices[vIdx2] - triVertices[vIdx0]).getNormalized();
}
const EdgeTriangleData* edgeTriangleData = edgeList.getEdgeTriangles();
const EdgeDescData* edgeToTriangle = edgeList.getEdgeToTriangles();
const PxU32* faceByEdge = edgeList.getFacesByEdges();
PX_ASSERT(edgeList.getNbFaces()==nbTris);
for(PxU32 i=0; i<nbTris; i++)
{
const IndexedTriangle32& triIdx = triIndices[i];
const PxU32 vIdx0 = triIdx.mRef[0];
const PxU32 vIdx1 = triIdx.mRef[1];
const PxU32 vIdx2 = triIdx.mRef[2];
const PxPlane triPlane(triVertices[vIdx0], tempNormalsPerTri_prealloc[i]);
const EdgeTriangleData& edgeTri = edgeTriangleData[i];
const EdgeDescData& edgeData0 = edgeToTriangle[edgeTri.mLink[0] & MSH_EDGE_LINK_MASK];
const EdgeDescData& edgeData1 = edgeToTriangle[edgeTri.mLink[1] & MSH_EDGE_LINK_MASK];
const EdgeDescData& edgeData2 = edgeToTriangle[edgeTri.mLink[2] & MSH_EDGE_LINK_MASK];
uint4 triAdjIdx;
triAdjIdx.x = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, faceByEdge + edgeData0.Offset, edgeData0.Count, vIdx0, vIdx1, triPlane, i);
triAdjIdx.y = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, faceByEdge + edgeData1.Offset, edgeData1.Count, vIdx1, vIdx2, triPlane, i);
triAdjIdx.z = findAdjacent(triVertices, tempNormalsPerTri_prealloc, triIndices, faceByEdge + edgeData2.Offset, edgeData2.Count, vIdx2, vIdx0, triPlane, i);
triAdjIdx.w = 0;
#ifdef CHECK_OLD_CODE_VS_NEW_CODE
PX_ASSERT(triAdjacencies[i].x == triAdjIdx.x);
PX_ASSERT(triAdjacencies[i].y == triAdjIdx.y);
PX_ASSERT(triAdjacencies[i].z == triAdjIdx.z);
#endif
triAdjacencies[i] = triAdjIdx;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,100 @@
// 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 "GuCooking.h"
#include "GuHeightField.h"
#include "foundation/PxFPU.h"
#include "common/PxInsertionCallback.h"
#include "CmUtils.h"
using namespace physx;
using namespace Gu;
bool immediateCooking::cookHeightField(const PxHeightFieldDesc& desc, PxOutputStream& stream)
{
if(!desc.isValid())
return PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Cooking::cookHeightField: user-provided heightfield descriptor is invalid!");
PX_FPU_GUARD;
HeightField hf(NULL);
if(!hf.loadFromDesc(desc))
{
hf.releaseMemory();
return false;
}
if(!hf.save(stream, platformMismatch()))
{
hf.releaseMemory();
return false;
}
hf.releaseMemory();
return true;
}
PxHeightField* immediateCooking::createHeightField(const PxHeightFieldDesc& desc, PxInsertionCallback& insertionCallback)
{
if(!desc.isValid())
{
PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Cooking::createHeightField: user-provided heightfield descriptor is invalid!");
return NULL;
}
PX_FPU_GUARD;
HeightField* hf;
PX_NEW_SERIALIZED(hf, HeightField)(NULL);
if(!hf->loadFromDesc(desc))
{
PX_DELETE(hf);
return NULL;
}
// create heightfield and set the HF data
HeightField* heightField = static_cast<HeightField*>(insertionCallback.buildObjectFromData(PxConcreteType::eHEIGHTFIELD, &hf->mData));
if(!heightField)
{
PX_DELETE(hf);
return NULL;
}
// copy the HeightField variables
heightField->mSampleStride = hf->mSampleStride;
heightField->mNbSamples = hf->mNbSamples;
heightField->mMinHeight = hf->mMinHeight;
heightField->mMaxHeight = hf->mMaxHeight;
heightField->mModifyCount = hf->mModifyCount;
PX_DELETE(hf);
return heightField;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
// 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 GU_COOKING_QUICKHULL_CONVEXHULLLIB_H
#define GU_COOKING_QUICKHULL_CONVEXHULLLIB_H
#include "GuCookingConvexHullLib.h"
#include "foundation/PxArray.h"
#include "foundation/PxUserAllocated.h"
namespace local
{
class QuickHull;
struct QuickHullVertex;
}
namespace physx
{
class ConvexHull;
//////////////////////////////////////////////////////////////////////////
// Quickhull lib constructs the hull from given input points. The resulting hull
// will only contain a subset of the input points. The algorithm does incrementally
// adds most furthest vertices to the starting simplex. The produced hulls are build with high precision
// and produce more stable and correct results, than the legacy algorithm.
class QuickHullConvexHullLib: public ConvexHullLib, public PxUserAllocated
{
PX_NOCOPY(QuickHullConvexHullLib)
public:
// functions
QuickHullConvexHullLib(const PxConvexMeshDesc& desc, const PxCookingParams& params);
~QuickHullConvexHullLib();
// computes the convex hull from provided points
virtual PxConvexMeshCookingResult::Enum createConvexHull();
// fills the convexmeshdesc with computed hull data
virtual void fillConvexMeshDesc(PxConvexMeshDesc& desc);
// provide the edge list information
virtual bool createEdgeList(const PxU32, const PxU8* , PxU8** , PxU16** , PxU16** );
protected:
// if vertex limit reached we need to expand the hull using the OBB slicing
PxConvexMeshCookingResult::Enum expandHullOBB();
// if vertex limit reached we need to expand the hull using the plane shifting
PxConvexMeshCookingResult::Enum expandHull();
// checks for collinearity and co planarity
// returns true if the simplex was ok, we can reuse the computed tolerances and min/max values
bool cleanupForSimplex(PxVec3* vertices, PxU32 vertexCount, local::QuickHullVertex* minimumVertex,
local::QuickHullVertex* maximumVertex, float& tolerance, float& planeTolerance);
// fill the result desc from quick hull convex
void fillConvexMeshDescFromQuickHull(PxConvexMeshDesc& desc);
// fill the result desc from cropped hull convex
void fillConvexMeshDescFromCroppedHull(PxConvexMeshDesc& desc);
private:
local::QuickHull* mQuickHull; // the internal quick hull representation
ConvexHull* mCropedConvexHull; //the hull cropped from OBB, used for vertex limit path
PxU8* mOutMemoryBuffer; // memory buffer used for output data
PxU16* mFaceTranslateTable; // translation table mapping output faces to internal quick hull table
};
}
#endif

View File

@@ -0,0 +1,417 @@
// 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 "GuCookingSDF.h"
#include "cooking/PxTriangleMeshDesc.h"
#include "GuSDF.h"
#include "GuCooking.h"
#include "PxSDFBuilder.h"
using namespace physx;
namespace
{
struct MeshData
{
MeshData(const PxTriangleMeshDesc& desc)
{
m_positions.resize(desc.points.count);
m_indices.resize(desc.triangles.count * 3);
immediateCooking::gatherStrided(desc.points.data, &m_positions[0], desc.points.count, sizeof(PxVec3), desc.points.stride);
immediateCooking::gatherStrided(desc.triangles.data, &m_indices[0], desc.triangles.count, 3 * sizeof(PxU32), desc.triangles.stride);
}
void GetBounds(PxVec3& outMinExtents, PxVec3& outMaxExtents) const
{
PxVec3 minExtents(FLT_MAX);
PxVec3 maxExtents(-FLT_MAX);
for (PxU32 i = 0; i < m_positions.size(); ++i)
{
const PxVec3& a = m_positions[i];
minExtents = a.minimum(minExtents);
maxExtents = a.maximum(maxExtents);
}
outMinExtents = minExtents;
outMaxExtents = maxExtents;
}
PxArray<PxVec3> m_positions;
PxArray<PxU32> m_indices;
};
}
static void quantizeSparseSDF(PxSdfBitsPerSubgridPixel::Enum bitsPerSubgridPixel,
const PxArray<PxReal>& uncompressedSdfDataSubgrids, PxArray<PxU8>& compressedSdfDataSubgrids,
PxReal subgridsMinSdfValue, PxReal subgridsMaxSdfValue)
{
PxU32 bytesPerPixel = PxU32(bitsPerSubgridPixel);
compressedSdfDataSubgrids.resize(uncompressedSdfDataSubgrids.size() * bytesPerPixel);
PxReal* ptr32 = reinterpret_cast<PxReal*>(compressedSdfDataSubgrids.begin());
PxU16* ptr16 = reinterpret_cast<PxU16*>(compressedSdfDataSubgrids.begin());
PxU8* ptr8 = compressedSdfDataSubgrids.begin();
PxReal s = 1.0f / (subgridsMaxSdfValue - subgridsMinSdfValue);
for (PxU32 i = 0; i < uncompressedSdfDataSubgrids.size(); ++i)
{
PxReal v = uncompressedSdfDataSubgrids[i];
PxReal vNormalized;
if (v == FLT_MAX)
vNormalized = 0.0f; //Not all subgrids in the 3d texture are used, can assign an arbitrary value to unused subgrids
else
vNormalized = (v - subgridsMinSdfValue) * s;
switch (bitsPerSubgridPixel)
{
case PxSdfBitsPerSubgridPixel::e8_BIT_PER_PIXEL:
PX_ASSERT(vNormalized >= 0.0f);
PX_ASSERT(vNormalized <= 1.0f);
ptr8[i] = PxU8(255.0f * vNormalized);
break;
case PxSdfBitsPerSubgridPixel::e16_BIT_PER_PIXEL:
PX_ASSERT(vNormalized >= 0.0f);
PX_ASSERT(vNormalized <= 1.0f);
ptr16[i] = PxU16(65535.0f * vNormalized);
break;
case PxSdfBitsPerSubgridPixel::e32_BIT_PER_PIXEL:
ptr32[i] = v;
break;
}
}
}
static PX_FORCE_INLINE PxU32 idxCompact(PxU32 x, PxU32 y, PxU32 z, PxU32 width, PxU32 height)
{
return z * (width) * (height)+y * (width)+x;
}
static void convert16To32Bits(PxSimpleTriangleMesh mesh, PxArray<PxU32>& indices32)
{
indices32.resize(3 * mesh.triangles.count);
if (mesh.flags & PxMeshFlag::e16_BIT_INDICES)
{
// conversion; 16 bit index -> 32 bit index & stride
PxU32* dest = indices32.begin();
const PxU32* pastLastDest = indices32.begin() + 3 * mesh.triangles.count;
const PxU8* source = reinterpret_cast<const PxU8*>(mesh.triangles.data);
while (dest < pastLastDest)
{
const PxU16 * trig16 = reinterpret_cast<const PxU16*>(source);
*dest++ = trig16[0];
*dest++ = trig16[1];
*dest++ = trig16[2];
source += mesh.triangles.stride;
}
}
else
{
immediateCooking::gatherStrided(mesh.triangles.data, indices32.begin(), mesh.triangles.count, sizeof(PxU32) * 3, mesh.triangles.stride);
}
}
static bool createSDFSparse(PxTriangleMeshDesc& desc, PxSDFDesc& sdfDesc, PxArray<PxReal>& sdfCoarse, PxArray<PxU8>& sdfDataSubgrids,
PxArray<PxU32>& sdfSubgridsStartSlots)
{
PX_ASSERT(sdfDesc.subgridSize > 0);
MeshData mesh(desc);
PxVec3 meshLower, meshUpper;
if (sdfDesc.sdfBounds.isEmpty())
mesh.GetBounds(meshLower, meshUpper);
else
{
meshLower = sdfDesc.sdfBounds.minimum;
meshUpper = sdfDesc.sdfBounds.maximum;
}
PxVec3 edges = meshUpper - meshLower;
const PxReal spacing = sdfDesc.spacing;
// tweak spacing to avoid edge cases for vertices laying on the boundary
// just covers the case where an edge is a whole multiple of the spacing.
PxReal spacingEps = spacing * (1.0f - 1e-4f);
// make sure to have at least one particle in each dimension
PxI32 dx, dy, dz;
dx = spacing > edges.x ? 1 : PxI32(edges.x / spacingEps);
dy = spacing > edges.y ? 1 : PxI32(edges.y / spacingEps);
dz = spacing > edges.z ? 1 : PxI32(edges.z / spacingEps);
dx += 4;
dy += 4;
dz += 4;
//Make sure that dx, dy and dz are multiple of subgridSize
dx = ((dx + sdfDesc.subgridSize - 1) / sdfDesc.subgridSize) * sdfDesc.subgridSize;
dy = ((dy + sdfDesc.subgridSize - 1) / sdfDesc.subgridSize) * sdfDesc.subgridSize;
dz = ((dz + sdfDesc.subgridSize - 1) / sdfDesc.subgridSize) * sdfDesc.subgridSize;
PX_ASSERT(dx % sdfDesc.subgridSize == 0);
PX_ASSERT(dy % sdfDesc.subgridSize == 0);
PX_ASSERT(dz % sdfDesc.subgridSize == 0);
// we shift the voxelization bounds so that the voxel centers
// lie symmetrically to the center of the object. this reduces the
// chance of missing features, and also better aligns the particles
// with the mesh
PxVec3 meshOffset;
meshOffset.x = 0.5f * (spacing - (edges.x - (dx - 1)*spacing));
meshOffset.y = 0.5f * (spacing - (edges.y - (dy - 1)*spacing));
meshOffset.z = 0.5f * (spacing - (edges.z - (dz - 1)*spacing));
meshLower -= meshOffset;
sdfDesc.meshLower = meshLower;
sdfDesc.dims.x = dx;
sdfDesc.dims.y = dy;
sdfDesc.dims.z = dz;
PxArray<PxU32> indices32;
PxArray<PxVec3> vertices;
const PxVec3* verticesPtr = NULL;
bool baseMeshSpecified = sdfDesc.baseMesh.triangles.data && sdfDesc.baseMesh.points.data;
if (baseMeshSpecified)
{
convert16To32Bits(sdfDesc.baseMesh, indices32);
if (sdfDesc.baseMesh.points.stride != sizeof(PxVec3))
{
vertices.resize(sdfDesc.baseMesh.points.count);
immediateCooking::gatherStrided(sdfDesc.baseMesh.points.data, vertices.begin(), sdfDesc.baseMesh.points.count, sizeof(PxVec3), sdfDesc.baseMesh.points.stride);
verticesPtr = vertices.begin();
}
else
verticesPtr = reinterpret_cast<const PxVec3*>(sdfDesc.baseMesh.points.data);
}
PxReal subgridsMinSdfValue = 0.0f;
PxReal subgridsMaxSdfValue = 1.0f;
PxReal narrowBandThickness = sdfDesc.narrowBandThicknessRelativeToSdfBoundsDiagonal * edges.magnitude();
bool success = true; // catch GPU errors.
if (sdfDesc.sdfBuilder == NULL)
{
PxArray<PxReal> denseSdf;
PxArray<PxReal> sparseSdf;
Gu::SDFUsingWindingNumbersSparse(
baseMeshSpecified ? verticesPtr : &mesh.m_positions[0],
baseMeshSpecified ? indices32.begin() : &mesh.m_indices[0],
baseMeshSpecified ? indices32.size() : mesh.m_indices.size(),
dx, dy, dz,
meshLower, meshLower + PxVec3(static_cast<PxReal>(dx), static_cast<PxReal>(dy), static_cast<PxReal>(dz)) * spacing, narrowBandThickness, sdfDesc.subgridSize,
sdfCoarse, sdfSubgridsStartSlots, sparseSdf, denseSdf, subgridsMinSdfValue, subgridsMaxSdfValue, 16, sdfDesc.sdfBuilder);
PxArray<PxReal> uncompressedSdfDataSubgrids;
Gu::convertSparseSDFTo3DTextureLayout(dx, dy, dz, sdfDesc.subgridSize, sdfSubgridsStartSlots.begin(), sparseSdf.begin(), sparseSdf.size(), uncompressedSdfDataSubgrids,
sdfDesc.sdfSubgrids3DTexBlockDim.x, sdfDesc.sdfSubgrids3DTexBlockDim.y, sdfDesc.sdfSubgrids3DTexBlockDim.z);
if (sdfDesc.bitsPerSubgridPixel == 4)
{
//32bit values are stored as normal floats while 16bit and 8bit values are scaled to 0...1 range and then scaled back to original range
subgridsMinSdfValue = 0.0f;
subgridsMaxSdfValue = 1.0f;
}
quantizeSparseSDF(sdfDesc.bitsPerSubgridPixel, uncompressedSdfDataSubgrids, sdfDataSubgrids,
subgridsMinSdfValue, subgridsMaxSdfValue);
}
else
{
PxU32* indices = baseMeshSpecified ? indices32.begin() : &mesh.m_indices[0];
PxU32 numTriangleIndices = baseMeshSpecified ? indices32.size() : mesh.m_indices.size();
const PxVec3* verts = baseMeshSpecified ? verticesPtr : &mesh.m_positions[0];
PxU32 numVertices = baseMeshSpecified ? sdfDesc.baseMesh.points.count : mesh.m_positions.size();
PxArray<PxU32> repairedIndices;
//Analyze the mesh to catch and fix some special cases
//There are meshes where every triangle is present once with cw and once with ccw orientation. Try to filter out only one set
Gu::analyzeAndFixMesh(verts, indices, numTriangleIndices, repairedIndices);
const PxU32* ind = repairedIndices.size() > 0 ? repairedIndices.begin() : indices;
if (repairedIndices.size() > 0)
numTriangleIndices = repairedIndices.size();
//The GPU SDF builder does sparse SDF, 3d texture layout and quantization in one go to best utilize the gpu
success = sdfDesc.sdfBuilder->buildSparseSDF(verts,
numVertices,
ind,
numTriangleIndices,
dx, dy, dz,
meshLower, meshLower + PxVec3(static_cast<PxReal>(dx), static_cast<PxReal>(dy), static_cast<PxReal>(dz)) * spacing, narrowBandThickness, sdfDesc.subgridSize, sdfDesc.bitsPerSubgridPixel,
sdfCoarse, sdfSubgridsStartSlots, sdfDataSubgrids, subgridsMinSdfValue, subgridsMaxSdfValue,
sdfDesc.sdfSubgrids3DTexBlockDim.x, sdfDesc.sdfSubgrids3DTexBlockDim.y, sdfDesc.sdfSubgrids3DTexBlockDim.z, 0);
}
sdfDesc.sdf.count = sdfCoarse.size();
sdfDesc.sdf.stride = sizeof(PxReal);
sdfDesc.sdf.data = sdfCoarse.begin();
sdfDesc.sdfSubgrids.count = sdfDataSubgrids.size();
sdfDesc.sdfSubgrids.stride = sizeof(PxU8);
sdfDesc.sdfSubgrids.data = sdfDataSubgrids.begin();
sdfDesc.sdfStartSlots.count = sdfSubgridsStartSlots.size();
sdfDesc.sdfStartSlots.stride = sizeof(PxU32);
sdfDesc.sdfStartSlots.data = sdfSubgridsStartSlots.begin();
sdfDesc.subgridsMinSdfValue = subgridsMinSdfValue;
sdfDesc.subgridsMaxSdfValue = subgridsMaxSdfValue;
return success; // false if we had GPU errors.
}
static bool createSDF(PxTriangleMeshDesc& desc, PxSDFDesc& sdfDesc, PxArray<PxReal>& sdf, PxArray<PxU8>& sdfDataSubgrids, PxArray<PxU32>& sdfSubgridsStartSlots)
{
if (sdfDesc.subgridSize > 0)
{
return createSDFSparse(desc, sdfDesc, sdf, sdfDataSubgrids, sdfSubgridsStartSlots);
}
MeshData mesh(desc);
PxVec3 meshLower, meshUpper;
if (sdfDesc.sdfBounds.isEmpty())
mesh.GetBounds(meshLower, meshUpper);
else
{
meshLower = sdfDesc.sdfBounds.minimum;
meshUpper = sdfDesc.sdfBounds.maximum;
}
PxVec3 edges = meshUpper - meshLower;
const PxReal spacing = sdfDesc.spacing;
// tweak spacing to avoid edge cases for vertices laying on the boundary
// just covers the case where an edge is a whole multiple of the spacing.
PxReal spacingEps = spacing * (1.0f - 1e-4f);
// make sure to have at least one particle in each dimension
PxI32 dx, dy, dz;
dx = spacing > edges.x ? 1 : PxI32(edges.x / spacingEps);
dy = spacing > edges.y ? 1 : PxI32(edges.y / spacingEps);
dz = spacing > edges.z ? 1 : PxI32(edges.z / spacingEps);
dx += 4;
dy += 4;
dz += 4;
const PxU32 numVoxels = dx * dy * dz;
// we shift the voxelization bounds so that the voxel centers
// lie symmetrically to the center of the object. this reduces the
// chance of missing features, and also better aligns the particles
// with the mesh
PxVec3 meshOffset;
meshOffset.x = 0.5f * (spacing - (edges.x - (dx - 1)*spacing));
meshOffset.y = 0.5f * (spacing - (edges.y - (dy - 1)*spacing));
meshOffset.z = 0.5f * (spacing - (edges.z - (dz - 1)*spacing));
meshLower -= meshOffset;
sdfDesc.meshLower = meshLower;
sdfDesc.dims.x = dx;
sdfDesc.dims.y = dy;
sdfDesc.dims.z = dz;
sdf.resize(numVoxels);
PxArray<PxU32> indices32;
PxArray<PxVec3> vertices;
const PxVec3* verticesPtr = NULL;
bool baseMeshSpecified = sdfDesc.baseMesh.triangles.data && sdfDesc.baseMesh.points.data;
if (baseMeshSpecified)
{
convert16To32Bits(sdfDesc.baseMesh, indices32);
if (sdfDesc.baseMesh.points.stride != sizeof(PxVec3))
{
vertices.resize(sdfDesc.baseMesh.points.count);
immediateCooking::gatherStrided(sdfDesc.baseMesh.points.data, vertices.begin(), sdfDesc.baseMesh.points.count, sizeof(PxVec3), sdfDesc.baseMesh.points.stride);
verticesPtr = vertices.begin();
}
else
verticesPtr = reinterpret_cast<const PxVec3*>(sdfDesc.baseMesh.points.data);
}
PxU32* indices = baseMeshSpecified ? indices32.begin() : &mesh.m_indices[0];
PxU32 numTriangleIndices = baseMeshSpecified ? indices32.size() : mesh.m_indices.size();
const PxVec3* verts = baseMeshSpecified ? verticesPtr : &mesh.m_positions[0];
PxU32 numVertices = baseMeshSpecified ? sdfDesc.baseMesh.points.count : mesh.m_positions.size();
if (sdfDesc.sdfBuilder == NULL)
{
Gu::SDFUsingWindingNumbers(verts, indices, numTriangleIndices, dx, dy, dz, &sdf[0], meshLower,
meshLower + PxVec3(static_cast<PxReal>(dx), static_cast<PxReal>(dy), static_cast<PxReal>(dz)) * spacing, NULL, true,
sdfDesc.numThreadsForSdfConstruction, sdfDesc.sdfBuilder);
}
else
{
PxArray<PxU32> repairedIndices;
//Analyze the mesh to catch and fix some special cases
//There are meshes where every triangle is present once with cw and once with ccw orientation. Try to filter out only one set
Gu::analyzeAndFixMesh(verts, indices, numTriangleIndices, repairedIndices);
const PxU32* ind = repairedIndices.size() > 0 ? repairedIndices.begin() : indices;
if (repairedIndices.size() > 0)
numTriangleIndices = repairedIndices.size();
bool success = sdfDesc.sdfBuilder->buildSDF(verts, numVertices, ind, numTriangleIndices, dx, dy, dz, meshLower,
meshLower + PxVec3(static_cast<PxReal>(dx), static_cast<PxReal>(dy), static_cast<PxReal>(dz)) * spacing, true, &sdf[0]);
// return false if the cooking failed.
if (!success)
return false;
}
sdfDesc.sdf.count = sdfDesc.dims.x * sdfDesc.dims.y * sdfDesc.dims.z;
sdfDesc.sdf.stride = sizeof(PxReal);
sdfDesc.sdf.data = &sdf[0];
return true;
}
bool physx::buildSDF(PxTriangleMeshDesc& desc, PxArray<PxReal>& sdf, PxArray<PxU8>& sdfDataSubgrids, PxArray<PxU32>& sdfSubgridsStartSlots)
{
PxSDFDesc& sdfDesc = *desc.sdfDesc;
if (!sdfDesc.sdf.data && sdfDesc.spacing > 0.f)
{
// Calculate signed distance field here if no sdf data provided.
if (!createSDF(desc, sdfDesc, sdf, sdfDataSubgrids, sdfSubgridsStartSlots))
return false;
sdfDesc.sdf.stride = sizeof(PxReal);
}
return true;
}

View File

@@ -0,0 +1,42 @@
// 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 GU_COOKING_SDF_BUILDER_H
#define GU_COOKING_SDF_BUILDER_H
#include "foundation/PxArray.h"
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
class PxTriangleMeshDesc;
bool buildSDF(PxTriangleMeshDesc& desc, PxArray<PxReal>& sdf, PxArray<PxU8>& sdfDataSubgrids, PxArray<PxU32>& sdfSubgridsStartSlots);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
// 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 GU_COOKING_TETRAHEDRON_MESH_H
#define GU_COOKING_TETRAHEDRON_MESH_H
#include "cooking/PxCooking.h"
#include "GuMeshData.h"
namespace physx
{
class TetrahedronMeshBuilder
{
PX_NOCOPY(TetrahedronMeshBuilder)
public:
static bool loadFromDesc(const PxTetrahedronMeshDesc& simulationMeshDesc, const PxTetrahedronMeshDesc& collisionMeshDesc,
PxDeformableVolumeSimulationDataDesc deformableVolumeDataDesc, Gu::TetrahedronMeshData& simulationMesh, Gu::DeformableVolumeSimulationData& simulationData,
Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData, Gu::CollisionMeshMappingData& mappingData,
const PxCookingParams& params, bool validateMesh = false);
static bool saveTetrahedronMeshData(PxOutputStream& stream, bool platformMismatch, const PxCookingParams& params,
const Gu::TetrahedronMeshData& mesh);
static bool saveDeformableVolumeMeshData(PxOutputStream& stream, bool platformMismatch, const PxCookingParams& params,
const Gu::TetrahedronMeshData& simulationMesh, const Gu::DeformableVolumeSimulationData& simulationData,
const Gu::TetrahedronMeshData& collisionMesh, const Gu::DeformableVolumeCollisionData& collisionData,
const Gu::CollisionMeshMappingData& mappingData);
//PxMeshMidPhase::Enum getMidphaseID() const { return PxMeshMidPhase::eBVH34; }
static bool createMidPhaseStructure(Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData, const PxCookingParams& params);
static void saveMidPhaseStructure(PxOutputStream& stream, bool mismatch, const Gu::DeformableVolumeCollisionData& collisionData);
static void computeTetData(const PxTetrahedronMeshDesc& desc, Gu::TetrahedronMeshData& mesh);
static bool createGRBMidPhaseAndData(const PxU32 originalTriangleCount, Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData, const PxCookingParams& params);
static void computeSimData(const PxTetrahedronMeshDesc& desc, Gu::TetrahedronMeshData& simulationMesh, Gu::DeformableVolumeSimulationData& simulationData, const PxCookingParams& params);
static void computeModelsMapping(Gu::TetrahedronMeshData& simulationMesh, const Gu::TetrahedronMeshData& collisionMesh, const Gu::DeformableVolumeCollisionData& collisionData,
Gu::CollisionMeshMappingData& mappingData, bool buildGPUData, const PxBoundedData* vertexToTet);
static void createCollisionModelMapping(const Gu::TetrahedronMeshData& collisionMesh, const Gu::DeformableVolumeCollisionData& collisionData, Gu::CollisionMeshMappingData& mappingData);
static void recordTetrahedronIndices(const Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData, bool buildGPUData);
static bool importMesh(const PxTetrahedronMeshDesc& collisionMeshDesc, const PxCookingParams& params,
Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData, bool validate = false);
static bool computeCollisionData(const PxTetrahedronMeshDesc& collisionMeshDesc, Gu::TetrahedronMeshData& collisionMesh, Gu::DeformableVolumeCollisionData& collisionData,
const PxCookingParams& params, bool validateMesh = false);
};
class BV32TetrahedronMeshBuilder
{
public:
static bool createMidPhaseStructure(const PxCookingParams& params, Gu::TetrahedronMeshData& meshData, Gu::BV32Tree& bv32Tree, Gu::DeformableVolumeCollisionData& collisionData);
static void saveMidPhaseStructure(Gu::BV32Tree* tree, PxOutputStream& stream, bool mismatch);
};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
// 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 GU_COOKING_TRIANGLE_MESH_H
#define GU_COOKING_TRIANGLE_MESH_H
#include "GuMeshData.h"
#include "cooking/PxCooking.h"
namespace physx
{
namespace Gu
{
class EdgeList;
}
class TriangleMeshBuilder
{
public:
TriangleMeshBuilder(Gu::TriangleMeshData& mesh, const PxCookingParams& params);
virtual ~TriangleMeshBuilder();
virtual PxMeshMidPhase::Enum getMidphaseID() const = 0;
// Called by base code when midphase structure should be built
virtual bool createMidPhaseStructure() = 0;
// Called by base code when midphase structure should be saved
virtual void saveMidPhaseStructure(PxOutputStream& stream, bool mismatch) const = 0;
// Called by base code when mesh index format has changed and the change should be reflected in midphase structure
virtual void onMeshIndexFormatChange() {}
bool cleanMesh(bool validate, PxTriangleMeshCookingResult::Enum* condition);
void remapTopology(const PxU32* order);
void createVertMapping();
void createSharedEdgeData(bool buildAdjacencies, bool buildActiveEdges);
void recordTriangleIndices();
bool createGRBMidPhaseAndData(const PxU32 originalTriangleCount);
void createGRBData();
bool loadFromDesc(const PxTriangleMeshDesc&, PxTriangleMeshCookingResult::Enum* condition, bool validate = false);
bool save(PxOutputStream& stream, bool platformMismatch, const PxCookingParams& params) const;
void checkMeshIndicesSize();
PX_FORCE_INLINE Gu::TriangleMeshData& getMeshData() { return mMeshData; }
protected:
bool importMesh(const PxTriangleMeshDesc& desc, PxTriangleMeshCookingResult::Enum* condition, bool validate = false);
bool loadFromDescInternal(PxTriangleMeshDesc&, PxTriangleMeshCookingResult::Enum* condition, bool validate = false);
void buildInertiaTensor(bool flipNormals = false);
void buildInertiaTensorFromSDF();
TriangleMeshBuilder& operator=(const TriangleMeshBuilder&);
Gu::EdgeList* mEdgeList;
const PxCookingParams& mParams;
Gu::TriangleMeshData& mMeshData;
};
class RTreeTriangleMeshBuilder : public TriangleMeshBuilder
{
public:
RTreeTriangleMeshBuilder(const PxCookingParams& params);
virtual ~RTreeTriangleMeshBuilder();
virtual PxMeshMidPhase::Enum getMidphaseID() const PX_OVERRIDE { return PxMeshMidPhase::eBVH33; }
virtual bool createMidPhaseStructure() PX_OVERRIDE;
virtual void saveMidPhaseStructure(PxOutputStream& stream, bool mismatch) const PX_OVERRIDE;
Gu::RTreeTriangleData mData;
};
class BV4TriangleMeshBuilder : public TriangleMeshBuilder
{
public:
BV4TriangleMeshBuilder(const PxCookingParams& params);
virtual ~BV4TriangleMeshBuilder();
virtual PxMeshMidPhase::Enum getMidphaseID() const PX_OVERRIDE { return PxMeshMidPhase::eBVH34; }
virtual bool createMidPhaseStructure() PX_OVERRIDE;
virtual void saveMidPhaseStructure(PxOutputStream& stream, bool mismatch) const PX_OVERRIDE;
virtual void onMeshIndexFormatChange();
Gu::BV4TriangleData mData;
};
class BV32TriangleMeshBuilder
{
public:
static bool createMidPhaseStructure(const PxCookingParams& params, Gu::TriangleMeshData& meshData, Gu::BV32Tree& bv32Tree);
static void saveMidPhaseStructure(Gu::BV32Tree* tree, PxOutputStream& stream, bool mismatch);
};
}
#endif

View File

@@ -0,0 +1,733 @@
// 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.
/*
* This code computes volume integrals needed to compute mass properties of polyhedral bodies.
* Based on public domain code by Brian Mirtich.
*/
#include "foundation/PxMemory.h"
#include "geometry/PxSimpleTriangleMesh.h"
#include "cooking/PxConvexMeshDesc.h"
#include "GuCookingVolumeIntegration.h"
#include "GuConvexMeshData.h"
#include "foundation/PxUtilities.h"
#include "foundation/PxVecMath.h"
// PT: code archeology: this initially came from ICE (IceVolumeIntegration.h/cpp). Consider revisiting.
namespace physx
{
using namespace aos;
namespace
{
class VolumeIntegrator
{
PX_NOCOPY(VolumeIntegrator)
public:
VolumeIntegrator(const PxSimpleTriangleMesh& mesh, PxF64 density) : mMass(0.0), mDensity(density), mMesh(mesh) {}
~VolumeIntegrator() {}
bool computeVolumeIntegrals(PxIntegrals& ir);
private:
struct Normal
{
PxVec3 normal;
PxF32 w;
};
struct Face
{
PxF64 Norm[3];
PxF64 w;
PxU32 Verts[3];
};
// Data structures
PxF64 mMass; //!< Mass
PxF64 mDensity; //!< Density
const PxSimpleTriangleMesh& mMesh;
PxU32 mA; //!< Alpha
PxU32 mB; //!< Beta
PxU32 mC; //!< Gamma
// Projection integrals
PxF64 mP1;
PxF64 mPa; //!< Pi Alpha
PxF64 mPb; //!< Pi Beta
PxF64 mPaa; //!< Pi Alpha^2
PxF64 mPab; //!< Pi AlphaBeta
PxF64 mPbb; //!< Pi Beta^2
PxF64 mPaaa; //!< Pi Alpha^3
PxF64 mPaab; //!< Pi Alpha^2Beta
PxF64 mPabb; //!< Pi AlphaBeta^2
PxF64 mPbbb; //!< Pi Beta^3
// Face integrals
PxF64 mFa; //!< FAlpha
PxF64 mFb; //!< FBeta
PxF64 mFc; //!< FGamma
PxF64 mFaa; //!< FAlpha^2
PxF64 mFbb; //!< FBeta^2
PxF64 mFcc; //!< FGamma^2
PxF64 mFaaa; //!< FAlpha^3
PxF64 mFbbb; //!< FBeta^3
PxF64 mFccc; //!< FGamma^3
PxF64 mFaab; //!< FAlpha^2Beta
PxF64 mFbbc; //!< FBeta^2Gamma
PxF64 mFcca; //!< FGamma^2Alpha
// The 10 volume integrals
PxF64 mT0; //!< ~Total mass
PxF64 mT1[3]; //!< Location of the center of mass
PxF64 mT2[3]; //!< Moments of inertia
PxF64 mTP[3]; //!< Products of inertia
// Internal methods
// bool Init();
PxVec3 computeCenterOfMass();
void computeInertiaTensor(PxF64* J);
void computeCOMInertiaTensor(PxF64* J);
void computeFaceNormal(Face & f, PxU32 * indices);
void computeProjectionIntegrals(const Face& f);
void computeFaceIntegrals(const Face& f);
};
#define X 0u
#define Y 1u
#define Z 2u
void VolumeIntegrator::computeFaceNormal(Face & f, PxU32 * indices)
{
const PxU8 * vertPointer = reinterpret_cast<const PxU8*>(mMesh.points.data);
const PxU32 stride = mMesh.points.stride;
//two edges
const PxVec3 d1 = (*reinterpret_cast<const PxVec3 *>(vertPointer + stride * indices[1] )) - (*reinterpret_cast<const PxVec3 *>(vertPointer + stride * indices[0] ));
const PxVec3 d2 = (*reinterpret_cast<const PxVec3 *>(vertPointer + stride * indices[2] )) - (*reinterpret_cast<const PxVec3 *>(vertPointer + stride * indices[1] ));
PxVec3 normal = d1.cross(d2);
normal.normalizeSafe();
f.w = - PxF64(normal.dot((*reinterpret_cast<const PxVec3 *>(vertPointer + stride * indices[0] )) ));
f.Norm[0] = PxF64(normal.x);
f.Norm[1] = PxF64(normal.y);
f.Norm[2] = PxF64(normal.z);
}
/**
* Computes volume integrals for a polyhedron by summing surface integrals over its faces.
* \param ir [out] a result structure.
* \return true if success
*/
bool VolumeIntegrator::computeVolumeIntegrals(PxIntegrals& ir)
{
// Clear all integrals
mT0 = mT1[X] = mT1[Y] = mT1[Z] = mT2[X] = mT2[Y] = mT2[Z] = mTP[X] = mTP[Y] = mTP[Z] = 0;
Face f;
const PxU8* trigPointer = reinterpret_cast<const PxU8*>(mMesh.triangles.data);
const PxU32 nbTris = mMesh.triangles.count;
const PxU32 stride = mMesh.triangles.stride;
for(PxU32 i=0;i<nbTris;i++, trigPointer += stride)
{
if (mMesh.flags & PxMeshFlag::e16_BIT_INDICES)
{
f.Verts[0] = (reinterpret_cast<const PxU16 *>(trigPointer))[0];
f.Verts[1] = (reinterpret_cast<const PxU16 *>(trigPointer))[1];
f.Verts[2] = (reinterpret_cast<const PxU16 *>(trigPointer))[2];
}
else
{
f.Verts[0] = (reinterpret_cast<const PxU32 *>(trigPointer)[0]);
f.Verts[1] = (reinterpret_cast<const PxU32 *>(trigPointer)[1]);
f.Verts[2] = (reinterpret_cast<const PxU32 *>(trigPointer)[2]);
}
if (mMesh.flags & PxMeshFlag::eFLIPNORMALS)
{
PxU32 t = f.Verts[1];
f.Verts[1] = f.Verts[2];
f.Verts[2] = t;
}
//compute face normal:
computeFaceNormal(f,f.Verts);
if(f.Norm[X] * f.Norm[X] + f.Norm[Y] * f.Norm[Y] + f.Norm[Z] * f.Norm[Z] < 1e-20)
continue;
// Compute alpha/beta/gamma as the right-handed permutation of (x,y,z) that maximizes |n|
const PxF64 nx = fabs(f.Norm[X]);
const PxF64 ny = fabs(f.Norm[Y]);
const PxF64 nz = fabs(f.Norm[Z]);
if (nx > ny && nx > nz) mC = X;
else mC = (ny > nz) ? Y : Z;
mA = (mC + 1) % 3;
mB = (mA + 1) % 3;
// Compute face contribution
computeFaceIntegrals(f);
// Update integrals
mT0 += f.Norm[X] * ((mA == X) ? mFa : ((mB == X) ? mFb : mFc));
mT1[mA] += f.Norm[mA] * mFaa;
mT1[mB] += f.Norm[mB] * mFbb;
mT1[mC] += f.Norm[mC] * mFcc;
mT2[mA] += f.Norm[mA] * mFaaa;
mT2[mB] += f.Norm[mB] * mFbbb;
mT2[mC] += f.Norm[mC] * mFccc;
mTP[mA] += f.Norm[mA] * mFaab;
mTP[mB] += f.Norm[mB] * mFbbc;
mTP[mC] += f.Norm[mC] * mFcca;
}
mT1[X] /= 2; mT1[Y] /= 2; mT1[Z] /= 2;
mT2[X] /= 3; mT2[Y] /= 3; mT2[Z] /= 3;
mTP[X] /= 2; mTP[Y] /= 2; mTP[Z] /= 2;
// Fill result structure
ir.COM = computeCenterOfMass();
computeInertiaTensor(reinterpret_cast<PxF64*>(ir.inertiaTensor));
computeCOMInertiaTensor(reinterpret_cast<PxF64*>(ir.COMInertiaTensor));
ir.mass = mMass;
return true;
}
/**
* Computes the center of mass.
* \return The center of mass.
*/
PxVec3 VolumeIntegrator::computeCenterOfMass()
{
// Compute center of mass
PxVec3 COM(0.0f);
if(mT0!=0.0)
{
COM.x = float(mT1[X] / mT0);
COM.y = float(mT1[Y] / mT0);
COM.z = float(mT1[Z] / mT0);
}
return COM;
}
/**
* Setups the inertia tensor relative to the origin.
* \param it [out] the returned inertia tensor.
*/
void VolumeIntegrator::computeInertiaTensor(PxF64* it)
{
PxF64 J[3][3];
// Compute inertia tensor
J[X][X] = mDensity * (mT2[Y] + mT2[Z]);
J[Y][Y] = mDensity * (mT2[Z] + mT2[X]);
J[Z][Z] = mDensity * (mT2[X] + mT2[Y]);
J[X][Y] = J[Y][X] = - mDensity * mTP[X];
J[Y][Z] = J[Z][Y] = - mDensity * mTP[Y];
J[Z][X] = J[X][Z] = - mDensity * mTP[Z];
PxMemCopy(it, J, 9*sizeof(PxF64));
}
/**
* Setups the inertia tensor relative to the COM.
* \param it [out] the returned inertia tensor.
*/
void VolumeIntegrator::computeCOMInertiaTensor(PxF64* it)
{
PxF64 J[3][3];
mMass = mDensity * mT0;
const PxVec3 COM = computeCenterOfMass();
const PxVec3 MassCOM(PxF32(mMass) * COM);
const PxVec3 MassCOM2(MassCOM.x * COM.x, MassCOM.y * COM.y, MassCOM.z * COM.z);
// Compute initial inertia tensor
computeInertiaTensor(reinterpret_cast<PxF64*>(J));
// Translate inertia tensor to center of mass
// Huyghens' theorem:
// Jx'x' = Jxx - m*(YG^2+ZG^2)
// Jy'y' = Jyy - m*(ZG^2+XG^2)
// Jz'z' = Jzz - m*(XG^2+YG^2)
// XG, YG, ZG = new origin
// YG^2+ZG^2 = dx^2
J[X][X] -= PxF64(MassCOM2.y + MassCOM2.z);
J[Y][Y] -= PxF64(MassCOM2.z + MassCOM2.x);
J[Z][Z] -= PxF64(MassCOM2.x + MassCOM2.y);
// Huyghens' theorem:
// Jx'y' = Jxy - m*XG*YG
// Jy'z' = Jyz - m*YG*ZG
// Jz'x' = Jzx - m*ZG*XG
// ### IS THE SIGN CORRECT ?
J[X][Y] = J[Y][X] += PxF64(MassCOM.x * COM.y);
J[Y][Z] = J[Z][Y] += PxF64(MassCOM.y * COM.z);
J[Z][X] = J[X][Z] += PxF64(MassCOM.z * COM.x);
PxMemCopy(it, J, 9*sizeof(PxF64));
}
/**
* Computes integrals over a face projection from the coordinates of the projections vertices.
* \param f [in] a face structure.
*/
void VolumeIntegrator::computeProjectionIntegrals(const Face& f)
{
mP1 = mPa = mPb = mPaa = mPab = mPbb = mPaaa = mPaab = mPabb = mPbbb = 0.0;
const PxU8* vertPointer = reinterpret_cast<const PxU8*>(mMesh.points.data);
const PxU32 stride = mMesh.points.stride;
for(PxU32 i=0;i<3;i++)
{
const PxVec3& p0 = *reinterpret_cast<const PxVec3 *>(vertPointer + stride * (f.Verts[i]) );
const PxVec3& p1 = *reinterpret_cast<const PxVec3 *>(vertPointer + stride * (f.Verts[(i+1) % 3]) );
const PxF64 a0 = PxF64(p0[mA]);
const PxF64 b0 = PxF64(p0[mB]);
const PxF64 a1 = PxF64(p1[mA]);
const PxF64 b1 = PxF64(p1[mB]);
const PxF64 da = a1 - a0; // DeltaA
const PxF64 db = b1 - b0; // DeltaB
const PxF64 a0_2 = a0 * a0; // Alpha0^2
const PxF64 a0_3 = a0_2 * a0; // ...
const PxF64 a0_4 = a0_3 * a0;
const PxF64 b0_2 = b0 * b0;
const PxF64 b0_3 = b0_2 * b0;
const PxF64 b0_4 = b0_3 * b0;
const PxF64 a1_2 = a1 * a1;
const PxF64 a1_3 = a1_2 * a1;
const PxF64 b1_2 = b1 * b1;
const PxF64 b1_3 = b1_2 * b1;
const PxF64 C1 = a1 + a0;
const PxF64 Ca = a1*C1 + a0_2;
const PxF64 Caa = a1*Ca + a0_3;
const PxF64 Caaa = a1*Caa + a0_4;
const PxF64 Cb = b1*(b1 + b0) + b0_2;
const PxF64 Cbb = b1*Cb + b0_3;
const PxF64 Cbbb = b1*Cbb + b0_4;
const PxF64 Cab = 3*a1_2 + 2*a1*a0 + a0_2;
const PxF64 Kab = a1_2 + 2*a1*a0 + 3*a0_2;
const PxF64 Caab = a0*Cab + 4*a1_3;
const PxF64 Kaab = a1*Kab + 4*a0_3;
const PxF64 Cabb = 4*b1_3 + 3*b1_2*b0 + 2*b1*b0_2 + b0_3;
const PxF64 Kabb = b1_3 + 2*b1_2*b0 + 3*b1*b0_2 + 4*b0_3;
mP1 += db*C1;
mPa += db*Ca;
mPaa += db*Caa;
mPaaa += db*Caaa;
mPb += da*Cb;
mPbb += da*Cbb;
mPbbb += da*Cbbb;
mPab += db*(b1*Cab + b0*Kab);
mPaab += db*(b1*Caab + b0*Kaab);
mPabb += da*(a1*Cabb + a0*Kabb);
}
mP1 /= 2.0;
mPa /= 6.0;
mPaa /= 12.0;
mPaaa /= 20.0;
mPb /= -6.0;
mPbb /= -12.0;
mPbbb /= -20.0;
mPab /= 24.0;
mPaab /= 60.0;
mPabb /= -60.0;
}
#define SQR(x) ((x)*(x)) //!< Returns x square
#define CUBE(x) ((x)*(x)*(x)) //!< Returns x cube
/**
* Computes surface integrals over a polyhedral face from the integrals over its projection.
* \param f [in] a face structure.
*/
void VolumeIntegrator::computeFaceIntegrals(const Face& f)
{
computeProjectionIntegrals(f);
const PxF64 w = f.w;
const PxF64* n = f.Norm;
const PxF64 k1 = 1 / n[mC];
const PxF64 k2 = k1 * k1;
const PxF64 k3 = k2 * k1;
const PxF64 k4 = k3 * k1;
mFa = k1 * mPa;
mFb = k1 * mPb;
mFc = -k2 * (n[mA]*mPa + n[mB]*mPb + w*mP1);
mFaa = k1 * mPaa;
mFbb = k1 * mPbb;
mFcc = k3 * (SQR(n[mA])*mPaa + 2*n[mA]*n[mB]*mPab + SQR(n[mB])*mPbb + w*(2*(n[mA]*mPa + n[mB]*mPb) + w*mP1));
mFaaa = k1 * mPaaa;
mFbbb = k1 * mPbbb;
mFccc = -k4 * (CUBE(n[mA])*mPaaa + 3*SQR(n[mA])*n[mB]*mPaab
+ 3*n[mA]*SQR(n[mB])*mPabb + CUBE(n[mB])*mPbbb
+ 3*w*(SQR(n[mA])*mPaa + 2*n[mA]*n[mB]*mPab + SQR(n[mB])*mPbb)
+ w*w*(3*(n[mA]*mPa + n[mB]*mPb) + w*mP1));
mFaab = k1 * mPaab;
mFbbc = -k2 * (n[mA]*mPabb + n[mB]*mPbbb + w*mPbb);
mFcca = k3 * (SQR(n[mA])*mPaaa + 2*n[mA]*n[mB]*mPaab + SQR(n[mB])*mPabb + w*(2*(n[mA]*mPaa + n[mB]*mPab) + w*mPa));
}
/*
* This code computes volume integrals needed to compute mass properties of polyhedral bodies.
* Based on public domain code by David Eberly.
*/
class VolumeIntegratorEberly
{
PX_NOCOPY(VolumeIntegratorEberly)
public:
VolumeIntegratorEberly(const PxConvexMeshDesc& desc, PxF64 density) : mDesc(desc), mMass(0), mMassR(0), mDensity(density) {}
~VolumeIntegratorEberly() {}
bool computeVolumeIntegralsSIMD(PxIntegrals& ir, const PxVec3& origin);
bool computeVolumeIntegrals(PxIntegrals& ir, const PxVec3& origin);
private:
const PxConvexMeshDesc& mDesc;
PxF64 mMass;
PxReal mMassR;
PxF64 mDensity;
};
PX_FORCE_INLINE void subexpressions(PxF64 w0, PxF64 w1, PxF64 w2, PxF64& f1, PxF64& f2, PxF64& f3, PxF64& g0, PxF64& g1, PxF64& g2)
{
PxF64 temp0 = w0 + w1;
f1 = temp0 + w2;
PxF64 temp1 = w0*w0;
PxF64 temp2 = temp1 + w1*temp0;
f2 = temp2 + w2*f1;
f3 = w0*temp1 + w1*temp2 + w2*f2;
g0 = f2 + w0*(f1 + w0);
g1 = f2 + w1*(f1 + w1);
g2 = f2 + w2*(f1 + w2);
}
PX_FORCE_INLINE void subexpressionsSIMD(const Vec4V& w0, const Vec4V& w1, const Vec4V& w2,
Vec4V& f1, Vec4V& f2, Vec4V& f3, Vec4V& g0, Vec4V& g1, Vec4V& g2)
{
const Vec4V temp0 = V4Add(w0, w1);
f1 = V4Add(temp0, w2);
const Vec4V temp1 = V4Mul(w0,w0);
const Vec4V temp2 = V4MulAdd(w1, temp0, temp1);
f2 = V4MulAdd(w2, f1, temp2);
// f3 = w0.multiply(temp1) + w1.multiply(temp2) + w2.multiply(f2);
const Vec4V ad0 = V4Mul(w0, temp1);
const Vec4V ad1 = V4MulAdd(w1, temp2, ad0);
f3 = V4MulAdd(w2, f2, ad1);
g0 = V4MulAdd(w0, V4Add(f1, w0), f2); // f2 + w0.multiply(f1 + w0);
g1 = V4MulAdd(w1, V4Add(f1, w1), f2); // f2 + w1.multiply(f1 + w1);
g2 = V4MulAdd(w2, V4Add(f1, w2), f2); // f2 + w2.multiply(f1 + w2);
}
/**
* Computes volume integrals for a polyhedron by summing surface integrals over its faces. SIMD version
* \param ir [out] a result structure.
* \param origin [in] the origin of the mesh vertices. All vertices will be shifted accordingly prior to computing the volume integrals.
Can improve accuracy, for example, if the centroid is used in the case of a convex mesh. Note: the returned inertia will not be relative to this origin but relative to (0,0,0).
* \return true if success
*/
bool VolumeIntegratorEberly::computeVolumeIntegralsSIMD(PxIntegrals& ir, const PxVec3& origin)
{
FloatV mult = FLoad(1.0f/6.0f);
const Vec4V multV = V4Load(1.0f/24.0f);
const Vec4V multV2 = V4Load(1.0f/60.0f);
const Vec4V multVV = V4Load(1.0f/120.0f);
// order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx
FloatV intg = FLoad(0.0f);
Vec4V intgV = V4Load(0.0f);
Vec4V intgV2 = V4Load(0.0f);
Vec4V intgVV = V4Load(0.0f);
const Vec4V originV = Vec4V_From_PxVec3_WUndefined(origin);
const FloatV zeroV = FLoad(0.0f);
const PxVec3* hullVerts = static_cast<const PxVec3*> (mDesc.points.data);
const Gu::HullPolygonData* hullPolygons = static_cast<const Gu::HullPolygonData*> (mDesc.polygons.data);
for (PxU32 i = 0; i < mDesc.polygons.count; i++)
{
const Gu::HullPolygonData& polygon = hullPolygons[i];
const PxU8* data = static_cast<const PxU8*>(mDesc.indices.data) + polygon.mVRef8;
const PxU32 nbVerts = polygon.mNbVerts;
PX_ASSERT(nbVerts > 2);
const Vec4V normalV = V4LoadU(&polygon.mPlane.n.x);
for (PxU32 j = 0; j < nbVerts - 2; j++)
{
// Should be safe to V4Load, we allocate one more vertex each time
const Vec4V vertex0 = V4LoadU(&hullVerts[data[0]].x);
const Vec4V vertex1 = V4LoadU(&hullVerts[data[j + 1]].x);
const Vec4V vertex2 = V4LoadU(&hullVerts[data[j + 2]].x);
const Vec4V p0 = V4Sub(vertex0, originV);
Vec4V p1 = V4Sub(vertex1, originV);
Vec4V p2 = V4Sub(vertex2, originV);
const Vec4V p0YZX = V4PermYZXW(p0);
const Vec4V p1YZX = V4PermYZXW(p1);
const Vec4V p2YZX = V4PermYZXW(p2);
// get edges and cross product of edges
Vec4V d = V4Cross(V4Sub(p1, p0), V4Sub(p2, p0)); // (p1 - p0).cross(p2 - p0);
const FloatV dist = V4Dot3(d, normalV);
//if(cp.dot(normalV) < 0)
if(FAllGrtr(zeroV, dist))
{
d = V4Neg(d);
Vec4V temp = p1;
p1 = p2;
p2 = temp;
}
// compute integral terms
Vec4V f1; Vec4V f2; Vec4V f3; Vec4V g0; Vec4V g1; Vec4V g2;
subexpressionsSIMD(p0, p1, p2, f1, f2, f3, g0, g1, g2);
// update integrals
intg = FScaleAdd(V4GetX(d), V4GetX(f1), intg); //intg += d.x*f1.x;
intgV = V4MulAdd(d, f2, intgV); // intgV +=d.multiply(f2);
intgV2 = V4MulAdd(d, f3, intgV2); // intgV2 += d.multiply(f3);
const Vec4V ad0 = V4Mul(p0YZX, g0);
const Vec4V ad1 = V4MulAdd(p1YZX, g1, ad0);
const Vec4V ad2 = V4MulAdd(p2YZX, g2, ad1);
intgVV = V4MulAdd(d, ad2, intgVV); //intgVV += d.multiply(p0YZX.multiply(g0) + p1YZX.multiply(g1) + p2YZX.multiply(g2));
}
}
intg = FMul(intg, mult); // intg *= mult;
intgV = V4Mul(intgV, multV);
intgV2 = V4Mul(intgV2, multV2);
intgVV = V4Mul(intgVV, multVV);
// center of mass ir.COM = intgV/mMassR;
const Vec4V comV = V4ScaleInv(intgV, intg);
// we rewrite the mass, but then we set it back
V4StoreU(comV, &ir.COM.x);
FStore(intg, &mMassR);
ir.mass = PxF64(mMassR); // = intg;
PxVec3 intg2;
V3StoreU(Vec3V_From_Vec4V(intgV2), intg2);
PxVec3 intVV;
V3StoreU(Vec3V_From_Vec4V(intgVV), intVV);
// inertia tensor relative to the provided origin parameter
ir.inertiaTensor[0][0] = PxF64(intg2.y + intg2.z);
ir.inertiaTensor[1][1] = PxF64(intg2.x + intg2.z);
ir.inertiaTensor[2][2] = PxF64(intg2.x + intg2.y);
ir.inertiaTensor[0][1] = ir.inertiaTensor[1][0] = PxF64(-intVV.x);
ir.inertiaTensor[1][2] = ir.inertiaTensor[2][1] = PxF64(-intVV.y);
ir.inertiaTensor[0][2] = ir.inertiaTensor[2][0] = PxF64(-intVV.z);
// inertia tensor relative to center of mass
ir.COMInertiaTensor[0][0] = ir.inertiaTensor[0][0] -PxF64(mMassR*(ir.COM.y*ir.COM.y+ir.COM.z*ir.COM.z));
ir.COMInertiaTensor[1][1] = ir.inertiaTensor[1][1] -PxF64(mMassR*(ir.COM.z*ir.COM.z+ir.COM.x*ir.COM.x));
ir.COMInertiaTensor[2][2] = ir.inertiaTensor[2][2] -PxF64(mMassR*(ir.COM.x*ir.COM.x+ir.COM.y*ir.COM.y));
ir.COMInertiaTensor[0][1] = ir.COMInertiaTensor[1][0] = (ir.inertiaTensor[0][1] +PxF64(mMassR*ir.COM.x*ir.COM.y));
ir.COMInertiaTensor[1][2] = ir.COMInertiaTensor[2][1] = (ir.inertiaTensor[1][2] +PxF64(mMassR*ir.COM.y*ir.COM.z));
ir.COMInertiaTensor[0][2] = ir.COMInertiaTensor[2][0] = (ir.inertiaTensor[0][2] +PxF64(mMassR*ir.COM.z*ir.COM.x));
// inertia tensor relative to (0,0,0)
if (!origin.isZero())
{
PxVec3 sum = ir.COM + origin;
ir.inertiaTensor[0][0] -= PxF64(mMassR*((ir.COM.y*ir.COM.y+ir.COM.z*ir.COM.z) - (sum.y*sum.y+sum.z*sum.z)));
ir.inertiaTensor[1][1] -= PxF64(mMassR*((ir.COM.z*ir.COM.z+ir.COM.x*ir.COM.x) - (sum.z*sum.z+sum.x*sum.x)));
ir.inertiaTensor[2][2] -= PxF64(mMassR*((ir.COM.x*ir.COM.x+ir.COM.y*ir.COM.y) - (sum.x*sum.x+sum.y*sum.y)));
ir.inertiaTensor[0][1] = ir.inertiaTensor[1][0] = ir.inertiaTensor[0][1] + PxF64(mMassR*((ir.COM.x*ir.COM.y) - (sum.x*sum.y)));
ir.inertiaTensor[1][2] = ir.inertiaTensor[2][1] = ir.inertiaTensor[1][2] + PxF64(mMassR*((ir.COM.y*ir.COM.z) - (sum.y*sum.z)));
ir.inertiaTensor[0][2] = ir.inertiaTensor[2][0] = ir.inertiaTensor[0][2] + PxF64(mMassR*((ir.COM.z*ir.COM.x) - (sum.z*sum.x)));
ir.COM = sum;
}
return true;
}
/**
* Computes volume integrals for a polyhedron by summing surface integrals over its faces.
* \param ir [out] a result structure.
* \param origin [in] the origin of the mesh vertices. All vertices will be shifted accordingly prior to computing the volume integrals.
Can improve accuracy, for example, if the centroid is used in the case of a convex mesh. Note: the returned inertia will not be relative to this origin but relative to (0,0,0).
* \return true if success
*/
bool VolumeIntegratorEberly::computeVolumeIntegrals(PxIntegrals& ir, const PxVec3& origin)
{
const PxF64 mult[10] = {1.0/6.0,1.0/24.0,1.0/24.0,1.0/24.0,1.0/60.0,1.0/60.0,1.0/60.0,1.0/120.0,1.0/120.0,1.0/120.0};
PxF64 intg[10] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; // order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx
const PxVec3* hullVerts = static_cast<const PxVec3*> (mDesc.points.data);
for (PxU32 i = 0; i < mDesc.polygons.count; i++)
{
const Gu::HullPolygonData& polygon = (static_cast<const Gu::HullPolygonData*> (mDesc.polygons.data))[i];
const PxU8* Data = static_cast<const PxU8*>(mDesc.indices.data) + polygon.mVRef8;
const PxU32 NbVerts = polygon.mNbVerts;
for (PxU32 j = 0; j < NbVerts - 2; j++)
{
const PxVec3 p0 = hullVerts[Data[0]] - origin;
PxVec3 p1 = hullVerts[Data[(j + 1) % NbVerts]] - origin;
PxVec3 p2 = hullVerts[Data[(j + 2) % NbVerts]] - origin;
PxVec3 cp = (p1 - p0).cross(p2 - p0);
if(cp.dot(polygon.mPlane.n) < 0)
{
cp = -cp;
PxSwap(p1,p2);
}
PxF64 x0 = PxF64(p0.x); PxF64 y0 = PxF64(p0.y); PxF64 z0 = PxF64(p0.z);
PxF64 x1 = PxF64(p1.x); PxF64 y1 = PxF64(p1.y); PxF64 z1 = PxF64(p1.z);
PxF64 x2 = PxF64(p2.x); PxF64 y2 = PxF64(p2.y); PxF64 z2 = PxF64(p2.z);
// get edges and cross product of edges
PxF64 d0 = PxF64(cp.x); PxF64 d1 = PxF64(cp.y); PxF64 d2 = PxF64(cp.z);
// compute integral terms
PxF64 f1x; PxF64 f2x; PxF64 f3x; PxF64 g0x; PxF64 g1x; PxF64 g2x;
PxF64 f1y; PxF64 f2y; PxF64 f3y; PxF64 g0y; PxF64 g1y; PxF64 g2y;
PxF64 f1z; PxF64 f2z; PxF64 f3z; PxF64 g0z; PxF64 g1z; PxF64 g2z;
subexpressions(x0, x1, x2, f1x, f2x, f3x, g0x, g1x, g2x);
subexpressions(y0, y1, y2, f1y, f2y, f3y, g0y, g1y, g2y);
subexpressions(z0, z1, z2, f1z, f2z, f3z, g0z, g1z, g2z);
// update integrals
intg[0] += d0*f1x;
intg[1] += d0*f2x; intg[2] += d1*f2y; intg[3] += d2*f2z;
intg[4] += d0*f3x; intg[5] += d1*f3y; intg[6] += d2*f3z;
intg[7] += d0*(y0*g0x + y1*g1x + y2*g2x);
intg[8] += d1*(z0*g0y + z1*g1y + z2*g2y);
intg[9] += d2*(x0*g0z + x1*g1z + x2*g2z);
}
}
for (PxU32 i = 0; i < 10; i++)
{
intg[i] *= mult[i];
}
ir.mass = mMass = intg[0];
// center of mass
ir.COM.x = PxReal(intg[1]/mMass);
ir.COM.y = PxReal(intg[2]/mMass);
ir.COM.z = PxReal(intg[3]/mMass);
// inertia tensor relative to the provided origin parameter
ir.inertiaTensor[0][0] = intg[5]+intg[6];
ir.inertiaTensor[1][1] = intg[4]+intg[6];
ir.inertiaTensor[2][2] = intg[4]+intg[5];
ir.inertiaTensor[0][1] = ir.inertiaTensor[1][0] = -intg[7];
ir.inertiaTensor[1][2] = ir.inertiaTensor[2][1] = -intg[8];
ir.inertiaTensor[0][2] = ir.inertiaTensor[2][0] = -intg[9];
// inertia tensor relative to center of mass
ir.COMInertiaTensor[0][0] = ir.inertiaTensor[0][0] -mMass*PxF64((ir.COM.y*ir.COM.y+ir.COM.z*ir.COM.z));
ir.COMInertiaTensor[1][1] = ir.inertiaTensor[1][1] -mMass*PxF64((ir.COM.z*ir.COM.z+ir.COM.x*ir.COM.x));
ir.COMInertiaTensor[2][2] = ir.inertiaTensor[2][2] -mMass*PxF64((ir.COM.x*ir.COM.x+ir.COM.y*ir.COM.y));
ir.COMInertiaTensor[0][1] = ir.COMInertiaTensor[1][0] = (ir.inertiaTensor[0][1] +mMass*PxF64(ir.COM.x*ir.COM.y));
ir.COMInertiaTensor[1][2] = ir.COMInertiaTensor[2][1] = (ir.inertiaTensor[1][2] +mMass*PxF64(ir.COM.y*ir.COM.z));
ir.COMInertiaTensor[0][2] = ir.COMInertiaTensor[2][0] = (ir.inertiaTensor[0][2] +mMass*PxF64(ir.COM.z*ir.COM.x));
// inertia tensor relative to (0,0,0)
if (!origin.isZero())
{
PxVec3 sum = ir.COM + origin;
ir.inertiaTensor[0][0] -= mMass*PxF64((ir.COM.y*ir.COM.y+ir.COM.z*ir.COM.z) - (sum.y*sum.y+sum.z*sum.z));
ir.inertiaTensor[1][1] -= mMass*PxF64((ir.COM.z*ir.COM.z+ir.COM.x*ir.COM.x) - (sum.z*sum.z+sum.x*sum.x));
ir.inertiaTensor[2][2] -= mMass*PxF64((ir.COM.x*ir.COM.x+ir.COM.y*ir.COM.y) - (sum.x*sum.x+sum.y*sum.y));
ir.inertiaTensor[0][1] = ir.inertiaTensor[1][0] = ir.inertiaTensor[0][1] + mMass*PxF64((ir.COM.x*ir.COM.y) - (sum.x*sum.y));
ir.inertiaTensor[1][2] = ir.inertiaTensor[2][1] = ir.inertiaTensor[1][2] + mMass*PxF64((ir.COM.y*ir.COM.z) - (sum.y*sum.z));
ir.inertiaTensor[0][2] = ir.inertiaTensor[2][0] = ir.inertiaTensor[0][2] + mMass*PxF64((ir.COM.z*ir.COM.x) - (sum.z*sum.x));
ir.COM = sum;
}
return true;
}
} // namespace
// Wrapper
bool computeVolumeIntegrals(const PxSimpleTriangleMesh& mesh, PxReal density, PxIntegrals& integrals)
{
VolumeIntegrator v(mesh, PxF64(density));
return v.computeVolumeIntegrals(integrals);
}
// Wrapper
bool computeVolumeIntegralsEberly(const PxConvexMeshDesc& mesh, PxReal density, PxIntegrals& integrals, const PxVec3& origin, bool useSimd)
{
VolumeIntegratorEberly v(mesh, PxF64(density));
if(useSimd)
return v.computeVolumeIntegralsSIMD(integrals, origin);
else
return v.computeVolumeIntegrals(integrals, origin);
}
}

View File

@@ -0,0 +1,89 @@
// 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 GU_COOKING_VOLUME_INTEGRATION_H
#define GU_COOKING_VOLUME_INTEGRATION_H
#include "foundation/PxVec3.h"
#include "foundation/PxMat33.h"
namespace physx
{
class PxSimpleTriangleMesh;
class PxConvexMeshDesc;
/**
\brief Data structure used to store mass properties.
*/
struct PxIntegrals
{
PxVec3 COM; //!< Center of mass
PxF64 mass; //!< Total mass
PxF64 inertiaTensor[3][3]; //!< Inertia tensor (mass matrix) relative to the origin
PxF64 COMInertiaTensor[3][3]; //!< Inertia tensor (mass matrix) relative to the COM
/**
\brief Retrieve the inertia tensor relative to the center of mass.
\param inertia Inertia tensor.
*/
void getInertia(PxMat33& inertia)
{
for(PxU32 j=0;j<3;j++)
{
for(PxU32 i=0;i<3;i++)
{
inertia(i,j) = PxF32(COMInertiaTensor[i][j]);
}
}
}
/**
\brief Retrieve the inertia tensor relative to the origin.
\param inertia Inertia tensor.
*/
void getOriginInertia(PxMat33& inertia)
{
for(PxU32 j=0;j<3;j++)
{
for(PxU32 i=0;i<3;i++)
{
inertia(i,j) = PxF32(inertiaTensor[i][j]);
}
}
}
};
bool computeVolumeIntegrals(const PxSimpleTriangleMesh& mesh, PxReal density, PxIntegrals& integrals);
// specialized method taking polygons directly, so we don't need to compute and store triangles for each polygon
bool computeVolumeIntegralsEberly(const PxConvexMeshDesc& mesh, PxReal density, PxIntegrals& integrals, const PxVec3& origin, bool useSimd); // Eberly simplified method
}
#endif

View File

@@ -0,0 +1,970 @@
// 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 "foundation/PxBounds3.h"
#include "foundation/PxMemory.h"
#include "common/PxTolerancesScale.h"
#include "foundation/PxSimpleTypes.h"
#include "foundation/PxSort.h"
#include "foundation/PxAllocator.h"
#include "foundation/PxVecMath.h"
#include "foundation/PxInlineArray.h"
#include "GuRTree.h"
#include "GuRTreeCooking.h"
#define PRINT_RTREE_COOKING_STATS 0 // AP: keeping this frequently used macro for diagnostics/benchmarking
#if PRINT_RTREE_COOKING_STATS
#include <stdio.h>
#endif
using namespace physx::Gu;
using namespace physx::aos;
namespace physx
{
// Google "wikipedia QuickSelect" for algorithm explanation
namespace quickSelect {
#define SWAP32(x, y) { PxU32 tmp = y; y = x; x = tmp; }
// left is the index of the leftmost element of the subarray
// right is the index of the rightmost element of the subarray (inclusive)
// number of elements in subarray = right-left+1
template<typename LtEq>
PxU32 partition(PxU32* PX_RESTRICT a, PxU32 left, PxU32 right, PxU32 pivotIndex, const LtEq& cmpLtEq)
{
PX_ASSERT(pivotIndex >= left && pivotIndex <= right);
PxU32 pivotValue = a[pivotIndex];
SWAP32(a[pivotIndex], a[right]) // Move pivot to end
PxU32 storeIndex = left;
for (PxU32 i = left; i < right; i++) // left <= i < right
if (cmpLtEq(a[i], pivotValue))
{
SWAP32(a[i], a[storeIndex]);
storeIndex++;
}
SWAP32(a[storeIndex], a[right]); // Move pivot to its final place
for (PxU32 i = left; i < storeIndex; i++)
PX_ASSERT(cmpLtEq(a[i], a[storeIndex]));
for (PxU32 i = storeIndex+1; i <= right; i++)
PX_ASSERT(cmpLtEq(a[storeIndex], a[i]));
return storeIndex;
}
// left is the index of the leftmost element of the subarray
// right is the index of the rightmost element of the subarray (inclusive)
// number of elements in subarray = right-left+1
// recursive version
template<typename LtEq>
void quickFindFirstK(PxU32* PX_RESTRICT a, PxU32 left, PxU32 right, PxU32 k, const LtEq& cmpLtEq)
{
PX_ASSERT(k <= right-left+1);
if (right > left)
{
// select pivotIndex between left and right
PxU32 pivotIndex = (left + right) >> 1;
PxU32 pivotNewIndex = partition(a, left, right, pivotIndex, cmpLtEq);
// now all elements to the left of pivotNewIndex are < old value of a[pivotIndex] (bottom half values)
if (pivotNewIndex > left + k) // new condition
quickFindFirstK(a, left, pivotNewIndex-1, k, cmpLtEq);
if (pivotNewIndex < left + k)
quickFindFirstK(a, pivotNewIndex+1, right, k+left-pivotNewIndex-1, cmpLtEq);
}
}
// non-recursive version
template<typename LtEq>
void quickSelectFirstK(PxU32* PX_RESTRICT a, PxU32 left, PxU32 right, PxU32 k, const LtEq& cmpLtEq)
{
PX_ASSERT(k <= right-left+1);
for (;;)
{
PxU32 pivotIndex = (left+right) >> 1;
PxU32 pivotNewIndex = partition(a, left, right, pivotIndex, cmpLtEq);
PxU32 pivotDist = pivotNewIndex - left + 1;
if (pivotDist == k)
return;
else if (k < pivotDist)
{
PX_ASSERT(pivotNewIndex > 0);
right = pivotNewIndex - 1;
}
else
{
k = k - pivotDist;
left = pivotNewIndex+1;
}
}
}
} // namespace quickSelect
// Intermediate non-quantized representation for RTree node in a page (final format is SIMD transposed page)
struct RTreeNodeNQ
{
PxBounds3 bounds;
PxI32 childPageFirstNodeIndex; // relative to the beginning of all build tree nodes array
PxI32 leafCount; // -1 for empty nodes, 0 for non-terminal nodes, number of enclosed tris if non-zero (LeafTriangles), also means a terminal node
struct U {}; // selector struct for uninitialized constructor
RTreeNodeNQ(U) {} // uninitialized constructor
RTreeNodeNQ() : bounds(PxBounds3::empty()), childPageFirstNodeIndex(-1), leafCount(0) {}
};
// SIMD version of bounds class
struct PxBounds3V
{
struct U {}; // selector struct for uninitialized constructor
Vec3V mn, mx;
PxBounds3V(Vec3VArg mn_, Vec3VArg mx_) : mn(mn_), mx(mx_) {}
PxBounds3V(U) {} // uninitialized constructor
PX_FORCE_INLINE Vec3V getExtents() const { return V3Sub(mx, mn); }
PX_FORCE_INLINE void include(const PxBounds3V& other) { mn = V3Min(mn, other.mn); mx = V3Max(mx, other.mx); }
// convert vector extents to PxVec3
PX_FORCE_INLINE const PxVec3 getMinVec3() const { PxVec3 ret; V3StoreU(mn, ret); return ret; }
PX_FORCE_INLINE const PxVec3 getMaxVec3() const { PxVec3 ret; V3StoreU(mx, ret); return ret; }
};
static void buildFromBounds(
Gu::RTree& resultTree, const PxBounds3V* allBounds, PxU32 numBounds,
PxArray<PxU32>& resultPermute, RTreeCooker::RemapCallback* rc, Vec3VArg allMn, Vec3VArg allMx,
PxReal sizePerfTradeOff, PxMeshCookingHint::Enum hint);
/////////////////////////////////////////////////////////////////////////
void RTreeCooker::buildFromTriangles(
Gu::RTree& result, const PxVec3* verts, PxU32 numVerts, const PxU16* tris16, const PxU32* tris32, PxU32 numTris,
PxArray<PxU32>& resultPermute, RTreeCooker::RemapCallback* rc, PxReal sizePerfTradeOff01, PxMeshCookingHint::Enum hint)
{
PX_UNUSED(numVerts);
PxArray<PxBounds3V> allBounds;
allBounds.reserve(numTris);
Vec3V allMn = Vec3V_From_FloatV(FMax()), allMx = Vec3V_From_FloatV(FNegMax());
Vec3V eps = V3Splat(FLoad(5e-4f)); // AP scaffold: use PxTolerancesScale here?
// build RTree AABB bounds from triangles, conservative bound inflation is also performed here
for(PxU32 i = 0; i < numTris; i ++)
{
PxU32 i0, i1, i2;
PxU32 i3 = i*3;
if(tris16)
{
i0 = tris16[i3]; i1 = tris16[i3+1]; i2 = tris16[i3+2];
} else
{
i0 = tris32[i3]; i1 = tris32[i3+1]; i2 = tris32[i3+2];
}
PX_ASSERT_WITH_MESSAGE(i0 < numVerts && i1 < numVerts && i2 < numVerts ,"Input mesh triangle's vertex index exceeds specified numVerts.");
Vec3V v0 = V3LoadU(verts[i0]), v1 = V3LoadU(verts[i1]), v2 = V3LoadU(verts[i2]);
Vec3V mn = V3Sub(V3Min(V3Min(v0, v1), v2), eps); // min over 3 verts, subtract eps to inflate
Vec3V mx = V3Add(V3Max(V3Max(v0, v1), v2), eps); // max over 3 verts, add eps to inflate
allMn = V3Min(allMn, mn); allMx = V3Max(allMx, mx);
allBounds.pushBack(PxBounds3V(mn, mx));
}
buildFromBounds(result, allBounds.begin(), numTris, resultPermute, rc, allMn, allMx, sizePerfTradeOff01, hint);
}
/////////////////////////////////////////////////////////////////////////
// Fast but lower quality 4-way split sorting using repeated application of quickselect
// comparator template struct for sortin gbounds centers given a coordinate index (x,y,z=0,1,2)
struct BoundsLTE
{
PxU32 coordIndex;
const PxVec3* PX_RESTRICT boundCenters; // AP: precomputed centers are faster than recomputing the centers
BoundsLTE(PxU32 coordIndex_, const PxVec3* boundCenters_)
: coordIndex(coordIndex_), boundCenters(boundCenters_)
{}
PX_FORCE_INLINE bool operator()(const PxU32 & idx1, const PxU32 & idx2) const
{
PxF32 center1 = boundCenters[idx1][coordIndex];
PxF32 center2 = boundCenters[idx2][coordIndex];
return (center1 <= center2);
}
};
// ======================================================================
// Quick sorting method
// recursive sorting procedure:
// 1. find min and max extent along each axis for the current cluster
// 2. split input cluster into two 3 times using quickselect, splitting off a quarter of the initial cluster size each time
// 3. the axis is potentialy different for each split using the following
// approximate splitting heuristic - reduce max length by some estimated factor to encourage split along other axis
// since we cut off between a quarter to a half of elements in this direction per split
// the reduction for first split should be *0.75f but we use 0.8
// to account for some node overlap. This is somewhat of an arbitrary choice and there's room for improvement.
// 4. recurse on new clusters (goto step 1)
//
struct SubSortQuick
{
static const PxReal reductionFactors[RTREE_N-1];
enum { NTRADEOFF = 9 };
static const PxU32 stopAtTrisPerLeaf1[NTRADEOFF]; // presets for PxCookingParams::meshSizePerformanceTradeoff implementation
const PxU32* permuteEnd;
const PxU32* permuteStart;
const PxBounds3V* allBounds;
PxArray<PxVec3> boundCenters;
PxU32 maxBoundsPerLeafPage;
// initialize the context for the sorting routine
SubSortQuick(PxU32* permute, const PxBounds3V* allBounds_, PxU32 allBoundsSize, PxReal sizePerfTradeOff01)
: allBounds(allBounds_)
{
permuteEnd = permute + allBoundsSize;
permuteStart = permute;
PxU32 boundsCount = allBoundsSize;
boundCenters.reserve(boundsCount); // AP - measured that precomputing centers helps with perf significantly (~20% on 1k verts)
for(PxU32 i = 0; i < boundsCount; i++)
boundCenters.pushBack( allBounds[i].getMinVec3() + allBounds[i].getMaxVec3() );
PxU32 iTradeOff = PxMin<PxU32>( PxU32(PxMax<PxReal>(0.0f, sizePerfTradeOff01)*NTRADEOFF), NTRADEOFF-1 );
maxBoundsPerLeafPage = stopAtTrisPerLeaf1[iTradeOff];
}
// implements the sorting/splitting procedure
void sort4(
PxU32* PX_RESTRICT permute, const PxU32 clusterSize, // beginning and size of current recursively processed cluster
PxArray<RTreeNodeNQ>& resultTree, PxU32& maxLevels,
PxBounds3V& subTreeBound, PxU32 level = 0)
{
if(level == 0)
maxLevels = 1;
else
maxLevels = PxMax(maxLevels, level+1);
PX_ASSERT(permute + clusterSize <= permuteEnd);
PX_ASSERT(maxBoundsPerLeafPage >= RTREE_N-1);
const PxU32 cluster4 = PxMax<PxU32>(clusterSize/RTREE_N, 1);
PX_ASSERT(clusterSize > 0);
// find min and max world bound for current cluster
Vec3V mx = allBounds[permute[0]].mx, mn = allBounds[permute[0]].mn; PX_ASSERT(permute[0] < boundCenters.size());
for(PxU32 i = 1; i < clusterSize; i ++)
{
PX_ASSERT(permute[i] < boundCenters.size());
mx = V3Max(mx, allBounds[permute[i]].mx);
mn = V3Min(mn, allBounds[permute[i]].mn);
}
PX_ALIGN_PREFIX(16) PxReal maxElem[4] PX_ALIGN_SUFFIX(16);
V3StoreA(V3Sub(mx, mn), *reinterpret_cast<PxVec3*>(maxElem)); // compute the dimensions and store into a scalar maxElem array
// split along the longest axis
const PxU32 maxDiagElement = PxU32(maxElem[0] > maxElem[1] && maxElem[0] > maxElem[2] ? 0 : (maxElem[1] > maxElem[2] ? 1 : 2));
BoundsLTE cmpLte(maxDiagElement, boundCenters.begin());
const PxU32 startNodeIndex = resultTree.size();
resultTree.resizeUninitialized(startNodeIndex+RTREE_N); // at each recursion level we add 4 nodes to the tree
PxBounds3V childBound( (PxBounds3V::U()) ); // start off uninitialized for performance
const PxI32 leftover = PxMax<PxI32>(PxI32(clusterSize - cluster4*(RTREE_N-1)), 0);
PxU32 totalCount = 0;
for(PxU32 i = 0; i < RTREE_N; i++)
{
// split off cluster4 count nodes out of the entire cluster for each i
const PxU32 clusterOffset = cluster4*i;
PxU32 count1; // cluster4 or leftover depending on whether it's the last cluster
if(i < RTREE_N-1)
{
// only need to so quickSelect for the first pagesize-1 clusters
if(clusterOffset <= clusterSize-1)
{
quickSelect::quickSelectFirstK(permute, clusterOffset, clusterSize-1, cluster4, cmpLte);
// approximate heuristic - reduce max length by some estimated factor to encourage split along other axis
// since we cut off a quarter of elements in this direction the reduction should be *0.75f but we use 0.8
// to account for some node overlap. This is somewhat of an arbitrary choice though
maxElem[cmpLte.coordIndex] *= reductionFactors[i];
// recompute cmpLte.coordIndex from updated maxElements
cmpLte.coordIndex = PxU32(maxElem[0] > maxElem[1] && maxElem[0] > maxElem[2] ? 0 : (maxElem[1] > maxElem[2] ? 1 : 2));
}
count1 = cluster4;
} else
{
count1 = PxU32(leftover);
// verify that leftover + sum of previous clusters adds up to clusterSize or leftover is 0
// leftover can be 0 if clusterSize<RTREE_N, this is generally rare, can happen for meshes with < RTREE_N tris
PX_ASSERT(leftover == 0 || cluster4*i + count1 == clusterSize);
}
RTreeNodeNQ& curNode = resultTree[startNodeIndex+i];
totalCount += count1; // accumulate total node count
if(count1 <= maxBoundsPerLeafPage) // terminal page according to specified maxBoundsPerLeafPage
{
if(count1 && totalCount <= clusterSize)
{
// this will be true most of the time except when the total number of triangles in the mesh is < PAGESIZE
curNode.leafCount = PxI32(count1);
curNode.childPageFirstNodeIndex = PxI32(clusterOffset + PxU32(permute-permuteStart));
childBound = allBounds[permute[clusterOffset+0]];
for(PxU32 i1 = 1; i1 < count1; i1++)
{
const PxBounds3V& bnd = allBounds[permute[clusterOffset+i1]];
childBound.include(bnd);
}
} else
{
// since we are required to have PAGESIZE nodes per page for simd, we fill any leftover with empty nodes
// we should only hit this if the total number of triangles in the mesh is < PAGESIZE
childBound.mn = childBound.mx = V3Zero(); // shouldn't be necessary but setting just in case
curNode.bounds.setEmpty();
curNode.leafCount = -1;
curNode.childPageFirstNodeIndex = -1; // using -1 for empty node
}
} else // not a terminal page, recurse on count1 nodes cluster
{
curNode.childPageFirstNodeIndex = PxI32(resultTree.size());
curNode.leafCount = 0;
sort4(permute+cluster4*i, count1, resultTree, maxLevels, childBound, level+1);
}
if(i == 0)
subTreeBound = childBound; // initialize subTreeBound with first childBound
else
subTreeBound.include(childBound); // expand subTreeBound with current childBound
// can use curNode since the reference change due to resizing in recursive call, need to recompute the pointer
RTreeNodeNQ& curNode1 = resultTree[startNodeIndex+i];
curNode1.bounds.minimum = childBound.getMinVec3(); // update node bounds using recursively computed childBound
curNode1.bounds.maximum = childBound.getMaxVec3();
}
}
};
// heuristic size reduction factors for splitting heuristic (see how it's used above)
const PxReal SubSortQuick::reductionFactors[RTREE_N-1] = {0.8f, 0.7f, 0.6f};
// sizePerf trade-off presets for sorting routines
const PxU32 SubSortQuick::stopAtTrisPerLeaf1[SubSortQuick::NTRADEOFF] = {16, 14, 12, 10, 8, 7, 6, 5, 4};
/////////////////////////////////////////////////////////////////////////
// SAH sorting method
//
// Preset table: lower index=better size -> higher index = better perf
static const PxU32 NTRADEOFF = 15;
// % -24 -23 -17 -15 -10 -8 -5 -3 0 +3 +3 +5 +7 +8 +9 - % raycast MeshSurface*Random benchmark perf
// K 717 734 752 777 793 811 824 866 903 939 971 1030 1087 1139 1266 - testzone size in K
// # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - preset number
static const PxU32 stopAtTrisPerPage[NTRADEOFF] = { 64, 60, 56, 48, 46, 44, 40, 36, 32, 28, 24, 20, 16, 12, 12};
static const PxU32 stopAtTrisPerLeaf[NTRADEOFF] = { 16, 14, 12, 10, 9, 8, 8, 6, 5, 5, 5, 4, 4, 4, 2}; // capped at 2 anyway
/////////////////////////////////////////////////////////////////////////
// comparator struct for sorting the bounds along a specified coordIndex (coordIndex=0,1,2 for X,Y,Z)
struct SortBoundsPredicate
{
PxU32 coordIndex;
const PxBounds3V* allBounds;
SortBoundsPredicate(PxU32 coordIndex_, const PxBounds3V* allBounds_) : coordIndex(coordIndex_), allBounds(allBounds_)
{}
bool operator()(const PxU32 & idx1, const PxU32 & idx2) const
{
// using the bounds center for comparison
PxF32 center1 = V3ReadXYZ(allBounds[idx1].mn)[coordIndex] + V3ReadXYZ(allBounds[idx1].mx)[coordIndex];
PxF32 center2 = V3ReadXYZ(allBounds[idx2].mn)[coordIndex] + V3ReadXYZ(allBounds[idx2].mx)[coordIndex];
return (center1 < center2);
}
};
/////////////////////////////////////////////////////////////////////////
// auxiliary class for SAH build (SAH = surface area heuristic)
struct Interval
{
PxU32 start, count;
Interval(PxU32 s, PxU32 c) : start(s), count(c) {}
};
// SAH function - returns surface area for given AABB extents
static PX_FORCE_INLINE void PxSAH(const Vec3VArg v, PxF32& sah)
{
FStore(V3Dot(v, V3PermZXY(v)), &sah); // v.x*v.y + v.y*v.z + v.x*v.z;
}
struct SubSortSAH
{
PxU32* PX_RESTRICT permuteStart, *PX_RESTRICT tempPermute;
const PxBounds3V* PX_RESTRICT allBounds;
PxF32* PX_RESTRICT metricL;
PxF32* PX_RESTRICT metricR;
const PxU32* PX_RESTRICT xOrder, *PX_RESTRICT yOrder, *PX_RESTRICT zOrder;
const PxU32* PX_RESTRICT xRanks, *PX_RESTRICT yRanks, *PX_RESTRICT zRanks;
PxU32* PX_RESTRICT tempRanks;
PxU32 nbTotalBounds;
PxU32 iTradeOff;
// precompute various values used during sort
SubSortSAH(
PxU32* permute, const PxBounds3V* allBounds_, PxU32 numBounds,
const PxU32* xOrder_, const PxU32* yOrder_, const PxU32* zOrder_,
const PxU32* xRanks_, const PxU32* yRanks_, const PxU32* zRanks_, PxReal sizePerfTradeOff01)
: permuteStart(permute), allBounds(allBounds_),
xOrder(xOrder_), yOrder(yOrder_), zOrder(zOrder_),
xRanks(xRanks_), yRanks(yRanks_), zRanks(zRanks_), nbTotalBounds(numBounds)
{
metricL = PX_ALLOCATE(PxF32, numBounds, "metricL");
metricR = PX_ALLOCATE(PxF32, numBounds, "metricR");
tempPermute = PX_ALLOCATE(PxU32, (numBounds*2+1), "tempPermute");
tempRanks = PX_ALLOCATE(PxU32, numBounds, "tempRanks");
iTradeOff = PxMin<PxU32>( PxU32(PxMax<PxReal>(0.0f, sizePerfTradeOff01)*NTRADEOFF), NTRADEOFF-1 );
}
~SubSortSAH() // release temporarily used memory
{
PX_FREE(metricL);
PX_FREE(metricR);
PX_FREE(tempPermute);
PX_FREE(tempRanks);
}
////////////////////////////////////////////////////////////////////
// returns split position for second array start relative to permute ptr
PxU32 split(PxU32* permute, PxU32 clusterSize)
{
if(clusterSize <= 1)
return 0;
if(clusterSize == 2)
return 1;
PxI32 minCount = clusterSize >= 4 ? 2 : 1;
PxI32 splitStartL = minCount; // range=[startL->endL)
PxI32 splitEndL = PxI32(clusterSize-minCount);
PxI32 splitStartR = PxI32(clusterSize-splitStartL); // range=(endR<-startR], startR > endR
PxI32 splitEndR = PxI32(clusterSize-splitEndL);
PX_ASSERT(splitEndL-splitStartL == splitStartR-splitEndR);
PX_ASSERT(splitStartL <= splitEndL);
PX_ASSERT(splitStartR >= splitEndR);
PX_ASSERT(splitEndR >= 1);
PX_ASSERT(splitEndL < PxI32(clusterSize));
// pick the best axis with some splitting metric
// axis index is X=0, Y=1, Z=2
PxF32 minMetric[3];
PxU32 minMetricSplit[3];
const PxU32* ranks3[3] = { xRanks, yRanks, zRanks };
const PxU32* orders3[3] = { xOrder, yOrder, zOrder };
for(PxU32 coordIndex = 0; coordIndex <= 2; coordIndex++)
{
SortBoundsPredicate sortPredicateLR(coordIndex, allBounds);
const PxU32* rank = ranks3[coordIndex];
const PxU32* order = orders3[coordIndex];
// build ranks in tempPermute
if(clusterSize == nbTotalBounds) // AP: about 4% perf gain from this optimization
{
// if this is a full cluster sort, we already have it done
for(PxU32 i = 0; i < clusterSize; i ++)
tempPermute[i] = order[i];
} else
{
// sort the tempRanks
for(PxU32 i = 0; i < clusterSize; i ++)
tempRanks[i] = rank[permute[i]];
PxSort(tempRanks, clusterSize);
for(PxU32 i = 0; i < clusterSize; i ++) // convert back from ranks to indices
tempPermute[i] = order[tempRanks[i]];
}
// we consider overlapping intervals for minimum sum of metrics
// left interval is from splitStartL up to splitEndL
// right interval is from splitStartR down to splitEndR
// first compute the array metricL
Vec3V boundsLmn = allBounds[tempPermute[0]].mn; // init with 0th bound
Vec3V boundsLmx = allBounds[tempPermute[0]].mx; // init with 0th bound
PxI32 ii;
for(ii = 1; ii < splitStartL; ii++) // sweep right to include all bounds up to splitStartL-1
{
boundsLmn = V3Min(boundsLmn, allBounds[tempPermute[ii]].mn);
boundsLmx = V3Max(boundsLmx, allBounds[tempPermute[ii]].mx);
}
PxU32 countL0 = 0;
for(ii = splitStartL; ii <= splitEndL; ii++) // compute metric for inclusive bounds from splitStartL to splitEndL
{
boundsLmn = V3Min(boundsLmn, allBounds[tempPermute[ii]].mn);
boundsLmx = V3Max(boundsLmx, allBounds[tempPermute[ii]].mx);
PxSAH(V3Sub(boundsLmx, boundsLmn), metricL[countL0++]);
}
// now we have metricL
// now compute the array metricR
Vec3V boundsRmn = allBounds[tempPermute[clusterSize-1]].mn; // init with last bound
Vec3V boundsRmx = allBounds[tempPermute[clusterSize-1]].mx; // init with last bound
for(ii = PxI32(clusterSize-2); ii > splitStartR; ii--) // include bounds to the left of splitEndR down to splitStartR
{
boundsRmn = V3Min(boundsRmn, allBounds[tempPermute[ii]].mn);
boundsRmx = V3Max(boundsRmx, allBounds[tempPermute[ii]].mx);
}
PxU32 countR0 = 0;
for(ii = splitStartR; ii >= splitEndR; ii--) // continue sweeping left, including bounds and recomputing the metric
{
boundsRmn = V3Min(boundsRmn, allBounds[tempPermute[ii]].mn);
boundsRmx = V3Max(boundsRmx, allBounds[tempPermute[ii]].mx);
PxSAH(V3Sub(boundsRmx, boundsRmn), metricR[countR0++]);
}
PX_ASSERT((countL0 == countR0) && (countL0 == PxU32(splitEndL-splitStartL+1)));
// now iterate over splitRange and compute the minimum sum of SAHLeft*countLeft + SAHRight*countRight
PxU32 minMetricSplitPosition = 0;
PxF32 minMetricLocal = PX_MAX_REAL;
const PxI32 hsI32 = PxI32(clusterSize/2);
const PxI32 splitRange = (splitEndL-splitStartL+1);
for(ii = 0; ii < splitRange; ii++)
{
PxF32 countL = PxF32(ii+minCount); // need to add minCount since ii iterates over splitRange
PxF32 countR = PxF32(splitRange-ii-1+minCount);
PX_ASSERT(PxU32(countL + countR) == clusterSize);
const PxF32 metric = (countL*metricL[ii] + countR*metricR[splitRange-ii-1]);
const PxU32 splitPos = PxU32(ii+splitStartL);
if(metric < minMetricLocal ||
(metric <= minMetricLocal && // same metric but more even split
PxAbs(PxI32(splitPos)-hsI32) < PxAbs(PxI32(minMetricSplitPosition)-hsI32)))
{
minMetricLocal = metric;
minMetricSplitPosition = splitPos;
}
}
minMetric[coordIndex] = minMetricLocal;
minMetricSplit[coordIndex] = minMetricSplitPosition;
// sum of axis lengths for both left and right AABBs
}
PxU32 winIndex = 2;
if(minMetric[0] <= minMetric[1] && minMetric[0] <= minMetric[2])
winIndex = 0;
else if(minMetric[1] <= minMetric[2])
winIndex = 1;
const PxU32* rank = ranks3[winIndex];
const PxU32* order = orders3[winIndex];
if(clusterSize == nbTotalBounds) // AP: about 4% gain from this special case optimization
{
// if this is a full cluster sort, we already have it done
for(PxU32 i = 0; i < clusterSize; i ++)
permute[i] = order[i];
} else
{
// sort the tempRanks
for(PxU32 i = 0; i < clusterSize; i ++)
tempRanks[i] = rank[permute[i]];
PxSort(tempRanks, clusterSize);
for(PxU32 i = 0; i < clusterSize; i ++)
permute[i] = order[tempRanks[i]];
}
PxU32 splitPoint = minMetricSplit[winIndex];
if(clusterSize == 3 && splitPoint == 0)
splitPoint = 1; // special case due to rounding
return splitPoint;
}
// compute surface area for a given split
PxF32 computeSA(const PxU32* permute, const Interval& split) // both permute and i are relative
{
PX_ASSERT(split.count >= 1);
Vec3V bmn = allBounds[permute[split.start]].mn;
Vec3V bmx = allBounds[permute[split.start]].mx;
for(PxU32 i = 1; i < split.count; i++)
{
const PxBounds3V& b1 = allBounds[permute[split.start+i]];
bmn = V3Min(bmn, b1.mn); bmx = V3Max(bmx, b1.mx);
}
PxF32 ret; PxSAH(V3Sub(bmx, bmn), ret);
return ret;
}
////////////////////////////////////////////////////////////////////
// main SAH sort routine
void sort4(PxU32* permute, PxU32 clusterSize,
PxArray<RTreeNodeNQ>& resultTree, PxU32& maxLevels, PxU32 level = 0, RTreeNodeNQ* parentNode = NULL)
{
PX_UNUSED(parentNode);
if(level == 0)
maxLevels = 1;
else
maxLevels = PxMax(maxLevels, level+1);
PxU32 splitPos[RTREE_N];
for(PxU32 j = 0; j < RTREE_N; j++)
splitPos[j] = j+1;
if(clusterSize >= RTREE_N)
{
// split into RTREE_N number of regions via RTREE_N-1 subsequent splits
// each split is represented as a current interval
// we iterate over currently active intervals and compute it's surface area
// then we split the interval with maximum surface area
// AP scaffold: possible optimization - seems like computeSA can be cached for unchanged intervals
PxInlineArray<Interval, 1024> splits;
splits.pushBack(Interval(0, clusterSize));
for(PxU32 iSplit = 0; iSplit < RTREE_N-1; iSplit++)
{
PxF32 maxSAH = -FLT_MAX;
PxU32 maxSplit = 0xFFFFffff;
for(PxU32 i = 0; i < splits.size(); i++)
{
if(splits[i].count == 1)
continue;
PxF32 SAH = computeSA(permute, splits[i])*splits[i].count;
if(SAH > maxSAH)
{
maxSAH = SAH;
maxSplit = i;
}
}
PX_ASSERT(maxSplit != 0xFFFFffff);
// maxSplit is now the index of the interval in splits array with maximum surface area
// we now split it into 2 using the split() function
Interval old = splits[maxSplit];
PX_ASSERT(old.count > 1);
PxU32 splitLocal = split(permute+old.start, old.count); // relative split pos
PX_ASSERT(splitLocal >= 1);
PX_ASSERT(old.count-splitLocal >= 1);
splits.pushBack(Interval(old.start, splitLocal));
splits.pushBack(Interval(old.start+splitLocal, old.count-splitLocal));
splits.replaceWithLast(maxSplit);
splitPos[iSplit] = old.start+splitLocal;
}
// verification code, make sure split counts add up to clusterSize
PX_ASSERT(splits.size() == RTREE_N);
PxU32 sum = 0;
PX_UNUSED(sum);
for(PxU32 j = 0; j < RTREE_N; j++)
sum += splits[j].count;
PX_ASSERT(sum == clusterSize);
}
else // clusterSize < RTREE_N
{
// make it so splitCounts based on splitPos add up correctly for small cluster sizes
for(PxU32 i = clusterSize; i < RTREE_N-1; i++)
splitPos[i] = clusterSize;
}
// sort splitPos index array using quicksort (just a few values)
PxSort(splitPos, RTREE_N-1);
splitPos[RTREE_N-1] = clusterSize; // splitCount[n] is computed as splitPos[n+1]-splitPos[n], so we need to add this last value
// now compute splitStarts and splitCounts from splitPos[] array. Also perform a bunch of correctness verification
PxU32 splitStarts[RTREE_N];
PxU32 splitCounts[RTREE_N];
splitStarts[0] = 0;
splitCounts[0] = splitPos[0];
PxU32 sumCounts = splitCounts[0];
PX_UNUSED(sumCounts);
for(PxU32 j = 1; j < RTREE_N; j++)
{
splitStarts[j] = splitPos[j-1];
PX_ASSERT(splitStarts[j-1]<=splitStarts[j]);
splitCounts[j] = splitPos[j]-splitPos[j-1];
PX_ASSERT(splitCounts[j] > 0 || clusterSize < RTREE_N);
sumCounts += splitCounts[j];
PX_ASSERT(splitStarts[j-1]+splitCounts[j-1]<=splitStarts[j]);
}
PX_ASSERT(sumCounts == clusterSize);
PX_ASSERT(splitStarts[RTREE_N-1]+splitCounts[RTREE_N-1]<=clusterSize);
// mark this cluster as terminal based on clusterSize <= stopAtTrisPerPage parameter for current iTradeOff user specified preset
bool terminalClusterByTotalCount = (clusterSize <= stopAtTrisPerPage[iTradeOff]);
// iterate over splitCounts for the current cluster, if any of counts exceed 16 (which is the maximum supported by LeafTriangles
// we cannot mark this cluster as terminal (has to be split more)
for(PxU32 s = 0; s < RTREE_N; s++)
if(splitCounts[s] > 16) // LeafTriangles doesn't support > 16 tris
terminalClusterByTotalCount = false;
// iterate over all the splits
for(PxU32 s = 0; s < RTREE_N; s++)
{
RTreeNodeNQ rtn;
PxU32 splitCount = splitCounts[s];
if(splitCount > 0) // splits shouldn't be empty generally
{
// sweep left to right and compute min and max SAH for each individual bound in current split
PxBounds3V b = allBounds[permute[splitStarts[s]]];
PxF32 sahMin; PxSAH(b.getExtents(), sahMin);
PxF32 sahMax = sahMin;
// AP scaffold - looks like this could be optimized (we are recomputing bounds top down)
for(PxU32 i = 1; i < splitCount; i++)
{
PxU32 localIndex = i + splitStarts[s];
const PxBounds3V& b1 = allBounds[permute[localIndex]];
PxF32 sah1; PxSAH(b1.getExtents(), sah1);
sahMin = PxMin(sahMin, sah1);
sahMax = PxMax(sahMax, sah1);
b.include(b1);
}
rtn.bounds.minimum = V3ReadXYZ(b.mn);
rtn.bounds.maximum = V3ReadXYZ(b.mx);
// if bounds differ widely (according to some heuristic preset), we continue splitting
// this is important for a mixed cluster with large and small triangles
bool okSAH = (sahMax/sahMin < 40.0f);
if(!okSAH)
terminalClusterByTotalCount = false; // force splitting this cluster
bool stopSplitting = // compute the final splitting criterion
splitCount <= 2 || (okSAH && splitCount <= 3) // stop splitting at 2 nodes or if SAH ratio is OK and splitCount <= 3
|| terminalClusterByTotalCount || splitCount <= stopAtTrisPerLeaf[iTradeOff];
if(stopSplitting)
{
// this is a terminal page then, mark as such
// first node index is relative to the top level input array beginning
rtn.childPageFirstNodeIndex = PxI32(splitStarts[s]+(permute-permuteStart));
rtn.leafCount = PxI32(splitCount);
PX_ASSERT(splitCount <= 16); // LeafTriangles doesn't support more
}
else
{
// this is not a terminal page, we will recompute this later, after we recurse on subpages (label ZZZ)
rtn.childPageFirstNodeIndex = -1;
rtn.leafCount = 0;
}
}
else // splitCount == 0 at this point, this is an empty paddding node (with current presets it's very rare)
{
PX_ASSERT(splitCount == 0);
rtn.bounds.setEmpty();
rtn.childPageFirstNodeIndex = -1;
rtn.leafCount = -1;
}
resultTree.pushBack(rtn); // push the new node into the resultTree array
}
if(terminalClusterByTotalCount) // abort recursion if terminal cluster
return;
// recurse on subpages
PxU32 parentIndex = resultTree.size() - RTREE_N; // save the parentIndex as specified (array can be resized during recursion)
for(PxU32 s = 0; s<RTREE_N; s++)
{
RTreeNodeNQ* sParent = &resultTree[parentIndex+s]; // array can be resized and relocated during recursion
if(sParent->leafCount == 0) // only split pages that were marked as non-terminal during splitting (see "label ZZZ" above)
{
// all child nodes will be pushed inside of this recursive call,
// so we set the child pointer for parent node to resultTree.size()
sParent->childPageFirstNodeIndex = PxI32(resultTree.size());
sort4(permute+splitStarts[s], splitCounts[s], resultTree, maxLevels, level+1, sParent);
}
}
}
};
/////////////////////////////////////////////////////////////////////////
// initializes the input permute array with identity permutation
// and shuffles it so that new sorted index, newIndex = resultPermute[oldIndex]
static void buildFromBounds(
Gu::RTree& result, const PxBounds3V* allBounds, PxU32 numBounds,
PxArray<PxU32>& permute, RTreeCooker::RemapCallback* rc, Vec3VArg allMn, Vec3VArg allMx,
PxReal sizePerfTradeOff01, PxMeshCookingHint::Enum hint)
{
PX_UNUSED(sizePerfTradeOff01);
PxBounds3V treeBounds(allMn, allMx);
// start off with an identity permutation
permute.resize(0);
permute.reserve(numBounds+1);
for(PxU32 j = 0; j < numBounds; j ++)
permute.pushBack(j);
const PxU32 sentinel = 0xABCDEF01;
permute.pushBack(sentinel);
// load sorted nodes into an RTreeNodeNQ tree representation
// build the tree structure from sorted nodes
const PxU32 pageSize = RTREE_N;
PxArray<RTreeNodeNQ> resultTree;
resultTree.reserve(numBounds*2);
PxU32 maxLevels = 0;
if(hint == PxMeshCookingHint::eSIM_PERFORMANCE) // use high quality SAH build
{
PxArray<PxU32> xRanks(numBounds), yRanks(numBounds), zRanks(numBounds), xOrder(numBounds), yOrder(numBounds), zOrder(numBounds);
PxMemCopy(xOrder.begin(), permute.begin(), sizeof(xOrder[0])*numBounds);
PxMemCopy(yOrder.begin(), permute.begin(), sizeof(yOrder[0])*numBounds);
PxMemCopy(zOrder.begin(), permute.begin(), sizeof(zOrder[0])*numBounds);
// sort by shuffling the permutation, precompute sorted ranks for x,y,z-orders
PxSort(xOrder.begin(), xOrder.size(), SortBoundsPredicate(0, allBounds));
for(PxU32 i = 0; i < numBounds; i++) xRanks[xOrder[i]] = i;
PxSort(yOrder.begin(), yOrder.size(), SortBoundsPredicate(1, allBounds));
for(PxU32 i = 0; i < numBounds; i++) yRanks[yOrder[i]] = i;
PxSort(zOrder.begin(), zOrder.size(), SortBoundsPredicate(2, allBounds));
for(PxU32 i = 0; i < numBounds; i++) zRanks[zOrder[i]] = i;
SubSortSAH ss(permute.begin(), allBounds, numBounds,
xOrder.begin(), yOrder.begin(), zOrder.begin(), xRanks.begin(), yRanks.begin(), zRanks.begin(), sizePerfTradeOff01);
ss.sort4(permute.begin(), numBounds, resultTree, maxLevels);
} else
{ // use fast cooking path
PX_ASSERT(hint == PxMeshCookingHint::eCOOKING_PERFORMANCE);
SubSortQuick ss(permute.begin(), allBounds, numBounds, sizePerfTradeOff01);
PxBounds3V discard((PxBounds3V::U()));
ss.sort4(permute.begin(), permute.size()-1, resultTree, maxLevels, discard); // AP scaffold: need to implement build speed/runtime perf slider
}
PX_ASSERT(permute[numBounds] == sentinel); // verify we didn't write past the array
permute.popBack(); // discard the sentinel value
#if PRINT_RTREE_COOKING_STATS // stats code
PxU32 totalLeafTris = 0;
PxU32 numLeaves = 0;
PxI32 maxLeafTris = 0;
PxU32 numEmpty = 0;
for(PxU32 i = 0; i < resultTree.size(); i++)
{
PxI32 leafCount = resultTree[i].leafCount;
numEmpty += (resultTree[i].bounds.isEmpty());
if(leafCount > 0)
{
numLeaves++;
totalLeafTris += leafCount;
if(leafCount > maxLeafTris)
maxLeafTris = leafCount;
}
}
printf("AABBs total/empty=%d/%d\n", resultTree.size(), numEmpty);
printf("numTris=%d, numLeafAABBs=%d, avgTrisPerLeaf=%.2f, maxTrisPerLeaf = %d\n",
numBounds, numLeaves, PxF32(totalLeafTris)/numLeaves, maxLeafTris);
#endif
PX_ASSERT(RTREE_N*sizeof(RTreeNodeQ) == sizeof(RTreePage)); // needed for nodePtrMultiplier computation to be correct
const int nodePtrMultiplier = sizeof(RTreeNodeQ); // convert offset as count in qnodes to page ptr
// Quantize the tree. AP scaffold - might be possible to merge this phase with the page pass below this loop
PxArray<RTreeNodeQ> qtreeNodes;
PxU32 firstEmptyIndex = PxU32(-1);
PxU32 resultCount = resultTree.size();
qtreeNodes.reserve(resultCount);
for(PxU32 i = 0; i < resultCount; i++) // AP scaffold - eliminate this pass
{
RTreeNodeNQ & u = resultTree[i];
RTreeNodeQ q;
q.setLeaf(u.leafCount > 0); // set the leaf flag
if(u.childPageFirstNodeIndex == -1) // empty node?
{
if(firstEmptyIndex == PxU32(-1))
firstEmptyIndex = qtreeNodes.size();
q.minx = q.miny = q.minz = FLT_MAX; // AP scaffold improvement - use empty 1e30 bounds instead and reference a valid leaf
q.maxx = q.maxy = q.maxz = -FLT_MAX; // that will allow to remove the empty node test from the runtime
q.ptr = firstEmptyIndex*nodePtrMultiplier; PX_ASSERT((q.ptr & 1) == 0);
q.setLeaf(true); // label empty node as leaf node
} else
{
// non-leaf node
q.minx = u.bounds.minimum.x;
q.miny = u.bounds.minimum.y;
q.minz = u.bounds.minimum.z;
q.maxx = u.bounds.maximum.x;
q.maxy = u.bounds.maximum.y;
q.maxz = u.bounds.maximum.z;
if(u.leafCount > 0)
{
q.ptr = PxU32(u.childPageFirstNodeIndex);
rc->remap(&q.ptr, q.ptr, PxU32(u.leafCount));
PX_ASSERT(q.isLeaf()); // remap is expected to set the isLeaf bit
}
else
{
// verify that all children bounds are included in the parent bounds
for(PxU32 s = 0; s < RTREE_N; s++)
{
const RTreeNodeNQ& child = resultTree[u.childPageFirstNodeIndex+s];
PX_UNUSED(child);
// is a sentinel node or is inside parent's bounds
PX_ASSERT(child.leafCount == -1 || child.bounds.isInside(u.bounds));
}
q.ptr = PxU32(u.childPageFirstNodeIndex * nodePtrMultiplier);
PX_ASSERT(q.ptr % RTREE_N == 0);
q.setLeaf(false);
}
}
qtreeNodes.pushBack(q);
}
// build the final rtree image
result.mInvDiagonal = PxVec4(1.0f);
PX_ASSERT(qtreeNodes.size() % RTREE_N == 0);
result.mTotalNodes = qtreeNodes.size();
result.mTotalPages = result.mTotalNodes / pageSize;
result.mPages = static_cast<RTreePage*>(
PxAlignedAllocator<128>().allocate(sizeof(RTreePage)*result.mTotalPages, PX_FL));
result.mBoundsMin = PxVec4(V3ReadXYZ(treeBounds.mn), 0.0f);
result.mBoundsMax = PxVec4(V3ReadXYZ(treeBounds.mx), 0.0f);
result.mDiagonalScaler = (result.mBoundsMax - result.mBoundsMin) / 65535.0f;
result.mPageSize = pageSize;
result.mNumLevels = maxLevels;
PX_ASSERT(result.mTotalNodes % pageSize == 0);
result.mNumRootPages = 1;
for(PxU32 j = 0; j < result.mTotalPages; j++)
{
RTreePage& page = result.mPages[j];
for(PxU32 k = 0; k < RTREE_N; k ++)
{
const RTreeNodeQ& n = qtreeNodes[j*RTREE_N+k];
page.maxx[k] = n.maxx;
page.maxy[k] = n.maxy;
page.maxz[k] = n.maxz;
page.minx[k] = n.minx;
page.miny[k] = n.miny;
page.minz[k] = n.minz;
page.ptrs[k] = n.ptr;
}
}
//printf("Tree size=%d\n", result.mTotalPages*sizeof(RTreePage));
#if PX_DEBUG
result.validate(); // make sure the child bounds are included in the parent and other validation
#endif
}
} // namespace physx

View File

@@ -0,0 +1,55 @@
// 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 GU_COOKING_RTREE_H
#define GU_COOKING_RTREE_H
#include "cooking/PxCooking.h"
#include "foundation/PxArray.h"
#include "GuMeshData.h"
#include "GuRTree.h"
namespace physx
{
struct RTreeCooker
{
struct RemapCallback // a callback to convert indices from triangle to LeafTriangles or other uses
{
virtual ~RemapCallback() {}
virtual void remap(PxU32* rtreePtr, PxU32 start, PxU32 leafCount) = 0;
};
// triangles will be remapped so that newIndex = resultPermute[oldIndex]
static void buildFromTriangles(
Gu::RTree& resultTree, const PxVec3* verts, PxU32 numVerts, const PxU16* tris16, const PxU32* tris32, PxU32 numTris,
PxArray<PxU32>& resultPermute, RemapCallback* rc, PxReal sizePerfTradeOff01, PxMeshCookingHint::Enum hint);
};
}
#endif