feat: Reimplementation of prism generator #65

Merged
glen merged 3 commits from prism into main 2024-02-19 21:52:56 +00:00
7 changed files with 402 additions and 80 deletions
Showing only changes of commit 279bb4b30b - Show all commits

View File

@ -38,6 +38,7 @@
"adapptypes.js", "adapptypes.js",
"conway.js", "conway.js",
"options.js", "options.js",
"prism.js",
"deps/GeoGebra/deployggb.js", "deps/GeoGebra/deployggb.js",
"deps/GeoGebra/HTML5/5.0/webSimple/4B19686283BEF852F4C88C93522FB9A3.cache.js", "deps/GeoGebra/HTML5/5.0/webSimple/4B19686283BEF852F4C88C93522FB9A3.cache.js",
"deps/GeoGebra/HTML5/5.0/webSimple/webSimple.nocache.js", "deps/GeoGebra/HTML5/5.0/webSimple/webSimple.nocache.js",

View File

@ -34,17 +34,17 @@
url: 'https://code.studioinfinity.org/glen/archematics.git', url: 'https://code.studioinfinity.org/glen/archematics.git',
}, },
devDependencies: { devDependencies: {
'@danielx/civet': '^0.6.73', '@danielx/civet': '^0.6.76',
'@types/firefox-webext-browser': '^120.0.0', '@types/firefox-webext-browser': '^120.0.0',
'@types/jquery': '^3.5.29', '@types/jquery': '^3.5.29',
'@webcomponents/custom-elements': '^1.6.0', '@webcomponents/custom-elements': '^1.6.0',
'http-server': '^14.1.1', 'http-server': '^14.1.1',
rollup: '^4.11.0', rollup: '^4.12.0',
typescript: '^5.3.3', typescript: '^5.3.3',
'webextension-polyfill': '^0.10.0', 'webextension-polyfill': '^0.10.0',
}, },
dependencies: { dependencies: {
colorsea: '^1.2.1', colorsea: '^1.2.1',
vrml1to97: '^0.3.2', vrml1to97: '^0.4.0',
}, },
} }

View File

