@guinetik/gcanvas 1.0.2 → 1.0.4

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 (217) hide show
  1. package/dist/gcanvas.es.js +25656 -0
  2. package/dist/gcanvas.es.min.js +1 -0
  3. package/dist/gcanvas.umd.js +1 -0
  4. package/dist/gcanvas.umd.min.js +1 -0
  5. package/package.json +23 -6
  6. package/src/game/objects/index.js +1 -0
  7. package/src/game/objects/spritesheet.js +260 -0
  8. package/src/game/ui/theme.js +6 -0
  9. package/src/io/keys.js +9 -1
  10. package/src/math/boolean.js +481 -0
  11. package/src/math/index.js +1 -0
  12. package/.github/workflows/release.yaml +0 -70
  13. package/.jshintrc +0 -4
  14. package/.vscode/settings.json +0 -22
  15. package/CLAUDE.md +0 -310
  16. package/blackhole.jpg +0 -0
  17. package/demo.png +0 -0
  18. package/demos/CNAME +0 -1
  19. package/demos/animations.html +0 -31
  20. package/demos/basic.html +0 -38
  21. package/demos/baskara.html +0 -31
  22. package/demos/bezier.html +0 -35
  23. package/demos/beziersignature.html +0 -29
  24. package/demos/blackhole.html +0 -28
  25. package/demos/blob.html +0 -35
  26. package/demos/coordinates.html +0 -698
  27. package/demos/cube3d.html +0 -23
  28. package/demos/demos.css +0 -303
  29. package/demos/dino.html +0 -42
  30. package/demos/easing.html +0 -28
  31. package/demos/events.html +0 -195
  32. package/demos/fluent.html +0 -647
  33. package/demos/fluid-simple.html +0 -22
  34. package/demos/fluid.html +0 -37
  35. package/demos/fractals.html +0 -36
  36. package/demos/gameobjects.html +0 -626
  37. package/demos/genart.html +0 -26
  38. package/demos/gendream.html +0 -26
  39. package/demos/group.html +0 -36
  40. package/demos/home.html +0 -587
  41. package/demos/index.html +0 -376
  42. package/demos/isometric.html +0 -34
  43. package/demos/js/animations.js +0 -452
  44. package/demos/js/basic.js +0 -204
  45. package/demos/js/baskara.js +0 -751
  46. package/demos/js/bezier.js +0 -692
  47. package/demos/js/beziersignature.js +0 -241
  48. package/demos/js/blackhole/accretiondisk.obj.js +0 -379
  49. package/demos/js/blackhole/blackhole.obj.js +0 -318
  50. package/demos/js/blackhole/index.js +0 -409
  51. package/demos/js/blackhole/particle.js +0 -56
  52. package/demos/js/blackhole/starfield.obj.js +0 -218
  53. package/demos/js/blob.js +0 -2276
  54. package/demos/js/coordinates.js +0 -840
  55. package/demos/js/cube3d.js +0 -789
  56. package/demos/js/dino.js +0 -1420
  57. package/demos/js/easing.js +0 -477
  58. package/demos/js/fluent.js +0 -183
  59. package/demos/js/fluid-simple.js +0 -253
  60. package/demos/js/fluid.js +0 -527
  61. package/demos/js/fractals.js +0 -931
  62. package/demos/js/fractalworker.js +0 -93
  63. package/demos/js/gameobjects.js +0 -176
  64. package/demos/js/genart.js +0 -268
  65. package/demos/js/gendream.js +0 -209
  66. package/demos/js/group.js +0 -140
  67. package/demos/js/info-toggle.js +0 -25
  68. package/demos/js/isometric.js +0 -863
  69. package/demos/js/kerr.js +0 -1556
  70. package/demos/js/lavalamp.js +0 -590
  71. package/demos/js/layout.js +0 -354
  72. package/demos/js/mondrian.js +0 -285
  73. package/demos/js/opacity.js +0 -275
  74. package/demos/js/painter.js +0 -484
  75. package/demos/js/particles-showcase.js +0 -514
  76. package/demos/js/particles.js +0 -299
  77. package/demos/js/patterns.js +0 -397
  78. package/demos/js/penrose/artifact.js +0 -69
  79. package/demos/js/penrose/blackhole.js +0 -121
  80. package/demos/js/penrose/constants.js +0 -73
  81. package/demos/js/penrose/game.js +0 -943
  82. package/demos/js/penrose/lore.js +0 -278
  83. package/demos/js/penrose/penrosescene.js +0 -892
  84. package/demos/js/penrose/ship.js +0 -216
  85. package/demos/js/penrose/sounds.js +0 -211
  86. package/demos/js/penrose/voidparticle.js +0 -55
  87. package/demos/js/penrose/voidscene.js +0 -258
  88. package/demos/js/penrose/voidship.js +0 -144
  89. package/demos/js/penrose/wormhole.js +0 -46
  90. package/demos/js/pipeline.js +0 -555
  91. package/demos/js/plane3d.js +0 -256
  92. package/demos/js/platformer.js +0 -1579
  93. package/demos/js/scene.js +0 -304
  94. package/demos/js/scenes.js +0 -320
  95. package/demos/js/schrodinger.js +0 -410
  96. package/demos/js/schwarzschild.js +0 -1023
  97. package/demos/js/shapes.js +0 -628
  98. package/demos/js/space/alien.js +0 -171
  99. package/demos/js/space/boom.js +0 -98
  100. package/demos/js/space/boss.js +0 -353
  101. package/demos/js/space/buff.js +0 -73
  102. package/demos/js/space/bullet.js +0 -102
  103. package/demos/js/space/constants.js +0 -85
  104. package/demos/js/space/game.js +0 -1884
  105. package/demos/js/space/hud.js +0 -112
  106. package/demos/js/space/laserbeam.js +0 -179
  107. package/demos/js/space/lightning.js +0 -277
  108. package/demos/js/space/minion.js +0 -192
  109. package/demos/js/space/missile.js +0 -212
  110. package/demos/js/space/player.js +0 -430
  111. package/demos/js/space/powerup.js +0 -90
  112. package/demos/js/space/starfield.js +0 -58
  113. package/demos/js/space/starpower.js +0 -90
  114. package/demos/js/spacetime.js +0 -559
  115. package/demos/js/sphere3d.js +0 -229
  116. package/demos/js/sprite.js +0 -473
  117. package/demos/js/svgtween.js +0 -204
  118. package/demos/js/tde/accretiondisk.js +0 -471
  119. package/demos/js/tde/blackhole.js +0 -219
  120. package/demos/js/tde/blackholescene.js +0 -209
  121. package/demos/js/tde/config.js +0 -59
  122. package/demos/js/tde/index.js +0 -820
  123. package/demos/js/tde/jets.js +0 -290
  124. package/demos/js/tde/lensedstarfield.js +0 -154
  125. package/demos/js/tde/tdestar.js +0 -297
  126. package/demos/js/tde/tidalstream.js +0 -372
  127. package/demos/js/tde_old/blackhole.obj.js +0 -354
  128. package/demos/js/tde_old/debris.obj.js +0 -791
  129. package/demos/js/tde_old/flare.obj.js +0 -239
  130. package/demos/js/tde_old/index.js +0 -448
  131. package/demos/js/tde_old/star.obj.js +0 -812
  132. package/demos/js/tiles.js +0 -312
  133. package/demos/js/tweendemo.js +0 -79
  134. package/demos/js/visibility.js +0 -102
  135. package/demos/kerr.html +0 -28
  136. package/demos/lavalamp.html +0 -27
  137. package/demos/layouts.html +0 -37
  138. package/demos/logo.svg +0 -4
  139. package/demos/loop.html +0 -84
  140. package/demos/mondrian.html +0 -32
  141. package/demos/og_image.png +0 -0
  142. package/demos/opacity.html +0 -36
  143. package/demos/painter.html +0 -39
  144. package/demos/particles-showcase.html +0 -28
  145. package/demos/particles.html +0 -24
  146. package/demos/patterns.html +0 -33
  147. package/demos/penrose-game.html +0 -31
  148. package/demos/pipeline.html +0 -737
  149. package/demos/plane3d.html +0 -24
  150. package/demos/platformer.html +0 -43
  151. package/demos/scene.html +0 -33
  152. package/demos/scenes.html +0 -96
  153. package/demos/schrodinger.html +0 -27
  154. package/demos/schwarzschild.html +0 -27
  155. package/demos/shapes.html +0 -16
  156. package/demos/space.html +0 -85
  157. package/demos/spacetime.html +0 -27
  158. package/demos/sphere3d.html +0 -24
  159. package/demos/sprite.html +0 -18
  160. package/demos/svgtween.html +0 -29
  161. package/demos/tde.html +0 -28
  162. package/demos/tiles.html +0 -28
  163. package/demos/transforms.html +0 -400
  164. package/demos/tween.html +0 -45
  165. package/demos/visibility.html +0 -33
  166. package/docs/README.md +0 -230
  167. package/docs/api/FluidSystem.md +0 -173
  168. package/docs/concepts/architecture-overview.md +0 -204
  169. package/docs/concepts/coordinate-system.md +0 -384
  170. package/docs/concepts/lifecycle.md +0 -255
  171. package/docs/concepts/rendering-pipeline.md +0 -279
  172. package/docs/concepts/shapes-vs-gameobjects.md +0 -187
  173. package/docs/concepts/tde-zorder.md +0 -106
  174. package/docs/concepts/two-layer-architecture.md +0 -229
  175. package/docs/fluid-dynamics.md +0 -99
  176. package/docs/getting-started/first-game.md +0 -354
  177. package/docs/getting-started/hello-world.md +0 -269
  178. package/docs/getting-started/installation.md +0 -175
  179. package/docs/modules/collision/README.md +0 -453
  180. package/docs/modules/fluent/README.md +0 -1075
  181. package/docs/modules/game/README.md +0 -303
  182. package/docs/modules/isometric-camera.md +0 -210
  183. package/docs/modules/isometric.md +0 -275
  184. package/docs/modules/painter/README.md +0 -328
  185. package/docs/modules/particle/README.md +0 -559
  186. package/docs/modules/shapes/README.md +0 -221
  187. package/docs/modules/shapes/base/euclidian.md +0 -123
  188. package/docs/modules/shapes/base/geometry2d.md +0 -204
  189. package/docs/modules/shapes/base/renderable.md +0 -215
  190. package/docs/modules/shapes/base/shape.md +0 -262
  191. package/docs/modules/shapes/base/transformable.md +0 -243
  192. package/docs/modules/shapes/hierarchy.md +0 -218
  193. package/docs/modules/state/README.md +0 -577
  194. package/docs/modules/util/README.md +0 -99
  195. package/docs/modules/util/camera3d.md +0 -412
  196. package/docs/modules/util/scene3d.md +0 -395
  197. package/index.html +0 -17
  198. package/jsdoc.json +0 -50
  199. package/scripts/build-demo.js +0 -69
  200. package/scripts/bundle4llm.js +0 -276
  201. package/scripts/clearconsole.js +0 -48
  202. package/test/math/orbital.test.js +0 -61
  203. package/test/math/tensor.test.js +0 -114
  204. package/test/particle/emitter.test.js +0 -204
  205. package/test/particle/particle-system.test.js +0 -310
  206. package/test/particle/particle.test.js +0 -116
  207. package/test/particle/updaters.test.js +0 -386
  208. package/test/setup.js +0 -120
  209. package/test/shapes/euclidian.test.js +0 -44
  210. package/test/shapes/geometry.test.js +0 -86
  211. package/test/shapes/group.test.js +0 -86
  212. package/test/shapes/rectangle.test.js +0 -64
  213. package/test/shapes/transform.test.js +0 -379
  214. package/test/util/camera3d.test.js +0 -428
  215. package/test/util/scene3d.test.js +0 -352
  216. package/vite.config.js +0 -50
  217. package/vitest.config.js +0 -13
