Implement runtime clock state management and lighting

This commit is contained in:
2026-04-12 04:32:46 +02:00
parent 6a518f692d
commit 09525d28d4

View File

@@ -82,6 +82,15 @@ import {
type RuntimeInteractionPrompt
} from "./runtime-interaction-system";
import { RuntimeAudioSystem } from "./runtime-audio-system";
import {
advanceRuntimeClockState,
areRuntimeClockStatesEqual,
cloneRuntimeClockState,
createRuntimeClockState,
reconfigureRuntimeClockState,
resolveRuntimeDayNightWorldState,
type RuntimeClockState
} from "./runtime-project-time";
import { ThirdPersonNavigationController } from "./third-person-navigation-controller";
import { resolveUnderwaterFogState } from "./underwater-fog";
import { resolveWaterContact } from "./water-volume-utils";
@@ -213,7 +222,13 @@ export class RuntimeHost {
null;
private currentInteractionPrompt: RuntimeInteractionPrompt | null = null;
private currentSceneLoadState: RuntimeSceneLoadState | null = null;
private currentClockState: RuntimeClockState | null = null;
private lastPublishedClockState: RuntimeClockState | null = null;
private currentPlayerAudioHooks: RuntimePlayerAudioHookState | null = null;
private runtimeClockStateHandler:
| ((state: RuntimeClockState) => void)
| null = null;
private clockPublishAccumulator = 0;
private cameraEffectVerticalOffset = 0;
private cameraEffectVerticalVelocity = 0;
private cameraEffectPitchOffset = 0;
@@ -396,6 +411,7 @@ export class RuntimeHost {
this.sceneReady = false;
this.runtimeScene = runtimeScene;
this.currentWorld = runtimeScene.world;
this.syncRuntimeClockState(runtimeScene.time);
this.activeController?.deactivate(this.controllerContext, {
releasePointerLock: !preservePointerLockDuringLoad
});
@@ -492,6 +508,16 @@ export class RuntimeHost {
}
}
setRuntimeClockStateHandler(
handler: ((state: RuntimeClockState) => void) | null
) {
this.runtimeClockStateHandler = handler;
if (handler !== null && this.currentClockState !== null) {
handler(cloneRuntimeClockState(this.currentClockState));
}
}
setSceneExitHandler(
handler:
| ((request: RuntimeSceneExitTransitionRequest) => void)
@@ -522,6 +548,8 @@ export class RuntimeHost {
this.advancedRenderingComposer = null;
this.currentAdvancedRenderingSettings = null;
this.scene.fog = null;
this.currentClockState = null;
this.lastPublishedClockState = null;
if (this.renderer !== null) {
this.renderer.autoClear = true;
}
@@ -558,6 +586,34 @@ export class RuntimeHost {
this.sceneLoadStateHandler?.(state);
}
private syncRuntimeClockState(timeSettings: RuntimeSceneDefinition["time"]) {
this.currentClockState =
this.currentClockState === null
? createRuntimeClockState(timeSettings)
: reconfigureRuntimeClockState(this.currentClockState, timeSettings);
this.clockPublishAccumulator = 0;
this.publishRuntimeClockState(true);
}
private publishRuntimeClockState(force = false) {
if (this.currentClockState === null) {
return;
}
const nextState = cloneRuntimeClockState(this.currentClockState);
if (
!force &&
this.lastPublishedClockState !== null &&
areRuntimeClockStatesEqual(this.lastPublishedClockState, nextState)
) {
return;
}
this.lastPublishedClockState = nextState;
this.runtimeClockStateHandler?.(cloneRuntimeClockState(nextState));
}
private activateDesiredNavigationController() {
if (this.runtimeScene === null || !this.sceneReady) {
return;
@@ -631,18 +687,6 @@ export class RuntimeHost {
}
const world = this.currentWorld;
this.ambientLight.color.set(world.ambientLight.colorHex);
this.ambientLight.intensity = world.ambientLight.intensity;
this.sunLight.color.set(world.sunLight.colorHex);
this.sunLight.intensity = world.sunLight.intensity;
this.sunLight.position
.set(
world.sunLight.direction.x,
world.sunLight.direction.y,
world.sunLight.direction.z
)
.normalize()
.multiplyScalar(18);
if (world.background.mode === "image") {
const texture =
@@ -656,6 +700,8 @@ export class RuntimeHost {
this.scene.environmentIntensity = 1;
}
this.applyDayNightLighting();
if (this.renderer !== null) {
configureAdvancedRenderingRenderer(
this.renderer,
@@ -667,6 +713,30 @@ export class RuntimeHost {
this.applyShadowState();
}
private applyDayNightLighting() {
if (this.currentWorld === null) {
return;
}
const resolvedWorld = resolveRuntimeDayNightWorldState(
this.currentWorld,
this.currentClockState
);
this.ambientLight.color.set(resolvedWorld.ambientLight.colorHex);
this.ambientLight.intensity = resolvedWorld.ambientLight.intensity;
this.sunLight.color.set(resolvedWorld.sunLight.colorHex);
this.sunLight.intensity = resolvedWorld.sunLight.intensity;
this.sunLight.position
.set(
resolvedWorld.sunLight.direction.x,
resolvedWorld.sunLight.direction.y,
resolvedWorld.sunLight.direction.z
)
.normalize()
.multiplyScalar(18);
}
private async buildCollisionWorld(
requestId: number,
colliders: RuntimeSceneDefinition["colliders"],
@@ -1750,6 +1820,20 @@ export class RuntimeHost {
uniform.value = this.volumeTime;
}
if (this.currentClockState !== null) {
this.currentClockState = advanceRuntimeClockState(
this.currentClockState,
dt
);
this.applyDayNightLighting();
this.clockPublishAccumulator += dt;
if (this.clockPublishAccumulator >= 0.25) {
this.clockPublishAccumulator = 0;
this.publishRuntimeClockState();
}
}
for (const mixer of this.animationMixers.values()) {
mixer.update(dt);
}