Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f6d4001
fix: allow angular limits on the full circle
sebcrozet Aug 8, 2026
6891341
feat: add CoefficientCombineRule::GeometricMean
sebcrozet Aug 8, 2026
e6855c7
fix: pass the embedded wasm module to init() as an object in the comp…
sebcrozet Aug 8, 2026
3d7680e
fix: apply the vehicle skid clamp even when the side impulse is zero
tomo0613 Jun 7, 2026
3cc6092
fix: derive wheel rotation from the wheel's own rolling direction
sebcrozet Aug 8, 2026
67e86ff
test: an empty polyline collider steps and queries without panicking
sebcrozet Aug 8, 2026
e48ea7b
test: characterize the coupled linear-axes joint motor models
sebcrozet Aug 8, 2026
fe6234e
feat: expose the spherical joint's per-axis motors in the JS bindings
sebcrozet Aug 8, 2026
669595c
docs: rigid_body_link returns None for bodies outside any multibody
sebcrozet Aug 8, 2026
f820330
docs: document the validity scope of SolverContact::contact_indices
sebcrozet Aug 8, 2026
91af70b
fix: point the compat raw.d.ts at the right wasm module for feature v…
sebcrozet Aug 8, 2026
67930f3
perf: filter out same-parent collider pairs directly in the broad phase
sebcrozet Aug 8, 2026
e9c7898
docs: detail the trade-offs between compound shapes and multiple coll…
sebcrozet Aug 8, 2026
e307494
chore: parallelize the native CI job and cache cargo builds
sebcrozet Aug 8, 2026
df66b8a
fix: canonicalize signed zeros in stored contact impulses
sebcrozet Aug 8, 2026
58fd0fc
chore: cargo fmt
sebcrozet Aug 8, 2026
4f6dddd
chore: verify the determinism goldens on aarch64 CI runners
sebcrozet Aug 8, 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
153 changes: 107 additions & 46 deletions .github/workflows/rapier-ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ on:
pull_request:
branches: [master]

# A new push to a PR cancels the still-running build of the previous push.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
CARGO_TERM_COLOR: always
# Incremental compilation only pays off for local rebuild loops; in CI it just
# slows the build down and bloats the caches.
CARGO_INCREMENTAL: 0

