Published
Edited
Apr 15, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
ruleTable = Array(stateCount * 10)
.fill()
.map(() => Math.floor(Math.random() * stateCount))
Insert cell
updateOnTick = 1
Insert cell
tick = {
let tick = 0;
while (true) {
yield tick++;
}
}
Insert cell
stateCount = 3
Insert cell
commandRender = regl({
frag: `
precision mediump float;
const vec2 size = vec2(${canvas.width}, ${canvas.height});
const vec2 invSize = vec2(
${(1 / canvas.width).toPrecision(5)},
${(1 / canvas.height).toPrecision(5)});

const int radius = ${radius};
const float invRadius = ${(1 / radius).toPrecision(5)};
uniform sampler2D space;
varying vec2 uv;


vec3 getSimplexCellCoords(vec2 coords) {
const float c1 = 0.366025403784439; // 0.5*(sqrt(3.0)-1.0)
const float c2 = 0.211324865405187; // (3.0-sqrt(3.0))/6.0

vec2 doublecellPosition = floor(coords + dot(coords, vec2(c1)) );
vec2 x0 = coords - doublecellPosition + dot(doublecellPosition, vec2(c2));

return vec3(doublecellPosition, x0.x > x0.y ? 1 : -1);
}
void main() {
vec3 simplexCellCoords = getSimplexCellCoords(uv * size);
vec4 doublecell = texture2D(space, simplexCellCoords.xy * invRadius);
float cell = (simplexCellCoords.z <= 0.0) ? doublecell.x : doublecell.y;

if (int(cell * 3.0) == 2) {
gl_FragColor = vec4(1, 0, 0, 1);
} else if (int(cell * 3.0) == 1) {
gl_FragColor = vec4(0, 1, 0, 1);
} else {
gl_FragColor = vec4(0, 0, 1, 1);
}
}`,

vert: `
precision mediump float;
attribute vec2 position;
varying vec2 uv;
uniform mat3 zoomMatrix;
void main() {
vec2 zoomedPosition = (zoomMatrix * vec3(position, 1)).xy;
uv = 0.5 * (zoomedPosition + 1.0);
gl_Position = vec4(position, 0, 1);
}`,
uniforms: {
space: (_, { space }) => space,
zoomMatrix: (_, { zoomMatrix }) => zoomMatrix
},
attributes: { position: [-3, -1, 3, -1, 0, 2] },
count: 3,
depth: { enable: false }
})
Insert cell
ruleTableCode = ruleTable
.map((r, i) => `if (combinedState == ${i}) { return ${r}; }`)
.join(`\n`)
Insert cell
commandUpdate = regl({
frag: `
precision mediump float;
uniform sampler2D space;
const int radius = ${radius};
const float invRadius = ${(1 / radius).toPrecision(5)};
varying vec2 uv;
varying vec2 cellPosition;
const int ruleTableLength = 2 * 4;
int getSymmetryState(int n1, int n2, int n3) {
if (n1 == 0 && n2 == 0 && n3 == 0) { return 0; }
if (n1 == 0 && n2 == 0 && n3 == 1) { return 1; }
if (n1 == 0 && n2 == 0 && n3 == 2) { return 4; }
if (n1 == 0 && n2 == 1 && n3 == 0) { return 1; }
if (n1 == 0 && n2 == 1 && n3 == 1) { return 2; }
if (n1 == 0 && n2 == 1 && n3 == 2) { return 5; }
if (n1 == 0 && n2 == 2 && n3 == 0) { return 4; }
if (n1 == 0 && n2 == 2 && n3 == 1) { return 5; }
if (n1 == 0 && n2 == 2 && n3 == 2) { return 6; }
if (n1 == 1 && n2 == 0 && n3 == 0) { return 1; }
if (n1 == 1 && n2 == 0 && n3 == 1) { return 2; }
if (n1 == 1 && n2 == 0 && n3 == 2) { return 5; }
if (n1 == 1 && n2 == 1 && n3 == 0) { return 2; }
if (n1 == 1 && n2 == 1 && n3 == 1) { return 3; }
if (n1 == 1 && n2 == 1 && n3 == 2) { return 7; }
if (n1 == 1 && n2 == 2 && n3 == 0) { return 5; }
if (n1 == 1 && n2 == 2 && n3 == 1) { return 7; }
if (n1 == 1 && n2 == 2 && n3 == 2) { return 8; }
if (n1 == 2 && n2 == 0 && n3 == 0) { return 4; }
if (n1 == 2 && n2 == 0 && n3 == 1) { return 5; }
if (n1 == 2 && n2 == 0 && n3 == 2) { return 6; }
if (n1 == 2 && n2 == 1 && n3 == 0) { return 5; }
if (n1 == 2 && n2 == 1 && n3 == 1) { return 7; }
if (n1 == 2 && n2 == 1 && n3 == 2) { return 8; }
if (n1 == 2 && n2 == 2 && n3 == 0) { return 6; }
if (n1 == 2 && n2 == 2 && n3 == 1) { return 8; }
if (n1 == 2 && n2 == 2 && n3 == 2) { return 9; }
}
int getNextState(int c, int n1, int n2, int n3) {
int n = getSymmetryState(n1, n2, n3);
int stateCount = ${stateCount};
int combinedState = n * stateCount + c;
${ruleTableCode}
}

void main() {
int nextUpperCell = getNextState(
int(3.0 * texture2D(space, cellPosition * invRadius).g),
int(3.0 * texture2D(space, (cellPosition + vec2(1.0, 0.0)) * invRadius).r),
int(3.0 * texture2D(space, (cellPosition + vec2(0.0, -1.0)) * invRadius).r),
int(3.0 * texture2D(space, cellPosition * invRadius).r));

int nextLowerCell = getNextState(
int(3.0 * texture2D(space, cellPosition * invRadius).r),
int(3.0 * texture2D(space, (cellPosition + vec2(-1.0, 0.0)) * invRadius).g),
int(3.0 * texture2D(space, (cellPosition + vec2(0.0, 1.0)) * invRadius).g),
int(3.0 * texture2D(space, cellPosition * invRadius).g));

gl_FragColor = vec4(float(nextLowerCell) / 3.0, float(nextUpperCell) / 3.0, 0.0, 0.0);
}`,

vert: `
precision mediump float;
const int radius = ${radius};
const float invRadius = ${(1 / radius).toPrecision(5)};
attribute vec2 position;
varying vec2 uv;
varying vec2 cellPosition;
void main() {
uv = 0.5 * (position + 1.0);
cellPosition = uv * float(radius);
gl_Position = vec4(position, 0, 1);
}`,
uniforms: {
space: (_, { space }) => space
},
framebuffer: (_, { nextSpace }) => nextSpace,
attributes: { position: [-3, -1, 3, -1, 0, 2] },
count: 3,
depth: { enable: false }
})
Insert cell
mutable state = (reset,
Array(2)
.fill()
.map(() =>
regl.framebuffer({
color: regl.texture({
radius,
data: initialState,
wrap: 'repeat'
}),
depth: false,
depthStencil: false
})
))
Insert cell
initialState = Array(radius * radius * 4)
.fill()
.map(
(_, i) =>
((Math.random() > 0.8 ? (Math.random() > 0.8 ? 2 : 1) : 0) / 3) * 255
)
Insert cell
Insert cell
updater = {
step;
if (tick % updateOnTick != 0) {
return;
}
commandUpdate(
{
nextSpace: mutable state[0],
space: mutable state[1]
},
() => {
regl.draw();
mutable state = [mutable state[1], mutable state[0]];
}
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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