feat: implement more commands needed for Alberts trisection

These include circleSlider, line cutoff, and bichord. Also
  makes the order of points in the chord command more true to
  Joyce. Represents more progress on #36. Next up is dealing with
  the pivot parameter.
This commit is contained in:
Glen Whitney 2023-10-10 20:05:29 -07:00
parent 5433719440
commit bb1713a674
4 changed files with 119 additions and 54 deletions

View File

@ -18,8 +18,12 @@
Alter execution of the translated applet: <br/>
<label for="showall">Show all applet entities, even hidden ones</label>
<input type="checkbox" id="showall"><br/>
<label for="showaux">Show geogebra auxiliaries (not in applet)</label>
<label for="showaux">Show GeoGebra auxiliaries (not in applet)</label>
<input type="checkbox" id="showaux">
<br/>
<br/>
<label for="algebra">Show the GeoGebra algebra pane</label>
<input type="checkbox" id="algebra">
<script src="options.js" type="module"></script>
</body>

View File

@ -64,7 +64,7 @@ document.addEventListener "DOMContentLoaded", async =>
use3d = true
break
codebase .= browser.runtime.getURL 'deps/GeoGebra/HTML5/5.0/'
codebase += use3d ? 'web3d' : 'webSimple'
codebase += (use3d or config.algebra) ? 'web3d' : 'webSimple'
adapParams: AdapParams := {codebase, config, joyceApplets }
apars := JSON.stringify(adapParams)
addScriptTag(browser.runtime.getURL 'deps/GeoGebra/deployggb.js').then =>

View File

