feat: Add VRML97 Conway notation generator

This commit is contained in:
Glen Whitney 2024-02-16 00:23:31 -08:00
parent 37bea13d30
commit 44158f5595
6 changed files with 373 additions and 50 deletions

View File

@ -36,6 +36,7 @@
"deps/x_ite/assets/components/Text.js",
"adapptlet.js",
"adapptypes.js",
"conway.js",
"options.js",
"deps/GeoGebra/deployggb.js",
"deps/GeoGebra/HTML5/5.0/webSimple/4B19686283BEF852F4C88C93522FB9A3.cache.js",

View File

@ -34,12 +34,12 @@
url: 'https://code.studioinfinity.org/glen/archematics.git',
},
devDependencies: {
'@danielx/civet': '^0.6.72',
'@danielx/civet': '^0.6.73',
'@types/firefox-webext-browser': '^120.0.0',
'@types/jquery': '^3.5.29',
'@webcomponents/custom-elements': '^1.6.0',
'http-server': '^14.1.1',
rollup: '^4.10.0',
rollup: '^4.11.0',
typescript: '^5.3.3',
'webextension-polyfill': '^0.10.0',
},

View File

@ -14,8 +14,8 @@ dependencies:
devDependencies:
'@danielx/civet':
specifier: ^0.6.72
version: 0.6.72(typescript@5.3.3)
specifier: ^0.6.73
version: 0.6.73(typescript@5.3.3)
'@types/firefox-webext-browser':
specifier: ^120.0.0
version: 120.0.0
@ -29,8 +29,8 @@ devDependencies:
specifier: ^14.1.1
version: 14.1.1
rollup:
specifier: ^4.10.0
version: 4.10.0
specifier: ^4.11.0
version: 4.11.0
typescript:
specifier: ^5.3.3
version: 5.3.3
@ -47,8 +47,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@danielx/civet@0.6.72(typescript@5.3.3):
resolution: {integrity: sha512-jumnIbXbdFs0ZiKN62fmD+p8QGi+E0jmtc02dKz9wIIoPkODsa4XXlBrS5BRR5fr3w5d3ah8Vq7gWt+DL9Wa0Q==}
/@danielx/civet@0.6.73(typescript@5.3.3):
resolution: {integrity: sha512-VOq02JgXNArsLzq/I2OnZtn16exiF8/mIhpR5O6Tsc+97RU6Qe8EP8XJskh/Hm9koUODzWUW8UuFpFdVzm7wTA==}
engines: {node: '>=19 || ^18.6.0 || ^16.17.0'}
hasBin: true
peerDependencies:
@ -78,104 +78,104 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@rollup/rollup-android-arm-eabi@4.10.0:
resolution: {integrity: sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==}
/@rollup/rollup-android-arm-eabi@4.11.0:
resolution: {integrity: sha512-BV+u2QSfK3i1o6FucqJh5IK9cjAU6icjFFhvknzFgu472jzl0bBojfDAkJLBEsHFMo+YZg6rthBvBBt8z12IBQ==}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-android-arm64@4.10.0:
resolution: {integrity: sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==}
/@rollup/rollup-android-arm64@4.11.0:
resolution: {integrity: sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-arm64@4.10.0:
resolution: {integrity: sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==}
/@rollup/rollup-darwin-arm64@4.11.0:
resolution: {integrity: sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-x64@4.10.0:
resolution: {integrity: sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==}
/@rollup/rollup-darwin-x64@4.11.0:
resolution: {integrity: sha512-OvqIgwaGAwnASzXaZEeoJY3RltOFg+WUbdkdfoluh2iqatd090UeOG3A/h0wNZmE93dDew9tAtXgm3/+U/B6bw==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.10.0:
resolution: {integrity: sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==}
/@rollup/rollup-linux-arm-gnueabihf@4.11.0:
resolution: {integrity: sha512-X17s4hZK3QbRmdAuLd2EE+qwwxL8JxyVupEqAkxKPa/IgX49ZO+vf0ka69gIKsaYeo6c1CuwY3k8trfDtZ9dFg==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-gnu@4.10.0:
resolution: {integrity: sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==}
/@rollup/rollup-linux-arm64-gnu@4.11.0:
resolution: {integrity: sha512-673Lu9EJwxVB9NfYeA4AdNu0FOHz7g9t6N1DmT7bZPn1u6bTF+oZjj+fuxUcrfxWXE0r2jxl5QYMa9cUOj9NFg==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-musl@4.10.0:
resolution: {integrity: sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==}
/@rollup/rollup-linux-arm64-musl@4.11.0:
resolution: {integrity: sha512-yFW2msTAQNpPJaMmh2NpRalr1KXI7ZUjlN6dY/FhWlOclMrZezm5GIhy3cP4Ts2rIAC+IPLAjNibjp1BsxCVGg==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-riscv64-gnu@4.10.0:
resolution: {integrity: sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==}
/@rollup/rollup-linux-riscv64-gnu@4.11.0:
resolution: {integrity: sha512-kKT9XIuhbvYgiA3cPAGntvrBgzhWkGpBMzuk1V12Xuoqg7CI41chye4HU0vLJnGf9MiZzfNh4I7StPeOzOWJfA==}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-gnu@4.10.0:
resolution: {integrity: sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==}
/@rollup/rollup-linux-x64-gnu@4.11.0:
resolution: {integrity: sha512-6q4ESWlyTO+erp1PSCmASac+ixaDv11dBk1fqyIuvIUc/CmRAX2Zk+2qK1FGo5q7kyDcjHCFVwgGFCGIZGVwCA==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-musl@4.10.0:
resolution: {integrity: sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==}
/@rollup/rollup-linux-x64-musl@4.11.0:
resolution: {integrity: sha512-vIAQUmXeMLmaDN78HSE4Kh6xqof2e3TJUKr+LPqXWU4NYNON0MDN9h2+t4KHrPAQNmU3w1GxBQ/n01PaWFwa5w==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-arm64-msvc@4.10.0:
resolution: {integrity: sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==}
/@rollup/rollup-win32-arm64-msvc@4.11.0:
resolution: {integrity: sha512-LVXo9dDTGPr0nezMdqa1hK4JeoMZ02nstUxGYY/sMIDtTYlli1ZxTXBYAz3vzuuvKO4X6NBETciIh7N9+abT1g==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-ia32-msvc@4.10.0:
resolution: {integrity: sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==}
/@rollup/rollup-win32-ia32-msvc@4.11.0:
resolution: {integrity: sha512-xZVt6K70Gr3I7nUhug2dN6VRR1ibot3rXqXS3wo+8JP64t7djc3lBFyqO4GiVrhNaAIhUCJtwQ/20dr0h0thmQ==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-x64-msvc@4.10.0:
resolution: {integrity: sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==}
/@rollup/rollup-win32-x64-msvc@4.11.0:
resolution: {integrity: sha512-f3I7h9oTg79UitEco9/2bzwdciYkWr8pITs3meSDSlr1TdvQ7IxkQaaYN2YqZXX5uZhiYL+VuYDmHwNzhx+HOg==}
cpu: [x64]
os: [win32]
requiresBuild: true
@ -556,26 +556,26 @@ packages:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true
/rollup@4.10.0:
resolution: {integrity: sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==}
/rollup@4.11.0:
resolution: {integrity: sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
dependencies:
'@types/estree': 1.0.5
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.10.0
'@rollup/rollup-android-arm64': 4.10.0
'@rollup/rollup-darwin-arm64': 4.10.0
'@rollup/rollup-darwin-x64': 4.10.0
'@rollup/rollup-linux-arm-gnueabihf': 4.10.0
'@rollup/rollup-linux-arm64-gnu': 4.10.0
'@rollup/rollup-linux-arm64-musl': 4.10.0
'@rollup/rollup-linux-riscv64-gnu': 4.10.0
'@rollup/rollup-linux-x64-gnu': 4.10.0
'@rollup/rollup-linux-x64-musl': 4.10.0
'@rollup/rollup-win32-arm64-msvc': 4.10.0
'@rollup/rollup-win32-ia32-msvc': 4.10.0
'@rollup/rollup-win32-x64-msvc': 4.10.0
'@rollup/rollup-android-arm-eabi': 4.11.0
'@rollup/rollup-android-arm64': 4.11.0
'@rollup/rollup-darwin-arm64': 4.11.0
'@rollup/rollup-darwin-x64': 4.11.0
'@rollup/rollup-linux-arm-gnueabihf': 4.11.0
'@rollup/rollup-linux-arm64-gnu': 4.11.0
'@rollup/rollup-linux-arm64-musl': 4.11.0
'@rollup/rollup-linux-riscv64-gnu': 4.11.0
'@rollup/rollup-linux-x64-gnu': 4.11.0
'@rollup/rollup-linux-x64-musl': 4.11.0
'@rollup/rollup-win32-arm64-msvc': 4.11.0
'@rollup/rollup-win32-ia32-msvc': 4.11.0
'@rollup/rollup-win32-x64-msvc': 4.11.0
fsevents: 2.3.3
dev: true

300
src/conway.civet Normal file
View File

@ -0,0 +1,300 @@
type Face = number[]
type XYZ = [number, number, number]
type Polyhedron = {face: Face[], xyz: XYZ[]}
type Notation = string
// Useful constants
rt2 := Math.sqrt 2
rth := rt2/2
phi := (1 + Math.sqrt 5)/2
ihp := 1/phi
tau := 2*Math.PI
// edge midpoints on unit sphere for all seeds
icosahedron: Polyhedron :=
face: [[0,1,9], [0,8,1], [0,4,8], [0,5,4], [0,9,5],
[4,5,2], [4,2,10], [4,10,8], [8,10,6], [8,6,1],
[1,6,7], [1,7,9], [9,7,11], [9,11,5], [5,11,2],
[3,2,11], [3,10,2], [3,6,10], [3,7,6], [3,11,7]]
xyz: [[0,ihp,1], [0,-ihp,1], [0,ihp,-1], [0,-ihp,-1],
[ihp,1,0], [-ihp,1,0], [ihp,-1,0], [-ihp,-1,0],
[1,0,ihp], [1,0,-ihp], [-1,0,ihp], [-1,0,-ihp]]
polyCache: Record<Notation, Polyhedron> :=
'': face: [], xyz: []
T:
face: [[0,1,2], [0,2,3], [0,3,1], [1,3,2]]
xyz: [[1,1,1], [1,-1,-1], [-1,1,-1], [-1,-1,1]]
O:
face: [[0,1,2], [0,2,3], [0,3,4], [0,4,1],
[1,4,5], [1,5,2], [2,5,3], [3,5,4]]
xyz: [[0,0,rt2], [rt2,0,0], [0,rt2,0], [-rt2,0,0], [0,-rt2,0], [0,0,-rt2]]
C:
face: [[3,0,1,2], [3,4,5,0], [0,5,6,1], [1,6,7,2], [2,7,4,3], [5,4,7,6]]
xyz: [[rth,rth,rth], [-rth,rth,rth], [-rth,-rth,rth], [rth,-rth,rth],
[rth,-rth,-rth], [rth,rth,-rth], [-rth,rth,-rth], [-rth,-rth,-rth]]
I: icosahedron
D: geomDual(icosahedron)
export function generateVRML(notation: Notation): string
outputVRML notation, generatePoly notation
function generatePoly(notation: Notation): Polyhedron
getStandardPoly standardize notation
function getStandardPoly(notation: Notation): Polyhedron
if notation in polyCache then return polyCache[notation]
[ , op, arg, rest] := notation.match(/^(.)(\d*)(.*)$/) or []
parent := getStandardPoly rest
// may have created what we want by side effect
if notation in polyCache then return polyCache[notation]
dispatch op, Number(arg or 0), parent, notation // will do the caching
// Convenience tuple maker
function ð<Tup extends unknown[]>(...arg: Tup): Tup arg
rawStandardizations :=
P4$: 'C', A3$: 'O', Y3$: 'T', // Seed synonyms
e: 'aa', b: 'ta', o: 'jj', m: 'kj', // abbreviations
[String.raw`t(\d*)`]: 'd$1d', j: 'dad', s: 'dgd', // dual operations
dd: '', ad: 'a', gd: 'g', // absorption of duals
// Remainder are all simplifications/unique selections for seeds:
aY: 'A', dT: 'T', gT: 'D', aT: 'O', dC: 'O', dO: 'C',
dI: 'D', dD: 'I', aO: 'aC', aI: 'aD', gO: 'gC', gI: 'gD'
standardizations :=
(ð RegExp(pat, 'g'), rep for pat, rep in rawStandardizations)
function standardize(notation: Notation): Notation
for [pat, rep] of standardizations
notation = notation.replace(pat, rep)
notation
function orb(r: number, n: number,
height: number, t = 0): XYZ[]
// A regular n-gon inscribed in a horizontal circle of radius r
// at given height, rotated t*tau/n from standard position.
theta := tau/n
rho := t*theta
for i of [0...n]
[r*Math.cos(rho + i*theta), r*Math.sin(rho + i*theta), height] as XYZ
seeds :=
P: (n: number) => // Prism
theta := tau/n
halfEdge := Math.sin theta/2
xyz := orb(1, n, halfEdge) ++ orb(1, n, -halfEdge)
face := [[n-1..0], [n...2*n]] // top and bottom
for i of [0...n]
ip1 := (i+1)%n // next vertex around
face.push [i, ip1, ip1+n, i+n]
{face, xyz}
A: (n: number) => // Antiprism
theta := tau/n
halfHeight .= Math.sqrt
1 - 4/(4 + 2*Math.cos(theta/2) - 2*Math.cos(theta))
faceRadius .= Math.sqrt 1-halfHeight*halfHeight
// Scale to put edge midpoints on unit sphere
f := mag [halfHeight, faceRadius*Math.cos(theta/2), 0]
halfHeight /= f
faceRadius /= f
xyz := orb(faceRadius, n, halfHeight)
++ orb(faceRadius, n, halfHeight, 0.5)
face := [[n-1..0], [n...2*n]] // top and bottom
for i of [0...n]
face.push [i, (i+1)%n, i+n]
face.push [i, i+n, n + (n+i-1)%n]
{face, xyz}
Y: (n: number) => // pYramid
// Canonical solution by Intelligenti Pauca and Ed Pegg, see
// https://math.stackexchange.com/questions/2286628/canonical-pyramid-polynomials
theta := tau/n
c := Math.cos theta/2
baseRadius := Math.sqrt 2/(c*(1+c))
xyz := orb baseRadius, n, Math.tan theta/4
xyz.push [0, 0, -1/Math.tan theta/4]
face := ([i, (i+1)%n, n] for i of [0...n])
face.unshift [n-1..0]
{face, xyz}
type SeedOp = keyof typeof seeds
// Syntactic sugar to deal with weird TypeScript typing:
operator æ<T>(A: T[], i: number) A.at(i) as T
transforms :=
k: (P: Polyhedron, n: number, notation: string): Polyhedron => // kis[n]
// aka "elevate" -- add a pyramid on each (n-sided) face
centers := faceCenters P
xyz := P.xyz.slice()
face: Face[] := []
for each f, ix of P.face
if n is 0 or f.length is n
v := xyz.length
xyz.push centers[ix]
for each j of [0...f.length]
face.push [v, f æ (j-1), f[j]]
else face.push f.slice()
adjustXYZ({face, xyz}, notation, 3)
type TransformOp = keyof typeof transforms
function dispatch(op: string, n: number,
P: Polyhedron, notation: string): Polyhedron
return .= P
if op in seeds
return = seeds[op as SeedOp] n
else if op in transforms
return = transforms[op as TransformOp] P, n, notation
polyCache[notation] = return.value
function topoDual(P: Polyhedron): Polyhedron
// Note this maintains correspondence between V and F indices, but
// places every vertex at the origin! Don't use without geometrizing
// in some way.
face:
for v of [0...P.xyz.length]
infaces :=
for f, index of P.face
unless f.includes v continue
ð f, index
start := infaces[0][1];
current .= start
do
verts := P.face[current]
preV := verts æ (verts.indexOf(v)-1)
nextIx := infaces.findIndex ([face, label]) =>
label !== current and face.includes preV
current = infaces[nextIx][1]
until current is start
xyz:
Array(P.face.length).fill([0,0,0]) // warning, every vertex is ===
function geomDual(P: Polyhedron): Polyhedron
// Takes the vertices of the dual to be the face centers of P
// all scaled so that the midpoint of the first edge is unit distance
// from the origin
return := topoDual(P)
newVertices := faceCenters(P)
aface := return.value.face[0]
mid2 := add newVertices[aface[0]], newVertices[aface[1]]
factor := 2/mag mid2
for each v of newVertices
scale(v, factor)
return.value.xyz = newVertices
function faceCenters(P: Polyhedron): XYZ[]
for each face of P.face
scale face.reduce((ctr,v) => accumulate(ctr, P.xyz[v]), [0,0,0]),
1/face.length
function adjustXYZ(P: Polyhedron, notation: string, iterations = 1): Polyhedron
dualNotation := 'd' + notation
D .= topoDual P
if dualNotation in polyCache
console.error 'Error: Creating', notation, 'after its dual'
D = polyCache[dualNotation]
for iter of [1..iterations]
D.xyz = reciprocalC P
P.xyz = reciprocalC D
polyCache[dualNotation] = D
P
function reciprocalC(P: Polyhedron): XYZ[]
return := faceCenters P
for each v of return.value
scale v, 1/mag2 v
// arithmetic on 3-vectors
function accumulate(basket: XYZ, egg: XYZ)
basket[0] += egg[0]
basket[1] += egg[1]
basket[2] += egg[2]
basket
function copy(a: XYZ)
ð a[0], a[1], a[2]
function add(a: XYZ, b: XYZ)
accumulate copy(a), b
function mag2(a: XYZ)
a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
function mag(a: XYZ)
Math.sqrt mag2 a
function scale(subject: XYZ, by: number)
subject[0] *= by
subject[1] *= by
subject[2] *= by
subject
// VRML97 generation
function outputVRML(notation: Notation, P: Polyhedron): string
```
#VRML V2.0 utf8
Group { children [
WorldInfo { # Generated by GTW's reimplementation of GWH's Conway script.
title "${notation} ${stats P}"
info "Generated by GTW's Conway-notation script inspired by GWH's."
info "By using this script, you agree that this image is released"
info "into the public domain, although you are requested to cite"
info "George Hart's Encyclopedia of Polyhedra as the source." }
Background {
groundColor [ .2 .5 1 ] # light blue
skycolor [ .2 .5 1] }
NavigationInfo { type [ "EXAMINE" ] }
DirectionalLight {direction -.5 -1 1 intensity 0.75}
DirectionalLight {direction .5 1 -1 intensity 0.75}
${polyVRML P, colorScheme()} ] }
```
function stats(P: Polyhedron): string
edges := P.face.reduce((e,f) => e + f.length, 0) / 2
`[${P.face.length} faces, ${edges} edges, ${P.xyz.length} vertices]`
useVerts := 'USE verts'
function defVerts(v: XYZ[])
`DEF verts Coordinate {
point [
${v.map(.join ' ').join(",\n ")} ] }`
function polyVRML(P: Polyhedron, color: string): string
facePartition := color ? [P.face]
: P.face.reduce ((parts, f) =>
(parts[f.length] ??= []).push f
parts),
[] as Face[][]
emittedCoords .= ''
shapes :=
for part of facePartition
unless part continue
`
Shape {
appearance Appearance {
material Material {
diffuseColor ${color or colorBySides part[0].length} } }
geometry IndexedFaceSet {
coord ${emittedCoords ? useVerts : (emittedCoords = defVerts P.xyz)}
coordIndex [
${part.map(.join ', ').join(", -1,\n ")}, -1 ] }}`
shapes.join "\n"
function colorScheme()
button := document.getElementsByName('color')[0] as HTMLInputElement
button.checked ? '1 1 1' : ''
faceColors: Record<number, string> :=
3: '0.9 0.3 0.3' // red
4: '0.4 0.4 1.0' // blue
5: '0.2 0.9 0.3' // green
6: '0.9 0.9 0.2' // yellow
7: '0.5 0.25 0.25' // brown
8: '0.8 0.2 0.8' // magenta
9: '0.5 0.2 0.8' // purple
10: '0.1 0.9 0.9' // cyan
12: '1.0 0.6 0.1' // orange
function colorBySides(n: number)
if n in faceColors
return faceColors[n]
return '0.5 0.5 0.5' // gray

View File

@ -101,3 +101,25 @@ links.after ->
eye.setAttribute 'data', 'off'
$(eye).css 'text-decoration', 'none'
$(eye.lastElementChild as Element).hide()
// See if we are on George Hart's Conway-notation generator page
inputs := $('input[type="button"][value="Generate"][onclick="viewVRML()"]')
if inputs.length is 1
// Seems so, fix the generator
// Note that modifying the onclick prop is not the recommended way to
// change button click functionality, but we need to clear out the old
// behavior so I wasn't sure how else to do it
inputs.prop 'onclick', (i, val) => () =>
import(browser.runtime.getURL('conway.js')).then (conway) =>
notation := $('input[name="notation"]').val()
unless notation then return
vrml := conway.generateVRML notation.toString()
viewerSpan := $(`<span>${vrml}</span>`)
viewerSpan.css 'float', 'left'
$('form[name="input"]').first().before viewerSpan
// See if we are on George Hart's prism generator page
prisms := $('input[type="button"][value="View"][onclick="ViewVRML()"]')
if prisms.length is 1
// Seems so, fix the generator
console.log 'Need to fix the prism generator'

View File

@ -21,7 +21,7 @@ done
npx rollup public/js/giveAwrl.js --dir $1
npx rollup public/js/adapptlet.js --file $1/adapptlet.js
npx rollup public/js/adapptext.js --file $1/adapptext.js
cp public/js/options.js public/js/adapptypes.js $1
cp public/js/options.js public/js/adapptypes.js public/js/conway.js $1
cp node_modules/webextension-polyfill/dist/browser-polyfill.js $1
cp node_modules/@webcomponents/custom-elements/custom-elements.min.js $1
zip -r $1 $1