Q3 Physics Engine

Gorilla3D provides an integrated 3D physics engine for a variaty of colliders and rigid body types. Basically the physics engine sets up a “parallel universe” besides your visual 3D scene. It allows to process collision detection fast and asynchronously.

So remember, when integrating Q3 physics into your scene, you have to register colliding objects in this “parallel universe”. This can be done by design-time collider setup in the TGorillaPhysicsSystem component, or by calling registration methods like: AddSphereCollider(), AddBoxCollider(), AddTerrainCollider(), ….

The physics engine will then link a rigid body to the visual feedback component (TGorillaModel, TGorillaCube, …). By default the engine will take care of visual object movement and rotation.

But you're allowed to overwrite this behaviour for example by various callback events. You can change movement or react on specific object collisions.

Computation

The physics system can be run in main thread or in a separated thread. If running in main thread you have to be aware that computation might affect rendering and slow down performance. To activate asyncronously computation, simply switch TGorillaPhysicsSystem.Async to TRUE.

On main thread execution you have to tell the Q3 Physics Engine to check for collisions and perform movement/rotation on regular basis. While in asyncronously mode this is automatically done for you.

The most easy way to do this, is by a simple 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. But remember: A timer may not be called very accurate because of too many tasks in main thread.

Remark: Since v0.8.3.1982 we simplified the Step() call. A delta time request is no longer needed.

procedure TForm1.PhysicsTimer1Timer(Sender: TObject);
begin
  GorillaPhysicsSystem1.Step();
  [...]
end;

Start

To enable physics computation you have to enable the controller, by simply activating it at runtime or at designtime.

GorillaPhysicsSystem1.Active := true;

Remark: If colliders were set up at designtime, only activate physics system at runtime. Because of different component loading order, a linked component may not be present at the moment of activation. This would lead to false collider registration.

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 have no rigid body component attached to them, so the physics engine does not consider them to be moving. (Avoid moving these frequently or you'll violate that expectation). Use static colliders for level geometry like the ground and immovable walls, or stationary trigger volumes. Static colliders on their own won't set off trigger/collision messages, unless there's a rigid body on the other participant.
TQ3BodyType.eDynamicBodyDynamic bodies have a rigid body component attached to them and their eKinematic flag is not set. These objects move at the whims of physics according to their linear/angular velocities and the forces/torques and collision impacts exerted on them. The physics engine takes responsibility for resolving their collisions with static, kinematic, and other dynamic objects and rebounding them as needed. Use these for basic physics objects you want to be able to stack & topple and have them behave plausibly with minimal intervention, or for objects that you want to steer in a physics-focused way, like a rocketship.
TQ3BodyType.eKinematicBodyKinematic bodies have a rigid body component with a set eKinematic flag. This tells the physics engine “this object moves, but I'll handle that part” — the kinematic object will process collisions with other rigid bodies, but only dynamic objects will automatically react by bouncing away, and cause messages to be sent. The kinematic object itself won't move except how you tell it to with MovePosition or MoveRotation — its velocity won't automatically integrate each timestep. Use this for objects that you want to control in ways that don't behave like simple physics bodies — like a bipedal character controller or highly custom vehicle controls. Use physics queries like overlap checks and shape casts to scan for collisions preemptively, since they won't stop your object automatically.

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:

Method
procedure AddBoxCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings);Add a box collider for a specific TControl3D instance to the physics system. At first you will need to create a TGorillaColliderSettings structure, where to define the body type (static, dynamic, kinematic)
procedure AddSphereCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings);Add a spherical collider for a specific TControl3D instance to the physics system. At first you will need to create a TGorillaColliderSettings structure, where to define the body type (static, dynamic, kinematic)
procedure AddCapsuleCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings; const ARadius, AHeight : Single);Add a capsule collider for a specific TControl3D instance to the physics system. At first you will need to create a TGorillaColliderSettings structure, where to define the body type (static, dynamic, kinematic)
procedure AddParticleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const ATransformation : TMatrix3D; const ARadius : Single; out ABody : TQ3Body);Add a particle collider for a specific pointer value to the physics system. At first you will need to create a TGorillaColliderSettings structure, where to define the body type (static, dynamic, kinematic). Also provide a starting transformation matrix and a radius of the particle. The procedure will return a TQ3Body instance as pointer which should be linked with your particle structure (AParticle). This method is automatically used by the physics particle influencer class.
procedure AddTerrainCollider(const AMesh : TCustomMesh; const APrefab : TGorillaColliderSettings);Add a terrain collider for a specific TCustomMesh (terrain) instance to the physics system. At first you will need to create a TGorillaColliderSettings structure, where to define the body type (static, dynamic, kinematic). Of course the body type is flexible, but for terrains you should use the static body type.
procedure AddTerrainCollider(const ATerrain : TGorillaMesh; const APrefab : TGorillaColliderSettings);Add a terrain collider for a specific TGorillaMesh instance to the physics system. At first you will need to create a TGorillaColliderSettings structure where to define the body type (static, dynamic, kinematic). Of course the body type is flexible, but for terrains you should use the static body type.
procedure AddMeshCollider(const AMesh : TCustomMesh; const APrefab : TGorillaColliderSettings);Add a capsule collider for a specific TCustomMesh instance to the physics system. At first you will need to create a TGorillaColliderSettings structure where to define the body type (static, dynamic, kinematic)
procedure AddMeshCollider(const AMesh : TGorillaMesh; const APrefab : TGorillaColliderSettings);Add a capsule collider for a specific TGorillaMesh instance to the physics system. At first you will need to create a TGorillaColliderSettings structure where to define the body type (static, dynamic, kinematic)

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 TControl3D, TCustomMesh and TGorillaMesh are supported for colliders. But for mesh or terrain colliders only TCustomMesh and TGorillaMesh/TGorillaModel are allowed. Other components do not have any vertex information.

