Files
PhysX4.1/physx/source/simulationcontroller/src/ScConstraintSim.cpp
2025-11-28 23:13:44 +05:30

491 lines
17 KiB
C++

//
// 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-2021 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 "ScScene.h"
#include "ScConstraintProjectionManager.h"
#include "ScBodySim.h"
#include "ScStaticSim.h"
#include "PxsContext.h"
#include "ScConstraintCore.h"
#include "ScConstraintSim.h"
#include "ScConstraintInteraction.h"
#include "ScElementSimInteraction.h"
#include "CmVisualization.h"
#include "ScObjectIDTracker.h"
#include "DyContext.h"
using namespace physx;
PX_FORCE_INLINE void invalidateConstraintGroupsOnAdd(Sc::ConstraintProjectionManager& cpm, Sc::BodySim* b0, Sc::BodySim* b1, Sc::ConstraintSim& constraint)
{
// constraint groups get built by starting from dirty constraints that need projection. If a non-projecting constraint gets added
// we need to restart the whole process (we do not want to track dirty non-projecting constraints because of a scenario where
// all constraints of a group get switched to non-projecting which should kill the group and not rebuild a new one).
if (b0 && b0->getConstraintGroup())
cpm.invalidateGroup(*b0->getConstraintGroup(), &constraint);
if (b1 && b1->getConstraintGroup())
cpm.invalidateGroup(*b1->getConstraintGroup(), &constraint);
}
Sc::ConstraintSim::ConstraintSim(ConstraintCore& core, RigidCore* r0, RigidCore* r1, Scene& scene) :
mScene (scene),
mCore (core),
mInteraction(NULL),
mFlags (0)
{
mBodies[0] = (r0 && (r0->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r0->getSim()) : 0;
mBodies[1] = (r1 && (r1->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r1->getSim()) : 0;
mLowLevelConstraint.index = scene.getConstraintIDTracker().createID();
Ps::Array<Dy::ConstraintWriteback, Ps::VirtualAllocator>& writeBackPool = scene.getDynamicsContext()->getConstraintWriteBackPool();
if (mLowLevelConstraint.index >= writeBackPool.capacity())
{
writeBackPool.reserve(writeBackPool.capacity() * 2);
}
writeBackPool.resize(PxMax(writeBackPool.size(), mLowLevelConstraint.index + 1));
writeBackPool[mLowLevelConstraint.index].initialize();
if (!createLLConstraint())
return;
PxReal linBreakForce, angBreakForce;
core.getBreakForce(linBreakForce, angBreakForce);
if ((linBreakForce < PX_MAX_F32) || (angBreakForce < PX_MAX_F32))
setFlag(eBREAKABLE);
core.setSim(this);
ConstraintProjectionManager& cpm = scene.getProjectionManager();
if (!needsProjection())
invalidateConstraintGroupsOnAdd(cpm, mBodies[0], mBodies[1], *this);
else
cpm.addToPendingGroupUpdates(*this);
ConstraintSim* cs = this; // to make the Wii U compiler happy
mInteraction = mScene.getConstraintInteractionPool()->construct(cs,
r0 ? *r0->getSim() : scene.getStaticAnchor(),
r1 ? *r1->getSim() : scene.getStaticAnchor());
PX_ASSERT(!mInteraction->isRegistered()); // constraint interactions must not register in the scene, there is a list of Sc::ConstraintSim instead
}
Sc::ConstraintSim::~ConstraintSim()
{
PX_ASSERT(mInteraction); // This is fine now, a body which gets removed from the scene removes all constraints automatically
PX_ASSERT(!mInteraction->isRegistered()); // constraint interactions must not register in the scene, there is a list of Sc::ConstraintSim instead
if (readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
mScene.getProjectionManager().removeFromPendingGroupUpdates(*this);
if (!isBroken())
mInteraction->destroy();
mScene.getConstraintIDTracker().releaseID(mLowLevelConstraint.index);
mScene.getConstraintInteractionPool()->destroy(mInteraction);
destroyLLConstraint();
mCore.setSim(NULL);
}
bool Sc::ConstraintSim::createLLConstraint()
{
Dy::Constraint& llc = mLowLevelConstraint;
ConstraintCore& core = getCore();
PxU32 constantBlockSize = core.getConstantBlockSize();
void* constantBlock = mScene.allocateConstraintBlock(constantBlockSize);
if(!constantBlock)
{
Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "Constraint: could not allocate low-level resources.");
return false;
}
//Ensure the constant block isn't just random data because some functions may attempt to use it before it is
//setup. Specifically pvd visualization of joints
//-CN
PxMemZero( constantBlock, constantBlockSize);
core.getBreakForce(llc.linBreakForce, llc.angBreakForce);
llc.flags = core.getFlags();
llc.constantBlockSize = PxU16(constantBlockSize);
llc.solverPrep = core.getSolverPrep();
llc.project = core.getProject();
llc.constantBlock = constantBlock;
//llc.index = mLowLevelConstraint.index;
llc.body0 = mBodies[0] ? &mBodies[0]->getLowLevelBody() : 0;
llc.body1 = mBodies[1] ? &mBodies[1]->getLowLevelBody() : 0;
llc.bodyCore0 = mBodies[0] ? &llc.body0->getCore() : NULL;
llc.bodyCore1 = mBodies[1] ? &llc.body1->getCore() : NULL;
llc.minResponseThreshold = core.getMinResponseThreshold();
return true;
}
void Sc::ConstraintSim::destroyLLConstraint()
{
if(mLowLevelConstraint.constantBlock)
{
mScene.deallocateConstraintBlock(mLowLevelConstraint.constantBlock,
mLowLevelConstraint.constantBlockSize);
}
}
void Sc::ConstraintSim::preBodiesChange()
{
PX_ASSERT(mInteraction);
BodySim* b = getConstraintGroupBody();
if (b)
mScene.getProjectionManager().invalidateGroup(*b->getConstraintGroup(), this);
if (!isBroken())
mInteraction->destroy();
mScene.getConstraintInteractionPool()->destroy(mInteraction);
mInteraction = NULL;
}
void Sc::ConstraintSim::postBodiesChange(RigidCore* r0, RigidCore* r1)
{
PX_ASSERT(mInteraction == NULL);
BodySim* b0 = (r0 && (r0->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r0->getSim()) : 0;
BodySim* b1 = (r1 && (r1->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r1->getSim()) : 0;
ConstraintProjectionManager& cpm = mScene.getProjectionManager();
PxConstraintFlags::InternalType projectionNeeded = getCore().getFlags() & PxConstraintFlag::ePROJECTION; // can not use "needsProjection()" because that takes into account whether the constraint is broken
if (!projectionNeeded)
invalidateConstraintGroupsOnAdd(cpm, b0, b1, *this);
else if (!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
cpm.addToPendingGroupUpdates(*this);
Dy::Constraint& c = mLowLevelConstraint;
c.body0 = b0 ? &b0->getLowLevelBody() : NULL;
c.body1 = b1 ? &b1->getLowLevelBody() : NULL;
c.bodyCore0 = c.body0 ? &c.body0->getCore() : NULL;
c.bodyCore1 = c.body1 ? &c.body1->getCore() : NULL;
mBodies[0] = b0;
mBodies[1] = b1;
ConstraintSim* cs = this; // to make the Wii U compiler happy
mInteraction = mScene.getConstraintInteractionPool()->construct(cs,
r0 ? *r0->getSim() : mScene.getStaticAnchor(),
r1 ? *r1->getSim() : mScene.getStaticAnchor());
}
void Sc::ConstraintSim::checkMaxForceExceeded()
{
PX_ASSERT(readFlag(eCHECK_MAX_FORCE_EXCEEDED));
Dy::ConstraintWriteback& solverOutput = mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
if(solverOutput.broken)
{
setFlag(ConstraintSim::eBROKEN);
mScene.addBrokenConstraint(&mCore);
mCore.breakApart();
mInteraction->destroy();
// update related SIPs
{
ActorSim& a0 = mInteraction->getActorSim0();
ActorSim& a1 = mInteraction->getActorSim1();
ActorSim& actor = (a0.getActorInteractionCount()< a1.getActorInteractionCount()) ? a0 : a1;
actor.setActorsInteractionsDirty(InteractionDirtyFlag::eFILTER_STATE, NULL, InteractionFlag::eRB_ELEMENT);
// because broken constraints can re-enable contact response between the two bodies
}
PX_ASSERT(!readFlag(eCHECK_MAX_FORCE_EXCEEDED));
}
}
void Sc::ConstraintSim::getForce(PxVec3& lin, PxVec3& ang)
{
const PxReal recipDt = mScene.getOneOverDt();
Dy::ConstraintWriteback& solverOutput= mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
lin = solverOutput.linearImpulse * recipDt;
ang = solverOutput.angularImpulse * recipDt;
}
void Sc::ConstraintSim::setBreakForceLL(PxReal linear, PxReal angular)
{
PxU8 wasBreakable = readFlag(eBREAKABLE);
PxU8 isBreakable;
if ((linear < PX_MAX_F32) || (angular < PX_MAX_F32))
isBreakable = eBREAKABLE;
else
isBreakable = 0;
if (isBreakable != wasBreakable)
{
if (isBreakable)
{
PX_ASSERT(!readFlag(eCHECK_MAX_FORCE_EXCEEDED));
setFlag(eBREAKABLE);
if (mInteraction->readInteractionFlag(InteractionFlag::eIS_ACTIVE))
mScene.addActiveBreakableConstraint(this, mInteraction);
}
else
{
if (readFlag(eCHECK_MAX_FORCE_EXCEEDED))
mScene.removeActiveBreakableConstraint(this);
clearFlag(eBREAKABLE);
}
}
mLowLevelConstraint.linBreakForce = linear;
mLowLevelConstraint.angBreakForce = angular;
}
void Sc::ConstraintSim::postFlagChange(PxConstraintFlags oldFlags, PxConstraintFlags newFlags)
{
mLowLevelConstraint.flags = newFlags;
// PT: don't convert to bool if not needed
const PxU32 hadProjection = (oldFlags & PxConstraintFlag::ePROJECTION);
const PxU32 needsProjection = (newFlags & PxConstraintFlag::ePROJECTION);
if(needsProjection && !hadProjection)
{
PX_ASSERT(!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE)); // Non-projecting constrainst should not be part of the update list
Sc::BodySim* b0 = getBody(0);
Sc::BodySim* b1 = getBody(1);
if ((!b0 || b0->getConstraintGroup()) && (!b1 || b1->getConstraintGroup()))
{
// Already part of a constraint group but not as a projection constraint -> re-generate projection tree
PX_ASSERT(b0 != NULL || b1 != NULL);
if (b0)
b0->getConstraintGroup()->markForProjectionTreeRebuild(mScene.getProjectionManager());
else
b1->getConstraintGroup()->markForProjectionTreeRebuild(mScene.getProjectionManager());
}
else
{
// Not part of a constraint group yet
mScene.getProjectionManager().addToPendingGroupUpdates(*this);
}
}
else if(!needsProjection && hadProjection)
{
if (!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
{
Sc::BodySim* b = getConstraintGroupBody();
if (b)
{
PX_ASSERT(b->getConstraintGroup());
mScene.getProjectionManager().invalidateGroup(*b->getConstraintGroup(), NULL);
}
// This is conservative but it could be the case that this constraint with projection was the only
// one in the group and thus the whole group must be killed. If we had a counter for the number of
// projecting constraints per group, we could just update the projection tree if the counter was
// larger than 1. But switching the projection flag does not seem likely anyway.
}
else
mScene.getProjectionManager().removeFromPendingGroupUpdates(*this); // Was part of a group which got invalidated
PX_ASSERT(!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE)); // make sure the expected post-condition is met for all paths
}
}
Sc::RigidSim& Sc::ConstraintSim::getRigid(PxU32 i)
{
PX_ASSERT(mInteraction);
if (i == 0)
return static_cast<RigidSim&>(mInteraction->getActorSim0());
else
return static_cast<RigidSim&>(mInteraction->getActorSim1());
}
bool Sc::ConstraintSim::hasDynamicBody()
{
return (mBodies[0] && (!mBodies[0]->isKinematic())) || (mBodies[1] && (!mBodies[1]->isKinematic()));
}
static void constrainMotion(PxsRigidBody* body, PxTransform& targetPose)
{
//Now constraint deltaPos and deltaRot
const PxU32 lockFlags = body->mCore->lockFlags;
if (lockFlags)
{
const PxTransform& currBody2World = body->mCore->body2World;
PxVec3 deltaPos = targetPose.p - currBody2World.p;
PxQuat deltaQ = targetPose.q * currBody2World.q.getConjugate();
if (deltaQ.w < 0) //shortest angle.
deltaQ = -deltaQ;
PxReal angle;
PxVec3 axis;
deltaQ.toRadiansAndUnitAxis(angle, axis);
PxVec3 deltaRot = axis * angle;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_X)
deltaPos.x = 0.0f;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_Y)
deltaPos.y = 0.0f;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_Z)
deltaPos.z = 0.0f;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_X)
deltaRot.x = 0.0f;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y)
deltaRot.y = 0.0f;
if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z)
deltaRot.z = 0.0f;
targetPose.p = currBody2World.p + deltaPos;
PxReal w2 = deltaRot.magnitudeSquared();
if (w2 != 0.0f)
{
PxReal w = PxSqrt(w2);
const PxReal v = w * 0.5f;
PxReal s, q;
Ps::sincos(v, s, q);
s /= w;
const PxVec3 pqr = deltaRot * s;
const PxQuat quatVel(pqr.x, pqr.y, pqr.z, 0);
PxQuat result = quatVel * currBody2World.q;
result += currBody2World.q * q;
targetPose.q = result.getNormalized();
}
else
{
targetPose.q = currBody2World.q;
}
}
}
void Sc::ConstraintSim::projectPose(BodySim* childBody, Ps::Array<BodySim*>& projectedBodies)
{
#if PX_DEBUG
// We expect bodies in low level constraints to have same order as high level counterpart
PxsRigidBody* b0 = mLowLevelConstraint.body0;
PxsRigidBody* b1 = mLowLevelConstraint.body1;
PX_ASSERT( (childBody == getBody(0) && &childBody->getLowLevelBody() == b0) ||
(childBody == getBody(1) && &childBody->getLowLevelBody() == b1) );
#endif
Dy::Constraint& constraint = getLowLevelConstraint();
bool projectToBody0 = childBody == getBody(1);
PxsRigidBody* body0 = constraint.body0,
* body1 = constraint.body1;
PxTransform body0ToWorld = body0 ? body0->getPose() : PxTransform(PxIdentity);
PxTransform body1ToWorld = body1 ? body1->getPose() : PxTransform(PxIdentity);
(*constraint.project)(constraint.constantBlock, body0ToWorld, body1ToWorld, projectToBody0);
if(projectToBody0)
{
PX_ASSERT(body1);
//Constrain new pose to valid world motion
constrainMotion(body1, body1ToWorld);
body1->setPose(body1ToWorld);
projectedBodies.pushBack(getBody(1));
}
else
{
PX_ASSERT(body0);
//Constrain new pose to valid world motion
constrainMotion(body0, body0ToWorld);
body0->setPose(body0ToWorld);
projectedBodies.pushBack(getBody(0));
}
}
bool Sc::ConstraintSim::needsProjection()
{
const Dy::ConstraintWriteback& solverOutput = mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
return (getCore().getFlags() & PxConstraintFlag::ePROJECTION ) && !solverOutput.broken;
}
PX_INLINE Sc::BodySim* Sc::ConstraintSim::getConstraintGroupBody()
{
BodySim* b = NULL;
if (mBodies[0] && mBodies[0]->getConstraintGroup())
b = mBodies[0];
else if (mBodies[1] && mBodies[1]->getConstraintGroup())
b = mBodies[1];
return b;
}
void Sc::ConstraintSim::visualize(PxRenderBuffer& output)
{
if(!(getCore().getFlags() & PxConstraintFlag::eVISUALIZATION))
return;
PxsRigidBody* b0 = mLowLevelConstraint.body0;
PxsRigidBody* b1 = mLowLevelConstraint.body1;
const PxTransform idt(PxIdentity);
const PxTransform& t0 = b0 ? b0->getPose() : idt;
const PxTransform& t1 = b1 ? b1->getPose() : idt;
const PxReal frameScale = mScene.getVisualizationScale() * mScene.getVisualizationParameter(PxVisualizationParameter::eJOINT_LOCAL_FRAMES);
const PxReal limitScale = mScene.getVisualizationScale() * mScene.getVisualizationParameter(PxVisualizationParameter::eJOINT_LIMITS);
Cm::RenderOutput renderOut(static_cast<Cm::RenderBuffer &>(output));
Cm::ConstraintImmediateVisualizer viz(frameScale, limitScale, renderOut);
PxU32 flags = 0;
if(frameScale!=0.0f)
flags |= PxConstraintVisualizationFlag::eLOCAL_FRAMES;
if(limitScale!=0.0f)
flags |= PxConstraintVisualizationFlag::eLIMITS;
mCore.getVisualize()(viz, mLowLevelConstraint.constantBlock, t0, t1, flags);
}
void Sc::ConstraintSim::setConstantsLL(void* addr)
{
PxMemCopy(mLowLevelConstraint.constantBlock, addr, mLowLevelConstraint.constantBlockSize);
getAnyBody()->getScene().getSimulationController()->updateJoint(mInteraction->getEdgeIndex(), &mLowLevelConstraint);
}