@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,290 @@
|
|
|
1
|
+
import { GameObject, Painter, Tweenetik, Easing } from "../../../src/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RelativisticJets - Bipolar jets shooting from black hole poles
|
|
5
|
+
*
|
|
6
|
+
* Physics:
|
|
7
|
+
* - Particles ejected along ±Y axis (perpendicular to disk)
|
|
8
|
+
* - Conical spread gives characteristic jet shape
|
|
9
|
+
* - Velocity decreases with distance (deceleration)
|
|
10
|
+
* - Bright blue-white core fading to orange at edges
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const JET_CONFIG = {
|
|
14
|
+
// Particle properties
|
|
15
|
+
maxParticles: 6000,
|
|
16
|
+
particleLifetime: 8.0, // Shorter lifetime - continuous turnover
|
|
17
|
+
spawnRatePerSecond: 800, // Particles per SECOND (not per frame)
|
|
18
|
+
|
|
19
|
+
// Jet geometry
|
|
20
|
+
coneAngle: 0.06, // Tight cone for focused beams
|
|
21
|
+
initialSpeed: 1200, // Fast ejection - relativistic!
|
|
22
|
+
speedVariation: 300, // Random variation in speed
|
|
23
|
+
|
|
24
|
+
// Jet length - shoots off screen
|
|
25
|
+
maxLength: 50000, // Way off screen
|
|
26
|
+
|
|
27
|
+
// Visual - bright particles
|
|
28
|
+
colorCore: { r: 220, g: 240, b: 255 }, // Blue-white core
|
|
29
|
+
colorEdge: { r: 255, g: 160, b: 80 }, // Orange edge
|
|
30
|
+
sizeMin: 0.8,
|
|
31
|
+
sizeMax: 1.0,
|
|
32
|
+
|
|
33
|
+
// Animation
|
|
34
|
+
activationDuration: 0.3, // Quick ignition
|
|
35
|
+
deactivationDuration: 5.0, // Graceful fade
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class RelativisticJets extends GameObject {
|
|
39
|
+
constructor(game, options = {}) {
|
|
40
|
+
super(game, options);
|
|
41
|
+
|
|
42
|
+
this.camera = options.camera;
|
|
43
|
+
this.bhRadius = options.bhRadius ?? 50;
|
|
44
|
+
|
|
45
|
+
// State
|
|
46
|
+
this.active = false;
|
|
47
|
+
this.intensity = 0; // 0-1, controls spawn rate and brightness
|
|
48
|
+
this.isDeactivating = false; // Prevent multiple deactivate() calls
|
|
49
|
+
|
|
50
|
+
// Particle arrays - one for each jet (up and down)
|
|
51
|
+
this.particles = [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
init() {
|
|
55
|
+
this.particles = [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Activate jets with intensity ramp-up
|
|
60
|
+
* Uses easeOutExpo for explosive ignition feel
|
|
61
|
+
*/
|
|
62
|
+
activate() {
|
|
63
|
+
if (this.active) return;
|
|
64
|
+
this.active = true;
|
|
65
|
+
this.intensity = 0;
|
|
66
|
+
// Explosive start, then sustains - like jets igniting
|
|
67
|
+
Tweenetik.to(this, { intensity: 1 }, JET_CONFIG.activationDuration, Easing.easeOutExpo);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Deactivate jets with fade-out
|
|
72
|
+
*/
|
|
73
|
+
deactivate() {
|
|
74
|
+
if (!this.active || this.isDeactivating) return;
|
|
75
|
+
this.isDeactivating = true;
|
|
76
|
+
// Slow graceful fade
|
|
77
|
+
Tweenetik.to(this, { intensity: 0 }, JET_CONFIG.deactivationDuration, Easing.easeInQuad, {
|
|
78
|
+
onComplete: () => {
|
|
79
|
+
this.active = false;
|
|
80
|
+
this.isDeactivating = false;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pulse the jets - boost intensity for sustained firing
|
|
87
|
+
*/
|
|
88
|
+
pulse() {
|
|
89
|
+
if (!this.active) return;
|
|
90
|
+
this.intensity = 1;
|
|
91
|
+
Tweenetik.to(this, { intensity: 0.6 }, 2.0, Easing.easeOutQuad);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Spawn jet particles from both poles
|
|
96
|
+
* @param {number} dt - Delta time for frame-rate independent spawning
|
|
97
|
+
*/
|
|
98
|
+
spawnParticles(dt) {
|
|
99
|
+
if (this.particles.length >= JET_CONFIG.maxParticles) return;
|
|
100
|
+
|
|
101
|
+
// Frame-rate independent spawning
|
|
102
|
+
const spawnCount = Math.floor(JET_CONFIG.spawnRatePerSecond * dt * this.intensity);
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < spawnCount; i++) {
|
|
105
|
+
// Spawn from both poles (up and down)
|
|
106
|
+
const direction = Math.random() < 0.5 ? 1 : -1;
|
|
107
|
+
|
|
108
|
+
// Conical spread - random angle within cone
|
|
109
|
+
const spreadAngle = Math.random() * JET_CONFIG.coneAngle;
|
|
110
|
+
const azimuth = Math.random() * Math.PI * 2;
|
|
111
|
+
|
|
112
|
+
// Convert to velocity components
|
|
113
|
+
const speed = JET_CONFIG.initialSpeed +
|
|
114
|
+
(Math.random() - 0.5) * JET_CONFIG.speedVariation;
|
|
115
|
+
|
|
116
|
+
// Y is the main jet direction, x/z give the spread
|
|
117
|
+
const vy = direction * speed * Math.cos(spreadAngle);
|
|
118
|
+
const spreadMag = speed * Math.sin(spreadAngle);
|
|
119
|
+
const vx = spreadMag * Math.cos(azimuth);
|
|
120
|
+
const vz = spreadMag * Math.sin(azimuth);
|
|
121
|
+
|
|
122
|
+
// Start position - slightly offset from BH center along jet axis
|
|
123
|
+
const startOffset = this.bhRadius * 1.034;
|
|
124
|
+
|
|
125
|
+
this.particles.push({
|
|
126
|
+
x: vx * 0.01, // Tiny initial spread
|
|
127
|
+
y: direction * startOffset,
|
|
128
|
+
z: vz * 0.01,
|
|
129
|
+
vx,
|
|
130
|
+
vy,
|
|
131
|
+
vz,
|
|
132
|
+
age: 0,
|
|
133
|
+
direction, // Track which jet (for color)
|
|
134
|
+
size: JET_CONFIG.sizeMin + Math.random() * (JET_CONFIG.sizeMax - JET_CONFIG.sizeMin),
|
|
135
|
+
// Core particles (small spread) are brighter
|
|
136
|
+
isCore: spreadAngle < JET_CONFIG.coneAngle * 0.3,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
update(dt) {
|
|
142
|
+
super.update(dt);
|
|
143
|
+
|
|
144
|
+
if (!this.active) return;
|
|
145
|
+
|
|
146
|
+
// Spawn new particles (dt-based for consistent rate)
|
|
147
|
+
this.spawnParticles(dt);
|
|
148
|
+
|
|
149
|
+
// Update existing particles
|
|
150
|
+
const maxDist = this.bhRadius * JET_CONFIG.maxLength;
|
|
151
|
+
|
|
152
|
+
for (let i = this.particles.length - 1; i >= 0; i--) {
|
|
153
|
+
const p = this.particles[i];
|
|
154
|
+
p.age += dt;
|
|
155
|
+
|
|
156
|
+
// Remove old particles
|
|
157
|
+
if (p.age > JET_CONFIG.particleLifetime) {
|
|
158
|
+
this.particles.splice(i, 1);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Remove particles that traveled too far
|
|
163
|
+
const dist = Math.abs(p.y);
|
|
164
|
+
if (dist > maxDist) {
|
|
165
|
+
this.particles.splice(i, 1);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// No deceleration - relativistic jets maintain speed
|
|
170
|
+
// Particles stream continuously at near-constant velocity
|
|
171
|
+
|
|
172
|
+
// Move particle
|
|
173
|
+
p.x += p.vx * dt;
|
|
174
|
+
p.y += p.vy * dt;
|
|
175
|
+
p.z += p.vz * dt;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Build render list with camera projection
|
|
181
|
+
*/
|
|
182
|
+
buildRenderList() {
|
|
183
|
+
const renderList = [];
|
|
184
|
+
if (!this.camera || this.particles.length === 0) return renderList;
|
|
185
|
+
|
|
186
|
+
for (const p of this.particles) {
|
|
187
|
+
// Transform to camera space
|
|
188
|
+
const cosY = Math.cos(this.camera.rotationY);
|
|
189
|
+
const sinY = Math.sin(this.camera.rotationY);
|
|
190
|
+
let xCam = p.x * cosY - p.z * sinY;
|
|
191
|
+
let zCam = p.x * sinY + p.z * cosY;
|
|
192
|
+
|
|
193
|
+
const cosX = Math.cos(this.camera.rotationX);
|
|
194
|
+
const sinX = Math.sin(this.camera.rotationX);
|
|
195
|
+
let yCam = p.y * cosX - zCam * sinX;
|
|
196
|
+
zCam = p.y * sinX + zCam * cosX;
|
|
197
|
+
|
|
198
|
+
// Perspective projection
|
|
199
|
+
const perspectiveScale = this.camera.perspective / (this.camera.perspective + zCam);
|
|
200
|
+
const screenX = xCam * perspectiveScale;
|
|
201
|
+
const screenY = yCam * perspectiveScale;
|
|
202
|
+
|
|
203
|
+
// Skip particles behind camera
|
|
204
|
+
if (zCam < -this.camera.perspective + 10) continue;
|
|
205
|
+
|
|
206
|
+
// Color: core is blue-white, edge is orange
|
|
207
|
+
// Also fade with distance from BH
|
|
208
|
+
const distFactor = Math.min(1, Math.abs(p.y) / (this.bhRadius * JET_CONFIG.maxLength * 0.5));
|
|
209
|
+
const ageFactor = 1 - (p.age / JET_CONFIG.particleLifetime);
|
|
210
|
+
|
|
211
|
+
let color;
|
|
212
|
+
if (p.isCore) {
|
|
213
|
+
// Core: bright blue-white
|
|
214
|
+
color = {
|
|
215
|
+
r: JET_CONFIG.colorCore.r,
|
|
216
|
+
g: JET_CONFIG.colorCore.g,
|
|
217
|
+
b: JET_CONFIG.colorCore.b,
|
|
218
|
+
};
|
|
219
|
+
} else {
|
|
220
|
+
// Edge: lerp toward orange with distance
|
|
221
|
+
color = {
|
|
222
|
+
r: JET_CONFIG.colorCore.r + (JET_CONFIG.colorEdge.r - JET_CONFIG.colorCore.r) * distFactor,
|
|
223
|
+
g: JET_CONFIG.colorCore.g + (JET_CONFIG.colorEdge.g - JET_CONFIG.colorCore.g) * distFactor,
|
|
224
|
+
b: JET_CONFIG.colorCore.b + (JET_CONFIG.colorEdge.b - JET_CONFIG.colorCore.b) * distFactor,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Alpha based on age, intensity, and distance
|
|
229
|
+
const alpha = ageFactor * this.intensity * (1 - distFactor * 0.5);
|
|
230
|
+
|
|
231
|
+
renderList.push({
|
|
232
|
+
x: screenX,
|
|
233
|
+
y: screenY,
|
|
234
|
+
z: zCam,
|
|
235
|
+
scale: perspectiveScale,
|
|
236
|
+
color,
|
|
237
|
+
alpha,
|
|
238
|
+
size: p.size * (p.isCore ? 1.5 : 1),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Sort back to front
|
|
243
|
+
renderList.sort((a, b) => b.z - a.z);
|
|
244
|
+
return renderList;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Clear all particles
|
|
249
|
+
*/
|
|
250
|
+
clear() {
|
|
251
|
+
this.particles = [];
|
|
252
|
+
this.active = false;
|
|
253
|
+
this.intensity = 0;
|
|
254
|
+
this.isDeactivating = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Update BH radius
|
|
259
|
+
*/
|
|
260
|
+
updateBHRadius(radius) {
|
|
261
|
+
this.bhRadius = radius;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
render() {
|
|
265
|
+
super.render();
|
|
266
|
+
|
|
267
|
+
if (!this.active || !this.camera || this.particles.length === 0) return;
|
|
268
|
+
|
|
269
|
+
const cx = this.game.width / 2;
|
|
270
|
+
const cy = this.game.height / 2;
|
|
271
|
+
const renderList = this.buildRenderList();
|
|
272
|
+
|
|
273
|
+
Painter.useCtx((ctx) => {
|
|
274
|
+
// Reset transform
|
|
275
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
276
|
+
ctx.globalCompositeOperation = "lighter";
|
|
277
|
+
|
|
278
|
+
for (const item of renderList) {
|
|
279
|
+
const { r, g, b } = item.color;
|
|
280
|
+
|
|
281
|
+
ctx.fillStyle = `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${item.alpha})`;
|
|
282
|
+
ctx.beginPath();
|
|
283
|
+
ctx.arc(cx + item.x, cy + item.y, item.size * item.scale, 0, Math.PI * 2);
|
|
284
|
+
ctx.fill();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ctx.globalCompositeOperation = "source-over";
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LensedStarfield - Starfield with gravitational lensing around black hole
|
|
3
|
+
*
|
|
4
|
+
* Extends the base StarField to add camera-space lensing effects:
|
|
5
|
+
* - Stars behind the black hole are displaced radially outward
|
|
6
|
+
* - Creates the "Einstein ring" effect where light bends around massive objects
|
|
7
|
+
* - Lensing strength can be animated for dramatic effect
|
|
8
|
+
*/
|
|
9
|
+
import { Painter } from "../../../src/index.js";
|
|
10
|
+
import { applyGravitationalLensing } from "../../../src/math/gr.js";
|
|
11
|
+
import { StarField } from "../blackhole/starfield.obj.js";
|
|
12
|
+
|
|
13
|
+
const LENSING_CONFIG = {
|
|
14
|
+
// How far the lensing effect reaches in screen pixels
|
|
15
|
+
effectRadiusPixels: 600,
|
|
16
|
+
|
|
17
|
+
// Base strength (multiplied by lensingStrength property)
|
|
18
|
+
baseStrength: 200,
|
|
19
|
+
|
|
20
|
+
// Falloff exponent - higher = tighter effect around BH
|
|
21
|
+
falloff: 0.008,
|
|
22
|
+
|
|
23
|
+
// Minimum screen distance to apply lensing (avoid division issues)
|
|
24
|
+
minDistance: 5,
|
|
25
|
+
|
|
26
|
+
// Occlusion radius multiplier (stars within BH radius * this are hidden)
|
|
27
|
+
occlusionMultiplier: 1.15,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class LensedStarfield extends StarField {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Game} game
|
|
33
|
+
* @param {Object} options
|
|
34
|
+
* @param {Camera3D} options.camera
|
|
35
|
+
* @param {BlackHole} options.blackHole - Reference to black hole for radius
|
|
36
|
+
* @param {number} options.starCount
|
|
37
|
+
*/
|
|
38
|
+
constructor(game, options = {}) {
|
|
39
|
+
super(game, options);
|
|
40
|
+
|
|
41
|
+
// Reference to black hole (for radius and position)
|
|
42
|
+
this.blackHole = options.blackHole ?? null;
|
|
43
|
+
|
|
44
|
+
// Animated lensing strength (0 = off, 1 = full)
|
|
45
|
+
// Allows ramping up during disruption phases
|
|
46
|
+
this.lensingStrength = options.lensingStrength ?? 1.0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update black hole reference (if set after construction)
|
|
51
|
+
*/
|
|
52
|
+
setBlackHole(bh) {
|
|
53
|
+
this.blackHole = bh;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Override render to apply gravitational lensing
|
|
58
|
+
*/
|
|
59
|
+
render() {
|
|
60
|
+
// Skip parent render - we do our own with lensing
|
|
61
|
+
if (!this.camera) return;
|
|
62
|
+
|
|
63
|
+
const cx = this.game.width / 2;
|
|
64
|
+
const cy = this.game.height / 2;
|
|
65
|
+
const time = performance.now() / 1000;
|
|
66
|
+
|
|
67
|
+
// Lensing parameters (strength scales with lensingStrength property)
|
|
68
|
+
const lensingPower = LENSING_CONFIG.baseStrength * this.lensingStrength;
|
|
69
|
+
const effectRadius = LENSING_CONFIG.effectRadiusPixels;
|
|
70
|
+
|
|
71
|
+
Painter.useCtx((ctx) => {
|
|
72
|
+
ctx.globalCompositeOperation = "source-over";
|
|
73
|
+
|
|
74
|
+
for (const star of this.stars) {
|
|
75
|
+
// === CAMERA SPACE TRANSFORMATION ===
|
|
76
|
+
const cosY = Math.cos(this.camera.rotationY);
|
|
77
|
+
const sinY = Math.sin(this.camera.rotationY);
|
|
78
|
+
let xCam = star.x * cosY - star.z * sinY;
|
|
79
|
+
let zCam = star.x * sinY + star.z * cosY;
|
|
80
|
+
|
|
81
|
+
const cosX = Math.cos(this.camera.rotationX);
|
|
82
|
+
const sinX = Math.sin(this.camera.rotationX);
|
|
83
|
+
let yCam = star.y * cosX - zCam * sinX;
|
|
84
|
+
zCam = star.y * sinX + zCam * cosX;
|
|
85
|
+
|
|
86
|
+
// Skip stars behind camera
|
|
87
|
+
if (zCam < -this.camera.perspective + 50) continue;
|
|
88
|
+
|
|
89
|
+
// === PERSPECTIVE PROJECTION ===
|
|
90
|
+
const perspectiveScale = this.camera.perspective / (this.camera.perspective + zCam);
|
|
91
|
+
let screenX = xCam * perspectiveScale;
|
|
92
|
+
let screenY = yCam * perspectiveScale;
|
|
93
|
+
|
|
94
|
+
// === GRAVITATIONAL LENSING (screen space) ===
|
|
95
|
+
// Only apply to stars "behind" the black hole (zCam > 0)
|
|
96
|
+
if (lensingPower > 0 && zCam > 0) {
|
|
97
|
+
const lensed = applyGravitationalLensing(
|
|
98
|
+
screenX, screenY,
|
|
99
|
+
effectRadius,
|
|
100
|
+
lensingPower,
|
|
101
|
+
LENSING_CONFIG.falloff,
|
|
102
|
+
LENSING_CONFIG.minDistance
|
|
103
|
+
);
|
|
104
|
+
screenX = lensed.x;
|
|
105
|
+
screenY = lensed.y;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// === OCCLUSION CHECK ===
|
|
109
|
+
// Hide stars that fall within the black hole's occlusion radius
|
|
110
|
+
if (this.blackHole) {
|
|
111
|
+
const bhScreenRadius = this.blackHole.currentRadius * LENSING_CONFIG.occlusionMultiplier;
|
|
112
|
+
const distFromCenter = Math.sqrt(screenX * screenX + screenY * screenY);
|
|
113
|
+
if (distFromCenter < bhScreenRadius) continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Final screen position
|
|
117
|
+
const finalX = cx + screenX;
|
|
118
|
+
const finalY = cy + screenY;
|
|
119
|
+
|
|
120
|
+
// Viewport cull
|
|
121
|
+
if (finalX < -30 || finalX > this.game.width + 30 ||
|
|
122
|
+
finalY < -30 || finalY > this.game.height + 30) continue;
|
|
123
|
+
|
|
124
|
+
// === TWINKLE ===
|
|
125
|
+
const val = Math.sin(time * star.twinkleSpeed + star.twinklePhase);
|
|
126
|
+
const alpha = 0.6 + 0.4 * val;
|
|
127
|
+
if (alpha < 0.1) continue;
|
|
128
|
+
|
|
129
|
+
// === DRAW ===
|
|
130
|
+
const finalSize = star.type.baseSize * star.baseScale * perspectiveScale;
|
|
131
|
+
const sprite = this.sprites.get(star.type.type);
|
|
132
|
+
if (!sprite) continue;
|
|
133
|
+
|
|
134
|
+
ctx.globalAlpha = alpha;
|
|
135
|
+
ctx.drawImage(
|
|
136
|
+
sprite,
|
|
137
|
+
finalX - finalSize * 2,
|
|
138
|
+
finalY - finalSize * 2,
|
|
139
|
+
finalSize * 4,
|
|
140
|
+
finalSize * 4
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
ctx.globalAlpha = 1.0;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|