@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,408 @@
1
+ import { Painter } from "../painter/painter";
2
+ import { Shape } from "./shape";
3
+
4
+ /**
5
+ * SVGShape - A specialized Shape class that can render SVG path data
6
+ */
7
+ export class SVGShape extends Shape {
8
+ /**
9
+ * @param {number} x - Center X position
10
+ * @param {number} y - Center Y position
11
+ * @param {string} svgPathData - SVG path data string (e.g. "M0,0 L10,10...")
12
+ * @param {object} options - Standard shape options plus SVG-specific options
13
+ * @param {number} [options.scale=1] - Scale factor for the SVG
14
+ * @param {boolean} [options.centerPath=true] - Automatically center the path
15
+ * @param {number} [options.animationProgress=1] - Animation progress (0-1)
16
+ */
17
+ constructor(svgPathData, options = {}) {
18
+ console.log("SVGShape", options.x)
19
+ super(options);
20
+ // SVG specific options
21
+ this.scale = options.scale || 1;
22
+ this.centerPath =
23
+ options.centerPath !== undefined ? options.centerPath : true;
24
+ this.animationProgress =
25
+ options.animationProgress !== undefined ? options.animationProgress : 1;
26
+
27
+ // Parse the SVG path data
28
+ this.svgPathData = svgPathData;
29
+ this.pathCommands = this.parseSVGPath(svgPathData);
30
+
31
+ // If centering is enabled, center and scale the path
32
+ if (this.centerPath) {
33
+ this.pathCommands = this.centerAndScalePath(
34
+ this.pathCommands,
35
+ this.scale
36
+ );
37
+ } else {
38
+ this.pathCommands = this.scalePath(this.pathCommands, this.scale);
39
+ }
40
+
41
+ // Drawing state tracking
42
+ this.prevX = 0;
43
+ this.prevY = 0;
44
+ this.currentPoint = { x: 0, y: 0 };
45
+ }
46
+
47
+ /**
48
+ * Parse an SVG path string into a command array for rendering
49
+ * @param {string} svgPath - SVG path data string
50
+ * @returns {Array} Array of path commands
51
+ */
52
+ parseSVGPath(svgPath) {
53
+ // Regular expressions to match SVG path commands
54
+ const moveRegex = /M\s*([-\d.]+)[,\s]*([-\d.]+)/g;
55
+ const lineRegex = /L\s*([-\d.]+)[,\s]*([-\d.]+)/g;
56
+ const curveRegex =
57
+ /C\s*([-\d.]+)[,\s]*([-\d.]+)\s*([-\d.]+)[,\s]*([-\d.]+)\s*([-\d.]+)[,\s]*([-\d.]+)/g;
58
+ const zRegex = /Z/g;
59
+
60
+ // Arrays to store the parsed commands
61
+ const commands = [];
62
+
63
+ // Match and process Move commands (M)
64
+ let match;
65
+ while ((match = moveRegex.exec(svgPath)) !== null) {
66
+ commands.push(["M", parseFloat(match[1]), parseFloat(match[2])]);
67
+ }
68
+
69
+ // Match and process Line commands (L)
70
+ while ((match = lineRegex.exec(svgPath)) !== null) {
71
+ // Convert line to bezier curve for animation
72
+ const x = parseFloat(match[1]);
73
+ const y = parseFloat(match[2]);
74
+
75
+ // Find the previous point to calculate the control points
76
+ let prevX = 0;
77
+ let prevY = 0;
78
+ for (let i = commands.length - 1; i >= 0; i--) {
79
+ const cmd = commands[i];
80
+ if (cmd[0] === "M") {
81
+ prevX = cmd[1];
82
+ prevY = cmd[2];
83
+ break;
84
+ } else if (cmd[0] === "C") {
85
+ prevX = cmd[5];
86
+ prevY = cmd[6];
87
+ break;
88
+ }
89
+ }
90
+
91
+ // Calculate control points for an approximated curve
92
+ // For a line, we can use control points that are 1/3 and 2/3 along the line
93
+ const cp1x = prevX + (x - prevX) / 3;
94
+ const cp1y = prevY + (y - prevY) / 3;
95
+ const cp2x = prevX + (2 * (x - prevX)) / 3;
96
+ const cp2y = prevY + (2 * (y - prevY)) / 3;
97
+
98
+ commands.push(["C", cp1x, cp1y, cp2x, cp2y, x, y]);
99
+ }
100
+
101
+ // Match and process Curve commands (C)
102
+ while ((match = curveRegex.exec(svgPath)) !== null) {
103
+ commands.push([
104
+ "C",
105
+ parseFloat(match[1]),
106
+ parseFloat(match[2]),
107
+ parseFloat(match[3]),
108
+ parseFloat(match[4]),
109
+ parseFloat(match[5]),
110
+ parseFloat(match[6]),
111
+ ]);
112
+ }
113
+
114
+ // Match and process Close commands (Z)
115
+ if (zRegex.test(svgPath)) {
116
+ commands.push(["Z"]);
117
+ }
118
+
119
+ return commands;
120
+ }
121
+
122
+ /**
123
+ * Center and scale the path commands
124
+ * @param {Array} path - Array of path commands
125
+ * @param {number} scale - Scale factor
126
+ * @returns {Array} Centered and scaled path commands
127
+ */
128
+ centerAndScalePath(path, scale) {
129
+ // Find the bounds of the path
130
+ let minX = Infinity,
131
+ minY = Infinity;
132
+ let maxX = -Infinity,
133
+ maxY = -Infinity;
134
+
135
+ for (const cmd of path) {
136
+ if (cmd[0] === "M") {
137
+ minX = Math.min(minX, cmd[1]);
138
+ minY = Math.min(minY, cmd[2]);
139
+ maxX = Math.max(maxX, cmd[1]);
140
+ maxY = Math.max(maxY, cmd[2]);
141
+ } else if (cmd[0] === "C") {
142
+ minX = Math.min(minX, cmd[1], cmd[3], cmd[5]);
143
+ minY = Math.min(minY, cmd[2], cmd[4], cmd[6]);
144
+ maxX = Math.max(maxX, cmd[1], cmd[3], cmd[5]);
145
+ maxY = Math.max(maxY, cmd[2], cmd[4], cmd[6]);
146
+ }
147
+ }
148
+
149
+ // Calculate center of the original path
150
+ const pathCenterX = (minX + maxX) / 2;
151
+ const pathCenterY = (minY + maxY) / 2;
152
+
153
+ // Save the original dimensions for bounds calculation
154
+ this.originalWidth = (maxX - minX) * scale;
155
+ this.originalHeight = (maxY - minY) * scale;
156
+
157
+ // Translate the center to the origin and scale
158
+ return path.map((cmd) => {
159
+ if (cmd[0] === "M") {
160
+ return [
161
+ "M",
162
+ (cmd[1] - pathCenterX) * scale,
163
+ (cmd[2] - pathCenterY) * scale,
164
+ ];
165
+ } else if (cmd[0] === "C") {
166
+ return [
167
+ "C",
168
+ (cmd[1] - pathCenterX) * scale,
169
+ (cmd[2] - pathCenterY) * scale,
170
+ (cmd[3] - pathCenterX) * scale,
171
+ (cmd[4] - pathCenterY) * scale,
172
+ (cmd[5] - pathCenterX) * scale,
173
+ (cmd[6] - pathCenterY) * scale,
174
+ ];
175
+ } else {
176
+ return [...cmd]; // Z command
177
+ }
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Scale the path commands without centering
183
+ * @param {Array} path - Array of path commands
184
+ * @param {number} scale - Scale factor
185
+ * @returns {Array} Scaled path commands
186
+ */
187
+ scalePath(path, scale) {
188
+ // Find the bounds for dimension calculation
189
+ let minX = Infinity,
190
+ minY = Infinity;
191
+ let maxX = -Infinity,
192
+ maxY = -Infinity;
193
+
194
+ for (const cmd of path) {
195
+ if (cmd[0] === "M") {
196
+ minX = Math.min(minX, cmd[1]);
197
+ minY = Math.min(minY, cmd[2]);
198
+ maxX = Math.max(maxX, cmd[1]);
199
+ maxY = Math.max(maxY, cmd[2]);
200
+ } else if (cmd[0] === "C") {
201
+ minX = Math.min(minX, cmd[1], cmd[3], cmd[5]);
202
+ minY = Math.min(minY, cmd[2], cmd[4], cmd[6]);
203
+ maxX = Math.max(maxX, cmd[1], cmd[3], cmd[5]);
204
+ maxY = Math.max(maxY, cmd[2], cmd[4], cmd[6]);
205
+ }
206
+ }
207
+
208
+ // Save the original dimensions for bounds calculation
209
+ this.originalWidth = (maxX - minX) * scale;
210
+ this.originalHeight = (maxY - minY) * scale;
211
+
212
+ // Scale without centering
213
+ return path.map((cmd) => {
214
+ if (cmd[0] === "M") {
215
+ return ["M", cmd[1] * scale, cmd[2] * scale];
216
+ } else if (cmd[0] === "C") {
217
+ return [
218
+ "C",
219
+ cmd[1] * scale,
220
+ cmd[2] * scale,
221
+ cmd[3] * scale,
222
+ cmd[4] * scale,
223
+ cmd[5] * scale,
224
+ cmd[6] * scale,
225
+ ];
226
+ } else {
227
+ return [...cmd]; // Z command
228
+ }
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Calculate a point along a bezier curve at time t
234
+ * @param {Array} segment - Path segment command
235
+ * @param {number} t - Time parameter (0-1)
236
+ * @returns {Object} Point coordinates {x, y}
237
+ */
238
+ getBezierPoint(segment, t) {
239
+ if (segment[0] === "M") {
240
+ // For move commands, just return the point
241
+ return { x: segment[1], y: segment[2] };
242
+ } else if (segment[0] === "C") {
243
+ // For Cubic Bezier curves, calculate the point at t
244
+ const startX = this.prevX;
245
+ const startY = this.prevY;
246
+ const cp1x = segment[1];
247
+ const cp1y = segment[2];
248
+ const cp2x = segment[3];
249
+ const cp2y = segment[4];
250
+ const endX = segment[5];
251
+ const endY = segment[6];
252
+
253
+ // Cubic Bezier formula
254
+ const x =
255
+ Math.pow(1 - t, 3) * startX +
256
+ 3 * Math.pow(1 - t, 2) * t * cp1x +
257
+ 3 * (1 - t) * Math.pow(t, 2) * cp2x +
258
+ Math.pow(t, 3) * endX;
259
+
260
+ const y =
261
+ Math.pow(1 - t, 3) * startY +
262
+ 3 * Math.pow(1 - t, 2) * t * cp1y +
263
+ 3 * (1 - t) * Math.pow(t, 2) * cp2y +
264
+ Math.pow(t, 3) * endY;
265
+
266
+ return { x, y };
267
+ }
268
+
269
+ return { x: 0, y: 0 };
270
+ }
271
+
272
+ /**
273
+ * Get a subset of the path up to the current animation progress
274
+ * @returns {Array} Array of path commands representing the partial path
275
+ */
276
+ getPartialPath() {
277
+ const result = [];
278
+ let totalSegments = this.pathCommands.length;
279
+ let segmentIndex = Math.floor(this.animationProgress * totalSegments);
280
+ let segmentProgress = (this.animationProgress * totalSegments) % 1;
281
+
282
+ // Initialize with a null previous point to indicate no previous point exists
283
+ let hasPrevPoint = false;
284
+ this.prevX = 0;
285
+ this.prevY = 0;
286
+
287
+ // Add all completed segments
288
+ for (let i = 0; i < segmentIndex; i++) {
289
+ const cmd = this.pathCommands[i];
290
+
291
+ // Add to result
292
+ result.push([...cmd]);
293
+
294
+ // Track points for bezier calculations
295
+ if (cmd[0] === "M") {
296
+ // For Move commands, just update position
297
+ this.prevX = cmd[1];
298
+ this.prevY = cmd[2];
299
+ hasPrevPoint = true; // Now we have a previous point
300
+ } else if (cmd[0] === "C") {
301
+ // For Bezier commands, update to end point
302
+ this.prevX = cmd[5];
303
+ this.prevY = cmd[6];
304
+ hasPrevPoint = true;
305
+ }
306
+ }
307
+
308
+ // Add the current segment with partial progress
309
+ if (segmentIndex < totalSegments) {
310
+ const currentSegment = this.pathCommands[segmentIndex];
311
+
312
+ if (currentSegment[0] === "M") {
313
+ // For Move commands, add the full move
314
+ result.push([...currentSegment]);
315
+ this.prevX = currentSegment[1];
316
+ this.prevY = currentSegment[2];
317
+ this.currentPoint = { x: currentSegment[1], y: currentSegment[2] };
318
+ hasPrevPoint = true;
319
+ } else if (currentSegment[0] === "C") {
320
+ if (!hasPrevPoint) {
321
+ // If there's no previous point but we're trying to draw a curve,
322
+ // we need to find the closest previous Move command
323
+ for (let i = segmentIndex - 1; i >= 0; i--) {
324
+ if (this.pathCommands[i][0] === "M") {
325
+ this.prevX = this.pathCommands[i][1];
326
+ this.prevY = this.pathCommands[i][2];
327
+ hasPrevPoint = true;
328
+ break;
329
+ }
330
+ }
331
+
332
+ // If still no previous point, use (0,0)
333
+ if (!hasPrevPoint) {
334
+ this.prevX = 0;
335
+ this.prevY = 0;
336
+ }
337
+ }
338
+
339
+ // Calculate the partial curve point
340
+ const point = this.getBezierPoint(currentSegment, segmentProgress);
341
+
342
+ // Add partial curve to result
343
+ result.push([
344
+ "C",
345
+ currentSegment[1],
346
+ currentSegment[2],
347
+ currentSegment[3],
348
+ currentSegment[4],
349
+ point.x,
350
+ point.y,
351
+ ]);
352
+
353
+ this.currentPoint = point;
354
+ }
355
+ }
356
+
357
+ return result;
358
+ }
359
+
360
+ /**
361
+ * Draw the SVG path
362
+ */
363
+ draw() {
364
+ super.draw();
365
+ // Get the path to draw based on animation progress
366
+ const pathToDraw = this.getPartialPath();
367
+ //console.log(pathToDraw);
368
+ // Use Painter to render the path
369
+ Painter.lines.path(
370
+ pathToDraw,
371
+ this.color,
372
+ this.stroke,
373
+ this.lineWidth
374
+ );
375
+ }
376
+
377
+ /**
378
+ * Get the current position of the "pen" for animations
379
+ * @returns {Object} Current point {x, y} in world coordinates
380
+ */
381
+ getCurrentPoint() {
382
+ return {
383
+ x: this.currentPoint.x,
384
+ y: this.currentPoint.y,
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Set the animation progress
390
+ * @param {number} progress - Animation progress (0-1)
391
+ */
392
+ setAnimationProgress(progress) {
393
+ this.animationProgress = Math.max(0, Math.min(1, progress));
394
+ }
395
+
396
+ /**
397
+ * Get bounds of the shape for hit detection and layout
398
+ * @returns {Object} Bounds {x, y, width, height}
399
+ */
400
+ calculateBounds() {
401
+ return {
402
+ x: this.x,
403
+ y: this.y,
404
+ width: this.originalWidth || 100,
405
+ height: this.originalHeight || 100,
406
+ };
407
+ }
408
+ }