// 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. #include "extensions/PxSamplingExt.h" #include "GuSDF.h" #include "foundation/PxQuat.h" #include "foundation/PxBounds3.h" #include "foundation/PxHashSet.h" #include "GuDistancePointTriangle.h" #include "extensions/PxRemeshingExt.h" #include "geometry/PxGeometryQuery.h" #include "PxQueryReport.h" #include "foundation/PxHashMap.h" #include "CmRandom.h" #include "GuAABBTreeNode.h" #include "GuAABBTree.h" #include "GuAABBTreeBounds.h" #include "GuWindingNumber.h" #include "foundation/PxMathUtils.h" #include "foundation/PxSort.h" using namespace physx; using namespace Gu; using namespace Cm; static const PxI32 neighborEdges[3][2] = { { 0, 1 }, { 2, 0 }, { 1, 2 } }; namespace { struct Int3 { PxI32 x; PxI32 y; PxI32 z; Int3(PxI32 x_, PxI32 y_, PxI32 z_) : x(x_), y(y_), z(z_) {} Int3() : x(0), y(0), z(0) {} }; struct ActiveSample { PxI32 mIndex; PxArray mNearbyTriangles; PxArray mCumulativeTriangleAreas; ActiveSample() : mIndex(-1) {} ActiveSample(PxI32 index, const PxArray& nearbyTriangles, const PxArray& cumulativeTriangleAreas) { mIndex = index; mNearbyTriangles = nearbyTriangles; mCumulativeTriangleAreas = cumulativeTriangleAreas; } }; struct PointWithNormal { PxVec3 mPoint; PxVec3 mNormal; PointWithNormal() {} PointWithNormal(const PxVec3& point, const PxVec3& normal) : mPoint(point), mNormal(normal) { } }; struct IndexWithNormal { PxI32 mIndex; PxVec3 mNormal; IndexWithNormal(PxI32 index, const PxVec3& normal) : mIndex(index), mNormal(normal) { } }; } // PT: TODO: refactor with other SDK implementation static void getBoundsFromPoints(const PxVec3* points, const PxU32 numPoints, PxVec3& outMinExtents, PxVec3& outMaxExtents) { PxVec3 minExtents(FLT_MAX); PxVec3 maxExtents(-FLT_MAX); // calculate face bounds for (PxU32 i = 0; i < numPoints; ++i) { const PxVec3& a = points[i]; minExtents = a.minimum(minExtents); maxExtents = a.maximum(maxExtents); } outMinExtents = minExtents; outMaxExtents = maxExtents; } static PX_FORCE_INLINE PxReal triArea(const PxVec3& a, const PxVec3& b, const PxVec3& c) { return 0.5f * (b - a).cross(c - a).magnitude(); } static PX_FORCE_INLINE PxReal triArea(const PxU32* tri, const PxVec3* points) { return triArea(points[tri[0]], points[tri[1]], points[tri[2]]); } static PX_FORCE_INLINE PxU64 edgeKey(PxI32 a, PxI32 b) { if (a < b) return ((PxU64(a)) << 32) | (PxU64(b)); else return ((PxU64(b)) << 32) | (PxU64(a)); } static void buildTriangleAdjacency(const PxU32* tris, PxU32 numTriangles, PxArray& result) { PxU32 l = 4 * numTriangles; //4 elements per triangle - waste one entry per triangle to get a power of 2 which allows for bit shift usage instead of modulo result.clear(); result.resize(l, -1); for (PxU32 i = 3; i < l; i += 4) result[i] = -2; PxHashMap edges; for (PxU32 i = 0; i < numTriangles; ++i) { const PxU32* tri = &tris[3 * i]; for (PxU32 j = 0; j < 3; ++j) { const PxU64 edge = edgeKey(tri[neighborEdges[j][0]], tri[neighborEdges[j][1]]); const PxPair* it = edges.find(edge); if (it) { result[4u * i + j] = it->second; result[it->second] = 4u * i + j; } else { PxU32 v = 4u * i + j; edges.insert(edge, v); } } } } static void collectTrianglesInSphere(const PxVec3& center, PxReal radius, PxI32 startTri, const PxU32* triangles, const PxVec3* points, const PxArray& adj, PxHashSet& result) { PxArray stack; stack.pushBack(startTri); result.clear(); result.insert(startTri); while (stack.size() > 0) { PxI32 tri = stack.popBack() * 4; for (PxI32 i = 0; i < 3; ++i) { PxI32 n = adj[tri + i] >> 2; if (n >= 0 && !result.contains(n)) { const PxU32* t = &triangles[3 * n]; if (Gu::distancePointTriangleSquared(center, points[t[0]], points[t[1]] - points[t[0]], points[t[2]] - points[t[0]]) < radius * radius) { result.insert(n); stack.pushBack(n); } } } } } static void createActiveSample(const PxArray& triangleAreaBuffer, PxI32 sampleIndex, const PxVec3& sample, PxReal radius, PxI32 startTri, const PxU32* triangles, const PxVec3* points, const PxArray& adj, ActiveSample& result) { PxHashSet nearbyTriangles; collectTrianglesInSphere(sample, radius, startTri, triangles, points, adj, nearbyTriangles); result.mNearbyTriangles.clear(); result.mNearbyTriangles.reserve(nearbyTriangles.size()); //for (PxI32 t : nearbyTriangles) for (PxHashSet::Iterator iter = nearbyTriangles.getIterator(); !iter.done(); ++iter) result.mNearbyTriangles.pushBack(*iter); result.mCumulativeTriangleAreas.clear(); result.mCumulativeTriangleAreas.resize(nearbyTriangles.size()); result.mCumulativeTriangleAreas[0] = triangleAreaBuffer[result.mNearbyTriangles[0]]; for (PxU32 i = 1; i < nearbyTriangles.size(); ++i) result.mCumulativeTriangleAreas[i] = result.mCumulativeTriangleAreas[i - 1] + triangleAreaBuffer[result.mNearbyTriangles[i]]; result.mIndex = sampleIndex; } //Returns the index of the element with value <= v static PxU32 binarySearch(const PxArray& sorted, PxReal v) { PxU32 low = 0; PxU32 up = PxU32(sorted.size()); while (up - low > 1) { PxU32 middle = (up + low) >> 1; PxReal m = sorted[middle]; if (v <= m) up = middle; else low = middle; } return low; } static PxVec3 randomPointOnTriangle(BasicRandom& rnd, const PxU32* tri, const PxVec3* points, PxVec3* barycentricCoordinates = NULL) { while (true) { PxReal a = rnd.rand(0.0f, 1.0f); PxReal b = rnd.rand(0.0f, 1.0f); PxReal sum = a + b; if (sum > 1) continue; PxReal c = 1 - a - b; if (barycentricCoordinates) (*barycentricCoordinates) = PxVec3(a, b, c); return points[tri[0]] * a + points[tri[1]] * b + points[tri[2]] * c; } } static bool samplePointInBallOnSurface(BasicRandom& rnd, const PxArray& cumulativeAreas, const PxVec3* points, const PxU32* triangles, const PxArray& nearbyTriangles, const PxVec3& point, PxReal radius, PxVec3& sample, PxI32& triId, PxI32 numAttempts = 30, PxVec3* barycentricCoordinates = NULL) { triId = -1; //Use variable upper bound as described in http://extremelearning.com.au/an-improved-version-of-bridsons-algorithm-n-for-poisson-disc-sampling/ PxReal step = radius / numAttempts; PxReal rUpper = radius + step; PxVec3 fallback; PxReal fallbackDist = FLT_MAX; PxI32 fallbackId = -1; PxVec3 fallbackBary; for (PxI32 i = 0; i < numAttempts; ++i) { PxReal totalArea = cumulativeAreas[cumulativeAreas.size() - 1]; PxReal r = rnd.rand(0.0f, 1.0f) * totalArea; PxI32 id; id = binarySearch(cumulativeAreas, r); triId = nearbyTriangles[id]; sample = randomPointOnTriangle(rnd, &triangles[3 * triId], points, barycentricCoordinates); const PxReal dist2 = (sample - point).magnitudeSquared(); if (dist2 > radius * radius && dist2 < rUpper * rUpper) return true; if (dist2 > radius * radius && dist2 < 4 * radius * radius && dist2 < fallbackDist) { fallbackDist = dist2; fallbackId = triId; fallback = sample; if (barycentricCoordinates) fallbackBary = *barycentricCoordinates; } rUpper += step; } if (fallbackId >= 0) { sample = fallback; triId = fallbackId; if (barycentricCoordinates) *barycentricCoordinates = fallbackBary; return true; } return false; } namespace { class PoissonSamplerShared { private: struct SparseGridNode { PxI32 mPointIndex; PxI32 mExcessStartIndex; PxI32 mExcessEndIndex; SparseGridNode(PxI32 pointIndex_) : mPointIndex(pointIndex_), mExcessStartIndex(0), mExcessEndIndex(-1) { } SparseGridNode() : mPointIndex(-1), mExcessStartIndex(0), mExcessEndIndex(-1) { } }; //Returns true if successful. False if too many cells are required (overflow) bool rebuildSparseGrid(); public: PoissonSamplerShared() : maxNumSamples(0), currentSamplingRadius(0.0f) {} //Returns true if successful. False if too many cells are required (overflow) bool setSamplingRadius(PxReal r); void addSamples(const PxArray& samples); PxU32 removeSamples(const PxArray& samples); PxI32 findSample(const PxVec3& p); PxReal minDistanceToOtherSamplesSquared(const PxVec3& p) const; const PxArray& getSamples() const { return result; } bool addPointToSparseGrid(const PxVec3& p, PxI32 pointIndex); protected: bool postAddPointToSparseGrid(const PxVec3& p, PxI32 pointIndex); bool preAddPointToSparseGrid(const PxVec3& p, PxI32 pointIndex); public: //Input PxI32 numSampleAttemptsAroundPoint; PxVec3 size; PxVec3 min; PxU32 maxNumSamples; //Intermediate data PxReal currentSamplingRadius; Int3 resolution; PxReal cellSize; PxArray occupiedCellBits; PxHashMap sparseGrid3D; PxArray excessList; Cm::BasicRandom rnd; bool gridResolutionValid = false; //Output PxArray result; PX_NOCOPY(PoissonSamplerShared) }; struct PointInVolumeTester { virtual bool pointInVolume(const PxVec3& p) const = 0; virtual ~PointInVolumeTester() {} }; } static PX_FORCE_INLINE bool pointInSphere(const PxVec3& p, const PxVec3& sphereCenter, PxReal sphereRadius) { return (p - sphereCenter).magnitudeSquared() < sphereRadius * sphereRadius; } namespace { struct AlwaysInsideTester : public PointInVolumeTester { AlwaysInsideTester() {} virtual bool pointInVolume(const PxVec3&) const { return true; } virtual ~AlwaysInsideTester() {} }; struct PointInSphereTester : public PointInVolumeTester { PxVec3 mCenter; PxReal mRadius; PointInSphereTester(const PxVec3& center, const PxReal radius) : mCenter(center), mRadius(radius) {} virtual bool pointInVolume(const PxVec3& p) const { return pointInSphere(p, mCenter, mRadius); } virtual ~PointInSphereTester() {} }; struct PointInOBBTester : public PointInVolumeTester { PxVec3 mBoxCenter; PxVec3 mBoxAxisAlignedExtents; PxQuat mBoxOrientation; PointInOBBTester(const PxVec3& boxCenter, const PxVec3& boxAxisAlignedExtents, const PxQuat boxOrientation) : mBoxCenter(boxCenter), mBoxAxisAlignedExtents(boxAxisAlignedExtents), mBoxOrientation(boxOrientation) {} virtual bool pointInVolume(const PxVec3& p) const { PxVec3 localPoint = mBoxOrientation.rotateInv(p - mBoxCenter); return localPoint.x >= -mBoxAxisAlignedExtents.x && localPoint.x <= mBoxAxisAlignedExtents.x && localPoint.y >= -mBoxAxisAlignedExtents.y && localPoint.y <= mBoxAxisAlignedExtents.y && localPoint.z >= -mBoxAxisAlignedExtents.z && localPoint.z <= mBoxAxisAlignedExtents.z; } virtual ~PointInOBBTester() {} }; class TriangleMeshPoissonSampler : public PxTriangleMeshPoissonSampler { public: TriangleMeshPoissonSampler(const PxU32* tris, PxU32 numTris, const PxVec3* pts_, PxU32 numPts, PxReal r, PxI32 numSampleAttemptsAroundPoint_ = 30, PxU32 maxNumSamples_ = 0); virtual void addSamplesInVolume(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples); virtual void addSamplesInSphere(const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples); virtual void addSamplesInBox(const PxBounds3& axisAlignedBox, const PxQuat& boxOrientation, bool createVolumeSamples); virtual const PxArray& getSampleTriangleIds() const { return triangleIds; } virtual const PxArray& getSampleBarycentrics() const { return barycentricCoordinates; } virtual bool setSamplingRadius(PxReal samplingRadius) { return poissonSamplerShared.setSamplingRadius(samplingRadius); } virtual void addSamples(const PxArray& samples) { poissonSamplerShared.addSamples(samples); } void createVolumeSamples(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, PxReal randomScale, PxReal r, bool addToSparseGrid = true); virtual PxU32 removeSamples(const PxArray& samples) { return poissonSamplerShared.removeSamples(samples); } virtual const PxArray& getSamples() const { return poissonSamplerShared.result; } virtual bool isPointInTriangleMesh(const PxVec3& p); virtual ~TriangleMeshPoissonSampler() { } public: bool pointInMesh(const PxVec3& p); PoissonSamplerShared poissonSamplerShared; //Input const PxVec3* originalPoints; const PxU32* originalTriangles; const PxU32 numOriginalTriangles; PxVec3 max; PxArray points; PxArray triangles; PxArray triangleMap; PxArray triangleAreaBuffer; PxArray adj; PxArray tree; PxHashMap clusters; //Intermediate data PxArray activeSamples; //Output PxArray triangleIds; PxArray barycentricCoordinates; PX_NOCOPY(TriangleMeshPoissonSampler) }; class ShapePoissonSampler : public PxPoissonSampler { public: ShapePoissonSampler(const PxGeometry& geometry_, const PxTransform& transform_, const PxBounds3& worldBounds_, PxReal r, PxI32 numSampleAttemptsAroundPoint_ = 30, PxU32 maxNumSamples_ = 0); virtual void addSamplesInVolume(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples); virtual void addSamplesInSphere(const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples); virtual void addSamplesInBox(const PxBounds3& axisAlignedBox, const PxQuat& boxOrientation, bool createVolumeSamples); virtual bool setSamplingRadius(PxReal samplingRadius) { return poissonSamplerShared.setSamplingRadius(samplingRadius); } virtual void addSamples(const PxArray& samples) { poissonSamplerShared.addSamples(samples); } void createVolumeSamples(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, PxReal randomScale, PxReal r, bool addToSparseGrid = true); virtual PxU32 removeSamples(const PxArray& samples) { return poissonSamplerShared.removeSamples(samples); } virtual const PxArray& getSamples() const { return poissonSamplerShared.result; } virtual ~ShapePoissonSampler() { } public: PoissonSamplerShared poissonSamplerShared; //Input const PxGeometry& shape; const PxTransform actorGlobalPose; //Intermediate data PxArray activeSamples; }; } bool TriangleMeshPoissonSampler::pointInMesh(const PxVec3& p) { return Gu::computeWindingNumber(tree.begin(), p, clusters, originalTriangles, originalPoints) > 0.5f; } PxPoissonSampler* physx::PxCreateShapeSampler(const PxGeometry& geometry, const PxTransform& transform, const PxBounds3& worldBounds, PxReal r, PxI32 numSampleAttemptsAroundPoint) { return PX_NEW(ShapePoissonSampler)(geometry, transform, worldBounds, r, numSampleAttemptsAroundPoint); } PxTriangleMeshPoissonSampler* physx::PxCreateTriangleMeshSampler(const PxU32* tris, PxU32 numTris, const PxVec3* pts, PxU32 numPts, PxReal r, PxI32 numSampleAttemptsAroundPoint) { return PX_NEW(TriangleMeshPoissonSampler)(tris, numTris, pts, numPts, r, numSampleAttemptsAroundPoint); } static PxVec3 computeBarycentricCoordinates(PxVec3 p, const PxVec3& a, PxVec3 b, PxVec3 c) { PxVec4 bary; PxComputeBarycentric(a, b, c, p, bary); return PxVec3(bary.x, bary.y, bary.z); } ShapePoissonSampler::ShapePoissonSampler(const PxGeometry& shape_, const PxTransform& actorGlobalPose_, const PxBounds3& worldBounds_, PxReal r, PxI32 numSampleAttemptsAroundPoint_, PxU32 maxNumSamples_) : shape(shape_), actorGlobalPose(actorGlobalPose_) { poissonSamplerShared.size = worldBounds_.maximum - worldBounds_.minimum; poissonSamplerShared.min = worldBounds_.minimum; poissonSamplerShared.numSampleAttemptsAroundPoint = numSampleAttemptsAroundPoint_; poissonSamplerShared.maxNumSamples = maxNumSamples_; setSamplingRadius(r); } TriangleMeshPoissonSampler::TriangleMeshPoissonSampler(const PxU32* tris, PxU32 numTris, const PxVec3* pts_, PxU32 numPts, PxReal r, PxI32 numSampleAttemptsAroundPoint_, PxU32 maxNumSamples_) : originalPoints(pts_), originalTriangles(tris), numOriginalTriangles(numTris) { poissonSamplerShared.currentSamplingRadius = 0.0f; poissonSamplerShared.numSampleAttemptsAroundPoint = numSampleAttemptsAroundPoint_; poissonSamplerShared.maxNumSamples = maxNumSamples_; getBoundsFromPoints(originalPoints, numPts, poissonSamplerShared.min, max); poissonSamplerShared.size = max - poissonSamplerShared.min; points.assign(originalPoints, originalPoints + numPts); triangles.assign(tris, tris + 3 * numTris); PxRemeshingExt::limitMaxEdgeLength(triangles, points, 2.0f * r, 100, &triangleMap, PxMax(10000u, 4 * numTris)); PxU32 numTriangles = triangles.size() / 3; triangleAreaBuffer.resize(numTriangles); for (PxU32 i = 0; i < numTriangles; ++i) triangleAreaBuffer[i] = triArea(&triangles[3 * i], points.begin()); buildTriangleAdjacency(triangles.begin(), numTriangles, adj); setSamplingRadius(r); } void PoissonSamplerShared::addSamples(const PxArray& samples) { if (samples.size() > 0) { for (PxU32 i = 0; i < samples.size(); ++i) { result.pushBack(samples[i]); } rebuildSparseGrid(); } } PxI32 PoissonSamplerShared::findSample(const PxVec3& p) { PxI32 x = PxI32((p.x - min.x) / cellSize); PxI32 y = PxI32((p.y - min.y) / cellSize); PxI32 z = PxI32((p.z - min.z) / cellSize); if (x >= resolution.x) x = resolution.x - 1; if (y >= resolution.y) y = resolution.y - 1; if (z >= resolution.z) z = resolution.z - 1; PxReal minDist = FLT_MAX; PxI32 index = -1; for (PxI32 oX = -1; oX <= 1; ++oX) { for (PxI32 oY = -1; oY <= 1; ++oY) { for (PxI32 oZ = -1; oZ <= 1; ++oZ) { const PxI32 xx = x + oX; const PxI32 yy = y + oY; const PxI32 zz = z + oZ; if (xx >= 0 && xx < resolution.x && yy >= 0 && yy < resolution.y && zz >= 0 && zz < resolution.z) { PxI32 cellIndex = xx + resolution.x * yy + (resolution.x * resolution.y) * zz; if ((occupiedCellBits[cellIndex >> 5] & (1u << (cellIndex & 31))) != 0) { const PxPair* it = sparseGrid3D.find(cellIndex); if (it) { const PxReal dist2 = (result[it->second.mPointIndex] - p).magnitudeSquared(); if (dist2 < minDist) { minDist = dist2; index = it->second.mPointIndex; } if (it->second.mExcessStartIndex >= 0) { for (PxI32 i = it->second.mExcessStartIndex; i < it->second.mExcessEndIndex; ++i) { const PxReal dist2_ = (result[excessList[i]] - p).magnitudeSquared(); if (dist2_ < minDist) { minDist = dist2_; index = it->second.mPointIndex; } } } if (minDist == 0.0f) { return index; } } } } } } } return -1; } PxU32 PoissonSamplerShared::removeSamples(const PxArray& samples) { if (samples.size() > 0) { PxArray samplesToRemove; samplesToRemove.reserve(samples.size()); for (PxU32 i = 0; i < samples.size(); ++i) { PxI32 index = findSample(samples[i]); PX_ASSERT(samples[i] == result[index]); if (index >= 0) samplesToRemove.pushBack(index); } PxSort(samplesToRemove.begin(), samplesToRemove.size()); PxI32 counter = 0; for (PxI32 i = PxI32(samplesToRemove.size()) - 1; i >= 0; --i) { result[samplesToRemove[i]] = result[result.size() - 1 - counter]; ++counter; } result.removeRange(result.size() - counter, counter); rebuildSparseGrid(); return samplesToRemove.size(); } return 0; } bool PoissonSamplerShared::setSamplingRadius(PxReal r) { if (r != currentSamplingRadius) { currentSamplingRadius = r; return rebuildSparseGrid(); } return gridResolutionValid; } bool PoissonSamplerShared::rebuildSparseGrid() { const PxReal dimension = 3.0f; cellSize = (currentSamplingRadius / PxSqrt(dimension)) * 0.9999f; const PxF64 cellsX = PxF64(size.x) / PxF64(cellSize); const PxF64 cellsY = PxF64(size.y) / PxF64(cellSize); const PxF64 cellsZ = PxF64(size.z) / PxF64(cellSize); resolution = Int3(PxMax(1, PxI32(ceil(cellsX))), PxMax(1, PxI32(ceil(cellsY))), PxMax(1, PxI32(ceil(cellsZ)))); const PxF64 numCellsDbl = PxF64(resolution.x) * PxF64(resolution.y) * PxI64(resolution.z); if (numCellsDbl >= (1u << 31) || cellsX >= (1u << 31) || cellsY >= (1u << 31) || cellsZ >= (1u << 31)) { gridResolutionValid = false; PxGetFoundation().error(physx::PxErrorCode::eINVALID_PARAMETER, PX_FL, "Internal grid resolution of sampler too high. Either a smaller mesh or a bigger radius must be used."); return false; } gridResolutionValid = true; PxU32 numCells = PxU32(resolution.x * resolution.y * resolution.z); occupiedCellBits.clear(); occupiedCellBits.resize((numCells + 32 - 1) / 32, 0); sparseGrid3D.clear(); for (PxU32 i = 0; i < result.size(); ++i) preAddPointToSparseGrid(result[i], i); PxI32 cumulativeSum = 0; for (PxHashMap::Iterator iter = sparseGrid3D.getIterator(); !iter.done(); ++iter) { if (iter->second.mExcessStartIndex > 0) { PxI32 start = cumulativeSum; cumulativeSum += iter->second.mExcessStartIndex; iter->second.mExcessStartIndex = start; iter->second.mExcessEndIndex = start - 1; } else { iter->second.mExcessStartIndex = -1; } } excessList.resize(cumulativeSum); for (PxU32 i = 0; i < result.size(); ++i) postAddPointToSparseGrid(result[i], i); return true; } bool PoissonSamplerShared::postAddPointToSparseGrid(const PxVec3& p, PxI32 pointIndex) { PxI32 x = PxI32((p.x - min.x) / cellSize); PxI32 y = PxI32((p.y - min.y) / cellSize); PxI32 z = PxI32((p.z - min.z) / cellSize); if (x >= resolution.x) x = resolution.x - 1; if (y >= resolution.y) y = resolution.y - 1; if (z >= resolution.z) z = resolution.z - 1; PxI32 cellIndex = x + resolution.x * y + (resolution.x * resolution.y) * z; SparseGridNode& n = sparseGrid3D[cellIndex]; if (n.mExcessStartIndex < 0) { PX_ASSERT(n.mPointIndex == pointIndex); return true; } if (n.mExcessEndIndex < n.mExcessStartIndex) { PX_ASSERT(n.mPointIndex == pointIndex); n.mExcessEndIndex++; return true; } else { excessList[n.mExcessEndIndex] = pointIndex; n.mExcessEndIndex++; return true; } } bool PoissonSamplerShared::preAddPointToSparseGrid(const PxVec3& p, PxI32 pointIndex) { PxI32 x = PxI32((p.x - min.x) / cellSize); PxI32 y = PxI32((p.y - min.y) / cellSize); PxI32 z = PxI32((p.z - min.z) / cellSize); if (x >= resolution.x) x = resolution.x - 1; if (y >= resolution.y) y = resolution.y - 1; if (z >= resolution.z) z = resolution.z - 1; PxI32 cellIndex = x + resolution.x * y + (resolution.x * resolution.y) * z; if ((occupiedCellBits[cellIndex >> 5] & (1u << (cellIndex & 31))) != 0) { SparseGridNode& n = sparseGrid3D[cellIndex]; n.mExcessStartIndex++; return true; } else { sparseGrid3D.insert(cellIndex, SparseGridNode(pointIndex)); occupiedCellBits[cellIndex >> 5] ^= (1u << (cellIndex & 31)); return true; } } bool PoissonSamplerShared::addPointToSparseGrid(const PxVec3& p, PxI32 pointIndex) { PxI32 x = PxI32((p.x - min.x) / cellSize); PxI32 y = PxI32((p.y - min.y) / cellSize); PxI32 z = PxI32((p.z - min.z) / cellSize); if (x >= resolution.x) x = resolution.x - 1; if (y >= resolution.y) y = resolution.y - 1; if (z >= resolution.z) z = resolution.z - 1; PxI32 cellIndex = x + resolution.x * y + (resolution.x * resolution.y) * z; //if (sparseGrid3D.ContainsKey(cellIndex)) // return false; sparseGrid3D.insert(cellIndex, pointIndex); occupiedCellBits[cellIndex >> 5] ^= (1u << (cellIndex & 31)); return true; } PxReal PoissonSamplerShared::minDistanceToOtherSamplesSquared(const PxVec3& p) const { PxI32 x = PxI32((p.x - min.x) / cellSize); PxI32 y = PxI32((p.y - min.y) / cellSize); PxI32 z = PxI32((p.z - min.z) / cellSize); if (x >= resolution.x) x = resolution.x - 1; if (y >= resolution.y) y = resolution.y - 1; if (z >= resolution.z) z = resolution.z - 1; PxReal minDist = FLT_MAX; for (PxI32 oX = -2; oX <= 2; ++oX) { for (PxI32 oY = -2; oY <= 2; ++oY) { for (PxI32 oZ = -2; oZ <= 2; ++oZ) { const PxI32 xx = x + oX; const PxI32 yy = y + oY; const PxI32 zz = z + oZ; if (xx >= 0 && xx < resolution.x && yy >= 0 && yy < resolution.y && zz >= 0 && zz < resolution.z) { PxI32 cellIndex = xx + resolution.x * yy + (resolution.x * resolution.y) * zz; if ((occupiedCellBits[cellIndex >> 5] & (1u << (cellIndex & 31))) != 0) { const PxPair* it = sparseGrid3D.find(cellIndex); if (it) { const PxReal dist2 = (result[it->second.mPointIndex] - p).magnitudeSquared(); if (dist2 < minDist) minDist = dist2; if (it->second.mExcessStartIndex >= 0) { for (PxI32 i = it->second.mExcessStartIndex; i < it->second.mExcessEndIndex; ++i) { const PxReal dist2_ = (result[excessList[i]] - p).magnitudeSquared(); if (dist2_ < minDist) minDist = dist2_; } } } } } } } } return minDist; } static void buildTree(const PxU32* triangles, const PxU32 numTriangles, const PxVec3* points, PxArray& tree, PxF32 enlargement = 1e-4f) { //Computes a bounding box for every triangle in triangles Gu::AABBTreeBounds boxes; boxes.init(numTriangles); for (PxU32 i = 0; i < numTriangles; ++i) { const PxU32* tri = &triangles[3 * i]; PxBounds3 box = PxBounds3::empty(); box.include(points[tri[0]]); box.include(points[tri[1]]); box.include(points[tri[2]]); box.fattenFast(enlargement); boxes.getBounds()[i] = box; } Gu::buildAABBTree(numTriangles, boxes, tree); } bool TriangleMeshPoissonSampler::isPointInTriangleMesh(const PxVec3& p) { if (tree.size() == 0) { //Lazy initialization buildTree(originalTriangles, numOriginalTriangles, originalPoints, tree); Gu::precomputeClusterInformation(tree.begin(), originalTriangles, numOriginalTriangles, originalPoints, clusters); } return pointInMesh(p); } void TriangleMeshPoissonSampler::addSamplesInBox(const PxBounds3& axisAlignedBox, const PxQuat& boxOrientation, bool createVolumeSamples) { PointInOBBTester pointInOBB(axisAlignedBox.getCenter(), axisAlignedBox.getExtents(), boxOrientation); addSamplesInVolume(pointInOBB, axisAlignedBox.getCenter(), axisAlignedBox.getExtents().magnitude(), createVolumeSamples); } void TriangleMeshPoissonSampler::addSamplesInSphere(const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples) { PointInSphereTester pointInSphere(sphereCenter, sphereRadius); addSamplesInVolume(pointInSphere, sphereCenter, sphereRadius, createVolumeSamples); } //Ideally the sphere center is located on the mesh's surface void TriangleMeshPoissonSampler::addSamplesInVolume(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, bool volumeSamples) { PxArray localActiveSamples; for (PxU32 i = 0; i < activeSamples.size();) { if (activeSamples[i].mIndex >= PxI32(poissonSamplerShared.result.size())) { activeSamples[i] = activeSamples[activeSamples.size() - 1]; activeSamples.remove(activeSamples.size() - 1); continue; } if (pointInSphere(poissonSamplerShared.result[activeSamples[i].mIndex], sphereCenter, sphereRadius)) localActiveSamples.pushBack(i); ++i; } if (localActiveSamples.size() == 0) { const PxReal r = poissonSamplerShared.currentSamplingRadius; //Try to find a seed sample for (PxU32 i = 0; i < triangles.size(); i += 3) { PxVec3 p = (1.0f / 3.0f) * (points[triangles[i]] + points[triangles[i + 1]] + points[triangles[i + 2]]); PxReal triRadius = PxSqrt(PxMax((p - points[triangles[i]]).magnitudeSquared(), PxMax((p - points[triangles[i + 1]]).magnitudeSquared(), (p - points[triangles[i + 2]]).magnitudeSquared()))); PxReal sum = triRadius + sphereRadius; if ((p - sphereCenter).magnitudeSquared() < sum * sum) { bool success = false; for (PxI32 j = 0; j < 30; ++j) { PxVec3 sample = randomPointOnTriangle(poissonSamplerShared.rnd, triangles.begin() + i, points.begin()); if (poissonSamplerShared.minDistanceToOtherSamplesSquared(sample) > r * r) { if (pointInVolume.pointInVolume(sample)) { PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); PxU32 sampleTriId = i / 3; ActiveSample as; createActiveSample(triangleAreaBuffer, newSampleId, sample, 2 * r, sampleTriId, triangles.begin(), points.begin(), adj, as); localActiveSamples.pushBack(activeSamples.size()); activeSamples.pushBack(as); poissonSamplerShared.result.pushBack(sample); triangleIds.pushBack(triangleMap[sampleTriId]); { const PxU32 triId = triangleMap[sampleTriId]; const PxU32* origTri = &originalTriangles[3 * triId]; barycentricCoordinates.pushBack(computeBarycentricCoordinates(sample, originalPoints[origTri[0]], originalPoints[origTri[1]], originalPoints[origTri[2]])); } poissonSamplerShared.addPointToSparseGrid(sample, newSampleId); success = true; if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; break; } } } if (success) break; } } } //Start poisson sampling while (localActiveSamples.size() > 0) { const PxReal r = poissonSamplerShared.currentSamplingRadius; PxI32 localSampleIndex = poissonSamplerShared.rnd.rand32() % localActiveSamples.size(); PxI32 selectedActiveSample = localActiveSamples[localSampleIndex]; const ActiveSample& s = activeSamples[selectedActiveSample]; bool successDist = false; bool success = false; for (PxI32 i = 0; i < poissonSamplerShared.numSampleAttemptsAroundPoint; ++i) { PxI32 sampleTriId; PxVec3 barycentricCoordinate; PxVec3 sample; if (samplePointInBallOnSurface(poissonSamplerShared.rnd, s.mCumulativeTriangleAreas, points.begin(), triangles.begin(), s.mNearbyTriangles, poissonSamplerShared.result[s.mIndex], r, sample, sampleTriId, 30, &barycentricCoordinate)) { if (poissonSamplerShared.minDistanceToOtherSamplesSquared(sample) > r * r) { successDist = true; if (pointInVolume.pointInVolume(sample)) { PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); ActiveSample as; createActiveSample(triangleAreaBuffer, newSampleId, sample, 2 * r, sampleTriId, triangles.begin(), points.begin(), adj, as); localActiveSamples.pushBack(activeSamples.size()); activeSamples.pushBack(as); poissonSamplerShared.result.pushBack(sample); triangleIds.pushBack(triangleMap[sampleTriId]); { const PxU32 triId = triangleMap[sampleTriId]; const PxU32* origTri = &originalTriangles[3 * triId]; barycentricCoordinates.pushBack(computeBarycentricCoordinates(sample, originalPoints[origTri[0]], originalPoints[origTri[1]], originalPoints[origTri[2]])); } poissonSamplerShared.addPointToSparseGrid(sample, newSampleId); success = true; if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; break; } } } } if (!successDist) { PxU32 oldId = activeSamples.size() - 1; activeSamples[selectedActiveSample] = activeSamples[activeSamples.size() - 1]; activeSamples.remove(activeSamples.size() - 1); for (PxU32 i = 0; i < localActiveSamples.size(); ++i) { if (localActiveSamples[i] == oldId) { localActiveSamples[i] = selectedActiveSample; break; } } } if (!success) { localActiveSamples[localSampleIndex] = localActiveSamples[localActiveSamples.size() - 1]; localActiveSamples.remove(localActiveSamples.size() - 1); } } if (volumeSamples) { PxReal randomScale = 0.1f; //Relative to particleSpacing PxReal r = (1.0f + 2.0f * randomScale) * 1.001f * poissonSamplerShared.currentSamplingRadius; createVolumeSamples(pointInVolume, sphereCenter, sphereRadius, randomScale, r); } } void TriangleMeshPoissonSampler::createVolumeSamples(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, PxReal randomScale, PxReal r, bool addToSparseGrid) { if (tree.size() == 0) { //Lazy initialization buildTree(originalTriangles, numOriginalTriangles, originalPoints, tree); Gu::precomputeClusterInformation(tree.begin(), originalTriangles, numOriginalTriangles, originalPoints, clusters); } PxVec3 sphereBoxMin = PxVec3(sphereCenter.x - sphereRadius, sphereCenter.y - sphereRadius, sphereCenter.z - sphereRadius) - poissonSamplerShared.min; PxVec3 sphereBoxMax = PxVec3(sphereCenter.x + sphereRadius, sphereCenter.y + sphereRadius, sphereCenter.z + sphereRadius) - poissonSamplerShared.min; Int3 start(PxI32(PxFloor(sphereBoxMin.x / r)), PxI32(PxFloor(sphereBoxMin.y / r)), PxI32(PxFloor(sphereBoxMin.z / r))); Int3 end(PxI32(PxCeil(sphereBoxMax.x / r)), PxI32(PxCeil(sphereBoxMax.y / r)), PxI32(PxCeil(sphereBoxMax.z / r))); for (PxI32 x = start.x; x < end.x; ++x) { for (PxI32 y = start.y; y < end.y; ++y) { for (PxI32 z = start.z; z < end.z; ++z) { PxVec3 p = poissonSamplerShared.min + PxVec3(x * r, y * r, z * r); p += PxVec3(poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius, poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius, poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius); if (pointInVolume.pointInVolume(p)) { if (poissonSamplerShared.minDistanceToOtherSamplesSquared(p) > poissonSamplerShared.currentSamplingRadius * poissonSamplerShared.currentSamplingRadius && pointInMesh(p)) { PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); poissonSamplerShared.result.pushBack(p); if (addToSparseGrid) poissonSamplerShared.addPointToSparseGrid(p, newSampleId); triangleIds.pushBack(-1); barycentricCoordinates.pushBack(PxVec3(0.0f)); if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; } } } } } } static PxU32 PX_FORCE_INLINE raycast(const PxGeometry& geometry, const PxTransform& transform, const PxVec3& rayOrigin, const PxVec3& rayDir, PxReal maxDist, PxHitFlags hitFlags, PxU32 maxHits, PxRaycastHit* rayHits) { return PxGeometryQuery::raycast( rayOrigin, rayDir, geometry, transform, maxDist, hitFlags, maxHits, rayHits); } static bool PX_FORCE_INLINE pointInShape(const PxGeometry& geometry, const PxTransform& transform, const PxVec3& p) { //This is a bit a work-around solution: When a raycast starts inside of a shape, it returns the ray origin as hit point PxRaycastHit hit; return PxGeometryQuery::raycast(p, PxVec3(1.0f, 0.0f, 0.0f), geometry, transform, 1.0f, PxHitFlag::ePOSITION, 1, &hit) > 0 && hit.position == p; } static bool projectionCallback(PxReal targetDistance, const PxGeometry& geometry, const PxTransform& transform, PxReal boundingBoxDiagonalLength, const PxVec3& p, const PxVec3& n, PointWithNormal& result) { PxRaycastHit hitPos; PxU32 numHitsPos = raycast(geometry, transform, p - boundingBoxDiagonalLength * n, n, 2 * boundingBoxDiagonalLength, PxHitFlag::eMESH_BOTH_SIDES | PxHitFlag::ePOSITION | PxHitFlag::eNORMAL, 1, &hitPos); PxRaycastHit hitNeg; PxU32 numHitsNeg = raycast(geometry, transform, p + boundingBoxDiagonalLength * n, -n, 2 * boundingBoxDiagonalLength, PxHitFlag::eMESH_BOTH_SIDES | PxHitFlag::ePOSITION | PxHitFlag::eNORMAL, 1, &hitNeg); targetDistance *= 0.5f; if (numHitsPos && numHitsNeg) { if (PxAbs((hitPos.position - p).magnitude() - targetDistance) < PxAbs((hitNeg.position - p).magnitude() - targetDistance)) result = PointWithNormal(hitPos.position, hitPos.normal); else result = PointWithNormal(hitNeg.position, hitNeg.normal); return true; } else if (numHitsPos) { result = PointWithNormal(hitPos.position, hitPos.normal); return true; } else if (numHitsNeg) { result = PointWithNormal(hitNeg.position, hitNeg.normal); return true; } return false; } static PxVec3 randomDirection(BasicRandom& rnd) { PxVec3 dir; do { dir = PxVec3((rnd.rand(0.0f, 1.0f) - 0.5f), (rnd.rand(0.0f, 1.0f) - 0.5f), (rnd.rand(0.0f, 1.0f) - 0.5f)); } while (dir.magnitudeSquared() < 1e-8f); return dir.getNormalized(); } static PxVec3 getPerpendicularVectorNormalized(const PxVec3& dir) { PxReal x = PxAbs(dir.x); PxReal y = PxAbs(dir.y); PxReal z = PxAbs(dir.z); if (x >= y && x >= z) return dir.cross(PxVec3(0, 1, 0)).getNormalized(); else if (y >= x && y >= z) return dir.cross(PxVec3(0, 0, 1)).getNormalized(); else return dir.cross(PxVec3(1, 0, 0)).getNormalized(); } static PxVec3 randomPointOnDisc(BasicRandom& rnd, const PxVec3& point, const PxVec3& normal, PxReal radius, PxReal rUpper) { //TODO: Use better random number generator PxReal r = radius + rnd.rand(0.0f, 1.0f) * (rUpper - radius); PxVec3 x = getPerpendicularVectorNormalized(normal); PxVec3 y = normal.cross(x).getNormalized(); PxReal angle = rnd.rand(0.0f, 1.0f) * (2.0f * 3.1415926535898f); return point + x * (r * PxCos(angle)) + y * (r * PxSin(angle)); } static bool samplePointInBallOnSurface(BasicRandom& rnd, const PxGeometry& shape, const PxTransform& actorGlobalPose, const PxReal diagonalLength, const PxVec3& point, const PxVec3& normal, PxReal radius, PointWithNormal& sample, PxI32 numAttempts = 30) { for (PxI32 i = 0; i < numAttempts; ++i) { PxVec3 p = randomPointOnDisc(rnd, point, normal, radius, 2 * radius); //Distort the direction of the normal a bit PxVec3 n = normal; do { n.x += 0.5f * (rnd.rand(0.0f, 1.0f) - 0.5f); n.y += 0.5f * (rnd.rand(0.0f, 1.0f) - 0.5f); n.z += 0.5f * (rnd.rand(0.0f, 1.0f) - 0.5f); } while (n.magnitudeSquared() < 1e-8f); n.normalize(); if (projectionCallback(radius, shape, actorGlobalPose, diagonalLength, p, n, sample)) { PxReal d2 = (sample.mPoint - point).magnitudeSquared(); if (d2 >= radius * radius && d2 < 4 * radius * radius) return true; } } return false; } void ShapePoissonSampler::addSamplesInBox(const PxBounds3& axisAlignedBox, const PxQuat& boxOrientation, bool createVolumeSamples) { PointInOBBTester pointInOBB(axisAlignedBox.getCenter(), axisAlignedBox.getExtents(), boxOrientation); addSamplesInVolume(pointInOBB, axisAlignedBox.getCenter(), axisAlignedBox.getExtents().magnitude(), createVolumeSamples); } void ShapePoissonSampler::addSamplesInSphere(const PxVec3& sphereCenter, PxReal sphereRadius, bool createVolumeSamples) { PointInSphereTester pointInSphere(sphereCenter, sphereRadius); addSamplesInVolume(pointInSphere, sphereCenter, sphereRadius, createVolumeSamples); } void ShapePoissonSampler::addSamplesInVolume(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, bool volumeSamples) { PxReal boundingBoxDiagonalLength = poissonSamplerShared.size.magnitude(); PxArray localActiveSamples; for (PxU32 i = 0; i < activeSamples.size();) { if (activeSamples[i].mIndex >= PxI32(poissonSamplerShared.result.size())) { activeSamples[i] = activeSamples[activeSamples.size() - 1]; activeSamples.remove(activeSamples.size() - 1); continue; } if (pointInSphere(poissonSamplerShared.result[activeSamples[i].mIndex], sphereCenter, sphereRadius)) localActiveSamples.pushBack(i); ++i; } if (localActiveSamples.size() == 0) { PxVec3 center = poissonSamplerShared.min + 0.5f * poissonSamplerShared.size; PointWithNormal sample; PxVec3 arbitrarySeedPointOnSurface; PxVec3 reference = sphereCenter - center; reference.normalizeSafe(); for (PxI32 i = 0; i < 1000; ++i) { PxVec3 dir = /*reference + 0.5f**/randomDirection(poissonSamplerShared.rnd); dir.normalize(); if (projectionCallback(poissonSamplerShared.currentSamplingRadius, shape, actorGlobalPose, boundingBoxDiagonalLength, sphereCenter, dir, sample)) { if (poissonSamplerShared.minDistanceToOtherSamplesSquared(sample.mPoint) > poissonSamplerShared.currentSamplingRadius * poissonSamplerShared.currentSamplingRadius) { if (pointInVolume.pointInVolume(sample.mPoint)) { PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); localActiveSamples.pushBack(activeSamples.size()); activeSamples.pushBack(IndexWithNormal(newSampleId, sample.mNormal)); poissonSamplerShared.result.pushBack(sample.mPoint); poissonSamplerShared.addPointToSparseGrid(sample.mPoint, newSampleId); if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; break; } } } } } while (localActiveSamples.size() > 0) { PxI32 localSampleIndex = poissonSamplerShared.rnd.rand32() % localActiveSamples.size(); PxI32 selectedActiveSample = localActiveSamples[localSampleIndex]; const IndexWithNormal& s = activeSamples[selectedActiveSample]; bool successDist = false; bool success = false; for (PxI32 i = 0; i < poissonSamplerShared.numSampleAttemptsAroundPoint; ++i) { PointWithNormal sample; if (samplePointInBallOnSurface(poissonSamplerShared.rnd, shape, actorGlobalPose, boundingBoxDiagonalLength, poissonSamplerShared.result[s.mIndex], s.mNormal, poissonSamplerShared.currentSamplingRadius, sample)) { if (poissonSamplerShared.minDistanceToOtherSamplesSquared(sample.mPoint) > poissonSamplerShared.currentSamplingRadius * poissonSamplerShared.currentSamplingRadius) { successDist = true; if (pointInVolume.pointInVolume(sample.mPoint)) { successDist = true; PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); localActiveSamples.pushBack(activeSamples.size()); activeSamples.pushBack(IndexWithNormal(newSampleId, sample.mNormal)); poissonSamplerShared.result.pushBack(sample.mPoint); poissonSamplerShared.addPointToSparseGrid(sample.mPoint, newSampleId); success = true; if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; break; } } } } if (!successDist) { PxU32 oldId = activeSamples.size() - 1; activeSamples[selectedActiveSample] = activeSamples[activeSamples.size() - 1]; activeSamples.remove(activeSamples.size() - 1); for (PxU32 i = 0; i < localActiveSamples.size(); ++i) { if (localActiveSamples[i] == oldId) { localActiveSamples[i] = selectedActiveSample; break; } } } if (!success) { localActiveSamples[localSampleIndex] = localActiveSamples[localActiveSamples.size() - 1]; localActiveSamples.remove(localActiveSamples.size() - 1); } } if (volumeSamples) { PxReal randomScale = 0.1f; //Relative to particleSpacing PxReal r = (1.0f + 2.0f * randomScale) * 1.001f * poissonSamplerShared.currentSamplingRadius; createVolumeSamples(pointInVolume, sphereCenter, sphereRadius, randomScale, r); } } void ShapePoissonSampler::createVolumeSamples(const PointInVolumeTester& pointInVolume, const PxVec3& sphereCenter, PxReal sphereRadius, PxReal randomScale, PxReal r, bool addToSparseGrid) { PxVec3 sphereBoxMin = PxVec3(sphereCenter.x - sphereRadius, sphereCenter.y - sphereRadius, sphereCenter.z - sphereRadius) - poissonSamplerShared.min; PxVec3 sphereBoxMax = PxVec3(sphereCenter.x + sphereRadius, sphereCenter.y + sphereRadius, sphereCenter.z + sphereRadius) - poissonSamplerShared.min; Int3 start(PxI32(PxFloor(sphereBoxMin.x / r)), PxI32(PxFloor(sphereBoxMin.y / r)), PxI32(PxFloor(sphereBoxMin.z / r))); Int3 end(PxI32(PxCeil(sphereBoxMax.x / r)), PxI32(PxCeil(sphereBoxMax.y / r)), PxI32(PxCeil(sphereBoxMax.z / r))); for (PxI32 x = start.x; x < end.x; ++x) { for (PxI32 y = start.y; y < end.y; ++y) { for (PxI32 z = start.z; z < end.z; ++z) { PxVec3 p = poissonSamplerShared.min + PxVec3(x * r, y * r, z * r); p += PxVec3(poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius, poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius, poissonSamplerShared.rnd.randomFloat32(-randomScale, randomScale) * poissonSamplerShared.currentSamplingRadius); if (pointInVolume.pointInVolume(p) && pointInShape(shape, actorGlobalPose, p)) { if (poissonSamplerShared.minDistanceToOtherSamplesSquared(p) > poissonSamplerShared.currentSamplingRadius * poissonSamplerShared.currentSamplingRadius) { PxI32 newSampleId = PxI32(poissonSamplerShared.result.size()); poissonSamplerShared.result.pushBack(p); if (addToSparseGrid) poissonSamplerShared.addPointToSparseGrid(p, newSampleId); if (poissonSamplerShared.maxNumSamples > 0 && poissonSamplerShared.result.size() >= poissonSamplerShared.maxNumSamples) return; } } } } } } //Use for triangle meshes //https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf bool PxSamplingExt::poissonSample(const PxSimpleTriangleMesh& mesh, PxReal r, PxArray& result, PxReal rVolume, PxArray* triangleIds, PxArray* barycentricCoordinates, const PxBounds3* axisAlignedBox, const PxQuat* boxOrientation, PxU32 maxNumSamples, PxU32 numSampleAttemptsAroundPoint) { TriangleMeshPoissonSampler sampler(reinterpret_cast(mesh.triangles.data), mesh.triangles.count, reinterpret_cast(mesh.points.data), mesh.points.count, r, numSampleAttemptsAroundPoint, maxNumSamples); if (!sampler.poissonSamplerShared.gridResolutionValid) { return false; } PxVec3 center = 0.5f*(sampler.max + sampler.poissonSamplerShared.min); PxReal boundingSphereRadius = 1.001f * (sampler.max - sampler.poissonSamplerShared.min).magnitude() * 0.5f; if (axisAlignedBox == NULL || boxOrientation == NULL) { sampler.addSamplesInSphere(center, boundingSphereRadius, false); if (rVolume > 0.0f) { AlwaysInsideTester tester; sampler.createVolumeSamples(tester, center, boundingSphereRadius, 0.1f, rVolume, false); } } else { sampler.addSamplesInBox(*axisAlignedBox, *boxOrientation, false); if (rVolume > 0.0f) { PointInOBBTester tester(axisAlignedBox->getCenter(), axisAlignedBox->getExtents(), *boxOrientation); sampler.createVolumeSamples(tester, center, boundingSphereRadius, 0.1f, rVolume, false); } } result = sampler.getSamples(); if (triangleIds) *triangleIds = sampler.getSampleTriangleIds(); if (barycentricCoordinates) *barycentricCoordinates = sampler.getSampleBarycentrics(); return true; } bool PxSamplingExt::poissonSample(const PxGeometry& geometry, const PxTransform& transform, const PxBounds3& worldBounds, PxReal r, PxArray& result, PxReal rVolume, const PxBounds3* axisAlignedBox, const PxQuat* boxOrientation, PxU32 maxNumSamples, PxU32 numSampleAttemptsAroundPoint) { PxVec3 center = worldBounds.getCenter(); ShapePoissonSampler sampler(geometry, transform, worldBounds, r, numSampleAttemptsAroundPoint, maxNumSamples); PxReal boundingSphereRadius = 1.001f * worldBounds.getExtents().magnitude(); if (!sampler.poissonSamplerShared.gridResolutionValid) return false; if (axisAlignedBox == NULL || boxOrientation == NULL) { sampler.addSamplesInSphere(center, worldBounds.getExtents().magnitude() * 1.001f, false); if (rVolume > 0.0f) { AlwaysInsideTester tester; sampler.createVolumeSamples(tester, center, boundingSphereRadius, 0.1f, rVolume, false); } } else { sampler.addSamplesInBox(*axisAlignedBox, *boxOrientation, false); if (rVolume > 0.0f) { PointInOBBTester tester(axisAlignedBox->getCenter(), axisAlignedBox->getExtents(), *boxOrientation); sampler.createVolumeSamples(tester, center, boundingSphereRadius, 0.1f, rVolume, false); } } result = sampler.getSamples(); return true; }