@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,49 @@
1
+ import { Motion } from "./motion";
2
+
3
+ /**
4
+ * Orbit motion animation (circular or elliptical)
5
+ *
6
+ * @param {number} centerX - X coordinate of orbit center
7
+ * @param {number} centerY - Y coordinate of orbit center
8
+ * @param {number} radiusX - X radius of the orbit (horizontal)
9
+ * @param {number} radiusY - Y radius of the orbit (vertical)
10
+ * @param {number} startAngle - Starting angle in radians
11
+ * @param {number} elapsedTime - Total elapsed time in seconds
12
+ * @param {number} duration - Duration of one full orbit in seconds
13
+ * @param {boolean} [loop=true] - Whether animation should loop
14
+ * @param {boolean} [clockwise=true] - Direction of orbit
15
+ * @param {Function} [easingFn=null] - Optional easing function to apply
16
+ * @param {Object} [callbacks] - Optional callback functions
17
+ * @param {Object} [state] - Internal state tracking for callbacks
18
+ * @returns {Object} Animation result with x, y coordinates and metadata
19
+ */
20
+ export function orbitV1(
21
+ centerX,
22
+ centerY,
23
+ radiusX,
24
+ radiusY,
25
+ startAngle,
26
+ elapsedTime,
27
+ duration,
28
+ loop = true,
29
+ clockwise = true,
30
+ easingFn = null,
31
+ callbacks = {},
32
+ state = null
33
+ ) {
34
+ // Update animation time and apply easing
35
+ const {
36
+ t,
37
+ easedT,
38
+ completed,
39
+ state: newState,
40
+ } = Motion._frame(elapsedTime, duration, loop, easingFn, callbacks, state);
41
+ // Calculate current angle
42
+ const direction = clockwise ? 1 : -1;
43
+ const angle = startAngle + direction * easedT * Math.PI * 2;
44
+ // Convert polar coordinates to Cartesian
45
+ const x = centerX + radiusX * Math.cos(angle);
46
+ const y = centerY + radiusY * Math.sin(angle);
47
+ // Return standardized result
48
+ return Motion.animationResult({ x, y, angle }, t, loop, completed, newState);
49
+ }
@@ -0,0 +1,39 @@
1
+ import { Motion } from "./motion";
2
+
3
+ /**
4
+ * Oscillate between min and max value using sine
5
+ *
6
+ * @param {number} min - Minimum value
7
+ * @param {number} max - Maximum value
8
+ * @param {number} elapsedTime - Total elapsed time in seconds
9
+ * @param {number} duration - Duration of one full oscillation in seconds
10
+ * @param {boolean} [loop=true] - Whether animation should loop
11
+ * @param {Function} [easingFn=null] - Optional easing function to apply
12
+ * @param {Object} [callbacks] - Optional callback functions
13
+ * @param {Object} [state] - Internal state tracking for callbacks
14
+ * @returns {Object} Animation result with value and metadata
15
+ */
16
+ export function oscillateV1(
17
+ min,
18
+ max,
19
+ elapsedTime,
20
+ duration,
21
+ loop = true,
22
+ easingFn = null,
23
+ callbacks = {},
24
+ state = null
25
+ ) {
26
+ // Update animation time and apply easing
27
+ const {
28
+ t,
29
+ easedT,
30
+ completed,
31
+ state: newState,
32
+ } = Motion._frame(elapsedTime, duration, loop, easingFn, callbacks, state);
33
+ // Calculate oscillation using sine
34
+ const amplitude = (max - min) / 2;
35
+ const center = min + amplitude;
36
+ const value = center + amplitude * Math.sin(easedT * Math.PI * 2);
37
+ // Return standardized result
38
+ return Motion.animationResult({ value }, t, loop, completed, newState);
39
+ }
@@ -0,0 +1,141 @@
1
+ import { Motion } from "./motion";
2
+
3
+ /**
4
+ * Parabolic arc interpolation
5
+ *
6
+ * @param {number} start - Start value
7
+ * @param {number} peak - Peak value
8
+ * @param {number} end - End value
9
+ * @param {number} elapsedTime - Total elapsed time in seconds
10
+ * @param {number} duration - Duration of animation in seconds
11
+ * @param {boolean} [loop=false] - Whether animation should loop (restart from beginning)
12
+ * @param {boolean} [yoyo=false] - Whether animation should reverse direction at the end
13
+ * @param {Function} [easingFn=null] - Optional easing function to apply
14
+ * @param {Object} [callbacks] - Optional callback functions
15
+ * @param {Object} [state] - Internal state tracking for callbacks
16
+ * @returns {Object} Animation result with value and metadata
17
+ */
18
+ export function parabolicV1(
19
+ start,
20
+ peak,
21
+ end,
22
+ elapsedTime,
23
+ duration,
24
+ loop = false,
25
+ yoyo = false,
26
+ easingFn = null,
27
+ callbacks = {},
28
+ state = null
29
+ ) {
30
+ // Initialize state if needed
31
+ if (!state) {
32
+ state = {
33
+ started: false,
34
+ loopCount: 0,
35
+ direction: 1, // 1 = forward, -1 = backward (for yoyo)
36
+ lastDirection: 1,
37
+ completed: false,
38
+ };
39
+ }
40
+ // Calculate normalized time (0-1)
41
+ let t = duration > 0 ? elapsedTime / duration : 1;
42
+ let completed = false;
43
+ let activeCallbacks = { ...callbacks };
44
+ // Handle yoyo and loop logic
45
+ if (yoyo || loop) {
46
+ // For yoyo + loop, or just loop: calculate cycle
47
+ if (loop) {
48
+ if (yoyo) {
49
+ // For yoyo + loop: full cycle is 2x duration (forward + backward)
50
+ const fullCycle = duration * 2;
51
+ const cycleTime = elapsedTime % fullCycle;
52
+ const cycleCount = Math.floor(elapsedTime / fullCycle);
53
+
54
+ // Determine direction and adjusted t
55
+ const newDirection = cycleTime < duration ? 1 : -1;
56
+ t =
57
+ newDirection === 1 ? cycleTime / duration : 2 - cycleTime / duration;
58
+
59
+ // Check for direction change for callbacks
60
+ if (newDirection !== state.direction) {
61
+ state.direction = newDirection;
62
+ if (state.direction === 1 && activeCallbacks.onLoop) {
63
+ activeCallbacks.onLoop(cycleCount);
64
+ }
65
+ }
66
+
67
+ // Track loop count
68
+ if (cycleCount > state.loopCount) {
69
+ state.loopCount = cycleCount;
70
+ }
71
+ } else {
72
+ // Just loop, no yoyo: reset t at each cycle
73
+ t = t % 1;
74
+
75
+ // Track loop count for callbacks
76
+ const newLoopCount = Math.floor(elapsedTime / duration);
77
+ if (newLoopCount > state.loopCount && activeCallbacks.onLoop) {
78
+ activeCallbacks.onLoop(newLoopCount);
79
+ state.loopCount = newLoopCount;
80
+ }
81
+ }
82
+ } else if (yoyo && !loop) {
83
+ // Yoyo without loop: complete one cycle then stop
84
+ if (t <= 1) {
85
+ // Forward part of the cycle
86
+ state.direction = 1;
87
+ } else if (t <= 2) {
88
+ // Backward part of the cycle
89
+ t = 2 - t;
90
+ state.direction = -1;
91
+ } else {
92
+ // Complete
93
+ t = 0;
94
+ completed = true;
95
+ state.direction = 1;
96
+ }
97
+ }
98
+ } else {
99
+ // No loop or yoyo: standard behavior
100
+ if (t >= 1) {
101
+ t = 1;
102
+ completed = true;
103
+ }
104
+ }
105
+ // Call onStart callback once
106
+ if (!state.started && activeCallbacks.onStart) {
107
+ activeCallbacks.onStart();
108
+ state.started = true;
109
+ }
110
+ // Call onComplete callback once when non-looping animation completes
111
+ if (completed && !state.completed && activeCallbacks.onComplete) {
112
+ activeCallbacks.onComplete();
113
+ state.completed = true;
114
+ }
115
+ // Apply easing if provided
116
+ const easedT = easingFn ? easingFn(t) : t;
117
+ // Calculate quadratic coefficients
118
+ // For a parabola that passes through three points: (0,start), (0.5,peak), (1,end)
119
+ const a = start + end - 2 * peak;
120
+ const b = 2 * (peak - start);
121
+ const c = start;
122
+ // Apply the quadratic formula: a*t^2 + b*t + c
123
+ const value = a * easedT * easedT + b * easedT + c;
124
+ // Update state for next frame
125
+ const newState = {
126
+ ...state,
127
+ lastDirection: state.direction,
128
+ completed: completed || state.completed,
129
+ };
130
+ // Return standardized result
131
+ return Motion.animationResult(
132
+ {
133
+ value,
134
+ direction: state.direction, // Include current direction (1 = forward, -1 = backward)
135
+ },
136
+ t,
137
+ loop || (yoyo && !completed),
138
+ completed,
139
+ newState
140
+ );
141
+ }
@@ -0,0 +1,147 @@
1
+ import { Motion } from "./motion";
2
+ /**
3
+ * Simple patrol animation that moves randomly within a radius
4
+ * Character moves along cardinal directions with waiting periods
5
+ * @function patrolV1
6
+ * @param {number} initialX - Initial X position (center point)
7
+ * @param {number} initialY - Initial Y position (center point)
8
+ * @param {number} elapsedTime - Total elapsed time in seconds
9
+ * @param {number} moveTime - Time to spend moving between points
10
+ * @param {number} waitTime - Time to wait at each point
11
+ * @param {number} radius - Maximum distance from center point
12
+ * @param {boolean} [loop=true] - Whether animation should loop
13
+ * @param {Object} [state] - Internal state tracking
14
+ * @returns {Object} Animation result with position and direction
15
+ *
16
+ * @section Metadata
17
+ * @property {boolean} requiresState - Indicates this animation must be called with a persistent state object across frames.
18
+ * This ensures consistent behavior (e.g. waypoint progress, direction, internal timers).
19
+ * You should initialize and reuse a `state` object outside the update loop.
20
+ */
21
+ export function patrolV1(
22
+ initialX,
23
+ initialY,
24
+ elapsedTime,
25
+ moveTime,
26
+ waitTime,
27
+ radius,
28
+ loop = true,
29
+ state = null
30
+ ) {
31
+ // Initialize state if not provided
32
+ if (!state) {
33
+ state = {
34
+ currentX: initialX,
35
+ currentY: initialY,
36
+ targetX: initialX,
37
+ targetY: initialY,
38
+ isWaiting: true,
39
+ waitStartTime: 0,
40
+ moveStartTime: 0,
41
+ moveCount: 0,
42
+ direction: "idle",
43
+ };
44
+ }
45
+ // Create a simple random function
46
+ const rand = () => Math.random();
47
+ // Calculate time and movement state
48
+ let isWaiting = state.isWaiting;
49
+ let x = state.currentX;
50
+ let y = state.currentY;
51
+ let direction = state.direction;
52
+ // Check if we need to transition between waiting and moving
53
+ if (isWaiting) {
54
+ // Currently waiting
55
+ if (elapsedTime - state.waitStartTime >= waitTime) {
56
+ // Wait period is over, pick a new target point
57
+ isWaiting = false;
58
+ state.moveStartTime = elapsedTime;
59
+ // Choose a new cardinal direction (up, down, left, right)
60
+ const directions = ["up", "down", "left", "right"];
61
+ direction = directions[Math.floor(rand() * 4)];
62
+ // Calculate potential new target based on direction
63
+ let newTargetX = state.currentX;
64
+ let newTargetY = state.currentY;
65
+ // Random move distance (20-80% of radius)
66
+ const moveDistance = radius * (0.2 + rand() * 0.6);
67
+ // handle direction by manipulating corresponding position
68
+ switch (direction) {
69
+ case "up":
70
+ newTargetY = state.currentY - moveDistance;
71
+ break;
72
+ case "down":
73
+ newTargetY = state.currentY + moveDistance;
74
+ break;
75
+ case "left":
76
+ newTargetX = state.currentX - moveDistance;
77
+ break;
78
+ case "right":
79
+ newTargetX = state.currentX + moveDistance;
80
+ break;
81
+ }
82
+ // If new target would be outside radius, move toward center instead
83
+ const newDistSq =
84
+ Math.pow(newTargetX - initialX, 2) + Math.pow(newTargetY - initialY, 2);
85
+ if (newDistSq > radius * radius) {
86
+ // Move back toward center
87
+ if (direction === "up" || direction === "down") {
88
+ // Moving vertically, adjust Y
89
+ newTargetY = initialY;
90
+ direction = state.currentY > initialY ? "up" : "down";
91
+ } else {
92
+ // Moving horizontally, adjust X
93
+ newTargetX = initialX;
94
+ direction = state.currentX > initialX ? "left" : "right";
95
+ }
96
+ }
97
+ // Update State
98
+ state.targetX = newTargetX;
99
+ state.targetY = newTargetY;
100
+ state.direction = direction;
101
+ state.moveCount++;
102
+ }
103
+ } else {
104
+ // Currently moving
105
+ const moveProgress = (elapsedTime - state.moveStartTime) / moveTime;
106
+ if (moveProgress >= 1) {
107
+ // Reached target, start waiting
108
+ isWaiting = true;
109
+ state.waitStartTime = elapsedTime;
110
+ state.currentX = state.targetX;
111
+ state.currentY = state.targetY;
112
+ direction = "idle";
113
+ } else {
114
+ // Interpolate position
115
+ x = state.currentX + (state.targetX - state.currentX) * moveProgress;
116
+ y = state.currentY + (state.targetY - state.currentY) * moveProgress;
117
+ }
118
+ }
119
+ // Update state
120
+ state.isWaiting = isWaiting;
121
+ state.direction = direction;
122
+ if (!isWaiting) {
123
+ state.currentX = x;
124
+ state.currentY = y;
125
+ }
126
+ // Calculate t value (0-1)
127
+ const cycleTime = moveTime + waitTime;
128
+ const t = (elapsedTime % cycleTime) / cycleTime;
129
+ // Calculate distance from center
130
+ const distanceFromCenter = Math.sqrt(
131
+ Math.pow(x - initialX, 2) + Math.pow(y - initialY, 2)
132
+ );
133
+ // Return result
134
+ return Motion.animationResult(
135
+ {
136
+ x,
137
+ y,
138
+ moving: !isWaiting,
139
+ direction,
140
+ distanceFromCenter,
141
+ },
142
+ t,
143
+ loop,
144
+ false,
145
+ state
146
+ );
147
+ }
@@ -0,0 +1,48 @@
1
+ import { Motion } from "./motion";
2
+
3
+ /**
4
+ * Pendulum animation - swings around a center angle with optional damping
5
+ *
6
+ * @param {number} originAngle - Resting angle in radians (typically 0)
7
+ * @param {number} amplitude - Maximum swing from center (in radians)
8
+ * @param {number} elapsedTime - Total elapsed time in seconds
9
+ * @param {number} duration - Time for one full swing (left-right-left)
10
+ * @param {boolean} [loop=true] - Whether the animation loops
11
+ * @param {boolean} [damped=false] - If true, swing slows down over time
12
+ * @param {Function} [easingFn=null] - Optional easing function
13
+ * @param {Object} [callbacks={}] - Optional callback functions
14
+ * @param {Object} [state=null] - Internal state tracking
15
+ * @returns {Object} Animation result with angle and metadata
16
+ */
17
+ export function pendulumV1(
18
+ originAngle,
19
+ amplitude,
20
+ elapsedTime,
21
+ duration,
22
+ loop = true,
23
+ damped = false,
24
+ easingFn = null,
25
+ callbacks = {},
26
+ state = null
27
+ ) {
28
+ const {
29
+ t,
30
+ easedT,
31
+ completed,
32
+ state: newState,
33
+ } = Motion._frame(elapsedTime, duration, loop, null, callbacks, state); // <- remove easing here
34
+
35
+ // Optional decay multiplier (energy loss)
36
+ const decay = damped && !loop ? Math.exp(-4 * t) : 1;
37
+
38
+ // Oscillate with cosine (max at start, zero at center)
39
+ let angle = originAngle + amplitude * Math.cos(easedT * 2 * Math.PI) * decay;
40
+
41
+ // Apply optional easing to the *angle*, not the time
42
+ if (easingFn) {
43
+ const normalized = (angle - originAngle) / (amplitude * decay);
44
+ angle = originAngle + easingFn((normalized + 1) / 2) * amplitude * decay * 2 - amplitude * decay;
45
+ }
46
+
47
+ return Motion.animationResult({ angle }, t, loop, completed, newState);
48
+ }
@@ -0,0 +1,88 @@
1
+ import { Easing } from "./easing";
2
+ import { Motion } from "./motion";
3
+ import { Tween } from "./tween";
4
+ /**
5
+ * Pulse between min and max value and back
6
+ *
7
+ * @param {number} min - Minimum value
8
+ * @param {number} max - Maximum value
9
+ * @param {number} elapsedTime - Total elapsed time in seconds
10
+ * @param {number} duration - Duration of one full pulse in seconds
11
+ * @param {boolean} [loop=true] - Whether animation should loop
12
+ * @param {boolean} [yoyo=false] - Whether to use separate easing for return journey
13
+ * @param {Function} [easingFn=null] - Optional easing function to apply
14
+ * @param {Object} [callbacks] - Optional callback functions
15
+ * @returns {Object} Animation result with value and metadata
16
+ */
17
+ export function pulseV1(
18
+ min,
19
+ max,
20
+ elapsedTime,
21
+ duration,
22
+ loop = true,
23
+ yoyo = false,
24
+ easingFn = null,
25
+ callbacks = {}
26
+ ) {
27
+ // Normalize time (0-1) within current cycle
28
+ let t = elapsedTime / duration;
29
+ let phase = "forward";
30
+ // Handle looping vs. non-looping
31
+ if (loop) {
32
+ // Calculate loop count (for callbacks)
33
+ const loopCount = Math.floor(t);
34
+ // Use only the fractional part for looping (0-1 repeating)
35
+ t = t % 1;
36
+ // Call onLoop callback if provided and we crossed a loop boundary
37
+ if (loopCount > 0 && callbacks.onLoop) {
38
+ callbacks.onLoop(loopCount);
39
+ }
40
+ } else {
41
+ // Clamp to 1 for non-looping animations
42
+ if (t > 1) t = 1;
43
+ }
44
+ // Call onStart callback if needed
45
+ if (t > 0 && elapsedTime <= duration && callbacks.onStart) {
46
+ callbacks.onStart();
47
+ }
48
+ let value;
49
+ if (yoyo) {
50
+ // Yoyo approach - separate forward and return journeys
51
+ if (t < 0.5) {
52
+ // Forward journey (0 to 0.5 becomes 0 to 1)
53
+ const adjustedT = t * 2;
54
+ // Apply easing if provided
55
+ const easedT = easingFn ? easingFn(adjustedT) : adjustedT;
56
+ // Interpolate from min to max
57
+ value = min + (max - min) * easedT;
58
+ phase = "forward";
59
+ } else {
60
+ // Return journey (0.5 to 1 becomes 0 to 1)
61
+ const adjustedT = (t - 0.5) * 2;
62
+ // Apply easing if provided
63
+ const easedT = easingFn ? easingFn(adjustedT) : adjustedT;
64
+ // Interpolate from max to min
65
+ value = max - (max - min) * easedT;
66
+ phase = "return";
67
+ // Call onYoyoTurn callback at the turning point
68
+ if (t >= 0.5 && t < 0.51 && callbacks.onYoyoTurn) {
69
+ callbacks.onYoyoTurn();
70
+ }
71
+ }
72
+ } else {
73
+ // Standard pulse approach (triangle wave)
74
+ // Apply easing to normalized time if provided
75
+ const easedT = easingFn ? easingFn(t) : t;
76
+ // Convert to 0-1-0 pattern
77
+ const adjusted = easedT < 0.5 ? easedT * 2 : 2 - easedT * 2;
78
+ value = min + (max - min) * adjusted;
79
+ }
80
+ // Check if non-looping animation is complete
81
+ const isDone = !loop && t >= 1;
82
+ // Call onComplete if animation has completed
83
+ if (isDone && callbacks.onComplete) {
84
+ callbacks.onComplete();
85
+ }
86
+ // Return standardized result
87
+ return Motion.animationResult({ value, phase }, t, loop, isDone);
88
+ }
@@ -0,0 +1,83 @@
1
+ import { Motion } from "./motion";
2
+
3
+ /**
4
+ * Shake animation with decreasing intensity
5
+ *
6
+ * @param {number} centerX - Center X position
7
+ * @param {number} centerY - Center Y position
8
+ * @param {number} maxOffsetX - Maximum X offset
9
+ * @param {number} maxOffsetY - Maximum Y offset
10
+ * @param {number} frequency - Frequency of shakes
11
+ * @param {number} decay - How quickly the shake decreases (0-1)
12
+ * @param {number} elapsedTime - Total elapsed time in seconds
13
+ * @param {number} duration - Duration of animation in seconds
14
+ * @param {boolean} [loop=false] - Whether animation should loop
15
+ * @param {Function} [easingFn=null] - Optional easing function to apply
16
+ * @param {Object} [callbacks] - Optional callback functions
17
+ * @param {Object} [state] - Internal state tracking for callbacks
18
+ * @returns {Object} Animation result with x, y coordinates and metadata
19
+ */
20
+ export function shakeV1(
21
+ centerX,
22
+ centerY,
23
+ maxOffsetX,
24
+ maxOffsetY,
25
+ frequency,
26
+ decay,
27
+ elapsedTime,
28
+ duration,
29
+ loop = false,
30
+ easingFn = null,
31
+ callbacks = {},
32
+ state = null
33
+ ) {
34
+ // Update animation time and apply easing
35
+ const {
36
+ t,
37
+ easedT,
38
+ completed,
39
+ state: newState,
40
+ } = Motion._frame(elapsedTime, duration, loop, easingFn, callbacks, state);
41
+
42
+ // Apply decay to reduce shake over time
43
+ const intensity = Math.pow(1 - easedT, decay);
44
+
45
+ // Create randomized but deterministic shake using sine/cosine at different frequencies
46
+ const angleX = easedT * Math.PI * 2 * frequency;
47
+ const angleY = easedT * Math.PI * 2 * frequency * 1.3; // Different frequency for Y
48
+
49
+ // Add multiple sine waves at different frequencies for more organic motion
50
+ const xOffset =
51
+ intensity *
52
+ maxOffsetX *
53
+ (Math.sin(angleX) * 0.6 +
54
+ Math.sin(angleX * 2.5) * 0.3 +
55
+ Math.sin(angleX * 5.6) * 0.1);
56
+
57
+ const yOffset =
58
+ intensity *
59
+ maxOffsetY *
60
+ (Math.cos(angleY) * 0.6 +
61
+ Math.cos(angleY * 2.7) * 0.3 +
62
+ Math.cos(angleY * 6.3) * 0.1);
63
+
64
+ // Make sure we end at the center
65
+ let x = centerX + xOffset;
66
+ let y = centerY + yOffset;
67
+
68
+ // Gradually return to center in the last 10% of the animation
69
+ if (easedT > 0.9) {
70
+ const returnT = (easedT - 0.9) / 0.1;
71
+ x = centerX + xOffset * (1 - returnT);
72
+ y = centerY + yOffset * (1 - returnT);
73
+ }
74
+
75
+ // Return standardized result
76
+ return Motion.animationResult(
77
+ { x, y, intensity },
78
+ t,
79
+ loop,
80
+ completed,
81
+ newState
82
+ );
83
+ }