2D Car Physics
Phase Two - Rigid Body SimulationOk, now we’re getting into some good stuff here. Let's put everything we just covered on the back burner now and talk about some physics. We’re going to be using a very simple Euler integration method. Basically, each frame we accumulate a bunch of forces (in our case from each wheel of the vehicle) and calculate the resultant acceleration, which is in the form of A=F/M (the same as F=MA, Newton’s second law of motion). We use this to modify Newton’s first law of motion, “an object in motion stays in motion…” So we calculate our A, and we integrate it into our V. Without an A, V would be constant, hence staying in motion, if no forces should act on it. Newton's third law gets applied in the form that any potential force the vehicle is applying to the ground, gets applied in the opposite direction to the vehicle (I'll explain this in the vehicle section). This topic is much easier to explain with symbols. So, P is our vehicle position, V is its linear velocity, F is the net force acting on it, M is its mass, A is the resultant acceleration, and T is the time step (the value our timer gave us from the last frame). A = F / M V = V + A * T P = P + V * TSo with a constant mass, and some force, we will generate acceleration, which will in turn generate velocity, which will in turn generate a displacement (a change in P). This is a basic linear rigid body simulator. Each frame, we total up some F, integrate it, and then zero out F to restart the accumulation the next frame. Now let’s talk about rotation. The angular case is nearly identical to the linear case (especially in 2D). Instead of P we have an Angle, instead of V we have an Angular Velocity, instead of F we have a torque, and instead of M we have inertia. So the angular model looks like this AngA = Torque / Inertia AngV = AngV + AngA * T Angle = Angle + AngV * TSimple huh? Now you may be wondering where this Torque came from. A torque is generated every time you apply a force. Lay a book down on your desk and push on the corner of it. The book should slide across the desk, but it should also begin to rotate. The slide is caused by the force. This rotation is caused by the torque, and the magnitude of the torque is directly proportional to how far away from the center of the object the force was applied. If you applied the force directly to the center of the object, the torque would be zero. We need to construct an AddForce function for our rigid body. This is what gets called every frame, once per wheel, to accumulate the chassis' rigid body force/torque. The linear case is simple, Force = Force + newForce. The angular case is a little trickier. We take the cross product of the force direction and the torque arm (the offset between where the force was applied and the center of mass of the body.) In 2D, this results in a scalar value that we can just add to Torque. So, Torque = Torque + TorqueArm.Cross(Force)
This is what that bit of code looks like. % is the cross product operator for my vector class.
public void AddForce(Vector worldForce, Vector worldOffset)
{
//add linar force
m_forces += worldForce;
//and it's associated torque
m_torque += worldOffset % worldForce;
}
You’ll notice the “world" prefix on the parameters. This is because all computation of the rigid body happens in world space. So as your book is rotating on the desk, the worldOffset value is changing, even though your finger is not moving on the book (this would be the relativeOffset). So if we know we’re applying a force “across the book, at the top right corner" we need to convert both “across" and “top right corner" into world space vectors, then add them to the rigid body.
Code DumpHere is my rigid body object. You’ll notice all the properties I mentioned above. It has a One thing you may be curious about is how I calculate the inertia value. That is a generalized formula I found at this link.
//our simulation object
class RigidBody
{
//linear properties
private Vector m_position = new Vector();
private Vector m_velocity = new Vector();
private Vector m_forces = new Vector();
private float m_mass;
//angular properties
private float m_angle;
private float m_angularVelocity;
private float m_torque;
private float m_inertia;
//graphical properties
private Vector m_halfSize = new Vector();
Rectangle rect = new Rectangle();
private Color m_color;
public RigidBody()
{
//set these defaults so we dont get divide by zeros
m_mass = 1.0f;
m_inertia = 1.0f;
}
//intialize out parameters
public void Setup(Vector halfSize, float mass, Color color)
{
//store physical parameters
m_halfSize = halfSize;
m_mass = mass;
m_color = color;
m_inertia = (1.0f / 12.0f) * (halfSize.X * halfSize.X)
* (halfSize.Y * halfSize.Y) * mass;
//generate our viewable rectangle
rect.X = (int)-m_halfSize.X;
rect.Y = (int)-m_halfSize.Y;
rect.Width = (int)(m_halfSize.X * 2.0f);
rect.Height = (int)(m_halfSize.Y * 2.0f);
}
public void SetLocation(Vector position, float angle)
{
m_position = position;
m_angle = angle;
}
public Vector GetPosition()
{
return m_position;
}
public void Update(float timeStep)
{
//integrate physics
//linear
Vector acceleration = m_forces / m_mass;
m_velocity += acceleration * timeStep;
m_position += m_velocity * timeStep;
m_forces = new Vector(0,0); //clear forces
//angular
float angAcc = m_torque / m_inertia;
m_angularVelocity += angAcc * timeStep;
m_angle += m_angularVelocity * timeStep;
m_torque = 0; //clear torque
}
public void Draw(Graphics graphics, Size buffersize)
{
//store transform, (like opengl's glPushMatrix())
Matrix mat1 = graphics.Transform;
//transform into position
graphics.TranslateTransform(m_position.X, m_position.Y);
graphics.RotateTransform(m_angle/(float)Math.PI * 180.0f);
try
{
//draw body
graphics.DrawRectangle(new Pen(m_color), rect);
//draw line in the "forward direction"
graphics.DrawLine(new Pen(Color.Yellow), 1, 0, 1, 5);
}
catch(OverflowException exc)
{
//physics overflow :(
}
//restore transform
graphics.Transform = mat1;
}
//take a relative vector and make it a world vector
public Vector RelativeToWorld(Vector relative)
{
Matrix mat = new Matrix();
PointF[] vectors = new PointF[1];
vectors[0].X = relative.X;
vectors[0].Y = relative.Y;
mat.Rotate(m_angle / (float)Math.PI * 180.0f);
mat.TransformVectors(vectors);
return new Vector(vectors[0].X, vectors[0].Y);
}
//take a world vector and make it a relative vector
public Vector WorldToRelative(Vector world)
{
Matrix mat = new Matrix();
PointF[] vectors = new PointF[1];
vectors[0].X = world.X;
vectors[0].Y = world.Y;
mat.Rotate(-m_angle / (float)Math.PI * 180.0f);
mat.TransformVectors(vectors);
return new Vector(vectors[0].X, vectors[0].Y);
}
//velocity of a point on body
public Vector PointVel(Vector worldOffset)
{
Vector tangent = new Vector(-worldOffset.Y, worldOffset.X);
return tangent * m_angularVelocity + m_velocity;
}
public void AddForce(Vector worldForce, Vector worldOffset)
{
//add linar force
m_forces += worldForce;
//and it's associated torque
m_torque += worldOffset % worldForce;
}
}
TestingTo make sure your rigid body works, instantiate one in your |
|