The Construct
Fragments of an Ordered World
Log in to post a comment.
Canvas.setpenopacity(1);
/* Turtletoy build (ESM -> ESM.define/require) */
const ESM = (() => {
'use strict';
const modules = Object.create(null);
function define(id, factory) {
modules[id] = { factory, exports: null };
}
function require(id) {
const m = modules[id];
if (m.exports) return m.exports;
m.exports = Object.create(null);
m.factory(m.exports, require);
return m.exports;
}
return { define, require };
})();
ESM.define("engine.js", (exports, require) => {
'use strict';
/* ========================= engine.js ========================= */
const { zbInit, zbClear, zbTri, zbGet } = require("zbuffer.js");
let turtle = null;
let FOV = 0.5;
let CAM_Z = 11;
let OUT_SCALE = 22;
let OUT_OX = 0;
let OUT_OY = 0;
let ZW = 1200;
let ZH = 1200;
let ZBUF = null;
function drawLine (x0, y0, x1, y1) {
const was = turtle.isdown();
turtle.penup();
turtle.goto(x0, y0);
turtle.pendown();
turtle.goto(x1, y1);
if (!was) turtle.penup();
};
const cubeQueue = [];
const cubePool = {
state: [],
top: 0,
alloc() {
return this.top ? this.state[--this.top] : new Float32Array(8 * 3);
},
free(a) {
this.state[this.top++] = a;
}
};
let ZB_OX = 100;
let ZB_OY = 100;
let ZB_SX = 1;
let ZB_SY = 1;
const EPS_Z = 1e-9;
let seed = (Math.random() * 1000000) | 0;
function randomSeed(s) {
s = Number(s);
if (seed === s) return;
seed = s;
noiseSeed(s);
}
function random01() {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
}
function random(a = 1, b) {
const t = random01();
return (b === undefined) ? t * a : a + (b - a) * t;
}
// ----- Perlin noise (Processing)
const PERLIN_YWRAPB = 4;
const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
const PERLIN_ZWRAPB = 8;
const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
const PERLIN_SIZE = 4095;
const perlin = new Float32Array(PERLIN_SIZE + 1);
let perlinOctaves = 4;
let perlinAmpFalloff = 0.5;
function noiseSeed(s) {
const save = seed;
seed = s;
for (let i = 0; i <= PERLIN_SIZE; i++) perlin[i] = random01();
seed = save;
}
noiseSeed(seed);
function noiseFsc(t) {
return 0.5 * (1 - Math.cos(Math.PI * t));
}
function noise(x, y = 0, z = 0) {
if (x < 0) x = -x;
if (y < 0) y = -y;
if (z < 0) z = -z;
let xi = x | 0, yi = y | 0, zi = z | 0;
let xf = x - xi;
let yf = y - yi;
let zf = z - zi;
let r = 0;
let ampl = 0.5;
for (let o = 0; o < perlinOctaves; o++) {
let of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
const rxf = noiseFsc(xf);
const ryf = noiseFsc(yf);
let n1 = perlin[of & PERLIN_SIZE];
n1 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n1);
let n2 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
n2 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
n1 += ryf * (n2 - n1);
of += PERLIN_ZWRAP;
n2 = perlin[of & PERLIN_SIZE];
n2 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n2);
let n3 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
n3 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3);
n2 += ryf * (n3 - n2);
n1 += noiseFsc(zf) * (n2 - n1);
r += n1 * ampl;
ampl *= perlinAmpFalloff;
xi <<= 1; xf *= 2;
yi <<= 1; yf *= 2;
zi <<= 1; zf *= 2;
if (xf >= 1) { xi++; xf--; }
if (yf >= 1) { yi++; yf--; }
if (zf >= 1) { zi++; zf--; }
}
return r;
}
function noiseDetail(lod = 4, falloff = 0.5) {
if (lod > 0) perlinOctaves = lod | 0;
if (falloff > 0) perlinAmpFalloff = falloff;
}
const matStack = [];
const matPool = {
state: [],
top: 0,
alloc() {
return this.top ? this.state[--this.top] : new Float32Array(16);
},
free(m) {
this.state[this.top++] = m;
}
};
let sp = 0;
const CUBE_V = new Float32Array([
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
]);
const FACE_Q = new Uint8Array([
4, 5, 6, 7,
1, 0, 3, 2,
5, 1, 2, 6,
0, 4, 7, 3,
7, 6, 2, 3,
0, 1, 5, 4
]);
const EDGE_V = new Uint8Array([
0, 1, 1, 2, 2, 3, 3, 0,
4, 5, 5, 6, 6, 7, 7, 4,
0, 4, 1, 5, 2, 6, 3, 7
]);
const P = new Float32Array(8 * 3);
const faceQueue = [];
const c = 65536;
let segX0 = new Float32Array(c);
let segY0 = new Float32Array(c);
let segX1 = new Float32Array(c);
let segY1 = new Float32Array(c);
let segN = 0;
let segKx0 = new Int32Array(c);
let segKy0 = new Int32Array(c);
let segKx1 = new Int32Array(c);
let segKy1 = new Int32Array(c);
let segUx = new Float32Array(c);
let segUy = new Float32Array(c);
let segNx = new Float32Array(c);
let segNy = new Float32Array(c);
let segC = new Float32Array(c);
let segT0 = new Float32Array(c);
let segT1 = new Float32Array(c);
function segAuxEnsure(n) {
const cap = segKx0.length;
if (n <= cap) return;
let ncap = cap;
while (ncap < n) ncap <<= 1;
{
const a = new Int32Array(ncap); a.set(segKx0); segKx0 = a;
const b = new Int32Array(ncap); b.set(segKy0); segKy0 = b;
const c = new Int32Array(ncap); c.set(segKx1); segKx1 = c;
const d = new Int32Array(ncap); d.set(segKy1); segKy1 = d;
}
{
const a = new Float32Array(ncap); a.set(segUx); segUx = a;
const b = new Float32Array(ncap); b.set(segUy); segUy = b;
const c = new Float32Array(ncap); c.set(segNx); segNx = c;
const d = new Float32Array(ncap); d.set(segNy); segNy = d;
const e = new Float32Array(ncap); e.set(segC); segC = e;
const f = new Float32Array(ncap); f.set(segT0); segT0 = f;
const g = new Float32Array(ncap); g.set(segT1); segT1 = g;
}
}
function hash4i(a, b, c, d) {
let h = (a * 73856093) ^ (b * 19349663) ^ (c * 83492791) ^ (d * 2654435761);
return h >>> 0;
}
function hash3i(a, b, c) {
let h = (a * 73856093) ^ (b * 19349663) ^ (c * 83492791);
return h >>> 0;
}
function segReset() {
segN = 0;
}
function segEnsure(n) {
const cap = segX0.length;
if (n <= cap) return;
let ncap = cap;
while (ncap < n) ncap <<= 1;
const x0 = new Float32Array(ncap);
const y0 = new Float32Array(ncap);
const x1 = new Float32Array(ncap);
const y1 = new Float32Array(ncap);
x0.set(segX0); y0.set(segY0); x1.set(segX1); y1.set(segY1);
segX0 = x0; segY0 = y0; segX1 = x1; segY1 = y1;
}
function segPush(x0, y0, x1, y1) {
segEnsure(segN + 1);
segX0[segN] = x0; segY0[segN] = y0;
segX1[segN] = x1; segY1[segN] = y1;
segN++;
}
function segFlush() {
const n0 = segN;
if (!n0) return;
segAuxEnsure(n0);
const Q = 10000;
const seen = new Map();
let n = 0;
for (let i = 0; i < n0; i++) {
let x0 = segX0[i], y0 = segY0[i], x1 = segX1[i], y1 = segY1[i];
if (x0 > x1 || (x0 === x1 && y0 > y1)) {
const tx = x0; x0 = x1; x1 = tx;
const ty = y0; y0 = y1; y1 = ty;
}
const kx0 = Math.round(x0 * Q) | 0;
const ky0 = Math.round(y0 * Q) | 0;
const kx1 = Math.round(x1 * Q) | 0;
const ky1 = Math.round(y1 * Q) | 0;
const h = hash4i(kx0, ky0, kx1, ky1);
let list = seen.get(h);
let dup = 0;
if (list) {
for (let j = 0; j < list.length; j++) {
const k = list[j];
if (segKx0[k] === kx0 && segKy0[k] === ky0 && segKx1[k] === kx1 && segKy1[k] === ky1) {
dup = 1;
break;
}
}
} else {
list = [];
seen.set(h, list);
}
if (dup) continue;
segX0[n] = x0; segY0[n] = y0;
segX1[n] = x1; segY1[n] = y1;
segKx0[n] = kx0; segKy0[n] = ky0;
segKx1[n] = kx1; segKy1[n] = ky1;
list.push(n);
n++;
}
if (!n) return;
const qDir = 1e-3;
const qC = 1e-3;
const buckets = new Map();
const MERGE_GAP = 5e-4;
for (let i = 0; i < n; i++) {
const x0 = segX0[i], y0 = segY0[i], x1 = segX1[i], y1 = segY1[i];
const dx = x1 - x0;
const dy = y1 - y0;
const len = Math.hypot(dx, dy);
if (len < 1e-8) continue;
let ux = dx / len;
let uy = dy / len;
if (ux < 0 || (ux === 0 && uy < 0)) { ux = -ux; uy = -uy; }
const nx = -uy;
const ny = ux;
const c = nx * x0 + ny * y0;
const t0 = ux * x0 + uy * y0;
const t1 = ux * x1 + uy * y1;
segUx[i] = ux; segUy[i] = uy;
segNx[i] = nx; segNy[i] = ny;
segC[i] = c;
segT0[i] = t0;
segT1[i] = t1;
const k0 = Math.round(ux / qDir) | 0;
const k1 = Math.round(uy / qDir) | 0;
const k2 = Math.round(c / qC) | 0;
const h = hash3i(k0, k1, k2);
let b = buckets.get(h);
if (!b) {
b = { k0, k1, k2, idx: [] };
buckets.set(h, b);
} else if (b.k0 !== k0 || b.k1 !== k1 || b.k2 !== k2) {
// rare hash collision, linear probe via small chain
let chain = b.chain;
if (!chain) { chain = []; b.chain = chain; }
let hit = null;
for (let j = 0; j < chain.length; j++) {
const bb = chain[j];
if (bb.k0 === k0 && bb.k1 === k1 && bb.k2 === k2) { hit = bb; break; }
}
if (!hit) { hit = { k0, k1, k2, idx: [] }; chain.push(hit); }
b = hit;
}
b.idx.push(i);
}
for (const b of buckets.values()) {
const first = b;
const list0 = first.idx;
if (list0.length) {
list0.sort((ia, ib) => segT0[ia] - segT0[ib]);
mergeBucket(list0);
}
const chain = first.chain;
if (chain) {
for (let ci = 0; ci < chain.length; ci++) {
const list = chain[ci].idx;
if (!list.length) continue;
list.sort((ia, ib) => segT0[ia] - segT0[ib]);
mergeBucket(list);
}
}
}
function mergeBucket(arr) {
let i0 = arr[0];
let t0 = segT0[i0];
let t1 = segT1[i0];
const ux = segUx[i0], uy = segUy[i0];
const nx = segNx[i0], ny = segNy[i0];
const c = segC[i0];
for (let j = 1; j < arr.length; j++) {
const i = arr[j];
const s0 = segT0[i];
const s1 = segT1[i];
if (s0 <= t1 + MERGE_GAP) {
if (s1 > t1) t1 = s1;
continue;
}
emitMerged(ux, uy, nx, ny, c, t0, t1);
t0 = s0;
t1 = s1;
}
emitMerged(ux, uy, nx, ny, c, t0, t1);
}
function emitMerged(ux, uy, nx, ny, c, t0, t1) {
const ax = ux * t0 + nx * c;
const ay = uy * t0 + ny * c;
const bx = ux * t1 + nx * c;
const by = uy * t1 + ny * c;
drawLine(ax, ay, bx, by);
}
}
function mat4Identity(m) {
m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 0;
m[4] = 0; m[5] = 1; m[6] = 0; m[7] = 0;
m[8] = 0; m[9] = 0; m[10] = 1; m[11] = 0;
m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;
return m;
}
function mat4Copy(out, a) { out.set(a); return out; }
function mat4Translate(m, x, y, z) {
m[12] += m[0] * x + m[4] * y + m[8] * z;
m[13] += m[1] * x + m[5] * y + m[9] * z;
m[14] += m[2] * x + m[6] * y + m[10] * z;
return m;
}
function mat4Scale(m, x, y, z) {
m[0] *= x; m[1] *= x; m[2] *= x;
m[4] *= y; m[5] *= y; m[6] *= y;
m[8] *= z; m[9] *= z; m[10] *= z;
return m;
}
function mat4RotateX(m, a) {
const s = Math.sin(a), c = Math.cos(a);
const a4 = m[4], a5 = m[5], a6 = m[6];
const a8 = m[8], a9 = m[9], a10 = m[10];
m[4] = a4 * c + a8 * s;
m[5] = a5 * c + a9 * s;
m[6] = a6 * c + a10 * s;
m[8] = a8 * c - a4 * s;
m[9] = a9 * c - a5 * s;
m[10] = a10 * c - a6 * s;
return m;
}
function mat4RotateY(m, a) {
const s = Math.sin(a), c = Math.cos(a);
const a0 = m[0], a1 = m[1], a2 = m[2];
const a8 = m[8], a9 = m[9], a10 = m[10];
m[0] = a0 * c - a8 * s;
m[1] = a1 * c - a9 * s;
m[2] = a2 * c - a10 * s;
m[8] = a0 * s + a8 * c;
m[9] = a1 * s + a9 * c;
m[10] = a2 * s + a10 * c;
return m;
}
function mat4RotateZ(m, a) {
const s = Math.sin(a), c = Math.cos(a);
const a0 = m[0], a1 = m[1], a2 = m[2];
const a4 = m[4], a5 = m[5], a6 = m[6];
m[0] = a0 * c + a4 * s;
m[1] = a1 * c + a5 * s;
m[2] = a2 * c + a6 * s;
m[4] = a4 * c - a0 * s;
m[5] = a5 * c - a1 * s;
m[6] = a6 * c - a2 * s;
return m;
}
function pushMatrix() {
const m = matPool.alloc();
if (sp) mat4Copy(m, matStack[sp - 1]);
else mat4Identity(m);
matStack[sp++] = m;
}
function popMatrix() {
if (sp <= 1) return;
matPool.free(matStack[--sp]);
}
function currentMatrix() { return matStack[sp - 1]; }
function transformPoint(out3, m, x, y, z) {
out3[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
out3[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
out3[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
}
function buildCubeAndRaster(m) {
const cube = cubePool.alloc();
for (let i = 0; i < 8; i++) {
const ix = i * 3;
transformPoint(
P.subarray(ix, ix + 3),
m,
CUBE_V[ix + 0],
CUBE_V[ix + 1],
CUBE_V[ix + 2]
);
}
for (let i = 0; i < 8; i++) {
const ix = i * 3;
const x = P[ix + 0], y = P[ix + 1], z = P[ix + 2];
const denom = CAM_Z - z;
const o = i * 3;
if (denom <= 0.01) {
cube[o + 2] = 0;
continue;
}
const invW = 1 / denom;
const k = (1 / Math.tan(FOV * 0.5)) * invW;
cube[o + 0] = (x * k) * OUT_SCALE + OUT_OX;
cube[o + 1] = (y * k) * OUT_SCALE + OUT_OY;
cube[o + 2] = invW;
}
rasterCubeFaces(cube);
cubeQueue.push(cube);
}
function rasterCubeFaces(cube) {
for (let f = 0; f < 6; f++) {
const ia = FACE_Q[f * 4 + 0];
const ib = FACE_Q[f * 4 + 1];
const ic = FACE_Q[f * 4 + 2];
const id = FACE_Q[f * 4 + 3];
const a = ia * 3, b = ib * 3, c = ic * 3, d = id * 3;
const ax = cube[a + 0], ay = cube[a + 1], az = cube[a + 2];
const bx = cube[b + 0], by = cube[b + 1], bz = cube[b + 2];
const cx = cube[c + 0], cy = cube[c + 1], cz = cube[c + 2];
const dx = cube[d + 0], dy = cube[d + 1], dz = cube[d + 2];
if (!az || !bz || !cz || !dz) continue;
zbTri(ax, ay, az, bx, by, bz, cx, cy, cz);
zbTri(ax, ay, az, cx, cy, cz, dx, dy, dz);
}
}
function emitCubeVisibleEdges(cube) {
for (let e = 0; e < EDGE_V.length; e += 2) {
const a = EDGE_V[e + 0] * 3;
const b = EDGE_V[e + 1] * 3;
const ax = cube[a + 0], ay = cube[a + 1], az = cube[a + 2];
const bx = cube[b + 0], by = cube[b + 1], bz = cube[b + 2];
if (!az || !bz) continue;
emitVisibleEdge(ax, ay, az, bx, by, bz);
}
}
function emitVisibleEdge(ax, ay, az, bx, by, bz) {
const pax = (ax + ZB_OX) * ZB_SX;
const pay = (ay + ZB_OY) * ZB_SY;
const pbx = (bx + ZB_OX) * ZB_SX;
const pby = (by + ZB_OY) * ZB_SY;
let x0 = pax, y0 = pay;
let x1 = pbx, y1 = pby;
let dx = x1 - x0;
let dy = y1 - y0;
const adx = Math.abs(dx);
const ady = Math.abs(dy);
let steps = Math.ceil(Math.max(adx, ady));
if (steps < 1) steps = 1;
const invSteps = 1 / steps;
const GAP_MAX = 1;
let run = 0;
let t0 = 0;
let gap = 0;
for (let i = 0; i <= steps; i++) {
const t = i * invSteps;
const px = x0 + dx * t;
const py = y0 + dy * t;
let ix = Math.floor(px);
let iy = Math.floor(py);
if (ix < 0) ix = 0;
else if (ix >= ZW) ix = ZW - 1;
if (iy < 0) iy = 0;
else if (iy >= ZH) iy = ZH - 1;
const z = az + (bz - az) * t;
let vis = 0;
let base = iy * ZW + ix;
if (z >= ZBUF[base] - EPS_Z) vis = 1;
if (!vis) {
const fx = px - ix;
if (fx < 0.001 && ix > 0) {
if (z >= ZBUF[base - 1] - EPS_Z) vis = 1;
} else if (fx > 0.999 && ix + 1 < ZW) {
if (z >= ZBUF[base + 1] - EPS_Z) vis = 1;
}
}
if (!vis) {
const fy = py - iy;
if (fy < 0.001 && iy > 0) {
if (z >= ZBUF[base - ZW] - EPS_Z) vis = 1;
} else if (fy > 0.999 && iy + 1 < ZH) {
if (z >= ZBUF[base + ZW] - EPS_Z) vis = 1;
}
}
if (vis) {
if (!run) { run = 1; t0 = t; }
gap = 0;
} else if (run) {
gap++;
if (gap > GAP_MAX) {
const t1 = t - gap * invSteps;
if (t1 > t0) emitEdgeSegment(ax, ay, bx, by, t0, t1);
run = 0;
gap = 0;
}
}
}
if (run) {
const t1 = 1 - gap * invSteps;
if (t1 > t0) emitEdgeSegment(ax, ay, bx, by, t0, t1);
}
}
function emitEdgeSegment(ax, ay, bx, by, t0, t1) {
const x0 = ax + (bx - ax) * t0;
const y0 = ay + (by - ay) * t0;
const x1 = ax + (bx - ax) * t1;
const y1 = ay + (by - ay) * t1;
const dx = x1 - x0;
const dy = y1 - y0;
if (dx * dx + dy * dy < 1e-6) return;
segPush(x0, y0, x1, y1);
}
function radians(d) { return d * Math.PI / 180; }
function rotateXDeg(d) { mat4RotateX(currentMatrix(), radians(d)); return api; }
function rotateYDeg(d) { mat4RotateY(currentMatrix(), radians(d)); return api; }
function rotateZDeg(d) { mat4RotateZ(currentMatrix(), radians(d)); return api; }
const api = {
setOutputTransform(scale, ox, oy) { OUT_SCALE = scale; OUT_OX = ox; OUT_OY = oy; return this; },
perspective(fov) { FOV = fov; return this; },
cameraZ(z) { CAM_Z = z; return this; },
beginFrame() {
zbClear();
cubeQueue.length = 0;
segReset();
sp = 0;
pushMatrix();
return this;
},
endFrame() {
for (let i = 0; i < cubeQueue.length; i++) {
emitCubeVisibleEdges(cubeQueue[i]);
cubePool.free(cubeQueue[i]);
}
cubeQueue.length = 0;
segFlush();
return this;
},
pushMatrix() { pushMatrix(); return this; },
popMatrix() { popMatrix(); return this; },
translate(x, y, z = 0) { mat4Translate(currentMatrix(), x, y, z); return this; },
scale(x, y = x, z = x) { mat4Scale(currentMatrix(), x, y, z); return this; },
rotateX(a) { mat4RotateX(currentMatrix(), a); return this; },
rotateY(a) { mat4RotateY(currentMatrix(), a); return this; },
rotateZ(a) { mat4RotateZ(currentMatrix(), a); return this; },
rotateXDeg,
rotateYDeg,
rotateZDeg,
radians,
randomSeed(s) { randomSeed(s); return this; },
noiseSeed(s) { noiseSeed(s); return this; },
random,
lerp(a, b, t) { return a + (b - a) * t; },
constrain(x, a, b) { return Math.max(a, Math.min(b, x)); },
noiseDetail(lod = 4, ampFalloff = 0.5) { noiseDetail(lod, ampFalloff); return this; },
noise,
box(w, h, d) {
const m = matPool.alloc();
mat4Copy(m, currentMatrix());
mat4Scale(m, w, h, d);
buildCubeAndRaster(m);
matPool.free(m);
return this;
}
};
function initEngine(t, cfg = {}) {
turtle = t;
if (cfg.fov != null) FOV = cfg.fov;
if (cfg.camZ != null) CAM_Z = cfg.camZ;
if (cfg.outScale != null) OUT_SCALE = cfg.outScale;
if (cfg.outOx != null) OUT_OX = cfg.outOx;
if (cfg.outOy != null) OUT_OY = cfg.outOy;
zbInit({ w: ZW, h: ZH, x0: -100, y0: -100, x1: 100, y1: 100 });
ZBUF = zbGet();
ZB_SX = ZW / 200;
ZB_SY = ZH / 200;
return api;
}
exports.initEngine = initEngine;
});
ESM.define("zbuffer.js", (exports, require) => {
'use strict';
/* ========================= zbuffer.js ========================= */
let w = 0, h = 0;
let depth = null;
let x0 = -100, y0 = -100;
let sx = 1, sy = 1;
function zbInit(cfg = {}) {
w = cfg.w | 0;
h = cfg.h | 0;
if (w <= 0) w = 200;
if (h <= 0) h = 200;
x0 = (cfg.x0 != null) ? cfg.x0 : -100;
y0 = (cfg.y0 != null) ? cfg.y0 : -100;
const x1 = (cfg.x1 != null) ? cfg.x1 : 100;
const y1 = (cfg.y1 != null) ? cfg.y1 : 100;
sx = w / (x1 - x0);
sy = h / (y1 - y0);
depth = new Float32Array(w * h);
zbClear();
}
function zbClear() {
const d = depth;
for (let i = 0; i < d.length; i++) d[i] = -Infinity;
}
function toPxX(x) { return (x - x0) * sx; }
function toPxY(y) { return (y - y0) * sy; }
function zbTri(ax, ay, az, bx, by, bz, cx, cy, cz) {
ax = toPxX(ax); ay = toPxY(ay);
bx = toPxX(bx); by = toPxY(by);
cx = toPxX(cx); cy = toPxY(cy);
const abx = bx - ax, aby = by - ay;
const acx = cx - ax, acy = cy - ay;
const area2 = abx * acy - aby * acx;
if (area2 <= 0) return;
let minX = ax; if (bx < minX) minX = bx; if (cx < minX) minX = cx;
let maxX = ax; if (bx > maxX) maxX = bx; if (cx > maxX) maxX = cx;
let minY = ay; if (by < minY) minY = by; if (cy < minY) minY = cy;
let maxY = ay; if (by > maxY) maxY = by; if (cy > maxY) maxY = cy;
let ix0 = Math.floor(minX);
let ix1 = Math.ceil(maxX);
let iy0 = Math.floor(minY);
let iy1 = Math.ceil(maxY);
if (ix1 < 0 || iy1 < 0 || ix0 >= w || iy0 >= h) return;
if (ix0 < 0) ix0 = 0;
if (iy0 < 0) iy0 = 0;
if (ix1 > w - 1) ix1 = w - 1;
if (iy1 > h - 1) iy1 = h - 1;
const invArea = 1 / area2;
const d = depth;
const A = (az * (by - cy) + bz * (cy - ay) + cz * (ay - by)) * invArea;
const B = (az * (cx - bx) + bz * (ax - cx) + cz * (bx - ax)) * invArea;
const C = (az * (bx * cy - cx * by) + bz * (cx * ay - ax * cy) + cz * (ax * by - bx * ay)) * invArea;
const bias = 0.5 * (Math.abs(A) + Math.abs(B)) + 1e-8;
for (let y = iy0; y <= iy1; y++) {
const py = y + 0.5;
for (let x = ix0; x <= ix1; x++) {
const px = x + 0.5;
const e0 = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
const e1 = (cx - bx) * (py - by) - (cy - by) * (px - bx);
const e2 = (ax - cx) * (py - cy) - (ay - cy) * (px - cx);
if (e0 < 0 || e1 < 0 || e2 < 0) continue;
const z = A * px + B * py + C;
const zb = z - bias;
const id = y * w + x;
if (zb > d[id]) d[id] = zb;
}
}
}
function zbGet() {
return depth;
}
exports.zbInit = zbInit;
exports.zbClear = zbClear;
exports.zbTri = zbTri;
exports.zbGet = zbGet;
});
ESM.define("sketch.js", (exports, require) => {
'use strict';
/* processing sketch port (match original PDE as closely as possible)
float time;
float rnoise(float x) {
float t1 = time * 4;
float t2 = (time - 1) * 4;
float n1 = noise(x, t1);
float n2 = noise(x, t2);
float n = lerp(n1, n2, time);
return constrain(n * 5 - 0.2, 0, 1);
}
void setup() {
size(500, 500, P3D);
colorMode(RGB, 1);
noiseDetail(1);
}
void draw() {
int totalFrames = 144 * 6;
time = (float)frameCount / totalFrames;
randomSeed(132);
background(1);
perspective(0.5, 1, 0.01, 100);
camera(
0, 0, 11,
0, 0, 0,
0, 1, 0
);
rotateX(-0.5 + sin(PI * 4 * time) * 0.1);
rotateY(-0.5 + sin(PI * 2 * time) * 0.2);
drawSurface(0, 0);
drawSurface(90, 0);
drawSurface(180, 0);
drawSurface(-90, 0);
drawSurface(90, 90);
drawSurface(90, -90);
}
void drawSurface(float rx, float ry) {
int columns = 2;
pushMatrix();
rotateY(radians(ry));
rotateX(radians(rx));
float offs = 0.5 * columns - 0.5;
translate(-offs, -0.5 * columns, -offs);
for (int ix = 0; ix < columns; ix++) {
for (int iy = 0; iy < columns; iy++) {
pushMatrix();
translate(ix, 0, iy);
float w = random(0.95, 1.0);
float h = 0.2;
drawUnit(w, h, 1, (int)random(4, 6));
popMatrix();
}
}
popMatrix();
}
void drawUnit(float w, float h, int lv, int maxLv) {
if (lv >= maxLv) return;
translate(0, -0.5 * h, 0);
box(w, h, w);
translate(0, -0.5 * h, 0);
for (int i = 0; i < 4; i++) {
float w2 = w * 0.5 * random(0.9, 1);
float h2 = h * random(0.1, 1.75);
h2 *= rnoise(random(0, 10));
float dx = w * 0.5 * ((i / 2) - 0.5);
float dy = w * 0.5 * ((i & 1) - 0.5);
int maxLv2 = maxLv + (int)random(-1, 1.1);
pushMatrix();
translate(dx, 0, dy);
drawUnit(w2, h2, lv + 1, maxLv2);
popMatrix();
}
}*/
let p = null;
let seed = 1;
let time = 1;
function setup(eng, cfg = {}) {
p = eng;
seed = cfg.seed ? cfg.seed : (Math.random() * 10000) | 0;
p.randomSeed(seed);
p.noiseDetail(1, 0.5);
}
function rnoise(x) {
const t1 = time * 4;
const t2 = (time - 1) * 4;
const n1 = p.noise(x, t1, 0);
const n2 = p.noise(x, t2, 0);
const n = p.lerp(n1, n2, time);
return p.constrain(n * 5 - 0.2, 0, 1);
}
function drawSurface(rxDeg, ryDeg) {
const columns = 2;
p.pushMatrix();
p.rotateYDeg(ryDeg);
p.rotateXDeg(rxDeg);
const offs = 0.5 * columns - 0.5;
p.translate(-offs, -0.5 * columns, -offs);
for (let ix = 0; ix < columns; ix++) {
for (let iy = 0; iy < columns; iy++) {
p.pushMatrix();
p.translate(ix, 0, iy);
const w = p.random(0.95, 1.0);
const h = 0.2;
drawUnit(w, h, 1, Math.floor(p.random(4, 7)));
p.popMatrix();
}
}
p.popMatrix();
}
function drawUnit(w, h, lv, maxLv) {
if (lv >= maxLv) return;
p.translate(0, -0.5 * h, 0);
p.box(w, h, w);
p.translate(0, -0.5 * h, 0);
for (let i = 0; i < 4; i++) {
const w2 = w * 0.5 * p.random(0.9, 1.0);
let h2 = h * p.random(0.2, 2);
h2 *= rnoise(p.random(0, 10));
const dx = w * 0.5 * (((i / 2) | 0) - 0.5);
const dz = w * 0.5 * ((i & 1) - 0.5);
let maxLv2 = maxLv + Math.floor(p.random(-1, 1.1));
if (maxLv2 < lv + 1) maxLv2 = lv + 1;
p.pushMatrix();
p.translate(dx, 0, dz);
drawUnit(w2, h2, lv + 1, maxLv2);
p.popMatrix();
}
}
function draw() {
p.randomSeed(seed);
p.beginFrame();
p.rotateX(p.random(-Math.PI, Math.PI));
p.rotateY(p.random(-Math.PI, Math.PI));
drawSurface(0, 0);
drawSurface(90, 0);
drawSurface(180, 0);
drawSurface(-90, 0);
drawSurface(90, 90);
drawSurface(90, -90);
p.endFrame();
};
exports.setup = setup;
exports.draw = draw;
});
// ----- boot -----
const { initEngine } = ESM.require("engine.js");
const sketch = ESM.require("sketch.js");
const seed = ""; // type=string, Enter your seed! (Empty = random)
const turtle = new Turtle();
turtle.penup();
const p = initEngine(turtle, {
fov: 0.5,
camZ: 11,
outScale: 100,
});
sketch.setup(p, { seed });
sketch.draw(0);