jobs:
check-fmt:
Expand All @@ -22,42 +30,80 @@ jobs:
RUSTDOCFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Cargo doc
run: cargo doc --features parallel,serde-serialize,debug-render -p rapier3d -p rapier2d -p rapier3d-meshloader -p rapier3d-urdf && cargo doc -p mjcf-rs --features msh && cargo doc -p rapier3d-mjcf --features stl,wavefront,msh
build-native:
# All the lints, plus the testbed/example feature-combination checks. This is the only
# job (besides publish-dry-run) that compiles the graphical stack (kiss3d), so it owns
# the cmake/xcb setup and the zune-core pin.
clippy:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run: sudo apt-get install -y cmake libxcb-composite0-dev
# WORKAROUND: zune-core 0.5.2 made its `warn!` macro expand to nothing, which breaks
# zune-jpeg (all 0.5.x), where it is called in expression position. Reached through the
# testbeds' kiss3d -> gltf/image dependency. Remove once upstream is fixed.
- name: Pin zune-core (0.5.2 breaks zune-jpeg)
run: cargo update -p zune-core@0.5 --precise 0.5.1
# Bare clippy covers every default-member with default features: the rapier crates,
# the testbeds, and the example crates (so no separate `cargo check` for those).
- name: Clippy
run: cargo clippy
- name: Clippy rapier2d
- name: Clippy rapier2d examples (parallel)
run: cargo clippy -p rapier-examples-2d --features parallel
- name: Clippy rapier3d
- name: Clippy rapier3d examples (parallel)
run: cargo clippy -p rapier-examples-3d --features parallel
- name: Clippy mjcf-rs
run: cargo clippy -p mjcf-rs --all-targets --features msh
- name: Clippy rapier3d-mjcf
run: cargo clippy -p rapier3d-mjcf --all-targets --features stl,wavefront,msh
- name: Check rapier_testbed2d --features parallel
run: cd crates/rapier_testbed2d; cargo check --features parallel;
- name: Check rapier_testbed3d --features parallel
run: cd crates/rapier_testbed3d; cargo check --features parallel;
- name: Check rapier2d --features enhanced-determinism
run: cd crates/rapier2d; cargo check --features enhanced-determinism;
- name: Check rapier3d --features enhanced-determinism
run: cd crates/rapier3d; cargo check --features enhanced-determinism;
# Builds the core crates in their feature variants, and runs the debug test suites.
# Scoped to the non-graphical crates: the testbeds/examples are compiled by `clippy`.
build-native:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Build rapier2d
run: cargo build --verbose -p rapier2d;
run: cargo build -p rapier2d;
- name: Build rapier3d
run: cargo build --verbose -p rapier3d;
run: cargo build -p rapier3d;
- name: Build rapier2d Parallel
run: cd crates/rapier2d; cargo build --verbose --features parallel;
run: cd crates/rapier2d; cargo build --features parallel;
- name: Build rapier3d Parallel
run: cd crates/rapier3d; cargo build --verbose --features parallel;
run: cd crates/rapier3d; cargo build --features parallel;
- name: Build rapier3d 8-lanes SIMD
run: cd crates/rapier3d; cargo build --verbose --features simd8;
run: cd crates/rapier3d; cargo build --features simd8;
- name: Run tests
run: cargo test
run: cargo test -p rapier2d -p rapier2d-f64 -p rapier3d -p rapier3d-f64 -p rapier3d-urdf -p rapier3d-meshloader
- name: Test mjcf-rs
run: cargo test -p mjcf-rs --features msh
- name: Test rapier3d-mjcf
run: cargo test -p rapier3d-mjcf --features stl,wavefront,msh
# The release-mode determinism suites. Grouped so that invocations sharing a feature
# set reuse each other's build artifacts; kept apart from the other release tests so
# both halves run in parallel.
tests-determinism:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Test determinism (SIMD backend)
run: cargo test -p rapier3d --release --features enhanced-determinism --test simd_backend_determinism
- name: Test SIMD backend op-level parity
Expand All @@ -66,18 +112,48 @@ jobs:
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize --test parallel_path_parity
- name: Test parallel-path parity (feature on)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize,parallel --test parallel_path_parity
- name: Test parallel-path parity (unsync-callbacks)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize,parallel,unsync-callbacks --test parallel_path_parity
# The native half of the cross-target check: the `wasm-determinism` job runs
# these same two tests, against the same goldens, on 32-bit pointers.
- name: Test snapshot portability (3D, native)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
- name: Test snapshot portability (2D, native)
run: cargo test -p rapier2d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
# The aarch64 leg of the cross-architecture determinism check: the same goldens the
# x86_64 job (above) and the wasm32 job (below) verify must also hold on arm NEON.
# This is what catches platform-split float semantics that are dynamically invisible —
# e.g. the signed zero a min/max tie stores differs between SSE/wasm and NEON, which
# diverged serialized snapshots on aarch64 for months without any x86-only job noticing.
tests-determinism-arm64:
runs-on: ubuntu-24.04-arm
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Test snapshot portability (3D, aarch64)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
- name: Test snapshot portability (2D, aarch64)
run: cargo test -p rapier2d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
- name: Test parallel-path parity (aarch64)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize --test parallel_path_parity
- name: Test determinism (SIMD backend, aarch64)
run: cargo test -p rapier3d --release --features enhanced-determinism --test simd_backend_determinism
# The remaining release-mode suites (non-enhanced-determinism feature sets).
tests-release:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Test thread-count determinism
run: cargo test -p rapier3d --release --features parallel,serde-serialize --test thread_count_determinism
- name: Test snapshot round-trip (3D)
run: cargo test -p rapier3d --release --features serde-serialize --test snapshot_roundtrip
- name: Test snapshot round-trip (2D)
run: cargo test -p rapier2d --release --features serde-serialize --test snapshot_roundtrip
# The native half of the cross-target check: the `wasm-determinism` job below runs
# these same two tests, against the same goldens, on 32-bit pointers.
- name: Test snapshot portability (3D, native)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
- name: Test snapshot portability (2D, native)
run: cargo test -p rapier2d --release --features enhanced-determinism,serde-serialize --test snapshot_portability
- name: Test single-worker deferred BVH
run: cargo test -p rapier3d --release --features parallel --test single_worker_deferred_bvh
# `unsync-callbacks` drops the `Sync` bound off the hooks/event traits; the test's
Expand All @@ -88,28 +164,6 @@ jobs:
# configuration rather than one that trivially has no bound.
- name: Test unsync callbacks (no parallel)
run: cargo test -p rapier3d --release --features unsync-callbacks --test unsync_callbacks
- name: Test parallel-path parity (unsync-callbacks)
run: cargo test -p rapier3d --release --features enhanced-determinism,serde-serialize,parallel,unsync-callbacks --test parallel_path_parity
- name: Test mjcf-rs
run: cargo test -p mjcf-rs --features msh
- name: Test rapier3d-mjcf
run: cargo test -p rapier3d-mjcf --features stl,wavefront,msh
- name: Check rapier_testbed2d
run: cargo check --verbose -p rapier_testbed2d;
- name: Check rapier_testbed3d
run: cargo check --verbose -p rapier_testbed3d;
- name: Check rapier_testbed2d --features parallel
run: cd crates/rapier_testbed2d; cargo check --verbose --features parallel;
- name: Check rapier_testbed3d --features parallel
run: cd crates/rapier_testbed3d; cargo check --verbose --features parallel;
- name: Check rapier_testbed2d --features enhanced-determinism
run: cd crates/rapier2d; cargo check --verbose --features enhanced-determinism;
- name: Check rapier_testbed3d --features enhanced-determinism
run: cd crates/rapier3d; cargo check --verbose --features enhanced-determinism;
- name: Check rapier-examples-2d
run: cargo check -j 1 --verbose -p rapier-examples-2d;
- name: Check rapier-examples-3d
run: cargo check -j 1 --verbose -p rapier-examples-3d;
build-no-std:
runs-on: ubuntu-latest
env:
Expand All @@ -121,6 +175,7 @@ jobs:
with:
toolchain: stable
targets: "x86_64-unknown-linux-gnu,thumbv7em-none-eabihf"
- uses: Swatinem/rust-cache@v2
- name: Check rapier2d thumbv7em-none-eabihf (no alloc)
run: cargo check --verbose -p rapier2d --no-default-features --features dim2,f32 --target=thumbv7em-none-eabihf
- name: Check rapier3d thumbv7em-none-eabihf (no alloc)
Expand All @@ -145,6 +200,13 @@ jobs:
# (manifold store, solver-graph buckets, raw color-mask slices) for UB.
# x86_64 only: glam's aarch64 NEON backend hits foreign intrinsics Miri does
# not implement (on Apple Silicon, use --target x86_64-unknown-linux-gnu).
#
# The SIMD solver paths lean harder on transmutes and raw slices; worth the
# extra interpretation time (~3.5x the scalar run). `parallel` also passes,
# but only manually (not in CI): crossbeam-epoch's container_of pattern
# violates Stacked Borrows in rayon's steal path, so it needs
# MIRIFLAGS="-Zmiri-tree-borrows -Zmiri-ignore-leaks" (ignore-leaks for
# rayon's never-joined global pool) and RAYON_NUM_THREADS=2.
miri:
runs-on: ubuntu-latest
steps:
Expand All @@ -154,25 +216,19 @@ jobs:
with:
toolchain: nightly
components: miri
- uses: Swatinem/rust-cache@v2
- name: Miri test rapier3d (tiny scenes)
run: cargo miri test -p rapier3d --test miri_scenes
- name: Miri test rapier2d (tiny scenes)
run: cargo miri test -p rapier2d --test miri_scenes
# The SIMD solver paths lean harder on transmutes and raw slices; worth the
# extra interpretation time (~3.5x the scalar run). `parallel` also passes,
# but only manually (not in CI): crossbeam-epoch's container_of pattern
# violates Stacked Borrows in rayon's steal path, so it needs
# MIRIFLAGS="-Zmiri-tree-borrows -Zmiri-ignore-leaks" (ignore-leaks for
# rayon's never-joined global pool) and RAYON_NUM_THREADS=2.
- name: Miri test rapier3d (tiny scenes)
run: cargo miri test -p rapier3d --test miri_scenes
build-wasm:
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- name: build rapier2d
run: cd crates/rapier2d && cargo build --verbose --target wasm32-unknown-unknown;
- name: build rapier3d
Expand All @@ -195,6 +251,7 @@ jobs:
with:
toolchain: stable
targets: wasm32-wasip1
- uses: Swatinem/rust-cache@v2
# `node:wasi` needs no command-line flag from 22 on.
- uses: actions/setup-node@v4
with:
Expand All @@ -217,8 +274,9 @@ jobs:
RUSTFLAGS: -D warnings
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run: sudo apt-get install -y cmake libxcb-composite0-dev
# See `build-native`: zune-core 0.5.2 breaks zune-jpeg, reached here through the
# See `clippy`: zune-core 0.5.2 breaks zune-jpeg, reached here through the
# testbed crates' kiss3d dependency. Remove once upstream is fixed.
- name: Pin zune-core (0.5.2 breaks zune-jpeg)
run: cargo update -p zune-core@0.5 --precise 0.5.1
Expand Down Expand Up @@ -247,6 +305,9 @@ jobs:
- uses: actions/checkout@v4
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
- uses: Swatinem/rust-cache@v2
with:
workspaces: website/docs-examples
- name: Tests injection + code snippets
run: cd website/docs-examples && cargo test
# Checks the JavaScript documentation snippets build (against the published @dimforge/rapier).
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,45 @@

