@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,277 @@
|
|
|
1
|
+
import { Painter } from "./painter";
|
|
2
|
+
|
|
3
|
+
export class PainterShapes {
|
|
4
|
+
// =========================================================================
|
|
5
|
+
// BASIC SHAPES
|
|
6
|
+
// =========================================================================
|
|
7
|
+
|
|
8
|
+
// Drawing functions that ensure opacity is applied
|
|
9
|
+
static rect(x, y, width, height, color) {
|
|
10
|
+
// Save current fillStyle
|
|
11
|
+
const oldFillStyle = Painter.ctx.fillStyle;
|
|
12
|
+
// Apply fill
|
|
13
|
+
Painter.colors.fill(color);
|
|
14
|
+
Painter.ctx.fillRect(x, y, width, height);
|
|
15
|
+
// Restore fillStyle
|
|
16
|
+
Painter.ctx.fillStyle = oldFillStyle;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static outlineRect(x, y, width, height, color, lineWidth = 1) {
|
|
20
|
+
// Save current styles
|
|
21
|
+
const oldStrokeStyle = Painter.ctx.strokeStyle;
|
|
22
|
+
const oldLineWidth = Painter.ctx.lineWidth;
|
|
23
|
+
// Apply stroke
|
|
24
|
+
Painter.ctx.strokeStyle = color;
|
|
25
|
+
Painter.ctx.lineWidth = lineWidth;
|
|
26
|
+
Painter.ctx.strokeRect(x, y, width, height);
|
|
27
|
+
// Restore styles
|
|
28
|
+
Painter.ctx.strokeStyle = oldStrokeStyle;
|
|
29
|
+
Painter.ctx.lineWidth = oldLineWidth;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Draw a rounded rectangle
|
|
34
|
+
* @param {number} x - X coordinate (top-left)
|
|
35
|
+
* @param {number} y - Y coordinate (top-left)
|
|
36
|
+
* @param {number} width - Width
|
|
37
|
+
* @param {number} height - Height
|
|
38
|
+
* @param {number|number[]} radii - Corner radius or array of radii for each corner
|
|
39
|
+
* @param {string|CanvasGradient} [fillColor] - Fill color
|
|
40
|
+
* @param {string|CanvasGradient} [strokeColor] - Stroke color
|
|
41
|
+
* @param {number} [lineWidth] - Line width
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
static roundRect(
|
|
45
|
+
x,
|
|
46
|
+
y,
|
|
47
|
+
width,
|
|
48
|
+
height,
|
|
49
|
+
radii = 0,
|
|
50
|
+
fillColor,
|
|
51
|
+
strokeColor,
|
|
52
|
+
lineWidth
|
|
53
|
+
) {
|
|
54
|
+
// Handle radius either as a single value or array
|
|
55
|
+
let radiusArray;
|
|
56
|
+
if (typeof radii === "number") {
|
|
57
|
+
radiusArray = [radii, radii, radii, radii]; // [topLeft, topRight, bottomRight, bottomLeft]
|
|
58
|
+
} else if (Array.isArray(radii)) {
|
|
59
|
+
// Ensure we have exactly 4 values
|
|
60
|
+
radiusArray =
|
|
61
|
+
radii.length === 4
|
|
62
|
+
? radii
|
|
63
|
+
: [
|
|
64
|
+
radii[0] || 0,
|
|
65
|
+
radii[1] || radii[0] || 0,
|
|
66
|
+
radii[2] || radii[0] || 0,
|
|
67
|
+
radii[3] || radii[1] || radii[0] || 0,
|
|
68
|
+
];
|
|
69
|
+
} else {
|
|
70
|
+
radiusArray = [0, 0, 0, 0];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [tlRadius, trRadius, brRadius, blRadius] = radiusArray;
|
|
74
|
+
const right = x + width;
|
|
75
|
+
const bottom = y + height;
|
|
76
|
+
|
|
77
|
+
Painter.lines.beginPath();
|
|
78
|
+
|
|
79
|
+
// Start from the top-left corner and draw clockwise
|
|
80
|
+
Painter.lines.moveTo(x + tlRadius, y);
|
|
81
|
+
|
|
82
|
+
// Top edge and top-right corner
|
|
83
|
+
Painter.lines.lineTo(right - trRadius, y);
|
|
84
|
+
this.arc(right - trRadius, y + trRadius, trRadius, -Math.PI / 2, 0);
|
|
85
|
+
|
|
86
|
+
// Right edge and bottom-right corner
|
|
87
|
+
Painter.lines.lineTo(right, bottom - brRadius);
|
|
88
|
+
this.arc(
|
|
89
|
+
right - brRadius,
|
|
90
|
+
bottom - brRadius,
|
|
91
|
+
brRadius,
|
|
92
|
+
0,
|
|
93
|
+
Math.PI / 2
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Bottom edge and bottom-left corner
|
|
97
|
+
Painter.lines.lineTo(x + blRadius, bottom);
|
|
98
|
+
this.arc(
|
|
99
|
+
x + blRadius,
|
|
100
|
+
bottom - blRadius,
|
|
101
|
+
blRadius,
|
|
102
|
+
Math.PI / 2,
|
|
103
|
+
Math.PI
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Left edge and top-left corner
|
|
107
|
+
Painter.lines.lineTo(x, y + tlRadius);
|
|
108
|
+
this.arc(
|
|
109
|
+
x + tlRadius,
|
|
110
|
+
y + tlRadius,
|
|
111
|
+
tlRadius,
|
|
112
|
+
Math.PI,
|
|
113
|
+
-Math.PI / 2
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
Painter.lines.closePath();
|
|
117
|
+
|
|
118
|
+
if (fillColor) {
|
|
119
|
+
Painter.fillStyle = fillColor;
|
|
120
|
+
Painter.colors.fill(fillColor);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (strokeColor) {
|
|
124
|
+
Painter.colors.stroke(strokeColor, lineWidth);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Draw a filled rounded rectangle
|
|
130
|
+
* @param {number} x - X coordinate (top-left)
|
|
131
|
+
* @param {number} y - Y coordinate (top-left)
|
|
132
|
+
* @param {number} width - Width
|
|
133
|
+
* @param {number} height - Height
|
|
134
|
+
* @param {number|number[]} radii - Corner radius or array of radii
|
|
135
|
+
* @param {string|CanvasGradient} [color] - Fill color
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
static fillRoundRect(x, y, width, height, radii = 0, color) {
|
|
139
|
+
this.roundRect(x, y, width, height, radii, color, null);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Draw a stroked rounded rectangle
|
|
144
|
+
* @param {number} x - X coordinate (top-left)
|
|
145
|
+
* @param {number} y - Y coordinate (top-left)
|
|
146
|
+
* @param {number} width - Width
|
|
147
|
+
* @param {number} height - Height
|
|
148
|
+
* @param {number|number[]} radii - Corner radius or array of radii
|
|
149
|
+
* @param {string|CanvasGradient} [color] - Stroke color
|
|
150
|
+
* @param {number} [lineWidth] - Line width
|
|
151
|
+
* @returns {void}
|
|
152
|
+
*/
|
|
153
|
+
static strokeRoundRect(x, y, width, height, radii = 0, color, lineWidth) {
|
|
154
|
+
this.roundRect(x, y, width, height, radii, null, color, lineWidth);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Draw a filled circle
|
|
159
|
+
* @param {number} x - Center X
|
|
160
|
+
* @param {number} y - Center Y
|
|
161
|
+
* @param {number} radius - Circle radius
|
|
162
|
+
* @param {string|CanvasGradient} [color] - Fill color
|
|
163
|
+
* @returns {void}
|
|
164
|
+
*/
|
|
165
|
+
static fillCircle(x, y, radius, color) {
|
|
166
|
+
Painter.logger.log("PainterShapes.fillCircle", x, y, radius, color);
|
|
167
|
+
Painter.lines.beginPath();
|
|
168
|
+
this.arc(x, y, radius, 0, Math.PI * 2);
|
|
169
|
+
Painter.colors.fill(color);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static arc(x, y, radius, startAngle, endAngle, counterclockwise) {
|
|
173
|
+
Painter.ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Draw a stroked circle
|
|
178
|
+
* @param {number} x - Center X
|
|
179
|
+
* @param {number} y - Center Y
|
|
180
|
+
* @param {number} radius - Circle radius
|
|
181
|
+
* @param {string|CanvasGradient} [color] - Stroke color
|
|
182
|
+
* @param {number} [lineWidth] - Line width
|
|
183
|
+
* @returns {void}
|
|
184
|
+
*/
|
|
185
|
+
static strokeCircle(x, y, radius, color, lineWidth) {
|
|
186
|
+
Painter.lines.beginPath();
|
|
187
|
+
this.arc(x, y, radius, 0, Math.PI * 2);
|
|
188
|
+
Painter.colors.stroke(color, lineWidth);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Draw a filled ellipse
|
|
193
|
+
* @param {number} x - Center X
|
|
194
|
+
* @param {number} y - Center Y
|
|
195
|
+
* @param {number} radiusX - X radius
|
|
196
|
+
* @param {number} radiusY - Y radius
|
|
197
|
+
* @param {number} [rotation=0] - Rotation in radians
|
|
198
|
+
* @param {string|CanvasGradient} [color] - Fill color
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
201
|
+
static fillEllipse(x, y, radiusX, radiusY, rotation = 0, color) {
|
|
202
|
+
Painter.lines.beginPath();
|
|
203
|
+
this.ellipse(x, y, radiusX, radiusY, rotation, 0, Math.PI * 2);
|
|
204
|
+
if (color) Painter.fillStyle = color;
|
|
205
|
+
Painter.colors.fill(color);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Draw a stroked ellipse
|
|
210
|
+
* @param {number} x - Center X
|
|
211
|
+
* @param {number} y - Center Y
|
|
212
|
+
* @param {number} radiusX - X radius
|
|
213
|
+
* @param {number} radiusY - Y radius
|
|
214
|
+
* @param {number} [rotation=0] - Rotation in radians
|
|
215
|
+
* @param {string|CanvasGradient} [color] - Stroke color
|
|
216
|
+
* @param {number} [lineWidth] - Line width
|
|
217
|
+
* @returns {void}
|
|
218
|
+
*/
|
|
219
|
+
static strokeEllipse(x, y, radiusX, radiusY, rotation = 0, color, lineWidth) {
|
|
220
|
+
Painter.lines.beginPath();
|
|
221
|
+
this.ellipse(x, y, radiusX, radiusY, rotation, 0, Math.PI * 2);
|
|
222
|
+
if (color) Painter.strokeStyle = color;
|
|
223
|
+
if (lineWidth !== undefined) Painter.lineWidth = lineWidth;
|
|
224
|
+
Painter.colors.stroke(color, lineWidth);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static ellipse(
|
|
228
|
+
x,
|
|
229
|
+
y,
|
|
230
|
+
radiusX,
|
|
231
|
+
radiusY,
|
|
232
|
+
rotation,
|
|
233
|
+
startAngle,
|
|
234
|
+
endAngle,
|
|
235
|
+
counterclockwise
|
|
236
|
+
) {
|
|
237
|
+
Painter.ctx.ellipse(
|
|
238
|
+
x,
|
|
239
|
+
y,
|
|
240
|
+
radiusX,
|
|
241
|
+
radiusY,
|
|
242
|
+
rotation,
|
|
243
|
+
startAngle,
|
|
244
|
+
endAngle,
|
|
245
|
+
counterclockwise
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Draw a polygon
|
|
251
|
+
* @param {Array<{x: number, y: number}>} points - Array of points
|
|
252
|
+
* @param {string|CanvasGradient} [fillColor] - Fill color
|
|
253
|
+
* @param {string|CanvasGradient} [strokeColor] - Stroke color
|
|
254
|
+
* @param {number} [lineWidth] - Line width
|
|
255
|
+
* @returns {void}
|
|
256
|
+
*/
|
|
257
|
+
static polygon(points, fillColor, strokeColor, lineWidth) {
|
|
258
|
+
if (points.length < 2) return;
|
|
259
|
+
|
|
260
|
+
Painter.lines.beginPath();
|
|
261
|
+
Painter.lines.moveTo(points[0].x, points[0].y);
|
|
262
|
+
|
|
263
|
+
for (let i = 1; i < points.length; i++) {
|
|
264
|
+
Painter.lines.lineTo(points[i].x, points[i].y);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Painter.lines.closePath();
|
|
268
|
+
|
|
269
|
+
if (fillColor) {
|
|
270
|
+
Painter.colors.fill(fillColor);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (strokeColor) {
|
|
274
|
+
Painter.colors.stroke(strokeColor, lineWidth);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { Painter } from "./painter";
|
|
2
|
+
export class PainterText {
|
|
3
|
+
|
|
4
|
+
static font() {
|
|
5
|
+
return Painter.ctx.font;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Set font for text drawing
|
|
10
|
+
* @param {string} font - Font specification (e.g. "24px 'Courier New', monospace")
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
static setFont(font) {
|
|
14
|
+
Painter.ctx.font = font;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set text alignment
|
|
19
|
+
* @param {CanvasTextAlign} align - Text alignment ('left', 'right', 'center', 'start', 'end')
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
static setTextAlign(align) {
|
|
23
|
+
Painter.ctx.textAlign = align;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set text baseline
|
|
28
|
+
* @param {CanvasTextBaseline} baseline - Text baseline ('top', 'hanging', 'middle', 'alphabetic', 'ideographic', 'bottom')
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
static setTextBaseline(baseline) {
|
|
32
|
+
Painter.ctx.textBaseline = baseline;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Draw filled text
|
|
37
|
+
* @param {string} text - Text to draw
|
|
38
|
+
* @param {number} x - X coordinate
|
|
39
|
+
* @param {number} y - Y coordinate
|
|
40
|
+
* @param {string|CanvasGradient} [color] - Text color
|
|
41
|
+
* @param {string} [font] - Font specification
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
static fillText(text, x, y, color, font) {
|
|
45
|
+
if (color) Painter.ctx.fillStyle = color;
|
|
46
|
+
if (font) Painter.ctx.font = font;
|
|
47
|
+
Painter.ctx.fillText(text, x, y);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Draw stroked text
|
|
52
|
+
* @param {string} text - Text to draw
|
|
53
|
+
* @param {number} x - X coordinate
|
|
54
|
+
* @param {number} y - Y coordinate
|
|
55
|
+
* @param {string|CanvasGradient} [color] - Text color
|
|
56
|
+
* @param {number} [lineWidth] - Line width
|
|
57
|
+
* @param {string} [font] - Font specification
|
|
58
|
+
* @returns {void}
|
|
59
|
+
*/
|
|
60
|
+
static strokeText(text, x, y, color, lineWidth, font) {
|
|
61
|
+
if (color) Painter.ctx.strokeStyle = color;
|
|
62
|
+
if (lineWidth !== undefined) Painter.ctx.lineWidth = lineWidth;
|
|
63
|
+
if (font) Painter.ctx.font = font;
|
|
64
|
+
Painter.ctx.strokeText(text, x, y);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Improved text measurement that accounts for baseline adjustments
|
|
69
|
+
* @param {string} text - Text to measure
|
|
70
|
+
* @param {string} [font] - Font specification
|
|
71
|
+
* @param {string} [align="start"] - Text alignment
|
|
72
|
+
* @param {string} [baseline="alphabetic"] - Text baseline
|
|
73
|
+
* @returns {{width: number, height: number, verticalAdjustment: number}} Text dimensions with adjustment
|
|
74
|
+
*/
|
|
75
|
+
static measureTextDimensions(
|
|
76
|
+
text,
|
|
77
|
+
font,
|
|
78
|
+
align = "start",
|
|
79
|
+
baseline = "alphabetic"
|
|
80
|
+
) {
|
|
81
|
+
if (font) Painter.ctx.font = font;
|
|
82
|
+
const metrics = Painter.ctx.measureText(text);
|
|
83
|
+
const width = metrics.width;
|
|
84
|
+
const height =
|
|
85
|
+
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
86
|
+
|
|
87
|
+
// Calculate vertical adjustment based on baseline
|
|
88
|
+
let verticalAdjustment = 0;
|
|
89
|
+
|
|
90
|
+
if (baseline === "middle") {
|
|
91
|
+
// For middle baseline, text is visually slightly higher than the mathematical middle
|
|
92
|
+
// This small correction factor improves visual alignment
|
|
93
|
+
verticalAdjustment = -1.5; // Empirically determined adjustment
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
width: width,
|
|
98
|
+
height: height,
|
|
99
|
+
verticalAdjustment: verticalAdjustment,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Measure text width
|
|
105
|
+
* @param {string} text - Text to measure
|
|
106
|
+
* @returns {number} Text width
|
|
107
|
+
*/
|
|
108
|
+
static measureTextWidth(text, font) {
|
|
109
|
+
if (font) Painter.ctx.font = font;
|
|
110
|
+
return Painter.ctx.measureText(text).width;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Draw filled and stroked text with outline
|
|
115
|
+
* @param {string} text - Text to draw
|
|
116
|
+
* @param {number} x - X coordinate
|
|
117
|
+
* @param {number} y - Y coordinate
|
|
118
|
+
* @param {string} fillColor - Fill color
|
|
119
|
+
* @param {string} strokeColor - Stroke color
|
|
120
|
+
* @param {number} strokeWidth - Stroke width
|
|
121
|
+
* @param {string} [font] - Font specification
|
|
122
|
+
* @returns {void}
|
|
123
|
+
*/
|
|
124
|
+
static outlinedText(text, x, y, fillColor, strokeColor, strokeWidth, font) {
|
|
125
|
+
if (font) Painter.ctx.font = font;
|
|
126
|
+
|
|
127
|
+
// Draw the stroke first
|
|
128
|
+
Painter.ctx.strokeStyle = strokeColor;
|
|
129
|
+
Painter.ctx.lineWidth = strokeWidth;
|
|
130
|
+
Painter.ctx.strokeText(text, x, y);
|
|
131
|
+
|
|
132
|
+
// Then draw the fill
|
|
133
|
+
Painter.ctx.fillStyle = fillColor;
|
|
134
|
+
Painter.ctx.fillText(text, x, y);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Draw text with a maximum width, wrapping to new lines as needed
|
|
139
|
+
* @param {string} text - Text to draw
|
|
140
|
+
* @param {number} x - X coordinate
|
|
141
|
+
* @param {number} y - Y coordinate
|
|
142
|
+
* @param {number} maxWidth - Maximum width before wrapping
|
|
143
|
+
* @param {number} lineHeight - Line height for wrapped text
|
|
144
|
+
* @param {string} [color] - Text color
|
|
145
|
+
* @param {string} [font] - Font specification
|
|
146
|
+
* @returns {number} Total height of drawn text
|
|
147
|
+
*/
|
|
148
|
+
static wrappedText(text, x, y, maxWidth, lineHeight, color, font) {
|
|
149
|
+
if (color) Painter.ctx.fillStyle = color;
|
|
150
|
+
if (font) Painter.ctx.font = font;
|
|
151
|
+
|
|
152
|
+
const words = text.split(" ");
|
|
153
|
+
let line = "";
|
|
154
|
+
let testLine = "";
|
|
155
|
+
let lineCount = 1;
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < words.length; i++) {
|
|
158
|
+
testLine = line + words[i] + " ";
|
|
159
|
+
const metrics = Painter.ctx.measureText(testLine);
|
|
160
|
+
const testWidth = metrics.width;
|
|
161
|
+
|
|
162
|
+
if (testWidth > maxWidth && i > 0) {
|
|
163
|
+
Painter.ctx.fillText(line, x, y);
|
|
164
|
+
line = words[i] + " ";
|
|
165
|
+
y += lineHeight;
|
|
166
|
+
lineCount++;
|
|
167
|
+
} else {
|
|
168
|
+
line = testLine;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Painter.ctx.fillText(line, x, y);
|
|
173
|
+
return lineCount * lineHeight;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Draw text along a path (like an arc)
|
|
178
|
+
* @param {string} text - Text to draw
|
|
179
|
+
* @param {Array} path - Array of points {x, y} defining the path
|
|
180
|
+
* @param {string} [color] - Text color
|
|
181
|
+
* @param {string} [font] - Font specification
|
|
182
|
+
* @param {boolean} [reverse=false] - Whether to draw text in reverse direction
|
|
183
|
+
* @returns {void}
|
|
184
|
+
*/
|
|
185
|
+
static textOnPath(text, path, color, font, reverse = false) {
|
|
186
|
+
if (path.length < 2) return;
|
|
187
|
+
|
|
188
|
+
if (color) Painter.ctx.fillStyle = color;
|
|
189
|
+
if (font) Painter.ctx.font = font;
|
|
190
|
+
|
|
191
|
+
// Get characters and their widths
|
|
192
|
+
const chars = text.split("");
|
|
193
|
+
const charWidths = chars.map((char) => Painter.ctx.measureText(char).width);
|
|
194
|
+
|
|
195
|
+
if (reverse) {
|
|
196
|
+
chars.reverse();
|
|
197
|
+
charWidths.reverse();
|
|
198
|
+
path.reverse();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Calculate total length of the path
|
|
202
|
+
let pathLength = 0;
|
|
203
|
+
for (let i = 1; i < path.length; i++) {
|
|
204
|
+
const dx = path[i].x - path[i - 1].x;
|
|
205
|
+
const dy = path[i].y - path[i - 1].y;
|
|
206
|
+
pathLength += Math.sqrt(dx * dx + dy * dy);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Calculate total width of text
|
|
210
|
+
const textWidth = charWidths.reduce((total, width) => total + width, 0);
|
|
211
|
+
|
|
212
|
+
// Calculate starting offset to center text on path
|
|
213
|
+
let offset = (pathLength - textWidth) / 2;
|
|
214
|
+
if (offset < 0) offset = 0;
|
|
215
|
+
|
|
216
|
+
// Draw each character
|
|
217
|
+
let currentOffset = offset;
|
|
218
|
+
for (let i = 0; i < chars.length; i++) {
|
|
219
|
+
const charWidth = charWidths[i];
|
|
220
|
+
|
|
221
|
+
// Find position and angle on path
|
|
222
|
+
const { x, y, angle } = getPositionOnPath(path, currentOffset);
|
|
223
|
+
|
|
224
|
+
// Draw the character
|
|
225
|
+
Painter.ctx.save();
|
|
226
|
+
Painter.ctx.translate(x, y);
|
|
227
|
+
Painter.ctx.rotate(angle);
|
|
228
|
+
Painter.ctx.fillText(chars[i], 0, 0);
|
|
229
|
+
Painter.ctx.restore();
|
|
230
|
+
|
|
231
|
+
currentOffset += charWidth;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Helper function for textOnPath
|
|
236
|
+
static getPositionOnPath(path, offset) {
|
|
237
|
+
let currentLength = 0;
|
|
238
|
+
|
|
239
|
+
for (let i = 1; i < path.length; i++) {
|
|
240
|
+
const p1 = path[i - 1];
|
|
241
|
+
const p2 = path[i];
|
|
242
|
+
const dx = p2.x - p1.x;
|
|
243
|
+
const dy = p2.y - p1.y;
|
|
244
|
+
const segmentLength = Math.sqrt(dx * dx + dy * dy);
|
|
245
|
+
|
|
246
|
+
if (currentLength + segmentLength >= offset) {
|
|
247
|
+
// Calculate position between p1 and p2
|
|
248
|
+
const t = (offset - currentLength) / segmentLength;
|
|
249
|
+
const x = p1.x + dx * t;
|
|
250
|
+
const y = p1.y + dy * t;
|
|
251
|
+
const angle = Math.atan2(dy, dx);
|
|
252
|
+
|
|
253
|
+
return { x, y, angle };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
currentLength += segmentLength;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// If offset is beyond path length, return last point
|
|
260
|
+
const lastPoint = path[path.length - 1];
|
|
261
|
+
const secondLastPoint = path[path.length - 2];
|
|
262
|
+
const angle = Math.atan2(
|
|
263
|
+
lastPoint.y - secondLastPoint.y,
|
|
264
|
+
lastPoint.x - secondLastPoint.x
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
x: lastPoint.x,
|
|
269
|
+
y: lastPoint.y,
|
|
270
|
+
angle,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParticleEmitter - Configuration and spawning logic for particles
|
|
3
|
+
*
|
|
4
|
+
* Defines spawn position, velocity, lifetime, and appearance properties.
|
|
5
|
+
* Supports continuous emission (rate) and burst spawning.
|
|
6
|
+
*/
|
|
7
|
+
export class ParticleEmitter {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} options - Emitter configuration
|
|
10
|
+
* @param {number} [options.rate=10] - Particles per second (continuous emission)
|
|
11
|
+
* @param {Object} [options.position] - Spawn position { x, y, z }
|
|
12
|
+
* @param {Object} [options.spread] - Position randomization { x, y, z }
|
|
13
|
+
* @param {Object} [options.velocity] - Initial velocity { x, y, z }
|
|
14
|
+
* @param {Object} [options.velocitySpread] - Velocity randomization { x, y, z }
|
|
15
|
+
* @param {Object} [options.lifetime] - Particle lifetime { min, max } in seconds
|
|
16
|
+
* @param {Object} [options.size] - Particle size { min, max }
|
|
17
|
+
* @param {Object} [options.color] - Base color { r, g, b, a }
|
|
18
|
+
* @param {string} [options.shape] - Particle shape: "circle", "square", "triangle"
|
|
19
|
+
*/
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
// Emission rate (particles per second)
|
|
22
|
+
this.rate = options.rate ?? 10;
|
|
23
|
+
|
|
24
|
+
// Spawn position and spread
|
|
25
|
+
this.position = { x: 0, y: 0, z: 0, ...options.position };
|
|
26
|
+
this.spread = { x: 0, y: 0, z: 0, ...options.spread };
|
|
27
|
+
|
|
28
|
+
// Initial velocity and spread
|
|
29
|
+
this.velocity = { x: 0, y: 0, z: 0, ...options.velocity };
|
|
30
|
+
this.velocitySpread = { x: 0, y: 0, z: 0, ...options.velocitySpread };
|
|
31
|
+
|
|
32
|
+
// Particle lifetime range (seconds)
|
|
33
|
+
this.lifetime = { min: 1, max: 2, ...options.lifetime };
|
|
34
|
+
|
|
35
|
+
// Particle size range
|
|
36
|
+
this.size = { min: 1, max: 1, ...options.size };
|
|
37
|
+
|
|
38
|
+
// Base color
|
|
39
|
+
this.color = { r: 255, g: 255, b: 255, a: 1, ...options.color };
|
|
40
|
+
|
|
41
|
+
// Particle shape
|
|
42
|
+
this.shape = options.shape ?? "circle";
|
|
43
|
+
|
|
44
|
+
// Emitter state
|
|
45
|
+
this.active = options.active !== false;
|
|
46
|
+
this._timer = 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Random value in range [-spread, spread].
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
_spread(spread) {
|
|
54
|
+
return (Math.random() - 0.5) * 2 * spread;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Random value in range [min, max].
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
_range(min, max) {
|
|
62
|
+
return min + Math.random() * (max - min);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize a particle with emitter settings.
|
|
67
|
+
* @param {Particle} p - Particle to initialize
|
|
68
|
+
*/
|
|
69
|
+
emit(p) {
|
|
70
|
+
// Position with spread
|
|
71
|
+
p.x = this.position.x + this._spread(this.spread.x);
|
|
72
|
+
p.y = this.position.y + this._spread(this.spread.y);
|
|
73
|
+
p.z = this.position.z + this._spread(this.spread.z);
|
|
74
|
+
|
|
75
|
+
// Velocity with spread
|
|
76
|
+
p.vx = this.velocity.x + this._spread(this.velocitySpread.x);
|
|
77
|
+
p.vy = this.velocity.y + this._spread(this.velocitySpread.y);
|
|
78
|
+
p.vz = this.velocity.z + this._spread(this.velocitySpread.z);
|
|
79
|
+
|
|
80
|
+
// Lifetime
|
|
81
|
+
p.lifetime = this._range(this.lifetime.min, this.lifetime.max);
|
|
82
|
+
p.age = 0;
|
|
83
|
+
p.alive = true;
|
|
84
|
+
|
|
85
|
+
// Size
|
|
86
|
+
p.size = this._range(this.size.min, this.size.max);
|
|
87
|
+
|
|
88
|
+
// Color (copy to avoid shared reference)
|
|
89
|
+
p.color.r = this.color.r;
|
|
90
|
+
p.color.g = this.color.g;
|
|
91
|
+
p.color.b = this.color.b;
|
|
92
|
+
p.color.a = this.color.a;
|
|
93
|
+
|
|
94
|
+
// Shape
|
|
95
|
+
p.shape = this.shape;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Update emitter and return number of particles to spawn.
|
|
100
|
+
* @param {number} dt - Delta time in seconds
|
|
101
|
+
* @returns {number} Number of particles to spawn this frame
|
|
102
|
+
*/
|
|
103
|
+
update(dt) {
|
|
104
|
+
if (!this.active || this.rate <= 0) return 0;
|
|
105
|
+
|
|
106
|
+
this._timer += dt;
|
|
107
|
+
const interval = 1 / this.rate;
|
|
108
|
+
let count = 0;
|
|
109
|
+
|
|
110
|
+
while (this._timer >= interval) {
|
|
111
|
+
this._timer -= interval;
|
|
112
|
+
count++;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return count;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Reset emission timer.
|
|
120
|
+
*/
|
|
121
|
+
reset() {
|
|
122
|
+
this._timer = 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Particle System Module
|
|
3
|
+
*
|
|
4
|
+
* High-performance particle system with optional Camera3D support.
|
|
5
|
+
*
|
|
6
|
+
* @module particle
|
|
7
|
+
*/
|
|
8
|
+
export { Particle } from "./particle.js";
|
|
9
|
+
export { ParticleEmitter } from "./emitter.js";
|
|
10
|
+
export { ParticleSystem } from "./particle-system.js";
|
|
11
|
+
export { Updaters } from "./updaters.js";
|