import type { ProjectManifest } from '../project/types'; import { resolveProjectMap } from './MapLoader'; import { composeProjectMap } from './MapComposer'; import { composePhysicalMap } from './physicalMap'; import { isSystemTerrainPreset, type PlacedMapAsset } from './types'; export interface MapStackComposition { data: Uint8Array; geomCount: number; warnings: string[]; } /** 将全部已放置地图实例依次合成到同一 MJCF;源地图条目本身不会自动进入场景。 */ export function composePlacedMapAssets( source: Uint8Array, generatedScenePath: string, manifest: ProjectManifest, assets: readonly PlacedMapAsset[], ): MapStackComposition { let data = source; let geomCount = 0; const warnings: string[] = []; const spawnOwnerId = assets.find( (asset) => asset.selection.kind === 'project' && asset.selection.spawnPointId, )?.id; // 程序地形最后合成,确保工程地图或机器人自带的无限 plane 不会覆盖沟壑/深坑。 const orderedAssets = [ ...assets.filter( (asset) => asset.selection.kind !== 'builtin' || !isSystemTerrainPreset(asset.selection.config.preset), ), ...assets.filter( (asset) => asset.selection.kind === 'builtin' && isSystemTerrainPreset(asset.selection.config.preset), ), ]; for (const asset of orderedAssets) { if (asset.selection.kind === 'builtin') { const composed = composePhysicalMap(data, asset.selection.config, { instanceId: asset.id, preserveExistingGround: true, }); data = composed.data; geomCount += composed.geomCount; if (composed.summary) warnings.push(`${asset.name}:${composed.summary}`); continue; } const resolvedMap = resolveProjectMap(manifest, asset.selection.descriptorPath); const composed = composeProjectMap( data, generatedScenePath, manifest, resolvedMap, asset.selection, { instanceId: asset.id, applySpawnPoint: asset.id === spawnOwnerId, }, ); data = composed.data; geomCount += composed.geomCount; warnings.push(...composed.warnings, `${asset.name}:${composed.summary}`); } return { data, geomCount, warnings }; }