### Added

- `CoefficientCombineRule::GeometricMean`: combines friction/restitution as
`sqrt(c1 * c2)`, the convention used by several other engines. It has the highest
rule priority, and clamps negative coefficients to zero before the square root.
- `DebugRenderStyle::sleep_eligible_color_multiplier`: the debug-renderer now draws bodies that
are eligible for sleep but still awake in a distinct color, making it easy to spot what is
keeping a pile awake.

### Fixed

- `Wheel::rotation` now accumulates from the wheel's own rolling direction on the
contact plane (`contact normal × axle`, the same forward direction used by the
friction update) instead of the chassis' `index_forward_axis`, so wheels visually
spin whenever the vehicle actually rolls (adapted from PR #950 by @tomo0613).
- `DynamicRayCastVehicleController` no longer has unlimited braking friction when a
wheel's side impulse is exactly zero (e.g. braking in a straight line): the skid
clamp now applies to the forward impulse as well, so braking strength is bounded by
`friction_slip` (PR #947 by @tomo0613).
- Angular joint limits are no longer restricted to `(-π, π)`: the limit rows now measure the
wrapped joint angle from the middle of the allowed range (with the joint axis as the row's
jacobian, like the angular motor rows), so a range may sit anywhere on the circle
(`[0, 3π/2]` stops the joint at 3π/2 instead of π/2, and ranges crossing the ±π seam like
`[3π/4, 5π/4]` work at all). A range wider than a full turn is indistinguishable from "no
limit" for an angle read off a relative rotation, so it now leaves the axis free instead of
clamping it at some folded-back angle.
- Joint limit rows (linear and angular) now cap their position-correction bias by
`IntegrationParameters::max_corrective_velocity`, like contacts always did: a deep limit
violation recovers over a few steps instead of catapulting the bodies.
- Python: `DynamicRayCastVehicleController.update_vehicle` now excludes the chassis body from
the suspension raycasts by default (their origins sit on its own collider).

### Modified

- The broad phase no longer creates pairs between colliders attached to the same
rigid-body (they can never collide). A single awake body carrying thousands of
mutually-overlapping colliders previously flooded the pair map and contact graph with
pairs the narrow phase re-discarded every step; the issue #970 scene (2,210 convex
colliders on one always-awake body) steps ~90x faster while stationary and ~18x
faster while moving. Note that `NarrowPhase::contact_pair` now returns `None` for
same-parent colliders instead of a manifold-less pair.
- Sleep eligibility is now judged on the actual per-step pose displacement (measured at the
body’s farthest point) instead of the velocities: a body held in place by contacts can sleep
even with residual solver velocities, while a body creeping through solver position
Expand Down
Loading
Loading