How to add Jolt JNI to an existing project

Adding Jolt JNI to an existing Java project is an 10-step process:

  1. Add libraries to the classpath.

  2. Load the native library.

  3. Register memory allocators and install callbacks.

  4. Create a factory and register the standard types.

  5. Create a temporary allocator and a job system.

  6. Define 2 kinds of collision layers (object and broadphase), the mapping from object layers to broadphase layers, and the collision filters for layers.

  7. Create and configure a physics system.

  8. Create and configure physics objects and add them to the system.

  9. Simulate the system.

  10. Test and tune as necessary.

Add libraries to classpath

Jolt JNI requires both a JVM library and a native library.

Pre-built Jolt JNI libraries are available from from Maven Central.

Build types: use "Debug" native libraries for development and troubleshooting, then switch to "Release" for performance testing and production.

Build flavors: use "Dp" native libraries to simulate large worlds (>1000 meters in diameter) otherwise use "Sp".

Gradle-built Android projects

Jolt JNI comes pre-built for Android as a pair of AARs (one for each build type). Each AAR includes both the JVM library and all necessary native libraries.

I suggest starting out with the Debug-type AAR. Add to the relevant "build.gradle" or "build.gradle.kts" file:

repositories {
    mavenCentral()
}
dependencies {
    implementation(
            group: "com.github.stephengold",
            name: "jolt-jni-Android",
            version: "0.9.9",
            classifier: "SpDebug",
            ext: "aar"
    )
}

For some older versions of Gradle, it’s necessary to replace implementation with compile.

Gradle-built desktop projects

Jolt JNI comes pre-built for desktops as a platform-independent JVM library plus a set of (separately-packaged) native libraries.

Because of how releases are built, the desktop JVM library is released under 8 distinct names (artifact IDs). In contrast, each desktop native library is specific to a particular platform, build type, and flavor.

I suggest starting with a JVM library plus the "DebugSp" native library for your development environment. For a "Linux on x86_64" environment, add to the relevant "build.gradle" or "build.gradle.kts" file:

repositories {
    mavenCentral()
}
dependencies {
    // JVM library:
    implementation("com.github.stephengold:jolt-jni-Windows64:0.9.9")

    // native libraries:
    runtimeOnly("com.github.stephengold:jolt-jni-Linux64:0.9.9:DebugSp")
        // Native libraries for other platforms also could be added.
}
  • For a 64-bit MS-Windows environment, replace "Linux64" with "Windows64".

  • For an "Apple silicon" MacOS environment, replace "Linux64" with "MacOSX_ARM64".

For some older versions of Gradle, it’s necessary to replace implementation with compile.

Load the native library

You must load Jolt JNI’s native library before instantiating any physics objects.

Android projects

Add to your physics initialization:

System.loadLibrary("joltjni");

Desktop projects

The JSnapLoader library may be used for this purpose. Add to the relevant "build.gradle" or "build.gradle.kts" file:

dependencies {
    implementation("io.github.electrostat-lab:snaploader:1.1.1-stable")
    runtimeOnly("com.github.oshi:oshi-core:6.8.0")
}

Add to your physics initialization:

import electrostatic4j.snaploader.LibraryInfo;
import electrostatic4j.snaploader.LoadingCriterion;
import electrostatic4j.snaploader.NativeBinaryLoader;
import electrostatic4j.snaploader.filesystem.DirectoryPath;
import electrostatic4j.snaploader.platform.NativeDynamicLibrary;
import electrostatic4j.snaploader.platform.util.PlatformPredicate;

// ...

LibraryInfo info = new LibraryInfo(null, "joltjni", DirectoryPath.USER_DIR);
NativeBinaryLoader loader = new NativeBinaryLoader(info);

NativeDynamicLibrary[] libraries = {
        new NativeDynamicLibrary("linux/aarch64/com/github/stephengold", PlatformPredicate.LINUX_ARM_64),
        new NativeDynamicLibrary("linux/armhf/com/github/stephengold", PlatformPredicate.LINUX_ARM_32),
        new NativeDynamicLibrary("linux/x86-64/com/github/stephengold", PlatformPredicate.LINUX_X86_64),
        new NativeDynamicLibrary("osx/aarch64/com/github/stephengold", PlatformPredicate.MACOS_ARM_64),
        new NativeDynamicLibrary("osx/x86-64/com/github/stephengold", PlatformPredicate.MACOS_X86_64),
        new NativeDynamicLibrary("windows/x86-64/com/github/stephengold", PlatformPredicate.WIN_X86_64)
};
loader.registerNativeLibraries(libraries).initPlatformLibrary();
loader.loadLibrary(LoadingCriterion.CLEAN_EXTRACTION);

Memory allocators and callbacks

To avoid the need to explicitly free Jolt-Physics objects, I recommend starting Jolt JNI’s built-in cleaner thread, which frees objects automatically during JVM garbage collection.

Jolt Physics allows custom allocators for native heap memory, but Jolt JNI exposes only the default allocator.

import com.github.stephengold.joltjni.Jolt;
import com.github.stephengold.joltjni.JoltPhysicsObject;

// ...

JoltPhysicsObject.startCleaner(); // to free Jolt-Physics objects automatically
Jolt.registerDefaultAllocator(); // tell Jolt Physics to use malloc/free

Similarly, Jolt Physics allows custom callbacks for native assertions and trace output, but Jolt JNI exposes only the default callbacks, which print to cout, the standard output.

