From 44158f5595675db92087b34126bb485da2b92434 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 16 Feb 2024 00:23:31 -0800 Subject: [PATCH] feat: Add VRML97 Conway notation generator --- etc/manifest.json | 1 + package.json5 | 4 +- pnpm-lock.yaml | 94 ++++++------- src/conway.civet | 300 ++++++++++++++++++++++++++++++++++++++++++ src/giveAwrl.civet | 22 ++++ tools/makePlugin.bash | 2 +- 6 files changed, 373 insertions(+), 50 deletions(-) create mode 100644 src/conway.civet diff --git a/etc/manifest.json b/etc/manifest.json index d352204..84ad5fe 100644 --- a/etc/manifest.json +++ b/etc/manifest.json @@ -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", diff --git a/package.json5 b/package.json5 index 420a342..4d1cc03 100644 --- a/package.json5 +++ b/package.json5 @@ -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', }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c61379..bbe7f3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/conway.civet b/src/conway.civet new file mode 100644 index 0000000..0aeca42 --- /dev/null +++ b/src/conway.civet @@ -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 := + '': 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 ð(...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 æ(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 := + 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 diff --git a/src/giveAwrl.civet b/src/giveAwrl.civet index 692ced2..1c1dc5d 100644 --- a/src/giveAwrl.civet +++ b/src/giveAwrl.civet @@ -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 := $(`${vrml}`) + 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' diff --git a/tools/makePlugin.bash b/tools/makePlugin.bash index 709d2cd..38e5b8f 100644 --- a/tools/makePlugin.bash +++ b/tools/makePlugin.bash @@ -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