@fps-games/editor 0.1.1-beta.6 → 0.1.1-beta.8
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/local-editor-harness.d.ts +26 -3
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +534 -36
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +33 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +231 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +3 -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 +8 -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 +4 -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 +4 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +40 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +237 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js +15 -50
- package/node_modules/@fps-games/editor-babylon/dist/projection-selection-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +5 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +61 -8
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +18 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js +142 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js +187 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-measurement-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js +188 -0
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-spatial-overlay-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +11 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +4 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js +97 -46
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +5 -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 +57 -7
- 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-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +61 -5
- 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.js +0 -2
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.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 +6 -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 +58 -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-panels.d.ts +8 -1
- 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 +358 -53
- 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-shared.d.ts +5 -0
- 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 +28 -8
- 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-shortcuts.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +4 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js +14 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-theme.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts +11 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js +148 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js.map +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +57 -1
- 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.d.ts +5 -4
- 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 +1108 -205
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +2 -2
- package/node_modules/@fps-games/editor-core/dist/index.d.ts +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/index.js +2 -0
- package/node_modules/@fps-games/editor-core/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js +6 -1
- package/node_modules/@fps-games/editor-core/dist/inspector.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +7 -0
- package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-graph.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts +1 -1
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts +12 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js +52 -0
- package/node_modules/@fps-games/editor-core/dist/selection-strategy.js.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +94 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +58 -0
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -0
- 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-protocol/package.json +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createEditorSession, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, createInspectorEditPayload, computeEditorTransformActionTargets, mergeInspectorSections, normalizeEditorTransformConstraint, serializedMultiObjectToInspectorObject, serializedObjectToInspectorObject, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
|
|
1
|
+
import { createEditorSession, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_VIEWPORT_TOOL_STATE, createEmptyEditorViewportSpatialOverlayState, cloneEditorViewportToolState, cloneEditorViewportSpatialOverlayState, createInspectorEditPayload, computeEditorTransformActionTargets, mergeInspectorSections, normalizeEditorTransformConstraint, resolveEditorSelectionCommand, serializedMultiObjectToInspectorObject, serializedObjectToInspectorObject, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
|
|
2
2
|
import { createLocalEditorBrowserUi, } from '@fps-games/editor-browser';
|
|
3
|
-
import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonTransformGizmoController, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
|
|
3
|
+
import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonTransformGizmoController, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
|
|
4
4
|
export function createLocalEditorHarness(options) {
|
|
5
5
|
const root = options.root ?? document.body;
|
|
6
6
|
const state = {
|
|
@@ -13,18 +13,25 @@ export function createLocalEditorHarness(options) {
|
|
|
13
13
|
babylon: null,
|
|
14
14
|
engine: null,
|
|
15
15
|
world: null,
|
|
16
|
+
grid: null,
|
|
17
|
+
gridVisible: true,
|
|
16
18
|
projection: null,
|
|
17
19
|
gizmo: null,
|
|
18
20
|
sceneCameraPreview: null,
|
|
19
21
|
sceneCameraPreviewEnabled: false,
|
|
20
22
|
sceneViewInput: null,
|
|
21
23
|
sceneViewCamera: null,
|
|
24
|
+
sceneViewMeasurement: null,
|
|
25
|
+
sceneViewSpatialOverlay: null,
|
|
22
26
|
selectionController: null,
|
|
23
27
|
boxSelection: null,
|
|
24
28
|
transformTool: 'select',
|
|
25
29
|
transformSpace: 'world',
|
|
26
30
|
transformConstraint: 'axis',
|
|
27
31
|
transformOperationSettings: cloneTransformOperationSettings(DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS),
|
|
32
|
+
viewportTools: cloneEditorViewportToolState(DEFAULT_EDITOR_VIEWPORT_TOOL_STATE),
|
|
33
|
+
viewportMeasurement: createEmptyViewportMeasurement(),
|
|
34
|
+
viewportSpatialOverlay: createEmptyEditorViewportSpatialOverlayState(),
|
|
28
35
|
duplicateDrag: null,
|
|
29
36
|
armedPlacement: null,
|
|
30
37
|
resizeHandler: null,
|
|
@@ -69,6 +76,10 @@ export function createLocalEditorHarness(options) {
|
|
|
69
76
|
if (selectItem(state, options, input))
|
|
70
77
|
harness.render();
|
|
71
78
|
},
|
|
79
|
+
onSelectionCommand: (command) => {
|
|
80
|
+
if (dispatchSelectionCommand(state, options, command))
|
|
81
|
+
harness.render();
|
|
82
|
+
},
|
|
72
83
|
onSceneGraphRename: (intent) => {
|
|
73
84
|
if (renameSceneGraphNode(state, options, intent))
|
|
74
85
|
harness.render();
|
|
@@ -77,6 +88,12 @@ export function createLocalEditorHarness(options) {
|
|
|
77
88
|
if (createSceneGraphGroup(state, options, intent))
|
|
78
89
|
harness.render();
|
|
79
90
|
},
|
|
91
|
+
onSceneGraphCreatePrimitive: options.documentAdapter.createSceneGraphCreatePrimitivePatch
|
|
92
|
+
? (intent) => {
|
|
93
|
+
if (createSceneGraphPrimitive(state, options, intent))
|
|
94
|
+
harness.render();
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
80
97
|
onSceneGraphDelete: (intent) => {
|
|
81
98
|
if (deleteSceneGraphNodes(state, options, intent))
|
|
82
99
|
harness.render();
|
|
@@ -174,14 +191,42 @@ export function createLocalEditorHarness(options) {
|
|
|
174
191
|
if (executeTransformAction(state, options, action))
|
|
175
192
|
harness.render();
|
|
176
193
|
},
|
|
194
|
+
onViewportViewPresetChange: (preset) => {
|
|
195
|
+
if (setViewportViewPreset(state, preset))
|
|
196
|
+
harness.render();
|
|
197
|
+
},
|
|
198
|
+
onViewportProjectionModeChange: (mode) => {
|
|
199
|
+
if (setViewportProjectionMode(state, mode))
|
|
200
|
+
harness.render();
|
|
201
|
+
},
|
|
202
|
+
onViewportOverlaySettingsChange: (settings) => {
|
|
203
|
+
if (setViewportOverlaySettings(state, settings))
|
|
204
|
+
harness.render();
|
|
205
|
+
},
|
|
206
|
+
onViewportUtilityToolChange: (tool) => {
|
|
207
|
+
if (setViewportUtilityTool(state, tool))
|
|
208
|
+
harness.render();
|
|
209
|
+
},
|
|
210
|
+
onViewportMeasurementClear: () => {
|
|
211
|
+
if (clearViewportMeasurement(state))
|
|
212
|
+
harness.render();
|
|
213
|
+
},
|
|
177
214
|
onSceneCameraPreviewToggle: (enabled) => {
|
|
178
215
|
if (setSceneCameraPreviewEnabled(state, options, enabled))
|
|
179
216
|
harness.render();
|
|
180
217
|
},
|
|
218
|
+
onGridVisibleChange: (visible) => {
|
|
219
|
+
if (setGridVisible(state, visible))
|
|
220
|
+
harness.render();
|
|
221
|
+
},
|
|
181
222
|
onFocusSelection: () => {
|
|
182
223
|
if (focusSelectedProjection(state))
|
|
183
224
|
harness.render();
|
|
184
225
|
},
|
|
226
|
+
onCancelEditorIntent: () => {
|
|
227
|
+
if (cancelEditorIntent(state, options))
|
|
228
|
+
harness.render();
|
|
229
|
+
},
|
|
185
230
|
onCancelActiveOperation: () => {
|
|
186
231
|
cancelActiveOperation(state);
|
|
187
232
|
harness.render();
|
|
@@ -191,6 +236,9 @@ export function createLocalEditorHarness(options) {
|
|
|
191
236
|
harness = {
|
|
192
237
|
render() {
|
|
193
238
|
syncSceneCameraPreview(state, options);
|
|
239
|
+
syncViewportCameraState(state);
|
|
240
|
+
syncViewportMeasurementState(state);
|
|
241
|
+
syncViewportSpatialOverlay(state);
|
|
194
242
|
ui.update(createUiState(state, options));
|
|
195
243
|
},
|
|
196
244
|
setTheme(theme) {
|
|
@@ -404,6 +452,224 @@ function normalizePositiveStep(value) {
|
|
|
404
452
|
function normalizePlacementMode(mode) {
|
|
405
453
|
return mode === 'ground' || mode === 'surface' ? mode : 'off';
|
|
406
454
|
}
|
|
455
|
+
function normalizeViewportViewPreset(preset) {
|
|
456
|
+
return preset === 'top' || preset === 'front' || preset === 'right' ? preset : 'perspective';
|
|
457
|
+
}
|
|
458
|
+
function normalizeViewportProjectionMode(mode) {
|
|
459
|
+
return mode === 'orthographic' ? 'orthographic' : 'perspective';
|
|
460
|
+
}
|
|
461
|
+
function createEmptyViewportMeasurement() {
|
|
462
|
+
return {
|
|
463
|
+
active: false,
|
|
464
|
+
start: null,
|
|
465
|
+
end: null,
|
|
466
|
+
preview: null,
|
|
467
|
+
distance: null,
|
|
468
|
+
screenStart: null,
|
|
469
|
+
screenEnd: null,
|
|
470
|
+
screenPreview: null,
|
|
471
|
+
label: null,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function cloneViewportMeasurement(measurement) {
|
|
475
|
+
return {
|
|
476
|
+
active: measurement.active,
|
|
477
|
+
start: measurement.start ? { ...measurement.start } : null,
|
|
478
|
+
end: measurement.end ? { ...measurement.end } : null,
|
|
479
|
+
preview: measurement.preview ? { ...measurement.preview } : null,
|
|
480
|
+
distance: measurement.distance,
|
|
481
|
+
screenStart: measurement.screenStart ? { ...measurement.screenStart } : null,
|
|
482
|
+
screenEnd: measurement.screenEnd ? { ...measurement.screenEnd } : null,
|
|
483
|
+
screenPreview: measurement.screenPreview ? { ...measurement.screenPreview } : null,
|
|
484
|
+
label: measurement.label ? { ...measurement.label } : null,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function setViewportViewPreset(state, preset) {
|
|
488
|
+
if (state.sceneCameraPreviewEnabled) {
|
|
489
|
+
state.status = 'Viewport view unavailable during Scene Camera preview';
|
|
490
|
+
state.statusTone = 'warning';
|
|
491
|
+
state.statusToneStatus = state.status;
|
|
492
|
+
state.statusDetails = 'Disable Scene Camera preview before switching editor viewport views.';
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
const nextPreset = normalizeViewportViewPreset(preset);
|
|
496
|
+
const target = resolveViewportPresetTarget(state);
|
|
497
|
+
const radius = resolveViewportPresetRadius(state);
|
|
498
|
+
const changed = state.sceneViewCamera?.setViewPreset(nextPreset, { target, radius }) ?? false;
|
|
499
|
+
if (!changed)
|
|
500
|
+
return false;
|
|
501
|
+
const cameraState = state.sceneViewCamera?.getState();
|
|
502
|
+
state.viewportTools = {
|
|
503
|
+
...state.viewportTools,
|
|
504
|
+
viewPreset: cameraState?.viewPreset ?? nextPreset,
|
|
505
|
+
projectionMode: cameraState?.projectionMode ?? (nextPreset === 'perspective' ? 'perspective' : 'orthographic'),
|
|
506
|
+
};
|
|
507
|
+
state.status = `Viewport view: ${state.viewportTools.viewPreset}`;
|
|
508
|
+
state.statusTone = 'default';
|
|
509
|
+
state.statusToneStatus = state.status;
|
|
510
|
+
state.statusDetails = '';
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
function setViewportProjectionMode(state, mode) {
|
|
514
|
+
if (state.sceneCameraPreviewEnabled) {
|
|
515
|
+
state.status = 'Viewport projection unavailable during Scene Camera preview';
|
|
516
|
+
state.statusTone = 'warning';
|
|
517
|
+
state.statusToneStatus = state.status;
|
|
518
|
+
state.statusDetails = 'Disable Scene Camera preview before switching editor viewport projection.';
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
const nextMode = normalizeViewportProjectionMode(mode);
|
|
522
|
+
const changed = state.sceneViewCamera?.setProjectionMode(nextMode) ?? false;
|
|
523
|
+
if (!changed)
|
|
524
|
+
return false;
|
|
525
|
+
const cameraState = state.sceneViewCamera?.getState();
|
|
526
|
+
state.viewportTools = {
|
|
527
|
+
...state.viewportTools,
|
|
528
|
+
projectionMode: cameraState?.projectionMode ?? nextMode,
|
|
529
|
+
};
|
|
530
|
+
state.status = `Viewport projection: ${state.viewportTools.projectionMode}`;
|
|
531
|
+
state.statusTone = 'default';
|
|
532
|
+
state.statusToneStatus = state.status;
|
|
533
|
+
state.statusDetails = '';
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
function setViewportOverlaySettings(state, settings) {
|
|
537
|
+
state.viewportTools = {
|
|
538
|
+
...state.viewportTools,
|
|
539
|
+
overlay: {
|
|
540
|
+
...state.viewportTools.overlay,
|
|
541
|
+
bounds: settings.bounds ?? state.viewportTools.overlay.bounds,
|
|
542
|
+
dimensions: settings.dimensions ?? state.viewportTools.overlay.dimensions,
|
|
543
|
+
edgeLengths: settings.edgeLengths ?? state.viewportTools.overlay.edgeLengths,
|
|
544
|
+
anchor: settings.anchor ?? state.viewportTools.overlay.anchor,
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
state.status = 'Viewport overlay settings updated';
|
|
548
|
+
state.statusTone = 'default';
|
|
549
|
+
state.statusToneStatus = state.status;
|
|
550
|
+
state.statusDetails = '';
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
function setViewportUtilityTool(state, tool) {
|
|
554
|
+
const nextTool = tool === 'measure-distance' ? 'measure-distance' : 'none';
|
|
555
|
+
state.viewportTools = {
|
|
556
|
+
...state.viewportTools,
|
|
557
|
+
activeUtilityTool: nextTool,
|
|
558
|
+
};
|
|
559
|
+
if (nextTool === 'measure-distance') {
|
|
560
|
+
state.viewportMeasurement = {
|
|
561
|
+
...cloneViewportMeasurement(state.viewportMeasurement),
|
|
562
|
+
active: !!state.viewportMeasurement.start && !state.viewportMeasurement.end,
|
|
563
|
+
};
|
|
564
|
+
state.status = 'Measure distance: pick first XZ ground point';
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
const unfinishedMeasurement = state.viewportMeasurement.active
|
|
568
|
+
|| (!!state.viewportMeasurement.start && !state.viewportMeasurement.end);
|
|
569
|
+
state.viewportMeasurement = unfinishedMeasurement
|
|
570
|
+
? state.sceneViewMeasurement?.clear() ?? createEmptyViewportMeasurement()
|
|
571
|
+
: {
|
|
572
|
+
...cloneViewportMeasurement(state.viewportMeasurement),
|
|
573
|
+
active: false,
|
|
574
|
+
preview: null,
|
|
575
|
+
screenPreview: null,
|
|
576
|
+
};
|
|
577
|
+
state.status = 'Viewport utility: none';
|
|
578
|
+
}
|
|
579
|
+
state.statusTone = 'default';
|
|
580
|
+
state.statusToneStatus = state.status;
|
|
581
|
+
state.statusDetails = '';
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
function clearViewportMeasurement(state) {
|
|
585
|
+
state.viewportMeasurement = state.sceneViewMeasurement?.clear() ?? createEmptyViewportMeasurement();
|
|
586
|
+
state.viewportTools = {
|
|
587
|
+
...state.viewportTools,
|
|
588
|
+
activeUtilityTool: 'none',
|
|
589
|
+
};
|
|
590
|
+
state.status = 'Measurement cleared';
|
|
591
|
+
state.statusTone = 'default';
|
|
592
|
+
state.statusToneStatus = state.status;
|
|
593
|
+
state.statusDetails = '';
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
export function applyLocalEditorHarnessViewportMeasurementPointerStart(state, event) {
|
|
597
|
+
if (state.viewportTools.activeUtilityTool !== 'measure-distance')
|
|
598
|
+
return false;
|
|
599
|
+
if (!state.viewportMeasurement.start || state.viewportMeasurement.end) {
|
|
600
|
+
const next = state.sceneViewMeasurement?.beginAt(event.clientX, event.clientY) ?? null;
|
|
601
|
+
if (!next)
|
|
602
|
+
return false;
|
|
603
|
+
state.viewportMeasurement = next;
|
|
604
|
+
state.status = 'Measure distance: pick second XZ ground point';
|
|
605
|
+
state.statusTone = 'default';
|
|
606
|
+
state.statusToneStatus = state.status;
|
|
607
|
+
state.statusDetails = '';
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
const next = state.sceneViewMeasurement?.completeAt(event.clientX, event.clientY) ?? null;
|
|
611
|
+
if (!next)
|
|
612
|
+
return false;
|
|
613
|
+
state.viewportMeasurement = next;
|
|
614
|
+
state.viewportTools = {
|
|
615
|
+
...state.viewportTools,
|
|
616
|
+
activeUtilityTool: 'none',
|
|
617
|
+
};
|
|
618
|
+
state.status = `Measured distance: ${formatViewportMeasurementDistance(next.distance)} units`;
|
|
619
|
+
state.statusTone = 'default';
|
|
620
|
+
state.statusToneStatus = state.status;
|
|
621
|
+
state.statusDetails = next.start && next.end
|
|
622
|
+
? `A (${formatViewportMeasurementDistance(next.start.x)}, ${formatViewportMeasurementDistance(next.start.z)}) · B (${formatViewportMeasurementDistance(next.end.x)}, ${formatViewportMeasurementDistance(next.end.z)})`
|
|
623
|
+
: '';
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
export function applyLocalEditorHarnessViewportMeasurementPointerMove(state, event) {
|
|
627
|
+
if (state.viewportTools.activeUtilityTool !== 'measure-distance')
|
|
628
|
+
return false;
|
|
629
|
+
const next = state.sceneViewMeasurement?.previewAt(event.clientX, event.clientY) ?? null;
|
|
630
|
+
if (!next)
|
|
631
|
+
return false;
|
|
632
|
+
state.viewportMeasurement = next;
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
export function applyLocalEditorHarnessViewportMeasurementPointerEnd(_state) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
function handleViewportMeasurementStart(state, event) {
|
|
639
|
+
return applyLocalEditorHarnessViewportMeasurementPointerStart(state, event);
|
|
640
|
+
}
|
|
641
|
+
function handleViewportMeasurementMove(state, event) {
|
|
642
|
+
return applyLocalEditorHarnessViewportMeasurementPointerMove(state, event);
|
|
643
|
+
}
|
|
644
|
+
function handleViewportMeasurementEnd(state) {
|
|
645
|
+
return applyLocalEditorHarnessViewportMeasurementPointerEnd(state);
|
|
646
|
+
}
|
|
647
|
+
function formatViewportMeasurementDistance(value) {
|
|
648
|
+
if (!Number.isFinite(value))
|
|
649
|
+
return '0';
|
|
650
|
+
const rounded = Math.abs(value) < 0.005 ? 0 : value;
|
|
651
|
+
return rounded.toFixed(2).replace(/\.?0+$/, '');
|
|
652
|
+
}
|
|
653
|
+
function resolveViewportPresetTarget(state) {
|
|
654
|
+
const selection = state.session?.getState().selection;
|
|
655
|
+
const activeId = selection?.activeId ?? null;
|
|
656
|
+
if (!activeId || selection?.selectedIds.length !== 1)
|
|
657
|
+
return null;
|
|
658
|
+
const bounds = state.projection?.getSelectionBounds([activeId]) ?? null;
|
|
659
|
+
return bounds?.center ?? null;
|
|
660
|
+
}
|
|
661
|
+
function resolveViewportPresetRadius(state) {
|
|
662
|
+
const selection = state.session?.getState().selection;
|
|
663
|
+
const activeId = selection?.activeId ?? null;
|
|
664
|
+
if (!activeId || selection?.selectedIds.length !== 1)
|
|
665
|
+
return undefined;
|
|
666
|
+
const bounds = state.projection?.getSelectionBounds([activeId]) ?? null;
|
|
667
|
+
if (!bounds)
|
|
668
|
+
return undefined;
|
|
669
|
+
const size = bounds.size;
|
|
670
|
+
const radius = Math.hypot(size.x, size.y, size.z);
|
|
671
|
+
return Number.isFinite(radius) && radius > 0 ? Math.max(radius * 1.5, 2) : undefined;
|
|
672
|
+
}
|
|
407
673
|
function validateTransformActionSelection(state, action) {
|
|
408
674
|
const selection = state.session?.getState().selection;
|
|
409
675
|
const selectedCount = selection?.selectedIds.length ?? 0;
|
|
@@ -430,10 +696,12 @@ async function createEditorWorld(state, options, render) {
|
|
|
430
696
|
cameraTarget: options.world?.cameraTarget,
|
|
431
697
|
cameraRadius: options.world?.cameraRadius,
|
|
432
698
|
clearColor: options.world?.clearColor,
|
|
699
|
+
sky: options.world?.sky,
|
|
433
700
|
useRightHandedSystem: options.world?.useRightHandedSystem,
|
|
434
701
|
enableDefaultCameraControls: false,
|
|
435
702
|
});
|
|
436
|
-
options.createGrid?.(babylon, world.scene);
|
|
703
|
+
const grid = options.createGrid?.(babylon, world.scene) ?? null;
|
|
704
|
+
grid?.setVisible(state.gridVisible);
|
|
437
705
|
const projection = createBabylonEditorProjection({
|
|
438
706
|
babylon,
|
|
439
707
|
scene: world.scene,
|
|
@@ -498,6 +766,10 @@ async function createEditorWorld(state, options, render) {
|
|
|
498
766
|
render();
|
|
499
767
|
},
|
|
500
768
|
});
|
|
769
|
+
const sceneViewMeasurement = createBabylonSceneViewMeasurementController({
|
|
770
|
+
babylon,
|
|
771
|
+
scene: world.scene,
|
|
772
|
+
});
|
|
501
773
|
const sceneViewInput = createBabylonSceneViewInputController({
|
|
502
774
|
canvas,
|
|
503
775
|
isEnabled: () => state.mode === 'editor',
|
|
@@ -505,7 +777,13 @@ async function createEditorWorld(state, options, render) {
|
|
|
505
777
|
isBoxSelectCandidate: (event) => selectionController.isBoxSelectionCandidate(event),
|
|
506
778
|
isViewPlaneMoveCandidate: (event) => gizmo.isViewPlaneMoveCandidate(event),
|
|
507
779
|
isPlacementCandidate: () => isPlacementArmed(state),
|
|
780
|
+
isMeasurementCandidate: () => state.viewportTools.activeUtilityTool === 'measure-distance',
|
|
508
781
|
onPointerIntentStart(event) {
|
|
782
|
+
if (event.state.intent === 'measurement') {
|
|
783
|
+
if (handleViewportMeasurementStart(state, event.originalEvent))
|
|
784
|
+
render();
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
509
787
|
if (event.state.intent === 'gizmo-drag') {
|
|
510
788
|
gizmo.preparePointerDrag(event.originalEvent);
|
|
511
789
|
return;
|
|
@@ -525,6 +803,11 @@ async function createEditorWorld(state, options, render) {
|
|
|
525
803
|
}
|
|
526
804
|
},
|
|
527
805
|
onPointerIntentMove(event) {
|
|
806
|
+
if (event.state.intent === 'measurement') {
|
|
807
|
+
if (handleViewportMeasurementMove(state, event.originalEvent))
|
|
808
|
+
render();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
528
811
|
if (event.state.intent === 'placement') {
|
|
529
812
|
if (previewArmedPlacement(state, event.originalEvent))
|
|
530
813
|
render();
|
|
@@ -544,6 +827,11 @@ async function createEditorWorld(state, options, render) {
|
|
|
544
827
|
}
|
|
545
828
|
},
|
|
546
829
|
onPointerIntentEnd(event) {
|
|
830
|
+
if (event.state.intent === 'measurement') {
|
|
831
|
+
if (handleViewportMeasurementEnd(state))
|
|
832
|
+
render();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
547
835
|
if (event.state.intent === 'placement') {
|
|
548
836
|
if (commitArmedPlacement(state, options, event.originalEvent))
|
|
549
837
|
render();
|
|
@@ -559,6 +847,10 @@ async function createEditorWorld(state, options, render) {
|
|
|
559
847
|
}
|
|
560
848
|
},
|
|
561
849
|
onPointerIntentCancel(event) {
|
|
850
|
+
if (event.state.intent === 'measurement') {
|
|
851
|
+
render();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
562
854
|
if (event.state.intent === 'placement') {
|
|
563
855
|
state.gizmo?.setPlacementMarker(null);
|
|
564
856
|
render();
|
|
@@ -593,6 +885,10 @@ async function createEditorWorld(state, options, render) {
|
|
|
593
885
|
scene: world.scene,
|
|
594
886
|
editorCamera: world.camera,
|
|
595
887
|
});
|
|
888
|
+
const sceneViewSpatialOverlay = createBabylonSceneViewSpatialOverlayController({
|
|
889
|
+
babylon,
|
|
890
|
+
scene: world.scene,
|
|
891
|
+
});
|
|
596
892
|
const document = state.session?.getState().workingDocument;
|
|
597
893
|
if (document) {
|
|
598
894
|
projection.projectNodes(options.documentAdapter.getProjectionNodes(document));
|
|
@@ -606,11 +902,14 @@ async function createEditorWorld(state, options, render) {
|
|
|
606
902
|
state.babylon = babylon;
|
|
607
903
|
state.engine = engine;
|
|
608
904
|
state.world = world;
|
|
905
|
+
state.grid = grid;
|
|
609
906
|
state.projection = projection;
|
|
610
907
|
state.gizmo = gizmo;
|
|
611
908
|
state.sceneCameraPreview = sceneCameraPreview;
|
|
612
909
|
state.sceneViewInput = sceneViewInput;
|
|
613
910
|
state.sceneViewCamera = sceneViewCamera;
|
|
911
|
+
state.sceneViewMeasurement = sceneViewMeasurement;
|
|
912
|
+
state.sceneViewSpatialOverlay = sceneViewSpatialOverlay;
|
|
614
913
|
state.selectionController = selectionController;
|
|
615
914
|
state.resizeHandler = resize;
|
|
616
915
|
}
|
|
@@ -624,15 +923,27 @@ function disposeEditorWorld(state) {
|
|
|
624
923
|
state.sceneCameraPreviewEnabled = false;
|
|
625
924
|
state.sceneViewCamera?.dispose();
|
|
626
925
|
state.sceneViewCamera = null;
|
|
926
|
+
state.sceneViewMeasurement?.dispose();
|
|
927
|
+
state.sceneViewMeasurement = null;
|
|
928
|
+
state.sceneViewSpatialOverlay?.dispose();
|
|
929
|
+
state.sceneViewSpatialOverlay = null;
|
|
627
930
|
state.sceneViewInput?.dispose();
|
|
628
931
|
state.sceneViewInput = null;
|
|
629
932
|
state.selectionController?.dispose();
|
|
630
933
|
state.selectionController = null;
|
|
631
934
|
state.boxSelection = null;
|
|
935
|
+
state.viewportSpatialOverlay = createEmptyEditorViewportSpatialOverlayState();
|
|
936
|
+
state.viewportMeasurement = createEmptyViewportMeasurement();
|
|
937
|
+
state.viewportTools = {
|
|
938
|
+
...state.viewportTools,
|
|
939
|
+
activeUtilityTool: 'none',
|
|
940
|
+
};
|
|
632
941
|
state.gizmo?.dispose();
|
|
633
942
|
state.gizmo = null;
|
|
634
943
|
state.projection?.dispose();
|
|
635
944
|
state.projection = null;
|
|
945
|
+
state.grid?.dispose();
|
|
946
|
+
state.grid = null;
|
|
636
947
|
state.engine?.stopRenderLoop?.();
|
|
637
948
|
state.world?.dispose();
|
|
638
949
|
state.engine?.dispose?.();
|
|
@@ -665,26 +976,16 @@ function selectItem(state, options, input) {
|
|
|
665
976
|
return false;
|
|
666
977
|
if (!isDocumentNodeSelectable(state, options, input.id))
|
|
667
978
|
return false;
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
selectedIds: [input.id],
|
|
679
|
-
activeId: input.id,
|
|
680
|
-
label: 'Add Selection',
|
|
681
|
-
}
|
|
682
|
-
: {
|
|
683
|
-
type: 'selection.replace',
|
|
684
|
-
selectedIds: [input.id],
|
|
685
|
-
activeId: input.id,
|
|
686
|
-
label: 'Select Item',
|
|
687
|
-
};
|
|
979
|
+
const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
|
|
980
|
+
const command = resolveEditorSelectionCommand({
|
|
981
|
+
selection,
|
|
982
|
+
targetIds: [input.id],
|
|
983
|
+
activeId: input.id,
|
|
984
|
+
gesture: 'click',
|
|
985
|
+
modifier: input.toggle ? 'toggle' : input.additive ? 'additive' : 'replace',
|
|
986
|
+
});
|
|
987
|
+
if (!command)
|
|
988
|
+
return false;
|
|
688
989
|
return dispatchSelectionCommand(state, options, command);
|
|
689
990
|
}
|
|
690
991
|
function handleContextAction(state, options, action) {
|
|
@@ -708,7 +1009,15 @@ function handleContextAction(state, options, action) {
|
|
|
708
1009
|
return createSceneGraphGroup(state, options, {
|
|
709
1010
|
parentId: action.parentId ?? null,
|
|
710
1011
|
activeId: action.activeId ?? null,
|
|
711
|
-
name: '
|
|
1012
|
+
name: 'Empty',
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
if (action.action === 'create-primitive') {
|
|
1016
|
+
return createSceneGraphPrimitive(state, options, {
|
|
1017
|
+
parentId: action.parentId ?? null,
|
|
1018
|
+
activeId: action.activeId ?? null,
|
|
1019
|
+
shape: action.shape,
|
|
1020
|
+
name: action.name,
|
|
712
1021
|
});
|
|
713
1022
|
}
|
|
714
1023
|
if (action.action === 'delete') {
|
|
@@ -781,17 +1090,17 @@ function createSceneGraphGroup(state, options, intent) {
|
|
|
781
1090
|
cancelActiveOperation(state);
|
|
782
1091
|
const patch = options.documentAdapter.createSceneGraphCreateGroupPatch?.(document, intent);
|
|
783
1092
|
if (!patch) {
|
|
784
|
-
state.status = 'Create
|
|
1093
|
+
state.status = 'Create empty rejected';
|
|
785
1094
|
return true;
|
|
786
1095
|
}
|
|
787
1096
|
const result = state.session.dispatch({
|
|
788
1097
|
type: 'document.patch',
|
|
789
|
-
label: patch.label ?? 'Create Empty
|
|
1098
|
+
label: patch.label ?? 'Create Empty',
|
|
790
1099
|
patch: patch.patch,
|
|
791
1100
|
targetId: patch.createdId ?? undefined,
|
|
792
1101
|
});
|
|
793
1102
|
if (!result.documentChanged) {
|
|
794
|
-
state.status = 'Create
|
|
1103
|
+
state.status = 'Create empty unchanged';
|
|
795
1104
|
return true;
|
|
796
1105
|
}
|
|
797
1106
|
const createdId = patch.createdId ?? null;
|
|
@@ -801,12 +1110,47 @@ function createSceneGraphGroup(state, options, intent) {
|
|
|
801
1110
|
type: 'selection.replace',
|
|
802
1111
|
selectedIds: [createdId],
|
|
803
1112
|
activeId: createdId,
|
|
804
|
-
label: 'Select Created
|
|
1113
|
+
label: 'Select Created Empty',
|
|
805
1114
|
}).selection;
|
|
806
1115
|
}
|
|
807
1116
|
rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
|
|
808
1117
|
state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
|
|
809
|
-
state.status = patch.label ?? (createdId ? `Created
|
|
1118
|
+
state.status = patch.label ?? (createdId ? `Created empty ${createdId}` : 'Created empty');
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
function createSceneGraphPrimitive(state, options, intent) {
|
|
1122
|
+
const document = state.session?.getState().workingDocument;
|
|
1123
|
+
if (state.mode !== 'editor' || !state.session || !document)
|
|
1124
|
+
return false;
|
|
1125
|
+
cancelActiveOperation(state);
|
|
1126
|
+
const patch = options.documentAdapter.createSceneGraphCreatePrimitivePatch?.(document, intent);
|
|
1127
|
+
if (!patch) {
|
|
1128
|
+
state.status = `Create ${intent.shape} rejected`;
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
const result = state.session.dispatch({
|
|
1132
|
+
type: 'document.patch',
|
|
1133
|
+
label: patch.label ?? `Create ${intent.shape}`,
|
|
1134
|
+
patch: patch.patch,
|
|
1135
|
+
targetId: patch.createdId ?? undefined,
|
|
1136
|
+
});
|
|
1137
|
+
if (!result.documentChanged) {
|
|
1138
|
+
state.status = `Create ${intent.shape} unchanged`;
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
const createdId = patch.createdId ?? null;
|
|
1142
|
+
let selection = result.selection;
|
|
1143
|
+
if (createdId && isNodeSelectableInDocument(options, result.workingDocument, createdId)) {
|
|
1144
|
+
selection = state.session.dispatch({
|
|
1145
|
+
type: 'selection.replace',
|
|
1146
|
+
selectedIds: [createdId],
|
|
1147
|
+
activeId: createdId,
|
|
1148
|
+
label: 'Select Created Primitive',
|
|
1149
|
+
}).selection;
|
|
1150
|
+
}
|
|
1151
|
+
rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
|
|
1152
|
+
state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
|
|
1153
|
+
state.status = patch.label ?? (createdId ? `Created ${intent.shape} ${createdId}` : `Created ${intent.shape}`);
|
|
810
1154
|
return true;
|
|
811
1155
|
}
|
|
812
1156
|
function deleteSceneGraphNodes(state, options, intent) {
|
|
@@ -996,27 +1340,27 @@ function groupSceneGraphSelection(state, options, intent) {
|
|
|
996
1340
|
const hierarchy = options.documentAdapter.getHierarchyItems(document);
|
|
997
1341
|
const coreValidation = validateSceneGraphGroupSelection(hierarchy, intent);
|
|
998
1342
|
if (!coreValidation.ok) {
|
|
999
|
-
state.status = `
|
|
1343
|
+
state.status = `Parent selection rejected: ${coreValidation.reason ?? 'invalid scene graph parent selection'}`;
|
|
1000
1344
|
return true;
|
|
1001
1345
|
}
|
|
1002
1346
|
const projectValidation = options.documentAdapter.validateSceneGraphGroupSelection?.(document, intent);
|
|
1003
1347
|
if (projectValidation && !projectValidation.ok) {
|
|
1004
|
-
state.status = `
|
|
1348
|
+
state.status = `Parent selection rejected: ${projectValidation.reason ?? 'project validation failed'}`;
|
|
1005
1349
|
return true;
|
|
1006
1350
|
}
|
|
1007
1351
|
const patch = options.documentAdapter.createSceneGraphGroupSelectionPatch?.(document, intent);
|
|
1008
1352
|
if (!patch) {
|
|
1009
|
-
state.status = '
|
|
1353
|
+
state.status = 'Parent selection rejected';
|
|
1010
1354
|
return true;
|
|
1011
1355
|
}
|
|
1012
1356
|
const result = state.session.dispatch({
|
|
1013
1357
|
type: 'document.patch',
|
|
1014
|
-
label: patch.label ?? '
|
|
1358
|
+
label: patch.label ?? 'Parent Selection',
|
|
1015
1359
|
patch: patch.patch,
|
|
1016
1360
|
targetId: patch.createdId,
|
|
1017
1361
|
});
|
|
1018
1362
|
if (!result.documentChanged) {
|
|
1019
|
-
state.status = '
|
|
1363
|
+
state.status = 'Parent selection unchanged';
|
|
1020
1364
|
return true;
|
|
1021
1365
|
}
|
|
1022
1366
|
let selection = result.selection;
|
|
@@ -1025,7 +1369,7 @@ function groupSceneGraphSelection(state, options, intent) {
|
|
|
1025
1369
|
type: 'selection.replace',
|
|
1026
1370
|
selectedIds: [patch.createdId],
|
|
1027
1371
|
activeId: patch.createdId,
|
|
1028
|
-
label: 'Select Created
|
|
1372
|
+
label: 'Select Created Parent',
|
|
1029
1373
|
}).selection;
|
|
1030
1374
|
}
|
|
1031
1375
|
else {
|
|
@@ -1033,7 +1377,7 @@ function groupSceneGraphSelection(state, options, intent) {
|
|
|
1033
1377
|
}
|
|
1034
1378
|
rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
|
|
1035
1379
|
state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
|
|
1036
|
-
state.status = patch.label ?? `
|
|
1380
|
+
state.status = patch.label ?? `Parented ${patch.changedIds?.length ?? intent.ids.length} node(s)`;
|
|
1037
1381
|
return true;
|
|
1038
1382
|
}
|
|
1039
1383
|
function sanitizeSelection(state, options, document, selection) {
|
|
@@ -1593,6 +1937,21 @@ function cancelActiveOperation(state) {
|
|
|
1593
1937
|
cancelActiveGizmoDrag(state);
|
|
1594
1938
|
clearArmedPlacement(state);
|
|
1595
1939
|
}
|
|
1940
|
+
function cancelEditorIntent(state, options) {
|
|
1941
|
+
if (state.mode !== 'editor')
|
|
1942
|
+
return false;
|
|
1943
|
+
if (state.viewportTools.activeUtilityTool === 'measure-distance'
|
|
1944
|
+
|| state.viewportMeasurement.start
|
|
1945
|
+
|| state.viewportMeasurement.end) {
|
|
1946
|
+
return clearViewportMeasurement(state);
|
|
1947
|
+
}
|
|
1948
|
+
const selection = state.session?.getSelection();
|
|
1949
|
+
if (selection && selection.selectedIds.length > 0) {
|
|
1950
|
+
return dispatchSelectionCommand(state, options, { type: 'selection.clear', label: 'Clear Selection' });
|
|
1951
|
+
}
|
|
1952
|
+
cancelActiveOperation(state);
|
|
1953
|
+
return true;
|
|
1954
|
+
}
|
|
1596
1955
|
function focusSelectedProjection(state) {
|
|
1597
1956
|
if (state.mode !== 'editor')
|
|
1598
1957
|
return false;
|
|
@@ -1716,6 +2075,18 @@ function setSceneCameraPreviewEnabled(state, options, enabled) {
|
|
|
1716
2075
|
state.status = 'Scene Camera preview enabled';
|
|
1717
2076
|
return true;
|
|
1718
2077
|
}
|
|
2078
|
+
function setGridVisible(state, visible) {
|
|
2079
|
+
const nextVisible = visible === true;
|
|
2080
|
+
if (state.gridVisible === nextVisible)
|
|
2081
|
+
return false;
|
|
2082
|
+
state.gridVisible = nextVisible;
|
|
2083
|
+
state.grid?.setVisible(nextVisible);
|
|
2084
|
+
state.status = nextVisible ? 'Grid visible' : 'Grid hidden';
|
|
2085
|
+
state.statusTone = 'default';
|
|
2086
|
+
state.statusToneStatus = state.status;
|
|
2087
|
+
state.statusDetails = '';
|
|
2088
|
+
return true;
|
|
2089
|
+
}
|
|
1719
2090
|
function syncSceneCameraPreview(state, options) {
|
|
1720
2091
|
const controller = state.sceneCameraPreview;
|
|
1721
2092
|
if (!controller)
|
|
@@ -1733,6 +2104,40 @@ function syncSceneCameraPreview(state, options) {
|
|
|
1733
2104
|
controller.setActive(true, rig);
|
|
1734
2105
|
return controller.isActive();
|
|
1735
2106
|
}
|
|
2107
|
+
function syncViewportCameraState(state) {
|
|
2108
|
+
const cameraState = state.sceneViewCamera?.getState();
|
|
2109
|
+
if (!cameraState)
|
|
2110
|
+
return;
|
|
2111
|
+
state.viewportTools = {
|
|
2112
|
+
...state.viewportTools,
|
|
2113
|
+
viewPreset: cameraState.viewPreset,
|
|
2114
|
+
projectionMode: cameraState.projectionMode,
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
function syncViewportSpatialOverlay(state) {
|
|
2118
|
+
const overlay = state.sceneViewSpatialOverlay;
|
|
2119
|
+
const selection = state.session?.getState().selection;
|
|
2120
|
+
const activeId = selection?.activeId ?? null;
|
|
2121
|
+
if (state.mode !== 'editor' || !overlay || !activeId || selection?.selectedIds.length !== 1) {
|
|
2122
|
+
state.viewportSpatialOverlay = createEmptyEditorViewportSpatialOverlayState();
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
const bounds = state.projection?.getSelectionBounds([activeId]) ?? null;
|
|
2126
|
+
const anchor = state.projection?.readNodeTransform(activeId)?.position ?? bounds?.center ?? null;
|
|
2127
|
+
state.viewportSpatialOverlay = overlay.compute({
|
|
2128
|
+
nodeId: activeId,
|
|
2129
|
+
bounds,
|
|
2130
|
+
anchor,
|
|
2131
|
+
settings: state.viewportTools.overlay,
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
function syncViewportMeasurementState(state) {
|
|
2135
|
+
if (state.mode !== 'editor')
|
|
2136
|
+
return;
|
|
2137
|
+
const measurement = state.sceneViewMeasurement?.getState();
|
|
2138
|
+
if (measurement)
|
|
2139
|
+
state.viewportMeasurement = measurement;
|
|
2140
|
+
}
|
|
1736
2141
|
function hasSceneCameraPreviewRig(state, options) {
|
|
1737
2142
|
const document = state.session?.getState().workingDocument ?? null;
|
|
1738
2143
|
return !!document && !!options.documentAdapter.getSceneCameraPreviewRig?.(document);
|
|
@@ -1784,6 +2189,9 @@ function createUiState(state, options) {
|
|
|
1784
2189
|
inspectorObject,
|
|
1785
2190
|
inspectorMultiObject,
|
|
1786
2191
|
boxSelection: state.boxSelection,
|
|
2192
|
+
coordinateAxes: options.world?.coordinateAxes === false
|
|
2193
|
+
? null
|
|
2194
|
+
: createSceneViewCoordinateAxesState(state),
|
|
1787
2195
|
transformTool: {
|
|
1788
2196
|
activeTool: state.gizmo?.getState().tool ?? state.transformTool,
|
|
1789
2197
|
activeSpace: state.gizmo?.getState().space ?? state.transformSpace,
|
|
@@ -1798,10 +2206,17 @@ function createUiState(state, options) {
|
|
|
1798
2206
|
canAlign: selectedIds.length >= 2 && activeId != null,
|
|
1799
2207
|
canDistribute: selectedIds.length >= 3,
|
|
1800
2208
|
},
|
|
2209
|
+
viewportTools: cloneEditorViewportToolState(state.viewportTools),
|
|
2210
|
+
viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
|
|
2211
|
+
viewportSpatialOverlay: cloneEditorViewportSpatialOverlayState(state.viewportSpatialOverlay),
|
|
1801
2212
|
sceneCameraPreview: {
|
|
1802
2213
|
enabled: state.sceneCameraPreviewEnabled,
|
|
1803
2214
|
available: hasSceneCameraPreviewRig(state, options),
|
|
1804
2215
|
},
|
|
2216
|
+
grid: {
|
|
2217
|
+
visible: state.gridVisible,
|
|
2218
|
+
available: !!state.grid,
|
|
2219
|
+
},
|
|
1805
2220
|
session: sessionState
|
|
1806
2221
|
? {
|
|
1807
2222
|
source: sessionState.source,
|
|
@@ -1813,6 +2228,89 @@ function createUiState(state, options) {
|
|
|
1813
2228
|
: null,
|
|
1814
2229
|
};
|
|
1815
2230
|
}
|
|
2231
|
+
function createSceneViewCoordinateAxesState(state) {
|
|
2232
|
+
if (state.mode !== 'editor')
|
|
2233
|
+
return null;
|
|
2234
|
+
const scene = state.world?.scene ?? null;
|
|
2235
|
+
const camera = scene?.activeCamera ?? state.world?.camera ?? null;
|
|
2236
|
+
const Vector3 = state.babylon?.Vector3;
|
|
2237
|
+
if (!scene || !camera || !Vector3)
|
|
2238
|
+
return null;
|
|
2239
|
+
const right = readSceneViewCameraDirection(camera, Vector3, { x: 1, y: 0, z: 0 });
|
|
2240
|
+
const up = readSceneViewCameraDirection(camera, Vector3, { x: 0, y: 1, z: 0 });
|
|
2241
|
+
const forward = readSceneViewCameraForward(scene, camera, Vector3);
|
|
2242
|
+
if (!right || !up || !forward)
|
|
2243
|
+
return null;
|
|
2244
|
+
return {
|
|
2245
|
+
axes: [
|
|
2246
|
+
createSceneViewCoordinateAxis('x', 'X', '#ff4b70', { x: 1, y: 0, z: 0 }, right, up, forward),
|
|
2247
|
+
createSceneViewCoordinateAxis('y', 'Y', '#75ff42', { x: 0, y: 1, z: 0 }, right, up, forward),
|
|
2248
|
+
createSceneViewCoordinateAxis('z', 'Z', '#4f86ff', { x: 0, y: 0, z: 1 }, right, up, forward),
|
|
2249
|
+
],
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
function createSceneViewCoordinateAxis(id, label, color, worldAxis, cameraRight, cameraUp, cameraForward) {
|
|
2253
|
+
const screenX = dotSceneViewAxisVec3(worldAxis, cameraRight);
|
|
2254
|
+
const screenY = -dotSceneViewAxisVec3(worldAxis, cameraUp);
|
|
2255
|
+
const projectedLength = Math.hypot(screenX, screenY);
|
|
2256
|
+
const fallback = getSceneViewAxisFallbackDirection(id);
|
|
2257
|
+
const direction = projectedLength > 0.0001
|
|
2258
|
+
? { x: screenX / projectedLength, y: screenY / projectedLength }
|
|
2259
|
+
: fallback;
|
|
2260
|
+
return {
|
|
2261
|
+
id,
|
|
2262
|
+
label,
|
|
2263
|
+
color,
|
|
2264
|
+
x: direction.x,
|
|
2265
|
+
y: direction.y,
|
|
2266
|
+
depth: dotSceneViewAxisVec3(worldAxis, cameraForward),
|
|
2267
|
+
scale: clampNumber(projectedLength, 0.36, 1),
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function readSceneViewCameraDirection(camera, Vector3, local) {
|
|
2271
|
+
if (!camera?.getDirection)
|
|
2272
|
+
return null;
|
|
2273
|
+
try {
|
|
2274
|
+
return normalizeSceneViewAxisVec3(camera.getDirection(new Vector3(local.x, local.y, local.z)));
|
|
2275
|
+
}
|
|
2276
|
+
catch {
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function readSceneViewCameraForward(scene, camera, Vector3) {
|
|
2281
|
+
const rayDirection = normalizeSceneViewAxisVec3(camera?.getForwardRay?.()?.direction);
|
|
2282
|
+
if (rayDirection)
|
|
2283
|
+
return rayDirection;
|
|
2284
|
+
return readSceneViewCameraDirection(camera, Vector3, {
|
|
2285
|
+
x: 0,
|
|
2286
|
+
y: 0,
|
|
2287
|
+
z: scene?.useRightHandedSystem ? -1 : 1,
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
function normalizeSceneViewAxisVec3(value) {
|
|
2291
|
+
const x = Number(value?.x) || 0;
|
|
2292
|
+
const y = Number(value?.y) || 0;
|
|
2293
|
+
const z = Number(value?.z) || 0;
|
|
2294
|
+
const length = Math.hypot(x, y, z);
|
|
2295
|
+
if (!Number.isFinite(length) || length <= 0.000001)
|
|
2296
|
+
return null;
|
|
2297
|
+
return { x: x / length, y: y / length, z: z / length };
|
|
2298
|
+
}
|
|
2299
|
+
function dotSceneViewAxisVec3(left, right) {
|
|
2300
|
+
return left.x * right.x + left.y * right.y + left.z * right.z;
|
|
2301
|
+
}
|
|
2302
|
+
function getSceneViewAxisFallbackDirection(id) {
|
|
2303
|
+
if (id === 'x')
|
|
2304
|
+
return { x: 1, y: 0 };
|
|
2305
|
+
if (id === 'y')
|
|
2306
|
+
return { x: 0, y: -1 };
|
|
2307
|
+
return { x: -1, y: 0 };
|
|
2308
|
+
}
|
|
2309
|
+
function clampNumber(value, min, max) {
|
|
2310
|
+
if (!Number.isFinite(value))
|
|
2311
|
+
return min;
|
|
2312
|
+
return Math.min(max, Math.max(min, value));
|
|
2313
|
+
}
|
|
1816
2314
|
function withRuntimeInspectorSections(state, options, document, inspectorObject) {
|
|
1817
2315
|
const baseInspectorObject = withInspectorComponentSections(state, options, document, inspectorObject);
|
|
1818
2316
|
const activeId = inspectorObject.activeId;
|