Q3 Physics Engine

Gorilla3D provides an integrated 3D physics engine for basic implementation.

Computation

The physics system only checks for collisions on explicit instruction, which means you have to tell Q3 PhysicsEngine to compute the next step on a fixed interval, by supplying a delta time since last update.

The most easy way to do this, is by a regular TTimer component. Use a timer with a near-to-reality interval, f.e. 16ms (~60 FPS) or 33ms (~30 FPS). This should lead to best results.

procedure TForm1.PhysicsTimer1Timer(Sender: TObject);
var LDelta : Single;
begin
  // get the delta time => time since the last step() was called
  LDelta := GorillaPhysicsSystem1.GetDeltaTime();
  GorillaPhysicsSystem1.Step(LDelta);
  [...]
end;

Start

To enable physics computation you have to enable the controller, by simply activating it.

GorillaPhysicsSystem1.Active := true;

Iterations

On collision detection all elements are compared to each other multiple times. Imagine three spheres, very close to each other. While one sphere is colliding with both other spheres. It's not enough to move sphere1 only once, because both other spheres influencing it at the same time. So physics computation iterates over collision detection multiple times, to simulate it as real as possible.

Iterations will define how accurate physics computation is. Allowed values are between 1 and 20.

  • The lower the value is, the faster computation is, but the less accurate.
  • The higher the value is, the slower computation is, but the more accurate.

A good iteration number range is 5 to 20. The default is set to 10.

GorillaPhysicsSystem1.Engine.SetIterations(20);

Bounds

The physics world should have boundaries, to prevent objects from falling forever. Not only it's in most cases senseless, because objects are out of sight. It's also very bad for performance, because unneeded computation has to be solved.

So set up bounds that suit your scene. When objects reaching those boundaries, the will be laid down to sleep and will not affect performance.

GorillaPhysicsSystem1.Engine.Bounds := TBoundingBox.Create(Point3D(0,0,0), 100, 100, 100);

Gravity

You can influence physics world gravity vector. The gravity vector is the direction objects falling.

// falling upwards instead of downwards
GorillaPhysicsSystem1.Engine.Gravity := Point3D(0, -1, 0);

Max Contacts

A builtin constant is the maximum contact count. It's currently set to 128. This is important to keep in mind when setting up a scene with many objects close to each other.

This limit is not configurable!

The contact count is the number of contacts of one object to other objects. When having huge scenes with hundreds of objects close together, this number will reach its limit very fast. This will lead to unsolved collisions! So collisions between some objects will not be considered! The contact count limit exists to keep performance at a high level. Even if there is no object limit, this will limit your scene indirectly.

Types of Body

Q3 Physics Engine allows to decide between 3 different body types.

Type Description
TQ3BodyType.eStaticBody Static bodies will not be move or influenced by mass and impulses. Use this type for defining a plane / floor.
TQ3BodyType.eDynamicBodyDynamic bodies will move, handle forces influenced by mass and impulses. Use this type by default for any kind of physics-engine handled instances.
TQ3BodyType.eKinematicBodyKinematic bodies will be moved, but will not be influenced by mass and impulses. Use this type as an alternative to dynamic bodies.

Colliders

Collision detection in physics is based on so called colliders. Colliders depending on the shape of a visual component like a sphere, box or capsule. For each shape there has to exist an individual collider type to handle intersection tests.

At the moment it is possible to automatically check for collisions by the following shapes:

  • Box
  • Sphere
  • Terrain
  • Particle
  • Capsule
  • Mesh (experimental)
  • Terrain (experimental)

The physics controller establishes a separated 3D world for collision detection. Even though you link visual components like TGorillaCube or TGorillaSphere, those are just links and are not utilized for computation.

On collider registration you push starting transformation information and shape data to the physics world:

  • AddBoxCollider()
  • AddSphereCollider()
  • AddCapsuleCollider()
  • AddParticleCollider()
  • AddTerrainCollider()
  • AddMeshCollider()

From there on the physics controller will compute transformation based on its own universe. Nevertheless, it's able to push back transformation to linked visual components. This will make usage much easier and you don't have to take care of that.

But if you want to take influence vice versa, that means, manipulating physics transformation from the visual scene, you have to use helper functionality.

Remote-Control

Derived components of TCustomMesh and TGorillaMesh are supported for colliders.

Threading

The physics controller uses a separated 3D universe, which differs from the visual world. That allows us to use asynchronous computation in a separated thread. This will increase performance enormously.

In case your using a thread for simulation, you don't need to call Step() routine anymore. The thread will automatically execute it.

uses
  Gorilla.Physics.Q3.Scene;
 
