From bb1713a67499081aa755daf0c2f8367929f5312c Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Tue, 10 Oct 2023 20:05:29 -0700 Subject: [PATCH] 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. --- etc/options.html | 6 +- src/adapptext.civet | 2 +- src/adapptlet.civet | 162 +++++++++++++++++++++++++++++-------------- src/adapptypes.civet | 3 +- 4 files changed, 119 insertions(+), 54 deletions(-) diff --git a/etc/options.html b/etc/options.html index e618cf5..b512c0e 100644 --- a/etc/options.html +++ b/etc/options.html @@ -18,8 +18,12 @@ Alter execution of the translated applet:

- + +
+
+ + diff --git a/src/adapptext.civet b/src/adapptext.civet index 8b0be59..5138235 100644 --- a/src/adapptext.civet +++ b/src/adapptext.civet @@ -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 => diff --git a/src/adapptlet.civet b/src/adapptlet.civet index e049d75..a08a6a2 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -35,6 +35,20 @@ type Description // with the otherName property: type JoyceElements = Record +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 := 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 := `${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 := 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 := 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 := 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 := 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 := 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) => diff --git a/src/adapptypes.civet b/src/adapptypes.civet index 5abf456..72d28ea 100644 --- a/src/adapptypes.civet +++ b/src/adapptypes.civet @@ -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>