How to add Jolt JNI to an existing project
Adding Jolt JNI to an existing project is an 10-step process:
-
Add libraries to the classpath.
-
Load the native library.
-
Register memory allocators and install callbacks.
-
Create a factory and register the standard types.
-
Create a temporary allocator and a job system.
-
Define 2 kinds of collision layers (object and broadphase), the mapping from object layers to broadphase layers, and the collision filters for layers.
-
Create and configure a physics system.
-
Create and configure physics objects and add them to the system.
-
Simulate the system.
-
Test and tune as necessary.
Add libraries to classpath
Jolt JNI requires both a JVM library and a native library. Both kinds of libraries are available pre-built from Maven Central.
Build types: use "Debug" native libraries for development and troubleshooting, then switch to "Release" libraries for performance testing and production.
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 a 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: "1.0.0",
classifier: "SpDebug",
ext: "aar"
)
}
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:1.0.0")
// native libraries:
runtimeOnly("com.github.stephengold:jolt-jni-Linux64:1.0.0: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".
Load the native library
You must load Jolt JNI’s native library before instantiating any physics objects.
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.1")
}
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 |
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 moving and non-moving bodies into separate layers and specify that non-moving bodies collide only with moving bodies, not with other non-moving bodies.
Each body belongs to an object layer, defaulting to layer 0. You can define up to 65_536 object layers.
Collision layers can be defined both for objects and broadphase:
-
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 moving and non-moving bodies into separate object layers and mapping both object layers to broadphase layer 0.
int numObjLayers = 2;
int numBpLayers = 1;
ObjectLayerPairFilterTable ovoFilter
= new ObjectLayerPairFilterTable(numObjLayers);
// Enable collisions between 2 moving bodies:
ovoFilter.enableCollision(objLayerMoving, objLayerMoving);
// Enable collisions between a moving body and a non-moving one:
ovoFilter.enableCollision(objLayerMoving, objLayerNonMoving);
// Disable collisions between 2 non-moving bodies:
ovoFilter.disableCollision(objLayerNonMoving, objLayerNonMoving);
// Map both object layers to broadphase layer 0:
BroadPhaseLayerInterfaceTable layerMap
= new BroadPhaseLayerInterfaceTable(numObjLayers, numBpLayers);
layerMap.mapObjectToBroadPhaseLayer(objLayerMoving, 0);
layerMap.mapObjectToBroadPhaseLayer(objLayerNonMoving, 0);
// Pre-compute the rules for colliding object layers with broadphase layers:
ObjectVsBroadPhaseLayerFilter ovbFilter
= new ObjectVsBroadPhaseLayerFilterTable(
layerMap, numBpLayers, ovoFilter, numObjLayers);
For more advice regarding layers, see the collision-detection section of the Jolt-Physics documentation.
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,
layerMap, ovbFilter, ovoFilter);
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);
Bodies 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 app (no graphics) that serves as a starting point for using Jolt JNI.
It illustrates:
-
loading a native library
-
creating a
PhysicsSystem
-
creating 2 bodies and adding them to the system
-
simulating 50 steps
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:
|