@ -35,6 +35,20 @@ type Description
// with the otherName property:
type JoyceElements = Record<AnyName, Description>
adapptScript := findAdappt() as HTMLScriptElement
function findAdappt()
scripts := document.querySelectorAll 'script'
for scrip of scripts
src := scrip.getAttribute 'src'
if src and src.includes 'adapptlet'
return scrip
adapParams: AdapParams :=
typeof GGBApplet is 'undefined'
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
: JSON.parse(adapptScript.dataset.params ?? '') as AdapParams
function postApplets(jApplets: AppletDescription[], codebase = '')
for each jApp of jApplets
params := {
@ -48,7 +62,7 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
backgroundRGB := [255, 255, 255] as RGB
config3d := contains3d jApp.params
if config3d
worked := api.enable3D true
api.enable3D true
api.setPerspective 'T'
// Get rid of the xy-plane indicator
xml .= api.getXML()
@ -56,6 +70,8 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
api.setXML xml
else if codebase.includes 'web3d'
api.setPerspective 'G'
if adapParams.config?.algebra
api.setPerspective '+A'
for name, value in jApp.params
dispatchJcommand
api, name, value, elements, backgroundRGB, config3d
@ -78,22 +94,6 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
if codebase then geoApp.setHTML5Codebase codebase
geoApp.inject jApp.id
adapptScript := findAdappt() as HTMLScriptElement
function findAdappt()
scripts := document.querySelectorAll 'script'
for scrip of scripts
src := scrip.getAttribute 'src'
if src and src.includes 'adapptlet'
return scrip
console.log 'In script', document.currentScript, adapptScript
adapParams: AdapParams :=
typeof GGBApplet is 'undefined'
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
: JSON.parse(adapptScript.dataset.params ?? '') as AdapParams
// Always use the final joyceApplets if there are any:
if joyceApplets.length
adapParams.joyceApplets = joyceApplets
@ -412,12 +412,24 @@ function geoname(jname: JoyceName, elements: JoyceElements): GeoName
return .= 'Geo' + numCode.toString(36);
return += '1' while return.value in elements
// Helper for similar point/line functions:
function cutoffExtend(
method: string, pt: string[], point?: string[], line?: string[]
): [string, string]
direction .= `UnitVector(Vector(${pt[0]},${pt[1]}))`
if line and (not point or point[0] !== pt[0])
direction = `UnitVector(${line[0]})`
displacement := `Distance(${pt[2]}, ${pt[3]})*${direction}`
source := method is 'cutoff' ? pt[0] : pt[1]
[source, displacement]
// All of the detailed semantics of each available command lies in this
// function.
classHandler: Record<JoyceClass, ClassHandler> :=
point: (name, method, args, index, is3d): Commander =>
return := freshCommander()
{commands, callbacks, parts, auxiliaries} := return.value
zeroVector := is3d ? 'Vector((0,0,0))' : 'Vector((0,0))'
aux := name + 'aUx'
parts[0].push name
switch method
@ -440,22 +452,25 @@ classHandler: Record<JoyceClass, ClassHandler> :=
`${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})`
`${name} = Intersect(${destination}, Ray(${center}, ${aux}4))`
auxiliaries.push ...[2..4].map (i) => `${aux}${i}`
'extend'
sp := args.subpoints
unless sp then return
direction .= `UnitVector(Vector(${sp[0]},${sp[1]}))`
if args.line and (
not args.point or args.point[0] !== sp[0])
direction = `UnitVector(${args.line[0]})`
displacement := `Distance(${sp[2]}, ${sp[3]})*${direction}`
commands.push `${name} = Translate(${sp[1]}, ${displacement})`
'circleSlider'
unless args.circle then return
commands.push `${name} = Point(${args.circle[0]})`
if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) =>
api.setCoords name, ...args.scalar as XYZ
/cutoff|extend/
pt := args.subpoints
unless pt and pt.length is 4 then return
[source, displacement] :=
cutoffExtend method, pt, args.point, args.line
commands.push `${name} = Translate(${source}, ${displacement})`
'first'
unless args.subpoints then return
commands.push `${name} = ${args.subpoints[0]}`
commands.push `${name} = Point(${args.subpoints[0]},${zeroVector})`
/fixed|free/
coords := args.scalar
unless coords then return
commands.push `${name} = (${coords.join ','})`
commands.push `${name} = Point({${coords.join ','}})`
if method is 'fixed'
callbacks.push (api: AppletObject) => api.setFixed name, true
'foot'
@ -476,7 +491,8 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${name} = Intersect(${l1},${e2})`
'last'
unless args.subpoints then return
commands.push `${name} = ${args.subpoints.at(-1)}`
commands.push
`${name} = Point(${args.subpoints.at(-1)}, ${zeroVector})`
'lineSegmentSlider'
segment .= args.line?[0]
unless segment
@ -528,7 +544,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push
`${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})`
line: (name, method, args) =>
line: (name, method, args, index, is3d) =>
return := freshCommander()
return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value
@ -536,10 +552,62 @@ classHandler: Record<JoyceClass, ClassHandler> :=
parts[1].push name
madeSegment .= false
switch method
'bichord'
// To match Joyce, we need to get the ordering here correct.
// we want the order so that start -> end sweeping past the
// center of the other circle is counterclockwise in the first
// circle
cr := args.circle
unless cr return
commands.push ...[1..2].map (n) =>
`${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${n})`
inPlane := is3d ? `Plane(${cr[0]})` : ''
ctr := cr.map (c) => `Center(${c})`
condition := (`Angle(${aux}1,${ctr[0]},${ctr[1]}${inPlane})`
+ `< Angle(${aux}2,${ctr[0]},${ctr[1]}${inPlane})`)
commands.push
`${aux}3 = If(${condition}, ${aux}2, ${aux}1)`
`${aux}4 = If(${condition}, ${aux}1, ${aux}2)`
ends[0] = aux + 3
ends[1] = aux + 4
auxiliaries.push ...[1..4].map (n) => aux + n
'chord'
// To match Joyce, we need to get the ordering here correct.
// The complicated condition about distances is modeled after
// Joyce's code, but it boils down to: take the endpoint of the
// chord closest to the first subpoint, unless that first
// subpoint is essentially at the midpoint of the chord, in
// which case start with the endpoint nearest the second subpoint.
unless args.subpoints and args.circle then return
// We intersect with the whole line, not just the segment:
line := `Line(${args.subpoints.join ','})`
pA := args.subpoints[0]
pB := args.subpoints[1]
commands.push ...[1..2].map (n) =>
`${aux}${n} = Intersect(${args.circle}, ${line}, ${n})`
s := `Distance(${aux}1,${aux}2)`
d := `Distance(${aux}1,${pA}) - Distance(${aux}2,${pA})`
condition := (`If(${s}/10^9 < abs(${d}), ${d} > 0,`
+ `Distance(${aux}2,${pB}) < Distance(${aux}1,${pB}))`)
commands.push
`${aux}3 = If(${condition}, ${aux}2, ${aux}1)`
`${aux}4 = If(${condition}, ${aux}1, ${aux}2)`
ends[0] = aux + 3
ends[1] = aux + 4
auxiliaries.push ...[1..4].map (n) => aux + n
'connect'
unless args.subpoints and args.subpoints.length is 2 then return
ends[0] = args.subpoints[0]
ends[1] = args.subpoints[1]
/cutoff|extend/
pt := args.subpoints
unless pt and pt.length is 4 then return
[source, displacement] :=
cutoffExtend method, pt, args.point, args.line
ends[0] = source
commands.push `${aux} = Translate(${source}, ${displacement})`
auxiliaries.push aux
ends[1] = aux
'parallel'
unless args.subpoints then return
[newStart, oldStart, oldEnd] := args.subpoints
@ -555,24 +623,6 @@ classHandler: Record<JoyceClass, ClassHandler> :=
madeSegment = true
else
commands.push `${aux}2 = Translate(${oldEnd}, ${aux}1)`
'chord'
// To match Joyce, we need to get the ordering here correct.
// ends[0] should be the one closer to args.subpoints[0]
unless args.subpoints and args.circle then return
line := `Line(${args.subpoints.join ','})`
pt := args.subpoints[0]
commands.push ...[1..2].map (n) =>
`${aux}${n} = Intersect(${args.circle}, ${line}, ${n})`
condition := `Distance(${aux}2,${pt}) < Distance(${aux}1,${pt})`
// NOTE: Joyce's code has special case for when pt is almost
// at midpoint of chord; in that case, it starts at endpoint
// closer to the second subpoint... postponing that nicety
commands.push
`${aux}3 = If(${condition}, ${aux}2, ${aux}1)`
`${aux}4 = If(${condition}, ${aux}1, ${aux}2)`
ends[0] = aux + 3
ends[1] = aux + 4
auxiliaries.push ...[1..4].map (n) => aux + n
unless madeSegment
commands.push `${name} = Segment(${ends[0]},${ends[1]})`
callbacks.push (api: AppletObject) => api.setLabelVisible name, true
@ -585,9 +635,19 @@ classHandler: Record<JoyceClass, ClassHandler> :=
parts[1].push name
switch method
'radius'
unless args.subpoints then return
[center, point] := args.subpoints
commands.push `${name} = Circle(${center}, ${point})`
pt := args.subpoints
unless pt then return
inPlane := args.plane ? `, ${args.plane[0]}` : ''
switch pt.length
when 2
[center, point] := pt
commands.push
`${name} = Circle(${center}, ${point}${inPlane})`
when 3
center := pt[0]
radius := `Distance(${pt[1]}, ${pt[2]})`
commands.push
`${name} = Circle(${center}, ${radius}${inPlane})`
callbacks.push (api: AppletObject) => api.setLabelVisible name, true
polygon: (name, method, args, index) =>

View File

@ -1,4 +1,5 @@
export const flags = ['color', 'commands', 'showall', 'showaux'] as const
export const flags = [
'color', 'commands', 'showall', 'showaux', 'algebra'] as const
export type FlagType = (typeof flags)[number]
export type ConfigType = Partial<Record<FlagType, boolean>>