From 5433719440d8d9392c62a55162e871026fb90ec4 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 9 Oct 2023 23:23:10 -0700 Subject: [PATCH 1/4] feat: Implement 'similar' point method for Steve Gray problem --- src/adapptlet.civet | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/adapptlet.civet b/src/adapptlet.civet index 010e0fd..e049d75 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -500,9 +500,26 @@ classHandler: Record := [center, direction] := args.subpoints // Note clockwise 90° rotation (3π/2) confirmed in Joyce source commands.push `${name} = Rotate(${direction}, 3*pi/2, ${center})` - 'proportion' - pt := args.subpoints + /proportion|similar/ + pt .= args.subpoints unless pt then return + // reduce the similar case to general proportion + if method is 'similar' + unless pt.length is 5 then return + sourcePlane .= '' + destPlane .= '' + if is3d + unless args.plane then return + destPlane = `, ${args.plane[0]}` + if args.plane.length > 1 + sourcePlane = `, ${args.plane[1]}` + else + sourcePlane = `, Plane(${pt[2]}, ${pt[3]}, ${pt[4]})` + angle := `Angle(${pt[3]}, ${pt[2]}, ${pt[4]}${sourcePlane})` + commands.push + `${aux} = Rotate(${pt[1]}, ${angle}, ${pt[0]}${destPlane})` + auxiliaries.push aux + pt = [pt[2], pt[3], pt[2], pt[4], pt[0], pt[1], pt[0], aux] len := `Distance(${pt[2]},${pt[3]})*Distance(${pt[4]},${pt[5]})` + `/ Distance(${pt[0]},${pt[1]})` direction := `UnitVector(Vector(${pt[6]}, ${pt[7]}))` -- 2.34.1 From bb1713a67499081aa755daf0c2f8367929f5312c Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Tue, 10 Oct 2023 20:05:29 -0700 Subject: [PATCH 2/4] 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> -- 2.34.1 From 85ad82d9e25984432d1b8d03e1d3dde11e570732 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 13 Oct 2023 10:17:16 -0700 Subject: [PATCH 3/4] feat: handle the 'pivot' parameter of the Geometry Applet Dealing with general click and drag on the applet and differentiating between a background drag and dragging an element seemed like too big a task, so this PR simply provides a slider to rotate the diagram when the pivot is defined. Implementing this required storing much more construction data, and also dealing head-on with GeoGebra's shall we say "strange" choice where the value of an expression depends on what name it is assigned to... The resolution of this last bit was to use different GeoGebra names for Geometry Applet points that start with something other than an uppercase Roman letter. --- etc/deps/geotypes/api.ts | 2 +- src/adapptlet.civet | 216 ++++++++++++++++++++++++++++----------- 2 files changed, 155 insertions(+), 63 deletions(-) diff --git a/etc/deps/geotypes/api.ts b/etc/deps/geotypes/api.ts index 859b5c7..a0d6917 100644 --- a/etc/deps/geotypes/api.ts +++ b/etc/deps/geotypes/api.ts @@ -101,7 +101,7 @@ export interface AppletObject { getXcoord(objName: string): number; getYcoord(objName: string): number; getZcoord(objName: string): number; - setCoords(objName: string, x: number, y: number, z: number): void; + setCoords(objName: string, x: number, y: number, z?: number): void; getValue(objName: string): number; getVersion(): string; getScreenshotBase64(callback: (data: string) => void, scale?: number): void; diff --git a/src/adapptlet.civet b/src/adapptlet.civet index a08a6a2..2542a4b 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -49,6 +49,28 @@ adapParams: AdapParams := ? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []} : JSON.parse(adapptScript.dataset.params ?? '') as AdapParams +type ConstructionData + id: string + bg: RGB + is3d: boolean + width: number + height: number + elements: JoyceElements + pivot: string + +// 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 +// a global list): + +type PivotData + api: AppletObject + pivot: string + lastAngle: number + rotatable: string[] + fixed: Record + +pivotData: Record := {} + function postApplets(jApplets: AppletDescription[], codebase = '') for each jApp of jApplets params := { @@ -58,10 +80,8 @@ function postApplets(jApplets: AppletDescription[], codebase = '') jApp.width, jApp.height, appletOnLoad: (api: AppletObject) => - elements: JoyceElements := {} - backgroundRGB := [255, 255, 255] as RGB - config3d := contains3d jApp.params - if config3d + is3d := contains3d jApp.params + if is3d api.enable3D true api.setPerspective 'T' // Get rid of the xy-plane indicator @@ -72,10 +92,24 @@ function postApplets(jApplets: AppletDescription[], codebase = '') api.setPerspective 'G' if adapParams.config?.algebra api.setPerspective '+A' + elements := {} + pivot .= '' + if 'pivot' in jApp.params + if is3d + console.warn('Geometry Applet "pivot" only supported for ' + + '2D constrcutions. Ignoring.') + else + pivot = geoname(jApp.id, elements, 'point') + 'Pivot' + pivotData[pivot] = { + api, jApp.params.pivot, + lastAngle: 0, rotatable: [], fixed: {}} + cdata: ConstructionData := { + bg: ([255, 255, 255] as RGB), + is3d, jApp.id, jApp.width, jApp.height, elements, pivot} for name, value in jApp.params dispatchJcommand - api, name, value, elements, backgroundRGB, config3d - if config3d + api, name, value, cdata + if is3d depth .= jApp.width if jApp.height > depth then depth = jApp.height api.setCoordSystem @@ -135,7 +169,7 @@ type ClassHandler = ( method: string, args: JoyceArguments, index: number, - is3d: boolean) => Commander + cdata: ConstructionData) => Commander type RGB = [number, number, number] type XYZ = RGB @@ -146,28 +180,26 @@ function dispatchJcommand( api: AppletObject, name: string, value: string, - elements: JoyceElements - backgroundRGB: RGB, - is3d: boolean): void + cdata: ConstructionData): void switch name 'background' - newback := joyce2rgb value, backgroundRGB + cdata.bg = joyce2rgb value, cdata.bg if adapParams.config?.commands console.log 'Setting background to', value, 'interpreted as', - newback - for i of [0..2] - backgroundRGB[i] = newback[i] - api.setGraphicsOptions 1, bgColor: colorsea(backgroundRGB).hex() + cdata.bg + api.setGraphicsOptions 1, bgColor: colorsea(cdata.bg).hex() 'title' if adapParams.config?.commands console.log 'Setting title to', value - corner := is3d ? 'Corner(-1,1)' : 'Corner(1,1)' + corner := cdata.is3d ? 'Corner(-1,1)' : 'Corner(1,1)' api.evalCommand `TitlePoint = ${corner} Text("${value}", TitlePoint + (2,5))` + 'pivot' + return // already handled in postApplets /e\[\d+\]/ num := parseInt(name.slice(2)) {commands, callbacks, parts} := - jToG value, elements, num, backgroundRGB, is3d + jToG value, num, cdata if commands.length lastTried .= 0 if commands.filter((&)).every (cmd) => @@ -180,16 +212,14 @@ function dispatchJcommand( (part of translation of '${value}') failed.` else console.warn `Could not parse command '${value}'` - else console.warn `Unkown param ${name} = ${value}` + else console.warn `Unknown param ${name} = ${value}` // Parses a Joyce element-creating command, extending the elements // by side effect: function jToG( jCom: string, - elements: JoyceElements, index: number, - backgroundRGB: RGB - is3d: boolean): Commander + cdata: ConstructionData): Commander [jname, klass, method, data, ...colors] := jCom.split ';' if adapParams.config?.commands console.log 'Defining', jname, 'as a', klass, 'constructed by', @@ -199,7 +229,9 @@ function jToG( console.warn `Unknown entity class ${klass}` return cmdr assertJoyceClass klass // shouldn't need to do that :-/ - name := if /^\p{L}\w*$/u.test jname then jname else geoname jname, elements + isPivot := !!cdata.pivot and jname is pivotData[cdata.pivot].pivot + name := geoname jname, cdata.elements, klass + if isPivot then pivotData[cdata.pivot].pivot = name args: JoyceArguments := {} usesCaptions := [] for each jdep of data.split ',' @@ -207,17 +239,17 @@ function jToG( if scalar is scalar // not NaN (args.scalar ?= []).push scalar continue - unless jdep in elements + unless jdep in cdata.elements console.warn `Reference to unknown geometric entity ${jdep} in $jCom}` return cmdr usesCaptions.push jdep - {klass: depKlass, otherName: depGeo, ends} := elements[jdep] + {klass: depKlass, otherName: depGeo, ends} := cdata.elements[jdep] (args[depKlass] ?= []).push depGeo if depKlass is 'point' (args.subpoints ?= []).push depGeo else if depKlass is 'line' (args.subpoints ?= []).push ...ends ?? [] - cmdr = classHandler[klass] name, method, args, index, is3d + cmdr = classHandler[klass] name, method, args, index, cdata unless name is jname then cmdr.callbacks.push (api: AppletObject) => api.setCaption name, jname api.setLabelStyle name, 3 // style CAPTION = 3 @@ -226,6 +258,25 @@ function jToG( for each aux of cmdr.auxiliaries api.setAuxiliary aux, true api.setVisible aux,false + + // set up the pivot if there is one + if isPivot + unless klass is 'point' + console.warn(`Can only pivot around a point, not the ${klass}` + + `named ${jname}. Ignoring.`) + cdata.pivot = '' + cmdr.commands.push(`${cdata.pivot} = ` + + `Slider(0°,360°,1°,1,${cdata.width/3},true,true,false,false)`) + cmdr.callbacks.push (api: AppletObject) => + api.setCaption cdata.pivot, 'Rotate Display' + api.setLabelStyle cdata.pivot, 3 + api.setCoords(cdata.pivot, 2*cdata.width/3, cdata.height-10) + // Not sure how to let TypeScript deal with putting a new function + // on the global window object, so punting at least for now: + // @ts-ignore + window.pivotListener = pivotListener + api.registerObjectUpdateListener(cdata.pivot, 'pivotListener') + // Create callback to assign colors if colors.length is 4 and colors.every (color) => invisible color cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false @@ -243,11 +294,11 @@ function jToG( console.log 'Fading out interior of', face if trace // hide the interior by making it transparent api.setFilling face, 0 - else if face not in elements + else if face not in cdata.elements console.log 'Hiding face', face if trace api.setVisible face, false else - faceRGB := joyce2rgb(colors[3] or 'brighter', backgroundRGB) + faceRGB := joyce2rgb(colors[3] or 'brighter', cdata.bg) deep := ['circle', 'polygon', 'sector'] filling := deep.includes(klass) ? 0.7 : 0.2 for each face of parts[2] @@ -259,28 +310,30 @@ function jToG( // Lines default to black: if invisible colors[2] for each line of parts[1] - if line is name or line not in elements + if line is name or line not in cdata.elements console.log 'Hiding line', line if trace api.setVisible line, false else - lineRGB := joyce2rgb(colors[2] or 'black', backgroundRGB) + lineRGB := joyce2rgb(colors[2] or 'black', cdata.bg) for each line of parts[1] console.log 'Coloring line', line, 'to', colors[2] if trace api.setVisible line, true api.setColor line, ...lineRGB // Now color the points: - console.log 'Considering point colors for', name if trace + if trace + console.log + 'Considering point colors for', name, 'of dimension', dimension if invisible colors[1] // Hide all the dim-0 elements that are not distinct independent // items: for each point of parts[0] - if point is name or point not in elements + if point is name or point not in cdata.elements console.log 'Hiding point', point if trace api.setVisible point, false else if dimension is 0 or colors[1] // Need to color the points - ptRGB := colors[1] ? joyce2rgb colors[1], backgroundRGB - : pointDefaultRGB name, method + ptRGB := colors[1] ? joyce2rgb colors[1], cdata.bg + : pointDefaultRGB name, method, isPivot for each point of parts[0] console.log 'Coloring point', point, 'to', colors[1] if trace api.setVisible point, true @@ -290,12 +343,12 @@ function jToG( if invisible colors[0] console.log 'Hiding label', name if trace api.setLabelVisible name, false - else if colors[dimension] and colors[dimension] is not colors[0] + else if colors[dimension+1] and colors[dimension+1] is not colors[0] // Have to make a text to provide the caption, since GeoGebra // doesn't allow caption different color from entity. textName := 'GeoText' + index locationExpr := switch klass - when 'point' then `1.02${name})` + when 'point' then `1.02${name}` when 'line' (`Midpoint(${name}) + ` + `Rotate(Direction(${name})*Length(${name})*0.02, pi/2)`) @@ -315,8 +368,12 @@ function jToG( unless parts[0].includes ex1 then ex1 = `Centroid(${ex1})` unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})` `(4*${ex1}+${ex2})/5` - api.evalCommand `${textName} = Text("${jname}", ${locationExpr})` - api.setColor textName, ...joyce2rgb colors[0], backgroundRGB + textCmd := `${textName} = Text("${jname}", ${locationExpr})` + textCol := joyce2rgb colors[0], cdata.bg + if trace + console.log `Making text '${textCmd}' colored`, textCol + api.evalCommand textCmd + api.setColor textName, ...textCol // and hide the underlying GeoGebra label api.setLabelVisible name, false else if colors[0] @@ -331,19 +388,35 @@ function jToG( console.log 'Setting label vis of', name, 'to', show if trace api.setLabelVisible name, show -// window[hideListener] = (arg) => -// api.setVisible name, false -// api.registerObjectUpdateListener name, hideListener - if cmdr.ends // line or sector - elements[jname] = - {otherName: name, usesCaptions, klass, cmdr.ends} - elements[name] = - {otherName: jname, usesCaptions, klass, cmdr.ends} - else // any other geometry - elements[jname] = {otherName: name, usesCaptions, klass} - elements[name] = {otherName: jname, usesCaptions, klass} + cdata.elements[jname] = {otherName: name, usesCaptions, klass, cmdr.ends} + cdata.elements[name] = {otherName: jname, usesCaptions, klass, cmdr.ends} cmdr +function pivotListener(slider: string) + pd := pivotData[slider] + newval := pd.api.getValue slider + if newval is pd.lastAngle then return + rotation := newval - pd.lastAngle + pd.lastAngle = newval + pX := pd.api.getXcoord pd.pivot + pY := pd.api.getYcoord pd.pivot + relX: Record := {} + relY: Record := {} + for each anchor of pd.rotatable + relX[anchor] = pd.api.getXcoord(anchor) - pX + relY[anchor] = pd.api.getYcoord(anchor) - pY + console.log 'Unfixing', anchor, relX[anchor], relY[anchor] + pd.api.setFixed anchor, false + ct := Math.cos rotation + st := Math.sin rotation + for each anchor of pd.rotatable + rX := relX[anchor]*ct - relY[anchor]*st + rY := relY[anchor]*ct + relX[anchor]*st + pd.api.setCoords(anchor, rX + pX, rY + pY) + if pd.fixed[anchor] + console.log 'Re-fixing', anchor, rX, rY + pd.api.setFixed anchor, true + function invisible(cname: string): boolean if adapParams.config?.showall then return false cname is '0' or cname is 'none' @@ -397,8 +470,8 @@ function joyce2rgb(cname: string, backgroundRGB?: RGB): RGB console.warn 'Could not parse color:', cname [128, 128, 128] -function pointDefaultRGB(name: string, method: string): RGB - // Need to short-circuit with green for pivot point, once that is implemented +function pointDefaultRGB(name: string, method: string, isPivot: boolean): RGB + if isPivot then return joyce2rgb 'green' switch method 'free' joyce2rgb 'red' @@ -406,7 +479,14 @@ function pointDefaultRGB(name: string, method: string): RGB joyce2rgb 'orange' else joyce2rgb 'black' -function geoname(jname: JoyceName, elements: JoyceElements): GeoName +function geoname( + jname: JoyceName, elements: JoyceElements, klass: JoyceClass): GeoName + unless jname.substring(0,3) is 'Geo' // those might clash + // Names with word characters starting with a capital are always good: + if /^[A-Z]['\w]*$/.test jname then return jname + // If it's not a point, can start with any letter: + if klass !== 'point' and /^\p{L}['\w]*$/u.test jname then return jname + // GeoGebra won't deal with this name, so hash it: numCode .= 0n numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname return .= 'Geo' + numCode.toString(36); @@ -426,11 +506,12 @@ function cutoffExtend( // All of the detailed semantics of each available command lies in this // function. classHandler: Record := - point: (name, method, args, index, is3d): Commander => + point: (name, method, args, index, cdata): Commander => return := freshCommander() {commands, callbacks, parts, auxiliaries} := return.value - zeroVector := is3d ? 'Vector((0,0,0))' : 'Vector((0,0))' + zeroVector := cdata.is3d ? 'Vector((0,0,0))' : 'Vector((0,0))' aux := name + 'aUx' + pivotable := cdata.pivot and name !== pivotData[cdata.pivot].pivot parts[0].push name switch method /angle(?:Bisector|Divider)/ @@ -445,7 +526,7 @@ classHandler: Record := commands.push `${destination} = Segment(${start}, ${end})` else destination = args.line[0] n := method is 'angleBisector' ? 2 : args.scalar?[0] - inPlane := is3d ? `, Plane(${start}, ${center}, ${end})` : '' + inPlane := cdata.is3d ? `, Plane(${start}, ${center}, ${end})` : '' commands.push `${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})` `${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)` @@ -454,7 +535,12 @@ classHandler: Record := auxiliaries.push ...[2..4].map (i) => `${aux}${i}` 'circleSlider' unless args.circle then return - commands.push `${name} = Point(${args.circle[0]})` + circ := args.circle[0] + commands.push `${name} = Point(${circ})` + if (pivotable + and cdata.elements[circ].ends?[0] // center + is pivotData[cdata.pivot].pivot) + pivotData[cdata.pivot].rotatable.push name if args.scalar and args.scalar.length callbacks.push (api: AppletObject) => api.setCoords name, ...args.scalar as XYZ @@ -466,12 +552,15 @@ classHandler: Record := commands.push `${name} = Translate(${source}, ${displacement})` 'first' unless args.subpoints then return - commands.push `${name} = Point(${args.subpoints[0]},${zeroVector})` + commands.push `${name} = ${args.subpoints[0]}` /fixed|free/ coords := args.scalar unless coords then return - commands.push `${name} = Point({${coords.join ','}})` + scoord := coords.join ',' + if pivotable then pivotData[cdata.pivot].rotatable.push name + commands.push `${name} = (${scoord})` if method is 'fixed' + if pivotable then pivotData[cdata.pivot].fixed[name] = true callbacks.push (api: AppletObject) => api.setFixed name, true 'foot' pt := args.subpoints @@ -492,7 +581,7 @@ classHandler: Record := 'last' unless args.subpoints then return commands.push - `${name} = Point(${args.subpoints.at(-1)}, ${zeroVector})` + `${name} = ${args.subpoints.at(-1)}` 'lineSegmentSlider' segment .= args.line?[0] unless segment @@ -524,7 +613,7 @@ classHandler: Record := unless pt.length is 5 then return sourcePlane .= '' destPlane .= '' - if is3d + if cdata.is3d unless args.plane then return destPlane = `, ${args.plane[0]}` if args.plane.length > 1 @@ -544,7 +633,7 @@ classHandler: Record := commands.push `${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})` - line: (name, method, args, index, is3d) => + line: (name, method, args, index, cdata) => return := freshCommander() return.value.ends = ['', ''] {commands, callbacks, parts, auxiliaries, ends} := return.value @@ -561,7 +650,7 @@ classHandler: Record := unless cr return commands.push ...[1..2].map (n) => `${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${n})` - inPlane := is3d ? `Plane(${cr[0]})` : '' + inPlane := cdata.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})`) @@ -630,7 +719,8 @@ classHandler: Record := circle: (name, method, args) => return := freshCommander() - {commands, callbacks, parts, auxiliaries} := return.value + return.value.ends = ['', ''] + {commands, callbacks, parts, auxiliaries, ends} := return.value parts[2].push name parts[1].push name switch method @@ -641,10 +731,12 @@ classHandler: Record := switch pt.length when 2 [center, point] := pt + ends[0] = center commands.push `${name} = Circle(${center}, ${point}${inPlane})` when 3 center := pt[0] + ends[0] = center radius := `Distance(${pt[1]}, ${pt[2]})` commands.push `${name} = Circle(${center}, ${radius}${inPlane})` -- 2.34.1 From 71a3fd4cc4ec46b4a30b169bfaffecd8e45f69aa Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Tue, 17 Oct 2023 18:04:50 -0700 Subject: [PATCH 4/4] feat: Get all Rostamian's old pages working This commit is just the last piece in the puzzle, implementing the perpendicular method for lines. Resolves #36. --- src/adapptlet.civet | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/adapptlet.civet b/src/adapptlet.civet index 2542a4b..7ae1262 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -257,6 +257,7 @@ function jToG( cmdr.callbacks.push (api: AppletObject) => for each aux of cmdr.auxiliaries api.setAuxiliary aux, true + api.setLabelVisible aux, false api.setVisible aux,false // set up the pivot if there is one @@ -405,7 +406,6 @@ function pivotListener(slider: string) for each anchor of pd.rotatable relX[anchor] = pd.api.getXcoord(anchor) - pX relY[anchor] = pd.api.getYcoord(anchor) - pY - console.log 'Unfixing', anchor, relX[anchor], relY[anchor] pd.api.setFixed anchor, false ct := Math.cos rotation st := Math.sin rotation @@ -414,7 +414,6 @@ function pivotListener(slider: string) rY := relY[anchor]*ct + relX[anchor]*st pd.api.setCoords(anchor, rX + pX, rY + pY) if pd.fixed[anchor] - console.log 'Re-fixing', anchor, rX, rY pd.api.setFixed anchor, true function invisible(cname: string): boolean @@ -712,6 +711,41 @@ classHandler: Record := madeSegment = true else commands.push `${aux}2 = Translate(${oldEnd}, ${aux}1)` + 'perpendicular' + pt := args.subpoints + unless pt then return + inPlane .= '' + if cdata.is3d + unless args.plane?.length is 1 then return + inPlane = `,${args.plane[0]}` + switch pt.length + when 2 + ends[0] = pt[0] + ends[1] = aux + auxiliaries.push aux + if args.line?.length is 1 + ln := args.line[0] + commands.push + `${name} = Rotate(${ln}, 3*pi/2, ${pt[0]}${inPlane})` + `${aux} = Vertex(${name}, 2)` + madeSegment = true + else + commands.push + `${aux} = Rotate(${pt[1]}, 3*pi/2, ${pt[0]}${inPlane})` + when 3 + return // TODO: line perpendicular to plane + when 4 + ends[0] = pt[0] + ends[1] = aux + 2 + auxiliaries.push aux+1, aux+2 + commands.push + `${aux}1 = Rotate(${pt[1]}, 3*pi/2, ${pt[0]}${inPlane})` + unitVec := `UnitVector(Vector(${pt[0]}, ${aux}1))` + dist := `Distance(${pt[2]}, ${pt[3]})` + commands.push + `${aux}2 = Translate(${pt[0]}, ${dist}*${unitVec})` + else return + unless madeSegment commands.push `${name} = Segment(${ends[0]},${ends[1]})` callbacks.push (api: AppletObject) => api.setLabelVisible name, true -- 2.34.1