• Jump To … +
    ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/canvas-048.js ./demo/canvas-049.js ./demo/canvas-050.js ./demo/canvas-051.js ./demo/canvas-052.js ./demo/canvas-053.js ./demo/canvas-054.js ./demo/canvas-055.js ./demo/canvas-056.js ./demo/canvas-057.js ./demo/canvas-058.js ./demo/canvas-059.js ./demo/canvas-060.js ./demo/canvas-061.js ./demo/canvas-062.js ./demo/canvas-063.js ./demo/canvas-064.js ./demo/canvas-065.js ./demo/canvas-066.js ./demo/canvas-067.js ./demo/canvas-068.js ./demo/canvas-069.js ./demo/canvas-070.js ./demo/canvas-071.js ./demo/canvas-072.js ./demo/canvas-073.js ./demo/canvas-201.js ./demo/canvas-202.js ./demo/canvas-203.js ./demo/canvas-204.js ./demo/canvas-205.js ./demo/canvas-206.js ./demo/canvas-207.js ./demo/canvas-208.js ./demo/canvas-209.js ./demo/canvas-210.js ./demo/canvas-211.js ./demo/canvas-212.js ./demo/delaunator-001.js ./demo/delaunator-002.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-015.js ./demo/dom-016.js ./demo/dom-017.js ./demo/dom-018.js ./demo/dom-019.js ./demo/dom-020.js ./demo/dom-021.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-021.js ./demo/filters-022.js ./demo/filters-023.js ./demo/filters-024.js ./demo/filters-025.js ./demo/filters-026.js ./demo/filters-027.js ./demo/filters-028.js ./demo/filters-029.js ./demo/filters-030.js ./demo/filters-031.js ./demo/filters-032.js ./demo/filters-033.js ./demo/filters-034.js ./demo/filters-035.js ./demo/filters-036.js ./demo/filters-037.js ./demo/filters-101.js ./demo/filters-102.js ./demo/filters-103.js ./demo/filters-104.js ./demo/filters-105.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/mediapipe-001.js ./demo/mediapipe-002.js ./demo/mediapipe-003.js ./demo/modules-001.js ./demo/modules-002.js ./demo/modules-003.js ./demo/modules-004.js ./demo/modules-005.js ./demo/modules-006.js ./demo/packets-001.js ./demo/packets-002.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/particles-017.js ./demo/snippets-001.js ./demo/snippets-002.js ./demo/snippets-003.js ./demo/snippets-004.js ./demo/snippets-005.js ./demo/snippets-006.js ./demo/temp-001.js ./demo/temp-shape-scale-investigation.js ./demo/tensorflow-001.js ./demo/tensorflow-002.js ./demo/utilities.js
  • §

    Demo Canvas 073

    2D Stable Fluids simulation (Stam 1999)

  • §

    Run code

    import * as scrawl from '../source/scrawl.js';
    
    import { reportSpeed } from './utilities.js';
  • §

    Scene setup

    Get a handle to the Canvas wrapper

    const canvas = scrawl.findCanvas('my-canvas');
  • §

    Namespacing boilerplate

    const namespace = canvas.name;
    const name = (n) => `${namespace}-${n}`;
    
    const [cWidth, cHeight] = canvas.get('dimensions');
    
    const _max = Math.max,
        _min = Math.min,
        _floor = Math.floor;
  • §

    Create the easel cell

    const eWidth = _floor(cWidth / 4),
        eHeight = _floor(cHeight / 4);
    
    const easel = canvas.buildCell({
        name: name('easel-cell'),
        dimensions: [eWidth, eHeight],
        cleared: false,
        compiled: false,
        shown: false,
    });
  • §

    Get handles to the Cell’s engine and pixel data

    const easelData = easel.getCellData(true),
        pixelState = easelData.pixelState;
    
    easel.paintCellData(easelData);
  • §

    Create the display Picture

    scrawl.makePicture({
        name: name('display'),
        dimensions: ['100%', '100%'],
        copyDimensions: ['100%', '100%'],
        asset: easel,
        imageSmoothingEnabled: false,
    });
  • §

    Fluid simulation

    const len = eWidth * eHeight;
    
    const eWm1 = eWidth - 1,
        eWm15 = eWidth - 1.5,
        eWm2 = eWidth - 2,
        eHm1 = eHeight - 1,
        eHm15 = eHeight - 1.5,
        eHm2 = eHeight - 2;
    
    const innerIndicesLUT = new Uint32Array(len * 3);
    
    let counter = 0
    for (let y = 1; y < eHm1; y++) {
    
        for (let x = 1; x < eWm1; x++) {
    
            const index = y * eWidth + x;
    
            innerIndicesLUT[counter++] = index;
            innerIndicesLUT[counter++] = x;
            innerIndicesLUT[counter++] = y;
        }
    }
    
    console.log(innerIndicesLUT)
  • §

    Tweakable variables

    const dt = 1/60,
        visc = 0.001,
        diff = 0.0001;
  • §

    Fields (double buffers for advection/diffuse)

    const u = new Float32Array(len),
        v = new Float32Array(len),
        u0 = new Float32Array(len),
        v0 = new Float32Array(len),
        p = new Float32Array(len),
        div = new Float32Array(len),
        dye = new Float32Array(len),
        dye0 = new Float32Array(len);
  • §

    Helper functions

    const setBoundary = function (field) {
    
        let i, index0, index1;
    
        for (i = 0; i < eHeight; i++) {
    
            index0 = i * eWidth + 0;
            index1 = i * eWidth + 1;
            field[index0] = field[index1];
    
            index0 = i * eWidth + eWm1;
            index1 = i * eWidth + eWm2;
            field[index0] = field[index1];
        }
    
        for (i = 0; i < eWidth; i++) {
    
            index0 = 0 * eWidth + i;
            index1 = 1 * eWidth + i;
            field[index0] = field[index1];
    
            index0 = eHm1 * eWidth + i;
            index1 = eHm2 * eWidth + i;
            field[index0] = field[index1];
        }
    };
    
    const jacobi = function (out, b, alpha, rcpBeta, iterations){
    
        let k, i, iz, index;
    
        for (k = 0; k < iterations; k++) {
    
            for (i = 0, iz = innerIndicesLUT.length; i < iz; i += 3) {
    
                index = innerIndicesLUT[i];
    
                out[index] = (
                    b[index] +
                    out[index - 1] +
                    out[index + 1] +
                    out[index - eWidth] +
                    out[index + eWidth]
                ) * rcpBeta;
            }
            setBoundary(out);
        }
    };
    
    const advect = function (out, inp, u, v, dt) {
    
        let i, iz, xf, yf, x0, x1, sx, y0, y1, sy, a, b, index, y0width, y1width;
    
        for (i = 0, iz = innerIndicesLUT.length; i < iz; i += 3) {
    
            index = innerIndicesLUT[i];
    
            xf = innerIndicesLUT[i + 1] - dt * u[index] * eWm2;
            xf = xf < 0.5 ? 0.5 : xf > eWm15 ? eWm15 : xf;
    
            yf = innerIndicesLUT[i + 2] - dt * v[index] * eHm2;
            yf = yf < 0.5 ? 0.5 : yf > eHm15 ? eHm15 : yf;
    
            x0 = _floor(xf);
            y0 = _floor(yf);
    
            x1 = x0 + 1;
            y1 = y0 + 1;
    
            sx = xf - x0;
            sy = yf - y0;
    
            y0width = y0 * eWidth;
            y1width = y1 * eWidth;
    
            a = inp[y0width + x0] * (1 - sx) + inp[y0width + x1] * sx;
            b = inp[y1width + x0] * (1 - sx) + inp[y1width + x1] * sx;
    
            out[index] = a * (1 - sy) + b * sy;
        }
        setBoundary(out);
    };
    
    const project = function (u, v, p, div) {
    
        let i, iz, index;
    
        for (i = 0, iz = innerIndicesLUT.length; i < iz; i += 3) {
    
            index = innerIndicesLUT[i];
    
            div[index] = -0.5 * (
                (u[index + 1] - u[index - 1]) +
                (v[index + eWidth] - v[index - eWidth])
            );
    
            p[index] = 0;
        }
        setBoundary(div);
        setBoundary(p);
  • §

    Solve ∇²p = div via Jacobi

        jacobi(p, div, 1, 0.25, 20);
    
        for (i = 0, iz = innerIndicesLUT.length; i < iz; i += 3) {
    
            index = innerIndicesLUT[i];
    
            u[index] -= 0.5 * (p[index + 1] - p[index - 1]);
            v[index] -= 0.5 * (p[index + eWidth] - p[index - eWidth]);
        }
        setBoundary(u);
        setBoundary(v);
    };
    
    const diffuse = function (out, inp, rate) {
    
        const a = rate,
            beta = 1 + 4 * a;
    
        out.set(inp);
    
        let k, i, iz, index;
    
        for (k = 0; k < 20; k++) {
    
            for (i = 0, iz = innerIndicesLUT.length; i < iz; i += 3) {
    
                index = innerIndicesLUT[i];
    
                out[index] = (
                    inp[index] +
                    a * (
                        out[index - 1] +
                        out[index + 1] +
                        out[index - eWidth] +
                        out[index + eWidth]
                    )
                ) / beta;
            }
            setBoundary(out);
        }
    }
  • §

    Simulation step

    const emitter1 = {
        x: 2,
        y: _floor(eHeight / 2) - 1,
        radius: 2,
        dyeRate: 1.0,
        vx: 1.5 / eWidth,
        vy: 0
    };
    const emitter2 = {
        x: eWidth - 2,
        y: _floor(eHeight / 2) + 1,
        radius: 2,
        dyeRate: 1.0,
        vx: -1.2 / eWidth,
        vy: 0
    };
    
    const step = function () {
  • §

    emitter 1 (to show something on the canvas)

        let { x: ex, y: ey, radius: r, dyeRate, vx, vy } = emitter1;
        let r2 = r * r,
            minX = _max(1, ex - r),
            maxX = _min(eWm2, ex + r),
            minY = _max(1, ey - r),
            maxY = _min(eHm2, ey + r);
    
        let y, x, dx, dy, i;
    
        for (y = minY; y <= maxY; y++) {
    
            for (x = minX; x <= maxX; x++) {
    
                dx = x - ex;
                dy = y - ey;
    
                if (dx * dx + dy * dy <= r2) {
    
                    i = y * eWidth + x;
                    dye[i] = _min(1, dye[i] + dyeRate * dt);
                    u[i] += vx;
                    v[i] += vy;
                }
            }
        }
  • §

    emitter 2 (to show some conflict)

        ({ x: ex, y: ey, radius: r, dyeRate, vx, vy } = emitter2);
        r2 = r * r;
        minX = _max(1, ex - r);
        maxX = _min(eWm2, ex + r);
        minY = _max(1, ey - r);
        maxY = _min(eHm2, ey + r);
    
        for (y = minY; y <= maxY; y++) {
    
            for (x = minX; x <= maxX; x++) {
    
                dx = x - ex;
                dy = y - ey;
    
                if (dx * dx + dy * dy <= r2) {
    
                    i = y * eWidth + x;
                    dye[i] = _min(1, dye[i] + dyeRate * dt);
                    u[i] += vx;
                    v[i] += vy;
                }
            }
        }
  • §

    add forces (example: decay dye slightly)

        for (let i = 0; i < len; i++) {
    
            dye[i] *= 0.995;
        }
  • §

    velocity diffusion

        diffuse(u0, u, visc);
        diffuse(v0, v, visc);
  • §

    project

        project(u0, v0, p, div);
  • §

    advect velocity

        advect(u, u0, u0, v0, dt);
        advect(v, v0, u0, v0, dt);
        project(u, v, p, div);
  • §

    advect dye

        advect(dye0, dye, u, v, dt);
  • §

    dye diffusion (optional)

        diffuse(dye, dye0, diff);
    };
    
    const drawCell = () => {
    
        let i, p, v, c;
    
        for (i = 0; i < len; i++) {
    
            v = _max(0, _min(1, dye[i]));
    
            c = (v * 255) | 0;
    
            p = pixelState[i];
    
            p.red = c;
            p.green = c;
            p.blue = 127 - (c / 2);
        }
        easel.paintCellData(easelData);
    };
  • §

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    const report = reportSpeed('#reportmessage');
  • §

    Create the Display cycle animation

    scrawl.makeRender({
    
        name: name('animation'),
        target: canvas,
    
        commence: () => {
            step();
            drawCell();
        },
    
        afterShow: report,
    });
  • §

    Development and testing

    console.log(scrawl.library);