Files
webeditor3d/src/document/scene-document.ts

512 lines
18 KiB
TypeScript
Raw Normal View History

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 = 83 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<string, Brush>;
terrains: Record<string, Terrain>;
paths: Record<string, ScenePath>;
modelInstances: Record<string, ModelInstance>;
entities: Record<string, EntityInstance>;
interactionLinks: Record<string, InteractionLink>;
}
export interface ProjectDocument {
version: typeof SCENE_DOCUMENT_VERSION;
name: string;
time: ProjectTimeSettings;
scheduler: ProjectScheduler;
sequences: ProjectSequenceLibrary;
activeSceneId: string;
scenes: Record<string, ProjectScene>;
materials: Record<string, MaterialDef>;
textures: Record<string, never>;
assets: Record<string, ProjectAssetRecord>;
}
export interface SceneDocument {
version: typeof SCENE_DOCUMENT_VERSION;
name: string;
time: ProjectTimeSettings;
scheduler: ProjectScheduler;
sequences: ProjectSequenceLibrary;
world: WorldSettings;
materials: Record<string, MaterialDef>;
textures: Record<string, never>;
assets: Record<string, ProjectAssetRecord>;
brushes: Record<string, Brush>;
terrains: Record<string, Terrain>;
paths: Record<string, ScenePath>;
modelInstances: Record<string, ModelInstance>;
entities: Record<string, EntityInstance>;
interactionLinks: Record<string, InteractionLink>;
}
export function createEmptySceneDocument(
overrides: Partial<
Pick<SceneDocument, "name" | "time" | "world" | "materials">
> = {}
): 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
);
}