 Home
 » Viewing Profile: Reputation: bzroom
bzroom
Member Since 20 Jan 2003Offline Last Active Nov 19 2014 01:22 AM
Community Stats
 Group Members
 Active Posts 2,555
 Profile Views 3,905
 Submitted Links 0
 Member Title Member
 Age Age Unknown
 Birthday Birthday Unknown

Gender
Not Telling
#4995034 2D driving physics woes (problems) 2D Vector Math questions
Posted by bzroom on 29 October 2012  06:50 AM
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
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
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
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
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 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
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
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 gbuffer 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
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
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
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
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
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); }
//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); }
graphics.ResetTransform(); graphics.ScaleTransform(screenScale, screenScale); graphics.TranslateTransform(buffersize.Width / 2.0f / screenScale, buffersize.Height / 2.0f / screenScale);
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; } }
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; } } }
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; }
//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; } }
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 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; }
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; } } }