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