Jolt.installDefaultAssertCallback();
Jolt.installDefaultTraceCallback();

In Release-type native libraries, assertions are disabled, so installDefaultAssertCallback() is a no-op.

Create factory and register types

Jolt Physics uses a factory object to create instances of classes based on their name or hash, for instance when deserializing saved data.

Once the factory is created, you should register all the standard types and install their collision handlers.

boolean success = Jolt.newFactory();
assert success;
Jolt.registerTypes();

Temporary allocator and job system

Jolt Physics needs a TempAllocator to allocate temporary memory during simulation. Since it’s difficult to predict how much temporary memory will be needed, I recommend using TempAllocatorMalloc, the most flexible implementation.

import com.github.stephengold.joltjni.TempAllocator;
import com.github.stephengold.joltjni.TempAllocatorMalloc;

// ...

TempAllocator tempAllocator = new TempAllocatorMalloc();

Similarly, Jolt Physics needs a JobSystem to assign simulation tasks to CPUs. Since it’s difficult to predict how many jobs and barriers will be needed, I suggest using the default limits, even though they may be larger than needed. For now, I assume you’ll want to use all available CPUs.

import com.github.stephengold.joltjni.JobSystem;
import com.github.stephengold.joltjni.JobSystemThreadPool;

// ...

int numWorkerThreads = Runtime.getRuntime().availableProcessors();
JobSystem jobSystem = new JobSystemThreadPool(
        Jolt.cMaxPhysicsJobs, Jolt.cMaxPhysicsBarriers, numWorkerThreads);

Collision layers

Jolt Physics allows you to organize bodies into collision layers and specify that some layers never collide. For instance, it’s typical to put all non-moving bodies into a separate layer and specify that they collide only with moving objects.

Collision layers can be defined differently for each phase of collision detection:

  • broadphase layers are used during broad-phase detection, and

  • object layers are used during narrow-phase detection.

The sole limitation is that there must be a consistent mapping from object layers to broadphase layers.

For now, I suggest putting all bodies in layer 0 during both broadphase and narrowphase.

int numBpLayers = 1;
int numObjLayers = 1;

// Define the mapping from object layers to broadphase layers:
BroadPhaseLayerInterface mapObj2Bp
        = new MapObj2Bp(numObjLayers, numBpLayers).add(0, 0);

// Disable collision filtering between object layers and broadphase layers:
ObjectVsBroadPhaseLayerFilter objVsBpFilter
        = new ObjVsBpFilter(numObjLayers, numBpLayers);

// Disable collision filtering between object layers:
ObjectLayerPairFilter objVsObjFilter
        = new ObjVsObjFilter(numObjLayers);

Create a physics system

As soon as you create a PhysicsSystem, you should configure it to the expected number of number of bodies, mutexes, body pairs, and contacts. In many cases, it’s difficult to predict how many resources will be needed, so I suggest setting these limits fairly high.

PhysicsSystem system = new PhysicsSystem();

int maxBodies = 5_000;
int numBodyMutexes = 0; // 0 means "use the default number"
int maxBodyPairs = 65_536;
int maxContacts = 20_480;
system.init(maxBodies, numBodyMutexes, maxBodyPairs, maxContacts,
       mapObj2Bp, objVsBpFilter, objVsObjFilter);

Add physics objects

Physics objects include:

  • bodies (Body)

    • soft bodies

    • rigid bodies

  • constraints (Constraint)

    • vehicles (VehicleConstraint)

  • characters (CharacterBase)

Body creation starts with a BodyCreationSettings object, which can be reused. Here’s a code fragment that creates a spherical rigid body:

// Create a collision shape:
float ballRadius = 1f;
ConstShape ballShape = new SphereShape(ballRadius);

// Create and configure body-creation settings:
BodyCreationSettings bcs = new BodyCreationSettings();
bcs.setShape(ballShape);

// Create a rigid body for a specific PhysicsSystem:
BodyInterface bi = physicsSystem.getBodyInterface();
Body ball = bi.createBody(bcs);

Collision objects aren’t simulated unless they’re added to a physics system. The best way is to use BodyInterface.addBody():

bi.addBody(ball, EActivation.Activate);

Simulate the system

To simulate a single 20-millisecond step:

float timePerStep = 0.02f; // in seconds
int numCollisionSteps = 1;
physicsSystem.update(
        timePerStep, numCollisionSteps, tempAllocator, jobSystem);

HelloJoltJni

HelloJoltJni is a complete console application (no graphics) that serves as a starting point for using Jolt JNI.

It illustrates:

  1. loading a native library

  2. creating a PhysicsSystem

  3. creating 2 bodies and adding them to the system

  4. simulating 50 steps

To lay the groundwork for future tutorials, HelloJoltJni defines 2 object layers: one for moving bodies and one for non-moving bodies.

In HelloJoltJni, we know there will only be 2 bodies; the PhysicsSystem limits are set accordingly.

HelloJoltJni is the first in a series of tutorial apps designed for hands-on learning. I expect you to not only study the source code, but to actually run the app as well. Take time now to set up a software development environment for this purpose!

For instance, if you install Git and a Java Development Kit, you should be able to launch tutorial apps from a command shell, like so:

  1. git clone https://github.com/stephengold/jolt-jni-docs.git

  2. cd jolt-jni-docs

  3. ./gradlew :java-apps:HelloJoltJni

Summary

  • Two libraries are required: a JVM library and a native library.

  • Physics objects aren’t simulated unless they’re added to a system.