Jump to content

  • Log In with Google      Sign In   
  • Create Account


bzroom

Member Since 20 Jan 2003
Offline Last Active Jun 16 2013 02:39 AM

#4995034 2D driving physics woes (problems) 2D Vector Math questions

Posted by bzroom on 29 October 2012 - 06:50 AM

Silly author, Y u no explain things?

The reason the dot product is used instead of the vector magnitude, is because we are looking for the magnitude _in the forward direction_. Which means the result is signed. You could have a velocity _not_ in the forward direction, for which the dot product result would be negative. In other case, you may have some gigantic relative velocity, magnitude 1000, but if the vector is perpendicular to the forward direction, the dot product, and forward velocity magnitude, will be zero.

Sorry, this article is extremely dated. Good to see it's still got some legs though!

The correct projection is

Vec Project( Vec a, Vec onto )
{
Vec norm = onto.Normalized( );
return norm * Dot( a, norm ); //dot product against normalized "onto" vector.
}

You likely _dont_ want to use the magnitude of this vector, you likely just want the dot product result which is signed. (positive if vectors originally pointed in same direction, negative if not, zero if perpendicular)


#4907735 Skeletal animation joint transformation question

Posted by bzroom on 30 January 2012 - 01:27 PM

I've programmed robots for many years, and there is inevitably a case where gimbal lock will be an issue, no matter how much you prepare for them.

How are quaternions better than matrices? Both store a single absolute orientation relative to their parent.

Also, we have custom ragdoll and it hardly uses TRS. The physics doesn't spit out TRS, so that seems like it would require extra work.

My bad on the 1/T thing. I was trying to choose a symbol that would be uniform across the board. So that the matrix proof line would look good.

They must be applied in the reverse order correct?
Because a rotate 1 plus translate (2,0), inverted is not rotate -1 plus translate (-2,0).


#4907529 Skeletal animation joint transformation question

Posted by bzroom on 29 January 2012 - 11:09 PM

i wasn't sure what SQT meant at first.. but now i get it. Scale Quaternion Translation? We normally call it PRS, for position rotation scale (which makes no suggestion of the storage types.)

Anyways. I would not store the bone data i mentioned in PRS format. I would only store the actual animation keyframe data in PRS, since it is much easier to interpolate than a fully composed matrix. It is completely impossible to avoid gimbal lock. Matrices are likely your best defense against gimbal lock for storing absolute tranforms. Incremental transformations, and affine transformations are where it will cause problems.

Object space = model space, yes.

Also, correct on the parentRel thing. The parent access needs to be checked so that first bone, who's parent is -1, does not access bad memory. But your pseudo code is correct.

The inverse of PRS, or TQS would be 1/S 1/R 1/P, or 1/S 1/Q 1/T. (ie. inverted affine transformations in the opposite order)
How you store a 1/S 1/R 1/P into a PRS likely requires composing a full matrix and decomposing it again.

Proof: P * R * S * 1/S * 1/R * 1/P = identity;

To answer your initial question about which part of PRS would store the parent relative offset. Well in the parent relative transform, that's be the P part.
struct PRS
{
   Vec3 P;
   Quat R;
   Vec3 S:
   PRS( const Matrix& m )
   {
	 P = m.Translation( );
	 R = m.Rotation( );
	 S = m.Scale( );
   }
   Matrix ToMatrix( ) const
   {
	  return Matix( P ) * Matrix( R ) * Matrix( S );
   }
   PRS ToInverse( ) const
   {
	 return PRS( Matrix( 1/S ) * Matrix( 1/R ) * Matrix( 1/P ) );
   }
};

You can easily lerp this structure to determine the animated bone delta, then use the ToMatrix method to use it in your final bone pallet procedure.


#4907458 Skeletal animation joint transformation question

Posted by bzroom on 29 January 2012 - 04:58 PM

Your file will contain a list of bones, the bone's primary feature will be its transform. This is often specified in object space. All the bones are all in the same space. You may need to translate your data to this format.

So that you have this
Struct Bone
{
   int parentIndex;
   Matrix objSpace;
   Matrix invObjSpace;
   Matrix parentRel;
};

objSpace = This is the bones absolute orientation in the file.
invObjSpace = Inverse of above.
currentBone.parentRel = ParentBone( currentBone ).invObjSpace * currentBone.objSpace;

These are stored in order from such that all parents occur in the array before children.

You can then compute their animated object transforms including a parent relative delta like so:
vector<Matrix> output( bones.size( ) );
for( int i = 0; i < bones.size( ); ++i )
   output[ i ] = output[ bones[ i ].parentIndex ] * bones[ i ].parentRel * boneDeltas[ i ];

You will need to work out how to store the root transform so that .parentIndex does not access the output vector out of bounds.

Skinning:
You can then run the skinning procedure with the invObjSpace transforms. Pass this inverse skinned data to the shader, the animated output vector transforms, and the vertices will be transformed back into their animated skinned position. You can modify this procedure in any way. It's just a big chain of matrix multiplications.


#4905630 Creating a render manager

Posted by bzroom on 23 January 2012 - 06:22 PM

lukesmith123. Please look at the very small code snipped i pasted. In the configure function it shows one way to get the mesh ptr to the scene. There are a ton of ways, but i have shown one very simple way.

The mesh resource is fetched, an instance is created, and it is added to the scene.


Mesh* m = scene.Resources.FetchResource<Mesh>( path );
mi = new MeshInstance( m );
scene.AddRenderable( mi );


#4905312 Creating a render manager

Posted by bzroom on 22 January 2012 - 09:03 PM

The resource manager is responsible for ensuring there is only one instance of each resource loaded. (one version of grass01.bmp, one version of terrain.obj, etc). It has the responsibility of determining the file location on the target device, and retrieving that file from the storage medium.

The data is likely already stored in a format specifically for the target platform. For example, directx targets may store their textures in dx5 compression, rather than .tga. They may store the meshes data exactly as it will be copied to VRAM.

Once the resource system has found the resource to be loaded, it will retrieve from disk. At that point it will be passed off to the load caller in such a way that it may be directly read into VRAM, or it may be copied from a temporary file cache to VRAM. Your resource system may have a set of loaders, maybe one per resource type. In the case of the mesh and texture loaders they will be given a renderer handle which we expose the device if necessary.

Your Loaders can be specialized to act like a factory and to determine file path extensions or what ever other data you might need.


Resources.AddLoader( new Loader<Mesh>( RenderDevice ) );
Resources.AddLoader( new Loader<Texture>( RenderDevice ) );


struct RenderableObject
{
   scoped_ptr<MeshInstance> mi;

  void Configure( Scene& scene, const FilePath& path )
  {
    Mesh* m = scene.Resources.FetchResource<Mesh>( path );
    mi = new MeshInstance( m );
    scene.AddRenderable( mi );
   }
   void Move( const Matrix& m )
   {
	 mi->Transform = m;
   }
};



#4904486 [Car Physics] - Precision about the Traction Circle

Posted by bzroom on 20 January 2012 - 12:44 AM

Not divided by 2.

It follows the rules of 2d normalization.
Take a look at these images: https://www.google.com/search?q=unit+circle

The components of your friction can be any coordinate pair along the circle, scaled by any scalar.

You can start by drawing a circle, with a radius of your friction limit. Then draw a line from the center outward, indicating the direction the force shall act. Any bit of the line that extends beyond the circle will be clipped, and you will be left only with the line that goes from the center to the circle. If the original friction vector did not reach the circle it wont be clipped.

This is a very simplistic explanation.

Friction will respond equally and oppositely, up until the friction limit, which is at the circle's edge. At that point it doesnt matter which direction you're going, it simply can't provide any more response force.

If you were using all 10 units of friction force in the longitudinal direction, and you needed some force in the latitude direction, it would immediately put you outside of the circle and the static friction would be broken / response clipped. How ever much friction is left over from either direction is how much you can use for the other. This is why it's not a good idea to use your brakes when you are cornering at the limit. Or vise versa. Friction response direction should be smoothly transferred between forward and side accelerations to not break this circle.


#4902792 Combining deferred rendering with other techniques

Posted by bzroom on 14 January 2012 - 05:06 PM

The quads, or other geometry, drawn for the lighting does not need to write to the depth buffer. you can turn off depth writes. or you can turn on depth writes and write the value you stored in your depth texture during the g-buffer creation pass, restoring the depth buffer.

it really just depends on how you have things set up and how clever you make it.

Alternatively, if you're just trying to render particles on top, you can use your depth texture from the g-buffer to have your particle shader fade out as the particle approaches the stored depth value. this is called soft particles. It will also allow you to render particles to a lower resolution for performance reasons.


#4902781 Combining deferred rendering with other techniques

Posted by bzroom on 14 January 2012 - 04:13 PM

You draw all that stuff after the shading (mini) pass.

Hopefully your depth buffer is preserved (or restored) so you can just enable depth testing as usual.

Then draw all your transparent objects with forward shading.


#4901931 I want to learn how to make physics engines - for real - what should I study?

Posted by bzroom on 12 January 2012 - 02:42 AM

There's no right or wrong way to go about anything. I would argue that the low level stuff isn't always fun.

But you need to know what it's like to write game code before you can write good engine code. If you live in a bubble in the engine you wont ever learn the requirements of a real game.

Therefore it would best if you first created games, before moving to developing low level/engine support. In the making of those games you will be doing all the required low level things, just without putting the pressure on it of being "official."

You should use every single physics engine, and every AI engine, and every rendering engine before you can say what you do and don't like about existing engines. Along the way, make somewhere between 5 to 10 good examples of each and you will be at a very competent level.

There's not really much time for school with that schedule. I've met very few skilled professionally trained people who's skill came from their professional training.


#4900470 Finding the midpoint of a bone for a ragdoll

Posted by bzroom on 07 January 2012 - 05:14 PM

We use our scene editor to place volumes relative to the character's bones. we save this scene purely for reference when setting up the ragdoll.

I know that would be a big step for your project, but that is how we do it. The transformation math is practically all handled by the editor.

When we go to construct the ragdoll we query the bind pose object space transform of the bone a volume is bound to. we then compute the offsets like so.

BoneToVolumeXform = invBoneObjectSpace * volumeObjectSpace
VolumeToBoneXform = invVolumeObjectSpace * boneObjectSpace

we assume the mass and body are centered around the volume. if not, the goal would be to compute the BoneToBody xforms and also the body to Volume xform so you can position the volume relative to the body.

We position the ragdoll constraints at the bone origins so that when they rotate, they rotate through the artist defined skeleton axes. skipping over joints is a little bit harder but not by much. You'll just need to decide what assumptions you'll make about the other bone's transforms but ultimately they will just be in some orientation that you'll need to integrate into the physics results when determining rig results.


#4612015 Export Editable Spline from 3dsmax

Posted by bzroom on 01 March 2010 - 05:23 PM


case IGameObject::IGAME_SPLINE:
{
IGameSpline *s = reinterpret_cast...

for each spline sp in s
for each knot k in sp
corner type
vectors



#4565876 Car acceleration

Posted by bzroom on 01 December 2009 - 09:30 AM

Reading this post and thread may help:
http://www.gamedev.net/community/forums/viewreply.asp?ID=3560185
http://www.gamedev.net/community/forums/topic.asp?topic_id=542915

The speed is not limited by forces, it is limited by the engine's maximum rpm (assuming the engine torque is capable of overcoming resistance forces, and it almost always can). At some point the engine cannot go any faster, if you want the car to go faster, you'd have to disconnect the engine, which means no more torque applied to the wheels.

The car is accelerated by the torque of the engine being converted to linear motion through the wheels.

Your forward key will apply the torque. Say you want a real basic engine simulation where it has a max torque which is scaled by the throttle. The throttle is your forward key. You can also incorperate a transmission with one or more gear ratios between the engine torque and wheel torque.

float engineSpeed = wheelSpeed / gearRatio;
if (engineSpeed < 0) stalled();
if (engineSpeed > maxEngineSpeed) exploded();

float torqueEngine = maxTorque * throttle;
float wheelTorque = torqueEngine * gearRatio;
carForce = wheelDirection * wheelTorque / wheelRadius;


#470497 2D Car Physics Tutorial

Posted by bzroom on 31 October 2007 - 07:27 PM

INTRO Someone asked me in the #gamedev IRC channel about how to make a 2d vehicle simulator. Instead of spending all day trying to explain the concepts to him, I decided just to write this tutorial. Please bear with me, this is my first tutorial. So as I mentioned we’re going to be learning how to make a basic 2d vehicle simulator. We’re going to do it in C# and try do use as few hacks as possible. I’ve broken the process down into three steps. First, we will learn how to setup a basic game application in C#.NET and how to draw some basic graphics (emphasis on basic.) Next, we will learn how to create a rigid body simulator using a simple Euler integrator with a variable time step. And last but not least, we will calculate vehicle forces simulating the tire patch contacting the road. And that’s all there is to it! Let’s get started. MATH REQUIREMENTS There are two ways to get through this tutorial, you can rush to the end and download the project, or you can read through it and hopefully I’ll be able to explain things clearly. If you choose the second route, you’re going to need to have a bit of math background. In a 2D simulation this is mostly in the form of a vector object. You’ll need to be able to add, subtract, dot, and project 2 vectors. Also you’ll need to be able to use a cross product. In 2D this is kind of a fake situation since we know the result will point in the screen’s direction, so the result is returned as a scalar. If you’re not familiar with any of these terms please look them up now. I tried to write this tutorial with out using a matrix object but eventually I cracked and used the Drawing2D.Matrix object to transform and inversely transform a vector between spaces. If you don’t know what I’m talking about let me give you an example. Let’s say your personal body is your “local space” and the room you’re sitting in is the “world space.” Let’s also say that your monitor is the front of the world, and your eyes look in the forward direction of your local space. If you turn side ways, and transform the monitor’s direction into your space, it is now the side direction. Vise versa, if you transform your facing direction into world space, it is the opposite side direction. This is a critical concept so please, if that didn’t make sense, do some searching on Google for transforming between spaces. The reason this is so important is because we will be doing all of our vehicle force calculations in local vehicle space. Yet the vehicle itself, and its integrator, persist in world space. Ok, Phase 1! Phase 1, as I mentioned, is to create the renderer; something graphical so we can actually see what our simulation is doing. This will make it a lot easier to debug. Create a windows form project in C# and place a picturebox control on it (name it screen). This control is where we will display our simulation. We could just start drawing to this screen but we’re going to be using double buffering as well, to avoid flicker, so we need to create the back buffer now. That bit of code looks like this.
        Graphics graphics; //gdi+
        Bitmap backbuffer;
        Size buffersize;

        //intialize rendering
        private void Init(Size size)
        {
            //setup rendering device
            buffersize = size;
            backbuffer = new Bitmap(buffersize.Width, buffersize.Height);
            graphics = Graphics.FromImage(backbuffer);
        }










The Init function must be called with the size of the “screen” control that you created on the form. This will create a bitmap “backbuffer” to which we can do our offscreen rendering. We’ll then take this backbuffer and draw it to the screen to illimnate any flickering. This is how you draw a basic shape to the back buffer, and present it to the screen.
        //main rendering function
        private void Render(Graphics g)
        {
            //clear back buffer
            graphics.Clear(Color.Black);

            //draw to back buffer
		graphics.DrawLine(new Pen(Color.Yellow), 1, 0, 1, 5);

            //present back buffer
            g.DrawImage(backbuffer, new Rectangle(0, 0, buffersize.Width,
              buffersize.Height), 0, 0, buffersize.Width, buffersize.Height,
              GraphicsUnit.Pixel);
        }










This function is called from the “on_paint” method of the screen control placed on our form. The on_paint method has a parameter “e” that contains a graphics object we can use to draw to the control. We pass this graphics object to the render function and as you can see, we draw the backbuffer to it as the very last step. Now by default, the graphics of a picturebox control has the origin in the top left corner, and extends downward for +y and to the right for +x. This is highly unnatural for most cases. In addition to that it, has extremely large units. Since we will be simulating in the metric system, I recommend introducing a scale factor to scale up the simulation to make it much more visible. The transformation looks like this and takes place after Graphics.Clear() is called.
  graphics.ResetTransform();
  graphics.ScaleTransform(screenScale, -screenScale);
  graphics.TranslateTransform(buffersize.Width / 2.0f / screenScale, -buffersize.Height / 2.0f / screenScale);



That transformation flips the Y axis so that +Y points up. It simulataneously scales the space by our “screenScale” factor (something like 3.0f should work fine. Next, we translate the graphics space into the center of the screen control. We translate by half of the screen dimensions divided by our scale (since we our now in the scaled space.) Now, drawing the line should draw it starting right at the center of the screen. Forms Wiring Up until now, I havn’t explained how to connect all the functions. The first thing you’ll need to do is call the render function from your on_paint event. Next, you’ll need to create a function that gets called continously to update our simulation. It is prefered to call this function on the Application_Idle event. So create an event handler for Application_Idle and have it call your “DoFrame” function. Inside this do frame function you’ll need to 1.) Process input, 2.) Update the simulation, and 3.) Invalidate the screen control so that an On_Paint gets triggerd and the simulation get’s drawn. You’ll also want to wire up some “key_down” and “key_up” events to keep track of key states. The Timer Since we don’t know how often our DoFrame function will be getting called, we need to code everything to handle a variable time step. To utilize this we must measure the time between DoFrame calls. So I’ll introduce the timer. The timer very simply query’s the number of miliseconds that have passed since the computer was turned on. So we store this number every frame, and on a subsiquent frame, we compute the difference which gives us the amount of time that has passed since the last frame. Here is my very simple timer object. Note: you will need to call GetETime in your intialize function in order to clear the timer, otherwise the first call to it will return the ammount of time that has passed since the computer was turned on.
    class Timer
    {
        //store last time sample
        private int lastTime = Environment.TickCount;
        private float etime;

        //calculate and return elapsed time since last call
        public float GetETime()
        {
            etime = (Environment.TickCount - lastTime) / 1000.0f;
            lastTime = Environment.TickCount;

            return etime;
        }
    }



Conclusion of phase 1 So up until now we’ve covered: setting up a rendering surface using GDI, wiring a form to process a game loop and draw it to the screen, and computing the time that has passed since the last frame. Our application looks like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;

namespace racing_simulation_2d
{
    //our main application form
    public partial class frmMain : Form
    {
        //graphics
        Graphics graphics; //gdi+
        Bitmap backbuffer;
        Size buffersize;
        const float screenScale = 3.0f;
        Timer timer = new Timer();

        //keyboard controls
        bool leftHeld = false, rightHeld = false;
        bool upHeld = false, downHeld = false;

        //vehicle controls
        float steering = 0; //-1 is left, 0 is center, 1 is right
        float throttle = 0; //0 is coasting, 1 is full throttle
        float brakes = 0; //0 is no brakes, 1 is full brakes

        public frmMain()
        {
            InitializeComponent();
            Application.Idle += new EventHandler(ApplicationIdle);

            screen.Paint += new PaintEventHandler(screen_Paint);
            this.KeyDown += new KeyEventHandler(onKeyDown);
            this.KeyUp += new KeyEventHandler(onKeyUp);

            Init(screen.Size);
        }

        //intialize rendering
        private void Init(Size size)
        {
            //setup rendering device
            buffersize = size;
            backbuffer = new Bitmap(buffersize.Width, buffersize.Height);
            graphics = Graphics.FromImage(backbuffer);

            timer.GetETime(); //reset timer
        }

        //main rendering function
        private void Render(Graphics g)
        {
            //clear back buffer
            graphics.Clear(Color.Black);
            graphics.ResetTransform();
            graphics.ScaleTransform(screenScale, -screenScale);
            graphics.TranslateTransform(buffersize.Width / 2.0f /
                screenScale, -buffersize.Height / 2.0f / screenScale);

            //draw to back buffer
            DrawScreen();

            //present back buffer
            g.DrawImage(backbuffer, new Rectangle(0, 0, buffersize.Width,
               buffersize.Height), 0, 0, buffersize.Width, 
               buffersize.Height, GraphicsUnit.Pixel);
        }

        //draw the screen
        private void DrawScreen()
        {
            //draw our simulation here
        }

        //process game logic
        private void DoFrame()
        {
            //get elapsed time since last frame
            float etime = timer.GetETime();

            //process input
            ProcessInput();

		//integrate our simulation here

            //redraw our screen
            screen.Invalidate();
        }


