Skip to main content

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 crates3D, through the nexus3d module. 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.

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
info

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.

Installing Nexus with pip​

The Python package is named dimforge-nexus3d, whereas the module imported by your code is named nexus3d:

python -m pip install dimforge-nexus3d

The plain nexus3d package on PyPI is an unrelated project. Make sure to install dimforge-nexus3d. The bindings target CPython 3.9 or newer. If a wheel is not available for your platform, build the bindings from the Nexus repository after installing Cargo and the shader compiler:

cargo install cargo-gpu --version 0.10.0
cargo gpu install --auto-install-rust-toolchain
python -m venv .venv
source .venv/bin/activate
python -m pip install maturin
maturin develop --release -m crates/nexus_python3d/Cargo.toml

The activation command above is for a Unix shell. On Windows, activate .venv with the script in its Scripts directory.

note

Only the 3D engine is exposed to Python. The Python bindings do not expose every low-level Rust API. This guide indicates these differences where they matter.

Basic simulation example​

Here is a complete src/main.rsmain.py file. The RigidBodyBuilder and ColliderBuilder describe the scene. The NexusState contains that scene, and the NexusPipeline advances it.

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(())
})
}
from nexus3d import (
NexusViewer, NexusPipeline, NexusState, RbdCoupling,
RigidBodyBuilder, ColliderBuilder, Vec3, Pose,
)


def main():
viewer = NexusViewer().with_running()
viewer.init_backend()
state = NexusState()
pipeline = NexusPipeline()

# Create the ground.
ground = RigidBodyBuilder.fixed().build()
collider = ColliderBuilder.cuboid(5.0, 0.1, 5.0).build()
handle = state.insert_rigid_body(ground, collider, RbdCoupling.NONE)
viewer.insert_shape(handle, collider.shared_shape(), Pose.IDENTITY)

# Create the falling ball.
ball = RigidBodyBuilder.dynamic().translation(Vec3(0.0, 3.0, 0.0)).build()
collider = ColliderBuilder.ball(0.5).build()
handle = state.insert_rigid_body(ball, collider, RbdCoupling.NONE)
viewer.insert_shape(handle, collider.shared_shape(), Pose.IDENTITY)

state.finalize(viewer)
viewer.add_directional_light(Vec3(1.0, -2.0, 3.0))
while viewer.render_frame():
if viewer.simulating():
pipeline.simulate(viewer, state)
viewer.sync(state)


if __name__ == "__main__":
main()

Run the example with cargo run --releasepython main.py. 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.

info

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. Run NexusViewer on the main thread. A normal Python script is the recommended starting point.