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.
This commit is contained in:
parent
bb1713a674
commit
85ad82d9e2
@ -101,7 +101,7 @@ export interface AppletObject {
|
|||||||
getXcoord(objName: string): number;
|
getXcoord(objName: string): number;
|
||||||
getYcoord(objName: string): number;
|
getYcoord(objName: string): number;
|
||||||
getZcoord(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;
|
getValue(objName: string): number;
|
||||||
getVersion(): string;
|
getVersion(): string;
|
||||||
getScreenshotBase64(callback: (data: string) => void, scale?: number): void;
|
getScreenshotBase64(callback: (data: string) => void, scale?: number): void;
|
||||||
|
@ -49,6 +49,28 @@ adapParams: AdapParams :=
|
|||||||
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
|
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
|
||||||
: JSON.parse(adapptScript.dataset.params ?? '') as AdapParams
|
: 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 = '')
|
function postApplets(jApplets: AppletDescription[], codebase = '')
|
||||||
for each jApp of jApplets
|
for each jApp of jApplets
|
||||||
params := {
|
params := {
|
||||||
@ -58,10 +80,8 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
|
|||||||
jApp.width,
|
jApp.width,
|
||||||
jApp.height,
|
jApp.height,
|
||||||
appletOnLoad: (api: AppletObject) =>
|
appletOnLoad: (api: AppletObject) =>
|
||||||
elements: JoyceElements := {}
|
is3d := contains3d jApp.params
|
||||||
backgroundRGB := [255, 255, 255] as RGB
|
if is3d
|
||||||
config3d := contains3d jApp.params
|
|
||||||
if config3d
|
|
||||||
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
|
||||||
@ -72,10 +92,24 @@ function postApplets(jApplets: AppletDescription[], codebase = '')
|
|||||||
api.setPerspective 'G'
|
api.setPerspective 'G'
|
||||||
if adapParams.config?.algebra
|
if adapParams.config?.algebra
|
||||||
api.setPerspective '+A'
|
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
|
for name, value in jApp.params
|
||||||
dispatchJcommand
|
dispatchJcommand
|
||||||
api, name, value, elements, backgroundRGB, config3d
|
api, name, value, cdata
|
||||||
if config3d
|
if is3d
|
||||||
depth .= jApp.width
|
depth .= jApp.width
|
||||||
if jApp.height > depth then depth = jApp.height
|
if jApp.height > depth then depth = jApp.height
|
||||||
api.setCoordSystem
|
api.setCoordSystem
|
||||||
@ -135,7 +169,7 @@ type ClassHandler = (
|
|||||||
method: string,
|
method: string,
|
||||||
args: JoyceArguments,
|
args: JoyceArguments,
|
||||||
index: number,
|
index: number,
|
||||||
is3d: boolean) => Commander
|
cdata: ConstructionData) => Commander
|
||||||
type RGB = [number, number, number]
|
type RGB = [number, number, number]
|
||||||
type XYZ = RGB
|
type XYZ = RGB
|
||||||
|
|
||||||
@ -146,28 +180,26 @@ function dispatchJcommand(
|
|||||||
api: AppletObject,
|
api: AppletObject,
|
||||||
name: string,
|
name: string,
|
||||||
value: string,
|
value: string,
|
||||||
elements: JoyceElements
|
cdata: ConstructionData): void
|
||||||
backgroundRGB: RGB,
|
|
||||||
is3d: boolean): void
|
|
||||||
switch name
|
switch name
|
||||||
'background'
|
'background'
|
||||||
newback := joyce2rgb value, backgroundRGB
|
cdata.bg = joyce2rgb value, cdata.bg
|
||||||
if adapParams.config?.commands
|
if adapParams.config?.commands
|
||||||
console.log 'Setting background to', value, 'interpreted as',
|
console.log 'Setting background to', value, 'interpreted as',
|
||||||
newback
|
cdata.bg
|
||||||
for i of [0..2]
|
api.setGraphicsOptions 1, bgColor: colorsea(cdata.bg).hex()
|
||||||
backgroundRGB[i] = newback[i]
|
|
||||||
api.setGraphicsOptions 1, bgColor: colorsea(backgroundRGB).hex()
|
|
||||||
'title'
|
'title'
|
||||||
if adapParams.config?.commands
|
if adapParams.config?.commands
|
||||||
console.log 'Setting title to', value
|
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}
|
api.evalCommand `TitlePoint = ${corner}
|
||||||
Text("${value}", TitlePoint + (2,5))`
|
Text("${value}", TitlePoint + (2,5))`
|
||||||
|
'pivot'
|
||||||
|
return // already handled in postApplets
|
||||||
/e\[\d+\]/
|
/e\[\d+\]/
|
||||||
num := parseInt(name.slice(2))
|
num := parseInt(name.slice(2))
|
||||||
{commands, callbacks, parts} :=
|
{commands, callbacks, parts} :=
|
||||||
jToG value, elements, num, backgroundRGB, is3d
|
jToG value, num, cdata
|
||||||
if commands.length
|
if commands.length
|
||||||
lastTried .= 0
|
lastTried .= 0
|
||||||
if commands.filter((&)).every (cmd) =>
|
if commands.filter((&)).every (cmd) =>
|
||||||
@ -180,16 +212,14 @@ function dispatchJcommand(
|
|||||||
(part of translation of '${value}')
|
(part of translation of '${value}')
|
||||||
failed.`
|
failed.`
|
||||||
else console.warn `Could not parse command '${value}'`
|
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
|
// Parses a Joyce element-creating command, extending the elements
|
||||||
// by side effect:
|
// by side effect:
|
||||||
function jToG(
|
function jToG(
|
||||||
jCom: string,
|
jCom: string,
|
||||||
elements: JoyceElements,
|
|
||||||
index: number,
|
index: number,
|
||||||
backgroundRGB: RGB
|
cdata: ConstructionData): Commander
|
||||||
is3d: boolean): Commander
|
|
||||||
[jname, klass, method, data, ...colors] := jCom.split ';'
|
[jname, klass, method, data, ...colors] := jCom.split ';'
|
||||||
if adapParams.config?.commands
|
if adapParams.config?.commands
|
||||||
console.log 'Defining', jname, 'as a', klass, 'constructed by',
|
console.log 'Defining', jname, 'as a', klass, 'constructed by',
|
||||||
@ -199,7 +229,9 @@ function jToG(
|
|||||||
console.warn `Unknown entity class ${klass}`
|
console.warn `Unknown entity class ${klass}`
|
||||||
return cmdr
|
return cmdr
|
||||||
assertJoyceClass klass // shouldn't need to do that :-/
|
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 := {}
|
args: JoyceArguments := {}
|
||||||
usesCaptions := []
|
usesCaptions := []
|
||||||
for each jdep of data.split ','
|
for each jdep of data.split ','
|
||||||
@ -207,17 +239,17 @@ function jToG(
|
|||||||
if scalar is scalar // not NaN
|
if scalar is scalar // not NaN
|
||||||
(args.scalar ?= []).push scalar
|
(args.scalar ?= []).push scalar
|
||||||
continue
|
continue
|
||||||
unless jdep in elements
|
unless jdep in cdata.elements
|
||||||
console.warn `Reference to unknown geometric entity ${jdep} in $jCom}`
|
console.warn `Reference to unknown geometric entity ${jdep} in $jCom}`
|
||||||
return cmdr
|
return cmdr
|
||||||
usesCaptions.push jdep
|
usesCaptions.push jdep
|
||||||
{klass: depKlass, otherName: depGeo, ends} := elements[jdep]
|
{klass: depKlass, otherName: depGeo, ends} := cdata.elements[jdep]
|
||||||
(args[depKlass] ?= []).push depGeo
|
(args[depKlass] ?= []).push depGeo
|
||||||
if depKlass is 'point'
|
if depKlass is 'point'
|
||||||
(args.subpoints ?= []).push depGeo
|
(args.subpoints ?= []).push depGeo
|
||||||
else if depKlass is 'line'
|
else if depKlass is 'line'
|
||||||
(args.subpoints ?= []).push ...ends ?? []
|
(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) =>
|
unless name is jname then cmdr.callbacks.push (api: AppletObject) =>
|
||||||
api.setCaption name, jname
|
api.setCaption name, jname
|
||||||
api.setLabelStyle name, 3 // style CAPTION = 3
|
api.setLabelStyle name, 3 // style CAPTION = 3
|
||||||
@ -226,6 +258,25 @@ function jToG(
|
|||||||
for each aux of cmdr.auxiliaries
|
for each aux of cmdr.auxiliaries
|
||||||
api.setAuxiliary aux, true
|
api.setAuxiliary aux, true
|
||||||
api.setVisible aux,false
|
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
|
// Create callback to assign colors
|
||||||
if colors.length is 4 and colors.every (color) => invisible color
|
if colors.length is 4 and colors.every (color) => invisible color
|
||||||
cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false
|
cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false
|
||||||
@ -243,11 +294,11 @@ function jToG(
|
|||||||
console.log 'Fading out interior of', face if trace
|
console.log 'Fading out interior of', face if trace
|
||||||
// hide the interior by making it transparent
|
// hide the interior by making it transparent
|
||||||
api.setFilling face, 0
|
api.setFilling face, 0
|
||||||
else if face not in elements
|
else if face not in cdata.elements
|
||||||
console.log 'Hiding face', face if trace
|
console.log 'Hiding face', face if trace
|
||||||
api.setVisible face, false
|
api.setVisible face, false
|
||||||
else
|
else
|
||||||
faceRGB := joyce2rgb(colors[3] or 'brighter', backgroundRGB)
|
faceRGB := joyce2rgb(colors[3] or 'brighter', cdata.bg)
|
||||||
deep := ['circle', 'polygon', 'sector']
|
deep := ['circle', 'polygon', 'sector']
|
||||||
filling := deep.includes(klass) ? 0.7 : 0.2
|
filling := deep.includes(klass) ? 0.7 : 0.2
|
||||||
for each face of parts[2]
|
for each face of parts[2]
|
||||||
@ -259,28 +310,30 @@ function jToG(
|
|||||||
// Lines default to black:
|
// Lines default to black:
|
||||||
if invisible colors[2]
|
if invisible colors[2]
|
||||||
for each line of parts[1]
|
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
|
console.log 'Hiding line', line if trace
|
||||||
api.setVisible line, false
|
api.setVisible line, false
|
||||||
else
|
else
|
||||||
lineRGB := joyce2rgb(colors[2] or 'black', backgroundRGB)
|
lineRGB := joyce2rgb(colors[2] or 'black', cdata.bg)
|
||||||
for each line of parts[1]
|
for each line of parts[1]
|
||||||
console.log 'Coloring line', line, 'to', colors[2] if trace
|
console.log 'Coloring line', line, 'to', colors[2] if trace
|
||||||
api.setVisible line, true
|
api.setVisible line, true
|
||||||
api.setColor line, ...lineRGB
|
api.setColor line, ...lineRGB
|
||||||
|
|
||||||
// Now color the points:
|
// 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]
|
if invisible colors[1]
|
||||||
// Hide all the dim-0 elements that are not distinct independent
|
// Hide all the dim-0 elements that are not distinct independent
|
||||||
// items:
|
// items:
|
||||||
for each point of parts[0]
|
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
|
console.log 'Hiding point', point if trace
|
||||||
api.setVisible point, false
|
api.setVisible point, false
|
||||||
else if dimension is 0 or colors[1] // Need to color the points
|
else if dimension is 0 or colors[1] // Need to color the points
|
||||||
ptRGB := colors[1] ? joyce2rgb colors[1], backgroundRGB
|
ptRGB := colors[1] ? joyce2rgb colors[1], cdata.bg
|
||||||
: pointDefaultRGB name, method
|
: pointDefaultRGB name, method, isPivot
|
||||||
for each point of parts[0]
|
for each point of parts[0]
|
||||||
console.log 'Coloring point', point, 'to', colors[1] if trace
|
console.log 'Coloring point', point, 'to', colors[1] if trace
|
||||||
api.setVisible point, true
|
api.setVisible point, true
|
||||||
@ -290,12 +343,12 @@ function jToG(
|
|||||||
if invisible colors[0]
|
if invisible colors[0]
|
||||||
console.log 'Hiding label', name if trace
|
console.log 'Hiding label', name if trace
|
||||||
api.setLabelVisible name, false
|
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
|
// Have to make a text to provide the caption, since GeoGebra
|
||||||
// doesn't allow caption different color from entity.
|
// doesn't allow caption different color from entity.
|
||||||
textName := 'GeoText' + index
|
textName := 'GeoText' + index
|
||||||
locationExpr := switch klass
|
locationExpr := switch klass
|
||||||
when 'point' then `1.02${name})`
|
when 'point' then `1.02${name}`
|
||||||
when 'line'
|
when 'line'
|
||||||
(`Midpoint(${name}) + `
|
(`Midpoint(${name}) + `
|
||||||
+ `Rotate(Direction(${name})*Length(${name})*0.02, pi/2)`)
|
+ `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 ex1 then ex1 = `Centroid(${ex1})`
|
||||||
unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})`
|
unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})`
|
||||||
`(4*${ex1}+${ex2})/5`
|
`(4*${ex1}+${ex2})/5`
|
||||||
api.evalCommand `${textName} = Text("${jname}", ${locationExpr})`
|
textCmd := `${textName} = Text("${jname}", ${locationExpr})`
|
||||||
api.setColor textName, ...joyce2rgb colors[0], backgroundRGB
|
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
|
// and hide the underlying GeoGebra label
|
||||||
api.setLabelVisible name, false
|
api.setLabelVisible name, false
|
||||||
else if colors[0]
|
else if colors[0]
|
||||||
@ -331,19 +388,35 @@ function jToG(
|
|||||||
console.log 'Setting label vis of', name, 'to', show if trace
|
console.log 'Setting label vis of', name, 'to', show if trace
|
||||||
api.setLabelVisible name, show
|
api.setLabelVisible name, show
|
||||||
|
|
||||||
// window[hideListener] = (arg) =>
|
cdata.elements[jname] = {otherName: name, usesCaptions, klass, cmdr.ends}
|
||||||
// api.setVisible name, false
|
cdata.elements[name] = {otherName: jname, usesCaptions, klass, cmdr.ends}
|
||||||
// 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}
|
|
||||||
cmdr
|
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
|
function invisible(cname: string): boolean
|
||||||
if adapParams.config?.showall then return false
|
if adapParams.config?.showall then return false
|
||||||
cname is '0' or cname is 'none'
|
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
|
console.warn 'Could not parse color:', cname
|
||||||
[128, 128, 128]
|
[128, 128, 128]
|
||||||
|
|
||||||
function pointDefaultRGB(name: string, method: string): RGB
|
function pointDefaultRGB(name: string, method: string, isPivot: boolean): RGB
|
||||||
// Need to short-circuit with green for pivot point, once that is implemented
|
if isPivot then return joyce2rgb 'green'
|
||||||
switch method
|
switch method
|
||||||
'free'
|
'free'
|
||||||
joyce2rgb 'red'
|
joyce2rgb 'red'
|
||||||
@ -406,7 +479,14 @@ function pointDefaultRGB(name: string, method: string): RGB
|
|||||||
joyce2rgb 'orange'
|
joyce2rgb 'orange'
|
||||||
else joyce2rgb 'black'
|
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 .= 0n
|
||||||
numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname
|
numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname
|
||||||
return .= 'Geo' + numCode.toString(36);
|
return .= 'Geo' + numCode.toString(36);
|
||||||
@ -426,11 +506,12 @@ function cutoffExtend(
|
|||||||
// 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, cdata): 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))'
|
zeroVector := cdata.is3d ? 'Vector((0,0,0))' : 'Vector((0,0))'
|
||||||
aux := name + 'aUx'
|
aux := name + 'aUx'
|
||||||
|
pivotable := cdata.pivot and name !== pivotData[cdata.pivot].pivot
|
||||||
parts[0].push name
|
parts[0].push name
|
||||||
switch method
|
switch method
|
||||||
/angle(?:Bisector|Divider)/
|
/angle(?:Bisector|Divider)/
|
||||||
@ -445,7 +526,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
commands.push `${destination} = Segment(${start}, ${end})`
|
commands.push `${destination} = Segment(${start}, ${end})`
|
||||||
else destination = args.line[0]
|
else destination = args.line[0]
|
||||||
n := method is 'angleBisector' ? 2 : args.scalar?[0]
|
n := method is 'angleBisector' ? 2 : args.scalar?[0]
|
||||||
inPlane := is3d ? `, Plane(${start}, ${center}, ${end})` : ''
|
inPlane := cdata.is3d ? `, Plane(${start}, ${center}, ${end})` : ''
|
||||||
commands.push
|
commands.push
|
||||||
`${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})`
|
`${aux}2 = Angle(${start}, ${center}, ${end}${inPlane})`
|
||||||
`${aux}3 = If(${aux}2 > pi, ${aux}2 - 2*pi, ${aux}2)`
|
`${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}`
|
auxiliaries.push ...[2..4].map (i) => `${aux}${i}`
|
||||||
'circleSlider'
|
'circleSlider'
|
||||||
unless args.circle then return
|
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
|
if args.scalar and args.scalar.length
|
||||||
callbacks.push (api: AppletObject) =>
|
callbacks.push (api: AppletObject) =>
|
||||||
api.setCoords name, ...args.scalar as XYZ
|
api.setCoords name, ...args.scalar as XYZ
|
||||||
@ -466,12 +552,15 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
commands.push `${name} = Translate(${source}, ${displacement})`
|
commands.push `${name} = Translate(${source}, ${displacement})`
|
||||||
'first'
|
'first'
|
||||||
unless args.subpoints then return
|
unless args.subpoints then return
|
||||||
commands.push `${name} = Point(${args.subpoints[0]},${zeroVector})`
|
commands.push `${name} = ${args.subpoints[0]}`
|
||||||
/fixed|free/
|
/fixed|free/
|
||||||
coords := args.scalar
|
coords := args.scalar
|
||||||
unless coords then return
|
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 method is 'fixed'
|
||||||
|
if pivotable then pivotData[cdata.pivot].fixed[name] = true
|
||||||
callbacks.push (api: AppletObject) => api.setFixed name, true
|
callbacks.push (api: AppletObject) => api.setFixed name, true
|
||||||
'foot'
|
'foot'
|
||||||
pt := args.subpoints
|
pt := args.subpoints
|
||||||
@ -492,7 +581,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
'last'
|
'last'
|
||||||
unless args.subpoints then return
|
unless args.subpoints then return
|
||||||
commands.push
|
commands.push
|
||||||
`${name} = Point(${args.subpoints.at(-1)}, ${zeroVector})`
|
`${name} = ${args.subpoints.at(-1)}`
|
||||||
'lineSegmentSlider'
|
'lineSegmentSlider'
|
||||||
segment .= args.line?[0]
|
segment .= args.line?[0]
|
||||||
unless segment
|
unless segment
|
||||||
@ -524,7 +613,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
unless pt.length is 5 then return
|
unless pt.length is 5 then return
|
||||||
sourcePlane .= ''
|
sourcePlane .= ''
|
||||||
destPlane .= ''
|
destPlane .= ''
|
||||||
if is3d
|
if cdata.is3d
|
||||||
unless args.plane then return
|
unless args.plane then return
|
||||||
destPlane = `, ${args.plane[0]}`
|
destPlane = `, ${args.plane[0]}`
|
||||||
if args.plane.length > 1
|
if args.plane.length > 1
|
||||||
@ -544,7 +633,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, index, is3d) =>
|
line: (name, method, args, index, cdata) =>
|
||||||
return := freshCommander()
|
return := freshCommander()
|
||||||
return.value.ends = ['', '']
|
return.value.ends = ['', '']
|
||||||
{commands, callbacks, parts, auxiliaries, ends} := return.value
|
{commands, callbacks, parts, auxiliaries, ends} := return.value
|
||||||
@ -561,7 +650,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
unless cr return
|
unless cr return
|
||||||
commands.push ...[1..2].map (n) =>
|
commands.push ...[1..2].map (n) =>
|
||||||
`${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${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})`
|
ctr := cr.map (c) => `Center(${c})`
|
||||||
condition := (`Angle(${aux}1,${ctr[0]},${ctr[1]}${inPlane})`
|
condition := (`Angle(${aux}1,${ctr[0]},${ctr[1]}${inPlane})`
|
||||||
+ `< Angle(${aux}2,${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) =>
|
circle: (name, method, args) =>
|
||||||
return := freshCommander()
|
return := freshCommander()
|
||||||
{commands, callbacks, parts, auxiliaries} := return.value
|
return.value.ends = ['', '']
|
||||||
|
{commands, callbacks, parts, auxiliaries, ends} := return.value
|
||||||
parts[2].push name
|
parts[2].push name
|
||||||
parts[1].push name
|
parts[1].push name
|
||||||
switch method
|
switch method
|
||||||
@ -641,10 +731,12 @@ classHandler: Record<JoyceClass, ClassHandler> :=
|
|||||||
switch pt.length
|
switch pt.length
|
||||||
when 2
|
when 2
|
||||||
[center, point] := pt
|
[center, point] := pt
|
||||||
|
ends[0] = center
|
||||||
commands.push
|
commands.push
|
||||||
`${name} = Circle(${center}, ${point}${inPlane})`
|
`${name} = Circle(${center}, ${point}${inPlane})`
|
||||||
when 3
|
when 3
|
||||||
center := pt[0]
|
center := pt[0]
|
||||||
|
ends[0] = center
|
||||||
radius := `Distance(${pt[1]}, ${pt[2]})`
|
radius := `Distance(${pt[1]}, ${pt[2]})`
|
||||||
commands.push
|
commands.push
|
||||||
`${name} = Circle(${center}, ${radius}${inPlane})`
|
`${name} = Circle(${center}, ${radius}${inPlane})`
|
||||||
|
Loading…
Reference in New Issue
Block a user