        //process keyboard input
        private void ProcessInput()
        {
            if (leftHeld)
                steering = -1;
            else if (rightHeld)
                steering = 1;
            else
                steering = 0;

            if (upHeld)
                throttle = 1;
            else
                throttle = 0;

            if (downHeld)
                brakes = 1;
            else
                brakes = 0;
        }

        private void onKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    leftHeld = true;
                    break;
                case Keys.Right:
                    rightHeld = true;
                    break;
                case Keys.Up:
                    upHeld = true;
                    break;
                case Keys.Down:
                    downHeld = true;
                    break;
                default: //no match found
                    return; //return so handled dosnt get set
            }

            //match found
            e.Handled = true;
        }

        private void onKeyUp(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    leftHeld = false;
                    break;
                case Keys.Right:
                    rightHeld = false;
                    break;
                case Keys.Up:
                    upHeld = false;
                    break;
                case Keys.Down:
                    downHeld = false;
                    break;
                default: //no match found
                    return; //return so handled dosnt get set
            }

            //match found
            e.Handled = true;
        }

        //rendering - only when screen is invalidated
        private void screen_Paint(object sender, PaintEventArgs e)
        {
            Render(e.Graphics);
        }

        //when the os gives us time, run the game
        private void ApplicationIdle(object sender, EventArgs e)
        {
            // While the application is still idle, run frame routine.
            DoFrame();
        }

        private void MenuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }

    //keep track of time between frames
    class Timer
    {
        //store last time sample
        private int lastTime = Environment.TickCount;
        private float etime;

        //calculate and return elapsed time since last call
        public float GetETime()
        {
            etime = (Environment.TickCount - lastTime) / 1000.0f;
            lastTime = Environment.TickCount;

            return etime;
        }
    }
}



Phase 2, Rigid Body Simulation Ok, now we’re getting into some good stuff here. Let's put everything we just covered on the back burner now, let’s 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 get's applied in the from 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 it’s linear velocity, F is the net force acting on it, M is it’s 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 * T
So 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 * T
Simple 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 Dump Here is my rigid body object. You’ll notice all the properties I mentioned above. It has a draw function which will draw it’s rectangle to the provided graphics object. It has an addforce function, a space conversion method, to and from world space (very handy), and a function that returns the velocity of a point on the body (in world space). This point velocity is a combination of the linear velocity and the angular velocity. But the angular velocity is multiplied by the distance the point is from the center of rotation and perpindicular to it’s offset direction. So to kill 2 birds with one stone, I simply find the orthogonal vector to the point offset and multiply it by the angular velocity (then add the linear velocity.) One thing you may be curious about is how I calculate the inertia value. That is a generalized formula I found at this link: http://howard.nebrwesleyan.edu/hhmi/fellows/pgomez/inertforms.html
    //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;
        }
    }



Testing To make sure your rigid body works, instantiate one in your Init() function and apply a force with some offset in the DoFrame function. If you apply a constant worldOffset, the body will continue to accelerate it’s angular velocity. If you take your offset and run it through the RelativeToWorld function, the body will angularly accelerate in one direction and then come back the other way, like a pendulum as the point the force is applied to changes. Play around with this for a while, this has to work and make sense in order for the next section to work. PHASE 3, THE VEHICLE Assuming everything has gone well above, you should have a rigid body actor in your scene that you can apply forces to and watch move around. Now all that’s left is to calculate these forces in a way that will simulate a vehicle. For that we are going to need a vehicle object. I recommend deriving directly from you rigid body object since the chassis is essentially a rigid body. In addition to that we will need to construct a “wheel" object. This wheel will handle the steering direction of each wheel, the velocity the wheel is spinning, and calculate the forces that that particular wheel applies to the chassis (all in vehicle space). Since our wheel is known to be constrained to the vehicle, we don’t need to simulate it as another rigid body (though you could, but not in the 2d case.) We will simply duplicate the angular properties of the rigid body in the wheel object. So we’ll need: Wheel Velocity, Wheel Inertia, and and Wheel Torque. We’ll also need the relative offset of the wheel in the vehicle space, and the angle the wheel is facing (this is constant for the back wheels, unless you want 4 wheel steering.) Just like the rigid body, the wheel's torque function acts as an accumulator, we add torques to it and after it gets integrated the torque is zero’d out. The add torque function is where you will apply a wheel torque from either the transmission (to make you go) or from the brakes (to make you stop). Internally the wheel will generate a torque caused by the friction on the road. The wheel object also needs a SetSteering function. This function calculates 2 vectors, an effective Side Direction, and an effective Forward direction (both in vehicle space). That the tire patch will act on. The force applied on the tire by the ground acting in the side direction will directly translate into the chassis. Mean while the force acting in the forward direction will not only act on the chassis, but it will induce a rotation of the tire. Here is the SetSteering function, you will see I used the Drawing2D.Matrix to transform the initial forward and side vectors by the steering angle (I had to convert the vectors to “points" in order to transform them by the matrix.)
            public void SetSteeringAngle(float newAngle)
            {
                Matrix mat = new Matrix();
                PointF[] vectors = new PointF[2];

                //foward vector
                vectors[0].X = 0;
                vectors[0].Y = 1;
                //side vector
                vectors[1].X = -1;
                vectors[1].Y = 0;

                mat.Rotate(newAngle / (float)Math.PI * 180.0f);
                mat.TransformVectors(vectors);

                m_forwardAxis = new Vector(vectors[0].X, vectors[0].Y);
                m_sideAxis = new Vector(vectors[1].X, vectors[1].Y);
            }



