465 lines
14 KiB
C++
465 lines
14 KiB
C++
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of NVIDIA CORPORATION nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
|
|
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
|
|
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
|
|
|
|
#include "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
|