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