Warping Coordinates

Strands are functions from coordinates to values. Warping changes which coordinates get passed in, and hence enable almost all transformations in WEFT.

The ~ operator

The syntax for remapping is strand(coordinate ~ expression). Instead of letting the strand sample at its normal coordinates, you’re telling it to sample somewhere else:

img.r(me.x ~ me.x - 10)

This shifts the image right. Which might feel backwards—you’re subtracting, but the image moves right? The intuition is that you’re not moving pixels. You’re changing where you look. To figure out what color should appear at position 50, you’re asking: “what was at position 40?” The data stays put, but your question shifts.

You can remap multiple coordinates at once:

img.r(me.x ~ me.x - 10, me.y ~ me.y + 5)

And the expression on the right side can be anything—math, other strands, whatever makes sense:

img.r(me.x ~ me.x + audio.amplitude * 50)

This displaces pixels horizontally based on audio loudness. The audio strand’s output becomes part of the coordinate calculation.

Tagging

Sometimes you want to parameterize something that isn’t a coordinate. Say you’ve defined a wave:

wave.val = sin(me.t * 10)

That 10 controls the frequency, but it’s just a number—there’s no way to refer to it. You can’t remap “the 10” because it doesn’t have a name. Tags fix this. The syntax $name(value) marks a value so it can be overridden later:

wave.val = sin(me.t * $speed(10))

Now speed is remappable with name speed, just like any other strand:

slower.val = wave.val(speed ~ 5)
faster.val = wave.val(speed ~ 20)

Both slower and faster are based on wave, but with different frequencies.

Dependencies

You can only remap things that a strand directly depends on. If you define:

inv.r = 1 - img.r

You can’t remap me.x through inv.r, because inv.r doesn’t directly mention me.x—it only mentions img.r. To remap the underlying coordinates, you’d need to go through img.r explicitly:

inv.r(img.r ~ img.r(me.x ~ me.x + 10))

This says: when evaluating inv.r, use a version of img.r that’s been shifted. It’s more verbose, but it’s explicit about where the remapping happens.

Work on a ~! operator to enable indirect or pass-through warping is in progress.