feat: Handle vrml generated on the fly in Conway notation page #62
268
src/conway.civet
268
src/conway.civet
@ -19,28 +19,28 @@ icosahedron: Polyhedron :=
|
|||||||
[3,2,11], [3,10,2], [3,6,10], [3,7,6], [3,11,7]]
|
[3,2,11], [3,10,2], [3,6,10], [3,7,6], [3,11,7]]
|
||||||
xyz: [[0,ihp,1], [0,-ihp,1], [0,ihp,-1], [0,-ihp,-1],
|
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]]
|
||||||
polyCache: Record<Notation, Polyhedron> :=
|
polyCache: Record<Notation, Polyhedron> :=
|
||||||
'': face: [], xyz: []
|
'': face: [], xyz: []
|
||||||
T:
|
T:
|
||||||
face: [[0,1,2], [0,2,3], [0,3,1], [1,3,2]]
|
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]]
|
||||||
O:
|
O:
|
||||||
face: [[0,1,2], [0,2,3], [0,3,4], [0,4,1],
|
face: [[0,2,1], [0,3,2], [0,4,3], [0,1,4],
|
||||||
[1,4,5], [1,5,2], [2,5,3], [3,5,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]]
|
xyz: [[0,0,rt2], [rt2,0,0], [0,rt2,0], [-rt2,0,0], [0,-rt2,0], [0,0,-rt2]]
|
||||||
C:
|
C:
|
||||||
face: [[3,0,1,2], [3,4,5,0], [0,5,6,1], [1,6,7,2], [2,7,4,3], [5,4,7,6]]
|
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]]
|
||||||
I: icosahedron
|
I: icosahedron
|
||||||
D: geomDual(icosahedron)
|
D: geomDual icosahedron
|
||||||
|
|
||||||
export function generateVRML(notation: Notation): string
|
export function generateVRML(notation: Notation): string
|
||||||
outputVRML notation, generatePoly notation
|
outputVRML notation, generatePoly notation
|
||||||
|
|
||||||
function generatePoly(notation: Notation): Polyhedron
|
function generatePoly(notation: Notation): Polyhedron
|
||||||
getStandardPoly standardize notation
|
getStandardPoly inform standardize notation
|
||||||
|
|
||||||
function getStandardPoly(notation: Notation): Polyhedron
|
function getStandardPoly(notation: Notation): Polyhedron
|
||||||
if notation in polyCache then return polyCache[notation]
|
if notation in polyCache then return polyCache[notation]
|
||||||
@ -48,25 +48,39 @@ function getStandardPoly(notation: Notation): Polyhedron
|
|||||||
parent := getStandardPoly rest
|
parent := getStandardPoly rest
|
||||||
// may have created what we want by side effect
|
// may have created what we want by side effect
|
||||||
if notation in polyCache then return polyCache[notation]
|
if notation in polyCache then return polyCache[notation]
|
||||||
dispatch op, Number(arg or 0), parent, notation // will do the caching
|
dispatch op, arg, parent, notation // will do the caching
|
||||||
|
|
||||||
// Convenience tuple maker
|
// Convenience tuple maker
|
||||||
function ð<Tup extends unknown[]>(...arg: Tup): Tup arg
|
function ð<Tup extends unknown[]>(...arg: Tup): Tup arg
|
||||||
|
|
||||||
|
// Note we now allow numeric arguments on all of the basic operations,
|
||||||
|
// kis/truncate, join/ambo, and gyro/snub. Likely some of the operations
|
||||||
|
// we are taking as composite could have reasonable numeric versions, but
|
||||||
|
// there didn't seem to be any sensible way to propagate such an argument
|
||||||
|
// to the operations in their rewrites. In other words, the numeric-limited
|
||||||
|
// operations may not be composite, or at least not in the same way. So
|
||||||
|
// we have just left them as applying throughout the polyhedron.
|
||||||
rawStandardizations :=
|
rawStandardizations :=
|
||||||
P4$: 'C', A3$: 'O', Y3$: 'T', // Seed synonyms
|
P4$: 'C', A3$: 'O', Y3$: 'T', // Seed synonyms
|
||||||
e: 'aa', b: 'ta', o: 'jj', m: 'kj', // abbreviations
|
e: 'aa', b: 'ta', o: 'jj', m: 'kj', // abbreviations
|
||||||
[String.raw`t(\d*)`]: 'd$1d', j: 'dad', s: 'dgd', // dual operations
|
[String.raw`t(\d*)`]: 'dk$1d',
|
||||||
dd: '', ad: 'a', gd: 'g', // absorption of duals
|
[String.raw`a(\d*)`]: 'dj$1d', // dual operations
|
||||||
|
[String.raw`s(\d*)`]: 'dg$1d',
|
||||||
|
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:
|
// Remainder are all simplifications/unique selections for seeds:
|
||||||
aY: 'A', dT: 'T', gT: 'D', aT: 'O', dC: 'O', dO: 'C',
|
aY: 'A', dT: 'T', gT: 'D', jT: 'C', dC: 'O', dO: 'C',
|
||||||
dI: 'D', dD: 'I', aO: 'aC', aI: 'aD', gO: 'gC', gI: 'gD'
|
dI: 'D', dD: 'I', rO: 'O', rC: 'C', rI: 'I', rD: 'D',
|
||||||
|
jO: 'jC', jI: 'jD', gO: 'gC', gI: 'gD'
|
||||||
standardizations :=
|
standardizations :=
|
||||||
(ð RegExp(pat, 'g'), rep for pat, rep in rawStandardizations)
|
(ð RegExp(pat, 'g'), rep for pat, rep in rawStandardizations)
|
||||||
|
|
||||||
function standardize(notation: Notation): Notation
|
function standardize(notation: Notation): Notation
|
||||||
for [pat, rep] of standardizations
|
lastNotation .= ''
|
||||||
notation = notation.replace(pat, rep)
|
while lastNotation != notation // iterate in case of rdrd, e.g.
|
||||||
|
lastNotation = notation
|
||||||
|
for [pat, rep] of standardizations
|
||||||
|
notation = notation.replace(pat, rep)
|
||||||
notation
|
notation
|
||||||
|
|
||||||
function orb(r: number, n: number,
|
function orb(r: number, n: number,
|
||||||
@ -80,6 +94,7 @@ function orb(r: number, n: number,
|
|||||||
|
|
||||||
seeds :=
|
seeds :=
|
||||||
P: (n: number) => // Prism
|
P: (n: number) => // Prism
|
||||||
|
unless n then n = 3
|
||||||
theta := tau/n
|
theta := tau/n
|
||||||
halfEdge := Math.sin theta/2
|
halfEdge := Math.sin theta/2
|
||||||
xyz := orb(1, n, halfEdge) ++ orb(1, n, -halfEdge)
|
xyz := orb(1, n, halfEdge) ++ orb(1, n, -halfEdge)
|
||||||
@ -89,6 +104,7 @@ seeds :=
|
|||||||
face.push [i, ip1, ip1+n, i+n]
|
face.push [i, ip1, ip1+n, i+n]
|
||||||
{face, xyz}
|
{face, xyz}
|
||||||
A: (n: number) => // Antiprism
|
A: (n: number) => // Antiprism
|
||||||
|
unless n then n = 4
|
||||||
theta := tau/n
|
theta := tau/n
|
||||||
halfHeight .= Math.sqrt
|
halfHeight .= Math.sqrt
|
||||||
1 - 4/(4 + 2*Math.cos(theta/2) - 2*Math.cos(theta))
|
1 - 4/(4 + 2*Math.cos(theta/2) - 2*Math.cos(theta))
|
||||||
@ -98,20 +114,24 @@ seeds :=
|
|||||||
halfHeight /= f
|
halfHeight /= f
|
||||||
faceRadius /= f
|
faceRadius /= f
|
||||||
xyz := orb(faceRadius, n, halfHeight)
|
xyz := orb(faceRadius, n, halfHeight)
|
||||||
++ orb(faceRadius, n, halfHeight, 0.5)
|
++ orb(faceRadius, n, -halfHeight, 0.5)
|
||||||
face := [[n-1..0], [n...2*n]] // top and bottom
|
face := [[n-1..0], [n...2*n]] // top and bottom
|
||||||
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}
|
||||||
Y: (n: number) => // pYramid
|
Y: (n: number) => // pYramid
|
||||||
|
unless n then n = 4
|
||||||
// Canonical solution by Intelligenti Pauca and Ed Pegg, see
|
// Canonical solution by Intelligenti Pauca and Ed Pegg, see
|
||||||
// https://math.stackexchange.com/questions/2286628/canonical-pyramid-polynomials
|
// https://math.stackexchange.com/questions/2286628/canonical-pyramid-polynomials
|
||||||
theta := tau/n
|
theta := tau/n
|
||||||
c := Math.cos theta/2
|
c := Math.cos theta/2
|
||||||
baseRadius := Math.sqrt 2/(c*(1+c))
|
baseRadius := Math.sqrt 2/(c*(1+c))
|
||||||
xyz := orb baseRadius, n, Math.tan theta/4
|
depth := Math.sqrt (1-c)/(1+c)
|
||||||
xyz.push [0, 0, -1/Math.tan theta/4]
|
height := 2*Math.sqrt 1/(1 - c*c)
|
||||||
|
xyz := orb baseRadius, n, depth
|
||||||
|
edgeMid2 := add xyz[0], xyz[1]
|
||||||
|
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}
|
||||||
@ -120,29 +140,94 @@ 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 kisjoin(P: Polyhedron, notation: string,
|
||||||
|
digits: string, join: boolean): Polyhedron
|
||||||
|
// kis and join are closely related operations. Both of them add a
|
||||||
|
// pyramid on a selection of faces; join then further deletes any
|
||||||
|
// _original_ edge bordered by two _new_ triangles, producing a quad.
|
||||||
|
// Faces are selected by their numbers of sides, using the given digits.
|
||||||
|
// If there are none, all faces are used. Otherwise, the digits are turned
|
||||||
|
// into a list of numbers by breaking after every digit except as needed
|
||||||
|
// to prevent leading 0s or isolated 1s or 2s (since no face has one or
|
||||||
|
// two sides); this way you can list any subset of the numbers 3 - 32,
|
||||||
|
// which is plenty.
|
||||||
|
// The operation is then applied just to faces with the numbers of edges on
|
||||||
|
// the list. e.g. k3412 will add pyramids to the triangles, quads, and
|
||||||
|
// dodecagon faces.
|
||||||
|
allowed := parseSides digits
|
||||||
|
// first collect a directory from face indices to new vertex numbers
|
||||||
|
nextVertex .= P.xyz.length
|
||||||
|
newVixes :=
|
||||||
|
for f of P.face
|
||||||
|
!digits or f.length is in allowed ? nextVertex++ : 0
|
||||||
|
if nextVertex is P.xyz.length then return P // nothing to do
|
||||||
|
xyz := P.xyz ++ faceCenters(P).filter (f,ix) => newVixes[ix]
|
||||||
|
face: Face[] := []
|
||||||
|
for each f, ix of P.face
|
||||||
|
v := newVixes[ix]
|
||||||
|
if v is 0
|
||||||
|
face.push f.slice()
|
||||||
|
continue
|
||||||
|
// Add the pyramid, possibly eliding edges:
|
||||||
|
for each w, jx of f
|
||||||
|
pw := f æ (jx-1)
|
||||||
|
neighbor .= 0
|
||||||
|
if join
|
||||||
|
neighbor = P.face.findIndex (g, gx) =>
|
||||||
|
gx !== ix and w is in g and pw is in g
|
||||||
|
if join and newVixes[neighbor] // elide this edge
|
||||||
|
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)
|
||||||
|
|
||||||
|
enum Gyway
|
||||||
|
FromCenter
|
||||||
|
AlongEdge
|
||||||
|
function gyropel(P: Polyhedron, notation: string,
|
||||||
|
digits: string, ...ways: Gyway[]): Polyhedron
|
||||||
|
// gyro and propellor are closely related operations. Both of them add new
|
||||||
|
// vertices one third of the way along each edge of each face selected
|
||||||
|
// by the digits argument (see kisjoin). They then differ in what edges
|
||||||
|
// are drawn to the new vertices. In gyro, another new vertex is added
|
||||||
|
// at the center of each face and connected to each of them; in propellor,
|
||||||
|
// they are just connected in sequence. For completeness, we also allow
|
||||||
|
// both at the same time, which is equivalent to propellor followed by kis
|
||||||
|
// just on the new rotated faces.
|
||||||
|
// TO BE IMPLEMENTED
|
||||||
|
|
||||||
|
function parseSides(digits: string): number[]
|
||||||
|
unless digits return []
|
||||||
|
tooSmall := ['1', '2']
|
||||||
|
last := digits.length - 1
|
||||||
|
return := []
|
||||||
|
current .= ''
|
||||||
|
pos .= 0
|
||||||
|
while pos <= last
|
||||||
|
current += digits[pos++]
|
||||||
|
nextDigit := digits[pos]
|
||||||
|
if (current is in tooSmall
|
||||||
|
or nextDigit is '0'
|
||||||
|
or pos == last and nextDigit is in tooSmall)
|
||||||
|
continue
|
||||||
|
return.value.push parseInt current
|
||||||
|
current = ''
|
||||||
|
|
||||||
transforms :=
|
transforms :=
|
||||||
k: (P: Polyhedron, n: number, notation: string): Polyhedron => // kis[n]
|
k: (P: Polyhedron, notation: string, digits: string) => // kis[n]
|
||||||
// aka "elevate" -- add a pyramid on each (n-sided) face
|
kisjoin P, notation, digits, false
|
||||||
centers := faceCenters P
|
j: (P: Polyhedron, notation: string, digits: string) => // join
|
||||||
xyz := P.xyz.slice()
|
kisjoin P, notation, digits, true
|
||||||
face: Face[] := []
|
|
||||||
for each f, ix of P.face
|
|
||||||
if n is 0 or f.length is n
|
|
||||||
v := xyz.length
|
|
||||||
xyz.push centers[ix]
|
|
||||||
for each j of [0...f.length]
|
|
||||||
face.push [v, f æ (j-1), f[j]]
|
|
||||||
else face.push f.slice()
|
|
||||||
adjustXYZ({face, xyz}, notation, 3)
|
|
||||||
type TransformOp = keyof typeof transforms
|
type TransformOp = keyof typeof transforms
|
||||||
|
|
||||||
function dispatch(op: string, n: number,
|
function dispatch(op: string, digits: string,
|
||||||
P: Polyhedron, notation: string): Polyhedron
|
P: Polyhedron, notation: string): Polyhedron
|
||||||
return .= P
|
return .= P
|
||||||
if op in seeds
|
if op in seeds
|
||||||
return = seeds[op as SeedOp] n
|
return = seeds[op as SeedOp] Number(digits) or 0
|
||||||
else if op in transforms
|
else if op in transforms
|
||||||
return = transforms[op as TransformOp] P, n, notation
|
return = transforms[op as TransformOp] P, notation, digits
|
||||||
polyCache[notation] = return.value
|
polyCache[notation] = return.value
|
||||||
|
|
||||||
function topoDual(P: Polyhedron): Polyhedron
|
function topoDual(P: Polyhedron): Polyhedron
|
||||||
@ -151,34 +236,67 @@ function topoDual(P: Polyhedron): Polyhedron
|
|||||||
// in some way.
|
// in some way.
|
||||||
face:
|
face:
|
||||||
for v of [0...P.xyz.length]
|
for v of [0...P.xyz.length]
|
||||||
infaces :=
|
infaces := // gather labeled list of faces contining v
|
||||||
for f, index of P.face
|
for f, index of P.face
|
||||||
unless f.includes v continue
|
unless f.includes v continue
|
||||||
ð f, index
|
ð f, index
|
||||||
start := infaces[0][1];
|
start := infaces[0][1];
|
||||||
current .= start
|
current .= start
|
||||||
|
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]
|
||||||
|
newface.push current
|
||||||
|
if newface.length > infaces.length
|
||||||
|
console.error 'In topoDual: Malformed polyhedron', P
|
||||||
|
break
|
||||||
until current is start
|
until current is start
|
||||||
|
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 ===
|
||||||
|
|
||||||
function geomDual(P: Polyhedron): Polyhedron
|
function geomDual(P: Polyhedron): Polyhedron
|
||||||
// Takes the vertices of the dual to be the face centers of P
|
return := topoDual P
|
||||||
// all scaled so that the midpoint of the first edge is unit distance
|
return.value.xyz = approxDualVertices P
|
||||||
// from the origin
|
|
||||||
return := topoDual(P)
|
function approxDualVertices(P: Polyhedron): XYZ[]
|
||||||
newVertices := faceCenters(P)
|
P.face.map (f) => approxDualVertex f, P.xyz
|
||||||
aface := return.value.face[0]
|
|
||||||
mid2 := add newVertices[aface[0]], newVertices[aface[1]]
|
operator dot(v: number[], w: number[])
|
||||||
factor := 2/mag mid2
|
v.reduce (l,r,i) => l + r*w[i], 0
|
||||||
for each v of newVertices
|
|
||||||
scale(v, factor)
|
function approxDualVertex(f: Face, v: XYZ[]): XYZ
|
||||||
return.value.xyz = newVertices
|
// For each edge of f, there is a plane containing it perpendicular
|
||||||
|
// to the line joining the origin to its nearest approach to the origin.
|
||||||
|
// This function returns the point closest to being on all of those planes
|
||||||
|
// (in the least-squares sense).
|
||||||
|
// This method seems to work well 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])
|
||||||
|
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
|
||||||
|
CMsource := (for c of [0..2]
|
||||||
|
(columns[r] dot columns[c] for r of [0..2])) as [XYZ, XYZ, XYZ]
|
||||||
|
cramerD := det ...CMsource
|
||||||
|
if Math.abs(cramerD) < 1e-6
|
||||||
|
console.error `Face ${f} of ${v.map (p) => '['+p+']'} ill conditioned`
|
||||||
|
return [0, 0, 0]
|
||||||
|
[ det(target,CMsource[1],CMsource[2])/cramerD,
|
||||||
|
det(CMsource[0],target,CMsource[2])/cramerD,
|
||||||
|
det(CMsource[0],CMsource[1],target)/cramerD ]
|
||||||
|
|
||||||
|
function det(a: XYZ, b: XYZ, c:XYZ)
|
||||||
|
a[0]*b[1]*c[2] + a[1]*b[2]*c[0] + a[2]*b[0]*c[1]
|
||||||
|
- a[2]*b[1]*c[0] - a[1]*b[0]*c[2] - a[0]*b[2]*c[1]
|
||||||
|
|
||||||
|
function tangentPoint(v: XYZ, w: XYZ) // closest point on vw to origin
|
||||||
|
d := sub w,v
|
||||||
|
sub v, scale d, d dot v / mag2 d
|
||||||
|
|
||||||
function faceCenters(P: Polyhedron): XYZ[]
|
function faceCenters(P: Polyhedron): XYZ[]
|
||||||
for each face of P.face
|
for each face of P.face
|
||||||
@ -209,12 +327,21 @@ function accumulate(basket: XYZ, egg: XYZ)
|
|||||||
basket[2] += egg[2]
|
basket[2] += egg[2]
|
||||||
basket
|
basket
|
||||||
|
|
||||||
|
function diminish(basket: XYZ, egg: XYZ)
|
||||||
|
basket[0] -= egg[0]
|
||||||
|
basket[1] -= egg[1]
|
||||||
|
basket[2] -= egg[2]
|
||||||
|
basket
|
||||||
|
|
||||||
function copy(a: XYZ)
|
function copy(a: XYZ)
|
||||||
ð a[0], a[1], a[2]
|
ð a[0], a[1], a[2]
|
||||||
|
|
||||||
function add(a: XYZ, b: XYZ)
|
function add(a: XYZ, b: XYZ)
|
||||||
accumulate copy(a), b
|
accumulate copy(a), b
|
||||||
|
|
||||||
|
function sub(a: XYZ, b: XYZ)
|
||||||
|
diminish copy(a), b
|
||||||
|
|
||||||
function mag2(a: XYZ)
|
function mag2(a: XYZ)
|
||||||
a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
|
a[0]*a[0] + a[1]*a[1] + a[2]*a[2]
|
||||||
|
|
||||||
@ -227,6 +354,13 @@ function scale(subject: XYZ, by: number)
|
|||||||
subject[2] *= by
|
subject[2] *= by
|
||||||
subject
|
subject
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
|
||||||
|
function inform(x: string)
|
||||||
|
$('input[name="inform"]').val(x)
|
||||||
|
x
|
||||||
|
|
||||||
|
|
||||||
// VRML97 generation
|
// VRML97 generation
|
||||||
|
|
||||||
function outputVRML(notation: Notation, P: Polyhedron): string
|
function outputVRML(notation: Notation, P: Polyhedron): string
|
||||||
@ -235,17 +369,26 @@ function outputVRML(notation: Notation, P: Polyhedron): string
|
|||||||
Group { children [
|
Group { children [
|
||||||
WorldInfo { # Generated by GTW's reimplementation of GWH's Conway script.
|
WorldInfo { # Generated by GTW's reimplementation of GWH's Conway script.
|
||||||
title "${notation} ${stats P}"
|
title "${notation} ${stats P}"
|
||||||
info "Generated by GTW's Conway-notation script inspired by GWH's."
|
info "Generated by GTW's Conway-notation script inspired by GWH's.
|
||||||
info "By using this script, you agree that this image is released"
|
By using this script, you agree that this image is released
|
||||||
info "into the public domain, although you are requested to cite"
|
into the public domain, although you are requested to cite
|
||||||
info "George Hart's Encyclopedia of Polyhedra as the source." }
|
George Hart's Encyclopedia of Polyhedra as the source." }
|
||||||
Background {
|
Background {
|
||||||
groundColor [ .2 .5 1 ] # light blue
|
groundColor [ .2 .5 1 ] # light blue
|
||||||
skycolor [ .2 .5 1] }
|
skyColor [ .2 .5 1 ] }
|
||||||
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}
|
||||||
${polyVRML P, colorScheme()} ] }
|
${polyVRML P, colorScheme()}
|
||||||
|
Shape {
|
||||||
|
appearance Appearance {
|
||||||
|
material Material {
|
||||||
|
diffuseColor 0 0 0 } }
|
||||||
|
geometry IndexedLineSet {
|
||||||
|
coord ${useVerts}
|
||||||
|
coordIndex [
|
||||||
|
${edgeIndices P} ] } }
|
||||||
|
${showDual() ? polyVRML geomDual(P), '0.5 0.5 0.5' : ''} ] }
|
||||||
```
|
```
|
||||||
|
|
||||||
function stats(P: Polyhedron): string
|
function stats(P: Polyhedron): string
|
||||||
@ -274,15 +417,19 @@ function polyVRML(P: Polyhedron, color: string): string
|
|||||||
material Material {
|
material Material {
|
||||||
diffuseColor ${color or colorBySides part[0].length} } }
|
diffuseColor ${color or colorBySides part[0].length} } }
|
||||||
geometry IndexedFaceSet {
|
geometry IndexedFaceSet {
|
||||||
|
ccw FALSE
|
||||||
coord ${emittedCoords ? useVerts : (emittedCoords = defVerts P.xyz)}
|
coord ${emittedCoords ? useVerts : (emittedCoords = defVerts P.xyz)}
|
||||||
coordIndex [
|
coordIndex [
|
||||||
${part.map(.join ', ').join(", -1,\n ")}, -1 ] }}`
|
${part.map(.join ', ').join(", -1,\n ")}, -1 ] }}`
|
||||||
shapes.join "\n"
|
shapes.join "\n"
|
||||||
|
|
||||||
function colorScheme()
|
function colorScheme
|
||||||
button := document.getElementsByName('color')[0] as HTMLInputElement
|
button := document.getElementsByName('color')[0] as HTMLInputElement
|
||||||
button.checked ? '1 1 1' : ''
|
button.checked ? '1 1 1' : ''
|
||||||
|
|
||||||
|
function showDual
|
||||||
|
false
|
||||||
|
|
||||||
faceColors: Record<number, string> :=
|
faceColors: Record<number, string> :=
|
||||||
3: '0.9 0.3 0.3' // red
|
3: '0.9 0.3 0.3' // red
|
||||||
4: '0.4 0.4 1.0' // blue
|
4: '0.4 0.4 1.0' // blue
|
||||||
@ -297,4 +444,15 @@ faceColors: Record<number, string> :=
|
|||||||
function colorBySides(n: number)
|
function colorBySides(n: number)
|
||||||
if n in faceColors
|
if n in faceColors
|
||||||
return faceColors[n]
|
return faceColors[n]
|
||||||
return '0.5 0.5 0.5' // gray
|
'0.5 0.5 0.5' // gray
|
||||||
|
|
||||||
|
function filtmap<T,U>(A: T[], m: (e:T, i: number, arr: T[]) => U)
|
||||||
|
A.map(m).filter (e) => !!e
|
||||||
|
|
||||||
|
function edgeIndices(P: Polyhedron)
|
||||||
|
sep := ",\n "
|
||||||
|
filtmap(P.face, (thisf) =>
|
||||||
|
filtmap(thisf, (v, ix, f) =>
|
||||||
|
preV := f æ (ix-1)
|
||||||
|
preV < v ? `${preV}, ${v}, -1` : '').join sep)
|
||||||
|
.join sep
|
||||||
|
@ -45,7 +45,7 @@ function makeBrowser(url: string, width: string, height: string)
|
|||||||
browser3D.baseURL = url
|
browser3D.baseURL = url
|
||||||
scene := await browser3D.createX3DFromString text
|
scene := await browser3D.createX3DFromString text
|
||||||
browser3D.replaceWorld scene
|
browser3D.replaceWorld scene
|
||||||
canvas
|
{canvas, browser3D}
|
||||||
|
|
||||||
// Put eye icons after all of the eligible links
|
// Put eye icons after all of the eligible links
|
||||||
links := $('a').filter -> knownExtensions.test @.getAttribute('href') ?? ''
|
links := $('a').filter -> knownExtensions.test @.getAttribute('href') ?? ''
|
||||||
@ -79,7 +79,7 @@ links.after ->
|
|||||||
overImg := floatLike and floatLike.tagName is 'IMG'
|
overImg := floatLike and floatLike.tagName is 'IMG'
|
||||||
width := overImg ? ($(floatLike).width() + 'px') : '150px'
|
width := overImg ? ($(floatLike).width() + 'px') : '150px'
|
||||||
height := overImg ? ($(floatLike).height() + 'px') : '150px'
|
height := overImg ? ($(floatLike).height() + 'px') : '150px'
|
||||||
canvas := await makeBrowser url, width, height
|
{canvas} := await makeBrowser url, width, height
|
||||||
if float
|
if float
|
||||||
canvas.style.float = float
|
canvas.style.float = float
|
||||||
if overImg
|
if overImg
|
||||||
@ -102,6 +102,9 @@ links.after ->
|
|||||||
$(eye).css 'text-decoration', 'none'
|
$(eye).css 'text-decoration', 'none'
|
||||||
$(eye.lastElementChild as Element).hide()
|
$(eye.lastElementChild as Element).hide()
|
||||||
|
|
||||||
|
let conwayBrowser: any
|
||||||
|
madeConway .= false
|
||||||
|
|
||||||
// See if we are on George Hart's Conway-notation generator page
|
// See if we are on George Hart's Conway-notation generator page
|
||||||
inputs := $('input[type="button"][value="Generate"][onclick="viewVRML()"]')
|
inputs := $('input[type="button"][value="Generate"][onclick="viewVRML()"]')
|
||||||
if inputs.length is 1
|
if inputs.length is 1
|
||||||
@ -114,12 +117,19 @@ if inputs.length is 1
|
|||||||
notation := $('input[name="notation"]').val()
|
notation := $('input[name="notation"]').val()
|
||||||
unless notation then return
|
unless notation then return
|
||||||
vrml := conway.generateVRML notation.toString()
|
vrml := conway.generateVRML notation.toString()
|
||||||
viewerSpan := $(`<span>${vrml}</span>`)
|
unless madeConway
|
||||||
viewerSpan.css 'float', 'left'
|
{canvas, browser3D} := await makeBrowser '', '250px', '250px'
|
||||||
$('form[name="input"]').first().before viewerSpan
|
conwayBrowser = browser3D
|
||||||
|
canvas.style.float = 'left'
|
||||||
|
canvas.style.marginRight = '1em'
|
||||||
|
$('form[name="input"]').first().before canvas
|
||||||
|
madeConway = true
|
||||||
|
scene := await conwayBrowser.createX3DFromString vrml
|
||||||
|
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()"]')
|
prisms := $('input[type="button"][value="View"][onclick="ViewVRML()"]')
|
||||||
if prisms.length is 1
|
if prisms.length is 1
|
||||||
// Seems so, fix the generator
|
// Seems so, fix the generator
|
||||||
console.log 'Need to fix the prism generator'
|
console.log 'Need to fix the prism generator'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user