feat: neon galaxy view with bloom, hazard tints, clouds
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { Container, Graphics, Text } from 'pixi.js';
|
||||
import { hazardColor, makeViewTransform } from './app.js';
|
||||
import { playerClouds } from '../sim/clouds.js';
|
||||
import { playerSystem } from '../sim/player.js';
|
||||
import { otherEnd } from '../sim/galaxy.js';
|
||||
|
||||
export class GalaxyView {
|
||||
// onSelectSystem(systemId) -> UI intel panel; onJump(laneId) -> goTo lane
|
||||
constructor(renderer, world, { onSelectSystem, onJump }) {
|
||||
this.renderer = renderer;
|
||||
this.world = world;
|
||||
this.onSelectSystem = onSelectSystem;
|
||||
this.onJump = onJump;
|
||||
this.container = new Container();
|
||||
this.labelContainer = new Container();
|
||||
renderer.world.addChild(this.container);
|
||||
renderer.labels.addChild(this.labelContainer);
|
||||
|
||||
this.staticG = new Graphics();
|
||||
this.cloudG = new Graphics();
|
||||
this.dynamicG = new Graphics();
|
||||
this.container.addChild(this.staticG, this.cloudG, this.dynamicG);
|
||||
this.hitNodes = new Container();
|
||||
this.container.addChild(this.hitNodes);
|
||||
this.buildStatic();
|
||||
}
|
||||
|
||||
buildStatic() {
|
||||
const { galaxy } = this.world;
|
||||
const v = makeViewTransform(this.renderer.app);
|
||||
this.v = v;
|
||||
const g = this.staticG;
|
||||
g.clear();
|
||||
for (const lane of galaxy.lanes) {
|
||||
const a = galaxy.systems[lane.a], b = galaxy.systems[lane.b];
|
||||
g.moveTo(v.x(a.x), v.y(a.y)).lineTo(v.x(b.x), v.y(b.y))
|
||||
.stroke({ width: 2, color: hazardColor(lane.hazard), alpha: 0.55 });
|
||||
}
|
||||
for (const s of galaxy.systems) {
|
||||
g.circle(v.x(s.x), v.y(s.y), 7).fill({ color: 0xcfeeff });
|
||||
}
|
||||
this.labelContainer.removeChildren();
|
||||
this.hitNodes.removeChildren();
|
||||
for (const s of galaxy.systems) {
|
||||
const label = new Text({ text: s.name, style: { fill: '#7fa8c9', fontSize: 11, fontFamily: 'system-ui' } });
|
||||
label.position.set(v.x(s.x) + 10, v.y(s.y) - 6);
|
||||
this.labelContainer.addChild(label);
|
||||
|
||||
const hit = new Graphics().circle(0, 0, 16).fill({ color: 0xffffff, alpha: 0.001 });
|
||||
hit.position.set(v.x(s.x), v.y(s.y));
|
||||
hit.eventMode = 'static';
|
||||
hit.cursor = 'pointer';
|
||||
hit.on('pointertap', () => this.handleSystemTap(s.id));
|
||||
this.hitNodes.addChild(hit);
|
||||
}
|
||||
}
|
||||
|
||||
handleSystemTap(systemId) {
|
||||
const sim = this.world;
|
||||
const cur = playerSystem(sim);
|
||||
// Clicking a neighbor = jump intent; clicking anything = intel selection.
|
||||
const lane = sim.galaxy.lanes.find(
|
||||
(l) => (l.a === cur && l.b === systemId) || (l.b === cur && l.a === systemId)
|
||||
);
|
||||
if (lane) this.onJump(lane.id);
|
||||
this.onSelectSystem(systemId);
|
||||
}
|
||||
|
||||
lanePoint(lane, fromSys, f) {
|
||||
const { galaxy } = this.world;
|
||||
const a = galaxy.systems[fromSys];
|
||||
const b = galaxy.systems[otherEnd(lane, fromSys)];
|
||||
return { x: this.v.x(a.x + (b.x - a.x) * f), y: this.v.y(a.y + (b.y - a.y) * f) };
|
||||
}
|
||||
|
||||
update() {
|
||||
const sim = this.world;
|
||||
const v = this.v;
|
||||
const cur = playerSystem(sim);
|
||||
|
||||
// Probability clouds: glowing strokes along lane segments, alpha ~ weight.
|
||||
const cg = this.cloudG;
|
||||
cg.clear();
|
||||
for (const cloud of playerClouds(sim, cur)) {
|
||||
for (const seg of cloud.segs) {
|
||||
const lane = sim.galaxy.lanes[seg.laneId];
|
||||
const p0 = this.lanePoint(lane, seg.fromSys, seg.f0);
|
||||
const p1 = this.lanePoint(lane, seg.fromSys, seg.f1);
|
||||
const alpha = Math.min(0.85, 0.18 + seg.w * 0.9);
|
||||
cg.moveTo(p0.x, p0.y).lineTo(p1.x, p1.y)
|
||||
.stroke({ width: 7, color: 0xffa94d, alpha });
|
||||
}
|
||||
}
|
||||
|
||||
// Player marker: bright cyan triangle at current position.
|
||||
const dg = this.dynamicG;
|
||||
dg.clear();
|
||||
const pos = this.playerXY();
|
||||
if (pos) {
|
||||
dg.poly([pos.x, pos.y - 8, pos.x + 6, pos.y + 6, pos.x - 6, pos.y + 6]).fill({ color: 0x4dffd2 });
|
||||
}
|
||||
// Halo on the player's current system.
|
||||
if (cur != null) {
|
||||
const s = sim.galaxy.systems[cur];
|
||||
dg.circle(v.x(s.x), v.y(s.y), 12).stroke({ width: 2, color: 0x4dffd2, alpha: 0.8 });
|
||||
}
|
||||
}
|
||||
|
||||
playerXY() {
|
||||
const sim = this.world;
|
||||
const loc = sim.player.loc;
|
||||
const v = this.v;
|
||||
if (loc.kind === 'lane') {
|
||||
const lane = sim.galaxy.lanes[loc.laneId];
|
||||
const f = Math.min(1, (sim.t - loc.departT) / (loc.arriveT - loc.departT));
|
||||
return this.lanePoint(lane, loc.fromSys, f);
|
||||
}
|
||||
const sysId = playerSystem(sim);
|
||||
if (sysId == null) return null;
|
||||
const s = sim.galaxy.systems[sysId];
|
||||
return { x: v.x(s.x), y: v.y(s.y) };
|
||||
}
|
||||
|
||||
setVisible(on) {
|
||||
this.container.visible = on;
|
||||
this.labelContainer.visible = on;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user