From f1ab5f82829cab5f50bc33c8d6a0c280f6be0469 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Sat, 11 Apr 2026 03:46:30 +0200 Subject: [PATCH] Introduce ProjectScene and ProjectDocument interfaces with utility functions --- src/document/scene-document.ts | 153 ++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/src/document/scene-document.ts b/src/document/scene-document.ts index 1658f062..a7749233 100644 --- a/src/document/scene-document.ts +++ b/src/document/scene-document.ts @@ -1,3 +1,4 @@ +import { createOpaqueId } from "../core/ids"; import type { Brush } from "./brushes"; import type { ModelInstance } from "../assets/model-instances"; import type { ProjectAssetRecord } from "../assets/project-assets"; @@ -6,7 +7,8 @@ import type { InteractionLink } from "../interactions/interaction-links"; import { cloneMaterialRegistry, createStarterMaterialRegistry, type MaterialDef } from "../materials/starter-material-library"; import { createDefaultWorldSettings, type WorldSettings } from "./world-settings"; -export const SCENE_DOCUMENT_VERSION = 21 as const; +export const SCENE_DOCUMENT_VERSION = 22 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; @@ -27,6 +29,27 @@ 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 DEFAULT_PROJECT_SCENE_ID = "scene-main" as const; + +export interface ProjectScene { + id: string; + name: string; + world: WorldSettings; + brushes: Record; + modelInstances: Record; + entities: Record; + interactionLinks: Record; +} + +export interface ProjectDocument { + version: typeof SCENE_DOCUMENT_VERSION; + activeSceneId: string; + scenes: Record; + materials: Record; + textures: Record; + assets: Record; +} + export interface SceneDocument { version: typeof SCENE_DOCUMENT_VERSION; name: string; @@ -54,3 +77,131 @@ export function createEmptySceneDocument(overrides: Partial> = {} +): ProjectScene { + return { + id: overrides.id ?? createOpaqueId("scene"), + name: overrides.name ?? "Untitled Scene", + world: overrides.world ?? createDefaultWorldSettings(), + brushes: {}, + modelInstances: {}, + entities: {}, + interactionLinks: {} + }; +} + +export function createEmptyProjectDocument( + overrides: Partial< + Pick + > & { + 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, + 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, + world: scene.world, + materials: projectDocument.materials, + textures: projectDocument.textures, + assets: projectDocument.assets, + brushes: scene.brushes, + modelInstances: scene.modelInstances, + entities: scene.entities, + interactionLinks: scene.interactionLinks + }; +} + +export function createProjectDocumentFromSceneDocument( + sceneDocument: SceneDocument, + sceneId = DEFAULT_PROJECT_SCENE_ID +): ProjectDocument { + return { + version: SCENE_DOCUMENT_VERSION, + activeSceneId: sceneId, + scenes: { + [sceneId]: { + id: sceneId, + name: sceneDocument.name, + world: sceneDocument.world, + brushes: sceneDocument.brushes, + 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, + materials: sceneDocument.materials, + textures: sceneDocument.textures, + assets: sceneDocument.assets, + scenes: { + ...projectDocument.scenes, + [sceneId]: { + ...previousScene, + name: sceneDocument.name, + world: sceneDocument.world, + brushes: sceneDocument.brushes, + modelInstances: sceneDocument.modelInstances, + entities: sceneDocument.entities, + interactionLinks: sceneDocument.interactionLinks + } + } + }; +}