feat(editor): edit hierarchical map nodes
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import type { MapNode, SceneData } from "@/types/editor/editor";
|
||||
import { parseMapNodes } from "@/utils/map/mapNodeValidation";
|
||||
import type {
|
||||
EditableMapNode,
|
||||
HierarchicalMapNode,
|
||||
MapNode,
|
||||
SceneData,
|
||||
} from "@/types/editor/editor";
|
||||
import {
|
||||
parseHierarchicalMapPayload,
|
||||
parseMapNodes,
|
||||
} from "@/utils/map/mapNodeValidation";
|
||||
|
||||
const MAP_JSON_PATH = "/map.json";
|
||||
const MODEL_FILE_NAMES = ["model.glb", "model.gltf"];
|
||||
@@ -45,9 +53,59 @@ async function loadMapSceneDataInternal(): Promise<SceneData | null> {
|
||||
}
|
||||
|
||||
const mapPayload: unknown = await response.json();
|
||||
const mapNodes = parseMapNodes(mapPayload);
|
||||
return createSceneDataFromMapPayload(mapPayload);
|
||||
}
|
||||
|
||||
export async function createSceneDataFromMapPayload(
|
||||
mapPayload: unknown,
|
||||
): Promise<SceneData> {
|
||||
const mapTree = parseHierarchicalMapPayload(mapPayload);
|
||||
const mapNodes = parseMapNodes(mapTree);
|
||||
const editableNodes = createEditableMapNodes(mapTree);
|
||||
const deduplicatedNodes = deduplicateMapNodes(mapNodes);
|
||||
return createSceneData(deduplicatedNodes);
|
||||
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, []);
|
||||
}
|
||||
|
||||
function createPositionKey(node: MapNode): string {
|
||||
@@ -84,9 +142,36 @@ function deduplicateMapNodes(nodes: MapNode[]): MapNode[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function createSceneData(mapNodes: MapNode[]): Promise<SceneData> {
|
||||
const models = await loadMapModelUrls(mapNodes);
|
||||
return { mapNodes, models };
|
||||
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(
|
||||
mapTree: HierarchicalMapNode | HierarchicalMapNode[],
|
||||
mapNodes: EditableMapNode[],
|
||||
modelLookupNodes: MapNode[],
|
||||
): Promise<SceneData> {
|
||||
const models = await loadMapModelUrls(modelLookupNodes);
|
||||
return { mapNodes, mapTree, models };
|
||||
}
|
||||
|
||||
async function loadMapModelUrls(
|
||||
|
||||
Reference in New Issue
Block a user