// This code contains NVIDIA Confidential Information and is disclosed to you // under a form of NVIDIA software license agreement provided separately to you. // // Notice // NVIDIA Corporation and its licensors retain all intellectual property and // proprietary rights in and to this software and related documentation and // any modifications thereto. Any use, reproduction, disclosure, or // distribution of this software and related documentation without an express // license agreement from NVIDIA Corporation is strictly prohibited. // // ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES // NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO // THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, // MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. // // Information and code furnished is believed to be accurate and reliable. // However, NVIDIA Corporation assumes no responsibility for the consequences of use of such // information or for any infringement of patents or other rights of third parties that may // result from its use. No license is granted by implication or otherwise under any patent // or patent rights of NVIDIA Corporation. Details are subject to change without notice. // This code supersedes and replaces all information previously supplied. // NVIDIA Corporation products are not authorized for use as critical // components in life support devices or systems without express written approval of // NVIDIA Corporation. // // Copyright (c) 2008-2020 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 "SwCollision.h" #include "SwCloth.h" #include "SwClothData.h" #include "IterationState.h" #include "BoundingBox.h" #include "PointInterpolator.h" #include "SwCollisionHelpers.h" #include #include // for memset #include "ps/PsSort.h" using namespace nv; using namespace physx; using namespace cloth; // the particle trajectory needs to penetrate more than 0.2 * radius to trigger continuous collision template const T4f cloth::SwCollision::sSkeletonWidth = simd4f(cloth::sqr(1 - 0.2f) - 1); #if NV_SIMD_SSE2 const Simd4i cloth::Gather::sIntSignBit = simd4i(0x80000000); const Simd4i cloth::Gather::sSignedMask = sIntSignBit | simd4i(0x7); #elif NV_SIMD_NEON const Simd4i cloth::Gather::sPack = simd4i(0x00000000, 0x04040404, 0x08080808, 0x0c0c0c0c); const Simd4i cloth::Gather::sOffset = simd4i(0x03020100); const Simd4i cloth::Gather::sShift = simd4i(2); const Simd4i cloth::Gather::sMask = simd4i(7); #endif namespace { const Simd4fTupleFactory sMaskX = simd4f(simd4i(~0, 0, 0, 0)); const Simd4fTupleFactory sMaskZ = simd4f(simd4i(0, 0, ~0, 0)); const Simd4fTupleFactory sMaskW = simd4f(simd4i(0, 0, 0, ~0)); const Simd4fTupleFactory gSimd4fOneXYZ = simd4f(1.0f, 1.0f, 1.0f, 0.0f); const Simd4fScalarFactory sGridLength = simd4f(8 - 1e-3f); // sGridSize const Simd4fScalarFactory sGridExpand = simd4f(1e-4f); const Simd4fTupleFactory sMinusFloatMaxXYZ = simd4f(-FLT_MAX, -FLT_MAX, -FLT_MAX, 0.0f); #if PX_PROFILE || PX_DEBUG template uint32_t horizontalSum(const T4f& x) { const float* p = array(x); return uint32_t(0.5f + p[0] + p[1] + p[2] + p[3]); } #endif // 7 elements are written to ptr! template void storeBounds(float* ptr, const cloth::BoundingBox& bounds) { store(ptr, bounds.mLower); store(ptr + 3, bounds.mUpper); } } struct cloth::SphereData { PxVec3 center; float radius; }; struct cloth::ConeData { PxVec3 center; float radius; // cone radius at center PxVec3 axis; float slope; // tan(alpha) float sqrCosine; // cos^2(alpha) float halfLength; uint32_t firstMask; uint32_t bothMask; }; struct cloth::TriangleData { PxVec3 base; float edge0DotEdge1; PxVec3 edge0; float edge0SqrLength; PxVec3 edge1; float edge1SqrLength; PxVec3 normal; float padding; float det; float denom; float edge0InvSqrLength; float edge1InvSqrLength; }; namespace nv { namespace cloth { template BoundingBox expandBounds(const BoundingBox& bbox, const SphereData* sIt, const SphereData* sEnd) { BoundingBox result = bbox; for (; sIt != sEnd; ++sIt) { T4f p = loadAligned(array(sIt->center)); T4f r = splat<3>(p); result.mLower = min(result.mLower, p - r); result.mUpper = max(result.mUpper, p + r); } return result; } } } namespace { template void generateSpheres(T4f* dIt, const SrcIterator& src, uint32_t count) { // have to copy out iterator to ensure alignment is maintained for (SrcIterator sIt = src; 0 < count--; ++sIt, ++dIt) *dIt = max(sMinusFloatMaxXYZ, *sIt); // clamp radius to 0 } void generateCones(cloth::ConeData* dst, const cloth::SphereData* sourceSpheres, const cloth::IndexPair* capsuleIndices, uint32_t numCones) { cloth::ConeData* cIt = dst; for (const cloth::IndexPair* iIt = capsuleIndices, *iEnd = iIt + numCones; iIt != iEnd; ++iIt, ++cIt) { // w element contains sphere radii PxVec4 first = reinterpret_cast(sourceSpheres[iIt->first]); PxVec4 second = reinterpret_cast(sourceSpheres[iIt->second]); PxVec4 center = (second + first) * 0.5f; PxVec4 axis = (second - first) * 0.5f; //half axis //axis.w = half of radii difference // |Axis|^2 float sqrAxisHalfLength = axis.x * axis.x + axis.y * axis.y + axis.z * axis.z; // http://jwilson.coe.uga.edu/emt669/Student.Folders/Kertscher.Jeff/Essay.3/Tangents.html // |Axis|^2 = |Cone|^2 + (sphere2Radius-sphere1Radius)^2 float sqrConeHalfLength = sqrAxisHalfLength - cloth::sqr(axis.w); float invAxisHalfLength = 1 / sqrtf(sqrAxisHalfLength); float invConeHalfLength = 1 / sqrtf(sqrConeHalfLength); if (sqrConeHalfLength <= 0.0f) invAxisHalfLength = invConeHalfLength = 0.0f; float axisHalfLength = sqrAxisHalfLength * invAxisHalfLength; float slope = axis.w * invConeHalfLength; cIt->center = PxVec3(center.x, center.y, center.z ); cIt->radius = (axis.w + first.w) * invConeHalfLength * axisHalfLength; //cone radius in the center cIt->axis = PxVec3(axis.x, axis.y, axis.z) * invAxisHalfLength; cIt->slope = slope; // cos()^2 = 1.0 - (radius difference / axis length)^2 // cos()^2 = 1.0 - (opposite/hypotenuse)^2 // cos()^2 = 1.0 - sin(angle between c2c1 and c2t1)^2 // cos()^2 = 1.0 - sin(angle between axis and c2t1)^2 cIt->sqrCosine = 1.0f - cloth::sqr(axis.w * invAxisHalfLength); cIt->halfLength = axisHalfLength; uint32_t firstMask = 0x1u << iIt->first; cIt->firstMask = firstMask; cIt->bothMask = firstMask | 0x1u << iIt->second; } } template void generatePlanes(T4f* dIt, const SrcIterator& src, uint32_t count) { // have to copy out iterator to ensure alignment is maintained for (SrcIterator sIt = src; 0 < count--; ++sIt, ++dIt) *dIt = *sIt; } template void generateTriangles(cloth::TriangleData* dIt, const SrcIterator& src, uint32_t count) { // have to copy out iterator to ensure alignment is maintained for (SrcIterator sIt = src; 0 < count--; ++dIt) { T4f p0 = *sIt; ++sIt; T4f p1 = *sIt; ++sIt; T4f p2 = *sIt; ++sIt; T4f edge0 = p1 - p0; T4f edge1 = p2 - p0; T4f normal = cross3(edge0, edge1); T4f edge0SqrLength = dot3(edge0, edge0); T4f edge1SqrLength = dot3(edge1, edge1); T4f edge0DotEdge1 = dot3(edge0, edge1); T4f normalInvLength = rsqrt(dot3(normal, normal)); T4f det = edge0SqrLength * edge1SqrLength - edge0DotEdge1 * edge0DotEdge1; T4f denom = edge0SqrLength + edge1SqrLength - edge0DotEdge1 - edge0DotEdge1; // there are definitely faster ways... T4f aux = select(sMaskX, det, denom); aux = select(sMaskZ, edge0SqrLength, aux); aux = select(sMaskW, edge1SqrLength, aux); storeAligned(&dIt->base.x, select(sMaskW, edge0DotEdge1, p0)); storeAligned(&dIt->edge0.x, select(sMaskW, edge0SqrLength, edge0)); storeAligned(&dIt->edge1.x, select(sMaskW, edge1SqrLength, edge1)); storeAligned(&dIt->normal.x, normal * normalInvLength); storeAligned(&dIt->det, recip<1>(aux)); } } } // namespace template cloth::SwCollision::CollisionData::CollisionData() : mSpheres(0), mCones(0) { } template cloth::SwCollision::SwCollision(SwClothData& clothData, SwKernelAllocator& alloc) : mClothData(clothData), mAllocator(alloc) { allocate(mCurData); if (mClothData.mEnableContinuousCollision || mClothData.mFrictionScale > 0.0f) { allocate(mPrevData); generateSpheres(reinterpret_cast(mPrevData.mSpheres), reinterpret_cast(clothData.mStartCollisionSpheres), clothData.mNumSpheres); generateCones(mPrevData.mCones, mPrevData.mSpheres, clothData.mCapsuleIndices, clothData.mNumCapsules); } } template cloth::SwCollision::~SwCollision() { deallocate(mCurData); deallocate(mPrevData); } template void cloth::SwCollision::operator()(const IterationState& state) { mNumCollisions = 0; collideConvexes(state); // discrete convex collision, no friction collideTriangles(state); // discrete triangle collision, no friction computeBounds(); if (!mClothData.mNumSpheres) return; bool lastIteration = state.mRemainingIterations == 1; const T4f* targetSpheres = reinterpret_cast(mClothData.mTargetCollisionSpheres); // generate sphere and cone collision data if (!lastIteration) { // interpolate spheres LerpIterator pIter(reinterpret_cast(mClothData.mStartCollisionSpheres), targetSpheres, state.getCurrentAlpha()); generateSpheres(reinterpret_cast(mCurData.mSpheres), pIter, mClothData.mNumSpheres); } else { // otherwise use the target spheres directly generateSpheres(reinterpret_cast(mCurData.mSpheres), targetSpheres, mClothData.mNumSpheres); } // generate cones even if test below fails because // continuous collision might need it in next iteration generateCones(mCurData.mCones, mCurData.mSpheres, mClothData.mCapsuleIndices, mClothData.mNumCapsules); if (buildAcceleration()) { if (mClothData.mEnableContinuousCollision) collideContinuousParticles(); mergeAcceleration(reinterpret_cast(mSphereGrid)); mergeAcceleration(reinterpret_cast(mConeGrid)); if (!mClothData.mEnableContinuousCollision) collideParticles(); collideVirtualParticles(); } if (mPrevData.mSpheres) ps::swap(mCurData, mPrevData); } template size_t cloth::SwCollision::estimateTemporaryMemory(const SwCloth& cloth) { size_t numTriangles = cloth.mStartCollisionTriangles.size(); size_t numPlanes = cloth.mStartCollisionPlanes.size(); const size_t kTriangleDataSize = sizeof(TriangleData) * numTriangles; const size_t kPlaneDataSize = sizeof(PxVec4) * numPlanes * 2; return std::max(kTriangleDataSize, kPlaneDataSize); } template size_t cloth::SwCollision::estimatePersistentMemory(const SwCloth& cloth) { size_t numCapsules = cloth.mCapsuleIndices.size(); size_t numSpheres = cloth.mStartCollisionSpheres.size(); size_t sphereDataSize = sizeof(SphereData) * numSpheres * 2; size_t coneDataSize = sizeof(ConeData) * numCapsules * 2; return sphereDataSize + coneDataSize; } template void cloth::SwCollision::allocate(CollisionData& data) { data.mSpheres = static_cast(mAllocator.allocate(sizeof(SphereData) * mClothData.mNumSpheres)); data.mCones = static_cast(mAllocator.allocate(sizeof(ConeData) * mClothData.mNumCapsules)); } template void cloth::SwCollision::deallocate(const CollisionData& data) { mAllocator.deallocate(data.mSpheres); mAllocator.deallocate(data.mCones); } template void cloth::SwCollision::computeBounds() { NV_CLOTH_PROFILE_ZONE("cloth::SwSolverKernel::computeBounds", /*ProfileContext::None*/ 0); T4f* prevIt = reinterpret_cast(mClothData.mPrevParticles); T4f* curIt = reinterpret_cast(mClothData.mCurParticles); T4f* curEnd = curIt + mClothData.mNumParticles; T4f floatMaxXYZ = -static_cast(sMinusFloatMaxXYZ); T4f lower = simd4f(FLT_MAX), upper = -lower; for (; curIt < curEnd; ++curIt, ++prevIt) { T4f current = *curIt; lower = min(lower, current); upper = max(upper, current); // if (current.w > 0) current.w = previous.w *curIt = select(current > floatMaxXYZ, *prevIt, current); } BoundingBox curBounds; curBounds.mLower = lower; curBounds.mUpper = upper; // don't change this order, storeBounds writes 7 floats BoundingBox prevBounds = loadBounds(mClothData.mCurBounds); storeBounds(mClothData.mCurBounds, curBounds); storeBounds(mClothData.mPrevBounds, prevBounds); } namespace { template T4i andNotIsZero(const T4i& left, const T4i& right) { return (left & ~right) == gSimd4iZero; } } // build per-axis mask arrays of spheres on the right/left of grid cell template void cloth::SwCollision::buildSphereAcceleration(const SphereData* sIt) { static const int maxIndex = sGridSize - 1; uint32_t mask = 0x1; //single bit mask for current sphere const SphereData* sEnd = sIt + mClothData.mNumSpheres; for (; sIt != sEnd; ++sIt, mask <<= 1) { T4f sphere = loadAligned(array(sIt->center)); T4f radius = splat<3>(sphere); //calculate the first and last cell index, for each axis, that contains the sphere T4i first = intFloor(min(max((sphere - radius) * mGridScale + mGridBias, gSimd4fZero), sGridLength)); //use both min and max to deal with bad grid scales T4i last = intFloor(min(max((sphere + radius) * mGridScale + mGridBias, gSimd4fZero), sGridLength)); const int* firstIdx = array(first); const int* lastIdx = array(last); uint32_t* firstIt = reinterpret_cast(mSphereGrid); uint32_t* lastIt = firstIt + 3 * sGridSize; //loop through the 3 axes for (uint32_t i = 0; i < 3; ++i, firstIt += sGridSize, lastIt += sGridSize) { //mark the sphere and everything to the right for (int j = firstIdx[i]; j <= maxIndex; ++j) firstIt[j] |= mask; //mark the sphere and everything to the left for (int j = lastIdx[i]; j >= 0; --j) lastIt[j] |= mask; } } } // generate cone masks from sphere masks template void cloth::SwCollision::buildConeAcceleration() { const ConeData* coneIt = mCurData.mCones; const ConeData* coneEnd = coneIt + mClothData.mNumCapsules; for (uint32_t coneMask = 0x1; coneIt != coneEnd; ++coneIt, coneMask <<= 1) { if (coneIt->radius == 0.0f) continue; uint32_t spheresMask = coneIt->bothMask; uint32_t* sphereIt = reinterpret_cast(mSphereGrid); uint32_t* sphereEnd = sphereIt + 6 * sGridSize; uint32_t* gridIt = reinterpret_cast(mConeGrid); for (; sphereIt != sphereEnd; ++sphereIt, ++gridIt) if (*sphereIt & spheresMask) *gridIt |= coneMask; } } // convert right/left mask arrays into single overlap array template void cloth::SwCollision::mergeAcceleration(uint32_t* firstIt) { uint32_t* firstEnd = firstIt + 3 * sGridSize; uint32_t* lastIt = firstEnd; for (; firstIt != firstEnd; ++firstIt, ++lastIt) *firstIt &= *lastIt; } // build mask of spheres/cones touching a regular grid along each axis template bool cloth::SwCollision::buildAcceleration() { // determine single bounding box around all spheres BoundingBox sphereBounds = expandBounds(emptyBounds(), mCurData.mSpheres, mCurData.mSpheres + mClothData.mNumSpheres); // determine single bounding box around all particles BoundingBox particleBounds = loadBounds(mClothData.mCurBounds); if (mClothData.mEnableContinuousCollision) { // extend bounds to include movement from previous frame sphereBounds = expandBounds(sphereBounds, mPrevData.mSpheres, mPrevData.mSpheres + mClothData.mNumSpheres); particleBounds = expandBounds(particleBounds, loadBounds(mClothData.mPrevBounds)); } BoundingBox bounds = intersectBounds(sphereBounds, particleBounds); // no collision checks needed if the intersection between particle bounds and sphere bounds is empty T4f edgeLength = (bounds.mUpper - bounds.mLower) & ~static_cast(sMaskW); if (!allGreaterEqual(edgeLength, gSimd4fZero)) return false; // calculate an expanded bounds to account for numerical inaccuracy const T4f expandedLower = bounds.mLower - abs(bounds.mLower) * sGridExpand; const T4f expandedUpper = bounds.mUpper + abs(bounds.mUpper) * sGridExpand; const T4f expandedEdgeLength = max(expandedUpper - expandedLower, gSimd4fEpsilon); // make grid minimal thickness and strict upper bound of spheres // grid maps bounds to 0-7 space (sGridLength =~= 8) mGridScale = sGridLength * recip<1>(expandedEdgeLength); mGridBias = -expandedLower * mGridScale; array(mGridBias)[3] = 1.0f; // needed for collideVirtualParticles() NV_CLOTH_ASSERT(allTrue(((bounds.mLower * mGridScale + mGridBias) >= simd4f(0.0f)) | sMaskW)); NV_CLOTH_ASSERT(allTrue(((bounds.mUpper * mGridScale + mGridBias) < simd4f(8.0f)) | sMaskW)); memset(mSphereGrid, 0, sizeof(uint32_t) * 6 * (sGridSize)); if (mClothData.mEnableContinuousCollision) buildSphereAcceleration(mPrevData.mSpheres); buildSphereAcceleration(mCurData.mSpheres); memset(mConeGrid, 0, sizeof(uint32_t) * 6 * (sGridSize)); buildConeAcceleration(); return true; } #ifdef _MSC_VER #define FORCE_INLINE __forceinline #else #define FORCE_INLINE inline __attribute__((always_inline)) #endif template FORCE_INLINE typename cloth::SwCollision::ShapeMask& cloth::SwCollision::ShapeMask:: operator = (const ShapeMask& right) { mCones = right.mCones; mSpheres = right.mSpheres; return *this; } template FORCE_INLINE typename cloth::SwCollision::ShapeMask& cloth::SwCollision::ShapeMask:: operator &= (const ShapeMask& right) { mCones = mCones & right.mCones; mSpheres = mSpheres & right.mSpheres; return *this; } // get collision shape masks from a single cell from a single axis of the acceleration structure template FORCE_INLINE typename cloth::SwCollision::ShapeMask cloth::SwCollision::getShapeMask(const T4f& position, const T4i* __restrict sphereGrid, const T4i* __restrict coneGrid) { // position are the grid positions along a single axis (x, y, or z) Gather gather(intFloor(position)); // get the bitmask indicating which cones/spheres overlap the grid cell ShapeMask result; result.mCones = gather(coneGrid); result.mSpheres = gather(sphereGrid); return result; } // lookup acceleration structure and return mask of potential intersection collision shapes template FORCE_INLINE typename cloth::SwCollision::ShapeMask cloth::SwCollision::getShapeMask(const T4f* __restrict positions) const { // positions are the particle positions T4f posX = positions[0] * splat<0>(mGridScale) + splat<0>(mGridBias); T4f posY = positions[1] * splat<1>(mGridScale) + splat<1>(mGridBias); T4f posZ = positions[2] * splat<2>(mGridScale) + splat<2>(mGridBias); // AND together the bit masks so only the cones/spheres remain // that overlap with the particle posision on all axis ShapeMask result = getShapeMask(posX, mSphereGrid, mConeGrid); // X result &= getShapeMask(posY, mSphereGrid + 2, mConeGrid + 2); // Y result &= getShapeMask(posZ, mSphereGrid + 4, mConeGrid + 4); // Z return result; } // lookup acceleration structure and return mask of potential intersection collision shapes for CCD template FORCE_INLINE typename cloth::SwCollision::ShapeMask cloth::SwCollision::getShapeMask(const T4f* __restrict prevPos, const T4f* __restrict curPos) const { // same as getShapeMask(const T4f* __restrict positions) but for continuous collision detection T4f scaleX = splat<0>(mGridScale); T4f scaleY = splat<1>(mGridScale); T4f scaleZ = splat<2>(mGridScale); T4f biasX = splat<0>(mGridBias); T4f biasY = splat<1>(mGridBias); T4f biasZ = splat<2>(mGridBias); T4f prevX = prevPos[0] * scaleX + biasX; T4f prevY = prevPos[1] * scaleY + biasY; T4f prevZ = prevPos[2] * scaleZ + biasZ; T4f curX = curPos[0] * scaleX + biasX; T4f curY = curPos[1] * scaleY + biasY; T4f curZ = curPos[2] * scaleZ + biasZ; // get maximum extent corner of the AABB containing both prevPos and curPos T4f maxX = min(max(prevX, curX), sGridLength); T4f maxY = min(max(prevY, curY), sGridLength); T4f maxZ = min(max(prevZ, curZ), sGridLength); ShapeMask result = getShapeMask(maxX, mSphereGrid, mConeGrid); // X result &= getShapeMask(maxY, mSphereGrid + 2, mConeGrid + 2); // Y result &= getShapeMask(maxZ, mSphereGrid + 4, mConeGrid + 4); // Z // get min extent corner of the AABB containing both prevPos and curPos T4f zero = gSimd4fZero; T4f minX = max(min(prevX, curX), zero); T4f minY = max(min(prevY, curY), zero); T4f minZ = max(min(prevZ, curZ), zero); result &= getShapeMask(minX, mSphereGrid + 6, mConeGrid + 6); // X result &= getShapeMask(minY, mSphereGrid + 8, mConeGrid + 8); // Y result &= getShapeMask(minZ, mSphereGrid + 10, mConeGrid + 10); // Z return result; } template struct cloth::SwCollision::ImpulseAccumulator { ImpulseAccumulator() : mDeltaX(gSimd4fZero) , mDeltaY(mDeltaX) , mDeltaZ(mDeltaX) , mVelX(mDeltaX) , mVelY(mDeltaX) , mVelZ(mDeltaX) , mNumCollisions(gSimd4fEpsilon) { } void add(const T4f& x, const T4f& y, const T4f& z, const T4f& scale, const T4f& mask) { NV_CLOTH_ASSERT(allTrue((mask & x) == (mask & x))); NV_CLOTH_ASSERT(allTrue((mask & y) == (mask & y))); NV_CLOTH_ASSERT(allTrue((mask & z) == (mask & z))); NV_CLOTH_ASSERT(allTrue((mask & scale) == (mask & scale))); T4f maskedScale = scale & mask; mDeltaX = mDeltaX + x * maskedScale; mDeltaY = mDeltaY + y * maskedScale; mDeltaZ = mDeltaZ + z * maskedScale; mNumCollisions = mNumCollisions + (gSimd4fOne & mask); } void addVelocity(const T4f& vx, const T4f& vy, const T4f& vz, const T4f& mask) { NV_CLOTH_ASSERT(allTrue((mask & vx) == (mask & vx))); NV_CLOTH_ASSERT(allTrue((mask & vy) == (mask & vy))); NV_CLOTH_ASSERT(allTrue((mask & vz) == (mask & vz))); mVelX = mVelX + (vx & mask); mVelY = mVelY + (vy & mask); mVelZ = mVelZ + (vz & mask); } void subtract(const T4f& x, const T4f& y, const T4f& z, const T4f& scale, const T4f& mask) { NV_CLOTH_ASSERT(allTrue((mask & x) == (mask & x))); NV_CLOTH_ASSERT(allTrue((mask & y) == (mask & y))); NV_CLOTH_ASSERT(allTrue((mask & z) == (mask & z))); NV_CLOTH_ASSERT(allTrue((mask & scale) == (mask & scale))); T4f maskedScale = scale & mask; mDeltaX = mDeltaX - x * maskedScale; mDeltaY = mDeltaY - y * maskedScale; mDeltaZ = mDeltaZ - z * maskedScale; mNumCollisions = mNumCollisions + (gSimd4fOne & mask); } T4f mDeltaX, mDeltaY, mDeltaZ; //depenetration delta T4f mVelX, mVelY, mVelZ; //frame offset of the collision shape (velocity * dt) T4f mNumCollisions; }; template FORCE_INLINE void cloth::SwCollision::collideSpheres(const T4i& sphereMask, const T4f* positions, ImpulseAccumulator& accum) const { const float* __restrict spherePtr = array(mCurData.mSpheres->center); bool frictionEnabled = mClothData.mFrictionScale > 0.0f; T4i mask4 = horizontalOr(sphereMask); uint32_t mask = uint32_t(array(mask4)[0]); while (mask) { uint32_t test = mask - 1; uint32_t offset = findBitSet(mask & ~test) * sizeof(SphereData); mask = mask & test; T4f sphere = loadAligned(spherePtr, offset); T4f deltaX = positions[0] - splat<0>(sphere); T4f deltaY = positions[1] - splat<1>(sphere); T4f deltaZ = positions[2] - splat<2>(sphere); T4f sqrDistance = gSimd4fEpsilon + deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; T4f negativeScale = gSimd4fOne - rsqrt(sqrDistance) * splat<3>(sphere); // negativeScale = 1 - radius/|position-sphere| T4f contactMask; if (!anyGreater(gSimd4fZero, negativeScale, contactMask)) continue; accum.subtract(deltaX, deltaY, deltaZ, negativeScale, contactMask); // -= delta * negativeScale // = delta - delta * radius/|position-sphere| if (frictionEnabled) { // load previous sphere pos const float* __restrict prevSpherePtr = array(mPrevData.mSpheres->center); T4f prevSphere = loadAligned(prevSpherePtr, offset); T4f velocity = sphere - prevSphere; accum.addVelocity(splat<0>(velocity), splat<1>(velocity), splat<2>(velocity), contactMask); } } } template FORCE_INLINE typename cloth::SwCollision::T4i cloth::SwCollision::collideCones(const T4f* __restrict positions, ImpulseAccumulator& accum) const { const float* __restrict centerPtr = array(mCurData.mCones->center); const float* __restrict axisPtr = array(mCurData.mCones->axis); const int32_t* __restrict auxiliaryPtr = reinterpret_cast(&mCurData.mCones->sqrCosine); bool frictionEnabled = mClothData.mFrictionScale > 0.0f; ShapeMask shapeMask = getShapeMask(positions); T4i mask4 = horizontalOr(shapeMask.mCones); uint32_t mask = uint32_t(array(mask4)[0]); while (mask) { uint32_t test = mask - 1; uint32_t coneIndex = findBitSet(mask & ~test); uint32_t offset = coneIndex * sizeof(ConeData); mask = mask & test; T4i test4 = mask4 - gSimd4iOne; T4f culled = simd4f(andNotIsZero(shapeMask.mCones, test4)); mask4 = mask4 & test4; T4f center = loadAligned(centerPtr, offset); // offset from center of cone to particle // delta = pos - center T4f deltaX = positions[0] - splat<0>(center); T4f deltaY = positions[1] - splat<1>(center); T4f deltaZ = positions[2] - splat<2>(center); //axis of the cone T4f axis = loadAligned(axisPtr, offset); T4f axisX = splat<0>(axis); T4f axisY = splat<1>(axis); T4f axisZ = splat<2>(axis); T4f slope = splat<3>(axis); // distance along cone axis (from center) T4f dot = deltaX * axisX + deltaY * axisY + deltaZ * axisZ; // interpolate radius T4f radius = dot * slope + splat<3>(center); // set radius to zero if cone is culled radius = max(radius, gSimd4fZero) & ~culled; // distance to axis // sqrDistance = |delta|^2 - |dot|^2 T4f sqrDistance = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ - dot * dot; T4i auxiliary = loadAligned(auxiliaryPtr, offset); T4i bothMask = splat<3>(auxiliary); T4f contactMask; if (!anyGreater(radius * radius, sqrDistance, contactMask)) { // cone only culled when spheres culled, ok to clear those too shapeMask.mSpheres = shapeMask.mSpheres & ~bothMask; continue; } // clamp to a small positive epsilon to avoid numerical error // making sqrDistance negative when point lies on the cone axis sqrDistance = max(sqrDistance, gSimd4fEpsilon); T4f invDistance = rsqrt(sqrDistance); //offset base to take slope in to account T4f base = dot + slope * sqrDistance * invDistance; // force left/rightMask to false if not inside cone base = base & contactMask; T4f halfLength = splat<1>(simd4f(auxiliary)); T4i leftMask = simd4i(base < -halfLength); T4i rightMask = simd4i(base > halfLength); // we use both mask because of the early out above. T4i firstMask = splat<2>(auxiliary); T4i secondMask = firstMask ^ bothMask; shapeMask.mSpheres = shapeMask.mSpheres & ~(firstMask & ~leftMask); shapeMask.mSpheres = shapeMask.mSpheres & ~(secondMask & ~rightMask); //contact normal direction deltaX = deltaX - base * axisX; deltaY = deltaY - base * axisY; deltaZ = deltaZ - base * axisZ; T4f sqrCosine = splat<0>(simd4f(auxiliary)); T4f scale = radius * invDistance * sqrCosine - sqrCosine; contactMask = contactMask & ~simd4f(leftMask | rightMask); if (!anyTrue(contactMask)) continue; accum.add(deltaX, deltaY, deltaZ, scale, contactMask); if (frictionEnabled) { uint32_t s0 = mClothData.mCapsuleIndices[coneIndex].first; uint32_t s1 = mClothData.mCapsuleIndices[coneIndex].second; float* prevSpheres = reinterpret_cast(mPrevData.mSpheres); float* curSpheres = reinterpret_cast(mCurData.mSpheres); // todo: could pre-compute sphere velocities or it might be // faster to compute cur/prev sphere positions directly T4f s0p0 = loadAligned(prevSpheres, s0 * sizeof(SphereData)); T4f s0p1 = loadAligned(curSpheres, s0 * sizeof(SphereData)); T4f s1p0 = loadAligned(prevSpheres, s1 * sizeof(SphereData)); T4f s1p1 = loadAligned(curSpheres, s1 * sizeof(SphereData)); T4f v0 = s0p1 - s0p0; T4f v1 = s1p1 - s1p0; T4f vd = v1 - v0; // dot is in the range -1 to 1, scale and bias to 0 to 1 dot = dot * gSimd4fHalf + gSimd4fHalf; // interpolate velocity at contact points T4f vx = splat<0>(v0) + dot * splat<0>(vd); T4f vy = splat<1>(v0) + dot * splat<1>(vd); T4f vz = splat<2>(v0) + dot * splat<2>(vd); accum.addVelocity(vx, vy, vz, contactMask); } } return shapeMask.mSpheres; } template FORCE_INLINE void cloth::SwCollision::collideSpheres(const T4i& sphereMask, const T4f* __restrict prevPos, T4f* __restrict curPos, ImpulseAccumulator& accum) const { const float* __restrict prevSpheres = array(mPrevData.mSpheres->center); const float* __restrict curSpheres = array(mCurData.mSpheres->center); bool frictionEnabled = mClothData.mFrictionScale > 0.0f; T4i mask4 = horizontalOr(sphereMask); uint32_t mask = uint32_t(array(mask4)[0]); while (mask) { uint32_t test = mask - 1; uint32_t offset = findBitSet(mask & ~test) * sizeof(SphereData); mask = mask & test; T4f prevSphere = loadAligned(prevSpheres, offset); T4f prevX = prevPos[0] - splat<0>(prevSphere); T4f prevY = prevPos[1] - splat<1>(prevSphere); T4f prevZ = prevPos[2] - splat<2>(prevSphere); T4f prevRadius = splat<3>(prevSphere); T4f curSphere = loadAligned(curSpheres, offset); T4f curX = curPos[0] - splat<0>(curSphere); T4f curY = curPos[1] - splat<1>(curSphere); T4f curZ = curPos[2] - splat<2>(curSphere); T4f curRadius = splat<3>(curSphere); T4f sqrDistance = gSimd4fEpsilon + curX * curX + curY * curY + curZ * curZ; T4f dotPrevPrev = prevX * prevX + prevY * prevY + prevZ * prevZ - prevRadius * prevRadius; T4f dotPrevCur = prevX * curX + prevY * curY + prevZ * curZ - prevRadius * curRadius; T4f dotCurCur = sqrDistance - curRadius * curRadius; T4f discriminant = dotPrevCur * dotPrevCur - dotCurCur * dotPrevPrev; T4f sqrtD = sqrt(discriminant); //we get -nan if there are no roots T4f halfB = dotPrevCur - dotPrevPrev; T4f minusA = dotPrevCur - dotCurCur + halfB; // time of impact or 0 if prevPos inside sphere T4f toi = recip(minusA) * min(gSimd4fZero, halfB + sqrtD); T4f collisionMask = (toi < gSimd4fOne) & (halfB < sqrtD); // skip continuous collision if the (un-clamped) particle // trajectory only touches the outer skin of the cone. T4f rMin = prevRadius + halfB * minusA * (curRadius - prevRadius); collisionMask = collisionMask & (discriminant > minusA * rMin * rMin * sSkeletonWidth); // a is negative when one relative sphere is contained in the other, // which is already handled by discrete collision. collisionMask = collisionMask & (minusA < -static_cast(gSimd4fEpsilon)); if (!allEqual(collisionMask, gSimd4fZero)) { T4f deltaX = prevX - curX; T4f deltaY = prevY - curY; T4f deltaZ = prevZ - curZ; T4f oneMinusToi = (gSimd4fOne - toi) & collisionMask; // reduce ccd impulse if (clamped) particle trajectory stays in sphere skin, // i.e. scale by exp2(-k) or 1/(1+k) with k = (tmin - toi) / (1 - toi) T4f minusK = sqrtD * recip(minusA * oneMinusToi) & (oneMinusToi > gSimd4fEpsilon); oneMinusToi = oneMinusToi * recip(gSimd4fOne - minusK); //Move curXYZ to toi point curX = curX + deltaX * oneMinusToi; curY = curY + deltaY * oneMinusToi; curZ = curZ + deltaZ * oneMinusToi; //curXYZ is now touching the sphere at toi //Note that curXYZ is also relative to the sphere center at toi //We assume that the point sticks to the sphere until the end of the frame curPos[0] = splat<0>(curSphere) + curX; curPos[1] = splat<1>(curSphere) + curY; curPos[2] = splat<2>(curSphere) + curZ; sqrDistance = gSimd4fEpsilon + curX * curX + curY * curY + curZ * curZ; } T4f negativeScale = gSimd4fOne - rsqrt(sqrDistance) * curRadius; T4f contactMask; if (!anyGreater(gSimd4fZero, negativeScale, contactMask)) continue; accum.subtract(curX, curY, curZ, negativeScale, contactMask); if (frictionEnabled) { T4f velocity = curSphere - prevSphere; accum.addVelocity(splat<0>(velocity), splat<1>(velocity), splat<2>(velocity), contactMask); } } } template FORCE_INLINE typename cloth::SwCollision::T4i cloth::SwCollision::collideCones(const T4f* __restrict prevPos, T4f* __restrict curPos, ImpulseAccumulator& accum) const { const float* __restrict prevCenterPtr = array(mPrevData.mCones->center); const float* __restrict prevAxisPtr = array(mPrevData.mCones->axis); const int32_t* __restrict prevAuxiliaryPtr = reinterpret_cast(&mPrevData.mCones->sqrCosine); const float* __restrict curCenterPtr = array(mCurData.mCones->center); const float* __restrict curAxisPtr = array(mCurData.mCones->axis); const int32_t* __restrict curAuxiliaryPtr = reinterpret_cast(&mCurData.mCones->sqrCosine); bool frictionEnabled = mClothData.mFrictionScale > 0.0f; ShapeMask shapeMask = getShapeMask(prevPos, curPos); T4i mask4 = horizontalOr(shapeMask.mCones); uint32_t mask = uint32_t(array(mask4)[0]); while (mask) { uint32_t test = mask - 1; uint32_t coneIndex = findBitSet(mask & ~test); uint32_t offset = coneIndex * sizeof(ConeData); mask = mask & test; T4i test4 = mask4 - gSimd4iOne; T4f culled = simd4f(andNotIsZero(shapeMask.mCones, test4)); mask4 = mask4 & test4; T4f prevCenter = loadAligned(prevCenterPtr, offset); T4f prevAxis = loadAligned(prevAxisPtr, offset); T4f prevAxisX = splat<0>(prevAxis); T4f prevAxisY = splat<1>(prevAxis); T4f prevAxisZ = splat<2>(prevAxis); T4f prevSlope = splat<3>(prevAxis); T4f prevX = prevPos[0] - splat<0>(prevCenter); T4f prevY = prevPos[1] - splat<1>(prevCenter); T4f prevZ = prevPos[2] - splat<2>(prevCenter); T4f prevT = prevY * prevAxisZ - prevZ * prevAxisY; T4f prevU = prevZ * prevAxisX - prevX * prevAxisZ; T4f prevV = prevX * prevAxisY - prevY * prevAxisX; T4f prevDot = prevX * prevAxisX + prevY * prevAxisY + prevZ * prevAxisZ; //distance along the axis T4f prevRadius = prevDot * prevSlope + splat<3>(prevCenter); T4f curCenter = loadAligned(curCenterPtr, offset); T4f curAxis = loadAligned(curAxisPtr, offset); T4f curAxisX = splat<0>(curAxis); T4f curAxisY = splat<1>(curAxis); T4f curAxisZ = splat<2>(curAxis); T4f curSlope = splat<3>(curAxis); T4i curAuxiliary = loadAligned(curAuxiliaryPtr, offset); T4f curX = curPos[0] - splat<0>(curCenter); T4f curY = curPos[1] - splat<1>(curCenter); T4f curZ = curPos[2] - splat<2>(curCenter); //curTUV = cross(curXYZ, curAxisXYZ) T4f curT = curY * curAxisZ - curZ * curAxisY; T4f curU = curZ * curAxisX - curX * curAxisZ; T4f curV = curX * curAxisY - curY * curAxisX; T4f curDot = curX * curAxisX + curY * curAxisY + curZ * curAxisZ; T4f curRadius = curDot * curSlope + splat<3>(curCenter); //Magnitude of cross product gives area of parallelogram |curAxisXYZ|*parallelogramHeight, |curAxisXYZ|=1 //parallelogramHeight is distance between the axis and the point T4f curSqrDistance = gSimd4fEpsilon + curT * curT + curU * curU + curV * curV; // set radius to zero if cone is culled prevRadius = max(prevRadius, gSimd4fZero) & ~culled; curRadius = max(curRadius, gSimd4fZero) & ~culled; //Use quadratic equation to solve for time of impact (against infinite cone) T4f dotPrevPrev = prevT * prevT + prevU * prevU + prevV * prevV - prevRadius * prevRadius; T4f dotPrevCur = prevT * curT + prevU * curU + prevV * curV - prevRadius * curRadius; T4f dotCurCur = curSqrDistance - curRadius * curRadius; T4f discriminant = dotPrevCur * dotPrevCur - dotCurCur * dotPrevPrev; T4f sqrtD = sqrt(discriminant); T4f halfB = dotPrevCur - dotPrevPrev; T4f minusA = dotPrevCur - dotCurCur + halfB; // time of impact or 0 if prevPos inside cone T4f toi = recip(minusA) * min(gSimd4fZero, halfB + sqrtD); T4f collisionMask = (toi < gSimd4fOne) & (halfB < sqrtD); // skip continuous collision if the (un-clamped) particle // trajectory only touches the outer skin of the cone. T4f rMin = prevRadius + halfB * minusA * (curRadius - prevRadius); collisionMask = collisionMask & (discriminant > minusA * rMin * rMin * sSkeletonWidth); // a is negative when one cone is contained in the other, // which is already handled by discrete collision. collisionMask = collisionMask & (minusA < -static_cast(gSimd4fEpsilon)); // test if any particle hits infinite cone (and 0