import type { RuntimeResolvedControlChannelValue, RuntimeResolvedDiscreteControlState } from "../controls/control-surface"; import type { RuntimeSceneDefinition } from "./runtime-scene-build"; function mutateRuntimeLight( runtimeScene: RuntimeSceneDefinition, entityId: string, mutate: ( light: | RuntimeSceneDefinition["localLights"]["pointLights"][number] | RuntimeSceneDefinition["localLights"]["spotLights"][number] ) => void ) { const pointLight = runtimeScene.localLights.pointLights.find( (candidate) => candidate.entityId === entityId ); if (pointLight !== undefined) { mutate(pointLight); return; } const spotLight = runtimeScene.localLights.spotLights.find( (candidate) => candidate.entityId === entityId ); if (spotLight !== undefined) { mutate(spotLight); } } function applyResolvedDiscreteControlStateToRuntimeScene( runtimeScene: RuntimeSceneDefinition, state: RuntimeResolvedDiscreteControlState ) { switch (state.type) { case "projectTimePaused": case "cameraRigOverride": case "actorPresence": case "actorAnimationPlayback": case "actorPathAssignment": // Runtime scene build already resolves scheduler-owned actor state. return; case "modelVisibility": { const modelInstance = runtimeScene.modelInstances.find( (candidate) => candidate.instanceId === state.target.modelInstanceId ); if (modelInstance !== undefined) { modelInstance.visible = state.value; } return; } case "soundPlayback": { const soundEmitter = runtimeScene.entities.soundEmitters.find( (candidate) => candidate.entityId === state.target.entityId ); if (soundEmitter !== undefined) { soundEmitter.autoplay = state.value; } return; } case "modelAnimationPlayback": { const modelInstance = runtimeScene.modelInstances.find( (candidate) => candidate.instanceId === state.target.modelInstanceId ); if (modelInstance !== undefined) { modelInstance.animationClipName = state.clipName ?? undefined; modelInstance.animationAutoplay = state.clipName !== null; modelInstance.animationLoop = state.clipName === null ? undefined : state.loop; } return; } case "lightEnabled": mutateRuntimeLight(runtimeScene, state.target.entityId, (light) => { light.enabled = state.value; }); return; case "lightColor": mutateRuntimeLight(runtimeScene, state.target.entityId, (light) => { light.colorHex = state.value; }); return; case "interactionEnabled": const interactable = runtimeScene.entities.interactables.find( (candidate) => candidate.entityId === state.target.entityId ); if (interactable !== undefined) { interactable.interactionEnabled = state.value; } return; case "ambientLightColor": runtimeScene.world.ambientLight.colorHex = state.value; return; case "sunLightColor": runtimeScene.world.sunLight.colorHex = state.value; return; } } function applyResolvedControlChannelValueToRuntimeScene( runtimeScene: RuntimeSceneDefinition, channelValue: RuntimeResolvedControlChannelValue ) { switch (channelValue.type) { case "lightIntensity": mutateRuntimeLight(runtimeScene, channelValue.descriptor.target.entityId, (light) => { light.intensity = channelValue.value; }); return; case "soundVolume": { const soundEmitter = runtimeScene.entities.soundEmitters.find( (candidate) => candidate.entityId === channelValue.descriptor.target.entityId ); if (soundEmitter !== undefined) { soundEmitter.volume = channelValue.value; } return; } case "ambientLightIntensity": runtimeScene.world.ambientLight.intensity = channelValue.value; return; case "sunLightIntensity": runtimeScene.world.sunLight.intensity = channelValue.value; return; } } export function applyResolvedControlStateToRuntimeScene( runtimeScene: RuntimeSceneDefinition ): RuntimeSceneDefinition { for (const state of runtimeScene.control.resolved.discrete) { applyResolvedDiscreteControlStateToRuntimeScene(runtimeScene, state); } for (const channelValue of runtimeScene.control.resolved.channels) { applyResolvedControlChannelValueToRuntimeScene(runtimeScene, channelValue); } return runtimeScene; }