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 Rigidbody 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 Rigidbody on the other participant. |
TQ3BodyType.eDynamicBody | Dynamic bodies have a Rigidbody component attached to them and their eKinematic flag is not set. These objects move at the whims of physics according to their velocities/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.eKinematicBody | Kinematic bodies have a Rigidbody 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 rigidbodies, 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:
- 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.
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.
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 |
---|---|---|
OnBeginContact | This event will be thrown when two bodies beginning to collide. | TOnGorillaPhysicsNotifyContact |
OnEndContact | This event will be thrown when two bodies ended their collision procedure. | TOnGorillaPhysicsNotifyContact |
OnBeginStep | Indicates 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 |
OnEndStep | Indicates 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 |
OnBeginUpdate | Indicates 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 |
OnEndUpdate | Indicates 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
// 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 or 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); begin {$IFDEF OLDCODE} GorillaPhysicsSystem1.Step(GorillaPhysicsSystem1.GetDeltaTime()); {$ELSE} GorillaPhysicsSystem1.Step(); {$ENDIF} // update visual components rendering after physics have moved/rotated those GorillaViewport1.Invalidate(); end;
Next step: FMOD Audio