research-method.ts
2.7 kB · typescript · 76 lines
1import * as math from "mrlyjs/math";2import { iso } from "mrlyjs/view";3import type { Color, Ink, Pen, Point } from "mrlyjs/view";45export const units = { math };67const ROWS = 9;8const WIDEST = 6.5;9const COUNTS = [1, 1, 3, 3, 6, 3, 3, 1, 1];1011type Quad = ReturnType<typeof math.three.quads>[number];1213let memo: Quad[][][] | undefined;1415function rows() {16 if (memo) return memo;17 const codes: string[][] = Array.from({ length: ROWS }, () => []);18 for (const design of math.bang.bang(3).canonical()) codes[design.rule().length].push(design.i);19 if (codes.some((row, r) => row.length !== COUNTS[r])) throw new Error(`research-method: rows of ${codes.map((row) => row.length)}, want ${COUNTS}`);20 memo = codes.map((row, r) =>21 row.map((code) => {22 const cube = math.three.create(code, 2, 1, 2);23 if (math.three.fills(cube) !== r) throw new Error(`research-method: design ${code} fills ${math.three.fills(cube)}, want ${r}`);24 return math.three.quads(cube);25 }),26 );27 return memo;28}2930function cage(pen: Pen, cx: number, cy: number, s: number, color: Color) {31 const corner = (i: number): Point => {32 const p = iso.project((i & 1) * 2 - 1, ((i >> 1) & 1) * 2 - 1, ((i >> 2) & 1) * 2 - 1);33 return [cx + p[0] * s, cy + p[1] * s];34 };35 for (let a = 0; a < 8; a++) {36 for (const bit of [1, 2, 4]) if (!(a & bit)) pen.segment(corner(a), corner(a | bit), s / 20, color);37 }38}3940function stamp(pen: Pen, quads: Quad[], cx: number, cy: number, s: number, shade: Color[], edge: Color) {41 const faces = [];42 for (const { normal, verts } of quads) {43 if (normal.x + normal.y + normal.z <= 0) continue;44 const tone = normal.z > 0 ? 0 : normal.y > 0 ? 1 : 2;45 let depth = 0;46 for (const v of verts) depth += Math.fround(Math.fround(v.x + v.y) + v.z);47 const pts = verts.map((v): Point => {48 const p = iso.project(v.x, v.y, v.z);49 return [cx + p[0] * s, cy + p[1] * s];50 });51 faces.push({ depth, tone, pts });52 }53 faces.sort((a, b) => a.depth - b.depth);54 for (const { tone, pts } of faces) {55 pen.polygon(pts, shade[tone]);56 pen.polyline([...pts, pts[0]], s / 14, edge);57 }58}5960export default function draw(pen: Pen, ink: Ink) {61 const frame = pen.frame(0.08);62 const shade = [ink.blue, ink.mix(ink.blue, ink.ground, 0.4), ink.mix(ink.blue, ink.ground, 0.65)];63 const wire = ink.mix(ink.line, ink.dim, 0.3);64 const [mx, my] = frame.center();65 const pitch = frame.h / ROWS;66 const step = frame.w / WIDEST;67 const s = pitch * 0.225;68 rows().forEach((row, r) => {69 const cy = my + (r - (ROWS - 1) / 2) * pitch;70 row.forEach((quads, j) => {71 const cx = mx + (j - (row.length - 1) / 2) * step;72 cage(pen, cx, cy, s, wire);73 stamp(pen, quads, cx, cy, s, shade, ink.ground);74 });75 });76}