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