diff options
Diffstat (limited to 'src/_posts')
-rw-r--r-- | src/_posts/2021-05-16-new-years-resolution-vibe-check.md | 62 | ||||
-rw-r--r-- | src/_posts/2021-05-26-viz-4.md | 213 |
2 files changed, 275 insertions, 0 deletions
diff --git a/src/_posts/2021-05-16-new-years-resolution-vibe-check.md b/src/_posts/2021-05-16-new-years-resolution-vibe-check.md new file mode 100644 index 0000000..965eac8 --- /dev/null +++ b/src/_posts/2021-05-16-new-years-resolution-vibe-check.md @@ -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. + diff --git a/src/_posts/2021-05-26-viz-4.md b/src/_posts/2021-05-26-viz-4.md new file mode 100644 index 0000000..cd6054a --- /dev/null +++ b/src/_posts/2021-05-26-viz-4.md @@ -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> |