feat(physics): wire physx sdk into build
This commit is contained in:
75
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBVH.cpp
vendored
Normal file
75
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBVH.cpp
vendored
Normal 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));
|
||||
}
|
||||
|
||||
354
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBigConvexDataBuilder.cpp
vendored
Normal file
354
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBigConvexDataBuilder.cpp
vendored
Normal 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;
|
||||
}
|
||||
70
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBigConvexDataBuilder.h
vendored
Normal file
70
engine/third_party/physx/source/geomutils/src/cooking/GuCookingBigConvexDataBuilder.h
vendored
Normal 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
|
||||
709
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullBuilder.cpp
vendored
Normal file
709
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullBuilder.cpp
vendored
Normal 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;
|
||||
}
|
||||
|
||||
87
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullBuilder.h
vendored
Normal file
87
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullBuilder.h
vendored
Normal 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
|
||||
|
||||
|
||||
285
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullLib.cpp
vendored
Normal file
285
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullLib.cpp
vendored
Normal 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);
|
||||
}
|
||||
92
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullLib.h
vendored
Normal file
92
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullLib.h
vendored
Normal 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
|
||||
922
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullUtils.cpp
vendored
Normal file
922
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullUtils.cpp
vendored
Normal 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;
|
||||
}
|
||||
171
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullUtils.h
vendored
Normal file
171
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexHullUtils.h
vendored
Normal 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
|
||||
254
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMesh.cpp
vendored
Normal file
254
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMesh.cpp
vendored
Normal 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;
|
||||
}
|
||||
626
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMeshBuilder.cpp
vendored
Normal file
626
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMeshBuilder.cpp
vendored
Normal 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
|
||||
101
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMeshBuilder.h
vendored
Normal file
101
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexMeshBuilder.h
vendored
Normal 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
|
||||
1331
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexPolygonsBuilder.cpp
vendored
Normal file
1331
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexPolygonsBuilder.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
61
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexPolygonsBuilder.h
vendored
Normal file
61
engine/third_party/physx/source/geomutils/src/cooking/GuCookingConvexPolygonsBuilder.h
vendored
Normal 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
|
||||
|
||||
|
||||
301
engine/third_party/physx/source/geomutils/src/cooking/GuCookingGrbTriangleMesh.h
vendored
Normal file
301
engine/third_party/physx/source/geomutils/src/cooking/GuCookingGrbTriangleMesh.h
vendored
Normal 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
|
||||
100
engine/third_party/physx/source/geomutils/src/cooking/GuCookingHF.cpp
vendored
Normal file
100
engine/third_party/physx/source/geomutils/src/cooking/GuCookingHF.cpp
vendored
Normal 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;
|
||||
}
|
||||
2591
engine/third_party/physx/source/geomutils/src/cooking/GuCookingQuickHullConvexHullLib.cpp
vendored
Normal file
2591
engine/third_party/physx/source/geomutils/src/cooking/GuCookingQuickHullConvexHullLib.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
97
engine/third_party/physx/source/geomutils/src/cooking/GuCookingQuickHullConvexHullLib.h
vendored
Normal file
97
engine/third_party/physx/source/geomutils/src/cooking/GuCookingQuickHullConvexHullLib.h
vendored
Normal 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
|
||||
417
engine/third_party/physx/source/geomutils/src/cooking/GuCookingSDF.cpp
vendored
Normal file
417
engine/third_party/physx/source/geomutils/src/cooking/GuCookingSDF.cpp
vendored
Normal 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;
|
||||
}
|
||||
42
engine/third_party/physx/source/geomutils/src/cooking/GuCookingSDF.h
vendored
Normal file
42
engine/third_party/physx/source/geomutils/src/cooking/GuCookingSDF.h
vendored
Normal 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
|
||||
2944
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTetrahedronMesh.cpp
vendored
Normal file
2944
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTetrahedronMesh.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
81
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTetrahedronMesh.h
vendored
Normal file
81
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTetrahedronMesh.h
vendored
Normal 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
|
||||
1425
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTriangleMesh.cpp
vendored
Normal file
1425
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTriangleMesh.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
122
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTriangleMesh.h
vendored
Normal file
122
engine/third_party/physx/source/geomutils/src/cooking/GuCookingTriangleMesh.h
vendored
Normal 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
|
||||
733
engine/third_party/physx/source/geomutils/src/cooking/GuCookingVolumeIntegration.cpp
vendored
Normal file
733
engine/third_party/physx/source/geomutils/src/cooking/GuCookingVolumeIntegration.cpp
vendored
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
89
engine/third_party/physx/source/geomutils/src/cooking/GuCookingVolumeIntegration.h
vendored
Normal file
89
engine/third_party/physx/source/geomutils/src/cooking/GuCookingVolumeIntegration.h
vendored
Normal 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
|
||||
970
engine/third_party/physx/source/geomutils/src/cooking/GuRTreeCooking.cpp
vendored
Normal file
970
engine/third_party/physx/source/geomutils/src/cooking/GuRTreeCooking.cpp
vendored
Normal 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
|
||||
55
engine/third_party/physx/source/geomutils/src/cooking/GuRTreeCooking.h
vendored
Normal file
55
engine/third_party/physx/source/geomutils/src/cooking/GuRTreeCooking.h
vendored
Normal 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
|
||||
Reference in New Issue
Block a user