@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,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParticleSystem - High-performance particle management GameObject
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Object pooling to minimize garbage collection
|
|
6
|
+
* - Composable updaters for physics and effects
|
|
7
|
+
* - Optional Camera3D integration with depth sorting
|
|
8
|
+
* - Multiple emitter support
|
|
9
|
+
* - Blend mode control
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const particles = new ParticleSystem(this, {
|
|
13
|
+
* camera: this.camera,
|
|
14
|
+
* depthSort: true,
|
|
15
|
+
* maxParticles: 3000,
|
|
16
|
+
* blendMode: "screen",
|
|
17
|
+
* updaters: [Updaters.velocity, Updaters.lifetime, Updaters.gravity(150)],
|
|
18
|
+
* });
|
|
19
|
+
* particles.addEmitter("fountain", new ParticleEmitter({ rate: 50 }));
|
|
20
|
+
* this.pipeline.add(particles);
|
|
21
|
+
*/
|
|
22
|
+
import { GameObject } from "../game/objects/go.js";
|
|
23
|
+
import { Painter } from "../painter/painter.js";
|
|
24
|
+
import { Particle } from "./particle.js";
|
|
25
|
+
import { Updaters } from "./updaters.js";
|
|
26
|
+
|
|
27
|
+
export class ParticleSystem extends GameObject {
|
|
28
|
+
/**
|
|
29
|
+
* @param {Game} game - Game instance
|
|
30
|
+
* @param {Object} options - System configuration
|
|
31
|
+
* @param {number} [options.maxParticles=5000] - Maximum active particles
|
|
32
|
+
* @param {Camera3D} [options.camera] - Optional camera for 3D projection
|
|
33
|
+
* @param {boolean} [options.depthSort=false] - Enable depth sorting (requires camera)
|
|
34
|
+
* @param {string} [options.blendMode="source-over"] - Canvas blend mode
|
|
35
|
+
* @param {Function[]} [options.updaters] - Array of updater functions
|
|
36
|
+
* @param {boolean} [options.worldSpace=false] - Position particles in world space
|
|
37
|
+
*/
|
|
38
|
+
constructor(game, options = {}) {
|
|
39
|
+
super(game, options);
|
|
40
|
+
|
|
41
|
+
// Particle storage
|
|
42
|
+
this.particles = [];
|
|
43
|
+
this.pool = [];
|
|
44
|
+
this.maxParticles = options.maxParticles ?? 5000;
|
|
45
|
+
|
|
46
|
+
// Emitters
|
|
47
|
+
this.emitters = new Map();
|
|
48
|
+
|
|
49
|
+
// Optional Camera3D for 3D projection
|
|
50
|
+
this.camera = options.camera ?? null;
|
|
51
|
+
this.depthSort = options.depthSort ?? false;
|
|
52
|
+
|
|
53
|
+
// Updaters (composable behaviors)
|
|
54
|
+
this.updaters = options.updaters ?? [
|
|
55
|
+
Updaters.velocity,
|
|
56
|
+
Updaters.lifetime,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Rendering
|
|
60
|
+
this.blendMode = options.blendMode ?? "source-over";
|
|
61
|
+
this.worldSpace = options.worldSpace ?? false;
|
|
62
|
+
|
|
63
|
+
// Stats
|
|
64
|
+
this._particleCount = 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Add an emitter to the system.
|
|
69
|
+
* @param {string} name - Emitter identifier
|
|
70
|
+
* @param {ParticleEmitter} emitter - Emitter instance
|
|
71
|
+
* @returns {ParticleSystem} this (for chaining)
|
|
72
|
+
*/
|
|
73
|
+
addEmitter(name, emitter) {
|
|
74
|
+
this.emitters.set(name, emitter);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Remove an emitter from the system.
|
|
80
|
+
* @param {string} name - Emitter identifier
|
|
81
|
+
* @returns {ParticleSystem} this (for chaining)
|
|
82
|
+
*/
|
|
83
|
+
removeEmitter(name) {
|
|
84
|
+
this.emitters.delete(name);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get an emitter by name.
|
|
90
|
+
* @param {string} name - Emitter identifier
|
|
91
|
+
* @returns {ParticleEmitter|undefined}
|
|
92
|
+
*/
|
|
93
|
+
getEmitter(name) {
|
|
94
|
+
return this.emitters.get(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Acquire a particle from pool or create new.
|
|
99
|
+
* @returns {Particle}
|
|
100
|
+
*/
|
|
101
|
+
acquire() {
|
|
102
|
+
if (this.pool.length > 0) {
|
|
103
|
+
return this.pool.pop();
|
|
104
|
+
}
|
|
105
|
+
return new Particle();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Release a particle back to pool.
|
|
110
|
+
* @param {Particle} particle
|
|
111
|
+
*/
|
|
112
|
+
release(particle) {
|
|
113
|
+
particle.reset();
|
|
114
|
+
this.pool.push(particle);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Emit particles using an emitter.
|
|
119
|
+
* @param {number} count - Number of particles to emit
|
|
120
|
+
* @param {ParticleEmitter} emitter - Emitter to use
|
|
121
|
+
*/
|
|
122
|
+
emit(count, emitter) {
|
|
123
|
+
for (let i = 0; i < count && this.particles.length < this.maxParticles; i++) {
|
|
124
|
+
const p = this.acquire();
|
|
125
|
+
emitter.emit(p);
|
|
126
|
+
this.particles.push(p);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Burst spawn particles.
|
|
132
|
+
* @param {number} count - Number of particles
|
|
133
|
+
* @param {ParticleEmitter|string} emitterOrName - Emitter instance or name
|
|
134
|
+
*/
|
|
135
|
+
burst(count, emitterOrName) {
|
|
136
|
+
const emitter = typeof emitterOrName === "string"
|
|
137
|
+
? this.emitters.get(emitterOrName)
|
|
138
|
+
: emitterOrName;
|
|
139
|
+
|
|
140
|
+
if (emitter) {
|
|
141
|
+
this.emit(count, emitter);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update all emitters and particles.
|
|
147
|
+
* @param {number} dt - Delta time in seconds
|
|
148
|
+
*/
|
|
149
|
+
update(dt) {
|
|
150
|
+
super.update(dt);
|
|
151
|
+
|
|
152
|
+
// Update emitters and spawn particles
|
|
153
|
+
for (const emitter of this.emitters.values()) {
|
|
154
|
+
if (emitter.active) {
|
|
155
|
+
const count = emitter.update(dt);
|
|
156
|
+
this.emit(count, emitter);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Update particles (iterate backwards for safe removal)
|
|
161
|
+
for (let i = this.particles.length - 1; i >= 0; i--) {
|
|
162
|
+
const p = this.particles[i];
|
|
163
|
+
|
|
164
|
+
// Apply all updaters
|
|
165
|
+
for (const updater of this.updaters) {
|
|
166
|
+
updater(p, dt, this);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Remove dead particles
|
|
170
|
+
if (!p.alive) {
|
|
171
|
+
this.release(p);
|
|
172
|
+
this.particles.splice(i, 1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this._particleCount = this.particles.length;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Render all particles.
|
|
181
|
+
*/
|
|
182
|
+
render() {
|
|
183
|
+
super.render();
|
|
184
|
+
|
|
185
|
+
if (this.particles.length === 0) return;
|
|
186
|
+
|
|
187
|
+
if (this.camera && this.depthSort) {
|
|
188
|
+
this.renderWithDepthSort();
|
|
189
|
+
} else {
|
|
190
|
+
this.renderSimple();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Simple 2D rendering (no depth sorting).
|
|
196
|
+
*/
|
|
197
|
+
renderSimple() {
|
|
198
|
+
Painter.useCtx((ctx) => {
|
|
199
|
+
ctx.globalCompositeOperation = this.blendMode;
|
|
200
|
+
|
|
201
|
+
for (const p of this.particles) {
|
|
202
|
+
this.drawParticle(ctx, p, p.x, p.y, 1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ctx.globalCompositeOperation = "source-over";
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 3D rendering with Camera3D projection and depth sorting.
|
|
211
|
+
*/
|
|
212
|
+
renderWithDepthSort() {
|
|
213
|
+
// Build render list with projections
|
|
214
|
+
const renderList = [];
|
|
215
|
+
|
|
216
|
+
for (const p of this.particles) {
|
|
217
|
+
const projected = this.camera.project(p.x, p.y, p.z);
|
|
218
|
+
|
|
219
|
+
// Cull particles behind camera
|
|
220
|
+
if (projected.z < -this.camera.perspective + 10) continue;
|
|
221
|
+
|
|
222
|
+
renderList.push({
|
|
223
|
+
p,
|
|
224
|
+
x: projected.x,
|
|
225
|
+
y: projected.y,
|
|
226
|
+
z: projected.z,
|
|
227
|
+
scale: projected.scale,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Sort back to front
|
|
232
|
+
renderList.sort((a, b) => b.z - a.z);
|
|
233
|
+
|
|
234
|
+
// Draw all particles
|
|
235
|
+
Painter.useCtx((ctx) => {
|
|
236
|
+
ctx.globalCompositeOperation = this.blendMode;
|
|
237
|
+
|
|
238
|
+
// Translate to center if using camera (camera projects relative to origin)
|
|
239
|
+
// Only do this if NOT inside a Scene3D (which already handles projection/centering)
|
|
240
|
+
const isProjected = this.parent && this.parent.constructor.name === "Scene3D";
|
|
241
|
+
|
|
242
|
+
if (!this.worldSpace && !isProjected) {
|
|
243
|
+
ctx.save();
|
|
244
|
+
ctx.translate(this.game.width / 2, this.game.height / 2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const item of renderList) {
|
|
248
|
+
this.drawParticle(ctx, item.p, item.x, item.y, item.scale);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!this.worldSpace && !isProjected) {
|
|
252
|
+
ctx.restore();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
ctx.globalCompositeOperation = "source-over";
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Draw a single particle.
|
|
261
|
+
* Override this method for custom particle rendering.
|
|
262
|
+
*
|
|
263
|
+
* @param {CanvasRenderingContext2D} ctx - Canvas context
|
|
264
|
+
* @param {Particle} p - Particle to draw
|
|
265
|
+
* @param {number} x - Screen X position
|
|
266
|
+
* @param {number} y - Screen Y position
|
|
267
|
+
* @param {number} scale - Size scale factor (from perspective)
|
|
268
|
+
*/
|
|
269
|
+
drawParticle(ctx, p, x, y, scale) {
|
|
270
|
+
const { r, g, b, a } = p.color;
|
|
271
|
+
const size = p.size * scale;
|
|
272
|
+
|
|
273
|
+
if (size < 0.5 || a <= 0) return;
|
|
274
|
+
|
|
275
|
+
ctx.fillStyle = `rgba(${Math.floor(r)},${Math.floor(g)},${Math.floor(b)},${a})`;
|
|
276
|
+
|
|
277
|
+
const shape = p.shape ?? "circle";
|
|
278
|
+
const half = size / 2;
|
|
279
|
+
|
|
280
|
+
ctx.beginPath();
|
|
281
|
+
|
|
282
|
+
if (shape === "circle") {
|
|
283
|
+
ctx.arc(x, y, half, 0, Math.PI * 2);
|
|
284
|
+
} else if (shape === "square") {
|
|
285
|
+
ctx.rect(x - half, y - half, size, size);
|
|
286
|
+
} else if (shape === "triangle") {
|
|
287
|
+
ctx.moveTo(x, y - half);
|
|
288
|
+
ctx.lineTo(x + half, y + half);
|
|
289
|
+
ctx.lineTo(x - half, y + half);
|
|
290
|
+
ctx.closePath();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
ctx.fill();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Clear all particles and return them to pool.
|
|
298
|
+
*/
|
|
299
|
+
clear() {
|
|
300
|
+
for (const p of this.particles) {
|
|
301
|
+
this.release(p);
|
|
302
|
+
}
|
|
303
|
+
this.particles = [];
|
|
304
|
+
this._particleCount = 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get current particle count.
|
|
309
|
+
* @type {number}
|
|
310
|
+
*/
|
|
311
|
+
get particleCount() {
|
|
312
|
+
return this._particleCount;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get pool size (recycled particles ready for reuse).
|
|
317
|
+
* @type {number}
|
|
318
|
+
*/
|
|
319
|
+
get poolSize() {
|
|
320
|
+
return this.pool.length;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Particle - Lightweight data class for particle systems
|
|
3
|
+
*
|
|
4
|
+
* Plain object with no methods for performance (2500+ particles efficient).
|
|
5
|
+
* Uses object pooling via reset() to minimize garbage collection.
|
|
6
|
+
*/
|
|
7
|
+
export class Particle {
|
|
8
|
+
constructor() {
|
|
9
|
+
// Position (3D capable)
|
|
10
|
+
this.x = 0;
|
|
11
|
+
this.y = 0;
|
|
12
|
+
this.z = 0;
|
|
13
|
+
|
|
14
|
+
// Velocity
|
|
15
|
+
this.vx = 0;
|
|
16
|
+
this.vy = 0;
|
|
17
|
+
this.vz = 0;
|
|
18
|
+
|
|
19
|
+
// Appearance
|
|
20
|
+
this.size = 1;
|
|
21
|
+
this.color = { r: 255, g: 255, b: 255, a: 1 };
|
|
22
|
+
this.shape = "circle"; // "circle", "square", "triangle"
|
|
23
|
+
|
|
24
|
+
// Lifecycle
|
|
25
|
+
this.age = 0;
|
|
26
|
+
this.lifetime = 1;
|
|
27
|
+
this.alive = true;
|
|
28
|
+
|
|
29
|
+
// Custom data (for domain-specific behaviors like orbital mechanics)
|
|
30
|
+
this.custom = {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reset all properties for object pooling.
|
|
35
|
+
* Called when particle is returned to pool.
|
|
36
|
+
*/
|
|
37
|
+
reset() {
|
|
38
|
+
this.x = 0;
|
|
39
|
+
this.y = 0;
|
|
40
|
+
this.z = 0;
|
|
41
|
+
|
|
42
|
+
this.vx = 0;
|
|
43
|
+
this.vy = 0;
|
|
44
|
+
this.vz = 0;
|
|
45
|
+
|
|
46
|
+
this.size = 1;
|
|
47
|
+
this.color.r = 255;
|
|
48
|
+
this.color.g = 255;
|
|
49
|
+
this.color.b = 255;
|
|
50
|
+
this.color.a = 1;
|
|
51
|
+
this.shape = "circle";
|
|
52
|
+
|
|
53
|
+
this.age = 0;
|
|
54
|
+
this.lifetime = 1;
|
|
55
|
+
this.alive = true;
|
|
56
|
+
|
|
57
|
+
// Clear custom data
|
|
58
|
+
for (const key in this.custom) {
|
|
59
|
+
delete this.custom[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Progress through lifetime (0 = just born, 1 = about to die).
|
|
65
|
+
* Useful for fade/shrink effects.
|
|
66
|
+
* @type {number}
|
|
67
|
+
*/
|
|
68
|
+
get progress() {
|
|
69
|
+
return this.lifetime > 0 ? this.age / this.lifetime : 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Updaters - Composable particle behavior functions
|
|
3
|
+
*
|
|
4
|
+
* Each updater is a function with signature: (particle, dt, system) => void
|
|
5
|
+
* Some updaters are factories that return the actual updater function.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* updaters: [Updaters.velocity, Updaters.lifetime, Updaters.gravity(200)]
|
|
9
|
+
*/
|
|
10
|
+
export const Updaters = {
|
|
11
|
+
/**
|
|
12
|
+
* Apply velocity to position.
|
|
13
|
+
*/
|
|
14
|
+
velocity: (p, dt) => {
|
|
15
|
+
p.x += p.vx * dt;
|
|
16
|
+
p.y += p.vy * dt;
|
|
17
|
+
p.z += p.vz * dt;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Track age and kill particle when lifetime exceeded.
|
|
22
|
+
*/
|
|
23
|
+
lifetime: (p, dt) => {
|
|
24
|
+
p.age += dt;
|
|
25
|
+
if (p.age >= p.lifetime) {
|
|
26
|
+
p.alive = false;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply downward gravity.
|
|
32
|
+
* @param {number} [strength=200] - Gravity strength (pixels/second²)
|
|
33
|
+
* @returns {Function} Updater function
|
|
34
|
+
*/
|
|
35
|
+
gravity: (strength = 200) => (p, dt) => {
|
|
36
|
+
p.vy += strength * dt;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Apply upward gravity (for rising particles like fire).
|
|
41
|
+
* @param {number} [strength=100] - Rise strength (pixels/second²)
|
|
42
|
+
* @returns {Function} Updater function
|
|
43
|
+
*/
|
|
44
|
+
rise: (strength = 100) => (p, dt) => {
|
|
45
|
+
p.vy -= strength * dt;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Apply velocity damping/friction.
|
|
50
|
+
* @param {number} [factor=0.98] - Damping factor (0-1, lower = more friction)
|
|
51
|
+
* @returns {Function} Updater function
|
|
52
|
+
*/
|
|
53
|
+
damping: (factor = 0.98) => (p, dt) => {
|
|
54
|
+
// Apply per-frame (not dt-based for simplicity)
|
|
55
|
+
p.vx *= factor;
|
|
56
|
+
p.vy *= factor;
|
|
57
|
+
p.vz *= factor;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fade out alpha over lifetime.
|
|
62
|
+
*/
|
|
63
|
+
fadeOut: (p, dt) => {
|
|
64
|
+
p.color.a = Math.max(0, 1 - p.progress);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fade in then out (peak at 50% lifetime).
|
|
69
|
+
*/
|
|
70
|
+
fadeInOut: (p, dt) => {
|
|
71
|
+
const t = p.progress;
|
|
72
|
+
p.color.a = t < 0.5 ? t * 2 : (1 - t) * 2;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Shrink size over lifetime.
|
|
77
|
+
* @param {number} [endScale=0] - Final size multiplier (0 = disappear)
|
|
78
|
+
* @returns {Function} Updater function
|
|
79
|
+
*/
|
|
80
|
+
shrink: (endScale = 0) => {
|
|
81
|
+
// Store initial size on first call
|
|
82
|
+
return (p, dt) => {
|
|
83
|
+
if (p.custom._initialSize === undefined) {
|
|
84
|
+
p.custom._initialSize = p.size;
|
|
85
|
+
}
|
|
86
|
+
p.size = p.custom._initialSize * (1 - p.progress * (1 - endScale));
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Grow size over lifetime.
|
|
92
|
+
* @param {number} [endScale=2] - Final size multiplier
|
|
93
|
+
* @returns {Function} Updater function
|
|
94
|
+
*/
|
|
95
|
+
grow: (endScale = 2) => {
|
|
96
|
+
return (p, dt) => {
|
|
97
|
+
if (p.custom._initialSize === undefined) {
|
|
98
|
+
p.custom._initialSize = p.size;
|
|
99
|
+
}
|
|
100
|
+
p.size = p.custom._initialSize * (1 + p.progress * (endScale - 1));
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Interpolate color over lifetime.
|
|
106
|
+
* @param {Object} startColor - Start color { r, g, b }
|
|
107
|
+
* @param {Object} endColor - End color { r, g, b }
|
|
108
|
+
* @returns {Function} Updater function
|
|
109
|
+
*/
|
|
110
|
+
colorOverLife: (startColor, endColor) => (p, dt) => {
|
|
111
|
+
const t = p.progress;
|
|
112
|
+
p.color.r = Math.floor(startColor.r + (endColor.r - startColor.r) * t);
|
|
113
|
+
p.color.g = Math.floor(startColor.g + (endColor.g - startColor.g) * t);
|
|
114
|
+
p.color.b = Math.floor(startColor.b + (endColor.b - startColor.b) * t);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Add random wobble to velocity.
|
|
119
|
+
* @param {number} [strength=10] - Wobble strength
|
|
120
|
+
* @returns {Function} Updater function
|
|
121
|
+
*/
|
|
122
|
+
wobble: (strength = 10) => (p, dt) => {
|
|
123
|
+
p.vx += (Math.random() - 0.5) * strength * dt;
|
|
124
|
+
p.vy += (Math.random() - 0.5) * strength * dt;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Bounce off screen boundaries.
|
|
129
|
+
* @param {Object} bounds - Boundary { left, right, top, bottom }
|
|
130
|
+
* @param {number} [bounce=0.8] - Bounce factor (0-1)
|
|
131
|
+
* @returns {Function} Updater function
|
|
132
|
+
*/
|
|
133
|
+
bounds: (bounds, bounce = 0.8) => (p, dt) => {
|
|
134
|
+
if (p.x < bounds.left) {
|
|
135
|
+
p.x = bounds.left;
|
|
136
|
+
p.vx = Math.abs(p.vx) * bounce;
|
|
137
|
+
} else if (p.x > bounds.right) {
|
|
138
|
+
p.x = bounds.right;
|
|
139
|
+
p.vx = -Math.abs(p.vx) * bounce;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (p.y < bounds.top) {
|
|
143
|
+
p.y = bounds.top;
|
|
144
|
+
p.vy = Math.abs(p.vy) * bounce;
|
|
145
|
+
} else if (p.y > bounds.bottom) {
|
|
146
|
+
p.y = bounds.bottom;
|
|
147
|
+
p.vy = -Math.abs(p.vy) * bounce;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Attract particles toward a point.
|
|
153
|
+
* @param {Object} target - Target position { x, y, z }
|
|
154
|
+
* @param {number} [strength=100] - Attraction strength
|
|
155
|
+
* @returns {Function} Updater function
|
|
156
|
+
*/
|
|
157
|
+
attract: (target, strength = 100) => (p, dt) => {
|
|
158
|
+
const dx = target.x - p.x;
|
|
159
|
+
const dy = target.y - p.y;
|
|
160
|
+
const dz = (target.z ?? 0) - p.z;
|
|
161
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
162
|
+
|
|
163
|
+
if (dist > 1) {
|
|
164
|
+
const force = strength * dt / dist;
|
|
165
|
+
p.vx += dx * force;
|
|
166
|
+
p.vy += dy * force;
|
|
167
|
+
p.vz += dz * force;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Shape } from "./shape.js";
|
|
2
|
+
import { Painter } from "../painter/painter.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Arc - A circular arc (partial circle outline) without connecting to center.
|
|
6
|
+
*/
|
|
7
|
+
export class Arc extends Shape {
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {number} x
|
|
11
|
+
* @param {number} y
|
|
12
|
+
* @param {number} radius
|
|
13
|
+
* @param {number} startAngle - In radians
|
|
14
|
+
* @param {number} endAngle - In radians
|
|
15
|
+
* @param {object} options - Style options
|
|
16
|
+
*/
|
|
17
|
+
constructor(radius, startAngle, endAngle, options = {}) {
|
|
18
|
+
super(options);
|
|
19
|
+
this.radius = radius;
|
|
20
|
+
this.startAngle = startAngle;
|
|
21
|
+
this.endAngle = endAngle;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
draw() {
|
|
25
|
+
super.draw();
|
|
26
|
+
Painter.lines.beginPath();
|
|
27
|
+
Painter.shapes.arc(0, 0, this.radius, this.startAngle, this.endAngle, false);
|
|
28
|
+
|
|
29
|
+
if (this.stroke) {
|
|
30
|
+
Painter.colors.stroke(this.stroke, this.lineWidth);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getBounds() {
|
|
35
|
+
const r = this.radius;
|
|
36
|
+
return {
|
|
37
|
+
x: this.x,
|
|
38
|
+
y: this.y,
|
|
39
|
+
width: r * 2,
|
|
40
|
+
height: r * 2,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Shape } from "./shape.js";
|
|
2
|
+
import { Painter } from "../painter/painter.js";
|
|
3
|
+
export class Arrow extends Shape {
|
|
4
|
+
constructor(length, options = {}) {
|
|
5
|
+
super(options);
|
|
6
|
+
this.length = length;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
draw() {
|
|
10
|
+
super.draw();
|
|
11
|
+
const halfW = this.width / 2;
|
|
12
|
+
const headLength = this.length * 0.4;
|
|
13
|
+
const shaftLength = this.length - headLength;
|
|
14
|
+
|
|
15
|
+
Painter.lines.beginPath();
|
|
16
|
+
Painter.lines.moveTo(-shaftLength / 2, -halfW);
|
|
17
|
+
Painter.lines.lineTo(shaftLength / 2, -halfW);
|
|
18
|
+
Painter.lines.lineTo(shaftLength / 2, -this.width);
|
|
19
|
+
Painter.lines.lineTo(this.length / 2, 0);
|
|
20
|
+
Painter.lines.lineTo(shaftLength / 2, this.width);
|
|
21
|
+
Painter.lines.lineTo(shaftLength / 2, halfW);
|
|
22
|
+
Painter.lines.lineTo(-shaftLength / 2, halfW);
|
|
23
|
+
Painter.lines.closePath();
|
|
24
|
+
|
|
25
|
+
if (this.color) {
|
|
26
|
+
Painter.colors.fill(this.color);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.stroke) {
|
|
30
|
+
Painter.colors.stroke(this.stroke, this.lineWidth);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Shape } from "./shape.js";
|
|
2
|
+
import { Painter } from "../painter/painter.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BezierShape - A shape that renders any custom path using Painter.lines.path().
|
|
6
|
+
* Great for clouds, blobs, tails, ears, capes, bananas.
|
|
7
|
+
*/
|
|
8
|
+
export class BezierShape extends Shape {
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param {number} x - Center X
|
|
12
|
+
* @param {number} y - Center Y
|
|
13
|
+
* @param {Array} path - An array of path commands [['M', x, y], ['C', ...], ['Z']]
|
|
14
|
+
* @param {object} options - color, stroke, etc
|
|
15
|
+
*/
|
|
16
|
+
constructor(path = [], options = {}) {
|
|
17
|
+
//this.logger.log("new Bezier", options);
|
|
18
|
+
super(options);
|
|
19
|
+
this.path = path;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
draw() {
|
|
23
|
+
super.draw();
|
|
24
|
+
Painter.lines.path(
|
|
25
|
+
this.path,
|
|
26
|
+
this.color,
|
|
27
|
+
this.stroke,
|
|
28
|
+
this.lineWidth
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getBounds() {
|
|
33
|
+
// Not calculated from path (too complex); just approximate
|
|
34
|
+
const s = 50;
|
|
35
|
+
return {
|
|
36
|
+
x: this.x,
|
|
37
|
+
y: this.y,
|
|
38
|
+
width: s * 2,
|
|
39
|
+
height: s * 2,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|