Files
OpenFrontIO/src/client/graphics/HoverInfo.ts
T
scamiv 202f59a410 Refactor territory rendering to use WebGPU
Replace Canvas 2D-based territory rendering with a WebGPU implementation
for improved performance and scalability.

Major changes:
- Remove TerrainLayer: territory and terrain now rendered together in WebGPU
- Add TerritoryWebGLRenderer: new WebGPU-based renderer (1162 lines)
  - GPU-authoritative state management with compute shaders
  - Separate uniform buffers for compute and render passes
  - Efficient incremental updates via scatter/gather compute passes
  - Full rebuild compute pass for palette/theme changes
- Add defense post management:
  - Track defended state in GameMap (bit 12 in tile state)
  - Update defended tiles when defense posts are added/removed/moved
  - Update defended state when tiles change ownership
- Extract hover detection into HoverInfo utility:
  - Centralized logic for player/unit/wilderness detection
  - Used by PlayerInfoOverlay for cleaner separation of concerns
- Canvas architecture changes:
  - Main canvas now transparent (alpha: true)
  - WebGPU canvas renders background and territory
  - Overlay canvas renders UI elements on top
- Performance optimizations:
  - Compute shaders run at simulation rate (tick), not frame rate
  - Incremental tile updates via pending tiles set
  - Palette signature tracking to avoid unnecessary rebuilds
  - Defense posts signature tracking for efficient updates

Files changed:
- 10 files changed, 1447 insertions(+), 752 deletions(-)
- New: TerritoryWebGLRenderer.ts, HoverInfo.ts
- Removed: TerrainLayer.ts
- Modified: TerritoryLayer.ts (simplified from 710 to 250 lines)
2026-01-16 02:50:00 +01:00

74 lines
1.8 KiB
TypeScript

import { UnitType } from "../../core/game/Game";
import { TileRef } from "../../core/game/GameMap";
import { GameView, PlayerView, UnitView } from "../../core/game/GameView";
export type HoverInfo = {
player: PlayerView | null;
unit: UnitView | null;
isWilderness: boolean;
isIrradiatedWilderness: boolean;
};
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
game: GameView,
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
const dx = coord.x - x;
const dy = coord.y - y;
return Math.sqrt(dx * dx + dy * dy);
}
function distSortUnitWorld(coord: { x: number; y: number }, game: GameView) {
return (a: UnitView, b: UnitView) => {
const distA = euclideanDistWorld(coord, a.tile(), game);
const distB = euclideanDistWorld(coord, b.tile(), game);
return distA - distB;
};
}
export function getHoverInfo(
game: GameView,
worldCoord: { x: number; y: number },
): HoverInfo {
const info: HoverInfo = {
player: null,
unit: null,
isWilderness: false,
isIrradiatedWilderness: false,
};
if (!game.isValidCoord(worldCoord.x, worldCoord.y)) {
return info;
}
const tile = game.ref(worldCoord.x, worldCoord.y);
const owner = game.owner(tile);
if (owner && owner.isPlayer()) {
info.player = owner as PlayerView;
return info;
}
if (owner && !owner.isPlayer() && game.isLand(tile)) {
info.isIrradiatedWilderness = game.hasFallout(tile);
info.isWilderness = !info.isIrradiatedWilderness;
return info;
}
if (!game.isLand(tile)) {
const units = game
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), game) < 50)
.sort(distSortUnitWorld(worldCoord, game));
if (units.length > 0) {
info.unit = units[0];
}
}
return info;
}