ColliderSettings / ColliderPrefab

Registering a collider for a 3D object expects collider settings (TGorillaColliderSettings) or a collider prefab (TGorillaPhysicsColliderPrefab).

The collider prefab is a collection item for design time usage. It will automatically register a collider settings (TGorillaColliderSettings) for you when the physics system getting started.

While the collider settings (TGorillaColliderSettings) is the core structure for collider registration.

It gives you a number of possible settings:

Property Description
_TypeTGorillaPhysicsBodyType (TQ3BodyType.eStaticBody, TQ3BodyType.eDynamicBody or TQ3BodyType.eKinematicBody)
LockRotAxisXLocked rotation on the x axis.
LockRotAxisYLocked rotation on the y axis.
LockRotAxisZLocked rotation on the z axis.
LockMoveAxisXLocked translation on the x axis.
LockMoveAxisYLocked translation on the y axis.
LockMoveAxisZLocked translation on the z axis.
LinearDampingLinear Damping controls how much the physics body or constraint resists translation.
AngularDampingAngular Damping controls how much they resist rotating.
SlopAdditional slop value, which will be added to the computed penetration depth on collision.
AllowSubCollidersIf a object hierarchy is provided as control, it will try to add the same kind of collider for all sub-elements. Caution: This may produce unexpected behaviour especially on dynamic colliders.
AllowSleepAllows a body to go in sleeping mode and save resources. By default TRUE.
ActiveIs body enabled for physics computation.
DataSetup colliderdata: friction, restitution, density, sensor …
Data.FrictionFriction is a force between two surfaces that are sliding, or trying to slide, across each other. For example, when you try to push a book along the floor, friction makes this difficult. Friction always works in the direction opposite to the direction in which the object is moving, or trying to move.
Data.RestitutionThe coefficient of restitution is a measure of how much kinetic energy remains after the collision of two bodies. Its value ranges from 0 to 1.
Data.DensityIt is the mass of a material substance per unit volume.
Data.SensorA sensor is a device that detects and responds to some type of input from the physical environment.

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.

In the latest version (v0.8.3+) it became much easier to activate a separated thread, simply by designtime property or by code like this:

uses
  Gorilla.Physics.Q3.Scene;
 
[...]
begin
  // activate separated thread
  GorillaPhysicsSystem1.Async := true;
  // reactivate physics computation
  GorillaPhysicsSystem1.Active := true;
  [...]

But you're still able to setup a thread yourself, in case you need that. To do so, take a look at the following code:

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 various callback events to interact with the physics world.

Event Type Description
OnBeginContactThis event will be thrown when two bodies beginning to collide.TOnGorillaPhysicsNotifyContact
OnEndContactThis event will be thrown when two bodies ended their collision procedure.TOnGorillaPhysicsNotifyContact
OnBeginStepIndicates the start of a computation cycle. The event will not be synchronized. So if you assigned a computation thread, you'll need to take care of synchronization with main thread, if accessing visual elements.TNotifyEvent
OnEndStepIndicates the end of a computation cycle. The event will not be synchronized. So if you assigned a computation thread, you'll need to take care of synchronization with main thread, if accessing visual elements.TNotifyEvent
OnBeginUpdateIndicates the start of bodies update. The event will not be synchronized. So if you assigned a computation thread, you'll need to take care of synchronization with main thread, if accessing visual elements.TNotifyEvent
OnEndUpdateIndicates the end of bodies update. The event will not be synchronized. So if you assigned a computation thread, you'll need to take care of synchronization with main thread, if accessing visual elements.TNotifyEvent

