@doenet/doenetml 0.6.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (549) hide show
  1. package/.prettierrc +3 -0
  2. package/LICENSE +661 -0
  3. package/README.md +146 -0
  4. package/cypress/e2e/ActivityViewer/activityVariants.cy.js +1770 -0
  5. package/cypress/e2e/ActivityViewer/compiledActivity.cy.js +83 -0
  6. package/cypress/e2e/ActivityViewer/relationshipsAmongPages.cy.js +697 -0
  7. package/cypress/e2e/answerValidation/errorinnumbers.cy.js +2125 -0
  8. package/cypress/e2e/answerValidation/factoring.cy.js +1945 -0
  9. package/cypress/e2e/answerValidation/factoringOldAlgorithm.cy.js +892 -0
  10. package/cypress/e2e/answerValidation/functionanswers.cy.js +314 -0
  11. package/cypress/e2e/answerValidation/matchingpatterns.cy.js +287 -0
  12. package/cypress/e2e/answerValidation/matchpartial.cy.js +6711 -0
  13. package/cypress/e2e/answerValidation/pointlocation.cy.js +3989 -0
  14. package/cypress/e2e/answerValidation/symbolicequality.cy.js +1893 -0
  15. package/cypress/e2e/answerValidation/videoProgress.cy.js +210 -0
  16. package/cypress/e2e/assignNames/basiccopy.cy.js +2376 -0
  17. package/cypress/e2e/assignNames/collections.cy.js +9247 -0
  18. package/cypress/e2e/assignNames/selects.cy.js +105 -0
  19. package/cypress/e2e/assignNames/sequences.cy.js +1964 -0
  20. package/cypress/e2e/baseComponent/basecomponentproperties.cy.js +999 -0
  21. package/cypress/e2e/baseComponent/doenetMLtext.cy.js +427 -0
  22. package/cypress/e2e/chemistry/atom.cy.js +201 -0
  23. package/cypress/e2e/chemistry/ion.cy.js +608 -0
  24. package/cypress/e2e/chemistry/ioniccompound.cy.js +133 -0
  25. package/cypress/e2e/dynamicalsystem/cobwebpolyline.cy.js +2653 -0
  26. package/cypress/e2e/dynamicalsystem/equilibriumcurve.cy.js +311 -0
  27. package/cypress/e2e/dynamicalsystem/equilibriumline.cy.js +279 -0
  28. package/cypress/e2e/dynamicalsystem/equilibriumpoint.cy.js +283 -0
  29. package/cypress/e2e/dynamicalsystem/odesystem.cy.js +1834 -0
  30. package/cypress/e2e/equality/mathexpressions.cy.js +948 -0
  31. package/cypress/e2e/graphing/graphreferences.cy.js +978 -0
  32. package/cypress/e2e/graphing/graphreferences2.cy.js +615 -0
  33. package/cypress/e2e/linearAlgebra/eigenDecomposition.cy.js +401 -0
  34. package/cypress/e2e/tagSpecific/angle.cy.js +3898 -0
  35. package/cypress/e2e/tagSpecific/animatefromsequence.cy.js +2306 -0
  36. package/cypress/e2e/tagSpecific/answer.cy.js +31647 -0
  37. package/cypress/e2e/tagSpecific/bestfitline.cy.js +612 -0
  38. package/cypress/e2e/tagSpecific/blockquote.cy.js +30 -0
  39. package/cypress/e2e/tagSpecific/boolean.cy.js +742 -0
  40. package/cypress/e2e/tagSpecific/booleaninput.cy.js +1283 -0
  41. package/cypress/e2e/tagSpecific/booleanlist.cy.js +588 -0
  42. package/cypress/e2e/tagSpecific/booleanoperators.cy.js +596 -0
  43. package/cypress/e2e/tagSpecific/booleanoperatorsonmath.cy.js +498 -0
  44. package/cypress/e2e/tagSpecific/callaction.cy.js +2835 -0
  45. package/cypress/e2e/tagSpecific/choiceinput.cy.js +3205 -0
  46. package/cypress/e2e/tagSpecific/circle.cy.js +22036 -0
  47. package/cypress/e2e/tagSpecific/codeeditor.cy.js +1995 -0
  48. package/cypress/e2e/tagSpecific/collect.cy.js +5035 -0
  49. package/cypress/e2e/tagSpecific/componentsize.cy.js +502 -0
  50. package/cypress/e2e/tagSpecific/conditionalcontent.cy.js +3495 -0
  51. package/cypress/e2e/tagSpecific/contentBrowser.cy.js +335 -0
  52. package/cypress/e2e/tagSpecific/contentpicker.cy.js +261 -0
  53. package/cypress/e2e/tagSpecific/copy.cy.js +12627 -0
  54. package/cypress/e2e/tagSpecific/copy2.cy.js +5698 -0
  55. package/cypress/e2e/tagSpecific/curve.bezier.cy.js +12440 -0
  56. package/cypress/e2e/tagSpecific/curve.cy.js +1716 -0
  57. package/cypress/e2e/tagSpecific/curve.function.cy.js +1471 -0
  58. package/cypress/e2e/tagSpecific/curve.parametrized.cy.js +920 -0
  59. package/cypress/e2e/tagSpecific/document.cy.js +234 -0
  60. package/cypress/e2e/tagSpecific/endpoint.cy.js +197 -0
  61. package/cypress/e2e/tagSpecific/evaluate.cy.js +8895 -0
  62. package/cypress/e2e/tagSpecific/extract.cy.js +2282 -0
  63. package/cypress/e2e/tagSpecific/feedback.cy.js +2941 -0
  64. package/cypress/e2e/tagSpecific/function.cy.js +9450 -0
  65. package/cypress/e2e/tagSpecific/functioniterates.cy.js +1178 -0
  66. package/cypress/e2e/tagSpecific/functionoperators.cy.js +4047 -0
  67. package/cypress/e2e/tagSpecific/graph.cy.js +2491 -0
  68. package/cypress/e2e/tagSpecific/group.cy.js +683 -0
  69. package/cypress/e2e/tagSpecific/hint.cy.js +204 -0
  70. package/cypress/e2e/tagSpecific/image.cy.js +770 -0
  71. package/cypress/e2e/tagSpecific/integer.cy.js +206 -0
  72. package/cypress/e2e/tagSpecific/label.cy.js +800 -0
  73. package/cypress/e2e/tagSpecific/legend.cy.js +1001 -0
  74. package/cypress/e2e/tagSpecific/line.cy.js +12167 -0
  75. package/cypress/e2e/tagSpecific/linesegment.cy.js +4749 -0
  76. package/cypress/e2e/tagSpecific/lorem.cy.js +289 -0
  77. package/cypress/e2e/tagSpecific/map.cy.js +4476 -0
  78. package/cypress/e2e/tagSpecific/matchespattern.cy.js +693 -0
  79. package/cypress/e2e/tagSpecific/math.cy.js +10990 -0
  80. package/cypress/e2e/tagSpecific/mathdisplay.cy.js +2689 -0
  81. package/cypress/e2e/tagSpecific/mathinput.cy.js +15628 -0
  82. package/cypress/e2e/tagSpecific/mathinputgraph.cy.js +566 -0
  83. package/cypress/e2e/tagSpecific/mathlist.cy.js +4073 -0
  84. package/cypress/e2e/tagSpecific/mathoperators.cy.js +13851 -0
  85. package/cypress/e2e/tagSpecific/matrix.cy.js +8825 -0
  86. package/cypress/e2e/tagSpecific/matrixinput.cy.js +16277 -0
  87. package/cypress/e2e/tagSpecific/module.cy.js +1771 -0
  88. package/cypress/e2e/tagSpecific/number.cy.js +2221 -0
  89. package/cypress/e2e/tagSpecific/numberlist.cy.js +1285 -0
  90. package/cypress/e2e/tagSpecific/p.cy.js +72 -0
  91. package/cypress/e2e/tagSpecific/paginator.cy.js +2983 -0
  92. package/cypress/e2e/tagSpecific/parabola.cy.js +14331 -0
  93. package/cypress/e2e/tagSpecific/paragraphmarkup.cy.js +104 -0
  94. package/cypress/e2e/tagSpecific/periodicset.cy.js +1439 -0
  95. package/cypress/e2e/tagSpecific/piecewisefunction.cy.js +1055 -0
  96. package/cypress/e2e/tagSpecific/pluralize.cy.js +274 -0
  97. package/cypress/e2e/tagSpecific/point.cy.js +8895 -0
  98. package/cypress/e2e/tagSpecific/point2.cy.js +10259 -0
  99. package/cypress/e2e/tagSpecific/polygon.cy.js +5039 -0
  100. package/cypress/e2e/tagSpecific/polyline.cy.js +4704 -0
  101. package/cypress/e2e/tagSpecific/problem.cy.js +2768 -0
  102. package/cypress/e2e/tagSpecific/ray.cy.js +10770 -0
  103. package/cypress/e2e/tagSpecific/rectangle.cy.js +2143 -0
  104. package/cypress/e2e/tagSpecific/ref.cy.js +420 -0
  105. package/cypress/e2e/tagSpecific/regularPolygon.cy.js +1006 -0
  106. package/cypress/e2e/tagSpecific/regularPolygon2.cy.js +100 -0
  107. package/cypress/e2e/tagSpecific/regularPolygon3.cy.js +777 -0
  108. package/cypress/e2e/tagSpecific/samplerandomnumbers.cy.js +3619 -0
  109. package/cypress/e2e/tagSpecific/sectioning.cy.js +3530 -0
  110. package/cypress/e2e/tagSpecific/select.cy.js +5376 -0
  111. package/cypress/e2e/tagSpecific/selectfromsequence.cy.js +3846 -0
  112. package/cypress/e2e/tagSpecific/selectrandomnumbers.cy.js +2914 -0
  113. package/cypress/e2e/tagSpecific/sequence.cy.js +2093 -0
  114. package/cypress/e2e/tagSpecific/shuffle.cy.js +490 -0
  115. package/cypress/e2e/tagSpecific/sidebyside.cy.js +8057 -0
  116. package/cypress/e2e/tagSpecific/singlecharactercomponents.cy.js +72 -0
  117. package/cypress/e2e/tagSpecific/slider.cy.js +914 -0
  118. package/cypress/e2e/tagSpecific/solution.cy.js +109 -0
  119. package/cypress/e2e/tagSpecific/solveequations.cy.js +1026 -0
  120. package/cypress/e2e/tagSpecific/sort.cy.js +1685 -0
  121. package/cypress/e2e/tagSpecific/spreadsheet.cy.js +5971 -0
  122. package/cypress/e2e/tagSpecific/subsetofreals.cy.js +2725 -0
  123. package/cypress/e2e/tagSpecific/substitute.cy.js +2646 -0
  124. package/cypress/e2e/tagSpecific/tabular.cy.js +36 -0
  125. package/cypress/e2e/tagSpecific/text.cy.js +975 -0
  126. package/cypress/e2e/tagSpecific/textinput.cy.js +2177 -0
  127. package/cypress/e2e/tagSpecific/textlist.cy.js +369 -0
  128. package/cypress/e2e/tagSpecific/triangle.cy.js +1936 -0
  129. package/cypress/e2e/tagSpecific/triggerset.cy.js +2023 -0
  130. package/cypress/e2e/tagSpecific/updatevalue.cy.js +3288 -0
  131. package/cypress/e2e/tagSpecific/vector.cy.js +20183 -0
  132. package/cypress/e2e/tagSpecific/video.cy.js +612 -0
  133. package/cypress/e2e/tagSpecific/when.cy.js +202 -0
  134. package/cypress/e2e/variants/specifysinglevariant.cy.js +6726 -0
  135. package/cypress/e2e/variants/uniquevariants.cy.js +4846 -0
  136. package/cypress/fixtures/example.json +5 -0
  137. package/cypress/support/commands.js +32 -0
  138. package/cypress/support/e2e.js +31 -0
  139. package/cypress.config.js +18 -0
  140. package/docs/codeSnippet.jsx +11 -0
  141. package/docs/index.html +133 -0
  142. package/docs/index.jsx +138 -0
  143. package/docs/prism.css +3 -0
  144. package/index.html +14 -0
  145. package/index.js +21 -0
  146. package/media/answer_example.png +0 -0
  147. package/media/graph_example.png +0 -0
  148. package/media/graph_markup_example.png +0 -0
  149. package/package.json +83 -0
  150. package/public/favicon.ico +0 -0
  151. package/public/fonts/files/open-sans-v18-latin-700.woff +0 -0
  152. package/public/fonts/files/open-sans-v18-latin-700.woff2 +0 -0
  153. package/public/fonts/files/open-sans-v18-latin-700italic.woff +0 -0
  154. package/public/fonts/files/open-sans-v18-latin-700italic.woff2 +0 -0
  155. package/public/fonts/files/open-sans-v18-latin-italic.woff +0 -0
  156. package/public/fonts/files/open-sans-v18-latin-italic.woff2 +0 -0
  157. package/public/fonts/files/open-sans-v18-latin-light-italic.woff +0 -0
  158. package/public/fonts/files/open-sans-v18-latin-light-italic.woff2 +0 -0
  159. package/public/fonts/files/open-sans-v18-latin-light.woff +0 -0
  160. package/public/fonts/files/open-sans-v18-latin-light.woff2 +0 -0
  161. package/public/fonts/files/open-sans-v18-latin-regular.woff +0 -0
  162. package/public/fonts/files/open-sans-v18-latin-regular.woff2 +0 -0
  163. package/src/Core/ComponentTypes.js +426 -0
  164. package/src/Core/Core.js +11852 -0
  165. package/src/Core/CoreWorker.js +127 -0
  166. package/src/Core/Dependencies.js +8226 -0
  167. package/src/Core/Numerics.js +473 -0
  168. package/src/Core/ParameterStack.js +36 -0
  169. package/src/Core/ReadOnlyProxyHandler.js +41 -0
  170. package/src/Core/StateProxyHandler.js +88 -0
  171. package/src/Core/components/Aliases.js +67 -0
  172. package/src/Core/components/Angle.js +758 -0
  173. package/src/Core/components/AnimateFromSequence.js +922 -0
  174. package/src/Core/components/Answer.js +2087 -0
  175. package/src/Core/components/AsList.js +83 -0
  176. package/src/Core/components/AttractTo.js +245 -0
  177. package/src/Core/components/AttractToAngles.js +262 -0
  178. package/src/Core/components/AttractToConstraint.js +104 -0
  179. package/src/Core/components/AttractToGrid.js +315 -0
  180. package/src/Core/components/Award.js +906 -0
  181. package/src/Core/components/BestFitLine.js +318 -0
  182. package/src/Core/components/BezierControls.js +719 -0
  183. package/src/Core/components/BlockQuote.js +35 -0
  184. package/src/Core/components/Boolean.js +500 -0
  185. package/src/Core/components/BooleanInput.js +330 -0
  186. package/src/Core/components/BooleanList.js +396 -0
  187. package/src/Core/components/BooleanOperators.js +35 -0
  188. package/src/Core/components/BooleanOperatorsOfMath.js +148 -0
  189. package/src/Core/components/CallAction.js +261 -0
  190. package/src/Core/components/Caption.js +73 -0
  191. package/src/Core/components/Case.js +56 -0
  192. package/src/Core/components/Cell.js +439 -0
  193. package/src/Core/components/CellBlock.js +64 -0
  194. package/src/Core/components/Chart.js +795 -0
  195. package/src/Core/components/Choice.js +266 -0
  196. package/src/Core/components/ChoiceInput.js +1407 -0
  197. package/src/Core/components/Circle.js +2884 -0
  198. package/src/Core/components/CodeEditor.js +647 -0
  199. package/src/Core/components/CodeViewer.js +294 -0
  200. package/src/Core/components/CollaborateGroupSetup.js +46 -0
  201. package/src/Core/components/CollaborateGroups.js +119 -0
  202. package/src/Core/components/Collect.js +850 -0
  203. package/src/Core/components/Column.js +608 -0
  204. package/src/Core/components/ConditionalContent.js +468 -0
  205. package/src/Core/components/ConsiderAsResponses.js +49 -0
  206. package/src/Core/components/ConstrainTo.js +161 -0
  207. package/src/Core/components/ConstrainToAngles.js +244 -0
  208. package/src/Core/components/ConstrainToGraph.js +142 -0
  209. package/src/Core/components/ConstrainToGrid.js +175 -0
  210. package/src/Core/components/ConstraintUnion.js +119 -0
  211. package/src/Core/components/Constraints.js +497 -0
  212. package/src/Core/components/ContentBrowser.js +441 -0
  213. package/src/Core/components/ContentPicker.js +263 -0
  214. package/src/Core/components/ControlVectors.js +25 -0
  215. package/src/Core/components/Coords.js +63 -0
  216. package/src/Core/components/Copy.js +3412 -0
  217. package/src/Core/components/Curve.js +4130 -0
  218. package/src/Core/components/CustomAttribute.js +175 -0
  219. package/src/Core/components/DataFrame.js +357 -0
  220. package/src/Core/components/DiscreteSimulationResultList.js +342 -0
  221. package/src/Core/components/DiscreteSimulationResultPolyline.js +581 -0
  222. package/src/Core/components/Divisions.js +55 -0
  223. package/src/Core/components/Document.js +888 -0
  224. package/src/Core/components/Embed.js +65 -0
  225. package/src/Core/components/Endpoint.js +62 -0
  226. package/src/Core/components/Evaluate.js +321 -0
  227. package/src/Core/components/Extract.js +656 -0
  228. package/src/Core/components/Extrema.js +556 -0
  229. package/src/Core/components/Feedback.js +200 -0
  230. package/src/Core/components/FeedbackDefinitions.js +97 -0
  231. package/src/Core/components/Figure.js +148 -0
  232. package/src/Core/components/Footnote.js +73 -0
  233. package/src/Core/components/Function.js +5344 -0
  234. package/src/Core/components/FunctionIterates.js +306 -0
  235. package/src/Core/components/FunctionOperators.js +702 -0
  236. package/src/Core/components/Graph.js +1679 -0
  237. package/src/Core/components/Group.js +7 -0
  238. package/src/Core/components/HasSameFactoring.js +407 -0
  239. package/src/Core/components/Hint.js +241 -0
  240. package/src/Core/components/Image.js +524 -0
  241. package/src/Core/components/Indexing.js +79 -0
  242. package/src/Core/components/IntComma.js +64 -0
  243. package/src/Core/components/Integer.js +81 -0
  244. package/src/Core/components/Intersection.js +328 -0
  245. package/src/Core/components/Interval.js +29 -0
  246. package/src/Core/components/Label.js +492 -0
  247. package/src/Core/components/Latex.js +104 -0
  248. package/src/Core/components/Legend.js +329 -0
  249. package/src/Core/components/Line.js +2040 -0
  250. package/src/Core/components/LineSegment.js +882 -0
  251. package/src/Core/components/Lists.js +180 -0
  252. package/src/Core/components/Lorem.js +249 -0
  253. package/src/Core/components/MMeMen.js +377 -0
  254. package/src/Core/components/Map.js +873 -0
  255. package/src/Core/components/Markers.js +101 -0
  256. package/src/Core/components/MatchesPattern.js +339 -0
  257. package/src/Core/components/Math.js +2552 -0
  258. package/src/Core/components/MathInput.js +948 -0
  259. package/src/Core/components/MathList.js +828 -0
  260. package/src/Core/components/MathOperators.js +1286 -0
  261. package/src/Core/components/Matrix.js +497 -0
  262. package/src/Core/components/MatrixInput.js +3157 -0
  263. package/src/Core/components/MdMdnMrow.js +394 -0
  264. package/src/Core/components/Module.js +16 -0
  265. package/src/Core/components/Number.js +1031 -0
  266. package/src/Core/components/NumberList.js +550 -0
  267. package/src/Core/components/Option.js +24 -0
  268. package/src/Core/components/P.js +71 -0
  269. package/src/Core/components/Paginator.js +338 -0
  270. package/src/Core/components/Panel.js +126 -0
  271. package/src/Core/components/Parabola.js +1561 -0
  272. package/src/Core/components/ParagraphMarkup.js +59 -0
  273. package/src/Core/components/Pegboard.js +43 -0
  274. package/src/Core/components/PeriodicSet.js +291 -0
  275. package/src/Core/components/PiecewiseFunction.js +832 -0
  276. package/src/Core/components/Pluralize.js +198 -0
  277. package/src/Core/components/Point.js +1295 -0
  278. package/src/Core/components/Polygon.js +408 -0
  279. package/src/Core/components/Polyline.js +841 -0
  280. package/src/Core/components/RandomizedTextList.js +225 -0
  281. package/src/Core/components/Ray.js +1737 -0
  282. package/src/Core/components/Rectangle.js +1535 -0
  283. package/src/Core/components/Ref.js +350 -0
  284. package/src/Core/components/RegionBetweenCurveXAxis.js +124 -0
  285. package/src/Core/components/RegionHalfPlane.js +107 -0
  286. package/src/Core/components/RegularPolygon.js +2118 -0
  287. package/src/Core/components/RenderDoenetML.js +181 -0
  288. package/src/Core/components/Row.js +780 -0
  289. package/src/Core/components/SampleRandomNumbers.js +653 -0
  290. package/src/Core/components/Sectioning.js +303 -0
  291. package/src/Core/components/Select.js +947 -0
  292. package/src/Core/components/SelectFromSequence.js +1242 -0
  293. package/src/Core/components/SelectRandomNumbers.js +225 -0
  294. package/src/Core/components/Sequence.js +444 -0
  295. package/src/Core/components/Setup.js +53 -0
  296. package/src/Core/components/Shuffle.js +470 -0
  297. package/src/Core/components/SideBySide.js +2130 -0
  298. package/src/Core/components/SingleCharacterComponents.js +41 -0
  299. package/src/Core/components/Slider.js +819 -0
  300. package/src/Core/components/SolutionContainer.js +67 -0
  301. package/src/Core/components/Solutions.js +334 -0
  302. package/src/Core/components/SolveEquations.js +568 -0
  303. package/src/Core/components/Sort.js +398 -0
  304. package/src/Core/components/Sources.js +108 -0
  305. package/src/Core/components/Split.js +205 -0
  306. package/src/Core/components/Spreadsheet.js +1507 -0
  307. package/src/Core/components/StyleDefinitions.js +111 -0
  308. package/src/Core/components/SubsetOfReals.js +348 -0
  309. package/src/Core/components/SubsetOfRealsInput.js +1474 -0
  310. package/src/Core/components/Substitute.js +496 -0
  311. package/src/Core/components/SummaryStatistics.js +652 -0
  312. package/src/Core/components/Table.js +145 -0
  313. package/src/Core/components/Tabular.js +384 -0
  314. package/src/Core/components/Template.js +360 -0
  315. package/src/Core/components/Text.js +341 -0
  316. package/src/Core/components/TextInput.js +566 -0
  317. package/src/Core/components/TextList.js +442 -0
  318. package/src/Core/components/TextListFromString.js +137 -0
  319. package/src/Core/components/TextOperatorsOfMath.js +21 -0
  320. package/src/Core/components/Triangle.js +280 -0
  321. package/src/Core/components/TriggerSet.js +189 -0
  322. package/src/Core/components/TupleList.js +43 -0
  323. package/src/Core/components/UpdateValue.js +435 -0
  324. package/src/Core/components/VariantControl.js +36 -0
  325. package/src/Core/components/Vector.js +2478 -0
  326. package/src/Core/components/Verbatim.js +125 -0
  327. package/src/Core/components/Video.js +673 -0
  328. package/src/Core/components/When.js +198 -0
  329. package/src/Core/components/abstract/AngleListComponent.js +140 -0
  330. package/src/Core/components/abstract/BaseComponent.js +1496 -0
  331. package/src/Core/components/abstract/BlockComponent.js +5 -0
  332. package/src/Core/components/abstract/BooleanBaseOperator.js +88 -0
  333. package/src/Core/components/abstract/BooleanBaseOperatorOfMath.js +100 -0
  334. package/src/Core/components/abstract/BooleanBaseOperatorOneInput.js +44 -0
  335. package/src/Core/components/abstract/ComponentSize.js +789 -0
  336. package/src/Core/components/abstract/ComponentWithSelectableType.js +537 -0
  337. package/src/Core/components/abstract/CompositeComponent.js +142 -0
  338. package/src/Core/components/abstract/ConstraintComponent.js +19 -0
  339. package/src/Core/components/abstract/FunctionBaseOperator.js +680 -0
  340. package/src/Core/components/abstract/GraphicalComponent.js +56 -0
  341. package/src/Core/components/abstract/InlineComponent.js +5 -0
  342. package/src/Core/components/abstract/InlineRenderInlineChildren.js +63 -0
  343. package/src/Core/components/abstract/Input.js +192 -0
  344. package/src/Core/components/abstract/IntervalListComponent.js +218 -0
  345. package/src/Core/components/abstract/LineListComponent.js +114 -0
  346. package/src/Core/components/abstract/MathBaseOperator.js +631 -0
  347. package/src/Core/components/abstract/MathBaseOperatorOneInput.js +112 -0
  348. package/src/Core/components/abstract/PointListComponent.js +238 -0
  349. package/src/Core/components/abstract/SectioningComponent.js +1262 -0
  350. package/src/Core/components/abstract/SingleCharacterInline.js +23 -0
  351. package/src/Core/components/abstract/TextBaseOperatorOfMath.js +47 -0
  352. package/src/Core/components/abstract/TextOrInline.js +66 -0
  353. package/src/Core/components/abstract/VariableName.js +31 -0
  354. package/src/Core/components/abstract/VariableNameList.js +83 -0
  355. package/src/Core/components/abstract/VectorListComponent.js +235 -0
  356. package/src/Core/components/chemistry/Atom.js +910 -0
  357. package/src/Core/components/chemistry/ElectronConfiguration.js +36 -0
  358. package/src/Core/components/chemistry/Ion.js +684 -0
  359. package/src/Core/components/chemistry/IonicCompound.js +189 -0
  360. package/src/Core/components/chemistry/OrbitalDiagram.js +175 -0
  361. package/src/Core/components/chemistry/OrbitalDiagramInput.js +753 -0
  362. package/src/Core/components/chemistry/index.js +6 -0
  363. package/src/Core/components/commonsugar/breakstrings.js +627 -0
  364. package/src/Core/components/commonsugar/lists.js +177 -0
  365. package/src/Core/components/dynamicalSystems/CobwebPolyline.js +913 -0
  366. package/src/Core/components/dynamicalSystems/EquilibriumCurve.js +95 -0
  367. package/src/Core/components/dynamicalSystems/EquilibriumLine.js +93 -0
  368. package/src/Core/components/dynamicalSystems/EquilibriumPoint.js +93 -0
  369. package/src/Core/components/dynamicalSystems/ODESystem.js +943 -0
  370. package/src/Core/components/dynamicalSystems/index.js +5 -0
  371. package/src/Core/components/linearAlgebra/EigenDecomposition.js +294 -0
  372. package/src/Core/utils/array.js +30 -0
  373. package/src/Core/utils/booleanLogic.js +965 -0
  374. package/src/Core/utils/checkEquality.js +818 -0
  375. package/src/Core/utils/cid.js +29 -0
  376. package/src/Core/utils/componentInfoObjects.js +100 -0
  377. package/src/Core/utils/constraints.js +23 -0
  378. package/src/Core/utils/copy.js +572 -0
  379. package/src/Core/utils/deepFunctions.js +173 -0
  380. package/src/Core/utils/descendants.js +252 -0
  381. package/src/Core/utils/enumeration.js +234 -0
  382. package/src/Core/utils/feedback.js +84 -0
  383. package/src/Core/utils/function.js +1343 -0
  384. package/src/Core/utils/graphical.js +196 -0
  385. package/src/Core/utils/label.js +396 -0
  386. package/src/Core/utils/math.js +1056 -0
  387. package/src/Core/utils/naming.js +45 -0
  388. package/src/Core/utils/periodicSetEquality.js +403 -0
  389. package/src/Core/utils/randomNumbers.js +70 -0
  390. package/src/Core/utils/retrieveMedia.js +98 -0
  391. package/src/Core/utils/retrieveTextFile.js +140 -0
  392. package/src/Core/utils/returnAllPossibleVariants.js +73 -0
  393. package/src/Core/utils/rounding.js +316 -0
  394. package/src/Core/utils/sequence.js +754 -0
  395. package/src/Core/utils/serializedStateProcessing.js +4049 -0
  396. package/src/Core/utils/size.js +22 -0
  397. package/src/Core/utils/stateVariables.js +138 -0
  398. package/src/Core/utils/style.js +535 -0
  399. package/src/Core/utils/subset-of-reals.js +796 -0
  400. package/src/Core/utils/table.js +41 -0
  401. package/src/Core/utils/text.js +16 -0
  402. package/src/Core/utils/triggering.js +167 -0
  403. package/src/Core/utils/variants.js +477 -0
  404. package/src/DoenetML.css +308 -0
  405. package/src/DoenetML.jsx +201 -0
  406. package/src/Parser/doenet.grammar +90 -0
  407. package/src/Parser/doenet.js +33 -0
  408. package/src/Parser/doenet.terms.js +20 -0
  409. package/src/Parser/parser.js +266 -0
  410. package/src/Parser/tokens.js +129 -0
  411. package/src/Tools/CodeMirror.jsx +440 -0
  412. package/src/Tools/DarkmodeController.jsx +21 -0
  413. package/src/Tools/Footers/MathInputSelector.jsx +34 -0
  414. package/src/Tools/Footers/VirtualKeyboard.jsx +751 -0
  415. package/src/Tools/cypressTest/CypressTest.jsx +341 -0
  416. package/src/Tools/cypressTest/index.html +102 -0
  417. package/src/Tools/cypressTest/index.jsx +40 -0
  418. package/src/Viewer/ActivityViewer.jsx +1461 -0
  419. package/src/Viewer/PageViewer.jsx +1329 -0
  420. package/src/Viewer/renderers/alert.jsx +17 -0
  421. package/src/Viewer/renderers/angle.jsx +209 -0
  422. package/src/Viewer/renderers/answer.jsx +206 -0
  423. package/src/Viewer/renderers/asList.jsx +25 -0
  424. package/src/Viewer/renderers/blockQuote.jsx +41 -0
  425. package/src/Viewer/renderers/boolean.jsx +17 -0
  426. package/src/Viewer/renderers/booleanInput.css +105 -0
  427. package/src/Viewer/renderers/booleanInput.jsx +636 -0
  428. package/src/Viewer/renderers/button.jsx +369 -0
  429. package/src/Viewer/renderers/c.jsx +17 -0
  430. package/src/Viewer/renderers/callAction.jsx +18 -0
  431. package/src/Viewer/renderers/cell.jsx +59 -0
  432. package/src/Viewer/renderers/chart.jsx +83 -0
  433. package/src/Viewer/renderers/choiceInput.css +223 -0
  434. package/src/Viewer/renderers/choiceInput.jsx +535 -0
  435. package/src/Viewer/renderers/circle.jsx +990 -0
  436. package/src/Viewer/renderers/cobwebPolyline.jsx +442 -0
  437. package/src/Viewer/renderers/codeEditor.jsx +248 -0
  438. package/src/Viewer/renderers/codeViewer.jsx +105 -0
  439. package/src/Viewer/renderers/containerBlock.jsx +41 -0
  440. package/src/Viewer/renderers/containerInline.jsx +17 -0
  441. package/src/Viewer/renderers/contentBrowser.jsx +159 -0
  442. package/src/Viewer/renderers/contentPicker.jsx +160 -0
  443. package/src/Viewer/renderers/curve.jsx +1072 -0
  444. package/src/Viewer/renderers/ellipsis.jsx +17 -0
  445. package/src/Viewer/renderers/em.jsx +17 -0
  446. package/src/Viewer/renderers/embed.jsx +110 -0
  447. package/src/Viewer/renderers/feedback.jsx +74 -0
  448. package/src/Viewer/renderers/figure.jsx +131 -0
  449. package/src/Viewer/renderers/footnote.jsx +52 -0
  450. package/src/Viewer/renderers/graph.jsx +925 -0
  451. package/src/Viewer/renderers/hint.jsx +142 -0
  452. package/src/Viewer/renderers/image.jsx +581 -0
  453. package/src/Viewer/renderers/jsxgraph-distrib/jsxgraphcore.mjs +2 -0
  454. package/src/Viewer/renderers/jsxgraph-distrib/jsxgraphcore.mjs.map +1 -0
  455. package/src/Viewer/renderers/label.jsx +470 -0
  456. package/src/Viewer/renderers/legend.jsx +306 -0
  457. package/src/Viewer/renderers/line.jsx +511 -0
  458. package/src/Viewer/renderers/lineSegment.jsx +754 -0
  459. package/src/Viewer/renderers/list.jsx +111 -0
  460. package/src/Viewer/renderers/lq.jsx +12 -0
  461. package/src/Viewer/renderers/lsq.jsx +12 -0
  462. package/src/Viewer/renderers/math.jsx +582 -0
  463. package/src/Viewer/renderers/mathInput.css +10 -0
  464. package/src/Viewer/renderers/mathInput.jsx +425 -0
  465. package/src/Viewer/renderers/mathInputog.jsx +534 -0
  466. package/src/Viewer/renderers/mathList.jsx +39 -0
  467. package/src/Viewer/renderers/matrixInput.jsx +317 -0
  468. package/src/Viewer/renderers/mdash.jsx +12 -0
  469. package/src/Viewer/renderers/nbsp.jsx +12 -0
  470. package/src/Viewer/renderers/ndash.jsx +12 -0
  471. package/src/Viewer/renderers/number.jsx +454 -0
  472. package/src/Viewer/renderers/numberList.jsx +35 -0
  473. package/src/Viewer/renderers/orbitalDiagram.jsx +247 -0
  474. package/src/Viewer/renderers/orbitalDiagramInput.jsx +450 -0
  475. package/src/Viewer/renderers/p.jsx +38 -0
  476. package/src/Viewer/renderers/paginatorControls.jsx +41 -0
  477. package/src/Viewer/renderers/pegboard.jsx +239 -0
  478. package/src/Viewer/renderers/point.jsx +649 -0
  479. package/src/Viewer/renderers/polygon.jsx +612 -0
  480. package/src/Viewer/renderers/polyline.jsx +608 -0
  481. package/src/Viewer/renderers/pre.jsx +34 -0
  482. package/src/Viewer/renderers/q.jsx +17 -0
  483. package/src/Viewer/renderers/ray.jsx +410 -0
  484. package/src/Viewer/renderers/ref.jsx +149 -0
  485. package/src/Viewer/renderers/regionBetweenCurveXAxis.jsx +182 -0
  486. package/src/Viewer/renderers/renderDoenetML.jsx +56 -0
  487. package/src/Viewer/renderers/row.jsx +31 -0
  488. package/src/Viewer/renderers/rq.jsx +12 -0
  489. package/src/Viewer/renderers/rsq.jsx +12 -0
  490. package/src/Viewer/renderers/section.jsx +427 -0
  491. package/src/Viewer/renderers/sideBySide.jsx +80 -0
  492. package/src/Viewer/renderers/slider.jsx +800 -0
  493. package/src/Viewer/renderers/solution.jsx +134 -0
  494. package/src/Viewer/renderers/spreadsheet.jsx +83 -0
  495. package/src/Viewer/renderers/sq.jsx +17 -0
  496. package/src/Viewer/renderers/styles/global.css +14 -0
  497. package/src/Viewer/renderers/subsetOfRealsInput.jsx +392 -0
  498. package/src/Viewer/renderers/summaryStatistics.jsx +83 -0
  499. package/src/Viewer/renderers/table.jsx +78 -0
  500. package/src/Viewer/renderers/tabular.jsx +58 -0
  501. package/src/Viewer/renderers/tag.jsx +26 -0
  502. package/src/Viewer/renderers/text.jsx +439 -0
  503. package/src/Viewer/renderers/textInput.jsx +774 -0
  504. package/src/Viewer/renderers/textList.jsx +30 -0
  505. package/src/Viewer/renderers/triggerSet.jsx +52 -0
  506. package/src/Viewer/renderers/updateValue.jsx +30 -0
  507. package/src/Viewer/renderers/utils/css.js +13 -0
  508. package/src/Viewer/renderers/utils/graph.js +159 -0
  509. package/src/Viewer/renderers/utils/offGraphIndicators.js +91 -0
  510. package/src/Viewer/renderers/vector.jsx +678 -0
  511. package/src/Viewer/renderers/video.jsx +494 -0
  512. package/src/Viewer/useDoenetRenderer.jsx +128 -0
  513. package/src/main.jsx +16 -0
  514. package/src/media/fonts/files/open-sans-v18-latin-700.woff +0 -0
  515. package/src/media/fonts/files/open-sans-v18-latin-700.woff2 +0 -0
  516. package/src/media/fonts/files/open-sans-v18-latin-700italic.woff +0 -0
  517. package/src/media/fonts/files/open-sans-v18-latin-700italic.woff2 +0 -0
  518. package/src/media/fonts/files/open-sans-v18-latin-italic.woff +0 -0
  519. package/src/media/fonts/files/open-sans-v18-latin-italic.woff2 +0 -0
  520. package/src/media/fonts/files/open-sans-v18-latin-light-italic.woff +0 -0
  521. package/src/media/fonts/files/open-sans-v18-latin-light-italic.woff2 +0 -0
  522. package/src/media/fonts/files/open-sans-v18-latin-light.woff +0 -0
  523. package/src/media/fonts/files/open-sans-v18-latin-light.woff2 +0 -0
  524. package/src/media/fonts/files/open-sans-v18-latin-regular.woff +0 -0
  525. package/src/media/fonts/files/open-sans-v18-latin-regular.woff2 +0 -0
  526. package/src/test/testCode.doenet +26 -0
  527. package/src/test/testViewer.jsx +158 -0
  528. package/src/uiComponents/ActionButton.jsx +157 -0
  529. package/src/uiComponents/ActionButtonGroup.jsx +93 -0
  530. package/src/uiComponents/Button.jsx +160 -0
  531. package/src/uiComponents/ButtonGroup.jsx +56 -0
  532. package/src/uiComponents/ToggleButton.jsx +194 -0
  533. package/src/uiComponents/ToggleButtonGroup.jsx +77 -0
  534. package/src/utils/activityUtils.js +713 -0
  535. package/src/utils/array.js +17 -0
  536. package/src/utils/cid.js +34 -0
  537. package/src/utils/componentInfoObjects.js +89 -0
  538. package/src/utils/deepFunctions.js +165 -0
  539. package/src/utils/enumeration.js +226 -0
  540. package/src/utils/math.js +624 -0
  541. package/src/utils/naming.js +44 -0
  542. package/src/utils/retrieveTextFile.js +156 -0
  543. package/src/utils/returnAllPossibleVariants.js +81 -0
  544. package/src/utils/sequence.js +715 -0
  545. package/src/utils/serialize.js +29 -0
  546. package/src/utils/serializedStateProcessing.js +2587 -0
  547. package/src/utils/subset-of-reals.js +783 -0
  548. package/src/utils/url.js +19 -0
  549. package/vite.config.js +14 -0
