@fps-games/editor 0.1.5-beta.4 → 0.1.6-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +16 -16
- package/dist/local-editor-agent-actions.d.ts +8 -1
- package/dist/local-editor-agent-actions.d.ts.map +1 -1
- package/dist/local-editor-agent-actions.js +171 -12
- package/dist/local-editor-agent-actions.js.map +1 -1
- package/dist/local-editor-agent-context.d.ts +29 -1
- package/dist/local-editor-agent-context.d.ts.map +1 -1
- package/dist/local-editor-agent-context.js +403 -0
- package/dist/local-editor-agent-context.js.map +1 -1
- package/dist/local-editor-harness.d.ts +103 -5
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +1982 -101
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-marker-graph.d.ts +92 -0
- package/dist/local-editor-marker-graph.d.ts.map +1 -0
- package/dist/local-editor-marker-graph.js +1432 -0
- package/dist/local-editor-marker-graph.js.map +1 -0
- package/dist/local-editor-performance-stats.js +1 -1
- package/dist/local-editor-performance-stats.js.map +1 -1
- package/dist/playable-babylon-facade.d.ts +2 -2
- package/dist/playable-babylon-facade.d.ts.map +1 -1
- package/dist/playable-babylon-facade.js +1 -1
- package/dist/playable-babylon-facade.js.map +1 -1
- package/dist/playable-local-editor-host.d.ts +52 -7
- package/dist/playable-local-editor-host.d.ts.map +1 -1
- package/dist/playable-local-editor-host.js +23 -1
- package/dist/playable-local-editor-host.js.map +1 -1
- package/dist/playable-sdk.d.ts +4 -2
- package/dist/playable-sdk.d.ts.map +1 -1
- package/dist/playable-sdk.js +2 -1
- package/dist/playable-sdk.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js +11 -0
- package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +114 -18
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +3 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +3 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.js +3 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts +52 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js +1395 -0
- package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +4 -65
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +28 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +235 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts +37 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js +451 -0
- package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +24 -10
- package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js +20 -4
- package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts +9 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js +64 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +44 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +311 -70
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -5
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts +5 -2
- package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts +5 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js +23 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +69 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +38 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +8 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +7 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +4 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +1471 -29
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts +52 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js +131 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +9 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +234 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +10 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +4 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1274 -55
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +3 -3
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +2 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js +17 -3
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts +3 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +68 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +56 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +531 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +103 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +9 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +3 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +3 -7
- package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +6 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts +2 -10
- package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +8 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +66 -31
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js +40 -2
- package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +20 -0
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +68 -27
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -4
- package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +2 -1
- package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
- package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
- package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts +57 -3
- package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/agent.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +19 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts +128 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js +2 -0
- package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js.map +1 -0
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
import { SPATIAL_MARKER_GRAPH_SCHEMA_VERSION, } from '@fps-games/editor-protocol';
|
|
2
|
+
export const LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID = '__marker_graph__';
|
|
3
|
+
const LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_COLLISION_SEPARATOR = ':';
|
|
4
|
+
const LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_MARKER_SEGMENT = ':marker:';
|
|
5
|
+
export function resolveSpatialMarkerGraph(graph) {
|
|
6
|
+
if (!graph)
|
|
7
|
+
return null;
|
|
8
|
+
return {
|
|
9
|
+
...graph,
|
|
10
|
+
schemaVersion: graph.schemaVersion ?? SPATIAL_MARKER_GRAPH_SCHEMA_VERSION,
|
|
11
|
+
markers: graph.markers.map(resolveSpatialMarkerNode),
|
|
12
|
+
relations: graph.relations.map(relation => ({ ...relation })),
|
|
13
|
+
metadata: graph.metadata ? { ...graph.metadata } : undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createMarkerGraphHierarchyItems(hierarchy, graph, visibility = {}) {
|
|
17
|
+
const markers = graph?.markers ?? [];
|
|
18
|
+
const hiddenMarkerIds = normalizeHiddenMarkerIds(visibility.hiddenMarkerIds);
|
|
19
|
+
const markerIds = new Set(markers.map(marker => marker.id));
|
|
20
|
+
const hierarchyWithMarkerVisibility = hierarchy.map((item) => {
|
|
21
|
+
if (item.role !== 'marker' || !markerIds.has(item.id))
|
|
22
|
+
return item;
|
|
23
|
+
const documentEnabled = item.enabled !== false;
|
|
24
|
+
const documentDirectEnabled = item.directEnabled !== false;
|
|
25
|
+
const visible = documentEnabled && !hiddenMarkerIds.has(item.id);
|
|
26
|
+
const directVisible = documentDirectEnabled && !hiddenMarkerIds.has(item.id);
|
|
27
|
+
return {
|
|
28
|
+
...item,
|
|
29
|
+
enabled: visible,
|
|
30
|
+
directEnabled: directVisible,
|
|
31
|
+
canToggleEnabled: item.canToggleEnabled ?? true,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
const virtualMarkers = markers.filter(marker => !isMarkerBackedByHierarchyRow(hierarchy, marker.id));
|
|
35
|
+
if (virtualMarkers.length === 0)
|
|
36
|
+
return hierarchyWithMarkerVisibility;
|
|
37
|
+
const rootId = resolveMarkerGraphHierarchyRootId(hierarchy, virtualMarkers);
|
|
38
|
+
const rootVisible = visibility.rootVisible !== false;
|
|
39
|
+
return [
|
|
40
|
+
...hierarchyWithMarkerVisibility,
|
|
41
|
+
{
|
|
42
|
+
id: rootId,
|
|
43
|
+
label: 'Markers',
|
|
44
|
+
role: 'group',
|
|
45
|
+
icon: 'view-overlay',
|
|
46
|
+
selectable: true,
|
|
47
|
+
enabled: rootVisible,
|
|
48
|
+
directEnabled: rootVisible,
|
|
49
|
+
canToggleEnabled: true,
|
|
50
|
+
protected: false,
|
|
51
|
+
canHaveChildren: false,
|
|
52
|
+
renamable: false,
|
|
53
|
+
deletable: false,
|
|
54
|
+
draggable: false,
|
|
55
|
+
},
|
|
56
|
+
...virtualMarkers.map(marker => ({
|
|
57
|
+
id: createMarkerGraphHierarchyMarkerIdForRoot(rootId, marker.id),
|
|
58
|
+
label: marker.label,
|
|
59
|
+
parentId: rootId,
|
|
60
|
+
role: 'marker',
|
|
61
|
+
icon: 'view-overlay',
|
|
62
|
+
selectable: true,
|
|
63
|
+
enabled: !hiddenMarkerIds.has(marker.id),
|
|
64
|
+
directEnabled: !hiddenMarkerIds.has(marker.id),
|
|
65
|
+
canToggleEnabled: true,
|
|
66
|
+
protected: false,
|
|
67
|
+
canHaveChildren: false,
|
|
68
|
+
renamable: false,
|
|
69
|
+
deletable: false,
|
|
70
|
+
draggable: false,
|
|
71
|
+
})),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
export function isSpatialMarkerHierarchyVisible(markerId, visibility = {}) {
|
|
75
|
+
return visibility.rootVisible !== false
|
|
76
|
+
&& !normalizeHiddenMarkerIds(visibility.hiddenMarkerIds).has(markerId);
|
|
77
|
+
}
|
|
78
|
+
export function isMarkerGraphHierarchyId(graph, id, hierarchy) {
|
|
79
|
+
return readSpatialMarkerIdFromHierarchySelection(graph, id, hierarchy) !== null;
|
|
80
|
+
}
|
|
81
|
+
export function isMarkerGraphHierarchyRootRowId(graph, id, hierarchy) {
|
|
82
|
+
const markers = graph?.markers ?? [];
|
|
83
|
+
const virtualMarkers = markers.filter(marker => !isMarkerBackedByHierarchyRow(hierarchy, marker.id));
|
|
84
|
+
if (!id || virtualMarkers.length === 0)
|
|
85
|
+
return false;
|
|
86
|
+
return id === resolveMarkerGraphHierarchyRootId(hierarchy, virtualMarkers);
|
|
87
|
+
}
|
|
88
|
+
export function createMarkerGraphHierarchyMarkerId(markerId) {
|
|
89
|
+
return createMarkerGraphHierarchyMarkerIdForRoot(LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID, markerId);
|
|
90
|
+
}
|
|
91
|
+
export function isMarkerGraphHierarchyRootId(id) {
|
|
92
|
+
return id === LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID
|
|
93
|
+
|| isMarkerGraphHierarchyRootCollisionId(id);
|
|
94
|
+
}
|
|
95
|
+
export function readMarkerGraphHierarchyMarkerId(id) {
|
|
96
|
+
if (!id)
|
|
97
|
+
return null;
|
|
98
|
+
const markerSegmentIndex = id.indexOf(LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_MARKER_SEGMENT);
|
|
99
|
+
if (markerSegmentIndex < 0)
|
|
100
|
+
return null;
|
|
101
|
+
const rootId = id.slice(0, markerSegmentIndex);
|
|
102
|
+
if (!isMarkerGraphHierarchyRootId(rootId))
|
|
103
|
+
return null;
|
|
104
|
+
const encoded = id.slice(markerSegmentIndex + LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_MARKER_SEGMENT.length);
|
|
105
|
+
try {
|
|
106
|
+
return decodeURIComponent(encoded);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function readMarkerGraphHierarchyMarkerIdForGraph(graph, id, hierarchy) {
|
|
113
|
+
const markerId = readMarkerGraphHierarchyMarkerId(id);
|
|
114
|
+
if (!markerId)
|
|
115
|
+
return null;
|
|
116
|
+
const markers = graph?.markers ?? [];
|
|
117
|
+
if (!markers.some(marker => marker.id === markerId))
|
|
118
|
+
return null;
|
|
119
|
+
if (!hierarchy)
|
|
120
|
+
return markerId;
|
|
121
|
+
const virtualMarkers = markers.filter(marker => !isMarkerBackedByHierarchyRow(hierarchy, marker.id));
|
|
122
|
+
if (!virtualMarkers.some(marker => marker.id === markerId))
|
|
123
|
+
return null;
|
|
124
|
+
const rootId = resolveMarkerGraphHierarchyRootId(hierarchy, virtualMarkers);
|
|
125
|
+
return id === createMarkerGraphHierarchyMarkerIdForRoot(rootId, markerId)
|
|
126
|
+
? markerId
|
|
127
|
+
: null;
|
|
128
|
+
}
|
|
129
|
+
export function isSpatialMarkerBackedByHierarchyRow(graph, id, hierarchy) {
|
|
130
|
+
if (!id)
|
|
131
|
+
return false;
|
|
132
|
+
return !!graph?.markers.some(marker => marker.id === id)
|
|
133
|
+
&& isMarkerBackedByHierarchyRow(hierarchy, id);
|
|
134
|
+
}
|
|
135
|
+
export function readSpatialMarkerIdFromHierarchySelection(graph, id, hierarchy) {
|
|
136
|
+
if (!id)
|
|
137
|
+
return null;
|
|
138
|
+
const markers = graph?.markers ?? [];
|
|
139
|
+
if (!hierarchy && markers.some(marker => marker.id === id))
|
|
140
|
+
return id;
|
|
141
|
+
if (hierarchy && isMarkerBackedByHierarchyRow(hierarchy, id) && markers.some(marker => marker.id === id)) {
|
|
142
|
+
return id;
|
|
143
|
+
}
|
|
144
|
+
return readMarkerGraphHierarchyMarkerIdForGraph(graph, id, hierarchy);
|
|
145
|
+
}
|
|
146
|
+
function resolveMarkerGraphHierarchyRootId(hierarchy, markers) {
|
|
147
|
+
const existingIds = new Set(hierarchy.map(item => item.id));
|
|
148
|
+
if (isMarkerGraphRootCandidateAvailable(LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID, existingIds, markers)) {
|
|
149
|
+
return LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID;
|
|
150
|
+
}
|
|
151
|
+
let index = 1;
|
|
152
|
+
let candidate = `${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID}${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_COLLISION_SEPARATOR}${index}`;
|
|
153
|
+
while (!isMarkerGraphRootCandidateAvailable(candidate, existingIds, markers)) {
|
|
154
|
+
index += 1;
|
|
155
|
+
candidate = `${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID}${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_COLLISION_SEPARATOR}${index}`;
|
|
156
|
+
}
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
function isMarkerGraphRootCandidateAvailable(rootId, existingIds, markers) {
|
|
160
|
+
if (existingIds.has(rootId))
|
|
161
|
+
return false;
|
|
162
|
+
return markers.every(marker => !existingIds.has(createMarkerGraphHierarchyMarkerIdForRoot(rootId, marker.id)));
|
|
163
|
+
}
|
|
164
|
+
function isMarkerGraphHierarchyRootCollisionId(id) {
|
|
165
|
+
if (!id?.startsWith(`${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID}${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_COLLISION_SEPARATOR}`)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const suffix = id.slice(LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_ID.length + LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_ROOT_COLLISION_SEPARATOR.length);
|
|
169
|
+
return /^[1-9]\d*$/.test(suffix);
|
|
170
|
+
}
|
|
171
|
+
function createMarkerGraphHierarchyMarkerIdForRoot(rootId, markerId) {
|
|
172
|
+
return `${rootId}${LOCAL_EDITOR_MARKER_GRAPH_HIERARCHY_MARKER_SEGMENT}${encodeURIComponent(markerId)}`;
|
|
173
|
+
}
|
|
174
|
+
function isMarkerBackedByHierarchyRow(hierarchy, markerId) {
|
|
175
|
+
return hierarchy.some(item => item.id === markerId && item.role === 'marker');
|
|
176
|
+
}
|
|
177
|
+
function normalizeHiddenMarkerIds(value) {
|
|
178
|
+
if (!value)
|
|
179
|
+
return new Set();
|
|
180
|
+
return value instanceof Set ? value : new Set(value);
|
|
181
|
+
}
|
|
182
|
+
export function createMarkerGraphInspectorObject(input) {
|
|
183
|
+
const markerId = readSpatialMarkerIdFromHierarchySelection(input.graph, input.markerId, input.hierarchy);
|
|
184
|
+
if (!markerId)
|
|
185
|
+
return null;
|
|
186
|
+
const marker = findSpatialMarkerById(input.graph, markerId);
|
|
187
|
+
if (!marker)
|
|
188
|
+
return null;
|
|
189
|
+
const virtualHierarchyMarkerId = readMarkerGraphHierarchyMarkerIdForGraph(input.graph, input.markerId, input.hierarchy);
|
|
190
|
+
const hierarchyMarkerId = (input.hierarchy && isMarkerBackedByHierarchyRow(input.hierarchy, marker.id)) || virtualHierarchyMarkerId
|
|
191
|
+
? input.markerId
|
|
192
|
+
: createMarkerGraphHierarchyMarkerId(marker.id);
|
|
193
|
+
const resolvedMarker = resolveSpatialMarkerNode(marker);
|
|
194
|
+
const directRelations = input.graph ? getSpatialMarkerDirectRelations(input.graph, marker.id) : [];
|
|
195
|
+
const sections = [
|
|
196
|
+
createMarkerIdentityInspectorSection(input.document, resolvedMarker, input.markerTypes ?? []),
|
|
197
|
+
createMarkerGeometryInspectorSection(input.document, resolvedMarker),
|
|
198
|
+
createMarkerSpatialInspectorSection(input.document, resolvedMarker),
|
|
199
|
+
createMarkerRelationsInspectorSection(input.document, marker.id, directRelations, input.markerTypes ?? [], input.relationTypes ?? [], input.graph, input.hierarchy ?? [], input.activeRelationId ?? null, input.relationPick ?? null, input.relationVisibility ?? {}),
|
|
200
|
+
];
|
|
201
|
+
return {
|
|
202
|
+
targetIds: [hierarchyMarkerId],
|
|
203
|
+
activeId: hierarchyMarkerId,
|
|
204
|
+
label: marker.label,
|
|
205
|
+
document: input.document,
|
|
206
|
+
selection: {
|
|
207
|
+
targetIds: [hierarchyMarkerId],
|
|
208
|
+
activeId: hierarchyMarkerId,
|
|
209
|
+
targetKind: 'marker',
|
|
210
|
+
document: input.document,
|
|
211
|
+
capabilities: ['markerGraph'],
|
|
212
|
+
},
|
|
213
|
+
sections,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export function createMarkerGraphRootInspectorObject(input) {
|
|
217
|
+
if (!isMarkerGraphHierarchyRootRowId(input.graph, input.rootId, input.hierarchy))
|
|
218
|
+
return null;
|
|
219
|
+
const graph = input.graph ?? {
|
|
220
|
+
schemaVersion: SPATIAL_MARKER_GRAPH_SCHEMA_VERSION,
|
|
221
|
+
markers: [],
|
|
222
|
+
relations: [],
|
|
223
|
+
};
|
|
224
|
+
const markerTypes = input.markerTypes ?? [];
|
|
225
|
+
const relationTypes = input.relationTypes ?? [];
|
|
226
|
+
return {
|
|
227
|
+
targetIds: [input.rootId],
|
|
228
|
+
activeId: input.rootId,
|
|
229
|
+
label: 'Markers',
|
|
230
|
+
document: input.document,
|
|
231
|
+
selection: {
|
|
232
|
+
targetIds: [input.rootId],
|
|
233
|
+
activeId: input.rootId,
|
|
234
|
+
targetKind: 'marker-graph',
|
|
235
|
+
document: input.document,
|
|
236
|
+
capabilities: ['markerGraph'],
|
|
237
|
+
},
|
|
238
|
+
sections: [
|
|
239
|
+
{
|
|
240
|
+
id: 'markerGraph',
|
|
241
|
+
title: 'Marker Graph',
|
|
242
|
+
order: 0,
|
|
243
|
+
persistence: 'readonly',
|
|
244
|
+
properties: [
|
|
245
|
+
createMarkerInspectorProperty(input.document, {
|
|
246
|
+
path: 'markerGraph.schemaVersion',
|
|
247
|
+
label: 'Schema',
|
|
248
|
+
value: graph.schemaVersion,
|
|
249
|
+
valueType: 'number',
|
|
250
|
+
control: 'readonly',
|
|
251
|
+
readOnly: true,
|
|
252
|
+
persistence: 'readonly',
|
|
253
|
+
order: 0,
|
|
254
|
+
}),
|
|
255
|
+
createMarkerInspectorProperty(input.document, {
|
|
256
|
+
path: 'markerGraph.markers.count',
|
|
257
|
+
label: 'Markers',
|
|
258
|
+
value: graph.markers.length,
|
|
259
|
+
valueType: 'number',
|
|
260
|
+
control: 'readonly',
|
|
261
|
+
readOnly: true,
|
|
262
|
+
persistence: 'readonly',
|
|
263
|
+
order: 1,
|
|
264
|
+
}),
|
|
265
|
+
createMarkerInspectorProperty(input.document, {
|
|
266
|
+
path: 'markerGraph.relations.count',
|
|
267
|
+
label: 'Relations',
|
|
268
|
+
value: graph.relations.length,
|
|
269
|
+
valueType: 'number',
|
|
270
|
+
control: 'readonly',
|
|
271
|
+
readOnly: true,
|
|
272
|
+
persistence: 'readonly',
|
|
273
|
+
order: 2,
|
|
274
|
+
}),
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: 'markerGraphCatalog',
|
|
279
|
+
title: 'Catalog',
|
|
280
|
+
order: 10,
|
|
281
|
+
persistence: 'readonly',
|
|
282
|
+
properties: [
|
|
283
|
+
createMarkerInspectorProperty(input.document, {
|
|
284
|
+
path: 'markerGraph.markerTypes.summary',
|
|
285
|
+
label: 'Types',
|
|
286
|
+
value: markerTypes.map(definition => definition.label).join(', ') || 'None',
|
|
287
|
+
valueType: 'string',
|
|
288
|
+
control: 'readonly',
|
|
289
|
+
readOnly: true,
|
|
290
|
+
persistence: 'readonly',
|
|
291
|
+
order: 0,
|
|
292
|
+
}),
|
|
293
|
+
createMarkerInspectorProperty(input.document, {
|
|
294
|
+
path: 'markerGraph.relationTypes.summary',
|
|
295
|
+
label: 'Relations',
|
|
296
|
+
value: relationTypes.map(definition => definition.label).join(', ') || 'None',
|
|
297
|
+
valueType: 'string',
|
|
298
|
+
control: 'readonly',
|
|
299
|
+
readOnly: true,
|
|
300
|
+
persistence: 'readonly',
|
|
301
|
+
order: 1,
|
|
302
|
+
}),
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
export function createMarkerPanelState(graph, markerTypes, relationTypes = [], hierarchy = [], activeMarkerId = null, activeRelationId = null, relationPick = null, visibility = {}) {
|
|
309
|
+
if (!graph)
|
|
310
|
+
return null;
|
|
311
|
+
const usedByType = new Map();
|
|
312
|
+
const markerKindByType = new Map();
|
|
313
|
+
for (const marker of graph.markers) {
|
|
314
|
+
if (!marker.markerType.trim())
|
|
315
|
+
continue;
|
|
316
|
+
usedByType.set(marker.markerType, (usedByType.get(marker.markerType) ?? 0) + 1);
|
|
317
|
+
if (!markerKindByType.has(marker.markerType))
|
|
318
|
+
markerKindByType.set(marker.markerType, marker.kind);
|
|
319
|
+
}
|
|
320
|
+
const entries = new Map();
|
|
321
|
+
for (const definition of markerTypes)
|
|
322
|
+
entries.set(definition.type, definition);
|
|
323
|
+
for (const marker of graph.markers) {
|
|
324
|
+
if (!marker.markerType.trim())
|
|
325
|
+
continue;
|
|
326
|
+
if (!entries.has(marker.markerType)) {
|
|
327
|
+
entries.set(marker.markerType, {
|
|
328
|
+
type: marker.markerType,
|
|
329
|
+
label: marker.markerType,
|
|
330
|
+
kind: markerKindByType.get(marker.markerType) ?? marker.kind,
|
|
331
|
+
description: 'Marker type referenced by authored markers but not present in the catalog.',
|
|
332
|
+
metadata: {
|
|
333
|
+
builtIn: false,
|
|
334
|
+
inferred: true,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const createMarkerTypeItem = (definition) => {
|
|
340
|
+
const usedByCount = usedByType.get(definition.type) ?? 0;
|
|
341
|
+
const builtIn = definition.metadata?.builtIn === true;
|
|
342
|
+
return {
|
|
343
|
+
type: definition.type,
|
|
344
|
+
label: definition.label,
|
|
345
|
+
kind: definition.kind,
|
|
346
|
+
description: definition.description,
|
|
347
|
+
defaultTags: definition.defaultTags ? [...definition.defaultTags] : undefined,
|
|
348
|
+
builtIn,
|
|
349
|
+
usedByCount,
|
|
350
|
+
deletable: !builtIn && usedByCount === 0,
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
const markerTypeItems = [...entries.values()]
|
|
354
|
+
.sort((left, right) => left.label.localeCompare(right.label))
|
|
355
|
+
.map(createMarkerTypeItem);
|
|
356
|
+
const panel = {
|
|
357
|
+
title: '标记',
|
|
358
|
+
summary: '管理项目标记类型与标记关系。',
|
|
359
|
+
markerTypes: markerTypeItems,
|
|
360
|
+
relationTypes: createMarkerRelationTypeItems(relationTypes),
|
|
361
|
+
relationEndpointOptions: createMarkerRelationEndpointOptions(graph, hierarchy, visibility),
|
|
362
|
+
relations: createMarkerRelationItems(graph, relationTypes, hierarchy, activeMarkerId, activeRelationId, visibility),
|
|
363
|
+
activeMarkerId,
|
|
364
|
+
activeRelationId,
|
|
365
|
+
relationPick,
|
|
366
|
+
markersCount: graph.markers.length,
|
|
367
|
+
relationsCount: graph.relations.length,
|
|
368
|
+
markersVisible: visibility.markersVisible !== false,
|
|
369
|
+
...(visibility.markersVisible === false
|
|
370
|
+
? { status: '标记视图已关闭,关系线暂不显示。', statusTone: 'warning' }
|
|
371
|
+
: {}),
|
|
372
|
+
};
|
|
373
|
+
return panel;
|
|
374
|
+
}
|
|
375
|
+
export function createMarkerRelationCommand(input, graph, relationTypes = [], hierarchy = [], activeMarkerId = null) {
|
|
376
|
+
if (!graph) {
|
|
377
|
+
return { command: null, status: '当前文档没有 marker graph。', statusTone: 'warning' };
|
|
378
|
+
}
|
|
379
|
+
if (input.action === 'select') {
|
|
380
|
+
const relationId = input.relationId.trim();
|
|
381
|
+
if (!relationId)
|
|
382
|
+
return { command: null, status: '请选择要高亮的关系。', statusTone: 'warning' };
|
|
383
|
+
if (!graph.relations.some(relation => relation.id === relationId)) {
|
|
384
|
+
return { command: null, status: `关系不存在:${relationId}`, statusTone: 'warning' };
|
|
385
|
+
}
|
|
386
|
+
return { command: null, status: `高亮关系:${relationId}`, statusTone: 'success' };
|
|
387
|
+
}
|
|
388
|
+
if (input.action === 'delete') {
|
|
389
|
+
const relationId = input.relationId.trim();
|
|
390
|
+
if (!relationId)
|
|
391
|
+
return { command: null, status: '请选择要删除的关系。', statusTone: 'warning' };
|
|
392
|
+
if (!graph.relations.some(relation => relation.id === relationId)) {
|
|
393
|
+
return { command: null, status: `关系不存在:${relationId}`, statusTone: 'warning' };
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
command: { type: 'relation.delete', relationId },
|
|
397
|
+
status: `删除关系:${relationId}`,
|
|
398
|
+
statusTone: 'success',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (input.action !== 'create') {
|
|
402
|
+
return { command: null, status: '暂不支持的关系操作。', statusTone: 'warning' };
|
|
403
|
+
}
|
|
404
|
+
const type = input.type.trim();
|
|
405
|
+
if (!type)
|
|
406
|
+
return { command: null, status: '请选择关系类型。', statusTone: 'warning' };
|
|
407
|
+
const fromId = input.fromId?.trim() || activeMarkerId || '';
|
|
408
|
+
const fromKind = input.fromKind?.trim() || (fromId && activeMarkerId && fromId === activeMarkerId ? 'marker' : undefined);
|
|
409
|
+
const from = createRelationEndpointFromInput(fromKind, fromId, graph, hierarchy);
|
|
410
|
+
const to = createRelationEndpointFromInput(input.toKind, input.toId || '', graph, hierarchy);
|
|
411
|
+
if (!from)
|
|
412
|
+
return { command: null, status: '请选择有效的起点。', statusTone: 'warning' };
|
|
413
|
+
if (!to)
|
|
414
|
+
return { command: null, status: '请选择有效的终点。', statusTone: 'warning' };
|
|
415
|
+
if (from.kind === to.kind && from.id === to.id) {
|
|
416
|
+
return { command: null, status: '关系起点和终点不能相同。', statusTone: 'warning' };
|
|
417
|
+
}
|
|
418
|
+
const duplicate = graph.relations.find(relation => (relation.type === type
|
|
419
|
+
&& relation.from.kind === from.kind
|
|
420
|
+
&& relation.from.id === from.id
|
|
421
|
+
&& relation.to.kind === to.kind
|
|
422
|
+
&& relation.to.id === to.id));
|
|
423
|
+
if (duplicate) {
|
|
424
|
+
return { command: null, status: `关系已存在:${duplicate.id}`, statusTone: 'warning' };
|
|
425
|
+
}
|
|
426
|
+
const typeDefinition = relationTypes.find(definition => definition.type === type);
|
|
427
|
+
const directed = input.directed ?? typeDefinition?.directed ?? true;
|
|
428
|
+
const relation = {
|
|
429
|
+
id: createUniqueSpatialRelationId(graph.relations, type, from, to),
|
|
430
|
+
type,
|
|
431
|
+
label: typeDefinition?.label,
|
|
432
|
+
from,
|
|
433
|
+
to,
|
|
434
|
+
directed,
|
|
435
|
+
};
|
|
436
|
+
return {
|
|
437
|
+
command: { type: 'relation.create', relation },
|
|
438
|
+
status: `创建关系:${from.label ?? from.id} ${typeDefinition?.label ?? type} ${to.label ?? to.id}`,
|
|
439
|
+
statusTone: 'success',
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
export function createMarkerRelationCreateManyCommand(input, graph, relationTypes = [], hierarchy = [], activeMarkerId = null) {
|
|
443
|
+
if (!graph)
|
|
444
|
+
return { command: null, status: '当前文档没有 marker graph。', statusTone: 'warning' };
|
|
445
|
+
const type = input.type.trim();
|
|
446
|
+
if (!type)
|
|
447
|
+
return { command: null, status: '请选择关系类型。', statusTone: 'warning' };
|
|
448
|
+
const fromId = input.fromId?.trim() || activeMarkerId || '';
|
|
449
|
+
const fromKind = input.fromKind?.trim() || (fromId && activeMarkerId && fromId === activeMarkerId ? 'marker' : undefined);
|
|
450
|
+
const from = createRelationEndpointFromInput(fromKind, fromId, graph, hierarchy);
|
|
451
|
+
if (!from)
|
|
452
|
+
return { command: null, status: '请选择有效的起点。', statusTone: 'warning' };
|
|
453
|
+
const typeDefinition = relationTypes.find(definition => definition.type === type);
|
|
454
|
+
const directed = input.directed ?? typeDefinition?.directed ?? true;
|
|
455
|
+
const nextRelations = [];
|
|
456
|
+
let duplicateCount = 0;
|
|
457
|
+
let invalidCount = 0;
|
|
458
|
+
const seenTargets = new Set();
|
|
459
|
+
for (const endpointInput of input.toEndpoints) {
|
|
460
|
+
const targetKey = createRelationEndpointKey(endpointInput.kind ?? '', endpointInput.id ?? '');
|
|
461
|
+
if (seenTargets.has(targetKey)) {
|
|
462
|
+
duplicateCount += 1;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
seenTargets.add(targetKey);
|
|
466
|
+
const to = createRelationEndpointFromInput(endpointInput.kind, endpointInput.id ?? '', graph, hierarchy);
|
|
467
|
+
if (!to || (from.kind === to.kind && from.id === to.id)) {
|
|
468
|
+
invalidCount += 1;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const duplicate = [...graph.relations, ...nextRelations].find(relation => isSameRelationIdentity(relation, type, from, to));
|
|
472
|
+
if (duplicate) {
|
|
473
|
+
duplicateCount += 1;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
nextRelations.push({
|
|
477
|
+
id: createUniqueSpatialRelationId([...graph.relations, ...nextRelations], type, from, to),
|
|
478
|
+
type,
|
|
479
|
+
label: typeDefinition?.label,
|
|
480
|
+
from,
|
|
481
|
+
to,
|
|
482
|
+
directed,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (nextRelations.length === 0) {
|
|
486
|
+
const reason = invalidCount > 0 && duplicateCount > 0
|
|
487
|
+
? `无可创建关系:${invalidCount} 个无效端点,${duplicateCount} 个重复关系。`
|
|
488
|
+
: invalidCount > 0
|
|
489
|
+
? `无可创建关系:${invalidCount} 个无效端点。`
|
|
490
|
+
: duplicateCount > 0
|
|
491
|
+
? `关系已存在,跳过 ${duplicateCount} 个重复关系。`
|
|
492
|
+
: '请选择有效的终点。';
|
|
493
|
+
return { command: null, status: reason, statusTone: 'warning' };
|
|
494
|
+
}
|
|
495
|
+
const skippedParts = [
|
|
496
|
+
duplicateCount > 0 ? `跳过 ${duplicateCount} 个重复关系` : '',
|
|
497
|
+
invalidCount > 0 ? `跳过 ${invalidCount} 个无效端点` : '',
|
|
498
|
+
].filter(Boolean);
|
|
499
|
+
return {
|
|
500
|
+
command: nextRelations.length === 1
|
|
501
|
+
? { type: 'relation.create', relation: nextRelations[0] }
|
|
502
|
+
: { type: 'relation.create-many', relations: nextRelations },
|
|
503
|
+
status: skippedParts.length > 0
|
|
504
|
+
? `创建 ${nextRelations.length} 条关系,${skippedParts.join(',')}。`
|
|
505
|
+
: `创建 ${nextRelations.length} 条关系。`,
|
|
506
|
+
statusTone: skippedParts.length > 0 ? 'warning' : 'success',
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
export function createMarkerRelationDeleteManyCommand(relationIds, graph) {
|
|
510
|
+
if (!graph)
|
|
511
|
+
return { command: null, status: '当前文档没有 marker graph。', statusTone: 'warning' };
|
|
512
|
+
const existingIds = new Set(graph.relations.map(relation => relation.id));
|
|
513
|
+
const ids = [...new Set(relationIds.map(id => id.trim()).filter(id => id && existingIds.has(id)))];
|
|
514
|
+
if (ids.length === 0)
|
|
515
|
+
return { command: null, status: '请选择要删除的关系。', statusTone: 'warning' };
|
|
516
|
+
return {
|
|
517
|
+
command: ids.length === 1
|
|
518
|
+
? { type: 'relation.delete', relationId: ids[0] }
|
|
519
|
+
: { type: 'relation.delete-many', relationIds: ids },
|
|
520
|
+
status: `删除 ${ids.length} 条关系。`,
|
|
521
|
+
statusTone: 'success',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
export function createMarkerRelationDeleteConnectedCommand(markerId, graph) {
|
|
525
|
+
const id = markerId.trim();
|
|
526
|
+
if (!id)
|
|
527
|
+
return { command: null, status: '请选择标记。', statusTone: 'warning' };
|
|
528
|
+
const relationIds = (graph?.relations ?? [])
|
|
529
|
+
.filter(relation => endpointMatchesMarker(relation.from, id) || endpointMatchesMarker(relation.to, id))
|
|
530
|
+
.map(relation => relation.id);
|
|
531
|
+
return createMarkerRelationDeleteManyCommand(relationIds, graph);
|
|
532
|
+
}
|
|
533
|
+
export function createMarkerRelationReverseCommand(relationId, graph) {
|
|
534
|
+
if (!graph)
|
|
535
|
+
return { command: null, status: '当前文档没有 marker graph。', statusTone: 'warning' };
|
|
536
|
+
const id = relationId.trim();
|
|
537
|
+
if (!id)
|
|
538
|
+
return { command: null, status: '请选择要反转的关系。', statusTone: 'warning' };
|
|
539
|
+
const relation = graph.relations.find(candidate => candidate.id === id) ?? null;
|
|
540
|
+
if (!relation)
|
|
541
|
+
return { command: null, status: `关系不存在:${id}`, statusTone: 'warning' };
|
|
542
|
+
const duplicate = graph.relations.find(candidate => (candidate.id !== relation.id
|
|
543
|
+
&& isSameRelationIdentity(candidate, relation.type, relation.to, relation.from)));
|
|
544
|
+
if (duplicate)
|
|
545
|
+
return { command: null, status: `反转后关系会重复:${duplicate.id}`, statusTone: 'warning' };
|
|
546
|
+
return {
|
|
547
|
+
command: { type: 'relation.reverse', relationId: relation.id },
|
|
548
|
+
status: `反转关系:${relation.label ?? relation.type}`,
|
|
549
|
+
statusTone: 'success',
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function createMarkerRelationTypeItems(relationTypes) {
|
|
553
|
+
return relationTypes.map(definition => ({
|
|
554
|
+
type: definition.type,
|
|
555
|
+
label: definition.label || definition.type,
|
|
556
|
+
directed: definition.directed,
|
|
557
|
+
description: definition.description,
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
export function createMarkerRelationEndpointOptions(graph, hierarchy = [], visibility = {}) {
|
|
561
|
+
if (!graph)
|
|
562
|
+
return [];
|
|
563
|
+
const endpoints = new Map();
|
|
564
|
+
for (const marker of graph.markers) {
|
|
565
|
+
const hidden = isMarkerEndpointHiddenForRelationPanel(marker.id, graph, hierarchy, visibility);
|
|
566
|
+
endpoints.set(createRelationEndpointKey('marker', marker.id), {
|
|
567
|
+
kind: 'marker',
|
|
568
|
+
id: marker.id,
|
|
569
|
+
label: marker.label || marker.id,
|
|
570
|
+
...(hidden ? { visibility: 'hidden', status: '已隐藏' } : {}),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
for (const item of hierarchy) {
|
|
574
|
+
if (!isSceneObjectRelationEndpoint(item))
|
|
575
|
+
continue;
|
|
576
|
+
const key = createRelationEndpointKey('scene-object', item.id);
|
|
577
|
+
if (endpoints.has(key))
|
|
578
|
+
continue;
|
|
579
|
+
endpoints.set(key, {
|
|
580
|
+
kind: 'scene-object',
|
|
581
|
+
id: item.id,
|
|
582
|
+
label: item.label || item.id,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return [...endpoints.values()].sort((left, right) => {
|
|
586
|
+
const kindOrder = relationEndpointKindOrder(left.kind) - relationEndpointKindOrder(right.kind);
|
|
587
|
+
return kindOrder || left.label.localeCompare(right.label) || left.id.localeCompare(right.id);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function isMarkerEndpointHiddenForRelationPanel(markerId, graph, hierarchy, visibility) {
|
|
591
|
+
const hiddenMarkerIds = normalizeHiddenMarkerIds(visibility.hiddenMarkerIds);
|
|
592
|
+
if (hiddenMarkerIds.has(markerId))
|
|
593
|
+
return true;
|
|
594
|
+
if (isMarkerBackedByHierarchyRow(hierarchy, markerId)) {
|
|
595
|
+
return hierarchy.find(item => item.id === markerId)?.enabled === false;
|
|
596
|
+
}
|
|
597
|
+
return visibility.rootVisible === false;
|
|
598
|
+
}
|
|
599
|
+
function createMarkerRelationItems(graph, relationTypes, hierarchy, activeMarkerId, activeRelationId, visibility) {
|
|
600
|
+
const endpointOptions = new Map(createMarkerRelationEndpointOptions(graph, hierarchy, visibility).map(endpoint => [createRelationEndpointKey(endpoint.kind, endpoint.id), endpoint]));
|
|
601
|
+
const relationTypeLabels = new Map(relationTypes.map(definition => [definition.type, definition.label || definition.type]));
|
|
602
|
+
return graph.relations.map((relation) => {
|
|
603
|
+
const from = createRelationEndpointItem(relation.from, endpointOptions);
|
|
604
|
+
const to = createRelationEndpointItem(relation.to, endpointOptions);
|
|
605
|
+
const endpointStatus = [from.status, to.status].filter(Boolean).join('; ');
|
|
606
|
+
const visibilityStatus = visibility.markersVisible === false
|
|
607
|
+
? '标记视图已关闭,关系线暂不显示'
|
|
608
|
+
: endpointStatus;
|
|
609
|
+
return {
|
|
610
|
+
id: relation.id,
|
|
611
|
+
type: relation.type,
|
|
612
|
+
typeLabel: relation.label || relationTypeLabels.get(relation.type) || relation.type,
|
|
613
|
+
directed: relation.directed ?? relationTypes.find(definition => definition.type === relation.type)?.directed ?? true,
|
|
614
|
+
from,
|
|
615
|
+
to,
|
|
616
|
+
selected: !!activeMarkerId && (endpointMatchesMarker(relation.from, activeMarkerId) || endpointMatchesMarker(relation.to, activeMarkerId)),
|
|
617
|
+
active: !!activeRelationId && relation.id === activeRelationId,
|
|
618
|
+
...(visibilityStatus ? { status: visibilityStatus, statusTone: 'warning' } : {}),
|
|
619
|
+
};
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
function isSameRelationIdentity(relation, type, from, to) {
|
|
623
|
+
return relation.type === type
|
|
624
|
+
&& relation.from.kind === from.kind
|
|
625
|
+
&& relation.from.id === from.id
|
|
626
|
+
&& relation.to.kind === to.kind
|
|
627
|
+
&& relation.to.id === to.id;
|
|
628
|
+
}
|
|
629
|
+
function createRelationEndpointItem(endpoint, endpointOptions) {
|
|
630
|
+
const match = endpointOptions.get(createRelationEndpointKey(endpoint.kind, endpoint.id));
|
|
631
|
+
if (match)
|
|
632
|
+
return { ...match };
|
|
633
|
+
return {
|
|
634
|
+
kind: endpoint.kind,
|
|
635
|
+
id: endpoint.id,
|
|
636
|
+
label: endpoint.label || endpoint.id,
|
|
637
|
+
disabled: true,
|
|
638
|
+
status: isSupportedRelationEndpointKind(endpoint.kind)
|
|
639
|
+
? '目标不存在'
|
|
640
|
+
: `暂不支持的端点类型:${endpoint.kind}`,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function createRelationEndpointFromInput(kindInput, idInput, graph, hierarchy) {
|
|
644
|
+
const kind = normalizeRelationEndpointKind(kindInput);
|
|
645
|
+
const id = idInput?.trim() ?? '';
|
|
646
|
+
if (!kind || !id)
|
|
647
|
+
return null;
|
|
648
|
+
const endpoint = createMarkerRelationEndpointOptions(graph, hierarchy)
|
|
649
|
+
.find(candidate => candidate.kind === kind && candidate.id === id && candidate.disabled !== true);
|
|
650
|
+
if (!endpoint)
|
|
651
|
+
return null;
|
|
652
|
+
return {
|
|
653
|
+
kind,
|
|
654
|
+
id,
|
|
655
|
+
label: endpoint.label,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function normalizeRelationEndpointKind(kind) {
|
|
659
|
+
const normalized = kind?.trim();
|
|
660
|
+
return normalized === 'marker' || normalized === 'scene-object' ? normalized : null;
|
|
661
|
+
}
|
|
662
|
+
function isSupportedRelationEndpointKind(kind) {
|
|
663
|
+
return kind === 'marker' || kind === 'scene-object';
|
|
664
|
+
}
|
|
665
|
+
function isSceneObjectRelationEndpoint(item) {
|
|
666
|
+
return item.role !== 'root' && item.role !== 'group' && item.role !== 'marker';
|
|
667
|
+
}
|
|
668
|
+
function createUniqueSpatialRelationId(relations, type, from, to) {
|
|
669
|
+
const existing = new Set(relations.map(relation => relation.id));
|
|
670
|
+
const base = [
|
|
671
|
+
'rel',
|
|
672
|
+
relationIdSegment(type),
|
|
673
|
+
relationIdSegment(from.kind),
|
|
674
|
+
relationIdSegment(from.id),
|
|
675
|
+
relationIdSegment(to.kind),
|
|
676
|
+
relationIdSegment(to.id),
|
|
677
|
+
].join('_');
|
|
678
|
+
if (!existing.has(base))
|
|
679
|
+
return base;
|
|
680
|
+
let index = 2;
|
|
681
|
+
let candidate = `${base}-${index}`;
|
|
682
|
+
while (existing.has(candidate)) {
|
|
683
|
+
index += 1;
|
|
684
|
+
candidate = `${base}-${index}`;
|
|
685
|
+
}
|
|
686
|
+
return candidate;
|
|
687
|
+
}
|
|
688
|
+
function relationIdSegment(value) {
|
|
689
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'unknown';
|
|
690
|
+
}
|
|
691
|
+
function createRelationEndpointKey(kind, id) {
|
|
692
|
+
return `${kind}:${id}`;
|
|
693
|
+
}
|
|
694
|
+
function relationEndpointKindOrder(kind) {
|
|
695
|
+
if (kind === 'marker')
|
|
696
|
+
return 0;
|
|
697
|
+
if (kind === 'scene-object')
|
|
698
|
+
return 1;
|
|
699
|
+
return 2;
|
|
700
|
+
}
|
|
701
|
+
export function createMarkerGraphPatchFromInspectorEdit(graph, payload, hierarchy, markerTypes = []) {
|
|
702
|
+
const markerId = readSpatialMarkerIdFromHierarchySelection(graph, payload.targetId, hierarchy);
|
|
703
|
+
if (!markerId)
|
|
704
|
+
return null;
|
|
705
|
+
const marker = findSpatialMarkerById(graph, markerId);
|
|
706
|
+
if (!marker)
|
|
707
|
+
return null;
|
|
708
|
+
if (payload.path === 'marker.label') {
|
|
709
|
+
const label = typeof payload.value === 'string' ? payload.value.trim() : '';
|
|
710
|
+
if (!label)
|
|
711
|
+
return null;
|
|
712
|
+
return {
|
|
713
|
+
type: 'marker.update',
|
|
714
|
+
markerId: marker.id,
|
|
715
|
+
patch: { label },
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
if (payload.path === 'marker.markerType') {
|
|
719
|
+
const markerType = typeof payload.value === 'string' ? payload.value.trim() : '';
|
|
720
|
+
if (!markerType)
|
|
721
|
+
return null;
|
|
722
|
+
const markerTypeDefinition = markerTypes.find(definition => definition.type === markerType) ?? null;
|
|
723
|
+
if (markerTypeDefinition) {
|
|
724
|
+
return {
|
|
725
|
+
type: 'marker.update',
|
|
726
|
+
markerId: marker.id,
|
|
727
|
+
patch: createMarkerPresentationPatch(marker, markerTypeDefinition),
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
type: 'marker.update',
|
|
732
|
+
markerId: marker.id,
|
|
733
|
+
patch: { markerType },
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
if (payload.path === 'marker.tags') {
|
|
737
|
+
return {
|
|
738
|
+
type: 'marker.update',
|
|
739
|
+
markerId: marker.id,
|
|
740
|
+
patch: {
|
|
741
|
+
tags: readMarkerTags(payload.value),
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
if (payload.path === 'marker.metadata.color') {
|
|
746
|
+
const color = readMarkerColor(payload.value);
|
|
747
|
+
if (!color)
|
|
748
|
+
return null;
|
|
749
|
+
return {
|
|
750
|
+
type: 'marker.update',
|
|
751
|
+
markerId: marker.id,
|
|
752
|
+
patch: {
|
|
753
|
+
metadata: {
|
|
754
|
+
...(marker.metadata ?? {}),
|
|
755
|
+
color,
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (payload.path === 'marker.metadata.note') {
|
|
761
|
+
const note = typeof payload.value === 'string' ? payload.value.trim() : '';
|
|
762
|
+
return {
|
|
763
|
+
type: 'marker.update',
|
|
764
|
+
markerId: marker.id,
|
|
765
|
+
patch: {
|
|
766
|
+
metadata: {
|
|
767
|
+
...(marker.metadata ?? {}),
|
|
768
|
+
note,
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
export function resolveSpatialMarkerNode(marker) {
|
|
776
|
+
return {
|
|
777
|
+
...marker,
|
|
778
|
+
tags: marker.tags ? [...marker.tags] : undefined,
|
|
779
|
+
metadata: marker.metadata ? { ...marker.metadata } : undefined,
|
|
780
|
+
spatial: resolveSpatialFacts(marker.geometry, marker),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
export function resolveSpatialFacts(geometry, marker) {
|
|
784
|
+
switch (geometry.kind) {
|
|
785
|
+
case 'box':
|
|
786
|
+
return resolveBoxSpatialFacts(geometry.center, geometry.size, geometry.rotation, marker);
|
|
787
|
+
case 'point':
|
|
788
|
+
if (geometry.coordinateSpace === 'world') {
|
|
789
|
+
return {
|
|
790
|
+
status: 'resolved',
|
|
791
|
+
coordinateSpace: 'world',
|
|
792
|
+
origin: cloneVec3(geometry.position),
|
|
793
|
+
bounds: createBounds([geometry.position]),
|
|
794
|
+
dimensions: createVec3(0, 0, 0),
|
|
795
|
+
localFrame: createMarkerLocalFrame(geometry.position, marker),
|
|
796
|
+
target: geometry.target,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return createUnresolvedSpatialFacts('Local-space point markers require a target transform resolver and are not resolved in the MVP.', geometry.target);
|
|
800
|
+
case 'object-bounds':
|
|
801
|
+
if (marker?.semanticFrame?.origin) {
|
|
802
|
+
return {
|
|
803
|
+
status: 'resolved',
|
|
804
|
+
coordinateSpace: 'world',
|
|
805
|
+
origin: cloneVec3(marker.semanticFrame.origin),
|
|
806
|
+
bounds: createBounds([marker.semanticFrame.origin]),
|
|
807
|
+
dimensions: createVec3(0, 0, 0),
|
|
808
|
+
localFrame: createMarkerLocalFrame(marker.semanticFrame.origin, marker),
|
|
809
|
+
target: geometry.target,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
return createUnresolvedSpatialFacts('Object-bounds markers require an object bounds resolver and are not resolved in this pure graph pass.', geometry.target);
|
|
813
|
+
case 'polyhedron':
|
|
814
|
+
if (geometry.vertices.length === 0) {
|
|
815
|
+
return createUnresolvedSpatialFacts('Polyhedron marker has no vertices.');
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
status: 'resolved',
|
|
819
|
+
coordinateSpace: 'world',
|
|
820
|
+
origin: averageVec3(geometry.vertices),
|
|
821
|
+
bounds: createBounds(geometry.vertices),
|
|
822
|
+
dimensions: createBounds(geometry.vertices).size,
|
|
823
|
+
corners: geometry.vertices.map(cloneVec3),
|
|
824
|
+
localFrame: createMarkerLocalFrame(averageVec3(geometry.vertices), marker),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
export function getSpatialMarkerDirectRelations(graph, markerId) {
|
|
829
|
+
return graph.relations
|
|
830
|
+
.filter(relation => endpointMatchesMarker(relation.from, markerId) || endpointMatchesMarker(relation.to, markerId))
|
|
831
|
+
.map(relation => ({ ...relation }));
|
|
832
|
+
}
|
|
833
|
+
export function getSpatialRelationRelatedIds(relations, markerId) {
|
|
834
|
+
const markerIds = new Set();
|
|
835
|
+
const objectIds = new Set();
|
|
836
|
+
for (const relation of relations) {
|
|
837
|
+
collectRelatedEndpoint(relation.from, markerId, markerIds, objectIds);
|
|
838
|
+
collectRelatedEndpoint(relation.to, markerId, markerIds, objectIds);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
markerIds: [...markerIds],
|
|
842
|
+
objectIds: [...objectIds],
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
export function findSpatialMarkerById(graph, markerId) {
|
|
846
|
+
return graph?.markers.find(marker => marker.id === markerId) ?? null;
|
|
847
|
+
}
|
|
848
|
+
function createMarkerIdentityInspectorSection(document, marker, markerTypes) {
|
|
849
|
+
const markerTypeOptions = [
|
|
850
|
+
{ label: '未选择', value: '' },
|
|
851
|
+
...markerTypes.map(definition => ({
|
|
852
|
+
label: formatMarkerTypeLabel(definition.type, definition.label),
|
|
853
|
+
value: definition.type,
|
|
854
|
+
})),
|
|
855
|
+
];
|
|
856
|
+
return {
|
|
857
|
+
id: 'marker',
|
|
858
|
+
title: '标记',
|
|
859
|
+
order: 0,
|
|
860
|
+
persistence: 'document',
|
|
861
|
+
properties: [
|
|
862
|
+
createMarkerInspectorProperty(document, {
|
|
863
|
+
path: 'marker.label',
|
|
864
|
+
label: '名称',
|
|
865
|
+
value: marker.label,
|
|
866
|
+
valueType: 'string',
|
|
867
|
+
control: 'string',
|
|
868
|
+
order: 0,
|
|
869
|
+
}),
|
|
870
|
+
createMarkerInspectorProperty(document, {
|
|
871
|
+
path: 'marker.markerType',
|
|
872
|
+
label: '标记类型',
|
|
873
|
+
value: marker.markerType,
|
|
874
|
+
valueType: 'enum',
|
|
875
|
+
control: markerTypeOptions.length > 0 ? 'enum' : 'string',
|
|
876
|
+
options: markerTypeOptions.length > 0 ? markerTypeOptions : undefined,
|
|
877
|
+
order: 1,
|
|
878
|
+
}),
|
|
879
|
+
createMarkerInspectorProperty(document, {
|
|
880
|
+
path: 'marker.kind',
|
|
881
|
+
label: '空间类别',
|
|
882
|
+
value: marker.kind,
|
|
883
|
+
valueType: 'string',
|
|
884
|
+
control: 'readonly',
|
|
885
|
+
readOnly: true,
|
|
886
|
+
persistence: 'readonly',
|
|
887
|
+
order: 2,
|
|
888
|
+
}),
|
|
889
|
+
createMarkerInspectorProperty(document, {
|
|
890
|
+
path: 'marker.tags',
|
|
891
|
+
label: '标签',
|
|
892
|
+
value: marker.tags?.join(', ') ?? '',
|
|
893
|
+
valueType: 'string',
|
|
894
|
+
control: 'string',
|
|
895
|
+
order: 3,
|
|
896
|
+
}),
|
|
897
|
+
createMarkerInspectorProperty(document, {
|
|
898
|
+
path: 'marker.metadata.color',
|
|
899
|
+
label: '颜色',
|
|
900
|
+
value: readMarkerColor(marker.metadata?.color) ?? createMarkerColor(0.1, 0.85, 1),
|
|
901
|
+
valueType: 'color',
|
|
902
|
+
control: 'color',
|
|
903
|
+
commitMode: 'immediate',
|
|
904
|
+
order: 4,
|
|
905
|
+
}),
|
|
906
|
+
createMarkerInspectorProperty(document, {
|
|
907
|
+
path: 'marker.metadata.note',
|
|
908
|
+
label: '备注',
|
|
909
|
+
value: typeof marker.metadata?.note === 'string' ? marker.metadata.note : '',
|
|
910
|
+
valueType: 'string',
|
|
911
|
+
control: 'string',
|
|
912
|
+
order: 5,
|
|
913
|
+
}),
|
|
914
|
+
],
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function createMarkerGeometryInspectorSection(document, marker) {
|
|
918
|
+
return {
|
|
919
|
+
id: 'markerGeometry',
|
|
920
|
+
title: '几何',
|
|
921
|
+
order: 10,
|
|
922
|
+
persistence: 'readonly',
|
|
923
|
+
properties: [
|
|
924
|
+
createMarkerInspectorProperty(document, {
|
|
925
|
+
path: 'marker.geometry.kind',
|
|
926
|
+
label: '几何',
|
|
927
|
+
value: marker.geometry.kind,
|
|
928
|
+
valueType: 'string',
|
|
929
|
+
control: 'readonly',
|
|
930
|
+
readOnly: true,
|
|
931
|
+
persistence: 'readonly',
|
|
932
|
+
order: 0,
|
|
933
|
+
}),
|
|
934
|
+
],
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function createMarkerSpatialInspectorSection(document, marker) {
|
|
938
|
+
const spatial = marker.spatial;
|
|
939
|
+
const properties = [
|
|
940
|
+
createMarkerInspectorProperty(document, {
|
|
941
|
+
path: 'marker.spatial.status',
|
|
942
|
+
label: '空间',
|
|
943
|
+
value: spatial.status,
|
|
944
|
+
valueType: 'string',
|
|
945
|
+
control: 'readonly',
|
|
946
|
+
readOnly: true,
|
|
947
|
+
persistence: 'readonly',
|
|
948
|
+
order: 0,
|
|
949
|
+
}),
|
|
950
|
+
];
|
|
951
|
+
if (spatial.bounds) {
|
|
952
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
953
|
+
path: 'marker.spatial.bounds.center',
|
|
954
|
+
label: '包围盒中心',
|
|
955
|
+
value: spatial.bounds.center,
|
|
956
|
+
valueType: 'vec3',
|
|
957
|
+
control: 'readonly',
|
|
958
|
+
readOnly: true,
|
|
959
|
+
persistence: 'readonly',
|
|
960
|
+
order: 1,
|
|
961
|
+
}), createMarkerInspectorProperty(document, {
|
|
962
|
+
path: 'marker.spatial.bounds.size',
|
|
963
|
+
label: '包围盒尺寸',
|
|
964
|
+
value: spatial.bounds.size,
|
|
965
|
+
valueType: 'vec3',
|
|
966
|
+
control: 'readonly',
|
|
967
|
+
readOnly: true,
|
|
968
|
+
persistence: 'readonly',
|
|
969
|
+
order: 2,
|
|
970
|
+
}));
|
|
971
|
+
}
|
|
972
|
+
if (spatial.origin) {
|
|
973
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
974
|
+
path: 'marker.spatial.origin',
|
|
975
|
+
label: '锚点',
|
|
976
|
+
value: spatial.origin,
|
|
977
|
+
valueType: 'vec3',
|
|
978
|
+
control: 'readonly',
|
|
979
|
+
readOnly: true,
|
|
980
|
+
persistence: 'readonly',
|
|
981
|
+
order: 3,
|
|
982
|
+
}));
|
|
983
|
+
}
|
|
984
|
+
if (spatial.localFrame?.forward) {
|
|
985
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
986
|
+
path: 'marker.spatial.localFrame.forward',
|
|
987
|
+
label: '朝向',
|
|
988
|
+
value: spatial.localFrame.forward,
|
|
989
|
+
valueType: 'vec3',
|
|
990
|
+
control: 'readonly',
|
|
991
|
+
readOnly: true,
|
|
992
|
+
persistence: 'readonly',
|
|
993
|
+
order: 4,
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
996
|
+
if (spatial.localFrame?.right) {
|
|
997
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
998
|
+
path: 'marker.spatial.localFrame.right',
|
|
999
|
+
label: '右向',
|
|
1000
|
+
value: spatial.localFrame.right,
|
|
1001
|
+
valueType: 'vec3',
|
|
1002
|
+
control: 'readonly',
|
|
1003
|
+
readOnly: true,
|
|
1004
|
+
persistence: 'readonly',
|
|
1005
|
+
order: 5,
|
|
1006
|
+
}));
|
|
1007
|
+
}
|
|
1008
|
+
if (spatial.localFrame?.up) {
|
|
1009
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
1010
|
+
path: 'marker.spatial.localFrame.up',
|
|
1011
|
+
label: '上向',
|
|
1012
|
+
value: spatial.localFrame.up,
|
|
1013
|
+
valueType: 'vec3',
|
|
1014
|
+
control: 'readonly',
|
|
1015
|
+
readOnly: true,
|
|
1016
|
+
persistence: 'readonly',
|
|
1017
|
+
order: 6,
|
|
1018
|
+
}));
|
|
1019
|
+
}
|
|
1020
|
+
if (spatial.unresolvedReason) {
|
|
1021
|
+
properties.push(createMarkerInspectorProperty(document, {
|
|
1022
|
+
path: 'marker.spatial.unresolvedReason',
|
|
1023
|
+
label: '原因',
|
|
1024
|
+
value: spatial.unresolvedReason,
|
|
1025
|
+
valueType: 'string',
|
|
1026
|
+
control: 'readonly',
|
|
1027
|
+
readOnly: true,
|
|
1028
|
+
persistence: 'readonly',
|
|
1029
|
+
order: 7,
|
|
1030
|
+
}));
|
|
1031
|
+
}
|
|
1032
|
+
return {
|
|
1033
|
+
id: 'markerSpatial',
|
|
1034
|
+
title: '空间',
|
|
1035
|
+
order: 20,
|
|
1036
|
+
persistence: 'readonly',
|
|
1037
|
+
properties,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function createMarkerRelationsInspectorSection(document, activeMarkerId, relations, markerTypes, relationTypes, graph, hierarchy, activeRelationId, relationPick, visibility) {
|
|
1041
|
+
const typeLabels = new Map(relationTypes.map(definition => [definition.type, definition.label]));
|
|
1042
|
+
const markerLabels = new Map((graph?.markers ?? []).map(marker => [marker.id, marker.label || marker.id]));
|
|
1043
|
+
const sceneObjectLabels = new Map(hierarchy.map(item => [item.id, item.label || item.id]));
|
|
1044
|
+
const sceneObjectIds = new Set(hierarchy.map(item => item.id));
|
|
1045
|
+
const invalidRelations = relations.filter(relation => (isRelationEndpointInvalid(relation.from, markerLabels, sceneObjectIds)
|
|
1046
|
+
|| isRelationEndpointInvalid(relation.to, markerLabels, sceneObjectIds)));
|
|
1047
|
+
const relationPanel = graph
|
|
1048
|
+
? createMarkerPanelState(graph, markerTypes, relationTypes, hierarchy, activeMarkerId, activeRelationId, relationPick, visibility)
|
|
1049
|
+
: null;
|
|
1050
|
+
const hiddenRelationCount = (relationPanel?.relations ?? []).filter(relation => (relation.selected
|
|
1051
|
+
&& (relation.from.visibility === 'hidden' || relation.to.visibility === 'hidden'))).length;
|
|
1052
|
+
const relationStatus = visibility.markersVisible === false
|
|
1053
|
+
? '标记视图已关闭,关系线暂不显示'
|
|
1054
|
+
: invalidRelations.length > 0
|
|
1055
|
+
? `${invalidRelations.length} 个关系包含异常端点`
|
|
1056
|
+
: hiddenRelationCount > 0
|
|
1057
|
+
? `${hiddenRelationCount} 个关系端点已隐藏`
|
|
1058
|
+
: '正常';
|
|
1059
|
+
const relationEditorPanel = graph
|
|
1060
|
+
? relationPanel
|
|
1061
|
+
: null;
|
|
1062
|
+
return {
|
|
1063
|
+
id: 'markerRelations',
|
|
1064
|
+
title: '关系',
|
|
1065
|
+
order: 30,
|
|
1066
|
+
persistence: 'readonly',
|
|
1067
|
+
properties: [
|
|
1068
|
+
createMarkerInspectorProperty(document, {
|
|
1069
|
+
path: 'marker.relations.count',
|
|
1070
|
+
label: '数量',
|
|
1071
|
+
value: relations.length,
|
|
1072
|
+
valueType: 'number',
|
|
1073
|
+
control: 'readonly',
|
|
1074
|
+
readOnly: true,
|
|
1075
|
+
persistence: 'readonly',
|
|
1076
|
+
order: 0,
|
|
1077
|
+
}),
|
|
1078
|
+
createMarkerInspectorProperty(document, {
|
|
1079
|
+
path: 'marker.relations.summary',
|
|
1080
|
+
label: '直接关系',
|
|
1081
|
+
value: relations.map(relation => ([
|
|
1082
|
+
formatRelationEndpointSummary(relation.from, markerLabels, sceneObjectLabels),
|
|
1083
|
+
typeLabels.get(relation.type) ?? relation.type,
|
|
1084
|
+
formatRelationEndpointSummary(relation.to, markerLabels, sceneObjectLabels),
|
|
1085
|
+
].join(' '))).join(', ') || '无',
|
|
1086
|
+
valueType: 'string',
|
|
1087
|
+
control: 'readonly',
|
|
1088
|
+
readOnly: true,
|
|
1089
|
+
persistence: 'readonly',
|
|
1090
|
+
order: 1,
|
|
1091
|
+
}),
|
|
1092
|
+
createMarkerInspectorProperty(document, {
|
|
1093
|
+
path: 'marker.relations.status',
|
|
1094
|
+
label: '状态',
|
|
1095
|
+
value: relationStatus,
|
|
1096
|
+
valueType: 'string',
|
|
1097
|
+
control: 'readonly',
|
|
1098
|
+
readOnly: true,
|
|
1099
|
+
persistence: 'readonly',
|
|
1100
|
+
order: 2,
|
|
1101
|
+
}),
|
|
1102
|
+
createMarkerInspectorProperty(document, {
|
|
1103
|
+
path: 'marker.relations.editor',
|
|
1104
|
+
label: '关系设置',
|
|
1105
|
+
value: relationEditorPanel,
|
|
1106
|
+
valueType: 'object',
|
|
1107
|
+
control: 'custom',
|
|
1108
|
+
customControl: 'marker-relation-editor',
|
|
1109
|
+
controlOptions: {
|
|
1110
|
+
markerPanel: relationEditorPanel,
|
|
1111
|
+
},
|
|
1112
|
+
readOnly: true,
|
|
1113
|
+
persistence: 'readonly',
|
|
1114
|
+
order: 3,
|
|
1115
|
+
}),
|
|
1116
|
+
],
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function formatRelationEndpointSummary(endpoint, markerLabels, sceneObjectLabels) {
|
|
1120
|
+
const kindLabel = endpoint.kind === 'marker'
|
|
1121
|
+
? '标记'
|
|
1122
|
+
: endpoint.kind === 'scene-object'
|
|
1123
|
+
? '对象'
|
|
1124
|
+
: endpoint.kind;
|
|
1125
|
+
const label = endpoint.label
|
|
1126
|
+
|| (endpoint.kind === 'marker'
|
|
1127
|
+
? markerLabels.get(endpoint.id)
|
|
1128
|
+
: endpoint.kind === 'scene-object'
|
|
1129
|
+
? sceneObjectLabels.get(endpoint.id)
|
|
1130
|
+
: null)
|
|
1131
|
+
|| endpoint.id;
|
|
1132
|
+
return `${kindLabel}:${label}`;
|
|
1133
|
+
}
|
|
1134
|
+
function isRelationEndpointInvalid(endpoint, markerLabels, sceneObjectIds) {
|
|
1135
|
+
if (!isSupportedRelationEndpointKind(endpoint.kind))
|
|
1136
|
+
return true;
|
|
1137
|
+
if (endpoint.kind === 'marker')
|
|
1138
|
+
return !markerLabels.has(endpoint.id);
|
|
1139
|
+
if (endpoint.kind === 'scene-object')
|
|
1140
|
+
return !sceneObjectIds.has(endpoint.id);
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1143
|
+
function formatMarkerTypeLabel(type, fallback) {
|
|
1144
|
+
switch (type) {
|
|
1145
|
+
case 'collection-area': return '采集区域';
|
|
1146
|
+
case 'trade-zone': return '交易区';
|
|
1147
|
+
case 'entrance': return '入口';
|
|
1148
|
+
case 'exit': return '出口';
|
|
1149
|
+
case 'effect-socket': return '特效挂点';
|
|
1150
|
+
default: return fallback;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function createMarkerInspectorProperty(document, input) {
|
|
1154
|
+
const readOnly = input.readOnly === true;
|
|
1155
|
+
return {
|
|
1156
|
+
...input,
|
|
1157
|
+
readOnly,
|
|
1158
|
+
persistence: input.persistence ?? (readOnly ? 'readonly' : 'document'),
|
|
1159
|
+
commitMode: input.commitMode ?? (readOnly ? 'change' : 'blur'),
|
|
1160
|
+
document,
|
|
1161
|
+
validate: input.readOnly ? undefined : createMarkerInspectorValidator(input.path),
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function createMarkerInspectorValidator(path) {
|
|
1165
|
+
if (path === 'marker.label' || path === 'marker.markerType') {
|
|
1166
|
+
return (value) => {
|
|
1167
|
+
if (typeof value === 'string' && value.trim())
|
|
1168
|
+
return { ok: true, value: value.trim() };
|
|
1169
|
+
return { ok: false, message: 'Marker value must be a non-empty string.' };
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
if (path === 'marker.tags') {
|
|
1173
|
+
return (value) => ({ ok: true, value: readMarkerTags(value).join(', ') });
|
|
1174
|
+
}
|
|
1175
|
+
if (path === 'marker.metadata.color') {
|
|
1176
|
+
return (value) => {
|
|
1177
|
+
const color = readMarkerColor(value);
|
|
1178
|
+
return color
|
|
1179
|
+
? { ok: true, value: color }
|
|
1180
|
+
: { ok: false, message: 'Marker color must be a valid RGB color.' };
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
if (path === 'marker.metadata.note') {
|
|
1184
|
+
return (value) => ({
|
|
1185
|
+
ok: true,
|
|
1186
|
+
value: typeof value === 'string' ? value.trim() : '',
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
return undefined;
|
|
1190
|
+
}
|
|
1191
|
+
function createMarkerPresentationPatch(marker, markerTypeDefinition) {
|
|
1192
|
+
const markerKind = normalizeMarkerPresentationKind(markerTypeDefinition.kind ?? marker.kind);
|
|
1193
|
+
return {
|
|
1194
|
+
markerType: markerTypeDefinition.type,
|
|
1195
|
+
kind: markerKind,
|
|
1196
|
+
geometry: createMarkerPresentationGeometry(marker.geometry, markerKind),
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function createMarkerPresentationGeometry(geometry, markerKind) {
|
|
1200
|
+
if (markerKind === 'region') {
|
|
1201
|
+
if (geometry.kind === 'box')
|
|
1202
|
+
return structuredClone(geometry);
|
|
1203
|
+
return {
|
|
1204
|
+
kind: 'box',
|
|
1205
|
+
coordinateSpace: 'world',
|
|
1206
|
+
center: getSpatialMarkerGeometryOrigin(geometry),
|
|
1207
|
+
size: { x: 4, y: 1, z: 4 },
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
if (markerKind === 'object' && geometry.kind === 'object-bounds')
|
|
1211
|
+
return structuredClone(geometry);
|
|
1212
|
+
if (geometry.kind === 'point')
|
|
1213
|
+
return structuredClone(geometry);
|
|
1214
|
+
return {
|
|
1215
|
+
kind: 'point',
|
|
1216
|
+
coordinateSpace: 'world',
|
|
1217
|
+
position: getSpatialMarkerGeometryOrigin(geometry),
|
|
1218
|
+
...(geometry.kind === 'object-bounds' ? { target: structuredClone(geometry.target) } : {}),
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function getSpatialMarkerGeometryOrigin(geometry) {
|
|
1222
|
+
switch (geometry.kind) {
|
|
1223
|
+
case 'box':
|
|
1224
|
+
return { ...geometry.center };
|
|
1225
|
+
case 'point':
|
|
1226
|
+
return { ...geometry.position };
|
|
1227
|
+
case 'object-bounds':
|
|
1228
|
+
return { x: 0, y: 0, z: 0 };
|
|
1229
|
+
case 'polyhedron':
|
|
1230
|
+
return geometry.vertices.length > 0
|
|
1231
|
+
? createBoundsCenter(geometry.vertices)
|
|
1232
|
+
: { x: 0, y: 0, z: 0 };
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function normalizeMarkerPresentationKind(kind) {
|
|
1236
|
+
const normalized = kind?.trim();
|
|
1237
|
+
if (!normalized)
|
|
1238
|
+
return 'point';
|
|
1239
|
+
if (normalized === 'object-bound' || normalized === 'object-bounds')
|
|
1240
|
+
return 'object';
|
|
1241
|
+
return normalized;
|
|
1242
|
+
}
|
|
1243
|
+
function createBoundsCenter(points) {
|
|
1244
|
+
const min = {
|
|
1245
|
+
x: Math.min(...points.map(point => point.x)),
|
|
1246
|
+
y: Math.min(...points.map(point => point.y)),
|
|
1247
|
+
z: Math.min(...points.map(point => point.z)),
|
|
1248
|
+
};
|
|
1249
|
+
const max = {
|
|
1250
|
+
x: Math.max(...points.map(point => point.x)),
|
|
1251
|
+
y: Math.max(...points.map(point => point.y)),
|
|
1252
|
+
z: Math.max(...points.map(point => point.z)),
|
|
1253
|
+
};
|
|
1254
|
+
return {
|
|
1255
|
+
x: (min.x + max.x) / 2,
|
|
1256
|
+
y: (min.y + max.y) / 2,
|
|
1257
|
+
z: (min.z + max.z) / 2,
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function readVec3(value) {
|
|
1261
|
+
if (!value || typeof value !== 'object')
|
|
1262
|
+
return null;
|
|
1263
|
+
const candidate = value;
|
|
1264
|
+
const x = Number(candidate.x);
|
|
1265
|
+
const y = Number(candidate.y);
|
|
1266
|
+
const z = Number(candidate.z);
|
|
1267
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)
|
|
1268
|
+
? createVec3(x, y, z)
|
|
1269
|
+
: null;
|
|
1270
|
+
}
|
|
1271
|
+
function resolveBoxSpatialFacts(center, size, rotation, marker) {
|
|
1272
|
+
const corners = createBoxCorners(center, size, rotation);
|
|
1273
|
+
const bounds = createBounds(corners);
|
|
1274
|
+
return {
|
|
1275
|
+
status: 'resolved',
|
|
1276
|
+
coordinateSpace: 'world',
|
|
1277
|
+
origin: cloneVec3(center),
|
|
1278
|
+
bounds,
|
|
1279
|
+
dimensions: cloneVec3(size),
|
|
1280
|
+
corners,
|
|
1281
|
+
localFrame: {
|
|
1282
|
+
...createBoxLocalFrame(center, rotation),
|
|
1283
|
+
label: marker?.label,
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function createUnresolvedSpatialFacts(unresolvedReason, target) {
|
|
1288
|
+
return {
|
|
1289
|
+
status: 'unresolved',
|
|
1290
|
+
coordinateSpace: 'world',
|
|
1291
|
+
target,
|
|
1292
|
+
unresolvedReason,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function createBoxCorners(center, size, rotation) {
|
|
1296
|
+
const half = {
|
|
1297
|
+
x: size.x / 2,
|
|
1298
|
+
y: size.y / 2,
|
|
1299
|
+
z: size.z / 2,
|
|
1300
|
+
};
|
|
1301
|
+
const offsets = [
|
|
1302
|
+
createVec3(-half.x, -half.y, -half.z),
|
|
1303
|
+
createVec3(half.x, -half.y, -half.z),
|
|
1304
|
+
createVec3(-half.x, half.y, -half.z),
|
|
1305
|
+
createVec3(half.x, half.y, -half.z),
|
|
1306
|
+
createVec3(-half.x, -half.y, half.z),
|
|
1307
|
+
createVec3(half.x, -half.y, half.z),
|
|
1308
|
+
createVec3(-half.x, half.y, half.z),
|
|
1309
|
+
createVec3(half.x, half.y, half.z),
|
|
1310
|
+
];
|
|
1311
|
+
return offsets.map(offset => addVec3(center, rotateVec3(offset, rotation)));
|
|
1312
|
+
}
|
|
1313
|
+
function createBoxLocalFrame(origin, rotation) {
|
|
1314
|
+
return {
|
|
1315
|
+
origin: cloneVec3(origin),
|
|
1316
|
+
right: rotateVec3(createVec3(1, 0, 0), rotation),
|
|
1317
|
+
up: rotateVec3(createVec3(0, 1, 0), rotation),
|
|
1318
|
+
forward: rotateVec3(createVec3(0, 0, 1), rotation),
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
function createMarkerLocalFrame(origin, marker) {
|
|
1322
|
+
const authoredFrame = marker?.semanticFrame;
|
|
1323
|
+
return {
|
|
1324
|
+
origin: cloneVec3(origin),
|
|
1325
|
+
right: authoredFrame?.right ? cloneVec3(authoredFrame.right) : createVec3(1, 0, 0),
|
|
1326
|
+
up: authoredFrame?.up ? cloneVec3(authoredFrame.up) : createVec3(0, 1, 0),
|
|
1327
|
+
forward: authoredFrame?.forward ? cloneVec3(authoredFrame.forward) : createVec3(0, 0, 1),
|
|
1328
|
+
label: authoredFrame?.label ?? marker?.label,
|
|
1329
|
+
...(authoredFrame?.description ? { description: authoredFrame.description } : {}),
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function createBounds(points) {
|
|
1333
|
+
const first = points[0] ?? createVec3(0, 0, 0);
|
|
1334
|
+
const min = cloneVec3(first);
|
|
1335
|
+
const max = cloneVec3(first);
|
|
1336
|
+
for (const point of points.slice(1)) {
|
|
1337
|
+
min.x = Math.min(min.x, point.x);
|
|
1338
|
+
min.y = Math.min(min.y, point.y);
|
|
1339
|
+
min.z = Math.min(min.z, point.z);
|
|
1340
|
+
max.x = Math.max(max.x, point.x);
|
|
1341
|
+
max.y = Math.max(max.y, point.y);
|
|
1342
|
+
max.z = Math.max(max.z, point.z);
|
|
1343
|
+
}
|
|
1344
|
+
const size = createVec3(max.x - min.x, max.y - min.y, max.z - min.z);
|
|
1345
|
+
return {
|
|
1346
|
+
min,
|
|
1347
|
+
max,
|
|
1348
|
+
size,
|
|
1349
|
+
center: createVec3(min.x + size.x / 2, min.y + size.y / 2, min.z + size.z / 2),
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function rotateVec3(vector, rotation) {
|
|
1353
|
+
if (!rotation)
|
|
1354
|
+
return cloneVec3(vector);
|
|
1355
|
+
const cx = Math.cos(rotation.x);
|
|
1356
|
+
const sx = Math.sin(rotation.x);
|
|
1357
|
+
const cy = Math.cos(rotation.y);
|
|
1358
|
+
const sy = Math.sin(rotation.y);
|
|
1359
|
+
const cz = Math.cos(rotation.z);
|
|
1360
|
+
const sz = Math.sin(rotation.z);
|
|
1361
|
+
const y1 = vector.y * cx - vector.z * sx;
|
|
1362
|
+
const z1 = vector.y * sx + vector.z * cx;
|
|
1363
|
+
const x1 = vector.x;
|
|
1364
|
+
const x2 = x1 * cy + z1 * sy;
|
|
1365
|
+
const z2 = -x1 * sy + z1 * cy;
|
|
1366
|
+
const y2 = y1;
|
|
1367
|
+
return {
|
|
1368
|
+
x: x2 * cz - y2 * sz,
|
|
1369
|
+
y: x2 * sz + y2 * cz,
|
|
1370
|
+
z: z2,
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function addVec3(left, right) {
|
|
1374
|
+
return createVec3(left.x + right.x, left.y + right.y, left.z + right.z);
|
|
1375
|
+
}
|
|
1376
|
+
function averageVec3(points) {
|
|
1377
|
+
const sum = points.reduce((accumulator, point) => addVec3(accumulator, point), createVec3(0, 0, 0));
|
|
1378
|
+
return createVec3(sum.x / points.length, sum.y / points.length, sum.z / points.length);
|
|
1379
|
+
}
|
|
1380
|
+
function readMarkerTags(value) {
|
|
1381
|
+
if (Array.isArray(value)) {
|
|
1382
|
+
return [...new Set(value
|
|
1383
|
+
.map(item => typeof item === 'string' ? item.trim() : '')
|
|
1384
|
+
.filter(Boolean))];
|
|
1385
|
+
}
|
|
1386
|
+
if (typeof value !== 'string')
|
|
1387
|
+
return [];
|
|
1388
|
+
return [...new Set(value
|
|
1389
|
+
.split(',')
|
|
1390
|
+
.map(item => item.trim())
|
|
1391
|
+
.filter(Boolean))];
|
|
1392
|
+
}
|
|
1393
|
+
function readMarkerColor(value) {
|
|
1394
|
+
if (!value || typeof value !== 'object')
|
|
1395
|
+
return null;
|
|
1396
|
+
const candidate = value;
|
|
1397
|
+
const r = readColorChannel(candidate.r);
|
|
1398
|
+
const g = readColorChannel(candidate.g);
|
|
1399
|
+
const b = readColorChannel(candidate.b);
|
|
1400
|
+
return r == null || g == null || b == null
|
|
1401
|
+
? null
|
|
1402
|
+
: createMarkerColor(r, g, b);
|
|
1403
|
+
}
|
|
1404
|
+
function readColorChannel(value) {
|
|
1405
|
+
const channel = Number(value);
|
|
1406
|
+
if (!Number.isFinite(channel))
|
|
1407
|
+
return null;
|
|
1408
|
+
return Math.min(1, Math.max(0, channel));
|
|
1409
|
+
}
|
|
1410
|
+
function createMarkerColor(r, g, b) {
|
|
1411
|
+
return { r, g, b };
|
|
1412
|
+
}
|
|
1413
|
+
function cloneVec3(value) {
|
|
1414
|
+
return createVec3(value.x, value.y, value.z);
|
|
1415
|
+
}
|
|
1416
|
+
function createVec3(x, y, z) {
|
|
1417
|
+
return { x, y, z };
|
|
1418
|
+
}
|
|
1419
|
+
function endpointMatchesMarker(endpoint, markerId) {
|
|
1420
|
+
return endpoint.kind === 'marker' && endpoint.id === markerId;
|
|
1421
|
+
}
|
|
1422
|
+
function collectRelatedEndpoint(endpoint, markerId, markerIds, objectIds) {
|
|
1423
|
+
if (endpoint.kind === 'marker') {
|
|
1424
|
+
if (endpoint.id !== markerId)
|
|
1425
|
+
markerIds.add(endpoint.id);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (endpoint.kind === 'scene-object') {
|
|
1429
|
+
objectIds.add(endpoint.id);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
//# sourceMappingURL=local-editor-marker-graph.js.map
|