Force Calculation So, if the vehicle is sitting there not moving, with its front wheels turned, and you push it. A force will be generated in the opposite direction you push. This force gets projected onto these 2 directions. If the wheels were straight there would be no side force. So the vehicle would simply roll forward. But since the wheels are turned, there is a bit of the force that acts in the “effective side direction” so we apply an opposite force to the chassis. This is what causes you to turn when you steer the wheels. To get this force that gets projected onto the two directions, we need to first determine the velocity difference between the tire patch and the road. If the wheel is spinning at the same speed the ground is wizzing by, then there is effectively no force acting on the vehicle. But as soon as you slam on the brakes and stop the wheel, there is a huge velocity difference and this is what causes the force that stops your car. So here is the process broken down into 6 steps, for each wheel. Step 1, calculate the effective direction vectors (with steering function) Step 2, calculate velocity difference. The ground speed is determined via the “PointVel” function on the rigidbody, given the current wheel’s world offset. Step 3, project this velocity onto the 2 effective directions. Step 4, generate an equal and opposite force for the two direction and call this the “response force”. This is what gets added to the chassis for each wheel Step 5, calculate the torque that the forward response force created on the wheel, and add this to the wheel torque. Step 6, integrate the wheel torques into the wheel velocity. That bit of code looks like this:
            public Vector CalculateForce(Vector relativeGroundSpeed,
                float timeStep)
            {
                //calculate speed of tire patch at ground
                Vector patchSpeed = -m_forwardAxis * m_wheelSpeed *
                    m_wheelRadius;

                //get velocity difference between ground and patch
                Vector velDifference = relativeGroundSpeed + patchSpeed;

                //project ground speed onto side axis
                float forwardMag = 0;
                Vector sideVel = velDifference.Project(m_sideAxis);
                Vector forwardVel = velDifference.Project(m_forwardAxis,
                    out forwardMag);

                //calculate super fake friction forces
                //calculate response force
                Vector responseForce = -sideVel * 2.0f;
                responseForce -= forwardVel;

                //calculate torque on wheel
                m_wheelTorque += forwardMag * m_wheelRadius;

                //integrate total torque into wheel
                m_wheelSpeed += m_wheelTorque / m_wheelInertia * timeStep;

                //clear our transmission torque accumulator
                m_wheelTorque = 0;

                //return force acting on body
                return responseForce;
            }



ALMOST DONE!! We’re in the home stretch here now. Now we have a way to calculate the force each wheel generates on the chassis. Every frame all we have to do is set our transmission and brake torques, our steering angle, calculate each wheel force, add these to the chassis, and integrate the rigid body. Badaboom badabing, vehicle done! :) Super Code Dump Here is the entire source code for the project. It is also downloadable via the link at the very bottom. If you have any questions or comments please feel free to post them her and either I can make things more clear or maybe someone else could offer some better expertise. If you’d like you can email me at Kincaid05 on googles fine emailing service. Thanks for reading and I hope this was informative. -Matt Kincaid
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;

namespace racing_simulation_2d
{
    //our main application form
    public partial class frmMain : Form
    {
        //graphics
        Graphics graphics; //gdi+
        Bitmap backbuffer;
        Size buffersize;
        const float screenScale = 3.0f;
        Timer timer = new Timer();

        //keyboard controls
        bool leftHeld = false, rightHeld = false;
        bool upHeld = false, downHeld = false;

        //vehicle controls
        float steering = 0; //-1 is left, 0 is center, 1 is right
        float throttle = 0; //0 is coasting, 1 is full throttle
        float brakes = 0; //0 is no brakes, 1 is full brakes

        //game objects
        Vehicle vehicle = new Vehicle();

        public frmMain()
        {
            InitializeComponent();
            Application.Idle += new EventHandler(ApplicationIdle);

            screen.Paint += new PaintEventHandler(screen_Paint);
            this.KeyDown += new KeyEventHandler(onKeyDown);
            this.KeyUp += new KeyEventHandler(onKeyUp);

            Init(screen.Size);
        }

        //intialize rendering
        private void Init(Size size)
        {
            //setup rendering device
            buffersize = size;
            backbuffer = new Bitmap(buffersize.Width, buffersize.Height);
            graphics = Graphics.FromImage(backbuffer);

            timer.GetETime(); //reset timer

            vehicle.Setup(new Vector(3, 8)/2.0f, 5, Color.Red);
            vehicle.SetLocation(new Vector(0, 0), 0);
        }

