// 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 { public: typedef PxHashSet 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 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& outAdjacencyIndices, PxArray& outAdjacencies, PxU32 vertexCount, PxArray 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 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 mTriangleIndexBuffer; PxArray 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& 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 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 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 adjacencyIndices; PxArray 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 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