Jolt Physics 5.2.0: A Beginner’s Guide to a Multithreaded Rigid Body Physics Engine
Jolt Physics is a multithreaded rigid body physics and collision detection library designed specifically for games and VR applications, written in C++. It has emerged in modern game development due to its excellent performance and stability. This article will introduce the core features of Jolt Physics 5.2.0 and demonstrate how to integrate and use this powerful physics engine in your projects through practical code examples.
Core Features of Jolt Physics 5.2.0
As a high-performance physics engine, Jolt Physics 5.2.0 has the following notable features:
- Multithreaded Friendly Design: Its unique architecture allows loading, updating, and deleting physics bodies on different threads, as well as performing collision queries in parallel without affecting the main thread’s operation.
- Deterministic Simulation: Supports replicable physics simulations, allowing remote clients to reproduce the same physical effects by replicating inputs.
- Rich Shape and Constraint Support: Provides various basic shapes such as spheres, boxes, and capsules, as well as multiple constraint types like fixed, point, distance, and hinge.
- Cross-Platform Support: Supports multiple platforms including Windows, Linux, Android, macOS, iOS, and WebAssembly.
- Low Dependency: Only depends on the standard template library, without using RTTI and exception handling.
Integrating Jolt Physics 5.2.0
Environment Configuration and Compilation
First, obtain the Jolt Physics source code from GitHub and configure the project using CMake:
# Clone the repository
git clone https://github.com/jrouwe/JoltPhysics.git
cd JoltPhysics
# Generate build files using CMake
cmake -B Build -DCMAKE_BUILD_TYPE=Release -DCROSS_PLATFORM_DETERMINISTIC=ON
Ensure to enable the CROSS_PLATFORM_DETERMINISTIC option, which is crucial for ensuring physical consistency between Windows and Linux platforms.
Basic Initialization Code
The following is the basic code to initialize the Jolt Physics engine:
#include <Jolt/Jolt.h>
#include <Jolt/RegisterTypes.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Core/TempAllocator.h>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Physics/PhysicsSettings.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
using namespace JPH;
// Initialize the Jolt physics system
void InitializeJolt() {
// Register allocation hooks
RegisterDefaultAllocator();
// Create factory
Factory::sInstance = new Factory();
// Register all types
RegisterTypes();
// Create physics system
const uint cMaxBodies = 1024;
const uint cNumBodyMutexes = 0;
const uint cMaxBodyPairs = 1024;
const uint cMaxContactConstraints = 1024;
PhysicsSystem physics_system;
physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs,
cMaxContactConstraints);
}
Creating a Physics World and Rigid Bodies
Setting Up the Physics System
// Create task system and temporary allocator
TempAllocatorImpl temp_allocator(10 * 1024 * 1024);
JobSystemThreadPool job_system(cMaxPhysicsJobs, cMaxPhysicsBarriers,
thread::hardware_concurrency() - 1);
// Initialize the physics system
physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs,
cMaxContactConstraints, broad_phase_layer_interface,
object_vs_broad_phase_layer_filter,
object_vs_object_layer_filter);
Creating Rigid Bodies
// Create ground box
BodyInterface& body_interface = physics_system.GetBodyInterface();
// Create ground shape
BoxShapeSettings ground_shape_settings(Vec3(100.0f, 1.0f, 100.0f));
ShapeRefC ground_shape = ground_shape_settings.Create().Get();
// Create ground rigid body
BodyCreationSettings ground_settings(ground_shape, RVec3(0.0f, -1.0f, 0.0f),
Quat::sIdentity(), EMotionType::Static,
Layers::NON_MOVING);
Body* ground_body = body_interface.CreateBody(ground_settings);
body_interface.AddBody(ground_body->GetID(), EActivation::DontActivate);
// Create dynamic sphere
SphereShapeSettings sphere_shape_settings(1.0f);
ShapeRefC sphere_shape = sphere_shape_settings.Create().Get();
// Create sphere rigid body
BodyCreationSettings sphere_settings(sphere_shape, RVec3(0.0f, 10.0f, 0.0f),
Quat::sIdentity(), EMotionType::Dynamic,
Layers::MOVING);
BodyID sphere_id = body_interface.CreateAndAddBody(sphere_settings,
EActivation::Activate);
Physics Simulation Loop
Implement the update loop for the physics system:
void UpdatePhysics(PhysicsSystem& physics_system, float delta_time) {
// Optimize the physics system before each simulation step
physics_system.OptimizeBroadPhase();
// Execute physics simulation step
physics_system.Update(delta_time, 1, &temp_allocator, &job_system);
// Get all rigid body states
BodyInterface& body_interface = physics_system.GetBodyInterface();
BodyIDVector body_ids = physics_system.GetBodies();
for (BodyID id : body_ids) {
if (body_interface.IsAdded(id) && body_interface.IsActive(id)) {
// Get rigid body position and rotation
RVec3 position = body_interface.GetCenterOfMassPosition(id);
Quat rotation = body_interface.GetRotation(id);
// Update game object transformation
UpdateGameObject(id, position, rotation);
}
}
}
Collision Detection and Response
Jolt Physics provides a powerful collision detection system:
Collision Listener
class MyContactListener : public ContactListener {
public:
// Contact point addition callback
virtual ValidateResult OnContactValidate(const Body& inBody1,
const Body& inBody2,
RVec3Arg inBaseOffset,
const CollideShapeResult& inCollisionResult) override {
// Validate if the collision is valid
return ValidateResult::AcceptAllContacts;
}
// Contact point addition callback
virtual void OnContactAdded(const Body& inBody1,
const Body& inBody2,
const ContactManifold& inManifold,
ContactSettings& ioSettings) override {
// Handle collision start
printf("Collision started between body %u and %u\n",
inBody1.GetID().GetIndex(), inBody2.GetID().GetIndex());
}
// Contact point persistence callback
virtual void OnContactPersisted(const Body& inBody1,
const Body& inBody2,
const ContactManifold& inManifold,
ContactSettings& ioSettings) override {
// Handle ongoing collision
}
// Contact point removal callback
virtual void OnContactRemoved(const SubShapeIDPair& inSubShapePair) override {
// Handle collision end
printf("Collision ended\n");
}
};
// Register collision listener
MyContactListener contact_listener;
physics_system.SetContactListener(&contact_listener);
Ray Casting
// Perform ray casting
void PerformRayCast(PhysicsSystem& physics_system, RVec3Arg inOrigin,
Vec3Arg inDirection) {
// Create ray casting settings
RRayCast ray{inOrigin, inDirection};
RayCastSettings settings;
// Execute detection
RayCastResult result;
if (physics_system.GetNarrowPhaseQuery().CastRay(ray, settings, result)) {
// Handle ray hit
BodyID body_id = result.mBodyID;
printf("Ray hit body %u at distance %f\n",
body_id.GetIndex(), result.mFraction);
// Get hit point details
BodyLockRead lock(physics_system.GetBodyLockInterface(), body_id);
if (lock.Succeeded()) {
const Body& body = lock.GetBody();
// Handle hit information
}
}
}
Character Controller Implementation
Jolt Physics provides a dedicated character controller solution:
CharacterVirtual Controller
#include <Jolt/Physics/Character/CharacterVirtual.h>
// Create character controller
Ref<CharacterVirtual> CreateCharacter(PhysicsSystem& physics_system,
RVec3Arg inPosition) {
// Create capsule shape
const float character_height = 2.0f;
const float character_radius = 0.5f;
RefConst<Shape> character_shape = new CapsuleShape(
character_height * 0.5f - character_radius, character_radius);
// Create character settings
CharacterVirtualSettings settings;
settings.mShape = character_shape;
settings.mMaxSlopeAngle = DegreesToRadians(45.0f);
settings.mMass = 80.0f;
settings.mCharacterPadding = 0.02f;
// Create character instance
Ref<CharacterVirtual> character = new CharacterVirtual(
&settings, inPosition, Quat::sIdentity(), 0, &physics_system);
return character;
}
// Update character controller
void UpdateCharacter(CharacterVirtual& character, float delta_time,
Vec3Arg inMovementDirection) {
// Set character velocity
character.SetLinearVelocity(inMovementDirection * 5.0f);
// Apply gravity
Vec3 gravity = Vec3(0, -9.8f, 0);
// Update character
CharacterVirtual::ExtendedUpdateSettings update_settings;
character.ExtendedUpdate(delta_time, gravity, update_settings,
physics_system.GetDefaultBroadPhaseLayerFilter(),
physics_system.GetDefaultLayerFilter());
// Get character state
CharacterVirtual::EGroundState ground_state = character.GetGroundState();
switch (ground_state) {
case CharacterVirtual::EGroundState::OnGround:
// Handle ground state
break;
case CharacterVirtual::EGroundState::InAir:
// Handle air state
break;
case CharacterVirtual::EGroundState::OnSteepGround:
// Handle steep ground state
break;
}
}
Multithreading Optimization
Jolt Physics 5.2.0 achieves efficient multithreading through island splitting techniques:
Large-Scale Rigid Body Simulation Optimization
// Configure physics system to support large-scale simulation
PhysicsSettings settings;
settings.mUseLargeIslandSplitter = true; // Enable large island splitting
settings.mLargeIslandTreshold = 128; // Split threshold
settings.mMaxInFlightBodyPairs = 8192; // Increase in-flight body pair cache
// Configure task system to utilize multicore CPUs
JobSystemThreadPool::InitSettings job_settings;
job_settings.mMaxJobs = 1024;
job_settings.mMaxBarriers = 8;
job_settings.mNumThreads = thread::hardware_concurrency();
// Initialize physics system with multithreading support
physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs,
cMaxContactConstraints, broad_phase_layer_interface,
object_vs_broad_phase_layer_filter,
object_vs_object_layer_filter, settings);
Cross-Platform Consistency Assurance
To ensure consistent physical behavior between Windows and Linux platforms, special attention must be paid to compilation configurations:
# Enable cross-platform deterministic mode in CMake
if(CROSS_PLATFORM_DETERMINISTIC)
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise /fp:contract-")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffp-model=precise -ffp-contract=off")
endif()
endif()
Practical Application Example
Simple Physics Scene
The following code demonstrates the creation of a complete simple physics scene:
void CreateSimpleScene(PhysicsSystem& physics_system) {
BodyInterface& body_interface = physics_system.GetBodyInterface();
// Create static ground
BoxShapeSettings ground_shape_settings(Vec3(50.0f, 1.0f, 50.0f));
ShapeRefC ground_shape = ground_shape_settings.Create().Get();
BodyCreationSettings ground_settings(ground_shape, RVec3(0.0f, -2.0f, 0.0f),
Quat::sIdentity(), EMotionType::Static,
Layers::NON_MOVING);
body_interface.CreateAndAddBody(ground_settings, EActivation::DontActivate);
// Create dynamic cube mesh
BoxShapeSettings box_shape_settings(Vec3(1.0f, 1.0f, 1.0f));
box_shape_settings.SetDensity(1000.0f); // Set density
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
BodyCreationSettings box_settings(box_shape_settings.Create().Get(),
RVec3(i * 2.5f, 5.0f + j * 2.5f, 0.0f),
Quat::sIdentity(), EMotionType::Dynamic,
Layers::MOVING);
body_interface.CreateAndAddBody(box_settings, EActivation::Activate);
}
}
}
Conclusion
Jolt Physics 5.2.0, as a modern physics engine, provides powerful physics simulation capabilities for games and VR applications through its multithreaded friendly architecture, rich feature set, and excellent performance. This article introduced the basic integration methods, creation of the physics world, collision detection, character controllers, and other core functionalities, along with practical code examples.