mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
replace day/night cycle with a binary light/dark mode tied to UserSettings
The cycling sun/moon animation was distracting and not a fan favorite. Drops the cycle path entirely — RenderSettings.dayNight.mode is now "light" | "dark", and the cycle-only fields (cycleTicks, startPhase, noonHold, nightHold) plus the passEnabled.dayNight toggle are gone. getAmbient is a one-liner. The in-game mode follows the existing darkMode UserSetting (same one that drives the page-level CSS class); ClientGameRunner applies it on startup and on the per-key change event.
This commit is contained in:
@@ -29,7 +29,11 @@ import {
|
||||
} from "../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../core/game/GameView";
|
||||
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import {
|
||||
DARK_MODE_KEY,
|
||||
USER_SETTINGS_CHANGED_EVENT,
|
||||
UserSettings,
|
||||
} from "../core/game/UserSettings";
|
||||
import { WorkerClient } from "../core/worker/WorkerClient";
|
||||
import { getPersistentID } from "./Auth";
|
||||
import {
|
||||
@@ -429,6 +433,18 @@ async function createClientGame(
|
||||
const { view, glCanvas, cachedWebGLFrameCallback } =
|
||||
createWebGLView(gameMap);
|
||||
|
||||
// Bind the WebGL renderer's day/night mode to the existing darkMode
|
||||
// UserSetting so the in-game map matches the rest of the UI. Initial
|
||||
// apply + live updates via the per-key settings-changed event.
|
||||
const applyDayNightMode = (isDark: boolean): void => {
|
||||
view.getSettings().dayNight.mode = isDark ? "dark" : "light";
|
||||
};
|
||||
applyDayNightMode(userSettings.darkMode());
|
||||
globalThis.addEventListener(
|
||||
`${USER_SETTINGS_CHANGED_EVENT}:${DARK_MODE_KEY}`,
|
||||
(e) => applyDayNightMode((e as CustomEvent<string>).detail === "true"),
|
||||
);
|
||||
|
||||
const gameRenderer = createRenderer(
|
||||
inputOverlay,
|
||||
gameView,
|
||||
|
||||
@@ -18,7 +18,6 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
|
||||
toggle(s.passEnabled, "railroad", d.passEnabled),
|
||||
toggle(s.passEnabled, "fx", d.passEnabled),
|
||||
toggle(s.passEnabled, "bar", d.passEnabled),
|
||||
toggle(s.passEnabled, "dayNight", d.passEnabled),
|
||||
toggle(s.passEnabled, "nameDebug", d.passEnabled, "Name Debug Boxes"),
|
||||
]),
|
||||
|
||||
@@ -50,25 +49,7 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] {
|
||||
]),
|
||||
|
||||
folder("Day / Night", [
|
||||
select(
|
||||
s.dayNight,
|
||||
"mode",
|
||||
d.dayNight,
|
||||
["light", "dark", "cycle"],
|
||||
"Mode",
|
||||
),
|
||||
slider(s.dayNight, "cycleTicks", d.dayNight, 60, 6000, 10),
|
||||
slider(
|
||||
s.dayNight,
|
||||
"startPhase",
|
||||
d.dayNight,
|
||||
0,
|
||||
1,
|
||||
0.01,
|
||||
"Start Phase (0=noon)",
|
||||
),
|
||||
slider(s.dayNight, "noonHold", d.dayNight, 0, 1, 0.01, "Noon Hold"),
|
||||
slider(s.dayNight, "nightHold", d.dayNight, 0, 1, 0.01, "Night Hold"),
|
||||
select(s.dayNight, "mode", d.dayNight, ["light", "dark"], "Mode"),
|
||||
slider(s.dayNight, "nightAmbient", d.dayNight, 0, 1, 0.01),
|
||||
slider(s.dayNight, "dayAmbient", d.dayNight, 0, 1, 0.01),
|
||||
slider(s.dayNight, "falloffPower", d.dayNight, 0.5, 5, 0.1),
|
||||
|
||||
@@ -15,11 +15,6 @@ import { createFullscreenQuad, createProgram } from "../utils/gl-utils";
|
||||
import compositeFragSrc from "../shaders/day-night/composite.frag.glsl?raw";
|
||||
import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw";
|
||||
|
||||
function smoothstep(edge0: number, edge1: number, x: number): number {
|
||||
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
export class NightCompositePass {
|
||||
private gl: WebGL2RenderingContext;
|
||||
private settings: RenderSettings;
|
||||
@@ -51,38 +46,9 @@ export class NightCompositePass {
|
||||
// Ambient
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
getAmbient(tick: number): number {
|
||||
getAmbient(): number {
|
||||
const dn = this.settings.dayNight;
|
||||
|
||||
if (dn.mode === "light") return dn.dayAmbient;
|
||||
if (dn.mode === "dark") return dn.nightAmbient;
|
||||
|
||||
// Normalize phase to [0, 1), 0 = noon
|
||||
const phase = (((tick / dn.cycleTicks + dn.startPhase) % 1) + 1) % 1;
|
||||
|
||||
// Clamp holds so they never exceed the full cycle
|
||||
const noonHold = Math.min(dn.noonHold, 1);
|
||||
const nightHold = Math.min(dn.nightHold, Math.max(0, 1 - noonHold));
|
||||
const halfTransition = (1 - noonHold - nightHold) / 2;
|
||||
|
||||
// Region boundaries (all in [0, 1))
|
||||
const duskStart = noonHold / 2;
|
||||
const duskEnd = duskStart + halfTransition; // = 0.5 - nightHold/2
|
||||
const nightEnd = duskEnd + nightHold; // = 0.5 + nightHold/2
|
||||
const dawnEnd = nightEnd + halfTransition; // = 1 - noonHold/2
|
||||
|
||||
let t: number;
|
||||
if (phase < duskStart || phase >= dawnEnd) {
|
||||
t = 1; // noon hold
|
||||
} else if (phase < duskEnd) {
|
||||
t = smoothstep(duskEnd, duskStart, phase); // day → night
|
||||
} else if (phase < nightEnd) {
|
||||
t = 0; // midnight hold
|
||||
} else {
|
||||
t = smoothstep(nightEnd, dawnEnd, phase); // night → day
|
||||
}
|
||||
|
||||
return dn.nightAmbient + (dn.dayAmbient - dn.nightAmbient) * t;
|
||||
return dn.mode === "dark" ? dn.nightAmbient : dn.dayAmbient;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -90,12 +56,12 @@ export class NightCompositePass {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Pure combiner — receives captured scene + lightmap textures, outputs to screen. */
|
||||
draw(tick: number, sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void {
|
||||
draw(sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void {
|
||||
const gl = this.gl;
|
||||
gl.disable(gl.BLEND);
|
||||
|
||||
gl.useProgram(this.compositeProg);
|
||||
gl.uniform1f(this.uCompositeAmbient, this.getAmbient(tick));
|
||||
gl.uniform1f(this.uCompositeAmbient, this.getAmbient());
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, sceneTex);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"railroad": true,
|
||||
"fx": true,
|
||||
"bar": true,
|
||||
"dayNight": true,
|
||||
"nameDebug": false
|
||||
},
|
||||
"falloutBloom": {
|
||||
@@ -34,11 +33,7 @@
|
||||
"heatDecayPerTick": 1
|
||||
},
|
||||
"dayNight": {
|
||||
"mode": "cycle",
|
||||
"cycleTicks": 6000,
|
||||
"startPhase": 0,
|
||||
"noonHold": 0.25,
|
||||
"nightHold": 0.1,
|
||||
"mode": "light",
|
||||
"nightAmbient": 0.15,
|
||||
"dayAmbient": 1,
|
||||
"falloffPower": 2,
|
||||
|
||||
@@ -11,7 +11,6 @@ export interface RenderSettings {
|
||||
railroad: boolean;
|
||||
fx: boolean;
|
||||
bar: boolean;
|
||||
dayNight: boolean;
|
||||
nameDebug: boolean;
|
||||
};
|
||||
falloutBloom: {
|
||||
@@ -36,11 +35,7 @@ export interface RenderSettings {
|
||||
heatDecayPerTick: number;
|
||||
};
|
||||
dayNight: {
|
||||
mode: "light" | "dark" | "cycle";
|
||||
cycleTicks: number;
|
||||
startPhase: number; // 0–1, where 0 = noon, 0.25 = dusk, 0.5 = midnight, 0.75 = dawn
|
||||
noonHold: number; // fraction of cycle held at full brightness (0–1)
|
||||
nightHold: number; // fraction of cycle held at full darkness (0–1); noonHold+nightHold ≤ 1
|
||||
mode: "light" | "dark";
|
||||
nightAmbient: number;
|
||||
dayAmbient: number;
|
||||
falloffPower: number;
|
||||
|
||||
@@ -135,7 +135,6 @@ export class GPURenderer {
|
||||
|
||||
private animId: number | null = null;
|
||||
private frameTick = 0;
|
||||
private gameTick = 0;
|
||||
private mapW = 0;
|
||||
private mapH = 0;
|
||||
|
||||
@@ -598,7 +597,6 @@ export class GPURenderer {
|
||||
updateUnits(units: Map<number, UnitState>, gameTick: number): void {
|
||||
this.lastUnits = units;
|
||||
this.frameTick++;
|
||||
this.gameTick = gameTick;
|
||||
this.unitPass.updateUnits(units, this.frameTick);
|
||||
this.barPass.updateBars(units, this.lastStructures, gameTick);
|
||||
this.pointLightPass.updateLights(units);
|
||||
@@ -1015,7 +1013,7 @@ export class GPURenderer {
|
||||
);
|
||||
const lightTex = this.lightmapPass.draw(cam, cw, ch);
|
||||
toScreen(this.gl, cw, ch, () =>
|
||||
this.nightCompositePass.draw(this.gameTick, sceneTex, lightTex),
|
||||
this.nightCompositePass.draw(sceneTex, lightTex),
|
||||
);
|
||||
} else {
|
||||
toScreen(this.gl, cw, ch, () => this.drawBaseLayer(cam));
|
||||
@@ -1025,11 +1023,7 @@ export class GPURenderer {
|
||||
}
|
||||
|
||||
private isNightActive(): boolean {
|
||||
const mode = this.settings.dayNight.mode;
|
||||
return (
|
||||
mode === "dark" ||
|
||||
(mode === "cycle" && this.settings.passEnabled.dayNight)
|
||||
);
|
||||
return this.settings.dayNight.mode === "dark";
|
||||
}
|
||||
|
||||
private resizeSceneTargetIfNeeded(cw: number, ch: number): void {
|
||||
@@ -1099,10 +1093,7 @@ export class GPURenderer {
|
||||
|
||||
if (this.gridView) this.coordinateGridPass.draw(cam, zoom);
|
||||
if (pe.name && !this.gridView)
|
||||
this.namePass.draw(
|
||||
cam,
|
||||
this.nightCompositePass.getAmbient(this.gameTick),
|
||||
);
|
||||
this.namePass.draw(cam, this.nightCompositePass.getAmbient());
|
||||
|
||||
this.radialMenuPass.draw();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user