← geometric-flows

Stretch and stack

Stretch-and-stack flow on lattices: the unit cell warps under the flow and restacks, driven by the shared lattice computation.

Source

common.glsl

// Stretch and Stack — driven by the shared JS lattice driver (script.js).
// uLattice (the lattice mat2) and uFlowInv (inverse of the per-frame flow) are
// pushed from script.js; uScale comes from the UI. UnitCell warps the previous
// frame by uFlowInv to animate the stretch, like the original Shadertoy.

#include "../shared/2d_utils.glsl"
#include "../shared/coord_utils.glsl"

bool fadeMoves   = false;   // fade the warped trail instead of hard-clearing it

vec4 backCol = vec4(1., 1., 1., 0.);
vec4 dotCol  = vec4(.8, .05, .04, .1);
vec4 edgeCol = vec4(.651, .669, .95, .1);

// Screen origin for this view: horizontally centred, one third up from the bottom.
vec2 viewOffset(in vec3 res) { return vec2(res.x * 0.5, res.y / 3.); }

fullLattice.glsl

// Whole-plane view. With basis b1 = (a, c) and b2 = (-b, d) (the driver
// keeps b1 rightward and b2 leftward), UnitCell draws its picture on a
// two-box staircase cell: (-a, 0)x(0, d) and (0, b)x(0, c), whose combined
// area is the parallelogram's. Here each pixel is first reduced into the
// parallelogram cell, then walked down the staircase into the box pair, and
// UnitCell is sampled there, tiling the plane with copies.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 col;

    vec2 p = coords(fragCoord, iResolution, uScale, viewOffset(iResolution));

    mat2 m = mat2(uLattice);

    float a = vec4(m).x;
    float b = -vec4(m).z;
    float c = vec4(m).y;
    float d = vec4(m).w;

    mat2 im = inverse(m);
    vec2 pC = p-m*floor(im*p);

    // Walk pC from the parallelogram into the box pair. The shift counts
    // divide by a box width (a or b); when that width is ~0 (axis-aligned
    // basis, e.g. the identity start lattice) the count must be 0, not x/0.
    if(a > b)
    {
        if(pC.x < 0. && pC.y > d) { pC = pC - vec2(-b,d); }
        else if(pC.x > 0.)
        {
            pC = pC + (b > 1e-6 ? floor(pC.x/b) : 0.)*vec2(-b, d);
            if(pC.y > c )
            {
                pC = pC - floor((pC.y-c)/d)*vec2(-b, d);
                pC = pC - vec2(a,c);
            }
        }
    }
    else
    {
        if(pC.x > 0. && pC.y > c) { pC = pC - vec2(a,c); }
        else if(pC.x < 0.)
        {
            pC = pC + (a > 1e-6 ? floor(-pC.x/a) : 0.)*vec2(a, c);
            if(pC.y > d )
            {
                pC = pC - floor((pC.y-d)/c)*vec2(a, c);
                pC = pC - vec2(-b,d);
            }
        }
    }

	vec2 uv = iCoords(pC, iResolution, uScale, viewOffset(iResolution)) / iResolution.xy;

    // Keep the sample off the buffer's top edge.
    if(uv.y > .998) { uv.y = .998;}

    col = vec4(texture(UnitCell,uv).xyz,1);

    fragColor = col;
}

image.glsl

// Composite: show either the whole-plane view (uView 1) or the single
// warped unit cell.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 col;

	vec2 uv = fragCoord.xy / iResolution.xy;

    // Keep the sample off the buffer's top edge.
    if(uv.y > .998) { uv.y = .998;}

    if(uView == 1) { col = vec4(texture(fullLattice,uv).xyz,1); }
    else { col = vec4(texture(UnitCell,uv).xyz,1); }

    fragColor = col;
}

UnitCell.glsl

// Unit cell — warps the previous frame back by one flow step (uFlowInv) to
// animate the stretch, then draws the current fundamental domain on top.
// The domain is the two-box staircase cell (see fullLattice.glsl): the blue
// box spans (-b1.x, 0) to (0, b2.y), the red box (0, 0) to (-b2.x, b1.y).
// Restored from the original Shadertoy, with uFlowInv replacing gFlow(-1.)
// and uLattice replacing the bufA matrix.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 p = coords(fragCoord, iResolution, uScale, viewOffset(iResolution));

    // Where this pixel came from one flow step ago.
    vec2 oFrag = iCoords(mat2(uFlowInv) * p, iResolution, uScale, viewOffset(iResolution));
    vec2 uv = oFrag / iResolution.xy;

    vec4 col = backCol;
    vec4 pt = uLattice;       // (b1.x, b1.y, b2.x, b2.y)
    float mixing = 0.;

    if (iFrame == 0 || uNewLattice || uv.x > 1. || uv.x < 0. || uv.y > .99 || uv.y < 0.)
    {
        // Fresh start (or the warp reaches outside the buffer): draw the
        // cell solid.
        col = drBox(col, p, vec4(-pt.x, 0., 0., pt.w), vec4(edgeCol.xyz, 1.), .0);
        col = drBox(col, p, vec4(0., 0., -pt.z, pt.y), vec4(dotCol.xyz, 1.), .0);
    }
    else
    {
        col = vec4(texture(UnitCell, uv).xyz, 1.);
        vec4 fullCol = vec4(texture(fullLattice, uv).xyz, 1.);

        if (fadeMoves) {
            col = mix(col, vec4(backCol.xyz, 0), .1);
            mixing = .1;
        } else {
        	col = vec4(backCol.xyz,0);
            mixing = 1.;
        }

        if (uFade == 1) {
            // Repaint the cell with the warped whole-plane colours at full
            // strength, then tint it very lightly with the box colours.
            mixing = 1.0;
            col = drBox(col, p, vec4(-pt.x, 0., 0., pt.w), vec4(fullCol.xyz, mixing), 0.01);
            col = drBox(col, p, vec4(0., 0., -pt.z, pt.y), vec4(fullCol.xyz, mixing), 0.01);
            mixing = .01;
            col = drBox(col, p, vec4(-pt.x, 0., 0., pt.w), vec4(edgeCol.xyz, mixing), .001);
            col = drBox(col, p, vec4(0., 0., -pt.z, pt.y), vec4(dotCol.xyz, mixing), .001);
        } else {
            col = drBox(col, p, vec4(-pt.x, 0., 0., pt.w), vec4(edgeCol.xyz, mixing), .1);
            col = drBox(col, p, vec4(0., 0., -pt.z, pt.y), vec4(dotCol.xyz, mixing), .1);
        }
    }

    fragColor = col;
}

lattice/script.js

// 2dFlowJS: the plain lattice-flow shader UI. All the driving logic lives in
// the shared lattice driver; this script is just its instantiation, so state
// is per-instance and other shaders can layer their own UI on the same core.
import { createLatticeDriver } from '../shared/lattice_driver.js';

const driver = createLatticeDriver();

export const setup = driver.setup;
export const onFrame = driver.onFrame;
export const onSetLattice = driver.onSetLattice;