Always render player name when under the cursor

This commit is contained in:
evanpelle
2026-06-02 12:04:05 -07:00
parent f1045a2022
commit 431f22ac94
4 changed files with 25 additions and 3 deletions
+1
View File
@@ -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);
@@ -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);
@@ -98,6 +98,9 @@ export class NamePass {
// Reusable per-tick lookup maps (avoid allocation + GC)
private alivePlayerIDs = new Set<string>();
private troopsByPlayerID = new Map<string, number>();
// Hovered player's small ID (0 = no highlight, matches TerritoryPass).
private highlightOwnerID = 0;
private playerStateByID = new Map<string, PlayerState>();
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);
@@ -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);