"polite" rigid body that doesn't affect others

Post Reply
kossio
Posts: 10
Joined: Tue Jul 18, 2017 7:48 pm

"polite" rigid body that doesn't affect others

Post by kossio »

Hello all ^_^

I'm trying to implement object picking like in Half-Life. the player presses a button and grabs the object so it can be carried around.
I currently do this by btPoint2PointConstraint constraint. It works fine so far, but the problem I have is that the "grabbed" body, cases total mayhem by adding huge velocities to every dynamic rigid body that it touches.

Ideally I want this object to be a bit more "polite" and add smaller velocities. Is there a way to achieve that? Should I use a different constraint? Is it possible to make that body be resolved as if all other dynamic bodies were static (this is not my ideal solution)? Could you suggest other solution to this problem?
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: "polite" rigid body that doesn't affect others

Post by drleviathan »

I haven't used constraints much so this advice is based purely on what I read in the API and code, not from true experience:

The btPoint2PointConstraint is not very configurable. After looking at the code it looks like a foundation was laid for making the constraint configurable but it was never finished.

Meanwhile the btGenetric6DofConstraint appears to be configurable... but the configuration may be complex so there would be a learning curve and you'd have to experiment (or study the code in much detail) to figure out how to do it. In short: each degree of freedom has a corresponding "LimitMotor" which has a bunch of parameters that can be tweaked to make the motor soft/hard, fast/slow, and when to kick in. You would have to create the constraint, fetch each motor, and adjust its various parameters accordingly.

One simple thing to try would be to make the grabbed object very lightweight such that it gets pushed around more than it pushes. A problem with this approach would be that if it is too light then you can get instabilities and your simulation blows up when a NaN is introduced to the calculations.

Another idea is to use a custom Action instead of a constraint. This would involve deriving from btActionInterface which has only one method you need to override: updateAction(). The Bullet API guarantees that it will call updateAction() every substep for any Action added to the DynamicsWorld. It would be up to you to add the knowledge and intelligence to your CustomAction, but it could perform spring logic or do very simple critically damped motion (CDM) toward a target on a specific RigidBody (that was passed to it in its CTOR). If your object has low mass then the CDM solution can work very well and will not bang other objects around. There are a few gotchas if you choose this path, for example your updateAction() method must check body->isActive() before applying any forces or slamming any velocities, else you risk very surprising behavior when the object eventually gets activated.
kossio
Posts: 10
Joined: Tue Jul 18, 2017 7:48 pm

Re: "polite" rigid body that doesn't affect others

Post by kossio »

Thanks a lot for the help! I've implemented an action, it isn't perfect and I'm not sure how well it will works with different Steps per Frame, but here is the code if someone gets here trying do achieve the same thing. I'm still open to suggestions if there are any other solutions.

Code: Select all

struct PickedObjectAction : btActionInterface
{
	btVector3 targetPoint; // The location that we trying to reach.
	float maxPullSpeed; // The maximum speed that is going to be used to pull the object.
	btRigidBody* pickedBody = nullptr; // The body that's being controlled.

	// Some values that we've changed in order to control the body.
	btVector3 originalGravity;
	btVector3 originalAngularFactor;
	btVector3 originalInterial;
	float originalMass;

	PickedObjectAction(btRigidBody* const pickedBody, btVector3 const targetPoint, float const maxPullSpeed)
	{
		btVector3 const btZero(0.f, 0.f, 0.f);

		this->pickedBody = pickedBody;
		this->targetPoint = targetPoint;
		this->maxPullSpeed = maxPullSpeed;

		// Save the values that we are about to modify.
		originalGravity = pickedBody->getGravity();
		originalAngularFactor = pickedBody->getAngularFactor();
		originalInterial = pickedBody->getLocalInertia();
		originalMass = 1.f / pickedBody->getInvMass();

		// Clear the gravity and disable the rotation of the object.
		pickedBody->setLinearVelocity(btZero);
		pickedBody->setAngularVelocity(btZero);

		pickedBody->setGravity(btZero);
		pickedBody->setAngularFactor(btZero);

		btTransform const trJustPosition 
			= btTransform(btQuaternion::getIdentity(), pickedBody->getWorldTransform().getOrigin());
		pickedBody->setWorldTransform(trJustPosition);

		// Make the mass something really low in order to highly descrease how hard we push other objects when 
		// trying to reach our destination.
		float mass = 0.01f;
		btVector3 intertia;
		pickedBody->getCollisionShape()->calculateLocalInertia(mass, intertia);
		pickedBody->setMassProps(mass, intertia);
	}

	PickedObjectAction::~PickedObjectAction()
	{
		leaveAlone();
	}

	void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) final
	{
		btVector3 diff = targetPoint - pickedBody->getWorldTransform().getOrigin();

		// Check if we are close to the target location, if so do not apply any velocty in order to stabilze the behavior.
		if(diff.length() > 0.01f)
		{
			// Compute the value that is going to be applied and clamp;
			btVector3 vel = diff / deltaTimeStep;// / pickedBody->getInvMass();
			float const str = vel.length();
			if(str > maxPullSpeed) {
				vel *= maxPullSpeed / str;
			}

			pickedBody->setLinearVelocity(vel);
		}
		else
		{
			pickedBody->setLinearVelocity(btVector3(0,0,0));
		}
	}

	// Restore the original properties of the rigid body and leave it alone.
	void leaveAlone()
	{
		if(pickedBody)
		{
			pickedBody->setGravity(originalGravity);
			pickedBody->setAngularFactor(originalAngularFactor);
			pickedBody->setMassProps(originalMass, originalInterial);

			pickedBody = nullptr;
		}
	}

	void debugDraw(btIDebugDraw* debugDrawer) final 
	{
		btTransform tr(btQuaternion::getIdentity(), targetPoint);
		debugDrawer->drawTransform(tr, 1.f);
	}
};
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: "polite" rigid body that doesn't affect others

Post by drleviathan »

Here is a simple implementation that you can try. It assumes the object is moving toward its target position with exponential decay, which makes the velocity a scalar multiple of the displacement. The math for this looks like:

Code: Select all

x(t) = X exp(- t/timescale)
v(t) = d/dt(x(t)) = (-X/timescale) exp(-t/timescale) = (-1/timescale) x(t)
Note, the minus sign in the velocity indicates that it points in the opposite direction as the displacement, which points away from the target position. In the code below the diff variable points toward the target position so there is no need for an extra minus sign.

This method is simple and easy to tune. For a faster, more responsive action use small timescales. For slow ponderous action use large timescales. Left unobstructed the object will get very close to its target position within three or four timescales. It is stable as long as the timescale is larger than about twice your substep deltaTime.

Code: Select all

   void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) final
   {
      btVector3 diff = targetPoint - pickedBody->getWorldTransform().getOrigin();
      const btScalar CLOSE_ENOUGH = btScalar(0.001f);
      if(diff.length() > CLOSE_ENOUGH)
      {
         // use critically damped motion with no history (e.g. slam the velocity)
         const btScalar EXPONENTIAL_TIMESCALE = btScalar(0.05f);
         btScalar vel = diff / EXPONENTIAL_TIMESCALE;
         pickedBody->setLinearVelocity(vel);
      }
      else
      {
         pickedBody->setLinearVelocity(btVector3(0,0,0));
      }
   }
kossio
Posts: 10
Joined: Tue Jul 18, 2017 7:48 pm

Re: "polite" rigid body that doesn't affect others

Post by kossio »

Thanks for the suggestion, I'll give it a try!
Post Reply