add subtle player-tile highlight on nation hover

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.
This commit is contained in:
evanpelle
2026-05-17 20:35:22 -07:00
parent c197f5864f
commit fb45c27d82
9 changed files with 73 additions and 7 deletions
+7
View File
@@ -445,6 +445,13 @@ async function createClientGame(
(e) => applyDayNightMode((e as CustomEvent<string>).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,
+12
View File
@@ -19,6 +19,10 @@ const PALETTE_SIZE = 4096;
export class WebGLFrameBuilder {
private readonly palette: Float32Array;
private readonly knownSmallIDs = new Set<number>();
// 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()) {
+10 -1
View File
@@ -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,
+12
View File
@@ -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<K extends GameViewEventType>(
@@ -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);
+2 -1
View File
@@ -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,
+1
View File
@@ -68,6 +68,7 @@ export interface RenderSettings {
emberColorBrightB: number;
emberStrengthUnowned: number;
highlightBrighten: number;
highlightFillBrighten: number;
highlightThicken: number;
defensePostRange: number;
embargoTintRatio: number;
+1 -4
View File
@@ -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);
@@ -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;
}