Physics Engine
A physics engine simulates the behavior of physical objects in a virtual environment. This includes aspects like gravity, collisions, friction, and rigid body dynamics. It's the backbone for realistic interactions within a 3D world. It allows to connect bodies by so called constraints / joints to let them interact in a user-specific way.
Gorilla3D offers a flexible approach to physics simulation by allowing access to different physics engines by drivers. This means that developers can choose the engine that best suits their project's needs, whether it's performance or features.
We currently provide drivers for Q3 Physics and JOLT Physics.
- Q3 Physics is an embedded engine without any external library, it has the most common and simple features available.
- JOLT Physics is a mature physics engine accessable by an external library provided by our Gorilla3D package. It has a lot of great features and is proven by many commercial AAA games. But it's currently Windows only.
You can choose between the available engines by simply changing the TGorillaPhysicsSystem.Driver property.
Drivers
By our physics driver architecture you're able to choose the physics engine you like:
- TGorillaPhysicsDriverType.Q3Physics
- TGorillaPhysicsDriverType.JOLTPhysics
- TGorillaPhysicsDriverType.CustomPhysics
By this approach you should work the same with the TGorillaPhysicsSystem component for all physics engines. In most cases you will not access the specific physics driver directly. Instead you will always use TGorillaPhysicsSystem functions and properties, which are forwarded to the configured physics driver.
All drivers should behave the same, but for some properties it's not possible to generalize. Also some settings are only available for a certain driver.
TGorillaPhysicsDriverType.CustomPhysics driver type is reserved for user-specific implementations of a physics engine.
For driver class management the physics system component supports a few functions:
Function | Description |
---|---|
class procedure TGorillaPhysicsSystem.InitializeDefaultDriverClasses(); | Registers the default physics drivers for Q3 Physics and JOLT Physics. |
class procedure TGorillaPhysicsSystem.FinalizeDefaultDriverClasses(); | Deregisters all physics drivers from available list. Will call ClearDriverClasses(). |
class procedure TGorillaPhysicsSystem.RegisterDriverClass(const AEngineName : String; AClass : TGorillaPhysicsDriverClass); | Registers a specific physics driver class with a unique name. |
class procedure TGorillaPhysicsSystem.UnregisterDriverClass(const AEngineName : String); | Deregisters a certain physics driver by its name. |
class procedure TGorillaPhysicsSystem.UnregisterDriverClass(AClass : TGorillaPhysicsDriverClass); | Deregisters a certain physics driver by its class. |
class function TGorillaPhysicsSystem.GetDriverClass(const AEngineName : String) : TGorillaPhysicsDriverClass; | Returns a certain physics driver class by its name. If no driver with this name can be found, the result is NIL. |
class function TGorillaPhysicsSystem.GetDriverClass(const AType : TGorillaPhysicsDriverType) : TGorillaPhysicsDriverClass; | Returns a certain physics driver class by the general driver enumeration type (Q3Physics, JOLTPhysics, CustomPhysics). If no driver of this type can be found, the result is NIL. |
class procedure TGorillaPhysicsSystem.ClearDriverClasses(); | Clears the internal list of registered driver classes. |
If you plan to implement your own physics engine driver, this implementation should be based on the class TGorillaPhysicsDriver. Nearly all declared functions are abstract and need to be overwrited for engine specific handling.
/// <summary> /// Abstract class to write your own physics engine driver. Currently we're /// providing access to Q3 Physics Engine and JOLT Physics. /// </summary> TGorillaPhysicsDriver = class protected FSystem : TGorillaControl; {TGorillaPhysicsSystem} function GetEngine() : TObject; virtual; abstract; function GetListener() : TGorillaPhysicsContactListener; virtual; abstract; public class function GetDriverName() : String; virtual; abstract; class function GetDriverType() : TGorillaPhysicsDriverType; virtual; abstract; public property PhysicsSystem : TGorillaControl read FSystem; property Engine : TObject read GetEngine; property Listener : TGorillaPhysicsContactListener read GetListener; constructor Create(ASystem : TGorillaControl); virtual; destructor Destroy(); override; { Published Engine Specific Methods } procedure DoOnRenderColliders(ASender: TObject; AContext: TContext3D; const AState : TComponentState; const ARenderColliders : Boolean); virtual; abstract; procedure DoSetBounds(const ABounds : TBoundingBox); virtual; abstract; function CreateJointInfo(const APrefab : TGorillaPhysicsJointPrefab) : Pointer; virtual; abstract; procedure DoRegisterJoint(const APrefab : TGorillaPhysicsJointPrefab; ABodyA, ABodyB : TGorillaPhysicsBody); virtual; abstract; procedure DoUnRegisterJoint(const APrefab : TGorillaPhysicsJointPrefab; ABodyA, ABodyB : TGorillaPhysicsBody); virtual; abstract; procedure DoReactivateJointBodiesNTS(ABody : TGorillaPhysicsBody); virtual; abstract; { Published Remote Collider / Body Controls } procedure DoRemoteMeshTransform(const ACtrl : TControl3D); virtual; abstract; procedure DoRemoteBodyTransform(const ABody : TGorillaPhysicsBody; const ATranslation : TPoint3D; const ARotationAngle : TPoint3D); overload; virtual; abstract; procedure DoRemoteBodyTransform(const ABody : TGorillaPhysicsBody; const ATranslation : TPoint3D; const ARotation : TMatrix); overload; virtual; abstract; procedure DoRemoteBodyImpulse(const ABody : TGorillaPhysicsBody; const AImpulse : TPoint3D); virtual; abstract; procedure DoRemoteBodyForce(const ABody : TGorillaPhysicsBody; const AForce : TPoint3D; const ADirect : Boolean = false); virtual; abstract; procedure DoRemoteBodyVelocity(const ABody : TGorillaPhysicsBody; const AVelocity : TPoint3D; const ADirect : Boolean = true); virtual; abstract; procedure DoRemoteBodyAngularVelocity(const ABody : TGorillaPhysicsBody; const AVelocity : TPoint3D); virtual; abstract; procedure DoRemoteBodyAngularImpulse(const ABody : TGorillaPhysicsBody; const AImpulse : TPoint3D; const APt : TPoint3D); virtual; abstract; { Published Engine Execution Control } procedure DoStep(); virtual; abstract; procedure DoOnBodyUpdate(ASender : TObject; const ACollider : TGorillaPhysicsCollider); virtual; abstract; { Published Collider Control } procedure DoRemoveCollider(const ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddBoxCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ASize : TPoint3D; out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddSphereCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddCapsuleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; const AHeight : Single; out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddCylinderCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AHalfHeight : Single; const ARadius : Single; const AConvexRadius : Single; // = 0.05f out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddConeCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AHeight : Single; const ARadius : Single; out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddConvexHullCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const APointCount : UInt32; const APoints : PPoint3D; out ABody : TGorillaPhysicsBody); virtual; abstract; procedure DoAddParticleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const ATransformation : TMatrix3D; const ARadius : Single; out ABody : TGorillaPhysicsBody); virtual; abstract; 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 : TGorillaPhysicsBody); virtual; abstract; 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 : TGorillaPhysicsBody); virtual; abstract; { Published Engine Driver Properties } function GetActive() : Boolean; virtual; abstract; procedure SetActive(const AValue : Boolean); virtual; abstract; function GetAsync() : Boolean; virtual; abstract; procedure SetAsync(const AValue : Boolean); virtual; abstract; procedure Block(); virtual; abstract; procedure Unblock(); virtual; abstract; function GetAccuracy() : Integer; virtual; abstract; procedure SetAccuracy(const AValue : Integer); virtual; abstract; function GetGravity() : TPoint3D; virtual; abstract; procedure SetGravity(const AValue : TPoint3D); virtual; abstract; function GetDeltaTime() : Single; virtual; abstract; { Published Engine Driver Events } function GetOnBeginStep() : TNotifyEvent; virtual; abstract; procedure SetOnBeginStep(const AValue: TNotifyEvent); virtual; abstract; function GetOnEndStep() : TNotifyEvent; virtual; abstract; procedure SetOnEndStep(const AValue: TNotifyEvent); virtual; abstract; function GetOnBeginUpdate() : TNotifyEvent; virtual; abstract; procedure SetOnBeginUpdate(const AValue: TNotifyEvent); virtual; abstract; function GetOnEndUpdate() : TNotifyEvent; virtual; abstract; procedure SetOnEndUpdate(const AValue: TNotifyEvent); virtual; abstract; { Physics Character Controller Methods } function GetCharacterHeight(ACtrl : TControl3D) : Single; virtual; abstract; procedure DoSetupCharacter(const ASetting : TGorillaPhysicsCharacterSetting; const AHeight : Single; const APrefab : TGorillaColliderSettings); virtual; abstract; procedure DoCharacterIdle(ACtrl : TControl3D); virtual; abstract; function DoCharacterMove(const ASetting : TGorillaPhysicsCharacterSetting; const AUpdateTime : Single; var AData : TRayCastReturn) : Boolean; virtual; abstract; procedure UnsetSleepEvent(ACtrl : TControl3D); virtual; abstract; procedure UnsetWakeUpEvent(ACtrl : TControl3D); virtual; abstract; procedure UnsetUpdateEvent(ACtrl : TControl3D); virtual; abstract; procedure BodyToAwake(ACtrl : TControl3D); virtual; abstract; procedure BodyToSleep(ACtrl : TControl3D); virtual; abstract; { Particle System Controller Methods } procedure SetUserSpecificUpdateCallback( const ACallback : TOnPhysicsUserSpecificUpdate); virtual; abstract; procedure SetUserSpecificBodyResolveCallback( const ACallback : TOnPhysicsUserSpecificBodyResolve); virtual; abstract; procedure DoEmitParticle(var ABody : TGorillaPhysicsBody; const AUserData : Pointer; const ATypeInfo : PTypeInfo; const APosition : TPoint3D; const AWeight : Single; const AEmitterTransformation : TMatrix3D); virtual; abstract; procedure DoDestroyParticle(var ABody : TGorillaPhysicsBody); virtual; abstract; end;
Q3 Physics Engine
Q3 Physics is an embedded library without third-party files needed.
It is supported for Windows 32-/64-Bit and Android 32-/64-Bit.
It's thin and fast, but very limited in it's functionality. It was developed by ourselves and support the most common simple shapes: box, sphere, capsule, mesh/terrain and a few joint/constraint types.
Nevertheless it's useful and good enough for many projects.
Because joint/constraint management in Q3 Physics is a bit different, please have a closer look at some further explanations here:
JOLT Physics Engine
JOLT Physics is multi core friendly rigid body physics and collision detection library. Suitable for games and VR applications. Used by Horizon Forbidden West.
The developer Jorrit Rouwe provides it on his public github repository:
https://github.com/jrouwe/JoltPhysics
We decided to expand our set of physics engine by JOLT because it's a very robust, fast and modern framework. It allows to register further types of shapes like cylinder, cone or convex hull and it expands the set of constraints / joints.
Currently we've implemented the basis of colliders and joints to be compatible with Gorilla3D physics system component.
But there is much more work to be done:
- PhysicsCharacterController support needed
- Motor settings not always working correctly
- Breaking events missing
- Vehicle constraint support
- Further constraint configuration
- Layer-Support
- Android Support
NOTICE: Currently JOLT Physics is only working for Windows 32-/ 64-Bit. Android support is still in development.
Library
Because JOLT Physics is integrated by a separated library (DLL) you have to ensure the IDE and your application are able to find it. We've developed a C++ library ourselves, based on the published source code repository. This library is only compatible with Gorilla3D due to individual structures and security validation.
When using the installation tool of Gorilla3D library files will automatically be copied to IDE binary installation directory (“jolt32.dll”).
This will make JOLT Physics available at design time. If this file can not be found by the IDE, f.e. due to manual installation, an exception will appear.
You have to ensure the same for each developed application that “jolt32.dll” and/or “jolt64.dll” are available in your application EXE file path.
At runtime you can modify the search path to a user-specific path by this code:
uses Gorilla.Physics.Driver.JOLT; // Set a user-specific path {$IFDEF CPUX86} TGorillaPhysicsJOLTDriver.LibraryPath := 'jolt\Bin\Windows\x86'; {$ELSE} TGorillaPhysicsJOLTDriver.LibraryPath := 'jolt\Bin\Windows\x64'; {$ENDIF} // Activating JOLT physics afterwards! FPhysics.Driver := TGorillaPhysicsDriverType.JOLTPhysics;
TGorillaPhysicsSystem
It is the main component for physics usage inside of your scene. Theoretically it is possible to use multiple physics system components in one scene, but it's not recommended. Instead of accessing the configured physics system directly for nearly all cases it is recommended to use functions and properties of the TGorillaPhysicsSystem component.
Properties
Synchronous / Asynchronous
Physics drivers support synchronous and asynchronous usage of physics world computation. This means you have to decide how and when you want to update the physics world.
Synchronous
Synchronous computation will be managed within the main-thread or the thread the physics system component was created.
This might seem the more simple approach, because feedback events and computation will happen in the same thread.
But you have to take care yourself to update the physics world. Only when updating or stepping physics computation, collision detection, joint-handling and object movement/rotation will be processed.
If using synchronous computation, the Async property of the TGorillaPhysicsSystem component should be set to FALSE.
This can easily be done by adding a TTimer component to your form and adding an OnTimer event. Inside the event call the Step method on each interval. 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.
procedure TForm1.Timer1Interval(Sender : TObject); begin GorillaPhysicsSystem1.Step(); end;
WARNING: Using this way of processing might lead into blocking and lagging 3D computation. If you have to many objects in your scene, it's better to choose asynchronous computation.
Asynchronous
The asynchronous approach allows to perform physics computation in a separated thread. This will increase performance, but might lead to issues during re-synchronization of movement/rotation or callback events. Even we're caching multiple modifications in a queue to synchronize only once, callback events are called inside the physics thread and need to be synchronized with the main thread in most cases.
This may lead to blocking or lagging 3D rendering.
NOTICE: But for nearly all scenes it is recommended to use asynchronous physics computation.
It is not necessary to set up a timer event for stepping/updating physics. Instead the physics thread will perform updates itself. So simply activate Async property.
procedure TForm1.Form1Show(Sender : TObject); begin GorillaPhysicsSystem1.Async := true; GorillaPhysicsSystem1.Active := true; 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.
Accuracy
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.Accuracy := 20;
NOTICE: In JOLT Physisc accuracy will always be 1.
RenderColliders
Activate physics collider rendering at runtime. Depending on the used physics driver the component will render bounding boxes, collision-points and joint/constraint settings to visualize your physics settings and for debugging physics behaviour.
WARNING: Those drawing methods using default FMX handling which is very slow. So only use this feature for analyzation and debugging purposes, otherwise rendering will get very slow!
Bounds
The physics world should have boundaries, to prevent objects from falling forever.
This is only relevant to Q3 Physics.
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.
TQ3Scene(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.Gravity := Point3D(0, -1, 0);
Max Contacts
A builtin constant is the maximum contact count:
- Q3 Physics is set to 96
- JOLT Physics is set to 1024
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
Physics engines decide between 3 types of body.
Type | Description |
---|---|
TGorillaPhysicsBodyType.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. |
TGorillaPhysicsBodyType.eDynamicBody | Dynamic 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. |
TGorillaPhysicsBodyType.eKinematicBody | Kinematic 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
- Capsule
- Particle
- Mesh
- Terrain
JOLT Physics expands the set of available colliders by the following shapes:
- Cylinder
- Cone
- ConvexHull
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; const ARadius : Single = 0); | 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). The optional ARadius argument will overwrite auto-radius detection if it is not zero. |
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). ARadius and AHeight arguments will overwrite auto-detection if they are not zero. |
procedure AddParticleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const ATransformation : TMatrix3D; const ARadius : Single; out ABody : TGorillaPhysicsBody); | 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 TGorillaPhysicsBody instance as pointer which should be linked with your particle structure (AParticle). This method is automatically used by the physics particle influencer class. Provided AData and AType arguments will be set to resolving the linked particle. |
procedure AddCylinderCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings; const AHalfHeight, ARadius, AConvexRadius : Single); | Add a cylinder 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). Radius, half-height and convex-radius will overwrite auto-detection values if they are not zero. Not supported by Q3 physics engine. |
procedure AddConeCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings; const AHeight, ARadius : Single); | Add a cone 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). Height and radius values will overwrite auto-detection values if they are not zero. Not supported by Q3 physics engine. |
procedure AddConvexHullCollider(const AControl : TControl3D; const APrefab : TGorillaColliderSettings; const APointCount : UInt32; const APoints : PPoint3D); | Add a convex hull 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). The method expects a number of points forming a convex hull shape. Not supported by Q3 physics engine. |
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) |
NOTICE: The created rigid body of the collider will automatically be set in the TControl3D.TagObject property of the 3D control provided to the specific method. When adding a particle collider the rigid body will be return in the ABody argument.
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, by so called 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 |
---|---|
_Type | TGorillaPhysicsBodyType (eStaticBody, eDynamicBody or eKinematicBody) |
LockRotAxisX | Locked rotation on the x axis. |
LockRotAxisY | Locked rotation on the y axis. |
LockRotAxisZ | Locked rotation on the z axis. |
LockMoveAxisX | Locked translation on the x axis. |
LockMoveAxisY | Locked translation on the y axis. |
LockMoveAxisZ | Locked translation on the z axis. |
LinearDamping | Linear Damping controls how much the physics body or constraint resists translation. |
AngularDamping | Angular Damping controls how much they resist rotating. |
Radius | Floating point value to overwrite auto-detection value, if not zero. |
Size | TPoint3D size of the specific collider will overwrite auto-detection values, if not zero. |
MinLinearVelocity | Limits minimum linear velocity of the rigid body collider. |
MaxLinearVelocity | Limits maximum linear velocity of the rigid body collider. |
MinAngularVelocity | Limits minimum angular velocity of the rigid body collider. |
MaxAngularVelocity | Limits maximum angular velocity of the rigid body collider. |
Slop | Additional slop value, which will be added to the computed penetration depth on collision. |
Layers | Set a layer mask of max. 8 layers to group specific bodies for collision detection. Only bodies in the same layer are able to collide with each other. Q3 Physics engine only, yet. |
AllowSubColliders | If 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. |
AllowSleep | Allows a body to go in sleeping mode and save resources. By default TRUE. |
Active | Is body enabled for physics computation. |
Data | Setup colliderdata: friction, restitution, density, sensor … |
Data.Friction | Friction 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.Restitution | The 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.Density | It is the mass of a material substance per unit volume. |
Data.Sensor | A sensor is a device that detects and responds to some type of input from the physical environment. |
The TGorillaPhysicsColliderPrefab offers further properties to predefine a rigid body:
Property | Descr |
---|---|
DisplayName | A user-specific name for this rigid body. |
Control | The linked TControl3D for which the rigid body should be created. |
Kind | TGorillaPhysicsColliderKind defining if it's a static, dynamic or kinematic rigid body. |
BodyType | TGorillaPhysicsBodyType (eStaticBody, eDynamicBody or eKinematicBody). Comparable to the _Type field of the TGorillaColliderSettings struct. |
Friction | Friction 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. |
Restitution | The 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. |
Density | It is the mass of a material substance per unit volume. |
Sensor | A sensor is a device that detects and responds to some type of input from the physical environment. |
You can also register collider specific events:
Event | Descr |
---|---|
OnBeginContact | TOnGorillaPhysicsCollisionEvent getting called when a collision with this rigid body starts. |
OnEndContact | TOnGorillaPhysicsCollisionEvent getting called when a collision with this rigid body ends. |
Auto-Detection of Size, Radius & More
Our physics system supports auto-detection for the size, a radius or a height of a collider, depending on the 3D control provided.
Because auto-detection is not always correct in every case you can overwrite those values.
We're are using the following priority order for value detection:
- Collider registration function parameter, f.e. calling AddSphereCollider(GorillaSphere1, LMyPrefab, 2.5), will take 2.5 as radius, instead of detecting the size of the sphere.
- TGorillaColliderSettings.Radius / TGorillaPhysicsColliderPrefab.Radius will then be preferred, if not zero.
- Finally the automatic detection by the size of the provided 3D control.
This priority order is the same for Size, Radius, Height or HalfHeight values.
Joints / Constraints
Since version 2724 we provide joints/constraints for colliders in Q3 Physics engine. For JOLT Physics they are of course supported too.
It is possible to setup joints at design and at runtime.
At design time go to the TGorillaPhysicsSystem component and open the Joints collection. Here you can add joint prefabs and configure them without coding.
We do support a few standard joint types:
JointType | Description |
---|---|
TGorillaPhysicsJointType.Fixed | Constrains two rigid bodies together, removing their ability to act independent of each other. |
TGorillaPhysicsJointType.BallAndSocket | Allows motion around an indefinite number of axes. Humans have such joints in the hips and shoulders. |
TGorillaPhysicsJointType.Hinge | Allows free rotation on one axis. Can be used for spinning wheels and carousels. |
TGorillaPhysicsJointType.LimitedHinge | Allows limited articulation on one axis. Humans have such joints in the fingers and knees. |
TGorillaPhysicsJointType.Prismatic | Constrains two bodies to a sliding motion on one axis. Can be used to make various sliding doors. |
TGorillaPhysicsJointType.StiffSpring | Constrains two bodies to be a certain distance apart from each other. |
TGorillaPhysicsJointType.Magnet | Constrains two bodies, where one is to be the magnet, while the other body is forced towards it in linear velocity by their difference in mass. |
TGorillaPhysicsJointType.Buoyancy | Constrains two bodies to simulate floating bodies, f.e. on water surface. |
JOLT Physics expands the functionality of constraints:
JointType | Description |
---|---|
TGorillaPhysicsJointType.Point | JOLT specific constraint type: Point |
TGorillaPhysicsJointType.SixDOF | JOLT specific constraint type: SixDOF |
TGorillaPhysicsJointType.SwingTwist | JOLT specific constraint type: SwingTwist |
TGorillaPhysicsJointType.Distance | JOLT specific constraint type: Distance |
TGorillaPhysicsJointType.Cone | JOLT specific constraint type: Cone |
TGorillaPhysicsJointType.RackAndPinion | JOLT specific constraint type: RackAndPinion |
TGorillaPhysicsJointType.Gear | JOLT specific constraint type: Gear |
TGorillaPhysicsJointType.Pulley | JOLT specific constraint type: Pulley |
Read more about joints here Q3 Physics Joints
Settings
Depending on the physics driver used not all properties are used. Also not all properties are used for each type of joint / constraint.
Especially for JOLT Physics we've expanded that list of settings by many new properties to allow the best configuration possible.
Controls
A joint/constraint is always a connection between two rigid bodies.
Each body has to have a registered rigid body, otherwise handling of the joint will be ignored.
Depending on the type of joint it's important which body is ControlA and which is ControlB.
Anchors
Setting up a joint/constraint might be confusing sometimes.
Besides choosing the correct type of joint, it is often necessary to configure its anchor points, fixed points and other settings correctly.
Lately we have modified the interpretation of anchor and fix-point configuration.
It's now, by default, relative to the specific control's position. So the LocalSpace property of the TGorillaPhysicsJointPrefab is set to TRUE.
If setting this value to FALSE, the anchors of the joint will be interpreted as world coordinates!
Axis
Another important setting for a working joint are the controls axis.
The define how and in which direction the control is treated depending on the kind of constraint.
Normal
This is also needed for some types of joints, f.e. the prismatic / slider constraint.
It tells the physics system in which direction the control is orientated perpendicular to it's movement direction.
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 - Synchronization needed! |
OnEndContact | This event will be thrown when two bodies ended their collision procedure. | TOnGorillaPhysicsNotifyContact - Synchronization needed! |
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 |
OnCollidersRegistered | Callback event when all collider prefabs were registered. | TNotifyEvent |
OnCollidersUnregistered | Callback event when all collider prefabs were deregistered. | TNotifyEvent |
OnJointsRegistered | Callback event when all joint prefabs were registered. | TNotifyEvent |
OnJointsUnregistered | Callback event when all joint prefabs were deregistered. | TNotifyEvent |
OnBeginContact/OnEndContact callbacks will give you both colliding objects as TGorillaPhysicsBody type (Gorilla.Physics.Types) and for Q3-Physics the current contact information in a PQ3ContactConstraint structure (Gorilla.Physics.Q3.Contact). For JOLT Physics AContact is always NIL. 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”.
Depending on the physics driver used, the return TGorillaPhysicsBody instance is one of the following types:
- Gorilla.Physics.Q3.Body.TQ3Body
- Gorilla.Physics.Driver.JOLT.TGorillaJOLTPhysicsBody
You then have to type-cast the body object to the driver-specific type, because TGorillaPhysicsBody is just the basis class without any fields.
The same for provided arguments of the type TGorillaPhysicsCollider.
- Gorilla.Physics.Q3.Collider.TQ3Collider
- not available in JOLT Physics, everything is handled by the physics body
procedure TUIMainWin.doOnBeginContact(const AContact : Pointer {PQ3ContactConstraint}; const AOffset, ANormal : TPoint3D; const ABodyA, ABodyB : TGorillaPhysicsBody); begin // Synchronize with the main thread: for Q3 Physics in asynchronous mode and always for JOLT physics! TThread.Synchronize( nil, procedure() begin // If using Q3 Physics Engine, we expect ABodyA and ABodyB to be a TQ3Body class object. if not Assigned(TQ3Body(ABodyA).UserDataType) then Exit; if not Assigned(TQ3Body(ABodyB).UserDataType) then Exit; if TComponent(TQ3Body(ABodyA).UserData).Name.Equals('Cube1') and TComponent(TQ3Body(ABodyB).UserData).Name.Equals('Sphere2') then begin FMX.Types.Log.D('expected collision between %s <=> %s', [TComponent(TQ3Body(ABodyA).UserData).Name, TComponent(TQ3Body(ABodyB).UserData).Name]); end else begin FMX.Types.Log.D('<ERROR> unexpected collision between %s <=> %s', [TQ3Body(ABodyA).UserDataType^.Name, TQ3Body(ABodyB).UserDataType^.Name]); end; end); end;
WARNING: It is recommended to synchronize OnBeginContact and OnEndContact events with the main thread when accessing visual elements, otherwise it might lead to access violations.
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.
For example if you want to shoot, push or pull an object or if you want to rotate it.
Therefore we've implemented some helper methods:
Method | Description |
---|---|
procedure RemoteMeshTransform(const ACtrl : TControl3D); | The method allows to modify position and rotation for a specific TControl3D in the physics system from external. This may be useful when there are external influences the physics system do not know of. |
procedure RemoteBodyTransform(const ABody : TGorillaPhysicsBody; const ATranslation : TPoint3D; const ARotationAngle : TPoint3D); | The method allows to modify position and rotation for a specific TGorillaPhysicsBody in the physics system from external by a translation and rotation value. This may be useful when there are external influences the physics system do not know of. |
procedure RemoteBodyTransform(const ACtrl : TControl3D; const ATranslation : TPoint3D; const ARotationAngle : TPoint3D); | The method allows to modify position and rotation for a specific control in the physics system from external by a translation and rotation value. This may be useful when there are external influences the physics system do not know of. |
procedure RemoteBodyTransform(const ABody : TGorillaPhysicsBody; const ATranslation : TPoint3D; const ARotation : TMatrix); | The method allows to modify position and rotation for a specific TGorillaPhysicsBody in the physics system from external by a translation and rotation value. This may be useful when there are external influences the physics system do not know of. |
procedure RemoteBodyTransform(const ACtrl : TControl3D; const ATranslation : TPoint3D; const ARotation : TMatrix); | The method allows to modify position and rotation for a specific control in the physics system from external by a translation and rotation value. This may be useful when there are external influences the physics system do not know of. |
procedure RemoteBodyImpulse(const ABody : TGorillaPhysicsBody; const AImpulse : TPoint3D); | Applies an impulse on a specific body. Use this method to push a physics controlled body, f.e. for third person models. |
procedure RemoteBodyImpulse(const ACtrl : TControl3D; const AImpulse : TPoint3D); | Applies an impulse on a specific control. Use this method to push a physics controlled body, f.e. for third person models. |
procedure RemoteBodyForce(const ABody : TGorillaPhysicsBody; const AForce : TPoint3D; const ADirect : Boolean = false); | Applies a force on a specific body. Use this method to push a physics controlled body, f.e. for third person models. |
procedure RemoteBodyForce(const ACtrl : TControl3D; const AForce : TPoint3D; const ADirect : Boolean = false); | Applies a force on a specific control. Use this method to push a physics controlled body, f.e. for third person models. |
procedure RemoteBodyVelocity(const ABody : TGorillaPhysicsBody; const AVelocity : TPoint3D; const ADirect : Boolean = true); | Set body velocity directly. Use this method to move the body, f.e. or first/third person controller. |
procedure RemoteBodyVelocity(const ACtrl : TControl3D; const AVelocity : TPoint3D; const ADirect : Boolean = true); | Set control velocity directly. Use this method to move the control, f.e. for first/third person controller. |
procedure RemoteBodyAngularVelocity(const ABody : TGorillaPhysicsBody; const AVelocity : TPoint3D); | Set body angular velocity directly. Use this method to rotate the body, f.e. for first/third person controller. |
procedure RemoteBodyAngularVelocity(const ACtrl : TControl3D; const AVelocity : TPoint3D); | Set control angular velocity directly. Use this method to rotate the control, f.e. for first/third person controller. |
procedure RemoteBodyAngularImpulse(const ABody : TGorillaPhysicsBody; const AImpulse : TPoint3D; const APt : TPoint3D); | Apply body angular impulse. Use this method to rotate the body. |
procedure RemoteBodyAngularImpulse(const ACtrl : TControl3D; const AImpulse : TPoint3D; const APt : TPoint3D); | Apply control angular impulse. Use this method to rotate the control. |
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.
You can simply call those by accessing the TGorillaPhysicsSystem component, like this:
// move cube upwards by impulse GorillaPhysicsSystem1.RemoteBodyImpulse(GorillaCube1, Point3D(0, -1, 0));
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.
In the following code we're updating physics world synchronously. So we have to use a timer event and call the step method ourselves. And because in that case 3D element modifications are event-based, we also have to call the Invalidate() function to update our rendering.
- 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(TGorillaPhysicsBodyType.eStaticBody); GorillaPhysicsSystem1.AddBoxCollider(Plane1, LPrefab); // the cube is a dynamic / moveable body which collides with the plane and sphere LPrefab := TGorillaColliderSettings.Create(TGorillaPhysicsBodyType.eDynamicBody); GorillaPhysicsSystem1.AddBoxCollider(GorillaCube1, LPrefab); // the sphere is a dynamic / moveable body which collides with the plane and cube LPrefab := TGorillaColliderSettings.Create(TGorillaPhysicsBodyType.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 : TGorillaPhysicsBody); virtual; |
procedure DoAddSphereCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; out ABody : TGorillaPhysicsBody); virtual; |
procedure DoAddCapsuleCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const ARadius : Single; const AHeight : Single; out ABody : TGorillaPhysicsBody); virtual; |
procedure DoAddCylinderCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AHalfHeight : Single; const ARadius : Single; const AConvexRadius : Single; out ABody : TGorillaPhysicsBody); virtual; |
procedure DoAddConeCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const AHeight : Single; const ARadius : Single; out ABody : TGorillaPhysicsBody); virtual; |
procedure DoAddConvexHullCollider(const AData : Pointer; const AType : PTypeInfo; const APrefab : TGorillaColliderSettings; const AAbsoluteMatrix : TMatrix3D; const APointCount : UInt32; const APoints : PPoint3D; out ABody : TGorillaPhysicsBody); 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 : TGorillaPhysicsBody); 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 : TGorillaPhysicsBody); 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: TGorillaPhysicsBody); 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: TGorillaPhysicsBody); 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”.
Physics Particles
It's possible to combine physics and particle effects in Q3 Physics and JOLT Physics.
The connection between both worlds is established by a influencer class in the particles module.
Once you've set up a TGorillaParticleEmitter component, you will need to add a TGorillaPhysicsInfluencer to your scene.
Then you simply connect your emitter component and the TGorillaPhysicsSystem component, by the supplied properties of the influencer.
Property | Description |
---|---|
Emitter | TGorillaParticleEmitter component to which the influencer will be attached to. |
Physics | TGorillaPhysicsSystem component which will process particle physics computation. |
The influencer will automatically register / deregister colliders in the physics system. Because particles are created, reused and destroyed dynamically by the emitter, the influencer has to take over this job for you.
The system creates sphere colliders for you. This is the most common and fastest collider especially when having hundreds of particles.
By the ParticleWeight property of the TGorillaParticleEmitter component, you control the collider size of the particles.
Take care, that the minimum size in physics is 0.3 for colliders. For both, Q3 and JOLT physics.
NOTICE: Because Q3 and JOLT Physics are different in performance, we recommend the following limits for the number of particles:
- Q3 Physics: max. 128 particles when using a terrain of mesh, otherwise max. 1024 - 2048 particles.
- JOLT Physics: max. 2048 particles
Next step: FMOD Audio