import { createOpaqueId } from "../core/ids"; import type { WhiteboxSelectionMode } from "../core/whitebox-selection-mode"; import type { Brush } from "./brushes"; import type { ModelInstance } from "../assets/model-instances"; import type { ProjectAssetRecord } from "../assets/project-assets"; import type { EntityInstance } from "../entities/entity-instances"; import type { InteractionLink } from "../interactions/interaction-links"; import { cloneMaterialRegistry, createStarterMaterialRegistry, type MaterialDef } from "../materials/starter-material-library"; import { createDefaultWorldSettings, type WorldSettings } from "./world-settings"; import { createDefaultProjectTimeSettings, type ProjectTimeSettings } from "./project-time-settings"; import { type ScenePath } from "./paths"; import { createEmptyProjectScheduler, type ProjectScheduler } from "../scheduler/project-scheduler"; import { createEmptyProjectSequenceLibrary, type ProjectSequenceLibrary } from "../sequencer/project-sequences"; import type { Terrain } from "./terrains"; export const SCENE_DOCUMENT_VERSION = 90 as const; export const WHITEBOX_FACE_CLIMBABLE_SCENE_DOCUMENT_VERSION = 90 as const; export const GOD_RAYS_SOURCE_SIZE_SCENE_DOCUMENT_VERSION = 89 as const; export const ATMOSPHERE_POLISH_SCENE_DOCUMENT_VERSION = 88 as const; export const GOD_RAYS_SCENE_DOCUMENT_VERSION = 87 as const; export const DISTANCE_FOG_SCENE_DOCUMENT_VERSION = 86 as const; export const DYNAMIC_GLOBAL_ILLUMINATION_SCENE_DOCUMENT_VERSION = 85 as const; export const PLAYER_START_MOUSE_INVERT_SCENE_DOCUMENT_VERSION = 84 as const; export const PLAYER_START_TARGETING_SETTINGS_SCENE_DOCUMENT_VERSION = 83 as const; export const PLAYER_START_INTERACT_BINDINGS_SCENE_DOCUMENT_VERSION = 82 as const; export const PLAYER_START_INTERACTION_ANGLE_SCENE_DOCUMENT_VERSION = 81 as const; export const PLAYER_START_INTERACTION_REACH_SCENE_DOCUMENT_VERSION = 80 as const; export const SHADER_SKY_AURORA_SCENE_DOCUMENT_VERSION = 79 as const; export const CAMERA_RIG_MAPPED_RAIL_SCENE_DOCUMENT_VERSION = 78 as const; export const CAMERA_RIG_CONTROL_SURFACE_SCENE_DOCUMENT_VERSION = 77 as const; export const CAMERA_RIG_RAIL_SCENE_DOCUMENT_VERSION = 76 as const; export const CELESTIAL_ORBIT_SETTINGS_SCENE_DOCUMENT_VERSION = 75 as const; export const CAMERA_RIG_ENTITY_SCENE_DOCUMENT_VERSION = 74 as const; export const SHADER_SKY_STAR_HORIZON_FADE_SCENE_DOCUMENT_VERSION = 73 as const; export const SHADER_SKY_HORIZON_HEIGHT_SCENE_DOCUMENT_VERSION = 72 as const; export const SHADER_SKY_SCENE_DOCUMENT_VERSION = 71 as const; export const CELESTIAL_BODY_OVERLAY_SCENE_DOCUMENT_VERSION = 70 as const; export const WHITEBOX_BOX_LIGHT_VOLUME_SCENE_DOCUMENT_VERSION = 69 as const; export const DAWN_DUSK_BACKGROUND_IMAGE_SCENE_DOCUMENT_VERSION = 68 as const; export const AUTHORED_TERRAIN_COLLISION_SCENE_DOCUMENT_VERSION = 67 as const; export const AUTHORED_TERRAIN_PAINT_SCENE_DOCUMENT_VERSION = 66 as const; export const AUTHORED_TERRAIN_FOUNDATION_SCENE_DOCUMENT_VERSION = 65 as const; export const FOLLOW_ACTOR_PATH_SMOOTH_SCENE_DOCUMENT_VERSION = 64 as const; export const NPC_DIALOGUE_LINE_SPEAKER_REMOVED_SCENE_DOCUMENT_VERSION = 63 as const; export const NPC_ONLY_DIALOGUES_SCENE_DOCUMENT_VERSION = 62 as const; export const WHITEBOX_EXPANDED_PRIMITIVES_SCENE_DOCUMENT_VERSION = 61 as const; export const WHITEBOX_PRIMITIVES_SCENE_DOCUMENT_VERSION = 60 as const; export const STARTER_PBR_MATERIAL_LIBRARY_SCENE_DOCUMENT_VERSION = 59 as const; export const SCENE_TRANSITION_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION = 58 as const; export const PROJECT_SEQUENCE_UNIFIED_VISIBILITY_SCENE_DOCUMENT_VERSION = 57 as const; export const PROJECT_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION = 56 as const; export const PROJECT_SEQUENCE_TIMING_SCENE_DOCUMENT_VERSION = 55 as const; export const PROJECT_SEQUENCE_CLIPS_SCENE_DOCUMENT_VERSION = 54 as const; export const PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION = 53 as const; export const PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION = 52 as const; export const NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION = 51 as const; export const PROJECT_DIALOGUE_LIBRARY_SCENE_DOCUMENT_VERSION = 50 as const; export const SCHEDULER_ACTOR_ROUTINE_EFFECTS_SCENE_DOCUMENT_VERSION = 49 as const; export const SCHEDULER_CONTROL_EFFECTS_SCENE_DOCUMENT_VERSION = 48 as const; export const EXPANDED_CONTROL_SURFACE_SCENE_DOCUMENT_VERSION = 47 as const; export const PROJECT_SCHEDULER_FOUNDATION_SCENE_DOCUMENT_VERSION = 46 as const; export const CONTROL_SURFACE_FOUNDATION_SCENE_DOCUMENT_VERSION = 45 as const; export const NPC_PRESENCE_SCENE_DOCUMENT_VERSION = 44 as const; export const PATH_FOUNDATION_SCENE_DOCUMENT_VERSION = 43 as const; export const NPC_COLLIDER_SCENE_DOCUMENT_VERSION = 42 as const; export const NPC_ENTITY_FOUNDATION_SCENE_DOCUMENT_VERSION = 41 as const; export const WORLD_TIME_ENVIRONMENT_SCENE_DOCUMENT_VERSION = 40 as const; export const PROJECT_TIME_NIGHT_BACKGROUND_SCENE_DOCUMENT_VERSION = 39 as const; export const PROJECT_TIME_DAY_NIGHT_PROFILE_SCENE_DOCUMENT_VERSION = 38 as const; export const PROJECT_TIME_SYSTEM_SCENE_DOCUMENT_VERSION = 37 as const; export const AUTHORED_OBJECT_STATE_SCENE_DOCUMENT_VERSION = 36 as const; export const PLAYER_START_AIR_DIRECTION_CONTROL_SCENE_DOCUMENT_VERSION = 35 as const; export const PLAYER_START_AIR_CONTROL_SCENE_DOCUMENT_VERSION = 34 as const; export const WHITEBOX_BEVEL_SCENE_DOCUMENT_VERSION = 32 as const; export const PLAYER_START_LOCOMOTION_CORE_SCENE_DOCUMENT_VERSION = 32 as const; export const PLAYER_START_MOVEMENT_TEMPLATE_SCENE_DOCUMENT_VERSION = 31 as const; export const STATIC_SIMPLE_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION = 30 as const; export const SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION = 29 as const; export const PROJECT_NAME_SCENE_DOCUMENT_VERSION = 28 as const; export const PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION = 27 as const; export const PLAYER_START_INPUT_BINDINGS_SCENE_DOCUMENT_VERSION = 26 as const; export const PLAYER_START_NAVIGATION_MODE_SCENE_DOCUMENT_VERSION = 25 as const; export const SCENE_TRANSITION_ENTITIES_SCENE_DOCUMENT_VERSION = 24 as const; export const MULTI_SCENE_FOUNDATION_SCENE_DOCUMENT_VERSION = 22 as const; export const WATER_SURFACE_DISPLACEMENT_SCENE_DOCUMENT_VERSION = 21 as const; export const WHITEBOX_BOX_VOLUME_SCENE_DOCUMENT_VERSION = 20 as const; export const WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION = 19 as const; export const WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION = 18 as const; export const PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION = 17 as const; export const IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION = 16 as const; export const ENTITY_NAMES_SCENE_DOCUMENT_VERSION = 15 as const; export const SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION = 13 as const; export const ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION = 12 as const; export const LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION = 10 as const; export const MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION = 9 as const; export const FOUNDATION_SCENE_DOCUMENT_VERSION = 1 as const; export const BOX_BRUSH_SCENE_DOCUMENT_VERSION = 2 as const; export const FACE_MATERIALS_SCENE_DOCUMENT_VERSION = 3 as const; export const RUNNER_V1_SCENE_DOCUMENT_VERSION = 4 as const; export const FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION = 5 as const; export const WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION = 6 as const; export const ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION = 7 as const; export const TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION = 8 as const; export const RUNNER_LOADING_SCREEN_SCENE_DOCUMENT_VERSION = 23 as const; export const DEFAULT_PROJECT_NAME = "Untitled Project" as const; export const DEFAULT_PROJECT_SCENE_ID = "scene-main" as const; export const DEFAULT_SCENE_EDITOR_SNAP_STEP = 1 as const; export type SceneEditorViewportLayoutMode = "single" | "quad"; export type SceneEditorViewportPanelId = | "topLeft" | "topRight" | "bottomLeft" | "bottomRight"; export type SceneEditorViewportViewMode = | "perspective" | "top" | "front" | "side"; export type SceneEditorViewportDisplayMode = | "normal" | "authoring" | "wireframe"; export interface SceneEditorPanelPreferences { viewMode: SceneEditorViewportViewMode; displayMode: SceneEditorViewportDisplayMode; } export interface SceneEditorPreferences { whiteboxSelectionMode: WhiteboxSelectionMode; whiteboxSnapEnabled: boolean; whiteboxSnapStep: number; viewportGridVisible: boolean; viewportLayoutMode: SceneEditorViewportLayoutMode; activeViewportPanelId: SceneEditorViewportPanelId; viewportQuadSplit: { x: number; y: number; }; viewportPanels: Record< SceneEditorViewportPanelId, SceneEditorPanelPreferences >; } export interface SceneLoadingScreenSettings { colorHex: string; headline: string | null; description: string | null; } export interface ProjectScene { id: string; name: string; loadingScreen: SceneLoadingScreenSettings; editorPreferences: SceneEditorPreferences; world: WorldSettings; brushes: Record; terrains: Record; paths: Record; modelInstances: Record; entities: Record; interactionLinks: Record; } export interface ProjectDocument { version: typeof SCENE_DOCUMENT_VERSION; name: string; time: ProjectTimeSettings; scheduler: ProjectScheduler; sequences: ProjectSequenceLibrary; activeSceneId: string; scenes: Record; materials: Record; textures: Record; assets: Record; } export interface SceneDocument { version: typeof SCENE_DOCUMENT_VERSION; name: string; time: ProjectTimeSettings; scheduler: ProjectScheduler; sequences: ProjectSequenceLibrary; world: WorldSettings; materials: Record; textures: Record; assets: Record; brushes: Record; terrains: Record; paths: Record; modelInstances: Record; entities: Record; interactionLinks: Record; } export function createEmptySceneDocument( overrides: Partial< Pick > = {} ): SceneDocument { return { version: SCENE_DOCUMENT_VERSION, name: overrides.name ?? "Untitled Scene", time: overrides.time ?? createDefaultProjectTimeSettings(), scheduler: createEmptyProjectScheduler(), sequences: createEmptyProjectSequenceLibrary(), world: overrides.world ?? createDefaultWorldSettings(), materials: cloneMaterialRegistry( overrides.materials ?? createStarterMaterialRegistry() ), textures: {}, assets: {}, brushes: {}, terrains: {}, paths: {}, modelInstances: {}, entities: {}, interactionLinks: {} }; } export function createEmptyProjectScene( overrides: Partial< Pick< ProjectScene, "id" | "name" | "loadingScreen" | "editorPreferences" | "world" > > = {} ): ProjectScene { return { id: overrides.id ?? createOpaqueId("scene"), name: overrides.name ?? "Untitled Scene", loadingScreen: cloneSceneLoadingScreenSettings( overrides.loadingScreen ?? createDefaultSceneLoadingScreenSettings() ), editorPreferences: cloneSceneEditorPreferences( overrides.editorPreferences ?? createDefaultSceneEditorPreferences() ), world: overrides.world ?? createDefaultWorldSettings(), brushes: {}, terrains: {}, paths: {}, modelInstances: {}, entities: {}, interactionLinks: {} }; } export function createEmptyProjectDocument( overrides: Partial< Pick< ProjectDocument, | "name" | "time" | "scheduler" | "sequences" | "activeSceneId" | "materials" | "textures" | "assets" > > & { sceneId?: string; sceneName?: string; world?: WorldSettings; } = {} ): ProjectDocument { const initialScene = createEmptyProjectScene({ id: overrides.sceneId ?? overrides.activeSceneId ?? DEFAULT_PROJECT_SCENE_ID, name: overrides.sceneName, world: overrides.world }); return { version: SCENE_DOCUMENT_VERSION, name: overrides.name ?? DEFAULT_PROJECT_NAME, time: overrides.time ?? createDefaultProjectTimeSettings(), scheduler: overrides.scheduler ?? createEmptyProjectScheduler(), sequences: overrides.sequences ?? createEmptyProjectSequenceLibrary(), activeSceneId: initialScene.id, scenes: { [initialScene.id]: initialScene }, materials: cloneMaterialRegistry( overrides.materials ?? createStarterMaterialRegistry() ), textures: overrides.textures ?? {}, assets: overrides.assets ?? {} }; } export function getProjectScene( projectDocument: ProjectDocument, sceneId = projectDocument.activeSceneId ): ProjectScene { const scene = projectDocument.scenes[sceneId]; if (scene === undefined) { throw new Error(`Project scene ${sceneId} does not exist.`); } return scene; } export function createSceneDocumentFromProject( projectDocument: ProjectDocument, sceneId = projectDocument.activeSceneId ): SceneDocument { const scene = getProjectScene(projectDocument, sceneId); return { version: projectDocument.version, name: scene.name, time: projectDocument.time, scheduler: projectDocument.scheduler, sequences: projectDocument.sequences, world: scene.world, materials: projectDocument.materials, textures: projectDocument.textures, assets: projectDocument.assets, brushes: scene.brushes, terrains: scene.terrains, paths: scene.paths, modelInstances: scene.modelInstances, entities: scene.entities, interactionLinks: scene.interactionLinks }; } export function createProjectDocumentFromSceneDocument( sceneDocument: SceneDocument, sceneId = DEFAULT_PROJECT_SCENE_ID, projectName = DEFAULT_PROJECT_NAME ): ProjectDocument { return { version: SCENE_DOCUMENT_VERSION, name: projectName, time: sceneDocument.time, scheduler: sceneDocument.scheduler, sequences: sceneDocument.sequences, activeSceneId: sceneId, scenes: { [sceneId]: { id: sceneId, name: sceneDocument.name, loadingScreen: createDefaultSceneLoadingScreenSettings(), editorPreferences: createDefaultSceneEditorPreferences(), world: sceneDocument.world, brushes: sceneDocument.brushes, terrains: sceneDocument.terrains, paths: sceneDocument.paths, modelInstances: sceneDocument.modelInstances, entities: sceneDocument.entities, interactionLinks: sceneDocument.interactionLinks } }, materials: sceneDocument.materials, textures: sceneDocument.textures, assets: sceneDocument.assets }; } export function applySceneDocumentToProject( projectDocument: ProjectDocument, sceneId: string, sceneDocument: SceneDocument ): ProjectDocument { const previousScene = getProjectScene(projectDocument, sceneId); return { ...projectDocument, version: SCENE_DOCUMENT_VERSION, time: sceneDocument.time, scheduler: sceneDocument.scheduler, sequences: sceneDocument.sequences, materials: sceneDocument.materials, textures: sceneDocument.textures, assets: sceneDocument.assets, scenes: { ...projectDocument.scenes, [sceneId]: { ...previousScene, name: sceneDocument.name, world: sceneDocument.world, brushes: sceneDocument.brushes, terrains: sceneDocument.terrains, paths: sceneDocument.paths, modelInstances: sceneDocument.modelInstances, entities: sceneDocument.entities, interactionLinks: sceneDocument.interactionLinks } } }; } export function createDefaultSceneLoadingScreenSettings(): SceneLoadingScreenSettings { return { colorHex: "#0d1117", headline: null, description: null }; } export function createDefaultSceneEditorPreferences(): SceneEditorPreferences { return { whiteboxSelectionMode: "object", whiteboxSnapEnabled: true, whiteboxSnapStep: DEFAULT_SCENE_EDITOR_SNAP_STEP, viewportGridVisible: true, viewportLayoutMode: "single", activeViewportPanelId: "topLeft", viewportQuadSplit: { x: 0.5, y: 0.5 }, viewportPanels: { topLeft: { viewMode: "perspective", displayMode: "normal" }, topRight: { viewMode: "top", displayMode: "authoring" }, bottomLeft: { viewMode: "front", displayMode: "authoring" }, bottomRight: { viewMode: "side", displayMode: "authoring" } } }; } export function cloneSceneEditorPreferences( preferences: SceneEditorPreferences ): SceneEditorPreferences { return { whiteboxSelectionMode: preferences.whiteboxSelectionMode, whiteboxSnapEnabled: preferences.whiteboxSnapEnabled, whiteboxSnapStep: preferences.whiteboxSnapStep, viewportGridVisible: preferences.viewportGridVisible, viewportLayoutMode: preferences.viewportLayoutMode, activeViewportPanelId: preferences.activeViewportPanelId, viewportQuadSplit: { ...preferences.viewportQuadSplit }, viewportPanels: { topLeft: { ...preferences.viewportPanels.topLeft }, topRight: { ...preferences.viewportPanels.topRight }, bottomLeft: { ...preferences.viewportPanels.bottomLeft }, bottomRight: { ...preferences.viewportPanels.bottomRight } } }; } export function cloneSceneLoadingScreenSettings( settings: SceneLoadingScreenSettings ): SceneLoadingScreenSettings { return { colorHex: settings.colorHex, headline: settings.headline, description: settings.description }; } export function areSceneLoadingScreenSettingsEqual( left: SceneLoadingScreenSettings, right: SceneLoadingScreenSettings ): boolean { return ( left.colorHex === right.colorHex && left.headline === right.headline && left.description === right.description ); }