122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import type { EditableMapDocument } from './editor/types';
|
|
import { normalizePhysicalMapConfig } from './physicalMap';
|
|
import {
|
|
mapSelectionTransform,
|
|
updatePlacedMapAsset,
|
|
type MapInstanceTransform,
|
|
type PlacedMapAsset,
|
|
} from './types';
|
|
|
|
function sameAsset(left: PlacedMapAsset | undefined, right: PlacedMapAsset | undefined): boolean {
|
|
return Boolean(
|
|
left &&
|
|
right &&
|
|
left.id === right.id &&
|
|
left.name === right.name &&
|
|
JSON.stringify(left.selection) === JSON.stringify(right.selection),
|
|
);
|
|
}
|
|
|
|
function cloneAsset(asset: PlacedMapAsset): PlacedMapAsset {
|
|
return { ...asset, selection: structuredClone(asset.selection) };
|
|
}
|
|
|
|
export interface MapSceneDraftSummary {
|
|
dirty: boolean;
|
|
changeCount: number;
|
|
/** 当前仍存在且相对应用基线新增或发生修改的实例 ID。 */
|
|
changedIds: string[];
|
|
/** 可直接在 Three.js 中预览的新增或已修改程序地图。 */
|
|
previewAssets: PlacedMapAsset[];
|
|
}
|
|
|
|
export interface MapEditorDraftPreviewInstance {
|
|
id: string;
|
|
document: EditableMapDocument;
|
|
transform: MapInstanceTransform;
|
|
}
|
|
|
|
/**
|
|
* 当前激活的认证场景由交互编辑层绘制;其余待提交工程地图必须保留只读预览,
|
|
* 否则切换到参数地形时会因为清空交互文档而从画布消失。
|
|
*/
|
|
export function mapEditorDraftPreviewInstances(
|
|
assets: readonly PlacedMapAsset[],
|
|
pendingIds: readonly string[],
|
|
documents: ReadonlyMap<string, EditableMapDocument>,
|
|
activeId?: string,
|
|
): MapEditorDraftPreviewInstance[] {
|
|
const pending = new Set(pendingIds);
|
|
return assets.flatMap((asset) => {
|
|
if (asset.id === activeId || !pending.has(asset.id) || asset.selection.kind !== 'project')
|
|
return [];
|
|
const document = documents.get(asset.selection.descriptorPath);
|
|
return document
|
|
? [{ id: asset.id, document, transform: mapSelectionTransform(asset.selection) }]
|
|
: [];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 所有地图实例共享同一个场景草稿边界。新增、删除、替换和实例参数修改只更新
|
|
* `current`,显式应用成功后才推进 `applied` 基线。
|
|
*/
|
|
export function summarizeMapSceneDraft(
|
|
current: readonly PlacedMapAsset[],
|
|
applied: readonly PlacedMapAsset[],
|
|
): MapSceneDraftSummary {
|
|
const currentById = new Map(current.map((asset) => [asset.id, asset]));
|
|
const appliedById = new Map(applied.map((asset) => [asset.id, asset]));
|
|
const changed = current.filter((asset) => !sameAsset(asset, appliedById.get(asset.id)));
|
|
const removed = applied.filter((asset) => !currentById.has(asset.id));
|
|
return {
|
|
dirty: changed.length + removed.length > 0,
|
|
changeCount: changed.length + removed.length,
|
|
changedIds: changed.map((asset) => asset.id),
|
|
previewAssets: changed.filter((asset) => asset.selection.kind === 'builtin').map(cloneAsset),
|
|
};
|
|
}
|
|
|
|
/** 放弃完整场景草稿,严格恢复上一次成功编译的实例顺序和状态。 */
|
|
export function restoreAppliedMapScene(applied: readonly PlacedMapAsset[]): PlacedMapAsset[] {
|
|
return applied.map(cloneAsset);
|
|
}
|
|
|
|
export function clonePlacedMapAssets(assets: readonly PlacedMapAsset[]): PlacedMapAsset[] {
|
|
return assets.map(cloneAsset);
|
|
}
|
|
|
|
/** 普通模型重载使用应用基线;只有显式场景提交才传入候选实例。 */
|
|
export function resolveMapSceneLoadAssets(
|
|
applied: readonly PlacedMapAsset[],
|
|
requested?: readonly PlacedMapAsset[],
|
|
): PlacedMapAsset[] {
|
|
return clonePlacedMapAssets(requested ?? applied);
|
|
}
|
|
|
|
/** 视口操纵器只更新参数地形草稿,不触发 MuJoCo 编译。 */
|
|
export function transformParametricMapAsset(
|
|
assets: readonly PlacedMapAsset[],
|
|
id: string,
|
|
position: readonly [number, number],
|
|
yawDeg: number,
|
|
): PlacedMapAsset[] | readonly PlacedMapAsset[] {
|
|
const target = assets.find((asset) => asset.id === id);
|
|
if (!target || target.selection.kind !== 'builtin' || target.selection.config.preset === 'none')
|
|
return assets;
|
|
const clean = (value: number) => Math.round(value * 1_000_000) / 1_000_000;
|
|
const config = normalizePhysicalMapConfig({
|
|
...target.selection.config,
|
|
positionX: clean(position[0]),
|
|
positionY: clean(position[1]),
|
|
yawDeg: clean(yawDeg),
|
|
});
|
|
if (
|
|
config.positionX === target.selection.config.positionX &&
|
|
config.positionY === target.selection.config.positionY &&
|
|
config.yawDeg === target.selection.config.yawDeg
|
|
)
|
|
return assets;
|
|
return updatePlacedMapAsset(assets, id, { kind: 'builtin', config });
|
|
}
|