OnBeginContact/OnEndContact callbacks will give you both colliding objects as TQ3Body type (Gorilla.Physics.Q3.Body) and the current contact information in a PQ3ContactConstraint structure (Gorilla.Physics.Q3.Contact). 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 AContact : PQ3ContactConstraint; 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 from the outside.

Therefore we've implemented some helper methods.

  • RemoteMeshTransform
  • RemoteBodyTransform
  • RemoteBodyImpulse
  • RemoteBodyForce
  • RemoteBodyVelocity
  • RemoteBodyAngularVelocity
// 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, modify linear/angular velocity 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 or may lead to unexpected behaviour.

WARNING: Modifying objects from the visual scene can have unexpected effects on collision detection. You may place or push an object inside another, which disables collision detection or causes unexpected behaviour!

Example

Runtime Integration

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);
begin
{$IFDEF OLDCODE}
  GorillaPhysicsSystem1.Step(GorillaPhysicsSystem1.GetDeltaTime());
{$ELSE}
  GorillaPhysicsSystem1.Step();
{$ENDIF}
 
  // update visual components rendering after physics have moved/rotated those
  GorillaViewport1.Invalidate();
end;

Override Collider-Registration

Default collider registration methods only support TControl3D objects. But it is sometimes needed to register other types of objects for colliders, like a virtual instance of a mesh.

Writing a helper class gives you access to the protected internal registration methods which are unspecified by type:

Method
procedure DoAddBoxCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ASize : TPoint3D; out ABody : TQ3Body); virtual;
procedure DoAddSphereCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; out ABody : TQ3Body); virtual;
procedure DoAddCapsuleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; const AHeight : Single; out ABody : TQ3Body); virtual;
procedure DoAddTerrainCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AMeshMatrix : TMatrix3D; const ASize : TPoint3D; const AMeshData : TMeshData; out ABody : TQ3Body); virtual;
procedure DoAddMeshCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AMeshMatrix : TMatrix3D; const ASize : TPoint3D; const AMeshData : TMeshData; out ABody : TQ3Body); virtual;
type
  /// <summary>
  /// Create a helper class to access protected methods for adding colliders.
  /// Default methods only handle TControl3D instances, but we
  /// want to add colliders dynamically for our virtual instances (TGorillaMeshInstance).
  /// </summary>
  TGorillaPhysicsHelper = class helper for TGorillaPhysicsSystem
    public
      procedure AddInstanceCollider(const ATemplate : TMeshDef;
        const AData: TGorillaMeshInstance; const APrefab: TGorillaColliderSettings;
        const AAbsoluteMatrix: TMatrix3D; const ASize: TPoint3D; const AFlipData : Boolean;
        out ABody: TQ3Body);
  end;
 
{ TGorillaPhysicsHelper }
 
procedure TGorillaPhysicsHelper.AddInstanceCollider(const ATemplate : TMeshDef;
  const AData: TGorillaMeshInstance; const APrefab: TGorillaColliderSettings;
  const AAbsoluteMatrix: TMatrix3D; const ASize: TPoint3D; const AFlipData : Boolean;
  out ABody: TQ3Body);
var LTransform : TMatrix3D;
begin
  // Get the translation from absolute instance matrix
  LTransform := TMatrix3D.CreateTranslation(
    TTransformationMatrixUtils.GetTranslationFromTransformationMatrix(AAbsoluteMatrix)
    );
 
  // Add a sphere collider for each instance with the maximum size of a side as radius
  // or use another internal registration method, like DoAddBoxCollider, DoAddCapsuleCollider, ...
  DoAddSphereCollider(AData, TypeInfo(TGorillaMeshInstance), APrefab,
    LTransform, Max(ASize.X, Max(ASize.Y, ASize.Z)), ABody);
end;

When you have declared this kind of helper class in your unit, the TGorillaPhysicsSystem component will then show you a new method to be callable “AddInstanceCollider”.

Render Colliders

You can render physics colliders for debugging purposes.

The most easy way is to insert a TDummy component into your TGorillaViewport and leave all transformation (position, rotation and scaling) zero, to not affect collider rendering. The add a “OnRender” event with the following code.

uses 
  Gorilla.Physics.Q3.Renderer;
 
const
{$IFDEF DEBUG}
  SHOW_PHYSICS_COLLIDERS = true;
{$ELSE}
  SHOW_PHYSICS_COLLIDERS = false; 
{$ENDIF}
 
procedure TForm1.Dummy1Render(Sender: TObject; Context: TContext3D);
var LRender : TQ3Render;
begin
  // Check if we want to see physics colliders
  if not SHOW_PHYSICS_COLLIDERS then
    Exit;
 
  // Here we render physics colliders for debugging
  LRender.Context := Context;
  GorillaPhysicsSystem1.Engine.Render(@LRender);
end;

Next step: FMOD Audio