TOP DOWN CAR

3
Sep
2012

Here is a demo of a TOP DOWN CAR in Box2dweb. A big thanks to iforce2d for their help in steering wheel. Their tutorials were of great help.

The main problem in making this car is cancelling the perpendicular velocity of each wheel else the car will just start rotating as soon as we change the direction by rotating the wheel. As of now there is no skidding in the car i.e. the car moves as if it sticks to the ground. This is implement by adding a velocity with magnitude equal to perpendicular velocity of wheel but in opposite direction to each wheel. So if we want the car to skid, say when the fast moving car suddenly turns, we will add perpendicular velocity slightly less than actual.

STEP 1: Create a world with zero gravity. Since we are creating a top down view, we don't need gravity.

var world = new b2World(new b2Vec2(0,0), true);
STEP 2: Create the body of car and four wheels. One thing to notice is that wheels have been set as sensor. The reason for this is that I didn't want the wheel to collide with other objects in the world. We can safely set the sensor property to false.
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(10,8);

var fixDef = new b2FixtureDef;
fixDef.density = 30;
fixDef.friction = .8;
fixDef.restitution = 0.1;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(.5,1.5);

//CAR BODY
var car = world.CreateBody(bodyDef);
car.CreateFixture(fixDef);

//WHEELS
var xx = car.GetWorldCenter().x;
var yy = car.GetWorldCenter().y;
var fr = wheel(xx+.5,yy-1); //front right wheel
var fl = wheel(xx-.5,yy-1); //front left wheel
var rr = wheel(xx+.5,yy+1); //rear right wheel
var rl = wheel(xx-.5,yy+1); //rear left wheel


function wheel(x,y)	{
	var bodyDef = new b2BodyDef;
	bodyDef.type = b2Body.b2_dynamicBody;
	bodyDef.position.Set(x,y);
	var fixDef = new b2FixtureDef;
	fixDef.density = 30;
	fixDef.friction = 10;
	fixDef.restitution = 0.1;
	fixDef.shape = new b2PolygonShape;
	fixDef.shape.SetAsBox(.2,.4);
	fixDef.isSensor = true;
	var wheel = world.CreateBody(bodyDef);
	wheel.CreateFixture(fixDef);
	return wheel;
}
STEP 3: Join the wheels to car body using revolute joints. One thing to notice is to enable motor in the revolute joint but set it's speed to zero. If the motor is not enabled, we won't be able to steer the wheel.
var jfr = revJoint(car,fr); // Joint between car body and front right wheel
var jfl = revJoint(car,fl); // Joint between car body and front left wheel
var jrr = revJoint(car,rr); // Joint between car body and rear right wheel
var jrl = revJoint(car,rl); // Joint between car body and rear left wheel

//  Revolute Joints
function revJoint(body1,wheel)	{
	var revoluteJointDef = new b2RevoluteJointDef();
	revoluteJointDef.Initialize(body1, wheel, wheel.GetWorldCenter());
	revoluteJointDef.motorSpeed = 0;
	revoluteJointDef.maxMotorTorque = 1000;
	revoluteJointDef.enableMotor = true;
	revoluteJoint = world.CreateJoint(revoluteJointDef);
	return revoluteJoint;
}
STEP 4: Receiving input from keyboard and STEERING. I'm using jQuery.
var maxSteeringAngle = 1;
var steeringAngle = 0;
var STEER_SPEED = 3;
var mspeed;
var sf,sb = false;
var ENGINE_SPEED = 300;

$(window).keydown(function(e) {
	var code = e.keyCode;
    console.log(code);
	if(code == 65) //LEFT
		steeringAngle = -maxSteeringAngle;
	if(code == 68) //RIGHT
		steeringAngle = maxSteeringAngle;
	if(code == 83) //FORWARD
		sf = true;
	if(code == 87) //BACKWARD
		sb = true;
});
$(window).keyup(function(e) {
	var code2 = e.keyCode;
    
	if(code2 == 68)
		steeringAngle = 0;
	if(code2 == 65)
		steeringAngle = 0;
	if(code2 == 87)
		sb = false;
	if(code2 == 83)
		sf = false;
});
More about the variable steeringAngle to be discussed in next step.

Here maxSteeringAngle is the maximum angle wheel can turn. Right now it is One Radian.