[...]
var LThread : TQ3SimulationThread;
begin
  // create simulation thread
  LThread := TQ3SimulationThread.Create(GorillaPhysicsSystem1.Engine);
  // link physics controller with thread
  GorillaPhysicsSystem1.Engine.Thread := LThread;
  // reactivate physics computation
  GorillaPhysicsSystem1.Active := true;
  [...]

(available since v0.8.2.1600+)

Callbacks

The physics controller provides 2 callback events to interact with the physics world.

  • OnBeginContact
  • OnEndContact

Those callbacks will give you the both colliding elements at this moment of TQ3Body type (Gorilla.Physics.Q3.Body). Each body has an untyped pointer property “UserData”, which contains the linked visual component. The type of that link you can request with the property “UserDataType”.

procedure TUIMainWin.doOnBeginContact(const ABodyA, ABodyB : TQ3Body);
begin
    if not Assigned(ABodyA.UserDataType) then
      Exit;
    if not Assigned(ABodyB.UserDataType) then
      Exit;      
 
    if TComponent(ABodyA.UserData).Name.Equals('Cube1') and TComponent(ABodyB.UserData).Name.Equals('Sphere2') then
    begin
      FMX.Types.Log.D('expected collision between %s <=> %s', 
        [TComponent(ABodyA.UserData).Name, TComponent(ABodyB.UserData).Name]);
    end
    else
    begin
      FMX.Types.Log.D('<ERROR> unexpected collision between %s <=> %s', 
        [ABodyA.UserDataType^.Name, ABodyB.UserDataType^.Name]);
    end;
 end;

Remote-Control

As you read above, the physics controller universe is separated from scene universe. But sometimes you need to control elements in physics world. Therefore we've implemented some helper methods.

  • RemoteMeshTransform
  • RemoteBodyTransform
  • RemoteBodyImpulse
  • RemoteBodyForce
// move cube upwards by impulse
GorillaPhysicsSystem1.RemoteBodyImpulse(GorillaCube1, Point3D(0, -1, 0));

By the methods above you're able to set a position and/or rotation explicitly, or to apply a force / impulse on a specific element.

CAUTION: By this methods you can influence physical mechanics so heavily, that overstep bounds / limits, which may lead to unexpected behaviour.

Example

For a working physics system you need a TGorillaPhysicsSystem component on your form.

Form1.pas
type
  TForm1 = class(TForm)
    GorillaViewport1: TGorillaViewport;
    Light1: TLight;
    Dummy1: TDummy;
    Camera1: TCamera;
    GorillaPhysicsSystem1: TGorillaPhysicsSystem;
    Plane1: TPlane;
    GorillaCube1: TGorillaCube;
    PhysicsTimer1: TTimer;
    GorillaSphere1: TGorillaSphere;
    LightMaterialSource1: TLightMaterialSource;
    LightMaterialSource2: TLightMaterialSource;
    procedure FormCreate(Sender: TObject);
    procedure PhysicsTimer1Timer(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.fmx}
 
uses
  Gorilla.Physics, Gorilla.Physics.Q3.Body;
 
procedure TForm1.FormCreate(Sender: TObject);
var LPrefab : TGorillaColliderSettings;
begin
  // the plane should be a static body, otherwise it will fall in to nothingness
  LPrefab := TGorillaColliderSettings.Create(TQ3BodyType.eStaticBody);
  GorillaPhysicsSystem1.AddBoxCollider(Plane1, LPrefab);
 
  // the cube is a dynamic / moveable body which collides with the plane and sphere
  LPrefab := TGorillaColliderSettings.Create(TQ3BodyType.eDynamicBody);
  GorillaPhysicsSystem1.AddBoxCollider(GorillaCube1, LPrefab);
 
  // the sphere is a dynamic / moveable body which collides with the plane and cube
  LPrefab := TGorillaColliderSettings.Create(TQ3BodyType.eDynamicBody);
  GorillaPhysicsSystem1.AddSphereCollider(GorillaSphere1, LPrefab);
 
  // we activate the physics system
  GorillaPhysicsSystem1.Active := true;
 
  // and we need to activate the physics timer for constant updates
  PhysicsTimer1.Enabled := true;
end;
 
procedure TForm1.PhysicsTimer1Timer(Sender: TObject);
var LDelta : Single;
begin
  // get the delta time => time since the last step() was called
  LDelta := GorillaPhysicsSystem1.GetDeltaTime();
  GorillaPhysicsSystem1.Step(LDelta);
  GorillaViewport1.Invalidate();
end;

Next step: FMOD Audio