Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4d81ebc
fix: null out forwarded refs when rigid bodies and colliders are dest…
DennisSmolek Oct 10, 2026
f352098
fix: use proper xyz arrays for trimesh/hull geometry, clear hull erro…
DennisSmolek Oct 10, 2026
f4e3d88
fix: scale heightfield collider args per axis without mutating them (…
DennisSmolek Oct 10, 2026
5335637
chore: migrate to yarn 4 via corepack
DennisSmolek Oct 10, 2026
fec152d
feat: add useCharacterController hook and CharacterController component
DennisSmolek Oct 10, 2026
3436c24
fix: apply collider mass, density and massProperties changes after mo…
DennisSmolek Oct 10, 2026
a8efa4c
fix: re-export commonly needed rapier enums from the package index (#…
DennisSmolek Oct 10, 2026
26cbcb3
feat: add character controller demo and changeset
DennisSmolek Oct 10, 2026
603e159
test: add regression test for changing RigidBody type after mount (#314)
DennisSmolek Oct 10, 2026
e1ab740
fix: avoid spreading key into JSX in InstancedRigidBodies (#691)
DennisSmolek Oct 10, 2026
a6bd1a4
fix: use three/examples/jsm imports instead of three-stdlib (#698)
DennisSmolek Oct 10, 2026
564cf92
docs: add pmndrs/docs site with getting-started and API pages
DennisSmolek Oct 10, 2026
7b0f150
docs: add advanced guides split from the readme
DennisSmolek Oct 10, 2026
4d73693
docs: add 2.x to 3.0 migration guide
DennisSmolek Oct 10, 2026
834c0d1
docs: add FAQ and troubleshooting page
DennisSmolek Oct 10, 2026
769d9b4
docs: slim readme to overview and links to the docs site
DennisSmolek Oct 10, 2026
6b25c4e
fix: disable frustum culling on instanced meshes driven by physics (#…
DennisSmolek Oct 10, 2026
efb9a0e
fix: document HeightfieldArgs and warn on mismatched heights length (…
DennisSmolek Oct 10, 2026
f130449
chore: add changeset for 3.0 bug fixes
DennisSmolek Oct 10, 2026
64b06f5
fix: make InstancedRigidBodies frustum culling opt-in via prop (#683)
DennisSmolek Oct 10, 2026
8de78e7
feat(soft-bodies): add SoftBody component and useSoftBody hook
DennisSmolek Oct 10, 2026
290df4f
chore!: move to r3f v10, three 0.186, react 19.2 and vitest 5
DennisSmolek Oct 10, 2026
2d8877b
style: format soft body state
DennisSmolek Oct 10, 2026
8d99499
build: replace preconstruct with rollup + esbuild + rollup-plugin-dts
DennisSmolek Oct 10, 2026
4bc1587
chore(demo): compile and build on r3f v10 / drei 11
DennisSmolek Oct 10, 2026
6f5aa73
chore: add eslint 9 flat config and prettier 3
DennisSmolek Oct 10, 2026
922ad84
fix: address review feedback on instanced culling, heightfield warnin…
DennisSmolek Oct 10, 2026
62b6c97
fix: character controller review follow-ups
DennisSmolek Oct 10, 2026
c57c7c5
ci: refresh workflows and enter changesets alpha prerelease mode
DennisSmolek Oct 10, 2026
a4a0c34
docs: add DEVELOPMENT.md for the yarn 4 / rollup tooling
DennisSmolek Oct 10, 2026
2c12a59
feat!: step physics in the r3f v10 scheduler's fixed-step phase
DennisSmolek Oct 10, 2026
e473000
fix: reuse one debug geometry instead of allocating one per frame (#666)
DennisSmolek Oct 10, 2026
9fe49f5
test: cover scheduler stepping, interpolation, parent space and debug
DennisSmolek Oct 10, 2026
a82ff17
chore: add changeset for scheduler stepping
DennisSmolek Oct 10, 2026
6ef8f89
test: restore the pre-v10 physics snapshot values
DennisSmolek Oct 10, 2026
e23be65
fix(soft-bodies): review follow-ups
DennisSmolek Oct 10, 2026
0d91d9f
refactor: reset the stepper accumulator on timeStep change without us…
DennisSmolek Oct 10, 2026
a6f8a3f
fix: address stepping review (parent order, late ancestor moves, inte…
DennisSmolek Oct 10, 2026
00c990c
test: regressions for nested bodies, late ancestor moves, teleports, …
DennisSmolek Oct 10, 2026
dce14bb
chore: update stepping changeset for review fixes
DennisSmolek Oct 10, 2026
58e15a2
feat(demo): add raycast vehicle example
DennisSmolek Oct 10, 2026
8ac0ac6
fix(demo): cradle physics, WebGPU-safe sensors text, immutable-props …
DennisSmolek Oct 10, 2026
2fd625c
Merge branch 'feat/3.0-foundation' (#787)
DennisSmolek Oct 10, 2026
b3150f9
Merge branch 'feat/v10-scheduler-stepping' (#788)
DennisSmolek Oct 10, 2026
452d9eb
feat(demo): add renderer toggle and auto-discovered example routes
DennisSmolek Oct 10, 2026
6828534
Merge branch 'fix/3.0-bugfixes' (#785)
DennisSmolek Oct 10, 2026
08d619c
Merge branch 'feat/character-controller' (#783)
DennisSmolek Oct 10, 2026
b0000d4
Merge branch 'feat/soft-bodies' (#786)
DennisSmolek Oct 10, 2026
6f38bcf
test(soft-bodies): await create() in the test setup
DennisSmolek Oct 10, 2026
e1fc545
Merge branch 'docs/3.0-docs-site' (#784)
DennisSmolek Oct 10, 2026
c325c48
Merge branch 'feat/demo-v10-webgpu' (#790)
DennisSmolek Oct 10, 2026
c009307
feat(character-controller): use the (world, dt) step callback contract
DennisSmolek Oct 10, 2026
37fbf0d
feat(soft-bodies): publish the subpath entry and sync once per frame
DennisSmolek Oct 10, 2026
17b8507
docs: document the merged 3.0 APIs and complete the migration guide
DennisSmolek Oct 10, 2026
8d7b56b
style: format with prettier 3
DennisSmolek Oct 10, 2026
e7dc1da
chore: ignore the prettier 3 reformat in git blame
DennisSmolek Oct 10, 2026
f828e75
fix: resolve the remaining eslint errors
DennisSmolek Oct 10, 2026
58a8424
ci: make the lint and format job blocking
DennisSmolek Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/calm-bugs-fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@react-three/rapier": patch
"@react-three/rapier-addons": patch
---

Bug fixes for 3.0:

- Forwarded refs on `RigidBody`, colliders and `InstancedRigidBodies` instances are now cleared when the underlying rapier object is destroyed, instead of pointing at freed objects (#780)
- Trimesh and convex hull colliders now read geometry positions correctly for interleaved, normalized and quantized attributes, and indices are converted to `Uint32Array`; failing to build a collider now throws a descriptive error (#781, #627)
- Heightfield collider args are scaled per axis and no longer mutated in place, which compounded scale on remount (#564)
- `mass`, `density` and `massProperties` collider props now update after mount (#711)
- Re-export `ActiveCollisionTypes`, `ActiveEvents`, `ActiveHooks` and `QueryFilterFlags` from the package index (#703)
- Add a regression test for changing the `RigidBody` `type` after mount (#314)
- `InstancedRigidBodies` no longer spreads `key` into JSX, which warned on React 19 (#691)
- `InstancedRigidBodies` disables frustum culling on its instanced mesh by default, since cached bounds go stale as bodies move; pass `frustumCulled` to re-enable it (#683)
- Replace `three-stdlib` with `three/examples/jsm` imports and remove the dependency (#698)
- Document `HeightfieldArgs` (`heights` must have `(rows + 1) * (cols + 1)` values) and warn in development when the length mismatches (#730)
10 changes: 10 additions & 0 deletions .changeset/character-controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@react-three/rapier": minor
---

Add a kinematic character controller built on Rapier's `KinematicCharacterController`.

- `useCharacterController(options)` creates and frees the controller (StrictMode safe), applies `offset`, `up`, `slide`, `maxSlopeClimbAngle`, `minSlopeSlideAngle`, `autostep`, `snapToGround`, `applyImpulsesToDynamicBodies`, `characterMass` and `normalNudgeFactor` reactively, and returns a stable `{ controller, move(rigidBody, desiredTranslation, opts) }` api. `move` computes the collision-resolved movement, calls `setNextKinematicTranslation` and returns a pooled `{ movement, grounded, collisions }`.
- `<CharacterController>` wraps a `kinematicPosition` `RigidBody`. Its ref exposes `{ controller, rigidBody, grounded, setVelocity(v), translate(delta) }`; the desired motion is applied once per fixed physics step with the fixed `dt`, so it is framerate independent. It also accepts `onGrounded`, `onAirborne` (edge triggered) and `onCharacterCollision`.

Options reverting to `undefined` restore Rapier's defaults. `<CharacterController>` defaults to `colliders={false}`. Gravity and jumping are intentionally left to userland. See the `character-controller` demo.
4 changes: 4 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"mode": "pre",
"tag": "alpha"
}
27 changes: 27 additions & 0 deletions .changeset/r3f-v10-foundation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"@react-three/rapier": major
"@react-three/rapier-addons": major
---

Move to the React Three Fiber v10 stack and modernize the package format.

### Requirements

- `@react-three/fiber` `>=10.0.0-0` (developed against `10.0.0-canary.a056612`; scheduler fixed-step support needs `@pmndrs/scheduler` 0.3+)
- `react` `>=19.0 <19.3` (the range r3f v10 supports)
- `three` `>=0.185.0`
- Node.js 22+ for tooling (`engines`)
- `@react-three/rapier-addons` now peers on `@react-three/rapier` `^3.0.0-0`

### Package format

- Built with rollup + esbuild instead of preconstruct. Each package ships an `exports` map with
ESM (`dist/index.mjs`) and CommonJS (`dist/index.cjs`) builds and matching type declarations
(`.d.mts` / `.d.cts`), and is marked `sideEffects: false`.
- Deep imports into package internals (e.g. `@react-three/rapier/src/...` or `dist/...` files)
are no longer possible; import from the package root.

### Behaviour notes

- Under r3f v10 `useFrame`'s state no longer has `clock`; use `state.elapsed` / `state.delta`
in your own frame callbacks.
17 changes: 17 additions & 0 deletions .changeset/scheduler-fixed-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@react-three/rapier": major
---

Step physics on the react-three-fiber v10 scheduler.

- With `updateLoop="follow"` (default) and a numeric `timeStep`, each world steps in its own fixed-timestep scheduler phase (`rapier:fixed:<n>`), which runs after r3f's `physics` phase. Changing `timeStep` or `maxSubSteps` retimes that phase in place, and phases are reused when worlds remount. Rendered transforms are synced once per frame in a new `rapier` phase, before `update`, and interpolated by the phase's overstep. A `rapier:late` pass right before `render` re-applies them if an ancestor moved during `update`. The shared `physics` phase timestep is never changed, and multiple `<Physics>` worlds with different `timeStep`s work side by side. Without scheduler fixed-phase support (`@pmndrs/scheduler` < 0.3), the same behaviour runs on an internal accumulator.
- New `maxSubSteps` prop (default `5`) caps the fixed steps a single frame may run. Surplus time is dropped instead of queued, so slow frames can no longer spiral (#697).
- **Breaking:** `updatePriority` now only orders this world's jobs within their scheduler phase (higher runs first, the r3f v10 convention). It no longer takes over r3f's render loop. Physics always steps before `update`, and r3f keeps rendering.
- `paused` banks no time, so resuming does not burst. `step()` from `useRapier()` works while paused and syncs meshes immediately.
- `useBeforePhysicsStep` and `useAfterPhysicsStep` callbacks receive `(world, dt)`, where `dt` is the step length. Existing single-argument callbacks keep working.
- Collision, contact-force and soft-body tear events are drained after every substep instead of once per frame.
- Bodies are synced parent-first into their parent's current space every frame, so moving, rotating or re-parenting an ancestor of a `<RigidBody>` after mount keeps its visual on the simulated body (#36), and nested bodies no longer lag the body that carries them.
- Interpolation no longer lerps across teleports made in `useBeforePhysicsStep`, manual `step()` calls, `setWorld()`, or `paused`/`interpolate` changes.
- `<Debug>` reuses one geometry and disposes it on unmount, instead of allocating one per frame and leaking on toggle (#666).
- With `frameloop="demand"`, the canvas is invalidated once per frame while any non-fixed body is awake.
- The world is freed after the unmount commit, so child cleanups run against a live world and StrictMode remounts are safe.
19 changes: 19 additions & 0 deletions .changeset/soft-bodies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@react-three/rapier": minor
---

Add soft bodies: `<SoftBody>` and `useSoftBody`, built on Rapier 0.21's deformable bodies.

Build cloth, rope and volumetric (jelly) bodies from the geometry of a `<mesh>` or `<line>`. Vertices are welded on position only, and the simulated positions are written into the geometry in the parent's local space once per rendered frame, interpolated with the same alpha as the rigid bodies (so cloth attached to an interpolated body stays on it), with normals and bounds recomputed. Tears are handled after every physics substep. Supports pinned particles, attachments to rigid bodies, stiffness/damping/mass, collisions with rigid bodies, and an `onTear` event.

```tsx
import { SoftBody } from "@react-three/rapier/soft-bodies";

<SoftBody type="cloth" pinned={(p) => p.y > 0.49} stiffness={12}>
<mesh position={[0, 3, 0]}>
<planeGeometry args={[1, 1, 20, 20]} />
</mesh>
</SoftBody>;
```

The API lives in the `@react-three/rapier/soft-bodies` entry point (a separate bundle that imports core from `@react-three/rapier`). `RapierContext` gains two internal sets: `softBodyTearCallbacks`, which `<Physics>` fills from Rapier's soft body tear events after each step, and `frameSyncCallbacks`, which `<Physics>` calls once per frame with the interpolation alpha after writing the rigid bodies.
5 changes: 5 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Bulk formatting commits, skipped by `git blame` (GitHub reads this file automatically;
# locally: git config blame.ignoreRevsFile .git-blame-ignore-revs).

# style: format with prettier 3
8d7b56b1a0ce4d23fcb275dc72a108aa14c934bf
26 changes: 26 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Setup
description: Enable Corepack (Yarn 4), set up Node.js with the Yarn cache and install dependencies

inputs:
node-version:
description: Node.js version
default: 22.x

runs:
using: composite
steps:
# Corepack must be enabled before setup-node so its Yarn cache step finds Yarn 4
# (the `packageManager` field), not the runner's global Yarn 1.
- name: Enable Corepack
shell: bash
run: corepack enable

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}
cache: yarn

- name: Install dependencies
shell: bash
run: yarn install --immutable
35 changes: 34 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/packages/react-three-rapier"
directory: "/" # yarn workspaces are discovered from the root
schedule:
interval: "weekly"
groups:
minor-and-patch:
applies-to: version-updates
update-types:
- "minor"
- "patch"
ignore:
# three, react/react-dom and @react-three/* track r3f v10 / drei v11 prereleases;
# major bumps are deliberate, hand-verified upgrades.
- dependency-name: "three"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/three"
update-types: ["version-update:semver-major"]
- dependency-name: "react"
update-types: ["version-update:semver-major"]
- dependency-name: "react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react"
update-types: ["version-update:semver-major"]
- dependency-name: "@types/react-dom"
update-types: ["version-update:semver-major"]
- dependency-name: "@react-three/*"
update-types: ["version-update:semver-major"]

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
github-actions:
update-types:
- "minor"
- "patch"
5 changes: 4 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
## Description

<!-- A summary of the changes. Add a video or screenshot if it makes sense! -->

### Type of change

<!-- Remove unrelated items -->

- 🐛 Bug fix
- ✨ New feature
- 📦 Other (tests, refactoring, docs, etc.)

### Checklist:

<!-- Check all that apply, remove any that don't -->

- [ ] 🔍 I have performed a self-review of my code
- [ ] 💬 I have commented my code, particularly in hard-to-understand areas
- [ ] 📗 I have made corresponding changes to the documentation
- [ ] ⭐️ My changes generate no new warnings
- [ ] 🧪 I have added tests
- [ ] 🟢 All new and existing unit tests pass
- [ ] 🟢 All new and existing unit tests pass
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI

on:
pull_request:
branches: [main, next]
push:
branches: [main, next]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- name: Typecheck packages and tests
run: yarn typecheck
- name: Typecheck demo
run: yarn workspace demo typecheck

test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
# vitest resolves the workspace packages from src, so no build is needed first
- name: Test
run: yarn vitest run

build:
name: Build & check packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- name: Build
run: yarn build
- name: publint + arethetypeswrong
run: yarn check:packages

demo:
name: Build demo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- name: Build demo
run: yarn demo:build

lint:
name: Lint & format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup
- name: Lint
run: yarn lint
- name: Format
if: ${{ !cancelled() }}
run: yarn format:check
19 changes: 5 additions & 14 deletions .github/workflows/r3-rapier-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: Setup Node.js environment
uses: actions/setup-node@v3.6.0
with:
node-version: 22.x

- name: Enable corepack
run: corepack enable

- name: Install Node Dependencies
run: yarn --immutable
- uses: ./.github/actions/setup

- name: Run typedoc
run: yarn docs:build

- name: Setup Pages
uses: actions/configure-pages@v2
uses: actions/configure-pages@v6

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
# Upload docs folder
path: "./packages/react-three-rapier/docs"

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
28 changes: 19 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
name: Release

# `main` publishes stable releases. `next` is the 3.0 integration branch and is in changesets
# pre mode (.changeset/pre.json, `yarn prerelease:enter`), so the same flow there opens a
# "Version Packages (alpha)" PR into `next` (the action appends the pre tag) and publishes `x.y.z-alpha.n` under the `alpha`
# dist-tag, leaving `latest` untouched. Run `yarn prerelease:exit` before merging `next` into
# `main`.
on:
push:
branches:
- main
- next

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
release:
name: Release
if: github.repository_owner == 'pmndrs'
runs-on: ubuntu-latest
permissions:
contents: write # changesets pushes the version branch
pull-requests: write # ...and opens the "Version Packages" PR
id-token: write # npm provenance / trusted publishing (OIDC)
steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: Setup Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 22

- name: Install Dependencies
run: yarn --immutable
- uses: ./.github/actions/setup

- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
publish: yarn release
version: yarn changeset:version
publish: yarn changeset:publish
commit: "chore(changeset): release packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# This is a Yarn 4 workspace, so changesets publishes with `yarn npm publish`, which
# reads YARN_NPM_AUTH_TOKEN rather than NPM_TOKEN. NPM_TOKEN is kept because
# changesets/action uses it to write the .npmrc it checks registry state with.
YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 changes: 0 additions & 31 deletions .github/workflows/test.yml

This file was deleted.

Loading
Loading