@where-stars-drift/core 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/LICENSE +20 -0
- package/README.md +143 -0
- package/dist/src/base/bounded-body.d.ts +8 -0
- package/dist/src/base/bounded-body.js +10 -0
- package/dist/src/base/celestial-body.d.ts +12 -0
- package/dist/src/base/celestial-body.js +11 -0
- package/dist/src/base/hoverable.d.ts +10 -0
- package/dist/src/base/hoverable.js +4 -0
- package/dist/src/base/massive-body.d.ts +8 -0
- package/dist/src/base/massive-body.js +10 -0
- package/dist/src/config/effects.d.ts +14 -0
- package/dist/src/config/effects.js +14 -0
- package/dist/src/config/grid.d.ts +10 -0
- package/dist/src/config/grid.js +10 -0
- package/dist/src/config/panel.d.ts +26 -0
- package/dist/src/config/panel.js +26 -0
- package/dist/src/config/simulation.d.ts +21 -0
- package/dist/src/config/simulation.js +23 -0
- package/dist/src/controllers/clan-controller.d.ts +7 -0
- package/dist/src/controllers/clan-controller.js +12 -0
- package/dist/src/controllers/debug-controller.d.ts +45 -0
- package/dist/src/controllers/debug-controller.js +82 -0
- package/dist/src/controllers/effects-controller.d.ts +17 -0
- package/dist/src/controllers/effects-controller.js +71 -0
- package/dist/src/controllers/hover-controller.d.ts +11 -0
- package/dist/src/controllers/hover-controller.js +107 -0
- package/dist/src/controllers/layer-controller.d.ts +16 -0
- package/dist/src/controllers/layer-controller.js +64 -0
- package/dist/src/controllers/physics-controller.d.ts +14 -0
- package/dist/src/controllers/physics-controller.js +152 -0
- package/dist/src/controllers/star-controller.d.ts +12 -0
- package/dist/src/controllers/star-controller.js +38 -0
- package/dist/src/controllers/starship-controller.d.ts +17 -0
- package/dist/src/controllers/starship-controller.js +58 -0
- package/dist/src/draw-debug-line.d.ts +9 -0
- package/dist/src/draw-debug-line.js +29 -0
- package/dist/src/entities/black-hole-factory.d.ts +15 -0
- package/dist/src/entities/black-hole-factory.js +23 -0
- package/dist/src/entities/black-hole-shapes.d.ts +9 -0
- package/dist/src/entities/black-hole-shapes.js +224 -0
- package/dist/src/entities/black-hole.d.ts +69 -0
- package/dist/src/entities/black-hole.js +210 -0
- package/dist/src/entities/clan-manager.d.ts +12 -0
- package/dist/src/entities/clan-manager.js +22 -0
- package/dist/src/entities/clans.d.ts +15 -0
- package/dist/src/entities/clans.js +76 -0
- package/dist/src/entities/comet.d.ts +27 -0
- package/dist/src/entities/comet.js +81 -0
- package/dist/src/entities/docking-point.d.ts +20 -0
- package/dist/src/entities/docking-point.js +22 -0
- package/dist/src/entities/fleet.d.ts +45 -0
- package/dist/src/entities/fleet.js +374 -0
- package/dist/src/entities/formations.d.ts +51 -0
- package/dist/src/entities/formations.js +340 -0
- package/dist/src/entities/meteor.d.ts +26 -0
- package/dist/src/entities/meteor.js +48 -0
- package/dist/src/entities/nebula.d.ts +18 -0
- package/dist/src/entities/nebula.js +43 -0
- package/dist/src/entities/orbit.d.ts +23 -0
- package/dist/src/entities/orbit.js +43 -0
- package/dist/src/entities/pulsar.d.ts +18 -0
- package/dist/src/entities/pulsar.js +41 -0
- package/dist/src/entities/ring.d.ts +13 -0
- package/dist/src/entities/ring.js +26 -0
- package/dist/src/entities/ringed-planet.d.ts +21 -0
- package/dist/src/entities/ringed-planet.js +68 -0
- package/dist/src/entities/sector-grid.d.ts +16 -0
- package/dist/src/entities/sector-grid.js +70 -0
- package/dist/src/entities/star-factory.d.ts +29 -0
- package/dist/src/entities/star-factory.js +47 -0
- package/dist/src/entities/star.d.ts +48 -0
- package/dist/src/entities/star.js +167 -0
- package/dist/src/entities/starship-classes.d.ts +0 -0
- package/dist/src/entities/starship-classes.js +2 -0
- package/dist/src/entities/starship.d.ts +91 -0
- package/dist/src/entities/starship.js +760 -0
- package/dist/src/entities/supernova.d.ts +26 -0
- package/dist/src/entities/supernova.js +54 -0
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +19 -0
- package/dist/src/lib/energy-stream.d.ts +5 -0
- package/dist/src/lib/energy-stream.js +98 -0
- package/dist/src/lib/quadtree.d.ts +31 -0
- package/dist/src/lib/quadtree.js +124 -0
- package/dist/src/lib/simplified-stream.d.ts +6 -0
- package/dist/src/lib/simplified-stream.js +19 -0
- package/dist/src/types.d.ts +14 -0
- package/dist/src/types.js +1 -0
- package/dist/src/ui/black-holes-panel.d.ts +2 -0
- package/dist/src/ui/black-holes-panel.js +76 -0
- package/dist/src/ui/clans-panel.d.ts +2 -0
- package/dist/src/ui/clans-panel.js +20 -0
- package/dist/src/ui/debug-panel-controller.d.ts +41 -0
- package/dist/src/ui/debug-panel-controller.js +285 -0
- package/dist/src/ui/fleets-panel.d.ts +2 -0
- package/dist/src/ui/fleets-panel.js +127 -0
- package/dist/src/ui/formations-panel.d.ts +3 -0
- package/dist/src/ui/formations-panel.js +129 -0
- package/dist/src/ui/panel-config.d.ts +26 -0
- package/dist/src/ui/panel-config.js +26 -0
- package/dist/src/ui/ships-panel.d.ts +12 -0
- package/dist/src/ui/ships-panel.js +61 -0
- package/dist/src/ui/stars-panel.d.ts +2 -0
- package/dist/src/ui/stars-panel.js +120 -0
- package/dist/src/where-stars-drift.d.ts +71 -0
- package/dist/src/where-stars-drift.js +440 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -0
- package/src/base/bounded-body.ts +14 -0
- package/src/base/celestial-body.ts +20 -0
- package/src/base/hoverable.ts +11 -0
- package/src/base/massive-body.ts +14 -0
- package/src/config/effects.ts +15 -0
- package/src/config/grid.ts +11 -0
- package/src/config/panel.ts +26 -0
- package/src/config/simulation.ts +25 -0
- package/src/controllers/clan-controller.ts +19 -0
- package/src/controllers/debug-controller.ts +112 -0
- package/src/controllers/effects-controller.ts +86 -0
- package/src/controllers/hover-controller.ts +128 -0
- package/src/controllers/layer-controller.ts +78 -0
- package/src/controllers/physics-controller.ts +173 -0
- package/src/controllers/star-controller.ts +51 -0
- package/src/controllers/starship-controller.ts +76 -0
- package/src/draw-debug-line.ts +37 -0
- package/src/entities/black-hole-factory.ts +28 -0
- package/src/entities/black-hole-shapes.ts +276 -0
- package/src/entities/black-hole.ts +246 -0
- package/src/entities/clan-manager.ts +33 -0
- package/src/entities/clans.ts +98 -0
- package/src/entities/comet.ts +102 -0
- package/src/entities/docking-point.ts +34 -0
- package/src/entities/fleet.ts +446 -0
- package/src/entities/formations.ts +423 -0
- package/src/entities/meteor.ts +59 -0
- package/src/entities/nebula.ts +50 -0
- package/src/entities/orbit.ts +53 -0
- package/src/entities/pulsar.ts +64 -0
- package/src/entities/ring.ts +42 -0
- package/src/entities/ringed-planet.ts +85 -0
- package/src/entities/sector-grid.ts +81 -0
- package/src/entities/star-factory.ts +59 -0
- package/src/entities/star.ts +222 -0
- package/src/entities/starship-classes.ts +1 -0
- package/src/entities/starship.ts +906 -0
- package/src/entities/supernova.ts +75 -0
- package/src/index.ts +24 -0
- package/src/lib/energy-stream.ts +127 -0
- package/src/lib/quadtree.ts +159 -0
- package/src/lib/simplified-stream.ts +28 -0
- package/src/types.ts +16 -0
- package/src/ui/black-holes-panel.ts +91 -0
- package/src/ui/clans-panel.ts +27 -0
- package/src/ui/debug-panel-controller.ts +339 -0
- package/src/ui/fleets-panel.ts +153 -0
- package/src/ui/formations-panel.ts +155 -0
- package/src/ui/panel-config.ts +26 -0
- package/src/ui/ships-panel.ts +85 -0
- package/src/ui/stars-panel.ts +146 -0
- package/src/where-stars-drift.ts +542 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the Supernova class for a visual pulse effect.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { BlackHle } from './black-hole';
|
|
6
|
+
|
|
7
|
+
// --- Supernova Configuration ---
|
|
8
|
+
export const SUPERNOVA_CONFIG = {
|
|
9
|
+
MAX_LIFE: 60, // 1 second in frames
|
|
10
|
+
RADIUS_MULTIPLIER: 2.5,
|
|
11
|
+
INITIAL_RADIUS_MULTIPLIER: 0.2,
|
|
12
|
+
COLOR: 'rgba(255, 255, 255, 0.8)',
|
|
13
|
+
SHADOW_BLUR: 30,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class Supernova {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
radius: number;
|
|
20
|
+
maxRadius: number;
|
|
21
|
+
life: number;
|
|
22
|
+
maxLife: number;
|
|
23
|
+
color: string;
|
|
24
|
+
initialRadius: number;
|
|
25
|
+
parent: BlackHle | null;
|
|
26
|
+
|
|
27
|
+
constructor(x: number, y: number, parentRadius: number, parent: BlackHle | null = null) {
|
|
28
|
+
this.x = x;
|
|
29
|
+
this.y = y;
|
|
30
|
+
this.initialRadius = parentRadius * SUPERNOVA_CONFIG.INITIAL_RADIUS_MULTIPLIER;
|
|
31
|
+
this.radius = this.initialRadius;
|
|
32
|
+
this.maxRadius = parentRadius * SUPERNOVA_CONFIG.RADIUS_MULTIPLIER;
|
|
33
|
+
this.maxLife = SUPERNOVA_CONFIG.MAX_LIFE;
|
|
34
|
+
this.life = this.maxLife;
|
|
35
|
+
this.color = SUPERNOVA_CONFIG.COLOR;
|
|
36
|
+
this.parent = parent;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isAlive(): boolean {
|
|
40
|
+
return this.life > 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
update() {
|
|
44
|
+
this.life--;
|
|
45
|
+
if (this.parent) {
|
|
46
|
+
this.x = this.parent.x;
|
|
47
|
+
this.y = this.parent.y;
|
|
48
|
+
}
|
|
49
|
+
const progress = (this.maxLife - this.life) / this.maxLife; // 0 to 1
|
|
50
|
+
// Ease-out cubic function for radius expansion
|
|
51
|
+
this.radius = this.initialRadius + (this.maxRadius - this.initialRadius) * (1 - Math.pow(1 - progress, 3));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
draw(ctx: CanvasRenderingContext2D) {
|
|
55
|
+
const progress = this.life / this.maxLife; // 1 to 0
|
|
56
|
+
const alpha = progress * 0.8; // Fade out
|
|
57
|
+
|
|
58
|
+
ctx.save();
|
|
59
|
+
ctx.globalAlpha = alpha;
|
|
60
|
+
ctx.shadowColor = this.color;
|
|
61
|
+
ctx.shadowBlur = SUPERNOVA_CONFIG.SHADOW_BLUR;
|
|
62
|
+
|
|
63
|
+
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius);
|
|
64
|
+
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
|
|
65
|
+
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)');
|
|
66
|
+
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
67
|
+
|
|
68
|
+
ctx.fillStyle = gradient;
|
|
69
|
+
ctx.beginPath();
|
|
70
|
+
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
71
|
+
ctx.fill();
|
|
72
|
+
|
|
73
|
+
ctx.restore();
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
export { WhereStarsDrift, type SimulationConfig, type SimulationStats } from './where-stars-drift';
|
|
3
|
+
|
|
4
|
+
export { Star } from './entities/star';
|
|
5
|
+
export { Pulsar } from './entities/pulsar';
|
|
6
|
+
export { RingedPlanet } from './entities/ringed-planet';
|
|
7
|
+
export { BlackHole, BlackHle, type BlackHoleType } from './entities/black-hole';
|
|
8
|
+
export { Starship, type ShipState, type ShipClass } from './entities/starship';
|
|
9
|
+
export { Fleet } from './entities/fleet';
|
|
10
|
+
export { type Clan } from './entities/clans';
|
|
11
|
+
export { formationRegistry, type Formation, type StrictFormation, type FreeFormation } from './entities/formations';
|
|
12
|
+
export { Comet } from './entities/comet';
|
|
13
|
+
export { Nebula } from './entities/nebula';
|
|
14
|
+
export { Supernova } from './entities/supernova';
|
|
15
|
+
export { SectorGrid } from './entities/sector-grid';
|
|
16
|
+
|
|
17
|
+
export { LayerController, type Layer } from './controllers/layer-controller';
|
|
18
|
+
export { PhysicsController } from './controllers/physics-controller';
|
|
19
|
+
export { StarshipController } from './controllers/starship-controller';
|
|
20
|
+
export { StarController } from './controllers/star-controller';
|
|
21
|
+
export { EffectsController } from './controllers/effects-controller';
|
|
22
|
+
|
|
23
|
+
export { SIMULATION_CONFIG, DEBUG_CONFIG } from './config/simulation';
|
|
24
|
+
export { GRID_CONFIG } from './config/grid';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Defines the energy stream rendering function.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BlackHle } from '../entities/black-hole';
|
|
7
|
+
import { BLACK_HOLE_CONFIG } from '../entities/black-hole';
|
|
8
|
+
import type { Star } from '../entities/star';
|
|
9
|
+
|
|
10
|
+
export const drawEnergyStream = (ctx: CanvasRenderingContext2D, hole: BlackHle, target: BlackHle, pulseTime: number, frameCount: number) => {
|
|
11
|
+
const dx = target.x - hole.x;
|
|
12
|
+
const dy = target.y - hole.y;
|
|
13
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
14
|
+
const angle = Math.atan2(dy, dx);
|
|
15
|
+
const perpAngle = angle + Math.PI / 2;
|
|
16
|
+
|
|
17
|
+
let glowColor: string;
|
|
18
|
+
let baseR: number, baseG: number, baseB: number;
|
|
19
|
+
|
|
20
|
+
switch(hole.type) {
|
|
21
|
+
case 'VORTEX':
|
|
22
|
+
glowColor = BLACK_HOLE_CONFIG.VORTEX.VORTEX_COLOR_INNER;
|
|
23
|
+
[baseR, baseG, baseB] = [255, 100, 0];
|
|
24
|
+
break;
|
|
25
|
+
case 'WARPED_DISK':
|
|
26
|
+
glowColor = BLACK_HOLE_CONFIG.WARPED_DISK.DISK_COLOR_INNER;
|
|
27
|
+
[baseR, baseG, baseB] = [255, 220, 150];
|
|
28
|
+
break;
|
|
29
|
+
case 'ACCRETION_DISK':
|
|
30
|
+
glowColor = `rgba(200, 220, 255, 0.8)`;
|
|
31
|
+
[baseR, baseG, baseB] = [200, 220, 255];
|
|
32
|
+
break;
|
|
33
|
+
case 'LENSING':
|
|
34
|
+
default:
|
|
35
|
+
glowColor = `rgba(255, 255, 255, 0.7)`;
|
|
36
|
+
[baseR, baseG, baseB] = [255, 255, 255];
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const pulse = (Math.sin(pulseTime * hole.pulseFrequency + hole.pulsePhase) + 1) / 2; // 0 to 1
|
|
41
|
+
|
|
42
|
+
const startX = hole.x;
|
|
43
|
+
const startY = hole.y;
|
|
44
|
+
|
|
45
|
+
ctx.save();
|
|
46
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
47
|
+
ctx.lineCap = 'round';
|
|
48
|
+
|
|
49
|
+
// --- Emission Nozzle ---
|
|
50
|
+
if (frameCount % 5 === 0) {
|
|
51
|
+
const nozzleLengthMultiplier = Math.random() * 7 + 3;
|
|
52
|
+
const potentialNozzleLength = hole.radius * nozzleLengthMultiplier;
|
|
53
|
+
hole.nozzleLength = Math.min(potentialNozzleLength, (4 / 5) * dist);
|
|
54
|
+
|
|
55
|
+
const nozzleTipX = startX + Math.cos(angle) * hole.nozzleLength;
|
|
56
|
+
const nozzleTipY = startY + Math.sin(angle) * hole.nozzleLength;
|
|
57
|
+
|
|
58
|
+
const nozzleGradient = ctx.createLinearGradient(
|
|
59
|
+
startX, startY,
|
|
60
|
+
nozzleTipX, nozzleTipY
|
|
61
|
+
);
|
|
62
|
+
nozzleGradient.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${0.1 + pulse * 0.2})`);
|
|
63
|
+
nozzleGradient.addColorStop(1, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
|
|
64
|
+
hole.nozzleColor = nozzleGradient;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (hole.nozzleLength > 0 && hole.nozzleColor) {
|
|
68
|
+
const nozzleTipX = startX + Math.cos(angle) * hole.nozzleLength;
|
|
69
|
+
const nozzleTipY = startY + Math.sin(angle) * hole.nozzleLength;
|
|
70
|
+
const nozzleBaseWidth = hole.radius * 2;
|
|
71
|
+
|
|
72
|
+
ctx.beginPath();
|
|
73
|
+
ctx.moveTo(nozzleTipX, nozzleTipY);
|
|
74
|
+
ctx.lineTo(startX + Math.cos(perpAngle) * nozzleBaseWidth / 2, startY + Math.sin(perpAngle) * nozzleBaseWidth / 2);
|
|
75
|
+
ctx.lineTo(startX - Math.cos(perpAngle) * nozzleBaseWidth / 2, startY - Math.sin(perpAngle) * nozzleBaseWidth / 2);
|
|
76
|
+
ctx.closePath();
|
|
77
|
+
|
|
78
|
+
ctx.fillStyle = hole.nozzleColor;
|
|
79
|
+
ctx.shadowColor = glowColor;
|
|
80
|
+
ctx.shadowBlur = 30;
|
|
81
|
+
ctx.fill();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// --- Main Stream ---
|
|
86
|
+
const endX = startX + dx;
|
|
87
|
+
const endY = startY + dy;
|
|
88
|
+
|
|
89
|
+
const minThicknessFactor = 0.3;
|
|
90
|
+
const maxThicknessFactor = 3.5;
|
|
91
|
+
const distanceFactor = Math.max(0, 1 - dist / BLACK_HOLE_CONFIG.ENERGY_STREAM_DISTANCE);
|
|
92
|
+
const thicknessMultiplier = minThicknessFactor + (maxThicknessFactor - minThicknessFactor) * distanceFactor;
|
|
93
|
+
|
|
94
|
+
// --- Outer Glow ---
|
|
95
|
+
const outerGlowAlpha = 0.2 + pulse * 0.3;
|
|
96
|
+
ctx.shadowColor = glowColor;
|
|
97
|
+
ctx.shadowBlur = 25;
|
|
98
|
+
|
|
99
|
+
const gradientOuter = ctx.createLinearGradient(startX, startY, endX, endY);
|
|
100
|
+
gradientOuter.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${outerGlowAlpha * 0.7})`);
|
|
101
|
+
gradientOuter.addColorStop(0.95, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
|
|
102
|
+
ctx.strokeStyle = gradientOuter;
|
|
103
|
+
ctx.lineWidth = hole.radius * (0.2 + pulse * 0.15) * thicknessMultiplier;
|
|
104
|
+
|
|
105
|
+
ctx.beginPath();
|
|
106
|
+
ctx.moveTo(startX, startY);
|
|
107
|
+
ctx.lineTo(endX, endY);
|
|
108
|
+
ctx.stroke();
|
|
109
|
+
|
|
110
|
+
// --- Inner Core ---
|
|
111
|
+
const innerGlowAlpha = 0.4 + pulse * 0.6;
|
|
112
|
+
ctx.shadowColor = `rgba(${baseR}, ${baseG}, ${baseB}, ${0.5 + pulse * 0.5})`;
|
|
113
|
+
ctx.shadowBlur = 12;
|
|
114
|
+
|
|
115
|
+
const gradientInner = ctx.createLinearGradient(startX, startY, endX, endY);
|
|
116
|
+
gradientInner.addColorStop(0, `rgba(${baseR}, ${baseG}, ${baseB}, ${innerGlowAlpha})`);
|
|
117
|
+
gradientInner.addColorStop(0.95, `rgba(${baseR}, ${baseG}, ${baseB}, 0)`);
|
|
118
|
+
ctx.strokeStyle = gradientInner;
|
|
119
|
+
ctx.lineWidth = hole.radius * 0.1 * thicknessMultiplier;
|
|
120
|
+
|
|
121
|
+
ctx.beginPath();
|
|
122
|
+
ctx.moveTo(startX, startY);
|
|
123
|
+
ctx.lineTo(endX, endY);
|
|
124
|
+
ctx.stroke();
|
|
125
|
+
|
|
126
|
+
ctx.restore();
|
|
127
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Defines the Quadtree class for spatial partitioning.
|
|
4
|
+
*/
|
|
5
|
+
import type { BoundedBody } from '../base/bounded-body';
|
|
6
|
+
|
|
7
|
+
// --- Quadtree Configuration ---
|
|
8
|
+
export const QUADTREE_CONFIG = {
|
|
9
|
+
MAX_OBJECTS: 10,
|
|
10
|
+
MAX_LEVELS: 5,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface Rectangle {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Quadtree {
|
|
21
|
+
private maxObjects: number;
|
|
22
|
+
private maxLevels: number;
|
|
23
|
+
private level: number;
|
|
24
|
+
private bounds: Rectangle;
|
|
25
|
+
private objects: BoundedBody[];
|
|
26
|
+
private nodes: Quadtree[];
|
|
27
|
+
|
|
28
|
+
constructor(bounds: Rectangle, level: number = 0) {
|
|
29
|
+
this.maxObjects = QUADTREE_CONFIG.MAX_OBJECTS;
|
|
30
|
+
this.maxLevels = QUADTREE_CONFIG.MAX_LEVELS;
|
|
31
|
+
this.level = level;
|
|
32
|
+
this.bounds = bounds;
|
|
33
|
+
this.objects = [];
|
|
34
|
+
this.nodes = [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public clear() {
|
|
38
|
+
this.objects = [];
|
|
39
|
+
this.nodes = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private split() {
|
|
43
|
+
const nextLevel = this.level + 1;
|
|
44
|
+
const subWidth = this.bounds.width / 2;
|
|
45
|
+
const subHeight = this.bounds.height / 2;
|
|
46
|
+
const x = this.bounds.x;
|
|
47
|
+
const y = this.bounds.y;
|
|
48
|
+
|
|
49
|
+
// Top right
|
|
50
|
+
this.nodes[0] = new Quadtree({ x: x + subWidth, y: y, width: subWidth, height: subHeight }, nextLevel);
|
|
51
|
+
// Top left
|
|
52
|
+
this.nodes[1] = new Quadtree({ x: x, y: y, width: subWidth, height: subHeight }, nextLevel);
|
|
53
|
+
// Bottom left
|
|
54
|
+
this.nodes[2] = new Quadtree({ x: x, y: y + subHeight, width: subWidth, height: subHeight }, nextLevel);
|
|
55
|
+
// Bottom right
|
|
56
|
+
this.nodes[3] = new Quadtree({ x: x + subWidth, y: y + subHeight, width: subWidth, height: subHeight }, nextLevel);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private getIndex(body: BoundedBody): number {
|
|
60
|
+
let index = -1;
|
|
61
|
+
const verticalMidpoint = this.bounds.x + (this.bounds.width / 2);
|
|
62
|
+
const horizontalMidpoint = this.bounds.y + (this.bounds.height / 2);
|
|
63
|
+
|
|
64
|
+
const topQuadrant = body.y < horizontalMidpoint && body.y + body.boundingRadius < horizontalMidpoint;
|
|
65
|
+
const bottomQuadrant = body.y > horizontalMidpoint;
|
|
66
|
+
|
|
67
|
+
if (body.x < verticalMidpoint && body.x + body.boundingRadius < verticalMidpoint) {
|
|
68
|
+
if (topQuadrant) {
|
|
69
|
+
index = 1; // Top left
|
|
70
|
+
} else if (bottomQuadrant) {
|
|
71
|
+
index = 2; // Bottom left
|
|
72
|
+
}
|
|
73
|
+
} else if (body.x > verticalMidpoint) {
|
|
74
|
+
if (topQuadrant) {
|
|
75
|
+
index = 0; // Top right
|
|
76
|
+
} else if (bottomQuadrant) {
|
|
77
|
+
index = 3; // Bottom right
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return index;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public insert(body: BoundedBody) {
|
|
84
|
+
if (this.nodes.length) {
|
|
85
|
+
const index = this.getIndex(body);
|
|
86
|
+
if (index !== -1) {
|
|
87
|
+
this.nodes[index].insert(body);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.objects.push(body);
|
|
93
|
+
|
|
94
|
+
if (this.objects.length > this.maxObjects && this.level < this.maxLevels) {
|
|
95
|
+
if (!this.nodes.length) {
|
|
96
|
+
this.split();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < this.objects.length) {
|
|
101
|
+
const index = this.getIndex(this.objects[i]);
|
|
102
|
+
if (index !== -1) {
|
|
103
|
+
this.nodes[index].insert(this.objects.splice(i, 1)[0]);
|
|
104
|
+
} else {
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public query(range: BoundedBody): BoundedBody[] {
|
|
112
|
+
let found: BoundedBody[] = [];
|
|
113
|
+
const { x, y, boundingRadius } = range;
|
|
114
|
+
const queryBounds = {
|
|
115
|
+
x: x - boundingRadius,
|
|
116
|
+
y: y - boundingRadius,
|
|
117
|
+
width: boundingRadius * 2,
|
|
118
|
+
height: boundingRadius * 2,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (!this.intersects(this.bounds, queryBounds)) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
found = found.concat(this.objects);
|
|
126
|
+
|
|
127
|
+
if (this.nodes.length) {
|
|
128
|
+
const index = this.getIndex(range);
|
|
129
|
+
if (index !== -1) {
|
|
130
|
+
found = found.concat(this.nodes[index].query(range));
|
|
131
|
+
} else {
|
|
132
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
133
|
+
found = found.concat(this.nodes[i].query(range));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private intersects(rect1: Rectangle, rect2: Rectangle): boolean {
|
|
142
|
+
return (
|
|
143
|
+
rect1.x < rect2.x + rect2.width &&
|
|
144
|
+
rect1.x + rect1.width > rect2.x &&
|
|
145
|
+
rect1.y < rect2.y + rect2.height &&
|
|
146
|
+
rect1.y + rect1.height > rect2.y
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public draw(ctx: CanvasRenderingContext2D) {
|
|
151
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
|
152
|
+
ctx.strokeRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
|
|
153
|
+
if (this.nodes.length) {
|
|
154
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
155
|
+
this.nodes[i].draw(ctx);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Defines a simplified, performant energy stream for star-to-hole effects.
|
|
4
|
+
*/
|
|
5
|
+
import type { Star } from '../entities/star';
|
|
6
|
+
import type { BlackHle } from '../entities/black-hole';
|
|
7
|
+
|
|
8
|
+
export const drawSimpleStarStream = (ctx: CanvasRenderingContext2D, star: Star, hole: BlackHle) => {
|
|
9
|
+
const dx = hole.x - star.x;
|
|
10
|
+
const dy = hole.y - star.y;
|
|
11
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
12
|
+
|
|
13
|
+
// Fade the stream as it gets closer to the hole
|
|
14
|
+
const maxDist = hole.radius + 50;
|
|
15
|
+
const alpha = Math.max(0, 1 - (dist / maxDist)) * 0.5;
|
|
16
|
+
|
|
17
|
+
if (alpha <= 0) return;
|
|
18
|
+
|
|
19
|
+
ctx.save();
|
|
20
|
+
ctx.beginPath();
|
|
21
|
+
ctx.moveTo(star.x, star.y);
|
|
22
|
+
ctx.lineTo(hole.x, hole.y);
|
|
23
|
+
ctx.strokeStyle = star.color;
|
|
24
|
+
ctx.globalAlpha = alpha;
|
|
25
|
+
ctx.lineWidth = star.radius * 0.8;
|
|
26
|
+
ctx.stroke();
|
|
27
|
+
ctx.restore();
|
|
28
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Starship } from './entities/starship';
|
|
3
|
+
import type { Formation } from './entities/formations';
|
|
4
|
+
import type { Clan } from './entities/clans';
|
|
5
|
+
import type { Fleet } from './entities/fleet';
|
|
6
|
+
import type { Star } from './entities/star';
|
|
7
|
+
import type { BlackHle } from './entities/black-hole';
|
|
8
|
+
|
|
9
|
+
export type CatalogData = {
|
|
10
|
+
starships: Starship[];
|
|
11
|
+
formations: Formation[];
|
|
12
|
+
clans: Clan[];
|
|
13
|
+
fleets: Fleet[];
|
|
14
|
+
stars: Star[];
|
|
15
|
+
blackHoles: BlackHle[];
|
|
16
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import { BlackHole } from '../entities/black-hole';
|
|
3
|
+
import { PANEL_CONFIG } from './panel-config';
|
|
4
|
+
|
|
5
|
+
const DESCRIPTIONS: Record<BlackHole['type'], string> = {
|
|
6
|
+
STANDARD: 'A simple black hole with a soft, ethereal glow.',
|
|
7
|
+
LENSING: 'Features a bright, sharp ring of light caused by gravitational lensing.',
|
|
8
|
+
ACCRETION_DISK: 'Characterized by a stable, flat disk of matter with a bright central line and relativistic jets.',
|
|
9
|
+
WARPED_DISK: 'Shows a dynamic, warped accretion disk that gives a 3D perspective.',
|
|
10
|
+
VORTEX: 'A highly active black hole with swirling arms of super-heated gas.',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function drawBlackHoleParameters(ctx: CanvasRenderingContext2D, hole: BlackHole, x: number, y: number) {
|
|
14
|
+
ctx.font = 'bold 12px "Roboto Mono", monospace';
|
|
15
|
+
ctx.fillStyle = '#fff';
|
|
16
|
+
ctx.textAlign = 'left';
|
|
17
|
+
ctx.textBaseline = 'top';
|
|
18
|
+
ctx.fillText(hole.type, x, y);
|
|
19
|
+
|
|
20
|
+
// Draw description
|
|
21
|
+
ctx.font = '11px "Roboto Mono", monospace';
|
|
22
|
+
ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
|
|
23
|
+
const description = DESCRIPTIONS[hole.type] || 'A mysterious gravitational anomaly.';
|
|
24
|
+
const words = description.split(' ');
|
|
25
|
+
let line = '';
|
|
26
|
+
let currentY = y + 20; // Start description lower
|
|
27
|
+
for (let n = 0; n < words.length; n++) {
|
|
28
|
+
const testLine = line + words[n] + ' ';
|
|
29
|
+
const metrics = ctx.measureText(testLine);
|
|
30
|
+
if (metrics.width > 200 && n > 0) { // Wrap text
|
|
31
|
+
ctx.fillText(line, x, currentY);
|
|
32
|
+
line = words[n] + ' ';
|
|
33
|
+
currentY += 12;
|
|
34
|
+
} else {
|
|
35
|
+
line = testLine;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
ctx.fillText(line, x, currentY);
|
|
39
|
+
currentY += 20;
|
|
40
|
+
|
|
41
|
+
// Draw parameters
|
|
42
|
+
ctx.font = '10px "Roboto Mono", monospace';
|
|
43
|
+
const lineHeight = 12;
|
|
44
|
+
|
|
45
|
+
const params: { label: string; value: string }[] = [
|
|
46
|
+
{ label: 'Type', value: hole.type },
|
|
47
|
+
{ label: 'Base Radius', value: hole.baseRadius.toFixed(2) },
|
|
48
|
+
{ label: 'Mass', value: hole.mass.toFixed(2) },
|
|
49
|
+
{ label: 'Merge State', value: `${hole.mergeState}` },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
params.forEach(param => {
|
|
53
|
+
ctx.fillStyle = PANEL_CONFIG.ACCENT_COLOR;
|
|
54
|
+
ctx.fillText(`${param.label}:`, x, currentY);
|
|
55
|
+
ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
|
|
56
|
+
ctx.fillText(param.value, x + 70, currentY);
|
|
57
|
+
currentY += lineHeight;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export function drawBlackHoleCatalog(ctx: CanvasRenderingContext2D, startY: number, blackHoleCatalog: BlackHole[], pulseTime: number, frameCount: number): number {
|
|
63
|
+
let currentY = startY + PANEL_CONFIG.PADDING;
|
|
64
|
+
|
|
65
|
+
ctx.font = PANEL_CONFIG.GROUP_FONT;
|
|
66
|
+
ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
|
|
67
|
+
ctx.textAlign = 'left';
|
|
68
|
+
ctx.textBaseline = 'top';
|
|
69
|
+
ctx.fillText('All Black Hole Types', PANEL_CONFIG.X + PANEL_CONFIG.PADDING, currentY);
|
|
70
|
+
currentY += PANEL_CONFIG.GROUP_HEADER_HEIGHT;
|
|
71
|
+
|
|
72
|
+
blackHoleCatalog.forEach(sampleHole => {
|
|
73
|
+
const drawingAreaHeight = 80;
|
|
74
|
+
const rowY = currentY + drawingAreaHeight / 2;
|
|
75
|
+
const xPos = PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 30;
|
|
76
|
+
|
|
77
|
+
ctx.save();
|
|
78
|
+
// Position the drawing relative to the catalog panel
|
|
79
|
+
ctx.translate(xPos, rowY);
|
|
80
|
+
// sampleHole.update(ctx, 0, 0, frameCount); // This is expensive, disable animation in catalog
|
|
81
|
+
sampleHole.draw(ctx, 0, 0); // Pass static pulseTime and frameCount
|
|
82
|
+
ctx.restore();
|
|
83
|
+
|
|
84
|
+
// Draw Title
|
|
85
|
+
drawBlackHoleParameters(ctx, sampleHole, xPos + 70, rowY - 35);
|
|
86
|
+
|
|
87
|
+
currentY += drawingAreaHeight + 45; // Add padding between entries
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return currentY - startY;
|
|
91
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Clan } from '../entities/clans';
|
|
3
|
+
import { PANEL_CONFIG } from './panel-config';
|
|
4
|
+
|
|
5
|
+
export function drawClanCatalog(ctx: CanvasRenderingContext2D, startY: number, clans: Clan[]): number {
|
|
6
|
+
ctx.font = PANEL_CONFIG.ROW_FONT;
|
|
7
|
+
|
|
8
|
+
clans.forEach((clan, i) => {
|
|
9
|
+
const y = startY + (i * PANEL_CONFIG.ROW_HEIGHT) + PANEL_CONFIG.PADDING + 15;
|
|
10
|
+
|
|
11
|
+
// Draw clan icon
|
|
12
|
+
ctx.save();
|
|
13
|
+
ctx.translate(PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 10, y);
|
|
14
|
+
ctx.fillStyle = clan.color;
|
|
15
|
+
ctx.strokeStyle = clan.color;
|
|
16
|
+
clan.icon(ctx, 12);
|
|
17
|
+
ctx.restore();
|
|
18
|
+
|
|
19
|
+
// Draw text
|
|
20
|
+
ctx.fillStyle = PANEL_CONFIG.TEXT_COLOR;
|
|
21
|
+
ctx.textAlign = 'left';
|
|
22
|
+
ctx.textBaseline = 'middle';
|
|
23
|
+
ctx.fillText(`${clan.name} (Fleets: ${clan.fleets.length}, Ships: ${clan.starships.length})`, PANEL_CONFIG.X + PANEL_CONFIG.PADDING + 30, y);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return (clans.length * PANEL_CONFIG.ROW_HEIGHT) + (PANEL_CONFIG.PADDING * 2);
|
|
27
|
+
}
|