155 lines
4.3 KiB
Markdown
155 lines
4.3 KiB
Markdown
|
---
|
||
|
title: >-
|
||
|
Visualization 3
|
||
|
description: >-
|
||
|
All the pixels.
|
||
|
series: viz
|
||
|
---
|
||
|
|
||
|
<canvas id="canvas" style="padding-bottom: 2rem;"></canvas>
|
||
|
|
||
|
This visualization is built from the ground up. On every frame a random set of
|
||
|
pixels is chosen. Each chosen pixel calculates the average of its color and the
|
||
|
color of a random neighbor. Some random color drift is added in as well. It
|
||
|
replaces its own color with that calculated color.
|
||
|
|
||
|
Choosing a neighbor is done using the "asteroid rule", ie a pixel at the very
|
||
|
top row is considered to be the neighbor of the pixel on the bottom row of the
|
||
|
same column.
|
||
|
|
||
|
Without the asteroid rule the pixels would all eventually converge into a single
|
||
|
uniform color, generally a light blue, due to the colors at the edge, the reds,
|
||
|
being quickly averaged away. With the asteroid rule in place the canvas has no
|
||
|
edges, thus no position on the canvas is favored and balance can be maintained.
|
||
|
|
||
|
<script type="text/javascript">
|
||
|
let rectSize = 12;
|
||
|
|
||
|
function randn(n) {
|
||
|
return Math.floor(Math.random() * n);
|
||
|
}
|
||
|
|
||
|
let canvas = document.getElementById("canvas");
|
||
|
canvas.width = window.innerWidth - (window.innerWidth % rectSize);
|
||
|
canvas.height = window.innerHeight- (window.innerHeight % rectSize);
|
||
|
let ctx = canvas.getContext("2d");
|
||
|
|
||
|
let w = canvas.width / rectSize;
|
||
|
let h = canvas.height / rectSize;
|
||
|
|
||
|
let matrices = new Array(2);
|
||
|
matrices[0] = new Array(w);
|
||
|
matrices[1] = new Array(w);
|
||
|
for (let x = 0; x < w; x++) {
|
||
|
matrices[0][x] = new Array(h);
|
||
|
matrices[1][x] = new Array(h);
|
||
|
for (let y = 0; y < h; y++) {
|
||
|
let el = {
|
||
|
h: 360 * (x / w),
|
||
|
s: "100%",
|
||
|
l: "50%",
|
||
|
};
|
||
|
matrices[0][x][y] = el;
|
||
|
matrices[1][x][y] = el;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// draw initial canvas, from here on out only individual rectangles will be
|
||
|
// filled as they get updated.
|
||
|
for (let x = 0; x < w; x++) {
|
||
|
for (let y = 0; y < h; y++) {
|
||
|
let el = matrices[0][x][y];
|
||
|
ctx.fillStyle = `hsl(${el.h}, ${el.s}, ${el.l})`;
|
||
|
ctx.fillRect(x * rectSize, y * rectSize, rectSize, rectSize);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
let requestAnimationFrame =
|
||
|
window.requestAnimationFrame ||
|
||
|
window.mozRequestAnimationFrame ||
|
||
|
window.webkitRequestAnimationFrame ||
|
||
|
window.msRequestAnimationFrame;
|
||
|
|
||
|
let neighbors = [
|
||
|
[-1, -1], [0, -1], [1, -1],
|
||
|
[-1, 0], [1, 0],
|
||
|
[-1, 1], [0, 1], [1, 1],
|
||
|
];
|
||
|
|
||
|
function randNeighborAsteroid(matrix, x, y) {
|
||
|
let neighborCoord = neighbors[randn(neighbors.length)];
|
||
|
let neighborX = x+neighborCoord[0];
|
||
|
let neighborY = y+neighborCoord[1];
|
||
|
neighborX = (neighborX + w) % w;
|
||
|
neighborY = (neighborY + h) % h;
|
||
|
return matrix[neighborX][neighborY];
|
||
|
}
|
||
|
|
||
|
function randNeighbor(matrix, x, y) {
|
||
|
while (true) {
|
||
|
let neighborCoord = neighbors[randn(neighbors.length)];
|
||
|
let neighborX = x+neighborCoord[0];
|
||
|
let neighborY = y+neighborCoord[1];
|
||
|
if (neighborX < 0 || neighborX >= w || neighborY < 0 || neighborY >= h) {
|
||
|
continue;
|
||
|
}
|
||
|
return matrix[neighborX][neighborY];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let drift = 10;
|
||
|
function genChildH(elA, elB) {
|
||
|
// set the two h values, h1 <= h2
|
||
|
let h1 = elA.h;
|
||
|
let h2 = elB.h;
|
||
|
if (h1 > h2) {
|
||
|
h1 = elB.h;
|
||
|
h2 = elA.h;
|
||
|
}
|
||
|
|
||
|
// diff must be between 0 (inclusive) and 360 (exclusive). If it's greater
|
||
|
// than 180 then it's not the shortest path around, that must be the other
|
||
|
// way around the circle.
|
||
|
let hChild;
|
||
|
let diff = h2 - h1;
|
||
|
if (diff > 180) {
|
||
|
diff = 360 - diff;
|
||
|
hChild = h2 + (diff / 2);
|
||
|
} else {
|
||
|
hChild = h1 + (diff / 2);
|
||
|
}
|
||
|
|
||
|
hChild += (Math.random() * drift * 2) - drift;
|
||
|
hChild = (hChild + 360) % 360;
|
||
|
return hChild;
|
||
|
}
|
||
|
|
||
|
let tick = 0;
|
||
|
function doTick() {
|
||
|
tick++;
|
||
|
let currI = tick % 2;
|
||
|
let curr = matrices[currI];
|
||
|
let lastI = (tick - 1) % 2;
|
||
|
let last = matrices[lastI];
|
||
|
|
||
|
for (let i = 0; i < (w * h / 2); i++) {
|
||
|
let x = randn(w);
|
||
|
let y = randn(h);
|
||
|
if (curr[x][y].lastTick == tick) continue;
|
||
|
|
||
|
let neighbor = randNeighborAsteroid(last, x, y);
|
||
|
curr[x][y].h = genChildH(curr[x][y], neighbor);
|
||
|
curr[x][y].lastTick = tick;
|
||
|
ctx.fillStyle = `hsl(${curr[x][y].h}, ${curr[x][y].s}, ${curr[x][y].l})`;
|
||
|
ctx.fillRect(x * rectSize, y * rectSize, rectSize, rectSize);
|
||
|
}
|
||
|
|
||
|
matrices[currI] = curr;
|
||
|
requestAnimationFrame(doTick);
|
||
|
}
|
||
|
|
||
|
requestAnimationFrame(doTick);
|
||
|
|
||
|
</script>
|