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}