mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-11 02:04:18 +00:00
Add white glow behind hovered player's name
The hovered player (tile owner under the cursor, already tracked via uHighlightOwnerID for cull bypass) now gets a soft white glow behind their name. The glow is derived from the MSDF distance field: a white band past the outline with quadratic falloff, composited behind the glyph and clamped to the SDF margin so it never clips at quad edges. Glow size and strength are tunable via hoverGlowWidth/hoverGlowAlpha in render-settings.json, exposed as sliders in the graphics settings modal (persisted as graphics overrides) and in the debug GUI. Includes schema and apply tests for the new override fields, covering the 0 edge case (0 disables the glow, not "unset").
This commit is contained in:
@@ -8,6 +8,8 @@ export const GraphicsOverridesSchema = z
|
||||
cullThreshold: z.number(),
|
||||
darkNames: z.boolean(),
|
||||
hoverFadeAlpha: z.number(),
|
||||
hoverGlowWidth: z.number(),
|
||||
hoverGlowAlpha: z.number(),
|
||||
})
|
||||
.partial(),
|
||||
structure: z
|
||||
|
||||
@@ -21,6 +21,12 @@ export function applyGraphicsOverrides(
|
||||
if (overrides.name?.hoverFadeAlpha !== undefined) {
|
||||
settings.name.hoverFadeAlpha = overrides.name.hoverFadeAlpha;
|
||||
}
|
||||
if (overrides.name?.hoverGlowWidth !== undefined) {
|
||||
settings.name.hoverGlowWidth = overrides.name.hoverGlowWidth;
|
||||
}
|
||||
if (overrides.name?.hoverGlowAlpha !== undefined) {
|
||||
settings.name.hoverGlowAlpha = overrides.name.hoverGlowAlpha;
|
||||
}
|
||||
if (overrides.structure?.classicIcons === true) {
|
||||
// Classic look: lighter player-colored shape behind a dark icon glyph,
|
||||
// with a touch of translucency.
|
||||
|
||||
@@ -243,6 +243,10 @@ export interface RenderSettings {
|
||||
statusRowOffset: number;
|
||||
/** Alpha multiplier applied to a name while the cursor is over it. */
|
||||
hoverFadeAlpha: number;
|
||||
/** White glow behind the hovered player's name: px past the outline. */
|
||||
hoverGlowWidth: number;
|
||||
/** Peak opacity of the hover glow (0 disables it). */
|
||||
hoverGlowAlpha: number;
|
||||
};
|
||||
fx: {
|
||||
shockwaveRingWidth: number;
|
||||
|
||||
@@ -326,6 +326,8 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
|
||||
slider(s.name, "emojiRowOffset", d.name, 0, 5, 0.1, "Emoji Row Offset"),
|
||||
slider(s.name, "statusRowOffset", d.name, 0, 5, 0.1, "Status Row Offset"),
|
||||
slider(s.name, "hoverFadeAlpha", d.name, 0, 1, 0.05, "Hover Fade Alpha"),
|
||||
slider(s.name, "hoverGlowWidth", d.name, 0, 8, 0.25, "Hover Glow Width"),
|
||||
slider(s.name, "hoverGlowAlpha", d.name, 0, 1, 0.05, "Hover Glow Alpha"),
|
||||
]),
|
||||
|
||||
folder("FX", [
|
||||
|
||||
@@ -49,6 +49,8 @@ export class TextProgram {
|
||||
private uOutlineColor: WebGLUniformLocation;
|
||||
private uOutlineUsePlayerColor: WebGLUniformLocation;
|
||||
private uFillUsePlayerColor: WebGLUniformLocation;
|
||||
private uHoverGlowWidth: WebGLUniformLocation;
|
||||
private uHoverGlowAlpha: WebGLUniformLocation;
|
||||
|
||||
private distanceRange: number;
|
||||
|
||||
@@ -128,6 +130,14 @@ export class TextProgram {
|
||||
this.program,
|
||||
"uFillUsePlayerColor",
|
||||
)!;
|
||||
this.uHoverGlowWidth = gl.getUniformLocation(
|
||||
this.program,
|
||||
"uHoverGlowWidth",
|
||||
)!;
|
||||
this.uHoverGlowAlpha = gl.getUniformLocation(
|
||||
this.program,
|
||||
"uHoverGlowAlpha",
|
||||
)!;
|
||||
|
||||
this.loadAtlas();
|
||||
}
|
||||
@@ -188,6 +198,8 @@ export class TextProgram {
|
||||
ns.outlineUsePlayerColor ? 1.0 : 0.0,
|
||||
);
|
||||
gl.uniform1f(this.uFillUsePlayerColor, ns.fillUsePlayerColor ? 1.0 : 0.0);
|
||||
gl.uniform1f(this.uHoverGlowWidth, ns.hoverGlowWidth);
|
||||
gl.uniform1f(this.uHoverGlowAlpha, ns.hoverGlowAlpha);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.atlasTex!);
|
||||
|
||||
@@ -207,7 +207,9 @@
|
||||
"nameShadeBot": 0.4,
|
||||
"emojiRowOffset": 1.4,
|
||||
"statusRowOffset": 1.4,
|
||||
"hoverFadeAlpha": 0.25
|
||||
"hoverFadeAlpha": 0.5,
|
||||
"hoverGlowWidth": 5,
|
||||
"hoverGlowAlpha": 0.75
|
||||
},
|
||||
"fx": {
|
||||
"shockwaveRingWidth": 0.04,
|
||||
|
||||
@@ -8,10 +8,13 @@ uniform float uNightAmbient;
|
||||
uniform vec3 uOutlineColor;
|
||||
uniform float uOutlineUsePlayerColor;
|
||||
uniform float uFillUsePlayerColor;
|
||||
uniform float uHoverGlowWidth; // px the white hover glow extends past the outline
|
||||
uniform float uHoverGlowAlpha; // peak opacity of the hover glow
|
||||
|
||||
in vec2 vUV;
|
||||
in vec4 vPlayerColor; // player territory color (rgb) + alpha
|
||||
in float vNameShade; // name fill grayscale shade (0.0 = black)
|
||||
flat in float vHighlight; // 1.0 when this player is hovered (white glow)
|
||||
out vec4 fragColor;
|
||||
|
||||
float median(float r, float g, float b) {
|
||||
@@ -42,20 +45,40 @@ void main() {
|
||||
float screenPxDist = screenPxRange * (sd - 0.5);
|
||||
float fillAlpha = clamp(screenPxDist + 0.5, 0.0, 1.0);
|
||||
|
||||
if (uOutlineWidth > 0.0) {
|
||||
// The SDF saturates at sd=0 (screenPxDist = -screenPxRange*0.5).
|
||||
// Reserve a 1px margin so saturated fragments always get alpha=0.
|
||||
float maxOutline = max(screenPxRange * 0.5 - 1.0, 0.0);
|
||||
float effectiveOutline = min(uOutlineWidth, maxOutline);
|
||||
// The SDF saturates at sd=0 (screenPxDist = -screenPxRange*0.5).
|
||||
// Reserve a 1px margin so saturated fragments always get alpha=0.
|
||||
float maxOutline = max(screenPxRange * 0.5 - 1.0, 0.0);
|
||||
float effectiveOutline = min(uOutlineWidth, maxOutline);
|
||||
|
||||
vec3 color;
|
||||
float coverage;
|
||||
if (uOutlineWidth > 0.0) {
|
||||
float outlineDist = screenPxDist + effectiveOutline;
|
||||
float outlineAlpha = clamp(outlineDist + 0.5, 0.0, 1.0);
|
||||
|
||||
vec3 nightOutlineColor = mix(vec3(0.0), uOutlineColor, borderT);
|
||||
vec3 borderColor = mix(nightOutlineColor, vPlayerColor.rgb, uOutlineUsePlayerColor);
|
||||
vec3 color = mix(borderColor, fillColor, fillAlpha);
|
||||
fragColor = vec4(color, vPlayerColor.a * outlineAlpha);
|
||||
color = mix(borderColor, fillColor, fillAlpha);
|
||||
coverage = outlineAlpha;
|
||||
} else {
|
||||
fragColor = vec4(fillColor, vPlayerColor.a * fillAlpha);
|
||||
color = fillColor;
|
||||
coverage = fillAlpha;
|
||||
}
|
||||
|
||||
// Soft white glow behind the hovered player's name. Width is clamped to
|
||||
// the SDF margin past the outline so it never hard-clips at the quad edge.
|
||||
if (vHighlight > 0.5 && uHoverGlowAlpha > 0.0) {
|
||||
float glowWidth = min(uHoverGlowWidth, max(maxOutline - effectiveOutline, 0.0));
|
||||
if (glowWidth > 0.0) {
|
||||
float g = clamp(1.0 + (screenPxDist + effectiveOutline) / glowWidth, 0.0, 1.0);
|
||||
float glowAlpha = g * g * uHoverGlowAlpha;
|
||||
float total = coverage + glowAlpha * (1.0 - coverage);
|
||||
if (total > 0.0) {
|
||||
color = mix(vec3(1.0), color, coverage / total);
|
||||
coverage = total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragColor = vec4(color, vPlayerColor.a * coverage);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ uniform float uHoverFadeAlpha; // alpha multiplier applied to that player's name
|
||||
out vec2 vUV;
|
||||
out vec4 vPlayerColor; // player territory color (rgb) + alpha
|
||||
out float vNameShade; // name fill grayscale shade (0.0 = black)
|
||||
flat out float vHighlight; // 1.0 when this player is hovered (white glow)
|
||||
|
||||
void main() {
|
||||
// 1. Decode instance ID → playerIdx, lineIdx, charPos
|
||||
@@ -57,6 +58,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vPlayerColor = vec4(0.0);
|
||||
vNameShade = 0.0;
|
||||
vHighlight = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vPlayerColor = vec4(0.0);
|
||||
vNameShade = 0.0;
|
||||
vHighlight = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,6 +81,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vPlayerColor = vec4(0.0);
|
||||
vNameShade = 0.0;
|
||||
vHighlight = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,6 +116,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vPlayerColor = vec4(0.0);
|
||||
vNameShade = 0.0;
|
||||
vHighlight = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,6 +141,7 @@ void main() {
|
||||
vUV = vec2(0.0);
|
||||
vPlayerColor = vec4(0.0);
|
||||
vNameShade = 0.0;
|
||||
vHighlight = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,4 +172,5 @@ void main() {
|
||||
vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y));
|
||||
vPlayerColor = vec4(pd2.rgb, pd2.a * hoverAlpha); // player territory color + alpha
|
||||
vNameShade = pd3.z; // name fill grayscale shade (0.0 = black)
|
||||
vHighlight = isHighlighted ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user