Add scene entry and exit interfaces and update spawn logic

This commit is contained in:
2026-04-11 04:34:01 +02:00
parent 5d06b73b12
commit fa705f18bb

View File

@@ -96,6 +96,12 @@ export interface RuntimePlayerStart {
collider: FirstPersonPlayerShape;
}
export interface RuntimeSceneEntry {
entityId: string;
position: Vec3;
yawDegrees: number;
}
export interface RuntimeSoundEmitter {
entityId: string;
position: Vec3;
@@ -129,6 +135,16 @@ export interface RuntimeInteractable {
enabled: boolean;
}
export interface RuntimeSceneExit {
entityId: string;
position: Vec3;
radius: number;
prompt: string;
enabled: boolean;
targetSceneId: string;
targetEntryEntityId: string;
}
export interface RuntimePointLight {
entityId: string;
position: Vec3;
@@ -165,14 +181,16 @@ export interface RuntimeModelInstance {
export interface RuntimeEntityCollection {
playerStarts: RuntimePlayerStart[];
sceneEntries: RuntimeSceneEntry[];
soundEmitters: RuntimeSoundEmitter[];
triggerVolumes: RuntimeTriggerVolume[];
teleportTargets: RuntimeTeleportTarget[];
interactables: RuntimeInteractable[];
sceneExits: RuntimeSceneExit[];
}
export interface RuntimeSpawnPoint {
source: "playerStart" | "fallback";
source: "playerStart" | "sceneEntry" | "fallback";
entityId: string | null;
position: Vec3;
yawDegrees: number;
@@ -193,9 +211,10 @@ export interface RuntimeSceneDefinition {
spawn: RuntimeSpawnPoint;
}
interface BuildRuntimeSceneOptions {
export interface BuildRuntimeSceneOptions {
navigationMode?: RuntimeNavigationMode;
loadedModelAssets?: Record<string, LoadedModelAsset>;
sceneEntryId?: string | null;
}
function cloneVec3(vector: Vec3): Vec3 {
@@ -411,10 +430,12 @@ interface RuntimeSceneCollections {
function buildRuntimeSceneCollections(document: SceneDocument): RuntimeSceneCollections {
const runtimeEntities: RuntimeEntityCollection = {
playerStarts: [],
sceneEntries: [],
soundEmitters: [],
triggerVolumes: [],
teleportTargets: [],
interactables: []
interactables: [],
sceneExits: []
};
const localLights: RuntimeLocalLightCollection = {
pointLights: [],
@@ -451,6 +472,13 @@ function buildRuntimeSceneCollections(document: SceneDocument): RuntimeSceneColl
collider: buildRuntimePlayerShape(entity)
});
break;
case "sceneEntry":
runtimeEntities.sceneEntries.push({
entityId: entity.id,
position: cloneVec3(entity.position),
yawDegrees: entity.yawDegrees
});
break;
case "soundEmitter":
runtimeEntities.soundEmitters.push({
entityId: entity.id,
@@ -493,6 +521,17 @@ function buildRuntimeSceneCollections(document: SceneDocument): RuntimeSceneColl
enabled: entity.enabled
});
break;
case "sceneExit":
runtimeEntities.sceneExits.push({
entityId: entity.id,
position: cloneVec3(entity.position),
radius: entity.radius,
prompt: entity.prompt,
enabled: entity.enabled,
targetSceneId: entity.targetSceneId,
targetEntryEntityId: entity.targetEntryEntityId
});
break;
default:
assertNever(entity);
}
@@ -537,6 +576,42 @@ function buildRuntimePlayerShape(
}
}
function resolveRuntimeSpawn(
playerStart: RuntimePlayerStart | null,
sceneEntries: RuntimeSceneEntry[],
sceneBounds: RuntimeSceneBounds | null,
sceneEntryId: string | null | undefined
): RuntimeSpawnPoint {
if (sceneEntryId !== undefined && sceneEntryId !== null) {
const sceneEntry =
sceneEntries.find((entry) => entry.entityId === sceneEntryId) ?? null;
if (sceneEntry === null) {
throw new Error(
`Runtime build could not resolve Scene Entry ${sceneEntryId}.`
);
}
return {
source: "sceneEntry",
entityId: sceneEntry.entityId,
position: cloneVec3(sceneEntry.position),
yawDegrees: sceneEntry.yawDegrees
};
}
if (playerStart !== null) {
return {
source: "playerStart",
entityId: playerStart.entityId,
position: cloneVec3(playerStart.position),
yawDegrees: playerStart.yawDegrees
};
}
return buildFallbackSpawn(sceneBounds);
}
export function buildRuntimeSceneFromDocument(document: SceneDocument, options: BuildRuntimeSceneOptions = {}): RuntimeSceneDefinition {
assertRuntimeSceneBuildable(document, {
navigationMode: options.navigationMode ?? "orbitVisitor",
@@ -606,14 +681,11 @@ export function buildRuntimeSceneFromDocument(document: SceneDocument, options:
interactionLinks,
playerStart,
playerCollider,
spawn:
playerStart === null
? buildFallbackSpawn(combinedSceneBounds)
: {
source: "playerStart",
entityId: playerStart.entityId,
position: cloneVec3(playerStart.position),
yawDegrees: playerStart.yawDegrees
}
spawn: resolveRuntimeSpawn(
playerStart,
collections.entities.sceneEntries,
combinedSceneBounds,
options.sceneEntryId
)
};
}