diff --git a/src/client/render/gl/RenderOverrides.ts b/src/client/render/gl/RenderOverrides.ts index 15c582d71..10eb57b68 100644 --- a/src/client/render/gl/RenderOverrides.ts +++ b/src/client/render/gl/RenderOverrides.ts @@ -28,14 +28,13 @@ export function applyGraphicsOverrides( 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. + // Classic look: lighter player-colored shape behind a darkened + // player-colored icon glyph (matching the old canvas renderer's + // structureColors().dark), with a touch of translucency. settings.structure.borderDarken = 0.7; settings.structure.fillDarken = 1.0; - settings.structure.iconR = 0; - settings.structure.iconG = 0; - settings.structure.iconB = 0; - settings.structure.iconAlpha = 0.75; + settings.structure.iconDarken = 0.3; + settings.structure.iconAlpha = 0.9; } if (overrides.mapOverlay?.highlightFillBrighten !== undefined) { settings.mapOverlay.highlightFillBrighten = diff --git a/src/client/render/gl/RenderSettings.ts b/src/client/render/gl/RenderSettings.ts index 936790a6a..b64491e77 100644 --- a/src/client/render/gl/RenderSettings.ts +++ b/src/client/render/gl/RenderSettings.ts @@ -182,6 +182,11 @@ export interface RenderSettings { iconR: number; iconG: number; iconB: number; + /** + * When > 0, the icon glyph is a darkened version of the player color + * (HSV value multiplier) instead of the flat iconR/G/B color. 0 = off. + */ + iconDarken: number; }; structureLevel: { scale: number; diff --git a/src/client/render/gl/passes/StructurePass.ts b/src/client/render/gl/passes/StructurePass.ts index be2484bb2..fb0a5588b 100644 --- a/src/client/render/gl/passes/StructurePass.ts +++ b/src/client/render/gl/passes/StructurePass.ts @@ -89,6 +89,7 @@ export class StructurePass { private uBorderDarken: WebGLUniformLocation; private uIconAlpha: WebGLUniformLocation; private uIconColor: WebGLUniformLocation; + private uIconDarken: WebGLUniformLocation; private vao: WebGLVertexArrayObject; private instanceBuf: DynamicInstanceBuffer; @@ -174,6 +175,7 @@ export class StructurePass { this.uBorderDarken = gl.getUniformLocation(this.program, "uBorderDarken")!; this.uIconAlpha = gl.getUniformLocation(this.program, "uIconAlpha")!; this.uIconColor = gl.getUniformLocation(this.program, "uIconColor")!; + this.uIconDarken = gl.getUniformLocation(this.program, "uIconDarken")!; // Texture unit bindings + ghost defaults gl.useProgram(this.program); @@ -370,6 +372,7 @@ export class StructurePass { gl.uniform1f(this.uBorderDarken, ss.borderDarken); gl.uniform1f(this.uIconAlpha, ss.iconAlpha); gl.uniform3f(this.uIconColor, ss.iconR, ss.iconG, ss.iconB); + gl.uniform1f(this.uIconDarken, ss.iconDarken); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index d3664ec64..db285fc1f 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -149,7 +149,8 @@ "iconAlpha": 1.0, "iconR": 1.0, "iconG": 1.0, - "iconB": 1.0 + "iconB": 1.0, + "iconDarken": 0 }, "structureLevel": { "scale": 1.2, diff --git a/src/client/render/gl/shaders/structure/structure.frag.glsl b/src/client/render/gl/shaders/structure/structure.frag.glsl index b6c7c5da9..7ff8c2c39 100644 --- a/src/client/render/gl/shaders/structure/structure.frag.glsl +++ b/src/client/render/gl/shaders/structure/structure.frag.glsl @@ -15,6 +15,7 @@ uniform float uFillDarken; // HSV value multiplier on icon fill uniform float uBorderDarken; // HSV value multiplier on icon border uniform float uIconAlpha; // global multiplier on final icon alpha uniform vec3 uIconColor; // color of the inner icon glyph (was white) +uniform float uIconDarken; // >0: glyph = darkened player color instead of uIconColor in vec2 vLocalPos; in vec2 vAtlasUV; @@ -132,7 +133,8 @@ void main() { } // Composite: tinted icon over player-colored shape - vec3 finalRGB = mix(bgColor.rgb, uIconColor, iconAlpha); + vec3 glyphColor = uIconDarken > 0.0 ? darken(fillColor.rgb, uIconDarken) : uIconColor; + vec3 finalRGB = mix(bgColor.rgb, glyphColor, iconAlpha); // Red X overlay for units marked for deletion if (vMarkedForDeletion > 0.5) { diff --git a/tests/GraphicsOverrides.test.ts b/tests/GraphicsOverrides.test.ts index 9bcd442b6..776ab7a93 100644 --- a/tests/GraphicsOverrides.test.ts +++ b/tests/GraphicsOverrides.test.ts @@ -211,19 +211,17 @@ describe("applyGraphicsOverrides", () => { expect(s.structure).toEqual(defaults.structure); }); - test("classicIcons=true → light shape + dark icon + 0.75 alpha", () => { + test("classicIcons=true → light shape + dark icon + 0.9 alpha", () => { const s = gen({ structure: { classicIcons: true }, }).structure; // Shape (circle behind) is mostly player color, lightly darkened. expect(s.fillDarken).toBe(1.0); expect(s.borderDarken).toBe(0.7); - // Icon glyph itself is black. - expect(s.iconR).toBe(0); - expect(s.iconG).toBe(0); - expect(s.iconB).toBe(0); + // Icon glyph is a darkened version of the player color. + expect(s.iconDarken).toBe(0.3); // Slightly translucent in classic mode. - expect(s.iconAlpha).toBe(0.75); + expect(s.iconAlpha).toBe(0.9); }); test("classicIcons=false or absent → keeps render-settings.json defaults (fully opaque)", () => { @@ -233,12 +231,12 @@ describe("applyGraphicsOverrides", () => { }).structure; expect(off.borderDarken).toBe(defaults.borderDarken); expect(off.fillDarken).toBe(defaults.fillDarken); - expect(off.iconR).toBe(defaults.iconR); + expect(off.iconDarken).toBe(0); expect(off.iconAlpha).toBe(1); const absent = gen({ structure: {} }).structure; expect(absent.borderDarken).toBe(defaults.borderDarken); expect(absent.fillDarken).toBe(defaults.fillDarken); - expect(absent.iconR).toBe(defaults.iconR); + expect(absent.iconDarken).toBe(0); expect(absent.iconAlpha).toBe(1); }); @@ -303,6 +301,6 @@ describe("applyGraphicsOverrides", () => { expect(s.name.nameScaleFactor).toBe(0.9); expect(s.structure.borderDarken).toBe(0.7); expect(s.structure.fillDarken).toBe(1.0); - expect(s.structure.iconAlpha).toBe(0.75); + expect(s.structure.iconAlpha).toBe(0.9); }); });