Skip to content

Instantly share code, notes, and snippets.

@Garciat
Last active January 2, 2025 10:58
Show Gist options
  • Save Garciat/4e586d81abd67a7e39f820020487b139 to your computer and use it in GitHub Desktop.
Save Garciat/4e586d81abd67a7e39f820020487b139 to your computer and use it in GitHub Desktop.
Vortex // Generates a random tiled vortex pattern. The code is particularly weird.
/* Lazy */
class Memoized {
constructor(it) {
this.it = it;
this.done = false;
this.mem = [];
}
[Symbol.iterator]() {
return new MemoizedIterator(this);
}
}
class MemoizedIterator {
constructor(m) {
this.m = m;
this.i = 0;
}
next() {
if (this.i < this.m.mem.length) {
return { value: this.m.mem[this.i++], done: false };
}
if (this.m.done) {
return { value: undefined, done: true };
}
const res = this.m.it.next();
if (res.done) {
this.m.done = true;
return { value: undefined, done: true };
}
const val = res.value;
this.m.mem[this.i] = val;
this.i += 1;
return { value: val, done: false };
}
}
function memoized(f) {
return new Chain(new Memoized(f()));
}
/* Iterator */
function* map(xs, f) {
for (let x of xs) {
yield f(x);
}
}
function* flatMap(xs, f) {
for (let x of xs) {
yield* f(x);
}
}
function forEach(xs, f) {
for (let x of xs) {
f(x);
}
}
function* take(xs, n) {
let i = 0;
for (let x of xs) {
if (i >= n) {
break;
}
yield x;
i += 1;
}
}
function* cycle(xs) {
while (true) {
yield* xs;
}
}
function* pairwise(xs) {
const sentinel = {};
let last = sentinel;
for (let x of xs) {
if (last !== sentinel) {
yield [last, x];
}
last = x;
}
}
function* takeWhile(xs, f) {
for (let x of xs) {
if (!f(x)) {
break;
}
yield x;
}
}
function* concat(xs, ys) {
yield* xs;
yield* ys;
}
function* zipWith(xs, ys, f) {
const itx = xs[Symbol.iterator]();
const ity = ys[Symbol.iterator]();
while (true) {
const rx = itx.next();
const ry = ity.next();
if (rx.done || ry.done) {
return;
}
const x = rx.value;
const y = ry.value;
yield f(x, y);
}
}
function* tail(xs) {
const itx = xs[Symbol.iterator]();
let rx = itx.next();
if (rx.done) {
return;
}
while (true) {
const rx = itx.next();
if (rx.done) {
return;
}
yield rx.value;
}
}
function zip(xs, ys) {
return zipWith(xs, ys, (x, y) => [x, y]);
}
function reduce(xs, o, f) {
let s = o;
for (let x of xs) {
s = f(s, x);
}
return s;
}
function* filter(xs, f) {
for (let x of xs) {
if (f(x)) {
yield x;
}
}
}
/* Chaining */
class Chain {
constructor(xs) {
this.xs = xs;
}
[Symbol.iterator]() {
return this.xs[Symbol.iterator]();
}
forEach(f) {
return forEach(this.xs, f);
}
reduce(o, f) {
return reduce(this.xs, o, f);
}
toArray() {
return Array.from(this.xs);
}
}
const CHAIN_OPERATORS = [
map, flatMap, take, cycle, pairwise, takeWhile, concat, zipWith, tail, zip, filter
];
for (let op of CHAIN_OPERATORS) {
Chain.prototype[op.name] = function (...args) {
args.unshift(this);
return new Chain(op.apply(undefined, args));
};
}
function chain(xs) {
return new Chain(xs);
}
<!DOCTYPE html>
<html>
<head>
<title>Vortex</title>
<style>html,body{margin:0;height:100%;}</style>
</head>
<body>
<script src="functional.js"></script>
<script defer>
'use strict';
/* Init */
const SPACEW = document.body.clientWidth;
const SPACEH = document.body.clientHeight;
const SPACE_QUAD = [[0, 0], [SPACEW, 0], [SPACEW, SPACEH], [0, SPACEH]];
const canvas = document.createElement('canvas');
canvas.width = SPACEW;
canvas.height = SPACEH;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
/* Maths */
function interp(seg, p) {
const x0 = seg[0][0];
const y0 = seg[0][1];
const dx = seg[1][0] - x0;
const dy = seg[1][1] - y0;
return [x0 + p * dx, y0 + p * dy];
}
function uniform(a, b) {
return a + (b - a) * Math.random();
}
function segmentLength(seg) {
const dx = seg[1][0] - seg[0][0];
const dy = seg[1][1] - seg[0][1];
return Math.sqrt(dx * dx + dy * dy);
}
/* Transforms */
const fst = x => x[0];
const snd = x => x[1];
function quadSegments(q) {
return chain(q).cycle().pairwise().take(4);
}
/* Drawing */
function drawSegment(s) {
ctx.beginPath();
ctx.moveTo(s[0][0], s[0][1]);
ctx.lineTo(s[1][0], s[1][1]);
ctx.lineWidth = 1;
ctx.stroke();
}
function drawQuad(q) {
quadSegments(q).forEach(drawSegment);
}
/* Vortexing */
function* split(quad, depth) {
if (depth === 0) {
yield quad;
return;
}
const line0 = quad.slice(0, 2);
const line1 = quad.slice(2, 4);
const r0 = uniform(0.1, 0.9);
const r1 = uniform(0.1, 0.9);
const p0 = interp(line0, r0);
const p1 = interp(line1, r1);
const q0 = [p0, p1, quad[3], quad[0]];
const q1 = [quad[1], quad[2], p1, p0];
yield* split(q0, depth - 1);
yield* split(q1, depth - 1);
}
function vortexInfinite(quad) {
const points = memoized(function* () {
yield quad[0];
yield* steps.map(snd);
});
const steps = memoized(function* () {
yield* zip(points, segments.tail().map(s => interp(s, 0.1)));
});
const segments = memoized(function* () {
yield* quadSegments(quad);
yield* steps;
});
return segments;
}
function vortex(quad) {
return vortexInfinite(quad).takeWhile(seg => segmentLength(seg) > 1);
}
/* GO ! */
chain(split(SPACE_QUAD, 4))
.flatMap(vortex)
.forEach(drawSegment);
</script>
</body>
<html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment