Java bindings for Erin Catto's Box3D.
Box3D4j generates bindings for the latest Box3D main branch and exposes the complete public C17 API, including worlds, bodies, shapes, joints, collision queries, events, character movement, recording, and replay.
The Java API stays close to the native Box3D API. Function and type names are kept unchanged where possible.
- Bindings for the complete public Box3D C API
- Tracks the latest Box3D
mainbranch - Automatic publishing for new upstream commits
- JavaCPP-based JNI bindings
- Bundled native libraries
- Java 8 runtime compatibility
- Compatible with Forge 1.16.5
- Linux x86-64
- Windows x86-64
- macOS x86-64
- macOS ARM64
Functions are available from:
cc.modlabs.box3d.global.Box3D
Structs, IDs, enums, and callback types are available directly under:
cc.modlabs.box3d
Names intentionally match the native Box3D API.
import cc.modlabs.box3d.b3WorldDef;
import cc.modlabs.box3d.b3WorldId;
import static cc.modlabs.box3d.global.Box3D.*;
b3WorldDef definition = b3DefaultWorldDef();
b3WorldId world = b3CreateWorld(definition);
try {
b3World_Step(world, 1.0f / 60.0f, 4);
} finally {
b3DestroyWorld(world);
}JavaCPP loads the bundled native library automatically when the API is first used.
Published artifacts target Java 8, so they can also be used by older JVM projects such as Forge 1.16.5 mods.
Existing getters remain available. For hot loops that synchronize many bodies, the optional
b3Body_GetTransformsPacked helper reads a batch in one JNI call without allocating native
result structs. It writes seven doubles per body: px, py, pz, qx, qy, qz, qw.
The quaternion uses vector components followed by the scalar component.
import cc.modlabs.box3d.b3BodyId;
import java.nio.DoubleBuffer;
import org.bytedeco.javacpp.DoublePointer;
import static cc.modlabs.box3d.global.Box3D.*;
// Set up once: copy your existing body IDs into a contiguous native array.
try (b3BodyId ids = new b3BodyId(bodyCount);
DoublePointer storage = new DoublePointer(7L * bodyCount)) {
for (int i = 0; i < bodyCount; i++) {
b3BodyId id = bodies[i];
ids.position(i).index1(id.index1()).world0(id.world0()).generation(id.generation());
}
ids.position(0);
DoubleBuffer transforms = storage.asBuffer();
// Repeat after each simulation step; reuse ids, storage, and transforms.
b3Body_GetTransformsPacked(ids, transforms, bodyCount);
double firstBodyX = transforms.get(0);
double firstBodyQuaternionW = transforms.get(6);
}Provide bodyCount valid IDs and space for at least 7 * bodyCount doubles from the
current buffer position. Positions are honored without being advanced. This low-level
native helper does not check buffer bounds or ID validity. Refresh the ID array when
bodies are destroyed or replaced, and do not step or mutate the worlds during the read.
The output is a caller-owned snapshot and remains valid after the next step.
Use a direct buffer or DoublePointer for this path; the generated double[] overload
may copy data. Packing into doubles does not change the native engine's precision mode.
For scenes where relatively few bodies move, the existing b3World_GetBodyMoveEvents
helper exposes the native move-event array without allocating a result struct. Its data
is transient; consume it before modifying or stepping the world again.
To read those events without per-event field access through JNI, use
b3World_CopyBodyMoveEventsPacked. It copies only the current step's move events into
reusable buffers in one JNI call, without allocating native result structs:
// Allocate once; use LongPointer, DoublePointer, and BytePointer with capacities
// eventCapacity, 7L * eventCapacity, and eventCapacity, respectively.
LongBuffer movedIds = idStorage.asBuffer();
DoubleBuffer movedTransforms = transformStorage.asBuffer();
ByteBuffer fellAsleep = sleepStorage.asBuffer();
int required = b3World_CopyBodyMoveEventsPacked(
world, movedIds, movedTransforms, fellAsleep, eventCapacity);
// Only Math.min(required, eventCapacity) entries were written.
// If required > eventCapacity, grow the buffers and retry before changing the world.The method returns the total event count, even when the output is too small. Passing
capacity zero with null outputs queries the required size without copying. Positive
capacity requires writable buffers with at least capacity longs, 7 * capacity doubles,
and capacity bytes remaining. Native code trusts these bounds. Buffer positions are
honored without being advanced. Re-reading does not consume events, and copied data
remains valid after the world changes. Call once after each step; events represent that
step, not all changes since the last read.
Each ID uses Box3D's b3StoreBodyId encoding; compute a matching Java key once when
registering a body:
long key = ((long) id.index1() << 32)
| ((id.world0() & 0xffffL) << 16)
| (id.generation() & 0xffffL);Transforms use the same seven-double layout as b3Body_GetTransformsPacked. Sleep flags
are 0 or 1; a 1 reports the step where the body went to sleep. Keep that body's
last transform when later steps omit it. The original event API also exposes user data;
the packed helper uses body IDs for application lookup.
Measure b3World_Step separately from reading transforms and application work. Batching
reduces binding overhead; it does not accelerate the solver itself or establish the cause
of a performance difference against a Java engine. Native libraries are compiled during
the build, not at application startup.
Artifacts are published to the public ModLabs repository.
No credentials are required.
repositories {
maven("https://repo-api.modlabs.cc/repo/maven/maven-public/")
}
dependencies {
implementation("cc.modlabs:box3d4j:git-3362633-1.0.3")
}repositories {
maven { url "https://repo-api.modlabs.cc/repo/maven/maven-public/" }
}
dependencies {
implementation "cc.modlabs:box3d4j:git-3362633-1.0.3"
}<repositories>
<repository>
<id>modlabs-maven-public</id>
<url>https://repo-api.modlabs.cc/repo/maven/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>cc.modlabs</groupId>
<artifactId>box3d4j</artifactId>
<version>git-3362633-1.0.3</version>
</dependency>
</dependencies>Builds from Box3D main use the upstream Box3D commit in the version:
git-<Box3D short commit>-1.0.3
For example:
git-3fc20f5-1.0.3
CI checks Box3D main daily and publishes a new artifact when the upstream commit changes.
Tagged builds use the tag version instead. For example:
v0.1.0-1.0.3
publishes:
0.1.0-1.0.3
This makes it possible to tell which Box3D revision a Box3D4j artifact was generated from.
Requirements:
- JDK 17
- CMake 3.22 or newer
- Git
- A C/C++ compiler
Build with:
./gradlew clean test jarThe build:
- Clones the latest Box3D
main - Builds Box3D statically
- Generates Java and JNI bindings from the public Box3D headers
- Builds the JNI library
- Packages the native library into the JAR
A system installation of Box3D is not used.
The native target can be selected with javacppPlatform.
Supported values:
linux-x86_64
macosx-arm64
macosx-x86_64
windows-x86_64
For example:
./gradlew jar -PjavacppPlatform=linux-x86_64Native code must be built on the matching operating system.
GitHub Actions builds and tests Box3D4j on:
- Linux x86-64
- Windows x86-64
- macOS x86-64
- macOS ARM64
The platform-specific outputs are combined into:
box3d4j-all.jar
The combined artifact is published as:
cc.modlabs:box3d4j:<version>
It contains:
- Box3D JNI libraries for all supported platforms
- The matching JavaCPP native runtimes
- JavaCPP Java classes
- Generated Box3D4j classes
Consumers only need the published Box3D4j artifact on the classpath.
A JAR built locally contains native binaries for the selected local target. The cross-platform -all.jar is assembled by CI because Windows and macOS binaries need to be built on their respective operating systems.
Box3D4j is generated from the public headers of:
https://github.com/erincatto/box3d
The project follows Box3D main rather than only tracking tagged releases.
Because Box3D is still evolving, upstream API changes can result in corresponding changes to the Java API.
Box3D4j is licensed under the MIT License.
The bundled Box3D code is also MIT licensed. Generated bindings retain the upstream SPDX notices and license information:
The bundled JavaCPP runtime is redistributed under the Apache License 2.0: