From 5b1ca40b26a3b0736be489492872865ddc2155f4 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sun, 22 Oct 2023 17:33:01 +0000 Subject: [PATCH] feat: Implement additional Geometry Applet commands toward Book I (#45) Reviewed-on: https://code.studioinfinity.org/glen/archematics/pulls/45 Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- package.json5 | 8 +- pnpm-lock.yaml | 104 +++++++++--------- src/adapptlet.civet | 263 +++++++++++++++++++++++++++++++------------- 3 files changed, 243 insertions(+), 132 deletions(-) diff --git a/package.json5 b/package.json5 index 077cb78..ca1e631 100644 --- a/package.json5 +++ b/package.json5 @@ -34,12 +34,12 @@ url: 'https://code.studioinfinity.org/glen/archematics.git', }, devDependencies: { - '@danielx/civet': '^0.6.43', - '@types/firefox-webext-browser': '^111.0.2', - '@types/jquery': '^3.5.22', + '@danielx/civet': '^0.6.46', + '@types/firefox-webext-browser': '^111.0.3', + '@types/jquery': '^3.5.24', '@webcomponents/custom-elements': '^1.6.0', 'http-server': '^14.1.1', - rollup: '^4.0.2', + rollup: '^4.1.4', typescript: '^5.2.2', 'webextension-polyfill': '^0.10.0', }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df466fd..e07d757 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,14 +14,14 @@ dependencies: devDependencies: '@danielx/civet': - specifier: ^0.6.43 - version: 0.6.43(typescript@5.2.2) + specifier: ^0.6.46 + version: 0.6.46(typescript@5.2.2) '@types/firefox-webext-browser': - specifier: ^111.0.2 - version: 111.0.2 + specifier: ^111.0.3 + version: 111.0.3 '@types/jquery': - specifier: ^3.5.22 - version: 3.5.22 + specifier: ^3.5.24 + version: 3.5.24 '@webcomponents/custom-elements': specifier: ^1.6.0 version: 1.6.0 @@ -29,8 +29,8 @@ devDependencies: specifier: ^14.1.1 version: 14.1.1 rollup: - specifier: ^4.0.2 - version: 4.0.2 + specifier: ^4.1.4 + version: 4.1.4 typescript: specifier: ^5.2.2 version: 5.2.2 @@ -47,8 +47,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@danielx/civet@0.6.43(typescript@5.2.2): - resolution: {integrity: sha512-K4G9Rq4J6TY1H+XJOXCxY7IZqlLK+roL1ufvJQaOIrzx+KJ7F8zdn/4GKM7F7NKD4BgJl3avBqHAMzuUWRkzRg==} + /@danielx/civet@0.6.46(typescript@5.2.2): + resolution: {integrity: sha512-8WjtFrnK9TbBg5nVIQWFkFeWvDFBv/jFYYaasjiaT4+Vwr6TpZfbXnB/A4uj1sETagPtdNJPgasVpE1LHdrm6A==} engines: {node: '>=19 || ^18.6.0 || ^16.17.0'} hasBin: true peerDependencies: @@ -78,108 +78,108 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@rollup/rollup-android-arm-eabi@4.0.2: - resolution: {integrity: sha512-xDvk1pT4vaPU2BOLy0MqHMdYZyntqpaBf8RhBiezlqG9OjY8F50TyctHo8znigYKd+QCFhCmlmXHOL/LoaOl3w==} + /@rollup/rollup-android-arm-eabi@4.1.4: + resolution: {integrity: sha512-WlzkuFvpKl6CLFdc3V6ESPt7gq5Vrimd2Yv9IzKXdOpgbH4cdDSS1JLiACX8toygihtH5OlxyQzhXOph7Ovlpw==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.0.2: - resolution: {integrity: sha512-lqCglytY3E6raze27DD9VQJWohbwCxzqs9aSHcj5X/8hJpzZfNdbsr4Ja9Hqp6iPyF53+5PtPx0pKRlkSvlHZg==} + /@rollup/rollup-android-arm64@4.1.4: + resolution: {integrity: sha512-D1e+ABe56T9Pq2fD+R3ybe1ylCDzu3tY4Qm2Mj24R9wXNCq35+JbFbOpc2yrroO2/tGhTobmEl2Bm5xfE/n8RA==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.0.2: - resolution: {integrity: sha512-nkBKItS6E6CCzvRwgiKad+j+1ibmL7SIInj7oqMWmdkCjiSX6VeVZw2mLlRKIUL+JjsBgpATTfo7BiAXc1v0jA==} + /@rollup/rollup-darwin-arm64@4.1.4: + resolution: {integrity: sha512-7vTYrgEiOrjxnjsgdPB+4i7EMxbVp7XXtS+50GJYj695xYTTEMn3HZVEvgtwjOUkAP/Q4HDejm4fIAjLeAfhtg==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.0.2: - resolution: {integrity: sha512-vX2C8xvWPIbpEgQht95+dY6BReKAvtDgPDGi0XN0kWJKkm4WdNmq5dnwscv/zxvi+n6jUTBhs6GtpkkWT4q8Gg==} + /@rollup/rollup-darwin-x64@4.1.4: + resolution: {integrity: sha512-eGJVZScKSLZkYjhTAESCtbyTBq9SXeW9+TX36ki5gVhDqJtnQ5k0f9F44jNK5RhAMgIj0Ht9+n6HAgH0gUUyWQ==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.0.2: - resolution: {integrity: sha512-DVFIfcHOjgmeHOAqji4xNz2wczt1Bmzy9MwBZKBa83SjBVO/i38VHDR+9ixo8QpBOiEagmNw12DucG+v55tCrg==} + /@rollup/rollup-linux-arm-gnueabihf@4.1.4: + resolution: {integrity: sha512-HnigYSEg2hOdX1meROecbk++z1nVJDpEofw9V2oWKqOWzTJlJf1UXVbDE6Hg30CapJxZu5ga4fdAQc/gODDkKg==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.0.2: - resolution: {integrity: sha512-GCK/a9ItUxPI0V5hQEJjH4JtOJO90GF2Hja7TO+EZ8rmkGvEi8/ZDMhXmcuDpQT7/PWrTT9RvnG8snMd5SrhBQ==} + /@rollup/rollup-linux-arm64-gnu@4.1.4: + resolution: {integrity: sha512-TzJ+N2EoTLWkaClV2CUhBlj6ljXofaYzF/R9HXqQ3JCMnCHQZmQnbnZllw7yTDp0OG5whP4gIPozR4QiX+00MQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.0.2: - resolution: {integrity: sha512-cLuBp7rOjIB1R2j/VazjCmHC7liWUur2e9mFflLJBAWCkrZ+X0+QwHLvOQakIwDymungzAKv6W9kHZnTp/Mqrg==} + /@rollup/rollup-linux-arm64-musl@4.1.4: + resolution: {integrity: sha512-aVPmNMdp6Dlo2tWkAduAD/5TL/NT5uor290YvjvFvCv0Q3L7tVdlD8MOGDL+oRSw5XKXKAsDzHhUOPUNPRHVTQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.0.2: - resolution: {integrity: sha512-Zqw4iVnJr2naoyQus0yLy7sLtisCQcpdMKUCeXPBjkJtpiflRime/TMojbnl8O3oxUAj92mxr+t7im/RbgA20w==} + /@rollup/rollup-linux-x64-gnu@4.1.4: + resolution: {integrity: sha512-77Fb79ayiDad0grvVsz4/OB55wJRyw9Ao+GdOBA9XywtHpuq5iRbVyHToGxWquYWlEf6WHFQQnFEttsAzboyKg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.0.2: - resolution: {integrity: sha512-jJRU9TyUD/iMqjf8aLAp7XiN3pIj5v6Qcu+cdzBfVTKDD0Fvua4oUoK8eVJ9ZuKBEQKt3WdlcwJXFkpmMLk6kg==} + /@rollup/rollup-linux-x64-musl@4.1.4: + resolution: {integrity: sha512-/t6C6niEQTqmQTVTD9TDwUzxG91Mlk69/v0qodIPUnjjB3wR4UA3klg+orR2SU3Ux2Cgf2pWPL9utK80/1ek8g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.0.2: - resolution: {integrity: sha512-ZkS2NixCxHKC4zbOnw64ztEGGDVIYP6nKkGBfOAxEPW71Sji9v8z3yaHNuae/JHPwXA+14oDefnOuVfxl59SmQ==} + /@rollup/rollup-win32-arm64-msvc@4.1.4: + resolution: {integrity: sha512-ZY5BHHrOPkMbCuGWFNpJH0t18D2LU6GMYKGaqaWTQ3CQOL57Fem4zE941/Ek5pIsVt70HyDXssVEFQXlITI5Gg==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.0.2: - resolution: {integrity: sha512-3SKjj+tvnZ0oZq2BKB+fI+DqYI83VrRzk7eed8tJkxeZ4zxJZcLSE8YDQLYGq1tZAnAX+H076RHHB4gTZXsQzw==} + /@rollup/rollup-win32-ia32-msvc@4.1.4: + resolution: {integrity: sha512-XG2mcRfFrJvYyYaQmvCIvgfkaGinfXrpkBuIbJrTl9SaIQ8HumheWTIwkNz2mktCKwZfXHQNpO7RgXLIGQ7HXA==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.0.2: - resolution: {integrity: sha512-MBdJIOxRauKkry7t2q+rTHa3aWjVez2eioWg+etRVS3dE4tChhmt5oqZYr48R6bPmcwEhxQr96gVRfeQrLbqng==} + /@rollup/rollup-win32-x64-msvc@4.1.4: + resolution: {integrity: sha512-ANFqWYPwkhIqPmXw8vm0GpBEHiPpqcm99jiiAp71DbCSqLDhrtr019C5vhD0Bw4My+LmMvciZq6IsWHqQpl2ZQ==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@types/firefox-webext-browser@111.0.2: - resolution: {integrity: sha512-NS7izfYOnQI/Opf3YdZSKkI5Ox89SqEffJHK2zfGY2BYEVuWuM6pSwDRglGl4W0SM84oUQfvLyYH4X6EQZAJ2w==} + /@types/firefox-webext-browser@111.0.3: + resolution: {integrity: sha512-yboaUEJfY6AGB1hJIWzRGNa/qQUCC/2CAgBSWkdJ8rml4k0bXIyxlWYLSRTr6jHT29gdHkdHsPVuF5McSNERfw==} dev: true - /@types/jquery@3.5.22: - resolution: {integrity: sha512-ISQFeUK5GwRftLK4PVvKTWEVCxZ2BpaqBz0TWkIq5w4vGojxZP9+XkqgcPjxoqmPeew+HLyWthCBvK7GdF5NYA==} + /@types/jquery@3.5.24: + resolution: {integrity: sha512-V/TG69ge5amcr8Ap7vY3SObqKfZlV7ttqcYnNcYnndI77ySIRi05+3GjvfwRtE2qalAC2ySLIL1ker512sI20g==} dependencies: '@types/sizzle': 2.3.3 dev: true @@ -544,23 +544,23 @@ packages: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true - /rollup@4.0.2: - resolution: {integrity: sha512-MCScu4usMPCeVFaiLcgMDaBQeYi1z6vpWxz0r0hq0Hv77Y2YuOTZldkuNJ54BdYBH3e+nkrk6j0Rre/NLDBYzg==} + /rollup@4.1.4: + resolution: {integrity: sha512-U8Yk1lQRKqCkDBip/pMYT+IKaN7b7UesK3fLSTuHBoBJacCE+oBqo/dfG/gkUdQNNB2OBmRP98cn2C2bkYZkyw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.0.2 - '@rollup/rollup-android-arm64': 4.0.2 - '@rollup/rollup-darwin-arm64': 4.0.2 - '@rollup/rollup-darwin-x64': 4.0.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.0.2 - '@rollup/rollup-linux-arm64-gnu': 4.0.2 - '@rollup/rollup-linux-arm64-musl': 4.0.2 - '@rollup/rollup-linux-x64-gnu': 4.0.2 - '@rollup/rollup-linux-x64-musl': 4.0.2 - '@rollup/rollup-win32-arm64-msvc': 4.0.2 - '@rollup/rollup-win32-ia32-msvc': 4.0.2 - '@rollup/rollup-win32-x64-msvc': 4.0.2 + '@rollup/rollup-android-arm-eabi': 4.1.4 + '@rollup/rollup-android-arm64': 4.1.4 + '@rollup/rollup-darwin-arm64': 4.1.4 + '@rollup/rollup-darwin-x64': 4.1.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.1.4 + '@rollup/rollup-linux-arm64-gnu': 4.1.4 + '@rollup/rollup-linux-arm64-musl': 4.1.4 + '@rollup/rollup-linux-x64-gnu': 4.1.4 + '@rollup/rollup-linux-x64-musl': 4.1.4 + '@rollup/rollup-win32-arm64-msvc': 4.1.4 + '@rollup/rollup-win32-ia32-msvc': 4.1.4 + '@rollup/rollup-win32-x64-msvc': 4.1.4 fsevents: 2.3.3 dev: true diff --git a/src/adapptlet.civet b/src/adapptlet.civet index 7ae1262..3046e4e 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -196,6 +196,12 @@ function dispatchJcommand( Text("${value}", TitlePoint + (2,5))` 'pivot' return // already handled in postApplets + 'align' + console.warn + 'Label alignment is not available in GeoGebra' + 'translation, as there is no facility for automatically' + 'positioning labels. However, they can be dragged manually.' + return /e\[\d+\]/ num := parseInt(name.slice(2)) {commands, callbacks, parts} := @@ -491,7 +497,7 @@ function geoname( return .= 'Geo' + numCode.toString(36); return += '1' while return.value in elements -// Helper for similar point/line functions: +// Helpers for some corresponding point/line functions: function cutoffExtend( method: string, pt: string[], point?: string[], line?: string[] ): [string, string] @@ -502,6 +508,35 @@ function cutoffExtend( source := method is 'cutoff' ? pt[0] : pt[1] [source, displacement] +function proportionSimilar( + method: string, args: JoyceArguments, cdata: ConstructionData, + aux: string, commands: string[], auxiliaries: string[] + ): readonly [string, string] + bad := ['', ''] as const + pt .= args.subpoints + unless pt then return bad + // reduce the similar case to general proportion + if method is 'similar' + unless pt.length is 5 then return bad + sourcePlane .= '' + destPlane .= '' + if cdata.is3d + unless args.plane then return bad + 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]}))` + return [pt[6], `${len}*${direction}`] + // All of the detailed semantics of each available command lies in this // function. classHandler: Record := @@ -514,24 +549,10 @@ classHandler: Record := parts[0].push name switch method /angle(?:Bisector|Divider)/ - // Note we just ignore a possible plane argument; it's irrelevant - unless args.subpoints return - [start, center, end] := args.subpoints - // see if we need to make the destination segment from start to end - destination .= '' - unless args.line?.length is 1 and args.point?[0] is center - destination = aux + '1' - auxiliaries.push destination - commands.push `${destination} = Segment(${start}, ${end})` - else destination = args.line[0] - n := method is 'angleBisector' ? 2 : args.scalar?[0] - 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)` - `${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})` - `${name} = Intersect(${destination}, Ray(${center}, ${aux}4))` - auxiliaries.push ...[2..4].map (i) => `${aux}${i}` + {center, foot} := + makeAngDiv(method, args, cdata, aux, auxiliaries, commands) + unless foot return + commands.push `${name} = ${foot}` 'circleSlider' unless args.circle then return circ := args.circle[0] @@ -592,12 +613,23 @@ classHandler: Record := if args.scalar and args.scalar.length callbacks.push (api: AppletObject) => api.setCoords name, ...args.scalar as XYZ + 'lineSlider' + pt := args.subpoints + unless pt and pt.length is 2 then return + commands.push `${name} = Point(Line(${pt[0]}, ${pt[1]}))` + if args.scalar and args.scalar.length + callbacks.push (api: AppletObject) => + api.setCoords name, ...args.scalar as XYZ 'midpoint' if args.line commands.push `${name} = Midpoint(${args.line[0]})` else commands.push `${name} = Midpoint(${args.point?[0]},${args.point?[1]})` + 'parallelogram' + pt := args.subpoints + unless pt then return + commands.push `${name} = ${pt[0]} + ${pt[2]} - ${pt[1]}` 'perpendicular' // Note only the two-point option implemented so far unless args.subpoints return @@ -605,29 +637,10 @@ classHandler: Record := // Note clockwise 90° rotation (3π/2) confirmed in Joyce source commands.push `${name} = Rotate(${direction}, 3*pi/2, ${center})` /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 cdata.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]}))` - commands.push `${name} = Translate(${pt[6]}, ${len}*${direction})` + [source, displacement] := + proportionSimilar method, args, cdata, aux, commands, auxiliaries + unless source then return + commands.push `${name} = Translate(${source}, ${displacement})` 'vertex' commands.push `${name} = Vertex(${args.polygon?[0]},${args.scalar?[0]})` @@ -640,6 +653,15 @@ classHandler: Record := parts[1].push name madeSegment .= false switch method + /angle(?:Bisector|Divider)/ + {center, foot} := + makeAngDiv(method, args, cdata, aux, auxiliaries, commands) + unless foot return + auxiliaries.push aux + 'F' + commands.push + `${aux}F = ${foot}` + ends[0] = center + ends[1] = aux+'F' 'bichord' // To match Joyce, we need to get the ordering here correct. // we want the order so that start -> end sweeping past the @@ -649,7 +671,7 @@ classHandler: Record := unless cr return commands.push ...[1..2].map (n) => `${aux}${n} = Intersect(${cr[0]}, ${cr[1]}, ${n})` - inPlane := cdata.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})`) @@ -745,6 +767,14 @@ classHandler: Record := commands.push `${aux}2 = Translate(${pt[0]}, ${dist}*${unitVec})` else return + /proportion|similar/ + [source, displacement] := + proportionSimilar method, args, cdata, aux, commands, auxiliaries + unless source then return + ends[0] = source + commands.push `${aux}1 = Translate(${source},${displacement})` + auxiliaries.push aux+1 + ends[1] = aux+1 unless madeSegment commands.push `${name} = Segment(${ends[0]},${ends[1]})` @@ -755,9 +785,14 @@ classHandler: Record := return := freshCommander() return.value.ends = ['', ''] {commands, callbacks, parts, auxiliaries, ends} := return.value - parts[2].push name + aux := name + 'aUx' parts[1].push name + circle .= '' switch method + 'circumcircle' + pt := args.subpoints + unless pt and pt.length is 3 then return + circle = `Circle(${pt.join ','})` 'radius' pt := args.subpoints unless pt then return @@ -766,30 +801,37 @@ classHandler: Record := when 2 [center, point] := pt ends[0] = center - commands.push - `${name} = Circle(${center}, ${point}${inPlane})` + circle = `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})` + circle = `Circle(${center}, ${radius}${inPlane})` + commands.push + `${aux} = ${circle}` // for the filling + `${name} = ${circle}` // for the perimeter + parts[2].push aux + makeLinesInvisible callbacks, aux callbacks.push (api: AppletObject) => api.setLabelVisible name, true - polygon: (name, method, args, index) => + polygon: (name, method, args, index, cdata) => return := freshCommander() {commands, callbacks, parts, auxiliaries} := return.value parts[2].push name - // what to push for edges? + aux := name + 'aUx' switch method - 'equilateralTriangle' + /equilateralTriangle|square|regularPolygon/ pt := args.subpoints unless pt then return + N .= 3 + if method is 'square' then N = 4 + else if method is 'regularPolygon' and args.scalar + N = args.scalar[0] commands.push '' // hack, make sure there is a command parts[0].push pt[0], pt[1] callbacks.push (api: AppletObject, moreParts: DimParts) => made:= api.evalCommandGetLabels - `${name} = Polygon(${pt[1]},${pt[0]}, 3)` + `${name} = Polygon(${pt[0]},${pt[1]}, ${N})` if not made return for each obj of made.split ',' if obj is name continue @@ -801,10 +843,42 @@ classHandler: Record := 'point' moreParts[0].push newObj api.setVisible newObj, false - /triangle|quadrilateral/ - pt := args.subpoints - unless pt then return - commands.push '' + /triangle|similar|parallelogram|application|quadrilateral|octagon/ + unless args.subpoints then return + pt .= args.subpoints + if method is 'parallelogram' + unless pt.length is 3 then return + commands.push `${aux} = ${pt[0]} + ${pt[2]} - ${pt[1]}` + auxiliaries.push aux + pt = [...pt, aux] + else if method is 'application' + unless pt.length is 3 then return + unless args.polygon?.length is 1 then return + direction := `UnitVector(${pt[2]} - ${pt[0]})` + angle := `Angle(${pt[1]},${pt[0]},${pt[2]})` + length := `Area(${args.polygon})` + + `/(Distance(${pt[0]},${pt[1]})*sin(${angle}))` + commands.push ...[0..1].map (n) => + `${aux}${n} = ${pt[n]} + ${length}*${direction}` + auxiliaries.push aux+0, aux+1 + pt = [pt[0], pt[1], aux+1, aux+0] + else if method is 'similar' + unless pt.length is 5 then return + if cdata.is3d and not args.plane then return + inSourcePlane := cdata.is3d + ? `, Plane(${pt[2]},${pt[3]},${pt[4]})` + : '' + inDestPlane := cdata.is3d ? (', ' + args.plane?[0]) : '' + factor := + `Distance(${pt[2]},${pt[4]})/Distance(${pt[2]},${pt[3]})` + commands.push + `${aux}1 = Angle(${pt[3]},${pt[2]},${pt[4]}${inSourcePlane})` + `${aux}2 = Rotate(${pt[1]},${aux}1,${pt[0]}${inDestPlane})` + `${aux}3 = ${pt[0]} + (${aux}2 - ${pt[0]})*${factor}` + auxiliaries.push ...[1..3].map (n) => aux + n + pt = [pt[0], pt[1], aux+3] + else + commands.push '' parts[0].push ...pt callbacks.push (api: AppletObject, moreParts: DimParts) => made := api.evalCommandGetLabels @@ -823,32 +897,25 @@ classHandler: Record := aux := name + 'aUx' parts[2].push name switch method - 'sector' + /arc|sector/ unless args.subpoints?.length is 3 return parts[0].push ...args.subpoints - [center, end, start] := args.subpoints + [center, end, start] .= args.subpoints + parms .= center + ', ' + start + ', ' + end + prefix .= 'Circular' + if method is 'arc' + temp := end + end = center + center = temp + parms = start + ', ' + center + ', ' + end + prefix = 'Circumcircular' ends[0] = start ends[1] = end - parms := center + ', ' + start + ', ' + end commands.push - `${name} = CircularSector(${parms})` - `${aux}1 = CircularArc(${parms})` + `${name} = ${prefix}Sector(${parms})` + `${aux}1 = ${prefix}Arc(${parms})` parts[1].push aux + 1 - callbacks.push (api: AppletObject) => - api.setLineThickness name, 1 - // The rest of this function is a weird roundabout way to make - // the lines of the sector have zero opacity. - // I got it from - // https://www.reddit.com/r/geogebra/comments/12cbr85/setlineopacity_command/ - // I don't really understand how/why it works, but it seems to - // So that's good enough for me - xml .= api.getXML name - xml = xml.replace /opacity="\d+"/, 'opacity="0"' - api.evalXML xml - // This last step is especially confusing... I think - // evaluating the modified XML created a sort of second - // copy of the entity, and so we have to hide the original one - api.setVisible name, false + makeLinesInvisible callbacks, name plane: (name, method, args) => return := freshCommander() @@ -900,3 +967,47 @@ classHandler: Record := moreParts[1].push newObj 'triangle' moreParts[2].push newObj + +// Helper for dividing an angle +function makeAngDiv( + method:string, + args: JoyceArguments, + cdata: ConstructionData, + aux: string, + auxiliaries: string[], + commands: string[]) + // Note we just ignore a possible plane argument; it's irrelevant + unless args.subpoints return center: '', foot: '' + [start, center, end] := args.subpoints + // see if we need to make the destination segment from start to end + destination .= '' + unless args.line?.length is 1 and args.point?[0] is center + destination = aux + '1' + auxiliaries.push destination + commands.push `${destination} = Segment(${start}, ${end})` + else destination = args.line[0] + n := method is 'angleBisector' ? 2 : args.scalar?[0] + 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)` + `${aux}4 = Rotate(${start}, ${aux}3/${n}, ${center}${inPlane})` + auxiliaries.push ...[2..4].map (i) => `${aux}${i}` + return {center, foot: `Intersect(${destination}, Ray(${center}, ${aux}4))`} + +// helper for separating color of perimeter and interior: +function makeLinesInvisible(callbacks: GeogebraCallback[], name: string) + callbacks.push (api: AppletObject) => + api.setLineThickness name, 1 + // The rest of this function is a weird roundabout way to make + // the lines of the sector have zero opacity. I got it from + // https://www.reddit.com/r/geogebra/comments/12cbr85/setlineopacity_command/ + // I don't really understand how/why it works, but it seems to + // So that's good enough for me + xml .= api.getXML name + xml = xml.replace /opacity="\d+"/, 'opacity="0"' + api.evalXML xml + // This last step is especially confusing... I think + // evaluating the modified XML created a sort of second + // copy of the entity, and so we have to hide the original one + api.setVisible name, false