Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
622cafb
feat(soft-body): add real-time soft bodies with deformable surface co…
sebcrozet Aug 23, 2026
67389b3
feat(soft-body): add the 2D and 3D demos and integration tests
sebcrozet Aug 28, 2026
257579f
feat(soft-body): add the FEM solver behind the `fem` feature, with de…
sebcrozet Aug 29, 2026
e41746b
feat(soft-body): add clusters: SoftFrame proxies, per-cluster shape m…
sebcrozet Aug 30, 2026
2e215bd
feat(testbed): add soft joints demos, mouse dragging of dynamic objec…
sebcrozet Sep 3, 2026
2c3c41c
feat(soft-body): bind collision meshes to clusters with `ColliderSet:…
sebcrozet Sep 4, 2026
af937d9
feat(soft-body): tangle prevention and detection: recovery settings, …
sebcrozet Sep 5, 2026
949f89e
feat(soft-body): tangle recovery: stand-down, expel-only gate and cro…
sebcrozet Sep 5, 2026
a361f4c
feat(soft-body): intersection-volume constraints for crossed soft and…
sebcrozet Sep 6, 2026
89f2f0b
feat(soft-body): self-repulsion guided by the fold's volume normal
sebcrozet Sep 10, 2026
c47f6a9
feat(soft-body): FEM augmented mass for every constraint, volume elem…
sebcrozet Sep 10, 2026
8fbe6a2
feat(soft-body): tear by force with a smoothed load, per-element resi…
sebcrozet Sep 10, 2026
4be54fa
feat(soft-body): plastic flow of the edge rest lengths with yield, cr…
sebcrozet Sep 10, 2026
80bffe3
feat(soft-body): tear events and pieces, blade cuts, point and radial…
sebcrozet Sep 11, 2026
3ee4747
feat(testbed): show the measured frame time and the recent step peak …
sebcrozet Sep 11, 2026
615c7a8
feat(soft-body): predictive vertex contacts against convex rigid shapes
sebcrozet Sep 11, 2026
76bf93e
feat(soft-body): measure-preserving particle splits for cracks and cu…
sebcrozet Sep 17, 2026
e49538b
feat(soft-body): never split a pinned particle; the testbed draws and…
sebcrozet Sep 17, 2026
3b20792
feat(soft-body): stress-based plasticity with rest shapes following t…
sebcrozet Sep 17, 2026
4c7b3f0
feat(testbed): soft-body stress scenes, cloth on a Keva tower, transl…
sebcrozet Sep 17, 2026
52486e6
fix(soft-body): two-sided contacts for open 2D surfaces and per-endpo…
sebcrozet Sep 17, 2026
479e5fb
fix(testbed): time frames from the renderer's return so vsync waits a…
sebcrozet Sep 17, 2026
28c4465
chore: switch to parry and glamx git dependencies
sebcrozet Sep 17, 2026
bfddf8f
chore: switch back to the published version of glamx
sebcrozet Sep 17, 2026
23ca596
feat(typescript): expose soft bodies in the JS bindings and testbeds
sebcrozet Sep 17, 2026
084e084
feat(python): expose soft bodies in the Python bindings and testbed
sebcrozet Sep 17, 2026
6f87595
docs(website): soft-body examples for the Rust and JS snippets, and t…
sebcrozet Sep 17, 2026
cc74509
chore: switch to parry 0.31
sebcrozet Sep 18, 2026
6ed93a3
feat: improve handling of the ORIENTED flag and oriented/shell soft-b…
sebcrozet Sep 18, 2026
c234c12
chore: CI fixes
sebcrozet Sep 19, 2026
a92192f
chore: small demo tweaks
sebcrozet Sep 19, 2026
4eab4e5
chore: more CI fixes
sebcrozet Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 20 additions & 4 deletions .github/workflows/rapier-ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,18 +310,34 @@ jobs:
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).
# Checks the JavaScript documentation snippets build against the in-tree bindings, so a
# snippet may use an API before it is released on npm. The snippet package depends on
# `typescript/builds/rapier{2,3}d/pkg` through a `file:` path, which this job builds first
# (the same two steps a local checkout runs).
website-javascript-snippets:
runs-on: ubuntu-latest
strategy:
matrix:
dimension: [2d, 3d]
defaults:
run:
working-directory: ./website/docs-examples/${{ matrix.dimension }}/javascript
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
workspaces: typescript
- name: Generate the binding crates
working-directory: ./typescript
run: |
npm ci
cargo run -p prepare_builds -- -d dim${{ matrix.dimension == '2d' && '2' || '3' }} -f non-deterministic
- name: Build the in-tree bindings
working-directory: ./typescript/builds/rapier${{ matrix.dimension }}
run: |
npm i
npm run build:wasm
npm run build:ts
- name: Install dependencies
working-directory: ./website/docs-examples/${{ matrix.dimension }}/javascript
run: npm ci
- name: Builds javascript snippets.
working-directory: ./website/docs-examples/${{ matrix.dimension }}/javascript
run: npm run build
2 changes: 2 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ ignore-hidden = false
[default]
extend-ignore-re = [
"\\bPolygon::PN\\b",
# Index notation in the strain-constraint docs: `m_ba`, `ε_ba` (the b,a entry).
"_ba\\b",
]

# Case sensitive, matches entire word.
Expand Down
19 changes: 10 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@ needless_lifetimes = "allow"
[workspace.dependencies]
# Core math
nalgebra = { version = "0.35", default-features = false, features = ["macros"] }
glamx = { version = "0.3", default-features = false }
glamx = { version = "0.3.1", default-features = false }
simba = { version = "0.10.2", default-features = false }
num-traits = { version = "0.2", default-features = false }
approx = { version = "0.5", default-features = false }

# Parry (each crate picks its own variant)
parry2d = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry3d = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry2d-f64 = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry3d-f64 = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry2d = { version = "0.31.1", default-features = false, features = ["required-features"] }
parry3d = { version = "0.31.1", default-features = false, features = ["required-features"] }
parry2d-f64 = { version = "0.31.1", default-features = false, features = ["required-features"] }
parry3d-f64 = { version = "0.31.1", default-features = false, features = ["required-features"] }

# Utilities
arrayvec = { version = "0.7", default-features = false }
Expand Down Expand Up @@ -159,10 +159,11 @@ tempfile = "3"

#kiss3d = { git = "https://github.com/sebcrozet/kiss3d", branch = "render-2d" }
#nalgebra = { git = "https://github.com/dimforge/nalgebra", branch = "dev" }
#parry2d = { git = "https://github.com/dimforge/parry", branch = "master" }
#parry3d = { git = "https://github.com/dimforge/parry", branch = "master" }
#parry2d-f64 = { git = "https://github.com/dimforge/parry", branch = "master" }
#parry3d-f64 = { git = "https://github.com/dimforge/parry", branch = "master" }
#glamx = { git = "https://github.com/dimforge/glamx", branch = "fix-lerp2" }
#parry2d = { git = "https://github.com/dimforge/parry", branch = "soft-bodies" }
#parry3d = { git = "https://github.com/dimforge/parry", branch = "soft-bodies" }
#parry2d-f64 = { git = "https://github.com/dimforge/parry", branch = "soft-bodies" }
#parry3d-f64 = { git = "https://github.com/dimforge/parry", branch = "soft-bodies" }

# See https://github.com/EmbarkStudios/puffin/pull/234
puffin_egui = { git = "https://github.com/tedsteen/puffin.git", rev = "11771ebe00fd257aedbb545df3339ad597b1cc34" }
Expand Down
10 changes: 10 additions & 0 deletions crates/rapier2d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,23 @@ alloc = [
std = [
"alloc",
"parry2d-f64/std",
# Needed by `SoftBodyBuilder::volumetric`, for the Delaunay refinement. Kept out of
# `alloc`: spade's `robust` dependency does not build without the standard library.
"parry2d-f64/spade",
"nalgebra/std",
"simba/std",
"num-traits/std",
"approx/std",
"thiserror/std",
"serde?/std",
]
# Adds an alternative FEM solver for soft bodies, selected per body with
# `SoftBodyBuilder::solver(SoftBodySolver::Fem)`. Instead of resolving elasticity
# as sequential impulse rows, the FEM path assembles the element forces and
# tangent stiffness of a strain-energy density and takes one implicit Euler step
# per substep, solving `(M + h D + h^2 K) dv = b` over the whole body. Off by
# default: it adds per-body solver state and is still experimental.
fem = []
parallel = ["dep:rayon", "std", "parry2d-f64/parallel"]
# Drops the `Sync` requirement from callbacks (`PhysicsHooks`, `EventHandler`).
# As a side effect, this also removes the dedicated threadpool API from the physics world
Expand Down
10 changes: 10 additions & 0 deletions crates/rapier2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ alloc = [
std = [
"alloc",
"parry2d/std",
# Needed by `SoftBodyBuilder::volumetric`, for the Delaunay refinement. Kept out of
# `alloc`: spade's `robust` dependency does not build without the standard library.
"parry2d/spade",
"nalgebra/std",
"simba/std",
"num-traits/std",
Expand All @@ -48,6 +51,13 @@ std = [
"wide/std",
"serde?/std",
]
# Adds an alternative FEM solver for soft bodies, selected per body with
# `SoftBodyBuilder::solver(SoftBodySolver::Fem)`. Instead of resolving elasticity
# as sequential impulse rows, the FEM path assembles the element forces and
# tangent stiffness of a strain-energy density and takes one implicit Euler step
# per substep, solving `(M + h D + h^2 K) dv = b` over the whole body. Off by
# default: it adds per-body solver state and is still experimental.
fem = []
parallel = ["dep:rayon", "std", "parry2d/parallel"]
# Drops the `Sync` requirement from callbacks (`PhysicsHooks`, `EventHandler`).
# As a side effect, this also removes the dedicated threadpool API from the physics world
Expand Down
3 changes: 3 additions & 0 deletions crates/rapier2d/tests/ccd_default_vs_fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Harness {
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
Expand All @@ -26,6 +27,7 @@ impl Harness {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
Expand All @@ -48,6 +50,7 @@ impl Harness {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&(),
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier2d/tests/ccd_oriented_wall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn fires_through(wall: ColliderBuilder, speed: f32) -> f32 {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut soft_bodies = SoftBodySet::new();
let mut pipeline = PhysicsPipeline::new();
let mut bf = BroadPhaseBvh::new();
let mut nf = NarrowPhase::new();
Expand Down Expand Up @@ -37,6 +38,7 @@ fn fires_through(wall: ColliderBuilder, speed: f32) -> f32 {
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut soft_bodies,
&mut ccd,
&(),
&(),
Expand Down
5 changes: 5 additions & 0 deletions crates/rapier2d/tests/ccd_semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ impl EventHandler for EventCollector {
_total_force_magnitude: Real,
) {
}

fn handle_soft_body_tear_event(&self, _soft_bodies: &SoftBodySet, _event: &SoftBodyTearEvent) {}
}

struct Harness {
bodies: RigidBodySet,
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
Expand All @@ -57,6 +60,7 @@ impl Harness {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
Expand All @@ -80,6 +84,7 @@ impl Harness {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&self.events,
Expand Down
3 changes: 3 additions & 0 deletions crates/rapier2d/tests/issue_629_large_ground_stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct Harness {
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
Expand All @@ -27,6 +28,7 @@ impl Harness {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
Expand All @@ -48,6 +50,7 @@ impl Harness {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&(),
Expand Down
3 changes: 3 additions & 0 deletions crates/rapier2d/tests/issue_643_polyline_corner_stuck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct Harness {
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
Expand All @@ -28,6 +29,7 @@ impl Harness {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
Expand All @@ -49,6 +51,7 @@ impl Harness {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&(),
Expand Down
3 changes: 3 additions & 0 deletions crates/rapier2d/tests/issue_669_polyline_ghost_collisions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct Harness {
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
pipeline: PhysicsPipeline,
bf: BroadPhaseBvh,
nf: NarrowPhase,
Expand All @@ -27,6 +28,7 @@ impl Harness {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
pipeline: PhysicsPipeline::new(),
bf: BroadPhaseBvh::new(),
nf: NarrowPhase::new(),
Expand All @@ -48,6 +50,7 @@ impl Harness {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ fn sliding_cuboid_contact_reporting_does_not_panic() {

let (collision_send, collision_recv) = channel();
let (force_send, force_recv) = channel();
let events = ChannelEventCollector::new(collision_send, force_send);
let (tear_send, _tear_recv) = channel();
let events = ChannelEventCollector::new(collision_send, force_send, tear_send);

let mut num_collision_events = 0;
for _ in 0..300 {
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier2d/tests/issue_752_oneway_platform_normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn oneway_platform_with_custom_normal() {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut soft_bodies = SoftBodySet::new();
let mut pipeline = PhysicsPipeline::new();
let mut bf = BroadPhaseBvh::new();
let mut nf = NarrowPhase::new();
Expand Down Expand Up @@ -64,6 +65,7 @@ fn oneway_platform_with_custom_normal() {
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut soft_bodies,
&mut ccd,
&hooks,
&(),
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier2d/tests/issue_772_pin_slot_joint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn pin_slot_joint_allows_one_translation_and_free_rotation() {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut soft_bodies = SoftBodySet::new();
let mut pipeline = PhysicsPipeline::new();
let mut bf = BroadPhaseBvh::new();
let mut nf = NarrowPhase::new();
Expand Down Expand Up @@ -41,6 +42,7 @@ fn pin_slot_joint_allows_one_translation_and_free_rotation() {
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut soft_bodies,
&mut ccd,
&(),
&(),
Expand Down
4 changes: 3 additions & 1 deletion crates/rapier2d/tests/issue_818_nan_tangent_impulse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fn capsule_on_tilted_cuboid_reports_finite_impulses() {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut soft_bodies = SoftBodySet::new();
let mut pipeline = PhysicsPipeline::new();
let mut broad_phase = DefaultBroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
Expand Down Expand Up @@ -57,13 +58,14 @@ fn capsule_on_tilted_cuboid_reports_finite_impulses() {
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut soft_bodies,
&mut ccd,
&(),
&(),
);

for pair in narrow_phase.contact_pairs() {
for manifold in &pair.manifolds {
for manifold in pair.manifolds() {
for pt in &manifold.points {
assert!(
pt.data.impulse.is_finite(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn rebound(e: f32, prediction_distance: Option<f32>) -> f32 {
let mut narrow_phase = NarrowPhase::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut soft_bodies = SoftBodySet::new();
let mut ccd = CCDSolver::new();
let mut params = IntegrationParameters::default();
if let Some(pred) = prediction_distance {
Expand All @@ -51,6 +52,7 @@ fn rebound(e: f32, prediction_distance: Option<f32>) -> f32 {
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut soft_bodies,
&mut ccd,
&(),
&(),
Expand Down
4 changes: 4 additions & 0 deletions crates/rapier2d/tests/miri_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct World {
colliders: ColliderSet,
impulse_joints: ImpulseJointSet,
multibody_joints: MultibodyJointSet,
soft_bodies: SoftBodySet,
islands: IslandManager,
broad_phase: DefaultBroadPhase,
narrow_phase: NarrowPhase,
Expand All @@ -38,6 +39,7 @@ impl World {
colliders: ColliderSet::new(),
impulse_joints: ImpulseJointSet::new(),
multibody_joints: MultibodyJointSet::new(),
soft_bodies: SoftBodySet::new(),
islands: IslandManager::new(),
broad_phase: DefaultBroadPhase::new(),
narrow_phase: NarrowPhase::new(),
Expand All @@ -59,6 +61,7 @@ impl World {
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.soft_bodies,
&mut self.ccd,
&(),
&(),
Expand Down Expand Up @@ -216,6 +219,7 @@ fn body_removal_midrun() {
&mut w.colliders,
&mut w.impulse_joints,
&mut w.multibody_joints,
&mut w.soft_bodies,
true,
);
w.run(60);
Expand Down
Loading
Loading