From 431f22ac942f1851682b8c7c70579aff1c839271 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Tue, 2 Jun 2026 12:04:05 -0700 Subject: [PATCH] Always render player name when under the cursor --- src/client/render/gl/Renderer.ts | 1 + src/client/render/gl/passes/name-pass/TextProgram.ts | 7 +++++++ src/client/render/gl/passes/name-pass/index.ts | 10 +++++++++- src/client/render/gl/shaders/name/name.vert.glsl | 10 ++++++++-- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/client/render/gl/Renderer.ts b/src/client/render/gl/Renderer.ts index 25fb47e43..41110b3ed 100644 --- a/src/client/render/gl/Renderer.ts +++ b/src/client/render/gl/Renderer.ts @@ -919,6 +919,7 @@ export class GPURenderer { setHighlightOwner(ownerID: number): void { this.borderPass.setHighlightOwner(ownerID); this.territoryPass.setHighlightOwner(ownerID); + this.namePass.setHighlightOwner(ownerID); } setHighlightStructureTypes(unitTypes: string[] | null): void { this.structurePass.setHighlightTypes(unitTypes); diff --git a/src/client/render/gl/passes/name-pass/TextProgram.ts b/src/client/render/gl/passes/name-pass/TextProgram.ts index d76c57e40..0ba71c99b 100644 --- a/src/client/render/gl/passes/name-pass/TextProgram.ts +++ b/src/client/render/gl/passes/name-pass/TextProgram.ts @@ -41,6 +41,7 @@ export class TextProgram { private uNameScaleFactor: WebGLUniformLocation; private uNameScaleCap: WebGLUniformLocation; private uTroopSizeMultiplier: WebGLUniformLocation; + private uHighlightOwnerID: WebGLUniformLocation; private uOutlineWidth: WebGLUniformLocation; private uNightAmbient: WebGLUniformLocation; private uOutlineColor: WebGLUniformLocation; @@ -105,6 +106,10 @@ export class TextProgram { this.program, "uTroopSizeMultiplier", )!; + this.uHighlightOwnerID = gl.getUniformLocation( + this.program, + "uHighlightOwnerID", + )!; this.uOutlineWidth = gl.getUniformLocation(this.program, "uOutlineWidth")!; this.uNightAmbient = gl.getUniformLocation(this.program, "uNightAmbient")!; this.uOutlineColor = gl.getUniformLocation(this.program, "uOutlineColor")!; @@ -148,6 +153,7 @@ export class TextProgram { vao: WebGLVertexArrayObject, maxPlayers: number, ambient: number, + highlightOwnerID: number, ): void { if (!this.atlasReady) return; @@ -163,6 +169,7 @@ export class TextProgram { gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor); gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap); gl.uniform1f(this.uTroopSizeMultiplier, ns.troopSizeMultiplier); + gl.uniform1f(this.uHighlightOwnerID, highlightOwnerID); gl.uniform1f(this.uOutlineWidth, ns.outlineWidth); gl.uniform1f(this.uNightAmbient, ambient); gl.uniform3f(this.uOutlineColor, ns.outlineR, ns.outlineG, ns.outlineB); diff --git a/src/client/render/gl/passes/name-pass/index.ts b/src/client/render/gl/passes/name-pass/index.ts index d93d37c1c..e8e7e910c 100644 --- a/src/client/render/gl/passes/name-pass/index.ts +++ b/src/client/render/gl/passes/name-pass/index.ts @@ -98,6 +98,9 @@ export class NamePass { // Reusable per-tick lookup maps (avoid allocation + GC) private alivePlayerIDs = new Set(); private troopsByPlayerID = new Map(); + + // Hovered player's small ID (0 = no highlight, matches TerritoryPass). + private highlightOwnerID = 0; private playerStateByID = new Map(); constructor( @@ -498,7 +501,7 @@ export class NamePass { // Column 4: flagLayerIdx, emojiAtlasIdx, [free], [free] d[off + 16] = slot.flagLayerIdx; d[off + 17] = slot.emojiAtlasIdx; - d[off + 18] = 0; + d[off + 18] = slot.static.smallID; d[off + 19] = 0; // Column 5: crown, traitor, disconnected, alliance @@ -526,6 +529,10 @@ export class NamePass { // Render // ------------------------------------------------------------------------- + setHighlightOwner(ownerID: number): void { + this.highlightOwnerID = ownerID; + } + draw(cameraMatrix: Float32Array, ambient: number): void { if (!this.textProgram.ready) return; if (this.slots.size === 0) return; @@ -583,6 +590,7 @@ export class NamePass { this.vao, this.maxPlayers, ambient, + this.highlightOwnerID, ); this.statusIconProgram.draw(cameraMatrix, this.settings, this.vao); this.iconProgram.draw(cameraMatrix, this.settings, this.vao); diff --git a/src/client/render/gl/shaders/name/name.vert.glsl b/src/client/render/gl/shaders/name/name.vert.glsl index 42b6c0c3d..eb0a9ee44 100644 --- a/src/client/render/gl/shaders/name/name.vert.glsl +++ b/src/client/render/gl/shaders/name/name.vert.glsl @@ -27,6 +27,7 @@ uniform float uCullThreshold; uniform float uNameScaleFactor; uniform float uNameScaleCap; uniform float uTroopSizeMultiplier; +uniform float uHighlightOwnerID; out vec2 vUV; out vec4 vPlayerColor; // player territory color (rgb) + alpha @@ -45,6 +46,8 @@ void main() { vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive vec4 pd2 = texelFetch(uPlayerData, ivec2(2, playerIdx), 0); // r, g, b, a vec4 pd3 = texelFetch(uPlayerData, ivec2(3, playerIdx), 0); // nameLen, troopLen, isHuman, 0 + vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagLayerIdx, emojiAtlasIdx, smallID, 0 + float smallID = pd4.z; // Early out: dead player if (pd1.w <= 0.0) { @@ -90,16 +93,19 @@ void main() { float nameWorldScale = (nameSize * nameScale) / uFontSize; float worldScale = nameWorldScale; + bool isHighlighted = uHighlightOwnerID > 0.0 && smallID == uHighlightOwnerID; + // Troop count is smaller if (lineIdx == 1) { worldScale *= uTroopSizeMultiplier; } - // Zoom-based culling: compute screen-space size and skip if too small + // Zoom-based culling: compute screen-space size and skip if too small. + // Highlighted (hovered) names bypass the cull so they're always visible. // uCamera[0][0] is the x-scale component of the camera matrix float cameraScale = length(vec2(uCamera[0][0], uCamera[1][0])); float screenSize = nameWorldScale * uBase * cameraScale; - if (screenSize < uCullThreshold) { + if (screenSize < uCullThreshold && !isHighlighted) { gl_Position = vec4(0.0); vUV = vec2(0.0); vPlayerColor = vec4(0.0);