@@ -1,692 +0,0 @@
1
- import {
2
- Game,
3
- Scene,
4
- FPSCounter,
5
- Button,
6
- HorizontalLayout,
7
- VerticalLayout,
8
- Painter,
9
- ToggleButton,
10
- Cursor,
11
- TextShape,
12
- Circle,
13
- Rectangle,
14
- BezierShape,
15
- ShapeGOFactory,
16
- Position,
17
- } from "../../src/index";
18
-
19
- /**
20
- * Main Game class for the Bezier Demo
21
- */
22
- class BezierDemoGame extends Game {
23
- constructor(canvas) {
24
- super(canvas);
25
- this.enableFluidSize();
26
- this.backgroundColor = "black";
27
- }
28
-
29
- /**
30
- * Check if we're on a touch device
31
- */
32
- isTouchDevice() {
33
- return (
34
- "ontouchstart" in window ||
35
- navigator.maxTouchPoints > 0 ||
36
- navigator.msMaxTouchPoints > 0
37
- );
38
- }
39
-
40
- /**
41
- * Check if screen is narrow (mobile width)
42
- */
43
- isMobile() {
44
- return this.width < 600;
45
- }
46
-
47
- /**
48
- * Get responsive configuration based on screen size
49
- */
50
- getResponsiveConfig() {
51
- const isMobile = this.isMobile();
52
- return {
53
- // Use shorter labels on mobile
54
- addLabel: isMobile ? "➕ Add" : "➕ Add Points",
55
- editLabel: isMobile ? "✋ Edit" : "✋ Edit Points",
56
- cutLabel: isMobile ? "✂️ Cut" : "✂️ Cut Shape",
57
- clearLabel: isMobile ? "🧼" : "🧼 Clear",
58
- // Narrower buttons on mobile
59
- buttonWidth: isMobile ? 70 : 125,
60
- clearWidth: isMobile ? 40 : 100,
61
- // UI layout dimensions
62
- layoutWidth: isMobile ? 280 : 500,
63
- layoutHeight: 50,
64
- };
65
- }
66
-
67
- init() {
68
- super.init();
69
- // Create the scenes
70
- this.bezierScene = new BezierScene(this, {
71
- debug: true,
72
- debugColor: "yellow",
73
- });
74
-
75
- const config = this.getResponsiveConfig();
76
-
77
- this.uiScene = new BezierUIScene(this, this.bezierScene, {
78
- debug: true,
79
- debugColor: "magenta",
80
- width: config.layoutWidth,
81
- height: config.layoutHeight,
82
- padding: 10,
83
- anchor: Position.BOTTOM_CENTER,
84
- anchorOffsetY: -20,
85
- });
86
-
87
- // Add them to the pipeline
88
- this.pipeline.add(this.bezierScene);
89
- this.pipeline.add(this.uiScene);
90
- this.uiScene.init();
91
-
92
- // Show FPS counter
93
- this.pipeline.add(new FPSCounter(this, { anchor: "bottom-right" }));
94
-
95
- // Only setup custom cursor on non-touch devices
96
- if (!this.isTouchDevice()) {
97
- this.addCursor = new TextShape("➕", {
98
- font: "24px monospace",
99
- color: "white",
100
- });
101
- this.editCursor = new TextShape("✋", {
102
- font: "24px monospace",
103
- color: "white",
104
- });
105
- this.cursor = new Cursor(this, this.addCursor, this.addCursor, {
106
- x: 0,
107
- y: 0,
108
- });
109
- }
110
- }
111
-
112
- onResize() {
113
- if (this.uiScene) {
114
- this.uiScene.onResize();
115
- }
116
- }
117
- }
118
-
119
- /**
120
- * The BezierScene:
121
- * - Allows users to place control points by clicking
122
- * - Visualizes the Bezier curve as it's being created
123
- * - Provides UI to finish/clear the curve
124
- */
125
- class BezierScene extends Scene {
126
- constructor(game, options = {}) {
127
- super(game, options);
128
- this.MARGIN = 0;
129
- this.interactive = true;
130
- // Control points the user has placed
131
- this.points = [];
132
- // Current bezier path that will be visualized
133
- this.bezierPath = [];
134
- // The bezier shape that will be rendered
135
- this.bezierShape = null;
136
- // Point being dragged (if any)
137
- this.draggedPointIndex = -1;
138
- // Visual elements for control points
139
- this.controlPointShapes = [];
140
- // User interface state
141
- this.mode = "add"; // "add" or "edit"
142
- // Forward input events to the bezier scene
143
- this.game.events.on("inputdown", (e) => {
144
- //console.log("inputdown", e);
145
- const x = e.x - this.width / 2;
146
- const y = e.y - this.height / 2;
147
- this.pointerDown(x, y);
148
- });
149
- // Forward input move event to the bezier scene
150
- this.game.events.on("inputmove", (e) => {
151
- const x = e.x - this.width / 2;
152
- const y = e.y - this.height / 2;
153
- this.pointerMove(x, y);
154
- });
155
- // Forward input up event to the bezier scene
156
- this.game.events.on("inputup", (e) => {
157
- this.pointerUp();
158
- });
159
- //
160
- this.userBeziers = [];
161
- }
162
-
163
- /**
164
- * Set the current mode (add points or edit existing points)
165
- */
166
- setMode(mode) {
167
- this.mode = mode;
168
-
169
- // Only update cursor on non-touch devices
170
- if (this.game.cursor) {
171
- if (mode === "add") {
172
- this.game.cursor.normalShape = this.game.cursor.pressedShape =
173
- this.game.addCursor;
174
- this.game.cursor.offsetX = 1;
175
- this.game.cursor.offsetY = -6;
176
- } else {
177
- this.game.cursor.normalShape = this.game.cursor.pressedShape =
178
- this.game.editCursor;
179
- this.game.cursor.offsetX = 3;
180
- this.game.cursor.offsetY = -3;
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * Add a control point at the specified position
187
- */
188
- addPoint(x, y) {
189
- x = x - 50;
190
- y = y - 50;
191
- this.points.push({ x, y });
192
- // Create a visual representation of the control point
193
- const pointShape = new Circle(6, {
194
- color: "#00FF00",
195
- stroke: "#e2FFe2",
196
- lineWidth: 2,
197
- });
198
- const go = ShapeGOFactory.create(this.game, pointShape, { x: x, y: y });
199
- this.controlPointShapes.push(go);
200
- this.add(go);
201
- // Update the bezier path
202
- this.updateBezierPath();
203
- }
204
-
205
- /**
206
- * Updates the Bezier path based on current points
207
- * This is where we convert our points array into path commands
208
- */
209
- /**
210
- * Updates the Bezier path based on current points
211
- * This is where we convert our points array into path commands
212
- */
213
- updateBezierPath() {
214
- if (this.points.length < 2) {
215
- // Need at least 2 points to create a path
216
- this.bezierPath = [];
217
- if (this.bezierShape) {
218
- this.remove(this.bezierShape);
219
- this.bezierShape = null;
220
- }
221
- return;
222
- }
223
- // Start with a move command to the first point
224
- const path = [["M", this.points[0].x, this.points[0].y]];
225
- // Create appropriate curve segments based on the number of points
226
- if (this.points.length === 2) {
227
- // With just 2 points, create a line
228
- path.push(["L", this.points[1].x, this.points[1].y]);
229
- } else if (this.points.length === 3) {
230
- // With 3 points, create a quadratic curve (one control point)
231
- path.push([
232
- "Q",
233
- this.points[1].x,
234
- this.points[1].y,
235
- this.points[2].x,
236
- this.points[2].y,
237
- ]);
238
- } else {
239
- // For 4+ points, create cubic Bezier segments
240
- for (let i = 1; i < this.points.length; i += 3) {
241
- if (i + 2 < this.points.length) {
242
- // We have enough points for a cubic curve
243
- path.push([
244
- "C",
245
- this.points[i].x,
246
- this.points[i].y,
247
- this.points[i + 1].x,
248
- this.points[i + 1].y,
249
- this.points[i + 2].x,
250
- this.points[i + 2].y,
251
- ]);
252
- } else if (i + 1 < this.points.length) {
253
- // Just enough for a quadratic curve
254
- path.push([
255
- "Q",
256
- this.points[i].x,
257
- this.points[i].y,
258
- this.points[i + 1].x,
259
- this.points[i + 1].y,
260
- ]);
261
- } else {
262
- // Just add a line to the last point
263
- path.push(["L", this.points[i].x, this.points[i].y]);
264
- }
265
- }
266
- }
267
-
268
- this.bezierPath = path;
269
-
270
- // If we already have a shape, update its path
271
- if (this.bezierShape) {
272
- this.bezierShape.shape.path = path;
273
- this.bezierShape.render();
274
- } else {
275
- const centerX = 0;
276
- const centerY = 0;
277
- // Create the bezier shape
278
- const bezierShapeObj = new BezierShape(path, {
279
- color: Painter.colors.randomColorHSL(),
280
- stroke: "rgba(255, 255, 255, 0.8)",
281
- lineWidth: 3,
282
- });
283
-
284
- // Create a GameObject using the factory
285
- this.bezierShape = ShapeGOFactory.create(this.game, bezierShapeObj, {
286
- x: centerX,
287
- y: centerY,
288
- });
289
-
290
- // Add the GameObject to the scene
291
- this.add(this.bezierShape);
292
- }
293
- }
294
-
295
- /**
296
- * Cut the current bezier shape and store it with jitter animation
297
- */
298
- cutShape() {
299
- // Only proceed if we have a valid bezier curve
300
- if (this.points.length >= 2 && this.bezierShape) {
301
- // Create a deep copy of the current bezier data
302
- const cutBezier = {
303
- points: [...this.points.map((p) => ({ ...p }))],
304
- path: [...this.bezierPath.map((cmd) => [...cmd])],
305
- shape: this.bezierShape,
306
- originalPath: [...this.bezierPath.map((cmd) => [...cmd])], // Store original path for animation
307
- jitterAmount: 5, // Base jitter amount
308
- jitterSpeed: 1.5 + Math.random() * 0.5, // Random speed variation
309
- jitterPhase: Math.random() * Math.PI * 2, // Random starting phase
310
- };
311
-
312
- cutBezier.jitterAmount = 5 * cutBezier.points.length;
313
-
314
- // Add to our collection of user beziers
315
- this.userBeziers.push(cutBezier);
316
-
317
- // Remove the current bezier shape from control but keep it in the scene
318
- this.bezierShape = null;
319
-
320
- // Clear current editing points but keep the shape visible
321
- this.points = [];
322
- this.bezierPath = [];
323
-
324
- // Remove visual control points
325
- for (const pointShape of this.controlPointShapes) {
326
- this.remove(pointShape);
327
- }
328
- this.controlPointShapes = [];
329
- this.draggedPointIndex = -1;
330
-
331
- //console.log(`Cut bezier shape - ${this.userBeziers.length} total shapes`);
332
- } else {
333
- //console.log("Need at least 2 points to cut a shape");
334
- }
335
- }
336
-
337
- /**
338
- * Clear all points and reset the demo
339
- * Updated to also clear all cut bezier shapes
340
- */
341
- clear() {
342
- // Remove visual elements
343
- for (const shape of this.controlPointShapes) {
344
- this.remove(shape);
345
- }
346
-
347
- // Remove the current bezier shape if exists
348
- if (this.bezierShape) {
349
- this.remove(this.bezierShape);
350
- this.bezierShape = null;
351
- }
352
-
353
- // Additionally remove all cut bezier shapes
354
- for (const bezier of this.userBeziers) {
355
- this.remove(bezier.shape);
356
- }
357
-
358
- // Reset all data
359
- this.points = [];
360
- this.bezierPath = [];
361
- this.controlPointShapes = [];
362
- this.draggedPointIndex = -1;
363
- this.userBeziers = [];
364
- }
365
-
366
- /**
367
- * Return the bezier path as a string for display/export
368
- */
369
- getPathString() {
370
- if (!this.bezierPath.length) return "No path defined";
371
-
372
- return JSON.stringify(this.bezierPath);
373
- }
374
-
375
- /**
376
- * Find the index of a control point near the given coordinates
377
- */
378
- findNearbyPoint(x, y, radius = 20) {
379
- for (let i = 0; i < this.points.length; i++) {
380
- const point = this.points[i];
381
- const dx = point.x - x;
382
- const dy = point.y - y;
383
- const distance = Math.sqrt(dx * dx + dy * dy);
384
-
385
- if (distance <= radius) {
386
- return i;
387
- }
388
- }
389
- return -1;
390
- }
391
-
392
- /**
393
- * Check if a point is within the UI area
394
- */
395
- isInUIArea(x, y) {
396
- // Get screen coordinates (input coords are already screen coords)
397
- const screenY = y + this.height / 2;
398
- // UI is at bottom, check if click is in bottom 80px
399
- return screenY > this.game.height - 80;
400
- }
401
-
402
- /**
403
- * Pointer down handler
404
- */
405
- pointerDown(x, y) {
406
- // Skip if clicking on UI area (buttons)
407
- if (this.game.canvas.style.cursor === "pointer" || this.isInUIArea(x, y)) {
408
- return;
409
- }
410
- if (this.mode === "add") {
411
- // In add mode, place a new control point
412
- this.addPoint(x, y);
413
- } else {
414
- // In edit mode, check if we're clicking on an existing point
415
- const pointIndex = this.findNearbyPoint(x - 50, y - 50);
416
- if (pointIndex >= 0) {
417
- this.draggedPointIndex = pointIndex;
418
- }
419
- }
420
- }
421
-
422
- /**
423
- * Pointer move handler
424
- */
425
- pointerMove(x, y) {
426
- if (this.draggedPointIndex >= 0) {
427
- // Update the dragged point position
428
- this.points[this.draggedPointIndex].x = x - 50;
429
- this.points[this.draggedPointIndex].y = y - 50;
430
-
431
- // Update the visual control point
432
- const pointShape = this.controlPointShapes[this.draggedPointIndex];
433
- pointShape.x = x - 50;
434
- pointShape.y = y - 50;
435
-
436
- // Update the Bezier curve
437
- this.updateBezierPath();
438
- }
439
- }
440
-
441
- /**
442
- * Pointer up handler
443
- */
444
- pointerUp() {
445
- this.draggedPointIndex = -1;
446
- }
447
-
448
- /**
449
- * Render additional visual helpers
450
- */
451
- draw() {
452
- super.draw();
453
- // If we have at least 2 points, draw guide lines between control points
454
- if (this.points.length >= 2) {
455
- Painter.save();
456
- // Draw dashed lines connecting control points
457
- Painter.colors.setStrokeColor("rgba(0, 255, 0, 0.8)");
458
- Painter.lines.setLineWidth(1);
459
- // Set up a dashed line style
460
- Painter.ctx.setLineDash([5, 5]);
461
- Painter.lines.beginPath();
462
- Painter.lines.moveTo(this.points[0].x, this.points[0].y);
463
-
464
- for (let i = 1; i < this.points.length; i++) {
465
- Painter.lines.lineTo(this.points[i].x, this.points[i].y);
466
- }
467
-
468
- Painter.colors.stroke();
469
- Painter.ctx.setLineDash([]); // Reset dash
470
- Painter.restore();
471
- }
472
- }
473
-
474
- static gco = [
475
- "source-over",
476
- "multiply",
477
- "screen",
478
- "overlay",
479
- "darken",
480
- "lighten",
481
- "color-dodge",
482
- "color-burn",
483
- "hard-light",
484
- "soft-light",
485
- "difference",
486
- "exclusion",
487
- "hue",
488
- "saturation",
489
- "color",
490
- "luminosity",
491
- ];
492
-
493
- #prevWidth = 0;
494
- #prevHeight = 0;
495
-
496
- /**
497
- * Update method that adds the jitter animation to cut bezier shapes
498
- */
499
- update(dt) {
500
- // Update scene dimensions based on margin
501
- this.width = this.game.width - this.MARGIN * 2;
502
- this.height = this.game.height - this.MARGIN * 2;
503
-
504
- // Center the scene in the game
505
- this.x = this.game.width / 2;
506
- this.y = this.game.height / 2;
507
- const filter = Painter.ctx.globalCompositeOperation;
508
- Painter.ctx.globalCompositeOperation = "screen";
509
- // Update jitter animation for all cut beziers
510
- for (const bezier of this.userBeziers) {
511
- // Update the jitter phase
512
- bezier.jitterPhase += dt * bezier.jitterSpeed * 5;
513
-
514
- // Create a new jittered path based on the original
515
- const jitteredPath = [];
516
- for (
517
- let cmdIndex = 0;
518
- cmdIndex < bezier.originalPath.length;
519
- cmdIndex++
520
- ) {
521
- const originalCmd = bezier.originalPath[cmdIndex];
522
- const newCmd = [...originalCmd]; // Make a copy
523
-
524
- // Only modify coordinate values (not the command type at index 0)
525
- for (let i = 1; i < newCmd.length; i++) {
526
- if (typeof newCmd[i] === "number") {
527
- // Apply a sine wave jitter with unique offset for each point
528
- const offset =
529
- Math.sin(bezier.jitterPhase + i * 0.3 + cmdIndex * 0.7) *
530
- bezier.jitterAmount;
531
- newCmd[i] = originalCmd[i] + offset;
532
- }
533
- }
534
- jitteredPath.push(newCmd);
535
- }
536
-
537
- // Apply the jittered path to the shape
538
- bezier.shape.shape.path = jitteredPath;
539
- }
540
- //Painter.effects.setBlendMode(filter);
541
- super.update(dt);
542
-
543
- if (this.#prevWidth !== this.width || this.#prevHeight !== this.height) {
544
- this.markBoundsDirty();
545
- }
546
- this.#prevWidth = this.width;
547
- this.#prevHeight = this.height;
548
- }
549
- }
550
-
551
- /**
552
- * UI for the Bezier Demo with controls for adding/editing points,
553
- * clearing the canvas, and switching between modes.
554
- */
555
- class BezierUIScene extends Scene {
556
- constructor(game, bezierScene, options = {}) {
557
- super(game, options);
558
- this.bezierScene = bezierScene;
559
- this.onMenu = false;
560
- this.currentMode = null;
561
- this._lastMobileState = null;
562
- }
563
-
564
- /**
565
- * Get button style configuration (transparent background)
566
- */
567
- getButtonStyle() {
568
- return {
569
- colorHoverBg: "transparent",
570
- colorDefaultBg: "transparent",
571
- colorPressedBg: "transparent",
572
- colorDefaultText: "white",
573
- };
574
- }
575
-
576
- init() {
577
- this.createButtons();
578
- }
579
-
580
- /**
581
- * Create buttons with current responsive config
582
- */
583
- createButtons() {
584
- // Clear existing layout
585
- if (this.layout) {
586
- this.remove(this.layout);
587
- this.layout = null;
588
- }
589
-
590
- const config = this.game.getResponsiveConfig();
591
- const buttonStyle = this.getButtonStyle();
592
-
593
- this.layout = new HorizontalLayout(this.game, {
594
- width: config.layoutWidth,
595
- height: config.layoutHeight,
596
- spacing: 5,
597
- });
598
-
599
- this.addModeButton = this.layout.add(
600
- new ToggleButton(this.game, {
601
- text: config.addLabel,
602
- width: config.buttonWidth,
603
- height: 32,
604
- ...buttonStyle,
605
- startToggled: true,
606
- onToggle: (active) => {
607
- if (this.currentMode && this.currentMode !== this.addModeButton) {
608
- this.currentMode.toggle(false);
609
- }
610
- if (active) {
611
- this.bezierScene.setMode("add");
612
- this.currentMode = this.addModeButton;
613
- }
614
- },
615
- })
616
- );
617
-
618
- this.editModeButton = this.layout.add(
619
- new ToggleButton(this.game, {
620
- text: config.editLabel,
621
- width: config.buttonWidth,
622
- height: 32,
623
- ...buttonStyle,
624
- onToggle: (active) => {
625
- if (this.currentMode && this.currentMode !== this.editModeButton) {
626
- this.currentMode.toggle(false);
627
- }
628
- if (active) {
629
- this.bezierScene.setMode("edit");
630
- this.currentMode = this.editModeButton;
631
- }
632
- },
633
- })
634
- );
635
-
636
- this.cutModeButton = this.layout.add(
637
- new Button(this.game, {
638
- text: config.cutLabel,
639
- width: config.buttonWidth,
640
- height: 32,
641
- ...buttonStyle,
642
- onClick: () => {
643
- if (this.editModeButton) this.editModeButton.toggle(false);
644
- if (this.addModeButton) this.addModeButton.toggle(false);
645
- this.currentMode = null;
646
- this.bezierScene.cutShape();
647
- },
648
- })
649
- );
650
-
651
- this.clearButton = this.layout.add(
652
- new Button(this.game, {
653
- text: config.clearLabel,
654
- width: config.clearWidth,
655
- height: 32,
656
- ...buttonStyle,
657
- onClick: () => {
658
- this.bezierScene.clear();
659
- },
660
- })
661
- );
662
-
663
- this.add(this.layout);
664
- this.currentMode = this.addModeButton;
665
-
666
- // Update scene dimensions
667
- this.width = config.layoutWidth;
668
- this.height = config.layoutHeight;
669
- this.markBoundsDirty();
670
- }
671
-
672
- /**
673
- * Handle resize - recreate buttons if mobile state changed
674
- */
675
- onResize() {
676
- const isMobile = this.game.isMobile();
677
-
678
- // Only recreate if mobile state changed
679
- if (this._lastMobileState !== isMobile) {
680
- this._lastMobileState = isMobile;
681
- this.createButtons();
682
- }
683
-
684
- // Update scene dimensions
685
- const config = this.game.getResponsiveConfig();
686
- this.width = config.layoutWidth;
687
- this.height = config.layoutHeight;
688
- this.markBoundsDirty();
689
- }
690
- }
691
- /// Main game class
692
- window.BezierDemoGame = BezierDemoGame;