feat(simulation): optimize and persist collision viewing

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 15:18:34 +03:00
parent 5e97fa293f
commit a65ae9c285
13 changed files with 662 additions and 65 deletions
@@ -1,4 +1,6 @@
export type SimulationSceneType = "interior" | "outdoor" | "object";
export type SimulationTransformAxis = "x" | "y" | "z";
export type SimulationViewerQuality = "low" | "medium" | "high" | "ultra" | "maximum";
export type SimulationProjectStatus =
| "uploading"
| "queued"
@@ -32,6 +34,14 @@ export interface SimulationWorldManifest {
};
}
export interface SimulationViewerSettings {
schemaVersion: "missioncore.simulation-viewer-settings/v1";
quality: SimulationViewerQuality;
visual: { inverted: boolean; axis: SimulationTransformAxis };
collision: { inverted: boolean; axis: SimulationTransformAxis };
camera: { invertHorizontal: boolean; invertVertical: boolean };
}
export interface SimulationProject {
schemaVersion: "missioncore.simulation-project/v1";
projectId: string;
@@ -60,6 +70,7 @@ export interface SimulationProject {
byteLength: number;
}>;
worldManifest: SimulationWorldManifest | null;
viewerSettings: SimulationViewerSettings;
error: string | null;
createdAtUtc: string;
updatedAtUtc: string;
@@ -223,6 +234,30 @@ export async function updateSimulationProject(
return parseProject(await jsonResponse(response));
}
export async function saveSimulationViewerSettings(
projectId: string,
settings: SimulationViewerSettings,
): Promise<SimulationProject> {
const response = await fetch(
`${API_ROOT}/${encodeURIComponent(projectId)}/viewer-settings`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
schema_version: settings.schemaVersion,
quality: settings.quality,
visual: settings.visual,
collision: settings.collision,
camera: {
invert_horizontal: settings.camera.invertHorizontal,
invert_vertical: settings.camera.invertVertical,
},
}),
},
);
return parseProject(await jsonResponse(response));
}
export async function retrySimulationProject(projectId: string): Promise<SimulationProject> {
const response = await fetch(`${API_ROOT}/${encodeURIComponent(projectId)}/build`, {
method: "POST",
@@ -251,7 +286,7 @@ function parseProject(value: unknown): SimulationProject {
const record = objectValue(value, "simulation project");
exactKeys(record, [
"schema_version", "project_id", "name", "scene_type", "status", "source", "provider",
"artifacts", "world_manifest", "error", "created_at_utc", "updated_at_utc",
"artifacts", "world_manifest", "viewer_settings", "error", "created_at_utc", "updated_at_utc",
], "simulation project");
if (record.schema_version !== "missioncore.simulation-project/v1") {
throw new Error("Проект симуляции вернул неподдерживаемую версию.");
@@ -301,12 +336,47 @@ function parseProject(value: unknown): SimulationProject {
};
}),
worldManifest: record.world_manifest === null ? null : parseWorldManifest(record.world_manifest),
viewerSettings: parseViewerSettings(record.viewer_settings),
error: nullableString(record.error, "project error"),
createdAtUtc: stringValue(record.created_at_utc, "created at"),
updatedAtUtc: stringValue(record.updated_at_utc, "updated at"),
};
}
function parseViewerSettings(value: unknown): SimulationViewerSettings {
const record = objectValue(value, "viewer settings");
exactKeys(record, ["schema_version", "quality", "visual", "collision", "camera"], "viewer settings");
if (record.schema_version !== "missioncore.simulation-viewer-settings/v1") {
throw new Error("Настройки сцены вернули неподдерживаемую версию.");
}
const visual = parseLayerViewerSettings(record.visual, "visual settings");
const collision = parseLayerViewerSettings(record.collision, "collision settings");
const camera = objectValue(record.camera, "camera settings");
exactKeys(camera, ["invert_horizontal", "invert_vertical"], "camera settings");
return {
schemaVersion: "missioncore.simulation-viewer-settings/v1",
quality: qualityValue(record.quality),
visual,
collision,
camera: {
invertHorizontal: booleanValue(camera.invert_horizontal, "horizontal camera inversion"),
invertVertical: booleanValue(camera.invert_vertical, "vertical camera inversion"),
},
};
}
function parseLayerViewerSettings(
value: unknown,
label: string,
): { inverted: boolean; axis: SimulationTransformAxis } {
const record = objectValue(value, label);
exactKeys(record, ["inverted", "axis"], label);
return {
inverted: booleanValue(record.inverted, `${label} inversion`),
axis: axisValue(record.axis),
};
}
function parseWorldManifest(value: unknown): SimulationWorldManifest {
const record = objectValue(value, "world manifest");
const visual = objectValue(record.visual, "visual manifest");
@@ -396,6 +466,18 @@ function sceneTypeValue(value: unknown): SimulationSceneType {
return invalid("scene type");
}
function axisValue(value: unknown): SimulationTransformAxis {
if (value === "x" || value === "y" || value === "z") return value;
return invalid("transform axis");
}
function qualityValue(value: unknown): SimulationViewerQuality {
if (["low", "medium", "high", "ultra", "maximum"].includes(String(value))) {
return value as SimulationViewerQuality;
}
return invalid("viewer quality");
}
function statusValue(value: unknown): SimulationProjectStatus {
if (["uploading", "queued", "processing", "importing", "ready", "failed"].includes(String(value))) {
return value as SimulationProjectStatus;