feat: Get viewer for notation generator working

This commit is contained in:
Glen Whitney 2024-02-17 09:10:42 -08:00
parent 44158f5595
commit af2d9e02c7
2 changed files with 228 additions and 60 deletions

View File

@ -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

View File

@ -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'