From ecfc74317be92c23e227f84450591625cbacfb5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Crozet?=
Date: Sat, 15 Aug 2026 22:45:31 +0200
Subject: [PATCH] Release v0.5.0
---
CHANGELOG.md | 143 +++++++++++++++++++++++++++++
Cargo.toml | 44 ++++-----
README.md | 7 +-
crates/examples2d/Cargo.toml | 2 +-
crates/examples3d/Cargo.toml | 2 +-
crates/nexus_viewer2d/Cargo.toml | 2 +-
crates/nexus_viewer3d/Cargo.toml | 2 +-
website/src/pages/demos.tsx | 4 +-
website/src/pages/index.module.css | 25 ++---
website/src/pages/index.tsx | 14 ++-
10 files changed, 200 insertions(+), 45 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e69de29b..fe769985 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -0,0 +1,143 @@
+## v0.5.0 (16 August 2026)
+
+### Breaking changes
+
+- ⚠ `NexusState::insert_rigid_body`/`insert_rigid_body_in` take an extra `RbdCoupling` argument.
+ Pass `RbdCoupling::None` for a rigid-body-only scene.
+- ⚠ Default solver parameters changed: `contact_damping_ratio` `5.0` → `10.0`,
+ `normalized_allowed_linear_error` `0.001` → `0.005`, `normalized_max_corrective_velocity`
+ `10.0` → `3.0`, `normalized_prediction_distance` `0.002` → `0.02`.
+- ⚠ The viewer's `BackendType::Rapier` (CPU rapier reference backend) was removed. The remaining
+ backends all run the nexus pipeline: `Gpu`, `Cpu`, `Cuda`, `Metal`.
+- ⚠ Examples are prefixed by the subsystem they exercise (`boxes3` → `rbd_boxes3`). The
+ `bench_joints3`, `bench_multibody_pendulum3` and `bench_urdf3` benchmarks were removed.
+- ⚠ Python: the PyPI distribution is now `dimforge-nexus3d` (the import name stays `nexus3d`).
+- Built against rapier `0.35` and parry `0.30` (was rapier `0.34`/parry `0.29`).
+
+### Added
+
+- **`nexus_mpm`: a GPU Material Point Method solver, in 2D and 3D**, behind the `mpm` feature.
+ Particles are added and removed by chunk (`NexusState::add_particles`, `extend_chunk`,
+ `remove_chunk`), on a sparse sorted grid with substepping and a CFL timestep bound.
+- MPM constitutive models: linear and Neo-Hookean elasticity, Drucker-Prager sand (with cohesion),
+ a weakly-compressible fluid, and the Stomakhin snow model, all built through `ParticleModel`.
+- `RbdCoupling::MpmOneWay`: colliders act as moving boundaries for the particles, with per-body
+ `stick`/`slip`/`separate`/`non-reflecting` conditions and optional CPIC for thin obstacles.
+- `RbdCoupling::MpmTwoWay` hands a body over to MPM entirely: the rigid-body pipeline treats it as
+ static while MPM integrates it from the particle impulses. At most 16 coupled bodies (CPIC limit).
+- Multibody self-contacts: two links of the same multibody now collide, unless the multibody
+ disables self-contacts.
+- Restitution on multibody contacts, applied as an end-of-step pass seeded from the approach
+ velocity measured at the start of the step.
+- Per-link external forces/torques and gravity scale on multibody links
+ (`GpuMultibodySet::set_link_external_wrench`), and DOF couplings between two joint axes.
+- Multibody motors can be read back and retargeted at runtime (`GpuMultibodySet::set_motor`,
+ `set_motors`, `motor`), plus `set_num_internal_pgs_iterations` and `set_implicit_coriolis`.
+- `RbdSimParams::static_contact_natural_frequency`/`static_contact_damping_ratio`: contacts
+ touching a fixed body get their own, stiffer by default, softness coefficients.
+- `RbdSimParams::normalized_max_linear_velocity` (default `400.0` m/s) caps the linear velocity
+ after each substep so speculative contacts stay reliable. Set to `f32::MAX` to disable.
+- A brute-force O(n²) broad-phase, used instead of the LBVH for environments with at most
+ 64 colliders.
+- `NexusState::rbd_world_mut_untracked`: mutate a rapier world after `finalize` without marking
+ the GPU state dirty.
+- Viewer: `snap_rgb` frame capture, configurable resolution, a headless mode, a vsync toggle,
+ pipelined capture (`render_async`/`render_flush`) and kiss3d's GPU path tracer
+ (`raytrace_frame`), all exposed to Python (PRs #7, #8 and #11 by @haixuanTao).
+- Python bindings for MPM (`set_mpm_params`, `add_particles`, `ParticleModel`, …).
+- A `web-compat` feature on the shader crates, enabled automatically when targeting `wasm32`.
+
+### Modified
+
+- The contact solver follows rapier's TGS-soft relax pass: the unbiased normal rhs is refreshed
+ from the post-integration poses, instead of stripping CFM and bias from the constraints in place.
+- `NexusState::set_rbd_gravity` applies to free rigid-bodies and multibody links alike, and works
+ in 2D (where the third component is ignored). It used to be 3D- and multibody-only.
+- Extensive, mostly result-identical pipeline optimizations: frame-to-frame coloring, contacts
+ bucket-sorted by color, fused colored sweeps, a shared-memory multibody PGS sweep, an SoA link
+ workspace, LBVH subtree pruning, and skipping the pipelines that are provably inert.
+
+### Fixed
+
+- `RbdState::from_rapier` zero-filled the velocity buffer, dropping every body's initial linear
+ and angular velocity (PR #10 by @haixuanTao).
+- Multibody joint limits no longer emit a constraint row while the joint sits strictly inside its
+ bounds, where it can never apply an impulse (PR #14 by @haixuanTao).
+- Contact manifold reduction now matches rapier's, including its degenerate-selection guards.
+- The fused multibody solver kernels no longer place barriers under non-uniform control flow,
+ which WebGPU rejects; the loop bound is now a uniform holding the max over all multibodies.
+- Out-of-bounds writes to the polygonal-feature pair buffer, and constraint counting over the
+ padded capacity instead of the real contact count.
+- The LBVH pair traversal used a `while` loop, which naga might miscompile on macos.
+
+## v0.4.0 (04 July 2026)
+
+Complete rewrite. Nexus is now a full GPU physics engine written in
+[rust-gpu](https://github.com/Rust-GPU/rust-gpu), with everything from the broad-phase to the
+constraint solver running on the device.
+
+### Breaking changes
+
+- ⚠ Shaders are written in Rust and compiled to SPIR-V with rust-gpu, replacing Slang.
+ `slang-hal`/`stensor` are replaced by [khal](https://crates.io/crates/khal)/
+ [vortx](https://crates.io/crates/vortx), and the `comptime`/`runtime` features are gone.
+- ⚠ Backends are selected by the `webgpu` (default), `metal`, `cpu`, `cpu-parallel` and `cuda`
+ features. Shader-facing math moved from `nalgebra` to [glamx](https://crates.io/crates/glamx).
+- ⚠ `nexus2d`/`nexus3d` are now umbrella crates over `nexus_rbd2d`/`nexus_rbd3d` (behind the `rbd`
+ feature) plus `NexusState`/`NexusPipeline`. The old `dynamics::{BodyDesc, GpuBodySet, …}` API and
+ the `BodyCoupling`/`BodyCouplingEntry` types were removed.
+
+### Added
+
+- A GPU broad-phase: parallel LBVH construction over Morton codes (with a GPU radix sort and
+ prefix sum) and a bounded, stackless pair traversal.
+- A GPU narrow-phase: analytic contacts for primitive pairs, GJK/EPA with SAT-based feature
+ clipping otherwise. Balls, cuboids, capsules, cones, cylinders, convex shapes, polylines,
+ trimeshes.
+- A rigid-body solver: TGS-soft contacts with graph-colored Gauss-Seidel sweeps, cross-frame
+ warmstarting, Coulomb friction and speculative contacts, tuned by `RbdSimParams`.
+- Impulse joints: ball, fixed, prismatic and revolute, with limits and motors.
+- A reduced-coordinates multibody solver (3D): articulated-body dynamics with a per-multibody mass
+ matrix and LU solve, joint limits/motors, and loop-closing impulse joints.
+- `NexusState`/`NexusPipeline`: one rapier world per *environment*, baked into GPU buffers on
+ `finalize` and stepped in parallel. Batched environments make nexus usable as an RL simulator.
+- Incremental insertion and removal of rigid-bodies, plus capacity reservation and a resize policy
+ for the collision buffers.
+- A cross-platform viewer (`nexus_viewer2d`/`nexus_viewer3d`) built on kiss3d, with a demo picker,
+ a backend selector, per-kernel GPU timings, and a full set of 2D and 3D demos.
+- URDF and MJCF robot loading, including the MuJoCo Menagerie models.
+- Python bindings for the 3D engine and viewer (`crates/nexus_python3d`), published on PyPI.
+- A [website](https://nexus.dimforge.com) with the demos compiled to WebAssembly.
+
+## v0.3.0 (20 January 2026)
+
+### Added
+
+- `comptime` and `runtime` features to select whether the Slang shaders are compiled by the
+ crate's `build.rs` or at runtime.
+- Backend selection features: `webgpu`, `vulkan`, `metal`, `cpu` and `cuda`.
+
+### Modified
+
+- Update to `slang-hal`/`stensor` 0.3 and rapier 0.31.
+
+## v0.2.1 (27 October 2025)
+
+### Fixed
+
+- Fix the 2D build with slang-compiler 2025.19.1: the angular inertia is a scalar in 2D, so
+ applying an impulse or integrating forces must not go through `mul`.
+
+## v0.2.0 (27 October 2025)
+
+### Modified
+
+- Update to wgpu 27, `slang-hal`/`stensor` 0.2 and rapier 0.30.
+- The crate is now fully documented (`#![warn(missing_docs)]`), and `BodyCoupling`/
+ `BodyCouplingEntry` are re-exported from `nexus::dynamics`.
+
+## v0.1.0 (20 September 2025)
+
+Initial release: GPU rigid-body state (poses, velocities, forces, mass-properties) and Slang
+shaders for shapes, geometric queries (ray-casting, point projection, contacts) and force/velocity
+integration, with conversion from a rapier `RigidBodySet`/`ColliderSet`.
diff --git a/Cargo.toml b/Cargo.toml
index 15d3db0f..d2ced59a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ members = [
resolver = "2"
[workspace.package]
-version = "0.4.0"
+version = "0.5.0"
authors = ["Sébastien Crozet "]
edition = "2024"
license = "MIT OR Apache-2.0"
@@ -30,9 +30,9 @@ exclude = ["**/.DS_Store"]
[workspace.dependencies]
glamx = { version = "0.3", default-features = false, features = ["nostd-libm", "bytemuck"] }
include_dir = "0.7"
-khal-std = "0.2"
-khal = { version = "0.2", features = ["derive"]}
-vortx = { version = "0.3", features = ["unsafe_remove_boundchecks"] }
+khal-std = "0.3"
+khal = { version = "0.3", features = ["derive"]}
+vortx = { version = "0.4", features = ["unsafe_remove_boundchecks"] }
bytemuck = { version = "1", features = ["derive"] }
web-time = "1"
static_assertions = "1"
@@ -42,19 +42,19 @@ bvh = "0.7"
# Physics / geometry. default-features off for the no_std shader crates; host
# crates opt back in with `features = ["default"]`.
-rapier2d = { version = "0.35", default-features = false }
-rapier3d = { version = "0.35", default-features = false }
+rapier2d = { version = "0.35.2", default-features = false }
+rapier3d = { version = "0.35.2", default-features = false }
rapier3d-urdf = "0.35"
rapier3d-mjcf = { version = "0.35", features = ["stl", "wavefront", "msh"] }
parry2d = { version = "0.30", default-features = false }
parry3d = { version = "0.30", default-features = false }
# Viewer / examples deps
-kiss3d = "0.45.1"
+kiss3d = "0.46.0"
Inflector = "0.11"
oorandom = "11"
anyhow = "1"
-pollster = "0.4"
+pollster = "1"
getrandom = { version = "0.2", features = ["js"] }
# Dev deps
@@ -64,27 +64,27 @@ approx = "0.5"
rand = "0.9"
# Shaders deps
-khal-builder = "0.2"
-vortx-shaders = { version = "0.3", default-features = false }
+khal-builder = "0.3"
+vortx-shaders = { version = "0.4", default-features = false }
unroll = "0.1"
crunchy = "0.2.4"
# Shader crates
-nexus_rbd_shaders2d = { version = "0.4.0", path = "crates/nexus_rbd_shaders2d" }
-nexus_rbd_shaders3d = { version = "0.4.0", path = "crates/nexus_rbd_shaders3d" }
-nexus_mpm_shaders2d = { version = "0.4.0", path = "crates/nexus_mpm_shaders2d" }
-nexus_mpm_shaders3d = { version = "0.4.0", path = "crates/nexus_mpm_shaders3d" }
+nexus_rbd_shaders2d = { version = "0.5.0", path = "crates/nexus_rbd_shaders2d" }
+nexus_rbd_shaders3d = { version = "0.5.0", path = "crates/nexus_rbd_shaders3d" }
+nexus_mpm_shaders2d = { version = "0.5.0", path = "crates/nexus_mpm_shaders2d" }
+nexus_mpm_shaders3d = { version = "0.5.0", path = "crates/nexus_mpm_shaders3d" }
# Internal crates. rbd is pulled both with defaults and with default-features
# off (mpm wants just the dim feature), so it mirrors the rapier/parry pattern.
-nexus2d = { version = "0.4.0", path = "crates/nexus2d" }
-nexus3d = { version = "0.4.0", path = "crates/nexus3d" }
-nexus_rbd2d = { version = "0.4.0", path = "crates/nexus_rbd2d", default-features = false }
-nexus_rbd3d = { version = "0.4.0", path = "crates/nexus_rbd3d", default-features = false }
-nexus_mpm2d = { version = "0.4.0", path = "crates/nexus_mpm2d" }
-nexus_mpm3d = { version = "0.4.0", path = "crates/nexus_mpm3d" }
-nexus_viewer2d = { version = "0.4.0", path = "crates/nexus_viewer2d" }
-nexus_viewer3d = { version = "0.4.0", path = "crates/nexus_viewer3d" }
+nexus2d = { version = "0.5.0", path = "crates/nexus2d" }
+nexus3d = { version = "0.5.0", path = "crates/nexus3d" }
+nexus_rbd2d = { version = "0.5.0", path = "crates/nexus_rbd2d", default-features = false }
+nexus_rbd3d = { version = "0.5.0", path = "crates/nexus_rbd3d", default-features = false }
+nexus_mpm2d = { version = "0.5.0", path = "crates/nexus_mpm2d" }
+nexus_mpm3d = { version = "0.5.0", path = "crates/nexus_mpm3d" }
+nexus_viewer2d = { version = "0.5.0", path = "crates/nexus_viewer2d" }
+nexus_viewer3d = { version = "0.5.0", path = "crates/nexus_viewer3d" }
[workspace.lints]
rust.unexpected_cfgs = { level = "warn", check-cfg = [
diff --git a/README.md b/README.md
index 96ec1d28..691a7e8b 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,11 @@ multiphysics engine, running compute shaders via WebGPU. Shaders are written in
Nexus is organized into independent physics modules, each available in 2D and 3D:
-- **nexus_rbd** - Rigid-body dynamics: colliders (boxes, balls, convex shapes, trimeshes, heightfields), joints (ball, fixed,
- prismatic, revolute), contact resolution.
+- **nexus_rbd** - Rigid-body dynamics: colliders (boxes, balls, capsules, cones, cylinders, convex shapes, polylines,
+ trimeshes), joints (ball, fixed, prismatic, revolute), contact resolution.
+- **nexus_mpm** - Material Point Method: a hybrid particle/grid-based method for simulating deformable objects, granular
+ materials, fluids, etc. Supports one-way coupling with rigid-bodies (= rigid-bodies can push particles but particles
+ cannot push rigid-bodies).
## Prerequisites
diff --git a/crates/examples2d/Cargo.toml b/crates/examples2d/Cargo.toml
index 717537ee..7517291e 100644
--- a/crates/examples2d/Cargo.toml
+++ b/crates/examples2d/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nexus_examples_2d"
-version = "0.4.0"
+version = "0.5.0"
authors = ["Sébastien Crozet "]
edition = { workspace = true }
publish = false
diff --git a/crates/examples3d/Cargo.toml b/crates/examples3d/Cargo.toml
index cb2e05ca..9d60f77a 100644
--- a/crates/examples3d/Cargo.toml
+++ b/crates/examples3d/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nexus_examples_3d"
-version = "0.4.0"
+version = "0.5.0"
authors = ["Sébastien Crozet "]
edition = { workspace = true }
publish = false
diff --git a/crates/nexus_viewer2d/Cargo.toml b/crates/nexus_viewer2d/Cargo.toml
index 29b41a86..61a8741d 100644
--- a/crates/nexus_viewer2d/Cargo.toml
+++ b/crates/nexus_viewer2d/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nexus_viewer2d"
-version = "0.4.0"
+version = "0.5.0"
authors = ["Sébastien Crozet "]
description = "Viewer for 2D GPU physics demos with nexus."
edition = { workspace = true }
diff --git a/crates/nexus_viewer3d/Cargo.toml b/crates/nexus_viewer3d/Cargo.toml
index b14824c0..c5141da7 100644
--- a/crates/nexus_viewer3d/Cargo.toml
+++ b/crates/nexus_viewer3d/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "nexus_viewer3d"
-version = "0.4.0"
+version = "0.5.0"
authors = ["Sébastien Crozet "]
description = "Viewer for 3D GPU physics demos with nexus."
edition = { workspace = true }
diff --git a/website/src/pages/demos.tsx b/website/src/pages/demos.tsx
index 22368fb5..1d055d97 100644
--- a/website/src/pages/demos.tsx
+++ b/website/src/pages/demos.tsx
@@ -8,14 +8,14 @@ const demos = [
name: '3d',
demo: 'all_examples3',
title: '3D Demos',
- description: 'Rigid-body dynamics demos in 3D',
+ description: 'Rigid-body dynamics and MPM demos in 3D',
source: 'https://github.com/dimforge/nexus/tree/main/crates/examples3d',
},
{
name: '2d',
demo: 'all_examples2',
title: '2D Demos',
- description: 'Rigid-body dynamics demos in 2D',
+ description: 'Rigid-body dynamics and MPM demos in 2D',
source: 'https://github.com/dimforge/nexus/tree/main/crates/examples2d',
},
];
diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css
index 148d08fe..8b6fcd44 100644
--- a/website/src/pages/index.module.css
+++ b/website/src/pages/index.module.css
@@ -77,29 +77,32 @@
background: var(--ifm-background-surface-color);
}
+/* Flex (not grid) so a partially-filled last row stays centered. */
.featureGrid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
gap: 2rem;
}
+.feature {
+ flex: 0 1 calc(33.333% - 2rem);
+ text-align: center;
+ padding: 1.5rem;
+}
+
@media screen and (max-width: 996px) {
- .featureGrid {
- grid-template-columns: repeat(2, 1fr);
+ .feature {
+ flex-basis: calc(50% - 2rem);
}
}
@media screen and (max-width: 576px) {
- .featureGrid {
- grid-template-columns: 1fr;
+ .feature {
+ flex-basis: 100%;
}
}
-.feature {
- text-align: center;
- padding: 1.5rem;
-}
-
.feature h3 {
margin: 0.5rem 0;
}
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index b2f7737e..97c24318 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -52,9 +52,10 @@ function Intro(): ReactNode {
Rigid-body dynamics is available today, with colliders, joints,
- articulated multibodies, and URDF/MJCF robots. Other solvers —
- MPM for fluids and deformables, DEM for granular media, etc. —
- are work in progress.
+ articulated multibodies, and URDF/MJCF robots. So is MPM, for
+ deformable objects, granular materials, and fluids, with one-way
+ coupling from rigid-bodies. Other solvers — DEM for granular
+ media, FEM, etc. — are work in progress.
Rigid-Body Dynamics
Boxes, balls, convex shapes, trimeshes, heightfields, joints, and multibodies.
+
+ 🏖️
+
Material Point Method
+
Deformable objects, granular materials, and fluids, coupled with rigid-bodies.
+
🤖
Robotics-Ready
@@ -120,7 +126,7 @@ export default function Home(): ReactNode {
return (
+ description="Nexus is a cross-platform GPU-accelerated multiphysics engine for Rust: rigid-body dynamics and MPM running as compute shaders via WebGPU, with shaders written in Rust using Rust-GPU.">