Skip to content

Free-body angular friction defaults

14 · Contact and friction

A ball spinning at 25 rad/s and a ball rolling at 1 m/s, as in the spin and rolling friction tutorial. Instead of setting steel:condim on every shape, SolverSteel(free_rigid_torsional_friction=..., free_rigid_rolling_friction=...) gives every free-body contact a spin and a rolling coefficient [m] with one argument each, replacing the shapes' condim gate and coefficients for that coefficient only.

0.00 s

What to look at

  • Default (condim 3, no override): the spinner still turns at 20.5 rad/s after 4 s and the roller rolls at 0.67 m/s.
  • Per-shape condim 6 and the override 0.01 / 0.004 m stop both balls identically: spin by 0.67 s, roll by 2.0 s.
  • Tripling the override stops them in 0.23 s and 0.7 s.

Key parameter

free_rigid_torsional_friction / free_rigid_rolling_friction

Coefficients in metres for every free-body contact; None (default) keeps the shapes' condim and coefficients. Experimental, construction-only.

Try changing

  • Sweep the rolling coefficient from 0.001 to 0.02 m in a loop of solvers.
  • Set only the torsional override and leave rolling to the shapes.

Concept

Solver-wide angular friction

What it is

Spin and rolling rows are normally opt-in per shape through steel:condim, with coefficients from shape_material_mu_torsional and _rolling. The two constructor overrides turn the rows on for every free-body contact and use one coefficient each, without touching shape data.

In this scene

The overrides reproduce per-shape condim 6 exactly (both stop in 0.67 s and 2.0 s); three times the coefficients stop the balls about three times sooner.

Considerations

Cost
Up to six rows per contact instead of three, as with condim 6; size free_body_constraint_max for it.
Interactions
Free-body contacts only; articulated links use steel:min_torsional_patch_radius. Requires the interleaved row schedule, fixed_normal friction and no contact compliance.
Accuracy
One coefficient for all shapes: mixed materials need per-shape values.
Use it when
Sweeping or randomizing rolling resistance, or using assets you cannot edit.

The script

cd tutorials && python 14_free_angular_defaults.py prints each variant's final metric; --record out.json also writes the replay. Every tutorial imports replay.py from the same folder.

tutorials/14_free_angular_defaults.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Model-wide spin and rolling friction for free bodies with one SolverSteel argument."""
import newton
import numpy as np
import warp as wp
from newton.solvers import SolverSteel
from replay import Recorder
DT = 1.0 / 240.0
SUBSTEPS = 8
def build(condim=3):
builder = newton.ModelBuilder()
SolverSteel.register_custom_attributes(builder)
cfg = newton.ModelBuilder.ShapeConfig(
density=800.0, mu=0.6, mu_torsional=0.01, mu_rolling=0.004
)
builder.add_ground_plane(cfg=cfg)
spinner = builder.add_body(xform=wp.transform((0.0, 0.0, 0.08), wp.quat_identity()))
builder.add_shape_sphere(spinner, radius=0.08, cfg=cfg)
marker = newton.ModelBuilder.ShapeConfig(density=0.0, has_shape_collision=False)
builder.add_shape_box(
spinner,
xform=wp.transform((0.0, 0.0, 0.08), wp.quat_identity()),
hx=0.06,
hy=0.008,
hz=0.004,
cfg=marker,
)
roller = builder.add_body(xform=wp.transform((-0.6, 0.35, 0.08), wp.quat_identity()))
builder.add_shape_sphere(roller, radius=0.08, cfg=cfg)
model = builder.finalize()
model.steel.condim.fill_(condim)
# spin one ball about the vertical, roll the other at 1 m/s
model.joint_qd.assign(np.array([0, 0, 0, 0, 0, 25.0, 1.0, 0, 0, 0, 0, 0], dtype=np.float32))
return model
def simulate(rec, label, condim=3, **options):
model = build(condim)
solver = SolverSteel(model, **options)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
newton.eval_fk(model, model.joint_q, model.joint_qd, state_0)
run = rec.variant(label, model)
for _ in range(rec.frames(4.0)):
qd = state_0.body_qd.numpy()
run.frame(state_0, spin=qd[0, 5], roll=np.linalg.norm(qd[1, :3]))
for _ in range(SUBSTEPS):
pipeline.collide(state_0, contacts)
solver.step(state_0, state_1, control, contacts, DT)
state_0, state_1 = state_1, state_0
run.check(solver)
if __name__ == "__main__":
rec = Recorder()
simulate(rec, "condim 3, no override (default)")
simulate(rec, "per-shape steel:condim 6", condim=6)
# one argument per coefficient, for every free-body contact, without touching shape data
simulate(
rec,
"free_rigid_*_friction = 0.01 / 0.004",
free_rigid_torsional_friction=0.01,
free_rigid_rolling_friction=0.004,
)
simulate(
rec,
"override 0.03 / 0.012 (3x)",
free_rigid_torsional_friction=0.03,
free_rigid_rolling_friction=0.012,
)
rec.save()

Recorded with SolverSteel on Newton 1.9.0.dev0, Warp 1.19.0, NVIDIA RTX PRO 6000 Blackwell Server Edition. Replays read the state back every frame, so they are not a speed benchmark. Script · page text