        //main rendering function
        private void Render(Graphics g)
        {
            //clear back buffer
            graphics.Clear(Color.Black);
            graphics.ResetTransform();
            graphics.ScaleTransform(screenScale, -screenScale);
            graphics.TranslateTransform(buffersize.Width / 2.0f /
               screenScale, -buffersize.Height / 2.0f / screenScale);

            //draw to back buffer
            DrawScreen();

            //present back buffer
            g.DrawImage(backbuffer, new Rectangle(0, 0, buffersize.Width,
               buffersize.Height), 0, 0, buffersize.Width,
               buffersize.Height, GraphicsUnit.Pixel);
        }

        //draw the screen
        private void DrawScreen()
        {
            vehicle.Draw(graphics, buffersize);
        }

        //process game logic
        private void DoFrame()
        {
            //get elapsed time since last frame
            float etime = timer.GetETime();

            //process input
            ProcessInput();

            //apply vehicle controls
            vehicle.SetSteering(steering);
            vehicle.SetThrottle(throttle, menu.Checked);
            vehicle.SetBrakes(brakes);

            //integrate vehicle physics
            vehicle.Update(etime);

            //keep the vehicle on the screen
            ConstrainVehicle();

            //redraw our screen
            screen.Invalidate();
        }

        //keep the vehicle on the screen
        private void ConstrainVehicle()
        {
            Vector position = vehicle.GetPosition();
            Vector screenSize = new Vector(screen.Width /
               screenScale, screen.Height / screenScale);

            while (position.X > screenSize.X / 2.0f) 
            { position.X -= screenSize.X; }
            while (position.Y > screenSize.Y / 2.0f) 
            { position.Y -= screenSize.Y; }
            while (position.X < -screenSize.X / 2.0f) 
            { position.X += screenSize.X; }
            while (position.Y < -screenSize.Y / 2.0f) 
            { position.Y += screenSize.Y; }
        }

        //process keyboard input
        private void ProcessInput()
        {
            if (leftHeld)
                steering = -1;
            else if (rightHeld)
                steering = 1;
            else
                steering = 0;

            if (upHeld)
                throttle = 1;
            else
                throttle = 0;

            if (downHeld)
                brakes = 1;
            else
                brakes = 0;
        }

        private void onKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    leftHeld = true;
                    break;
                case Keys.Right:
                    rightHeld = true;
                    break;
                case Keys.Up:
                    upHeld = true;
                    break;
                case Keys.Down:
                    downHeld = true;
                    break;
                default: //no match found
                    return; //return so handled dosnt get set
            }

