Skip to content

Spin and rolling friction

06 · Contact and friction

A ball spinning at 25 rad/s about the vertical and a ball rolling at 1 m/s. A ball touches the ground at one point, so sliding friction alone stops neither. The per-shape attribute steel:condim uses MuJoCo's convention: 3 is sliding only, 4 adds a spin row bounded by μ_torsional times the normal load, and 6 also adds a rolling pair bounded by μ_rolling.

0.00 s

What to look at

  • condim 3: both balls keep going.
  • condim 4: the spinning ball stops in about a second; the rolling ball keeps rolling.
  • condim 6: both stop.

Key parameter

steel:condim

Per shape, 3, 4 or 6. Applies to free-body contacts; register it with SolverSteel.register_custom_attributes(builder) and fill model.steel.condim.

Try changing

  • Raise mu_rolling to 0.02 and watch the rolling ball stop sooner.
  • Give only the ground condim 6: a contact takes the larger coefficient of its two shapes.

Concept

Spin and rolling friction

What it is

Sliding friction acts at the contact point, so it cannot stop a ball spinning about the contact normal or rolling without slip. condim 4 adds a spin row bounded by μ_torsional·N, and condim 6 adds two rolling rows bounded by μ_rolling·N, with N the patch's pooled normal load.

In this scene

With condim 3 the spinning ball still turns at 19.5 rad/s after 5 s; condim 4 stops it in about 0.6 s, and condim 6 also stops the rolling ball in about 2 s.

Considerations

Cost
Up to six rows per contact instead of three; raise free_body_constraint_max if constraint_overflow reports drops.
Interactions
Read only for free bodies; articulated links use steel:min_torsional_patch_radius. Contacts share a patch only if their coefficients match.
Use it when
Balls, wheels and pucks that should come to rest. Keep condim 3 for boxes.

The script

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

tutorials/06_spin_rolling_friction.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Spin and rolling friction on free bodies with the per-shape steel:condim attribute."""
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):
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)
# free-joint velocity is (linear, angular): spin one ball, roll the other
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):
model = build(condim)
solver = SolverSteel(model)
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(5.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 (sliding only, default)", 3)
simulate(rec, "condim 4 (+ spin)", 4)
simulate(rec, "condim 6 (+ spin + rolling)", 6)
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