auto-git:

[change] src/rendering/world-shader-sky.ts
This commit is contained in:
2026-04-22 17:50:28 +02:00
parent e57ec63ed8
commit 9aaab3fb5c

View File

@@ -1,13 +1,20 @@
import type { Vec2, Vec3 } from "../core/vector";
import type { ProjectTimeSettings } from "../document/project-time-settings";
import {
HOURS_PER_DAY,
normalizeTimeOfDayHours,
type ProjectTimeSettings
} from "../document/project-time-settings";
import {
createDefaultWorldTimePhaseProfile,
type WorldBackgroundSettings,
type WorldSettings
} from "../document/world-settings";
import {
resolveRuntimeDayNightWorldState,
resolveRuntimeDayNightPhaseWeights,
resolveRuntimeTimeState,
type RuntimeDayNightPhaseWeights,
type RuntimeDayPhase,
type RuntimeDayNightWorldState,
type RuntimeResolvedTimeState
} from "../runtime-three/runtime-project-time";
@@ -60,10 +67,48 @@ export interface WorldShaderSkyRenderState {
};
}
export interface WorldShaderSkyEnvironmentPhaseStates {
day: WorldShaderSkyRenderState | null;
dawn: WorldShaderSkyRenderState | null;
dusk: WorldShaderSkyRenderState | null;
night: WorldShaderSkyRenderState | null;
}
export interface WorldShaderSkyEnvironmentPhaseBlend {
basePhase: RuntimeDayPhase;
overlayPhase: RuntimeDayPhase | null;
blendAmount: number;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
function wrapTimeForward(hours: number, originHours: number): number {
let wrappedHours = normalizeTimeOfDayHours(hours);
while (wrappedHours < originHours) {
wrappedHours += HOURS_PER_DAY;
}
return wrappedHours;
}
function resolveTimeWindowMidpoint(
startTimeOfDayHours: number,
endTimeOfDayHours: number
): number {
const wrappedEndTime = wrapTimeForward(endTimeOfDayHours, startTimeOfDayHours);
return normalizeTimeOfDayHours(
startTimeOfDayHours + (wrappedEndTime - startTimeOfDayHours) / 2
);
}
function quantizeNumberToBucket(value: number, step: number): number {
return Math.round(value / step);
}
function parseHexColor(colorHex: string): { r: number; g: number; b: number } {
return {
r: Number.parseInt(colorHex.slice(1, 3), 16),
@@ -331,3 +376,203 @@ export function resolveWorldShaderSkyRenderState(
}
};
}
export function resolveWorldShaderSkyEnvironmentPhaseStates(
world: WorldSettings,
timeSettings: ProjectTimeSettings | null
): WorldShaderSkyEnvironmentPhaseStates {
if (world.background.mode !== "shader") {
return {
day: null,
dawn: null,
dusk: null,
night: null
};
}
if (!world.projectTimeLightingEnabled || timeSettings === null) {
const resolvedWorld: RuntimeDayNightWorldState = {
ambientLight: {
...world.ambientLight
},
sunLight: {
...world.sunLight,
direction: {
...world.sunLight.direction
}
},
moonLight: null,
background: {
...world.background
},
nightBackgroundOverlay: null,
daylightFactor: 1
};
const dayState = resolveWorldShaderSkyRenderState(
world,
resolvedWorld,
null,
null
);
return {
day: dayState,
dawn: dayState,
dusk: dayState,
night: dayState
};
}
const dawnHalfDuration =
Math.max(timeSettings.dawnDurationHours, 0.001) / 2;
const duskHalfDuration =
Math.max(timeSettings.duskDurationHours, 0.001) / 2;
const dawnStart = timeSettings.sunriseTimeOfDayHours - dawnHalfDuration;
const dawnEnd = timeSettings.sunriseTimeOfDayHours + dawnHalfDuration;
const duskStart = timeSettings.sunsetTimeOfDayHours - duskHalfDuration;
const duskEnd = timeSettings.sunsetTimeOfDayHours + duskHalfDuration;
const representativeTimes = {
dawn: resolveTimeWindowMidpoint(dawnStart, dawnEnd),
day: resolveTimeWindowMidpoint(dawnEnd, duskStart),
dusk: resolveTimeWindowMidpoint(duskStart, duskEnd),
night: resolveTimeWindowMidpoint(duskEnd, dawnStart)
};
const resolveRepresentativeState = (
timeOfDayHours: number
): WorldShaderSkyRenderState | null => {
const resolvedTime = resolveRuntimeTimeState(timeSettings, {
timeOfDayHours,
dayCount: 0,
dayLengthMinutes: timeSettings.dayLengthMinutes
});
const resolvedWorld = resolveRuntimeDayNightWorldState(
world,
timeSettings,
{
timeOfDayHours,
dayCount: 0,
dayLengthMinutes: timeSettings.dayLengthMinutes
},
resolvedTime
);
return resolveWorldShaderSkyRenderState(
world,
resolvedWorld,
resolvedTime,
timeSettings
);
};
return {
day: resolveRepresentativeState(representativeTimes.day),
dawn: resolveRepresentativeState(representativeTimes.dawn),
dusk: resolveRepresentativeState(representativeTimes.dusk),
night: resolveRepresentativeState(representativeTimes.night)
};
}
export function createWorldShaderSkyEnvironmentCacheKey(
state: WorldShaderSkyRenderState
): string {
return JSON.stringify({
presetId: state.presetId,
sky: [
state.sky.topColorHex,
state.sky.bottomColorHex,
quantizeNumberToBucket(state.sky.horizonHeight, 0.01)
],
sun: [
state.celestial.sunColorHex,
quantizeNumberToBucket(state.celestial.sunDirection.x, 0.01),
quantizeNumberToBucket(state.celestial.sunDirection.y, 0.01),
quantizeNumberToBucket(state.celestial.sunDirection.z, 0.01),
quantizeNumberToBucket(state.celestial.sunIntensity, 0.05),
quantizeNumberToBucket(state.celestial.sunDiscSizeDegrees, 0.05),
state.celestial.sunVisible ? 1 : 0
],
moon: [
state.celestial.moonColorHex,
quantizeNumberToBucket(state.celestial.moonDirection.x, 0.01),
quantizeNumberToBucket(state.celestial.moonDirection.y, 0.01),
quantizeNumberToBucket(state.celestial.moonDirection.z, 0.01),
quantizeNumberToBucket(state.celestial.moonIntensity, 0.05),
quantizeNumberToBucket(state.celestial.moonDiscSizeDegrees, 0.05),
state.celestial.moonVisible ? 1 : 0
],
stars: [
quantizeNumberToBucket(state.stars.density, 0.05),
quantizeNumberToBucket(state.stars.brightness, 0.05),
quantizeNumberToBucket(state.stars.visibility, 0.05),
quantizeNumberToBucket(state.stars.horizonFadeOffset, 0.01),
quantizeNumberToBucket(state.stars.rotationRadians, 0.02)
],
clouds: [
state.clouds.tintHex,
quantizeNumberToBucket(state.clouds.coverage, 0.05),
quantizeNumberToBucket(state.clouds.density, 0.05),
quantizeNumberToBucket(state.clouds.softness, 0.05),
quantizeNumberToBucket(state.clouds.scale, 0.05),
quantizeNumberToBucket(state.clouds.height, 0.05),
quantizeNumberToBucket(state.clouds.heightVariation, 0.05),
quantizeNumberToBucket(state.clouds.opacity, 0.05),
quantizeNumberToBucket(state.clouds.opacityRandomness, 0.05),
quantizeNumberToBucket(state.clouds.driftOffset.x, 0.02),
quantizeNumberToBucket(state.clouds.driftOffset.y, 0.02)
]
});
}
export function createWorldShaderSkyEnvironmentPhaseCacheKey(
states: WorldShaderSkyEnvironmentPhaseStates
): string {
return JSON.stringify({
day:
states.day === null
? null
: createWorldShaderSkyEnvironmentCacheKey(states.day),
dawn:
states.dawn === null
? null
: createWorldShaderSkyEnvironmentCacheKey(states.dawn),
dusk:
states.dusk === null
? null
: createWorldShaderSkyEnvironmentCacheKey(states.dusk),
night:
states.night === null
? null
: createWorldShaderSkyEnvironmentCacheKey(states.night)
});
}
export function resolveWorldShaderSkyEnvironmentPhaseBlend(
state: Pick<WorldShaderSkyRenderState, "time">
): WorldShaderSkyEnvironmentPhaseBlend | null {
const activePhases = (
["day", "dawn", "dusk", "night"] as const
).filter((phase) => state.time.phaseWeights[phase] > 1e-4);
if (activePhases.length === 0) {
return null;
}
if (activePhases.length === 1) {
return {
basePhase: activePhases[0],
overlayPhase: null,
blendAmount: 0
};
}
const [basePhase, overlayPhase] = activePhases;
const baseWeight = state.time.phaseWeights[basePhase];
const overlayWeight = state.time.phaseWeights[overlayPhase];
const totalWeight = Math.max(baseWeight + overlayWeight, 1e-6);
return {
basePhase,
overlayPhase,
blendAmount: overlayWeight / totalWeight
};
}