From fb45c27d829707a3298bd327fbcaddda2094a074 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 20:35:22 -0700 Subject: [PATCH] add subtle player-tile highlight on nation hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hover wiring already pushed setHighlightOwner into the border pass, but the WebGL canvas has pointer-events: none (post-migration to the inputOverlay div) so MapInteraction's pointermove listener never fired. Forward pointermove from the input overlay to view.handlePointerMove so hover actually triggers. While there, brighten every tile owned by the hovered player — the territory frag shader now reads uHighlightOwner / uHighlightBrighten and mixes toward white when the tile owner matches. Wired through territory-pass.ts; renderer.setHighlightOwner forwards to both border and territory passes. New highlightFillBrighten setting (0.15) keeps the fill tint tunable independently of the existing highlightBrighten border setting, which is dropped from 0.6 → 0.25 so neither effect blows out. --- src/client/ClientGameRunner.ts | 7 +++++++ src/client/WebGLFrameBuilder.ts | 12 ++++++++++++ src/client/render/gl/debug/layout.ts | 11 ++++++++++- src/client/render/gl/game-view.ts | 12 ++++++++++++ src/client/render/gl/passes/territory-pass.ts | 18 ++++++++++++++++++ src/client/render/gl/render-settings.json | 3 ++- src/client/render/gl/render-settings.ts | 1 + src/client/render/gl/renderer.ts | 5 +---- .../gl/shaders/map-overlay/territory.frag.glsl | 11 ++++++++++- 9 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index db807385f..934e5bb4d 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -445,6 +445,13 @@ async function createClientGame( (e) => applyDayNightMode((e as CustomEvent).detail === "true"), ); + // The WebGL canvas has pointer-events: none so input flows through the + // overlay div. Forward pointermove to the WebGL view's MapInteraction so + // hover-driven features (highlight owner, etc.) still work. + inputOverlay.addEventListener("pointermove", (e) => + view.handlePointerMove(e), + ); + const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index 5393b9279..b98228ef2 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -19,6 +19,10 @@ const PALETTE_SIZE = 4096; export class WebGLFrameBuilder { private readonly palette: Float32Array; private readonly knownSmallIDs = new Set(); + // The renderer needs to know which player is "me" so affiliation tint, + // unit colors, and SAM-radius perspective work. Push it once the local + // player's update arrives (may take several ticks during join). + private localPlayerSmallID = 0; constructor(private readonly view: WebGLGameView) { this.palette = new Float32Array(PALETTE_SIZE * 2 * 4); @@ -26,9 +30,17 @@ export class WebGLFrameBuilder { update(gameView: GameView): void { this.syncPlayers(gameView); + this.syncLocalPlayer(gameView); uploadFrameData(this.view, gameView.frameData()); } + private syncLocalPlayer(gameView: GameView): void { + const sid = gameView.myPlayer()?.smallID() ?? 0; + if (sid === this.localPlayerSmallID) return; + this.localPlayerSmallID = sid; + this.view.setLocalPlayerID(sid); + } + private syncPlayers(gameView: GameView): void { const newPlayers: PlayerStatic[] = []; for (const p of gameView.players()) { diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/layout.ts index 14551bf2e..e94f2ef9b 100644 --- a/src/client/render/gl/debug/layout.ts +++ b/src/client/render/gl/debug/layout.ts @@ -116,7 +116,16 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { 0, 1, 0.01, - "Highlight Brighten", + "Highlight Brighten (border)", + ), + slider( + s.mapOverlay, + "highlightFillBrighten", + d.mapOverlay, + 0, + 1, + 0.01, + "Highlight Brighten (fill)", ), slider( s.mapOverlay, diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/game-view.ts index 6786d9cc1..48d7f0306 100644 --- a/src/client/render/gl/game-view.ts +++ b/src/client/render/gl/game-view.ts @@ -110,6 +110,18 @@ export class GameView { if (rect.width > 0) this.renderer.resize(rect.width, rect.height); } + /** + * Forward a pointermove event into the MapInteraction handler. The WebGL + * canvas itself has pointer-events: none (input flows through a separate + * overlay div in the main client), so the listener bound to `canvas` in + * the constructor never actually fires for game-mode input. Callers that + * own the active input element forward pointermove events here so hover + * tracking + setHighlightOwner still work. + */ + handlePointerMove(e: PointerEvent): void { + this.interaction.handlePointerMove(e); + } + // ---- Event system ---- on( diff --git a/src/client/render/gl/passes/territory-pass.ts b/src/client/render/gl/passes/territory-pass.ts index c2e748575..63c0a664a 100644 --- a/src/client/render/gl/passes/territory-pass.ts +++ b/src/client/render/gl/passes/territory-pass.ts @@ -34,6 +34,9 @@ export class TerritoryPass { private uCharcoalBase: WebGLUniformLocation; private uCharcoalVariation: WebGLUniformLocation; private uCharcoalAlpha: WebGLUniformLocation; + private uHighlightOwner: WebGLUniformLocation; + private uHighlightBrighten: WebGLUniformLocation; + private highlightOwner = 0; private vao: WebGLVertexArrayObject; private tileTex: WebGLTexture; @@ -101,6 +104,14 @@ export class TerritoryPass { this.program, "uCharcoalAlpha", )!; + this.uHighlightOwner = gl.getUniformLocation( + this.program, + "uHighlightOwner", + )!; + this.uHighlightBrighten = gl.getUniformLocation( + this.program, + "uHighlightBrighten", + )!; gl.useProgram(this.program); gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); @@ -319,6 +330,11 @@ export class TerritoryPass { this.altView = active; } + /** Set the hovered player's smallID for territory-fill brightening (0 = off). */ + setHighlightOwner(ownerID: number): void { + this.highlightOwner = ownerID; + } + /** Draw territory fill + fallout charcoal. Blending must be enabled by caller. */ draw(cameraMatrix: Float32Array): void { this.flushTileTexture(); @@ -334,6 +350,8 @@ export class TerritoryPass { gl.uniform1f(this.uCharcoalBase, mo.charcoalBase); gl.uniform1f(this.uCharcoalVariation, mo.charcoalVariation); gl.uniform1f(this.uCharcoalAlpha, mo.charcoalAlpha); + gl.uniform1ui(this.uHighlightOwner, this.highlightOwner); + gl.uniform1f(this.uHighlightBrighten, mo.highlightFillBrighten); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.tileTex); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index e38b0e3c1..e7d2058ec 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -65,7 +65,8 @@ "emberColorBrightG": 0.5, "emberColorBrightB": 0.05, "emberStrengthUnowned": 0.5, - "highlightBrighten": 0.6, + "highlightBrighten": 0.25, + "highlightFillBrighten": 0.15, "highlightThicken": 2, "defensePostRange": 30, "embargoTintRatio": 0.35, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index d9bda1b20..2aa9cfc60 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -68,6 +68,7 @@ export interface RenderSettings { emberColorBrightB: number; emberStrengthUnowned: number; highlightBrighten: number; + highlightFillBrighten: number; highlightThicken: number; defensePostRange: number; embargoTintRatio: number; diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts index a1e831cae..d71521712 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/renderer.ts @@ -694,10 +694,6 @@ export class GPURenderer { this.railroadPass.updateGhostPreview(data); this.rangeCirclePass.updateGhostPreview(data); this.crosshairPass.updateGhostPreview(data); - if (data) this.localPlayerID = data.ownerID; - this.samRadiusPass.setLocalPlayer(this.localPlayerID); - this.affiliationPalette.setLocalPlayer(this.localPlayerID); - this.unitPass.setLocalPlayer(this.localPlayerID); this.samGhostVisible = data !== null && SAM_RADIUS_GHOST_TYPES.has(data.ghostType); this.samRadiusPass.setVisible( @@ -723,6 +719,7 @@ export class GPURenderer { setHighlightOwner(ownerID: number): void { this.borderPass.setHighlightOwner(ownerID); + this.territoryPass.setHighlightOwner(ownerID); } setHighlightStructureTypes(unitTypes: string[] | null): void { this.structurePass.setHighlightTypes(unitTypes); diff --git a/src/client/render/gl/shaders/map-overlay/territory.frag.glsl b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl index 94c0579b4..91664f4b4 100644 --- a/src/client/render/gl/shaders/map-overlay/territory.frag.glsl +++ b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl @@ -10,6 +10,8 @@ uniform int uAltView; uniform float uCharcoalBase; uniform float uCharcoalVariation; uniform float uCharcoalAlpha; +uniform uint uHighlightOwner; // 0 = no highlight; otherwise smallID of hovered owner +uniform float uHighlightBrighten; // mix amount toward white for highlighted tiles in vec2 vWorldPos; out vec4 fragColor; @@ -38,5 +40,12 @@ void main() { // --- Territory fill (owned) --- float u = (float(owner) + 0.5) / float(PALETTE_SIZE); - fragColor = texture(uPalette, vec2(u, 0.25)); + vec4 color = texture(uPalette, vec2(u, 0.25)); + + // Hover highlight: brighten every tile owned by the hovered player. + if (uHighlightOwner != 0u && owner == uHighlightOwner) { + color.rgb = mix(color.rgb, vec3(1.0), uHighlightBrighten); + } + + fragColor = color; }