From 9fbb65788317c7267a804c2c44b9fd73bb36fcaa Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 31 Mar 2026 05:09:52 +0200 Subject: [PATCH] Add support for first room polish scene document version --- src/document/migrate-scene-document.ts | 76 ++++++++++++++++++++------ 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src/document/migrate-scene-document.ts b/src/document/migrate-scene-document.ts index 3f2d4110..6f8ab127 100644 --- a/src/document/migrate-scene-document.ts +++ b/src/document/migrate-scene-document.ts @@ -13,12 +13,13 @@ import { import { BOX_BRUSH_SCENE_DOCUMENT_VERSION, FACE_MATERIALS_SCENE_DOCUMENT_VERSION, + FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION, FOUNDATION_SCENE_DOCUMENT_VERSION, RUNNER_V1_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, - type SceneDocument, - type WorldSettings + type SceneDocument } from "./scene-document"; +import { isWorldBackgroundMode, type WorldBackgroundSettings, type WorldSettings } from "./world-settings"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -32,6 +33,16 @@ function expectFiniteNumber(value: unknown, label: string): number { return value; } +function expectNonNegativeFiniteNumber(value: unknown, label: string): number { + const numberValue = expectFiniteNumber(value, label); + + if (numberValue < 0) { + throw new Error(`${label} must be zero or greater.`); + } + + return numberValue; +} + function expectString(value: unknown, label: string): string { if (typeof value !== "string") { throw new Error(`${label} must be a string.`); @@ -121,6 +132,12 @@ function readVec3(value: unknown, label: string) { }; } +function assertNonZeroVec3(vector: { x: number; y: number; z: number }, label: string) { + if (vector.x === 0 && vector.y === 0 && vector.z === 0) { + throw new Error(`${label} must not be the zero vector.`); + } +} + function expectMaterialPattern(value: unknown, label: string): MaterialPattern { if (value !== "grid" && value !== "checker" && value !== "stripes" && value !== "diamond") { throw new Error(`${label} must be a supported starter material pattern.`); @@ -303,29 +320,39 @@ function readWorldSettings(value: unknown): WorldSettings { throw new Error("world.sunLight must be an object."); } - const direction = sunLight.direction; + const direction = readVec3(sunLight.direction, "world.sunLight.direction"); + assertNonZeroVec3(direction, "world.sunLight.direction"); - if (!isRecord(direction)) { - throw new Error("world.sunLight.direction must be an object."); + const backgroundMode = expectString(background.mode, "world.background.mode"); + let resolvedBackground: WorldBackgroundSettings; + + if (!isWorldBackgroundMode(backgroundMode)) { + throw new Error("world.background.mode must be a supported background mode."); + } + + if (backgroundMode === "solid") { + resolvedBackground = { + mode: "solid", + colorHex: expectHexColor(background.colorHex, "world.background.colorHex") + }; + } else { + resolvedBackground = { + mode: "verticalGradient", + topColorHex: expectHexColor(background.topColorHex, "world.background.topColorHex"), + bottomColorHex: expectHexColor(background.bottomColorHex, "world.background.bottomColorHex") + }; } return { - background: { - mode: expectLiteralString(background.mode, "solid", "world.background.mode"), - colorHex: expectHexColor(background.colorHex, "world.background.colorHex") - }, + background: resolvedBackground, ambientLight: { colorHex: expectHexColor(ambientLight.colorHex, "world.ambientLight.colorHex"), - intensity: expectFiniteNumber(ambientLight.intensity, "world.ambientLight.intensity") + intensity: expectNonNegativeFiniteNumber(ambientLight.intensity, "world.ambientLight.intensity") }, sunLight: { colorHex: expectHexColor(sunLight.colorHex, "world.sunLight.colorHex"), - intensity: expectFiniteNumber(sunLight.intensity, "world.sunLight.intensity"), - direction: { - x: expectFiniteNumber(direction.x, "world.sunLight.direction.x"), - y: expectFiniteNumber(direction.y, "world.sunLight.direction.y"), - z: expectFiniteNumber(direction.z, "world.sunLight.direction.z") - } + intensity: expectNonNegativeFiniteNumber(sunLight.intensity, "world.sunLight.intensity"), + direction } }; } @@ -452,6 +479,23 @@ export function migrateSceneDocument(source: unknown): SceneDocument { }; } + if (source.version === FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION) { + const materials = readMaterialRegistry(source.materials, "materials"); + + return { + version: SCENE_DOCUMENT_VERSION, + name: expectString(source.name, "name"), + world: readWorldSettings(source.world), + materials, + textures: expectEmptyCollection(source.textures, "textures"), + assets: expectEmptyCollection(source.assets, "assets"), + brushes: readBrushes(source.brushes, materials, false), + modelInstances: expectEmptyCollection(source.modelInstances, "modelInstances"), + entities: readEntities(source.entities), + interactionLinks: expectEmptyCollection(source.interactionLinks, "interactionLinks") + }; + } + if (source.version !== SCENE_DOCUMENT_VERSION) { throw new Error(`Unsupported scene document version: ${String(source.version)}.`); }