@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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flare - Luminous burst effect for TDE demo
|
|
3
|
+
*
|
|
4
|
+
* Renders a bright flare using direct canvas drawing.
|
|
5
|
+
* State computed in update(), rendered in draw() via Painter.useCtx().
|
|
6
|
+
*/
|
|
7
|
+
import { GameObject, Painter } from "../../../src/index.js";
|
|
8
|
+
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
// Colors - BRIGHTER
|
|
11
|
+
colorPeak: { r: 255, g: 255, b: 255 },
|
|
12
|
+
colorMid: { r: 255, g: 220, b: 150 },
|
|
13
|
+
colorFade: { r: 255, g: 150, b: 80 },
|
|
14
|
+
|
|
15
|
+
// Animation
|
|
16
|
+
pulseSpeed: 8,
|
|
17
|
+
pulseAmount: 0.2,
|
|
18
|
+
|
|
19
|
+
// Shadow/glow - MORE INTENSE
|
|
20
|
+
glowBlurBase: 100,
|
|
21
|
+
|
|
22
|
+
// Flare size multipliers - LARGER AND DENSER
|
|
23
|
+
outerMultiplier: 2.0,
|
|
24
|
+
innerMultiplier: 0.8,
|
|
25
|
+
coreMultiplier: 0.35,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class Flare extends GameObject {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Game} game - Game instance
|
|
31
|
+
* @param {Object} options
|
|
32
|
+
* @param {number} options.radius - Flare radius
|
|
33
|
+
* @param {Camera3D} options.camera - Camera for projection
|
|
34
|
+
*/
|
|
35
|
+
constructor(game, options = {}) {
|
|
36
|
+
super(game, options);
|
|
37
|
+
|
|
38
|
+
this.radius = options.radius ?? 100;
|
|
39
|
+
this.intensity = 0;
|
|
40
|
+
this.targetIntensity = 0;
|
|
41
|
+
this.pulsePhase = 0;
|
|
42
|
+
this.camera = options.camera;
|
|
43
|
+
|
|
44
|
+
// Screen position (computed in update)
|
|
45
|
+
this.screenX = game.width / 2;
|
|
46
|
+
this.screenY = game.height / 2;
|
|
47
|
+
this.screenScale = 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize flare.
|
|
52
|
+
*/
|
|
53
|
+
init() {
|
|
54
|
+
// Nothing to pre-create
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get current color based on intensity.
|
|
59
|
+
*/
|
|
60
|
+
getCurrentColor() {
|
|
61
|
+
if (this.intensity > 0.7) {
|
|
62
|
+
// Peak - white
|
|
63
|
+
const t = (this.intensity - 0.7) / 0.3;
|
|
64
|
+
return this.lerpColor(CONFIG.colorMid, CONFIG.colorPeak, t);
|
|
65
|
+
} else if (this.intensity > 0.3) {
|
|
66
|
+
// Mid - orange
|
|
67
|
+
const t = (this.intensity - 0.3) / 0.4;
|
|
68
|
+
return this.lerpColor(CONFIG.colorFade, CONFIG.colorMid, t);
|
|
69
|
+
} else {
|
|
70
|
+
// Fade - red
|
|
71
|
+
return CONFIG.colorFade;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Linearly interpolate between two colors.
|
|
77
|
+
*/
|
|
78
|
+
lerpColor(c1, c2, t) {
|
|
79
|
+
return {
|
|
80
|
+
r: Math.round(c1.r + (c2.r - c1.r) * t),
|
|
81
|
+
g: Math.round(c1.g + (c2.g - c1.g) * t),
|
|
82
|
+
b: Math.round(c1.b + (c2.b - c1.b) * t),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get effective intensity with pulse.
|
|
88
|
+
*/
|
|
89
|
+
getEffectiveIntensity() {
|
|
90
|
+
const pulse = Math.sin(this.pulsePhase) * CONFIG.pulseAmount;
|
|
91
|
+
return Math.max(0, Math.min(1, this.intensity + pulse * this.intensity));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Update radius.
|
|
96
|
+
*/
|
|
97
|
+
updateRadius(radius) {
|
|
98
|
+
this.radius = radius;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Trigger the flare.
|
|
103
|
+
*/
|
|
104
|
+
trigger() {
|
|
105
|
+
this.intensity = 1;
|
|
106
|
+
this.targetIntensity = 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Set intensity directly.
|
|
111
|
+
*/
|
|
112
|
+
setIntensity(intensity) {
|
|
113
|
+
this.intensity = Math.max(0, Math.min(1, intensity));
|
|
114
|
+
this.targetIntensity = this.intensity;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Start fading out.
|
|
119
|
+
*/
|
|
120
|
+
fadeOut() {
|
|
121
|
+
this.targetIntensity = 0.3;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reset flare.
|
|
126
|
+
*/
|
|
127
|
+
reset() {
|
|
128
|
+
this.intensity = 0;
|
|
129
|
+
this.targetIntensity = 0;
|
|
130
|
+
this.pulsePhase = 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update - compute render state.
|
|
135
|
+
*/
|
|
136
|
+
update(dt) {
|
|
137
|
+
super.update(dt);
|
|
138
|
+
|
|
139
|
+
// Update pulse
|
|
140
|
+
this.pulsePhase += dt * CONFIG.pulseSpeed;
|
|
141
|
+
|
|
142
|
+
// Smooth intensity transition
|
|
143
|
+
const diff = this.targetIntensity - this.intensity;
|
|
144
|
+
if (Math.abs(diff) > 0.001) {
|
|
145
|
+
this.intensity += diff * dt * 2;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Project (0,0,0) through camera to get screen position
|
|
149
|
+
if (this.camera) {
|
|
150
|
+
const projected = this.camera.project(0, 0, 0);
|
|
151
|
+
this.screenX = this.game.width / 2 + projected.x;
|
|
152
|
+
this.screenY = this.game.height / 2 + projected.y;
|
|
153
|
+
this.screenScale = projected.scale;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Draw the flare.
|
|
159
|
+
* Uses Painter.useCtx() for direct canvas drawing.
|
|
160
|
+
*/
|
|
161
|
+
draw() {
|
|
162
|
+
const effectiveIntensity = this.getEffectiveIntensity();
|
|
163
|
+
if (effectiveIntensity < 0.01) return;
|
|
164
|
+
|
|
165
|
+
const scaledRadius = this.radius * this.screenScale;
|
|
166
|
+
const color = this.getCurrentColor();
|
|
167
|
+
|
|
168
|
+
Painter.useCtx((ctx) => {
|
|
169
|
+
// Outer glow (largest, most transparent)
|
|
170
|
+
const outerRadius =
|
|
171
|
+
scaledRadius * CONFIG.outerMultiplier * effectiveIntensity;
|
|
172
|
+
const outerGradient = ctx.createRadialGradient(
|
|
173
|
+
this.screenX,
|
|
174
|
+
this.screenY,
|
|
175
|
+
0,
|
|
176
|
+
this.screenX,
|
|
177
|
+
this.screenY,
|
|
178
|
+
outerRadius,
|
|
179
|
+
);
|
|
180
|
+
outerGradient.addColorStop(
|
|
181
|
+
0,
|
|
182
|
+
`rgba(${color.r}, ${color.g}, ${color.b}, ${effectiveIntensity * 0.6})`,
|
|
183
|
+
);
|
|
184
|
+
outerGradient.addColorStop(
|
|
185
|
+
0.3,
|
|
186
|
+
`rgba(${color.r}, ${Math.round(color.g * 0.7)}, ${Math.round(color.b * 0.5)}, ${effectiveIntensity * 0.4})`,
|
|
187
|
+
);
|
|
188
|
+
outerGradient.addColorStop(
|
|
189
|
+
0.6,
|
|
190
|
+
`rgba(${color.r}, ${Math.round(color.g * 0.5)}, ${Math.round(color.b * 0.3)}, ${effectiveIntensity * 0.2})`,
|
|
191
|
+
);
|
|
192
|
+
outerGradient.addColorStop(1, "rgba(255, 50, 0, 0)");
|
|
193
|
+
|
|
194
|
+
ctx.fillStyle = outerGradient;
|
|
195
|
+
ctx.shadowColor = `rgba(${color.r}, ${color.g}, ${color.b}, ${effectiveIntensity * 0.9})`;
|
|
196
|
+
ctx.shadowBlur = CONFIG.glowBlurBase * effectiveIntensity;
|
|
197
|
+
ctx.beginPath();
|
|
198
|
+
ctx.arc(this.screenX, this.screenY, outerRadius, 0, Math.PI * 2);
|
|
199
|
+
ctx.fill();
|
|
200
|
+
|
|
201
|
+
// Inner glow (smaller, brighter)
|
|
202
|
+
const innerRadius =
|
|
203
|
+
scaledRadius * CONFIG.innerMultiplier * effectiveIntensity;
|
|
204
|
+
const innerGradient = ctx.createRadialGradient(
|
|
205
|
+
this.screenX,
|
|
206
|
+
this.screenY,
|
|
207
|
+
0,
|
|
208
|
+
this.screenX,
|
|
209
|
+
this.screenY,
|
|
210
|
+
innerRadius,
|
|
211
|
+
);
|
|
212
|
+
innerGradient.addColorStop(
|
|
213
|
+
0,
|
|
214
|
+
`rgba(${color.r}, ${color.g}, ${color.b}, ${effectiveIntensity})`,
|
|
215
|
+
);
|
|
216
|
+
innerGradient.addColorStop(
|
|
217
|
+
0.5,
|
|
218
|
+
`rgba(${color.r}, ${color.g}, ${color.b}, ${effectiveIntensity * 0.6})`,
|
|
219
|
+
);
|
|
220
|
+
innerGradient.addColorStop(1, "rgba(255, 150, 50, 0)");
|
|
221
|
+
|
|
222
|
+
ctx.shadowBlur = CONFIG.glowBlurBase * 0.6 * effectiveIntensity;
|
|
223
|
+
ctx.fillStyle = innerGradient;
|
|
224
|
+
ctx.beginPath();
|
|
225
|
+
ctx.arc(this.screenX, this.screenY, innerRadius, 0, Math.PI * 2);
|
|
226
|
+
ctx.fill();
|
|
227
|
+
|
|
228
|
+
// Core (brightest center)
|
|
229
|
+
const coreRadius =
|
|
230
|
+
scaledRadius * CONFIG.coreMultiplier * effectiveIntensity;
|
|
231
|
+
ctx.shadowColor = "rgba(255, 255, 255, 1)";
|
|
232
|
+
ctx.shadowBlur = 25 * effectiveIntensity;
|
|
233
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${effectiveIntensity})`;
|
|
234
|
+
ctx.beginPath();
|
|
235
|
+
ctx.arc(this.screenX, this.screenY, coreRadius, 0, Math.PI * 2);
|
|
236
|
+
ctx.fill();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tidal Disruption Event - Cinematic Visualization
|
|
3
|
+
*
|
|
4
|
+
* A star torn apart by a supermassive black hole's tidal forces.
|
|
5
|
+
* Uses ParticleSystem for debris, Shapes for black hole/flare,
|
|
6
|
+
* and StateMachine for phase management.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
Game,
|
|
10
|
+
Camera3D,
|
|
11
|
+
StateMachine,
|
|
12
|
+
Painter,
|
|
13
|
+
Text,
|
|
14
|
+
Scene,
|
|
15
|
+
} from "../../../src/index.js";
|
|
16
|
+
|
|
17
|
+
import { BlackHole } from "./blackhole.obj.js";
|
|
18
|
+
import { Star } from "./star.obj.js";
|
|
19
|
+
import { DebrisManager } from "./debris.obj.js";
|
|
20
|
+
import { Flare } from "./flare.obj.js";
|
|
21
|
+
import { StarField } from "../blackhole/starfield.obj.js";
|
|
22
|
+
|
|
23
|
+
// Configuration
|
|
24
|
+
const CONFIG = {
|
|
25
|
+
// Sizing (as fraction of screen)
|
|
26
|
+
bhRadiusRatio: 0.08,
|
|
27
|
+
starRadiusRatio: 0.05, // Smaller base size - distance scaling makes it look bigger when far
|
|
28
|
+
tidalRadiusRatio: 0.35,
|
|
29
|
+
|
|
30
|
+
// Star - starts FAR, falls IN
|
|
31
|
+
starParticleCount: 8000, // Much denser for fluid look
|
|
32
|
+
starStartDistance: 0.6, // Matches apoapsisRatio - start at farthest point
|
|
33
|
+
|
|
34
|
+
// Camera
|
|
35
|
+
cameraDistance: 0.7,
|
|
36
|
+
autoRotateSpeed: 0.05, // Slower rotation to admire the view
|
|
37
|
+
|
|
38
|
+
// Phase durations (seconds) - FASTER, MORE DRAMATIC
|
|
39
|
+
phases: {
|
|
40
|
+
approach: 10.0, // Longer approach to build tension
|
|
41
|
+
stretch: 3.0, // Orbital stretching
|
|
42
|
+
disrupt: 5.0, // Extended disruption - particles spiral off
|
|
43
|
+
accrete: 6.0, // Longer accretion for more drama
|
|
44
|
+
flare: 2.0, // Bright flare
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Visual
|
|
48
|
+
backgroundColor: "#020206",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
class TDEDemo extends Game {
|
|
52
|
+
constructor(canvas) {
|
|
53
|
+
super(canvas);
|
|
54
|
+
this.backgroundColor = CONFIG.backgroundColor;
|
|
55
|
+
this.enableFluidSize();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
init() {
|
|
59
|
+
super.init();
|
|
60
|
+
this.time = 0;
|
|
61
|
+
|
|
62
|
+
// Calculate scaled sizes
|
|
63
|
+
this.updateScaledSizes();
|
|
64
|
+
|
|
65
|
+
// Setup Camera3D - lower perspective = stronger depth effect
|
|
66
|
+
this.camera = new Camera3D({
|
|
67
|
+
rotationX: 1.2, // ~70 degrees - lower angle for Interstellar look
|
|
68
|
+
rotationY: 0,
|
|
69
|
+
perspective: this.baseScale * CONFIG.cameraDistance,
|
|
70
|
+
autoRotate: true,
|
|
71
|
+
autoRotateSpeed: CONFIG.autoRotateSpeed,
|
|
72
|
+
});
|
|
73
|
+
this.camera.enableMouseControl(this.canvas);
|
|
74
|
+
|
|
75
|
+
// Create Starfield (background) - LOTS OF STARS
|
|
76
|
+
this.starfield = new StarField(this, {
|
|
77
|
+
camera: this.camera,
|
|
78
|
+
starCount: 5000,
|
|
79
|
+
});
|
|
80
|
+
this.starfield.init();
|
|
81
|
+
this.starfield.zIndex = 0;
|
|
82
|
+
this.pipeline.add(this.starfield);
|
|
83
|
+
|
|
84
|
+
// Create Flare (renders as background glow)
|
|
85
|
+
this.flare = new Flare(this, {
|
|
86
|
+
radius: this.bhRadius * 4,
|
|
87
|
+
camera: this.camera,
|
|
88
|
+
});
|
|
89
|
+
this.flare.init();
|
|
90
|
+
this.flare.zIndex = 10;
|
|
91
|
+
this.pipeline.add(this.flare);
|
|
92
|
+
|
|
93
|
+
// Create a Scene to properly manage z-ordering of star and black hole
|
|
94
|
+
this.mainScene = new Scene(this, { sortByZIndex: true });
|
|
95
|
+
this.mainScene.zIndex = 30;
|
|
96
|
+
this.pipeline.add(this.mainScene);
|
|
97
|
+
|
|
98
|
+
// Create Black Hole (event horizon at center)
|
|
99
|
+
this.blackHole = new BlackHole(this, {
|
|
100
|
+
radius: this.bhRadius,
|
|
101
|
+
glowIntensity: 0,
|
|
102
|
+
camera: this.camera,
|
|
103
|
+
});
|
|
104
|
+
this.blackHole.init();
|
|
105
|
+
this.blackHole.zIndex = 50; // Middle layer
|
|
106
|
+
this.mainScene.add(this.blackHole);
|
|
107
|
+
|
|
108
|
+
// Create Star (approaching from outside)
|
|
109
|
+
// zIndex updated dynamically: behind BH when z > 0, in front when z < 0
|
|
110
|
+
this.star = new Star(this, {
|
|
111
|
+
camera: this.camera,
|
|
112
|
+
radius: this.starRadius,
|
|
113
|
+
particleCount: CONFIG.starParticleCount,
|
|
114
|
+
baseScale: this.baseScale,
|
|
115
|
+
startDistance: this.baseScale * CONFIG.starStartDistance,
|
|
116
|
+
});
|
|
117
|
+
this.star.init();
|
|
118
|
+
this.star.zIndex = 25; // Start behind black hole
|
|
119
|
+
this.mainScene.add(this.star);
|
|
120
|
+
|
|
121
|
+
// Create Debris Manager (accretion disk particles with lensing)
|
|
122
|
+
this.debrisManager = new DebrisManager(this, {
|
|
123
|
+
camera: this.camera,
|
|
124
|
+
bhRadius: this.bhRadius,
|
|
125
|
+
baseScale: this.baseScale,
|
|
126
|
+
});
|
|
127
|
+
this.debrisManager.init();
|
|
128
|
+
this.debrisManager.zIndex = 75; // On top of black hole
|
|
129
|
+
this.mainScene.add(this.debrisManager);
|
|
130
|
+
|
|
131
|
+
// Initialize state machine
|
|
132
|
+
this.initStateMachine();
|
|
133
|
+
|
|
134
|
+
// Click to restart
|
|
135
|
+
this.canvas.addEventListener("click", () => {
|
|
136
|
+
if (this.fsm.is("stable")) {
|
|
137
|
+
this.restart();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Info label (always on top)
|
|
142
|
+
this.infoLabel = new Text(this, "", {
|
|
143
|
+
x: 15,
|
|
144
|
+
y: this.height - 30,
|
|
145
|
+
color: "#888",
|
|
146
|
+
font: "11px monospace",
|
|
147
|
+
});
|
|
148
|
+
this.infoLabel.zIndex = 100;
|
|
149
|
+
this.pipeline.add(this.infoLabel);
|
|
150
|
+
|
|
151
|
+
// Flash overlay for dramatic collapse moment
|
|
152
|
+
this.flashIntensity = 0;
|
|
153
|
+
this.flashDecay = 3.0; // How fast flash fades (higher = faster)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
initStateMachine() {
|
|
157
|
+
this.fsm = StateMachine.fromSequence(
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
name: "approach",
|
|
161
|
+
duration: CONFIG.phases.approach,
|
|
162
|
+
enter: () => this.onApproachEnter(),
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "stretch",
|
|
166
|
+
duration: CONFIG.phases.stretch,
|
|
167
|
+
enter: () => this.onStretchEnter(),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "disrupt",
|
|
171
|
+
duration: CONFIG.phases.disrupt,
|
|
172
|
+
enter: () => this.onDisruptEnter(),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "accrete",
|
|
176
|
+
duration: CONFIG.phases.accrete,
|
|
177
|
+
enter: () => this.onAccreteEnter(),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "flare",
|
|
181
|
+
duration: CONFIG.phases.flare,
|
|
182
|
+
enter: () => this.onFlareEnter(),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "stable",
|
|
186
|
+
duration: Infinity,
|
|
187
|
+
enter: () => this.onStableEnter(),
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
{ context: this },
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Phase callbacks
|
|
195
|
+
onApproachEnter() {
|
|
196
|
+
this.star.startApproach();
|
|
197
|
+
// No pre-made disk - it forms organically from star particles
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
onStretchEnter() {
|
|
201
|
+
this.star.startStretch();
|
|
202
|
+
// Glow controlled by awakening - just set target intensity
|
|
203
|
+
this.blackHole.setGlowIntensity(0.5);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
onDisruptEnter() {
|
|
207
|
+
this.star.startDisrupt();
|
|
208
|
+
// Glow controlled by awakening - just set target intensity
|
|
209
|
+
this.blackHole.setGlowIntensity(0.8);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
onAccreteEnter() {
|
|
213
|
+
// Flash white when star collapses!
|
|
214
|
+
this.flashIntensity = 1.0;
|
|
215
|
+
|
|
216
|
+
// Transfer remaining star particles to debris - these form the final disk
|
|
217
|
+
const debris = this.star.releaseAllParticles();
|
|
218
|
+
this.debrisManager.addDebris(debris);
|
|
219
|
+
|
|
220
|
+
// FLARE TRIGGERS IMMEDIATELY ON COLLISION - not waiting for flare phase
|
|
221
|
+
this.flare.trigger();
|
|
222
|
+
this.blackHole.setGlowIntensity(1.0);
|
|
223
|
+
|
|
224
|
+
// Activate relativistic jets during accretion (only if BH is awakened enough)
|
|
225
|
+
this.blackHole.setJetsActive(true);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onFlareEnter() {
|
|
229
|
+
// Flare already triggered in accrete - this phase is just for decay
|
|
230
|
+
// Keep intensity high
|
|
231
|
+
this.flare.setIntensity(1.0);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
onStableEnter() {
|
|
235
|
+
this.blackHole.setGlowIntensity(1.0); // Keep full glow after collision
|
|
236
|
+
this.blackHole.setJetsActive(false); // Turn off jets when stable
|
|
237
|
+
this.flare.fadeOut();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
restart() {
|
|
241
|
+
this.star.reset();
|
|
242
|
+
this.debrisManager.clear();
|
|
243
|
+
this.flare.reset();
|
|
244
|
+
this.flashIntensity = 0;
|
|
245
|
+
this.blackHole.setGlowIntensity(0);
|
|
246
|
+
this.blackHole.resetMass();
|
|
247
|
+
this.fsm.setState("approach");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
updateScaledSizes() {
|
|
251
|
+
this.baseScale = Math.min(this.width, this.height);
|
|
252
|
+
this.bhRadius = this.baseScale * CONFIG.bhRadiusRatio;
|
|
253
|
+
this.starRadius = this.baseScale * CONFIG.starRadiusRatio;
|
|
254
|
+
this.tidalRadius = this.baseScale * CONFIG.tidalRadiusRatio;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
onResize() {
|
|
258
|
+
this.updateScaledSizes();
|
|
259
|
+
|
|
260
|
+
if (this.camera) {
|
|
261
|
+
this.camera.perspective = this.baseScale * CONFIG.cameraDistance;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.blackHole) {
|
|
265
|
+
this.blackHole.updateRadius(this.bhRadius);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.star) {
|
|
269
|
+
this.star.updateSizing(this.starRadius, this.baseScale);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (this.debrisManager) {
|
|
273
|
+
this.debrisManager.updateSizing(this.bhRadius, this.baseScale);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (this.flare) {
|
|
277
|
+
this.flare.updateRadius(this.bhRadius * 4);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (this.infoLabel) {
|
|
281
|
+
this.infoLabel.y = this.height - 30;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
update(dt) {
|
|
286
|
+
super.update(dt);
|
|
287
|
+
this.time += dt;
|
|
288
|
+
|
|
289
|
+
// Update camera
|
|
290
|
+
this.camera.update(dt);
|
|
291
|
+
|
|
292
|
+
// Update state machine
|
|
293
|
+
this.fsm.update(dt);
|
|
294
|
+
|
|
295
|
+
// Update components based on current phase
|
|
296
|
+
const state = this.fsm.state;
|
|
297
|
+
const progress = this.fsm.progress;
|
|
298
|
+
|
|
299
|
+
// Black hole feeds on debris during ALL phases (awakens progressively)
|
|
300
|
+
const accretionRate = this.debrisManager.getAccretionRate();
|
|
301
|
+
if (accretionRate > 0) {
|
|
302
|
+
this.blackHole.addMass(accretionRate * dt * 0.01);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (state === "approach") {
|
|
306
|
+
this.star.updateApproach(dt, progress);
|
|
307
|
+
// ORGANIC STREAMING FROM THE VERY START
|
|
308
|
+
// As star approaches, tidal forces gradually pull matter off
|
|
309
|
+
// Drift starts immediately but very weak, builds up over time
|
|
310
|
+
const driftStrength = progress * progress; // Quadratic buildup
|
|
311
|
+
this.star.applyParticleDrift(dt, driftStrength);
|
|
312
|
+
|
|
313
|
+
// Start releasing particles from 10% onward - very slow at first
|
|
314
|
+
if (progress > 0.1) {
|
|
315
|
+
const releaseProgress = (progress - 0.1) / 0.9;
|
|
316
|
+
// Mild tidal stretch that builds up
|
|
317
|
+
this.star.applyTidalStretch(releaseProgress * 0.2);
|
|
318
|
+
// Release particles - slow at first, faster as star approaches
|
|
319
|
+
const released = this.star.releaseParticles(releaseProgress * 0.25);
|
|
320
|
+
if (released.length > 0) {
|
|
321
|
+
this.debrisManager.addDebris(released);
|
|
322
|
+
// BH GROWS as star releases particles
|
|
323
|
+
this.blackHole.addMass(released.length * 0.002);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} else if (state === "stretch") {
|
|
327
|
+
this.star.updateStretch(dt, progress, { x: 0, y: 0, z: 0 });
|
|
328
|
+
// DRIFT particles toward BH - stronger during stretch
|
|
329
|
+
this.star.applyParticleDrift(dt, 1.0 + progress);
|
|
330
|
+
// Continue streaming during stretch - more aggressively
|
|
331
|
+
const released = this.star.releaseParticles(0.3 + progress * 0.4);
|
|
332
|
+
if (released.length > 0) {
|
|
333
|
+
this.debrisManager.addDebris(released);
|
|
334
|
+
// BH GROWS as star releases particles
|
|
335
|
+
this.blackHole.addMass(released.length * 0.003);
|
|
336
|
+
}
|
|
337
|
+
} else if (state === "disrupt") {
|
|
338
|
+
this.star.updateDisrupt(dt, progress);
|
|
339
|
+
// DRIFT particles toward BH - maximum during disruption
|
|
340
|
+
this.star.applyParticleDrift(dt, 2.0 + progress);
|
|
341
|
+
// Continue releasing particles during disruption
|
|
342
|
+
const released = this.star.releaseParticles(0.7 + progress * 0.3);
|
|
343
|
+
if (released.length > 0) {
|
|
344
|
+
this.debrisManager.addDebris(released);
|
|
345
|
+
// BH GROWS as star releases particles
|
|
346
|
+
this.blackHole.addMass(released.length * 0.004);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// COLLISION CHECK - trigger accrete when star is close to BH
|
|
350
|
+
const starDist = Math.sqrt(
|
|
351
|
+
this.star.centerX ** 2 +
|
|
352
|
+
this.star.centerY ** 2 +
|
|
353
|
+
this.star.centerZ ** 2,
|
|
354
|
+
);
|
|
355
|
+
if (starDist < this.bhRadius * 2) {
|
|
356
|
+
// Star has reached the black hole - trigger accrete immediately
|
|
357
|
+
this.fsm.setState("accrete");
|
|
358
|
+
}
|
|
359
|
+
} else if (state === "flare") {
|
|
360
|
+
this.flare.setIntensity(1 - progress * 0.5);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Update star z-ordering based on camera-space z position
|
|
364
|
+
// Star behind black hole (z > 0) = lower zIndex, in front (z < 0) = higher zIndex
|
|
365
|
+
// Use hysteresis to prevent jittering when star is near the z=0 plane
|
|
366
|
+
if (this.star && this.star.visible) {
|
|
367
|
+
const starCameraZ = this.star.getCameraZ();
|
|
368
|
+
const hysteresis = this.bhRadius * 0.5; // Threshold to prevent jittering
|
|
369
|
+
|
|
370
|
+
let newZIndex = this.star.zIndex;
|
|
371
|
+
if (starCameraZ > hysteresis) {
|
|
372
|
+
// Clearly behind BH
|
|
373
|
+
newZIndex = 25;
|
|
374
|
+
} else if (starCameraZ < -hysteresis) {
|
|
375
|
+
// Clearly in front of BH
|
|
376
|
+
newZIndex = 75;
|
|
377
|
+
}
|
|
378
|
+
// If within hysteresis range, keep current zIndex to avoid flickering
|
|
379
|
+
|
|
380
|
+
if (this.star.zIndex !== newZIndex) {
|
|
381
|
+
this.star.zIndex = newZIndex;
|
|
382
|
+
// Mark z-order as dirty for re-sorting
|
|
383
|
+
this.mainScene._collection._zOrderDirty = true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Decay flash intensity
|
|
388
|
+
if (this.flashIntensity > 0) {
|
|
389
|
+
this.flashIntensity = Math.max(
|
|
390
|
+
0,
|
|
391
|
+
this.flashIntensity - dt * this.flashDecay,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Update info label
|
|
396
|
+
this.updateInfoLabel();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
updateInfoLabel() {
|
|
400
|
+
const state = this.fsm.state;
|
|
401
|
+
const progress = this.fsm.progress;
|
|
402
|
+
|
|
403
|
+
const stateLabels = {
|
|
404
|
+
approach: "Star Approaching",
|
|
405
|
+
stretch: "Tidal Stretching",
|
|
406
|
+
disrupt: "Stellar Disruption",
|
|
407
|
+
accrete: "Debris Accretion",
|
|
408
|
+
flare: "Luminous Flare",
|
|
409
|
+
stable: "Stable Disk",
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const stateColors = {
|
|
413
|
+
approach: "#88f",
|
|
414
|
+
stretch: "#fa8",
|
|
415
|
+
disrupt: "#f88",
|
|
416
|
+
accrete: "#ff8",
|
|
417
|
+
flare: "#fff",
|
|
418
|
+
stable: "#8a8",
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
this.infoLabel.color = stateColors[state] || "#888";
|
|
422
|
+
|
|
423
|
+
if (state === "stable") {
|
|
424
|
+
this.infoLabel.text = `${stateLabels[state]} — click to restart`;
|
|
425
|
+
} else {
|
|
426
|
+
this.infoLabel.text = `${stateLabels[state]}: ${Math.round(progress * 100)}%`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
render() {
|
|
431
|
+
// Pipeline already sorts by zIndex via ZOrderedCollection
|
|
432
|
+
super.render();
|
|
433
|
+
|
|
434
|
+
// Draw flash overlay on top of everything using direct canvas
|
|
435
|
+
if (this.flashIntensity > 0) {
|
|
436
|
+
Painter.useCtx((ctx) => {
|
|
437
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${this.flashIntensity})`;
|
|
438
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
window.addEventListener("load", () => {
|
|
445
|
+
const canvas = document.getElementById("game");
|
|
446
|
+
const demo = new TDEDemo(canvas);
|
|
447
|
+
demo.start();
|
|
448
|
+
});
|