Skip to content

Cloth resting under its own weight away from the world origin grips harder than its friction says

What is wrong

  • A VBD sheet that rests on a support under its own weight should slide when the slope passes its friction angle.
  • On a table top at 0.75 m it holds, or creeps at a fraction of the right speed. This holds with make_vbd(..., coarse_contact=True) too, which slides it correctly at z = 0.
  • The friction load of each light vertex comes out many times its weight, so friction is effectively several times mu for a sheet loaded only by its weight.
  • Pressed contacts are not affected: a pad dragging the sheet across a table at 0.4 m or 0.75 m follows Coulomb as it does at z = 0.

Number

CPU, make_vbd (log barrier), 4 ms, 10 iterations. A 7 x 7-vertex sheet on 8.5 mm cells: 20 mg vertices, r 1 mm. mu_eff 0.2, a 35 deg slope made by tilting gravity, 0.2 s. The block value is 80.4 mm. Measured on wp11-coarse-contact on 2026-11-08, with development probe scripts (not shipped).

table top contact law stock VBD coarse_contact
z = 0 log barrier 0.17 mm 81.9 mm
z = 0 linear penalty 0.06 mm 80.9 mm
z = 0.75 m log barrier 0.16 mm 10.5 mm
z = 0.75 m linear penalty 0.04 mm 0.035 mm
  • Stock holds at every height. That is a separate gap: the slope test's stock xfail in tests/solvers/vbd/test_coarse_contact.py. That law (mu 0.5, 0.4 s, block 128.7 mm) gives 128.0 mm with coarse_contact at z = 0 and 0.6 mm at 0.75 m (its strict xfail).
  • A plane tilted at the origin. Its normal coordinate mixes y and z of about 3 cm. Barrier with coarse_contact: 42.9 mm against 80.4 at mu 0.2, and 2.2 mm against 32.2 at mu 0.5.
  • Per-vertex trace (penalty, tilted plane, 49 vertices). The normal converges to 1.9 nm by iteration 3. Then 8-14 vertices per iteration land 40, 80 or 160 nm deep. Their contact force is up to 250x the vertex weight, and the sheet's summed load is 15-30x its weight.
  • The drag law at 0.4 m and 0.75 m (C7 pad, coarse_contact): 0.973 of the pad's travel, the same as at z = 0.
  • Not yet measured on library scenes.

Why

  • Positions are float32 world coordinates. A coordinate can only move in steps of its float32 spacing: 2 nm at 3 cm, 30 nm at 0.4 m, 60 nm at 0.75 m.
  • The penalty's resting depth is smaller than that. It is about weight / ke = 2e-5 x 9.81 / 1.3e5 = 1.5 nm, so a resting vertex lands one spacing to either side of it:
  • out of contact, the next solve pushes it tens of nm deep;
  • in contact, its force is ke x depth (8 mN = 40x the weight at 60 nm).
  • Friction follows that force. It is mu x the current normal force, so the sheet sticks.
  • The barrier rests deeper, but on the tilted plane it shows the same quantized depths.
  • Pressed contacts are unaffected: a pad pushing far more than one spacing x ke resolves fine.

Workaround

  • When a result depends on cloth sliding under its own weight: put the support near the world origin (the table top at z = 0, the sheet within a few cm of it) and use coarse_contact=True (opt-in, SCR-WP11-6 step 3).
  • Otherwise, treat friction between a resting sheet and a support well away from the origin as an upper bound.

Planned fix

  • SCR-WP11-6 follow-up (WP11), not started. Candidates:
  • take each vertex's friction load from its normal momentum balance (resolution m / dt^2 x spacing, under 1e-3 of the weight) instead of the contact law's force;
  • give VBD positions a per-object origin.
  • After step 3's regression list.