Created
May 9, 2022 22:40
-
-
Save jcreedcmu/bbe8c9eda2d7d37ac7fe72505c6b614c to your computer and use it in GitHub Desktop.
penrose tiles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Point, rot90, rotate, vdiv, vdot, vplus, vscale } from './point'; | |
const NARROW_COLOR = '#ffffff'; | |
const WIDE_COLOR = '#7777ff'; | |
const BG_COLOR = WIDE_COLOR; // '#dff'; | |
const LINE_WIDTH = 0.25; | |
const width = 1024; | |
const height = 768; | |
const OFF_MIN = -9; | |
const OFF_MAX = 8; | |
const NUM_DIRS = 5; | |
const offsetOfDirection = [0.5, 0.5, 0.5, 0.5, 0.5]; | |
const vec = { x: 1, y: 0 }; | |
const PIX_PER_UNIT = 15; | |
type Line = { | |
n: Point, // we assume throughout this is a unit vector | |
length: number, | |
off: number | |
}; | |
function getNormal(dir: number): Point { | |
return rotate(vec, dir * Math.PI / NUM_DIRS); | |
} | |
function getLine(dir: number, off: number): Line { | |
return { | |
length: 5, | |
n: getNormal(dir), | |
off: off + offsetOfDirection[dir], | |
} | |
} | |
function solve(line1: Line, line2: Line): Point { | |
return { | |
x: (line2.n.y * line1.off - line1.n.y * line2.off) / | |
(line2.n.y * line1.n.x - line1.n.y * line2.n.x), | |
y: (line2.n.x * line1.off - line1.n.x * line2.off) / | |
(line2.n.x * line1.n.y - line1.n.x * line2.n.y), | |
} | |
} | |
function drawLine(d: CanvasRenderingContext2D, line: Line) { | |
const nn = rot90(line.n); | |
const offp = vscale(line.n, PIX_PER_UNIT * line.off); | |
const p1 = vplus(offp, vscale(nn, PIX_PER_UNIT * line.length)); | |
const p2 = vplus(offp, vscale(nn, -PIX_PER_UNIT * line.length)); | |
d.beginPath(); | |
d.moveTo(p1.x, p1.y); | |
d.lineTo(p2.x, p2.y); | |
d.stroke(); | |
} | |
function drawAxes(d: CanvasRenderingContext2D) { | |
d.beginPath(); | |
d.moveTo(-width / 2, 0.5); | |
d.lineTo(width / 2, 0.5); | |
d.stroke(); | |
d.beginPath(); | |
d.moveTo(0.5, -height / 2); | |
d.lineTo(0.5, height / 2); | |
d.stroke(); | |
} | |
function drawLines(d: CanvasRenderingContext2D) { | |
for (let off = OFF_MIN; off <= OFF_MAX; off++) { | |
for (let dir = 0; dir < NUM_DIRS; dir++) { | |
drawLine(d, getLine(dir, off)); | |
} | |
} | |
} | |
function drawPoint(d: CanvasRenderingContext2D, p: Point) { | |
const RADIUS = 3; | |
const pp = vscale(p, PIX_PER_UNIT); | |
d.beginPath(); | |
d.arc(pp.x, pp.y, RADIUS, 0, 2 * Math.PI); | |
d.fill(); | |
} | |
function drawPoints(d: CanvasRenderingContext2D) { | |
for (let off1 = OFF_MIN; off1 <= OFF_MAX; off1++) { | |
for (let off2 = OFF_MIN; off2 <= OFF_MAX; off2++) { | |
for (let dir1 = 0; dir1 < NUM_DIRS; dir1++) { | |
for (let dir2 = dir1 + 1; dir2 < NUM_DIRS; dir2++) { | |
drawPoint(d, solve(getLine(dir1, off1), getLine(dir2, off2))); | |
} | |
} | |
} | |
} | |
} | |
function getFullCoords(dir1: number, off1: number, dir2: number, off2: number): number[] { | |
const p = solve(getLine(dir1, off1), getLine(dir2, off2)); | |
const rv: number[] = []; | |
for (let ix = 0; ix < NUM_DIRS; ix++) { | |
const off = offsetOfDirection[ix]; | |
rv[ix] = Math.floor(vdot(getNormal(ix), p) - off); | |
} | |
rv[dir1] = off1 - 1; | |
rv[dir2] = off2 - 1; | |
return rv; | |
} | |
function drawCell(d: CanvasRenderingContext2D, d1: number, d2: number, coords: number[]) { | |
let origin: Point = { x: 0, y: 0 }; | |
coords.forEach((coord, dir) => { | |
origin = vplus(origin, vscale(getNormal(dir), PIX_PER_UNIT * coord)); | |
}); | |
const vv1 = vscale(getNormal(d1), PIX_PER_UNIT); | |
const vv2 = vscale(getNormal(d2), PIX_PER_UNIT) | |
const pa = vplus(origin, vv1); | |
const pb = vplus(pa, vv2); | |
const pc = vplus(origin, vv2); | |
d.beginPath() | |
d.moveTo(origin.x, origin.y); | |
d.lineTo(pa.x, pa.y); | |
d.lineTo(pb.x, pb.y); | |
d.lineTo(pc.x, pc.y); | |
d.lineTo(origin.x, origin.y); | |
d.fill(); | |
if (LINE_WIDTH > 0) | |
d.stroke(); | |
} | |
function drawCells(d: CanvasRenderingContext2D) { | |
for (let off1 = OFF_MIN; off1 <= OFF_MAX; off1++) { | |
for (let off2 = OFF_MIN; off2 <= OFF_MAX; off2++) { | |
for (let dir1 = 0; dir1 < NUM_DIRS; dir1++) { | |
for (let dir2 = dir1 + 1; dir2 < NUM_DIRS; dir2++) { | |
const diff = Math.abs(dir1 - dir2); | |
if (diff == 1 || diff == 4) d.fillStyle = NARROW_COLOR; | |
if (diff == 2 || diff == 3) d.fillStyle = WIDE_COLOR; | |
drawCell(d, dir1, dir2, getFullCoords(dir1, off1, dir2, off2)); | |
} | |
} | |
} | |
} | |
} | |
function go() { | |
const c = document.getElementsByTagName('canvas')[0]; | |
c.width = width; | |
c.height = height; | |
const d = c.getContext('2d')!; | |
d.fillStyle = BG_COLOR; | |
d.fillRect(0, 0, width, height); | |
d.translate(width / 2, height / 2); | |
d.scale(1, -1); | |
d.strokeStyle = '#9dd'; | |
//drawAxes(d); | |
d.strokeStyle = "#000"; | |
d.lineWidth = LINE_WIDTH; | |
// drawLines(d); | |
// d.fillStyle = '#333'; | |
// drawPoints(d); | |
drawCells(d); | |
} | |
go(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment