Jolt Physics 5.2.0: A Beginner’s Guide to a Multithreaded Rigid Body Physics Engine

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.

Leave a Comment