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.">