# Libgdx Bullet Physics

> Use when writing libGDX Java/Kotlin code involving 3D physics with the Bullet wrapper — btRigidBody, btDiscreteDynamicsWorld, collision shapes, ContactListener, raycasting, MotionState, or debug drawing. Use when debugging segfaults, native memory leaks, bodies not moving, or collision detection failures in the libGDX Bullet extension.

- **Type:** Skill
- **Install:** `agentstack add skill-kyu-n-gdx-claude-skills-libgdx-bullet-physics`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [kyu-n](https://agentstack.voostack.com/s/kyu-n)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [kyu-n](https://github.com/kyu-n)
- **Source:** https://github.com/kyu-n/gdx-claude-skills/tree/master/skills/libgdx-bullet-physics

## Install

```sh
agentstack add skill-kyu-n-gdx-claude-skills-libgdx-bullet-physics
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# libGDX Bullet Physics Wrapper

Quick reference for the libGDX Bullet extension (`com.badlogic.gdx.physics.bullet.*`). This is a JNI wrapper around the C++ Bullet Physics library. All `bt*` objects are backed by native memory — the Java GC does NOT free them. Manual `dispose()` is mandatory.

**Not supported on HTML5/GWT.**

## Gradle Dependencies

```gradle
// core
api "com.badlogicgames.gdx:gdx-bullet:$gdxVersion"
// desktop
implementation "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-desktop"
// android (all four)
natives "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-armeabi-v7a"
natives "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-arm64-v8a"
natives "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-x86"
natives "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-x86_64"
// ios
implementation "com.badlogicgames.gdx:gdx-bullet-platform:$gdxVersion:natives-ios"
```

## Initialization

```java
Bullet.init();  // MUST call before any bt* class. Loads native library. Segfault otherwise.
```

Three overloads:
```java
Bullet.init()                                   // useRefCounting=false, logging=true
Bullet.init(boolean useRefCounting)              // logging=true
Bullet.init(boolean useRefCounting, boolean logging)
```

Call once in `create()`, guard against double-init. Reference counting enables automatic `obtain()`/`release()` on shared objects (e.g., `btCompoundShape` child shapes).

## Dynamics World Setup

Five objects, created in this order:

```java
btDefaultCollisionConfiguration collisionConfig = new btDefaultCollisionConfiguration();
btCollisionDispatcher dispatcher = new btCollisionDispatcher(collisionConfig);
btDbvtBroadphase broadphase = new btDbvtBroadphase();
btSequentialImpulseConstraintSolver solver = new btSequentialImpulseConstraintSolver();
btDiscreteDynamicsWorld world = new btDiscreteDynamicsWorld(
    dispatcher, broadphase, solver, collisionConfig);
world.setGravity(new Vector3(0, -10f, 0));  // takes libGDX Vector3, NOT btVector3
```

Constructor: `btDiscreteDynamicsWorld(btDispatcher, btBroadphaseInterface, btConstraintSolver, btCollisionConfiguration)`. Parameter order is dispatcher, broadphase, solver, config.

### Stepping

```java
// 3-arg form (recommended):
world.stepSimulation(delta, maxSubSteps, fixedTimeStep);
// delta: frame time; maxSubSteps: catch-up limit; fixedTimeStep: physics tick (1/60f typical)

// Return value: int — number of substeps performed
float delta = Math.min(1f / 30f, Gdx.graphics.getDeltaTime());
world.stepSimulation(delta, 5, 1f / 60f);
```

**DO NOT use the 1-arg form** `stepSimulation(delta)` — it defaults `maxSubSteps=1`, so any frame taking longer than 1/60s loses simulation time. Always pass all three arguments.

### Adding / Removing Bodies

```java
world.addRigidBody(body);
world.addRigidBody(body, collisionGroup, collisionMask);  // group/mask are int (bit flags)
world.removeRigidBody(body);
```

There is NO `removeAndDestroyBody()` — you must remove then dispose separately.

## Collision Shapes

All in `com.badlogic.gdx.physics.bullet.collision`. Constructors take libGDX `Vector3` (not `btVector3`).

| Shape | Constructor | Notes |
|---|---|---|
| `btBoxShape` | `(Vector3 halfExtents)` | Half-extents, not full size |
| `btSphereShape` | `(float radius)` | |
| `btCapsuleShape` | `(float radius, float height)` | Y-axis. Also `btCapsuleShapeX`, `btCapsuleShapeZ` |
| `btCylinderShape` | `(Vector3 halfExtents)` | Y-axis. Also `btCylinderShapeX`, `btCylinderShapeZ` |
| `btConeShape` | `(float radius, float height)` | Y-axis. Also `btConeShapeX`, `btConeShapeZ` |
| `btConvexHullShape` | `()` then `addPoint(Vector3)` | Or `(FloatBuffer)` — must be direct buffer |
| `btBvhTriangleMeshShape` | `(Array)` | Static concave meshes only. libGDX convenience |
| `btCompoundShape` | `()` | `addChildShape(Matrix4 localTransform, btCollisionShape)` |

**Gotchas:**
- `btBvhTriangleMeshShape` is for **static** geometry only — do not use for dynamic bodies.
- `btConvexHullShape(FloatBuffer)` requires a **direct** buffer (`ByteBuffer.allocateDirect()`).
- Capsule/Cylinder/Cone default to Y-axis. Use the `X`/`Z` suffixed classes for other axes.
- `btCompoundShape.addChildShape()` calls `obtain()` on child shapes when reference counting is enabled.

### calculateLocalInertia

```java
Vector3 localInertia = new Vector3();
shape.calculateLocalInertia(mass, localInertia);  // output parameter — writes into localInertia
```

On `btCollisionShape`. Must call for dynamic bodies (mass > 0). Static bodies use `Vector3.Zero`.

## Rigid Bodies

### Static vs Dynamic vs Kinematic

| Type | Mass | Inertia | Flags | Moved by |
|---|---|---|---|---|
| Static | 0 | `Vector3.Zero` | (auto `CF_STATIC_OBJECT`) | Nothing |
| Dynamic | > 0 | `calculateLocalInertia()` | (none needed) | Physics |
| Kinematic | 0 | `Vector3.Zero` | `CF_KINEMATIC_OBJECT` | Application code via MotionState |

### Construction

Two patterns — both work. Direct construction bypasses the info object:

```java
// Pattern 1: via ConstructionInfo (reusable for multiple bodies)
Vector3 localInertia = new Vector3();
shape.calculateLocalInertia(mass, localInertia);
btRigidBody.btRigidBodyConstructionInfo info =
    new btRigidBody.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
info.setRestitution(0.5f);
info.setFriction(0.8f);
btRigidBody body = new btRigidBody(info);
info.dispose();  // can dispose after body creation

// Pattern 2: direct
btRigidBody body = new btRigidBody(mass, motionState, shape, localInertia);
```

`btRigidBodyConstructionInfo` is an inner class of `btRigidBody`. Constructor: `(float mass, btMotionState motionState, btCollisionShape shape, Vector3 localInertia)`. The `motionState` parameter can be `null` — set it later via `body.setMotionState()`.

### Kinematic Body Setup

```java
btRigidBody body = new btRigidBody(0f, motionState, shape, Vector3.Zero);
body.setCollisionFlags(body.getCollisionFlags()
    | btCollisionObject.CollisionFlags.CF_KINEMATIC_OBJECT);
body.setActivationState(Collision.DISABLE_DEACTIVATION);  // CRITICAL
```

Without `DISABLE_DEACTIVATION`, Bullet stops calling `getWorldTransform()` on the MotionState after the body sleeps — your kinematic body freezes.

### Key btRigidBody Methods

```java
// Forces (accumulated, applied at next step, cleared by clearForces)
body.applyCentralForce(Vector3 force);
body.applyForce(Vector3 force, Vector3 relPos);
body.applyTorque(Vector3 torque);
body.clearForces();

// Impulses (instant velocity change)
body.applyCentralImpulse(Vector3 impulse);
body.applyImpulse(Vector3 impulse, Vector3 relPos);

// Velocity
body.setLinearVelocity(Vector3 vel);
body.setAngularVelocity(Vector3 vel);

// Axis locking (e.g., 2D physics: lock Z axis)
body.setLinearFactor(new Vector3(1, 1, 0));
body.setAngularFactor(new Vector3(0, 0, 1));

// Damping
body.setDamping(float linearDamping, float angularDamping);

// Sleeping
body.setSleepingThresholds(float linear, float angular);
body.activate();  // wake up a sleeping body
```

### btCollisionObject (base class)

```java
body.setWorldTransform(Matrix4 transform);
body.getWorldTransform(Matrix4 out);    // output parameter form
Matrix4 t = body.getWorldTransform();   // allocating form

body.setUserValue(int value);           // int stored on native side — survives GC
int v = body.getUserValue();
body.userData = myObject;               // Java-only Object field — NOT on native side

body.setCollisionFlags(int flags);
body.setActivationState(int state);
body.isStaticOrKinematicObject();
```

### Collision Flags (`btCollisionObject.CollisionFlags`)

| Constant | Value | Use |
|---|---|---|
| `CF_STATIC_OBJECT` | 1 | Auto-set for mass=0 bodies |
| `CF_KINEMATIC_OBJECT` | 2 | Must set manually for kinematic |
| `CF_NO_CONTACT_RESPONSE` | 4 | Trigger/sensor (detects but no physics response) |
| `CF_CUSTOM_MATERIAL_CALLBACK` | 8 | Required for `onContactAdded` |
| `CF_CHARACTER_OBJECT` | 16 | For character controllers |

### Activation States (`com.badlogic.gdx.physics.bullet.collision.Collision`)

| Constant | Value |
|---|---|
| `Collision.ACTIVE_TAG` | 1 |
| `Collision.ISLAND_SLEEPING` | 2 |
| `Collision.WANTS_DEACTIVATION` | 3 |
| `Collision.DISABLE_DEACTIVATION` | 4 |
| `Collision.DISABLE_SIMULATION` | 5 |

## MotionState

Syncs physics transform with render transform. Bullet calls `setWorldTransform` after simulating dynamic bodies, and `getWorldTransform` for kinematic bodies.

```java
static class MyMotionState extends btMotionState {
    private final Matrix4 transform;  // share with ModelInstance.transform

    public MyMotionState(Matrix4 transform) {
        this.transform = transform;
    }

    @Override
    public void getWorldTransform(Matrix4 worldTrans) {
        worldTrans.set(transform);  // Bullet reads from your transform (kinematic)
    }

    @Override
    public void setWorldTransform(Matrix4 worldTrans) {
        transform.set(worldTrans);  // Bullet writes its result (dynamic)
    }
}

// Usage — share the ModelInstance's transform matrix:
MyMotionState motionState = new MyMotionState(modelInstance.transform);
btRigidBody body = new btRigidBody(mass, motionState, shape, localInertia);
```

`btDefaultMotionState` also exists (`com.badlogic.gdx.physics.bullet.linearmath`) but the custom pattern above is standard in libGDX because it shares the `Matrix4` directly with `ModelInstance`.

**Gotcha:** MotionState takes `com.badlogic.gdx.math.Matrix4` — the wrapper converts to/from native Bullet transforms automatically.

## Contact Detection

### ContactListener

`com.badlogic.gdx.physics.bullet.collision.ContactListener` — instantiate to enable globally. Only one overload per callback event.

Four event types, each with multiple overloads (btCollisionObject, userValue int, or btCollisionObjectWrapper variants). The `int userValue` variants are fastest (no JNI object mapping).

```java
ContactListener contactListener = new ContactListener() {
    @Override
    public boolean onContactAdded(int userValue0, int partId0, int index0,
            boolean match0, int userValue1, int partId1, int index1, boolean match1) {
        // match0/match1 = true if that object's flag matched the other's filter
        if (match0) { /* userValue0's object was matched */ }
        return true;
    }
};
```

**Filtering setup:**
```java
body.setContactCallbackFlag(1  instances` keyed by native pointer. This keeps Java wrappers alive while Bullet references them. Disposal removes the entry.

`userData` is a Java-only `Object` field — invisible to native code. Use `setUserValue(int)` for data that must survive on the native side (e.g., entity array index for fast ContactListener lookups).

### Reference Counting (optional)

```java
Bullet.init(true);  // enable ref counting
shape.obtain();     // +1
shape.release();    // -1, auto-disposes at 0
```

`btCompoundShape.addChildShape()` auto-calls `obtain()` on child shapes when enabled.

## Advanced Topics (brief)

**Constraints:** `btPoint2PointConstraint`, `btHingeConstraint`, `btConeTwistConstraint`, `btSliderConstraint`, `btGeneric6DofConstraint`, `btGeneric6DofSpringConstraint`, `btGeneric6DofSpring2Constraint`, `btFixedConstraint`. Add via `world.addConstraint(constraint, disableCollisionBetweenBodies)`. Remove before disposing world.

**Character controller:** `btKinematicCharacterController` with `btPairCachingGhostObject`. Requires `btGhostPairCallback` registered on the broadphase's overlapping pair cache (`broadphase.getOverlappingPairCache().setInternalGhostPairCallback(new btGhostPairCallback())`). Works with any broadphase including `btDbvtBroadphase`. Added to world via `world.addAction(controller)`.

**Collision-only world:** Use `btCollisionWorld` (3-arg constructor: dispatcher, broadphase, config — no solver) for collision detection without dynamics.

**InternalTickCallback:** Override `onInternalTick(btDynamicsWorld, float timeStep)` for per-substep callbacks. Attach as pre-tick or post-tick.

**Soft bodies, vehicles:** Available in the wrapper but advanced — consult the Bullet C++ documentation.

## Common Mistakes

1. **Forgetting `Bullet.init()`** — Any `bt*` class instantiated before `init()` causes a segfault. Call it once in `create()`.
2. **Not disposing `bt*` objects** — Native memory leak. Every `bt*` object, ray callback, MotionState, and ConstructionInfo needs `dispose()`. Java GC does NOT free the native allocation reliably.
3. **Wrong disposal order** — Disposing the world while bodies are still in it, or disposing shapes while bodies reference them, causes crashes. Remove all bodies first, then dispose in reverse creation order.
4. **Skipping `calculateLocalInertia()` for dynamic bodies** — Body will have zero inertia and won't rotate or respond to off-center forces correctly. Always call `shape.calculateLocalInertia(mass, outVector)` when mass > 0.
5. **Using 1-arg `stepSimulation(delta)`** — Defaults `maxSubSteps` to 1. Any frame longer than 1/60s loses simulation time. Always use the 3-arg form.
6. **Forgetting `DISABLE_DEACTIVATION` on kinematic bodies** — Bullet deactivates the body and stops calling `getWorldTransform()`. The kinematic body appears frozen.
7. **Not resetting ray callbacks before reuse** — `ClosestRayResultCallback` retains state from the previous test. Must call `setCollisionObject(null)` and `setClosestHitFraction(1f)` before each `rayTest()`.
8. **Disposing ConstructionInfo with shared MotionState/shape references** — `btRigidBodyConstructionInfo` does NOT take ownership. The MotionState and shape must outlive the body. Dispose the info early if you want, but keep the shape and MotionState alive.
9. **Expecting Box2D-style pixels-per-meter scaling** — Bullet uses real-world units (meters). No PPM conversion needed. A 1-unit box is 1 meter.
10. **Sharing `btBvhTriangleMeshShape` with dynamic bodies** — Concave triangle mesh shapes are static-only. Use `btConvexHullShape` or `btCompoundShape` for dynamic bodies.
11. **Forgetting `CF_CUSTOM_MATERIAL_CALLBACK` for `onContactAdded`** — The callback silently never fires. At least one colliding body must have this flag set.
12. **Storing Java references only in `userData`** — This field is Java-only. For ContactListener performance, use `setUserValue(int)` to store an entity index, then look up your game object from an array.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kyu-n](https://github.com/kyu-n)
- **Source:** [kyu-n/gdx-claude-skills](https://github.com/kyu-n/gdx-claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kyu-n-gdx-claude-skills-libgdx-bullet-physics
- Seller: https://agentstack.voostack.com/s/kyu-n
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
