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.

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); } |
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() ); } } |

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; |

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.

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.

Testbed test: iforce2d_HovercarSuspension.h
Linux binary
Windows binary
YouTube video