Prismatic Joints

This demonstration uses one prismatic joint to keep the piston moving vertically alongside the cylinder wall and three revolute joints for the conrod and crank. We found it easier to achieve the required output by motorising the crankshaft rather than the sliding motion of the piston.

Try commenting out the line FSliderJoint := Tb2PrismaticJoint(FWorld.CreateJoint(FSliderJointDef)); to see a more entertaining motion graphic without the constraints of the prismatic joint!

You can compare this code with that of our Lazarus version.

Demonstration

Box2DPrismatic.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, System.Colors;

type
  TCanvasProject = class(TW3CustomGameApplication)
  private
    const FRAME_RATE = 1 / 60;
    const SCALE = 10;
    const RESTITUTION = 1;
    const FRICTION = 0.3;
    const WALL_WIDTH = 0.1;
    const CYLINDER_LENGTH = 3.0;
    const BORE = 2;
    const PISTON_LENGTH = 1.5;
    const ROD_WIDTH = 0.5;
    const ROD_LENGTH = 3.5;
    const CRANK_WIDTH = 0.5;
    const CRANK_LENGTH = 1.0;
    FWorld: Tb2World;
    FCylinderR, FPiston, FConrod, FCrank: Tb2Body;
    FHingeJointDef: Tb2RevoluteJointDef;
    FHingeJoint, FMotorisedJoint: Tb2RevoluteJoint;
    FSliderJointDef: Tb2PrismaticJointDef;
    FSliderJoint: Tb2PrismaticJoint;
    FFrameCount: integer;
  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;
  // cylinder wall
  FixtureDef.Shape := Tb2PolygonShape.Create;
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(WALL_WIDTH / 2, CYLINDER_LENGTH / 2);
  BodyDef.Position.SetXY(0.5 * GameView.Width / SCALE, 0.5 * GameView.Height / SCALE);
  FCylinderR := FWorld.CreateBody(BodyDef);
  FCylinderR.CreateFixture(FixtureDef); // cylinder right wall
  // Create piston
  BodyDef.BodyType := btDynamicBody;
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(BORE / 2, PISTON_LENGTH / 2);
  FPiston := FWorld.CreateBody(BodyDef);
  FPiston.CreateFixture(FixtureDef);
  // Create conrod and crank
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(ROD_WIDTH / 2, ROD_LENGTH / 2);
  FConrod := FWorld.CreateBody(BodyDef);
  FConrod.CreateFixture(FixtureDef);
  Tb2PolygonShape(FixtureDef.Shape).SetAsBox(CRANK_WIDTH / 2, CRANK_LENGTH / 2);
  FCrank := FWorld.CreateBody(BodyDef);
  FCrank.CreateFixture(FixtureDef);
  // Create prismatic joint for piston and cylinder wall
  FSliderJointDef := Tb2PrismaticJointDef.Create;
  FSliderJointDef.BodyA := FPiston;
  FSliderJointDef.BodyB := FCylinderR;
  FSliderJointDef.CollideConnected := False;
  FSliderJointDef.LocalAxisA := Tb2Vec2.Create(0, 1); // vertical
  FSliderJointDef.ReferenceAngle := 0;
  // Set anchors at right side of piston and left side of wall.
  asm
   (@FSliderJointDef).localAnchorA = new Box2D.Common.Math.b2Vec2(1, 0);
   (@FSliderJointDef).localAnchorB = new Box2D.Common.Math.b2Vec2(-0.05, 0);
  end;
  FSliderJointDef.enableMotor := False;
  FSliderJoint := Tb2PrismaticJoint(FWorld.CreateJoint(FSliderJointDef));
  // Create revolute joint for piston and conrod
  FHingeJointDef := Tb2RevoluteJointDef.Create;
  FHingeJointDef.BodyA := FPiston;
  FHingeJointDef.BodyB := FConrod;
  FHingeJointDef.CollideConnected := False;
  FHingeJointDef.LocalAnchorA := Tb2Vec2.Create(0, 0);
  FHingeJointDef.LocalAnchorB := Tb2Vec2.Create(0, -0.45 * ROD_LENGTH );
  FHingeJointDef.enableMotor := False;
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Create revolute joint for conrod and crank
  FHingeJointDef.BodyA := FConrod;
  FHingeJointDef.BodyB := FCrank;
  FHingeJointDef.LocalAnchorA := Tb2Vec2.Create(0, 0.45 * ROD_LENGTH);
  FHingeJointDef.LocalAnchorB := Tb2Vec2.Create(0, 0.45 * CRANK_LENGTH);
  FHingeJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));
  // Create revolute joint for cylinder wall and crank
  FHingeJointDef.BodyA := FCylinderR;
  FHingeJointDef.BodyB := FCrank;
  // Position the axle carefully to the left of and lower than the fixed cylinder wall.
  FHingeJointDef.LocalAnchorA := Tb2Vec2.Create(-1.05, 3.2);
  FHingeJointDef.LocalAnchorB := Tb2Vec2.Create(0, -0.45 * CRANK_LENGTH);
  FHingeJointDef.enableMotor := True;
  FHingeJointDef.motorSpeed := 5;
  FHingeJointDef.maxMotorTorque := 10000;
  FMotorisedJoint := Tb2RevoluteJoint(FWorld.CreateJoint(FHingeJointDef));

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

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

