|

Stroke Definition

A stroke knows where it goes and how wide it is. It does not know what it looks like.

StrokeDef is the whole definition of one stroke: an array of points, a width on each side of those points, and a reference to the renderer that turns the two into geometry. Splitting it this way means the same path can be drawn as a hard-edged ribbon, a tapered brush mark, or a volumetric tube by swapping one field.

public/lib/StrokeDef.js

StrokeDef

Constructed from a single options object.

  • pointsTHREE.Vector3[]. Control points, not final vertices. Renderers treat them as a centripetal Catmull-Rom curve and resample it; two points are the minimum.
  • widthLeft — width on the left of the spine. Default 0.02.
  • widthRight — width on the right. Defaults to widthLeft, giving a symmetric stroke.
  • renderer — the StrokeRenderer used by build().

“Left” is the +90° rotation of the tangent in the XY plane. Because the two sides are stored separately, a stroke can lean off the path that generated it — the spine stays where the gesture put it while the visible mark thickens to one side.

build() hands the definition to its renderer and returns a THREE.Object3D. maxWidth() samples both sides and returns the largest value; polylineLength measures the control polygon, which is a cheap approximation renderers do not rely on — they measure the resampled curve instead.

Width

A width is one of three things, resolved by resolveWidth(width, t) where t runs 0 → 1 over arc length.

  • number — constant along the stroke.
  • number[] — sampled evenly along the stroke and linearly interpolated between entries. Useful for recorded pressure.
  • (t) => number — evaluated per sample. Useful for analytic profiles.

Because t is normalised over arc length rather than over the control point index, a width profile keeps its shape when the control points are unevenly spaced. A taper written as sin(πt) peaks at the halfway point of the drawn mark, not at the middle entry of the array.

The renderer reference

renderer is a reference, not a subclass hook: one renderer instance can build any number of strokes, because it carries style and never state about a particular stroke. build() simply hands the definition over.

Renderers, the contract they share, and RibbonStrokeRenderer are documented on Renderers.