Files
OpenFrontIO/src/client/graphics/HoverInfo.ts
T
scamiv bcd1412f75 refactor: restructure WebGPU territory renderer into extensible pass-based architecture
Refactor the monolithic TerritoryWebGLRenderer into a modular, extensible
architecture that separates ground truth computation from rendering passes.
This change also includes related improvements to game state management and
hover information handling.

WebGPU Architecture Refactor:
- Extract all shaders to external .wgsl files (no inlined shaders)
- Separate ground truth data management (GroundTruthData) from rendering
- Create pass-based architecture with ComputePass and RenderPass interfaces
- Implement compute passes: StateUpdatePass, DefendedClearPass, DefendedUpdatePass
- Implement render pass: TerritoryRenderPass
- Add TerritoryRenderer orchestrator with dependency-based execution ordering
- Add WebGPUDevice for device initialization and management
- Add ShaderLoader utility for loading .wgsl files via Vite ?raw imports

Performance Optimizations:
- Dependency order computed once at init (topological sort)
- Early exit checks at orchestrator and pass levels
- Bind groups rebuilt when textures/buffers are recreated
- Zero per-frame allocations (reuse command encoders and staging buffers)

Architecture Benefits:
- Easy to extend with new compute/render passes (borders, temporal smoothing, etc.)
- Clear separation between tick-based compute and frame-based rendering
- All shaders in external files for better maintainability
- Ground truth data computed once and reused by all passes

Related Changes:
- Add defended tile state support to GameMap (isDefended/setDefended)
- Expose tileStateView() for direct GPU state access
- Extract hover info logic to HoverInfo utility
- Remove TerrainLayer (terrain now rendered by WebGPU territory pass)
- Update GameRenderer to use transparent overlay canvas
- Add viewOffset() method to TransformHandler

Files:
- Deleted: TerritoryWebGLRenderer.ts (1217 lines), TerrainLayer.ts (77 lines)
- Added: 17 new files in webgpu/ directory structure
- Updated: TerritoryLayer.ts, GameRenderer.ts, PlayerInfoOverlay.ts,
  GameMap.ts, GameView.ts, GameImpl.ts, TransformHandler.ts, vite-env.d.ts
2026-02-05 21:46:47 +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;
}