diff options
Diffstat (limited to 'src/_posts/2021-05-26-viz-4.md')
-rw-r--r-- | src/_posts/2021-05-26-viz-4.md | 213 |
1 files changed, 0 insertions, 213 deletions
diff --git a/src/_posts/2021-05-26-viz-4.md b/src/_posts/2021-05-26-viz-4.md deleted file mode 100644 index cd6054a..0000000 --- a/src/_posts/2021-05-26-viz-4.md +++ /dev/null @@ -1,213 +0,0 @@ ---- -title: >- - Visualization 4 -description: >- - Birth, death, and colors. -series: viz -tags: tech art ---- - -<canvas id="canvas" style="padding-bottom: 2rem;" width="100%" height="100%"></canvas> - -This visualization is a conglomeration of ideas from all the previous ones. On -each tick up to 20 new pixels are generated. The color of each new pixel is -based on the average color of its neighbors, plus some random drift. - -Each pixel dies after a certain number of ticks, `N`. A pixel's life can be -extended by up to `8N` ticks, one for each neighbor it has which is still alive. -This mechanism accounts for the strange behavior which is seen when the -visualization first loads, but also allows for more coherent clusters of pixels -to hold together as time goes on. - -The asteroid rule is also in effect in this visualization, so the top row and -bottom row pixels are neighbors of each other, and similarly for the rightmost -and leftmost column pixels. - -<script type="text/javascript"> - -function randn(n) { - return Math.floor(Math.random() * n); -} - -const canvas = document.getElementById("canvas"); -const parentWidth = canvas.parentElement.offsetWidth; - -const rectSize = Math.floor(parentWidth /100 /2) *2; // must be even number -console.log("rectSize", rectSize); - -canvas.width = parentWidth - rectSize - (parentWidth % rectSize); -canvas.height = canvas.width * 0.75; -canvas.height -= canvas.height % rectSize; -const ctx = canvas.getContext("2d"); - -const w = (canvas.width / rectSize) - 1; -const h = (canvas.height / rectSize) - 1; - -class Elements { - constructor() { - this.els = {}; - this.diff = {}; - } - - _normCoord(coord) { - if (typeof coord !== 'string') coord = JSON.stringify(coord); - return coord; - } - - get(coord) { - return this.els[this._normCoord(coord)]; - } - - getAll() { - return Object.values(this.els); - } - - set(coord, el) { - this.diff[this._normCoord(coord)] = {action: "set", coord: coord, ...el}; - } - - unset(coord) { - this.diff[this._normCoord(coord)] = {action: "unset"}; - } - - drawDiff(ctx) { - for (const coordStr in this.diff) { - const el = this.diff[coordStr]; - const coord = JSON.parse(coordStr); - - if (el.action == "set") { - ctx.fillStyle = `hsl(${el.h}, ${el.s}, ${el.l})`; - } else { - ctx.fillStyle = `#FFF`; - } - - ctx.fillRect(coord[0]*rectSize, coord[1]*rectSize, rectSize, rectSize); - } - } - - applyDiff() { - for (const coordStr in this.diff) { - const el = this.diff[coordStr]; - delete this.diff[coordStr]; - - if (el.action == "set") { - delete el.action; - this.els[coordStr] = el; - } else { - delete this.els[coordStr]; - } - } - } -} - -const neighbors = [ - [-1, -1], [0, -1], [1, -1], - [-1, 0], /* [0, 0], */ [1, 0], - [-1, 1], [0, 1], [1, 1], -]; - -function neighborsOf(coord) { - return neighbors.map((n) => { - let nX = coord[0]+n[0]; - let nY = coord[1]+n[1]; - nX = (nX + w) % w; - nY = (nY + h) % h; - return [nX, nY]; - }); -} - -function randEmptyNeighboringCoord(els, coord) { - const neighbors = neighborsOf(coord).sort(() => Math.random() - 0.5); - for (const nCoord of neighbors) { - if (!els.get(nCoord)) return nCoord; - } - return null; -} - -function neighboringElsOf(els, coord) { - const neighboringEls = []; - for (const nCoord of neighborsOf(coord)) { - const el = els.get(nCoord); - if (el) neighboringEls.push(el); - } - return neighboringEls; -} - -const drift = 30; -function newEl(nEls) { - - // for each h (which can be considered as degrees around a circle) break the h - // down into x and y vectors, and add those up separately. Then find the angle - // between those two resulting vectors, and that's the "average" h value. - let x = 0; - let y = 0; - nEls.forEach((el) => { - const hRad = el.h * Math.PI / 180; - x += Math.cos(hRad); - y += Math.sin(hRad); - }); - - let h = Math.atan2(y, x); - h = h / Math.PI * 180; - - // apply some random drift, normalize - h += (Math.random() * drift * 2) - drift; - h = (h + 360) % 360; - - return { - h: h, - s: "100%", - l: "50%", - }; -} - -const requestAnimationFrame = - window.requestAnimationFrame || - window.mozRequestAnimationFrame || - window.webkitRequestAnimationFrame || - window.msRequestAnimationFrame; - -const els = new Elements(); - -const maxNewElsPerTick = 20; -const deathThresh = 20; - -let tick = 0; -function doTick() { - tick++; - - const allEls = els.getAll().sort(() => Math.random() - 0.5); - - if (allEls.length == 0) { - els.set([w/2, h/2], { - h: randn(360), - s: "100%", - l: "50%", - }); - } - - let newEls = 0; - for (const el of allEls) { - const nCoord = randEmptyNeighboringCoord(els, el.coord); - if (!nCoord) continue; // el has no empty neighboring spots - - const nEl = newEl(neighboringElsOf(els, nCoord)) - nEl.tick = tick; - els.set(nCoord, nEl); - - newEls++; - if (newEls >= maxNewElsPerTick) break; - } - - for (const el of allEls) { - const nEls = neighboringElsOf(els, el.coord); - if (tick - el.tick - (nEls.length * deathThresh) >= deathThresh) els.unset(el.coord); - } - - els.drawDiff(ctx); - els.applyDiff(); - requestAnimationFrame(doTick); -} -requestAnimationFrame(doTick); - -</script> |