            //match found
            e.Handled = true;
        }

        private void onKeyUp(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    leftHeld = false;
                    break;
                case Keys.Right:
                    rightHeld = false;
                    break;
                case Keys.Up:
                    upHeld = false;
                    break;
                case Keys.Down:
                    downHeld = false;
                    break;
                default: //no match found
                    return; //return so handled dosnt get set
            }

            //match found
            e.Handled = true;
        }

        //rendering - only when screen is invalidated
        private void screen_Paint(object sender, PaintEventArgs e)
        {
            Render(e.Graphics);
        }

        //when the os gives us time, run the game
        private void ApplicationIdle(object sender, EventArgs e)
        {
            // While the application is still idle, run frame routine.
            DoFrame();
        }

        private void MenuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }

    //our vehicle object
    class Vehicle : RigidBody
    {
        private class Wheel
        {
            private Vector m_forwardAxis, m_sideAxis;
            private float m_wheelTorque, m_wheelSpeed,
                 m_wheelInertia, m_wheelRadius;
            private Vector m_Position = new Vector();

            public Wheel(Vector position, float radius)
            {
                m_Position = position;
                SetSteeringAngle(0);
                m_wheelSpeed = 0;
                m_wheelRadius = radius;
                m_wheelInertia = radius * radius; //fake value
            }

            public void SetSteeringAngle(float newAngle)
            {
                Matrix mat = new Matrix();
                PointF[] vectors = new PointF[2];

                //foward vector
                vectors[0].X = 0;
                vectors[0].Y = 1;
                //side vector
                vectors[1].X = -1;
                vectors[1].Y = 0;

                mat.Rotate(newAngle / (float)Math.PI * 180.0f);
                mat.TransformVectors(vectors);

                m_forwardAxis = new Vector(vectors[0].X, vectors[0].Y);
                m_sideAxis = new Vector(vectors[1].X, vectors[1].Y);
            }

            public void AddTransmissionTorque(float newValue)
            {
                m_wheelTorque += newValue;
            }

            public float GetWheelSpeed()
            {
                return m_wheelSpeed;
            }

            public Vector GetAttachPoint()
            {
                return m_Position;
            }

            public Vector CalculateForce(Vector relativeGroundSpeed,
                float timeStep)
            {
                //calculate speed of tire patch at ground
                Vector patchSpeed = -m_forwardAxis * m_wheelSpeed *
                    m_wheelRadius;

                //get velocity difference between ground and patch
                Vector velDifference = relativeGroundSpeed + patchSpeed;

                //project ground speed onto side axis
                float forwardMag = 0;
                Vector sideVel = velDifference.Project(m_sideAxis);
                Vector forwardVel = velDifference.Project(m_forwardAxis,
                    out forwardMag);

                //calculate super fake friction forces
                //calculate response force
                Vector responseForce = -sideVel * 2.0f;
                responseForce -= forwardVel;

                //calculate torque on wheel
                m_wheelTorque += forwardMag * m_wheelRadius;

                //integrate total torque into wheel
                m_wheelSpeed += m_wheelTorque / m_wheelInertia * timeStep;

                //clear our transmission torque accumulator
                m_wheelTorque = 0;

                //return force acting on body
                return responseForce;
            }
        }
        private Wheel [] wheels = new Wheel[4];

        new public void Setup(Vector halfSize, float mass, Color color)
        {
            //front wheels
            wheels[0] = new Wheel(new Vector(halfSize.X, halfSize.Y), 0.5f);
            wheels[1] = new Wheel(new Vector(-halfSize.X, halfSize.Y), 0.5f);

            //rear wheels
            wheels[2] = new Wheel(new Vector(halfSize.X, -halfSize.Y), 0.5f);
            wheels[3] = new Wheel(new Vector(-halfSize.X, -halfSize.Y), 0.5f);

            base.Setup(halfSize, mass, color);
        }

        public void SetSteering(float steering)
        {
            const float steeringLock = 0.75f;

            //apply steering angle to front wheels
            wheels[0].SetSteeringAngle(-steering * steeringLock);
            wheels[1].SetSteeringAngle(-steering * steeringLock);
        }

        public void SetThrottle(float throttle, bool allWheel)
        {
            const float torque = 20.0f;

            //apply transmission torque to back wheels
            if (allWheel)
            {
                wheels[0].AddTransmissionTorque(throttle * torque);
                wheels[1].AddTransmissionTorque(throttle * torque);
            }

            wheels[2].AddTransmissionTorque(throttle * torque);
            wheels[3].AddTransmissionTorque(throttle * torque);
        }

        public void SetBrakes(float brakes)
        {
            const float brakeTorque = 4.0f;

            //apply brake torque apposing wheel vel
            foreach (Wheel wheel in wheels)
            {
                float wheelVel = wheel.GetWheelSpeed();
                wheel.AddTransmissionTorque(-wheelVel * brakeTorque * brakes);
            }
        }

        new public void Update(float timeStep)
        {
            foreach (Wheel wheel in wheels)
            {
                Vector worldWheelOffset = 
                    base.RelativeToWorld(wheel.GetAttachPoint());
                Vector worldGroundVel = base.PointVel(worldWheelOffset);
                Vector relativeGroundSpeed =
                    base.WorldToRelative(worldGroundVel);
                Vector relativeResponseForce = 
                    wheel.CalculateForce(relativeGroundSpeed, timeStep);
                Vector worldResponseForce =
                    base.RelativeToWorld(relativeResponseForce);

                base.AddForce(worldResponseForce, worldWheelOffset);
            }

            base.Update(timeStep);
        }
    }

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

    //mini 2d vector :)
    class Vector
    {
        public float X, Y;

        public Vector(){X = 0; Y = 0;}
        public Vector(float x, float y){X = x; Y = y;}

        //length property        
        public float Length
        {
            get
            {
                return (float)Math.Sqrt((double)(X * X + Y * Y ));
            }
        }

        //addition
        public static Vector operator +(Vector L, Vector R)
        {
            return new Vector(L.X + R.X, L.Y + R.Y);
        }

        //subtraction
        public static Vector operator -(Vector L, Vector R)
        {
            return new Vector(L.X - R.X, L.Y - R.Y);
        }

        //negative
        public static Vector operator -(Vector R)
        {
            Vector temp = new Vector(-R.X, -R.Y);
            return temp;
        }

        //scalar multiply
        public static Vector operator *(Vector L, float R)
        {
            return new Vector(L.X * R, L.Y * R);
        }

        //divide multiply
        public static Vector operator /(Vector L, float R)
        {
            return new Vector(L.X / R, L.Y / R);
        }

        //dot product
        public static float operator *(Vector L, Vector R)
        {
            return (L.X * R.X + L.Y * R.Y);
        }

        //cross product, in 2d this is a scalar since
        //we know it points in the Z direction
        public static float operator %(Vector L, Vector R)
        {
            return (L.X*R.Y - L.Y*R.X);
        }

        //normalize the vector
        public void normalize()
        {
            float mag = Length;

            X /= mag;
            Y /= mag;
        }

        //project this vector on to v
        public Vector Project(Vector v)
        {
            //projected vector = (this dot v) * v;
            float thisDotV = this * v;
            return v * thisDotV;
        }

        //project this vector on to v, return signed magnatude
        public Vector Project(Vector v, out float mag)
        {
            //projected vector = (this dot v) * v;
            float thisDotV = this * v;
            mag = thisDotV;
            return v * thisDotV;
        }
    }

    //keep track of time between frames
    class Timer
    {
        //store last time sample
        private int lastTime = Environment.TickCount;
        private float etime;

        //calculate and return elapsed time since last call
        public float GetETime()
        {
            etime = (Environment.TickCount - lastTime) / 1000.0f;
            lastTime = Environment.TickCount;

            return etime;
        }
    }
}




Download Source. http://www.mediafire.com/?390xi0219wz Edit: 1.) Fixed angle conversion mistake 2.) Added side force multipler to correct under steer. 3.) Uploaded new demo package. [Edited by - bzroom on November 4, 2007 9:29:32 PM]


PARTNERS