@guinetik/gcanvas 1.0.0

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 (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,1124 @@
1
+ import { bezierV1 } from "./bezier.js";
2
+ import { bounceV1 } from "./bounce.js";
3
+ import { floatV1 } from "./float.js";
4
+ import { followPath } from "./follow.js";
5
+ import { orbitV1 } from "./orbit.js";
6
+ import { oscillateV1 } from "./oscillate.js";
7
+ import { parabolicV1 } from "./parabolic.js";
8
+ import { patrolV1 } from "./patrol.js";
9
+ import { pendulumV1 } from "./pendulum.js";
10
+ import { pulseV1 } from "./pulse.js";
11
+ import { hopV1 } from "./hop.js";
12
+ import { shakeV1 } from "./shake.js";
13
+ import { spiralV1 } from "./spiral.js";
14
+ import { springV1 } from "./spring.js";
15
+ import { swingV1 } from "./swing.js";
16
+ import { waypointV1 } from "./waypoint.js";
17
+
18
+ /**
19
+ * @class
20
+ * A utility class that provides a collection of stateless animation primitive functions meant to have a simple but consistent API.
21
+ *
22
+ * Each animation method accepts time parameters and returns interpolated values without storing
23
+ * state between calls, making it suitable for any rendering loop or game engine.
24
+ *
25
+ * This class does not mutate values on target objects; instead it returns a result with the value for the current frame.
26
+ *
27
+ * The caller is responsible for updating their entities with the result.
28
+ *
29
+ * The goal is to provide out-of-the box, configurable animation loops that can be adapted to diferent contexts.
30
+ *
31
+ * If you want simple object property interpolation checkout {@link Tweenetik}.<br/><br/>
32
+ *
33
+ * Unlike {@link Tweenetik}, which mutates target objects directly and manages time internally for UI transitions,
34
+ * Motion is designed for real-time game loops or simulations where you control the update timing (`dt`) externally.
35
+ * <br/>
36
+ * Use Motion when you want precise, stateless, loop-driven animations tied to elapsed time.
37
+ * <br/>
38
+ * Use Tweenetik for one-off UI animations like button presses, transitions, or effects decoupled from game time.
39
+ * <br/><br/>
40
+ * Each animation method follows the same pattern:<br/>
41
+ * - Takes elapsed time and duration parameters<br/>
42
+ * - Returns a standardized result object with animation values and metadata<br/>
43
+ * - Maintains a stateless design (input same time parameters, get same result)<br/>
44
+ * - Supports looping, callbacks, and customizable parameters<br/>
45
+ *<br/><br/>
46
+ * Core animation concepts:<br/>
47
+ * - Animations are driven by elapsed time, not frame counts<br/>
48
+ * - Values are interpolated using normalized time (0-1)<br/>
49
+ * - Optional easing functions can be applied to create custom motion curves<br/>
50
+ * - All animations support loop and yoyo options for cyclic behavior<br/>
51
+ *
52
+ * @example
53
+ * // Basic usage with a game loop
54
+ * function update(dt) {
55
+ * // Update animation time
56
+ * character.animTime += dt;
57
+ *
58
+ * // Get interpolated position using a spring animation
59
+ * const result = Motion.spring(
60
+ * 0, // Initial position
61
+ * 100, // Target position
62
+ * character.animTime, // Current time
63
+ * 1.5, // Duration
64
+ * true, // Loop
65
+ * true, // Yoyo
66
+ * { stiffness: 0.7, damping: 0.5 }
67
+ * );
68
+ *
69
+ * // Apply the result
70
+ * character.x = result.value;
71
+ * }
72
+ *
73
+ * @example
74
+ * // Animating along a path with waypoints
75
+ * function updateGuard(dt) {
76
+ * guard.animTime += dt;
77
+ *
78
+ * const result = Motion.waypoint(
79
+ * guard, // Target object
80
+ * guard.animTime, // Current time
81
+ * [ // Array of waypoints
82
+ * [0, 0],
83
+ * [100, 0],
84
+ * [100, 100],
85
+ * [0, 100]
86
+ * ],
87
+ * 50, // Speed (units per second)
88
+ * 2, // Wait time at each point
89
+ * true // Loop
90
+ * );
91
+ *
92
+ * // Apply position
93
+ * guard.x = result.x;
94
+ * guard.y = result.y;
95
+ *
96
+ * // Update animation state
97
+ * if (result.moving) {
98
+ * guard.playAnimation('walk_' + result.direction);
99
+ * } else {
100
+ * guard.playAnimation('idle');
101
+ * }
102
+ * }
103
+ *
104
+ * @example
105
+ * // Creating a floating effect
106
+ * function updateBoat(dt) {
107
+ * boat.animTime += dt;
108
+ *
109
+ * const result = Motion.float(
110
+ * boat.originalX,
111
+ * boat.originalY,
112
+ * boat.animTime,
113
+ * 5, // Duration
114
+ * 0.5, // Speed
115
+ * 0.7, // Energy/randomness
116
+ * 15, // Float radius
117
+ * true // Loop
118
+ * );
119
+ *
120
+ * // Apply position
121
+ * boat.x = result.x;
122
+ * boat.y = result.y;
123
+ * }
124
+ */
125
+ export class Motion {
126
+
127
+ /**
128
+ * Base animation result constructor
129
+ * Creates a standardized result object for all animations
130
+ *
131
+ * @param {Object} values - The calculated animation values
132
+ * @param {number} t - Current normalized time (0-1)
133
+ * @param {boolean} loop - Whether the animation is looping
134
+ * @param {boolean} completed - Whether a non-looping animation has completed
135
+ * @param {Object} state - Internal state object for continuity between calls
136
+ * @returns {Object} Standardized animation result
137
+ */
138
+ static animationResult(values, t, loop, completed = false, state = null) {
139
+ return {
140
+ ...values, // Animation-specific values (x, y, value, etc.)
141
+ t, // Normalized time (0-1)
142
+ progress: t, // Alias for normalized time
143
+ loop, // Whether animation is looping
144
+ completed, // Whether animation has completed (non-looping only)
145
+ state, // Internal state for the next call
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Processes time and calculates normalized t value
151
+ * Handles looping, duration, and triggers callbacks
152
+ *
153
+ * @param {number} elapsedTime - Total elapsed time in seconds
154
+ * @param {number} duration - Duration of one animation cycle in seconds
155
+ * @param {boolean} loop - Whether animation should loop
156
+ * @param {Object} [callbacks] - Optional callback functions
157
+ * @param {Function} [callbacks.onStart] - Called when animation starts
158
+ * @param {Function} [callbacks.onComplete] - Called when animation completes
159
+ * @param {Function} [callbacks.onLoop] - Called when animation loops
160
+ * @param {Object} [state] - Internal state tracking for callbacks
161
+ * @returns {Object} { t, completed, state }
162
+ */
163
+ static _step(
164
+ elapsedTime,
165
+ duration,
166
+ loop,
167
+ callbacks = {},
168
+ state = { started: false, loopCount: 0 }
169
+ ) {
170
+ // Calculate normalized time (0 to 1)
171
+ let t = duration > 0 ? elapsedTime / duration : 1;
172
+ let completed = false;
173
+ // Handle callback state if not provided
174
+ state = state || { started: false, loopCount: 0 };
175
+ // Call onStart callback once
176
+ if (!state.started && callbacks.onStart) {
177
+ callbacks.onStart();
178
+ state.started = true;
179
+ }
180
+
181
+ // Handle looping
182
+ if (loop) {
183
+ // Get only the fractional part for looping animations
184
+ t = t % 1;
185
+ // Track loop count for onLoop callback
186
+ const newLoopCount = Math.floor(elapsedTime / duration);
187
+ if (newLoopCount > state.loopCount && callbacks.onLoop) {
188
+ callbacks.onLoop(newLoopCount);
189
+ state.loopCount = newLoopCount;
190
+ }
191
+ } else {
192
+ // Clamp to 1 for non-looping animations
193
+ if (t >= 1) {
194
+ t = 1;
195
+ completed = true;
196
+ // Call onComplete callback once
197
+ if (!state.completed && callbacks.onComplete) {
198
+ callbacks.onComplete();
199
+ state.completed = true;
200
+ }
201
+ }
202
+ }
203
+
204
+ return { t, completed, state };
205
+ }
206
+
207
+ /**
208
+ * Updates animation time and applies easing
209
+ *
210
+ * @param {number} elapsedTime - Total elapsed time in seconds
211
+ * @param {number} duration - Duration of animation in seconds
212
+ * @param {boolean} loop - Whether animation should loop
213
+ * @param {Function} [easingFn=null] - Easing function to apply
214
+ * @param {Object} [callbacks] - Optional callback functions
215
+ * @param {Object} [state] - Internal state tracking for callbacks
216
+ * @returns {Object} { t, easedT, completed, state }
217
+ */
218
+ static _frame(
219
+ elapsedTime,
220
+ duration,
221
+ loop,
222
+ easingFn = null,
223
+ callbacks = {},
224
+ state = null
225
+ ) {
226
+ // Process time and handle callbacks
227
+ const {
228
+ t,
229
+ completed,
230
+ state: newState,
231
+ } = this._step(elapsedTime, duration, loop, callbacks, state);
232
+ // Apply easing if provided
233
+ const easedT = easingFn ? easingFn(t) : t;
234
+ return { t, easedT, completed, state: newState };
235
+ }
236
+
237
+ /**
238
+ * Oscillate between min and max value using sine
239
+ *
240
+ * @param {number} min - Minimum value
241
+ * @param {number} max - Maximum value
242
+ * @param {number} elapsedTime - Total elapsed time in seconds
243
+ * @param {number} duration - Duration of one full oscillation in seconds
244
+ * @param {boolean} [loop=true] - Whether animation should loop
245
+ * @param {Function} [easingFn=null] - Optional easing function to apply
246
+ * @param {Object} [callbacks] - Optional callback functions
247
+ * @param {Object} [state] - Internal state tracking for callbacks
248
+ * @returns {Object} Animation result with value and metadata
249
+ */
250
+ static oscillate(
251
+ min,
252
+ max,
253
+ elapsedTime,
254
+ duration,
255
+ loop = true,
256
+ easingFn = null,
257
+ callbacks = {},
258
+ state = null
259
+ ) {
260
+ return oscillateV1(
261
+ min,
262
+ max,
263
+ elapsedTime,
264
+ duration,
265
+ loop,
266
+ easingFn,
267
+ callbacks,
268
+ state
269
+ );
270
+ }
271
+
272
+ /**
273
+ * Parabolic arc interpolation
274
+ *
275
+ * @param {number} start - Start value
276
+ * @param {number} peak - Peak value
277
+ * @param {number} end - End value
278
+ * @param {number} elapsedTime - Total elapsed time in seconds
279
+ * @param {number} duration - Duration of animation in seconds
280
+ * @param {boolean} [loop=false] - Whether animation should loop (restart from beginning)
281
+ * @param {boolean} [yoyo=false] - Whether animation should reverse direction at the end
282
+ * @param {Function} [easingFn=null] - Optional easing function to apply
283
+ * @param {Object} [callbacks] - Optional callback functions
284
+ * @param {Object} [state] - Internal state tracking for callbacks
285
+ * @returns {Object} Animation result with value and metadata
286
+ */
287
+ static parabolic(
288
+ start,
289
+ peak,
290
+ end,
291
+ elapsedTime,
292
+ duration,
293
+ loop = false,
294
+ yoyo = false,
295
+ easingFn = null,
296
+ callbacks = {},
297
+ state = null
298
+ ) {
299
+ return parabolicV1(
300
+ start,
301
+ peak,
302
+ end,
303
+ elapsedTime,
304
+ duration,
305
+ loop,
306
+ yoyo,
307
+ easingFn,
308
+ callbacks,
309
+ state
310
+ );
311
+ }
312
+
313
+ /**
314
+ * Patrol animation - creates movement around an area with natural pausing
315
+ * Perfect for characters patrolling or objects drifting within bounds
316
+ *
317
+ * @param {Object} target - Object with x,y properties defining the center point
318
+ * @param {number} elapsedTime - Total elapsed time in seconds
319
+ * @param {number} duration - Duration of one full float cycle in seconds
320
+ * @param {number} speed - Movement speed multiplier (0.1-2.0 recommended)
321
+ * @param {number} randomness - How random/unpredictable the float path is (0-1)
322
+ * @param {number} radius - Radius of float area (object will move within -radius to +radius)
323
+ * @param {boolean} [loop=true] - Whether animation should loop
324
+ * @param {Function} [easingFn=null] - Optional easing function to apply
325
+ * @param {Object} [callbacks] - Optional callback functions
326
+ * @param {Object} [state] - Internal state tracking for callbacks and initial position
327
+ * @returns {Object} Animation result with x, y coordinates and metadata
328
+ */
329
+ static float(
330
+ target,
331
+ elapsedTime,
332
+ duration,
333
+ speed,
334
+ randomness,
335
+ radius,
336
+ loop = true,
337
+ easingFn = null,
338
+ callbacks = {},
339
+ state = null
340
+ ) {
341
+ return floatV1(
342
+ target,
343
+ elapsedTime,
344
+ duration,
345
+ speed,
346
+ randomness,
347
+ radius,
348
+ loop,
349
+ easingFn,
350
+ callbacks,
351
+ state
352
+ );
353
+ }
354
+
355
+ /**
356
+ * Spring animation that uses elastic easing functions for a bouncy spring effect
357
+ * Stateless implementation that doesn't require previous frame state
358
+ *
359
+ * @param {number} initial - Initial value (starting position)
360
+ * @param {number} target - Target value (ending position)
361
+ * @param {number} elapsedTime - Total elapsed time in seconds
362
+ * @param {number} duration - Duration of one complete cycle in seconds
363
+ * @param {boolean} [loop=false] - Whether animation should loop
364
+ * @param {boolean} [yoyo=false] - Whether animation should return to initial value
365
+ * @param {Object} [springParams] - Spring parameters
366
+ * @param {number} [springParams.stiffness=0.3] - Spring stiffness (0-1)
367
+ * @param {number} [springParams.damping=0.6] - Damping factor (0-1)
368
+ * @param {Object} [callbacks] - Optional callback functions
369
+ * @param {Function} [callbacks.onStart] - Called when animation starts
370
+ * @param {Function} [callbacks.onComplete] - Called when animation completes
371
+ * @param {Function} [callbacks.onLoop] - Called when animation loops
372
+ * @param {Function} [callbacks.onYoyoTurn] - Called when yoyo animation changes direction
373
+ * @returns {Object} Animation result with value, velocity and metadata
374
+ */
375
+ static spring(
376
+ initial,
377
+ target,
378
+ elapsedTime,
379
+ duration,
380
+ loop = false,
381
+ yoyo = false,
382
+ springParams = {},
383
+ callbacks = {}
384
+ ) {
385
+ return springV1(
386
+ initial,
387
+ target,
388
+ elapsedTime,
389
+ duration,
390
+ loop,
391
+ yoyo,
392
+ springParams,
393
+ callbacks
394
+ );
395
+ }
396
+
397
+ static swing(
398
+ centerX,
399
+ centerY,
400
+ maxAngle,
401
+ elapsedTime,
402
+ duration,
403
+ loop = true,
404
+ yoyo = true,
405
+ easingFn = null,
406
+ callbacks = {},
407
+ state = null
408
+ ) {
409
+ return swingV1(
410
+ centerX,
411
+ centerY,
412
+ maxAngle,
413
+ elapsedTime,
414
+ duration,
415
+ loop,
416
+ yoyo,
417
+ easingFn,
418
+ callbacks,
419
+ state
420
+ );
421
+ }
422
+
423
+ static pendulum(
424
+ originAngle,
425
+ amplitude,
426
+ elapsedTime,
427
+ duration,
428
+ loop = true,
429
+ damped = false,
430
+ easingFn = null,
431
+ callbacks = {},
432
+ state = null
433
+ ) {
434
+ return pendulumV1(
435
+ originAngle,
436
+ amplitude,
437
+ elapsedTime,
438
+ duration,
439
+ loop,
440
+ damped,
441
+ easingFn,
442
+ callbacks,
443
+ state
444
+ );
445
+ }
446
+
447
+ /**
448
+ * Pulse between min and max value and back
449
+ *
450
+ * @param {number} min - Minimum value
451
+ * @param {number} max - Maximum value
452
+ * @param {number} elapsedTime - Total elapsed time in seconds
453
+ * @param {number} duration - Duration of one full pulse in seconds
454
+ * @param {boolean} [loop=true] - Whether animation should loop
455
+ * @param {boolean} [yoyo=false] - Whether to use separate easing for return journey
456
+ * @param {Function} [easingFn=null] - Optional easing function to apply
457
+ * @param {Object} [callbacks] - Optional callback functions
458
+ * @returns {Object} Animation result with value and metadata
459
+ */
460
+ static pulse(
461
+ min,
462
+ max,
463
+ elapsedTime,
464
+ duration,
465
+ loop = true,
466
+ yoyo = false,
467
+ easingFn = null,
468
+ callbacks = {}
469
+ ) {
470
+ return pulseV1(
471
+ min,
472
+ max,
473
+ elapsedTime,
474
+ duration,
475
+ loop,
476
+ yoyo,
477
+ easingFn,
478
+ (callbacks = {})
479
+ );
480
+ }
481
+
482
+ /**
483
+ * Spiral motion animation
484
+ *
485
+ * @param {number} centerX - X coordinate of spiral center
486
+ * @param {number} centerY - Y coordinate of spiral center
487
+ * @param {number} startRadius - Starting radius of the spiral
488
+ * @param {number} endRadius - Ending radius of the spiral
489
+ * @param {number} startAngle - Starting angle in radians
490
+ * @param {number} revolutions - Number of complete revolutions
491
+ * @param {number} elapsedTime - Total elapsed time in seconds
492
+ * @param {number} duration - Duration of animation in seconds
493
+ * @param {boolean} [loop=false] - Whether animation should loop (restart from beginning)
494
+ * @param {boolean} [yoyo=false] - Whether animation should reverse direction at the end
495
+ * @param {Function} [easingFn=null] - Optional easing function to apply
496
+ * @param {Object} [callbacks] - Optional callback functions
497
+ * @param {Object} [state] - Internal state tracking for callbacks
498
+ * @returns {Object} Animation result with x, y coordinates and metadata
499
+ */
500
+ static spiral(
501
+ centerX,
502
+ centerY,
503
+ startRadius,
504
+ endRadius,
505
+ startAngle,
506
+ revolutions,
507
+ elapsedTime,
508
+ duration,
509
+ loop = false,
510
+ yoyo = false,
511
+ easingFn = null,
512
+ callbacks = {},
513
+ state = null
514
+ ) {
515
+ return spiralV1(
516
+ centerX,
517
+ centerY,
518
+ startRadius,
519
+ endRadius,
520
+ startAngle,
521
+ revolutions,
522
+ elapsedTime,
523
+ duration,
524
+ loop,
525
+ yoyo,
526
+ easingFn,
527
+ callbacks,
528
+ state
529
+ );
530
+ }
531
+
532
+ /**
533
+ * Orbit motion animation (circular or elliptical)
534
+ *
535
+ * @param {number} centerX - X coordinate of orbit center
536
+ * @param {number} centerY - Y coordinate of orbit center
537
+ * @param {number} radiusX - X radius of the orbit (horizontal)
538
+ * @param {number} radiusY - Y radius of the orbit (vertical)
539
+ * @param {number} startAngle - Starting angle in radians
540
+ * @param {number} elapsedTime - Total elapsed time in seconds
541
+ * @param {number} duration - Duration of one full orbit in seconds
542
+ * @param {boolean} [loop=true] - Whether animation should loop
543
+ * @param {boolean} [clockwise=true] - Direction of orbit
544
+ * @param {Function} [easingFn=null] - Optional easing function to apply
545
+ * @param {Object} [callbacks] - Optional callback functions
546
+ * @param {Object} [state] - Internal state tracking for callbacks
547
+ * @returns {Object} Animation result with x, y coordinates and metadata
548
+ */
549
+ static orbit(
550
+ centerX,
551
+ centerY,
552
+ radiusX,
553
+ radiusY,
554
+ startAngle,
555
+ elapsedTime,
556
+ duration,
557
+ loop = true,
558
+ clockwise = true,
559
+ easingFn = null,
560
+ callbacks = {},
561
+ state = null
562
+ ) {
563
+ return orbitV1(
564
+ centerX,
565
+ centerY,
566
+ radiusX,
567
+ radiusY,
568
+ startAngle,
569
+ elapsedTime,
570
+ duration,
571
+ loop,
572
+ clockwise,
573
+ easingFn,
574
+ callbacks,
575
+ state
576
+ );
577
+ }
578
+
579
+ /**
580
+ * Bezier curve motion animation with yoyo support
581
+ *
582
+ * @param {Array<number>} p0 - Start point [x, y]
583
+ * @param {Array<number>} p1 - Control point 1 [x, y]
584
+ * @param {Array<number>} p2 - Control point 2 [x, y]
585
+ * @param {Array<number>} p3 - End point [x, y]
586
+ * @param {number} elapsedTime - Total elapsed time in seconds
587
+ * @param {number} duration - Duration of animation in seconds
588
+ * @param {boolean} [loop=false] - Whether animation should loop
589
+ * @param {boolean} [yoyo=false] - Whether animation should return to start
590
+ * @param {Function} [easingFn=null] - Optional easing function to apply
591
+ * @param {Object} [callbacks] - Optional callback functions
592
+ * @param {Object} [state] - Internal state tracking for callbacks
593
+ * @returns {Object} Animation result with x, y coordinates and metadata
594
+ */
595
+ static bezier(
596
+ p0,
597
+ p1,
598
+ p2,
599
+ p3,
600
+ elapsedTime,
601
+ duration,
602
+ loop = false,
603
+ yoyo = false,
604
+ easingFn = null,
605
+ callbacks = {},
606
+ state = null
607
+ ) {
608
+ return bezierV1(
609
+ p0,
610
+ p1,
611
+ p2,
612
+ p3,
613
+ elapsedTime,
614
+ duration,
615
+ loop,
616
+ yoyo,
617
+ easingFn,
618
+ callbacks,
619
+ state
620
+ );
621
+ }
622
+
623
+ /**
624
+ * Bounce animation - object drops and bounces with diminishing height
625
+ *
626
+ * @param {number} maxHeight - Maximum height (negative y value)
627
+ * @param {number} groundY - Ground position (positive y value)
628
+ * @param {number} bounceCount - Number of bounces to perform
629
+ * @param {number} elapsedTime - Total elapsed time in seconds
630
+ * @param {number} duration - Duration of animation in seconds
631
+ * @param {boolean} [loop=false] - Whether animation should loop
632
+ * @param {Function} [easingFn=null] - Optional easing function to apply
633
+ * @param {Object} [callbacks] - Optional callback functions
634
+ * @param {Object} [state] - Internal state tracking for callbacks
635
+ * @returns {Object} Animation result with y position and metadata
636
+ */
637
+ static bounce(
638
+ maxHeight,
639
+ groundY,
640
+ bounceCount,
641
+ elapsedTime,
642
+ duration,
643
+ loop = false,
644
+ easingFn = null,
645
+ callbacks = {},
646
+ state = null
647
+ ) {
648
+ return bounceV1(
649
+ maxHeight,
650
+ groundY,
651
+ bounceCount,
652
+ elapsedTime,
653
+ duration,
654
+ loop,
655
+ easingFn,
656
+ callbacks,
657
+ state
658
+ );
659
+ }
660
+
661
+ /**
662
+ * Shake animation with decreasing intensity
663
+ *
664
+ * @param {number} centerX - Center X position
665
+ * @param {number} centerY - Center Y position
666
+ * @param {number} maxOffsetX - Maximum X offset
667
+ * @param {number} maxOffsetY - Maximum Y offset
668
+ * @param {number} frequency - Frequency of shakes
669
+ * @param {number} decay - How quickly the shake decreases (0-1)
670
+ * @param {number} elapsedTime - Total elapsed time in seconds
671
+ * @param {number} duration - Duration of animation in seconds
672
+ * @param {boolean} [loop=false] - Whether animation should loop
673
+ * @param {Function} [easingFn=null] - Optional easing function to apply
674
+ * @param {Object} [callbacks] - Optional callback functions
675
+ * @param {Object} [state] - Internal state tracking for callbacks
676
+ * @returns {Object} Animation result with x, y coordinates and metadata
677
+ */
678
+ static shake(
679
+ centerX,
680
+ centerY,
681
+ maxOffsetX,
682
+ maxOffsetY,
683
+ frequency,
684
+ decay,
685
+ elapsedTime,
686
+ duration,
687
+ loop = false,
688
+ easingFn = null,
689
+ callbacks = {},
690
+ state = null
691
+ ) {
692
+ return shakeV1(
693
+ centerX,
694
+ centerY,
695
+ maxOffsetX,
696
+ maxOffsetY,
697
+ frequency,
698
+ decay,
699
+ elapsedTime,
700
+ duration,
701
+ loop,
702
+ easingFn,
703
+ callbacks,
704
+ state
705
+ );
706
+ }
707
+
708
+ static follow(
709
+ points,
710
+ closed = false,
711
+ elapsedTime,
712
+ duration,
713
+ loop = false,
714
+ easingFn = null,
715
+ callbacks = {},
716
+ state = null
717
+ ) {
718
+ return followPath(
719
+ points,
720
+ closed,
721
+ elapsedTime,
722
+ duration,
723
+ loop,
724
+ easingFn,
725
+ callbacks,
726
+ state
727
+ );
728
+ }
729
+
730
+ /**
731
+ * Waypoint is a patrol animation that follows a path of waypoints with proper waiting periods
732
+ * Moves characters along cardinal directions (horizontal and vertical movement)
733
+ *
734
+ * @param {Object} target - Object with x,y properties (not used for position calculation)
735
+ * @param {number} elapsedTime - Total elapsed time in seconds
736
+ * @param {Array<Array<number>>} waypoints - Array of waypoints [[x1,y1], [x2,y2], ...]
737
+ * @param {number} speed - Movement speed in units per second
738
+ * @param {number} waitTime - Time to wait at each waypoint in seconds
739
+ * @param {boolean} [loop=true] - Whether patrol should loop back to start
740
+ * @param {Object} [callbacks] - Optional callback functions
741
+ * @param {Function} [callbacks.onWaypointReached] - Called when reaching a waypoint
742
+ * @param {Function} [callbacks.onWaitStart] - Called when starting to wait at a waypoint
743
+ * @param {Function} [callbacks.onWaitEnd] - Called when done waiting at a waypoint
744
+ * @param {Function} [callbacks.onPatrolComplete] - Called when patrol is complete (non-looping only)
745
+ * @param {Object} [state] - Internal state tracking
746
+ * @returns {Object} Animation result with position and patrol metadata
747
+ */
748
+ static waypoint(
749
+ target,
750
+ elapsedTime,
751
+ waypoints,
752
+ speed,
753
+ waitTime,
754
+ loop = true,
755
+ callbacks = {},
756
+ state = null
757
+ ) {
758
+ return waypointV1(
759
+ target,
760
+ elapsedTime,
761
+ waypoints,
762
+ speed,
763
+ waitTime,
764
+ loop,
765
+ callbacks,
766
+ state
767
+ );
768
+ }
769
+
770
+ /**
771
+ * Simple patrol animation that moves randomly within a radius
772
+ * Character moves along cardinal directions with waiting periods
773
+ *
774
+ * @param {number} initialX - Initial X position (center point)
775
+ * @param {number} initialY - Initial Y position (center point)
776
+ * @param {number} elapsedTime - Total elapsed time in seconds
777
+ * @param {number} moveTime - Time to spend moving between points
778
+ * @param {number} waitTime - Time to wait at each point
779
+ * @param {number} radius - Maximum distance from center point
780
+ * @param {boolean} [loop=true] - Whether animation should loop
781
+ * @param {Object} [state] - Internal state tracking
782
+ * @returns {Object} Animation result with position and direction
783
+ */
784
+ static patrol(
785
+ initialX,
786
+ initialY,
787
+ elapsedTime,
788
+ moveTime,
789
+ waitTime,
790
+ radius,
791
+ loop = true,
792
+ state = null
793
+ ) {
794
+ return patrolV1(
795
+ initialX,
796
+ initialY,
797
+ elapsedTime,
798
+ moveTime,
799
+ waitTime,
800
+ radius,
801
+ loop,
802
+ state
803
+ );
804
+ }
805
+
806
+ /**
807
+ * Hop animation - makes the object jump up and down rhythmically
808
+ *
809
+ * @param {number} baseY - The ground/base Y position
810
+ * @param {number} hopHeight - Maximum height (negative Y offset)
811
+ * @param {number} elapsedTime - Elapsed time in seconds
812
+ * @param {number} duration - Duration of one hop (up and down)
813
+ * @param {boolean} [loop=true] - Whether the hop repeats
814
+ * @param {Function} [easingFn=null] - Optional easing for jump arc
815
+ * @param {Object} [callbacks={}] - Optional callback functions
816
+ * @param {Object} [state=null] - Internal state
817
+ * @returns {Object} Animation result with y position
818
+ */
819
+ static hop(
820
+ baseY,
821
+ hopHeight,
822
+ elapsedTime,
823
+ duration,
824
+ loop = true,
825
+ yoyo = true,
826
+ easingFn = null,
827
+ callbacks = {},
828
+ state = null
829
+ ) {
830
+ return hopV1(
831
+ baseY,
832
+ hopHeight,
833
+ elapsedTime,
834
+ duration,
835
+ loop,
836
+ yoyo,
837
+ easingFn,
838
+ callbacks,
839
+ state
840
+ );
841
+ }
842
+
843
+ /**
844
+ * !!!!
845
+ * ANIMATION GROUPING
846
+ * VERY MUCH EXPERIMENTAL AT THIS POINT
847
+ * !!!!
848
+ */
849
+
850
+ /**
851
+ * Group multiple animations together
852
+ *
853
+ * @param {Array<Function>} animations - Array of animation function references
854
+ * @param {Array<Array>} animationArgs - Array of argument arrays for each animation
855
+ * @param {number} elapsedTime - Total elapsed time in seconds
856
+ * @param {number} duration - Duration of animation in seconds
857
+ * @param {boolean} [loop=false] - Whether animation should loop
858
+ * @param {Function} [easingFn=null] - Optional easing function to apply to all animations
859
+ * @param {Object} [callbacks] - Optional callback functions
860
+ * @param {Object} [state] - Internal state tracking for callbacks and animations
861
+ * @returns {Object} Combined animation result with results from all animations
862
+ */
863
+ static group(
864
+ animations,
865
+ animationArgs,
866
+ elapsedTime,
867
+ duration,
868
+ loop = false,
869
+ easingFn = null,
870
+ callbacks = {},
871
+ state = null
872
+ ) {
873
+ // Initialize state with animation states array if not provided
874
+ if (!state) {
875
+ state = {
876
+ started: false,
877
+ loopCount: 0,
878
+ animationStates: Array(animations.length).fill(null),
879
+ };
880
+ }
881
+
882
+ // Update animation time and apply easing
883
+ const {
884
+ t,
885
+ easedT,
886
+ completed,
887
+ state: newState,
888
+ } = this._frame(elapsedTime, duration, loop, easingFn, callbacks, state);
889
+
890
+ // Run all animations with same timing
891
+ const results = {};
892
+
893
+ for (let i = 0; i < animations.length; i++) {
894
+ const animFn = animations[i];
895
+ const args = [...animationArgs[i]];
896
+
897
+ // Replace timing arguments with our timing
898
+ // Find time/duration/loop arguments based on the function signature
899
+ if (
900
+ animFn === this.parabolic ||
901
+ animFn === this.oscillate ||
902
+ animFn === this.pulse
903
+ ) {
904
+ // Format: (params..., elapsedTime, duration, loop, ...)
905
+ args[3] = elapsedTime;
906
+ args[4] = duration;
907
+ args[5] = loop;
908
+ if (args[6] === undefined) args[6] = easingFn;
909
+ } else if (animFn === this.spring) {
910
+ // Format: (current, target, elapsedTime, duration, loop, ...)
911
+ args[2] = elapsedTime;
912
+ args[3] = duration;
913
+ args[4] = loop;
914
+ } else if (animFn === this.spiral || animFn === this.bezier) {
915
+ // Format: (params..., elapsedTime, duration, loop, ...)
916
+ args[6] = elapsedTime;
917
+ args[7] = duration;
918
+ args[8] = loop;
919
+ if (args[9] === undefined) args[9] = easingFn;
920
+ } else if (animFn === this.orbit) {
921
+ // Format: (params..., elapsedTime, duration, loop, clockwise, ...)
922
+ args[5] = elapsedTime;
923
+ args[6] = duration;
924
+ args[7] = loop;
925
+ // Skip args[8] as it's clockwise
926
+ if (args[9] === undefined) args[9] = easingFn;
927
+ } else if (animFn === this.bounce || animFn === this.shake) {
928
+ // Format: (params..., elapsedTime, duration, loop, ...)
929
+ args[6] = elapsedTime;
930
+ args[7] = duration;
931
+ args[8] = loop;
932
+ if (args[9] === undefined) args[9] = easingFn;
933
+ } else if (animFn === this.followPath) {
934
+ // Format: (points, closed, elapsedTime, duration, loop, ...)
935
+ args[2] = elapsedTime;
936
+ args[3] = duration;
937
+ args[4] = loop;
938
+ if (args[5] === undefined) args[5] = easingFn;
939
+ }
940
+
941
+ // Add state to arguments
942
+ args.push(callbacks);
943
+ args.push(newState.animationStates[i]);
944
+
945
+ // Run animation
946
+ const result = animFn.apply(this, args);
947
+
948
+ // Store updated state
949
+ newState.animationStates[i] = result.state;
950
+
951
+ // Add result to results object with key based on index
952
+ const key = `anim${i}`;
953
+ results[key] = result;
954
+ }
955
+
956
+ // Return combined result
957
+ return this.animationResult(results, t, loop, completed, newState);
958
+ }
959
+
960
+ /**
961
+ * Sequence multiple animations one after another
962
+ *
963
+ * @param {Array<Function>} animations - Array of animation function references
964
+ * @param {Array<Array>} animationArgs - Array of argument arrays for each animation
965
+ * @param {Array<number>} durations - Array of durations for each animation
966
+ * @param {number} elapsedTime - Total elapsed time in seconds
967
+ * @param {boolean} [loop=false] - Whether the entire sequence should loop
968
+ * @param {Array<Function>} [easingFns=null] - Optional array of easing functions for each animation
969
+ * @param {Object} [callbacks] - Optional callback functions for the entire sequence
970
+ * @param {Object} [animCallbacks] - Optional array of callback objects for individual animations
971
+ * @param {Object} [state] - Internal state tracking
972
+ * @returns {Object} Animation result from current active animation with sequence metadata
973
+ */
974
+ static sequence(
975
+ animations,
976
+ animationArgs,
977
+ durations,
978
+ elapsedTime,
979
+ loop = false,
980
+ easingFns = null,
981
+ callbacks = {},
982
+ animCallbacks = null,
983
+ state = null
984
+ ) {
985
+ // Initialize state with animation states array if not provided
986
+ if (!state) {
987
+ state = {
988
+ started: false,
989
+ loopCount: 0,
990
+ animationStates: Array(animations.length).fill(null),
991
+ currentAnim: 0,
992
+ animStartTimes: [0],
993
+ totalDuration: 0,
994
+ };
995
+
996
+ // Calculate start times and total duration
997
+ let timeAccumulator = 0;
998
+ for (let i = 0; i < durations.length; i++) {
999
+ timeAccumulator += durations[i];
1000
+ if (i < durations.length - 1) {
1001
+ state.animStartTimes.push(timeAccumulator);
1002
+ }
1003
+ }
1004
+ state.totalDuration = timeAccumulator;
1005
+ }
1006
+
1007
+ // Handle loop logic for the entire sequence
1008
+ let localElapsedTime = elapsedTime;
1009
+ if (loop && state.totalDuration > 0) {
1010
+ localElapsedTime = elapsedTime % state.totalDuration;
1011
+
1012
+ // Track loop count for callback
1013
+ const newLoopCount = Math.floor(elapsedTime / state.totalDuration);
1014
+ if (newLoopCount > state.loopCount && callbacks.onLoop) {
1015
+ callbacks.onLoop(newLoopCount);
1016
+ state.loopCount = newLoopCount;
1017
+ }
1018
+ }
1019
+
1020
+ // Call onStart callback once
1021
+ if (!state.started && callbacks.onStart) {
1022
+ callbacks.onStart();
1023
+ state.started = true;
1024
+ }
1025
+
1026
+ // Find current animation based on elapsed time
1027
+ let currentAnim = 0;
1028
+ for (let i = animations.length - 1; i >= 0; i--) {
1029
+ if (localElapsedTime >= state.animStartTimes[i]) {
1030
+ currentAnim = i;
1031
+ break;
1032
+ }
1033
+ }
1034
+
1035
+ // Update current animation property
1036
+ state.currentAnim = currentAnim;
1037
+
1038
+ // Calculate time within current animation
1039
+ const animStartTime = state.animStartTimes[currentAnim];
1040
+ const animElapsedTime = localElapsedTime - animStartTime;
1041
+ const animDuration = durations[currentAnim];
1042
+
1043
+ // Get animation function and arguments
1044
+ const animFn = animations[currentAnim];
1045
+ const args = [...animationArgs[currentAnim]];
1046
+
1047
+ // Apply proper timing to animation args based on function type
1048
+ // This follows the same pattern as in group()
1049
+ if (
1050
+ animFn === this.parabolic ||
1051
+ animFn === this.oscillate ||
1052
+ animFn === this.pulse
1053
+ ) {
1054
+ args[3] = animElapsedTime;
1055
+ args[4] = animDuration;
1056
+ args[5] = false; // Never loop individual animations
1057
+ if (easingFns && easingFns[currentAnim]) args[6] = easingFns[currentAnim];
1058
+ } else if (animFn === this.spring) {
1059
+ args[2] = animElapsedTime;
1060
+ args[3] = animDuration;
1061
+ args[4] = false;
1062
+ } else if (animFn === this.spiral || animFn === this.bezier) {
1063
+ args[6] = animElapsedTime;
1064
+ args[7] = animDuration;
1065
+ args[8] = false;
1066
+ if (easingFns && easingFns[currentAnim]) args[9] = easingFns[currentAnim];
1067
+ } else if (animFn === this.orbit) {
1068
+ args[5] = animElapsedTime;
1069
+ args[6] = animDuration;
1070
+ args[7] = false;
1071
+ // Skip args[8] as it's clockwise
1072
+ if (easingFns && easingFns[currentAnim]) args[9] = easingFns[currentAnim];
1073
+ } else if (animFn === this.bounce || animFn === this.shake) {
1074
+ args[6] = animElapsedTime;
1075
+ args[7] = animDuration;
1076
+ args[8] = false;
1077
+ if (easingFns && easingFns[currentAnim]) args[9] = easingFns[currentAnim];
1078
+ } else if (animFn === this.followPath) {
1079
+ args[2] = animElapsedTime;
1080
+ args[3] = animDuration;
1081
+ args[4] = false;
1082
+ if (easingFns && easingFns[currentAnim]) args[5] = easingFns[currentAnim];
1083
+ }
1084
+
1085
+ // Add individual animation callbacks if provided
1086
+ const currentAnimCallbacks =
1087
+ animCallbacks && animCallbacks[currentAnim]
1088
+ ? animCallbacks[currentAnim]
1089
+ : {};
1090
+
1091
+ // Run animation with its state
1092
+ const result = animFn.apply(this, [
1093
+ ...args,
1094
+ currentAnimCallbacks,
1095
+ state.animationStates[currentAnim],
1096
+ ]);
1097
+
1098
+ // Store updated state
1099
+ state.animationStates[currentAnim] = result.state;
1100
+
1101
+ // Check if the entire sequence is completed
1102
+ const completed = !loop && localElapsedTime >= state.totalDuration;
1103
+
1104
+ // Call onComplete callback once when the entire sequence completes
1105
+ if (completed && !state.completed && callbacks.onComplete) {
1106
+ callbacks.onComplete();
1107
+ state.completed = true;
1108
+ }
1109
+
1110
+ // Return enhanced result with sequence metadata
1111
+ return this.animationResult(
1112
+ {
1113
+ ...result,
1114
+ currentAnim,
1115
+ totalAnimations: animations.length,
1116
+ sequenceProgress: Math.min(localElapsedTime / state.totalDuration, 1),
1117
+ },
1118
+ localElapsedTime / state.totalDuration, // t normalized to entire sequence
1119
+ loop,
1120
+ completed,
1121
+ state
1122
+ );
1123
+ }
1124
+ }