Getting started
Nexus is a physics engine for simulating rigid-bodies and deformable materials with compute shaders.
It is available in 2D and 3D, through the nexus2d and nexus3d crates. This page shows how to set up a simple simulation with a ball falling on a fixed ground.
The guide describes the APIs in the 0.7 source tree; an older installed release may expose different methods.
The following pages separate rigid-body dynamics from
MPM particles and materials, with simulation state and viewer
covering the common workflow.
Setting up Nexus with Cargo
Nexus relies on the official Rust package manager Cargo for dependency resolution and compilation.
The rbd feature enables rigid-body dynamics, and the mpm feature enables the Material Point Method.
Keep in mind that these two features are not enabled by default.
Nexus uses Rust-GPU to compile its compute shaders. Before building your project, install the shader compiler and the toolchain it needs:
cargo install cargo-gpu --version 0.10.0
cargo gpu install --auto-install-rust-toolchain
Then create a new project and add the dependencies corresponding to the dimension you want to simulate.
NexusViewer is optional for Nexus itself, but is used by the example below.
- Example 2D
- Example 3D
cargo new example-using-nexus
cd example-using-nexus
cargo add nexus2d@0.7 --features rbd,mpm
cargo add nexus_viewer2d@0.7 pollster@1 anyhow@1
cargo new example-using-nexus
cd example-using-nexus
cargo add nexus3d@0.7 --features rbd,mpm
cargo add nexus_viewer3d@0.7 pollster@1 anyhow@1
The nexus2d::rapier and nexus3d::rapier modules re-export the version of Rapier used by Nexus.
Using this re-export avoids mixing different versions of
RigidBodyBuilder and
ColliderBuilder. Their concepts and
builder settings are documented in the Rapier user-guide. Nexus uses its own NexusPipeline, so not every Rapier
setting is implemented by Nexus.
Basic simulation example
Here is a complete src/main.rs file. The
RigidBodyBuilder and
ColliderBuilder describe the scene. The NexusState contains that scene, and the NexusPipeline advances it.
- Example 2D
- Example 3D
use nexus_viewer2d::NexusViewer;
use nexus2d::prelude::{NexusPipeline, NexusState, RbdCoupling};
use nexus2d::rapier::prelude::*;
fn main() -> anyhow::Result<()> {
pollster::block_on(async {
let mut viewer = NexusViewer::new(Vec::new()).await.with_running();
viewer.init_backend();
let mut state = NexusState::default();
let mut pipeline = NexusPipeline::default();
/* Create the ground. */
let ground = RigidBodyBuilder::fixed().build();
let collider = ColliderBuilder::cuboid(5.0, 0.1).build();
let shape = collider.shared_shape().clone();
let handle = state.insert_rigid_body(ground, collider, RbdCoupling::None);
viewer.insert_shape(handle, &shape, Pose::IDENTITY);
/* Create the falling ball. */
let ball = RigidBodyBuilder::dynamic()
.translation(Vec2::new(0.0, 3.0))
.build();
let collider = ColliderBuilder::ball(0.5).build();
let shape = collider.shared_shape().clone();
let handle = state.insert_rigid_body(ball, collider, RbdCoupling::None);
viewer.insert_shape(handle, &shape, Pose::IDENTITY);
/* Upload the scene before rendering it. */
state.finalize(viewer.backend()).await?;
while viewer.render_frame().await {
if viewer.simulating() {
pipeline
.simulate(viewer.backend(), &mut state, None)
.await?;
}
viewer.sync(&mut state, None).await?;
}
Ok(())
})
}
use nexus_viewer3d::NexusViewer;
use nexus3d::prelude::{NexusPipeline, NexusState, RbdCoupling};
use nexus3d::rapier::prelude::*;
fn main() -> anyhow::Result<()> {
pollster::block_on(async {
let mut viewer = NexusViewer::new(Vec::new()).await.with_running();
viewer.init_backend();
let mut state = NexusState::default();
let mut pipeline = NexusPipeline::default();
/* Create the ground. */
let ground = RigidBodyBuilder::fixed().build();
let collider = ColliderBuilder::cuboid(5.0, 0.1, 5.0).build();
let shape = collider.shared_shape().clone();
let handle = state.insert_rigid_body(ground, collider, RbdCoupling::None);
viewer.insert_shape(handle, &shape, Pose::IDENTITY);
/* Create the falling ball. */
let ball = RigidBodyBuilder::dynamic()
.translation(Vec3::new(0.0, 3.0, 0.0))
.build();
let collider = ColliderBuilder::ball(0.5).build();
let shape = collider.shared_shape().clone();
let handle = state.insert_rigid_body(ball, collider, RbdCoupling::None);
viewer.insert_shape(handle, &shape, Pose::IDENTITY);
/* Upload the scene before rendering it. */
state.finalize(viewer.backend()).await?;
viewer
.scene3d_mut()
.add_directional_light(Vec3::new(1.0, -2.0, 3.0));
while viewer.render_frame().await {
if viewer.simulating() {
pipeline
.simulate(viewer.backend(), &mut state, None)
.await?;
}
viewer.sync(&mut state, None).await?;
}
Ok(())
})
}
Run the example with cargo run --release.
The NexusViewer opens a window, and viewer.with_running() starts the simulation immediately.
The window's controls change the value returned by viewer.simulating(), which determines whether the loop calls pipeline.simulate.
Call viewer.init_backend() before state.finalize or pipeline.simulate. The three operations initialize the backend,
upload the scene, and advance physics respectively. See simulation state and viewer.
The example uses pollster::block_on to drive asynchronous work in a native executable. A browser application
needs an asynchronous browser entry point instead. The repository's WebAssembly demos show this setup.