parent
681c5d8123
commit
1b1bc6ffd6
@ -0,0 +1,62 @@ |
|||||||
|
--- |
||||||
|
title: >- |
||||||
|
New Year's Resolution Vibe Check |
||||||
|
description: >- |
||||||
|
The not-quite-halfway progress report. |
||||||
|
--- |
||||||
|
|
||||||
|
It's been over five months since I started my New Year's resolution, where I |
||||||
|
committed to writing 52 blog posts by the end of the year. This week I'm on the |
||||||
|
first vacation I've been able to take since the pandemic started, and, for lack |
||||||
|
of anything else to really write about, am doing an almost-halfway checkup on |
||||||
|
the whole process. |
||||||
|
|
||||||
|
Almost immediately into the process I wished I'd set my sights a bit lower. One |
||||||
|
post a week is a pretty intense pace, it turns out. If I were to reset the |
||||||
|
parameters of the resolution I would probably halve the requirements, down to |
||||||
|
26 posts in the year. One concern would be that I would be more likely to forget |
||||||
|
to do the bi-weekly post, whereas with the current system it's coupled with my |
||||||
|
normal work rhythm and so stays more top of mind. But I think I'd have a much |
||||||
|
easier time (perhaps even twice as easy!), so it might balance out. |
||||||
|
|
||||||
|
My thought in the beginning was that I could write on Friday afternoons or |
||||||
|
Monday mornings as a bookend to working, but what's generally happened is that I |
||||||
|
write on weekends. During the week the energy to write something up just isn't |
||||||
|
there; writing posts is a kind of work all on its own, and I can only bring |
||||||
|
myself to do so much work everyday. |
||||||
|
|
||||||
|
Lately it's been particularly difficult to pump out the posts. Obviously a large |
||||||
|
component of this is that I quickly picked all the low hanging fruit that were |
||||||
|
on my mind when I started this resolution, but an unexpected culprit has also |
||||||
|
appeared: seasons. When I started the resolution it was still winter, and during |
||||||
|
the cold months it's a lot easier to stay inside and work on a computer. As the |
||||||
|
weather warms it's been harder to find time though, in between working on the |
||||||
|
garden and going out and doing things with friends. |
||||||
|
|
||||||
|
Figuring out what to write about is becoming more of a challenge as well |
||||||
|
(obviously, given the topic of this post). Ideally I'd like to post about things |
||||||
|
I'm _doing_, rather than just talking about some topic, and for the most part |
||||||
|
I've mostly kept to that. Constantly posting about ideas I have or opinions I |
||||||
|
hold isn't really contributing any real work, unless the ideas or opinions are |
||||||
|
really groundbreaking (they're not). If, on the other hand, I use the posts as a |
||||||
|
kind of background motivation to get up and do something useful, so I can write |
||||||
|
about what I did, then at least progress has been made on _something_. |
||||||
|
|
||||||
|
The catch there is that I've now added an additional "thing" to do every week, |
||||||
|
in addition to the weekly post, and, as previously covered, I just don't have |
||||||
|
the time and energy for that. So some posts (ahem) are pretty much fluff, and I |
||||||
|
barely have the energy for those! Yet another reason to wish I'd committed to 26 |
||||||
|
in the year, I suppose. |
||||||
|
|
||||||
|
It hasn't been all added stress and strife though. Doing the posts _has_ caused |
||||||
|
me to work on side projects more, and even better quite a few people I know have |
||||||
|
given me really good feedback on what I've been doing, and some have even |
||||||
|
started getting involved. So, in the sense of being a way to inform others about |
||||||
|
the things I'm working on, the posts are a great success! And I've definitely |
||||||
|
been more consistent about working on side projects this year. |
||||||
|
|
||||||
|
I'll wrap this up and continue with my vacation. Summary: blog is more extra |
||||||
|
work than expected, it's maybe worth it, but it would be more worth it if I |
||||||
|
halved my pace. I'm not _going_ to halve my pace, because that's not how |
||||||
|
resolutions work. The end. |
||||||
|
|
@ -0,0 +1,213 @@ |
|||||||
|
--- |
||||||
|
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> |
Loading…
Reference in new issue