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
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 Versions 2.2 and 3.0 of Smart Mobile Studio.
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.