Published
Edited
May 2, 2020
6 stars
Also listed in…
Snacks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Feel free to edit this to add more options in the form above
// Did you find something really cool? Let me know!
ruleArr =
[
{name:"original",
rule:[{numNN:[1,2,3,4]},
{numNNN:[1,2,3,4]}]},
{name:"tangle",
rule:[{numNN:[1], numNNN:[0]},
{numNN:[0], numNNN:[1]}]},
{name:"two-color moss",
rule:[{numNN:[0], numNNN:[1]},
{numNN:[2], p:0.01}]},
{name:"horizontal & vertical fuzz",
rule:[{numNN:[1], numNNN:[0]},
{numNN:[1], numNNN:[2], p:0.5}]},
{name:"diamond flakes",
rule:[{numNN:[2,3,4], numNNN:[2,3,4]},
{numNN:[1], numNNN:[2]},
{numNN:[1], numNNN:[0], p:0.1}]}
]
Insert cell
Insert cell
function testCustom(custom) {
function isBadNumArr(arr) {
return (arr !== undefined &&
(!Array.isArray(arr) || arr.some(d => typeof d !== "number")))
}
try {
const r = JSON.parse(custom);
if (r.some(
({numNN,numNNN,p}) =>
isBadNumArr(numNN) || isBadNumArr(numNNN) ||
(p !== undefined && typeof p !== "number")
))
throw new Error('invalid custom rule');
return true;
} catch(e) {
return false; // invalid rule
}
}
Insert cell
customRule = testCustom(object.custom) ?
JSON.parse(object.custom) :
[{numNN:[5]}] // a do-nothing rule to (kind-of) stop execution
Insert cell
rules = (object.rules === "custom" ? customRule : ruleArr[+object.rules].rule).map(
({numNN, numNNN, p}) =>
({nn,nnn}) =>
(numNN === undefined || numNN.some(d => d === nn)) &&
(numNNN === undefined || numNNN.some(d => d === nnn)) &&
(p === undefined || Math.random() < p)
)
Insert cell
function funcRules(nn,nnn) {
// rules should encode something in DNF https://en.wikipedia.org/wiki/Disjunctive_normal_form
return !rules.reduce((a2,v2) => a2 || v2({nn,nnn}), false);
}
Insert cell
function render(ctx, particles, {imageData, values, vEmpty, vStuck, vFloat, vStuck2, vFloat2}) {
for (let i = 0; i < values.length; i++) {
values[i] = vEmpty;
}
for (var i = 0; i < count; i++) {
const x = particles.x[i];
const y = particles.y[i];
const stuck = particles.stuck[i];
if ((x+y) % 2)
values[y * width + x] = stuck ? vStuck : vFloat;
else
values[y * width + x] = stuck ? vStuck2 : vFloat2;
}
ctx.putImageData(imageData, 0, 0);
}
Insert cell
field = {
reset;
const field = new Uint8Array(width * height);
const fcenterX = Math.floor(width / 2);
const fcenterY = Math.floor(height / 2);
qt.add([fcenterX,fcenterY]);
field[fcenterX + fcenterY * width] = 1;
return field;
}
Insert cell
particles = {
reset;
const particles = {
x: new Uint16Array(count),
y: new Uint16Array(count),
stuck: new Uint8Array(count)
};
for (let i = 0; i < count; i++) {
if (fast.fast)
// resetSingleParticle2(particles, i);
resetSingleParticle3(particles, i);
else
resetSingleParticle(particles, i);
}
return particles;
}
Insert cell
resetSingleParticle = (particles, i) => {
let x = Math.floor(Math.random() * width);
let y = Math.floor(Math.random() * height);

while (field[y * width + x]) {
x = Math.floor(Math.random() * width);
y = Math.floor(Math.random() * height);
}

particles.x[i] = x;
particles.y[i] = y;
}
Insert cell
// particles come in from further out
resetSingleParticle2 = (particles, i) => {
let x = Math.floor(4*(Math.random()-0.5) * width);
let y = Math.floor(4*(Math.random()-0.5) * height);

while (field[y * width + x]) {
x = Math.floor(Math.random() * width);
y = Math.floor(Math.random() * height);
}

particles.x[i] = x;
particles.y[i] = y;
}
Insert cell
// particles arrive from boundary
resetSingleParticle3 = (particles, i) => {
let x,
y;
switch (Math.floor(Math.random()*4)) {
case 0: {
y=0;
x = Math.floor(width*Math.random());
break;
}
case 1: {
y = height - 1;
x = Math.floor(width*Math.random());
break;
}
case 2: {
x=0;
y = Math.floor(height*Math.random());
break;
}
case 3: {
x=width - 1;
y = Math.floor(height*Math.random());
break;
}
}
particles.x[i] = x;
particles.y[i] = y;
}
Insert cell
function alone(i) {
let cx = particles.x[i];
let cy = particles.y[i];

let lx = cx - 1;
let rx = cx + 1;
let ty = cy - 1;
let by = cy + 1;

if (
cx <= 0 ||
cx >= width-1 ||
lx <= 0 ||
lx >= width-1 ||
rx <= 0 ||
rx >= width-1 ||
cy <= 0 ||
cy >= height-1 ||
ty <= 0 ||
ty >= height-1 ||
by <= 0 ||
by >= height-1
) {
return true;
}

cy *= width;
by *= width;
ty *= width;

const nn = field[cx + ty] + field[lx + cy] + field[rx + cy] + field[cx + by];
const nnn = field[lx + ty] + field[lx + by] + field[rx + ty] + field[rx + by];
if (navigator.userAgent.match('HeadlessChrome'))
return multipleRules(nn,nnn);
return funcRules(nn,nnn);
}
Insert cell
multipleRules = {
reset;
return multipleRulesGenerator();
}
Insert cell
function multipleRulesGenerator() {
let t = 0;
return (nn,nnn) => {
const rules = ruleArr[t < 5000 ? 0 : (t < 30000 ? 4 : 3)].rule.map(
({numNN, numNNN, p}) =>
({nn,nnn}) =>
(numNN === undefined || numNN.some(d => d === nn)) &&
(numNNN === undefined || numNNN.some(d => d === nnn)) &&
(p === undefined || Math.random() < p)
)
++t;
// rules should encode something in DNF https://en.wikipedia.org/wiki/Disjunctive_normal_form
return !rules.reduce((a2,v2) => a2 || v2({nn,nnn}), false);
}
}
Insert cell
updateSingleParticle = i => {
let x = particles.x[i];
let y = particles.y[i];

x += Math.random() > 0.5 ? 1 : -1;
y += Math.random() > 0.5 ? 1 : -1;

if (x < 0 || y < 0 || x >= width || y >= height) {
resetSingleParticle(particles, i);
} else {
particles.x[i] = x;
particles.y[i] = y;
}

if (!alone(i)) {
particles.stuck[i] = 1;
field[y * width + x] = 1;
}
}
Insert cell
updateSingleParticleFast = i => {
let x = particles.x[i];
let y = particles.y[i];

const [closex, closey] = qt.find(x,y);
const dx = x-closex,
dy = y-closey;
const rsq = dx*dx + dy*dy;
// const rsq = Math.abs(dx) + Math.abs(dy);
if (rsq > 2) {
const [dx,dy] = getPointFromCircle(rsq);
x += dx;
y += dy;
} else {
x += Math.random() > 0.5 ? 1 : -1;
y += Math.random() > 0.5 ? 1 : -1;
}
if (x < 0 || y < 0 || x >= width || y >= height) {
resetSingleParticle3(particles, i);
x = particles.x[i];
y = particles.y[i];
} else {
particles.x[i] = x;
particles.y[i] = y;
}

if (!alone(i)) {
particles.stuck[i] = 1;
field[y * width + x] = 1;
qt.add([x,y]);
}
}
Insert cell
qt = {
fast;
reset;
return d3.quadtree().extent([[0,0],[width,height]]);
}
Insert cell
function getPointFromCircle(rsq) {
let x = (Math.random()-0.5);
let y = (Math.random()-0.5);
let r0sq = x*x + y*y;
while (r0sq > 1) {
x = (Math.random()-0.5);
y = (Math.random()-0.5);
r0sq = x*x + y*y;
}
const r0 = Math.sqrt(0.5*rsq/r0sq);
return [Math.round(x*r0), Math.round(y*r0)];
}
Insert cell
count = fast.count
Insert cell
height = Math.floor(width * 0.75)
Insert cell
width = Math.floor(window.innerWidth * fast.size)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more