@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.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module FluentGO
|
|
3
|
+
* @description Builder class for GameObject operations in the fluent API
|
|
4
|
+
*
|
|
5
|
+
* Provides chainable methods for adding shapes, motions, and children to GameObjects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { GameObject } from "../game/objects/go.js";
|
|
9
|
+
import { GameObjectShapeWrapper } from "../game/objects/wrapper.js";
|
|
10
|
+
import { Group } from "../shapes/group.js";
|
|
11
|
+
|
|
12
|
+
// Import all shapes
|
|
13
|
+
import { Circle } from "../shapes/circle.js";
|
|
14
|
+
import { Rectangle } from "../shapes/rect.js";
|
|
15
|
+
import { RoundedRectangle } from "../shapes/roundrect.js";
|
|
16
|
+
import { Square } from "../shapes/square.js";
|
|
17
|
+
import { Triangle } from "../shapes/triangle.js";
|
|
18
|
+
import { Star } from "../shapes/star.js";
|
|
19
|
+
import { Diamond } from "../shapes/diamond.js";
|
|
20
|
+
import { Hexagon } from "../shapes/hexagon.js";
|
|
21
|
+
import { Heart } from "../shapes/heart.js";
|
|
22
|
+
import { Line } from "../shapes/line.js";
|
|
23
|
+
import { Arc } from "../shapes/arc.js";
|
|
24
|
+
import { Ring } from "../shapes/ring.js";
|
|
25
|
+
import { Polygon } from "../shapes/poly.js";
|
|
26
|
+
import { Arrow } from "../shapes/arrow.js";
|
|
27
|
+
import { Cross } from "../shapes/cross.js";
|
|
28
|
+
import { Pin } from "../shapes/pin.js";
|
|
29
|
+
import { Cloud } from "../shapes/clouds.js";
|
|
30
|
+
import { TextShape } from "../shapes/text.js";
|
|
31
|
+
import { ImageShape } from "../shapes/image.js";
|
|
32
|
+
import { SVGShape } from "../shapes/svg.js";
|
|
33
|
+
|
|
34
|
+
// Import motion system
|
|
35
|
+
import { Motion } from "../motion/motion.js";
|
|
36
|
+
import { Tweenetik } from "../motion/tweenetik.js";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* FluentGO - Builder class for GameObject operations
|
|
40
|
+
*/
|
|
41
|
+
export class FluentGO {
|
|
42
|
+
/** @type {import('./fluent-scene.js').FluentScene|FluentGO} */
|
|
43
|
+
#parent;
|
|
44
|
+
/** @type {GameObject} */
|
|
45
|
+
#go;
|
|
46
|
+
/** @type {Object} */
|
|
47
|
+
#refs;
|
|
48
|
+
/** @type {Object} */
|
|
49
|
+
#state;
|
|
50
|
+
/** @type {Array} */
|
|
51
|
+
#shapes = [];
|
|
52
|
+
/** @type {Array} */
|
|
53
|
+
#motions = [];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('./fluent-scene.js').FluentScene|FluentGO} parent - Parent context
|
|
57
|
+
* @param {GameObject} go - Wrapped GameObject instance
|
|
58
|
+
* @param {Object} refs - Shared refs object
|
|
59
|
+
* @param {Object} state - Shared state object
|
|
60
|
+
*/
|
|
61
|
+
constructor(parent, go, refs, state) {
|
|
62
|
+
this.#parent = parent;
|
|
63
|
+
this.#go = go;
|
|
64
|
+
this.#refs = refs;
|
|
65
|
+
this.#state = state;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─────────────────────────────────────────────────────────
|
|
69
|
+
// SHAPE SHORTCUTS
|
|
70
|
+
// ─────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Add a Circle shape
|
|
74
|
+
* @param {Object} [opts] - Circle options
|
|
75
|
+
* @param {number} [opts.radius] - Circle radius
|
|
76
|
+
* @param {string} [opts.fill] - Fill color
|
|
77
|
+
* @param {string} [opts.stroke] - Stroke color
|
|
78
|
+
* @returns {FluentGO}
|
|
79
|
+
*/
|
|
80
|
+
circle(opts = {}) {
|
|
81
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
82
|
+
const { radius = 30, ...rest } = normalized;
|
|
83
|
+
const shape = new Circle(radius, rest);
|
|
84
|
+
return this.#addShapeInstance(shape);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Add a Rectangle shape
|
|
89
|
+
* @param {Object} [opts] - Rectangle options
|
|
90
|
+
* @param {number} [opts.width] - Rectangle width
|
|
91
|
+
* @param {number} [opts.height] - Rectangle height
|
|
92
|
+
* @param {string} [opts.fill] - Fill color
|
|
93
|
+
* @returns {FluentGO}
|
|
94
|
+
*/
|
|
95
|
+
rect(opts = {}) {
|
|
96
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
97
|
+
const shape = new Rectangle(normalized);
|
|
98
|
+
return this.#addShapeInstance(shape);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Add a RoundedRectangle shape
|
|
103
|
+
* @param {Object} [opts] - RoundedRectangle options
|
|
104
|
+
* @param {number} [opts.radius] - Corner radius
|
|
105
|
+
* @returns {FluentGO}
|
|
106
|
+
*/
|
|
107
|
+
roundRect(opts = {}) {
|
|
108
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
109
|
+
const { radius = 10, ...rest } = normalized;
|
|
110
|
+
const shape = new RoundedRectangle(radius, rest);
|
|
111
|
+
return this.#addShapeInstance(shape);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Add a Square shape
|
|
116
|
+
* @param {Object} [opts] - Square options
|
|
117
|
+
* @param {number} [opts.size] - Square size
|
|
118
|
+
* @returns {FluentGO}
|
|
119
|
+
*/
|
|
120
|
+
square(opts = {}) {
|
|
121
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
122
|
+
const { size = 50, ...rest } = normalized;
|
|
123
|
+
const shape = new Square(size, rest);
|
|
124
|
+
return this.#addShapeInstance(shape);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Add a Star shape
|
|
129
|
+
* @param {Object} [opts] - Star options
|
|
130
|
+
* @param {number} [opts.points] - Number of points (spikes)
|
|
131
|
+
* @param {number} [opts.radius] - Outer radius
|
|
132
|
+
* @param {number} [opts.inset] - Inner radius ratio (0-1)
|
|
133
|
+
* @returns {FluentGO}
|
|
134
|
+
*/
|
|
135
|
+
star(opts = {}) {
|
|
136
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
137
|
+
const { radius = 40, points = 5, inset = 0.5, ...rest } = normalized;
|
|
138
|
+
const shape = new Star(radius, points, inset, rest);
|
|
139
|
+
return this.#addShapeInstance(shape);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add a Triangle shape
|
|
144
|
+
* @param {Object} [opts] - Triangle options
|
|
145
|
+
* @param {number} [opts.size] - Triangle size
|
|
146
|
+
* @returns {FluentGO}
|
|
147
|
+
*/
|
|
148
|
+
triangle(opts = {}) {
|
|
149
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
150
|
+
const { size = 50, ...rest } = normalized;
|
|
151
|
+
const shape = new Triangle(size, rest);
|
|
152
|
+
return this.#addShapeInstance(shape);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Add a Polygon shape
|
|
157
|
+
* @param {Object} [opts] - Polygon options
|
|
158
|
+
* @param {Array} [opts.points] - Polygon vertices
|
|
159
|
+
* @returns {FluentGO}
|
|
160
|
+
*/
|
|
161
|
+
poly(opts = {}) {
|
|
162
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
163
|
+
const shape = new Polygon(normalized);
|
|
164
|
+
return this.#addShapeInstance(shape);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add a Line shape
|
|
169
|
+
* @param {Object} [opts] - Line options
|
|
170
|
+
* @param {number} [opts.length] - Line length
|
|
171
|
+
* @returns {FluentGO}
|
|
172
|
+
*/
|
|
173
|
+
line(opts = {}) {
|
|
174
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
175
|
+
const { length = 40, ...rest } = normalized;
|
|
176
|
+
const shape = new Line(length, rest);
|
|
177
|
+
return this.#addShapeInstance(shape);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Add a Hexagon shape
|
|
182
|
+
* @param {Object} [opts] - Hexagon options
|
|
183
|
+
* @param {number} [opts.radius] - Hexagon radius
|
|
184
|
+
* @returns {FluentGO}
|
|
185
|
+
*/
|
|
186
|
+
hexagon(opts = {}) {
|
|
187
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
188
|
+
const { radius = 30, ...rest } = normalized;
|
|
189
|
+
const shape = new Hexagon(radius, rest);
|
|
190
|
+
return this.#addShapeInstance(shape);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add a Diamond shape
|
|
195
|
+
* @param {Object} [opts] - Diamond options
|
|
196
|
+
* @returns {FluentGO}
|
|
197
|
+
*/
|
|
198
|
+
diamond(opts = {}) {
|
|
199
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
200
|
+
const shape = new Diamond(normalized);
|
|
201
|
+
return this.#addShapeInstance(shape);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Add a Heart shape
|
|
206
|
+
* @param {Object} [opts] - Heart options
|
|
207
|
+
* @param {number} [opts.size] - Heart size (sets width and height)
|
|
208
|
+
* @param {number} [opts.width] - Heart width
|
|
209
|
+
* @param {number} [opts.height] - Heart height
|
|
210
|
+
* @returns {FluentGO}
|
|
211
|
+
*/
|
|
212
|
+
heart(opts = {}) {
|
|
213
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
214
|
+
const { size, ...rest } = normalized;
|
|
215
|
+
if (size && !rest.width) rest.width = size;
|
|
216
|
+
if (size && !rest.height) rest.height = size;
|
|
217
|
+
const shape = new Heart(rest);
|
|
218
|
+
return this.#addShapeInstance(shape);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Add an Arc shape
|
|
223
|
+
* @param {Object} [opts] - Arc options
|
|
224
|
+
* @param {number} [opts.radius] - Arc radius
|
|
225
|
+
* @param {number} [opts.startAngle] - Start angle
|
|
226
|
+
* @param {number} [opts.endAngle] - End angle
|
|
227
|
+
* @returns {FluentGO}
|
|
228
|
+
*/
|
|
229
|
+
arc(opts = {}) {
|
|
230
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
231
|
+
const { radius = 30, startAngle = 0, endAngle = Math.PI, ...rest } = normalized;
|
|
232
|
+
const shape = new Arc(radius, startAngle, endAngle, rest);
|
|
233
|
+
return this.#addShapeInstance(shape);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Add a Ring shape
|
|
238
|
+
* @param {Object} [opts] - Ring options
|
|
239
|
+
* @param {number} [opts.innerRadius] - Inner radius
|
|
240
|
+
* @param {number} [opts.outerRadius] - Outer radius
|
|
241
|
+
* @returns {FluentGO}
|
|
242
|
+
*/
|
|
243
|
+
ring(opts = {}) {
|
|
244
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
245
|
+
const { outerRadius = 40, innerRadius = 20, ...rest } = normalized;
|
|
246
|
+
const shape = new Ring(outerRadius, innerRadius, rest);
|
|
247
|
+
return this.#addShapeInstance(shape);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Add an Arrow shape
|
|
252
|
+
* @param {Object} [opts] - Arrow options
|
|
253
|
+
* @param {number} [opts.length] - Arrow length
|
|
254
|
+
* @returns {FluentGO}
|
|
255
|
+
*/
|
|
256
|
+
arrow(opts = {}) {
|
|
257
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
258
|
+
const { length = 50, ...rest } = normalized;
|
|
259
|
+
const shape = new Arrow(length, rest);
|
|
260
|
+
return this.#addShapeInstance(shape);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Add a Cross shape
|
|
265
|
+
* @param {Object} [opts] - Cross options
|
|
266
|
+
* @param {number} [opts.size] - Cross size
|
|
267
|
+
* @param {number} [opts.thickness] - Cross thickness
|
|
268
|
+
* @returns {FluentGO}
|
|
269
|
+
*/
|
|
270
|
+
cross(opts = {}) {
|
|
271
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
272
|
+
const { size = 40, thickness = 10, ...rest } = normalized;
|
|
273
|
+
const shape = new Cross(size, thickness, rest);
|
|
274
|
+
return this.#addShapeInstance(shape);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Add a Pin shape
|
|
279
|
+
* @param {Object} [opts] - Pin options
|
|
280
|
+
* @param {number} [opts.radius] - Pin radius
|
|
281
|
+
* @returns {FluentGO}
|
|
282
|
+
*/
|
|
283
|
+
pin(opts = {}) {
|
|
284
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
285
|
+
const { radius = 20, ...rest } = normalized;
|
|
286
|
+
const shape = new Pin(radius, rest);
|
|
287
|
+
return this.#addShapeInstance(shape);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Add a Cloud shape
|
|
292
|
+
* @param {Object} [opts] - Cloud options
|
|
293
|
+
* @param {number} [opts.size] - Cloud size
|
|
294
|
+
* @returns {FluentGO}
|
|
295
|
+
*/
|
|
296
|
+
cloud(opts = {}) {
|
|
297
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
298
|
+
const { size, width, height, ...rest } = normalized;
|
|
299
|
+
// Cloud constructor: (size, options)
|
|
300
|
+
const cloudSize = size || Math.min(width || 40, height || 40);
|
|
301
|
+
const shape = new Cloud(cloudSize, rest);
|
|
302
|
+
return this.#addShapeInstance(shape);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Add a TextShape
|
|
307
|
+
* @param {string} content - Text content
|
|
308
|
+
* @param {Object} [opts] - Text options
|
|
309
|
+
* @param {string} [opts.font] - Font specification
|
|
310
|
+
* @param {string} [opts.fill] - Text color
|
|
311
|
+
* @returns {FluentGO}
|
|
312
|
+
*/
|
|
313
|
+
text(content, opts = {}) {
|
|
314
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
315
|
+
// TextShape uses 'color' not 'fillColor'
|
|
316
|
+
if (normalized.fillColor) {
|
|
317
|
+
normalized.color = normalized.fillColor;
|
|
318
|
+
delete normalized.fillColor;
|
|
319
|
+
}
|
|
320
|
+
const shape = new TextShape(content, normalized);
|
|
321
|
+
return this.#addShapeInstance(shape);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Add an Image shape
|
|
326
|
+
* @param {HTMLImageElement|ImageData|string} src - Image source
|
|
327
|
+
* @param {Object} [opts] - Image options
|
|
328
|
+
* @returns {FluentGO}
|
|
329
|
+
*/
|
|
330
|
+
image(src, opts = {}) {
|
|
331
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
332
|
+
|
|
333
|
+
if (typeof src === 'string') {
|
|
334
|
+
// Load image from URL
|
|
335
|
+
const img = new Image();
|
|
336
|
+
img.src = src;
|
|
337
|
+
const shape = new ImageShape(img, normalized);
|
|
338
|
+
|
|
339
|
+
// Update when loaded
|
|
340
|
+
img.onload = () => {
|
|
341
|
+
shape._bitmap = img;
|
|
342
|
+
shape._width = opts.width ?? img.width;
|
|
343
|
+
shape._height = opts.height ?? img.height;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
return this.#addShapeInstance(shape);
|
|
347
|
+
} else {
|
|
348
|
+
const shape = new ImageShape(src, normalized);
|
|
349
|
+
return this.#addShapeInstance(shape);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Add an SVG shape
|
|
355
|
+
* @param {string} path - SVG path data
|
|
356
|
+
* @param {Object} [opts] - SVG options
|
|
357
|
+
* @returns {FluentGO}
|
|
358
|
+
*/
|
|
359
|
+
svg(path, opts = {}) {
|
|
360
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
361
|
+
const shape = new SVGShape({ path, ...normalized });
|
|
362
|
+
return this.#addShapeInstance(shape);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Generic shape add by class
|
|
367
|
+
* Note: This passes opts as the first argument to the constructor.
|
|
368
|
+
* For shapes with specific signatures, use the dedicated methods
|
|
369
|
+
* (circle, star, etc.) or create the shape instance and use addShapeInstance.
|
|
370
|
+
* @param {Function} ShapeClass - Shape constructor
|
|
371
|
+
* @param {Object} [opts] - Shape options
|
|
372
|
+
* @returns {FluentGO}
|
|
373
|
+
*/
|
|
374
|
+
add(ShapeClass, opts = {}) {
|
|
375
|
+
const normalized = this.#normalizeShapeOpts(opts);
|
|
376
|
+
const shape = new ShapeClass(normalized);
|
|
377
|
+
return this.#addShapeInstance(shape);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Add a shape instance to the GO
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
#addShapeInstance(shape) {
|
|
385
|
+
this.#shapes.push(shape);
|
|
386
|
+
|
|
387
|
+
// Store shape reference on the GO for rendering
|
|
388
|
+
if (this.#shapes.length === 1) {
|
|
389
|
+
// First shape - store directly and set up rendering
|
|
390
|
+
this.#go._fluentShape = shape;
|
|
391
|
+
this.#go.renderable = shape;
|
|
392
|
+
|
|
393
|
+
// Hook into draw to render the shape
|
|
394
|
+
const originalDraw = this.#go.draw?.bind(this.#go) || (() => {});
|
|
395
|
+
this.#go.draw = function() {
|
|
396
|
+
originalDraw();
|
|
397
|
+
if (this._fluentShape && this.visible) {
|
|
398
|
+
this._fluentShape.render();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Add getBounds for hit-testing support
|
|
403
|
+
const go = this.#go;
|
|
404
|
+
this.#go.getBounds = function() {
|
|
405
|
+
if (go._fluentShape && go._fluentShape.getBounds) {
|
|
406
|
+
return go._fluentShape.getBounds();
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
};
|
|
410
|
+
} else {
|
|
411
|
+
// Multiple shapes - use a Group
|
|
412
|
+
if (!(this.#go._fluentShape instanceof Group)) {
|
|
413
|
+
const firstShape = this.#shapes[0];
|
|
414
|
+
const group = new Group();
|
|
415
|
+
group.add(firstShape);
|
|
416
|
+
this.#go._fluentShape = group;
|
|
417
|
+
this.#go.renderable = group;
|
|
418
|
+
}
|
|
419
|
+
this.#go._fluentShape.add(shape);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Normalize shape options (shorthand conversions)
|
|
427
|
+
* @private
|
|
428
|
+
*/
|
|
429
|
+
#normalizeShapeOpts(opts) {
|
|
430
|
+
const normalized = { ...opts };
|
|
431
|
+
|
|
432
|
+
// Shorthand: fill → fillColor (for Shape base class which uses 'color')
|
|
433
|
+
if (opts.fill !== undefined) {
|
|
434
|
+
normalized.color = opts.fill;
|
|
435
|
+
normalized.fillColor = opts.fill;
|
|
436
|
+
delete normalized.fill;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Shorthand: stroke → strokeColor
|
|
440
|
+
if (opts.stroke !== undefined) {
|
|
441
|
+
normalized.strokeColor = opts.stroke;
|
|
442
|
+
delete normalized.stroke;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return normalized;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─────────────────────────────────────────────────────────
|
|
449
|
+
// MOTION SHORTCUTS
|
|
450
|
+
// ─────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Add oscillation motion
|
|
454
|
+
* @param {Object} [opts] - Motion options
|
|
455
|
+
* @param {string} [opts.prop='y'] - Property to animate
|
|
456
|
+
* @param {number} [opts.min=-50] - Minimum offset
|
|
457
|
+
* @param {number} [opts.max=50] - Maximum offset
|
|
458
|
+
* @param {number} [opts.duration=2] - Duration in seconds
|
|
459
|
+
* @returns {FluentGO}
|
|
460
|
+
*/
|
|
461
|
+
oscillate(opts = {}) {
|
|
462
|
+
return this.motion('oscillate', opts);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Add pulse motion (scale animation)
|
|
467
|
+
* @param {Object} [opts] - Motion options
|
|
468
|
+
* @param {string} [opts.prop='scale'] - Property to animate
|
|
469
|
+
* @param {number} [opts.min=0.8] - Minimum value
|
|
470
|
+
* @param {number} [opts.max=1.2] - Maximum value
|
|
471
|
+
* @param {number} [opts.duration=1] - Duration in seconds
|
|
472
|
+
* @returns {FluentGO}
|
|
473
|
+
*/
|
|
474
|
+
pulse(opts = {}) {
|
|
475
|
+
return this.motion('pulse', opts);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Add orbit motion
|
|
480
|
+
* @param {Object} [opts] - Motion options
|
|
481
|
+
* @param {number} [opts.centerX] - Orbit center X
|
|
482
|
+
* @param {number} [opts.centerY] - Orbit center Y
|
|
483
|
+
* @param {number} [opts.radiusX=100] - X radius
|
|
484
|
+
* @param {number} [opts.radiusY=100] - Y radius
|
|
485
|
+
* @param {number} [opts.duration=3] - Orbit period in seconds
|
|
486
|
+
* @param {boolean} [opts.clockwise=true] - Direction
|
|
487
|
+
* @returns {FluentGO}
|
|
488
|
+
*/
|
|
489
|
+
orbit(opts = {}) {
|
|
490
|
+
return this.motion('orbit', opts);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Add float motion (random wandering)
|
|
495
|
+
* @param {Object} [opts] - Motion options
|
|
496
|
+
* @param {number} [opts.radius=20] - Float radius
|
|
497
|
+
* @param {number} [opts.speed=0.5] - Float speed
|
|
498
|
+
* @param {number} [opts.randomness=0.3] - Randomness factor
|
|
499
|
+
* @param {number} [opts.duration=5] - Duration
|
|
500
|
+
* @returns {FluentGO}
|
|
501
|
+
*/
|
|
502
|
+
float(opts = {}) {
|
|
503
|
+
return this.motion('float', opts);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Add shake motion
|
|
508
|
+
* @param {Object} [opts] - Motion options
|
|
509
|
+
* @param {number} [opts.intensity=5] - Shake intensity
|
|
510
|
+
* @param {number} [opts.frequency=20] - Shake frequency
|
|
511
|
+
* @param {number} [opts.decay=0.9] - Decay factor
|
|
512
|
+
* @param {number} [opts.duration=0.5] - Duration
|
|
513
|
+
* @returns {FluentGO}
|
|
514
|
+
*/
|
|
515
|
+
shake(opts = {}) {
|
|
516
|
+
return this.motion('shake', opts);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Add bounce motion
|
|
521
|
+
* @param {Object} [opts] - Motion options
|
|
522
|
+
* @param {number} [opts.height=100] - Bounce height
|
|
523
|
+
* @param {number} [opts.bounces=3] - Number of bounces
|
|
524
|
+
* @param {number} [opts.duration=2] - Duration
|
|
525
|
+
* @returns {FluentGO}
|
|
526
|
+
*/
|
|
527
|
+
bounce(opts = {}) {
|
|
528
|
+
return this.motion('bounce', opts);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Add spring motion
|
|
533
|
+
* @param {Object} [opts] - Motion options
|
|
534
|
+
* @returns {FluentGO}
|
|
535
|
+
*/
|
|
536
|
+
spring(opts = {}) {
|
|
537
|
+
return this.motion('spring', opts);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Add spiral motion
|
|
542
|
+
* @param {Object} [opts] - Motion options
|
|
543
|
+
* @param {number} [opts.startRadius=50] - Starting radius
|
|
544
|
+
* @param {number} [opts.endRadius=150] - Ending radius
|
|
545
|
+
* @param {number} [opts.revolutions=3] - Number of revolutions
|
|
546
|
+
* @param {number} [opts.duration=4] - Duration
|
|
547
|
+
* @returns {FluentGO}
|
|
548
|
+
*/
|
|
549
|
+
spiral(opts = {}) {
|
|
550
|
+
return this.motion('spiral', opts);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Add pendulum motion
|
|
555
|
+
* @param {Object} [opts] - Motion options
|
|
556
|
+
* @param {number} [opts.amplitude=45] - Swing amplitude in degrees
|
|
557
|
+
* @param {number} [opts.duration=2] - Period
|
|
558
|
+
* @returns {FluentGO}
|
|
559
|
+
*/
|
|
560
|
+
pendulum(opts = {}) {
|
|
561
|
+
return this.motion('pendulum', opts);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Add waypoint/patrol motion
|
|
566
|
+
* @param {Object} [opts] - Motion options
|
|
567
|
+
* @param {Array} [opts.waypoints] - Array of {x, y} points
|
|
568
|
+
* @param {number} [opts.speed=100] - Movement speed
|
|
569
|
+
* @param {number} [opts.waitTime=0] - Wait time at each point
|
|
570
|
+
* @returns {FluentGO}
|
|
571
|
+
*/
|
|
572
|
+
waypoint(opts = {}) {
|
|
573
|
+
return this.motion('waypoint', opts);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Generic motion add
|
|
578
|
+
* @param {string} type - Motion type name
|
|
579
|
+
* @param {Object} [opts] - Motion options
|
|
580
|
+
* @returns {FluentGO}
|
|
581
|
+
*/
|
|
582
|
+
motion(type, opts = {}) {
|
|
583
|
+
// Store motion config for processing in update loop
|
|
584
|
+
this.#motions.push({ type, opts });
|
|
585
|
+
|
|
586
|
+
// Initialize motion state on GO
|
|
587
|
+
if (!this.#go._fluentMotions) {
|
|
588
|
+
this.#go._fluentMotions = [];
|
|
589
|
+
this.#go._motionTime = 0;
|
|
590
|
+
|
|
591
|
+
// Store base position for relative motions
|
|
592
|
+
this.#go._baseX = this.#go.x;
|
|
593
|
+
this.#go._baseY = this.#go.y;
|
|
594
|
+
|
|
595
|
+
// Inject motion processing into GO update
|
|
596
|
+
const originalUpdate = this.#go.update?.bind(this.#go) || (() => {});
|
|
597
|
+
const self = this;
|
|
598
|
+
this.#go.update = function(dt) {
|
|
599
|
+
originalUpdate(dt);
|
|
600
|
+
this._motionTime += dt;
|
|
601
|
+
self.#processMotions(dt);
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
this.#go._fluentMotions.push({ type, opts, state: null });
|
|
606
|
+
return this;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Process all motions on this GO
|
|
611
|
+
* @private
|
|
612
|
+
*/
|
|
613
|
+
#processMotions(dt) {
|
|
614
|
+
for (const motion of this.#go._fluentMotions) {
|
|
615
|
+
const result = this.#applyMotion(motion, dt);
|
|
616
|
+
motion.state = result?.state;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Apply a single motion
|
|
622
|
+
* @private
|
|
623
|
+
*/
|
|
624
|
+
#applyMotion(motion, dt) {
|
|
625
|
+
const { type, opts, state } = motion;
|
|
626
|
+
const t = this.#go._motionTime;
|
|
627
|
+
const go = this.#go;
|
|
628
|
+
|
|
629
|
+
switch (type) {
|
|
630
|
+
case 'oscillate': {
|
|
631
|
+
const { prop = 'y', min = -50, max = 50, duration = 2 } = opts;
|
|
632
|
+
const result = Motion.oscillate(min, max, t, duration, true);
|
|
633
|
+
|
|
634
|
+
// Store base value if not set
|
|
635
|
+
const baseProp = `_base_${prop}`;
|
|
636
|
+
if (go[baseProp] === undefined) {
|
|
637
|
+
go[baseProp] = go[prop];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
go[prop] = go[baseProp] + result.value;
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
case 'pulse': {
|
|
645
|
+
const { prop = 'scale', min = 0.8, max = 1.2, duration = 1 } = opts;
|
|
646
|
+
const result = Motion.pulse(min, max, t, duration, true);
|
|
647
|
+
|
|
648
|
+
if (prop === 'scale') {
|
|
649
|
+
go.scaleX = result.value;
|
|
650
|
+
go.scaleY = result.value;
|
|
651
|
+
} else if (prop === 'opacity' && go.renderable) {
|
|
652
|
+
go.renderable.opacity = result.value;
|
|
653
|
+
} else {
|
|
654
|
+
go[prop] = result.value;
|
|
655
|
+
}
|
|
656
|
+
return result;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
case 'orbit': {
|
|
660
|
+
const {
|
|
661
|
+
centerX = go._baseX,
|
|
662
|
+
centerY = go._baseY,
|
|
663
|
+
radiusX = 100,
|
|
664
|
+
radiusY = 100,
|
|
665
|
+
duration = 3,
|
|
666
|
+
clockwise = true
|
|
667
|
+
} = opts;
|
|
668
|
+
|
|
669
|
+
// Store orbit center on first call
|
|
670
|
+
if (!go._orbitCenter) {
|
|
671
|
+
go._orbitCenter = { x: centerX, y: centerY };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const result = Motion.orbit(
|
|
675
|
+
go._orbitCenter.x, go._orbitCenter.y,
|
|
676
|
+
radiusX, radiusY, 0, t, duration, true, clockwise
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
go.x = result.x;
|
|
680
|
+
go.y = result.y;
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
case 'float': {
|
|
685
|
+
const { radius = 20, speed = 0.5, randomness = 0.3, duration = 5 } = opts;
|
|
686
|
+
|
|
687
|
+
// Initialize float state
|
|
688
|
+
if (!go._floatState) {
|
|
689
|
+
go._floatState = {
|
|
690
|
+
baseX: go._baseX,
|
|
691
|
+
baseY: go._baseY
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const result = Motion.float(
|
|
696
|
+
go._floatState, t, duration, speed, randomness, radius, true
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
go.x = result.x;
|
|
700
|
+
go.y = result.y;
|
|
701
|
+
return result;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
case 'shake': {
|
|
705
|
+
const {
|
|
706
|
+
intensity = 5,
|
|
707
|
+
frequency = 20,
|
|
708
|
+
decay = 0.9,
|
|
709
|
+
duration = 0.5
|
|
710
|
+
} = opts;
|
|
711
|
+
|
|
712
|
+
const result = Motion.shake(
|
|
713
|
+
go._baseX, go._baseY,
|
|
714
|
+
intensity, intensity, frequency, decay, t, duration, true
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
go.x = result.x;
|
|
718
|
+
go.y = result.y;
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
case 'bounce': {
|
|
723
|
+
const { height = 100, bounces = 3, duration = 2 } = opts;
|
|
724
|
+
const result = Motion.bounce(height, go._baseY, bounces, t, duration, true);
|
|
725
|
+
|
|
726
|
+
go.y = result.y;
|
|
727
|
+
return result;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
case 'spiral': {
|
|
731
|
+
const {
|
|
732
|
+
startRadius = 50,
|
|
733
|
+
endRadius = 150,
|
|
734
|
+
revolutions = 3,
|
|
735
|
+
duration = 4
|
|
736
|
+
} = opts;
|
|
737
|
+
|
|
738
|
+
if (!go._spiralCenter) {
|
|
739
|
+
go._spiralCenter = { x: go._baseX, y: go._baseY };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const result = Motion.spiral(
|
|
743
|
+
go._spiralCenter.x, go._spiralCenter.y,
|
|
744
|
+
startRadius, endRadius, 0, revolutions, t, duration, true
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
go.x = result.x;
|
|
748
|
+
go.y = result.y;
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
case 'pendulum': {
|
|
753
|
+
const { amplitude = 45, duration = 2, damped = false } = opts;
|
|
754
|
+
const result = Motion.pendulum(0, amplitude, t, duration, true, damped);
|
|
755
|
+
|
|
756
|
+
go.rotation = result.value * (Math.PI / 180);
|
|
757
|
+
return result;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
case 'waypoint': {
|
|
761
|
+
const { waypoints = [], speed = 100, waitTime = 0 } = opts;
|
|
762
|
+
|
|
763
|
+
if (waypoints.length === 0) return { state: null };
|
|
764
|
+
|
|
765
|
+
const result = Motion.waypoint(
|
|
766
|
+
go, t, waypoints, speed, waitTime, true, null, state
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
go.x = result.x ?? go.x;
|
|
770
|
+
go.y = result.y ?? go.y;
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
default:
|
|
775
|
+
console.warn(`Unknown motion type: ${type}`);
|
|
776
|
+
return { state: null };
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// ─────────────────────────────────────────────────────────
|
|
781
|
+
// TWEEN SHORTCUTS
|
|
782
|
+
// ─────────────────────────────────────────────────────────
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Tween properties over time
|
|
786
|
+
* @param {Object} props - Properties to tween { x: 100, y: 200 }
|
|
787
|
+
* @param {Object} [opts] - Tween options
|
|
788
|
+
* @param {number} [opts.duration=1] - Duration in seconds
|
|
789
|
+
* @param {string|Function} [opts.easing='easeOutQuad'] - Easing function
|
|
790
|
+
* @param {number} [opts.delay=0] - Delay before starting
|
|
791
|
+
* @param {Function} [opts.onComplete] - Completion callback
|
|
792
|
+
* @returns {FluentGO}
|
|
793
|
+
*/
|
|
794
|
+
tween(props, opts = {}) {
|
|
795
|
+
const { duration = 1, easing = 'easeOutQuad', delay = 0, onComplete } = opts;
|
|
796
|
+
|
|
797
|
+
if (delay > 0) {
|
|
798
|
+
setTimeout(() => {
|
|
799
|
+
Tweenetik.to(this.#go, props, duration, easing, { onComplete });
|
|
800
|
+
}, delay * 1000);
|
|
801
|
+
} else {
|
|
802
|
+
Tweenetik.to(this.#go, props, duration, easing, { onComplete });
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// ─────────────────────────────────────────────────────────
|
|
809
|
+
// CHILD GAMEOBJECTS
|
|
810
|
+
// ─────────────────────────────────────────────────────────
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Create a child GameObject
|
|
814
|
+
*
|
|
815
|
+
* Supports multiple signatures:
|
|
816
|
+
* - child() - Create plain child GO
|
|
817
|
+
* - child(options) - Create plain child with options
|
|
818
|
+
* - child(CustomClass) - Create custom child class
|
|
819
|
+
* - child(CustomClass, options) - Custom class with options
|
|
820
|
+
* - child(options, builderFn) - Plain child with builder
|
|
821
|
+
* - child(CustomClass, options, builderFn) - Custom class with builder
|
|
822
|
+
*
|
|
823
|
+
* @param {Object|Function} [optsOrClass] - Child GO options or custom class
|
|
824
|
+
* @param {Object|Function} [optsOrBuilder] - Options or builder function
|
|
825
|
+
* @param {Function} [builderFn] - Optional builder callback
|
|
826
|
+
* @returns {FluentGO}
|
|
827
|
+
*/
|
|
828
|
+
child(optsOrClass, optsOrBuilder, builderFn) {
|
|
829
|
+
// Parse flexible arguments
|
|
830
|
+
let GOClass, opts, builder;
|
|
831
|
+
|
|
832
|
+
if (typeof optsOrClass === 'function' && optsOrClass.prototype) {
|
|
833
|
+
// child(CustomClass) or child(CustomClass, options) or child(CustomClass, options, builder)
|
|
834
|
+
GOClass = optsOrClass;
|
|
835
|
+
if (typeof optsOrBuilder === 'function') {
|
|
836
|
+
opts = {};
|
|
837
|
+
builder = optsOrBuilder;
|
|
838
|
+
} else {
|
|
839
|
+
opts = optsOrBuilder || {};
|
|
840
|
+
builder = builderFn;
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
// child() or child(options) or child(options, builder)
|
|
844
|
+
GOClass = GameObject;
|
|
845
|
+
opts = optsOrClass || {};
|
|
846
|
+
builder = optsOrBuilder;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// GameObject constructor signature is (game, options)
|
|
850
|
+
const childGO = new GOClass(this.#go.game, opts);
|
|
851
|
+
|
|
852
|
+
this.#go.addChild(childGO);
|
|
853
|
+
|
|
854
|
+
// Register in refs if named
|
|
855
|
+
if (opts.name) {
|
|
856
|
+
this.#refs[opts.name] = childGO;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const fluentChild = new FluentGO(this, childGO, this.#refs, this.#state);
|
|
860
|
+
|
|
861
|
+
if (builder) {
|
|
862
|
+
builder(fluentChild);
|
|
863
|
+
return this; // Return parent GO context
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return fluentChild;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// ─────────────────────────────────────────────────────────
|
|
870
|
+
// EVENTS
|
|
871
|
+
// ─────────────────────────────────────────────────────────
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Register event handler on this GO
|
|
875
|
+
* @param {string} event - Event name
|
|
876
|
+
* @param {Function} handler - Handler function
|
|
877
|
+
* @returns {FluentGO}
|
|
878
|
+
*/
|
|
879
|
+
on(event, handler) {
|
|
880
|
+
const ctx = {
|
|
881
|
+
go: this.#go,
|
|
882
|
+
shapes: this.#shapes,
|
|
883
|
+
refs: this.#refs,
|
|
884
|
+
state: this.#state
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// Enable interactivity for mouse/input events
|
|
888
|
+
const interactiveEvents = ['click', 'mousedown', 'mouseup', 'mousemove', 'mouseover', 'mouseout', 'inputdown', 'inputup', 'inputmove'];
|
|
889
|
+
if (interactiveEvents.includes(event)) {
|
|
890
|
+
this.#go.interactive = true;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (this.#go.events) {
|
|
894
|
+
this.#go.events.on(event, (e) => handler(ctx, e));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return this;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Custom update function for this GO
|
|
902
|
+
* @param {Function} fn - Update function (dt, context)
|
|
903
|
+
* @returns {FluentGO}
|
|
904
|
+
*/
|
|
905
|
+
update(fn) {
|
|
906
|
+
const originalUpdate = this.#go.update?.bind(this.#go) || (() => {});
|
|
907
|
+
const shapes = this.#shapes;
|
|
908
|
+
const refs = this.#refs;
|
|
909
|
+
const state = this.#state;
|
|
910
|
+
const go = this.#go;
|
|
911
|
+
|
|
912
|
+
this.#go.update = (dt) => {
|
|
913
|
+
originalUpdate(dt);
|
|
914
|
+
fn(dt, { go, shapes, refs, state });
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
return this;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// ─────────────────────────────────────────────────────────
|
|
921
|
+
// TRANSFORM SHORTCUTS
|
|
922
|
+
// ─────────────────────────────────────────────────────────
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Set position
|
|
926
|
+
* @param {number} x - X position
|
|
927
|
+
* @param {number} y - Y position
|
|
928
|
+
* @returns {FluentGO}
|
|
929
|
+
*/
|
|
930
|
+
pos(x, y) {
|
|
931
|
+
this.#go.x = x;
|
|
932
|
+
this.#go.y = y;
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Set scale
|
|
938
|
+
* @param {number} sx - X scale
|
|
939
|
+
* @param {number} [sy] - Y scale (defaults to sx)
|
|
940
|
+
* @returns {FluentGO}
|
|
941
|
+
*/
|
|
942
|
+
scale(sx, sy) {
|
|
943
|
+
this.#go.scaleX = sx;
|
|
944
|
+
this.#go.scaleY = sy ?? sx;
|
|
945
|
+
return this;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Set rotation
|
|
950
|
+
* @param {number} degrees - Rotation in degrees
|
|
951
|
+
* @returns {FluentGO}
|
|
952
|
+
*/
|
|
953
|
+
rotate(degrees) {
|
|
954
|
+
this.#go.rotation = degrees * (Math.PI / 180);
|
|
955
|
+
return this;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Set opacity
|
|
960
|
+
* @param {number} value - Opacity (0-1)
|
|
961
|
+
* @returns {FluentGO}
|
|
962
|
+
*/
|
|
963
|
+
opacity(value) {
|
|
964
|
+
this.#go.opacity = value;
|
|
965
|
+
if (this.#go.renderable) {
|
|
966
|
+
this.#go.renderable.opacity = value;
|
|
967
|
+
}
|
|
968
|
+
return this;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Set z-index
|
|
973
|
+
* @param {number} value - Z-index value
|
|
974
|
+
* @returns {FluentGO}
|
|
975
|
+
*/
|
|
976
|
+
zIndex(value) {
|
|
977
|
+
this.#go.zIndex = value;
|
|
978
|
+
return this;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// ─────────────────────────────────────────────────────────
|
|
982
|
+
// NAVIGATION
|
|
983
|
+
// ─────────────────────────────────────────────────────────
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Navigate back to parent context
|
|
987
|
+
* @returns {import('./fluent-scene.js').FluentScene|FluentGO}
|
|
988
|
+
*/
|
|
989
|
+
end() {
|
|
990
|
+
return this.#parent;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Create sibling GO (shortcut to scene.go)
|
|
995
|
+
* @param {Object} [opts] - GO options
|
|
996
|
+
* @returns {FluentGO}
|
|
997
|
+
*/
|
|
998
|
+
go(opts) {
|
|
999
|
+
// Navigate up to scene and create new GO
|
|
1000
|
+
let parent = this.#parent;
|
|
1001
|
+
while (parent && !parent.sceneInstance) {
|
|
1002
|
+
parent = parent.end?.();
|
|
1003
|
+
}
|
|
1004
|
+
if (parent && parent.go) {
|
|
1005
|
+
return parent.go(opts);
|
|
1006
|
+
}
|
|
1007
|
+
throw new Error('Cannot find scene context');
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Switch to another scene
|
|
1012
|
+
* @param {string} name - Scene name
|
|
1013
|
+
* @param {Object} [opts] - Scene options
|
|
1014
|
+
* @returns {import('./fluent-scene.js').FluentScene}
|
|
1015
|
+
*/
|
|
1016
|
+
scene(name, opts) {
|
|
1017
|
+
let parent = this.#parent;
|
|
1018
|
+
while (parent && !parent.sceneInstance) {
|
|
1019
|
+
parent = parent.end?.();
|
|
1020
|
+
}
|
|
1021
|
+
if (parent && parent.scene) {
|
|
1022
|
+
return parent.scene(name, opts);
|
|
1023
|
+
}
|
|
1024
|
+
throw new Error('Cannot find game context');
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// ─────────────────────────────────────────────────────────
|
|
1028
|
+
// SHORTCUTS
|
|
1029
|
+
// ─────────────────────────────────────────────────────────
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Start the game
|
|
1033
|
+
* @returns {import('./fluent-game.js').FluentGame}
|
|
1034
|
+
*/
|
|
1035
|
+
start() {
|
|
1036
|
+
let parent = this.#parent;
|
|
1037
|
+
while (parent && parent.end) {
|
|
1038
|
+
const next = parent.end();
|
|
1039
|
+
if (next === parent) break;
|
|
1040
|
+
parent = next;
|
|
1041
|
+
}
|
|
1042
|
+
return parent.start();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// ─────────────────────────────────────────────────────────
|
|
1046
|
+
// ACCESSORS
|
|
1047
|
+
// ─────────────────────────────────────────────────────────
|
|
1048
|
+
|
|
1049
|
+
/** @returns {GameObject} Underlying GameObject instance */
|
|
1050
|
+
get goInstance() { return this.#go; }
|
|
1051
|
+
|
|
1052
|
+
/** @returns {Array} All shapes added to this GO */
|
|
1053
|
+
get shapes() { return this.#shapes; }
|
|
1054
|
+
|
|
1055
|
+
/** @returns {Object} Named object references */
|
|
1056
|
+
get refs() { return this.#refs; }
|
|
1057
|
+
|
|
1058
|
+
/** @returns {Object} Shared state */
|
|
1059
|
+
get state() { return this.#state; }
|
|
1060
|
+
}
|