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/> Alter execution of the translated applet: <br/>
<label for="showall">Show all applet entities, even hidden ones</label> <label for="showall">Show all applet entities, even hidden ones</label>
<input type="checkbox" id="showall"><br/> <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"> <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> <script src="options.js" type="module"></script>
</body> </body>

View File

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

View File

@ -35,6 +35,20 @@ type Description
// with the otherName property: // with the otherName property:
type JoyceElements = Record<AnyName, Description> 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 = '') function postApplets(jApplets: AppletDescription[], codebase = '')
for each jApp of jApplets for each jApp of jApplets
params := { params := {
@ -48,7 +62,7 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
backgroundRGB := [255, 255, 255] as RGB backgroundRGB := [255, 255, 255] as RGB
config3d := contains3d jApp.params config3d := contains3d jApp.params
if config3d if config3d
worked := api.enable3D true api.enable3D true
api.setPerspective 'T' api.setPerspective 'T'
// Get rid of the xy-plane indicator // Get rid of the xy-plane indicator
xml .= api.getXML() xml .= api.getXML()
@ -56,6 +70,8 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
api.setXML xml api.setXML xml
else if codebase.includes 'web3d' else if codebase.includes 'web3d'
api.setPerspective 'G' api.setPerspective 'G'
if adapParams.config?.algebra
api.setPerspective '+A'
for name, value in jApp.params for name, value in jApp.params
dispatchJcommand dispatchJcommand
api, name, value, elements, backgroundRGB, config3d api, name, value, elements, backgroundRGB, config3d
@ -78,22 +94,6 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
if codebase then geoApp.setHTML5Codebase codebase if codebase then geoApp.setHTML5Codebase codebase
geoApp.inject jApp.id 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: // Always use the final joyceApplets if there are any:
if joyceApplets.length if joyceApplets.length
adapParams.joyceApplets = joyceApplets adapParams.joyceApplets = joyceApplets
@ -412,12 +412,24 @@ function geoname(jname: JoyceName, elements: JoyceElements): GeoName
return .= 'Geo' + numCode.toString(36); return .= 'Geo' + numCode.toString(36);
return += '1' while return.value in elements 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 // 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, is3d): Commander => point: (name, method, args, index, is3d): Commander =>
return := freshCommander() return := freshCommander()
{commands, callbacks, parts, auxiliaries} := return.value {commands, callbacks, parts, auxiliaries} := return.value
zeroVector := is3d ? 'Vector((0,0,0))' : 'Vector((0,0))'
aux := name + 'aUx' aux := name + 'aUx'
parts[0].push name parts[0].push name
switch method switch method
@ -440,22 +452,25 @@ classHandler: Record<JoyceClass, ClassHandler> :=
`${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})` `${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})`
`${name} = Intersect(${destination}, Ray(${center}, ${aux}4))` `${name} = Intersect(${destination}, Ray(${center}, ${aux}4))`
auxiliaries.push ...[2..4].map (i) => `${aux}${i}` auxiliaries.push ...[2..4].map (i) => `${aux}${i}`
'extend' 'circleSlider'
sp := args.subpoints unless args.circle then return
unless sp then return commands.push `${name} = Point(${args.circle[0]})`
direction .= `UnitVector(Vector(${sp[0]},${sp[1]}))` if args.scalar and args.scalar.length
if args.line and ( callbacks.push (api: AppletObject) =>
not args.point or args.point[0] !== sp[0]) api.setCoords name, ...args.scalar as XYZ
direction = `UnitVector(${args.line[0]})` /cutoff|extend/
displacement := `Distance(${sp[2]}, ${sp[3]})*${direction}` pt := args.subpoints
commands.push `${name} = Translate(${sp[1]}, ${displacement})` 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' 'first'
unless args.subpoints then return unless args.subpoints then return
commands.push `${name} = ${args.subpoints[0]}` commands.push `${name} = Point(${args.subpoints[0]},${zeroVector})`
/fixed|free/ /fixed|free/
coords := args.scalar coords := args.scalar
unless coords then return unless coords then return
commands.push `${name} = (${coords.join ','})` commands.push `${name} = Point({${coords.join ','}})`
if method is 'fixed' if method is 'fixed'
callbacks.push (api: AppletObject) => api.setFixed name, true callbacks.push (api: AppletObject) => api.setFixed name, true
'foot' 'foot'
@ -476,7 +491,8 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${name} = Intersect(${l1},${e2})` commands.push `${name} = Intersect(${l1},${e2})`
'last' 'last'
unless args.subpoints then return unless args.subpoints then return
commands.push `${name} = ${args.subpoints.at(-1)}` commands.push
`${name} = Point(${args.subpoints.at(-1)}, ${zeroVector})`
'lineSegmentSlider' 'lineSegmentSlider'
segment .= args.line?[0] segment .= args.line?[0]
unless segment unless segment
@ -528,7 +544,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push commands.push
`${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})` `${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})`
line: (name, method, args) => line: (name, method, args, index, is3d) =>
return := freshCommander() return := freshCommander()
return.value.ends = ['', ''] return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value {commands, callbacks, parts, auxiliaries, ends} := return.value
@ -536,10 +552,62 @@ classHandler: Record<JoyceClass, ClassHandler> :=
parts[1].push name parts[1].push name
madeSegment .= false madeSegment .= false
switch method 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' '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]
ends[1] = args.subpoints[1] 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' 'parallel'
unless args.subpoints then return unless args.subpoints then return
[newStart, oldStart, oldEnd] := args.subpoints [newStart, oldStart, oldEnd] := args.subpoints
@ -555,24 +623,6 @@ classHandler: Record<JoyceClass, ClassHandler> :=
madeSegment = true madeSegment = true
else else
commands.push `${aux}2 = Translate(${oldEnd}, ${aux}1)` 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 unless madeSegment
commands.push `${name} = Segment(${ends[0]},${ends[1]})` commands.push `${name} = Segment(${ends[0]},${ends[1]})`
callbacks.push (api: AppletObject) => api.setLabelVisible name, true callbacks.push (api: AppletObject) => api.setLabelVisible name, true
@ -585,9 +635,19 @@ classHandler: Record<JoyceClass, ClassHandler> :=
parts[1].push name parts[1].push name
switch method switch method
'radius' 'radius'
unless args.subpoints then return pt := args.subpoints
[center, point] := args.subpoints unless pt then return
commands.push `${name} = Circle(${center}, ${point})` 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 callbacks.push (api: AppletObject) => api.setLabelVisible name, true
polygon: (name, method, args, index) => 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 FlagType = (typeof flags)[number]
export type ConfigType = Partial<Record<FlagType, boolean>> export type ConfigType = Partial<Record<FlagType, boolean>>