Revolute Joints
This example shows you how to use a revolute joint for each axle, with the rear one motorised. Instead of constructing a complete vehicle we restrict our demonstration to the chassis and wheels. We provide the chassis with a starting position and the wheels take their position from the anchor points on the chassis. The values of the constants are chosen so that, with only one axle motorised, the chassis gets stuck on the second ramp after moving over the first. We hope that you will be tempted to experiment with the constants and to develop your own vehicles and terrains.
You can compare this code with that of our form-based Lazarus and PasSFML versions.
Demonstration
If you do not see the demonstration, your school security system might have blocked it. Click here to try it on a separate page.
Smart Pascal Code
This code compiles with Version 2.2 of Smart Mobile Studio to give the output shown above. It also compiles with Version 3.0 (using an identical Box2DWrapper unit), but in that case the resultant javascript has errors.
unit Unit1; interface uses System.Types, SmartCL.System, SmartCL.Components, SmartCL.Controls, SmartCL.Application, SmartCL.Game, SmartCL.GameApp, SmartCL.Graphics, Box2DWrapper; type TCanvasProject = class(TW3CustomGameApplication) private const BW = 0.5; // bar width (for the ground) const CHASSIS_WIDTH = 5; const CHASSIS_HEIGHT = 1; const WHEEL_RADIUS = 1; const AXLE_DX = 0.2; // Distance of axle from nearest end of chassis const AXLE_DY = 0.4; // Distance of axle from bottom of chassis const FRAME_RATE = 1 / 60; const SCALE = 10; const RESTITUTION = 0.2; const FRICTION = 0.5; FWorld: Tb2World; FChassis, FRearWheel, FFrontWheel: Tb2Body; FRamps: array[1..2] of Tb2Body; FAxleJointDef: Tb2RevoluteJointDef; FAxleJoint: Tb2RevoluteJoint; protected procedure ApplicationStarting; override; procedure ApplicationClosing; override; procedure PaintView(Canvas: TW3Canvas); override; end; implementation procedure TCanvasProject.ApplicationStarting; var FixtureDef: Tb2FixtureDef; BodyDef: Tb2BodyDef; begin inherited; FWorld := Tb2World.Create( Tb2Vec2.Create(0.0, 10.0), // gravity True // allow sleep ); // Create fixture definition (used to describe fixture objects) FixtureDef := Tb2FixtureDef.Create; FixtureDef.Density := 1.0; FixtureDef.Friction := FRICTION; FixtureDef.Restitution := RESTITUTION; // Create body definition class (used to describe body objects) BodyDef := Tb2BodyDef.Create; BodyDef.BodyType := btStaticBody; // Create the bottom bar. FixtureDef.Shape := Tb2PolygonShape.Create; Tb2PolygonShape(FixtureDef.Shape).SetAsBox(0.5 * GameView.Width / SCALE, BW / 2); BodyDef.Position.SetXY(0.5 * GameView.Width / SCALE, GameView.Height / SCALE - BW / 2); FWorld.CreateBody(BodyDef).CreateFixture(FixtureDef); // Bottom horizontal bar // Create two ramps. var Vertices: Tb2Vec2Array; Vertices[0] := Tb2Vec2.Create(-5, 1.0); Vertices[1] := Tb2Vec2.Create(5, -1.0); Vertices[2] := Tb2Vec2.Create(5, 1.0); var TriangleShape: Tb2PolygonShape; TriangleShape := Tb2PolygonShape.Create; TriangleShape.SetAsArray(Vertices, 3); // passing array of Tb2Vec2 to the shape FixtureDef.Shape := TriangleShape; BodyDef.Position.SetXY(0.3 * GameView.Width / SCALE, GameView.Height / SCALE - 0.5 ); FRamps[1] := FWorld.CreateBody(BodyDef); Framps[1].CreateFixture(FixtureDef); BodyDef.Position.SetXY(0.3 * GameView.Width / SCALE + 7, GameView.Height / SCALE - 0.5 ); FRamps[2] := FWorld.CreateBody(BodyDef); Framps[2].CreateFixture(FixtureDef); // Create chassis FixtureDef.Shape := Tb2PolygonShape.Create; BodyDef.BodyType := btDynamicBody; Tb2PolygonShape(FixtureDef.Shape).SetAsBox(CHASSIS_WIDTH / 2, CHASSIS_HEIGHT / 2); BodyDef.Position.SetXY(2, GameView.Height / SCALE - 0.5 * CHASSIS_HEIGHT - BW - (WHEEL_RADIUS - AXLE_DY)); FChassis := FWorld.CreateBody(BodyDef); FChassis.CreateFixture(FixtureDef); // Create wheels FixtureDef.Shape := Tb2CircleShape.Create(WHEEL_RADIUS); FRearWheel := FWorld.CreateBody(BodyDef); FRearWheel.CreateFixture(FixtureDef); FFrontWheel := FWorld.CreateBody(BodyDef); FFrontWheel.CreateFixture(FixtureDef); // Create revolute joint for rear wheel FAxleJointDef := Tb2RevoluteJointDef.Create; FAxleJointDef.BodyA := FChassis; FAxleJointDef.BodyB := FRearWheel; FAxleJointDef.CollideConnected := False; FAxleJointDef.LocalAnchorA := Tb2Vec2.Create(AXLE_DX - CHASSIS_WIDTH / 2, CHASSIS_HEIGHT / 2 - AXLE_DY); FAxleJointDef.LocalAnchorB := Tb2Vec2.Create(0, 0); FAxleJointDef.enableMotor := True; FAxleJointDef.motorSpeed := 3.5; FAxleJointDef.maxMotorTorque := 1000; FAxleJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FAxleJointDef)); FAxleJointDef.enableMotor := False; // Revolute joint for front wheel FAxleJointDef.BodyB := FFrontWheel; FAxleJointDef.LocalAnchorA := Tb2Vec2.Create(CHASSIS_WIDTH / 2 - AXLE_DX, CHASSIS_HEIGHT / 2 - AXLE_DY); FAxleJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FAxleJointDef)); GameView.Delay := 5; GameView.StartSession(False); end; procedure TCanvasProject.ApplicationClosing; begin GameView.EndSession; inherited; end; procedure TCanvasProject.PaintView(Canvas: TW3Canvas); begin // Draw background and bottom bar Canvas.FillStyle := 'rgb(0, 200, 0)'; Canvas.FillRectF(0, 0, GameView.Width , GameView.Height); Canvas.FillStyle := 'rgb(0, 0, 0)'; Canvas.FillRectF(0, GameView.Height - BW * SCALE, GameView.Width, BW * SCALE); // Draw both ramps for var j := 1 to 2 do begin var Pos := FRamps[j].GetPosition; var FixtureList := FRamps[j].GetFixtureList; var Shape := FixtureList.GetShape; var Vert: Tb2Vec2array; asm @Vert = (@Shape).GetVertices(); end; Canvas.BeginPath; // Draw filled triangle. Canvas.MoveToF((Vert[0].X + Pos.X) * SCALE, (Vert[0].Y + Pos.Y) * SCALE); for var i := 1 to 2 do Canvas.LineToF((Vert[i].X + Pos.X) * SCALE, (Vert[i].Y + Pos.Y) * SCALE); Canvas.ClosePath; Canvas.Fill; end; // Advance and draw movable shapes (wheels and chassis) FWorld.Advance(FRAME_RATE, 6, 2); var Pos := FRearWheel.GetPosition; Canvas.BeginPath; Canvas.FillStyle := 'rgb(20, 20, 20)'; Canvas.Ellipse((Pos.X - WHEEL_RADIUS) * SCALE, (Pos.Y - WHEEL_RADIUS) * SCALE, (Pos.X + WHEEL_RADIUS) * SCALE, (Pos.Y + WHEEL_RADIUS) * SCALE); Pos := FFrontWheel.GetPosition; Canvas.Ellipse((Pos.X - WHEEL_RADIUS) * SCALE, (Pos.Y - WHEEL_RADIUS) * SCALE, (Pos.X + WHEEL_RADIUS) * SCALE, (Pos.Y + WHEEL_RADIUS) * SCALE); Canvas.Fill; Pos := FChassis.GetPosition; var Theta := FChassis.GetAngle; Canvas.Save; Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE); Canvas.Rotate(Theta); Canvas.FillStyle := 'rgb(200, 0, 0)'; Canvas.FillRectF(-CHASSIS_WIDTH * SCALE / 2, -CHASSIS_HEIGHT * SCALE / 2, CHASSIS_WIDTH * SCALE, CHASSIS_HEIGHT * SCALE); Canvas.Restore; FWorld.ClearForces; end; end.