import * as scrawl from '../source/scrawl.js';
import { reportSpeed } from './utilities.js';import * as scrawl from '../source/scrawl.js';
import { reportSpeed } from './utilities.js';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({
opaque: true,
includeGridCoords: false,
includePolarCoords: false,
});
const 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);
};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,
});console.log(scrawl.library);