Add simulation update and runtime scene handling in ViewportHost

This commit is contained in:
2026-04-14 20:44:38 +02:00
parent f2dbd80724
commit 753aa75aa4

View File

@@ -185,6 +185,11 @@ import { resolveViewportFocusTarget } from "./viewport-focus";
import {
createSoundEmitterMarkerMeshes
} from "./viewport-entity-markers";
import {
resolveRuntimeDayNightWorldState,
type RuntimeClockState
} from "../runtime-three/runtime-project-time";
import type { RuntimeSceneDefinition } from "../runtime-three/runtime-scene-build";
import { resolveTransformPointerDownIntent } from "./transform-pointer-intent";
import { resolveDominantLocalAxisForWorldAxis } from "./transform-axis-mapping";
import {
@@ -329,6 +334,7 @@ interface EntityRenderObjects {
interface LocalLightRenderObjects {
group: Group;
light: PointLight | SpotLight;
}
export class ViewportHost {
@@ -404,6 +410,8 @@ export class ViewportHost {
>();
private currentDocument: SceneDocument | null = null;
private currentWorld: WorldSettings | null = null;
private currentSimulationScene: RuntimeSceneDefinition | null = null;
private currentSimulationClock: RuntimeClockState | null = null;
private currentAdvancedRenderingSettings: AdvancedRenderingSettings | null =
null;
private advancedRenderingComposer: EffectComposer | null = null;
@@ -632,6 +640,23 @@ export class ViewportHost {
this.applyWorld();
}
updateSimulation(
runtimeScene: RuntimeSceneDefinition | null,
clock: RuntimeClockState | null
) {
this.currentSimulationScene = runtimeScene;
this.currentSimulationClock = clock;
this.applyWorld();
if (this.currentDocument === null) {
return;
}
this.rebuildLocalLights(this.currentDocument);
this.rebuildEntityMarkers(this.currentDocument, this.currentSelection);
this.rebuildModelInstances(this.currentDocument, this.currentSelection);
}
updateDocument(document: SceneDocument, selection: EditorSelection) {
this.currentDocument = document;
this.currentSelection = selection;
@@ -1240,23 +1265,36 @@ export class ViewportHost {
return;
}
const world = this.currentWorld;
const world = this.currentSimulationScene?.world ?? this.currentWorld;
const resolvedWorld =
this.currentSimulationScene !== null && this.currentSimulationClock !== null
? resolveRuntimeDayNightWorldState(
world,
this.currentSimulationScene.time,
this.currentSimulationClock
)
: null;
const rendererSettings =
this.displayMode !== "normal"
? {
...cloneAdvancedRenderingSettings(world.advancedRendering),
enabled: false
}
...cloneAdvancedRenderingSettings(world.advancedRendering),
enabled: false
}
: world.advancedRendering;
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;
const displayedAmbientLight =
resolvedWorld?.ambientLight ?? world.ambientLight;
const displayedSunLight = resolvedWorld?.sunLight ?? world.sunLight;
const displayedBackground = resolvedWorld?.background ?? world.background;
this.ambientLight.color.set(displayedAmbientLight.colorHex);
this.ambientLight.intensity = displayedAmbientLight.intensity;
this.sunLight.color.set(displayedSunLight.colorHex);
this.sunLight.intensity = displayedSunLight.intensity;
this.sunLight.position
.set(
world.sunLight.direction.x,
world.sunLight.direction.y,
world.sunLight.direction.z
displayedSunLight.direction.x,
displayedSunLight.direction.y,
displayedSunLight.direction.z
)
.normalize()
.multiplyScalar(18);
@@ -1268,12 +1306,12 @@ export class ViewportHost {
this.scene.background = null;
this.scene.environment = null;
this.scene.environmentIntensity = 1;
} else if (world.background.mode === "image") {
} else if (displayedBackground.mode === "image") {
const texture =
this.loadedImageAssets[world.background.assetId]?.texture ?? null;
this.loadedImageAssets[displayedBackground.assetId]?.texture ?? null;
this.scene.background = texture;
this.scene.environment = texture;
this.scene.environmentIntensity = world.background.environmentIntensity;
this.scene.environmentIntensity = displayedBackground.environmentIntensity;
} else {
this.scene.background = null;
this.scene.environment = null;
@@ -3397,6 +3435,27 @@ export class ViewportHost {
private rebuildLocalLights(document: SceneDocument) {
this.clearLocalLights();
if (this.currentSimulationScene !== null) {
for (const pointLight of this.currentSimulationScene.localLights.pointLights) {
const renderObjects = this.createPointLightRuntimeObjects(pointLight);
renderObjects.group.visible =
pointLight.enabled && this.displayMode !== "wireframe";
this.localLightGroup.add(renderObjects.group);
this.localLightRenderObjects.set(pointLight.entityId, renderObjects);
}
for (const spotLight of this.currentSimulationScene.localLights.spotLights) {
const renderObjects = this.createSpotLightRuntimeObjects(spotLight);
renderObjects.group.visible =
spotLight.enabled && this.displayMode !== "wireframe";
this.localLightGroup.add(renderObjects.group);
this.localLightRenderObjects.set(spotLight.entityId, renderObjects);
}
this.applyShadowState();
return;
}
for (const entity of getEntityInstances(document.entities)) {
if (!entity.enabled) {
continue;
@@ -3694,6 +3753,23 @@ export class ViewportHost {
selection: EditorSelection
) {
this.clearEntityMarkers();
const runtimeNpcDefinitionsByEntityId = new Map(
(this.currentSimulationScene?.npcDefinitions ?? []).map((npc) => [
npc.entityId,
npc
])
);
const runtimeInteractablesByEntityId = new Map(
(this.currentSimulationScene?.entities.interactables ?? []).map(
(interactable) => [interactable.entityId, interactable]
)
);
const runtimeSceneExitsByEntityId = new Map(
(this.currentSimulationScene?.entities.sceneExits ?? []).map((sceneExit) => [
sceneExit.entityId,
sceneExit
])
);
for (const entity of getEntityInstances(document.entities)) {
if (!entity.enabled || !entity.visible) {
@@ -3702,7 +3778,59 @@ export class ViewportHost {
const selected =
selection.kind === "entities" && selection.ids.includes(entity.id);
const renderObjects = this.createEntityRenderObjects(entity, selected);
let renderObjects: EntityRenderObjects | null = null;
switch (entity.kind) {
case "npc": {
const runtimeNpc = runtimeNpcDefinitionsByEntityId.get(entity.id);
if (runtimeNpc !== undefined) {
if (!runtimeNpc.active || !runtimeNpc.visible) {
continue;
}
renderObjects = this.createNpcRenderObjects(
{
...entity,
position: runtimeNpc.position,
yawDegrees: runtimeNpc.yawDegrees
},
selected
);
break;
}
renderObjects = this.createEntityRenderObjects(entity, selected);
break;
}
case "interactable": {
const runtimeInteractable =
runtimeInteractablesByEntityId.get(entity.id) ?? null;
renderObjects = this.createInteractableRenderObjects(
entity.id,
entity.position,
entity.radius,
selected,
runtimeInteractable?.interactionEnabled ?? entity.interactionEnabled
);
break;
}
case "sceneExit": {
const runtimeSceneExit = runtimeSceneExitsByEntityId.get(entity.id) ?? null;
renderObjects = this.createInteractableRenderObjects(
entity.id,
entity.position,
entity.radius,
selected,
runtimeSceneExit?.interactionEnabled ?? entity.interactionEnabled,
selected ? SCENE_EXIT_SELECTED_COLOR : SCENE_EXIT_COLOR
);
break;
}
default:
renderObjects = this.createEntityRenderObjects(entity, selected);
break;
}
if (this.displayMode === "wireframe") {
this.applyWireframePresentation(renderObjects.group);