feat: Get all of Rostamian's old pages working #44

Merged
glen merged 4 commits from rostamian_old into main 2023-10-18 01:07:58 +00:00
2 changed files with 155 additions and 63 deletions
Showing only changes of commit 85ad82d9e2 - Show all commits

View File

@ -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;

View File

@ -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<string, boolean>
pivotData: Record<string, PivotData> := {}
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<string, number> := {}
relY: Record<string, number> := {}
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
'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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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<JoyceClass, ClassHandler> :=
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})`