// 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_WINDING_NUMBER_T_H #define GU_WINDING_NUMBER_T_H #include "GuTriangle.h" #include "foundation/PxArray.h" #include "foundation/PxHashMap.h" #include "foundation/PxVec3.h" #include "GuBVH.h" #include "GuAABBTreeQuery.h" #include "GuAABBTreeNode.h" #include "GuWindingNumberCluster.h" namespace physx { namespace Gu { using Triangle = Gu::IndexedTriangleT; template struct SecondOrderClusterApproximationT : public ClusterApproximationT { PxMat33 WeightedOuterProductSum; PX_FORCE_INLINE SecondOrderClusterApproximationT() {} PX_FORCE_INLINE SecondOrderClusterApproximationT(R radius, R areaSum, const V3& weightedCentroid, const V3& weightedNormalSum, const PxMat33& weightedOuterProductSum) : ClusterApproximationT(radius, areaSum, weightedCentroid, weightedNormalSum), WeightedOuterProductSum(weightedOuterProductSum) { } }; //Evaluates a first order winding number approximation for a given cluster (cluster = bunch of triangles) template PX_FORCE_INLINE R firstOrderClusterApproximation(const V3& weightedCentroid, const V3& weightedNormalSum, const V3& evaluationPoint) { const V3 dir = weightedCentroid - evaluationPoint; const R l = dir.magnitude(); return (R(0.25 / 3.141592653589793238462643383) / (l * l * l)) * weightedNormalSum.dot(dir); } template PX_FORCE_INLINE R clusterApproximation(const ClusterApproximationT& c, const V3& evaluationPoint) { return firstOrderClusterApproximation(c.WeightedCentroid, c.WeightedNormalSum, evaluationPoint); } //Evaluates a second order winding number approximation for a given cluster (cluster = bunch of triangles) template PX_FORCE_INLINE R secondOrderClusterApproximation(const V3& weightedCentroid, const V3& weightedNormalSum, const PxMat33& weightedOuterProductSum, const V3& evaluationPoint) { const V3 dir = weightedCentroid - evaluationPoint; const R l = dir.magnitude(); const R l2 = l * l; const R scaling = R(0.25 / 3.141592653589793238462643383) / (l2 * l); const R firstOrder = scaling * weightedNormalSum.dot(dir); const R scaling2 = -R(3.0) * scaling / l2; const R m11 = scaling + scaling2 * dir.x * dir.x, m12 = scaling2 * dir.x * dir.y, m13 = scaling2 * dir.x * dir.z; const R m21 = scaling2 * dir.y * dir.x, m22 = scaling + scaling2 * dir.y * dir.y, m23 = scaling2 * dir.y * dir.z; const R m31 = scaling2 * dir.z * dir.x, m32 = scaling2 * dir.z * dir.y, m33 = scaling + scaling2 * dir.z * dir.z; return firstOrder + (weightedOuterProductSum.column0.x * m11 + weightedOuterProductSum.column1.x * m12 + weightedOuterProductSum.column2.x * m13 + weightedOuterProductSum.column0.y * m21 + weightedOuterProductSum.column1.y * m22 + weightedOuterProductSum.column2.y * m23 + weightedOuterProductSum.column0.z * m31 + weightedOuterProductSum.column1.z * m32 + weightedOuterProductSum.column2.z * m33); } template PX_FORCE_INLINE R clusterApproximation(const SecondOrderClusterApproximationT& c, const V3& evaluationPoint) { return secondOrderClusterApproximation(c.WeightedCentroid, c.WeightedNormalSum, c.WeightedOuterProductSum, evaluationPoint); } //Computes parameters to approximately represent a cluster (cluster = bunch of triangles) to be used to compute a winding number approximation template void approximateCluster(const PxArray& triangleSet, PxU32 start, PxU32 end, const PxU32* triangles, const V3* points, const PxArray& triangleAreas, const PxArray& triangleNormalsTimesTriangleArea, const PxArray& triangleCentroids, ClusterApproximationT& cluster) { V3 weightedCentroid(0., 0., 0.); R areaSum = 0; V3 weightedNormalSum(0., 0., 0.); for (PxU32 i = start; i < end; ++i) { PxI32 triId = triangleSet[i]; areaSum += triangleAreas[triId]; weightedCentroid += triangleCentroids[triId] * triangleAreas[triId]; weightedNormalSum += triangleNormalsTimesTriangleArea[triId]; } weightedCentroid = weightedCentroid / areaSum; R radiusSquared = 0; for (PxU32 i = start; i < end; ++i) { PxI32 triId = triangleSet[i]; const PxU32* tri = &triangles[3 * triId]; R d2 = (weightedCentroid - points[tri[0]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; d2 = (weightedCentroid - points[tri[1]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; d2 = (weightedCentroid - points[tri[2]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; } cluster = ClusterApproximationT(PxSqrt(radiusSquared), areaSum, weightedCentroid, weightedNormalSum/*, weightedOuterProductSum*/); } //Computes parameters to approximately represent a cluster (cluster = bunch of triangles) to be used to compute a winding number approximation template void approximateCluster(const PxArray& triangleSet, PxU32 start, PxU32 end, const PxU32* triangles, const V3* points, const PxArray& triangleAreas, const PxArray& triangleNormalsTimesTriangleArea, const PxArray& triangleCentroids, SecondOrderClusterApproximationT& cluster) { V3 weightedCentroid(0., 0., 0.); R areaSum = 0; V3 weightedNormalSum(0., 0., 0.); for (PxU32 i = start; i < end; ++i) { PxI32 triId = triangleSet[i]; areaSum += triangleAreas[triId]; weightedCentroid += triangleCentroids[triId] * triangleAreas[triId]; weightedNormalSum += triangleNormalsTimesTriangleArea[triId]; } weightedCentroid = weightedCentroid / areaSum; R radiusSquared = 0; PxMat33 weightedOuterProductSum(PxZERO::PxZero); for (PxU32 i = start; i < end; ++i) { PxI32 triId = triangleSet[i]; const PxU32* tri = &triangles[3 * triId]; R d2 = (weightedCentroid - points[tri[0]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; d2 = (weightedCentroid - points[tri[1]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; d2 = (weightedCentroid - points[tri[2]]).magnitudeSquared(); if (d2 > radiusSquared) radiusSquared = d2; weightedOuterProductSum = weightedOuterProductSum + PxMat33::outer(triangleCentroids[triId] - weightedCentroid, triangleNormalsTimesTriangleArea[triId]); } cluster = SecondOrderClusterApproximationT(PxSqrt(radiusSquared), areaSum, weightedCentroid, weightedNormalSum, weightedOuterProductSum); } //Exact winding number evaluation, needs to be called for every triangle close to the winding number query point template PX_FORCE_INLINE R evaluateExact(V3 a, V3 b, V3 c, const V3& p) { const R twoOver4PI = R(0.5 / 3.141592653589793238462643383); a -= p; b -= p; c -= p; const R la = a.magnitude(), lb = b.magnitude(), lc = c.magnitude(); const R y = a.x * b.y * c.z - a.x * b.z * c.y - a.y * b.x * c.z + a.y * b.z * c.x + a.z * b.x * c.y - a.z * b.y * c.x; const R x = (la * lb * lc + (a.x * b.x + a.y * b.y + a.z * b.z) * lc + (b.x * c.x + b.y * c.y + b.z * c.z) * la + (c.x * a.x + c.y * a.y + c.z * a.z) * lb); return twoOver4PI * PxAtan2(y, x); } struct Section { PxI32 start; PxI32 end; Section(PxI32 s, PxI32 e) : start(s), end(e) {} }; //Helper method that recursively traverses the given BVH tree and computes a cluster approximation for every node and links it to the node template void precomputeClusterInformation(PxI32 nodeId, const BVHNode* tree, const PxU32* triangles, const PxU32 numTriangles, const V3* points, PxHashMap>& infos, const PxArray triangleAreas, const PxArray& triangleNormalsTimesTriangleArea, const PxArray& triangleCentroids) { PxArray stack; stack.pushBack(nodeId); PxArray
returnStack; PxArray triIndices; triIndices.reserve(numTriangles); infos.reserve(PxU32(1.2f*numTriangles)); while (stack.size() > 0) { nodeId = stack.popBack(); if (nodeId >= 0) { const BVHNode& node = tree[nodeId]; if (node.isLeaf()) { triIndices.pushBack(node.getPrimitiveIndex()); returnStack.pushBack(Section(triIndices.size() - 1, triIndices.size())); continue; } stack.pushBack(-nodeId - 1); //Marker for return index stack.pushBack(node.getPosIndex()); stack.pushBack(node.getPosIndex() + 1); } else { Section trianglesA = returnStack.popBack(); Section trianglesB = returnStack.popBack(); Section sum(trianglesB.start, trianglesA.end); nodeId = -nodeId - 1; ClusterApproximationT c; approximateCluster(triIndices, sum.start, sum.end, triangles, points, triangleAreas, triangleNormalsTimesTriangleArea, triangleCentroids, c); infos.insert(PxU32(nodeId), c); returnStack.pushBack(sum); } } } //Precomputes a cluster approximation for every node in the BVH tree template void precomputeClusterInformation(const BVHNode* tree, const PxU32* triangles, const PxU32 numTriangles, const V3* points, PxHashMap>& result, PxI32 rootNodeIndex) { PxArray triangleAreas; triangleAreas.resize(numTriangles); PxArray triangleNormalsTimesTriangleArea; triangleNormalsTimesTriangleArea.resize(numTriangles); PxArray triangleCentroids; triangleCentroids.resize(numTriangles); for (PxU32 i = 0; i < numTriangles; ++i) { const PxU32* tri = &triangles[3 * i]; const V3& a = points[tri[0]]; const V3& b = points[tri[1]]; const V3& c = points[tri[2]]; triangleNormalsTimesTriangleArea[i] = (b - a).cross(c - a) * R(0.5); triangleAreas[i] = triangleNormalsTimesTriangleArea[i].magnitude(); triangleCentroids[i] = (a + b + c) * R(1.0 / 3.0); } result.clear(); precomputeClusterInformation(rootNodeIndex, tree, triangles, numTriangles, points, result, triangleAreas, triangleNormalsTimesTriangleArea, triangleCentroids); } template class WindingNumberTraversalController { public: R mWindingNumber = 0; private: const PxU32* mTriangles; const V3* mPoints; const PxHashMap>& mClusters; V3 mQueryPoint; R mDistanceThresholdBeta; public: PX_FORCE_INLINE WindingNumberTraversalController(const PxU32* triangles, const V3* points, const PxHashMap>& clusters, const V3& queryPoint, R distanceThresholdBeta = 2) : mTriangles(triangles), mPoints(points), mClusters(clusters), mQueryPoint(queryPoint), mDistanceThresholdBeta(distanceThresholdBeta) { } PX_FORCE_INLINE Gu::TraversalControl::Enum analyze(const BVHNode& node, PxI32 nodeIndex) { if (node.isLeaf()) { PX_ASSERT(node.getNbPrimitives() == 1); const PxU32* tri = &mTriangles[3 * node.getPrimitiveIndex()]; mWindingNumber += evaluateExact(mPoints[tri[0]], mPoints[tri[1]], mPoints[tri[2]], mQueryPoint); return Gu::TraversalControl::eDontGoDeeper; } const ClusterApproximationT& cluster = mClusters.find(nodeIndex)->second; const R distSquared = (mQueryPoint - cluster.WeightedCentroid).magnitudeSquared(); const R threshold = mDistanceThresholdBeta * cluster.Radius; if (distSquared > threshold * threshold) { //mWindingNumber += secondOrderClusterApproximation(cluster.WeightedCentroid, cluster.WeightedNormalSum, cluster.WeightedOuterProductSum, mQueryPoint); mWindingNumber += firstOrderClusterApproximation(cluster.WeightedCentroid, cluster.WeightedNormalSum, mQueryPoint); // secondOrderClusterApproximation(cluster.WeightedCentroid, cluster.WeightedNormalSum, cluster.WeightedOuterProductSum, mQueryPoint); return Gu::TraversalControl::eDontGoDeeper; } return Gu::TraversalControl::eGoDeeper; } private: PX_NOCOPY(WindingNumberTraversalController) }; template R computeWindingNumber(const BVHNode* tree, const V3& q, R beta, const PxHashMap>& clusters, const PxU32* triangles, const V3* points) { WindingNumberTraversalController c(triangles, points, clusters, q, beta); traverseBVH>(tree, c); return c.mWindingNumber; } } } #endif