Skip to content

Rigid Body Simulation

Overview

NovaPhy's rigid body pipeline handles free-floating bodies with collision detection and constraint resolution.

Creating Bodies

import numpy as np
import novaphy

builder = novaphy.ModelBuilder()

# Ground plane
builder.add_ground_plane(y=0.0)

# Free body; mass and inertia are inferred from shape density at finalize().
idx = builder.add_body(
    novaphy.Transform.from_translation(
        np.array([0.0, 5.0, 0.0], dtype=np.float32)
    )
)
builder.add_shape_box(
    idx,
    xform=novaphy.Transform.identity(),
    hx=0.5,
    hy=0.5,
    hz=0.5,
    cfg=novaphy.ShapeConfig(density=1.0, mu=0.5),
)

model = builder.finalize()

Collision Detection

Broadphase

Filters candidate collision pairs using axis-aligned bounding boxes:

  • Explicit — uses shape pairs precomputed by the builder
  • Sweep and Prune (SAP) — sort-based broadphase for larger scenes
  • NXN / all-pairs — simple AABB checks for small scenes and tests

Narrowphase

Generates exact contact points for each candidate pair:

Pair Algorithm
Sphere-Sphere Analytic distance check
Sphere-Plane Signed distance to plane
Box-Sphere Closest point on box
Box-Plane Vertex projection
Box-Box Separating Axis Theorem (analytic contacts)
Convex-Convex contact core

Run collision explicitly before stepping a solver:

pipeline = novaphy.CollisionPipeline(model, broad_phase="sap")
contacts = pipeline.contacts()

state.clear_forces()
pipeline.collide(state, contacts)
solver.step(state, state, control, contacts, dt)

Solvers

SolverSemiImplicit (Default)

Sequential Impulse solver with:

  • Warm starting from previous frame
  • Accumulated impulse clamping
  • Coulomb friction (tangential impulse <= mu * normal impulse)
  • Baumgarte stabilization for penetration correction

SolverXPBD

Extended Position Based Dynamics with compliance-based constraints. Operates in maximal coordinates.

python python/demos/demo_pyramid_ball.py --solver xpbd
python python/demos/xpbd/demo_xpbd_stack.py

Modifying State

State is owned by the caller. For cross-device-safe pose updates, read all poses with get_transforms_numpy(), edit those arrays, then call set_transforms_numpy(positions, quaternions). Use the per-body velocity setters for velocity updates.

Python binding limitation

state.body_q, state.body_qd, and state.transforms are writable views or references only for CPU state. CUDA access returns host snapshots. Also, changing a CPU state.transforms entry alone does not synchronize the flat state.body_q buffer. Prefer the synchronized bulk pose setter and the dedicated velocity setters:

state = model.state()

positions, quaternions = state.get_transforms_numpy()
positions[0] = np.array([0.0, 2.0, 0.0], dtype=np.float32)
state.set_transforms_numpy(positions, quaternions)

# Set linear / angular velocity for body index 0.
state.set_linear_velocity(0, np.array([1.0, 0.0, 0.0], dtype=np.float32))
state.set_angular_velocity(0, np.array([0.0, 0.0, 1.0], dtype=np.float32))

# Read back without relying on a mutable list binding.
positions, quaternions = state.get_transforms_numpy()

Demos

Demo Description
demo_pyramid_ball.py Pyramid stack with sphere projectile
demo_friction_ramp.py Boxes on a 30-degree ramp
demo_wall_break.py Wall hit by a sphere
demo_dominoes.py Chain reaction dominos
demo_unified_collision.py Gallery of all collision pairs