parent
7e15864c35
commit
8d1de40ef7
@ -0,0 +1,154 @@ |
|||||||
|
--- |
||||||
|
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> |
Loading…
Reference in new issue