@@ -0,0 +1,4130 @@
1
+ import GraphicalComponent from "./abstract/GraphicalComponent";
2
+ import { returnBreakStringsSugarFunction } from "./commonsugar/breakstrings";
3
+
4
+ import me from "math-expressions";
5
+ import { returnBezierFunctions } from "../utils/function";
6
+ import {
7
+ returnRoundingAttributeComponentShadowing,
8
+ returnRoundingAttributes,
9
+ returnRoundingStateVariableDefinitions,
10
+ } from "../utils/rounding";
11
+ import { returnWrapNonLabelsSugarFunction } from "../utils/label";
12
+
13
+ export default class Curve extends GraphicalComponent {
14
+ constructor(args) {
15
+ super(args);
16
+
17
+ Object.assign(this.actions, {
18
+ moveControlVector: this.moveControlVector.bind(this),
19
+ moveThroughPoint: this.moveThroughPoint.bind(this),
20
+ changeVectorControlDirection:
21
+ this.changeVectorControlDirection.bind(this),
22
+ switchCurve: this.switchCurve.bind(this),
23
+ curveClicked: this.curveClicked.bind(this),
24
+ curveFocused: this.curveFocused.bind(this),
25
+ });
26
+ }
27
+ static componentType = "curve";
28
+ static rendererType = "curve";
29
+
30
+ static primaryStateVariableForDefinition = "fShadow";
31
+
32
+ static createAttributesObject() {
33
+ let attributes = super.createAttributesObject();
34
+
35
+ attributes.draggable = {
36
+ createComponentOfType: "boolean",
37
+ createStateVariable: "draggable",
38
+ defaultValue: true,
39
+ public: true,
40
+ forRenderer: true,
41
+ };
42
+
43
+ attributes.labelPosition = {
44
+ createComponentOfType: "text",
45
+ createStateVariable: "labelPosition",
46
+ defaultValue: "upperright",
47
+ public: true,
48
+ forRenderer: true,
49
+ toLowerCase: true,
50
+ validValues: [
51
+ "upperright",
52
+ "upperleft",
53
+ "lowerright",
54
+ "lowerleft",
55
+ "top",
56
+ "bottom",
57
+ "left",
58
+ "right",
59
+ ],
60
+ };
61
+
62
+ attributes.flipFunction = {
63
+ createComponentOfType: "boolean",
64
+ createStateVariable: "flipFunction",
65
+ defaultValue: false,
66
+ public: true,
67
+ forRenderer: true,
68
+ };
69
+ attributes.numDiscretizationPoints = {
70
+ createComponentOfType: "number",
71
+ createStateVariable: "numDiscretizationPoints",
72
+ defaultValue: 1000,
73
+ public: true,
74
+ };
75
+ attributes.periodic = {
76
+ createComponentOfType: "boolean",
77
+ createStateVariable: "periodic",
78
+ defaultValue: false,
79
+ public: true,
80
+ };
81
+ attributes.splineTension = {
82
+ createComponentOfType: "number",
83
+ createStateVariable: "splineTension",
84
+ defaultValue: 0.8,
85
+ clamp: [0, 1],
86
+ public: true,
87
+ };
88
+ attributes.extrapolateBackward = {
89
+ createComponentOfType: "boolean",
90
+ createStateVariable: "extrapolateBackward",
91
+ defaultValue: false,
92
+ public: true,
93
+ forRenderer: true,
94
+ };
95
+ attributes.extrapolateForward = {
96
+ createComponentOfType: "boolean",
97
+ createStateVariable: "extrapolateForward",
98
+ defaultValue: false,
99
+ public: true,
100
+ forRenderer: true,
101
+ };
102
+ attributes.splineForm = {
103
+ createComponentOfType: "text",
104
+ createStateVariable: "splineForm",
105
+ defaultValue: "centripetal",
106
+ public: true,
107
+ toLowerCase: true,
108
+ validValues: ["centripetal", "uniform"],
109
+ };
110
+ attributes.variable = {
111
+ createComponentOfType: "_variableName",
112
+ createStateVariable: "variableForChild",
113
+ defaultValue: me.fromAst("x"),
114
+ };
115
+
116
+ attributes.through = {
117
+ createComponentOfType: "_pointListComponent",
118
+ };
119
+ attributes.parMin = {
120
+ createComponentOfType: "math",
121
+ };
122
+ attributes.parMax = {
123
+ createComponentOfType: "math",
124
+ };
125
+
126
+ attributes.nearestPointAsCurve = {
127
+ createComponentOfType: "boolean",
128
+ createStateVariable: "nearestPointAsCurvePrelim",
129
+ defaultValue: false,
130
+ };
131
+
132
+ Object.assign(attributes, returnRoundingAttributes());
133
+
134
+ return attributes;
135
+ }
136
+
137
+ static returnSugarInstructions() {
138
+ let sugarInstructions = super.returnSugarInstructions();
139
+
140
+ let breakIntoFunctionsByCommas = function (childrenToBreak) {
141
+ let childrenToComponentFunction = (x) => ({
142
+ componentType: "function",
143
+ children: x,
144
+ });
145
+
146
+ let breakFunction = returnBreakStringsSugarFunction({
147
+ childrenToComponentFunction,
148
+ mustStripOffOuterParentheses: true,
149
+ });
150
+
151
+ let result = breakFunction({ matchedChildren: childrenToBreak });
152
+
153
+ let functionChildren = [];
154
+
155
+ if (result.success) {
156
+ functionChildren = result.newChildren;
157
+ } else {
158
+ // if didn't succeed,
159
+ // then just wrap children with a function
160
+ functionChildren = [
161
+ {
162
+ componentType: "function",
163
+ children: childrenToBreak,
164
+ },
165
+ ];
166
+ }
167
+ return functionChildren;
168
+ };
169
+
170
+ sugarInstructions.push({
171
+ replacementFunction: returnWrapNonLabelsSugarFunction({
172
+ onlyStringOrMacros: true,
173
+ customWrappingFunction: breakIntoFunctionsByCommas,
174
+ }),
175
+ });
176
+
177
+ return sugarInstructions;
178
+ }
179
+
180
+ static returnChildGroups() {
181
+ let childGroups = super.returnChildGroups();
182
+
183
+ childGroups.push(
184
+ ...[
185
+ {
186
+ group: "functions",
187
+ componentTypes: ["function"],
188
+ },
189
+ {
190
+ group: "bezierControls",
191
+ componentTypes: ["bezierControls"],
192
+ },
193
+ ],
194
+ );
195
+
196
+ return childGroups;
197
+ }
198
+
199
+ static returnStateVariableDefinitions({ numerics }) {
200
+ let stateVariableDefinitions = super.returnStateVariableDefinitions({
201
+ numerics,
202
+ });
203
+
204
+ Object.assign(
205
+ stateVariableDefinitions,
206
+ returnRoundingStateVariableDefinitions(),
207
+ );
208
+
209
+ stateVariableDefinitions.styleDescription = {
210
+ public: true,
211
+ shadowingInstructions: {
212
+ createComponentOfType: "text",
213
+ },
214
+ returnDependencies: () => ({
215
+ selectedStyle: {
216
+ dependencyType: "stateVariable",
217
+ variableName: "selectedStyle",
218
+ },
219
+ document: {
220
+ dependencyType: "ancestor",
221
+ componentType: "document",
222
+ variableNames: ["theme"],
223
+ },
224
+ }),
225
+ definition: function ({ dependencyValues }) {
226
+ let lineColorWord;
227
+ if (dependencyValues.document?.stateValues.theme === "dark") {
228
+ lineColorWord = dependencyValues.selectedStyle.lineColorWordDarkMode;
229
+ } else {
230
+ lineColorWord = dependencyValues.selectedStyle.lineColorWord;
231
+ }
232
+
233
+ let styleDescription = dependencyValues.selectedStyle.lineWidthWord;
234
+ if (dependencyValues.selectedStyle.lineStyleWord) {
235
+ if (styleDescription) {
236
+ styleDescription += " ";
237
+ }
238
+ styleDescription += dependencyValues.selectedStyle.lineStyleWord;
239
+ }
240
+
241
+ if (styleDescription) {
242
+ styleDescription += " ";
243
+ }
244
+
245
+ styleDescription += lineColorWord;
246
+
247
+ return { setValue: { styleDescription } };
248
+ },
249
+ };
250
+
251
+ stateVariableDefinitions.styleDescriptionWithNoun = {
252
+ public: true,
253
+ shadowingInstructions: {
254
+ createComponentOfType: "text",
255
+ },
256
+ returnDependencies: () => ({
257
+ styleDescription: {
258
+ dependencyType: "stateVariable",
259
+ variableName: "styleDescription",
260
+ },
261
+ }),
262
+ definition: function ({ dependencyValues }) {
263
+ let styleDescriptionWithNoun =
264
+ dependencyValues.styleDescription + " curve";
265
+
266
+ return { setValue: { styleDescriptionWithNoun } };
267
+ },
268
+ };
269
+
270
+ stateVariableDefinitions.curveType = {
271
+ forRenderer: true,
272
+ returnDependencies: () => ({
273
+ functionChildren: {
274
+ dependencyType: "child",
275
+ childGroups: ["functions"],
276
+ },
277
+ through: {
278
+ dependencyType: "attributeComponent",
279
+ attributeName: "through",
280
+ },
281
+ }),
282
+ definition({ dependencyValues }) {
283
+ let curveType = "function";
284
+ if (dependencyValues.through !== null) {
285
+ curveType = "bezier";
286
+ } else if (dependencyValues.functionChildren.length > 1) {
287
+ curveType = "parameterization";
288
+ }
289
+
290
+ return { setValue: { curveType } };
291
+ },
292
+ };
293
+
294
+ // fShadow will be null unless curve was created via an adapter
295
+ // In case of adapter,
296
+ // given the primaryStateVariableForDefinition static variable,
297
+ // the definition of fShadow will be changed to be the value
298
+ // that shadows the component adapted
299
+ stateVariableDefinitions.fShadow = {
300
+ defaultValue: null,
301
+ isLocation: true,
302
+ hasEssential: true,
303
+ returnDependencies: () => ({}),
304
+ definition: () => ({
305
+ useEssentialOrDefaultValue: {
306
+ fShadow: true,
307
+ },
308
+ }),
309
+ };
310
+
311
+ stateVariableDefinitions.graphXmin = {
312
+ forRenderer: true,
313
+ additionalStateVariablesDefined: [
314
+ {
315
+ variableName: "graphXmax",
316
+ forRenderer: true,
317
+ },
318
+ {
319
+ variableName: "graphYmin",
320
+ forRenderer: true,
321
+ },
322
+ {
323
+ variableName: "graphYmax",
324
+ forRenderer: true,
325
+ },
326
+ ],
327
+ returnDependencies: () => ({
328
+ graphAncestor: {
329
+ dependencyType: "ancestor",
330
+ componentType: "graph",
331
+ variableNames: ["xmin", "xmax", "ymin", "ymax"],
332
+ },
333
+ }),
334
+ definition({ dependencyValues }) {
335
+ if (dependencyValues.graphAncestor) {
336
+ return {
337
+ setValue: {
338
+ graphXmin: dependencyValues.graphAncestor.stateValues.xmin,
339
+ graphXmax: dependencyValues.graphAncestor.stateValues.xmax,
340
+ graphYmin: dependencyValues.graphAncestor.stateValues.ymin,
341
+ graphYmax: dependencyValues.graphAncestor.stateValues.ymax,
342
+ },
343
+ };
344
+ } else {
345
+ return {
346
+ setValue: {
347
+ graphXmin: null,
348
+ graphXmax: null,
349
+ graphYmin: null,
350
+ graphYmax: null,
351
+ },
352
+ };
353
+ }
354
+ },
355
+ };
356
+
357
+ stateVariableDefinitions.parMax = {
358
+ public: true,
359
+ isLocation: true,
360
+ shadowingInstructions: {
361
+ createComponentOfType: "number",
362
+ addAttributeComponentsShadowingStateVariables:
363
+ returnRoundingAttributeComponentShadowing(),
364
+ },
365
+ forRenderer: true,
366
+ returnDependencies: () => ({
367
+ curveType: {
368
+ dependencyType: "stateVariable",
369
+ variableName: "curveType",
370
+ },
371
+ parMaxAttr: {
372
+ dependencyType: "attributeComponent",
373
+ attributeName: "parMax",
374
+ variableNames: ["value"],
375
+ },
376
+ numThroughPoints: {
377
+ dependencyType: "stateVariable",
378
+ variableName: "numThroughPoints",
379
+ },
380
+ extrapolateForward: {
381
+ dependencyType: "stateVariable",
382
+ variableName: "extrapolateForward",
383
+ },
384
+ functionChild: {
385
+ dependencyType: "child",
386
+ childGroups: ["functions"],
387
+ variableNames: ["domain"],
388
+ },
389
+ adapterSourceDomain: {
390
+ dependencyType: "adapterSourceStateVariable",
391
+ variableName: "domain",
392
+ },
393
+ graphXmin: {
394
+ dependencyType: "stateVariable",
395
+ variableName: "graphXmin",
396
+ },
397
+ graphXmax: {
398
+ dependencyType: "stateVariable",
399
+ variableName: "graphXmax",
400
+ },
401
+ graphYmin: {
402
+ dependencyType: "stateVariable",
403
+ variableName: "graphYmin",
404
+ },
405
+ graphYmax: {
406
+ dependencyType: "stateVariable",
407
+ variableName: "graphYmax",
408
+ },
409
+ flipFunction: {
410
+ dependencyType: "stateVariable",
411
+ variableName: "flipFunction",
412
+ },
413
+ }),
414
+ definition: function ({ dependencyValues }) {
415
+ let parMax;
416
+ if (dependencyValues.curveType === "bezier") {
417
+ parMax = dependencyValues.numThroughPoints - 1;
418
+ if (dependencyValues.extrapolateForward) {
419
+ parMax *= 2;
420
+ }
421
+ } else if (dependencyValues.parMaxAttr !== null) {
422
+ parMax =
423
+ dependencyValues.parMaxAttr.stateValues.value.evaluate_to_constant();
424
+ } else if (dependencyValues.curveType === "function") {
425
+ let domain = null;
426
+ if (dependencyValues.functionChild.length === 1) {
427
+ domain = dependencyValues.functionChild[0].stateValues.domain;
428
+ } else {
429
+ domain = dependencyValues.adapterSourceDomain;
430
+ }
431
+ if (domain !== null) {
432
+ domain = domain[0];
433
+ parMax = me.fromAst(domain.tree[1][2]).evaluate_to_constant();
434
+ }
435
+ let graphMin, graphMax;
436
+ if (dependencyValues.flipFunction) {
437
+ graphMax = dependencyValues.graphYmax;
438
+ graphMin = dependencyValues.graphYmin;
439
+ } else {
440
+ graphMax = dependencyValues.graphXmax;
441
+ graphMin = dependencyValues.graphXmin;
442
+ }
443
+ if (graphMax !== null && graphMin !== null) {
444
+ if (parMax === undefined) {
445
+ parMax = graphMax + 0.1 * (graphMax - graphMin);
446
+ } else {
447
+ parMax = Math.min(parMax, graphMax + 0.1 * (graphMax - graphMin));
448
+ }
449
+ }
450
+
451
+ if (parMax === undefined) {
452
+ parMax = Infinity;
453
+ }
454
+ } else {
455
+ parMax = 10;
456
+ }
457
+
458
+ return { setValue: { parMax } };
459
+ },
460
+ };
461
+
462
+ stateVariableDefinitions.parMin = {
463
+ forRenderer: true,
464
+ isLocation: true,
465
+ public: true,
466
+ shadowingInstructions: {
467
+ createComponentOfType: "number",
468
+ addAttributeComponentsShadowingStateVariables:
469
+ returnRoundingAttributeComponentShadowing(),
470
+ },
471
+ returnDependencies: () => ({
472
+ curveType: {
473
+ dependencyType: "stateVariable",
474
+ variableName: "curveType",
475
+ },
476
+ parMinAttr: {
477
+ dependencyType: "attributeComponent",
478
+ attributeName: "parMin",
479
+ variableNames: ["value"],
480
+ },
481
+ numThroughPoints: {
482
+ dependencyType: "stateVariable",
483
+ variableName: "numThroughPoints",
484
+ },
485
+ extrapolateBackward: {
486
+ dependencyType: "stateVariable",
487
+ variableName: "extrapolateBackward",
488
+ },
489
+ functionChild: {
490
+ dependencyType: "child",
491
+ childGroups: ["functions"],
492
+ variableNames: ["domain"],
493
+ },
494
+ adapterSourceDomain: {
495
+ dependencyType: "adapterSourceStateVariable",
496
+ variableName: "domain",
497
+ },
498
+ graphXmin: {
499
+ dependencyType: "stateVariable",
500
+ variableName: "graphXmin",
501
+ },
502
+ graphXmax: {
503
+ dependencyType: "stateVariable",
504
+ variableName: "graphXmax",
505
+ },
506
+ graphYmin: {
507
+ dependencyType: "stateVariable",
508
+ variableName: "graphYmin",
509
+ },
510
+ graphYmax: {
511
+ dependencyType: "stateVariable",
512
+ variableName: "graphYmax",
513
+ },
514
+ flipFunction: {
515
+ dependencyType: "stateVariable",
516
+ variableName: "flipFunction",
517
+ },
518
+ }),
519
+ definition: function ({ dependencyValues }) {
520
+ let parMin;
521
+ if (dependencyValues.curveType === "bezier") {
522
+ parMin = 0;
523
+ if (dependencyValues.extrapolateBackward) {
524
+ parMin = -(dependencyValues.numThroughPoints - 1);
525
+ }
526
+ } else if (dependencyValues.parMinAttr !== null) {
527
+ parMin =
528
+ dependencyValues.parMinAttr.stateValues.value.evaluate_to_constant();
529
+ } else if (dependencyValues.curveType === "function") {
530
+ let domain = null;
531
+ if (dependencyValues.functionChild.length === 1) {
532
+ domain = dependencyValues.functionChild[0].stateValues.domain;
533
+ } else {
534
+ domain = dependencyValues.adapterSourceDomain;
535
+ }
536
+ if (domain !== null) {
537
+ domain = domain[0];
538
+ parMin = me.fromAst(domain.tree[1][1]).evaluate_to_constant();
539
+ }
540
+ let graphMin, graphMax;
541
+ if (dependencyValues.flipFunction) {
542
+ graphMax = dependencyValues.graphYmax;
543
+ graphMin = dependencyValues.graphYmin;
544
+ } else {
545
+ graphMax = dependencyValues.graphXmax;
546
+ graphMin = dependencyValues.graphXmin;
547
+ }
548
+ if (graphMax !== null && graphMin !== null) {
549
+ if (parMin === undefined) {
550
+ parMin = graphMin + 0.1 * (graphMin - graphMax);
551
+ } else {
552
+ parMin = Math.max(parMin, graphMin + 0.1 * (graphMin - graphMax));
553
+ }
554
+ }
555
+ if (parMin === undefined) {
556
+ parMin = -Infinity;
557
+ }
558
+ } else {
559
+ parMin = -10;
560
+ }
561
+ return { setValue: { parMin } };
562
+ },
563
+ };
564
+
565
+ stateVariableDefinitions.domainForFunctions = {
566
+ returnDependencies: () => ({
567
+ parMin: {
568
+ dependencyType: "stateVariable",
569
+ variableName: "parMin",
570
+ },
571
+ parMax: {
572
+ dependencyType: "stateVariable",
573
+ variableName: "parMax",
574
+ },
575
+ }),
576
+ definition({ dependencyValues }) {
577
+ // closed interval [parMin, parMax]
578
+ let interval = me.fromAst([
579
+ "interval",
580
+ ["tuple", dependencyValues.parMin, dependencyValues.parMax],
581
+ ["tuple", true, true],
582
+ ]);
583
+ return {
584
+ setValue: { domainForFunctions: [interval] },
585
+ };
586
+ },
587
+ };
588
+
589
+ stateVariableDefinitions.numThroughPoints = {
590
+ returnDependencies: () => ({
591
+ through: {
592
+ dependencyType: "attributeComponent",
593
+ attributeName: "through",
594
+ variableNames: ["numPoints"],
595
+ },
596
+ }),
597
+ definition({ dependencyValues }) {
598
+ let numThroughPoints = 0;
599
+ if (dependencyValues.through !== null) {
600
+ numThroughPoints = dependencyValues.through.stateValues.numPoints;
601
+ }
602
+ return { setValue: { numThroughPoints } };
603
+ },
604
+ };
605
+
606
+ stateVariableDefinitions.numDimensions = {
607
+ public: true,
608
+ shadowingInstructions: {
609
+ createComponentOfType: "number",
610
+ },
611
+ returnDependencies() {
612
+ return {
613
+ through: {
614
+ dependencyType: "attributeComponent",
615
+ attributeName: "through",
616
+ variableNames: ["numDimensions"],
617
+ },
618
+ };
619
+ },
620
+ definition: function ({ dependencyValues }) {
621
+ if (dependencyValues.through !== null) {
622
+ let numDimensions =
623
+ dependencyValues.through.stateValues.numDimensions;
624
+ return {
625
+ setValue: { numDimensions },
626
+ checkForActualChange: { numDimensions: true },
627
+ };
628
+ } else {
629
+ // curve through zero points
630
+ return { setValue: { numDimensions: 2 } };
631
+ }
632
+ },
633
+ };
634
+
635
+ stateVariableDefinitions.throughPoints = {
636
+ public: true,
637
+ isLocation: true,
638
+ shadowingInstructions: {
639
+ createComponentOfType: "math",
640
+ addAttributeComponentsShadowingStateVariables:
641
+ returnRoundingAttributeComponentShadowing(),
642
+ returnWrappingComponents(prefix) {
643
+ if (prefix === "throughPointX") {
644
+ return [];
645
+ } else {
646
+ // throughPoint or entire array
647
+ // wrap inner dimension by both <point> and <xs>
648
+ // don't wrap outer dimension (for entire array)
649
+ return [
650
+ ["point", { componentType: "mathList", isAttribute: "xs" }],
651
+ ];
652
+ }
653
+ },
654
+ },
655
+ isArray: true,
656
+ numDimensions: 2,
657
+ entryPrefixes: ["throughPointX", "throughPoint"],
658
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
659
+ if (arrayEntryPrefix === "throughPointX") {
660
+ // throughPointX1_2 is the 2nd component of the first throughPoint
661
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
662
+ if (
663
+ indices.length === 2 &&
664
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
665
+ ) {
666
+ if (arraySize) {
667
+ if (indices.every((x, i) => x < arraySize[i])) {
668
+ return [String(indices)];
669
+ } else {
670
+ return [];
671
+ }
672
+ } else {
673
+ // If not given the array size,
674
+ // then return the array keys assuming the array is large enough.
675
+ // Must do this as it is used to determine potential array entries.
676
+ return [String(indices)];
677
+ }
678
+ } else {
679
+ return [];
680
+ }
681
+ } else {
682
+ // throughPoint3 is all components of the third throughPoint
683
+
684
+ let pointInd = Number(varEnding) - 1;
685
+ if (!(Number.isInteger(pointInd) && pointInd >= 0)) {
686
+ return [];
687
+ }
688
+
689
+ if (!arraySize) {
690
+ // If don't have array size, we just need to determine if it is a potential entry.
691
+ // Return the first entry assuming array is large enough
692
+ return [pointInd + ",0"];
693
+ }
694
+ if (pointInd < arraySize[0]) {
695
+ // array of "pointInd,i", where i=0, ..., arraySize[1]-1
696
+ return Array.from(
697
+ Array(arraySize[1]),
698
+ (_, i) => pointInd + "," + i,
699
+ );
700
+ } else {
701
+ return [];
702
+ }
703
+ }
704
+ },
705
+ arrayVarNameFromPropIndex(propIndex, varName) {
706
+ if (varName === "throughPoints") {
707
+ if (propIndex.length === 1) {
708
+ return "throughPoint" + propIndex[0];
709
+ } else {
710
+ // if propIndex has additional entries, ignore them
711
+ return `throughPointX${propIndex[0]}_${propIndex[1]}`;
712
+ }
713
+ }
714
+ if (varName.slice(0, 12) === "throughPoint") {
715
+ // could be throughPoint or throughPointX
716
+ let throughPointNum = Number(varName.slice(12));
717
+ if (Number.isInteger(throughPointNum) && throughPointNum > 0) {
718
+ // if propIndex has additional entries, ignore them
719
+ return `throughPointX${throughPointNum}_${propIndex[0]}`;
720
+ }
721
+ }
722
+ return null;
723
+ },
724
+ returnArraySizeDependencies: () => ({
725
+ numThroughPoints: {
726
+ dependencyType: "stateVariable",
727
+ variableName: "numThroughPoints",
728
+ },
729
+ numDimensions: {
730
+ dependencyType: "stateVariable",
731
+ variableName: "numDimensions",
732
+ },
733
+ }),
734
+ returnArraySize({ dependencyValues }) {
735
+ return [
736
+ dependencyValues.numThroughPoints,
737
+ dependencyValues.numDimensions,
738
+ ];
739
+ },
740
+ returnArrayDependenciesByKey({ arrayKeys }) {
741
+ let dependenciesByKey = {};
742
+ for (let arrayKey of arrayKeys) {
743
+ let [pointInd, dim] = arrayKey.split(",");
744
+ let varEnding = Number(pointInd) + 1 + "_" + (Number(dim) + 1);
745
+
746
+ dependenciesByKey[arrayKey] = {
747
+ through: {
748
+ dependencyType: "attributeComponent",
749
+ attributeName: "through",
750
+ variableNames: ["pointX" + varEnding],
751
+ },
752
+ };
753
+ }
754
+ return { dependenciesByKey };
755
+ },
756
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
757
+ // console.log('array definition of curve throughPoints');
758
+ // console.log(JSON.parse(JSON.stringify(dependencyValuesByKey)))
759
+ // console.log(arrayKeys);
760
+
761
+ let throughPoints = {};
762
+
763
+ for (let arrayKey of arrayKeys) {
764
+ let [pointInd, dim] = arrayKey.split(",");
765
+ let varEnding = Number(pointInd) + 1 + "_" + (Number(dim) + 1);
766
+
767
+ let through = dependencyValuesByKey[arrayKey].through;
768
+ if (through !== null && through.stateValues["pointX" + varEnding]) {
769
+ throughPoints[arrayKey] = through.stateValues["pointX" + varEnding];
770
+ } else {
771
+ throughPoints[arrayKey] = me.fromAst(0);
772
+ }
773
+ }
774
+
775
+ return { setValue: { throughPoints } };
776
+ },
777
+ async inverseArrayDefinitionByKey({
778
+ desiredStateVariableValues,
779
+ dependencyValuesByKey,
780
+ dependencyNamesByKey,
781
+ initialChange,
782
+ stateValues,
783
+ }) {
784
+ // console.log(`inverseArrayDefinition of throughPoints of curve`);
785
+ // console.log(desiredStateVariableValues)
786
+ // console.log(JSON.parse(JSON.stringify(stateValues)))
787
+ // console.log(dependencyValuesByKey);
788
+
789
+ // if not draggable, then disallow initial change
790
+ if (initialChange && !(await stateValues.draggable)) {
791
+ return { success: false };
792
+ }
793
+
794
+ let instructions = [];
795
+ for (let arrayKey in desiredStateVariableValues.throughPoints) {
796
+ let [pointInd, dim] = arrayKey.split(",");
797
+ let varEnding = Number(pointInd) + 1 + "_" + (Number(dim) + 1);
798
+
799
+ if (
800
+ dependencyValuesByKey[arrayKey].through !== null &&
801
+ dependencyValuesByKey[arrayKey].through.stateValues[
802
+ "pointX" + varEnding
803
+ ]
804
+ ) {
805
+ instructions.push({
806
+ setDependency: dependencyNamesByKey[arrayKey].through,
807
+ desiredValue: desiredStateVariableValues.throughPoints[arrayKey],
808
+ childIndex: 0,
809
+ variableIndex: 0,
810
+ });
811
+ } else {
812
+ return { success: false };
813
+ }
814
+ }
815
+
816
+ return {
817
+ success: true,
818
+ instructions,
819
+ };
820
+ },
821
+ };
822
+
823
+ stateVariableDefinitions.numericalThroughPoints = {
824
+ isArray: true,
825
+ entryPrefixes: ["numericalThroughPoint"],
826
+ forRenderer: true,
827
+ returnArraySizeDependencies: () => ({
828
+ numThroughPoints: {
829
+ dependencyType: "stateVariable",
830
+ variableName: "numThroughPoints",
831
+ },
832
+ }),
833
+ returnArraySize({ dependencyValues }) {
834
+ return [dependencyValues.numThroughPoints];
835
+ },
836
+ returnArrayDependenciesByKey({ arrayKeys }) {
837
+ let dependenciesByKey = {};
838
+
839
+ for (let arrayKey of arrayKeys) {
840
+ dependenciesByKey[arrayKey] = {
841
+ throughPoint: {
842
+ dependencyType: "stateVariable",
843
+ variableName: "throughPoint" + (Number(arrayKey) + 1),
844
+ },
845
+ };
846
+ }
847
+
848
+ return { dependenciesByKey };
849
+ },
850
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
851
+ let numericalThroughPoints = {};
852
+
853
+ for (let arrayKey of arrayKeys) {
854
+ let pt = dependencyValuesByKey[arrayKey].throughPoint.map((x) =>
855
+ x.evaluate_to_constant(),
856
+ );
857
+ if (!pt.every((x) => Number.isFinite(x))) {
858
+ pt = Array(pt.length).fill(NaN);
859
+ }
860
+ numericalThroughPoints[arrayKey] = pt;
861
+ }
862
+
863
+ return { setValue: { numericalThroughPoints } };
864
+ },
865
+ };
866
+
867
+ stateVariableDefinitions.haveBezierControls = {
868
+ forRenderer: true,
869
+ returnDependencies: () => ({
870
+ controlChild: {
871
+ dependencyType: "child",
872
+ childGroups: ["bezierControls"],
873
+ },
874
+ }),
875
+ definition({ dependencyValues }) {
876
+ return {
877
+ setValue: {
878
+ haveBezierControls: dependencyValues.controlChild.length > 0,
879
+ },
880
+ };
881
+ },
882
+ };
883
+
884
+ stateVariableDefinitions.bezierControlsAlwaysVisible = {
885
+ forRenderer: true,
886
+ returnDependencies: () => ({
887
+ controlChild: {
888
+ dependencyType: "child",
889
+ childGroups: ["bezierControls"],
890
+ variableNames: ["alwaysVisible"],
891
+ },
892
+ }),
893
+ definition({ dependencyValues }) {
894
+ return {
895
+ setValue: {
896
+ bezierControlsAlwaysVisible:
897
+ dependencyValues.controlChild.length > 0 &&
898
+ dependencyValues.controlChild[0].stateValues.alwaysVisible,
899
+ },
900
+ };
901
+ },
902
+ };
903
+
904
+ stateVariableDefinitions.vectorControlDirections = {
905
+ public: true,
906
+ isLocation: true,
907
+ shadowingInstructions: {
908
+ createComponentOfType: "text",
909
+ },
910
+ isArray: true,
911
+ entryPrefixes: ["vectorControlDirection"],
912
+ forRenderer: true,
913
+ returnArraySizeDependencies: () => ({
914
+ numThroughPoints: {
915
+ dependencyType: "stateVariable",
916
+ variableName: "numThroughPoints",
917
+ },
918
+ }),
919
+ returnArraySize({ dependencyValues }) {
920
+ return [dependencyValues.numThroughPoints];
921
+ },
922
+ returnArrayDependenciesByKey({ arrayKeys }) {
923
+ let dependenciesByKey = {};
924
+ for (let arrayKey of arrayKeys) {
925
+ dependenciesByKey[arrayKey] = {
926
+ controlChild: {
927
+ dependencyType: "child",
928
+ childGroups: ["bezierControls"],
929
+ variableNames: ["direction" + (Number(arrayKey) + 1)],
930
+ },
931
+ };
932
+ }
933
+
934
+ let globalDependencies = {
935
+ haveBezierControls: {
936
+ dependencyType: "stateVariable",
937
+ variableName: "haveBezierControls",
938
+ },
939
+ };
940
+
941
+ return { dependenciesByKey, globalDependencies };
942
+ },
943
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
944
+ let vectorControlDirections = {};
945
+
946
+ for (let arrayKey of arrayKeys) {
947
+ let controlChild = dependencyValuesByKey[arrayKey].controlChild;
948
+
949
+ if (controlChild && controlChild.length > 0) {
950
+ vectorControlDirections[arrayKey] =
951
+ controlChild[0].stateValues["direction" + (Number(arrayKey) + 1)];
952
+ } else {
953
+ vectorControlDirections[arrayKey] = "none";
954
+ }
955
+ }
956
+
957
+ return {
958
+ setValue: { vectorControlDirections },
959
+ };
960
+ },
961
+ inverseArrayDefinitionByKey({
962
+ desiredStateVariableValues,
963
+ dependencyNamesByKey,
964
+ dependencyValuesByKey,
965
+ globalDependencyValues,
966
+ }) {
967
+ // if don't have bezier controls, cannot change directions,
968
+ // they all stay at none so that have a spline
969
+ if (!globalDependencyValues.haveBezierControls) {
970
+ return { success: false };
971
+ }
972
+
973
+ let instructions = [];
974
+ for (let arrayKey in desiredStateVariableValues.vectorControlDirections) {
975
+ let controlChild = dependencyValuesByKey[arrayKey].controlChild;
976
+
977
+ if (controlChild && controlChild.length > 0) {
978
+ instructions.push({
979
+ setDependency: dependencyNamesByKey[arrayKey].controlChild,
980
+ desiredValue:
981
+ desiredStateVariableValues.vectorControlDirections[arrayKey],
982
+ childIndex: 0,
983
+ variableIndex: 0,
984
+ });
985
+ }
986
+ }
987
+
988
+ return {
989
+ success: true,
990
+ instructions,
991
+ };
992
+ },
993
+ };
994
+
995
+ stateVariableDefinitions.hiddenControls = {
996
+ public: true,
997
+ shadowingInstructions: {
998
+ createComponentOfType: "boolean",
999
+ },
1000
+ isArray: true,
1001
+ entryPrefixes: ["hiddenControl"],
1002
+ forRenderer: true,
1003
+ returnArraySizeDependencies: () => ({
1004
+ numThroughPoints: {
1005
+ dependencyType: "stateVariable",
1006
+ variableName: "numThroughPoints",
1007
+ },
1008
+ }),
1009
+ returnArraySize({ dependencyValues }) {
1010
+ return [dependencyValues.numThroughPoints];
1011
+ },
1012
+ returnArrayDependenciesByKey({ arrayKeys }) {
1013
+ let dependenciesByKey = {};
1014
+ for (let arrayKey of arrayKeys) {
1015
+ dependenciesByKey[arrayKey] = {
1016
+ controlChild: {
1017
+ dependencyType: "child",
1018
+ childGroups: ["bezierControls"],
1019
+ variableNames: ["hiddenControl" + (Number(arrayKey) + 1)],
1020
+ },
1021
+ };
1022
+ }
1023
+
1024
+ let globalDependencies = {
1025
+ haveBezierControls: {
1026
+ dependencyType: "stateVariable",
1027
+ variableName: "haveBezierControls",
1028
+ },
1029
+ };
1030
+
1031
+ return { dependenciesByKey, globalDependencies };
1032
+ },
1033
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
1034
+ let hiddenControls = {};
1035
+
1036
+ for (let arrayKey of arrayKeys) {
1037
+ let controlChild = dependencyValuesByKey[arrayKey].controlChild;
1038
+
1039
+ if (controlChild && controlChild.length > 0) {
1040
+ hiddenControls[arrayKey] =
1041
+ controlChild[0].stateValues[
1042
+ "hiddenControl" + (Number(arrayKey) + 1)
1043
+ ];
1044
+ } else {
1045
+ hiddenControls[arrayKey] = false;
1046
+ }
1047
+ }
1048
+
1049
+ return {
1050
+ setValue: { hiddenControls },
1051
+ };
1052
+ },
1053
+ inverseArrayDefinitionByKey({
1054
+ desiredStateVariableValues,
1055
+ dependencyNamesByKey,
1056
+ dependencyValuesByKey,
1057
+ globalDependencyValues,
1058
+ }) {
1059
+ if (!globalDependencyValues.haveBezierControls) {
1060
+ return { success: false };
1061
+ }
1062
+
1063
+ let instructions = [];
1064
+ for (let arrayKey in desiredStateVariableValues.hiddenControls) {
1065
+ let controlChild = dependencyValuesByKey[arrayKey].controlChild;
1066
+
1067
+ if (controlChild && controlChild.length > 0) {
1068
+ instructions.push({
1069
+ setDependency: dependencyNamesByKey[arrayKey].controlChild,
1070
+ desiredValue: desiredStateVariableValues.hiddenControls[arrayKey],
1071
+ childIndex: 0,
1072
+ variableIndex: 0,
1073
+ });
1074
+ }
1075
+ }
1076
+
1077
+ return {
1078
+ success: true,
1079
+ instructions,
1080
+ };
1081
+ },
1082
+ };
1083
+
1084
+ stateVariableDefinitions.controlVectors = {
1085
+ isArray: true,
1086
+ public: true,
1087
+ isLocation: true,
1088
+ shadowingInstructions: {
1089
+ createComponentOfType: "math",
1090
+ addAttributeComponentsShadowingStateVariables:
1091
+ returnRoundingAttributeComponentShadowing(),
1092
+ returnWrappingComponents(prefix) {
1093
+ if (prefix === "controlVectorX") {
1094
+ return [];
1095
+ } else {
1096
+ // controlVector or entire array
1097
+ // wrap inner dimension by both <vector> and <xs>
1098
+ // don't wrap outer dimension (for entire array)
1099
+ return [
1100
+ ["vector", { componentType: "mathList", isAttribute: "xs" }],
1101
+ ];
1102
+ }
1103
+ },
1104
+ },
1105
+ entryPrefixes: ["controlVectorX", "controlVector", "controlVectors"],
1106
+ numDimensions: 3,
1107
+ stateVariablesDeterminingDependencies: [
1108
+ "vectorControlDirections",
1109
+ "numThroughPoints",
1110
+ ],
1111
+ returnEntryDimensions: (prefix) => (prefix === "controlVectors" ? 2 : 1),
1112
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
1113
+ if (arrayEntryPrefix === "controlVectorX") {
1114
+ // controlVectorX3_2_1 is the first component of the second control vector
1115
+ // controlling the third point
1116
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
1117
+ if (
1118
+ indices.length === 3 &&
1119
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
1120
+ ) {
1121
+ if (arraySize) {
1122
+ if (indices.every((x, i) => x < arraySize[i])) {
1123
+ return [String(indices)];
1124
+ } else {
1125
+ return [];
1126
+ }
1127
+ } else {
1128
+ // If not given the array size,
1129
+ // then return the array keys assuming the array is large enough.
1130
+ // Must do this as it is used to determine potential array entries.
1131
+ return [String(indices)];
1132
+ }
1133
+ } else {
1134
+ return [];
1135
+ }
1136
+ } else if (arrayEntryPrefix === "controlVectors") {
1137
+ // controlVectors2 is both vectors controlling the second point
1138
+ let index = Number(varEnding) - 1;
1139
+ if (Number.isInteger(index) && index >= 0) {
1140
+ if (!arraySize) {
1141
+ // if don't' have array size, just return first entry assuming large enough size
1142
+ return [String(index) + ",0,0"];
1143
+ }
1144
+ if (index < arraySize[0]) {
1145
+ let result = [];
1146
+ for (let i = 0; i < arraySize[1]; i++) {
1147
+ let row = [];
1148
+ for (let j = 0; j < arraySize[2]; j++) {
1149
+ row.push(`${index},${i},${j}`);
1150
+ }
1151
+ result.push(row);
1152
+ }
1153
+ return result;
1154
+ } else {
1155
+ return [];
1156
+ }
1157
+ } else {
1158
+ return [];
1159
+ }
1160
+ } else {
1161
+ // controlVector3_2 is all components of the second control vector
1162
+ // controlling the third point
1163
+
1164
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
1165
+ if (
1166
+ !(
1167
+ indices.length === 2 &&
1168
+ indices.every((x) => Number.isInteger(x) && x >= 0)
1169
+ )
1170
+ ) {
1171
+ return [];
1172
+ }
1173
+
1174
+ if (!arraySize) {
1175
+ // if don't' have array size, just return first entry assuming large enough size
1176
+ return [String(indices) + ",0"];
1177
+ }
1178
+ if (indices.every((x, i) => x < arraySize[i])) {
1179
+ return Array.from(
1180
+ Array(arraySize[2]),
1181
+ (_, i) => String(indices) + "," + i,
1182
+ );
1183
+ } else {
1184
+ return [];
1185
+ }
1186
+ }
1187
+ },
1188
+ arrayVarNameFromPropIndex(propIndex, varName) {
1189
+ if (varName === "controlVectors") {
1190
+ if (propIndex.length === 1) {
1191
+ // controlVectors[2] return both vectors controlling point 2
1192
+ return `controlVectors${propIndex[0]}`;
1193
+ }
1194
+ if (propIndex.length === 2) {
1195
+ // controlVectors[3][2] return the second vector controlling point 3
1196
+ return `controlVector${propIndex[0]}_${propIndex[1]}`;
1197
+ } else {
1198
+ // if propIndex has additional entries, ignore them
1199
+ return `controlVectorX${propIndex[0]}_${propIndex[1]}_${propIndex[2]}`;
1200
+ }
1201
+ }
1202
+ // TODO: do we want to handle a case like controlVector3_2[1]?
1203
+ return null;
1204
+ },
1205
+ returnArraySizeDependencies: () => ({
1206
+ numThroughPoints: {
1207
+ dependencyType: "stateVariable",
1208
+ variableName: "numThroughPoints",
1209
+ },
1210
+ numDimensions: {
1211
+ dependencyType: "stateVariable",
1212
+ variableName: "numDimensions",
1213
+ },
1214
+ }),
1215
+ returnArraySize({ dependencyValues }) {
1216
+ return [
1217
+ dependencyValues.numThroughPoints,
1218
+ 2,
1219
+ dependencyValues.numDimensions,
1220
+ ];
1221
+ },
1222
+ returnArrayDependenciesByKey({ arrayKeys, stateValues }) {
1223
+ let globalDependencies = {
1224
+ haveBezierControls: {
1225
+ dependencyType: "stateVariable",
1226
+ variableName: "haveBezierControls",
1227
+ },
1228
+ numThroughPoints: {
1229
+ dependencyType: "stateVariable",
1230
+ variableName: "numThroughPoints",
1231
+ },
1232
+ numDimensions: {
1233
+ dependencyType: "stateVariable",
1234
+ variableName: "numDimensions",
1235
+ },
1236
+ splineTension: {
1237
+ dependencyType: "stateVariable",
1238
+ variableName: "splineTension",
1239
+ },
1240
+ splineForm: {
1241
+ dependencyType: "stateVariable",
1242
+ variableName: "splineForm",
1243
+ },
1244
+ };
1245
+
1246
+ let dependenciesByKey = {};
1247
+
1248
+ for (let arrayKey of arrayKeys) {
1249
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1250
+ let varEndings = arrayIndices.map((x) => x + 1);
1251
+ let jointVarEnding = varEndings.join("_");
1252
+
1253
+ dependenciesByKey[arrayKey] = {
1254
+ direction: {
1255
+ dependencyType: "stateVariable",
1256
+ variableName: "vectorControlDirection" + varEndings[0],
1257
+ },
1258
+ controlChild: {
1259
+ dependencyType: "child",
1260
+ childGroups: ["bezierControls"],
1261
+ variableNames: ["control" + jointVarEnding],
1262
+ },
1263
+ };
1264
+
1265
+ let pointInd = arrayIndices[0];
1266
+ let direction = stateValues.vectorControlDirections[pointInd];
1267
+ let indsToCheck = [];
1268
+ if (direction === "none") {
1269
+ indsToCheck = [pointInd - 1, pointInd, pointInd + 1];
1270
+ } else if (direction === "previous") {
1271
+ indsToCheck = [pointInd, pointInd + 1];
1272
+ } else if (direction === "next") {
1273
+ indsToCheck = [pointInd - 1, pointInd];
1274
+ }
1275
+
1276
+ for (let ind of indsToCheck) {
1277
+ if (ind >= 0 && ind < stateValues.numThroughPoints) {
1278
+ dependenciesByKey[arrayKey]["throughPoint" + (ind + 1)] = {
1279
+ dependencyType: "stateVariable",
1280
+ variableName: "throughPoint" + (ind + 1),
1281
+ };
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ return { globalDependencies, dependenciesByKey };
1287
+ },
1288
+ arrayDefinitionByKey({
1289
+ globalDependencyValues,
1290
+ dependencyValuesByKey,
1291
+ arrayKeys,
1292
+ }) {
1293
+ // console.log('definition of controlVectors for curve')
1294
+ // console.log(JSON.parse(JSON.stringify(dependencyValuesByKey)));
1295
+ // console.log(JSON.parse(JSON.stringify(globalDependencyValues)));
1296
+ // console.log(JSON.parse(JSON.stringify(arrayKeys)))
1297
+
1298
+ let newControlValues = {};
1299
+
1300
+ for (let arrayKey of arrayKeys) {
1301
+ // we have calculated this only for 2D
1302
+ if (globalDependencyValues.numDimensions !== 2) {
1303
+ newControlValues[arrayKey] = me.fromAst(NaN);
1304
+ continue;
1305
+ }
1306
+
1307
+ // since calculate two control vectors at once with symmetric
1308
+ // and both dimensions of each vector at once
1309
+ // it is possible that arrayKey was calculated earlier in the loop
1310
+ if (arrayKey in newControlValues) {
1311
+ continue;
1312
+ }
1313
+
1314
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1315
+ let varEndings = arrayIndices.map((x) => x + 1);
1316
+ let jointVarEnding = varEndings.join("_");
1317
+
1318
+ let pointInd = arrayIndices[0];
1319
+ let vectorInd = arrayIndices[1];
1320
+
1321
+ let direction = dependencyValuesByKey[arrayKey].direction;
1322
+ // if (!direction) {
1323
+ // direction = "none";
1324
+ // }
1325
+
1326
+ if (direction === "none") {
1327
+ // if direction is none, then determine both first and second control vector
1328
+ // via spline (they will be symmetric)
1329
+
1330
+ let point2 =
1331
+ dependencyValuesByKey[arrayKey]["throughPoint" + (pointInd + 1)];
1332
+
1333
+ let point1, point3;
1334
+ if (pointInd > 0) {
1335
+ point1 =
1336
+ dependencyValuesByKey[arrayKey]["throughPoint" + pointInd];
1337
+ }
1338
+ if (pointInd < globalDependencyValues.numThroughPoints) {
1339
+ point3 =
1340
+ dependencyValuesByKey[arrayKey][
1341
+ "throughPoint" + (pointInd + 2)
1342
+ ];
1343
+ }
1344
+
1345
+ let { coordsNumeric, numericEntries } =
1346
+ calculateControlVectorFromSpline({
1347
+ tau: globalDependencyValues.splineTension,
1348
+ eps: numerics.eps,
1349
+ splineForm: globalDependencyValues.splineForm,
1350
+ point1,
1351
+ point2,
1352
+ point3,
1353
+ });
1354
+
1355
+ for (let dim = 0; dim < 2; dim++) {
1356
+ let arrayKeyDim = pointInd + "," + vectorInd + "," + dim;
1357
+ let flippedArrayKeyDim =
1358
+ pointInd + "," + (1 - vectorInd) + "," + dim;
1359
+
1360
+ if (vectorInd === 0) {
1361
+ // arrayKey corresponds to first vector
1362
+ newControlValues[arrayKeyDim] = coordsNumeric[dim];
1363
+ newControlValues[flippedArrayKeyDim] = me.fromAst(
1364
+ -coordsNumeric[dim].tree,
1365
+ );
1366
+ } else {
1367
+ // arrayKey corresponds to second vector
1368
+ newControlValues[arrayKeyDim] = me.fromAst(
1369
+ -coordsNumeric[dim].tree,
1370
+ );
1371
+ newControlValues[flippedArrayKeyDim] = coordsNumeric[dim];
1372
+ }
1373
+ }
1374
+ } else if (
1375
+ (arrayIndices[1] === 0 && direction === "next") ||
1376
+ (arrayIndices[1] === 1 && direction === "previous")
1377
+ ) {
1378
+ // calculate control vector from spline
1379
+
1380
+ // only two of these three will be defined
1381
+ let point1 =
1382
+ dependencyValuesByKey[arrayKey]["throughPoint" + pointInd];
1383
+ let point2 =
1384
+ dependencyValuesByKey[arrayKey]["throughPoint" + (pointInd + 1)];
1385
+ let point3 =
1386
+ dependencyValuesByKey[arrayKey]["throughPoint" + (pointInd + 2)];
1387
+
1388
+ let { coordsNumeric, numericEntries } =
1389
+ calculateControlVectorFromSpline({
1390
+ tau: globalDependencyValues.splineTension,
1391
+ eps: numerics.eps,
1392
+ splineForm: globalDependencyValues.splineForm,
1393
+ point1: point1 ? point1 : point3,
1394
+ point2,
1395
+ point3: undefined,
1396
+ });
1397
+
1398
+ for (let dim = 0; dim < 2; dim++) {
1399
+ let arrayKeyDim = pointInd + "," + vectorInd + "," + dim;
1400
+ newControlValues[arrayKeyDim] = coordsNumeric[dim];
1401
+ }
1402
+ } else {
1403
+ // if have a vector from control child, use that
1404
+ newControlValues[arrayKey] =
1405
+ dependencyValuesByKey[arrayKey].controlChild[0].stateValues[
1406
+ "control" + jointVarEnding
1407
+ ];
1408
+ }
1409
+ }
1410
+
1411
+ return {
1412
+ setValue: {
1413
+ controlVectors: newControlValues,
1414
+ },
1415
+ };
1416
+ },
1417
+ inverseArrayDefinitionByKey({
1418
+ desiredStateVariableValues,
1419
+ dependencyNamesByKey,
1420
+ dependencyValuesByKey,
1421
+ globalDependencyValues,
1422
+ }) {
1423
+ // console.log('inverse definition of controlVectors for curve')
1424
+ // console.log(JSON.parse(JSON.stringify(desiredStateVariableValues)));
1425
+ // console.log(JSON.parse(JSON.stringify(dependencyValuesByKey)));
1426
+ // console.log(JSON.parse(JSON.stringify(globalDependencyValues)));
1427
+
1428
+ // if don't have bezier controls, cannot change control vectors,
1429
+ // they all stay at those calculated from spline
1430
+ // Also can't change if aren't in 2D
1431
+ if (
1432
+ !globalDependencyValues.haveBezierControls ||
1433
+ globalDependencyValues.numDimensions !== 2
1434
+ ) {
1435
+ return { success: false };
1436
+ }
1437
+
1438
+ let instructions = [];
1439
+ for (let arrayKey in desiredStateVariableValues.controlVectors) {
1440
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1441
+ let varEndings = arrayIndices.map((x) => x + 1);
1442
+ let jointVarEnding = varEndings.join("_");
1443
+
1444
+ // if find the control on the control child,
1445
+ // set its value to the desired value
1446
+ let controlChild = dependencyValuesByKey[arrayKey].controlChild;
1447
+ if (controlChild.length > 0) {
1448
+ let control =
1449
+ controlChild[0].stateValues["control" + jointVarEnding];
1450
+ if (control) {
1451
+ instructions.push({
1452
+ setDependency: dependencyNamesByKey[arrayKey].controlChild,
1453
+ desiredValue:
1454
+ desiredStateVariableValues.controlVectors[arrayKey],
1455
+ childIndex: 0,
1456
+ variableIndex: 0,
1457
+ });
1458
+ }
1459
+ }
1460
+ }
1461
+
1462
+ return {
1463
+ success: true,
1464
+ instructions,
1465
+ };
1466
+ },
1467
+ };
1468
+
1469
+ stateVariableDefinitions.numericalControlVectors = {
1470
+ isArray: true,
1471
+ entryPrefixes: ["numericalControlVector"],
1472
+ forRenderer: true,
1473
+ numDimensions: 2,
1474
+ returnArraySizeDependencies: () => ({
1475
+ numThroughPoints: {
1476
+ dependencyType: "stateVariable",
1477
+ variableName: "numThroughPoints",
1478
+ },
1479
+ }),
1480
+ returnArraySize({ dependencyValues }) {
1481
+ return [dependencyValues.numThroughPoints, 2];
1482
+ },
1483
+ returnArrayDependenciesByKey({ arrayKeys }) {
1484
+ let dependenciesByKey = {};
1485
+
1486
+ for (let arrayKey of arrayKeys) {
1487
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1488
+ let varEndings = arrayIndices.map((x) => x + 1);
1489
+ let jointVarEnding = varEndings.join("_");
1490
+
1491
+ dependenciesByKey[arrayKey] = {
1492
+ controlVector: {
1493
+ dependencyType: "stateVariable",
1494
+ variableName: "controlVector" + jointVarEnding,
1495
+ },
1496
+ };
1497
+ }
1498
+
1499
+ return { dependenciesByKey };
1500
+ },
1501
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
1502
+ // control vectors already have numerical entries,
1503
+ // so just need to take tree from math expressions
1504
+
1505
+ let numericalControlVectors = {};
1506
+
1507
+ for (let arrayKey of arrayKeys) {
1508
+ let vect = dependencyValuesByKey[arrayKey].controlVector.map(
1509
+ (x) => x.tree,
1510
+ );
1511
+ numericalControlVectors[arrayKey] = vect;
1512
+ }
1513
+
1514
+ return { setValue: { numericalControlVectors } };
1515
+ },
1516
+ };
1517
+
1518
+ stateVariableDefinitions.controlPoints = {
1519
+ isArray: true,
1520
+ public: true,
1521
+ isLocation: true,
1522
+ shadowingInstructions: {
1523
+ createComponentOfType: "math",
1524
+ addAttributeComponentsShadowingStateVariables:
1525
+ returnRoundingAttributeComponentShadowing(),
1526
+ returnWrappingComponents(prefix) {
1527
+ if (prefix === "controlPointX") {
1528
+ return [];
1529
+ } else {
1530
+ // controlPoint or entire array
1531
+ // wrap inner dimension by both <point> and <xs>
1532
+ // don't wrap outer dimension (for entire array)
1533
+ return [
1534
+ ["point", { componentType: "mathList", isAttribute: "xs" }],
1535
+ ];
1536
+ }
1537
+ },
1538
+ },
1539
+ entryPrefixes: ["controlPointX", "controlPoint", "controlPoints"],
1540
+ numDimensions: 3,
1541
+ returnEntryDimensions: (prefix) => (prefix === "controlPoints" ? 2 : 1),
1542
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
1543
+ if (arrayEntryPrefix === "controlPointX") {
1544
+ // controlPointX3_2_1 is the first component of the second control point
1545
+ // controlling the third point
1546
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
1547
+ if (
1548
+ indices.length === 3 &&
1549
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
1550
+ ) {
1551
+ if (arraySize) {
1552
+ if (indices.every((x, i) => x < arraySize[i])) {
1553
+ return [String(indices)];
1554
+ } else {
1555
+ return [];
1556
+ }
1557
+ } else {
1558
+ // If not given the array size,
1559
+ // then return the array keys assuming the array is large enough.
1560
+ // Must do this as it is used to determine potential array entries.
1561
+ return [String(indices)];
1562
+ }
1563
+ } else {
1564
+ return [];
1565
+ }
1566
+ } else if (arrayEntryPrefix === "controlPoints") {
1567
+ // controlPoints2 is both points controlling the second point
1568
+ let index = Number(varEnding) - 1;
1569
+ if (Number.isInteger(index) && index >= 0) {
1570
+ if (!arraySize) {
1571
+ // if don't' have array size, just return first entry assuming large enough size
1572
+ return [String(index) + ",0,0"];
1573
+ }
1574
+ if (index < arraySize[0]) {
1575
+ let result = [];
1576
+ for (let i = 0; i < arraySize[1]; i++) {
1577
+ let row = [];
1578
+ for (let j = 0; j < arraySize[2]; j++) {
1579
+ row.push(`${index},${i},${j}`);
1580
+ }
1581
+ result.push(row);
1582
+ }
1583
+ return result;
1584
+ } else {
1585
+ return [];
1586
+ }
1587
+ } else {
1588
+ return [];
1589
+ }
1590
+ } else {
1591
+ // controlPoint3_2 is all components of the second control point
1592
+ // controlling the third point
1593
+
1594
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
1595
+ if (
1596
+ !(
1597
+ indices.length === 2 &&
1598
+ indices.every((x) => Number.isInteger(x) && x >= 0)
1599
+ )
1600
+ ) {
1601
+ return [];
1602
+ }
1603
+
1604
+ if (!arraySize) {
1605
+ // if don't' have array size, just return first entry assuming large enough size
1606
+ return [String(indices) + ",0"];
1607
+ }
1608
+ if (indices.every((x, i) => x < arraySize[i])) {
1609
+ return Array.from(
1610
+ Array(arraySize[2]),
1611
+ (_, i) => String(indices) + "," + i,
1612
+ );
1613
+ } else {
1614
+ return [];
1615
+ }
1616
+ }
1617
+ },
1618
+ arrayVarNameFromPropIndex(propIndex, varName) {
1619
+ if (varName === "controlPoints") {
1620
+ if (propIndex.length === 1) {
1621
+ // controlPoints[2] return both points controlling point 2
1622
+ return `controlPoints${propIndex[0]}`;
1623
+ }
1624
+ if (propIndex.length === 2) {
1625
+ // controlPoints[3][2] return the second point controlling point 3
1626
+ return `controlPoint${propIndex[0]}_${propIndex[1]}`;
1627
+ } else {
1628
+ // if propIndex has additional entries, ignore them
1629
+ return `controlPointX${propIndex[0]}_${propIndex[1]}_${propIndex[2]}`;
1630
+ }
1631
+ }
1632
+ // TODO: do we want to handle a case like controlPoint3_2[1]?
1633
+ return null;
1634
+ },
1635
+ returnArraySizeDependencies: () => ({
1636
+ numThroughPoints: {
1637
+ dependencyType: "stateVariable",
1638
+ variableName: "numThroughPoints",
1639
+ },
1640
+ numDimensions: {
1641
+ dependencyType: "stateVariable",
1642
+ variableName: "numDimensions",
1643
+ },
1644
+ }),
1645
+ returnArraySize({ dependencyValues }) {
1646
+ return [
1647
+ dependencyValues.numThroughPoints,
1648
+ 2,
1649
+ dependencyValues.numDimensions,
1650
+ ];
1651
+ },
1652
+ returnArrayDependenciesByKey({ arrayKeys }) {
1653
+ let globalDependencies = {
1654
+ haveBezierControls: {
1655
+ dependencyType: "stateVariable",
1656
+ variableName: "haveBezierControls",
1657
+ },
1658
+ numDimensions: {
1659
+ dependencyType: "stateVariable",
1660
+ variableName: "numDimensions",
1661
+ },
1662
+ };
1663
+
1664
+ let dependenciesByKey = {};
1665
+
1666
+ for (let arrayKey of arrayKeys) {
1667
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1668
+ let varEndings = arrayIndices.map((x) => x + 1);
1669
+ let jointVarEnding = varEndings.join("_");
1670
+
1671
+ dependenciesByKey[arrayKey] = {
1672
+ throughPointX: {
1673
+ dependencyType: "stateVariable",
1674
+ variableName:
1675
+ "throughPointX" + varEndings[0] + "_" + varEndings[2],
1676
+ },
1677
+ controlVectorX: {
1678
+ dependencyType: "stateVariable",
1679
+ variableName: "controlVectorX" + jointVarEnding,
1680
+ },
1681
+ };
1682
+ }
1683
+
1684
+ return { globalDependencies, dependenciesByKey };
1685
+ },
1686
+ arrayDefinitionByKey({
1687
+ globalDependencyValues,
1688
+ dependencyValuesByKey,
1689
+ arrayKeys,
1690
+ }) {
1691
+ // console.log('definition of controlPoints for curve')
1692
+ // console.log(JSON.parse(JSON.stringify(dependencyValuesByKey)));
1693
+ // console.log(JSON.parse(JSON.stringify(globalDependencyValues)));
1694
+ // console.log(JSON.parse(JSON.stringify(arrayKeys)))
1695
+
1696
+ let newControlValues = {};
1697
+
1698
+ for (let arrayKey of arrayKeys) {
1699
+ // we have calculated this only for 2D
1700
+ if (globalDependencyValues.numDimensions !== 2) {
1701
+ newControlValues[arrayKey] = me.fromAst(NaN);
1702
+ } else {
1703
+ let vectorX = dependencyValuesByKey[arrayKey].controlVectorX;
1704
+
1705
+ if (vectorX) {
1706
+ let pointX =
1707
+ dependencyValuesByKey[
1708
+ arrayKey
1709
+ ].throughPointX.evaluate_to_constant();
1710
+ newControlValues[arrayKey] = me.fromAst(pointX + vectorX.tree);
1711
+ } else {
1712
+ newControlValues[arrayKey] = null;
1713
+ }
1714
+ }
1715
+ }
1716
+ return {
1717
+ setValue: {
1718
+ controlPoints: newControlValues,
1719
+ },
1720
+ };
1721
+ },
1722
+ inverseArrayDefinitionByKey({
1723
+ desiredStateVariableValues,
1724
+ dependencyNamesByKey,
1725
+ dependencyValuesByKey,
1726
+ globalDependencyValues,
1727
+ }) {
1728
+ // if don't have bezier controls, cannot change control vectors,
1729
+ // they all stay at those calculated from spline
1730
+ // Also can't change if aren't in 2D
1731
+ if (
1732
+ !globalDependencyValues.haveBezierControls ||
1733
+ globalDependencyValues.numDimensions !== 2
1734
+ ) {
1735
+ return { success: false };
1736
+ }
1737
+
1738
+ let instructions = [];
1739
+ for (let arrayKey in desiredStateVariableValues.controlPoints) {
1740
+ // if find the control on the control child,
1741
+ // set its value to the desired value
1742
+ let vectorX = dependencyValuesByKey[arrayKey].controlVectorX;
1743
+ if (vectorX) {
1744
+ let pointX = dependencyValuesByKey[arrayKey].throughPointX;
1745
+
1746
+ let desiredPoint =
1747
+ desiredStateVariableValues.controlPoints[arrayKey];
1748
+ if (desiredPoint.tree) {
1749
+ desiredPoint = desiredPoint.tree;
1750
+ }
1751
+ let desiredValue = me.fromAst([
1752
+ "+",
1753
+ desiredPoint,
1754
+ ["-", pointX.tree],
1755
+ ]);
1756
+
1757
+ instructions.push({
1758
+ setDependency: dependencyNamesByKey[arrayKey].controlVectorX,
1759
+ desiredValue,
1760
+ });
1761
+ }
1762
+ }
1763
+
1764
+ return {
1765
+ success: true,
1766
+ instructions,
1767
+ };
1768
+ },
1769
+ };
1770
+
1771
+ stateVariableDefinitions.numericalControlPoints = {
1772
+ isArray: true,
1773
+ entryPrefixes: ["numericalControlPoint"],
1774
+ forRenderer: true,
1775
+ numDimensions: 2,
1776
+ returnArraySizeDependencies: () => ({
1777
+ numThroughPoints: {
1778
+ dependencyType: "stateVariable",
1779
+ variableName: "numThroughPoints",
1780
+ },
1781
+ }),
1782
+ returnArraySize({ dependencyValues }) {
1783
+ return [dependencyValues.numThroughPoints, 2];
1784
+ },
1785
+ returnArrayDependenciesByKey({ arrayKeys }) {
1786
+ let dependenciesByKey = {};
1787
+
1788
+ for (let arrayKey of arrayKeys) {
1789
+ let arrayIndices = arrayKey.split(",").map((x) => Number(x));
1790
+ let varEndings = arrayIndices.map((x) => x + 1);
1791
+ let jointVarEnding = varEndings.join("_");
1792
+
1793
+ dependenciesByKey[arrayKey] = {
1794
+ controlPoint: {
1795
+ dependencyType: "stateVariable",
1796
+ variableName: "controlPoint" + jointVarEnding,
1797
+ },
1798
+ };
1799
+ }
1800
+
1801
+ return { dependenciesByKey };
1802
+ },
1803
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
1804
+ // control points have numerical entries, to just take expression trees
1805
+ let numericalControlPoints = {};
1806
+
1807
+ for (let arrayKey of arrayKeys) {
1808
+ let pt = dependencyValuesByKey[arrayKey].controlPoint.map(
1809
+ (x) => x.tree,
1810
+ );
1811
+ numericalControlPoints[arrayKey] = pt;
1812
+ }
1813
+
1814
+ return { setValue: { numericalControlPoints } };
1815
+ },
1816
+ };
1817
+
1818
+ stateVariableDefinitions.splineCoeffs = {
1819
+ isArray: true,
1820
+ returnArraySizeDependencies: () => ({
1821
+ numThroughPoints: {
1822
+ dependencyType: "stateVariable",
1823
+ variableName: "numThroughPoints",
1824
+ },
1825
+ }),
1826
+ returnArraySize({ dependencyValues }) {
1827
+ return [dependencyValues.numThroughPoints - 1];
1828
+ },
1829
+ returnArrayDependenciesByKey({ arrayKeys }) {
1830
+ let dependenciesByKey = {};
1831
+
1832
+ for (let arrayKey of arrayKeys) {
1833
+ let ind1 = Number(arrayKey) + 1;
1834
+ let ind2 = ind1 + 1;
1835
+
1836
+ dependenciesByKey[arrayKey] = {
1837
+ previousPoint: {
1838
+ dependencyType: "stateVariable",
1839
+ variableName: "numericalThroughPoint" + ind1,
1840
+ },
1841
+ nextPoint: {
1842
+ dependencyType: "stateVariable",
1843
+ variableName: "numericalThroughPoint" + ind2,
1844
+ },
1845
+ previousVector: {
1846
+ dependencyType: "stateVariable",
1847
+ variableName: "numericalControlVector" + ind1 + "_2",
1848
+ },
1849
+ nextVector: {
1850
+ dependencyType: "stateVariable",
1851
+ variableName: "numericalControlVector" + ind2 + "_1",
1852
+ },
1853
+ };
1854
+ }
1855
+
1856
+ return { dependenciesByKey };
1857
+ },
1858
+ arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) {
1859
+ let newSpineCoeffs = {};
1860
+
1861
+ for (let arrayKey of arrayKeys) {
1862
+ let p1 = dependencyValuesByKey[arrayKey].previousPoint;
1863
+ let p2 = dependencyValuesByKey[arrayKey].nextPoint;
1864
+ let cv1 = dependencyValuesByKey[arrayKey].previousVector;
1865
+ let cv2 = dependencyValuesByKey[arrayKey].nextVector;
1866
+
1867
+ let c = [];
1868
+ for (let dim = 0; dim < 2; dim++) {
1869
+ c.push(
1870
+ initCubicPoly(p1[dim], p2[dim], 3 * cv1[dim], -3 * cv2[dim]),
1871
+ );
1872
+ }
1873
+
1874
+ newSpineCoeffs[arrayKey] = c;
1875
+ }
1876
+
1877
+ return {
1878
+ setValue: {
1879
+ splineCoeffs: newSpineCoeffs,
1880
+ },
1881
+ };
1882
+ },
1883
+ };
1884
+
1885
+ stateVariableDefinitions.extrapolateBackwardCoeffs = {
1886
+ stateVariablesDeterminingDependencies: ["extrapolateBackward"],
1887
+ additionalStateVariablesDefined: [
1888
+ {
1889
+ variableName: "extrapolateBackwardMode",
1890
+ public: true,
1891
+ shadowingInstructions: {
1892
+ createComponentOfType: "text",
1893
+ },
1894
+ },
1895
+ ],
1896
+ returnDependencies({ stateValues }) {
1897
+ let dependencies = {
1898
+ extrapolateBackward: {
1899
+ dependencyType: "stateVariable",
1900
+ variableName: "extrapolateBackward",
1901
+ },
1902
+ numThroughPoints: {
1903
+ dependencyType: "stateVariable",
1904
+ variableName: "numThroughPoints",
1905
+ },
1906
+ };
1907
+
1908
+ if (stateValues.extrapolateBackward) {
1909
+ dependencies.firstSplineCoeffs = {
1910
+ dependencyType: "stateVariable",
1911
+ variableName: "splineCoeffs1",
1912
+ };
1913
+ dependencies.graphXmin = {
1914
+ dependencyType: "stateVariable",
1915
+ variableName: "graphXmin",
1916
+ };
1917
+ dependencies.graphXmax = {
1918
+ dependencyType: "stateVariable",
1919
+ variableName: "graphXmax",
1920
+ };
1921
+ dependencies.graphYmin = {
1922
+ dependencyType: "stateVariable",
1923
+ variableName: "graphYmin",
1924
+ };
1925
+ dependencies.graphYmax = {
1926
+ dependencyType: "stateVariable",
1927
+ variableName: "graphYmax",
1928
+ };
1929
+ }
1930
+
1931
+ return dependencies;
1932
+ },
1933
+ definition({ dependencyValues }) {
1934
+ if (
1935
+ !dependencyValues.extrapolateBackward ||
1936
+ !dependencyValues.firstSplineCoeffs
1937
+ ) {
1938
+ return {
1939
+ setValue: {
1940
+ extrapolateBackwardCoeffs: null,
1941
+ extrapolateBackwardMode: "",
1942
+ },
1943
+ };
1944
+ }
1945
+
1946
+ // extrapolate as a parabola oriented with the coordinate axes
1947
+ // that matches the curvature of the first spline segment
1948
+
1949
+ let cx = dependencyValues.firstSplineCoeffs[0];
1950
+ let cy = dependencyValues.firstSplineCoeffs[1];
1951
+
1952
+ let x0 = cx[0];
1953
+ let xp0 = cx[1];
1954
+ let xpp0 = 2 * cx[2];
1955
+
1956
+ let y0 = cy[0];
1957
+ let yp0 = cy[1];
1958
+ let ypp0 = 2 * cy[2];
1959
+
1960
+ let d = xp0 * xp0 + yp0 * yp0;
1961
+
1962
+ let fac = (yp0 * xpp0 - xp0 * ypp0) / (d * d);
1963
+
1964
+ if (
1965
+ Math.abs(fac) < 1e-12 ||
1966
+ Math.abs(xp0) < 1e-12 ||
1967
+ Math.abs(yp0) < 1e-12
1968
+ ) {
1969
+ // curvature is zero or pointed at right angle
1970
+ // extrapolate as straight line
1971
+
1972
+ let xpEffective = xp0;
1973
+ let ypEffective = yp0;
1974
+
1975
+ if (
1976
+ dependencyValues.graphXmin !== null &&
1977
+ dependencyValues.graphXmax !== null &&
1978
+ dependencyValues.graphYmin !== null &&
1979
+ dependencyValues.graphYmax !== null
1980
+ ) {
1981
+ // if in graph, scale speed if needed to make sure leave graph
1982
+
1983
+ let xscale =
1984
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
1985
+ let yscale =
1986
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
1987
+ let tMax = dependencyValues.numThroughPoints - 1;
1988
+
1989
+ let scaleSpeedToReachXEdge = xscale / tMax / Math.abs(xpEffective);
1990
+ let scaleSpeedToReachYEdge = yscale / tMax / Math.abs(ypEffective);
1991
+
1992
+ let minScale = Math.min(
1993
+ scaleSpeedToReachXEdge,
1994
+ scaleSpeedToReachYEdge,
1995
+ );
1996
+
1997
+ if (minScale > 1) {
1998
+ xpEffective *= minScale;
1999
+ ypEffective *= minScale;
2000
+ }
2001
+ }
2002
+
2003
+ let c = [
2004
+ [x0, xpEffective, 0],
2005
+ [y0, ypEffective, 0],
2006
+ ];
2007
+
2008
+ return {
2009
+ setValue: {
2010
+ extrapolateBackwardCoeffs: c,
2011
+ extrapolateBackwardMode: "line",
2012
+ },
2013
+ };
2014
+ }
2015
+
2016
+ let dTx = yp0 * fac;
2017
+ let dTy = -xp0 * fac;
2018
+
2019
+ if (dTx * xp0 > 0) {
2020
+ // if curving toward the vertical direction
2021
+ // orient the parabola vertically
2022
+
2023
+ let r = dTx / dTy;
2024
+ let rFactor = (1 + r * r) ** 2;
2025
+
2026
+ let xpEffective = xp0;
2027
+
2028
+ if (
2029
+ dependencyValues.graphXmin !== null &&
2030
+ dependencyValues.graphXmax !== null &&
2031
+ dependencyValues.graphYmin !== null &&
2032
+ dependencyValues.graphYmax !== null
2033
+ ) {
2034
+ // if we are in graph, make sure that the speed of the parametrization
2035
+ // is fast enough for the curve to leave the graph while
2036
+ // the parameter increases by the amount numThroughPoints - 1
2037
+
2038
+ let xscale =
2039
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
2040
+ let yscale =
2041
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
2042
+
2043
+ let tMax = dependencyValues.numThroughPoints - 1;
2044
+
2045
+ let minSpeedToReachXEdge = xscale / tMax;
2046
+
2047
+ // y = a*v^2*t^2 + b*v*t
2048
+ // where a = dTy*rFactor/2 and b = -r.
2049
+ // Find minimum v where reach edge by tMax
2050
+
2051
+ let minSpeedToReachYEdge = Infinity;
2052
+
2053
+ if (dTy !== 0) {
2054
+ let alpha = ((dTy * rFactor) / 2) * tMax * tMax;
2055
+ let beta = -r * tMax;
2056
+ // y = alpha*v^2 + beta*v
2057
+ // if alpha > 0, solve for y = yscale
2058
+ // else if alpha < 0 solve for y = -yscale
2059
+ let sr = Math.sqrt(beta * beta + 4 * Math.abs(alpha) * yscale);
2060
+ minSpeedToReachYEdge =
2061
+ (Math.abs(beta) + sr) / (2 * Math.abs(alpha));
2062
+ }
2063
+
2064
+ let minSpeed = Math.min(minSpeedToReachXEdge, minSpeedToReachYEdge);
2065
+
2066
+ if (minSpeed > Math.abs(xpEffective)) {
2067
+ xpEffective *= minSpeed / Math.abs(xpEffective);
2068
+ }
2069
+ }
2070
+
2071
+ let v = -xpEffective * r;
2072
+ let a = dTy * xpEffective * xpEffective * rFactor;
2073
+
2074
+ let c = [
2075
+ [x0, xpEffective, 0],
2076
+ [y0, v, a / 2],
2077
+ ];
2078
+
2079
+ return {
2080
+ setValue: {
2081
+ extrapolateBackwardCoeffs: c,
2082
+ extrapolateBackwardMode: "parabolaVertical",
2083
+ },
2084
+ };
2085
+ } else {
2086
+ // if curving toward the horizontal direction
2087
+ // orient the parabola horizontally
2088
+
2089
+ let r = dTy / dTx;
2090
+ let rFactor = (1 + r * r) ** 2;
2091
+
2092
+ let ypEffective = yp0;
2093
+
2094
+ if (
2095
+ dependencyValues.graphXmin !== null &&
2096
+ dependencyValues.graphXmax !== null &&
2097
+ dependencyValues.graphYmin !== null &&
2098
+ dependencyValues.graphYmax !== null
2099
+ ) {
2100
+ // if we are in graph, make sure that the speed of the parametrization
2101
+ // is fast enough for the curve to leave the graph while
2102
+ // the parameter increases by the amount numThroughPoints - 1
2103
+
2104
+ let xscale =
2105
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
2106
+ let yscale =
2107
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
2108
+
2109
+ let tMax = dependencyValues.numThroughPoints - 1;
2110
+
2111
+ let minSpeedToReachYEdge = yscale / tMax;
2112
+
2113
+ // y = a*v^2*t^2 + b*v*t
2114
+ // where a = dTy*rFactor/2 and b = -r.
2115
+ // Find minimum v where reach edge by tMax
2116
+
2117
+ let minSpeedToReachXEdge = Infinity;
2118
+
2119
+ if (dTx !== 0) {
2120
+ let alpha = ((dTx * rFactor) / 2) * tMax * tMax;
2121
+ let beta = -r * tMax;
2122
+ // c = alpha*v^2 + beta*v
2123
+ // if alpha > 0, solve for x = xscale
2124
+ // else if alpha < 0 solve for x = -xscale
2125
+ let sr = Math.sqrt(beta * beta + 4 * Math.abs(alpha) * xscale);
2126
+ minSpeedToReachXEdge =
2127
+ (Math.abs(beta) + sr) / (2 * Math.abs(alpha));
2128
+ }
2129
+
2130
+ let minSpeed = Math.min(minSpeedToReachXEdge, minSpeedToReachYEdge);
2131
+
2132
+ if (minSpeed > Math.abs(ypEffective)) {
2133
+ ypEffective *= minSpeed / Math.abs(ypEffective);
2134
+ }
2135
+ }
2136
+
2137
+ let v = -ypEffective * r;
2138
+ let a = dTx * ypEffective * ypEffective * rFactor;
2139
+
2140
+ let c = [
2141
+ [x0, v, a / 2],
2142
+ [y0, ypEffective, 0],
2143
+ ];
2144
+
2145
+ return {
2146
+ setValue: {
2147
+ extrapolateBackwardCoeffs: c,
2148
+ extrapolateBackwardMode: "parabolaHorizontal",
2149
+ },
2150
+ };
2151
+ }
2152
+ },
2153
+ };
2154
+
2155
+ stateVariableDefinitions.extrapolateForwardCoeffs = {
2156
+ stateVariablesDeterminingDependencies: [
2157
+ "numThroughPoints",
2158
+ "extrapolateForward",
2159
+ ],
2160
+ additionalStateVariablesDefined: [
2161
+ {
2162
+ variableName: "extrapolateForwardMode",
2163
+ public: true,
2164
+ shadowingInstructions: {
2165
+ createComponentOfType: "text",
2166
+ },
2167
+ },
2168
+ ],
2169
+ returnDependencies({ stateValues }) {
2170
+ let dependencies = {
2171
+ extrapolateForward: {
2172
+ dependencyType: "stateVariable",
2173
+ variableName: "extrapolateForward",
2174
+ },
2175
+ numThroughPoints: {
2176
+ dependencyType: "stateVariable",
2177
+ variableName: "numThroughPoints",
2178
+ },
2179
+ };
2180
+
2181
+ if (
2182
+ stateValues.extrapolateForward &&
2183
+ stateValues.numThroughPoints >= 2
2184
+ ) {
2185
+ dependencies.lastSplineCoeffs = {
2186
+ dependencyType: "stateVariable",
2187
+ variableName: "splineCoeffs" + (stateValues.numThroughPoints - 1),
2188
+ };
2189
+ dependencies.graphXmin = {
2190
+ dependencyType: "stateVariable",
2191
+ variableName: "graphXmin",
2192
+ };
2193
+ dependencies.graphXmax = {
2194
+ dependencyType: "stateVariable",
2195
+ variableName: "graphXmax",
2196
+ };
2197
+ dependencies.graphYmin = {
2198
+ dependencyType: "stateVariable",
2199
+ variableName: "graphYmin",
2200
+ };
2201
+ dependencies.graphYmax = {
2202
+ dependencyType: "stateVariable",
2203
+ variableName: "graphYmax",
2204
+ };
2205
+ }
2206
+ return dependencies;
2207
+ },
2208
+ definition({ dependencyValues }) {
2209
+ if (
2210
+ !dependencyValues.extrapolateForward ||
2211
+ !dependencyValues.lastSplineCoeffs
2212
+ ) {
2213
+ return {
2214
+ setValue: {
2215
+ extrapolateForwardCoeffs: null,
2216
+ extrapolateForwardMode: "",
2217
+ },
2218
+ };
2219
+ }
2220
+
2221
+ // extrapolate as a parabola oriented with the coordinate axes
2222
+ // that matches the curvature of the first spline segment
2223
+
2224
+ let cx = dependencyValues.lastSplineCoeffs[0];
2225
+ let cy = dependencyValues.lastSplineCoeffs[1];
2226
+
2227
+ let x0 = cx[0] + cx[1] + cx[2] + cx[3];
2228
+ let xp0 = cx[1] + 2 * cx[2] + 3 * cx[3];
2229
+ let xpp0 = 2 * cx[2] + 6 * cx[3];
2230
+
2231
+ let y0 = cy[0] + cy[1] + cy[2] + cy[3];
2232
+ let yp0 = cy[1] + 2 * cy[2] + 3 * cy[3];
2233
+ let ypp0 = 2 * cy[2] + 6 * cy[3];
2234
+
2235
+ let d = xp0 * xp0 + yp0 * yp0;
2236
+
2237
+ let fac = (yp0 * xpp0 - xp0 * ypp0) / (d * d);
2238
+
2239
+ if (
2240
+ Math.abs(fac) < 1e-12 ||
2241
+ Math.abs(xp0) < 1e-12 ||
2242
+ Math.abs(yp0) < 1e-12
2243
+ ) {
2244
+ // curvature is zero or pointed at right angle
2245
+ // extrapolate as straight line
2246
+
2247
+ let xpEffective = xp0;
2248
+ let ypEffective = yp0;
2249
+
2250
+ if (
2251
+ dependencyValues.graphXmin !== null &&
2252
+ dependencyValues.graphXmax !== null &&
2253
+ dependencyValues.graphYmin !== null &&
2254
+ dependencyValues.graphYmax !== null
2255
+ ) {
2256
+ // if in graph, scale speed if needed to make sure leave graph
2257
+
2258
+ let xscale =
2259
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
2260
+ let yscale =
2261
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
2262
+ let tMax = dependencyValues.numThroughPoints - 1;
2263
+
2264
+ let scaleSpeedToReachXEdge = xscale / tMax / Math.abs(xpEffective);
2265
+ let scaleSpeedToReachYEdge = yscale / tMax / Math.abs(ypEffective);
2266
+
2267
+ let minScale = Math.min(
2268
+ scaleSpeedToReachXEdge,
2269
+ scaleSpeedToReachYEdge,
2270
+ );
2271
+
2272
+ if (minScale > 1) {
2273
+ xpEffective *= minScale;
2274
+ ypEffective *= minScale;
2275
+ }
2276
+ }
2277
+
2278
+ let c = [
2279
+ [x0, xpEffective, 0],
2280
+ [y0, ypEffective, 0],
2281
+ ];
2282
+
2283
+ return {
2284
+ setValue: {
2285
+ extrapolateForwardCoeffs: c,
2286
+ extrapolateForwardMode: "line",
2287
+ },
2288
+ };
2289
+ }
2290
+
2291
+ let dTx = yp0 * fac;
2292
+ let dTy = -xp0 * fac;
2293
+
2294
+ if (dTx * xp0 < 0) {
2295
+ // if curving toward the vertical direction
2296
+ // orient the parabola vertically
2297
+
2298
+ let r = dTx / dTy;
2299
+ let rFactor = (1 + r * r) ** 2;
2300
+
2301
+ let xpEffective = xp0;
2302
+
2303
+ if (
2304
+ dependencyValues.graphXmin !== null &&
2305
+ dependencyValues.graphXmax !== null &&
2306
+ dependencyValues.graphYmin !== null &&
2307
+ dependencyValues.graphYmax !== null
2308
+ ) {
2309
+ // if we are in graph, make sure that the speed of the parametrization
2310
+ // is fast enough for the curve to leave the graph while
2311
+ // the parameter increases by the amount numThroughPoints - 1
2312
+
2313
+ let xscale =
2314
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
2315
+ let yscale =
2316
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
2317
+
2318
+ let tMax = dependencyValues.numThroughPoints - 1;
2319
+
2320
+ let minSpeedToReachXEdge = xscale / tMax;
2321
+
2322
+ // y = a*v^2*t^2 + b*v*t
2323
+ // where a = dTy*rFactor/2 and b = -r.
2324
+ // Find minimum v where reach edge by tMax
2325
+
2326
+ let minSpeedToReachYEdge = Infinity;
2327
+
2328
+ if (dTy !== 0) {
2329
+ let alpha = ((dTy * rFactor) / 2) * tMax * tMax;
2330
+ let beta = -r * tMax;
2331
+ // y = alpha*v^2 + beta*v
2332
+ // if alpha > 0, solve for y = yscale
2333
+ // else if alpha < 0 solve for y = -yscale
2334
+ let sr = Math.sqrt(beta * beta + 4 * Math.abs(alpha) * yscale);
2335
+ minSpeedToReachYEdge =
2336
+ (Math.abs(beta) + sr) / (2 * Math.abs(alpha));
2337
+ }
2338
+
2339
+ let minSpeed = Math.min(minSpeedToReachXEdge, minSpeedToReachYEdge);
2340
+
2341
+ if (minSpeed > Math.abs(xpEffective)) {
2342
+ xpEffective *= minSpeed / Math.abs(xpEffective);
2343
+ }
2344
+ }
2345
+
2346
+ let v = -xpEffective * r;
2347
+ let a = dTy * xpEffective * xpEffective * rFactor;
2348
+
2349
+ let c = [
2350
+ [x0, xpEffective, 0],
2351
+ [y0, v, a / 2],
2352
+ ];
2353
+
2354
+ return {
2355
+ setValue: {
2356
+ extrapolateForwardCoeffs: c,
2357
+ extrapolateForwardMode: "parabolaVertical",
2358
+ },
2359
+ };
2360
+ } else {
2361
+ // if curving toward the horizontal direction
2362
+ // orient the parabola horizontally
2363
+
2364
+ let r = dTy / dTx;
2365
+ let rFactor = (1 + r * r) ** 2;
2366
+
2367
+ let ypEffective = yp0;
2368
+
2369
+ if (
2370
+ dependencyValues.graphXmin !== null &&
2371
+ dependencyValues.graphXmax !== null &&
2372
+ dependencyValues.graphYmin !== null &&
2373
+ dependencyValues.graphYmax !== null
2374
+ ) {
2375
+ // if we are in graph, make sure that the speed of the parametrization
2376
+ // is fast enough for the curve to leave the graph while
2377
+ // the parameter increases by the amount numThroughPoints - 1
2378
+
2379
+ let xscale =
2380
+ dependencyValues.graphXmax - dependencyValues.graphXmin;
2381
+ let yscale =
2382
+ dependencyValues.graphYmax - dependencyValues.graphYmin;
2383
+
2384
+ let tMax = dependencyValues.numThroughPoints - 1;
2385
+
2386
+ let minSpeedToReachYEdge = yscale / tMax;
2387
+
2388
+ // y = a*v^2*t^2 + b*v*t
2389
+ // where a = dTy*rFactor/2 and b = -r.
2390
+ // Find minimum v where reach edge by tMax
2391
+
2392
+ let minSpeedToReachXEdge = Infinity;
2393
+
2394
+ if (dTx !== 0) {
2395
+ let alpha = ((dTx * rFactor) / 2) * tMax * tMax;
2396
+ let beta = -r * tMax;
2397
+ // c = alpha*v^2 + beta*v
2398
+ // if alpha > 0, solve for x = xscale
2399
+ // else if alpha < 0 solve for x = -xscale
2400
+ let sr = Math.sqrt(beta * beta + 4 * Math.abs(alpha) * xscale);
2401
+ minSpeedToReachXEdge =
2402
+ (Math.abs(beta) + sr) / (2 * Math.abs(alpha));
2403
+ }
2404
+
2405
+ let minSpeed = Math.min(minSpeedToReachXEdge, minSpeedToReachYEdge);
2406
+
2407
+ if (minSpeed > Math.abs(ypEffective)) {
2408
+ ypEffective *= minSpeed / Math.abs(ypEffective);
2409
+ }
2410
+ }
2411
+
2412
+ let v = -ypEffective * r;
2413
+ let a = dTx * ypEffective * ypEffective * rFactor;
2414
+
2415
+ let c = [
2416
+ [x0, v, a / 2],
2417
+ [y0, ypEffective, 0],
2418
+ ];
2419
+
2420
+ return {
2421
+ setValue: {
2422
+ extrapolateForwardCoeffs: c,
2423
+ extrapolateForwardMode: "parabolaHorizontal",
2424
+ },
2425
+ };
2426
+ }
2427
+ },
2428
+ };
2429
+
2430
+ stateVariableDefinitions.fs = {
2431
+ isArray: true,
2432
+ entryPrefixes: ["f"],
2433
+ additionalStateVariablesDefined: [
2434
+ {
2435
+ variableName: "fDefinitions",
2436
+ isArray: true,
2437
+ forRenderer: true,
2438
+ entryPrefixes: ["fDefinition"],
2439
+ },
2440
+ ],
2441
+ public: true,
2442
+ shadowingInstructions: {
2443
+ createComponentOfType: "function",
2444
+ addStateVariablesShadowingStateVariables: {
2445
+ fDefinitions: {
2446
+ stateVariableToShadow: "fDefinitions",
2447
+ },
2448
+ domain: {
2449
+ stateVariableToShadow: "domainForFunctions",
2450
+ },
2451
+ },
2452
+ addAttributeComponentsShadowingStateVariables:
2453
+ returnRoundingAttributeComponentShadowing(),
2454
+ },
2455
+ returnArraySizeDependencies: () => ({
2456
+ functionChildren: {
2457
+ dependencyType: "child",
2458
+ childGroups: ["functions"],
2459
+ },
2460
+ curveType: {
2461
+ dependencyType: "stateVariable",
2462
+ variableName: "curveType",
2463
+ },
2464
+ }),
2465
+ returnArraySize({ dependencyValues }) {
2466
+ if (dependencyValues.curveType === "bezier") {
2467
+ return [2];
2468
+ } else {
2469
+ return [Math.max(1, dependencyValues.functionChildren.length)];
2470
+ }
2471
+ },
2472
+ returnArrayDependenciesByKey({ arrayKeys }) {
2473
+ let globalDependencies = {
2474
+ curveType: {
2475
+ dependencyType: "stateVariable",
2476
+ variableName: "curveType",
2477
+ },
2478
+ numericalThroughPoints: {
2479
+ dependencyType: "stateVariable",
2480
+ variableName: "numericalThroughPoints",
2481
+ },
2482
+ splineCoeffs: {
2483
+ dependencyType: "stateVariable",
2484
+ variableName: "splineCoeffs",
2485
+ },
2486
+ numThroughPoints: {
2487
+ dependencyType: "stateVariable",
2488
+ variableName: "numThroughPoints",
2489
+ },
2490
+ extrapolateBackward: {
2491
+ dependencyType: "stateVariable",
2492
+ variableName: "extrapolateBackward",
2493
+ },
2494
+ extrapolateBackwardCoeffs: {
2495
+ dependencyType: "stateVariable",
2496
+ variableName: "extrapolateBackwardCoeffs",
2497
+ },
2498
+ extrapolateForward: {
2499
+ dependencyType: "stateVariable",
2500
+ variableName: "extrapolateForward",
2501
+ },
2502
+ extrapolateForwardCoeffs: {
2503
+ dependencyType: "stateVariable",
2504
+ variableName: "extrapolateForwardCoeffs",
2505
+ },
2506
+ };
2507
+
2508
+ let dependenciesByKey = {};
2509
+ for (let arrayKey of arrayKeys) {
2510
+ dependenciesByKey[arrayKey] = {
2511
+ functionChild: {
2512
+ dependencyType: "child",
2513
+ childGroups: ["functions"],
2514
+ variableNames: ["numericalf", "fDefinition"],
2515
+ childIndices: [arrayKey],
2516
+ },
2517
+ };
2518
+ if (Number(arrayKey) === 0) {
2519
+ (dependenciesByKey[arrayKey].fShadow = {
2520
+ dependencyType: "stateVariable",
2521
+ variableName: "fShadow",
2522
+ }),
2523
+ (dependenciesByKey[arrayKey].fDefinitionAdapted = {
2524
+ dependencyType: "adapterSourceStateVariable",
2525
+ variableName: "fDefinition",
2526
+ });
2527
+ }
2528
+ }
2529
+ return { globalDependencies, dependenciesByKey };
2530
+ },
2531
+ arrayDefinitionByKey({
2532
+ globalDependencyValues,
2533
+ dependencyValuesByKey,
2534
+ arrayKeys,
2535
+ }) {
2536
+ if (globalDependencyValues.curveType === "bezier") {
2537
+ let bezierArguments = {
2538
+ functionType: "bezier",
2539
+ numThroughPoints: globalDependencyValues.numThroughPoints,
2540
+ numericalThroughPoints:
2541
+ globalDependencyValues.numericalThroughPoints,
2542
+ splineCoeffs: globalDependencyValues.splineCoeffs,
2543
+ extrapolateForward: globalDependencyValues.extrapolateForward,
2544
+ extrapolateForwardCoeffs:
2545
+ globalDependencyValues.extrapolateForwardCoeffs,
2546
+ extrapolateBackward: globalDependencyValues.extrapolateBackward,
2547
+ extrapolateBackwardCoeffs:
2548
+ globalDependencyValues.extrapolateBackwardCoeffs,
2549
+ };
2550
+
2551
+ let fs = {};
2552
+ let fDefinitions = {};
2553
+ bezierArguments.component = 0;
2554
+ fs[0] = returnBezierFunctions(bezierArguments);
2555
+ fDefinitions[0] = bezierArguments;
2556
+
2557
+ bezierArguments = { ...bezierArguments };
2558
+ bezierArguments.component = 1;
2559
+ fs[1] = returnBezierFunctions(bezierArguments);
2560
+ fDefinitions[1] = bezierArguments;
2561
+
2562
+ return {
2563
+ setValue: {
2564
+ fs,
2565
+ fDefinitions,
2566
+ },
2567
+ };
2568
+ }
2569
+
2570
+ let fs = {};
2571
+ let fDefinitions = {};
2572
+ for (let arrayKey of arrayKeys) {
2573
+ let functionChild = dependencyValuesByKey[arrayKey].functionChild;
2574
+ if (functionChild.length === 1) {
2575
+ fs[arrayKey] = functionChild[0].stateValues.numericalf;
2576
+ fDefinitions[arrayKey] = functionChild[0].stateValues.fDefinition;
2577
+ } else {
2578
+ if (
2579
+ Number(arrayKey) === 0 &&
2580
+ dependencyValuesByKey[arrayKey].fShadow
2581
+ ) {
2582
+ fs[arrayKey] = dependencyValuesByKey[arrayKey].fShadow;
2583
+ // TODO: ???
2584
+ fDefinitions[arrayKey] =
2585
+ dependencyValuesByKey[arrayKey].fDefinitionAdapted;
2586
+ } else {
2587
+ fs[arrayKey] = () => 0;
2588
+ fDefinitions[arrayKey] = {
2589
+ functionType: "zero",
2590
+ };
2591
+ }
2592
+ }
2593
+ }
2594
+ return {
2595
+ setValue: { fs, fDefinitions },
2596
+ };
2597
+ },
2598
+ };
2599
+
2600
+ stateVariableDefinitions.f = {
2601
+ isAlias: true,
2602
+ targetVariableName: "f1",
2603
+ };
2604
+
2605
+ stateVariableDefinitions.allXCriticalPoints = {
2606
+ returnDependencies: () => ({
2607
+ splineCoeffs: {
2608
+ dependencyType: "stateVariable",
2609
+ variableName: "splineCoeffs",
2610
+ },
2611
+ fs: {
2612
+ dependencyType: "stateVariable",
2613
+ variableName: "fs",
2614
+ },
2615
+ curveType: {
2616
+ dependencyType: "stateVariable",
2617
+ variableName: "curveType",
2618
+ },
2619
+ }),
2620
+ definition({ dependencyValues }) {
2621
+ let allXCriticalPoints = [];
2622
+
2623
+ if (dependencyValues.curveType !== "bezier") {
2624
+ return { setValue: { allXCriticalPoints } };
2625
+ }
2626
+
2627
+ let fx = dependencyValues.fs[0];
2628
+ let fy = dependencyValues.fs[1];
2629
+
2630
+ let ts = [];
2631
+
2632
+ let xCriticalPointAtPreviousRight = false;
2633
+
2634
+ for (let [ind, cs] of dependencyValues.splineCoeffs.entries()) {
2635
+ let the_cs = cs[0];
2636
+
2637
+ let A = 3 * the_cs[3];
2638
+ let B = 2 * the_cs[2];
2639
+ let C = the_cs[1];
2640
+
2641
+ if (Math.abs(A) < 1e-14) {
2642
+ let t = -C / B;
2643
+
2644
+ xCriticalPointAtPreviousRight = addTimePointBezier({
2645
+ t,
2646
+ ind,
2647
+ ts,
2648
+ ignoreLeft: xCriticalPointAtPreviousRight,
2649
+ });
2650
+ } else {
2651
+ let discrim = B * B - 4 * A * C;
2652
+
2653
+ if (discrim == 0) {
2654
+ let t = -B / (2 * A);
2655
+ xCriticalPointAtPreviousRight = addTimePointBezier({
2656
+ t,
2657
+ ind,
2658
+ ts,
2659
+ ignoreLeft: xCriticalPointAtPreviousRight,
2660
+ });
2661
+ } else if (discrim > 0) {
2662
+ let sqd = Math.sqrt(discrim);
2663
+ let newTs = [(-B - sqd) / (2 * A), (-B + sqd) / (2 * A)];
2664
+ if (A < 0) {
2665
+ newTs = [newTs[1], newTs[0]];
2666
+ }
2667
+ let foundRight = false;
2668
+ for (let t of newTs) {
2669
+ let temp = addTimePointBezier({
2670
+ t,
2671
+ ind,
2672
+ ts,
2673
+ ignoreLeft: xCriticalPointAtPreviousRight,
2674
+ });
2675
+ if (temp) {
2676
+ foundRight = true;
2677
+ }
2678
+ }
2679
+ xCriticalPointAtPreviousRight = foundRight;
2680
+ } else {
2681
+ xCriticalPointAtPreviousRight = false;
2682
+ }
2683
+ }
2684
+ }
2685
+
2686
+ for (let t of ts) {
2687
+ allXCriticalPoints.push([fx(t), fy(t)]);
2688
+ }
2689
+
2690
+ return { setValue: { allXCriticalPoints } };
2691
+ },
2692
+ };
2693
+
2694
+ stateVariableDefinitions.numXCriticalPoints = {
2695
+ public: true,
2696
+ shadowingInstructions: {
2697
+ createComponentOfType: "integer",
2698
+ },
2699
+ returnDependencies: () => ({
2700
+ allXCriticalPoints: {
2701
+ dependencyType: "stateVariable",
2702
+ variableName: "allXCriticalPoints",
2703
+ },
2704
+ }),
2705
+ definition({ dependencyValues }) {
2706
+ return {
2707
+ setValue: {
2708
+ numXCriticalPoints: dependencyValues.allXCriticalPoints.length,
2709
+ },
2710
+ };
2711
+ },
2712
+ };
2713
+
2714
+ stateVariableDefinitions.xCriticalPoints = {
2715
+ public: true,
2716
+ shadowingInstructions: {
2717
+ createComponentOfType: "number",
2718
+ addAttributeComponentsShadowingStateVariables:
2719
+ returnRoundingAttributeComponentShadowing(),
2720
+ returnWrappingComponents(prefix) {
2721
+ if (prefix === "xCriticalPointX") {
2722
+ return [];
2723
+ } else {
2724
+ // point or entire array
2725
+ // wrap inner dimension by both <point> and <xs>
2726
+ // don't wrap outer dimension (for entire array)
2727
+ return [
2728
+ ["point", { componentType: "mathList", isAttribute: "xs" }],
2729
+ ];
2730
+ }
2731
+ },
2732
+ },
2733
+ isArray: true,
2734
+ numDimensions: 2,
2735
+ entryPrefixes: ["xCriticalPointX", "xCriticalPoint"],
2736
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
2737
+ if (arrayEntryPrefix === "xCriticalPointX") {
2738
+ // xCriticalPointX1_2 is the 2nd component of the first point
2739
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
2740
+ if (
2741
+ indices.length === 2 &&
2742
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
2743
+ ) {
2744
+ if (arraySize) {
2745
+ if (indices.every((x, i) => x < arraySize[i])) {
2746
+ return [String(indices)];
2747
+ } else {
2748
+ return [];
2749
+ }
2750
+ } else {
2751
+ // If not given the array size,
2752
+ // then return the array keys assuming the array is large enough.
2753
+ // Must do this as it is used to determine potential array entries.
2754
+ return [String(indices)];
2755
+ }
2756
+ } else {
2757
+ return [];
2758
+ }
2759
+ } else {
2760
+ // xCriticalPoint3 is all components of the third xCriticalPoint
2761
+
2762
+ let pointInd = Number(varEnding) - 1;
2763
+ if (!(Number.isInteger(pointInd) && pointInd >= 0)) {
2764
+ return [];
2765
+ }
2766
+
2767
+ if (!arraySize) {
2768
+ // If don't have array size, we just need to determine if it is a potential entry.
2769
+ // Return the first entry assuming array is large enough
2770
+ return [pointInd + ",0"];
2771
+ }
2772
+ if (pointInd < arraySize[0]) {
2773
+ // array of "pointInd,i", where i=0, ..., arraySize[1]-1
2774
+ return Array.from(
2775
+ Array(arraySize[1]),
2776
+ (_, i) => pointInd + "," + i,
2777
+ );
2778
+ } else {
2779
+ return [];
2780
+ }
2781
+ }
2782
+ },
2783
+ arrayVarNameFromPropIndex(propIndex, varName) {
2784
+ if (varName === "xCriticalPoints") {
2785
+ if (propIndex.length === 1) {
2786
+ return "xCriticalPoint" + propIndex[0];
2787
+ } else {
2788
+ // if propIndex has additional entries, ignore them
2789
+ return `xCriticalPointX${propIndex[0]}_${propIndex[1]}`;
2790
+ }
2791
+ }
2792
+ if (varName.slice(0, 14) === "xCriticalPoint") {
2793
+ // could be xCriticalPoint or xCriticalPointX
2794
+ let xCriticalPointNum = Number(varName.slice(14));
2795
+ if (Number.isInteger(xCriticalPointNum) && xCriticalPointNum > 0) {
2796
+ // if propIndex has additional entries, ignore them
2797
+ return `xCriticalPointX${xCriticalPointNum}_${propIndex[0]}`;
2798
+ }
2799
+ }
2800
+ return null;
2801
+ },
2802
+ returnArraySizeDependencies: () => ({
2803
+ numXCriticalPoints: {
2804
+ dependencyType: "stateVariable",
2805
+ variableName: "numXCriticalPoints",
2806
+ },
2807
+ }),
2808
+ returnArraySize({ dependencyValues }) {
2809
+ return [dependencyValues.numXCriticalPoints, 2];
2810
+ },
2811
+ returnArrayDependenciesByKey() {
2812
+ let globalDependencies = {
2813
+ allXCriticalPoints: {
2814
+ dependencyType: "stateVariable",
2815
+ variableName: "allXCriticalPoints",
2816
+ },
2817
+ };
2818
+
2819
+ return { globalDependencies };
2820
+ },
2821
+ arrayDefinitionByKey({ globalDependencyValues }) {
2822
+ // console.log(`array definition by key of function xCriticalPoints`)
2823
+ // console.log(globalDependencyValues)
2824
+
2825
+ let xCriticalPoints = {};
2826
+
2827
+ for (
2828
+ let ptInd = 0;
2829
+ ptInd < globalDependencyValues.__array_size[0];
2830
+ ptInd++
2831
+ ) {
2832
+ for (let i = 0; i < 2; i++) {
2833
+ let arrayKey = `${ptInd},${i}`;
2834
+
2835
+ xCriticalPoints[arrayKey] =
2836
+ globalDependencyValues.allXCriticalPoints[ptInd][i];
2837
+ }
2838
+ }
2839
+
2840
+ return { setValue: { xCriticalPoints } };
2841
+ },
2842
+ };
2843
+
2844
+ stateVariableDefinitions.allYCriticalPoints = {
2845
+ returnDependencies: () => ({
2846
+ splineCoeffs: {
2847
+ dependencyType: "stateVariable",
2848
+ variableName: "splineCoeffs",
2849
+ },
2850
+ fs: {
2851
+ dependencyType: "stateVariable",
2852
+ variableName: "fs",
2853
+ },
2854
+ curveType: {
2855
+ dependencyType: "stateVariable",
2856
+ variableName: "curveType",
2857
+ },
2858
+ }),
2859
+ definition({ dependencyValues }) {
2860
+ let allYCriticalPoints = [];
2861
+
2862
+ if (dependencyValues.curveType !== "bezier") {
2863
+ return { setValue: { allYCriticalPoints } };
2864
+ }
2865
+
2866
+ let fx = dependencyValues.fs[0];
2867
+ let fy = dependencyValues.fs[1];
2868
+
2869
+ let ts = [];
2870
+
2871
+ let yCriticalPointAtPreviousRight = false;
2872
+
2873
+ for (let [ind, cs] of dependencyValues.splineCoeffs.entries()) {
2874
+ let the_cs = cs[1];
2875
+
2876
+ let A = 3 * the_cs[3];
2877
+ let B = 2 * the_cs[2];
2878
+ let C = the_cs[1];
2879
+
2880
+ if (Math.abs(A) < 1e-14) {
2881
+ let t = -C / B;
2882
+
2883
+ yCriticalPointAtPreviousRight = addTimePointBezier({
2884
+ t,
2885
+ ind,
2886
+ ts,
2887
+ ignoreLeft: yCriticalPointAtPreviousRight,
2888
+ });
2889
+ } else {
2890
+ let discrim = B * B - 4 * A * C;
2891
+
2892
+ if (discrim == 0) {
2893
+ let t = -B / (2 * A);
2894
+
2895
+ yCriticalPointAtPreviousRight = addTimePointBezier({
2896
+ t,
2897
+ ind,
2898
+ ts,
2899
+ ignoreLeft: yCriticalPointAtPreviousRight,
2900
+ });
2901
+ } else if (discrim > 0) {
2902
+ let sqd = Math.sqrt(discrim);
2903
+ let newTs = [(-B - sqd) / (2 * A), (-B + sqd) / (2 * A)];
2904
+ if (A < 0) {
2905
+ newTs = [newTs[1], newTs[0]];
2906
+ }
2907
+ let foundRight = false;
2908
+ for (let t of newTs) {
2909
+ let temp = addTimePointBezier({
2910
+ t,
2911
+ ind,
2912
+ ts,
2913
+ ignoreLeft: yCriticalPointAtPreviousRight,
2914
+ });
2915
+ if (temp) {
2916
+ foundRight = true;
2917
+ }
2918
+ }
2919
+ yCriticalPointAtPreviousRight = foundRight;
2920
+ } else {
2921
+ yCriticalPointAtPreviousRight = false;
2922
+ }
2923
+ }
2924
+ }
2925
+
2926
+ for (let t of ts) {
2927
+ allYCriticalPoints.push([fx(t), fy(t)]);
2928
+ }
2929
+
2930
+ return { setValue: { allYCriticalPoints } };
2931
+ },
2932
+ };
2933
+
2934
+ stateVariableDefinitions.numYCriticalPoints = {
2935
+ public: true,
2936
+ shadowingInstructions: {
2937
+ createComponentOfType: "integer",
2938
+ },
2939
+ returnDependencies: () => ({
2940
+ allYCriticalPoints: {
2941
+ dependencyType: "stateVariable",
2942
+ variableName: "allYCriticalPoints",
2943
+ },
2944
+ }),
2945
+ definition({ dependencyValues }) {
2946
+ return {
2947
+ setValue: {
2948
+ numYCriticalPoints: dependencyValues.allYCriticalPoints.length,
2949
+ },
2950
+ };
2951
+ },
2952
+ };
2953
+
2954
+ stateVariableDefinitions.yCriticalPoints = {
2955
+ public: true,
2956
+ shadowingInstructions: {
2957
+ createComponentOfType: "number",
2958
+ addAttributeComponentsShadowingStateVariables:
2959
+ returnRoundingAttributeComponentShadowing(),
2960
+ returnWrappingComponents(prefix) {
2961
+ if (prefix === "yCriticalPointX") {
2962
+ return [];
2963
+ } else {
2964
+ // point or entire array
2965
+ // wrap inner dimension by both <point> and <xs>
2966
+ // don't wrap outer dimension (for entire array)
2967
+ return [
2968
+ ["point", { componentType: "mathList", isAttribute: "xs" }],
2969
+ ];
2970
+ }
2971
+ },
2972
+ },
2973
+ isArray: true,
2974
+ numDimensions: 2,
2975
+ entryPrefixes: ["yCriticalPointX", "yCriticalPoint"],
2976
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
2977
+ if (arrayEntryPrefix === "yCriticalPointX") {
2978
+ // yCriticalPointX1_2 is the 2nd component of the first point
2979
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
2980
+ if (
2981
+ indices.length === 2 &&
2982
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
2983
+ ) {
2984
+ if (arraySize) {
2985
+ if (indices.every((x, i) => x < arraySize[i])) {
2986
+ return [String(indices)];
2987
+ } else {
2988
+ return [];
2989
+ }
2990
+ } else {
2991
+ // If not given the array size,
2992
+ // then return the array keys assuming the array is large enough.
2993
+ // Must do this as it is used to determine potential array entries.
2994
+ return [String(indices)];
2995
+ }
2996
+ } else {
2997
+ return [];
2998
+ }
2999
+ } else {
3000
+ // yCriticalPoint3 is all components of the third yCriticalPoint
3001
+
3002
+ let pointInd = Number(varEnding) - 1;
3003
+ if (!(Number.isInteger(pointInd) && pointInd >= 0)) {
3004
+ return [];
3005
+ }
3006
+
3007
+ if (!arraySize) {
3008
+ // If don't have array size, we just need to determine if it is a potential entry.
3009
+ // Return the first entry assuming array is large enough
3010
+ return [pointInd + ",0"];
3011
+ }
3012
+ if (pointInd < arraySize[0]) {
3013
+ // array of "pointInd,i", where i=0, ..., arraySize[1]-1
3014
+ return Array.from(
3015
+ Array(arraySize[1]),
3016
+ (_, i) => pointInd + "," + i,
3017
+ );
3018
+ } else {
3019
+ return [];
3020
+ }
3021
+ }
3022
+ },
3023
+ arrayVarNameFromPropIndex(propIndex, varName) {
3024
+ if (varName === "yCriticalPoints") {
3025
+ if (propIndex.length === 1) {
3026
+ return "yCriticalPoint" + propIndex[0];
3027
+ } else {
3028
+ // if propIndex has additional entries, ignore them
3029
+ return `yCriticalPointX${propIndex[0]}_${propIndex[1]}`;
3030
+ }
3031
+ }
3032
+ if (varName.slice(0, 14) === "yCriticalPoint") {
3033
+ // could be yCriticalPoint or yCriticalPointX
3034
+ let yCriticalPointNum = Number(varName.slice(14));
3035
+ if (Number.isInteger(yCriticalPointNum) && yCriticalPointNum > 0) {
3036
+ // if propIndex has additional entries, ignore them
3037
+ return `yCriticalPointX${yCriticalPointNum}_${propIndex[0]}`;
3038
+ }
3039
+ }
3040
+ return null;
3041
+ },
3042
+ returnArraySizeDependencies: () => ({
3043
+ numYCriticalPoints: {
3044
+ dependencyType: "stateVariable",
3045
+ variableName: "numYCriticalPoints",
3046
+ },
3047
+ }),
3048
+ returnArraySize({ dependencyValues }) {
3049
+ return [dependencyValues.numYCriticalPoints, 2];
3050
+ },
3051
+ returnArrayDependenciesByKey() {
3052
+ let globalDependencies = {
3053
+ allYCriticalPoints: {
3054
+ dependencyType: "stateVariable",
3055
+ variableName: "allYCriticalPoints",
3056
+ },
3057
+ };
3058
+
3059
+ return { globalDependencies };
3060
+ },
3061
+ arrayDefinitionByKey({ globalDependencyValues }) {
3062
+ // console.log(`array definition by key of function yCriticalPoints`)
3063
+ // console.log(globalDependencyValues)
3064
+
3065
+ let yCriticalPoints = {};
3066
+
3067
+ for (
3068
+ let ptInd = 0;
3069
+ ptInd < globalDependencyValues.__array_size[0];
3070
+ ptInd++
3071
+ ) {
3072
+ for (let i = 0; i < 2; i++) {
3073
+ let arrayKey = `${ptInd},${i}`;
3074
+
3075
+ yCriticalPoints[arrayKey] =
3076
+ globalDependencyValues.allYCriticalPoints[ptInd][i];
3077
+ }
3078
+ }
3079
+
3080
+ return { setValue: { yCriticalPoints } };
3081
+ },
3082
+ };
3083
+
3084
+ stateVariableDefinitions.allCurvatureChangePoints = {
3085
+ returnDependencies: () => ({
3086
+ splineCoeffs: {
3087
+ dependencyType: "stateVariable",
3088
+ variableName: "splineCoeffs",
3089
+ },
3090
+ fs: {
3091
+ dependencyType: "stateVariable",
3092
+ variableName: "fs",
3093
+ },
3094
+ curveType: {
3095
+ dependencyType: "stateVariable",
3096
+ variableName: "curveType",
3097
+ },
3098
+ }),
3099
+ definition({ dependencyValues }) {
3100
+ let allCurvatureChangePoints = [];
3101
+
3102
+ if (dependencyValues.curveType !== "bezier") {
3103
+ return { setValue: { allCurvatureChangePoints } };
3104
+ }
3105
+
3106
+ let fx = dependencyValues.fs[0];
3107
+ let fy = dependencyValues.fs[1];
3108
+
3109
+ let ts = [];
3110
+
3111
+ let changePointAtPreviousRight = false;
3112
+
3113
+ for (let [ind, cs] of dependencyValues.splineCoeffs.entries()) {
3114
+ let [dx, cx, bx, ax] = cs[0];
3115
+ let [dy, cy, by, ay] = cs[1];
3116
+
3117
+ let A = 3 * (bx * ay - by * ax);
3118
+ let B = 3 * (cx * ay - cy * ax);
3119
+ let C = cx * by - cy * bx;
3120
+
3121
+ if (Math.abs(A) < 1e-14) {
3122
+ let t = -C / B;
3123
+
3124
+ changePointAtPreviousRight = addTimePointBezier({
3125
+ t,
3126
+ ind,
3127
+ ts,
3128
+ ignoreLeft: changePointAtPreviousRight,
3129
+ });
3130
+ } else {
3131
+ let discrim = B * B - 4 * A * C;
3132
+
3133
+ if (discrim == 0) {
3134
+ let t = -B / (2 * A);
3135
+
3136
+ changePointAtPreviousRight = addTimePointBezier({
3137
+ t,
3138
+ ind,
3139
+ ts,
3140
+ ignoreLeft: changePointAtPreviousRight,
3141
+ });
3142
+ } else if (discrim > 0) {
3143
+ let sqd = Math.sqrt(discrim);
3144
+ let newTs = [(-B - sqd) / (2 * A), (-B + sqd) / (2 * A)];
3145
+ if (A < 0) {
3146
+ newTs = [newTs[1], newTs[0]];
3147
+ }
3148
+ let foundRight = false;
3149
+ for (let t of newTs) {
3150
+ let temp = addTimePointBezier({
3151
+ t,
3152
+ ind,
3153
+ ts,
3154
+ ignoreLeft: changePointAtPreviousRight,
3155
+ });
3156
+ if (temp) {
3157
+ foundRight = true;
3158
+ }
3159
+ }
3160
+ changePointAtPreviousRight = foundRight;
3161
+ } else {
3162
+ changePointAtPreviousRight = false;
3163
+ }
3164
+ }
3165
+ }
3166
+
3167
+ for (let t of ts) {
3168
+ allCurvatureChangePoints.push([fx(t), fy(t)]);
3169
+ }
3170
+
3171
+ return { setValue: { allCurvatureChangePoints } };
3172
+ },
3173
+ };
3174
+
3175
+ stateVariableDefinitions.numCurvatureChangePoints = {
3176
+ public: true,
3177
+ shadowingInstructions: {
3178
+ createComponentOfType: "integer",
3179
+ },
3180
+ returnDependencies: () => ({
3181
+ allCurvatureChangePoints: {
3182
+ dependencyType: "stateVariable",
3183
+ variableName: "allCurvatureChangePoints",
3184
+ },
3185
+ }),
3186
+ definition({ dependencyValues }) {
3187
+ return {
3188
+ setValue: {
3189
+ numCurvatureChangePoints:
3190
+ dependencyValues.allCurvatureChangePoints.length,
3191
+ },
3192
+ };
3193
+ },
3194
+ };
3195
+
3196
+ stateVariableDefinitions.curvatureChangePoints = {
3197
+ public: true,
3198
+ shadowingInstructions: {
3199
+ createComponentOfType: "number",
3200
+ addAttributeComponentsShadowingStateVariables:
3201
+ returnRoundingAttributeComponentShadowing(),
3202
+ returnWrappingComponents(prefix) {
3203
+ if (prefix === "curvatureChangePointX") {
3204
+ return [];
3205
+ } else {
3206
+ // point or entire array
3207
+ // wrap inner dimension by both <point> and <xs>
3208
+ // don't wrap outer dimension (for entire array)
3209
+ return [
3210
+ ["point", { componentType: "mathList", isAttribute: "xs" }],
3211
+ ];
3212
+ }
3213
+ },
3214
+ },
3215
+ isArray: true,
3216
+ numDimensions: 2,
3217
+ entryPrefixes: ["curvatureChangePointX", "curvatureChangePoint"],
3218
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
3219
+ if (arrayEntryPrefix === "curvatureChangePointX") {
3220
+ // curvatureChangePointX1_2 is the 2nd component of the first point
3221
+ let indices = varEnding.split("_").map((x) => Number(x) - 1);
3222
+ if (
3223
+ indices.length === 2 &&
3224
+ indices.every((x, i) => Number.isInteger(x) && x >= 0)
3225
+ ) {
3226
+ if (arraySize) {
3227
+ if (indices.every((x, i) => x < arraySize[i])) {
3228
+ return [String(indices)];
3229
+ } else {
3230
+ return [];
3231
+ }
3232
+ } else {
3233
+ // If not given the array size,
3234
+ // then return the array keys assuming the array is large enough.
3235
+ // Must do this as it is used to determine potential array entries.
3236
+ return [String(indices)];
3237
+ }
3238
+ } else {
3239
+ return [];
3240
+ }
3241
+ } else {
3242
+ // curvatureChangePoint3 is all components of the third curvatureChangePoint
3243
+
3244
+ let pointInd = Number(varEnding) - 1;
3245
+ if (!(Number.isInteger(pointInd) && pointInd >= 0)) {
3246
+ return [];
3247
+ }
3248
+
3249
+ if (!arraySize) {
3250
+ // If don't have array size, we just need to determine if it is a potential entry.
3251
+ // Return the first entry assuming array is large enough
3252
+ return [pointInd + ",0"];
3253
+ }
3254
+ if (pointInd < arraySize[0]) {
3255
+ // array of "pointInd,i", where i=0, ..., arraySize[1]-1
3256
+ return Array.from(
3257
+ Array(arraySize[1]),
3258
+ (_, i) => pointInd + "," + i,
3259
+ );
3260
+ } else {
3261
+ return [];
3262
+ }
3263
+ }
3264
+ },
3265
+ arrayVarNameFromPropIndex(propIndex, varName) {
3266
+ if (varName === "curvatureChangePoints") {
3267
+ if (propIndex.length === 1) {
3268
+ return "curvatureChangePoint" + propIndex[0];
3269
+ } else {
3270
+ // if propIndex has additional entries, ignore them
3271
+ return `curvatureChangePointX${propIndex[0]}_${propIndex[1]}`;
3272
+ }
3273
+ }
3274
+ if (varName.slice(0, 20) === "curvatureChangePoint") {
3275
+ // could be curvatureChangePoint or curvatureChangePointX
3276
+ let curvatureChangePointNum = Number(varName.slice(20));
3277
+ if (
3278
+ Number.isInteger(curvatureChangePointNum) &&
3279
+ curvatureChangePointNum > 0
3280
+ ) {
3281
+ // if propIndex has additional entries, ignore them
3282
+ return `curvatureChangePointX${curvatureChangePointNum}_${propIndex[0]}`;
3283
+ }
3284
+ }
3285
+ return null;
3286
+ },
3287
+ returnArraySizeDependencies: () => ({
3288
+ numCurvatureChangePoints: {
3289
+ dependencyType: "stateVariable",
3290
+ variableName: "numCurvatureChangePoints",
3291
+ },
3292
+ }),
3293
+ returnArraySize({ dependencyValues }) {
3294
+ return [dependencyValues.numCurvatureChangePoints, 2];
3295
+ },
3296
+ returnArrayDependenciesByKey() {
3297
+ let globalDependencies = {
3298
+ allCurvatureChangePoints: {
3299
+ dependencyType: "stateVariable",
3300
+ variableName: "allCurvatureChangePoints",
3301
+ },
3302
+ };
3303
+
3304
+ return { globalDependencies };
3305
+ },
3306
+ arrayDefinitionByKey({ globalDependencyValues }) {
3307
+ // console.log(`array definition by key of function curvatureChangePoints`)
3308
+ // console.log(globalDependencyValues)
3309
+
3310
+ let curvatureChangePoints = {};
3311
+
3312
+ for (
3313
+ let ptInd = 0;
3314
+ ptInd < globalDependencyValues.__array_size[0];
3315
+ ptInd++
3316
+ ) {
3317
+ for (let i = 0; i < 2; i++) {
3318
+ let arrayKey = `${ptInd},${i}`;
3319
+
3320
+ curvatureChangePoints[arrayKey] =
3321
+ globalDependencyValues.allCurvatureChangePoints[ptInd][i];
3322
+ }
3323
+ }
3324
+
3325
+ return { setValue: { curvatureChangePoints } };
3326
+ },
3327
+ };
3328
+
3329
+ stateVariableDefinitions.nearestPointAsCurve = {
3330
+ returnDependencies: () => ({
3331
+ nearestPointAsCurvePrelim: {
3332
+ dependencyType: "stateVariable",
3333
+ variableName: "nearestPointAsCurvePrelim",
3334
+ },
3335
+ functionChild: {
3336
+ dependencyType: "child",
3337
+ childGroups: ["functions"],
3338
+ variableNames: ["nearestPointAsCurve"],
3339
+ },
3340
+ adapterSourceValue: {
3341
+ dependencyType: "adapterSourceStateVariable",
3342
+ variableName: "nearestPointAsCurve",
3343
+ },
3344
+ }),
3345
+ definition({ dependencyValues, usedDefault }) {
3346
+ let nearestPointAsCurve = dependencyValues.nearestPointAsCurvePrelim;
3347
+ if (usedDefault.nearestPointAsCurvePrelim) {
3348
+ if (dependencyValues.functionChild.length > 0) {
3349
+ nearestPointAsCurve =
3350
+ dependencyValues.functionChild[0].stateValues.nearestPointAsCurve;
3351
+ } else if (dependencyValues.adapterSourceValue !== null) {
3352
+ nearestPointAsCurve = dependencyValues.adapterSourceValue;
3353
+ }
3354
+ }
3355
+ return { setValue: { nearestPointAsCurve } };
3356
+ },
3357
+ };
3358
+
3359
+ stateVariableDefinitions.nearestPoint = {
3360
+ returnDependencies: () => ({
3361
+ curveType: {
3362
+ dependencyType: "stateVariable",
3363
+ variableName: "curveType",
3364
+ },
3365
+ fs: {
3366
+ dependencyType: "stateVariable",
3367
+ variableName: "fs",
3368
+ },
3369
+ flipFunction: {
3370
+ dependencyType: "stateVariable",
3371
+ variableName: "flipFunction",
3372
+ },
3373
+ numDiscretizationPoints: {
3374
+ dependencyType: "stateVariable",
3375
+ variableName: "numDiscretizationPoints",
3376
+ },
3377
+ parMin: {
3378
+ dependencyType: "stateVariable",
3379
+ variableName: "parMin",
3380
+ },
3381
+ parMax: {
3382
+ dependencyType: "stateVariable",
3383
+ variableName: "parMax",
3384
+ },
3385
+ periodic: {
3386
+ dependencyType: "stateVariable",
3387
+ variableName: "periodic",
3388
+ },
3389
+ graphXmin: {
3390
+ dependencyType: "stateVariable",
3391
+ variableName: "graphXmin",
3392
+ },
3393
+ graphXmax: {
3394
+ dependencyType: "stateVariable",
3395
+ variableName: "graphXmax",
3396
+ },
3397
+ graphYmin: {
3398
+ dependencyType: "stateVariable",
3399
+ variableName: "graphYmin",
3400
+ },
3401
+ graphYmax: {
3402
+ dependencyType: "stateVariable",
3403
+ variableName: "graphYmax",
3404
+ },
3405
+ nearestPointAsCurve: {
3406
+ dependencyType: "stateVariable",
3407
+ variableName: "nearestPointAsCurve",
3408
+ },
3409
+ }),
3410
+ definition({ dependencyValues }) {
3411
+ let nearestPointFunction = null;
3412
+
3413
+ if (dependencyValues.curveType === "function") {
3414
+ nearestPointFunction = getNearestPointFunctionCurve({
3415
+ dependencyValues,
3416
+ numerics,
3417
+ });
3418
+ } else if (
3419
+ ["parameterization", "bezier"].includes(dependencyValues.curveType)
3420
+ ) {
3421
+ nearestPointFunction = getNearestPointParametrizedCurve({
3422
+ dependencyValues,
3423
+ numerics,
3424
+ });
3425
+ }
3426
+
3427
+ return {
3428
+ setValue: { nearestPoint: nearestPointFunction },
3429
+ };
3430
+ },
3431
+ };
3432
+
3433
+ return stateVariableDefinitions;
3434
+ }
3435
+
3436
+ async moveControlVector({
3437
+ controlVector,
3438
+ controlVectorInds,
3439
+ transient,
3440
+ actionId,
3441
+ sourceInformation = {},
3442
+ skipRendererUpdate = false,
3443
+ }) {
3444
+ let desiredVector = {
3445
+ [controlVectorInds + ",0"]: me.fromAst(controlVector[0]),
3446
+ [controlVectorInds + ",1"]: me.fromAst(controlVector[1]),
3447
+ };
3448
+
3449
+ if (transient) {
3450
+ return await this.coreFunctions.performUpdate({
3451
+ updateInstructions: [
3452
+ {
3453
+ updateType: "updateValue",
3454
+ componentName: this.componentName,
3455
+ stateVariable: "controlVectors",
3456
+ value: desiredVector,
3457
+ sourceDetails: { controlVectorMoved: controlVectorInds },
3458
+ },
3459
+ ],
3460
+ transient,
3461
+ actionId,
3462
+ sourceInformation,
3463
+ skipRendererUpdate,
3464
+ });
3465
+ } else {
3466
+ return await this.coreFunctions.performUpdate({
3467
+ updateInstructions: [
3468
+ {
3469
+ updateType: "updateValue",
3470
+ componentName: this.componentName,
3471
+ stateVariable: "controlVectors",
3472
+ value: desiredVector,
3473
+ sourceDetails: { controlVectorMoved: controlVectorInds },
3474
+ },
3475
+ ],
3476
+ actionId,
3477
+ sourceInformation,
3478
+ skipRendererUpdate,
3479
+ event: {
3480
+ verb: "interacted",
3481
+ object: {
3482
+ componentId: this.componentName,
3483
+ },
3484
+ result: {
3485
+ ["controlVector" + controlVectorInds.join("_")]: controlVector,
3486
+ },
3487
+ },
3488
+ });
3489
+ }
3490
+ }
3491
+
3492
+ async moveThroughPoint({
3493
+ throughPoint,
3494
+ throughPointInd,
3495
+ transient,
3496
+ actionId,
3497
+ sourceInformation = {},
3498
+ skipRendererUpdate = false,
3499
+ }) {
3500
+ let desiredPoint = {
3501
+ [throughPointInd + ",0"]: me.fromAst(throughPoint[0]),
3502
+ [throughPointInd + ",1"]: me.fromAst(throughPoint[1]),
3503
+ };
3504
+
3505
+ if (transient) {
3506
+ return await this.coreFunctions.performUpdate({
3507
+ updateInstructions: [
3508
+ {
3509
+ updateType: "updateValue",
3510
+ componentName: this.componentName,
3511
+ stateVariable: "throughPoints",
3512
+ value: desiredPoint,
3513
+ sourceDetails: { throughPointMoved: throughPointInd },
3514
+ },
3515
+ ],
3516
+ transient,
3517
+ actionId,
3518
+ sourceInformation,
3519
+ skipRendererUpdate,
3520
+ });
3521
+ } else {
3522
+ return await this.coreFunctions.performUpdate({
3523
+ updateInstructions: [
3524
+ {
3525
+ updateType: "updateValue",
3526
+ componentName: this.componentName,
3527
+ stateVariable: "throughPoints",
3528
+ value: desiredPoint,
3529
+ sourceDetails: { throughPointMoved: throughPointInd },
3530
+ },
3531
+ ],
3532
+ actionId,
3533
+ sourceInformation,
3534
+ skipRendererUpdate,
3535
+ event: {
3536
+ verb: "interacted",
3537
+ object: {
3538
+ componentId: this.componentName,
3539
+ },
3540
+ result: {
3541
+ ["throughPoint" + throughPointInd]: throughPoint,
3542
+ },
3543
+ },
3544
+ });
3545
+ }
3546
+ }
3547
+
3548
+ async changeVectorControlDirection({
3549
+ direction,
3550
+ throughPointInd,
3551
+ actionId,
3552
+ sourceInformation = {},
3553
+ skipRendererUpdate = false,
3554
+ }) {
3555
+ return await this.coreFunctions.performUpdate({
3556
+ updateInstructions: [
3557
+ {
3558
+ updateType: "updateValue",
3559
+ componentName: this.componentName,
3560
+ stateVariable: "vectorControlDirection",
3561
+ value: { [throughPointInd]: direction },
3562
+ },
3563
+ ],
3564
+ actionId,
3565
+ sourceInformation,
3566
+ skipRendererUpdate,
3567
+ });
3568
+ }
3569
+
3570
+ switchCurve() {}
3571
+
3572
+ async curveClicked({
3573
+ actionId,
3574
+ name,
3575
+ sourceInformation = {},
3576
+ skipRendererUpdate = false,
3577
+ }) {
3578
+ if (!(await this.stateValues.fixed)) {
3579
+ await this.coreFunctions.triggerChainedActions({
3580
+ triggeringAction: "click",
3581
+ componentName: name, // use name rather than this.componentName to get original name if adapted
3582
+ actionId,
3583
+ sourceInformation,
3584
+ skipRendererUpdate,
3585
+ });
3586
+ }
3587
+
3588
+ this.coreFunctions.resolveAction({ actionId });
3589
+ }
3590
+
3591
+ async curveFocused({
3592
+ actionId,
3593
+ name,
3594
+ sourceInformation = {},
3595
+ skipRendererUpdate = false,
3596
+ }) {
3597
+ if (!(await this.stateValues.fixed)) {
3598
+ await this.coreFunctions.triggerChainedActions({
3599
+ triggeringAction: "focus",
3600
+ componentName: name, // use name rather than this.componentName to get original name if adapted
3601
+ actionId,
3602
+ sourceInformation,
3603
+ skipRendererUpdate,
3604
+ });
3605
+ }
3606
+
3607
+ this.coreFunctions.resolveAction({ actionId });
3608
+ }
3609
+ }
3610
+
3611
+ function getNearestPointFunctionCurve({ dependencyValues, numerics }) {
3612
+ let flipFunction = dependencyValues.flipFunction;
3613
+ let f = dependencyValues.fs[0];
3614
+ let numDiscretizationPoints = dependencyValues.numDiscretizationPoints;
3615
+ let parMax = dependencyValues.parMax;
3616
+ let parMin = dependencyValues.parMin;
3617
+
3618
+ return function ({ variables, scales }) {
3619
+ let x1 = variables.x1?.evaluate_to_constant();
3620
+ let x2 = variables.x2?.evaluate_to_constant();
3621
+
3622
+ // Note: if x1 and and x2 are not numbers,
3623
+ // the below algorithm may yield values calculated at parMin or parMax
3624
+
3625
+ let xscale = scales[0];
3626
+ let yscale = scales[1];
3627
+
3628
+ // compute values at the actual endpoints, if they exist
3629
+
3630
+ let x1AtLeftEndpoint, x2AtLeftEndpoint;
3631
+ if (parMin !== -Infinity) {
3632
+ x1AtLeftEndpoint = parMin;
3633
+ x2AtLeftEndpoint = f(parMin);
3634
+
3635
+ if (!Number.isFinite(x2AtLeftEndpoint)) {
3636
+ // in case function was defined on an open interval
3637
+ // check a point just to the right
3638
+ let parMinAdjust = parMin * 0.99999 + parMax * 0.00001;
3639
+ let x2AtLeftEndpointAdjust = f(parMinAdjust);
3640
+ if (Number.isFinite(x2AtLeftEndpointAdjust)) {
3641
+ x1AtLeftEndpoint = parMinAdjust;
3642
+ x2AtLeftEndpoint = x2AtLeftEndpointAdjust;
3643
+ }
3644
+ }
3645
+ if (flipFunction) {
3646
+ let temp = x1AtLeftEndpoint;
3647
+ x1AtLeftEndpoint = x2AtLeftEndpoint;
3648
+ x2AtLeftEndpoint = temp;
3649
+ }
3650
+ }
3651
+
3652
+ let x1AtRightEndpoint, x2AtRightEndpoint;
3653
+ if (parMax !== Infinity) {
3654
+ x1AtRightEndpoint = parMax;
3655
+ x2AtRightEndpoint = f(parMax);
3656
+
3657
+ if (!Number.isFinite(x2AtRightEndpoint)) {
3658
+ // in case function was defined on an open interval
3659
+ // check a point just to the left
3660
+ let parMaxAdjust = parMin * 0.00001 + parMax * 0.99999;
3661
+ let x2AtRightEndpointAdjust = f(parMaxAdjust);
3662
+ if (Number.isFinite(x2AtRightEndpointAdjust)) {
3663
+ x1AtRightEndpoint = parMaxAdjust;
3664
+ x2AtRightEndpoint = x2AtRightEndpointAdjust;
3665
+ }
3666
+ }
3667
+
3668
+ if (flipFunction) {
3669
+ let temp = x1AtRightEndpoint;
3670
+ x1AtRightEndpoint = x2AtRightEndpoint;
3671
+ x2AtRightEndpoint = temp;
3672
+ }
3673
+ }
3674
+
3675
+ if (
3676
+ !dependencyValues.nearestPointAsCurve ||
3677
+ !(Number.isFinite(x1) && Number.isFinite(x2))
3678
+ ) {
3679
+ // first find nearest point when treating a function
3680
+ // (or an inverse function)
3681
+ // which finds a the nearest point vertically
3682
+ // (or horizontally)
3683
+ // assuming the function is defined at that point
3684
+
3685
+ let x1AsFunction, x2AsFunction;
3686
+ if (flipFunction) {
3687
+ x2AsFunction = x2;
3688
+ x1AsFunction = f(x2AsFunction);
3689
+ } else {
3690
+ x1AsFunction = x1;
3691
+ x2AsFunction = f(x1AsFunction);
3692
+ }
3693
+
3694
+ // if function isn't defined at current variable value, replace with
3695
+ // value at nearest endpoint
3696
+
3697
+ if (!(Number.isFinite(x1AsFunction) && Number.isFinite(x2AsFunction))) {
3698
+ let distLeft, distRight;
3699
+ if (flipFunction) {
3700
+ distLeft = Math.abs(parMin - x2);
3701
+ distRight = Math.abs(parMax - x2);
3702
+ } else {
3703
+ distLeft = Math.abs(parMin - x1);
3704
+ distRight = Math.abs(parMax - x1);
3705
+ }
3706
+
3707
+ if (distLeft < distRight || !Number.isFinite(distRight)) {
3708
+ x1AsFunction = x1AtLeftEndpoint;
3709
+ x2AsFunction = x2AtLeftEndpoint;
3710
+ } else {
3711
+ x1AsFunction = x1AtRightEndpoint;
3712
+ x2AsFunction = x2AtRightEndpoint;
3713
+ }
3714
+ }
3715
+
3716
+ // assuming we found a finite point, we're done
3717
+ if (Number.isFinite(x1AsFunction) && Number.isFinite(x2AsFunction)) {
3718
+ let result = {
3719
+ x1: x1AsFunction,
3720
+ x2: x2AsFunction,
3721
+ };
3722
+ if (variables.x3 !== undefined) {
3723
+ result.x3 = 0;
3724
+ }
3725
+ return result;
3726
+ }
3727
+
3728
+ // if we don't have finite values for both componentts
3729
+ // there's nothing more we can do
3730
+ if (!(Number.isFinite(x1) && Number.isFinite(x2))) {
3731
+ return {};
3732
+ }
3733
+ }
3734
+
3735
+ // if we are finding nearest point as a curve,
3736
+ // or if finding nearest point as a function failed,
3737
+ // find the overall nearest point from the curve to (x1,x2)
3738
+
3739
+ let minfunc = function (x) {
3740
+ let dx1 = x1;
3741
+ let dx2 = x2;
3742
+
3743
+ if (flipFunction) {
3744
+ dx1 -= f(x);
3745
+ dx2 -= x;
3746
+ } else {
3747
+ dx1 -= x;
3748
+ dx2 -= f(x);
3749
+ }
3750
+
3751
+ dx1 /= xscale;
3752
+ dx2 /= yscale;
3753
+
3754
+ return dx1 * dx1 + dx2 * dx2;
3755
+ };
3756
+
3757
+ let minT = 0;
3758
+ let maxT = 1;
3759
+ if (parMin !== -Infinity) {
3760
+ minT = parMin;
3761
+ }
3762
+ if (parMax !== -Infinity) {
3763
+ maxT = parMax;
3764
+ }
3765
+
3766
+ let Nsteps = numDiscretizationPoints;
3767
+ let delta = (maxT - minT) / Nsteps;
3768
+
3769
+ // sample Nsteps values of x between [minT, maxT]
3770
+ // and find one where the value of minfunc is smallest
3771
+ // Will create an interval [tIntervalMin, tIntervalMax]
3772
+ // around that point (unless that point is minT or maxT)
3773
+ // to run numerical minimizer over that interval
3774
+
3775
+ let tAtMin = minT;
3776
+ let fAtMin = minfunc(minT);
3777
+ let tIntervalMin = minT;
3778
+ let tIntervalMax = minT + delta;
3779
+
3780
+ let fprev, tprev;
3781
+
3782
+ for (let step = 1; step <= Nsteps; step++) {
3783
+ let tnew = minT + step * delta;
3784
+ let fnew = minfunc(tnew);
3785
+ if (
3786
+ Number.isFinite(fnew) &&
3787
+ Number.isFinite(fprev) &&
3788
+ (fnew < fAtMin || Number.isNaN(fAtMin))
3789
+ ) {
3790
+ tAtMin = tnew;
3791
+ fAtMin = fnew;
3792
+ tIntervalMin = tprev;
3793
+ if (step === Nsteps) {
3794
+ tIntervalMax = tnew;
3795
+ } else {
3796
+ tIntervalMax = tnew + delta;
3797
+ }
3798
+ }
3799
+
3800
+ fprev = fnew;
3801
+ tprev = tnew;
3802
+ }
3803
+
3804
+ // haven't necessarily checked f at tIntervalMax
3805
+ let fAtIntervalMax = minfunc(tIntervalMax);
3806
+ if (!Number.isFinite(fAtIntervalMax)) {
3807
+ tIntervalMax -= delta;
3808
+ }
3809
+
3810
+ let result = numerics.fminbr(minfunc, [tIntervalMin, tIntervalMax]);
3811
+ tAtMin = result.x;
3812
+
3813
+ let x1AtMin = tAtMin;
3814
+ let x2AtMin = f(x1AtMin);
3815
+
3816
+ let currentD2;
3817
+
3818
+ if (
3819
+ !(result.success && Number.isFinite(x1AtMin) && Number.isFinite(x2AtMin))
3820
+ ) {
3821
+ currentD2 = Infinity;
3822
+ } else {
3823
+ if (flipFunction) {
3824
+ [x1AtMin, x2AtMin] = [x2AtMin, x1AtMin];
3825
+ }
3826
+
3827
+ currentD2 =
3828
+ Math.pow((x1AtMin - x1) / xscale, 2) +
3829
+ Math.pow((x2AtMin - x2) / yscale, 2);
3830
+ }
3831
+
3832
+ // replace with endpoints if closer
3833
+
3834
+ if (parMin !== -Infinity) {
3835
+ let leftEndpointD2 =
3836
+ Math.pow((x1AtLeftEndpoint - x1) / xscale, 2) +
3837
+ Math.pow((x2AtLeftEndpoint - x2) / yscale, 2);
3838
+ if (leftEndpointD2 < currentD2) {
3839
+ x1AtMin = x1AtLeftEndpoint;
3840
+ x2AtMin = x2AtLeftEndpoint;
3841
+ currentD2 = leftEndpointD2;
3842
+ }
3843
+ }
3844
+
3845
+ if (parMax !== Infinity) {
3846
+ let rightEndpointD2 =
3847
+ Math.pow((x1AtRightEndpoint - x1) / xscale, 2) +
3848
+ Math.pow((x2AtRightEndpoint - x2) / yscale, 2);
3849
+ if (rightEndpointD2 < currentD2) {
3850
+ x1AtMin = x1AtRightEndpoint;
3851
+ x2AtMin = x2AtRightEndpoint;
3852
+ currentD2 = rightEndpointD2;
3853
+ }
3854
+ }
3855
+
3856
+ result = {
3857
+ x1: x1AtMin,
3858
+ x2: x2AtMin,
3859
+ };
3860
+
3861
+ if (variables.x3 !== undefined) {
3862
+ result.x3 = 0;
3863
+ }
3864
+
3865
+ return result;
3866
+ };
3867
+ }
3868
+
3869
+ function getNearestPointParametrizedCurve({ dependencyValues, numerics }) {
3870
+ let fs = dependencyValues.fs;
3871
+ let parMin = dependencyValues.parMin;
3872
+ let parMax = dependencyValues.parMax;
3873
+ let numDiscretizationPoints = dependencyValues.numDiscretizationPoints;
3874
+ let periodic = dependencyValues.periodic;
3875
+
3876
+ return function ({ variables, scales }) {
3877
+ let xscale = scales[0];
3878
+ let yscale = scales[1];
3879
+
3880
+ // TODO: extend to dimensions other than N=2
3881
+
3882
+ if (dependencyValues.fs.length !== 2) {
3883
+ return {};
3884
+ }
3885
+
3886
+ let x1 = variables.x1?.evaluate_to_constant();
3887
+ let x2 = variables.x2?.evaluate_to_constant();
3888
+
3889
+ if (!(Number.isFinite(x1) && Number.isFinite(x2))) {
3890
+ return {};
3891
+ }
3892
+
3893
+ let minfunc = function (t) {
3894
+ let dx1 = (x1 - fs[0](t)) / xscale;
3895
+ let dx2 = (x2 - fs[1](t)) / yscale;
3896
+
3897
+ return dx1 * dx1 + dx2 * dx2;
3898
+ };
3899
+
3900
+ let minT = parMin;
3901
+ let maxT = parMax;
3902
+
3903
+ let Nsteps = numDiscretizationPoints;
3904
+ let delta = (maxT - minT) / Nsteps;
3905
+
3906
+ // sample Nsteps values of x between [minT, maxT]
3907
+ // and find one where the value of minfunc is smallest
3908
+ // Will create an interval [tIntervalMin, tIntervalMax]
3909
+ // around that point (unless that point is minT or maxT)
3910
+ // to run numerical minimizer over that interval
3911
+
3912
+ let tAtMin = minT;
3913
+ let fAtMin = minfunc(minT);
3914
+ let tIntervalMin = minT;
3915
+ let tIntervalMax = minT + delta;
3916
+
3917
+ let fprev, tprev;
3918
+
3919
+ for (let step = 1; step <= Nsteps; step++) {
3920
+ let tnew = minT + step * delta;
3921
+ let fnew = minfunc(tnew);
3922
+ if (
3923
+ Number.isFinite(fnew) &&
3924
+ Number.isFinite(fprev) &&
3925
+ (fnew < fAtMin || Number.isNaN(fAtMin))
3926
+ ) {
3927
+ tAtMin = tnew;
3928
+ fAtMin = fnew;
3929
+ tIntervalMin = tprev;
3930
+ if (step === Nsteps) {
3931
+ tIntervalMax = tnew;
3932
+ } else {
3933
+ tIntervalMax = tnew + delta;
3934
+ }
3935
+ }
3936
+
3937
+ fprev = fnew;
3938
+ tprev = tnew;
3939
+ }
3940
+
3941
+ if (periodic) {
3942
+ // if have periodic
3943
+ // and tAtMin is at endpoint, make interval span past endpoint
3944
+ if (Math.abs(tAtMin - minT) < numerics.eps) {
3945
+ // append interval for delta for last interval before minT
3946
+ tIntervalMin = minT - delta;
3947
+ } else if (Math.abs(tAtMin - maxT) < numerics.eps) {
3948
+ // append interval for delta for first interval after minT
3949
+ tIntervalMax = maxT + delta;
3950
+ }
3951
+ }
3952
+
3953
+ let result = numerics.fminbr(minfunc, [tIntervalMin, tIntervalMax]);
3954
+ tAtMin = result.x;
3955
+
3956
+ let x1AtMin = fs[0](tAtMin);
3957
+ let x2AtMin = fs[1](tAtMin);
3958
+
3959
+ // replace with endpoints if closer
3960
+ let fMin = minfunc(tAtMin);
3961
+
3962
+ if (
3963
+ !result.success &&
3964
+ Number.isFinite(x1AtMin) &&
3965
+ Number.isFinite(x2AtMin)
3966
+ ) {
3967
+ fMin = Infinity;
3968
+ }
3969
+
3970
+ if (Number.isFinite(parMin)) {
3971
+ let fParMin = minfunc(parMin);
3972
+ if (fParMin < fMin) {
3973
+ tAtMin = parMin;
3974
+ x1AtMin = fs[0](tAtMin);
3975
+ x2AtMin = fs[1](tAtMin);
3976
+ fMin = fParMin;
3977
+ }
3978
+ }
3979
+
3980
+ if (Number.isFinite(parMax)) {
3981
+ let fParMax = minfunc(parMax);
3982
+ if (fParMax < fMin) {
3983
+ tAtMin = parMax;
3984
+ x1AtMin = fs[0](tAtMin);
3985
+ x2AtMin = fs[1](tAtMin);
3986
+ fMin = fParMax;
3987
+ }
3988
+ }
3989
+
3990
+ result = {
3991
+ x1: x1AtMin,
3992
+ x2: x2AtMin,
3993
+ };
3994
+
3995
+ if (variables.x3 !== undefined) {
3996
+ result.x3 = 0;
3997
+ }
3998
+
3999
+ return result;
4000
+ };
4001
+ }
4002
+
4003
+ function calculateControlVectorFromSpline({
4004
+ tau,
4005
+ eps,
4006
+ point1,
4007
+ point2,
4008
+ point3,
4009
+ splineForm,
4010
+ }) {
4011
+ let dist = function (p1, p2) {
4012
+ let dx = p1[0] - p2[0];
4013
+ let dy = p1[1] - p2[1];
4014
+ return Math.sqrt(dx * dx + dy * dy);
4015
+ };
4016
+
4017
+ let p1, p2, p3;
4018
+
4019
+ if (point2) {
4020
+ p2 = point2.map((x) => x.evaluate_to_constant());
4021
+ } else {
4022
+ return {
4023
+ coordsNumeric: [me.fromAst(NaN), me.fromAst(NaN)],
4024
+ numericEntries: false,
4025
+ };
4026
+ }
4027
+
4028
+ if (point3) {
4029
+ p3 = point3.map((x) => x.evaluate_to_constant());
4030
+
4031
+ if (point1) {
4032
+ p1 = point1.map((x) => x.evaluate_to_constant());
4033
+ } else {
4034
+ p1 = [2 * p2[0] - p3[0], 2 * p2[1] - p3[1]];
4035
+ }
4036
+ } else {
4037
+ if (point1) {
4038
+ p1 = point1.map((x) => x.evaluate_to_constant());
4039
+ p3 = [2 * p2[0] - p1[0], 2 * p2[1] - p1[1]];
4040
+ } else {
4041
+ return {
4042
+ coordsNumeric: [me.fromAst(NaN), me.fromAst(NaN)],
4043
+ numericEntries: false,
4044
+ };
4045
+ }
4046
+ }
4047
+
4048
+ let cv = [];
4049
+
4050
+ if (splineForm === "centripetal") {
4051
+ let dt0 = dist(p1, p2);
4052
+ let dt1 = dist(p2, p3);
4053
+
4054
+ dt0 = Math.sqrt(dt0);
4055
+ dt1 = Math.sqrt(dt1);
4056
+
4057
+ if (dt1 < eps) {
4058
+ dt1 = 1.0;
4059
+ }
4060
+ if (dt0 < eps) {
4061
+ dt0 = dt1;
4062
+ }
4063
+
4064
+ for (let dim = 0; dim < 2; dim++) {
4065
+ let t1 =
4066
+ (p2[dim] - p1[dim]) / dt0 -
4067
+ (p3[dim] - p1[dim]) / (dt1 + dt0) +
4068
+ (p3[dim] - p2[dim]) / dt1;
4069
+
4070
+ // original algorithm would multiply by different dt's on each side
4071
+ // of the point
4072
+ // Took geometric mean so that control vectors are symmetric
4073
+ t1 *= tau * Math.sqrt(dt0 * dt1);
4074
+
4075
+ // Bezier control vector component lengths
4076
+ // are one third the respective derivative of the cubic
4077
+ // if (i === 0) {
4078
+ // cv.push(t1 / 3);
4079
+ // } else {
4080
+ cv.push(-t1 / 3);
4081
+ // }
4082
+ }
4083
+ } else {
4084
+ // uniform spline case
4085
+ for (let dim = 0; dim < 2; dim++) {
4086
+ // Bezier control vector component lengths
4087
+ // are one third the respective derivative of the cubic
4088
+ // if (i === 0) {
4089
+ // cv.push(tau * (p3[dim] - p1[dim]) / 3);
4090
+ // } else {
4091
+ cv.push((-tau * (p3[dim] - p1[dim])) / 3);
4092
+ // }
4093
+ }
4094
+ }
4095
+ let coordsNumeric = cv.map((x) => me.fromAst(x));
4096
+ let numericEntries = Number.isFinite(cv[0]) && Number.isFinite(cv[1]);
4097
+
4098
+ return { coordsNumeric, numericEntries };
4099
+ }
4100
+
4101
+ // Compute coefficients for a cubic polynomial
4102
+ // p(s) = c0 + c1*s + c2*s^2 + c3*s^3
4103
+ // such that
4104
+ // p(0) = x1, p(1) = x2
4105
+ // and
4106
+ // p'(0) = t1, p'(1) = t2
4107
+ function initCubicPoly(x1, x2, t1, t2) {
4108
+ return [x1, t1, -3 * x1 + 3 * x2 - 2 * t1 - t2, 2 * x1 - 2 * x2 + t1 + t2];
4109
+ }
4110
+
4111
+ function addTimePointBezier({ t, ind, ts, ignoreLeft = false }) {
4112
+ const eps = 1e-14;
4113
+ const one_plus_eps = 1 + eps;
4114
+ const one_minus_eps = 1 - eps;
4115
+
4116
+ let foundRight = false;
4117
+
4118
+ if (t >= eps) {
4119
+ if (t <= one_minus_eps) {
4120
+ ts.push(ind + t);
4121
+ } else if (t < one_plus_eps) {
4122
+ ts.push(ind + 1);
4123
+ foundRight = true;
4124
+ }
4125
+ } else if (t > -eps && !ignoreLeft) {
4126
+ ts.push(ind);
4127
+ }
4128
+
4129
+ return foundRight;
4130
+ }