Skip to content

Repository files navigation

Logo

⚡ Jolt physics in React

⚠️ Alpha. All APIs are subject to change. Pin an exact version if you build on it today. ⚠️


The Jolt Physics Engine is a highly capable, real-time physics engine designed for games and VR applications, built for Horizon Forbidden West.

@react-three/jolt (or r3/jolt) is a wrapper library designed to slot seamlessly into a react-three-fiber pipeline. Jolt is very powerful and flexible, sometimes at the cost of usability — the goal of this library is to give you a world-class physics simulation without the complexity or the pitfalls.

📖 Documentation

pmndrs.github.io/react-three-jolt

Introduction what this is, and Jolt vs Rapier
Installation peers, Vite, Next.js, Jolt build variants
Physics the world component and every prop
RigidBody bodies, BodyState, constraints, instancing
Shapes autodetection, <Shape>, heightfields
Queries raycasting, shapecasting, collide-shape
Collision groups group and sub-group filtering
Controllers character, camera rig, vehicles
Addons input commands and helpers
Memory & lifecycle who owns what in the WASM heap
SSR & Suspense loading, error boundaries, Next.js
Migration upgrading from the pre-2026 builds

Installation

npm install @react-three/jolt jolt-physics

jolt-physics, @react-three/fiber (>=10), three (>=0.185) and react/react-dom (>=19) are peer dependencies — see Installation.

Example

import { Canvas } from '@react-three/fiber';
import { Physics, RigidBody } from '@react-three/jolt';
import { Suspense } from 'react';

export function App() {
    return (
        <Canvas camera={{ position: [0, 5, 12] }}>
            <Suspense fallback={null}>
                <Physics gravity={[0, -9.81, 0]}>
                    <RigidBody position={[0, 8, 0]}>
                        <mesh>
                            <boxGeometry args={[1, 1, 1]} />
                            <meshStandardMaterial color="hotpink" />
                        </mesh>
                    </RigidBody>
                    <RigidBody type="static" position={[0, -1, 0]}>
                        <mesh>
                            <boxGeometry args={[20, 1, 20]} />
                            <meshStandardMaterial color="#444" />
                        </mesh>
                    </RigidBody>
                </Physics>
            </Suspense>
            <directionalLight position={[5, 10, 5]} />
            <ambientLight intensity={0.4} />
        </Canvas>
    );
}

<Physics> suspends while the Jolt WASM module loads, so it needs a <Suspense> boundary above it.

jolt-physics ships several builds (embedded wasm-compat, separate-file /wasm, debug and multi-threaded variants). Pass an initializer to <Physics module> to pick one — see Choosing a Jolt build for the table and the Vite / Next.js recipes.


Group Filtering

Jolt has two independent collision filters and they answer different questions.

Object layers (Layer in constants.ts) are the broad one: "what kind of thing is this" — moving, non-moving, kinematic, rig. They are pre-set in R3/Jolt (we plan to expose them later) and are what you would reach for to say "bullets never hit other bullets".

Collision groups are the narrow one: "should these two specific objects collide with each other". They are the right tool for a ragdoll whose upper arm shouldn't collide with its own torso, or a door that shouldn't collide with its own frame.

A body's collision group is a pair of numbers, a group and a subGroup:

  • Two bodies with different group ids always collide — the filter is skipped entirely.
  • Two bodies with the same group id consult the system-wide filter table, which decides based on their subGroup ids. Every sub group pair collides until you turn one off.

Give every body inside a group its own subGroup id.

Setting a group

On the <RigidBody> component, with the group / subGroup props. Both are reactive — change them at any time and the body is updated (and woken) on the next step.

<RigidBody group={1} subGroup={2}>
    <mesh>
        <boxGeometry args={[5, 0.5, 8]} />
        <meshStandardMaterial color="#ff4060" />
    </mesh>
</RigidBody>

Or from the BodySystem, either at creation or afterwards:

const handle = bodySystem.addBody(cubeMesh, { group: 1, subGroup: 2 });

const body = bodySystem.getBody(handle)!;
body.group = 1; // alias: body.collisionGroup
body.subGroup = 3; // alias: body.collisionSubGroup

A body with no group set never pays for filtering, so only set one where you need it.

Turning a pair off

const { bodySystem } = physicsSystem;

bodySystem.disableCollision(2, 3); // sub groups 2 and 3 pass through each other
bodySystem.enableCollision(2, 3); // ...and back again
bodySystem.setGroupCollision(2, 3, false); // same thing, as one call
bodySystem.isCollisionEnabled(2, 3); // -> false

This only affects bodies that share a group id. Two bodies in sub groups 2 and 3 but in different groups still collide.

Sub group ids

Sub group ids index a fixed-size table, bodySystem.subGroupCount (default 256). Jolt does not bounds check that index in the release build, so R3/Jolt range checks every id and warns instead of letting it corrupt the heap. If you need more than the default, raise it before creating the first body that uses a group:

Subgroup 0 is the default and acts like most other physics systems. Any two Bodies in the same parent Group and Subgroup-0 will not collide with each other.

Bodies in Subgroup-0 will also ignore bodies in Subgroup-1.

(Green in the example. Note how when coming to a rest the cubes do not stack nicely, instead merge with each other)

SubGroup-1

Doesnt Collide: 0
Collides: 1 2 Non-Members

'Subgroup-1' is an addition made by R3/Jolt and the one we recommend using for any “standard” objects.

Filtering is often done to create trapdoors or filters but you still want the objects to collide with each other. This subgroup allows objects to access filters/traps but still act as regular bodies. (Besides green, all boxes in the example are in subgroup-1)

Subgroup-2

Doesn’t collide: Non Group Bodies Collides: All subgroups

Doesnt Collide: Non-Members
Collides: 0 1 2

Subgroup-2 is best used as a block/filter device. Bodies in this subgroup ONLY collide with items in the same PARENT group. This means ALL OTHER bodies will ignore these bodies and fall right through them. However, items in the same “Group” will collide. (The blue filter in the example is in subgroup-2)


Project Outline

There are 4 phases planned for this library. We are currently in Phase 0 (Pre-Alpha).

Contributing

See the Development Guide and the Contributing docs page.

About

⚡ Jolt physics in React

Resources

Stars

118 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages