Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Actually limiting maximum speed in Box2D

Tags:

c++

box2d

I want to limit maximum speed a body can travel with.

The problem is, even if I do something like this answer suggests:

/* after applying forces from input for example */
b2Vec2 vel = body->GetLinearVelocity();
float speed = vel.Normalize();//normalizes vector and returns length
if ( speed > maxSpeed ) 
    body->SetLinearVelocity( maxSpeed * vel );

What if, for example, right before clamping the velocity I am applying some huge force to the body? Even if linear velocity is capped to maxSpeed for the moment, in the next timestep Box2D will take the b2Body::m_force value into account and effectively move my body faster than maxSpeed.

So I came up with this (had to move b2Body::m_force to public):

if ( speed > maxSpeed ) {
    body->SetLinearVelocity( maxSpeed * vel );
    body->m_force = b2Vec2(0, 0)
}

Yet this still doesn't handle the problem properly.

What if the velocity is slightly smaller than maxSpeed so the condition will not be hit, but still the m_force value will be big enough to increase velocity too much?

The point is I can't make accurate predictions as to how force will impact the velocity as I am stepping using delta accumulator and I don't know how many physics steps will be required for the moment.

Is there any way to handle this other than just to limit the velocity directly before integrating position in Box2D source code?

like image 546
Pythagoras of Samos Avatar asked Oct 02 '22 13:10

Pythagoras of Samos


2 Answers

My first attempt to solve this problem was by simply executing above pieces of code not every loop, but every physics substep, which means that if my delta accumulator tells me I have to perform n b2World::Step's, I also cap the velocity n times:

// source code taken form above link and modified for my purposes
for (int i = 0; i < nStepsClamped; ++ i)
{
    resetSmoothStates_ ();

    // here I execute whole systems that apply accelerations, drag forces and limit maximum velocities
    // ...
    if ( speed > maxSpeed ) 
         body->SetLinearVelocity( maxSpeed * vel );
    // ...

    singleStep_ (FIXED_TIMESTEP);

    // NOTE I'M CLEARING FORCES EVERY SUBSTEP to avoid excessive accumulation
    world_->ClearForces ();
}

Now while this gives me constant speed regardless of frame rate (which was my primary concern as my movement was jittery), it is not always <= maxSpeed. The same scenario: imagine a huge force applied just before capping the velocity and exceuting b2World::Step.

Now, I could simply calculate the actual force to be applied according to the current velocity, as I know the force will be applied only once until next validation, but there's another simple solution that I've already mentioned and eventually sticked with:

  1. Go to Box2D\Dynamics\b2Body.h
  2. Add float32 m_max_speed public member and initialize it with -1.f so initially we don't limit velocities for any body.
  3. Go to Box2D\Dynamics\b2Island.cpp.
  4. Locate line number 222.
  5. Add following if condition:

    m_positions[i].c = c;
    m_positions[i].a = a;
    
    if (b->m_max_speed >= 0.f) {
        float32 speed = v.Normalize();
        if (speed > b->m_max_speed)
            v *= b->m_max_speed;
        else v *= speed;
    }
    
    m_velocities[i].v = v;
    m_velocities[i].w = w; 
    

This will work even without substepping that I've described above but keep in mind that if you were to simulate air resistance, applying drag force every substep guarantees correctness of the simulation even with varying framerates.

like image 181
Pythagoras of Samos Avatar answered Oct 12 '22 15:10

Pythagoras of Samos


First, answer for yourself, who can apply force to a body. Box2D itself can impact bodies via contacts and gravity. Contacts are not using forces, but impulses. To manage them setup contact listener and modify normalImpulses and tangentImpulses . Gravity I think cant impact body a lot, but it also can be controlled via b2BodyDef::gravityScale. If your code aplying some manual forces, it maybe usefull to introduce some proxy interface to manage them.

I cant see some easy way, because at each step box2d makes several velocity and position iterations. So, forces and impulses applied to it at begin of step will cause change of position accordingly.

I cant imagine the way, how strict velocity without hacking box2d source code. By the way, I think it is not bad variant. For example, insert restriction in Dynamics/b2Island.cpp:219 (b2Island::Solve) to w and v variables.

like image 30
Pavel Avatar answered Oct 12 '22 15:10

Pavel