Skip to content

Depenetration speed limit

18 · Impacts and penetration

Three boxes spawned overlapping by 3 cm and 2 cm into the ground. Steel removes penetration with a velocity bias, and a large overlap gives a large bias, so by default the top box is thrown about 55 cm up. steel:rigid_body_max_depenetration_velocity caps the speed of that correction per free body, matching PhysX's maxDepenetrationVelocity, so the boxes separate gently.

0.00 s

What to look at

  • Unbounded: the top box jumps.
  • 0.2 m/s: the stack pushes apart in about half a second and stays put.

Key parameter

steel:rigid_body_max_depenetration_velocity

Per body [m/s], default unbounded. Fill model.steel.rigid_body_max_depenetration_velocity after registering the attributes.

Try changing

  • Try 1.0 m/s.
  • Combine it with baumgarte=0.8.

Concept

Depenetration speed limit

What it is

Overlap is removed through a velocity bias proportional to depth, so a deep overlap becomes a fast push. steel:rigid_body_max_depenetration_velocity caps that correction speed per free body, as PhysX's maxDepenetrationVelocity does.

In this scene

Unbounded, the top box is thrown about 55 cm; with a 0.2 m/s cap the overlapping boxes separate in about half a second and stay stacked.

Considerations

Cost
None.
Accuracy
Penetration lasts longer with a low cap; a cap far below the needed speed leaves objects overlapping for many steps.
Interactions
Applies to free bodies; articulations use velocity_iterations.
Use it when
Spawning or randomizing objects that may overlap.

The script

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

tutorials/18_depenetration_velocity.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Cap how fast overlapping bodies are pushed apart: steel:rigid_body_max_depenetration_velocity."""
import newton
import warp as wp
from newton.solvers import SolverSteel
from replay import Recorder
DT = 1.0 / 240.0
SUBSTEPS = 8
def build(max_speed):
builder = newton.ModelBuilder()
SolverSteel.register_custom_attributes(builder)
builder.add_ground_plane()
# three boxes spawned 3 cm into each other and 2 cm into the ground
for i in range(3):
box = builder.add_body(xform=wp.transform((0.0, 0.0, 0.03 + 0.07 * i), wp.quat_identity()))
builder.add_shape_box(box, hx=0.08, hy=0.08, hz=0.05)
model = builder.finalize()
model.steel.rigid_body_max_depenetration_velocity.fill_(max_speed)
return model
def simulate(rec, label, max_speed, **options):
model = build(max_speed)
solver = SolverSteel(model, **options)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
run = rec.variant(label, model)
for _ in range(rec.frames(1.5)):
q, qd = state_0.body_q.numpy(), state_0.body_qd.numpy()
run.frame(state_0, top_z=q[2, 2], top_speed=abs(qd[2, 2]))
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, "unbounded (default)", float("inf"))
simulate(rec, "max 0.2 m/s", 0.2)
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