// 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 __ATTACHMENTS_CUH__ #define __ATTACHMENTS_CUH__ #include "foundation/PxVecMath.h" using namespace physx; __device__ inline bool isConeLimitedEnabled(float maxAngle, float minDist, float maxDist) { return maxAngle >= 0.f || minDist >= 0.f || maxDist >= 0.f; } /** * \param maxAngle The cone opening angle measured from the axis. * \param minDist Minimum distance measure from the cone tip. * \param maxDist Maximum distance measured from the cone tip. * \param relPos Position relative to the cone tip for which the error is computed (the minimum translation * vector to get from relPos to the allowed cone volume) */ __device__ inline PxVec3 computeConeLimitedError(float maxAngle, float minDist, float maxDist, const PxVec3& coneAxis, const PxVec3& relPos) { PxReal len = relPos.magnitude(); // angle constraint PxVec3 dir; if(maxAngle == 0.f) { dir = coneAxis; } else if(maxAngle > 0.f) { dir = (len > 1.0e-6f) ? (relPos / len) : coneAxis; const PxReal cosAngle = dir.dot(coneAxis); PxReal cosMaxAngle; PxReal sinMaxAngle; PxSinCos(maxAngle, sinMaxAngle, cosMaxAngle); // could be precomputed if(cosAngle < cosMaxAngle) // if theta > maxAngle { PxVec3 t1 = dir.cross(coneAxis); PxVec3 b1 = coneAxis.cross(t1).getNormalized(); dir = cosMaxAngle * coneAxis + sinMaxAngle * b1; // new direction that is "maxAngle" deviated from the world axis. } } else { dir = (len > 1.0e-6f) ? (relPos / len) : coneAxis; } // length constraint len = PxClamp(len, minDist, maxDist >= 0.f ? maxDist : FLT_MAX); return relPos - len * dir; // ideal relPos = len * dir } PX_FORCE_INLINE __device__ PxVec3 calculateAttachmentDeltaImpulsePGS(const float4& raXn0_biasW, const float4& raXn1_biasW, const float4& raXn2_biasW, const float4& velMultiplierXYZ_invMassW, const float4& low_high_limits, const float4& worldAxis_angle, const PxgVelocityPackPGS& vel0, const PxVec3& linVel1, PxReal invDt, PxReal biasFactor, PxVec3& deltaLinVel, PxVec3& deltaAngVel) { const PxVec3 raXn0 = PxVec3(raXn0_biasW.x, raXn0_biasW.y, raXn0_biasW.z); const PxVec3 raXn1 = PxVec3(raXn1_biasW.x, raXn1_biasW.y, raXn1_biasW.z); const PxVec3 raXn2 = PxVec3(raXn2_biasW.x, raXn2_biasW.y, raXn2_biasW.z); // Compute the normal velocity of the constraint. const PxReal velOfRigidAtAttachmentPointX = vel0.linVel.x + vel0.angVel.dot(raXn0); const PxReal velOfRigidAtAttachmentPointY = vel0.linVel.y + vel0.angVel.dot(raXn1); const PxReal velOfRigidAtAttachmentPointZ = vel0.linVel.z + vel0.angVel.dot(raXn2); // Definition of errors is as follows // raXn0_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).x; // raXn1_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).y; // raXn2_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).z; const PxReal& positionErrorX = raXn0_biasW.w; const PxReal& positionErrorY = raXn1_biasW.w; const PxReal& positionErrorZ = raXn2_biasW.w; // For bias see here https://box2d.org/files/ErinCatto_SequentialImpulses_GDC2006.pdf // Slide 22, Bias Impulse PxVec3 velError(linVel1.x - positionErrorX * biasFactor * invDt, linVel1.y - positionErrorY * biasFactor * invDt, linVel1.z - positionErrorZ * biasFactor * invDt); if(isConeLimitedEnabled(worldAxis_angle.w, low_high_limits.x, low_high_limits.y)) { // we don't understand why we need to scale by biasFactor in order to get the right error offset for the cone. PxReal weirdScale = invDt * biasFactor; PxVec3 posError = velError * (1.0f / weirdScale); const PxVec3 worldAxis(worldAxis_angle.x, worldAxis_angle.y, worldAxis_angle.z); posError = computeConeLimitedError(worldAxis_angle.w, low_high_limits.x, low_high_limits.y, worldAxis, posError); velError = posError * weirdScale; } // deltaF for PGS: impulse const PxReal deltaF0 = (velError.x - velOfRigidAtAttachmentPointX) * velMultiplierXYZ_invMassW.x; const PxReal deltaF1 = (velError.y - velOfRigidAtAttachmentPointY) * velMultiplierXYZ_invMassW.y; const PxReal deltaF2 = (velError.z - velOfRigidAtAttachmentPointZ) * velMultiplierXYZ_invMassW.z; const PxVec3 deltaImpulse = PxVec3(deltaF0, deltaF1, deltaF2); const PxReal invMass0 = velMultiplierXYZ_invMassW.w; deltaLinVel = deltaImpulse * invMass0; deltaAngVel = raXn0 * deltaF0 + raXn1 * deltaF1 + raXn2 * deltaF2; return deltaImpulse; } template PX_FORCE_INLINE __device__ PxVec3 calculateAttachmentDeltaImpulsePGS(PxU32 offset, const ConstraintType& constraint, const PxgVelocityPackPGS& vel0, const PxVec3& linVel1, PxReal invDt, PxReal biasFactor, PxVec3& deltaLinVel, PxVec3& deltaAngVel) { return calculateAttachmentDeltaImpulsePGS(constraint.raXn0_biasW[offset], constraint.raXn1_biasW[offset], constraint.raXn2_biasW[offset], constraint.velMultiplierXYZ_invMassW[offset], constraint.low_high_limits[offset], constraint.axis_angle[offset], vel0, linVel1, invDt, biasFactor, deltaLinVel, deltaAngVel); } PX_FORCE_INLINE __device__ PxVec3 calculateAttachmentDeltaImpulseTGS(const float4& raXn0_biasW, const float4& raXn1_biasW, const float4& raXn2_biasW, const float4& velMultiplierXYZ_invMassW, const float4& low_high_limits, const float4& worldAxis_angle, const PxgVelocityPackTGS& vel0, const PxVec3& linDelta1, PxReal dt, PxReal biasCoefficient, bool isVelocityIteration, PxVec3& deltaLinVel, PxVec3& deltaAngVel) { const PxVec3 raXn0 = PxVec3(raXn0_biasW.x, raXn0_biasW.y, raXn0_biasW.z); const PxVec3 raXn1 = PxVec3(raXn1_biasW.x, raXn1_biasW.y, raXn1_biasW.z); const PxVec3 raXn2 = PxVec3(raXn2_biasW.x, raXn2_biasW.y, raXn2_biasW.z); const PxReal velOfRigidAtAttachmentPoint0 = vel0.linVel.x + vel0.angVel.dot(raXn0); const PxReal velOfRigidAtAttachmentPoint1 = vel0.linVel.y + vel0.angVel.dot(raXn1); const PxReal velOfRigidAtAttachmentPoint2 = vel0.linVel.z + vel0.angVel.dot(raXn2); const PxVec3 linDelta = linDelta1 - vel0.linDelta; // Definition of errors is as follows // raXn0_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).x; // raXn1_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).y; // raXn2_biasW.w = ((rigidBodyCoM + comToPoint) - attachedPointLocation).z; const PxReal& positionErrorX = raXn0_biasW.w; const PxReal& positionErrorY = raXn1_biasW.w; const PxReal& positionErrorZ = raXn2_biasW.w; // This is a position error as well (as opposed to a velocity error for PGS) PxVec3 tgsError(linDelta.x - positionErrorX - vel0.angDelta.dot(raXn0), linDelta.y - positionErrorY - vel0.angDelta.dot(raXn1), linDelta.z - positionErrorZ - vel0.angDelta.dot(raXn2)); if(isConeLimitedEnabled(worldAxis_angle.w, low_high_limits.x, low_high_limits.y)) { const PxVec3 worldAxis(worldAxis_angle.x, worldAxis_angle.y, worldAxis_angle.z); tgsError = computeConeLimitedError(worldAxis_angle.w, low_high_limits.x, low_high_limits.y, worldAxis, tgsError); } const PxReal velDt = isVelocityIteration ? 0.f : dt; // deltaF for TGS: position delta multiplied by effective inertia // Bias coefficient is already multiplied by dt // Bias seems to kind of act like damping const PxReal deltaF0 = (tgsError.x - velOfRigidAtAttachmentPoint0 * velDt) * velMultiplierXYZ_invMassW.x * biasCoefficient; const PxReal deltaF1 = (tgsError.y - velOfRigidAtAttachmentPoint1 * velDt) * velMultiplierXYZ_invMassW.y * biasCoefficient; const PxReal deltaF2 = (tgsError.z - velOfRigidAtAttachmentPoint2 * velDt) * velMultiplierXYZ_invMassW.z * biasCoefficient; // const PxVec3 deltaImpulse = (normal0 * deltaF0 + normal1 * deltaF1 + normal2 * deltaF2); const PxVec3 deltaImpulse = PxVec3(deltaF0, deltaF1, deltaF2); deltaLinVel = deltaImpulse * velMultiplierXYZ_invMassW.w; deltaAngVel = raXn0 * deltaF0 + raXn1 * deltaF1 + raXn2 * deltaF2; return deltaImpulse; } template PX_FORCE_INLINE __device__ PxVec3 calculateAttachmentDeltaImpulseTGS(PxU32 offset, const ConstraintType& constraint, const PxgVelocityPackTGS& vel0, const PxVec3& linDelta1, PxReal dt, PxReal biasCoefficient, bool isVelocityIteration, PxVec3& deltaLinVel, PxVec3& deltaAngVel) { return calculateAttachmentDeltaImpulseTGS(constraint.raXn0_biasW[offset], constraint.raXn1_biasW[offset], constraint.raXn2_biasW[offset], constraint.velMultiplierXYZ_invMassW[offset], constraint.low_high_limits[offset], constraint.axis_angle[offset], vel0, linDelta1, dt, biasCoefficient, isVelocityIteration, deltaLinVel, deltaAngVel); } #endif // __ATTACHMENTS_CUH__