STEER_SPEED decides the speed of rotation of wheel. It is arbitrarily chosen as 3. Higher the value more will be rotation speed.

Significance of steeringAngle will be discussed shorlty.

Now put the following code in the update().
mspeed = steeringAngle - jfl.GetJointAngle();
jfl.SetMotorSpeed(mspeed * STEER_SPEED);
mspeed = steeringAngle - jfr.GetJointAngle();
jfr.SetMotorSpeed(mspeed * STEER_SPEED);
What happens here is if we want to turn right, we press 'd' (code = 68). The value of steeringAngle is set to maxSteeringAngle i.e. 1 radian.

Now in update function a variable mspeed is set to the difference between steeringAngle and current wheel angle. Since we are steering it right, 'current wheel angle' will be positive. Now we set the joint motor speed equal to product of this mspeed variable and STEER_SPEED and the wheel rotates to right.

As long as 'd' key is pressed steeringAngle is 1 radian and joint.GetJointAngle() goes on increasing which decreases the difference and hence the mspeed variable. This goes on until the wheel has rotated by 1 radian and at this point the value of mspeed variable is zero. At this point there will no further rotation of the wheel even if the key 'd' is pressed.

Now at this point when the we release the 'd' key the value of steeringAngle is set to zero and the value of mspeed will be negative as the joint has positive value of 1 radian. Since the value of mspeed is negative, wheel will start rotating in other direction until the joint angle becomes zero again.

The same will happen when we want to steer left by pressing 'a' button.

STEP 5: Moving forward and cancelling lateral velocity.

Add these codes in your file.
var p1r = new b2Vec2();
var p2r = new b2Vec2();
var p3r = new b2Vec2();

var p1l = new b2Vec2();
var p2l = new b2Vec2();
var p3l = new b2Vec2();

function steerforward()	{
		fr.ApplyForce(new b2Vec2(p3r.x,p3r.y),fr.GetWorldPoint(new b2Vec2(0,0)));
		fl.ApplyForce(new b2Vec2(p3l.x,p3l.y),fl.GetWorldPoint(new b2Vec2(0,0)));
}
function steerbackward()	{
		fr.ApplyForce(new b2Vec2(-p3r.x,-p3r.y),fr.GetWorldPoint(new b2Vec2(0,0)));
		fl.ApplyForce(new b2Vec2(-p3l.x,-p3l.y),fl.GetWorldPoint(new b2Vec2(0,0)));
}

function cancelVel(wheel)	{
	var aaaa=new b2Vec2();
	var bbbb=new b2Vec2();
	var newlocal=new b2Vec2();
	var newworld=new b2Vec2();
	aaaa=wheel.GetLinearVelocityFromLocalPoint(new b2Vec2(0,0));
	bbbb=wheel.GetLocalVector(aaaa);
	newlocal.x = -bbbb.x;
	newlocal.y = bbbb.y;
	newworld = wheel.GetWorldVector(newlocal);
	wheel.SetLinearVelocity(newworld);
}
And following codes in update().
cancelVel(fr);
cancelVel(fl);
cancelVel(rr);
cancelVel(rl);

p1r = fr.GetWorldCenter();
p2r = fr.GetWorldPoint(new b2Vec2(0,1));
p3r.x = (p2r.x - p1r.x)*ENGINE_SPEED;
p3r.y = (p2r.y - p1r.y)*ENGINE_SPEED;
		
p1l = fl.GetWorldCenter();
p2l = fl.GetWorldPoint(new b2Vec2(0,1));
p3l.x = (p2l.x - p1l.x)*ENGINE_SPEED;
p3l.y = (p2l.y - p1l.y)*ENGINE_SPEED;
			
if(sf == true)	steerforward();
if(sb == true)	steerbackward();
First let's see cancelVel(). In this function we are passing the wheel bodies to cancel the perpendicular velocity on the wheel. In this cancelVel() we see four vectors -- aaaa, bbbb, newlocal and newworld.

1) aaaa is the vector which stores the velocity of the concerned wheel at its center position (0,0). This velocity vector may have a component perpendicular to wheel.

2) bbbb vector converts vector aaaa in local coordinates (UV coordinates).

3) newlocal vector stores the vector bbbb with the U direction flipped i.e. local velocity in perpendicular (U) direction is made negative. This step effectively cancels the perpendicular component of velocity.

