@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,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlackHole - Event horizon and photon ring for TDE demo
|
|
3
|
+
*
|
|
4
|
+
* Renders the black hole using direct canvas drawing.
|
|
5
|
+
* State computed in update(), rendered in draw() via Painter.useCtx().
|
|
6
|
+
*/
|
|
7
|
+
import { GameObject, Painter, Easing } from "../../../src/index.js";
|
|
8
|
+
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
// Photon ring
|
|
11
|
+
photonRingRatio: 1.8,
|
|
12
|
+
photonRingGlowBase: 50,
|
|
13
|
+
|
|
14
|
+
// Growth - starts SMALL, grows as it feeds
|
|
15
|
+
initialMass: 0.1, // Start at 40% size
|
|
16
|
+
maxMass: 1.1, // Can grow up to 150% of base size
|
|
17
|
+
growthRate: 0.03, // How fast it grows per unit of mass added
|
|
18
|
+
|
|
19
|
+
// Dormant state - starts DARK, wakes up as it feeds
|
|
20
|
+
dormantGlow: 0.0, // NO glow when dormant (starts black)
|
|
21
|
+
dormantRingAlpha: 0.0, // NO photon ring when dormant
|
|
22
|
+
|
|
23
|
+
// Colors
|
|
24
|
+
glowColorInner: [255, 220, 150],
|
|
25
|
+
glowColorMid: [255, 150, 50],
|
|
26
|
+
glowColorOuter: [255, 50, 0],
|
|
27
|
+
|
|
28
|
+
// RELATIVISTIC JETS - fountain of particles from poles
|
|
29
|
+
jets: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
maxParticles: 1000,
|
|
32
|
+
spawnRate: 15, // particles per second when active
|
|
33
|
+
velocity: 800, // upward speed
|
|
34
|
+
spread: 40, // horizontal spread
|
|
35
|
+
lifetime: 2.0, // seconds
|
|
36
|
+
size: { min: 1, max: 2 },
|
|
37
|
+
color: { r: 250, g: 255, b: 255 }, // Blue-white
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class BlackHole extends GameObject {
|
|
42
|
+
/**
|
|
43
|
+
* @param {Game} game - Game instance
|
|
44
|
+
* @param {Object} options
|
|
45
|
+
* @param {number} options.radius - Event horizon radius
|
|
46
|
+
* @param {Camera3D} options.camera - Camera for projection
|
|
47
|
+
*/
|
|
48
|
+
constructor(game, options = {}) {
|
|
49
|
+
super(game, options);
|
|
50
|
+
|
|
51
|
+
this.baseRadius = options.radius ?? 50;
|
|
52
|
+
this.radius = this.baseRadius * Math.sqrt(CONFIG.initialMass); // Start smaller
|
|
53
|
+
this.glowIntensity = options.glowIntensity ?? 0;
|
|
54
|
+
this.accretionRate = 0;
|
|
55
|
+
this.camera = options.camera;
|
|
56
|
+
|
|
57
|
+
// Mass tracking for growth
|
|
58
|
+
this.mass = CONFIG.initialMass;
|
|
59
|
+
this.targetMass = CONFIG.initialMass;
|
|
60
|
+
|
|
61
|
+
// Awakening state - BH starts dormant (black), wakes up as it feeds
|
|
62
|
+
this.totalConsumed = 0;
|
|
63
|
+
this.awakeningLevel = 0; // 0 = dormant (pure black), 1 = fully awake (full glow/ring)
|
|
64
|
+
this.feedingPulse = 0; // Temporary glow boost when consuming particles
|
|
65
|
+
|
|
66
|
+
// Screen position (computed in update, used in draw)
|
|
67
|
+
this.screenX = game.width / 2;
|
|
68
|
+
this.screenY = game.height / 2;
|
|
69
|
+
this.screenScale = 1;
|
|
70
|
+
|
|
71
|
+
// Relativistic jets - particles shooting from poles
|
|
72
|
+
this.jetParticles = [];
|
|
73
|
+
this.jetActive = false;
|
|
74
|
+
this.jetSpawnAccum = 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Initialize.
|
|
79
|
+
*/
|
|
80
|
+
init() {
|
|
81
|
+
this.jetParticles = [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Activate jets (during accretion phase).
|
|
86
|
+
*/
|
|
87
|
+
setJetsActive(active) {
|
|
88
|
+
this.jetActive = active;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Update sizing when window resizes.
|
|
93
|
+
*/
|
|
94
|
+
updateRadius(radius) {
|
|
95
|
+
this.baseRadius = radius;
|
|
96
|
+
this.radius = this.baseRadius * Math.sqrt(this.mass);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set glow intensity (0-1).
|
|
101
|
+
*/
|
|
102
|
+
setGlowIntensity(intensity) {
|
|
103
|
+
this.glowIntensity = Math.max(0, Math.min(1, intensity));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Set accretion rate for color/intensity effects.
|
|
108
|
+
*/
|
|
109
|
+
setAccretionRate(rate) {
|
|
110
|
+
this.accretionRate = rate;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Add mass from accreted particles.
|
|
115
|
+
* Black hole grows and "wakes up" as it feeds.
|
|
116
|
+
*/
|
|
117
|
+
addMass(amount) {
|
|
118
|
+
// Grow SLOWLY over time
|
|
119
|
+
this.targetMass = Math.min(
|
|
120
|
+
CONFIG.maxMass,
|
|
121
|
+
this.targetMass + amount * CONFIG.growthRate * 0.5,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Track total consumed for awakening effect
|
|
125
|
+
this.totalConsumed += amount;
|
|
126
|
+
|
|
127
|
+
// Awakening: glow and ring intensity increase as BH feeds
|
|
128
|
+
// Slower awakening for more gradual effect
|
|
129
|
+
const awakeningProgress = Math.min(1, this.totalConsumed * 0.15);
|
|
130
|
+
this.awakeningLevel = Math.max(this.awakeningLevel, awakeningProgress);
|
|
131
|
+
|
|
132
|
+
// FEEDING PULSE - temporary glow boost when consuming
|
|
133
|
+
this.feedingPulse = Math.min(1, this.feedingPulse + amount * 0.3);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reset mass to initial value (dormant state).
|
|
138
|
+
*/
|
|
139
|
+
resetMass() {
|
|
140
|
+
this.mass = CONFIG.initialMass;
|
|
141
|
+
this.targetMass = CONFIG.initialMass;
|
|
142
|
+
this.radius = this.baseRadius * Math.sqrt(CONFIG.initialMass);
|
|
143
|
+
this.jetParticles = [];
|
|
144
|
+
this.jetActive = false;
|
|
145
|
+
this.totalConsumed = 0;
|
|
146
|
+
this.awakeningLevel = 0; // Starts fully dormant
|
|
147
|
+
this.feedingPulse = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Update - compute render state and jets.
|
|
152
|
+
*/
|
|
153
|
+
update(dt) {
|
|
154
|
+
super.update(dt);
|
|
155
|
+
|
|
156
|
+
// Smoothly grow mass toward target (SLOW growth)
|
|
157
|
+
if (this.mass !== this.targetMass) {
|
|
158
|
+
this.mass = Easing.lerp(this.mass, this.targetMass, 0.02);
|
|
159
|
+
this.radius = this.baseRadius * Math.sqrt(this.mass);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Decay feeding pulse over time (creates pulsing glow when feeding)
|
|
163
|
+
if (this.feedingPulse > 0) {
|
|
164
|
+
this.feedingPulse = Math.max(0, this.feedingPulse - dt * 2);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Project (0,0,0) through camera to get screen position
|
|
168
|
+
if (this.camera) {
|
|
169
|
+
const projected = this.camera.project(0, 0, 0);
|
|
170
|
+
this.screenX = this.game.width / 2 + projected.x;
|
|
171
|
+
this.screenY = this.game.height / 2 + projected.y;
|
|
172
|
+
this.screenScale = projected.scale;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update relativistic jets
|
|
176
|
+
this.updateJets(dt);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update jet particles - spawn new ones and move existing.
|
|
181
|
+
*/
|
|
182
|
+
updateJets(dt) {
|
|
183
|
+
const cfg = CONFIG.jets;
|
|
184
|
+
if (!cfg.enabled) return;
|
|
185
|
+
|
|
186
|
+
// Spawn new jet particles when active
|
|
187
|
+
if (this.jetActive && this.jetParticles.length < cfg.maxParticles) {
|
|
188
|
+
this.jetSpawnAccum += dt * cfg.spawnRate;
|
|
189
|
+
while (
|
|
190
|
+
this.jetSpawnAccum >= 1 &&
|
|
191
|
+
this.jetParticles.length < cfg.maxParticles
|
|
192
|
+
) {
|
|
193
|
+
this.jetSpawnAccum -= 1;
|
|
194
|
+
|
|
195
|
+
// Spawn from north pole (shooting upward)
|
|
196
|
+
const spread = (Math.random() - 0.5) * cfg.spread;
|
|
197
|
+
const spreadZ = (Math.random() - 0.5) * cfg.spread;
|
|
198
|
+
|
|
199
|
+
this.jetParticles.push({
|
|
200
|
+
x: spread,
|
|
201
|
+
y: -this.radius * 0.5, // Start just above BH
|
|
202
|
+
z: spreadZ,
|
|
203
|
+
vx: spread * 2,
|
|
204
|
+
vy: -cfg.velocity * (0.8 + Math.random() * 0.4), // Upward (negative y)
|
|
205
|
+
vz: spreadZ * 2,
|
|
206
|
+
age: 0,
|
|
207
|
+
lifetime: cfg.lifetime * (0.8 + Math.random() * 0.4),
|
|
208
|
+
size: cfg.size.min + Math.random() * (cfg.size.max - cfg.size.min),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Update existing particles
|
|
214
|
+
for (let i = this.jetParticles.length - 1; i >= 0; i--) {
|
|
215
|
+
const p = this.jetParticles[i];
|
|
216
|
+
p.age += dt;
|
|
217
|
+
|
|
218
|
+
if (p.age >= p.lifetime) {
|
|
219
|
+
this.jetParticles.splice(i, 1);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Move particle
|
|
224
|
+
p.x += p.vx * dt;
|
|
225
|
+
p.y += p.vy * dt;
|
|
226
|
+
p.z += p.vz * dt;
|
|
227
|
+
|
|
228
|
+
// Slight deceleration
|
|
229
|
+
p.vy *= 0.995;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Draw the black hole with photon ring glow and jets.
|
|
235
|
+
* Uses Painter.useCtx() for direct canvas drawing.
|
|
236
|
+
* BH starts dormant (pure black) and "wakes up" as it feeds.
|
|
237
|
+
*/
|
|
238
|
+
render() {
|
|
239
|
+
super.render();
|
|
240
|
+
const scaledRadius = this.radius * this.screenScale;
|
|
241
|
+
|
|
242
|
+
// Awakening-based intensity - BH starts black, glows as it feeds
|
|
243
|
+
// awakeningLevel: 0 = dormant (black), 1 = fully awake
|
|
244
|
+
const awakeningFactor = Easing.easeOutQuad(this.awakeningLevel);
|
|
245
|
+
|
|
246
|
+
// FEEDING PULSE adds extra glow when actively consuming
|
|
247
|
+
const pulseBoost = this.feedingPulse * 0.5;
|
|
248
|
+
|
|
249
|
+
// Glow intensity scales with awakening + activity + feeding pulse
|
|
250
|
+
const activeIntensity = this.glowIntensity * awakeningFactor + pulseBoost;
|
|
251
|
+
const totalIntensity = Math.min(1, activeIntensity);
|
|
252
|
+
|
|
253
|
+
// Ring only appears as BH awakens (also boosted by feeding)
|
|
254
|
+
const ringAlpha =
|
|
255
|
+
awakeningFactor * Math.max(0.3, this.glowIntensity) + pulseBoost * 0.3;
|
|
256
|
+
|
|
257
|
+
// Screen center for projections
|
|
258
|
+
const cx = this.game.width / 2;
|
|
259
|
+
const cy = this.game.height / 2;
|
|
260
|
+
|
|
261
|
+
Painter.useCtx((ctx) => {
|
|
262
|
+
// Draw jet particles FIRST (behind black hole)
|
|
263
|
+
if (this.jetParticles.length > 0) {
|
|
264
|
+
const cfg = CONFIG.jets;
|
|
265
|
+
ctx.shadowBlur = 3;
|
|
266
|
+
ctx.shadowColor = `rgba(${cfg.color.r}, ${cfg.color.g}, ${cfg.color.b}, 0.5)`;
|
|
267
|
+
|
|
268
|
+
for (const p of this.jetParticles) {
|
|
269
|
+
// Project through camera - absolute screen position
|
|
270
|
+
const projected = this.camera.project(p.x, p.y, p.z);
|
|
271
|
+
const jetScreenX = cx + projected.x;
|
|
272
|
+
const jetScreenY = cy + projected.y;
|
|
273
|
+
|
|
274
|
+
// Skip if behind camera
|
|
275
|
+
if (projected.scale <= 0) continue;
|
|
276
|
+
|
|
277
|
+
// Fade with age
|
|
278
|
+
const lifeRatio = 1 - p.age / p.lifetime;
|
|
279
|
+
const alpha = lifeRatio * 0.8;
|
|
280
|
+
|
|
281
|
+
ctx.fillStyle = `rgba(${cfg.color.r}, ${cfg.color.g}, ${cfg.color.b}, ${alpha})`;
|
|
282
|
+
ctx.beginPath();
|
|
283
|
+
ctx.arc(
|
|
284
|
+
jetScreenX,
|
|
285
|
+
jetScreenY,
|
|
286
|
+
p.size * projected.scale,
|
|
287
|
+
0,
|
|
288
|
+
Math.PI * 2,
|
|
289
|
+
);
|
|
290
|
+
ctx.fill();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Only draw glow effects if BH has awakened
|
|
295
|
+
if (totalIntensity > 0.01) {
|
|
296
|
+
// Outer glow layer - fades in as BH awakens
|
|
297
|
+
ctx.shadowBlur = 25 * totalIntensity;
|
|
298
|
+
ctx.shadowColor = `rgba(255, 100, 30, ${totalIntensity * 0.8})`;
|
|
299
|
+
ctx.fillStyle = `rgba(255, 120, 40, ${totalIntensity * 0.15})`;
|
|
300
|
+
ctx.beginPath();
|
|
301
|
+
ctx.arc(this.screenX, this.screenY, scaledRadius * 1.8, 0, Math.PI * 2);
|
|
302
|
+
ctx.fill();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Photon ring - only appears as BH awakens
|
|
306
|
+
if (ringAlpha > 0.01) {
|
|
307
|
+
ctx.shadowBlur = 12 * ringAlpha;
|
|
308
|
+
ctx.shadowColor = `rgba(255, 180, 60, ${ringAlpha * 0.9})`;
|
|
309
|
+
ctx.fillStyle = `rgba(255, 180, 80, ${ringAlpha * 0.3})`;
|
|
310
|
+
ctx.beginPath();
|
|
311
|
+
ctx.arc(
|
|
312
|
+
this.screenX,
|
|
313
|
+
this.screenY,
|
|
314
|
+
scaledRadius * 1.15,
|
|
315
|
+
0,
|
|
316
|
+
Math.PI * 2,
|
|
317
|
+
);
|
|
318
|
+
ctx.fill();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Event horizon (the void) - ALWAYS has subtle edge gradient
|
|
322
|
+
ctx.shadowBlur = 0;
|
|
323
|
+
|
|
324
|
+
// Gradient: slightly visible dark edge, pure black center
|
|
325
|
+
// Edge gets slightly brighter when awakened (more visible ring edge)
|
|
326
|
+
const edgeBrightness = Math.round(18 + awakeningFactor * 20); // 18-38
|
|
327
|
+
const midBrightness = Math.round(10 + awakeningFactor * 12); // 10-22
|
|
328
|
+
|
|
329
|
+
const horizonGradient = ctx.createRadialGradient(
|
|
330
|
+
this.screenX,
|
|
331
|
+
this.screenY,
|
|
332
|
+
scaledRadius * 0.6,
|
|
333
|
+
this.screenX,
|
|
334
|
+
this.screenY,
|
|
335
|
+
scaledRadius,
|
|
336
|
+
);
|
|
337
|
+
horizonGradient.addColorStop(0, "#000"); // Pure black center
|
|
338
|
+
horizonGradient.addColorStop(0.5, "#000"); // Still black
|
|
339
|
+
horizonGradient.addColorStop(
|
|
340
|
+
0.8,
|
|
341
|
+
`rgb(${midBrightness}, ${midBrightness}, ${midBrightness + 8})`,
|
|
342
|
+
);
|
|
343
|
+
horizonGradient.addColorStop(
|
|
344
|
+
1,
|
|
345
|
+
`rgb(${edgeBrightness}, ${edgeBrightness}, ${edgeBrightness + 8})`,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
ctx.fillStyle = horizonGradient;
|
|
349
|
+
ctx.beginPath();
|
|
350
|
+
ctx.arc(this.screenX, this.screenY, scaledRadius, 0, Math.PI * 2);
|
|
351
|
+
ctx.fill();
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|