feat: Reimplementation of prism generator (#65)
Resolves #16. Reviewed-on: #65 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
dba8870c83
commit
b74921341c
@ -38,6 +38,7 @@
|
||||
"adapptypes.js",
|
||||
"conway.js",
|
||||
"options.js",
|
||||
"prism.js",
|
||||
"deps/GeoGebra/deployggb.js",
|
||||
"deps/GeoGebra/HTML5/5.0/webSimple/4B19686283BEF852F4C88C93522FB9A3.cache.js",
|
||||
"deps/GeoGebra/HTML5/5.0/webSimple/webSimple.nocache.js",
|
||||
|
@ -34,17 +34,17 @@
|
||||
url: 'https://code.studioinfinity.org/glen/archematics.git',
|
||||
},
|
||||
devDependencies: {
|
||||
'@danielx/civet': '^0.6.73',
|
||||
'@danielx/civet': '^0.6.78',
|
||||
'@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.11.0',
|
||||
rollup: '^4.12.0',
|
||||
typescript: '^5.3.3',
|
||||
'webextension-polyfill': '^0.10.0',
|
||||
},
|
||||
dependencies: {
|
||||
colorsea: '^1.2.1',
|
||||
vrml1to97: '^0.3.2',
|
||||
vrml1to97: '^0.4.0',
|
||||
},
|
||||
}
|
||||
|
102
pnpm-lock.yaml
102
pnpm-lock.yaml
@ -9,13 +9,13 @@ dependencies:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
vrml1to97:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
|
||||
devDependencies:
|
||||
'@danielx/civet':
|
||||
specifier: ^0.6.73
|
||||
version: 0.6.73(typescript@5.3.3)
|
||||
specifier: ^0.6.78
|
||||
version: 0.6.78(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.11.0
|
||||
version: 4.11.0
|
||||
specifier: ^4.12.0
|
||||
version: 4.12.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.73(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-VOq02JgXNArsLzq/I2OnZtn16exiF8/mIhpR5O6Tsc+97RU6Qe8EP8XJskh/Hm9koUODzWUW8UuFpFdVzm7wTA==}
|
||||
/@danielx/civet@0.6.78(typescript@5.3.3):
|
||||
resolution: {integrity: sha512-GT8+Y+MIF7+SkiMSbh2diXWZckjbmWb8nPv17RAwYjFwhI5Z6lXtil+KPX2rVf0C/0ZFksYM65Rp2dJYxMYT3w==}
|
||||
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.11.0:
|
||||
resolution: {integrity: sha512-BV+u2QSfK3i1o6FucqJh5IK9cjAU6icjFFhvknzFgu472jzl0bBojfDAkJLBEsHFMo+YZg6rthBvBBt8z12IBQ==}
|
||||
/@rollup/rollup-android-arm-eabi@4.12.0:
|
||||
resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-android-arm64@4.11.0:
|
||||
resolution: {integrity: sha512-0ij3iw7sT5jbcdXofWO2NqDNjSVVsf6itcAkV2I6Xsq4+6wjW1A8rViVB67TfBEan7PV2kbLzT8rhOVWLI2YXw==}
|
||||
/@rollup/rollup-android-arm64@4.12.0:
|
||||
resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-arm64@4.11.0:
|
||||
resolution: {integrity: sha512-yPLs6RbbBMupArf6qv1UDk6dzZvlH66z6NLYEwqTU0VHtss1wkI4UYeeMS7TVj5QRVvaNAWYKP0TD/MOeZ76Zg==}
|
||||
/@rollup/rollup-darwin-arm64@4.12.0:
|
||||
resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-darwin-x64@4.11.0:
|
||||
resolution: {integrity: sha512-OvqIgwaGAwnASzXaZEeoJY3RltOFg+WUbdkdfoluh2iqatd090UeOG3A/h0wNZmE93dDew9tAtXgm3/+U/B6bw==}
|
||||
/@rollup/rollup-darwin-x64@4.12.0:
|
||||
resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm-gnueabihf@4.11.0:
|
||||
resolution: {integrity: sha512-X17s4hZK3QbRmdAuLd2EE+qwwxL8JxyVupEqAkxKPa/IgX49ZO+vf0ka69gIKsaYeo6c1CuwY3k8trfDtZ9dFg==}
|
||||
/@rollup/rollup-linux-arm-gnueabihf@4.12.0:
|
||||
resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-gnu@4.11.0:
|
||||
resolution: {integrity: sha512-673Lu9EJwxVB9NfYeA4AdNu0FOHz7g9t6N1DmT7bZPn1u6bTF+oZjj+fuxUcrfxWXE0r2jxl5QYMa9cUOj9NFg==}
|
||||
/@rollup/rollup-linux-arm64-gnu@4.12.0:
|
||||
resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-arm64-musl@4.11.0:
|
||||
resolution: {integrity: sha512-yFW2msTAQNpPJaMmh2NpRalr1KXI7ZUjlN6dY/FhWlOclMrZezm5GIhy3cP4Ts2rIAC+IPLAjNibjp1BsxCVGg==}
|
||||
/@rollup/rollup-linux-arm64-musl@4.12.0:
|
||||
resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-riscv64-gnu@4.11.0:
|
||||
resolution: {integrity: sha512-kKT9XIuhbvYgiA3cPAGntvrBgzhWkGpBMzuk1V12Xuoqg7CI41chye4HU0vLJnGf9MiZzfNh4I7StPeOzOWJfA==}
|
||||
/@rollup/rollup-linux-riscv64-gnu@4.12.0:
|
||||
resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-gnu@4.11.0:
|
||||
resolution: {integrity: sha512-6q4ESWlyTO+erp1PSCmASac+ixaDv11dBk1fqyIuvIUc/CmRAX2Zk+2qK1FGo5q7kyDcjHCFVwgGFCGIZGVwCA==}
|
||||
/@rollup/rollup-linux-x64-gnu@4.12.0:
|
||||
resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-linux-x64-musl@4.11.0:
|
||||
resolution: {integrity: sha512-vIAQUmXeMLmaDN78HSE4Kh6xqof2e3TJUKr+LPqXWU4NYNON0MDN9h2+t4KHrPAQNmU3w1GxBQ/n01PaWFwa5w==}
|
||||
/@rollup/rollup-linux-x64-musl@4.12.0:
|
||||
resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-arm64-msvc@4.11.0:
|
||||
resolution: {integrity: sha512-LVXo9dDTGPr0nezMdqa1hK4JeoMZ02nstUxGYY/sMIDtTYlli1ZxTXBYAz3vzuuvKO4X6NBETciIh7N9+abT1g==}
|
||||
/@rollup/rollup-win32-arm64-msvc@4.12.0:
|
||||
resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-ia32-msvc@4.11.0:
|
||||
resolution: {integrity: sha512-xZVt6K70Gr3I7nUhug2dN6VRR1ibot3rXqXS3wo+8JP64t7djc3lBFyqO4GiVrhNaAIhUCJtwQ/20dr0h0thmQ==}
|
||||
/@rollup/rollup-win32-ia32-msvc@4.12.0:
|
||||
resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@rollup/rollup-win32-x64-msvc@4.11.0:
|
||||
resolution: {integrity: sha512-f3I7h9oTg79UitEco9/2bzwdciYkWr8pITs3meSDSlr1TdvQ7IxkQaaYN2YqZXX5uZhiYL+VuYDmHwNzhx+HOg==}
|
||||
/@rollup/rollup-win32-x64-msvc@4.12.0:
|
||||
resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
@ -556,26 +556,26 @@ packages:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
dev: true
|
||||
|
||||
/rollup@4.11.0:
|
||||
resolution: {integrity: sha512-2xIbaXDXjf3u2tajvA5xROpib7eegJ9Y/uPlSFhXLNpK9ampCczXAhLEb5yLzJyG3LAdI1NWtNjDXiLyniNdjQ==}
|
||||
/rollup@4.12.0:
|
||||
resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
optionalDependencies:
|
||||
'@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
|
||||
'@rollup/rollup-android-arm-eabi': 4.12.0
|
||||
'@rollup/rollup-android-arm64': 4.12.0
|
||||
'@rollup/rollup-darwin-arm64': 4.12.0
|
||||
'@rollup/rollup-darwin-x64': 4.12.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.12.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.12.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.12.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.12.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.12.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.12.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.12.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.12.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.12.0
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
@ -639,8 +639,8 @@ packages:
|
||||
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
|
||||
dev: true
|
||||
|
||||
/vrml1to97@0.3.2:
|
||||
resolution: {integrity: sha512-1CYLeo9XawIr6ejjYgQj4H/l74Tb2EyyExbezRNHMpKvutVOi+1MplgFd0gHkQqQs13jNvJwRAB0LNzkf+41gQ==}
|
||||
/vrml1to97@0.4.0:
|
||||
resolution: {integrity: sha512-5gA9jr31f4z779ddST0EN+TNHrU1Auzf9GBeHoAjAZRBtwWqIehvDePGv73p8MM3ANueA6Z5puhtezK90Qy+kg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
|
167
src/conway.civet
167
src/conway.civet
@ -1,6 +1,6 @@
|
||||
type Face = number[]
|
||||
type XYZ = [number, number, number]
|
||||
type Polyhedron = {face: Face[], xyz: XYZ[]}
|
||||
type Polyhedron = {face: Face[], xyz: XYZ[], name?: string}
|
||||
|
||||
type Notation = string
|
||||
|
||||
@ -11,7 +11,28 @@ phi := (1 + Math.sqrt 5)/2
|
||||
ihp := 1/phi
|
||||
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 :=
|
||||
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],
|
||||
@ -20,19 +41,24 @@ icosahedron: Polyhedron :=
|
||||
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]]
|
||||
name: 'Icosahedron'
|
||||
|
||||
polyCache: Record<Notation, Polyhedron> :=
|
||||
'': face: [], xyz: []
|
||||
T:
|
||||
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]]
|
||||
name: 'Tetrahedron'
|
||||
O:
|
||||
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]]
|
||||
xyz: [[0,0,rt2], [rt2,0,0], [0,rt2,0], [-rt2,0,0], [0,-rt2,0], [0,0,-rt2]]
|
||||
name: 'Octahedron'
|
||||
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]]
|
||||
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]]
|
||||
name: 'Cube'
|
||||
I: icosahedron
|
||||
D: geomDual icosahedron
|
||||
|
||||
@ -69,7 +95,7 @@ rawStandardizations :=
|
||||
dd: '', rr: '', jd: 'j', gd: 'rgr', // absorption rules
|
||||
rd: 'dr', // these commute; others? If so, move 'r' in to cancel w/ seed
|
||||
// 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',
|
||||
jO: 'jC', jI: 'jD', gO: 'gC', gI: 'gD'
|
||||
standardizations :=
|
||||
@ -95,6 +121,13 @@ function orb(r: number, n: number,
|
||||
operator add(a: XYZ, b: XYZ)
|
||||
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 :=
|
||||
P: (n: number) => // Prism
|
||||
unless n then n = 3
|
||||
@ -105,7 +138,7 @@ seeds :=
|
||||
for i of [0...n]
|
||||
ip1 := (i+1)%n // next vertex around
|
||||
face.push [i, ip1, ip1+n, i+n]
|
||||
{face, xyz}
|
||||
{face, xyz, name: ngonal(n)+` prism`}
|
||||
A: (n: number) => // Antiprism
|
||||
unless n then n = 4
|
||||
theta := tau/n
|
||||
@ -122,7 +155,7 @@ seeds :=
|
||||
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}
|
||||
{face, xyz, name: ngonal(n)+` antiprism`}
|
||||
Y: (n: number) => // pYramid
|
||||
unless n then n = 4
|
||||
// Canonical solution by Intelligenti Pauca and Ed Pegg, see
|
||||
@ -137,11 +170,27 @@ seeds :=
|
||||
xyz.push [0, 0, depth-height]
|
||||
face := ([i, (i+1)%n, n] for i of [0...n])
|
||||
face.unshift [n-1..0]
|
||||
{face, xyz}
|
||||
{face, xyz, name: ngonal(n)+` pyramid`}
|
||||
type SeedOp = keyof typeof seeds
|
||||
|
||||
// Syntactic sugar to deal with weird TypeScript typing:
|
||||
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,
|
||||
digits: string, join: boolean): Polyhedron
|
||||
@ -173,7 +222,7 @@ function kisjoin(P: Polyhedron, notation: string,
|
||||
continue
|
||||
// Add the pyramid, possibly eliding edges:
|
||||
for each w, jx of f
|
||||
pw := f æ (jx-1)
|
||||
pw := f æ jx-1
|
||||
neighbor .= 0
|
||||
if join
|
||||
neighbor = P.face.findIndex (g, gx) =>
|
||||
@ -182,7 +231,35 @@ function kisjoin(P: Polyhedron, notation: string,
|
||||
if pw < w // avoid adding same face twice
|
||||
face.push [v, pw, newVixes[neighbor], 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?
|
||||
FromCenter := Symbol()
|
||||
@ -210,7 +287,7 @@ function gyropel(P: Polyhedron, notation: string,
|
||||
for each f of P.face
|
||||
if digits and f.length is not in allowed then continue
|
||||
for each v, ix of f
|
||||
pv := f æ (ix-1)
|
||||
pv := f æ ix-1
|
||||
(edgeV[pv] ??= [])[v] = xyz.length
|
||||
xyz.push lerp xyz[pv], xyz[v], 1/3
|
||||
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
|
||||
newFace: Face := []
|
||||
for each v, ix of f
|
||||
pv := f æ (ix-1)
|
||||
pv := f æ ix-1
|
||||
reverseV := edgeV[v]?[pv] ?? -1
|
||||
if reverseV >= 0 then newFace.push reverseV
|
||||
newFace.push v
|
||||
@ -233,8 +310,8 @@ function gyropel(P: Polyhedron, notation: string,
|
||||
if fromCenter then xyz.push centers[fx]
|
||||
aroundOutside: Face .= []
|
||||
for each v, ix of f
|
||||
pv := f æ (ix-1)
|
||||
ppv := f æ (ix-2)
|
||||
pv := f æ ix-1
|
||||
ppv := f æ ix-2
|
||||
firstNew := edgeV[ppv][pv]
|
||||
newSection := [firstNew]
|
||||
reverseV := edgeV[pv][ppv] ?? -1
|
||||
@ -251,7 +328,19 @@ function gyropel(P: Polyhedron, notation: string,
|
||||
face.push newSection
|
||||
else aroundOutside ++= newSection
|
||||
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[]
|
||||
unless digits return []
|
||||
@ -284,17 +373,19 @@ transforms :=
|
||||
r: (P: Polyhedron) => // reverse (mirror)
|
||||
face: (f.toReversed() for each f of P.face)
|
||||
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
|
||||
if digits
|
||||
console.error `Ignoring ${digits} arg of d in ${notation}`
|
||||
// Create a "fake" of P and adjust it (so we don't disturb the
|
||||
// cached P), which will create the dual
|
||||
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}`
|
||||
c: (P: Polyhedron, notation: string, digits: string) => // canonicalize
|
||||
face: P.face.slice()
|
||||
xyz: canonicalXYZ P, notation, Number(digits) or 10
|
||||
name: P.name
|
||||
x: approxCanonicalize // iterative direct adjustment algorithm
|
||||
type TransformOp = keyof typeof transforms
|
||||
|
||||
@ -326,7 +417,7 @@ function topoDual(P: Polyhedron): Polyhedron
|
||||
newface := []
|
||||
do
|
||||
verts := P.face[current]
|
||||
preV := verts æ (verts.indexOf(v)-1)
|
||||
preV := verts æ verts.indexOf(v)-1
|
||||
nextIx := infaces.findIndex ([face, label]) =>
|
||||
label !== current and face.includes preV
|
||||
current = infaces[nextIx][1]
|
||||
@ -338,6 +429,8 @@ function topoDual(P: Polyhedron): Polyhedron
|
||||
newface
|
||||
xyz:
|
||||
Array(P.face.length).fill([0,0,0]) // warning, every vertex is ===
|
||||
name: dualName P.name
|
||||
|
||||
|
||||
function geomDual(P: Polyhedron): Polyhedron
|
||||
return := topoDual P
|
||||
@ -346,7 +439,7 @@ function geomDual(P: Polyhedron): Polyhedron
|
||||
function approxDualVertices(P: Polyhedron): 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
|
||||
|
||||
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,
|
||||
// and very poorly otherwise. So it probably would not provide any better
|
||||
// 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
|
||||
columns := (normals.map(&[i]) for i of [0..2])
|
||||
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]),
|
||||
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
|
||||
dualNotation := 'd' + notation
|
||||
D .= topoDual P
|
||||
@ -418,7 +537,7 @@ function canonicalXYZ(P: Polyhedron, notation: string, iterations: number): XYZ[
|
||||
polyCache[dualNotation] = D
|
||||
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] ]
|
||||
|
||||
function reciprocalN(P: Polyhedron): XYZ[]
|
||||
@ -429,8 +548,8 @@ function reciprocalN(P: Polyhedron): XYZ[]
|
||||
for each vlabel, ix of f
|
||||
v := P.xyz[vlabel]
|
||||
accumulate centroid, v
|
||||
pv := P.xyz[f æ (ix-1)]
|
||||
ppv := P.xyz[f æ (ix-2)]
|
||||
pv := P.xyz[f æ ix-1]
|
||||
ppv := P.xyz[f æ ix-2]
|
||||
// original doc says unit normal but below isn't. Didn't help to try, tho
|
||||
nextNormal := (pv sub ppv) cross (v sub pv)
|
||||
// or instead, just chop down big ones: (didn't work either)
|
||||
@ -469,14 +588,14 @@ function approxCanonicalize(P: Polyhedron, notation: string,
|
||||
normalizeEdges xyz, edge
|
||||
if Math.max(...(xyz[i] dist start[i] for i of [0...V])) < THRESHOLD
|
||||
break
|
||||
{face: P.face.slice(), xyz}
|
||||
{face: P.face.slice(), xyz, P.name}
|
||||
|
||||
type Edge = [number, number]
|
||||
function edges(P:Polyhedron): Edge[]
|
||||
return: Edge[] := []
|
||||
for each f of P.face
|
||||
for each v, ix of f
|
||||
pv := f æ (ix-1)
|
||||
pv := f æ ix-1
|
||||
if pv < v then return.value.push ð pv, v
|
||||
|
||||
function normalizeEdges(xyz: XYZ[], edge: Edge[]): void
|
||||
@ -588,6 +707,7 @@ function inform(x: string)
|
||||
// VRML97 generation
|
||||
|
||||
function outputVRML(notation: Notation, P: Polyhedron): string
|
||||
shortDescrip := P.name or `A ${P.face.length}-hedron`
|
||||
```
|
||||
#VRML V2.0 utf8
|
||||
Group { children [
|
||||
@ -603,6 +723,7 @@ George Hart's Encyclopedia of Polyhedra as the source." }
|
||||
NavigationInfo { type [ "EXAMINE" ] }
|
||||
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()}
|
||||
Shape {
|
||||
appearance Appearance {
|
||||
@ -677,6 +798,6 @@ function edgeIndices(P: Polyhedron)
|
||||
sep := ",\n "
|
||||
filtmap(P.face, (thisf) =>
|
||||
filtmap(thisf, (v, ix, f) =>
|
||||
preV := f æ (ix-1)
|
||||
preV := f æ ix-1
|
||||
preV < v ? `${preV}, ${v}, -1` : '').join sep)
|
||||
.join sep
|
||||
|
@ -136,8 +136,48 @@ if inputs.length is 1
|
||||
conwayBrowser.replaceWorld scene
|
||||
|
||||
// See if we are on George Hart's prism generator page
|
||||
prisms := $('input[type="button"][value="View"][onclick="ViewVRML()"]')
|
||||
if prisms.length is 1
|
||||
panelFrame := $('frame[name="panel"][src="prism-maker-subpanel.html"]')
|
||||
if panelFrame.length is 1
|
||||
// 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
|
||||
|
282
src/prism.civet
Normal file
282
src/prism.civet
Normal file
@ -0,0 +1,282 @@
|
||||
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
|
||||
|
||||
// Array "postscript" operator
|
||||
operator ps<T>(main: T[], extra: T): T[]
|
||||
main.push extra
|
||||
main
|
||||
// Array "preface" operator
|
||||
operator pf<T>(extra: T, main: T[]): T[]
|
||||
main.unshift extra
|
||||
main
|
||||
|
||||
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) =>
|
||||
name := 'Prism'
|
||||
h := Math.sin theta/2
|
||||
|
||||
coords := orb(1, n, h, theta) ++ orb(1, n, -h, theta) ps [0,0,h] ps [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])
|
||||
vrml := firstFaces name, '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, name, '0.3 0.5 0.9', sides
|
||||
laterLines vrml, name, sides
|
||||
emitObject vrml
|
||||
|
||||
dipyr: (n, theta) =>
|
||||
name := 'Dipyramid'
|
||||
c := 1/Math.cos theta/2
|
||||
d := 1/Math.sin theta/2
|
||||
|
||||
coords := [0,0,d] pf orb(c, n, 0, theta, 0.5) ps [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])
|
||||
vrml := firstFaces name, '0.9 0.3 0.3', coords, cap++cup
|
||||
|
||||
edges := [1..n] pf ([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)
|
||||
|
||||
antip: (n, theta) =>
|
||||
name := 'Antiprism'
|
||||
{h, r} := antiprismDims theta
|
||||
|
||||
coords := (orb(r, n, h, theta) ++ orb(r, n, -h, theta, 0.5)
|
||||
ps [0,0,h] ps [0,0,-h])
|
||||
top := ([i, 2*n, (i+1)%n] for i of [0...n])
|
||||
bottom := ([(i+1)%n + n, 2*n + 1, i+n] for i of [0...n])
|
||||
vrml := firstFaces name, '0.8 0.7 0.5', coords, top ++ bottom
|
||||
|
||||
topsides := ([(i+1)%n, i+n, i] for i of [0...n])
|
||||
botsides := ([i, i+n, (n+i-1)%n + n] for i of [0...n])
|
||||
laterFaces vrml, name, '0.9 0.3 0.4', topsides ++ botsides
|
||||
laterLines vrml, name, [[0...n] ps 0, [n...2*n] ps n, ...topsides]
|
||||
emitObject vrml
|
||||
|
||||
trap: (n, theta) =>
|
||||
name := 'Trapezohedron'
|
||||
{h, r} := antiprismDims theta
|
||||
[t, z] := dualPoint r*Math.cos(theta/2), h, r, -h
|
||||
|
||||
botzig := orb(t, n, -z, theta) ps [0, 0, -1/h]
|
||||
topzig := orb(t, n, z, theta, 0.5) ps [0, 0, 1/h]
|
||||
coords := alternate botzig, topzig
|
||||
faces :=
|
||||
for i of [0...2*n]
|
||||
if i%2 then [2*n + 1, (i+2)%(2*n), (i+1)%(2*n), i]
|
||||
else [2*n, i, i+1, (i+2)%(2*n)]
|
||||
vrml := firstFaces name, '0.3 0.5 0.9', coords, faces
|
||||
|
||||
edges :=
|
||||
for i of [0...2*n]
|
||||
i%2 ? [2*n + 1, i, (i + 1)%(2*n)] : [2*n, i, i+1]
|
||||
laterLines vrml, name, edges
|
||||
emitObject vrml
|
||||
|
||||
'antiprism + trap': (n, theta) =>
|
||||
shapeMethod[shIx 'antip'](n, theta) + shapeMethod[shIx 'trap'](n, theta)
|
||||
|
||||
crossed: (n, theta) => // should really unify with antiprism, code is so close
|
||||
name := 'CrossedAntiprism'
|
||||
{h, r} := crossedDims theta
|
||||
|
||||
coords := (orb(r, n, h, theta) ++ orb(-r, n, -h, theta, 0.5)
|
||||
ps [0,0,h] ps [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])
|
||||
vrml := firstFaces name, '0.8 0.7 0.5', coords, top ++ bottom
|
||||
|
||||
topsides := ([(i+1)%n, i+n, i] for i of [0...n])
|
||||
botsides := ([i, i+n, (n+i-1)%n + n] for i of [0...n])
|
||||
laterFaces vrml, name, '0.9 0.3 0.4', topsides ++ botsides
|
||||
laterLines vrml, name, [[0...n] ps 0, [n...2*n] ps n, ...topsides]
|
||||
emitObject vrml
|
||||
|
||||
concave: (n, theta, d) =>
|
||||
name := 'ConcaveTrapezohedron'
|
||||
{h, r} := crossedDims theta
|
||||
[t, z] := dualPoint r*Math.cos(theta/2), h, -r, -h
|
||||
d ??= Math.floor (n-1)/2 // won't be needed, for TypeScript's sake
|
||||
eta := tau * (n-d)/n
|
||||
|
||||
botzig := orb(-t, n, -z, eta) ps [0, 0, -1/h]
|
||||
topzig := orb(-t, n, z, eta, 0.5) ps [0, 0, 1/h]
|
||||
coords := alternate botzig, topzig
|
||||
halfFaces :=
|
||||
for i of [0...2*n]
|
||||
i%2 ? [(i+1)%(2*n), i, 2*n + 1] : [2*n, i, i+1]
|
||||
otherFaces :=
|
||||
for i of [0...2*n]
|
||||
i%2 ? [2*n + 1, (i+2)%(2*n), (i+1)%(2*n)] : [2*n, i+1, (i+2)%(2*n)]
|
||||
vrml := firstFaces name, '0.3 0.5 0.9', coords, halfFaces ++ otherFaces
|
||||
laterLines vrml, name, halfFaces
|
||||
emitObject vrml
|
||||
|
||||
'prism + concave': (n, theta, d) =>
|
||||
shapeMethod[shIx 'cross'](n, theta) + shapeMethod[shIx 'conc'](n, theta, d)
|
||||
|
||||
polygon: (n, theta) =>
|
||||
name := 'Polygon'
|
||||
coords := orb(1, n, 0, theta) ps [0, 0, 0]
|
||||
faces := ([i, (i+1)%n, n] for i of [0...n])
|
||||
vrml := firstFaces name, '0.5 0.8 0.5', coords, faces
|
||||
laterLines vrml, name, [[0...n] ps 0]
|
||||
emitObject vrml
|
||||
|
||||
for key, method in shapeMethods
|
||||
shapeMethod[shIx key] = method
|
||||
|
||||
function antiprismDims(theta: number)
|
||||
h .= Math.sqrt 1 - 4/(4 + 2*Math.cos(theta/2) - 2*Math.cos(theta))
|
||||
r .= Math.sqrt 1 - h*h
|
||||
f := Math.sqrt h*h + (r*Math.cos theta/2)**2
|
||||
h /= f
|
||||
r /= f
|
||||
{h, r}
|
||||
|
||||
function crossedDims(theta: number)
|
||||
r .= 2/Math.sqrt 4 - 2*Math.cos(theta/2) - 2*Math.cos(theta)
|
||||
h .= Math.sqrt 1 - r*r
|
||||
f .= Math.sqrt h*h + (r*Math.cos theta/2)**2
|
||||
r /= f
|
||||
h /= f
|
||||
{h, r}
|
||||
|
||||
function dualPoint(x1: number, y1: number, x2: number, y2: number)
|
||||
len := Math.sqrt (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)
|
||||
vx := (y2 - y1)/len
|
||||
vy := (x1 - x2)/len
|
||||
d := vx*x1 + vy*y1
|
||||
[vx/d, vy/d]
|
||||
|
||||
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 alternate<T>(a: T[], b: T[])
|
||||
return: T[] := []
|
||||
return.value.push n, b[i] for each n, i of a
|
||||
|
||||
function firstFaces(name: string, color: string,
|
||||
coords: number[][], faces: number[][])
|
||||
return: string[] := []
|
||||
startShape return.value, color
|
||||
return.value.push 'geometry IndexedFaceSet {',
|
||||
`coord DEF ${name}Coords Coordinate { point [`
|
||||
return.value.push coord.join(' ') + ',' for each coord of coords
|
||||
return.value.push '] }',
|
||||
'creaseAngle 0 solid FALSE coordIndex ['
|
||||
return.value.push face.join(', ') + ', -1,' for each face of faces
|
||||
return.value.push '] } }'
|
||||
|
||||
function startShape(container: string[], color: string, colType = 'diffuse'): void
|
||||
container.push 'Shape { appearance Appearance {',
|
||||
' material Material {',
|
||||
` ${colType}Color ${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', 'emissive' // per VRML97 standard
|
||||
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"
|
@ -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/adapptext.js --file $1/adapptext.js
|
||||
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/@webcomponents/custom-elements/custom-elements.min.js $1
|
||||
zip -r $1 $1
|
||||
|
Loading…
Reference in New Issue
Block a user