Files
Mujoco_WASM/web_platform/src/map/MapLoader.ts
T
chenlin 3b4ced6437
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
feat(web-platform): release V0.7 地图模块
2026-09-01 14:23:49 +08:00

104 lines
4.1 KiB
TypeScript

import type { MapEntry, ProjectFile, ProjectManifest } from '../project/types';
import { decodeMapDefinition } from './mapSchema';
import { resolveProjectAssetPath } from './mapPaths';
import type { MapSelection, ResolvedProjectMap, VisualMapAsset } from './types';
import { decodeEditableMapDocument } from './editor/editorSchema';
function projectFile(manifest: ProjectManifest, path: string): ProjectFile {
const file = manifest.files.find((candidate) => candidate.path === path);
if (!file) throw new Error(`地图引用的资源不存在:${path}`);
return file;
}
export function discoverMapEntries(files: ProjectFile[]): MapEntry[] {
const paths = new Set(files.map((file) => file.path));
const filesByPath = new Map(files.map((file) => [file.path, file]));
const ids = new Set<string>();
return files
.filter((file) => /(^|\/)map\.json$/i.test(file.path))
.map((file) => {
const definition = decodeMapDefinition(file.data);
if (ids.has(definition.id)) throw new Error(`地图 id 重复:${definition.id}`);
ids.add(definition.id);
const physicsPath = definition.physics
? resolveProjectAssetPath(file.path, definition.physics.source)
: undefined;
const visualPath = definition.visual
? resolveProjectAssetPath(file.path, definition.visual.source)
: undefined;
const authoringPath = definition.authoring
? resolveProjectAssetPath(file.path, definition.authoring.source)
: undefined;
for (const resolved of [physicsPath, visualPath, authoringPath]) {
if (resolved && !paths.has(resolved))
throw new Error(`地图 ${definition.name} 引用的资源不存在:${resolved}`);
}
if (authoringPath) {
const authoring = filesByPath.get(authoringPath);
if (!authoring) throw new Error(`地图 ${definition.name} 的创作层不存在`);
const editable = decodeEditableMapDocument(authoring.data);
if (editable.mapId !== definition.id)
throw new Error(`地图 ${definition.name} 的创作层 mapId 必须为 ${definition.id}`);
}
return {
descriptorPath: file.path,
schemaVersion: definition.schemaVersion,
id: definition.id,
name: definition.name,
physicsPath,
visualPath,
...(authoringPath ? { authoringPath } : {}),
spawnPoints: definition.spawnPoints.map(({ id, name }) => ({ id, name })),
};
});
}
export function resolveProjectMap(
manifest: ProjectManifest,
descriptorPath: string,
): ResolvedProjectMap {
const descriptor = projectFile(manifest, descriptorPath);
const definition = decodeMapDefinition(descriptor.data);
const physicsPath = definition.physics
? resolveProjectAssetPath(descriptorPath, definition.physics.source)
: undefined;
const visualPath = definition.visual
? resolveProjectAssetPath(descriptorPath, definition.visual.source)
: undefined;
const authoringPath = definition.authoring
? resolveProjectAssetPath(descriptorPath, definition.authoring.source)
: undefined;
if (physicsPath) projectFile(manifest, physicsPath);
if (visualPath) projectFile(manifest, visualPath);
if (authoringPath) {
const editable = decodeEditableMapDocument(projectFile(manifest, authoringPath).data);
if (editable.mapId !== definition.id)
throw new Error(`地图 ${definition.name} 的创作层 mapId 必须为 ${definition.id}`);
}
return {
definition,
descriptorPath,
physicsPath,
visualPath,
authoringPath,
};
}
export function visualMapAsset(
manifest: ProjectManifest,
selection: MapSelection,
): VisualMapAsset | null {
if (selection.kind !== 'project') return null;
const resolved = resolveProjectMap(manifest, selection.descriptorPath);
if (!resolved.visualPath || !resolved.definition.visual) return null;
const visual = projectFile(manifest, resolved.visualPath);
return {
id: resolved.definition.id,
name: resolved.definition.name,
path: resolved.visualPath,
data: visual.data,
castShadow: resolved.definition.visual.castShadow ?? true,
receiveShadow: resolved.definition.visual.receiveShadow ?? true,
};
}