Introduction to Newton Physics#
The main components for a Newton simulation are:
Model: Encapsulates the physical structure, parameters, and configuration.
State: Represents the dynamic state (positions, velocities, etc.).
Solver: Steps the simulation by integrating physics and resolving contact, joint, and other constraints on the simulated objects.
See also the Overview page.
In this tutorial, we show how to set up the model and state for a simple scene of a few rigid bodies with different collision geometries falling on a ground plane, and how to step the simulation. We’ll also show how to visualize the resulting simulation.
Setup and Imports#
First, let’s import the necessary libraries:
[1]:
from pathlib import Path
import warp as wp
from pxr import Usd
import newton
# these dependencies are needed to load example assets and ingest meshes from USD
import newton.examples
import newton.usd
Warp 1.12.0.dev20260127 initialized:
CUDA driver not found or failed to initialize
Devices:
"cpu" : "x86_64"
Kernel cache:
/home/runner/.cache/warp/1.12.0.dev20260127
Warp CUDA warning: Could not find or load the NVIDIA CUDA driver. Proceeding in CPU-only mode.
Building a Model with ModelBuilder#
The ModelBuilder is the primary way to construct simulation scenes. Newton provides multiple approaches for building models:
Programmatic construction: Add bodies, shapes, joints, and constraints directly using
add_body(),add_shape_*(),add_joint_*()methodsImport from asset files: Load complete models from URDF, MJCF (MuJoCo XML), or USD files using
add_urdf(),add_mjcf(), andadd_usd()Parallel environments for RL: Use
replicate()to create multiple copies of an environment for parallel training, or combine different models withadd_builder()for modular scene construction
In this tutorial, we’ll focus on programmatic construction to understand the fundamentals. For examples showing asset import and parallelization, see newton/examples/basic/example_basic_urdf.py and newton/examples/robot/.
Let’s create a simple scene with various collision shapes falling onto a ground plane.
Step 1: Create the ModelBuilder#
[2]:
# Create a new model builder
builder = newton.ModelBuilder()
Step 2: Add Rigid Bodies with Collision Shapes#
Bodies are the fundamental dynamic entities in Newton. Each body can have one or more collision shapes attached to it.
The workflow is:
Create a body with
add_body()- returns a body indexAttach shapes to the body using
add_shape_*()methodsShapes contribute to the body’s mass/inertia
[3]:
# Add a ground plane (infinite static plane at z=0)
builder.add_ground_plane()
# Height from which to drop shapes
drop_z = 2.0
# SPHERE
sphere_pos = wp.vec3(0.0, -4.0, drop_z)
body_sphere = builder.add_body(
xform=wp.transform(p=sphere_pos, q=wp.quat_identity()),
key="sphere", # Optional: human-readable identifier
)
builder.add_shape_sphere(body_sphere, radius=0.5)
# CAPSULE
capsule_pos = wp.vec3(0.0, -2.0, drop_z)
body_capsule = builder.add_body(xform=wp.transform(p=capsule_pos, q=wp.quat_identity()), key="capsule")
builder.add_shape_capsule(body_capsule, radius=0.3, half_height=0.7)
# CYLINDER
cylinder_pos = wp.vec3(0.0, 0.0, drop_z)
body_cylinder = builder.add_body(xform=wp.transform(p=cylinder_pos, q=wp.quat_identity()), key="cylinder")
builder.add_shape_cylinder(body_cylinder, radius=0.4, half_height=0.6)
# Multi-Shape Collider
multi_shape_pos = wp.vec3(0.0, 2.0, drop_z)
body_multi_shape = builder.add_body(xform=wp.transform(p=multi_shape_pos, q=wp.quat_identity()), key="multi_shape")
# Now attach both a sphere and a box to the multi-shape body
# body-local shape offsets, offset sphere in x so the body will topple over
sphere_offset = wp.vec3(0.1, 0.0, -0.3)
box_offset = wp.vec3(0.0, 0.0, 0.3)
builder.add_shape_sphere(body_multi_shape, wp.transform(p=sphere_offset, q=wp.quat_identity()), radius=0.25)
builder.add_shape_box(body_multi_shape, wp.transform(p=box_offset, q=wp.quat_identity()), hx=0.25, hy=0.25, hz=0.25)
print(f"Added {builder.body_count} bodies with collision shapes")
Added 4 bodies with collision shapes
Step 3: Add a Mesh Body#
Newton can also simulate bodies with triangle-mesh collision shapes. Let’s load a mesh from a USD file:
[4]:
# Load a mesh from a USD file
usd_stage = Usd.Stage.Open(newton.examples.get_asset("bunny.usd"))
demo_mesh = newton.usd.get_mesh(usd_stage.GetPrimAtPath("/root/bunny"))
# Add the mesh as a rigid body
mesh_pos = wp.vec3(0.0, 4.0, drop_z - 0.5)
body_mesh = builder.add_body(xform=wp.transform(p=mesh_pos, q=wp.quat(0.5, 0.5, 0.5, 0.5)), key="bunny")
builder.add_shape_mesh(body_mesh, mesh=demo_mesh)
print(f"Added mesh body with {demo_mesh.vertices.shape[0]} vertices")
Module newton._src.geometry.inertia 8e8f74f load on device 'cpu' took 2453.57 ms (compiled)
Added mesh body with 6102 vertices
Step 4: Finalize the Model#
Once all bodies and shapes are added, we finalize the model. This converts the Python data structures into GPU-optimized arrays and makes the model ready for simulation.
Newton runs on GPU by default (if a GPU is available); you may force the compute device to CPU by setting use_cpu to True below.
[5]:
# Optional: Run the simulation on CPU
use_cpu = False
if use_cpu:
wp.set_device("cpu") # alternatively, pass device="cpu" to the finalize method
# Finalize the model - this creates the simulation-ready Model object
model = builder.finalize()
print(f"Model finalized for device {model.device}:")
print(f" Bodies: {model.body_count}")
print(f" Shapes: {model.shape_count}")
print(f" Joints: {model.joint_count}")
Module validate_and_correct_inertia_kernel_1911a1c2 f78b133 load on device 'cpu' took 2029.28 ms (compiled)
Module count_contact_points_6b041b6e 383e490 load on device 'cpu' took 1999.19 ms (compiled)
Model finalized for device cpu:
Bodies: 5
Shapes: 7
Joints: 5
Creating States and Control#
After finalizing the model, we can create the objects that hold time-varying data, i.e. the data that is changing with each simulation step.
State: Holds positions, velocities, and forces
Control: Holds user-set control inputs (joint torques, motor commands, etc.)
Contacts: Holds contacts generated by the collision pipeline for processing in the solver
Some solvers rely on input and output state to be separated in memory when running the solver.step() method, including XPBD. The MuJoCo solver, on the other hand, can be run with just a single state passed in as both input and output states.
Note that for differentiable simulations you may need to allocate a new state for every substep of the simulation. To accommodate these different use cases, we leave the memory management of the State and Control objects up to the user.
[6]:
# Create two state objects for time integration
state_0 = model.state() # Current state
state_1 = model.state() # Next state
# The control object is not used in this example, but we create it for completeness
control = model.control()
# Perform initial collision detection
contacts = model.collide(state_0)
print("State and control objects created")
Module newton._src.geometry.kernels 5945aba load on device 'cpu' took 4376.64 ms (compiled)
Module generate_handle_contact_pairs_kernel__locals__handle_contact_pairs_029a8a21 d9afb26 load on device 'cpu' took 3462.95 ms (compiled)
State and control objects created
Setting Up the Solver#
Newton provides multiple solver implementations. For this example, we use XPBD (Extended Position-Based Dynamics).
For other available solvers and their features/strengths, please refer to the Solvers feature overview.
[7]:
# Create the XPBD solver with 10 constraint iterations
solver = newton.solvers.SolverXPBD(model, iterations=10)
print(f"Solver created: {type(solver).__name__}")
Solver created: SolverXPBD
Configuring the Simulation Loop#
Let’s set up the simulation parameters and create a simulation function:
[8]:
# Simulation parameters
fps = 60 # Frames per second for visualization
frame_dt = 1.0 / fps # Time step per frame
sim_substeps = 10 # Number of physics substeps per frame
sim_dt = frame_dt / sim_substeps # Physics time step
print("Simulation configured:")
print(f" Frame rate: {fps} Hz")
print(f" Frame dt: {frame_dt:.4f} s")
print(f" Physics substeps: {sim_substeps}")
print(f" Physics dt: {sim_dt:.4f} s")
Simulation configured:
Frame rate: 60 Hz
Frame dt: 0.0167 s
Physics substeps: 10
Physics dt: 0.0017 s
The Simulation Function#
The core simulation loop executed for each substep follows this pattern:
Clear forces in
Statethat may have been set by the solver or user in the previous stepApply external forces
Detect collisions
Step the solver forward in time
Swap state buffers
[9]:
def simulate():
"""Run multiple physics substeps for one frame."""
global state_0, state_1
for _ in range(sim_substeps):
# 1. Clear forces in input state
state_0.clear_forces()
# 2. Apply control targets/forces, and viewer picking forces if using the OpenGL viewer
# update_control(state_0, control)
# viewer.apply_forces(state_0)
# 3. Detect collisions
contacts = model.collide(state_0)
# 4. Step the simulation by one physics timestep
solver.step(state_in=state_0, state_out=state_1, control=control, contacts=contacts, dt=sim_dt)
# 5. Swap states (next becomes current)
state_0, state_1 = state_1, state_0
GPU Acceleration with CUDA Graphs#
For maximum performance on CUDA devices, we can capture the simulation loop as a CUDA graph. This reduces kernel launch overhead significantly:
[10]:
# Capture the simulation as a CUDA graph (if running on GPU)
if wp.get_device().is_cuda:
with wp.ScopedCapture() as capture:
simulate()
graph = capture.graph
print("CUDA graph captured for optimized execution")
else:
graph = None
print("Running on CPU (no CUDA graph)")
Running on CPU (no CUDA graph)
Visualization#
There are several viewer types available in Newton that can be used to display and/or debug simulations, see the Visualization section in the docs.
In this example, we use the Viser viewer, which launches a web server and can be embedded in Jupyter notebooks or viewed in a browser.
[11]:
# Create the Viser viewer with a path to save the recording
recording_path = Path("../_static/recordings/00_introduction.viser").resolve()
recording_path.parent.mkdir(parents=True, exist_ok=True)
viewer = newton.viewer.ViewerViser(verbose=False, record_to_viser=str(recording_path))
# Set the model (this logs the static geometry)
viewer.set_model(model)
╭────── viser (listening *:8080) ───────╮ │ ╷ │ │ HTTP │ http://localhost:8080 │ │ Websocket │ ws://localhost:8080 │ │ ╵ │ ╰───────────────────────────────────────╯
Module newton._src.viewer.kernels ead5335 load on device 'cpu' took 3067.28 ms (compiled)
Running the Simulation#
Now let’s run the simulation and visualize it! We’ll simulate 500 frames (about 8 seconds at 60 fps).
[12]:
# Run the simulation
num_frames = 500
sim_time = 0.0 # Current simulation time in seconds
for _ in range(num_frames):
# Execute the simulation (use CUDA graph if available)
if graph:
wp.capture_launch(graph)
else:
simulate()
# Log the current state to the viewer
viewer.begin_frame(sim_time)
viewer.log_state(state_0)
# Log contacts to the viewer (not supported by the Viser viewer)
viewer.log_contacts(contacts, state_0)
viewer.end_frame()
# Advance simulation time
sim_time += frame_dt
print(f"\nSimulation complete! Total time: {sim_time:.2f} seconds")
viewer
Module newton._src.solvers.xpbd.kernels fb83121 load on device 'cpu' took 11416.69 ms (compiled)
Module newton._src.solvers.solver dc85a44 load on device 'cpu' took 2444.44 ms (compiled)
Simulation complete! Total time: 8.33 seconds
Next Steps#
To learn more about Newton:
Examples: Explore the
newton/examples/directory for more complex scenariosDocumentation: Visit newton-physics.github.io