archematics/src/prism.civet

283 lines
9.8 KiB
Plaintext

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"