One solver, four toys. Compliance is a real number in metres per newton, so the softness you ask for is the softness you get, whatever the timestep. The old way was a stiffness between zero and one that quietly meant something different every time you changed the substep count. Switch between them and watch the cloth change its mind.
The claim under test: an XPBD constraint with compliance a holding a load
F settles at a stretch of exactly a × F, no matter how the
timestep is chopped up, while classic PBD settles wherever the arithmetic happens to leave
it. This runs both solvers headless on the same one kilogram weight, once per substep count,
and compares each result against the closed form derived below. Nothing here reads the
picture on the canvas; it re-simulates from scratch.
| Not run yet. |
For a distance constraint C = |x1 - x2| - L with compliance
alpha and substep h, set at = alpha / h^2 and
iterate
dLambda = (-C - at * lambda) / (w1 + w2 + at), then
x_i += w_i * gradC_i * dLambda, then lambda += dLambda.
lambda starts at zero every substep. It is the force the solver has already
spent on this constraint, and carrying it is the difference between XPBD and PBD.
w is inverse mass, so a pinned particle has w = 0 and never
moves.
One particle of mass m hanging from a pin. At rest the prediction step
drops it by g h^2, so the gap the solver sees is C* + g h^2.
With a single constraint the problem is linear, so one iteration lands exactly on the
fixed point, which is C = C_predicted * at / (at + w). Set the two equal
and the steady state is
C* = g h^2 * at / w = g h^2 * (alpha / h^2) * m = alpha * m * g.
The h^2 cancels. That cancellation is the entire reason the compliance
slider means something. Take the / h^2 out of at and it does
not cancel any more, and the softness of your cloth goes back to being a property of
your framerate.
Classic PBD moves each end by k * C per iteration, so n
iterations multiply the gap by (1-k)^n. The same steady state argument
gives
C* = g h^2 * (1-k)^n / (1 - (1-k)^n)
which is proportional to h^2, so it falls with the square of the substep
count. Run the sweep and watch the orange line dive while the teal one sits flat.
Shoelace: A = 1/2 * sum(x_i*y_next - x_next*y_i). Differentiate and the
gradient at vertex i is (y_next - y_prev)/2 in x and
(x_prev - x_next)/2 in y, which is the perpendicular of the line joining
its two neighbours, at half length. The button above checks that analytic gradient
against a central difference on a random polygon, because a gradient you derived and a
gradient you tested are different things.