359 lines
11 KiB
TypeScript
359 lines
11 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
AUTHORED_OBJECT_STATE_SCENE_DOCUMENT_VERSION,
|
|
DEFAULT_PROJECT_NAME,
|
|
DEFAULT_SCENE_EDITOR_SNAP_STEP,
|
|
PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION,
|
|
PROJECT_TIME_DAY_NIGHT_PROFILE_SCENE_DOCUMENT_VERSION,
|
|
RUNNER_LOADING_SCREEN_SCENE_DOCUMENT_VERSION,
|
|
SCENE_DOCUMENT_VERSION,
|
|
createEmptyProjectDocument,
|
|
createEmptyProjectScene
|
|
} from "../../src/document/scene-document";
|
|
import { createDefaultProjectTimeSettings } from "../../src/document/project-time-settings";
|
|
import { createDefaultWorldSettings } from "../../src/document/world-settings";
|
|
import {
|
|
createSceneEntryEntity,
|
|
createSceneExitEntity
|
|
} from "../../src/entities/entity-instances";
|
|
import {
|
|
parseProjectDocumentJson,
|
|
serializeProjectDocument
|
|
} from "../../src/serialization/scene-document-json";
|
|
|
|
describe("project document JSON", () => {
|
|
it("round-trips the project name and authored scene loading overlay settings", () => {
|
|
const cellarEntry = createSceneEntryEntity({
|
|
id: "entity-scene-entry-cellar-stairs",
|
|
position: {
|
|
x: 1,
|
|
y: 0,
|
|
z: -2
|
|
},
|
|
yawDegrees: 180
|
|
});
|
|
const mainExit = createSceneExitEntity({
|
|
id: "entity-scene-exit-main-hatch",
|
|
position: {
|
|
x: 0,
|
|
y: 1,
|
|
z: 3
|
|
},
|
|
targetSceneId: "scene-cellar",
|
|
targetEntryEntityId: cellarEntry.id
|
|
});
|
|
const document = {
|
|
...createEmptyProjectDocument({
|
|
name: "Castle Project",
|
|
sceneName: "Entry"
|
|
}),
|
|
activeSceneId: "scene-cellar",
|
|
scenes: {
|
|
"scene-main": createEmptyProjectScene({
|
|
id: "scene-main",
|
|
name: "Entry"
|
|
}),
|
|
"scene-cellar": createEmptyProjectScene({
|
|
id: "scene-cellar",
|
|
name: "Cellar",
|
|
loadingScreen: {
|
|
colorHex: "#233041",
|
|
headline: "Descending",
|
|
description: "Dust and echoes settle before the next room appears."
|
|
}
|
|
})
|
|
}
|
|
};
|
|
document.scenes["scene-main"].entities[mainExit.id] = mainExit;
|
|
document.scenes["scene-cellar"].entities[cellarEntry.id] = cellarEntry;
|
|
document.scenes["scene-main"].editorPreferences = {
|
|
...document.scenes["scene-main"].editorPreferences,
|
|
whiteboxSelectionMode: "face",
|
|
whiteboxSnapEnabled: false,
|
|
whiteboxSnapStep: 0.5,
|
|
viewportGridVisible: false,
|
|
viewportLayoutMode: "quad",
|
|
activeViewportPanelId: "bottomRight",
|
|
viewportQuadSplit: {
|
|
x: 0.42,
|
|
y: 0.58
|
|
},
|
|
viewportPanels: {
|
|
...document.scenes["scene-main"].editorPreferences.viewportPanels,
|
|
topLeft: {
|
|
viewMode: "front",
|
|
displayMode: "wireframe"
|
|
}
|
|
}
|
|
};
|
|
document.scenes["scene-cellar"].editorPreferences = {
|
|
...document.scenes["scene-cellar"].editorPreferences,
|
|
whiteboxSelectionMode: "vertex",
|
|
whiteboxSnapStep: 2,
|
|
viewportLayoutMode: "quad",
|
|
activeViewportPanelId: "topRight",
|
|
viewportPanels: {
|
|
...document.scenes["scene-cellar"].editorPreferences.viewportPanels,
|
|
topLeft: {
|
|
viewMode: "side",
|
|
displayMode: "authoring"
|
|
}
|
|
}
|
|
};
|
|
document.scenes["scene-cellar"].world.advancedRendering = {
|
|
...document.scenes["scene-cellar"].world.advancedRendering,
|
|
enabled: true,
|
|
whiteboxBevel: {
|
|
enabled: true,
|
|
edgeWidth: 0.16,
|
|
normalStrength: 0.85
|
|
}
|
|
};
|
|
document.scenes["scene-cellar"].world.projectTimeLightingEnabled = false;
|
|
document.assets["asset-night-sky"] = {
|
|
id: "asset-night-sky",
|
|
kind: "image",
|
|
sourceName: "night-sky.png",
|
|
mimeType: "image/png",
|
|
storageKey: "project-asset:asset-night-sky",
|
|
byteLength: 2048,
|
|
metadata: {
|
|
kind: "image",
|
|
width: 1024,
|
|
height: 512,
|
|
hasAlpha: false,
|
|
warnings: []
|
|
}
|
|
};
|
|
document.time = {
|
|
startDayNumber: 3,
|
|
startTimeOfDayHours: 18.5,
|
|
dayLengthMinutes: 16,
|
|
sunriseTimeOfDayHours: 5.5,
|
|
sunsetTimeOfDayHours: 19.75,
|
|
dawnDurationHours: 1.25,
|
|
duskDurationHours: 1.75
|
|
};
|
|
document.scenes["scene-cellar"].world.timeOfDay = {
|
|
dawn: {
|
|
skyTopColorHex: "#6680bc",
|
|
skyBottomColorHex: "#f3b07a",
|
|
ambientColorHex: "#ffe0ba",
|
|
ambientIntensityFactor: 0.78,
|
|
lightColorHex: "#ffd29d",
|
|
lightIntensityFactor: 0.82
|
|
},
|
|
dusk: {
|
|
skyTopColorHex: "#313d70",
|
|
skyBottomColorHex: "#e27b5e",
|
|
ambientColorHex: "#f0bf9f",
|
|
ambientIntensityFactor: 0.58,
|
|
lightColorHex: "#ff9d79",
|
|
lightIntensityFactor: 0.61
|
|
},
|
|
night: {
|
|
background: {
|
|
mode: "image",
|
|
assetId: "asset-night-sky",
|
|
environmentIntensity: 0.42
|
|
},
|
|
ambientColorHex: "#1a2941",
|
|
ambientIntensityFactor: 0.22,
|
|
lightColorHex: "#95b0ff",
|
|
lightIntensityFactor: 0.19
|
|
}
|
|
};
|
|
|
|
const serializedDocument = serializeProjectDocument(document);
|
|
|
|
expect(parseProjectDocumentJson(serializedDocument)).toEqual(document);
|
|
});
|
|
|
|
it("migrates pre-project-time multi-scene documents to default project time settings", () => {
|
|
const migratedDocument = parseProjectDocumentJson(
|
|
JSON.stringify({
|
|
version: AUTHORED_OBJECT_STATE_SCENE_DOCUMENT_VERSION,
|
|
name: "Legacy Project",
|
|
activeSceneId: "scene-main",
|
|
scenes: {
|
|
"scene-main": createEmptyProjectScene({
|
|
id: "scene-main",
|
|
name: "Legacy Entry"
|
|
})
|
|
},
|
|
materials: createEmptyProjectDocument().materials,
|
|
textures: {},
|
|
assets: {}
|
|
})
|
|
);
|
|
|
|
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
|
|
expect(migratedDocument.time).toEqual(createDefaultProjectTimeSettings());
|
|
});
|
|
|
|
it("migrates legacy project time environment profiles into scene world settings", () => {
|
|
const legacyProject = createEmptyProjectDocument({
|
|
name: "Legacy Time Project",
|
|
sceneName: "Atrium"
|
|
});
|
|
const legacyScene = legacyProject.scenes[legacyProject.activeSceneId];
|
|
|
|
if (legacyScene === undefined) {
|
|
throw new Error("Expected the legacy project to contain an active scene.");
|
|
}
|
|
|
|
const migratedDocument = parseProjectDocumentJson(
|
|
JSON.stringify({
|
|
version: PROJECT_TIME_DAY_NIGHT_PROFILE_SCENE_DOCUMENT_VERSION,
|
|
name: legacyProject.name,
|
|
activeSceneId: legacyProject.activeSceneId,
|
|
scenes: {
|
|
[legacyScene.id]: {
|
|
...legacyScene,
|
|
world: {
|
|
...legacyScene.world,
|
|
projectTimeLightingEnabled: undefined
|
|
}
|
|
}
|
|
},
|
|
materials: legacyProject.materials,
|
|
textures: legacyProject.textures,
|
|
assets: legacyProject.assets,
|
|
time: {
|
|
startDayNumber: 2,
|
|
startTimeOfDayHours: 17.5,
|
|
dayLengthMinutes: 20,
|
|
sunriseTimeOfDayHours: 6.25,
|
|
sunsetTimeOfDayHours: 19.5,
|
|
dawnDurationHours: 1.25,
|
|
duskDurationHours: 1.75,
|
|
dawn: {
|
|
...createDefaultProjectTimeSettings().dawn,
|
|
ambientIntensityFactor: 0.74
|
|
},
|
|
dusk: {
|
|
...createDefaultProjectTimeSettings().dusk,
|
|
lightIntensityFactor: 0.63
|
|
},
|
|
night: {
|
|
...createDefaultProjectTimeSettings().night,
|
|
lightIntensityFactor: 0.21
|
|
}
|
|
}
|
|
})
|
|
);
|
|
|
|
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
|
|
expect(migratedDocument.time.startDayNumber).toBe(2);
|
|
expect(migratedDocument.time.startTimeOfDayHours).toBe(17.5);
|
|
expect(migratedDocument.time.dayLengthMinutes).toBe(20);
|
|
expect(migratedDocument.time.sunriseTimeOfDayHours).toBe(6.25);
|
|
expect(
|
|
migratedDocument.scenes[migratedDocument.activeSceneId]?.world.timeOfDay
|
|
.dawn.ambientIntensityFactor
|
|
).toBe(0.74);
|
|
expect(
|
|
migratedDocument.scenes[migratedDocument.activeSceneId]?.world.timeOfDay
|
|
.dusk.lightIntensityFactor
|
|
).toBe(0.63);
|
|
expect(
|
|
migratedDocument.scenes[migratedDocument.activeSceneId]?.world.timeOfDay
|
|
.night.lightIntensityFactor
|
|
).toBe(0.21);
|
|
expect(
|
|
migratedDocument.scenes[migratedDocument.activeSceneId]?.world.timeOfDay
|
|
.night.background
|
|
).toEqual(
|
|
createDefaultWorldSettings().timeOfDay.night.background
|
|
);
|
|
expect(
|
|
migratedDocument.scenes[migratedDocument.activeSceneId]?.world
|
|
.projectTimeLightingEnabled
|
|
).toBe(true);
|
|
});
|
|
|
|
it("migrates pre-project-name multi-scene documents to Untitled Project", () => {
|
|
const migratedDocument = parseProjectDocumentJson(
|
|
JSON.stringify({
|
|
version: PLAYER_START_GAMEPAD_CAMERA_LOOK_SCENE_DOCUMENT_VERSION,
|
|
activeSceneId: "scene-main",
|
|
scenes: {
|
|
"scene-main": (() => {
|
|
const legacyScene = createEmptyProjectScene({
|
|
id: "scene-main",
|
|
name: "Legacy Entry"
|
|
});
|
|
|
|
return {
|
|
...legacyScene,
|
|
editorPreferences: undefined
|
|
};
|
|
})()
|
|
},
|
|
materials: createEmptyProjectDocument().materials,
|
|
textures: {},
|
|
assets: {}
|
|
})
|
|
);
|
|
|
|
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
|
|
expect(migratedDocument.name).toBe(DEFAULT_PROJECT_NAME);
|
|
expect(migratedDocument.scenes["scene-main"]?.name).toBe("Legacy Entry");
|
|
expect(migratedDocument.scenes["scene-main"]?.editorPreferences).toMatchObject(
|
|
{
|
|
whiteboxSelectionMode: "object",
|
|
whiteboxSnapEnabled: true,
|
|
whiteboxSnapStep: DEFAULT_SCENE_EDITOR_SNAP_STEP,
|
|
viewportGridVisible: true,
|
|
viewportLayoutMode: "single",
|
|
activeViewportPanelId: "topLeft"
|
|
}
|
|
);
|
|
});
|
|
|
|
it("migrates v23 project documents without Scene Entry and Scene Exit entities", () => {
|
|
const legacyScene = createEmptyProjectScene({
|
|
id: "scene-main",
|
|
name: "Legacy Entry"
|
|
});
|
|
|
|
const migratedDocument = parseProjectDocumentJson(
|
|
JSON.stringify({
|
|
version: RUNNER_LOADING_SCREEN_SCENE_DOCUMENT_VERSION,
|
|
activeSceneId: "scene-main",
|
|
scenes: {
|
|
"scene-main": legacyScene
|
|
},
|
|
materials: createEmptyProjectDocument().materials,
|
|
textures: {},
|
|
assets: {}
|
|
})
|
|
);
|
|
|
|
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
|
|
expect(migratedDocument.scenes["scene-main"]?.loadingScreen).toEqual(
|
|
legacyScene.loadingScreen
|
|
);
|
|
});
|
|
|
|
it("rejects Scene Exit targets that point at a missing Scene Entry", () => {
|
|
const document = createEmptyProjectDocument({ sceneName: "Outside" });
|
|
const targetScene = createEmptyProjectScene({
|
|
id: "scene-house",
|
|
name: "House"
|
|
});
|
|
document.scenes[targetScene.id] = targetScene;
|
|
document.scenes["scene-main"].entities["entity-scene-exit-door"] =
|
|
createSceneExitEntity({
|
|
id: "entity-scene-exit-door",
|
|
targetSceneId: targetScene.id,
|
|
targetEntryEntityId: "missing-entry"
|
|
});
|
|
|
|
expect(() =>
|
|
parseProjectDocumentJson(JSON.stringify(document))
|
|
).toThrow("target entry");
|
|
});
|
|
});
|