t is not distance along a curve

A Bezier is defined by a parameter t from 0 to 1, and it is enormously tempting to treat that as position along the curve. It is not. The curve moves at different speeds through different parts of its own parameter range, and everything you place by t inherits that.

Thirteen markers on the same curve. Left: evenly spaced t. Right: approximately equal arc-length spacing.

Left, the markers bunch up in the tight hook and stretch out along the lazy opening, even though t was stepped in perfectly equal increments. Right, the table places them at approximately equal distances along the curve.

Why the parameter is not the distance

The speed along a curve is the magnitude of its derivative, |B'(t)|. For control points A, B, C and D, the cubic's derivative is:

B'(t) = 3[(1-t)^2(B-A) + 2(1-t)t(C-B) + t^2(D-C)]

Those differences are vectors. Their directions matter as well as their lengths: opposing vectors can cancel and bring the speed to zero. The ratio of control-polygon segment lengths doesn't reliably tell you the speed ratio. Evaluate the derivative or measure the spacing error instead.

Even a straight cubic can have uneven parameter speed if its control points aren't evenly spaced. For this curve, the maximum speed is about 3.08 times the minimum.

The fix is a small table

// build once
float len[N+1]; len[0] = 0;
for (i = 1..N) len[i] = len[i-1] + |B(i/N) - B((i-1)/N)|;

// then invert: given a target LENGTH, find the t
find i where len[i] >= target
f = (target - len[i-1]) / (len[i] - len[i-1])
t = (i-1 + f) / N

A general cubic has no elementary arc-length formula. Numerical integration and cumulative chord-length tables are common approaches. This demo uses 20 chord segments and linear interpolation within each table interval. For this specific curve, the resulting marker intervals differ from equal arc length by less than 1%. That's a measured approximation, not a guarantee for other control points.

Increase the sample count and compare the result, or subdivide adaptively to an error tolerance. Tight bends and cusps need particular care. A curve with zero total length needs a separate case: every marker belongs at the same point.

Everything that lands on a curve needs this

Rules of thumb

  1. t is a parameter, not a distance. Equal parameter steps give equal distances only when parameter speed is constant.
  2. Check the derivative or the resulting spacing before deciding the error is acceptable.
  3. Build a cumulative length table and invert it. Choose the sample count from the error you can accept.
  4. Interpolate inside the table bracket rather than snapping to the nearest sample.
  5. In an application, cache the table until the curve changes. This self-contained fragment shader recomputes it per fragment for simplicity.

References: derivatives of a Bezier curve and numerical arc-length methods. The segment distance function follows Inigo Quilez; the output dither uses Jorge Jimenez's interleaved gradient noise.

All 61 notes How to use them Credits

If you find anything wrong, let me know at hello@andrewdetwiler.com and I'll get it updated.