Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
143 changes: 143 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
44 changes: 22 additions & 22 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ members = [
resolver = "2"

[workspace.package]
version = "0.4.0"
version = "0.5.0"
authors = ["Sébastien Crozet <sebcrozet@dimforge.com>"]
edition = "2024"
license = "MIT OR Apache-2.0"
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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 = [
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion crates/examples2d/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nexus_examples_2d"
version = "0.4.0"
version = "0.5.0"
authors = ["Sébastien Crozet <developer@crozet.re>"]
edition = { workspace = true }
publish = false
Expand Down
2 changes: 1 addition & 1 deletion crates/examples3d/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nexus_examples_3d"
version = "0.4.0"
version = "0.5.0"
authors = ["Sébastien Crozet <developer@crozet.re>"]
edition = { workspace = true }
publish = false
Expand Down
2 changes: 1 addition & 1 deletion crates/nexus_viewer2d/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nexus_viewer2d"
version = "0.4.0"
version = "0.5.0"
authors = ["Sébastien Crozet <developer@crozet.re>"]
description = "Viewer for 2D GPU physics demos with nexus."
edition = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/nexus_viewer3d/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nexus_viewer3d"
version = "0.4.0"
version = "0.5.0"
authors = ["Sébastien Crozet <developer@crozet.re>"]
description = "Viewer for 3D GPU physics demos with nexus."
edition = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions website/src/pages/demos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
];
Expand Down
25 changes: 14 additions & 11 deletions website/src/pages/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
14 changes: 10 additions & 4 deletions website/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ function Intro(): ReactNode {
</p>
<p>
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.
</p>
<div className={styles.codeLinks}>
<Link
Expand Down Expand Up @@ -100,6 +101,11 @@ function Features(): ReactNode {
<h3>Rigid-Body Dynamics</h3>
<p>Boxes, balls, convex shapes, trimeshes, heightfields, joints, and multibodies.</p>
</div>
<div className={styles.feature}>
<span className={styles.featureIcon}>🏖️</span>
<h3>Material Point Method</h3>
<p>Deformable objects, granular materials, and fluids, coupled with rigid-bodies.</p>
</div>
<div className={styles.feature}>
<span className={styles.featureIcon}>🤖</span>
<h3>Robotics-Ready</h3>
Expand All @@ -120,7 +126,7 @@ export default function Home(): ReactNode {
return (
<Layout
title="Cross-platform GPU multiphysics simulation for Rust"
description="Nexus is a cross-platform GPU-accelerated multiphysics engine for Rust: rigid-body dynamics running as compute shaders via WebGPU, with shaders written in Rust using Rust-GPU.">
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.">
<HomepageHeader />
<main>
<Intro />
Expand Down
Loading