4) This newlocal vector is converted to world coordinates (XY) using GetWorldVector() and is stored in newworld vector.

5) This velocity vector when applied to wheel does not cause any rotation of car. The perpendicular component of newlocal vector deletes the velocity in the other perpendicular direction of the wheel. SUPERIMPOSITION.

The following images may help in better understanding.









Now to steer forward we press the 'w' button which sets the variable 'sf' to true. Now if you look in update() if sf is true steerforward() is called. And this steerforward() applies a force on both the front wheels which makes the car move. If sf is false, no force will be applied.
Same goes for steering backward where sb variable takes over.
Now you must be wondering what p1r, p2r and p3r vectors are (r stands for right and l stands for left). Let me explain. We all know vectors are product of magnitude and direction where direction is itself a unit vector (magnitude = 1). So here we must define a unit vector first.

1) vector p1r stores the world coordinates of center of wheel (local point 0,0).

2) vector p2r stores the world coordinates of a point on wheel 1 unit back along the length of the wheel (local point 0,1).

3) So to get 'x' value for Engine Speed we subtract the 'x' value of p2r and p1r (unit vector) and multiply it my engine speed (magnitude).

4) Same is done to get 'y' value.

5) In steerforward(), this vector p3 is applied to both wheels and in steerbackward(), vector p3 is applied in negative direction.

This way we apply a force in the direction of wheel.

That's it! We're done here. If you have any problem in understanding, please leave a comment or reply.


Click on the canvas and then use w - forward, s - backward, a - left and d - right to move the car.

By administrator at 12:13:12 PM 12 Comment(s)

Comments

Amazing stuff, I would indeed love a tutorial on this provided you have the time of course
By Nathan on 5 Sep, 2012 at 12:48:55 AM
Thanks for the encouragement. I'm working on the tutorial right now.
By administrator on 5 Sep, 2012 at 03:48:32 AM
Just read through this thanks a lot for the update. When I looked through it initially it was the cancelVel() function I was struggling to understand. aaaa and bbbb as variables can get a bit confusing :) Cheers
By Nathan on 11 Sep, 2012 at 12:57:00 AM
Yeah. I was in a hurry to finish this tutorial. And yes 'aaaa' and 'bbbb' can be quite difficult to understand but you understood it. Right?
By administrator on 11 Sep, 2012 at 04:21:30 PM

You don't happen to know what settings you would have to change to simulate the car driving on ice do you?

By Evertith on 28 Feb, 2013 at 09:04:40 PM

@Evertith: If you'll notice that in cancelVel() I've cancelled all of the sideways velocity.

newlocal.x = -bbbb.x;

For car driving on ice, the car will skid sideways and hence we'll not cancel all of the sideways velocity on REAR WHEEL but just a fraction of it.

if(wheel == rr || wheel == rl)
    newlocal.x = -bbbb.x * 0.8;

I think this should do it. Let me know.

By administrator on 1 Mar, 2013 at 03:34:06 PM

Can you show an example of how I could do this only using a single
body, with now wheels?

I building a top down arcade racer and want to implement sliding / drift but it
seems thatmodelling all wheels is overkill .

Many thanks .

By Tom on 8 Feb, 2014 at 07:48:10 AM

Refer to the comment by me above your comment. That might help. Change the value of 0.8 according to your need.

By administrator on 8 Feb, 2014 at 02:43:51 PM

Thanks for the reply. Ive got this working now - its great.

I do have one problem. Don't seem to be able to get the car to stop. I can accelerate forward and backward but when Im not holding up or down the car continues to move in last direction. How can I make it slow to a stop when not accelerating/decelerating?

By tom on 9 Feb, 2014 at 01:13:32 AM

Also, dont seem to be able to get it to move with ApplyForce - I have to use ApplyImpulse and on the body not the wheels. not sure where i ve gone wrong..

By tom on 9 Feb, 2014 at 01:13:46 AM

Can you explain why the update function -and only that one-
has a semicolon? function update(){…some code…};

By haris on 7 Jul, 2014 at 03:33:03 PM

@haris delete the semicolon. It won't make any difference.

By administrator on 8 Jul, 2014 at 04:08:12 AM

Add a Comment

Please enter the email address.Invalid format. (Won't be Displayed)
Notify me of followup comments
Enter the displayed Code: captcha