Vehicle Physics |
This article discusses how to simulate vehicles, like cars.
This topic contains the following sections:
There are 2 common ways to implement car physics:
Constraint Car:
The ConstraintCarSample in the Samples shows how to implement a very basic constraint car, where a car is created using rigid bodies and constraints. The chassis is a box and the wheels are cylindrical bodies. The wheels are connected to the chassis using constraints (HingeJoints for the rear-wheels, Hinge2Joints for the steerable front-wheels). The wheel rotation is controlled using motor constraints.
This works in principle but has a few disadvantages: We would need more complex joints to simulate suspension. The sliding behavior of wheels is different from standard rigid bodies. Real wheels aren’t rigid bodies. We have a lot of constraints that have to be configured very carefully. Etc. - For action games a different simulation method is much simpler and more customizable:
Ray-cast Car:
In a ray-cast car the chassis is a rigid body, but the wheels are only short rays that sample the ground. In the simulation, there are no cylindrical shapes or rigid bodies for the wheels. The wheels are only rendered as part of the visual model. A ray-cast car is simpler, more efficient and gives us a lot of tuning options.
The class Vehicle implements a ray-cast car which can be used to simulate vehicles, like racing cars, with a variable number of Wheels.
The VehicleSample (see Samples) shows how to use vehicle physics.
Here is a video of the vehicle sample:
This section describes the vehicle implementation of the Vehicle class and how it is used in the VehicleSample.
We use a single rigid body for the chassis of the car. You can use a simple box or a composite shape consisting of two boxes (lower car part + upper car part) as the shape. If you use a more detailed car model, it is better to use the convex hull of the mesh vertices as the shape because triangle meshes are not optimal for collision detection.
The original car model used in the sample has about 10,000 vertices – its convex hull about 500 vertices. That’s still quite a lot of vertices. Therefore we simplified the convex hull: Our convex hull generator allows to set an upper limit for the allowed number of vertices. In our example we set an upper limit of max. 64 vertices. The simplified convex hull has less vertices than the original convex hull. However, it is also slightly bigger. That’s the disadvantage of a simplified convex hull.
The convex hull worked quite well in most cases. Except in uneven terrain the car chassis could hit a hump in the ground and stop the car. The mesh of our sports car was too close to the ground for some uneven off-road parts in our level. To give the car a bit more freedom, we shrank the convex hull shape by a small factor (using a negative skin with of –4 cm).
In the sample the chassis shape is created like this:
// 1. Extract the vertices from the car model. TriangleMesh mesh = TriangleMesh.FromModel(_car); // 2. (Optional) Create simplified convex hull from mesh. DcelMesh convexHull = GeometryHelper.CreateConvexHull( mesh.Vertices, 64, -0.04f); // 3. Create convex polyhedron using the vertices of the // convex hull. ConvexPolyhedron chassisShape = new ConvexPolyhedron( convexHull.Vertices.Select(v => v.Position));
The mass frame defines the center of mass of a rigid body. Normally, a physics engine computes the center of mass automatically from the given shape. For the chassis, the automatically computed center of mass (COM) is not optimal because it assumes a uniform density in the whole chassis. Therefore, we adjust the position of the COM manually. And we apply another trick: We artificially place the COM lower to the ground. It can even be outside of the shape under the ground! Lowering the COM makes the car more stable. Cars with a high COM are unstable in tight curves and tend to overturn. Here is how we create the mass frame and the final rigid body for the chassis:
// The mass properties of the car. We use a mass of 800 kg. MassFrame mass = MassFrame.FromShapeAndMass( chassisShape, Vector3F.One, 800, 0.1f, 1); Pose pose = mass.Pose; pose.Position.Y -= 0.5f; // Lower the center of mass. pose.Position.Z = -0.5f; // Center should be below the driver. mass.Pose = pose; RigidBody chassis = new RigidBody(chassisShape, mass, material);
For the material: We use a standard material with standard restitution and friction values. The material is only important when the car chassis slides along walls or the ground in crashes.
We create a ray for each wheel. The ray of a wheel starts where the suspension is fixed to the chassis. It shoots down and its length is the suspension length plus the wheel radius. These rays are used to detect the ground distance and the normal vector of the ground in each frame. And in each frame we have to apply 3 forces per wheel:
The suspension is modeled as simple damped spring that has a spring constant (suspension stiffness) and a damping constant (suspension damping). Actually, we use two different damping coefficients: one when the suspension is compressing (higher) and another one when the suspension is extending (lower than the compression damping). The current suspension length is hit distance of the ray - wheel radius. We assume that the suspension spring is at rest when it is fully extended. The suspension force in pseudo code is
suspensionForce = stiffness * (restLength – currentLength) + damping * (previousLength – currentLength) / deltaTime
This force is applied to the chassis at the position where the wheel touches the ground. And an equal but opposite force is applied to the ground.
The goal of the side force is to stop any sideways motion of the wheel. We compute a vector in the ground plane that is orthogonal to the current rolling direction. Then we compute the force that is necessary to stop the sideways motion of the chassis at the wheel ground position.
The side force is computed but not applied immediately.
The forward force is applied to the chassis at the wheel ground position. It acts in the forward direction of the wheel and lies in the ground plane. This force accelerates the car (user steps onto the gas pedal) or decelerates the car (braking).
The forward force is further divided into the motor force and the brake force. The motor force increases when the user presses the forward button. It decreases and can get negative when the user presses the backwards button. The motor force returns to 0 when the user does not press the forward or backward buttons.
The brake force is set to a constant value when the user presses the handbrake button. If the motor force is larger than the brake force, then we use the difference as the magnitude of our forward force. If the brake force is larger, then we cannot simply use the brake force value as the magnitude of the forward force. The brake force could be too large and accelerate the car into the opposite direction - especially when the car is at rest. To compute the exact brake force, we compute the force that would stop any car motion in the forward direction. Then we limit this force by the current brake force.
After that we know the magnitude of the forward force – but we do not apply this force yet.
The computed forward and side forces are ideal, in the sense that the wheel is not sliding. But sliding a.k.a. drifting is one of the fun parts of racing games and we need to model this too.
When an object is pushed tangential to the ground, a friction force acts against the movement. This friction force is limited by
maxFrictionForce = frictionCoefficient * normalForce
For each wheel, we assume a constant friction coefficient. And we already know the normal force: It is simply the suspension force computed earlier. This maxFrictionForce limits the possible forward and side forces. We sum up the forward force (in forward direction) and the side force (in side direction) and get the sum force that lies in the ground plane. When the magnitude of this force is larger than the maxFrictionForce, the wheel is sliding and we clamp the magnitude to the maxFrictionForce. The clamped force is applied to the chassis at the wheel ground position.
We further compute the so-called skid energy, which is the energy of friction [1] when the car is sliding. This information can be used to control skid sounds or rendering of tire marks.
One important aspect of this sliding model is that forward force and side force are summed up before they are clamped. That means, the side force is indirectly limited by the current forward force. For example, if the car is at rest, it is hard to push the car sideways. But if the car is accelerating or decelerating, the maximal side force is reduced. In other words: Drive at a high speed in a curve and hit the handbrake, the braking wheels will start to slide.
Our wheels are only rays, still we have to compute a wheel rotation for visual wheel models. We simply compute the linear point velocity of the chassis at the wheel ground position in the forward direction (in the ground plane). The angular velocity of the wheel is then
wheel.AngularVelocity = forwardVelocity / wheel.Radius;
and the rotation angle is
wheel.RotationAngle += wheel.AngularVelocity * deltaTime;
You might want to change this. For example: Immediately stop the wheel rotation when the handbrake is active – for a dramatic effect.
To steer the car, we set the steering angle of the wheels. The steering angle defines the forward direction in which the forward force is applied. (The side force is always normal to the forward force.) The steering angle changes when the user presses the steering buttons (e.g. the left/right arrow keys on the keyboard). The steering angle does not change instantly. Instead, it changes over time until it reaches the maximal allowed steering angle. When the user is not pressing a steering button, the steering angle goes back to 0.
For very high speeds it is advisable to reduce the maximal allowed steering angles – small angles will suffice and keep the car controllable.
Good car physics needs a lot of tuning and also depends on the type of game. Following parameters should be considered for tuning:
The implemented car physics can be improved in several aspects: (The vehicle source code is available for licensees!)
In a third-person view the camera can be fixed behind the car. This is used in our sample but does not look very dynamic. A spring-based chase camera, like in the AppHub Chase Camera sample, could look better. Or, compute the camera direction as a linear interpolation of the current car orientation and the current driving direction.
The ConstraintVehicleSample (see Samples) contains an alternative vehicle physics implementation. The new implementation is very similar to the Vehicle class described above. It also uses rays to represent the wheels, but instead of a force effect it uses constraints which model the wheel forces.
Using constraints is more difficult than applying forces. However, they improve stability. For example, when a stiff spring is modeled with forces, the simulation will easily become unstable. Constraints can be used to model stable springs [2].
The alternative car physics implementation provides the following benefits:
*The last two changes could also easily be added to the old vehicle sample.
The disadvantage of the new implementation is that it is probably more difficult to understand; hence, more difficult to adapt.
[1] "Energy of Friction", Wikipedia, http://en.wikipedia.org/wiki/Friction#Energy_of_friction
[2] Erin Catto: "Soft Constraints – Reinventing the Spring", GDC 2011, http://code.google.com/p/box2d/downloads/detail?name=GDC2011_Catto_Erin_Soft_Constraints.pdf