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

Box2DRevolute.html

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

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.    

Programming - a skill for life!

Using the Box2D graphics engine for advanced games in Smart Mobile Studio