feat(physics): wire physx sdk into build

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

View File

@@ -0,0 +1,464 @@
// 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 "extensions/PxParticleClothCooker.h"
#include "foundation/PxArray.h"
#include "foundation/PxSort.h"
#include "foundation/PxHashMap.h"
#include "foundation/PxHashSet.h"
#include "GuInternal.h"
namespace physx
{
namespace ExtGpu
{
namespace
{
struct Edge
{
Edge(PxU32 v0, PxU32 v1, PxU32 triangle0, PxU32 triangle1, bool isLongest0, bool isLongest1)
{
a = PxMin(v0, v1);
b = PxMax(v0, v1);
triangleA = triangle0;
triangleB = triangle1;
longestA = isLongest0;
longestB = isLongest1;
PX_ASSERT_WITH_MESSAGE(a != b, "PxCreateInflatableFromMesh encountered a degenerate edge inside a triangle.");
}
PxU32 a, b;
PxU32 triangleA, triangleB;
bool longestA, longestB; //true if it is the longest edge of triangle
bool operator<(const Edge& other) const
{
if(a == other.a)
return b < other.b;
return a < other.a;
}
bool operator==(const Edge& other) const
{
if(a == other.a)
return b == other.b;
return false;
}
};
class EdgeHash
{
public:
uint32_t operator()(const Edge& e) const
{
return PxComputeHash(e.a) ^ PxComputeHash(e.b);
}
bool equal(const Edge& e0, const Edge& e1) const
{
return e0.a == e1.a && e0.b == e1.b;
}
};
class EdgeSet : public PxHashSet<Edge, EdgeHash>
{
public:
typedef PxHashSet<Edge, EdgeHash> Base;
typedef Base::Iterator Iterator;
void insert(const Edge& newEdge)
{
PX_ASSERT(newEdge.a < newEdge.b);
bool exists;
Edge* edge = mBase.create(newEdge, exists);
if (!exists)
{
PX_PLACEMENT_NEW(edge, Edge)(newEdge);
}
else
{
PX_ASSERT_WITH_MESSAGE(edge->triangleB == 0xffffffff, "Edge with more than 2 triangles found in PxCreateInflatableFromMesh");
edge->triangleB = newEdge.triangleA; //Add triangle info from duplicate to Unique edge
edge->longestB = newEdge.longestA;
}
}
};
template<typename T>
void partialSum(T* begin, T* end, T* dest)
{
*dest = *begin;
dest++;
begin++;
while(begin!=end)
{
*dest = dest[-1] + *begin;
dest++;
begin++;
}
}
/*
outAdjacencies is a list of adjacent vertices in outAdjacencies
outAdjacencyIndices is a list of indices to quickly find adjacent vertices in outAdjacencies.
all the adjacent vertices to vertex V are stored in outAdjacencies starting at outAdjacencyIndices[V] and ending at outAdjacencyIndices[V+1]
so the first vertex is outAdjacencies[outAdjacencyIndices[V]], and the last one is outAdjacencies[outAdjacencyIndices[V+1]-1]
*/
void gatherAdjacencies(PxArray<PxU32>& outAdjacencyIndices, PxArray<PxU32>& outAdjacencies,
PxU32 vertexCount, PxArray<Edge> const& inUniqueEdges, bool ignoreDiagonals = false
)
{
PX_ASSERT(outAdjacencyIndices.size() == 0);
PX_ASSERT(outAdjacencies.size() == 0);
outAdjacencyIndices.resize(vertexCount+1, 0);
//calculate valency
for(PxU32 i = 0; i < inUniqueEdges.size(); i++)
{
const Edge& edge = inUniqueEdges[i];
if(ignoreDiagonals && edge.longestA && edge.longestB)
continue;
outAdjacencyIndices[edge.a]++;
outAdjacencyIndices[edge.b]++;
}
partialSum(outAdjacencyIndices.begin(), outAdjacencyIndices.end(), outAdjacencyIndices.begin());
outAdjacencyIndices.back() = outAdjacencyIndices[vertexCount-1];
outAdjacencies.resize(outAdjacencyIndices.back(),0xffffffff);
for(PxU32 i = 0; i < inUniqueEdges.size(); i++)
{
const Edge& edge = inUniqueEdges[i];
if(ignoreDiagonals && edge.longestA && edge.longestB)
continue;
outAdjacencyIndices[edge.a]--;
outAdjacencies[outAdjacencyIndices[edge.a]]=edge.b;
outAdjacencyIndices[edge.b]--;
outAdjacencies[outAdjacencyIndices[edge.b]] = edge.a;
}
}
template <typename T, typename A>
A MaxArg(T const& vA, T const& vB, A const& aA, A const& aB)
{
if(vA > vB)
return aA;
return aB;
}
PxU32 GetOppositeVertex(PxU32* inTriangleIndices, PxU32 triangleIndex, PxU32 a, PxU32 b)
{
for(int i = 0; i<3; i++)
{
if(inTriangleIndices[triangleIndex+i] != a && inTriangleIndices[triangleIndex + i] !=b)
return inTriangleIndices[triangleIndex + i];
}
PX_ASSERT_WITH_MESSAGE(0, "Degenerate Triangle found in PxCreateInflatableFromMesh");
return 0;
}
Edge GetAlternateDiagonal(Edge const& edge, PxU32* inTriangleIndices)
{
PxU32 vA = GetOppositeVertex(inTriangleIndices, edge.triangleA, edge.a, edge.b);
PxU32 vB = GetOppositeVertex(inTriangleIndices, edge.triangleB, edge.a, edge.b);
bool longestA = true;
bool longestB = true;
PxU32 tA = 0xffffffff;
PxU32 tB = 0xffffffff;
return Edge(vA, vB, tA, tB, longestA, longestB);
}
} //namespace
class PxParticleClothCookerImpl : public PxParticleClothCooker, public PxUserAllocated
{
public:
PxParticleClothCookerImpl(PxU32 vertexCount, physx::PxVec4* inVertices, PxU32 triangleIndexCount, PxU32* inTriangleIndices,
PxU32 constraintTypeFlags, PxVec3 verticalDirection, PxReal bendingConstraintMaxAngle)
:
mVertexCount(vertexCount),
mVertices(inVertices),
mTriangleIndexCount(triangleIndexCount),
mTriangleIndices(inTriangleIndices),
mConstraintTypeFlags(constraintTypeFlags),
mVerticalDirection(verticalDirection),
mBendingConstraintMaxAngle(bendingConstraintMaxAngle)
{
}
virtual void release()
{
PX_DELETE_THIS;
}
/**
\brief generate the constraint and triangle per vertex information.
*/
virtual void cookConstraints(const PxParticleClothConstraint* constraints, const PxU32 numConstraints);
virtual PxU32* getTriangleIndices() { return mTriangleIndexBuffer.begin(); }
virtual PxU32 getTriangleIndicesCount() { return mTriangleIndexBuffer.size(); }
virtual PxParticleClothConstraint* getConstraints() { return mConstraintBuffer.begin(); }
virtual PxU32 getConstraintCount() { return mConstraintBuffer.size(); }
/**
\brief Computes the volume of a closed mesh and the contraintScale. Expects vertices in local space - 'close' to origin.
*/
virtual void calculateMeshVolume();
virtual float getMeshVolume() {return mMeshVolume;}
private:
PxArray<PxU32> mTriangleIndexBuffer;
PxArray<PxParticleClothConstraint> mConstraintBuffer;
PxU32 mVertexCount;
physx::PxVec4* mVertices; //we don't own this
PxU32 mTriangleIndexCount;
PxU32* mTriangleIndices; //we don't own this
PxU32 mConstraintTypeFlags;
PxVec3 mVerticalDirection;
float mBendingConstraintMaxAngle;
float mMeshVolume;
void addTriangle(PxArray<PxU32>& trianglesPerVertex, PxU32 triangleIndex)
{
for(int j = 0; j < 3; j++)
{
PxU32 vertexIndex = mTriangleIndices[triangleIndex + j];
mTriangleIndexBuffer.pushBack(vertexIndex);
trianglesPerVertex[vertexIndex]++;
}
}
};
void PxParticleClothCookerImpl::cookConstraints(const PxParticleClothConstraint* constraints, const PxU32 numConstraints)
{
EdgeSet edgeSet;
edgeSet.reserve(mTriangleIndexCount);
PxArray<PxU32> trianglesPerVertex;
trianglesPerVertex.resize(mVertexCount, 0);
mTriangleIndexBuffer.clear();
mTriangleIndexBuffer.reserve(mTriangleIndexCount);
mConstraintBuffer.clear();
mConstraintBuffer.reserve(mVertexCount*12);
//Add all edges to Edges
for(PxU32 i = 0; i<mTriangleIndexCount; i+=3)
{
//Get vertex indices
PxU32 v0 = mTriangleIndices[i + 0];
PxU32 v1 = mTriangleIndices[i + 1];
PxU32 v2 = mTriangleIndices[i + 2];
//Get vertex points
PxVec3 p0 = mVertices[v0].getXYZ();
PxVec3 p1 = mVertices[v1].getXYZ();
PxVec3 p2 = mVertices[v2].getXYZ();
//check which edge is the longest
float len0 = (p0 - p1).magnitude();
float len1 = (p1 - p2).magnitude();
float len2 = (p2 - p0).magnitude();
int longest = MaxArg(len0, PxMax(len1,len2), 0, MaxArg(len1, len2, 1, 2));
//Store edges
edgeSet.insert(Edge(v0, v1, i, 0xffffffff, longest == 0, false));
edgeSet.insert(Edge(v1, v2, i, 0xffffffff, longest == 1, false));
edgeSet.insert(Edge(v2, v0, i, 0xffffffff, longest == 2, false));
//Add triangle to mTriangleIndexBuffer and increment trianglesPerVertex values
addTriangle(trianglesPerVertex,i);
}
if (constraints)
{
//skip constraints cooking if provided by user
mConstraintBuffer.assign(constraints, constraints + numConstraints);
return;
}
trianglesPerVertex.clear();
trianglesPerVertex.shrink();
PxArray<Edge> uniqueEdges;
uniqueEdges.reserve(mTriangleIndexCount); //over allocate to avoid resizes
for (EdgeSet::Iterator iter = edgeSet.getIterator(); !iter.done(); ++iter)
{
const Edge& e = *iter;
uniqueEdges.pushBack(e);
}
//Maximum angle before it is a horizontal constraint
const float cosAngle45 = cosf(45.0f / 360.0f * PxTwoPi);
//Add all horizontal, vertical and shearing constraints
PxU32 constraintCount = uniqueEdges.size(); //we are going to push back more edges, but we don't need to process them
for(PxU32 i = 0; i < constraintCount; i++)
{
const Edge& edge = uniqueEdges[i];
PxParticleClothConstraint c;
c.particleIndexA = edge.a;
c.particleIndexB = edge.b;
//Get vertices's
PxVec3 vA = mVertices[c.particleIndexA].getXYZ();
PxVec3 vB = mVertices[c.particleIndexB].getXYZ();
//Calculate rest length
c.length = (vA - vB).magnitude();
if(edge.longestA && edge.longestB && (mConstraintTypeFlags & PxParticleClothConstraint::eTYPE_DIAGONAL_CONSTRAINT))
{
//Shearing constraint
c.constraintType = c.eTYPE_DIAGONAL_CONSTRAINT;
//add constraint
mConstraintBuffer.pushBack(c);
//We only have one of the quad diagonals in a triangle mesh, get the other one here
const Edge alternateEdge = GetAlternateDiagonal(edge, mTriangleIndices);
c.particleIndexA = alternateEdge.a;
c.particleIndexB = alternateEdge.b;
//Get vertices's
PxVec3 vA2 = mVertices[c.particleIndexA].getXYZ();
PxVec3 vB2 = mVertices[c.particleIndexB].getXYZ();
//Calculate rest length
c.length = (vA2 - vB2).magnitude();
//add constraint
mConstraintBuffer.pushBack(c);
if (mConstraintTypeFlags & PxParticleClothConstraint::eTYPE_DIAGONAL_BENDING_CONSTRAINT)
{
if (!edgeSet.contains(alternateEdge))
{
edgeSet.insert(alternateEdge);
uniqueEdges.pushBack(alternateEdge); //Add edge for bending constraint step
}
}
}
else
{
//horizontal/vertical constraint
PxVec3 dir = (vA - vB) / c.length;
if(mVerticalDirection.dot(dir)> cosAngle45)
c.constraintType = c.eTYPE_VERTICAL_CONSTRAINT;
else
c.constraintType = c.eTYPE_HORIZONTAL_CONSTRAINT;
if(mConstraintTypeFlags & c.constraintType)
{
//add constraint
mConstraintBuffer.pushBack(c);
}
}
}
if(!(mConstraintTypeFlags & PxParticleClothConstraint::eTYPE_BENDING_CONSTRAINT))
return;
//Get adjacency information needed for the bending constraints
PxArray<PxU32> adjacencyIndices;
PxArray<PxU32> adjacencies;
gatherAdjacencies(adjacencyIndices, adjacencies, mVertexCount, uniqueEdges, !(mConstraintTypeFlags & PxParticleClothConstraint::eTYPE_DIAGONAL_BENDING_CONSTRAINT));
//Maximum angle we consider to be parallel for the bending constraints
const float maxCosAngle = PxCos(mBendingConstraintMaxAngle);
for(PxU32 i = 0; i<mVertexCount; i++)
{
//For each vertex, find all adjacent vertex pairs, and add bending constraints for pairs that form roughly a straight line
PxVec3 center = mVertices[i].getXYZ();
for(PxU32 adjA = adjacencyIndices[i]; PxI32(adjA) < PxI32(adjacencyIndices[i+1])-1; adjA++)
{
PxVec3 a = mVertices[adjacencies[adjA]].getXYZ();
PxVec3 dir1 = (a-center).getNormalized();
float bestCosAngle = -1.0f;
PxU32 bestAdjB = 0xffffffff;
//Choose the most parallel adjB
for(PxU32 adjB = adjA+1; adjB < adjacencyIndices[i + 1]; adjB++)
{
PxVec3 b = mVertices[adjacencies[adjB]].getXYZ();
PxVec3 dir2 = (b - center).getNormalized();
float cosAngleAbs = PxAbs(dir1.dot(dir2));
if(cosAngleAbs > bestCosAngle)
{
bestCosAngle = cosAngleAbs;
bestAdjB = adjB;
}
}
//Check if the lines a-center and center-b are roughly parallel
if(bestCosAngle > maxCosAngle)
{
//Add bending constraint
PxParticleClothConstraint c;
c.particleIndexA = adjacencies[adjA];
c.particleIndexB = adjacencies[bestAdjB];
PX_ASSERT(c.particleIndexA != c.particleIndexB);
//Get vertices's
PxVec3 vA = mVertices[c.particleIndexA].getXYZ();
PxVec3 vB = mVertices[c.particleIndexB].getXYZ();
//Calculate rest length
c.length = (vA - vB).magnitude();
c.constraintType = c.eTYPE_BENDING_CONSTRAINT;
//add constraint
mConstraintBuffer.pushBack(c);
}
}
}
}
void PxParticleClothCookerImpl::calculateMeshVolume()
{
// the physx api takes volume*6 now.
mMeshVolume = Gu::computeTriangleMeshVolume(mVertices, mTriangleIndices, mTriangleIndexCount / 3) * 6.0f;
}
} // namespace ExtGpu
ExtGpu::PxParticleClothCooker* PxCreateParticleClothCooker(PxU32 vertexCount, PxVec4* inVertices, PxU32 triangleIndexCount, PxU32* inTriangleIndices,
PxU32 constraintTypeFlags, PxVec3 verticalDirection, PxReal bendingConstraintMaxAngle)
{
return PX_NEW(ExtGpu::PxParticleClothCookerImpl)(vertexCount, inVertices, triangleIndexCount, inTriangleIndices,
constraintTypeFlags, verticalDirection, bendingConstraintMaxAngle);
}
} // namespace physx