Skip to content

Friction combine modes

04 · Contact and friction

Boxes with μ = 0.1, 0.4 and 0.9 sit on a 25° ramp with μ = 0.4 (tan 25° ≈ 0.47). Each contact needs one coefficient for a pair of shapes, and friction_combine picks how to combine them, with the same modes as PhysX: average (default), min, multiply or max. The same boxes stick or slide depending on the mode.

0.00 s

What to look at

  • With average, only the μ = 0.9 box holds (0.65 > 0.47).
  • With max, the μ = 0.4 box slides too (max is 0.4 < 0.47), and the μ = 0.9 box holds.
  • With min and multiply every box slides.

Key parameter

friction_combine

"average", "min", "multiply" or "max" of the two shapes' mu.

Try changing

  • Set RAMP_MU = 1.0 and compare average and min.
  • Use restitution_combine the same way for bounce.

Concept

Combining material coefficients

What it is

A contact needs one friction coefficient, but each of its two shapes has one. friction_combine picks the rule: average (default), min, multiply or max, the modes PhysX offers. restitution_combine does the same for bounce.

In this scene

With a ramp μ of 0.4, the μ = 0.9 box holds under average (0.65) and max, but slides under min and multiply (0.36), so the same scene gives three different outcomes.

Considerations

Cost
None measurable: one lookup per contact.
Accuracy
Average and multiply make a high-friction shape slippery on a low-friction one; max and min let one material decide.
Interactions
contact_friction_scale scales each shape's coefficient before combining, so multiply squares a common scale. Patches only form between contacts whose combined coefficients match.
Use it when
Match the source of your assets: PhysX scenes often author a combine mode per material.

The script

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

tutorials/04_friction_combine.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Friction combine modes: how two shapes' mu values become one contact coefficient."""
import math
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
SLOPE = math.radians(25.0) # tan(25 deg) = 0.47
BOX_MU = [0.1, 0.4, 0.9]
RAMP_MU = 0.4
def build():
builder = newton.ModelBuilder()
builder.add_ground_plane()
tilt = wp.quat_from_axis_angle(wp.vec3(0.0, 1.0, 0.0), SLOPE)
builder.add_shape_box(
-1,
xform=wp.transform((0.0, 0.0, 0.4), tilt),
hx=0.9,
hy=0.45,
hz=0.03,
cfg=newton.ModelBuilder.ShapeConfig(mu=RAMP_MU),
)
normal = np.array(wp.quat_rotate(tilt, wp.vec3(0.0, 0.0, 1.0)))
uphill = np.array(wp.quat_rotate(tilt, wp.vec3(1.0, 0.0, 0.0)))
for i, mu in enumerate(BOX_MU):
p = np.array([0.0, 0.3 * i - 0.3, 0.4]) + 0.5 * uphill + 0.071 * normal
box = builder.add_body(xform=wp.transform(p, tilt))
builder.add_shape_box(
box, hx=0.04, hy=0.04, hz=0.04, cfg=newton.ModelBuilder.ShapeConfig(mu=mu)
)
return builder.finalize()
def simulate(rec, mode):
model = build()
solver = SolverSteel(model, friction_combine=mode)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
start = state_0.body_q.numpy()[:, :3].copy()
run = rec.variant(f'friction_combine="{mode}"', model)
for _ in range(rec.frames(2.0)):
slide = np.linalg.norm(state_0.body_q.numpy()[:, :3] - start, axis=1)
run.frame(state_0, **{f"mu_{mu}": d for mu, d in zip(BOX_MU, slide)})
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()
for mode in ["average", "min", "multiply", "max"]:
simulate(rec, mode)
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