Add Classic Icons toggle to Graphics Settings

Adds a "Classic icons" toggle in the structure-icons section of the
Graphics Settings modal. Off (default) keeps today's renderer look;
on switches to a classic style — lighter player-colored shape behind
a dark icon glyph, with 0.75 alpha for a subtle translucent feel.

Exposes the underlying tuning as new render-settings knobs
(`structure.fillDarken`, `borderDarken`, `iconAlpha`, `iconR/G/B`) and
threads them through the structure shader as uniforms, replacing the
previously hardcoded `darken(_, 0.65)` / `darken(_, 0.35)` calls and
the hardcoded white `vec3(1.0)` icon color. The `classicIcons` boolean
in the override schema is the single user-facing knob; the generator
derives the five underlying field values from it. Extends the
ClientGameRunner live-apply path to copy the `structure` slice too,
and adds tests covering the schema and preset derivation.
This commit is contained in:
evanpelle
2026-05-28 14:47:40 -07:00
parent e938e5936b
commit fc3d80ec73
9 changed files with 164 additions and 8 deletions
@@ -9,6 +9,11 @@ export const GraphicsOverridesSchema = z
darkNames: z.boolean(),
})
.partial(),
structure: z
.object({
classicIcons: z.boolean(),
})
.partial(),
})
.partial();
+20
View File
@@ -102,6 +102,16 @@ export interface RenderSettings {
shapes: Record<string, { scale: number; iconFill: number }>;
highlightOutlineWidth: number;
highlightDimAlpha: number;
/** HSV value multiplier applied to the icon fill (interior). 1.0 = no darkening. */
fillDarken: number;
/** HSV value multiplier applied to the icon border (outer ring). 1.0 = no darkening. */
borderDarken: number;
/** Multiplier on final icon alpha. 1.0 = opaque. */
iconAlpha: number;
/** RGB color of the inner icon glyph */
iconR: number;
iconG: number;
iconB: number;
};
structureLevel: {
scale: number;
@@ -279,6 +289,16 @@ export function generateRenderSettings(
if (overrides.name?.cullThreshold !== undefined) {
settings.name.cullThreshold = overrides.name.cullThreshold;
}
if (overrides.structure?.classicIcons === true) {
// Classic look: lighter player-colored shape behind a dark icon glyph,
// 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;
}
if (overrides.name?.darkNames !== undefined) {
const dark = overrides.name.darkNames;
// Dark: black fill + player-colored outline. Force outline RGB to black
@@ -85,6 +85,10 @@ export class StructurePass {
private uHighlightMask: WebGLUniformLocation;
private uHighlightOutlineW: WebGLUniformLocation;
private uHighlightDimAlpha: WebGLUniformLocation;
private uFillDarken: WebGLUniformLocation;
private uBorderDarken: WebGLUniformLocation;
private uIconAlpha: WebGLUniformLocation;
private uIconColor: WebGLUniformLocation;
private vao: WebGLVertexArrayObject;
private instanceBuf: DynamicInstanceBuffer;
@@ -166,6 +170,10 @@ export class StructurePass {
this.program,
"uHighlightDimAlpha",
)!;
this.uFillDarken = gl.getUniformLocation(this.program, "uFillDarken")!;
this.uBorderDarken = gl.getUniformLocation(this.program, "uBorderDarken")!;
this.uIconAlpha = gl.getUniformLocation(this.program, "uIconAlpha")!;
this.uIconColor = gl.getUniformLocation(this.program, "uIconColor")!;
// Texture unit bindings + ghost defaults
gl.useProgram(this.program);
@@ -358,6 +366,10 @@ export class StructurePass {
gl.uniform1i(this.uHighlightMask, this.highlightMask);
gl.uniform1f(this.uHighlightOutlineW, ss.highlightOutlineWidth);
gl.uniform1f(this.uHighlightDimAlpha, ss.highlightDimAlpha);
gl.uniform1f(this.uFillDarken, ss.fillDarken);
gl.uniform1f(this.uBorderDarken, ss.borderDarken);
gl.uniform1f(this.uIconAlpha, ss.iconAlpha);
gl.uniform3f(this.uIconColor, ss.iconR, ss.iconG, ss.iconB);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.paletteTex);
+7 -1
View File
@@ -116,7 +116,13 @@
}
},
"highlightOutlineWidth": 0.04,
"highlightDimAlpha": 0.3
"highlightDimAlpha": 0.3,
"fillDarken": 0.65,
"borderDarken": 0.35,
"iconAlpha": 1.0,
"iconR": 1.0,
"iconG": 1.0,
"iconB": 1.0
},
"structureLevel": {
"scale": 1.2,
@@ -11,6 +11,10 @@ uniform int uAltView;
uniform int uHighlightMask; // bitmask of atlas columns to highlight (0 = off)
uniform float uHighlightOutlineW; // outline width for highlighted structures
uniform float uHighlightDimAlpha; // alpha multiplier for non-highlighted structures
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)
in vec2 vLocalPos;
in vec2 vAtlasUV;
@@ -91,8 +95,8 @@ void main() {
if (uAltView != 0 && vUnderConstruction < 0.5) {
vec3 ac = texelFetch(uAffiliation, ivec2(int(vOwnerID), 1), 0).rgb;
fillColor = vec4(darken(ac, 0.65), 1.0);
borderColor = vec4(darken(ac, 0.35), 1.0);
fillColor = vec4(darken(ac, uFillDarken), 1.0);
borderColor = vec4(darken(ac, uBorderDarken), 1.0);
} else if (vUnderConstruction > 0.5) {
fillColor = vec4(198.0/255.0, 198.0/255.0, 198.0/255.0, 1.0);
borderColor = vec4(127.0/255.0, 127.0/255.0, 127.0/255.0, 1.0);
@@ -102,8 +106,8 @@ void main() {
borderColor = texture(uPalette, vec2(u, 0.75));
// Darken via HSV value so hue/saturation stay intact
// vScale < 1.0 = darker, > 1.0 = brighter
fillColor.rgb = darken(fillColor.rgb, 0.65);
borderColor.rgb = darken(borderColor.rgb, 0.35);
fillColor.rgb = darken(fillColor.rgb, uFillDarken);
borderColor.rgb = darken(borderColor.rgb, uBorderDarken);
fillColor.a = 1.0;
borderColor.a = 1.0;
}
@@ -127,8 +131,8 @@ void main() {
iconAlpha = iconSample.a * borderMask * inBounds;
}
// Composite: white icon over player-colored shape
vec3 finalRGB = mix(bgColor.rgb, vec3(1.0), iconAlpha);
// Composite: tinted icon over player-colored shape
vec3 finalRGB = mix(bgColor.rgb, uIconColor, iconAlpha);
// Red X overlay for units marked for deletion
if (vMarkedForDeletion > 0.5) {
@@ -147,7 +151,7 @@ void main() {
float tintActive = step(0.01, dot(uOutlineColor, uOutlineColor));
finalRGB = mix(finalRGB, uOutlineColor, tintActive * 0.5);
float finalAlpha = bgColor.a * outerAlpha * uGhostAlpha;
float finalAlpha = bgColor.a * outerAlpha * uGhostAlpha * uIconAlpha;
// Build-button hover highlight: white outline on matching types, dim the rest
if (uHighlightMask != 0) {