Distance Joints

Distance joints are used to keep the anchor points on two bodies a fixed distance apart, as if linked by an invisible rod. This example has only one distance joint, which is used to link the controller to the torso of the rag-doll marionette. Find the three lines of code for the creation of the distance joint near the end of the ApplicationStarting procedure. Use touch or (better) mouse to change the orientation of the controller and thereby have some control over the puppet.

You can compare the code with that of our Lazarus version (which was much more difficult to develop).

Demonstration

Box2DDistance.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;

// Rag-doll based on JavaScript trunk\Box2D\Testbed\Tests\TestRagDoll.js
// in a zip download from https://code.google.com/archive/p/box2d-html5/source

interface

uses
  System.Types, SmartCL.System, SmartCL.Components, SmartCL.Controls, SmartCL.Application,
  SmartCL.Game, SmartCL.GameApp, SmartCL.Graphics, Box2DWrapper, System.Colors,
  SmartCL.MouseTouch, SmartCL.Touch;

type
  TCanvasProject = class(TW3CustomGameApplication)
  private
    const BW = 0.5; // bar width (ground)
    const FRAME_RATE = 1 / 60;
    const SCALE = 10;
    const SCENE_WIDTH = 400;
    const SCENE_HEIGHT = 300;
    const START_X = 0.5 * SCENE_WIDTH / SCALE;
    const START_Y = 15;
    const RESTITUTION = 0.3;
    const FRICTION = 0.4;
    const RADIUS = 1.4; // head
    const CONTROLLER_WIDTH = 6;
    const CONTROLLER_HEIGHT = 3;
    const DEGTORAD = 3.142 / 180;
    FWorld: Tb2World;
    FHead: Tb2Body;
    FTorsos: array[0..2] of Tb2Body;
    FTorsoYcoords := [2.5, 4.3, 5.8];
    FLimbBodies: array[0..7] of Tb2Body;
    FLimbBodyHeights := [1.3, 1.2, 4.4, 4.0];
    FLimbBodyWidths := [3.6, 3.4, 1.5, 1.2];
    FLimbBodyXcoords := [-3.0, 3.0, -5.7, 5.7, -0.8, 0.8, -0.8, 0.8];
    FLimbBodyYcoords := [2.0, 2.0, 8.5, 12.0];
    FIsMouseDown: Boolean;
    FMouseJoint: Tb2MouseJoint;
    FController, FMount: Tb2Body;
    FMousePos: Tb2Vec2;
    FHingeJointDef: Tb2RevoluteJointDef;
    FHingeJoint: Tb2RevoluteJoint;
    FDistanceJointDef: Tb2DistanceJointDef;
    FDistanceJoint: Tb2DistanceJoint;
  protected
    procedure ApplicationStarting; override;
    procedure ApplicationClosing; override;
    procedure PaintView(Canvas: TW3Canvas); override;
    procedure MouseDownHandler(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure MouseMoveHandler(Sender: TObject; Shift: TShiftState; X, Y: Integer);
   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 * SCENE_WIDTH / SCALE, BW / 2);
  BodyDef.Position.SetXY(0.5 * SCENE_WIDTH / SCALE, SCENE_HEIGHT / SCALE - BW / 2);
  FWorld.CreateBody(BodyDef).CreateFixture(FixtureDef); // Bottom horizontal bar
  // Create the head
  BodyDef.BodyType := btDynamicBody;
  FixtureDef.shape := Tb2CircleShape.Create(RADIUS);
  BodyDef.Position.SetXY(START_X, START_Y);
  FHead := FWorld.CreateBody(BodyDef);
  FHead.CreateFixture(FixtureDef);

  FixtureDef.Shape := Tb2PolygonShape.Create;
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(1.5, 1.0);
  FixtureDef.restitution := 0.1;
  for var i := 0 to 2 do
    begin
      BodyDef.Position.SetXY(START_X, START_Y + FTorsoYcoords[i]);
      FTorsos[i] := FWorld.CreateBody(BodyDef);
      FTorsos[i].CreateFixture(FixtureDef);
    end;
  for var i := 0 to 7 do
    begin
      Tb2PolygonShape(FixtureDef.Shape).SetAsBox(FLimbBodyWidths[i div 2] / 2, FLimbBodyHeights[i div 2] / 2);
      BodyDef.Position.SetXY(START_X + FLimbBodyXcoords[i], START_Y + FLimbBodyYcoords[i div 2]);
      FLimbBodies[i] := FWorld.CreateBody(BodyDef);
      FLimbBodies[i].CreateFixture(FixtureDef);
    end;
  // Create controller.
  BodyDef.BodyType := btDynamicBody;
  BodyDef.Position.SetXY(0.5 * SCENE_WIDTH / SCALE, 5);
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(0.5 * CONTROLLER_WIDTH, 0.5 * CONTROLLER_HEIGHT);
  FController := FWorld.CreateBody(BodyDef);
  FController.CreateFixture(FixtureDef);
  // Create mount for controller
  BodyDef.BodyType := btStaticBody;
  FMount := FWorld.CreateBody(BodyDef);
  FMount.CreateFixture(FixtureDef);
  // Create revolute joints
  FHingeJointDef := Tb2RevoluteJointDef.Create;
  FHingeJointDef.EnableLimit := True;
  // Head to shoulders
  FHingeJointDef.LowerAngle := -10.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 10.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[0], FHead, Tb2Vec2.Create(START_X, (START_Y + 1.5)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Upper arm to shoulders
  // L
  FHingeJointDef.LowerAngle := -85.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 130.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[0], FLimbBodies[0], Tb2Vec2.Create(START_X - 1.8, (START_Y + 2.0)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // R
  FHingeJointDef.LowerAngle := -130.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 85.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[0], FLimbBodies[1], Tb2Vec2.Create(START_X + 1.8, (START_Y + 2.0)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Lower arm to upper arm
  // L
  FHingeJointDef.LowerAngle := -130.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 10.0 * DEGTORAD;
  FHingeJointDef.Initialize(FLimbBodies[0], FLimbBodies[2], Tb2Vec2.Create(START_X - 4.5, (START_Y + 2.0)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // R
  FHingeJointDef.LowerAngle := -10.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 130.0 * DEGTORAD;
  FHingeJointDef.Initialize(FLimbBodies[1], FLimbBodies[3], Tb2Vec2.Create(START_X + 4.5, (START_Y + 2.0)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Shoulders to stomach
  FHingeJointDef.LowerAngle := -15.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 15.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[0], FTorsos[1], Tb2Vec2.Create(START_X, (START_Y + 3.5)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Stomach to hips
  FHingeJointDef.Initialize(FTorsos[1], FTorsos[2], Tb2Vec2.Create(START_X, (START_Y + 5.0)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Torso to upper leg
  // L
  FHingeJointDef.LowerAngle := -25.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 45.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[2], FLimbBodies[4], Tb2Vec2.Create(START_X - 0.8, (START_Y + 7.2)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // R
  FHingeJointDef.LowerAngle := -45.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 25.0 * DEGTORAD;
  FHingeJointDef.Initialize(FTorsos[2], FLimbBodies[5], Tb2Vec2.Create(START_X + 0.8, (START_Y + 7.2)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Upper leg to lower leg
  // L
  FHingeJointDef.LowerAngle := -25.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 115.0 * DEGTORAD;
  FHingeJointDef.Initialize(FLimbBodies[4], FLimbBodies[6], Tb2Vec2.Create(START_X - 0.8, (START_Y + 10.5)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // R
  FHingeJointDef.LowerAngle := -115.0 * DEGTORAD;
  FHingeJointDef.UpperAngle := 25.0 * DEGTORAD;
  FHingeJointDef.Initialize(FLimbBodies[5], FLimbBodies[7], Tb2Vec2.Create(START_X + 0.8, (START_Y + 10.5)));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));

  // Mount to controller
  FHingeJointDef.LowerAngle := -20 * DEGTORAD;
  FHingeJointDef.UpperAngle := 20 * DEGTORAD;
  FHingeJointDef.Initialize(FMount, FController, Tb2Vec2.Create(0.5 * SCENE_WIDTH / SCALE, 5));
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));


  FDistanceJointDef := Tb2DistanceJointDef.Create;
  FDistanceJointDef.Initialize(FController, FTorsos[1],
                               Tb2Vec2.Create(0.5 * SCENE_WIDTH / SCALE - 2, 5),
                               Tb2Vec2.Create(START_X, START_Y));
  FDistanceJoint := Tb2DistanceJoint(FWorld.CreateJoint(FDistanceJointDef));

  // Mouse and touch handling
  GameView.OnMouseTouchClick := MouseDownHandler;
  GameView.OnMouseTouchRelease := lambda FIsMouseDown := False; end;
  GameView.OnMouseMove := MouseMoveHandler;
  GameView.OnTouchMove := lambda(sender: TObject; td: TW3TouchData) FMousePos.SetXY(
    td.Touches.Touches[0].PageX  / SCALE, td.Touches.Touches[0].PageY / SCALE); end;
  FMousePos := Tb2Vec2.Create(0, 0);

  GameView.Delay := 5;
  GameView.StartSession(False);
end;

procedure TCanvasProject.ApplicationClosing;
begin
  GameView.EndSession;
  inherited;
end;

procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
var
  MouseJointDef: Tb2MouseJointDef;
begin
  // Draw background and bottom bar
  Canvas.FillStyle := 'rgb(0, 200, 0)';
  Canvas.FillRectF(0, 0, SCENE_WIDTH , SCENE_HEIGHT);
  Canvas.FillStyle := 'rgb(0, 0, 0)';
  Canvas.FillRectF(0, SCENE_HEIGHT - BW * SCALE, SCENE_WIDTH, BW * SCALE);

  FWorld.Advance(FRAME_RATE, 6, 2);
  var Pos := FHead.GetPosition;
  Canvas.BeginPath;
  Canvas.FillStyle := ColorToWebStr(clDarkSalmon);
  Canvas.Ellipse((Pos.X - RADIUS) * SCALE, (Pos.Y - RADIUS) * SCALE, (Pos.X + RADIUS) * SCALE, (Pos.Y + RADIUS) * SCALE);
  Canvas.Fill;
  Canvas.FillStyle := 'blue';
  for var i := 0 to 2 do
    begin
      Pos := FTorsos[i].GetPosition;
      var Theta := FTorsos[i].GetAngle;
      Canvas.Save;
      Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE);
      Canvas.Rotate(Theta);
      if i = 2 then
        Canvas.FillStyle := 'black';
      Canvas.FillRectF(-1.5 * SCALE, - SCALE, 3 * SCALE, 2 * SCALE);
      Canvas.Restore;
    end;
  Canvas.FillStyle := 'blue';
  for var i := 0 to 7 do
    begin
      Pos := FLimbBodies[i].GetPosition;
      var Theta := FLimbBodies[i].GetAngle;
      Canvas.Save;
      Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE);
      Canvas.Rotate(Theta);
      if i >= 4 then
         Canvas.FillStyle := 'black';
      Canvas.FillRectF(-FLimbBodyWidths[i div 2] * SCALE / 2 ,-FLimbBodyHeights[i div 2] * SCALE / 2,
                       FLimbBodyWidths[i div 2] * SCALE, FLimbBodyHeights[i div 2] * SCALE);
      Canvas.Restore;
    end;
  // Controller
  // Handle mouse
  if FIsMouseDown and not Assigned(FMouseJoint) then
    begin
      MouseJointDef := Tb2MouseJointDef.Create;
      MouseJointDef.BodyA := FWorld.GetGroundBody;
      MouseJointDef.BodyB := FController;
      MouseJointDef.Target.SetXY(FMousePos.X, FMousePos.Y);
      MouseJointDef.CollideConnected := True;
      MouseJointDef.MaxForce := 300.0 * FController.GetMass;
      FMouseJoint := Tb2MouseJoint(FWorld.CreateJoint(MouseJointDef));
      FController.SetAwake(True);
    end;
  if Assigned(FMouseJoint) then
    if FIsMouseDown then
      FMouseJoint.SetTarget(FMousePos)
    else
      begin
        FWorld.DestroyJoint(FMouseJoint);
        FMouseJoint := nil;
      end;

  if Assigned(FController) then
    begin
      var Pos := FController.GetPosition;
      var Theta := FController.GetAngle;
      Canvas.Save;
      Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE);
      Canvas.Rotate(Theta);
      Canvas.FillStyle := 'red';
      Canvas.FillRectF((- 0.5 * CONTROLLER_WIDTH) * SCALE, (- 0.5 * CONTROLLER_HEIGHT) * SCALE,
                       CONTROLLER_WIDTH * SCALE, CONTROLLER_HEIGHT * SCALE);
      Canvas.Restore;
   end;

  FWorld.ClearForces;
end;

procedure TCanvasProject.MouseDownHandler(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  FMousePos.SetXY(X / SCALE, Y / SCALE);
  FIsMouseDown := True;
end;

procedure TCanvasProject.MouseMoveHandler(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  FMousePos.SetXY(X / SCALE, Y / SCALE);
end;

end.    

Programming - a skill for life!

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