Box2D C++ tutorials - Hovercar suspension

Last edited: July 14 2013


Halt! You should have a good understanding of
the basic tutorials before venturing further.


Suspension calculation


Wheeled vehicles typically have damped spring suspension which means that the distance between the chassis and the wheel will tend toward a specific rest position, but is limited in how fast it can move there. This kind of movement is simulated very well by Box2D's wheel joint. The distance joint also has similar behaviour to the linear part of the wheel joint. If you want to keep two bodies in alignment and with a suspension-like movement you could use a prismatic joint and a distance joint together.

But how about if you don't want any joints at all between the two bodies? A good example of this is a 'hovercar' type behavior where a body is suspended above the ground as if on a dampened spring when it is close to the ground, but there is nothing to stop it from moving further upwards. This means that the body could be above anything so we don't really want to make any joints between it and the ground.

In this topic we'll look at what forces should be applied to a body to get this kind of movement. We'll use a raycast to find out what is below the body and how far away it is. Although the example will look at a 'hovercar' scenario it's worth noting that you can do the same thing for player characters where the character is made from two bodies joined by a prismatic joint, instead of using a distance joint. The nice thing about that method is you can alter the target height to get the character to crouch, and you can customize the way the height is changed (eg. make it quicker to crouch than to stand up, etc).


The basics


After finding out how far the body is from the ground, all we really need to do is apply a force of the right magnitude. When the body is close to its target height we don't want it to move much so the force will be very small - think of this as a spring at its rest length - and when the body is close to the ground the force should be large. Hovercar suspension A common way for calculating the force of a spring is to multiply the distance from the rest position by a 'spring constant' which specifies how strong the spring is. So to achieve this it's just a matter of calling ApplyForce with a force calculated from the distance below the target height. At this point let's do a little coding to get us that far for now.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  {//ground
      b2EdgeShape edgeShape;
      edgeShape.Set( b2Vec2(-20,0), b2Vec2(20,0) );
  
      b2BodyDef bodyDef;
      m_world->CreateBody(&bodyDef)->CreateFixture(&edgeShape, 0);
  }
  
  {//hovercar
      b2BodyDef bodyDef;
      bodyDef.type = b2_dynamicBody;
      bodyDef.fixedRotation = true;
      bodyDef.position.Set(0,10);
      m_hovercarBody = m_world->CreateBody(&bodyDef);
  
      b2PolygonShape polygonShape;
      polygonShape.SetAsBox(2,0.5f);//4x1 box
      b2FixtureDef fixtureDef;
      fixtureDef.shape = &polygonShape;
      fixtureDef.density = 1;
  
      m_hovercarBody->CreateFixture(&fixtureDef);
  }
Now in the Step function we can cast a ray downwards to find the ground and apply a force depending on how far away it is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  float targetHeight = 3;
  float springConstant = 100;
  
  //make the ray at least as long as the target distance
  b2Vec2 startOfRay = m_hovercarBody->GetPosition();
  b2Vec2 endOfRay = m_hovercarBody->GetWorldPoint( b2Vec2(0,-5) );
  
  HovercarRayCastClosestCallback callback;
  m_world->RayCast(&callback, startOfRay, endOfRay);
  
  if ( callback.m_hit ) {
      float distanceAboveGround = (startOfRay - callback.m_point).Length();
      
      //dont do anything if too far above ground
      if ( distanceAboveGround < targetHeight ) {
          float distanceAwayFromTargetHeight = targetHeight - distanceAboveGround;
          m_hovercarBody->ApplyForce( b2Vec2(0,springConstant*distanceAwayFromTargetHeight),
                                          m_hovercarBody->GetWorldCenter() );
      }
  }
Hovercar suspension

The extra touch


Well that's a good start, but you'll notice that with only a spring to affect the body, it keeps bouncing forever. This is because there is no damping on the body which means that even though we are reducing the force applied as it gets closer to the target height, all the accumulated force we've applied in the previous time steps has not yet been countered by gravity.

One way to fix this is to use SetLinearDamping on the body, but this would cause the motion to be affected all the time, even when the body is way above the ground. We could also manually apply a vertical damping to the velocity of the body when it is within range of the ground, but really what we're trying to do is scale down the force, not damp velocity.

A better way is the 'look-ahead' technique used in the rotate to angle topic, to take into account the current velocity of the body. This will cause the calculated force to scale down earlier as the rest position is approached. Let's try that.
1
2
3
4
  //after checking if distanceAboveGround < targetHeight
  //replace distanceAboveGround with the 'look ahead' distance
  //this will look ahead 0.25 seconds - longer gives more 'damping'
  distanceAboveGround += 0.25f * m_hovercarBody->GetLinearVelocity().y;
This leaves the velocity of the body otherwise untampered with, so that it will interact normally with other influences in the world. Hovercar suspension

The finale


If you are very observant you might have noticed that the body does not come to rest at the height we wanted. This is because as we push up, gravity pushes down in a continual struggle. Rather than coming to rest at the position where the hover force we're applying would be zero, the body will come to rest at the position where our hover force and gravity are equal. So for the example above, rather than settling at a height of 3 units, the body will settle at a height of 2.6 units.

This could be countered by altering the spring constant, but to really get at the root of the problem it would be cleaner to cancel gravity for the body:
1
2
3
4
  //after checking if distanceAboveGround < targetHeight
  //negate gravity
  m_hovercarBody->ApplyForce( m_hovercarBody->GetMass() * -m_world->GetGravity(),
                                  m_hovercarBody->GetWorldCenter() );


Other considerations


With the simple example we have here, you'll find that the body is very sensitive to the surface contours, because there is only one ray. For example, in the situation below the ray can fit between the two boxes and is not seeing a good representation of the ground below. You would probably want to have more than one ray along the bottom of the body and take the average/minimum of them or something like that to avoid this problem. Hovercar suspension

Source code


Here is the source code for those who would like to try it out for themselves. This is a 'test' for the testbed, based on Box2D v2.3.0.
Box2D sticky projectiles
Testbed test: iforce2d_HovercarSuspension.h

Linux binary
Windows binary

YouTube video