feat: joyce commands needed for BookEleven (#57)

Reviewed-on: #57
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2024-02-10 07:01:20 +00:00 committed by Glen Whitney
parent 9e69613425
commit 0b241e010e
2 changed files with 237 additions and 59 deletions

View File

@ -13,7 +13,8 @@ obs := new MutationObserver (mutationList) =>
newNode := (newGenericNode as HTMLElement) newNode := (newGenericNode as HTMLElement)
newParent := (change.target as HTMLElement) newParent := (change.target as HTMLElement)
unless newNode.tagName is 'APPLET' then continue unless newNode.tagName is 'APPLET' then continue
unless newNode.getAttribute('code') is 'Geometry' then continue code := newNode.getAttribute('code')
unless code is 'Geometry' or code is 'Geometry.class' then continue
id .= newParent.getAttribute 'id' id .= newParent.getAttribute 'id'
unless id unless id
id = 'joyceApplet' + joyceApplets.length id = 'joyceApplet' + joyceApplets.length

View File

@ -56,7 +56,7 @@ function vertFlipped(coords: number[], cdata: ConstructionData): XYZ
coords = coords.slice() coords = coords.slice()
if cdata.is3d if cdata.is3d
if coords[Z] then coords[Z] = -coords[Z] if coords[Z] then coords[Z] = -coords[Z]
else coords[Y] = cdata.height - coords[Y] coords[Y] = cdata.height - coords[Y]
return coords as XYZ return coords as XYZ
type ConstructionData type ConstructionData
@ -67,6 +67,7 @@ type ConstructionData
height: number height: number
elements: JoyceElements elements: JoyceElements
pivot: string pivot: string
title?: string
// Global data setup for pivoting (ugh, but necessary because the api // Global data setup for pivoting (ugh, but necessary because the api
// is not passed to the callback, so we have to look up the slider in // is not passed to the callback, so we have to look up the slider in
@ -128,11 +129,12 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
if is3d if is3d
depth .= jApp.width depth .= jApp.width
if jApp.height > depth then depth = jApp.height if jApp.height > depth then depth = jApp.height
depth /= 8
api.setCoordSystem api.setCoordSystem
-10, 10 + jApp.width, -10 + jApp.width/6, 10 + 4*jApp.width/6,
-10, 10 + jApp.height, -10 + jApp.height/6, 10 + 4*jApp.height/6,
-depth - 10, depth + 10, -2*depth - 10, depth + 10,
false true
api.setAxesVisible 3, false, false, false api.setAxesVisible 3, false, false, false
api.setGridVisible 3, false api.setGridVisible 3, false
else else
@ -185,7 +187,8 @@ type ClassHandler = (
method: string, method: string,
args: JoyceArguments, args: JoyceArguments,
index: number, index: number,
cdata: ConstructionData) => Commander cdata: ConstructionData,
colors: string[]) => Commander
type RGB = [number, number, number] type RGB = [number, number, number]
type XYZ = RGB type XYZ = RGB
@ -207,8 +210,8 @@ function dispatchJcommand(
'title' 'title'
if adapParams.config?.commands if adapParams.config?.commands
console.log 'Setting title to', value console.log 'Setting title to', value
corner := cdata.is3d ? 'Corner(-1,1)' : 'Corner(1,1)' cdata.title = value
api.evalCommand `TitlePoint = ${corner} api.evalCommand `TitlePoint = Corner(1,1)
Text("${value}", TitlePoint + (2,5))` Text("${value}", TitlePoint + (2,5))`
'pivot' 'pivot'
return // already handled in postApplets return // already handled in postApplets
@ -257,7 +260,7 @@ function jToG(
args: JoyceArguments := {} args: JoyceArguments := {}
usesCaptions := [] usesCaptions := []
for each jdep of data.split ',' for each jdep of data.split ','
scalar := parseFloat jdep scalar := Number jdep
if scalar is scalar // not NaN if scalar is scalar // not NaN
(args.scalar ?= []).push scalar (args.scalar ?= []).push scalar
continue continue
@ -271,7 +274,7 @@ function jToG(
(args.subpoints ?= []).push depGeo (args.subpoints ?= []).push depGeo
else if depKlass is 'line' else if depKlass is 'line'
(args.subpoints ?= []).push ...ends ?? [] (args.subpoints ?= []).push ...ends ?? []
cmdr = classHandler[klass] name, method, args, index, cdata cmdr = classHandler[klass] name, method, args, index, cdata, colors
unless name is jname then cmdr.callbacks.push (api: AppletObject) => unless name is jname then cmdr.callbacks.push (api: AppletObject) =>
api.setCaption name, jname api.setCaption name, jname
api.setLabelStyle name, 3 // style CAPTION = 3 api.setLabelStyle name, 3 // style CAPTION = 3
@ -312,23 +315,26 @@ function jToG(
// we can adjust components after setting overall color, etc. // we can adjust components after setting overall color, etc.
// Color the "Faces"; they default to 'brighter': // Color the "Faces"; they default to 'brighter':
if invisible colors[3] if invisible(colors[3]) and (klass !== 'sphere' or invisible colors[2])
for each face of parts[2] for each face of parts[2]
if face is name if face is name
console.log 'Fading out interior of', face if traceC console.log 'Fading out interior of', face if traceC
// hide the interior by making it transparent // hide the interior by making it transparent
api.setFilling face, 0 api.setFilling face, 0
else if face not in cdata.elements else if face not in cdata.elements
console.log 'Hiding face', face if traceC console.log 'Hiding face', face if traceC
api.setVisible face, false api.setVisible face, false
else else
faceRGB := joyce2rgb(colors[3] or 'brighter', cdata.bg) surface .= colors[3]
if klass is 'sphere' and invisible surface
surface = colors[2] // for Joyce, spheres had one circular "edge"
faceRGB := joyce2rgb(surface or 'brighter', cdata.bg)
deep := ['circle', 'polygon', 'sector'] deep := ['circle', 'polygon', 'sector']
filling := deep.includes(klass) ? 0.7 : 0.2 filling := deep.includes(klass) ? 0.7 : 0.2
for each face of parts[2] for each face of parts[2]
if traceC if traceC
console.log 'Coloring face', face, 'to', console.log 'Coloring face', face, 'to',
colors[3], '=', faceRGB surface, '=', faceRGB
api.setVisible face, true api.setVisible face, true
api.setFilling face, filling api.setFilling face, filling
api.setColor face, ...faceRGB api.setColor face, ...faceRGB
@ -485,7 +491,7 @@ function joyce2rgb(cname: string, backgroundRGB?: RGB): RGB
/yellow/i /yellow/i
[255,255,0] [255,255,0]
/random/i /random/i
colorsea.random().lighten(40).rgb() colorsea.random().lighten(30).rgb()
/background/i /background/i
bg bg
/brighter/i /brighter/i
@ -514,7 +520,7 @@ function pointDefaultColorName(
function geoname( function geoname(
jname: JoyceName, elements: JoyceElements, klass: JoyceClass): GeoName jname: JoyceName, elements: JoyceElements, klass: JoyceClass): GeoName
unless jname.substring(0,3) is 'Geo' // those might clash unless jname is 'floor' or jname.substring(0,3) is 'Geo' // those might clash
// Names with word characters starting with a capital are always good: // Names with word characters starting with a capital are always good:
if /^[A-Z]['\w]*$/.test jname then return jname if /^[A-Z]['\w]*$/.test jname then return jname
// If it's not a point, can start with any letter: // If it's not a point, can start with any letter:
@ -568,13 +574,30 @@ function proportionSimilar(
// All of the detailed semantics of each available command lies in this // All of the detailed semantics of each available command lies in this
// function. // function.
classHandler: Record<JoyceClass, ClassHandler> := classHandler: Record<JoyceClass, ClassHandler> :=
point: (name, method, args, index, cdata): Commander => point: (name, method, args, index, cdata, colors): Commander =>
return := freshCommander() return := freshCommander()
{commands, callbacks, parts, auxiliaries} := return.value {commands, callbacks, parts, auxiliaries} := return.value
zeroVector := cdata.is3d ? 'Vector((0,0,0))' : 'Vector((0,0))' zeroVector := cdata.is3d ? 'Vector((0,0,0))' : 'Vector((0,0))'
defaultPlane := cdata.is3d ? ', xOyPlane' : ''
aux := name + 'aUx' aux := name + 'aUx'
pivotable := cdata.pivot and name !== pivotData[cdata.pivot].pivot pivotable := cdata.pivot and name !== pivotData[cdata.pivot].pivot
parts[0].push name parts[0].push name
// HACK: Special-case corrections for Joyce Elements Bk XI
if cdata.title is 'XI.4'
if name is 'Z' and method is 'fixed' and args.scalar?.length is 3
method = 'perpendicular'
args.subpoints = ['E','P2','A']
args.plane = ['baseplane']
colors[0] = '0'
colors[1] = '0'
if name is 'F' and method is 'lineSlider' and args.subpoints?[0] is 'E'
args.scalar = [160,40,60]
if cdata.title is 'XI.5'
if name is 'A' and method is 'free'
method = 'perpendicular'
args.subpoints = ['B','P1','P3']
args.plane = ['xOyPlane']
commands.push 'B=(80,140)'
switch method switch method
/angle(?:Bisector|Divider)/ /angle(?:Bisector|Divider)/
{center, foot} := {center, foot} :=
@ -604,6 +627,12 @@ classHandler: Record<JoyceClass, ClassHandler> :=
if args.scalar and args.scalar.length if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) => callbacks.push (api: AppletObject) =>
api.setCoords name, ...vertFlipped(args.scalar or [], cdata) api.setCoords name, ...vertFlipped(args.scalar or [], cdata)
'circumcenter'
unless args.subpoints?.length is 3 then return
commands.push
`${aux} = Circle(${args.subpoints.join ','})`
`${name} = Center(${aux})`
auxiliaries.push aux
/cutoff|extend/ /cutoff|extend/
pt := args.subpoints pt := args.subpoints
unless pt and pt.length is 4 then return unless pt and pt.length is 4 then return
@ -612,9 +641,9 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${name} = Translate(${source}, ${displacement})` commands.push `${name} = Translate(${source}, ${displacement})`
'first' 'first'
unless args.subpoints then return unless args.subpoints then return
// HACK: Special-case correction for Joyce Elements Bk II, prop 14
index .= 0 index .= 0
if name === 'H' and args.line and args.line[0] === 'HH2' // HACK: Special-case correction for Joyce Elements Bk II, prop 14
if cdata.title is 'II.14' and name is 'H' and args.line?[0] is 'HH2'
index = 1 index = 1
commands.push `${name} = ${args.subpoints[index]}` commands.push `${name} = ${args.subpoints[index]}`
/fixed|free/ /fixed|free/
@ -638,9 +667,9 @@ classHandler: Record<JoyceClass, ClassHandler> :=
// Checking Joyce source, means intersection of lines, not // Checking Joyce source, means intersection of lines, not
// intersection of line segments // intersection of line segments
pt := args.subpoints pt := args.subpoints
unless pt then return unless pt and pt.length > 1 then return
l1 := `Line(${pt[0]},${pt[1]})` l1 := `Line(${pt[0]},${pt[1]})`
e2 := args.plane ? args.plane[0] : `Line(${pt[2]},${pt[3]})` e2 := pt.length < 3 ? args.plane?[0] : `Line(${pt[2]},${pt[3]})`
commands.push `${name} = Intersect(${l1},${e2})` commands.push `${name} = Intersect(${l1},${e2})`
'last' 'last'
unless args.subpoints then return unless args.subpoints then return
@ -682,15 +711,49 @@ classHandler: Record<JoyceClass, ClassHandler> :=
unless pt then return unless pt then return
commands.push `${name} = ${pt[0]} + ${pt[2]} - ${pt[1]}` commands.push `${name} = ${pt[0]} + ${pt[2]} - ${pt[1]}`
'perpendicular' 'perpendicular'
// Note only the two-point option implemented so far pt := args.subpoints
unless args.subpoints return unless pt return
[center, direction] := args.subpoints inPlane := args.plane ? `,${args.plane[0]}` : defaultPlane
commands.push `${name} = Rotate(${direction}, pi/2, ${center})` center := pt[0]
switch pt.length
when 2
commands.push
`${name} = Rotate(${pt[1]}, pi/2, ${center}${inPlane})`
when 3 // perpendicular **to** the plane
// Uses lots of auxiliaries
radius := `Distance(${pt[1]}, ${pt[2]})`
commands.push
`${aux}1 = Circle(${center}, ${radius}${inPlane})`
`${aux}2 = PointIn(${aux}1)`
`${aux}3 = PerpendicularLine(${center}${inPlane})`
`${aux}4 = Plane(${aux}2, ${aux}3)`
`${name} = Rotate(${aux}2, pi/2, ${center}, ${aux}4)`
auxiliaries.push aux+n for n of [1..4]
when 4
commands.push
`${aux}1 = Ray(${center}, Rotate(${pt[1]}, pi/2, ${center}${inPlane}))`
`${aux}2 = Circle(${center}, Distance(${pt[2]},${pt[3]})${inPlane})`
`${name} = Intersect(${aux}1, ${aux}2)`
auxiliaries.push aux+1, aux+2
'planeSlider'
pln := args.plane?[0]
unless pln then return
commands.push `${name} = PointIn(${pln})`
if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) =>
api.setCoords name, ...vertFlipped(args.scalar or [], cdata)
/proportion|similar/ /proportion|similar/
[source, displacement] := [source, displacement] :=
proportionSimilar method, args, cdata, aux, commands, auxiliaries proportionSimilar method, args, cdata, aux, commands, auxiliaries
unless source then return unless source then return
commands.push `${name} = Translate(${source}, ${displacement})` commands.push `${name} = Translate(${source}, ${displacement})`
'sphereSlider'
sph := args.sphere?[0]
unless sph then return
commands.push `${name} = PointIn(${sph})`
if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) =>
api.setCoords name, ...vertFlipped(args.scalar or [], cdata)
'vertex' 'vertex'
commands.push commands.push
`${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})` `${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})`
@ -731,7 +794,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
`${aux}4 = If(${condition}, ${aux}2, ${aux}1)` `${aux}4 = If(${condition}, ${aux}2, ${aux}1)`
ends[0] = aux + 3 ends[0] = aux + 3
ends[1] = aux + 4 ends[1] = aux + 4
auxiliaries.push ...[1..4].map (n) => aux + n auxiliaries.push aux+n for n of [1..4]
'chord' 'chord'
// To match Joyce, we need to get the ordering here correct. // To match Joyce, we need to get the ordering here correct.
// The complicated condition about distances is modeled after // The complicated condition about distances is modeled after
@ -755,7 +818,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
`${aux}4 = If(${condition}, ${aux}1, ${aux}2)` `${aux}4 = If(${condition}, ${aux}1, ${aux}2)`
ends[0] = aux + 3 ends[0] = aux + 3
ends[1] = aux + 4 ends[1] = aux + 4
auxiliaries.push ...[1..4].map (n) => aux + n auxiliaries.push aux+n for n of [1..4]
'connect' 'connect'
unless args.subpoints and args.subpoints.length is 2 then return unless args.subpoints and args.subpoints.length is 2 then return
ends[0] = args.subpoints[0] ends[0] = args.subpoints[0]
@ -810,7 +873,17 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push commands.push
`${aux} = Rotate(${pt[1]}, pi/2, ${pt[0]}${inPlane})` `${aux} = Rotate(${pt[1]}, pi/2, ${pt[0]}${inPlane})`
when 3 when 3
return // TODO: line perpendicular to plane radius := `Distance(${pt[1]}, ${pt[2]})`
commands.push
`${aux}1 = PerpendicularLine(${pt[1]}${inPlane})`
`${aux}2 = Intersect(${aux}1${inPlane})`
`${aux}3 = Circle(${aux}2, ${radius}${inPlane})`
`${aux}4 = PointIn(${aux}3)`
`${aux}5 = Plane(${aux}4, ${aux}1)`
`${aux}6 = Rotate(${aux}4, pi/2, ${aux}2, ${aux}5)`
ends[0] = aux + 2
ends[1] = aux + 6
auxiliaries.push aux+n for n of [1..6]
when 4 when 4
ends[0] = pt[0] ends[0] = pt[0]
ends[1] = aux + 2 ends[1] = aux + 2
@ -836,22 +909,27 @@ classHandler: Record<JoyceClass, ClassHandler> :=
callbacks.push (api: AppletObject) => api.setLabelVisible name, true callbacks.push (api: AppletObject) => api.setLabelVisible name, true
parts[0].push ...ends parts[0].push ...ends
circle: (name, method, args) => circle: (name, method, args, index, cdata) =>
return := freshCommander() return := freshCommander()
return.value.ends = ['', ''] return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value {commands, callbacks, parts, auxiliaries, ends} := return.value
aux := name + 'aUx' aux := name + 'aUx'
parts[1].push name parts[1].push name
circle .= '' circle .= ''
defaultPlane := cdata.is3d ? ', xOyPlane' : ''
switch method switch method
'circumcircle' 'circumcircle'
pt := args.subpoints pt := args.subpoints
unless pt and pt.length is 3 then return unless pt and pt.length is 3 then return
circle = `Circle(${pt.join ','})` circle = `Circle(${pt.join ','})`
'intersection'
sph := args.sphere
unless sph and sph.length is 2 then return
circle = `IntersectConic(${sph[0]}, ${sph[1]})`
'radius' 'radius'
pt := args.subpoints pt := args.subpoints
unless pt then return unless pt then return
inPlane := args.plane ? `, ${args.plane[0]}` : '' inPlane := args.plane ? `, ${args.plane[0]}` : defaultPlane
switch pt.length switch pt.length
when 2 when 2
[center, point] := pt [center, point] := pt
@ -931,7 +1009,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
`${aux}1 = Angle(${pt[3]},${pt[2]},${pt[4]}${inSourcePlane})` `${aux}1 = Angle(${pt[3]},${pt[2]},${pt[4]}${inSourcePlane})`
`${aux}2 = Rotate(${pt[1]},${aux}1,${pt[0]}${inDestPlane})` `${aux}2 = Rotate(${pt[1]},${aux}1,${pt[0]}${inDestPlane})`
`${aux}3 = ${pt[0]} + (${aux}2 - ${pt[0]})*${factor}` `${aux}3 = ${pt[0]} + (${aux}2 - ${pt[0]})*${factor}`
auxiliaries.push ...[1..3].map (n) => aux + n auxiliaries.push aux+n for n of [1..3]
pt = [pt[0], pt[1], aux+3] pt = [pt[0], pt[1], aux+3]
else else
commands.push '' commands.push ''
@ -949,12 +1027,14 @@ classHandler: Record<JoyceClass, ClassHandler> :=
api.renameObject obj, newObj api.renameObject obj, newObj
moreParts[1].push newObj moreParts[1].push newObj
sector: (name, method, args, index) => sector: (name, method, args, index, cdata) =>
return := freshCommander() return := freshCommander()
return.value.ends = ['', ''] return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value {commands, callbacks, parts, auxiliaries, ends} := return.value
aux := name + 'aUx' aux := name + 'aUx'
parts[2].push name parts[2].push name
defaultPlane := cdata.is3d ? ', xOyPlane' : ''
inPlane .= args.plane ? `, ${args.plane[0]}` : defaultPlane
switch method switch method
/arc|sector/ /arc|sector/
unless args.subpoints?.length is 3 return unless args.subpoints?.length is 3 return
@ -968,12 +1048,14 @@ classHandler: Record<JoyceClass, ClassHandler> :=
center = temp center = temp
parms = start + ', ' + center + ', ' + end parms = start + ', ' + center + ', ' + end
prefix = 'Circumcircular' prefix = 'Circumcircular'
inPlane = '' // not needed in 3-point case
ends[0] = start ends[0] = start
ends[1] = end ends[1] = end
commands.push commands.push
`${name} = ${prefix}Sector(${parms})` `${name} = ${prefix}Sector(${parms}${inPlane})`
`${aux}1 = ${prefix}Arc(${parms})` `${aux}1 = ${prefix}Arc(${parms}${inPlane})`
parts[1].push aux + 1 parts[1].push aux + 1
auxiliaries.push aux + 1
makeLinesInvisible callbacks, name makeLinesInvisible callbacks, name
plane: (name, method, args) => plane: (name, method, args) =>
@ -984,38 +1066,133 @@ classHandler: Record<JoyceClass, ClassHandler> :=
'3points' '3points'
unless args.subpoints?.length is 3 then return unless args.subpoints?.length is 3 then return
commands.push `${name} = Plane(${args.subpoints.join ','})` commands.push `${name} = Plane(${args.subpoints.join ','})`
'parallel'
unless args.subpoints?.length is 1 then return
unless args.plane?.length is 1 then return
commands.push `${name} = Plane(${args.subpoints[0]}, ${args.plane[0]})`
'perpendicular' 'perpendicular'
unless args.subpoints?.length is 2 then return unless args.subpoints?.length is 2 then return
[thru, perp] := args.subpoints [thru, perp] := args.subpoints
commands.push commands.push
`${name} = PerpendicularPlane(${thru}, Line(${thru}, ${perp}))` `${name} = PerpendicularPlane(${thru}, Line(${thru}, ${perp}))`
sphere: (name, method, args) => freshCommander() sphere: (name, method, args) =>
polyhedron: (name, method, args, index) =>
return := freshCommander() return := freshCommander()
return.value.ends = ['', ''] return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value {commands, callbacks, parts, auxiliaries, ends} := return.value
aux := name + 'aUx' parts[2].push name
pt := args.subpoints
if method is 'radius' and pt
switch pt.length
when 2
[center, point] := pt
ends[0] = center
commands.push `${name} = Sphere(${center}, ${point})`
when 3
center := pt[0]
ends[0] = center
radius := `Distance(${pt[1]}, ${pt[2]})`
commands.push `${name} = Sphere(${center}, ${radius})`
polyhedron: (name, method, args, index, cdata) =>
return := freshCommander()
return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value
aux := geoname name + 'aUx', cdata.elements, 'point'
switch method switch method
'tetrahedron' 'parallelepiped'
pt := args.subpoints pt .= args.subpoints
unless pt and pt.length is 4 then return unless pt and pt.length is 4 then return
commands.push '' // hack, make sure there is a command // create all of the vertices we will need:
commands.push
`${aux}4 = ${pt[2]} + ${pt[1]} - ${pt[0]}`
`${aux}5 = ${pt[3]} + ${pt[1]} - ${pt[0]}`
`${aux}6 = ${pt[3]} + ${pt[2]} - ${pt[0]}`
`${aux}7 = ${pt[3]} + ${pt[2]} + ${pt[1]} - 2*${pt[0]}`
auxiliaries.push aux+i for i of [4..7]
pt = [...pt, ...auxiliaries]
parts[0].push ...pt parts[0].push ...pt
ends[0] = aux + 1 generalRecipe :=
ends[1] = pt[3] A: [0,1,4,2]
B: [0,1,5,3]
C: [0,2,6,3]
D: [3,5,7,6]
E: [2,4,7,6]
F: [1,4,7,5]
letters := ['A'..'F'] as const
recipe: Record<string, string[]> := {}
for ltr of letters
auxlet := aux + ltr
auxiliaries.push auxlet
parts[2].push auxlet
recipe[auxlet] = (pt[i] for each i of generalRecipe[ltr])
ends[0] = aux + 'A'
ends[1] = aux + 'D'
callbacks.push (api: AppletObject, moreParts: DimParts) => callbacks.push (api: AppletObject, moreParts: DimParts) =>
madeBase := api.evalCommandGetLabels ix .= 0
`${ends[0]} = Polygon(${pt[0]},${pt[1]},${pt[2]})` for piece in recipe
if not madeBase return madeIt := api.evalCommandGetLabels
for each obj of madeBase.split ',' `${piece} = Polygon(${recipe[piece].join ','})`
if obj is ends[0] continue if not madeIt return
for each obj of madeIt.split ','
if obj is piece continue
newObj := 'GeoAux' + index + obj + ix
api.renameObject obj, newObj
moreParts[1].push newObj
ix += 1
'prism'
unless args.polygon?.length is 1 then return
base := args.polygon[0]
ends[0] = base
pt := args.subpoints
unless pt and pt.length is 2 then return
commands.push
`${aux}1 = Vertex(${base},1) + ${pt[1]} - ${pt[0]}`
auxiliaries.push aux+1
ends[1] = aux+1
parts[0].push aux+1
parts[2].push ends[0]
callbacks.push (api: AppletObject, moreParts: DimParts) =>
made := api.evalCommandGetLabels
`${name} = Prism(${base}, ${aux}1)`
if not made return
for each obj of made.split ','
if obj is name continue
newObj := 'GeoAux' + index + obj newObj := 'GeoAux' + index + obj
api.renameObject obj, newObj api.renameObject obj, newObj
moreParts[1].push newObj switch api.getObjectType newObj
'point'
moreParts[0].push newObj
'segment'
moreParts[1].push newObj
else
moreParts[2].push newObj
/pyramid|tetrahedron/
base .= args.polygon?[0]
ends[0] = base or aux + 1
pt := args.subpoints
unless pt and pt.length > 0 then return
// A tetrahedron is just a pyramid where we have to build the
// base from three points ourselves. But it has to be done in the
// callback, since we have to capture the edges.
if method is 'tetrahedron' and pt.length !== 4 then return
commands.push '' // hack, make sure there is a command
parts[0].push ...pt
parts[2].push ends[0]
ends[1] = pt.at(-1) or ''
callbacks.push (api: AppletObject, moreParts: DimParts) =>
if not base
madeBase := api.evalCommandGetLabels
`${ends[0]} = Polygon(${pt[0]},${pt[1]},${pt[2]})`
if not madeBase return
for each obj of madeBase.split ','
if obj is ends[0] continue
newObj := 'GeoAux' + index + obj
api.renameObject obj, newObj
moreParts[1].push newObj
base = ends[0]
made := api.evalCommandGetLabels made := api.evalCommandGetLabels
`${name} = Pyramid(${aux}1, ${pt[3]})` `${name} = Pyramid(${base}, ${ends[1]})`
if not made return if not made return
for each obj of made.split ',' for each obj of made.split ','
if obj is name continue if obj is name continue
@ -1051,7 +1228,7 @@ function makeAngDiv(
`${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})` `${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})`
`${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)` `${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)`
`${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})` `${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})`
auxiliaries.push ...[2..4].map (i) => `${aux}${i}` auxiliaries.push aux+i for i of [2..4]
return {center, foot: `Intersect(${destination}, Ray(${center}, ${aux}4))`} return {center, foot: `Intersect(${destination}, Ray(${center}, ${aux}4))`}
// helper for separating color of perimeter and interior: // helper for separating color of perimeter and interior: