diff --git a/src/prism.civet b/src/prism.civet index ca5716e..d8a5658 100644 --- a/src/prism.civet +++ b/src/prism.civet @@ -74,6 +74,15 @@ DEF Far Viewpoint { // Useful constants tau := 2*Math.PI +// Array "postscript" operator +operator ps(main: T[], extra: T): T[] + main.push extra + main +// Array "preface" operator +operator pf(extra: T, main: T[]): T[] + main.unshift extra + main + function shIx(name: string): number shapeName.findIndex((sn) => name is in sn) @@ -81,40 +90,149 @@ type ShapeMethod = (n: number, theta: number, d?: number) => string shapeMethod: ShapeMethod[] := [] shapeMethods: Record := prism: (n, theta) => + name := 'Prism' h := Math.sin theta/2 - vrml: string[] := [] - coords := orb(1, n, h, theta) ++ orb(1, n, -h, theta) ++ [[0,0,h], [0,0,-h]] + 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]) - firstFaces vrml, 'Prism', '0.8 0.7 0.5', coords, top ++ bottom + 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, 'Prism', '0.3 0.5 0.9', sides - laterLines vrml, 'Prism', sides + 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 - vrml: string[] := [] - coords := [[0,0,d]] ++ orb(c, n, 0, theta, 0.5) ++ [[0,0,-d]] + 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]) - firstFaces vrml, name, '0.9 0.3 0.3', coords, cap++cup + vrml := firstFaces name, '0.9 0.3 0.3', coords, cap++cup - edges := [[1..n]] ++ ([0, i, n+1] for i of [1..n]) + 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 + [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) @@ -124,20 +242,26 @@ function orb(r: number, n: number, height: number, theta: number, t=0) 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 alternate(a: T[], b: T[]) + return: T[] := [] + return.value.push n, b[i] for each n, i of a -function startShape(container: string[], color: string): void +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 {', - ` diffuseColor ${color} } }` + ` ${colType}Color ${color} } }` function laterFaces(container: string[], name: string, color: string, faces: number[][]): void @@ -149,7 +273,7 @@ function laterFaces(container: string[], name: string, color: string, container.push '] } }' function laterLines(container: string[], name: string, edges: number[][]): void - startShape container, '0 0 0' + startShape container, '0 0 0', 'emissive' // per VRML97 standard container.push 'geometry IndexedLineSet {', `coord USE ${name}Coords`, 'coordIndex ['