cursorRibbon

An emitter that paints along the pointer's stroke: a ribbon of stamps between last frame's position and this one, so a fast flick leaves a continuous line, not dots.

cursorRibbon<TParams = unknown, TPrepared = undefined>(opts: {
    /** Pointer tracking: smoothing, the teleport distance, the minimum drag. */
    tracker?: PointerVelocityTrackerOptions
    /** Whether the stroke emits this frame. A live frame keeps the simulation awake. */
    activeWhen: (ptr: PointerFrame, f: FluidFrame) => boolean
    /** Seconds after the last activity before the simulation may sleep. */
    fadeSeconds: (f: FluidFrame) => number
    /** Extra bookkeeping per frame before the idle gate. */
    // InkFlow's color-cycle time drift.
    onFrame?: (f: FluidFrame, ptr: PointerFrame) => void
    /** The stamp pass: a pass function, or a `setup` that builds one on resources of its own. */
    pass: {kernel: FluidKernel} | {setup: (ctx: FluidSetupCtx) => KitComputePipeline}
    /** This frame's stamps. */
    ribbon: (
  f: FluidFrame,
  ptr: PointerFrame,
  values: TParams,
) => CursorRibbonSpec<TPrepared>
}): FluidEmitter<TParams> & {ptr(): PointerFrame | null; active(): boolean}

Import sim from shaders/std and use sim.fluids.cursorRibbon.

It tracks the pointer itself, marks the frame active when activeWhen says so, and sleeps the simulation fadeSeconds after the last activity. ribbon describes this frame's stamps. Read the pointer back with .ptr() and .active() inside values.

Parameters

NameTypeNotes
opts`{
/** Pointer tracking: smoothing, the teleport distance, the minimum drag. */
tracker?: PointerVelocityTrackerOptions
/** Whether the stroke emits this frame. A live frame keeps the simulation awake. */
activeWhen: (ptr: PointerFrame, f: FluidFrame) => boolean
/** Seconds after the last activity before the simulation may sleep. */
fadeSeconds: (f: FluidFrame) => number
/** Extra bookkeeping per frame before the idle gate. */
// InkFlow's color-cycle time drift.
onFrame?: (f: FluidFrame, ptr: PointerFrame) => void
/** The stamp pass: a pass function, or a `setup` that builds one on resources of its own. */
pass: {kernel: FluidKernel} \| {setup: (ctx: FluidSetupCtx) => KitComputePipeline}
/** This frame's stamps. */
ribbon: (f: FluidFrame, ptr: PointerFrame, values: TParams) => CursorRibbonSpec<TPrepared>

}` | |

Example

const strokes = cursorRibbon<Params>({activeWhen: (ptr) => ptr.smoothSpeed > 0.005, fadeSeconds: (f) => decayFadeSeconds(255, f.num('dissipation', 0.4)), pass: {kernel: splatKernel}, ribbon: (f, ptr, values) => ({stepSize: Math.max(0.004, f.num('emitRadius', 0.07) * 0.5), maxSteps: 64, write: (emitX, emitY) => f.writeParams({...values, emitX, emitY})})})

Tip. Raise the tracker's teleportGuard when fast flicks should paint: the default treats a quarter-canvas jump as the cursor re-entering and emits nothing.

See also

  • splat — An emitter at a fixed source: one pass per frame that adds dye and momentum where your pass function says (a cone from a point, a line along an edge).
  • fluidSim — A fluid the engine solves every frame.

Used by

InkFlow, SmokeFlow