procedure TCanvasProject.PaintView(Canvas: TW3Canvas);
begin
  inc(FFrameCount);
  // Draw background
  Canvas.FillStyle := 'rgb(0, 200, 0)';
  Canvas.FillRectF(0, 0, GameView.Width , GameView.Height);
  // Draw right cylinder wall
  var Pos := FCylinderR.GetPosition;
  Canvas.FillStyle := 'rgb(0, 0, 0)';
  Canvas.FillRectF((Pos.X  - 0.5 * WALL_WIDTH) * SCALE, (Pos.Y - 0.5 * CYLINDER_LENGTH)  * SCALE,
                   WALL_WIDTH * SCALE, CYLINDER_LENGTH * SCALE);

  // Advance and draw movable shapes (piston, conrod and crank)
  FWorld.Advance(FRAME_RATE, 6, 2);

  Pos := FPiston.GetPosition;
 // Canvas.FillStyle := 'rgb(0, 0, 0)';
  Canvas.FillStyle := ColorToWebStr(clSilver);
  Canvas.FillRectF((Pos.X  - 0.5 * BORE) * SCALE, (Pos.Y - 0.5 * PISTON_LENGTH)  * SCALE,
                   BORE * SCALE, PISTON_LENGTH * SCALE);

  Canvas.FillStyle := ColorToWebStr(clDarkSlateGray);
  Pos := FConrod.GetPosition;
  var Theta := FConrod.GetAngle;
  Canvas.Save;
  Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE);
  Canvas.Rotate(Theta);
  Canvas.FillRectF((-0.5 * ROD_WIDTH) * SCALE, (- 0.5 * ROD_LENGTH) * SCALE,
                   ROD_WIDTH * SCALE, ROD_LENGTH * SCALE);
  Canvas.Restore;

  Pos := FCrank.GetPosition;
  Theta := FCrank.GetAngle;
  Canvas.Save;
  Canvas.Translate(Pos.X * SCALE, Pos.Y * SCALE);
  Canvas.Rotate(Theta);
  Canvas.FillRectF((-0.5 * CRANK_WIDTH) * SCALE, (-0.5 * CRANK_LENGTH) * SCALE,
                   CRANK_WIDTH * SCALE, CRANK_LENGTH * SCALE);
  Canvas.Restore;
  if FFrameCount = 800 then
    FMotorisedJoint.SetMotorSpeed(0);
  FWorld.ClearForces;
end;

end.    

Programming - a skill for life!

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