Merge remote map editor updates
This commit is contained in:
@@ -1,13 +1,9 @@
|
||||
import type {
|
||||
EditableMapNode,
|
||||
HierarchicalMapNode,
|
||||
MapNode,
|
||||
SceneData,
|
||||
} from "@/types/editor/editor";
|
||||
import {
|
||||
parseHierarchicalMapPayload,
|
||||
parseMapNodes,
|
||||
} from "@/utils/map/mapNodeValidation";
|
||||
import { parseMapData } from "@/utils/map/mapNodeValidation";
|
||||
|
||||
const MAP_JSON_PATH = "/map.json";
|
||||
const MODEL_FILE_NAMES = ["model.glb", "model.gltf"];
|
||||
@@ -29,8 +25,12 @@ export async function loadMapSceneData(): Promise<SceneData | null> {
|
||||
}
|
||||
|
||||
loadingPromise = loadMapSceneDataInternal();
|
||||
cachedSceneData = await loadingPromise;
|
||||
loadingPromise = null;
|
||||
|
||||
try {
|
||||
cachedSceneData = await loadingPromise;
|
||||
} finally {
|
||||
loadingPromise = null;
|
||||
}
|
||||
|
||||
return cachedSceneData;
|
||||
}
|
||||
@@ -59,53 +59,9 @@ async function loadMapSceneDataInternal(): Promise<SceneData | null> {
|
||||
export async function createSceneDataFromMapPayload(
|
||||
mapPayload: unknown,
|
||||
): Promise<SceneData> {
|
||||
const mapTree = parseHierarchicalMapPayload(mapPayload);
|
||||
const mapNodes = parseMapNodes(mapTree);
|
||||
const editableNodes = createEditableMapNodes(mapTree);
|
||||
const { mapNodes, mapTree } = parseMapData(mapPayload);
|
||||
const deduplicatedNodes = deduplicateMapNodes(mapNodes);
|
||||
const deduplicatedEditableNodes = deduplicateEditableMapNodes(editableNodes);
|
||||
return createSceneData(mapTree, deduplicatedEditableNodes, deduplicatedNodes);
|
||||
}
|
||||
|
||||
function toMapNode(node: HierarchicalMapNode): MapNode {
|
||||
return {
|
||||
name: node.name,
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
type: node.type,
|
||||
};
|
||||
}
|
||||
|
||||
function flattenEditableMapNode(
|
||||
node: HierarchicalMapNode,
|
||||
path: number[],
|
||||
): EditableMapNode[] {
|
||||
if (node.name === "terrain") {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (node.role === "group") {
|
||||
return (
|
||||
node.children?.flatMap((child, index) =>
|
||||
flattenEditableMapNode(child, [...path, index]),
|
||||
) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
return [{ ...toMapNode(node), path }];
|
||||
}
|
||||
|
||||
function createEditableMapNodes(
|
||||
mapTree: HierarchicalMapNode | HierarchicalMapNode[],
|
||||
): EditableMapNode[] {
|
||||
if (Array.isArray(mapTree)) {
|
||||
return mapTree.flatMap((node, index) =>
|
||||
flattenEditableMapNode(node, [index]),
|
||||
);
|
||||
}
|
||||
|
||||
return flattenEditableMapNode(mapTree, []);
|
||||
return createSceneData(deduplicatedNodes, mapTree);
|
||||
}
|
||||
|
||||
function createPositionKey(node: MapNode): string {
|
||||
@@ -142,36 +98,12 @@ function deduplicateMapNodes(nodes: MapNode[]): MapNode[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
function deduplicateEditableMapNodes(
|
||||
nodes: EditableMapNode[],
|
||||
): EditableMapNode[] {
|
||||
const seen = new Set<string>();
|
||||
const result: EditableMapNode[] = [];
|
||||
|
||||
const sortedNodes = [...nodes].sort((a, b) => {
|
||||
if (a.type === "Object3D" && b.type !== "Object3D") return -1;
|
||||
if (a.type !== "Object3D" && b.type === "Object3D") return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
for (const node of sortedNodes) {
|
||||
const key = createPositionKey(node);
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function createSceneData(
|
||||
mapNodes: MapNode[],
|
||||
mapTree: HierarchicalMapNode | HierarchicalMapNode[],
|
||||
mapNodes: EditableMapNode[],
|
||||
modelLookupNodes: MapNode[],
|
||||
): Promise<SceneData> {
|
||||
const models = await loadMapModelUrls(modelLookupNodes);
|
||||
return { mapNodes, mapTree, models };
|
||||
const models = await loadMapModelUrls(mapNodes);
|
||||
return { mapNodes, models, mapTree };
|
||||
}
|
||||
|
||||
async function loadMapModelUrls(
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { HierarchicalMapNode, MapNode } from "../../types/editor/editor";
|
||||
|
||||
export interface ParsedMapNodes {
|
||||
mapNodes: MapNode[];
|
||||
mapTree: HierarchicalMapNode | HierarchicalMapNode[];
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
@@ -48,19 +53,25 @@ export function isHierarchicalMapNode(
|
||||
);
|
||||
}
|
||||
|
||||
function flattenMapNode(node: HierarchicalMapNode): MapNode[] {
|
||||
function flattenMapNode(node: HierarchicalMapNode, path: number[]): MapNode[] {
|
||||
const mapNode: MapNode = {
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
sourcePath: path,
|
||||
};
|
||||
if (node.role === "group") {
|
||||
return node.children?.flatMap(flattenMapNode) ?? [];
|
||||
const childNodes =
|
||||
node.children?.flatMap((child, index) =>
|
||||
flattenMapNode(child, [...path, index]),
|
||||
) ?? [];
|
||||
|
||||
if (node.role === "group" || node.type === "Mesh") {
|
||||
return childNodes;
|
||||
}
|
||||
|
||||
return [mapNode];
|
||||
return [mapNode, ...childNodes];
|
||||
}
|
||||
|
||||
export function parseHierarchicalMapPayload(
|
||||
@@ -78,12 +89,22 @@ export function parseHierarchicalMapPayload(
|
||||
}
|
||||
|
||||
export function parseMapNodes(value: unknown): MapNode[] {
|
||||
return parseMapData(value).mapNodes;
|
||||
}
|
||||
|
||||
export function parseMapData(value: unknown): ParsedMapNodes {
|
||||
if (Array.isArray(value) && value.every(isHierarchicalMapNode)) {
|
||||
return value.flatMap(flattenMapNode);
|
||||
return {
|
||||
mapNodes: value.flatMap((node, index) => flattenMapNode(node, [index])),
|
||||
mapTree: value,
|
||||
};
|
||||
}
|
||||
|
||||
if (isHierarchicalMapNode(value)) {
|
||||
return flattenMapNode(value);
|
||||
return {
|
||||
mapNodes: flattenMapNode(value, []),
|
||||
mapTree: value,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Invalid map node data");
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import { isInstancedMapNodeName } from "@/world/map-instancing/mapInstancingConfig";
|
||||
|
||||
const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking", "terrain"]);
|
||||
const RUNTIME_VEGETATION_NODE_NAMES = new Set([
|
||||
"arbre",
|
||||
"buisson",
|
||||
"champdeble",
|
||||
"champdesoja",
|
||||
"champsdetournesol",
|
||||
"sapin",
|
||||
]);
|
||||
|
||||
export function isRuntimeStructureMapNode(name: string): boolean {
|
||||
return MAP_STRUCTURE_NODE_NAMES.has(name);
|
||||
}
|
||||
|
||||
export function isRuntimeSingleMapNode(node: MapNode): boolean {
|
||||
if (isRuntimeStructureMapNode(node.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.type === "Mesh") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
!RUNTIME_VEGETATION_NODE_NAMES.has(node.name) &&
|
||||
!isInstancedMapNodeName(node.name)
|
||||
);
|
||||
}
|
||||
|
||||
export function isEditorVisibleMapNode(node: MapNode): boolean {
|
||||
return !isRuntimeStructureMapNode(node.name) && node.type !== "Mesh";
|
||||
}
|
||||
|
||||
export function getTerrainMapNode(nodes: readonly MapNode[]): MapNode | null {
|
||||
return nodes.find((node) => node.name === "terrain") ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user