@ -9,13 +9,13 @@ dependencies:
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
vrml1to97: vrml1to97:
specifier: ^0.3.2 specifier: ^0.4.0
version: 0.3.2 version: 0.4.0
devDependencies: devDependencies:
'@danielx/civet': '@danielx/civet':
specifier: ^0.6.73 specifier: ^0.6.76
version: 0.6.73(typescript@5.3.3) version: 0.6.76(typescript@5.3.3)
'@types/firefox-webext-browser': '@types/firefox-webext-browser':
specifier: ^120.0.0 specifier: ^120.0.0
version: 120.0.0 version: 120.0.0
@ -29,8 +29,8 @@ devDependencies:
specifier: ^14.1.1 specifier: ^14.1.1
version: 14.1.1 version: 14.1.1
rollup: rollup:
specifier: ^4.11.0 specifier: ^4.12.0
version: 4.11.0 version: 4.12.0
typescript: typescript:
specifier: ^5.3.3 specifier: ^5.3.3
version: 5.3.3 version: 5.3.3
@ -47,8 +47,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
dev: true dev: true
/@danielx/civet@0.6.73(typescript@5.3.3): /@danielx/civet@0.6.76(typescript@5.3.3):
resolution: {integrity: sha512-VOq02JgXNArsLzq/I2OnZtn16exiF8/mIhpR5O6Tsc+97RU6Qe8EP8XJskh/Hm9koUODzWUW8UuFpFdVzm7wTA==} resolution: {integrity: sha512-pOXbht6MAr56kNxXOJj9p+od3uRgnrLGxBL8oqM3ox4blXe6ufICC/ubiRjZQ+bZP5odaYDe7rhzGDIEc53N6A==}
engines: {node: '>=19 || ^18.6.0 || ^16.17.0'} engines: {node: '>=19 || ^18.6.0 || ^16.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -78,104 +78,104 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
dev: true dev: true
/@rollup/rollup-android-arm-eabi@4.11.0: /@rollup/rollup-android-arm-eabi@4.12.0:
resolution: {integrity: sha512-BV+u2QSfK3i1o6FucqJh5IK9cjAU6icjFFhvknzFgu472jzl0bBojfDAkJLBEsHFMo+YZg6rthBvBBt8z12IBQ==} resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-android-arm64@4.11.0: /@rollup/rollup-android-arm64@4.12.0:
resolution: {integrity: sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==} resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-darwin-arm64@4.11.0: /@rollup/rollup-darwin-arm64@4.12.0:
resolution: {integrity: sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==} resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-darwin-x64@4.11.0: /@rollup/rollup-darwin-x64@4.12.0:
resolution: {integrity: sha512-OvqIgwaGAwnASzXaZEeoJY3RltOFg+WUbdkdfoluh2iqatd090UeOG3A/h0wNZmE93dDew9tAtXgm3/+U/B6bw==} resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.11.0: /@rollup/rollup-linux-arm-gnueabihf@4.12.0:
resolution: {integrity: sha512-X17s4hZK3QbRmdAuLd2EE+qwwxL8JxyVupEqAkxKPa/IgX49ZO+vf0ka69gIKsaYeo6c1CuwY3k8trfDtZ9dFg==} resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-arm64-gnu@4.11.0: /@rollup/rollup-linux-arm64-gnu@4.12.0:
resolution: {integrity: sha512-673Lu9EJwxVB9NfYeA4AdNu0FOHz7g9t6N1DmT7bZPn1u6bTF+oZjj+fuxUcrfxWXE0r2jxl5QYMa9cUOj9NFg==} resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-arm64-musl@4.11.0: /@rollup/rollup-linux-arm64-musl@4.12.0:
resolution: {integrity: sha512-yFW2msTAQNpPJaMmh2NpRalr1KXI7ZUjlN6dY/FhWlOclMrZezm5GIhy3cP4Ts2rIAC+IPLAjNibjp1BsxCVGg==} resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-riscv64-gnu@4.11.0: /@rollup/rollup-linux-riscv64-gnu@4.12.0:
resolution: {integrity: sha512-kKT9XIuhbvYgiA3cPAGntvrBgzhWkGpBMzuk1V12Xuoqg7CI41chye4HU0vLJnGf9MiZzfNh4I7StPeOzOWJfA==} resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-x64-gnu@4.11.0: /@rollup/rollup-linux-x64-gnu@4.12.0:
resolution: {integrity: sha512-6q4ESWlyTO+erp1PSCmASac+ixaDv11dBk1fqyIuvIUc/CmRAX2Zk+2qK1FGo5q7kyDcjHCFVwgGFCGIZGVwCA==} resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-linux-x64-musl@4.11.0: /@rollup/rollup-linux-x64-musl@4.12.0:
resolution: {integrity: sha512-vIAQUmXeMLmaDN78HSE4Kh6xqof2e3TJUKr+LPqXWU4NYNON0MDN9h2+t4KHrPAQNmU3w1GxBQ/n01PaWFwa5w==} resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-win32-arm64-msvc@4.11.0: /@rollup/rollup-win32-arm64-msvc@4.12.0:
resolution: {integrity: sha512-LVXo9dDTGPr0nezMdqa1hK4JeoMZ02nstUxGYY/sMIDtTYlli1ZxTXBYAz3vzuuvKO4X6NBETciIh7N9+abT1g==} resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-win32-ia32-msvc@4.11.0: /@rollup/rollup-win32-ia32-msvc@4.12.0:
resolution: {integrity: sha512-xZVt6K70Gr3I7nUhug2dN6VRR1ibot3rXqXS3wo+8JP64t7djc3lBFyqO4GiVrhNaAIhUCJtwQ/20dr0h0thmQ==} resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
requiresBuild: true requiresBuild: true
dev: true dev: true
optional: true optional: true
/@rollup/rollup-win32-x64-msvc@4.11.0: /@rollup/rollup-win32-x64-msvc@4.12.0:
resolution: {integrity: sha512-f3I7h9oTg79UitEco9/2bzwdciYkWr8pITs3meSDSlr1TdvQ7IxkQaaYN2YqZXX5uZhiYL+VuYDmHwNzhx+HOg==} resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
requiresBuild: true requiresBuild: true
@ -556,26 +556,26 @@ packages:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true dev: true
/rollup@4.11.0: /rollup@4.12.0:
resolution: {integrity: sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==} resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
optionalDependencies: optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.11.0 '@rollup/rollup-android-arm-eabi': 4.12.0
'@rollup/rollup-android-arm64': 4.11.0 '@rollup/rollup-android-arm64': 4.12.0
'@rollup/rollup-darwin-arm64': 4.11.0 '@rollup/rollup-darwin-arm64': 4.12.0
'@rollup/rollup-darwin-x64': 4.11.0 '@rollup/rollup-darwin-x64': 4.12.0
'@rollup/rollup-linux-arm-gnueabihf': 4.11.0 '@rollup/rollup-linux-arm-gnueabihf': 4.12.0
'@rollup/rollup-linux-arm64-gnu': 4.11.0 '@rollup/rollup-linux-arm64-gnu': 4.12.0
'@rollup/rollup-linux-arm64-musl': 4.11.0 '@rollup/rollup-linux-arm64-musl': 4.12.0
'@rollup/rollup-linux-riscv64-gnu': 4.11.0 '@rollup/rollup-linux-riscv64-gnu': 4.12.0
'@rollup/rollup-linux-x64-gnu': 4.11.0 '@rollup/rollup-linux-x64-gnu': 4.12.0
'@rollup/rollup-linux-x64-musl': 4.11.0 '@rollup/rollup-linux-x64-musl': 4.12.0
'@rollup/rollup-win32-arm64-msvc': 4.11.0 '@rollup/rollup-win32-arm64-msvc': 4.12.0
'@rollup/rollup-win32-ia32-msvc': 4.11.0 '@rollup/rollup-win32-ia32-msvc': 4.12.0
'@rollup/rollup-win32-x64-msvc': 4.11.0 '@rollup/rollup-win32-x64-msvc': 4.12.0
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
@ -639,8 +639,8 @@ packages:
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
dev: true dev: true
/vrml1to97@0.3.2: /vrml1to97@0.4.0:
resolution: {integrity: sha512-1CYLeo9XawIr6ejjYgQj4H/l74Tb2EyyExbezRNHMpKvutVOi+1MplgFd0gHkQqQs13jNvJwRAB0LNzkf+41gQ==} resolution: {integrity: sha512-5gA9jr31f4z779ddST0EN+TNHrU1Auzf9GBeHoAjAZRBtwWqIehvDePGv73p8MM3ANueA6Z5puhtezK90Qy+kg==}
hasBin: true hasBin: true
dev: false dev: false

View File

@ -1,6 +1,6 @@
type Face = number[] type Face = number[]
type XYZ = [number, number, number] type XYZ = [number, number, number]
type Polyhedron = {face: Face[], xyz: XYZ[]} type Polyhedron = {face: Face[], xyz: XYZ[], name?: string}
type Notation = string type Notation = string
@ -11,7 +11,28 @@ phi := (1 + Math.sqrt 5)/2
ihp := 1/phi ihp := 1/phi
tau := 2*Math.PI tau := 2*Math.PI
// edge midpoints on unit sphere for all seeds // Sadly needs to be early because we initialize the Dodecahedron as the dual of
// the icosahedron:
// Only add one direction of each, will auto reverse as well
specialDuals: Record<string, string> :=
Tetrahedron: 'Tetrahedron',
Cube: 'Octahdron',
Dodecahedron: 'Icosahedron',
Cuboctahedron: 'Rhombic dodecahedron',
'truncated Tetrahedron': 'triakis Tetrahedron',
'truncated Cube': 'triakis Octahedron',
'truncated Octahedron': 'tetrakis Cube',
Rhombicuboctahedron: 'Deltoidal icositetrahedron',
'truncated Cuboctahedron': 'Disdyakis dodecahedron',
'snub Cube': 'pentagonal icositetrahedron',
Icosidodecahedron: 'Rhombic triacontahedron',
'truncated Dodecahedron': 'triakis Icosahedron',
'truncated Icosahedron': 'pentakis Dodecahedron',
Rhombicosidodecahedron: 'Deltoidal hexecontahedron',
'truncated Icosidodecahedron': 'Disdyakis triacontahedron',
'snub Dodecahedron': 'pentagonal hexecontahedron'
// All seeds are canonical, i.e., edges tangent to unit sphere
icosahedron: Polyhedron := icosahedron: Polyhedron :=
face: [[0,1,9], [0,8,1], [0,4,8], [0,5,4], [0,9,5], 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], [4,5,2], [4,2,10], [4,10,8], [8,10,6], [8,6,1],
@ -20,19 +41,24 @@ icosahedron: Polyhedron :=
xyz: [[0,ihp,1], [0,-ihp,1], [0,ihp,-1], [0,-ihp,-1], 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], [ihp,1,0], [-ihp,1,0], [ihp,-1,0], [-ihp,-1,0],
[1,0,ihp], [-1,0,ihp], [1,0,-ihp], [-1,0,-ihp]] [1,0,ihp], [-1,0,ihp], [1,0,-ihp], [-1,0,-ihp]]
name: 'Icosahedron'
polyCache: Record<Notation, Polyhedron> := polyCache: Record<Notation, Polyhedron> :=
'': face: [], xyz: [] '': face: [], xyz: []
T: T:
face: [[0,2,1], [0,3,2], [0,1,3], [1,2,3]] face: [[0,2,1], [0,3,2], [0,1,3], [1,2,3]]
xyz: [[1,1,1], [1,-1,-1], [-1,1,-1], [-1,-1,1]] xyz: [[1,1,1], [1,-1,-1], [-1,1,-1], [-1,-1,1]]
name: 'Tetrahedron'
O: O:
face: [[0,2,1], [0,3,2], [0,4,3], [0,1,4], face: [[0,2,1], [0,3,2], [0,4,3], [0,1,4],
[1,5,4], [1,2,5], [2,3,5], [3,4,5]] [1,5,4], [1,2,5], [2,3,5], [3,4,5]]
xyz: [[0,0,rt2], [rt2,0,0], [0,rt2,0], [-rt2,0,0], [0,-rt2,0], [0,0,-rt2]] xyz: [[0,0,rt2], [rt2,0,0], [0,rt2,0], [-rt2,0,0], [0,-rt2,0], [0,0,-rt2]]
name: 'Octahedron'
C: C:
face: [[0,3,2,1], [0,5,4,3], [0,1,6,5], [1,2,7,6], [2,3,4,7], [4,5,6,7]] face: [[0,3,2,1], [0,5,4,3], [0,1,6,5], [1,2,7,6], [2,3,4,7], [4,5,6,7]]
xyz: [[rth,rth,rth], [-rth,rth,rth], [-rth,-rth,rth], [rth,-rth,rth], 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]] [rth,-rth,-rth], [rth,rth,-rth], [-rth,rth,-rth], [-rth,-rth,-rth]]
name: 'Cube'
I: icosahedron I: icosahedron
D: geomDual icosahedron D: geomDual icosahedron
@ -69,7 +95,7 @@ rawStandardizations :=
dd: '', rr: '', jd: 'j', gd: 'rgr', // absorption rules dd: '', rr: '', jd: 'j', gd: 'rgr', // absorption rules
rd: 'dr', // these commute; others? If so, move 'r' in to cancel w/ seed rd: 'dr', // these commute; others? If so, move 'r' in to cancel w/ seed
// Remainder are all simplifications/unique selections for seeds: // Remainder are all simplifications/unique selections for seeds:
aY: 'A', dT: 'T', gT: 'D', jT: 'C', dC: 'O', dO: 'C', jY: 'dA', dT: 'T', gT: 'D', jT: 'C', dC: 'O', dO: 'C',
dI: 'D', dD: 'I', rO: 'O', rC: 'C', rI: 'I', rD: 'D', dI: 'D', dD: 'I', rO: 'O', rC: 'C', rI: 'I', rD: 'D',
jO: 'jC', jI: 'jD', gO: 'gC', gI: 'gD' jO: 'jC', jI: 'jD', gO: 'gC', gI: 'gD'
standardizations := standardizations :=
@ -95,6 +121,13 @@ function orb(r: number, n: number,
operator add(a: XYZ, b: XYZ) operator add(a: XYZ, b: XYZ)
accumulate copy(a), b accumulate copy(a), b
ngonalNames := 3: 'triangular', 4: 'square', 5: 'pentagonal', 6: 'hexagonal',
7: 'heptagonal', 8: 'octagonal', 9: 'enneagonal', 10: 'decagonal',
12: 'dodecagonal'
type SpecialNgon = keyof typeof ngonalNames
function ngonal(n: number)
ngonalNames[n as SpecialNgon] ?? n+`-gonal`
seeds := seeds :=
P: (n: number) => // Prism P: (n: number) => // Prism
unless n then n = 3 unless n then n = 3
@ -105,7 +138,7 @@ seeds :=
for i of [0...n] for i of [0...n]
ip1 := (i+1)%n // next vertex around ip1 := (i+1)%n // next vertex around
face.push [i, ip1, ip1+n, i+n] face.push [i, ip1, ip1+n, i+n]
{face, xyz} {face, xyz, name: ngonal(n)+` prism`}
A: (n: number) => // Antiprism A: (n: number) => // Antiprism
unless n then n = 4 unless n then n = 4
theta := tau/n theta := tau/n
@ -122,7 +155,7 @@ seeds :=
for i of [0...n] for i of [0...n]
face.push [i, (i+1)%n, i+n] face.push [i, (i+1)%n, i+n]
face.push [i, i+n, n + (n+i-1)%n] face.push [i, i+n, n + (n+i-1)%n]
{face, xyz} {face, xyz, name: ngonal(n)+` antiprism`}
Y: (n: number) => // pYramid Y: (n: number) => // pYramid
unless n then n = 4 unless n then n = 4
// Canonical solution by Intelligenti Pauca and Ed Pegg, see // Canonical solution by Intelligenti Pauca and Ed Pegg, see
@ -137,11 +170,27 @@ seeds :=
xyz.push [0, 0, depth-height] xyz.push [0, 0, depth-height]
face := ([i, (i+1)%n, n] for i of [0...n]) face := ([i, (i+1)%n, n] for i of [0...n])
face.unshift [n-1..0] face.unshift [n-1..0]
{face, xyz} {face, xyz, name: ngonal(n)+` pyramid`}
type SeedOp = keyof typeof seeds type SeedOp = keyof typeof seeds
// Syntactic sugar to deal with weird TypeScript typing: // Syntactic sugar to deal with weird TypeScript typing:
operator æ<T>(A: T[], i: number) A.at(i) as T operator æ<T>(A: T[], i: number) A.at(i) as T
function wordsof(s: string) 1 + (s.match(/\s+/g)?.length ?? 0)
specialJoins :=
Tetrahedron: 'Cube',
Cube: 'Rhombic dodecahedron', Octahedron: 'Rhombic dodecahedron',
Dodecahedron: 'Rhombic triacontahedron', Icosahedron: 'Rhombic triacontahedron',
'Rhombic dodecahedron': 'Deltoidal icositetrahedron',
'Rhombic triacontahedron': 'Deltoidal hexecontahedron',
'pentakis Dodecahedron': 'Rhombic enneacontahedron'
type SpecialJoin = keyof typeof specialJoins
specialKis :=
'Rhombic dodecahedron': 'Disdyakis dodecahedron'
'Rhombic triacontahedron': 'Disdyakis triacontahedron'
type SpecialKis = keyof typeof specialKis
kisWords:= 3: 'triakis', 4: 'tetrakis', 5: 'pentakis', 6: 'hexakis', 7: 'heptakis'
type KisNumber = keyof typeof kisWords
function kisjoin(P: Polyhedron, notation: string, function kisjoin(P: Polyhedron, notation: string,
digits: string, join: boolean): Polyhedron digits: string, join: boolean): Polyhedron
@ -173,7 +222,7 @@ function kisjoin(P: Polyhedron, notation: string,
continue continue
// Add the pyramid, possibly eliding edges: // Add the pyramid, possibly eliding edges:
for each w, jx of f for each w, jx of f
pw := f æ (jx-1) pw := f æ jx-1
neighbor .= 0 neighbor .= 0
if join if join
neighbor = P.face.findIndex (g, gx) => neighbor = P.face.findIndex (g, gx) =>
@ -182,7 +231,35 @@ function kisjoin(P: Polyhedron, notation: string,
if pw < w // avoid adding same face twice if pw < w // avoid adding same face twice
face.push [v, pw, newVixes[neighbor], w] face.push [v, pw, newVixes[neighbor], w]
else face.push [v, pw, w] else face.push [v, pw, w]
adjustXYZ {face, xyz}, notation, 3 // Nomenclature
let name: string|undefined
if P.name and wordsof(P.name) < 3 // don't go hog-wild with naming
if join
unless digits
name = specialJoins[P.name as SpecialJoin] ?? 'joined ' + P.name
else
size .= P.face[0].length
unless P.face.every (f) => f.length is size
size = 0
if !size and digits then size = Number(digits) or 0
// very special case
if size is 5 and P.name is 'pentagonal antiprism'
name = 'Icosahedron'
else if (!digits or Number(digits) is size) and size in kisWords
name = (specialKis[P.name as SpecialKis]
?? kisWords[size as KisNumber] + ' ' + P.name)
// Cheaty super special case
if notation is 'jk5djP5'
name = 'dual elongated pentagonal orthobirotunda'
// Done, fix up the vertices a bit
adjustXYZ {face, xyz, name}, notation, 3
specialGyro :=
Cube: 'pentagonal icositetrahedron', Octahedron: 'pentagonal icositetrahedron',
Icosahedron: 'pentagonal hexecontahedron',
Dodecahedron: 'pentagonal hexecontahedron'
type SpecialGyro = keyof typeof specialGyro
// how enums ought to work? // how enums ought to work?
FromCenter := Symbol() FromCenter := Symbol()
@ -210,7 +287,7 @@ function gyropel(P: Polyhedron, notation: string,
for each f of P.face for each f of P.face
if digits and f.length is not in allowed then continue if digits and f.length is not in allowed then continue
for each v, ix of f for each v, ix of f
pv := f æ (ix-1) pv := f æ ix-1
(edgeV[pv] ??= [])[v] = xyz.length (edgeV[pv] ??= [])[v] = xyz.length
xyz.push lerp xyz[pv], xyz[v], 1/3 xyz.push lerp xyz[pv], xyz[v], 1/3
if xyz.length is startV then return P // nothing to do if xyz.length is startV then return P // nothing to do
@ -223,7 +300,7 @@ function gyropel(P: Polyhedron, notation: string,
// Just collect all of the vertices around // Just collect all of the vertices around
newFace: Face := [] newFace: Face := []
for each v, ix of f for each v, ix of f
pv := f æ (ix-1) pv := f æ ix-1
reverseV := edgeV[v]?[pv] ?? -1 reverseV := edgeV[v]?[pv] ?? -1
if reverseV >= 0 then newFace.push reverseV if reverseV >= 0 then newFace.push reverseV
newFace.push v newFace.push v
@ -233,8 +310,8 @@ function gyropel(P: Polyhedron, notation: string,
if fromCenter then xyz.push centers[fx] if fromCenter then xyz.push centers[fx]
aroundOutside: Face .= [] aroundOutside: Face .= []
for each v, ix of f for each v, ix of f
pv := f æ (ix-1) pv := f æ ix-1
ppv := f æ (ix-2) ppv := f æ ix-2
firstNew := edgeV[ppv][pv] firstNew := edgeV[ppv][pv]
newSection := [firstNew] newSection := [firstNew]
reverseV := edgeV[pv][ppv] ?? -1 reverseV := edgeV[pv][ppv] ?? -1
@ -251,7 +328,19 @@ function gyropel(P: Polyhedron, notation: string,
face.push newSection face.push newSection
else aroundOutside ++= newSection else aroundOutside ++= newSection
if aroundOutside.length then face.push aroundOutside if aroundOutside.length then face.push aroundOutside
adjustXYZ {face, xyz}, notation, 3 let name: string|undefined
nw .= 0
if ((fromCenter xor alongEdge)
and P.name and !digits and (nw = wordsof(P.name)) < 3)
if alongEdge
if nw is 1 then name = 'propello' + P.name
else name = 'propellorized '+P.name
else
if P.name in specialGyro then name = specialGyro[P.name as SpecialGyro]
else if nw is 1 then name = 'gyro' + P.name
else name = 'gyrated '+P.name
// Done, fix up the vertices a bit
adjustXYZ {face, xyz, name}, notation, 3
function parseSides(digits: string): number[] function parseSides(digits: string): number[]
unless digits return [] unless digits return []
@ -284,17 +373,19 @@ transforms :=
r: (P: Polyhedron) => // reverse (mirror) r: (P: Polyhedron) => // reverse (mirror)
face: (f.toReversed() for each f of P.face) face: (f.toReversed() for each f of P.face)
xyz: (scale copy(v), -1 for each v of P.xyz) xyz: (scale copy(v), -1 for each v of P.xyz)
name: P.name // should we record that it's mirrored?
d: (P: Polyhedron, notation: string, digits: string) => // dual d: (P: Polyhedron, notation: string, digits: string) => // dual
if digits if digits
console.error `Ignoring ${digits} arg of d in ${notation}` console.error `Ignoring ${digits} arg of d in ${notation}`
// Create a "fake" of P and adjust it (so we don't disturb the // Create a "fake" of P and adjust it (so we don't disturb the
// cached P), which will create the dual // cached P), which will create the dual
parentNotation := notation[1+digits.length..] parentNotation := notation[1+digits.length..]
adjustXYZ {face: P.face.slice(), P.xyz}, parentNotation, 1 adjustXYZ {face: P.face.slice(), P.xyz, P.name}, parentNotation, 1
polyCache.`d${parentNotation}` polyCache.`d${parentNotation}`
c: (P: Polyhedron, notation: string, digits: string) => // canonicalize c: (P: Polyhedron, notation: string, digits: string) => // canonicalize
face: P.face.slice() face: P.face.slice()
xyz: canonicalXYZ P, notation, Number(digits) or 10 xyz: canonicalXYZ P, notation, Number(digits) or 10
name: P.name
x: approxCanonicalize // iterative direct adjustment algorithm x: approxCanonicalize // iterative direct adjustment algorithm
type TransformOp = keyof typeof transforms type TransformOp = keyof typeof transforms
@ -326,7 +417,7 @@ function topoDual(P: Polyhedron): Polyhedron
newface := [] newface := []
do do
verts := P.face[current] verts := P.face[current]
preV := verts æ (verts.indexOf(v)-1) preV := verts æ verts.indexOf(v)-1
nextIx := infaces.findIndex ([face, label]) => nextIx := infaces.findIndex ([face, label]) =>
label !== current and face.includes preV label !== current and face.includes preV
current = infaces[nextIx][1] current = infaces[nextIx][1]
@ -338,6 +429,8 @@ function topoDual(P: Polyhedron): Polyhedron
newface newface
xyz: xyz:
Array(P.face.length).fill([0,0,0]) // warning, every vertex is === Array(P.face.length).fill([0,0,0]) // warning, every vertex is ===
name: dualName P.name
function geomDual(P: Polyhedron): Polyhedron function geomDual(P: Polyhedron): Polyhedron
return := topoDual P return := topoDual P
@ -346,7 +439,7 @@ function geomDual(P: Polyhedron): Polyhedron
function approxDualVertices(P: Polyhedron): XYZ[] function approxDualVertices(P: Polyhedron): XYZ[]
P.face.map (f) => approxDualVertex f, P.xyz P.face.map (f) => approxDualVertex f, P.xyz
operator dot(v: number[], w: number[]) operator dot same (/) (v: number[], w: number[])
v.reduce (l,r,i) => l + r*w[i], 0 v.reduce (l,r,i) => l + r*w[i], 0
function approxDualVertex(f: Face, v: XYZ[]): XYZ function approxDualVertex(f: Face, v: XYZ[]): XYZ
@ -357,7 +450,9 @@ function approxDualVertex(f: Face, v: XYZ[]): XYZ
// This method seems to work OK when the neighborhood of f is convex, // This method seems to work OK when the neighborhood of f is convex,
// and very poorly otherwise. So it probably would not provide any better // and very poorly otherwise. So it probably would not provide any better
// canonicalization than other methods of approximating the dual. // canonicalization than other methods of approximating the dual.
normals := (tangentPoint(v[f æ (i-1)], v[f[i]]) for i of [0...f.length]) // It might be better to replace this with the approximate intersection of the
// reciprocal planes to each of the vertices of the face...
normals := (tangentPoint(v[f æ i-1], v[f[i]]) for i of [0...f.length])
sqlens := normals.map mag2 sqlens := normals.map mag2
columns := (normals.map(&[i]) for i of [0..2]) columns := (normals.map(&[i]) for i of [0..2])
target := (columns[i] dot sqlens for i of [0..2]) as XYZ target := (columns[i] dot sqlens for i of [0..2]) as XYZ
@ -387,6 +482,30 @@ function faceCenters(P: Polyhedron): XYZ[]
scale face.reduce((ctr,v) => accumulate(ctr, P.xyz[v]), [0,0,0]), scale face.reduce((ctr,v) => accumulate(ctr, P.xyz[v]), [0,0,0]),
1/face.length 1/face.length
function dualName(p: string|undefined)
unless 'Octahedron' in specialDuals
// one-time reversal of all special duals
specialDuals[dual] = poly for poly, dual in specialDuals
unless p return undefined
if p in specialDuals
return specialDuals[p]
words := p.split(' ')
if words[0] is 'dual' return words[1..].join ' '
if words.length is 2
switch words[1]
'prism'
return words[0] + ' bipyramid'
'bipyramid'
return words[0] + ' prism'
'antiprism'
return words[0] + ' trapezohedron'
'trapezohedron'
return words[0] + ' antiprism'
'pyramid'
return p // self-dual
return 'dual ' + p
function adjustXYZ(P: Polyhedron, notation: string, iterations = 1): Polyhedron function adjustXYZ(P: Polyhedron, notation: string, iterations = 1): Polyhedron
dualNotation := 'd' + notation dualNotation := 'd' + notation
D .= topoDual P D .= topoDual P
@ -418,7 +537,7 @@ function canonicalXYZ(P: Polyhedron, notation: string, iterations: number): XYZ[
polyCache[dualNotation] = D polyCache[dualNotation] = D
tempP.xyz tempP.xyz
operator cross(v: XYZ, w: XYZ): XYZ operator cross tighter (*) (v: XYZ, w: XYZ): XYZ
[ v[1]*w[2] - v[2]*w[1], v[2]*w[0] - v[0]*w[2], v[0]*w[1] - v[1]*w[0] ] [ v[1]*w[2] - v[2]*w[1], v[2]*w[0] - v[0]*w[2], v[0]*w[1] - v[1]*w[0] ]
function reciprocalN(P: Polyhedron): XYZ[] function reciprocalN(P: Polyhedron): XYZ[]
@ -429,8 +548,8 @@ function reciprocalN(P: Polyhedron): XYZ[]
for each vlabel, ix of f for each vlabel, ix of f
v := P.xyz[vlabel] v := P.xyz[vlabel]
accumulate centroid, v accumulate centroid, v
pv := P.xyz[f æ (ix-1)] pv := P.xyz[f æ ix-1]
ppv := P.xyz[f æ (ix-2)] ppv := P.xyz[f æ ix-2]
// original doc says unit normal but below isn't. Didn't help to try, tho // original doc says unit normal but below isn't. Didn't help to try, tho
nextNormal := (pv sub ppv) cross (v sub pv) nextNormal := (pv sub ppv) cross (v sub pv)
// or instead, just chop down big ones: (didn't work either) // or instead, just chop down big ones: (didn't work either)
@ -469,14 +588,14 @@ function approxCanonicalize(P: Polyhedron, notation: string,
normalizeEdges xyz, edge normalizeEdges xyz, edge
if Math.max(...(xyz[i] dist start[i] for i of [0...V])) < THRESHOLD if Math.max(...(xyz[i] dist start[i] for i of [0...V])) < THRESHOLD
break break
{face: P.face.slice(), xyz} {face: P.face.slice(), xyz, P.name}
type Edge = [number, number] type Edge = [number, number]
function edges(P:Polyhedron): Edge[] function edges(P:Polyhedron): Edge[]
return: Edge[] := [] return: Edge[] := []
for each f of P.face for each f of P.face
for each v, ix of f for each v, ix of f
pv := f æ (ix-1) pv := f æ ix-1
if pv < v then return.value.push ð pv, v if pv < v then return.value.push ð pv, v
function normalizeEdges(xyz: XYZ[], edge: Edge[]): void function normalizeEdges(xyz: XYZ[], edge: Edge[]): void
@ -588,6 +707,7 @@ function inform(x: string)
// VRML97 generation // VRML97 generation
function outputVRML(notation: Notation, P: Polyhedron): string function outputVRML(notation: Notation, P: Polyhedron): string
shortDescrip := P.name or `A ${P.face.length}-hedron`
``` ```
#VRML V2.0 utf8 #VRML V2.0 utf8
Group { children [ Group { children [
@ -603,6 +723,7 @@ George Hart's Encyclopedia of Polyhedra as the source." }
NavigationInfo { type [ "EXAMINE" ] } NavigationInfo { type [ "EXAMINE" ] }
DirectionalLight {direction -.5 -1 1 intensity 0.75} DirectionalLight {direction -.5 -1 1 intensity 0.75}
DirectionalLight {direction .5 1 -1 intensity 0.75} DirectionalLight {direction .5 1 -1 intensity 0.75}
Viewpoint { position 0 0 4.5 description "${shortDescrip}" }
${polyVRML P, colorScheme()} ${polyVRML P, colorScheme()}
Shape { Shape {
appearance Appearance { appearance Appearance {
@ -677,6 +798,6 @@ function edgeIndices(P: Polyhedron)
sep := ",\n " sep := ",\n "
filtmap(P.face, (thisf) => filtmap(P.face, (thisf) =>
filtmap(thisf, (v, ix, f) => filtmap(thisf, (v, ix, f) =>
preV := f æ (ix-1) preV := f æ ix-1
preV < v ? `${preV}, ${v}, -1` : '').join sep) preV < v ? `${preV}, ${v}, -1` : '').join sep)
.join sep .join sep

View File

@ -136,8 +136,48 @@ if inputs.length is 1
conwayBrowser.replaceWorld scene conwayBrowser.replaceWorld scene
// See if we are on George Hart's prism generator page // See if we are on George Hart's prism generator page
prisms := $('input[type="button"][value="View"][onclick="ViewVRML()"]') panelFrame := $('frame[name="panel"][src="prism-maker-subpanel.html"]')
if prisms.length is 1 if panelFrame.length is 1
// Seems so, fix the generator // Seems so, fix the generator
console.log 'Need to fix the prism generator' panelFrame.on "load", =>
panelDoc := frames[1].document
vrmlDoc := frames[0].document
vrmlBody := $('body', vrmlDoc)
// Grab the initial text while it is still easy to get
textNode := vrmlBody.contents()[0]
initialVrml1: string := textNode.textContent or ''
// Now build up the vrml frame as we want it
viewerDiv := $('<div></div>')
$('head').after $('<body></body>')
$('body').append viewerDiv
// We are presuming here that the body just contains a single
// text node. That should stay true unless GWH changes the page.
initialVrml97 := convert initialVrml1
{canvas, browser3D: prismBrowser} := await makeBrowser '', '300px', '300px'
viewerDiv.append canvas
initialScene := await prismBrowser.createX3DFromString initialVrml97
prismBrowser.replaceWorld initialScene
$(textNode).remove()
$('frame[name="vrml"]').remove()
// OK, finally have the layout cleaned up. Now we can set up our
// replacement generator:
prismBtn := $('input[type="button"][value="View"][onclick="ViewVRML()"]',
panelDoc)
unless prismBtn.length is 1 return
prismBtn.prop 'onclick', (i, val) => =>
import(browser.runtime.getURL('prism.js')).then (prism) =>
numerator :=
parseInt $('input[name="numerator"]', panelDoc).val() as string
denominator :=
parseInt $('input[name="denominator"]', panelDoc).val() as string
checks: boolean[] := []
$('input[name="what"]', panelDoc).each ->
checks.push (@ as HTMLInputElement).checked
return
{vrml, err} := prism.generateVRML numerator, denominator, checks
maybeDebug vrml
if err then alert err
if vrml
scene := await prismBrowser.createX3DFromString vrml
prismBrowser.replaceWorld scene

159
src/prism.civet Normal file
View File

@ -0,0 +1,159 @@
type GenVRML = vrml?: string, err?: string
function gcdpos(a: number, b: number)
[a, b] = [b, a%b] while b >= 1
a
shapeNamesRaw :=
```
prism|dipyramid|compound: prism + dipyramid
antiprism|trapezohedron|compound: antiprism + trapezohedron
crossed antiprism|concave trapezohedron
compound: crossed antiprism + concave trapezohedron|polygon
```
shapeName := shapeNamesRaw.split /[|\n]/
export function generateVRML(n: number, d: number, chk: boolean[])
unless chk[6..8].every !& or 2 < n/d < 3
return err: 'Crossed antiprisms require 2 < numerator/denominator < 3, but '
+ `it is ${n}/${d} = ${n/d}`
unless n > 0 and d > 0 return err: 'Numerator and denominator should be positive'
g := gcdpos n,d
n /= g
d /= g
if n < 3
return
err: `Numerator in lowest terms (currently ${n}) needs to be at least 3.`
unless 1 <= d < n
return err: 'Denominator should be strictly between 1 and numerator.'
if d > n/2 then d = n-d // equivalent
which := chk.findIndex (&)
chk[which] = false
if chk.some (&) return err: 'Currently only one shape option may be checked.'
name .= d > 1 ? `${n}/${d}-gonal` :
switch n
when 3: 'triangular'
when 4: 'square'
when 5: 'pentagonal'
when 6: 'hexagonal'
when 7: 'heptagonal'
when 8: 'octagonal'
else `${n}-gonal`
name += ' ' + shapeName[which]
{vrml: header(name) + vrmlBody which, n, d}
function header(name: string)
```
#VRML V2.0 utf8
WorldInfo {
title "${name}."
info [
"Generated by GTW's reimplementation of GWH's prism generator"
"By using this script, you agree that this image is released"
"into the public domain, although you are requested to cite"
"George Hart's Encyclopedia of Polyhedra as the source." ] }
Background {
groundColor [ 0.2 0.5 0.9 ]
skyColor [ 0.2 0.5 0.9 ] }
NavigationInfo { type [ "EXAMINE" ] }
DirectionalLight { direction -.5 -1 1 intensity 0.75 }
DirectionalLight { direction .5 1 -1 intensity 0.75 }
DEF General Viewpoint {
position 6 0 6 orientation 0 1 0 0.78 description "General" }
DEF Face Viewpoint {
position 0 -6 0 orientation 1 0 0 1.57 description "Face" }
DEF Top Viewpoint { position 0 0 8 description "Top" }
DEF Edge Viewpoint {
position 7 0 0 orientation 0 1 0 1.57 description "Edge" }
DEF Inside Viewpoint {
position 0 0 0 orientation 1 0 0 -1.57 description "Inside" }
DEF Far Viewpoint {
position -25 0 25 orientation 0 1 0 -0.78 description "Far" }
```
// Useful constants
tau := 2*Math.PI
function shIx(name: string): number
shapeName.findIndex((sn) => name is in sn)
type ShapeMethod = (n: number, theta: number, d?: number) => string
shapeMethod: ShapeMethod[] := []
shapeMethods: Record<string, ShapeMethod> :=
prism: (n, theta) =>
h := Math.sin theta/2
vrml: string[] := []
coords := orb(1, n, h, theta) ++ orb(1, n, -h, theta) ++ [[0,0,h], [0,0,-h]]
top := ([i, (i+1)%n, 2*n] for i of [0...n])
bottom := ([(i+1)%n + n, i+n, 2*n+1] for i of [0...n])
firstFaces vrml, 'Prism', '0.8 0.7 0.5', coords, top ++ bottom
sides := ([i, (i+1)%n, (i+1)%n + n, i+n] for i of [0...n])
laterFaces vrml, 'Prism', '0.3 0.5 0.9', sides
laterLines vrml, 'Prism', sides
emitObject vrml
dipyr: (n, theta) =>
name := 'Dipyramid'
c := 1/Math.cos theta/2
d := 1/Math.sin theta/2
vrml: string[] := []
coords := [[0,0,d]] ++ orb(c, n, 0, theta, 0.5) ++ [[0,0,-d]]
cap := ([0, i, i%n + 1] for i of [1..n])
cup := ([i%n + 1, i, n+1] for i of [1..n])
firstFaces vrml, name, '0.9 0.3 0.3', coords, cap++cup
edges := [[1..n]] ++ ([0, i, n+1] for i of [1..n])
laterLines vrml, name, edges
emitObject vrml
compound: (n, theta) =>
shapeMethod[shIx 'prism'](n, theta) + shapeMethod[shIx 'dipyr'](n, theta)
for key, method in shapeMethods
shapeMethod[shIx key] = method
function vrmlBody(which: number, n: number, d: number)
theta := tau * d/n
shapeMethod[which](n, theta, d)
function orb(r: number, n: number, height: number, theta: number, t=0)
rho := t*theta
for i of [0...n]
[r*Math.cos(rho+i*theta), r*Math.sin(rho+i*theta), height]
function firstFaces(container: string[], name: string, color: string,
coords: number[][], faces: number[][]): void
startShape container, color
container.push 'geometry IndexedFaceSet {',
`coord DEF ${name}Coords Coordinate { point [`
container.push coord.join(' ') + ',' for each coord of coords
container.push '] }', 'creaseAngle 0 solid FALSE coordIndex ['
container.push face.join(', ') + ', -1,' for each face of faces
container.push '] } }'
function startShape(container: string[], color: string): void
container.push 'Shape { appearance Appearance {',
' material Material {',
` diffuseColor ${color} } }`
function laterFaces(container: string[], name: string, color: string,
faces: number[][]): void
startShape container, color
container.push 'geometry IndexedFaceSet {',
`coord USE ${name}Coords`,
'creaseAngle 0 solid FALSE coordIndex ['
container.push face.join(', ') + ', -1,' for each face of faces
container.push '] } }'
function laterLines(container: string[], name: string, edges: number[][]): void
startShape container, '0 0 0'
container.push 'geometry IndexedLineSet {',
`coord USE ${name}Coords`,
'coordIndex ['
container.push edge.join(', ') + ', -1,' for each edge of edges
container.push '] } }'
function emitObject(container: string[]) container.join "\n"

View File

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