@fps-games/editor 0.1.1-beta.9 → 0.1.2-beta.2
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 +52 -4
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +437 -91
- package/dist/local-editor-harness.js.map +1 -1
- package/dist/local-editor-scene-render-scheduler.d.ts +33 -0
- package/dist/local-editor-scene-render-scheduler.d.ts.map +1 -0
- package/dist/local-editor-scene-render-scheduler.js +160 -0
- package/dist/local-editor-scene-render-scheduler.js.map +1 -0
- package/dist/local-editor-scene-view-interaction-runtime.d.ts +22 -0
- package/dist/local-editor-scene-view-interaction-runtime.d.ts.map +1 -0
- package/dist/local-editor-scene-view-interaction-runtime.js +80 -0
- package/dist/local-editor-scene-view-interaction-runtime.js.map +1 -0
- package/dist/local-editor-viewport-render-coordinator.d.ts +17 -0
- package/dist/local-editor-viewport-render-coordinator.d.ts.map +1 -0
- package/dist/local-editor-viewport-render-coordinator.js +102 -0
- package/dist/local-editor-viewport-render-coordinator.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -0
- 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 +9 -3
- 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.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +2 -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 +1 -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 +1 -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 +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +335 -32
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts +25 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js +162 -0
- package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +28 -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 +169 -25
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts +2 -22
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js +1 -121
- package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +4 -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 +37 -23
- 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 +100 -8
- package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts +6 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js +172 -106
- package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts +30 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js +120 -0
- package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +2 -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-hierarchy-actions.d.ts +2 -1
- 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 +167 -34
- 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 +2 -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 +105 -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-view.d.ts +3 -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 +49 -4
- 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-input-router.d.ts +4 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js +12 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +1 -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 +96 -14
- 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.js +1 -1
- 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 +7 -6
- 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-tooltip.js +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +59 -2
- 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 +1 -0
- 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 -0
- 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 +5 -2
- 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 +260 -40
- 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/scene-view-input.d.ts +7 -0
- package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts +22 -0
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/transform-gizmo.js.map +1 -1
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +6 -6
- package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +3 -3
- 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-protocol/package.json +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,9 @@
|
|
|
1
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,
|
|
3
|
+
import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonMainCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonTransformGizmoController, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
|
|
4
|
+
import { createLocalEditorSceneRenderScheduler, } from './local-editor-scene-render-scheduler';
|
|
5
|
+
import { createLocalEditorViewportRenderCoordinator, } from './local-editor-viewport-render-coordinator';
|
|
6
|
+
import { createLocalEditorSceneViewInteractionRuntime, } from './local-editor-scene-view-interaction-runtime';
|
|
4
7
|
export function createLocalEditorHarness(options) {
|
|
5
8
|
const root = options.root ?? document.body;
|
|
6
9
|
const state = {
|
|
@@ -17,10 +20,11 @@ export function createLocalEditorHarness(options) {
|
|
|
17
20
|
gridVisible: true,
|
|
18
21
|
projection: null,
|
|
19
22
|
gizmo: null,
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
mainCameraPreview: null,
|
|
24
|
+
mainCameraPreviewEnabled: false,
|
|
22
25
|
sceneViewInput: null,
|
|
23
26
|
sceneViewCamera: null,
|
|
27
|
+
sceneViewInteraction: null,
|
|
24
28
|
sceneViewMeasurement: null,
|
|
25
29
|
sceneViewSpatialOverlay: null,
|
|
26
30
|
selectionController: null,
|
|
@@ -34,6 +38,9 @@ export function createLocalEditorHarness(options) {
|
|
|
34
38
|
viewportSpatialOverlay: createEmptyEditorViewportSpatialOverlayState(),
|
|
35
39
|
duplicateDrag: null,
|
|
36
40
|
armedPlacement: null,
|
|
41
|
+
sceneRenderScheduler: null,
|
|
42
|
+
viewportRenderCoordinator: null,
|
|
43
|
+
sceneFrameStats: null,
|
|
37
44
|
resizeHandler: null,
|
|
38
45
|
status: 'Game running',
|
|
39
46
|
statusTone: 'default',
|
|
@@ -46,7 +53,13 @@ export function createLocalEditorHarness(options) {
|
|
|
46
53
|
root,
|
|
47
54
|
theme: options.theme,
|
|
48
55
|
localTestActions: options.localTestActions,
|
|
56
|
+
input: {
|
|
57
|
+
isShortcutReserved: (event) => state.sceneViewInput?.ownsKeyboardEvent(event) ?? false,
|
|
58
|
+
},
|
|
49
59
|
inspector: options.inspector,
|
|
60
|
+
hierarchy: {
|
|
61
|
+
contextActions: createBrowserHierarchyContextActions(state, options),
|
|
62
|
+
},
|
|
50
63
|
callbacks: {
|
|
51
64
|
onEnterEditor: () => {
|
|
52
65
|
void runExclusive(state, harness.render, () => harness.enterEditor());
|
|
@@ -138,16 +151,19 @@ export function createLocalEditorHarness(options) {
|
|
|
138
151
|
state.transformConstraint = normalizeTransformConstraint(tool, state.transformConstraint);
|
|
139
152
|
state.gizmo?.setTool(tool);
|
|
140
153
|
state.gizmo?.setConstraint(state.transformConstraint);
|
|
154
|
+
requestEditorSceneFrame(state, 'transform-tool-change');
|
|
141
155
|
harness.render();
|
|
142
156
|
},
|
|
143
157
|
onTransformSpaceChange: (space) => {
|
|
144
158
|
state.transformSpace = space;
|
|
145
159
|
state.gizmo?.setSpace(space);
|
|
160
|
+
requestEditorSceneFrame(state, 'transform-space-change');
|
|
146
161
|
harness.render();
|
|
147
162
|
},
|
|
148
163
|
onTransformConstraintChange: (constraint) => {
|
|
149
164
|
state.transformConstraint = normalizeTransformConstraint(state.transformTool, constraint);
|
|
150
165
|
state.gizmo?.setConstraint(state.transformConstraint);
|
|
166
|
+
requestEditorSceneFrame(state, 'transform-constraint-change');
|
|
151
167
|
harness.render();
|
|
152
168
|
},
|
|
153
169
|
onTransformSnapEnabledChange: (enabled) => {
|
|
@@ -159,6 +175,7 @@ export function createLocalEditorHarness(options) {
|
|
|
159
175
|
});
|
|
160
176
|
state.gizmo?.setOperationSettings(state.transformOperationSettings);
|
|
161
177
|
state.status = enabled ? 'Transform snap enabled' : 'Transform snap disabled';
|
|
178
|
+
requestEditorSceneFrame(state, 'transform-operation-settings-change');
|
|
162
179
|
harness.render();
|
|
163
180
|
},
|
|
164
181
|
onTransformSnapStepChange: (input) => {
|
|
@@ -175,6 +192,7 @@ export function createLocalEditorHarness(options) {
|
|
|
175
192
|
state.transformOperationSettings = updateTransformOperationSettings(state.transformOperationSettings, { snap });
|
|
176
193
|
state.gizmo?.setOperationSettings(state.transformOperationSettings);
|
|
177
194
|
state.status = `Transform snap ${input.kind} step ${value}`;
|
|
195
|
+
requestEditorSceneFrame(state, 'transform-operation-settings-change');
|
|
178
196
|
harness.render();
|
|
179
197
|
},
|
|
180
198
|
onPlacementModeChange: (mode) => {
|
|
@@ -185,6 +203,7 @@ export function createLocalEditorHarness(options) {
|
|
|
185
203
|
if (state.transformOperationSettings.placementMode === 'off')
|
|
186
204
|
clearArmedPlacement(state);
|
|
187
205
|
state.status = `Placement mode: ${state.transformOperationSettings.placementMode}`;
|
|
206
|
+
requestEditorSceneFrame(state, 'placement-mode-change');
|
|
188
207
|
harness.render();
|
|
189
208
|
},
|
|
190
209
|
onTransformAction: (action) => {
|
|
@@ -211,8 +230,8 @@ export function createLocalEditorHarness(options) {
|
|
|
211
230
|
if (clearViewportMeasurement(state))
|
|
212
231
|
harness.render();
|
|
213
232
|
},
|
|
214
|
-
|
|
215
|
-
if (
|
|
233
|
+
onMainCameraPreviewToggle: (enabled) => {
|
|
234
|
+
if (setMainCameraPreviewEnabled(state, options, enabled))
|
|
216
235
|
harness.render();
|
|
217
236
|
},
|
|
218
237
|
onGridVisibleChange: (visible) => {
|
|
@@ -229,17 +248,23 @@ export function createLocalEditorHarness(options) {
|
|
|
229
248
|
},
|
|
230
249
|
onCancelActiveOperation: () => {
|
|
231
250
|
cancelActiveOperation(state);
|
|
251
|
+
requestEditorSceneFrame(state, 'cancel-active-operation');
|
|
232
252
|
harness.render();
|
|
233
253
|
},
|
|
234
254
|
},
|
|
235
255
|
});
|
|
256
|
+
const renderUi = () => {
|
|
257
|
+
syncViewportCameraState(state);
|
|
258
|
+
syncViewportMeasurementState(state);
|
|
259
|
+
syncViewportSpatialOverlay(state);
|
|
260
|
+
ui.update(createUiState(state, options));
|
|
261
|
+
};
|
|
236
262
|
harness = {
|
|
237
263
|
render() {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
ui.update(createUiState(state, options));
|
|
264
|
+
renderUi();
|
|
265
|
+
},
|
|
266
|
+
notifyViewportRevealed(reason = 'viewport-revealed') {
|
|
267
|
+
state.viewportRenderCoordinator?.requestRevealFrame(reason);
|
|
243
268
|
},
|
|
244
269
|
setTheme(theme) {
|
|
245
270
|
ui.setTheme(theme);
|
|
@@ -307,10 +332,12 @@ export function createLocalEditorHarness(options) {
|
|
|
307
332
|
reduceDocument: options.documentAdapter.reduceDocument,
|
|
308
333
|
});
|
|
309
334
|
await options.worldAdapter.disposeGameWorld();
|
|
310
|
-
await createEditorWorld(state, options, harness.render);
|
|
335
|
+
await createEditorWorld(state, options, harness.render, stats => ui.updateSceneFrameStats?.(stats));
|
|
311
336
|
state.mode = 'editor';
|
|
312
337
|
state.summary = loadedSource?.summary ?? summarizeDocument(options, preparedDocument, source);
|
|
313
338
|
state.status = `GameWorld disposed; EditorWorld active; assets=${assets.length}`;
|
|
339
|
+
harness.render();
|
|
340
|
+
harness.notifyViewportRevealed('editor-enter');
|
|
314
341
|
},
|
|
315
342
|
async saveScene() {
|
|
316
343
|
cancelActiveOperation(state);
|
|
@@ -485,11 +512,11 @@ function cloneViewportMeasurement(measurement) {
|
|
|
485
512
|
};
|
|
486
513
|
}
|
|
487
514
|
function setViewportViewPreset(state, preset) {
|
|
488
|
-
if (state.
|
|
489
|
-
state.status = 'Viewport view unavailable during
|
|
515
|
+
if (state.mainCameraPreviewEnabled) {
|
|
516
|
+
state.status = 'Viewport view unavailable during Main Camera preview';
|
|
490
517
|
state.statusTone = 'warning';
|
|
491
518
|
state.statusToneStatus = state.status;
|
|
492
|
-
state.statusDetails = 'Disable
|
|
519
|
+
state.statusDetails = 'Disable Main Camera preview before switching editor viewport views.';
|
|
493
520
|
return true;
|
|
494
521
|
}
|
|
495
522
|
const nextPreset = normalizeViewportViewPreset(preset);
|
|
@@ -508,14 +535,15 @@ function setViewportViewPreset(state, preset) {
|
|
|
508
535
|
state.statusTone = 'default';
|
|
509
536
|
state.statusToneStatus = state.status;
|
|
510
537
|
state.statusDetails = '';
|
|
538
|
+
requestEditorSceneFrame(state, 'viewport-view-preset-change');
|
|
511
539
|
return true;
|
|
512
540
|
}
|
|
513
541
|
function setViewportProjectionMode(state, mode) {
|
|
514
|
-
if (state.
|
|
515
|
-
state.status = 'Viewport projection unavailable during
|
|
542
|
+
if (state.mainCameraPreviewEnabled) {
|
|
543
|
+
state.status = 'Viewport projection unavailable during Main Camera preview';
|
|
516
544
|
state.statusTone = 'warning';
|
|
517
545
|
state.statusToneStatus = state.status;
|
|
518
|
-
state.statusDetails = 'Disable
|
|
546
|
+
state.statusDetails = 'Disable Main Camera preview before switching editor viewport projection.';
|
|
519
547
|
return true;
|
|
520
548
|
}
|
|
521
549
|
const nextMode = normalizeViewportProjectionMode(mode);
|
|
@@ -531,6 +559,7 @@ function setViewportProjectionMode(state, mode) {
|
|
|
531
559
|
state.statusTone = 'default';
|
|
532
560
|
state.statusToneStatus = state.status;
|
|
533
561
|
state.statusDetails = '';
|
|
562
|
+
requestEditorSceneFrame(state, 'viewport-projection-mode-change');
|
|
534
563
|
return true;
|
|
535
564
|
}
|
|
536
565
|
function setViewportOverlaySettings(state, settings) {
|
|
@@ -682,7 +711,7 @@ function validateTransformActionSelection(state, action) {
|
|
|
682
711
|
? { ok: true }
|
|
683
712
|
: { ok: false, message: 'Distribute needs at least 3 selected objects' };
|
|
684
713
|
}
|
|
685
|
-
async function createEditorWorld(state, options,
|
|
714
|
+
async function createEditorWorld(state, options, renderUi, updateSceneFrameStats) {
|
|
686
715
|
disposeEditorWorld(state);
|
|
687
716
|
const canvas = options.worldAdapter.getCanvas();
|
|
688
717
|
if (!canvas)
|
|
@@ -700,13 +729,39 @@ async function createEditorWorld(state, options, render) {
|
|
|
700
729
|
useRightHandedSystem: options.world?.useRightHandedSystem,
|
|
701
730
|
enableDefaultCameraControls: false,
|
|
702
731
|
});
|
|
703
|
-
const
|
|
732
|
+
const sceneRenderScheduler = createLocalEditorSceneRenderScheduler((frame) => {
|
|
733
|
+
state.sceneViewInteraction?.updateFrame(frame);
|
|
734
|
+
world.render();
|
|
735
|
+
}, {
|
|
736
|
+
onStatsChange(stats) {
|
|
737
|
+
state.sceneFrameStats = stats;
|
|
738
|
+
updateSceneFrameStats(stats);
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
const viewportRenderCoordinator = createLocalEditorViewportRenderCoordinator({
|
|
742
|
+
scheduler: sceneRenderScheduler,
|
|
743
|
+
getEngine: () => engine,
|
|
744
|
+
getScene: () => world.scene,
|
|
745
|
+
});
|
|
746
|
+
const sceneViewInteraction = createLocalEditorSceneViewInteractionRuntime({
|
|
747
|
+
coordinator: viewportRenderCoordinator,
|
|
748
|
+
getCamera: () => state.sceneViewCamera,
|
|
749
|
+
});
|
|
750
|
+
state.sceneViewInteraction = sceneViewInteraction;
|
|
751
|
+
const grid = options.createGrid?.(babylon, world.scene, world.camera, {
|
|
752
|
+
getCamera: () => world.scene?.activeCamera ?? world.camera ?? null,
|
|
753
|
+
getEditorCamera: () => world.camera ?? null,
|
|
754
|
+
}) ?? null;
|
|
704
755
|
grid?.setVisible(state.gridVisible);
|
|
705
756
|
const projection = createBabylonEditorProjection({
|
|
706
757
|
babylon,
|
|
707
758
|
scene: world.scene,
|
|
708
759
|
importModel: options.worldAdapter.importProjectionModel,
|
|
709
760
|
logger: console,
|
|
761
|
+
onProjectionReady(event) {
|
|
762
|
+
syncCurrentSelectionToSceneArtifacts(state);
|
|
763
|
+
viewportRenderCoordinator.invalidateScene(`projection-${event.nodeId}-ready`);
|
|
764
|
+
},
|
|
710
765
|
});
|
|
711
766
|
const gizmo = createBabylonTransformGizmoController({
|
|
712
767
|
babylon,
|
|
@@ -716,27 +771,43 @@ async function createEditorWorld(state, options, render) {
|
|
|
716
771
|
initialSpace: state.transformSpace,
|
|
717
772
|
logger: console,
|
|
718
773
|
onDragStart(event) {
|
|
774
|
+
sceneViewInteraction.beginGizmoDrag();
|
|
719
775
|
state.status = event.targetIds.length > 1
|
|
720
776
|
? `Dragging ${event.duplicate ? 'duplicate ' : ''}${event.tool} ${event.targetIds.length} objects`
|
|
721
777
|
: `Dragging ${event.duplicate ? 'duplicate ' : ''}${event.tool} ${event.nodeId ?? event.activeId ?? 'selection'}`;
|
|
722
|
-
|
|
778
|
+
renderUi();
|
|
723
779
|
},
|
|
724
780
|
onDragUpdate() {
|
|
725
|
-
|
|
781
|
+
renderUi();
|
|
726
782
|
},
|
|
727
783
|
onDragEnd(event) {
|
|
728
|
-
|
|
729
|
-
|
|
784
|
+
sceneViewInteraction.endGizmoDrag();
|
|
785
|
+
const changed = commitGizmoTransform(state, options, event);
|
|
786
|
+
if (!changed)
|
|
787
|
+
requestEditorSceneFrame(state, 'gizmo-drag-end');
|
|
788
|
+
renderUi();
|
|
730
789
|
},
|
|
731
790
|
onDragCancel(event) {
|
|
791
|
+
sceneViewInteraction.endGizmoDrag();
|
|
732
792
|
if (event.duplicate && cancelDuplicateDrag(state, options)) {
|
|
733
|
-
|
|
793
|
+
renderUi();
|
|
734
794
|
return;
|
|
735
795
|
}
|
|
796
|
+
requestEditorSceneFrame(state, 'gizmo-drag-cancel');
|
|
736
797
|
state.status = event.targetIds.length > 1
|
|
737
798
|
? `Canceled ${event.tool} ${event.targetIds.length} objects`
|
|
738
799
|
: `Canceled ${event.tool} ${event.nodeId ?? event.activeId ?? 'selection'}`;
|
|
739
|
-
|
|
800
|
+
renderUi();
|
|
801
|
+
},
|
|
802
|
+
onDragBlocked(event) {
|
|
803
|
+
sceneViewInteraction.endGizmoDrag();
|
|
804
|
+
if (event.duplicate)
|
|
805
|
+
cancelDuplicateDrag(state, options);
|
|
806
|
+
requestEditorSceneFrame(state, 'gizmo-drag-blocked');
|
|
807
|
+
state.status = formatBlockedGizmoTransformStatus(event);
|
|
808
|
+
state.statusTone = 'warning';
|
|
809
|
+
state.statusToneStatus = state.status;
|
|
810
|
+
renderUi();
|
|
740
811
|
},
|
|
741
812
|
onDuplicateDragStart(input) {
|
|
742
813
|
return beginDuplicateDrag(state, options, input);
|
|
@@ -755,15 +826,15 @@ async function createEditorWorld(state, options, render) {
|
|
|
755
826
|
isOperationBlocked: () => state.gizmo?.getState().dragPhase === 'dragging',
|
|
756
827
|
onSelectionCommand(command) {
|
|
757
828
|
if (dispatchSelectionCommand(state, options, command))
|
|
758
|
-
|
|
829
|
+
renderUi();
|
|
759
830
|
},
|
|
760
831
|
onFocusIntent(nodeId) {
|
|
761
832
|
if (focusProjectionNode(state, nodeId))
|
|
762
|
-
|
|
833
|
+
renderUi();
|
|
763
834
|
},
|
|
764
835
|
onBoxSelectionChange(box) {
|
|
765
836
|
state.boxSelection = box;
|
|
766
|
-
|
|
837
|
+
renderUi();
|
|
767
838
|
},
|
|
768
839
|
});
|
|
769
840
|
const sceneViewMeasurement = createBabylonSceneViewMeasurementController({
|
|
@@ -779,9 +850,10 @@ async function createEditorWorld(state, options, render) {
|
|
|
779
850
|
isPlacementCandidate: () => isPlacementArmed(state),
|
|
780
851
|
isMeasurementCandidate: () => state.viewportTools.activeUtilityTool === 'measure-distance',
|
|
781
852
|
onPointerIntentStart(event) {
|
|
853
|
+
sceneViewInteraction.beginPointerIntent(event.state.intent);
|
|
782
854
|
if (event.state.intent === 'measurement') {
|
|
783
855
|
if (handleViewportMeasurementStart(state, event.originalEvent))
|
|
784
|
-
|
|
856
|
+
renderUi();
|
|
785
857
|
return;
|
|
786
858
|
}
|
|
787
859
|
if (event.state.intent === 'gizmo-drag') {
|
|
@@ -790,88 +862,104 @@ async function createEditorWorld(state, options, render) {
|
|
|
790
862
|
}
|
|
791
863
|
if (event.state.intent === 'placement') {
|
|
792
864
|
if (previewArmedPlacement(state, event.originalEvent))
|
|
793
|
-
|
|
865
|
+
renderUi();
|
|
794
866
|
return;
|
|
795
867
|
}
|
|
796
868
|
if (event.state.intent === 'view-plane-move') {
|
|
797
869
|
if (gizmo.beginViewPlaneMove(event.originalEvent))
|
|
798
|
-
|
|
870
|
+
renderUi();
|
|
799
871
|
return;
|
|
800
872
|
}
|
|
801
873
|
if (event.state.intent === 'selection-click' || event.state.intent === 'box-select') {
|
|
802
874
|
selectionController.beginPointerSelection(event.originalEvent);
|
|
875
|
+
requestEditorSceneFrame(state, `pointer-${event.state.intent}-start`);
|
|
803
876
|
}
|
|
804
877
|
},
|
|
805
878
|
onPointerIntentMove(event) {
|
|
806
879
|
if (event.state.intent === 'measurement') {
|
|
807
880
|
if (handleViewportMeasurementMove(state, event.originalEvent))
|
|
808
|
-
|
|
881
|
+
renderUi();
|
|
809
882
|
return;
|
|
810
883
|
}
|
|
811
884
|
if (event.state.intent === 'placement') {
|
|
812
885
|
if (previewArmedPlacement(state, event.originalEvent))
|
|
813
|
-
|
|
886
|
+
renderUi();
|
|
814
887
|
return;
|
|
815
888
|
}
|
|
816
889
|
if (event.state.intent === 'view-plane-move') {
|
|
817
890
|
if (gizmo.updateViewPlaneMove(event.originalEvent))
|
|
818
|
-
|
|
891
|
+
renderUi();
|
|
819
892
|
return;
|
|
820
893
|
}
|
|
821
|
-
if (!state.
|
|
822
|
-
|
|
894
|
+
if (!state.mainCameraPreviewEnabled && state.sceneViewCamera?.handlePointerIntentMove(event)) {
|
|
895
|
+
renderUi();
|
|
823
896
|
return;
|
|
824
897
|
}
|
|
825
898
|
if (event.state.intent === 'selection-click' || event.state.intent === 'box-select') {
|
|
826
899
|
selectionController.updatePointerSelection(event.originalEvent, event.state.intent);
|
|
827
900
|
}
|
|
901
|
+
requestEditorSceneFrame(state, `pointer-${event.state.intent}`);
|
|
828
902
|
},
|
|
829
903
|
onPointerIntentEnd(event) {
|
|
904
|
+
sceneViewInteraction.endPointerIntent(event.state.intent);
|
|
830
905
|
if (event.state.intent === 'measurement') {
|
|
831
906
|
if (handleViewportMeasurementEnd(state))
|
|
832
|
-
|
|
907
|
+
renderUi();
|
|
833
908
|
return;
|
|
834
909
|
}
|
|
835
910
|
if (event.state.intent === 'placement') {
|
|
836
911
|
if (commitArmedPlacement(state, options, event.originalEvent))
|
|
837
|
-
|
|
912
|
+
renderUi();
|
|
838
913
|
return;
|
|
839
914
|
}
|
|
840
915
|
if (event.state.intent === 'view-plane-move') {
|
|
841
|
-
if (gizmo.endViewPlaneMove(event.originalEvent))
|
|
842
|
-
|
|
916
|
+
if (gizmo.endViewPlaneMove(event.originalEvent)) {
|
|
917
|
+
requestEditorSceneFrame(state, 'view-plane-move-end');
|
|
918
|
+
renderUi();
|
|
919
|
+
}
|
|
843
920
|
return;
|
|
844
921
|
}
|
|
845
922
|
if (event.state.intent === 'selection-click' || event.state.intent === 'box-select') {
|
|
846
923
|
selectionController.endPointerSelection(event.originalEvent, event.state.intent);
|
|
847
924
|
}
|
|
925
|
+
requestEditorSceneFrame(state, `pointer-${event.state.intent}-end`);
|
|
848
926
|
},
|
|
849
927
|
onPointerIntentCancel(event) {
|
|
928
|
+
sceneViewInteraction.cancelPointerIntent(event.state.intent);
|
|
850
929
|
if (event.state.intent === 'measurement') {
|
|
851
|
-
|
|
930
|
+
renderUi();
|
|
852
931
|
return;
|
|
853
932
|
}
|
|
854
933
|
if (event.state.intent === 'placement') {
|
|
855
934
|
state.gizmo?.setPlacementMarker(null);
|
|
856
|
-
|
|
935
|
+
requestEditorSceneFrame(state, 'placement-marker-clear');
|
|
936
|
+
renderUi();
|
|
857
937
|
return;
|
|
858
938
|
}
|
|
859
939
|
if (event.state.intent === 'view-plane-move') {
|
|
860
940
|
gizmo.cancelDrag();
|
|
861
|
-
|
|
941
|
+
requestEditorSceneFrame(state, 'view-plane-move-cancel');
|
|
942
|
+
renderUi();
|
|
862
943
|
return;
|
|
863
944
|
}
|
|
864
945
|
if (event.state.intent === 'selection-click' || event.state.intent === 'box-select') {
|
|
865
946
|
selectionController.cancelBoxSelection();
|
|
866
|
-
|
|
947
|
+
renderUi();
|
|
867
948
|
}
|
|
949
|
+
requestEditorSceneFrame(state, `pointer-${event.state.intent}-cancel`);
|
|
868
950
|
},
|
|
869
951
|
onDoubleClick(event) {
|
|
870
952
|
selectionController.handleDoubleClick(event);
|
|
871
953
|
},
|
|
872
954
|
onWheel(event) {
|
|
873
|
-
if (!state.
|
|
874
|
-
|
|
955
|
+
if (!state.mainCameraPreviewEnabled && state.sceneViewCamera?.handleWheel(event)) {
|
|
956
|
+
requestEditorSceneFrame(state, 'camera-wheel');
|
|
957
|
+
renderUi();
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
onMovementKeysChange(event) {
|
|
961
|
+
sceneViewInteraction.updateMovementKeys(event.pressedMovementKeys);
|
|
962
|
+
renderUi();
|
|
875
963
|
},
|
|
876
964
|
});
|
|
877
965
|
const sceneViewCamera = createBabylonSceneViewCameraController({
|
|
@@ -880,7 +968,7 @@ async function createEditorWorld(state, options, render) {
|
|
|
880
968
|
camera: world.camera,
|
|
881
969
|
input: sceneViewInput,
|
|
882
970
|
});
|
|
883
|
-
const
|
|
971
|
+
const mainCameraPreview = createBabylonMainCameraPreviewController({
|
|
884
972
|
babylon,
|
|
885
973
|
scene: world.scene,
|
|
886
974
|
editorCamera: world.camera,
|
|
@@ -896,31 +984,44 @@ async function createEditorWorld(state, options, render) {
|
|
|
896
984
|
projection.syncSelection(selection);
|
|
897
985
|
gizmo.setSelection(selection);
|
|
898
986
|
}
|
|
899
|
-
const resize = () =>
|
|
987
|
+
const resize = () => {
|
|
988
|
+
engine.resize?.();
|
|
989
|
+
viewportRenderCoordinator.requestFrame('resize');
|
|
990
|
+
};
|
|
900
991
|
window.addEventListener('resize', resize);
|
|
901
|
-
engine.runRenderLoop?.(() => world.render());
|
|
902
992
|
state.babylon = babylon;
|
|
903
993
|
state.engine = engine;
|
|
904
994
|
state.world = world;
|
|
905
995
|
state.grid = grid;
|
|
906
996
|
state.projection = projection;
|
|
907
997
|
state.gizmo = gizmo;
|
|
908
|
-
state.
|
|
998
|
+
state.mainCameraPreview = mainCameraPreview;
|
|
909
999
|
state.sceneViewInput = sceneViewInput;
|
|
910
1000
|
state.sceneViewCamera = sceneViewCamera;
|
|
911
1001
|
state.sceneViewMeasurement = sceneViewMeasurement;
|
|
912
1002
|
state.sceneViewSpatialOverlay = sceneViewSpatialOverlay;
|
|
913
1003
|
state.selectionController = selectionController;
|
|
1004
|
+
state.sceneRenderScheduler = sceneRenderScheduler;
|
|
1005
|
+
state.viewportRenderCoordinator = viewportRenderCoordinator;
|
|
1006
|
+
state.sceneFrameStats = sceneRenderScheduler.getStats();
|
|
914
1007
|
state.resizeHandler = resize;
|
|
1008
|
+
await sceneRenderScheduler.waitForNextFrame('editor-world-created');
|
|
915
1009
|
}
|
|
916
1010
|
function disposeEditorWorld(state) {
|
|
1011
|
+
state.sceneViewInteraction?.dispose();
|
|
1012
|
+
state.sceneViewInteraction = null;
|
|
1013
|
+
state.viewportRenderCoordinator?.dispose();
|
|
1014
|
+
state.viewportRenderCoordinator = null;
|
|
1015
|
+
state.sceneRenderScheduler?.dispose();
|
|
1016
|
+
state.sceneRenderScheduler = null;
|
|
1017
|
+
state.sceneFrameStats = null;
|
|
917
1018
|
if (state.resizeHandler) {
|
|
918
1019
|
window.removeEventListener('resize', state.resizeHandler);
|
|
919
1020
|
state.resizeHandler = null;
|
|
920
1021
|
}
|
|
921
|
-
state.
|
|
922
|
-
state.
|
|
923
|
-
state.
|
|
1022
|
+
state.mainCameraPreview?.dispose();
|
|
1023
|
+
state.mainCameraPreview = null;
|
|
1024
|
+
state.mainCameraPreviewEnabled = false;
|
|
924
1025
|
state.sceneViewCamera?.dispose();
|
|
925
1026
|
state.sceneViewCamera = null;
|
|
926
1027
|
state.sceneViewMeasurement?.dispose();
|
|
@@ -944,18 +1045,20 @@ function disposeEditorWorld(state) {
|
|
|
944
1045
|
state.projection = null;
|
|
945
1046
|
state.grid?.dispose();
|
|
946
1047
|
state.grid = null;
|
|
947
|
-
state.engine?.stopRenderLoop?.();
|
|
948
1048
|
state.world?.dispose();
|
|
949
1049
|
state.engine?.dispose?.();
|
|
950
1050
|
state.babylon = null;
|
|
951
1051
|
state.world = null;
|
|
952
1052
|
state.engine = null;
|
|
953
1053
|
}
|
|
954
|
-
|
|
1054
|
+
function requestEditorSceneFrame(state, reason) {
|
|
1055
|
+
state.viewportRenderCoordinator?.requestFrame(reason);
|
|
1056
|
+
}
|
|
1057
|
+
async function runExclusive(state, renderUi, action) {
|
|
955
1058
|
if (state.busy)
|
|
956
1059
|
return;
|
|
957
1060
|
state.busy = true;
|
|
958
|
-
|
|
1061
|
+
renderUi();
|
|
959
1062
|
try {
|
|
960
1063
|
await action();
|
|
961
1064
|
}
|
|
@@ -968,7 +1071,7 @@ async function runExclusive(state, render, action) {
|
|
|
968
1071
|
}
|
|
969
1072
|
finally {
|
|
970
1073
|
state.busy = false;
|
|
971
|
-
|
|
1074
|
+
renderUi();
|
|
972
1075
|
}
|
|
973
1076
|
}
|
|
974
1077
|
function selectItem(state, options, input) {
|
|
@@ -988,9 +1091,92 @@ function selectItem(state, options, input) {
|
|
|
988
1091
|
return false;
|
|
989
1092
|
return dispatchSelectionCommand(state, options, command);
|
|
990
1093
|
}
|
|
1094
|
+
function createBrowserHierarchyContextActions(state, options) {
|
|
1095
|
+
return (options.hierarchy?.contextActions ?? []).map((registration) => ({
|
|
1096
|
+
id: registration.id,
|
|
1097
|
+
label: registration.label,
|
|
1098
|
+
shortcut: registration.shortcut,
|
|
1099
|
+
danger: registration.danger,
|
|
1100
|
+
placement: registration.placement,
|
|
1101
|
+
separatorBefore: registration.separatorBefore,
|
|
1102
|
+
visible: (browserContext) => {
|
|
1103
|
+
const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
|
|
1104
|
+
if (!context)
|
|
1105
|
+
return false;
|
|
1106
|
+
return registration.visible?.(context) ?? true;
|
|
1107
|
+
},
|
|
1108
|
+
disabled: (browserContext) => {
|
|
1109
|
+
const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
|
|
1110
|
+
if (!context)
|
|
1111
|
+
return 'No editable document is loaded.';
|
|
1112
|
+
return registration.disabled?.(context) ?? false;
|
|
1113
|
+
},
|
|
1114
|
+
}));
|
|
1115
|
+
}
|
|
1116
|
+
function createHarnessHierarchyContextActionContext(state, options, browserContext, payload) {
|
|
1117
|
+
const document = state.session?.getState().workingDocument ?? null;
|
|
1118
|
+
if (!document)
|
|
1119
|
+
return null;
|
|
1120
|
+
const activeId = browserContext.activeId;
|
|
1121
|
+
return {
|
|
1122
|
+
document,
|
|
1123
|
+
contextNodeId: browserContext.contextNodeId,
|
|
1124
|
+
targetIds: browserContext.targetIds,
|
|
1125
|
+
activeId,
|
|
1126
|
+
hierarchyItem: browserContext.node,
|
|
1127
|
+
projectionNode: browserContext.contextNodeId
|
|
1128
|
+
? options.documentAdapter.getProjectionNode(document, browserContext.contextNodeId)
|
|
1129
|
+
: null,
|
|
1130
|
+
hostServices: options.hostServices ?? null,
|
|
1131
|
+
payload,
|
|
1132
|
+
browserContext,
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function createBrowserContextForCustomAction(state, options, action) {
|
|
1136
|
+
const document = state.session?.getState().workingDocument ?? null;
|
|
1137
|
+
if (!document)
|
|
1138
|
+
return null;
|
|
1139
|
+
const uiState = createUiState(state, options);
|
|
1140
|
+
const node = action.contextNodeId
|
|
1141
|
+
? uiState.hierarchy.find(item => item.id === action.contextNodeId) ?? null
|
|
1142
|
+
: null;
|
|
1143
|
+
return {
|
|
1144
|
+
state: uiState,
|
|
1145
|
+
menuKind: node ? 'node' : 'blank',
|
|
1146
|
+
node,
|
|
1147
|
+
contextNodeId: action.contextNodeId,
|
|
1148
|
+
targetIds: action.targetIds,
|
|
1149
|
+
activeId: action.activeId,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
function runHierarchyCustomContextAction(state, options, action) {
|
|
1153
|
+
const registration = options.hierarchy?.contextActions?.find(candidate => candidate.id === action.id);
|
|
1154
|
+
if (!registration)
|
|
1155
|
+
return false;
|
|
1156
|
+
const browserContext = createBrowserContextForCustomAction(state, options, action);
|
|
1157
|
+
const context = browserContext
|
|
1158
|
+
? createHarnessHierarchyContextActionContext(state, options, browserContext, action.payload)
|
|
1159
|
+
: null;
|
|
1160
|
+
if (!context)
|
|
1161
|
+
return false;
|
|
1162
|
+
try {
|
|
1163
|
+
return registration.run(context) === true;
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1167
|
+
state.status = `${registration.label} failed`;
|
|
1168
|
+
state.statusTone = 'error';
|
|
1169
|
+
state.statusToneStatus = state.status;
|
|
1170
|
+
state.statusDetails = message;
|
|
1171
|
+
console.error('[LocalEditorHarness] hierarchy context action failed', error);
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
991
1175
|
function handleContextAction(state, options, action) {
|
|
992
1176
|
if (action.region !== 'hierarchy')
|
|
993
1177
|
return false;
|
|
1178
|
+
if (action.action === 'custom')
|
|
1179
|
+
return runHierarchyCustomContextAction(state, options, action);
|
|
994
1180
|
if (action.action === 'focus') {
|
|
995
1181
|
const activeId = action.activeId ?? action.targetIds[action.targetIds.length - 1] ?? null;
|
|
996
1182
|
const selectionChanged = activeId && !state.session?.getState().selection.selectedIds.includes(activeId)
|
|
@@ -1399,6 +1585,16 @@ function syncSelectionToProjection(state, selection) {
|
|
|
1399
1585
|
state.projection?.syncSelection(selection);
|
|
1400
1586
|
state.gizmo?.setSelection(selection);
|
|
1401
1587
|
state.gizmo?.refreshSelection();
|
|
1588
|
+
requestEditorSceneFrame(state, 'projection-selection');
|
|
1589
|
+
}
|
|
1590
|
+
function syncCurrentSelectionToSceneArtifacts(state) {
|
|
1591
|
+
syncSelectionToProjection(state, state.session?.getState().selection ?? { selectedIds: [], activeId: null });
|
|
1592
|
+
}
|
|
1593
|
+
function invalidateEditorScene(state, reason, options) {
|
|
1594
|
+
if (options && state.mainCameraPreviewEnabled) {
|
|
1595
|
+
syncMainCameraPreview(state, options);
|
|
1596
|
+
}
|
|
1597
|
+
state.viewportRenderCoordinator?.invalidateScene(reason);
|
|
1402
1598
|
}
|
|
1403
1599
|
function executeTransformAction(state, options, action) {
|
|
1404
1600
|
if (state.mode !== 'editor' || !state.session)
|
|
@@ -1501,11 +1697,12 @@ function armAssetPlacement(state, options, assetId) {
|
|
|
1501
1697
|
if (state.mode !== 'editor')
|
|
1502
1698
|
return false;
|
|
1503
1699
|
cancelActiveOperation(state);
|
|
1504
|
-
const asset = state
|
|
1700
|
+
const asset = findAssetByResolvedId(state, options, assetId);
|
|
1505
1701
|
if (!asset)
|
|
1506
1702
|
return false;
|
|
1507
1703
|
state.armedPlacement = { assetId, asset };
|
|
1508
1704
|
state.gizmo?.setPlacementMarker(null);
|
|
1705
|
+
requestEditorSceneFrame(state, 'placement-arm-change');
|
|
1509
1706
|
state.status = `Placement armed: ${formatAssetLabel(asset, assetId)} (${state.transformOperationSettings.placementMode})`;
|
|
1510
1707
|
return true;
|
|
1511
1708
|
}
|
|
@@ -1535,6 +1732,7 @@ function commitArmedPlacement(state, options, event) {
|
|
|
1535
1732
|
if (!armed || !hit) {
|
|
1536
1733
|
state.gizmo?.setPlacementMarker(null);
|
|
1537
1734
|
state.status = 'Placement rejected: no hit';
|
|
1735
|
+
requestEditorSceneFrame(state, 'placement-marker-clear');
|
|
1538
1736
|
return true;
|
|
1539
1737
|
}
|
|
1540
1738
|
const patch = options.documentAdapter.createPlacedAssetPatch?.({
|
|
@@ -1584,8 +1782,11 @@ function pickArmedPlacementHit(state, event) {
|
|
|
1584
1782
|
: state.gizmo?.pickPlacementHit(event.clientX, event.clientY, mode) ?? null;
|
|
1585
1783
|
}
|
|
1586
1784
|
function clearArmedPlacement(state) {
|
|
1785
|
+
const hadArmedPlacement = !!state.armedPlacement;
|
|
1587
1786
|
state.armedPlacement = null;
|
|
1588
1787
|
state.gizmo?.setPlacementMarker(null);
|
|
1788
|
+
if (hadArmedPlacement)
|
|
1789
|
+
requestEditorSceneFrame(state, 'placement-clear');
|
|
1589
1790
|
}
|
|
1590
1791
|
function addAssetToDocument(state, options, assetId, createOptions = {}) {
|
|
1591
1792
|
if (state.mode !== 'editor') {
|
|
@@ -1599,7 +1800,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
|
|
|
1599
1800
|
state.status = 'Asset creation rejected: document is not loaded';
|
|
1600
1801
|
return { ok: false, assetId, changed: false, status: state.status, error: 'document_not_loaded' };
|
|
1601
1802
|
}
|
|
1602
|
-
const asset = state
|
|
1803
|
+
const asset = findAssetByResolvedId(state, options, assetId);
|
|
1603
1804
|
if (!asset) {
|
|
1604
1805
|
state.status = `Asset creation rejected: ${assetId} not found`;
|
|
1605
1806
|
return { ok: false, assetId, changed: false, status: state.status, error: 'asset_not_found' };
|
|
@@ -1636,6 +1837,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
|
|
|
1636
1837
|
state.projection?.projectNode(projectedNode);
|
|
1637
1838
|
if (selectionResult)
|
|
1638
1839
|
syncSelectionToProjection(state, selectionResult.selection);
|
|
1840
|
+
invalidateEditorScene(state, 'projection-project-node', options);
|
|
1639
1841
|
}
|
|
1640
1842
|
}
|
|
1641
1843
|
return {
|
|
@@ -1928,6 +2130,22 @@ function restoreBatchTransformPreview(state, targets) {
|
|
|
1928
2130
|
transforms[target.id] = target.before;
|
|
1929
2131
|
state.projection?.setNodeTransformsPreview(transforms);
|
|
1930
2132
|
}
|
|
2133
|
+
function formatBlockedGizmoTransformStatus(event) {
|
|
2134
|
+
const target = event.targetIds.length > 1
|
|
2135
|
+
? `${event.targetIds.length} objects`
|
|
2136
|
+
: event.nodeId ?? event.activeId ?? 'selection';
|
|
2137
|
+
const failed = event.failedTargetId && event.targetIds.length > 1
|
|
2138
|
+
? ` at ${event.failedTargetId}`
|
|
2139
|
+
: '';
|
|
2140
|
+
return `Blocked ${event.tool} ${target}${failed}: ${formatTransformOperationBlockReason(event.reason)}`;
|
|
2141
|
+
}
|
|
2142
|
+
function formatTransformOperationBlockReason(reason) {
|
|
2143
|
+
if (reason === 'non-trs-representable')
|
|
2144
|
+
return 'result cannot be represented as position/rotation/scale';
|
|
2145
|
+
if (reason === 'non-invertible-parent')
|
|
2146
|
+
return 'parent transform cannot be inverted';
|
|
2147
|
+
return 'unsupported transform';
|
|
2148
|
+
}
|
|
1931
2149
|
function cancelActiveGizmoDrag(state) {
|
|
1932
2150
|
state.gizmo?.cancelDrag();
|
|
1933
2151
|
}
|
|
@@ -1976,6 +2194,8 @@ function focusProjectionNode(state, nodeId) {
|
|
|
1976
2194
|
state.status = focused
|
|
1977
2195
|
? `Focused ${nodeId} · ${formatEditorStatusTime(Date.now())}`
|
|
1978
2196
|
: `Focus failed: camera could not frame ${nodeId}`;
|
|
2197
|
+
if (focused)
|
|
2198
|
+
requestEditorSceneFrame(state, 'viewport-focus-selection');
|
|
1979
2199
|
return true;
|
|
1980
2200
|
}
|
|
1981
2201
|
function formatEditorStatusTime(timestamp) {
|
|
@@ -2021,6 +2241,7 @@ function rebuildProjectionFromDocument(state, options, document, selection) {
|
|
|
2021
2241
|
state.projection?.rebuild(options.documentAdapter.getProjectionNodes(document));
|
|
2022
2242
|
const sanitized = sanitizeSelection(state, options, document, selection);
|
|
2023
2243
|
syncSelectionToProjection(state, sanitized ?? selection);
|
|
2244
|
+
invalidateEditorScene(state, 'projection-rebuild', options);
|
|
2024
2245
|
}
|
|
2025
2246
|
function syncProjectionForDispatchResult(state, options, result, changedId) {
|
|
2026
2247
|
if (result.documentChanged && changedId && result.workingDocument) {
|
|
@@ -2036,6 +2257,9 @@ function syncProjectionForDispatchResult(state, options, result, changedId) {
|
|
|
2036
2257
|
const sanitized = result.workingDocument ? sanitizeSelection(state, options, result.workingDocument, selection) : null;
|
|
2037
2258
|
syncSelectionToProjection(state, sanitized ?? selection);
|
|
2038
2259
|
}
|
|
2260
|
+
if (result.documentChanged || result.selectionChanged) {
|
|
2261
|
+
invalidateEditorScene(state, 'projection-dispatch-result', options);
|
|
2262
|
+
}
|
|
2039
2263
|
}
|
|
2040
2264
|
function syncProjectionForChangedIds(state, options, document, changedIds) {
|
|
2041
2265
|
for (const changedId of changedIds) {
|
|
@@ -2045,6 +2269,7 @@ function syncProjectionForChangedIds(state, options, document, changedIds) {
|
|
|
2045
2269
|
}
|
|
2046
2270
|
const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
|
|
2047
2271
|
syncSelectionToProjection(state, selection);
|
|
2272
|
+
invalidateEditorScene(state, 'projection-sync-changed-ids', options);
|
|
2048
2273
|
}
|
|
2049
2274
|
function reprojectProjectionForChangedIds(state, options, document, changedIds) {
|
|
2050
2275
|
for (const changedId of changedIds) {
|
|
@@ -2052,27 +2277,29 @@ function reprojectProjectionForChangedIds(state, options, document, changedIds)
|
|
|
2052
2277
|
if (projectedNode)
|
|
2053
2278
|
state.projection?.projectNode(projectedNode);
|
|
2054
2279
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
state.gizmo?.refreshSelection();
|
|
2280
|
+
syncCurrentSelectionToSceneArtifacts(state);
|
|
2281
|
+
invalidateEditorScene(state, 'projection-reproject-changed-ids', options);
|
|
2058
2282
|
}
|
|
2059
|
-
function
|
|
2283
|
+
function setMainCameraPreviewEnabled(state, options, enabled) {
|
|
2060
2284
|
if (!enabled) {
|
|
2061
|
-
state.
|
|
2062
|
-
state.
|
|
2063
|
-
state.status = '
|
|
2285
|
+
state.mainCameraPreviewEnabled = false;
|
|
2286
|
+
state.mainCameraPreview?.setActive(false);
|
|
2287
|
+
state.status = 'Main Camera preview disabled';
|
|
2288
|
+
requestEditorSceneFrame(state, 'main-camera-preview-toggle');
|
|
2064
2289
|
return true;
|
|
2065
2290
|
}
|
|
2066
|
-
state.
|
|
2067
|
-
if (!
|
|
2068
|
-
state.
|
|
2069
|
-
state.status = '
|
|
2291
|
+
state.mainCameraPreviewEnabled = true;
|
|
2292
|
+
if (!syncMainCameraPreview(state, options)) {
|
|
2293
|
+
state.mainCameraPreviewEnabled = false;
|
|
2294
|
+
state.status = 'Main Camera preview unavailable';
|
|
2070
2295
|
state.statusTone = 'warning';
|
|
2071
2296
|
state.statusToneStatus = state.status;
|
|
2072
2297
|
state.statusDetails = 'The current document did not provide a Main Camera preview rig.';
|
|
2298
|
+
requestEditorSceneFrame(state, 'main-camera-preview-toggle');
|
|
2073
2299
|
return true;
|
|
2074
2300
|
}
|
|
2075
|
-
state.status = '
|
|
2301
|
+
state.status = 'Main Camera preview enabled';
|
|
2302
|
+
requestEditorSceneFrame(state, 'main-camera-preview-toggle');
|
|
2076
2303
|
return true;
|
|
2077
2304
|
}
|
|
2078
2305
|
function setGridVisible(state, visible) {
|
|
@@ -2085,18 +2312,19 @@ function setGridVisible(state, visible) {
|
|
|
2085
2312
|
state.statusTone = 'default';
|
|
2086
2313
|
state.statusToneStatus = state.status;
|
|
2087
2314
|
state.statusDetails = '';
|
|
2315
|
+
requestEditorSceneFrame(state, 'grid-visible-change');
|
|
2088
2316
|
return true;
|
|
2089
2317
|
}
|
|
2090
|
-
function
|
|
2091
|
-
const controller = state.
|
|
2318
|
+
function syncMainCameraPreview(state, options) {
|
|
2319
|
+
const controller = state.mainCameraPreview;
|
|
2092
2320
|
if (!controller)
|
|
2093
2321
|
return false;
|
|
2094
|
-
if (!state.
|
|
2322
|
+
if (!state.mainCameraPreviewEnabled) {
|
|
2095
2323
|
controller.setActive(false);
|
|
2096
2324
|
return false;
|
|
2097
2325
|
}
|
|
2098
2326
|
const document = state.session?.getState().workingDocument ?? null;
|
|
2099
|
-
const rig = document ? options.documentAdapter.
|
|
2327
|
+
const rig = document ? options.documentAdapter.getMainCameraPreviewRig?.(document) ?? null : null;
|
|
2100
2328
|
if (!rig) {
|
|
2101
2329
|
controller.setActive(false);
|
|
2102
2330
|
return false;
|
|
@@ -2115,6 +2343,11 @@ function syncViewportCameraState(state) {
|
|
|
2115
2343
|
};
|
|
2116
2344
|
}
|
|
2117
2345
|
function syncViewportSpatialOverlay(state) {
|
|
2346
|
+
const settings = state.viewportTools.overlay;
|
|
2347
|
+
if (!settings.bounds && !settings.dimensions && !settings.edgeLengths && !settings.anchor) {
|
|
2348
|
+
state.viewportSpatialOverlay = createEmptyEditorViewportSpatialOverlayState();
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2118
2351
|
const overlay = state.sceneViewSpatialOverlay;
|
|
2119
2352
|
const selection = state.session?.getState().selection;
|
|
2120
2353
|
const activeId = selection?.activeId ?? null;
|
|
@@ -2128,7 +2361,7 @@ function syncViewportSpatialOverlay(state) {
|
|
|
2128
2361
|
nodeId: activeId,
|
|
2129
2362
|
bounds,
|
|
2130
2363
|
anchor,
|
|
2131
|
-
settings
|
|
2364
|
+
settings,
|
|
2132
2365
|
});
|
|
2133
2366
|
}
|
|
2134
2367
|
function syncViewportMeasurementState(state) {
|
|
@@ -2138,9 +2371,9 @@ function syncViewportMeasurementState(state) {
|
|
|
2138
2371
|
if (measurement)
|
|
2139
2372
|
state.viewportMeasurement = measurement;
|
|
2140
2373
|
}
|
|
2141
|
-
function
|
|
2374
|
+
function hasMainCameraPreviewRig(state, options) {
|
|
2142
2375
|
const document = state.session?.getState().workingDocument ?? null;
|
|
2143
|
-
return !!document && !!options.documentAdapter.
|
|
2376
|
+
return !!document && !!options.documentAdapter.getMainCameraPreviewRig?.(document);
|
|
2144
2377
|
}
|
|
2145
2378
|
function createUiState(state, options) {
|
|
2146
2379
|
const sessionState = state.session?.getState();
|
|
@@ -2165,6 +2398,8 @@ function createUiState(state, options) {
|
|
|
2165
2398
|
const inspectorMultiObject = document && inspectorMultiObjectBase
|
|
2166
2399
|
? withRuntimeInspectorSections(state, options, document, inspectorMultiObjectBase)
|
|
2167
2400
|
: inspectorMultiObjectBase;
|
|
2401
|
+
const assets = dedupeLocalEditorBrowserAssetItems(state.assets
|
|
2402
|
+
.map(asset => toBrowserAssetItem(options, asset)));
|
|
2168
2403
|
return {
|
|
2169
2404
|
mode: state.mode,
|
|
2170
2405
|
busy: state.busy,
|
|
@@ -2173,10 +2408,8 @@ function createUiState(state, options) {
|
|
|
2173
2408
|
statusDetails: state.statusToneStatus === state.status ? state.statusDetails : '',
|
|
2174
2409
|
summary: state.summary,
|
|
2175
2410
|
assetFilter: state.assetFilter,
|
|
2176
|
-
assets
|
|
2177
|
-
|
|
2178
|
-
.map(asset => toBrowserAssetItem(options, asset)),
|
|
2179
|
-
assetCountLabel: `${state.assets.length} assets`,
|
|
2411
|
+
assets,
|
|
2412
|
+
assetCountLabel: `${assets.length} assets`,
|
|
2180
2413
|
hierarchy: document ? options.documentAdapter.getHierarchyItems(document) : [],
|
|
2181
2414
|
selectedIds,
|
|
2182
2415
|
activeId,
|
|
@@ -2209,9 +2442,12 @@ function createUiState(state, options) {
|
|
|
2209
2442
|
viewportTools: cloneEditorViewportToolState(state.viewportTools),
|
|
2210
2443
|
viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
|
|
2211
2444
|
viewportSpatialOverlay: cloneEditorViewportSpatialOverlayState(state.viewportSpatialOverlay),
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2445
|
+
sceneFrameStats: state.sceneFrameStats
|
|
2446
|
+
? { ...state.sceneFrameStats, activeReasons: [...state.sceneFrameStats.activeReasons] }
|
|
2447
|
+
: null,
|
|
2448
|
+
mainCameraPreview: {
|
|
2449
|
+
enabled: state.mainCameraPreviewEnabled,
|
|
2450
|
+
available: hasMainCameraPreviewRig(state, options),
|
|
2215
2451
|
},
|
|
2216
2452
|
grid: {
|
|
2217
2453
|
visible: state.gridVisible,
|
|
@@ -2242,6 +2478,8 @@ function createSceneViewCoordinateAxesState(state) {
|
|
|
2242
2478
|
if (!right || !up || !forward)
|
|
2243
2479
|
return null;
|
|
2244
2480
|
return {
|
|
2481
|
+
projectionMode: readSceneViewFreeCameraProjectionMode(state, camera),
|
|
2482
|
+
projectionToggleDisabled: state.mainCameraPreviewEnabled,
|
|
2245
2483
|
axes: [
|
|
2246
2484
|
createSceneViewCoordinateAxis('x', 'X', '#ff4b70', { x: 1, y: 0, z: 0 }, right, up, forward),
|
|
2247
2485
|
createSceneViewCoordinateAxis('y', 'Y', '#75ff42', { x: 0, y: 1, z: 0 }, right, up, forward),
|
|
@@ -2249,6 +2487,14 @@ function createSceneViewCoordinateAxesState(state) {
|
|
|
2249
2487
|
],
|
|
2250
2488
|
};
|
|
2251
2489
|
}
|
|
2490
|
+
function readSceneViewFreeCameraProjectionMode(state, camera) {
|
|
2491
|
+
return state.sceneViewCamera?.getState().projectionMode
|
|
2492
|
+
?? readSceneViewCameraProjectionMode(state, state.world?.camera ?? camera);
|
|
2493
|
+
}
|
|
2494
|
+
function readSceneViewCameraProjectionMode(state, camera) {
|
|
2495
|
+
const orthoMode = state.babylon?.Camera?.ORTHOGRAPHIC_CAMERA ?? 1;
|
|
2496
|
+
return camera?.mode === orthoMode ? 'orthographic' : 'perspective';
|
|
2497
|
+
}
|
|
2252
2498
|
function createSceneViewCoordinateAxis(id, label, color, worldAxis, cameraRight, cameraUp, cameraForward) {
|
|
2253
2499
|
const screenX = dotSceneViewAxisVec3(worldAxis, cameraRight);
|
|
2254
2500
|
const screenY = -dotSceneViewAxisVec3(worldAxis, cameraUp);
|
|
@@ -2382,9 +2628,10 @@ function createDefaultRuntimeInspectorSections(context) {
|
|
|
2382
2628
|
const properties = [];
|
|
2383
2629
|
if (projectionNode) {
|
|
2384
2630
|
properties.push(createRuntimeInspectorProperty('runtime.projection.nodeId', 'Projected ID', projectionNode.id, properties.length));
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2631
|
+
const projectionAsset = projectionNode.asset;
|
|
2632
|
+
const assetId = projectionAsset?.assetId ?? projectionAsset?.id ?? '';
|
|
2633
|
+
if (assetId)
|
|
2634
|
+
properties.push(createRuntimeInspectorProperty('runtime.projection.assetId', 'Asset ID', assetId, properties.length));
|
|
2388
2635
|
}
|
|
2389
2636
|
const runtimeClass = readRuntimeClassName(root);
|
|
2390
2637
|
if (runtimeClass)
|
|
@@ -2501,15 +2748,114 @@ function isNodeSelectableInDocument(options, document, id) {
|
|
|
2501
2748
|
}
|
|
2502
2749
|
function resolveAssetId(options, asset) {
|
|
2503
2750
|
return options.worldAdapter.resolveAssetId?.(asset)
|
|
2751
|
+
?? asset.assetId
|
|
2504
2752
|
?? asset.id;
|
|
2505
2753
|
}
|
|
2754
|
+
function findAssetByResolvedId(state, options, assetId) {
|
|
2755
|
+
let selected = null;
|
|
2756
|
+
for (const asset of state.assets) {
|
|
2757
|
+
if (resolveAssetId(options, asset) !== assetId)
|
|
2758
|
+
continue;
|
|
2759
|
+
const item = toBrowserAssetItem(options, asset);
|
|
2760
|
+
if (!selected || shouldReplaceBrowserAssetItem(selected.item, item)) {
|
|
2761
|
+
selected = { asset, item };
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
return selected?.asset;
|
|
2765
|
+
}
|
|
2506
2766
|
function toBrowserAssetItem(options, asset) {
|
|
2507
2767
|
return options.worldAdapter.toBrowserAssetItem?.(asset)
|
|
2508
2768
|
?? {
|
|
2509
|
-
id: asset.id,
|
|
2510
|
-
label: asset.label,
|
|
2769
|
+
id: asset.assetId ?? asset.id,
|
|
2770
|
+
label: asset.displayName ?? asset.label,
|
|
2771
|
+
guid: asset.guid,
|
|
2772
|
+
assetId: asset.assetId,
|
|
2773
|
+
kind: asset.kind,
|
|
2774
|
+
external: asset.external,
|
|
2775
|
+
origin: asset.origin,
|
|
2776
|
+
dedupeKey: asset.dedupeKey,
|
|
2777
|
+
placeable: asset.placeable,
|
|
2511
2778
|
meta: asset.meta,
|
|
2512
|
-
disabled: asset.placeable === false,
|
|
2779
|
+
disabled: asset.disabled ?? asset.placeable === false,
|
|
2513
2780
|
};
|
|
2514
2781
|
}
|
|
2782
|
+
export function dedupeLocalEditorBrowserAssetItems(items) {
|
|
2783
|
+
const byKey = new Map();
|
|
2784
|
+
for (const item of items) {
|
|
2785
|
+
const key = getBrowserAssetCanonicalKey(item);
|
|
2786
|
+
const existing = byKey.get(key);
|
|
2787
|
+
if (!existing || shouldReplaceBrowserAssetItem(existing, item)) {
|
|
2788
|
+
byKey.set(key, item);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
const projectDuplicateKeys = new Set();
|
|
2792
|
+
for (const item of byKey.values()) {
|
|
2793
|
+
if (!item.guid || item.origin !== 'project')
|
|
2794
|
+
continue;
|
|
2795
|
+
for (const duplicateKey of getBrowserAssetDuplicateSuppressionKeys(item)) {
|
|
2796
|
+
projectDuplicateKeys.add(duplicateKey);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return [...byKey.values()].filter((item) => {
|
|
2800
|
+
if (item.guid)
|
|
2801
|
+
return true;
|
|
2802
|
+
return !getBrowserAssetDuplicateSuppressionKeys(item)
|
|
2803
|
+
.some(duplicateKey => projectDuplicateKeys.has(duplicateKey));
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
function getBrowserAssetCanonicalKey(item) {
|
|
2807
|
+
const guid = normalizeBrowserAssetString(item.guid);
|
|
2808
|
+
if (guid)
|
|
2809
|
+
return `guid:${guid}`;
|
|
2810
|
+
const platformAssetId = normalizeBrowserAssetString(item.external?.platformAssetId);
|
|
2811
|
+
if (platformAssetId)
|
|
2812
|
+
return `external:${platformAssetId}`;
|
|
2813
|
+
const dedupeKey = normalizeBrowserAssetString(item.dedupeKey);
|
|
2814
|
+
if (dedupeKey)
|
|
2815
|
+
return `dedupe:${dedupeKey}`;
|
|
2816
|
+
const assetId = normalizeBrowserAssetString(item.assetId);
|
|
2817
|
+
if (assetId)
|
|
2818
|
+
return `asset:${assetId}`;
|
|
2819
|
+
return `id:${normalizeBrowserAssetString(item.id) ?? item.id}`;
|
|
2820
|
+
}
|
|
2821
|
+
function getBrowserAssetDuplicateSuppressionKeys(item) {
|
|
2822
|
+
const keys = [];
|
|
2823
|
+
pushBrowserAssetDuplicateKey(keys, 'external', item.external?.platformAssetId);
|
|
2824
|
+
pushBrowserAssetDuplicateKey(keys, 'dedupe', item.dedupeKey);
|
|
2825
|
+
pushBrowserAssetDuplicateKey(keys, 'asset', item.assetId);
|
|
2826
|
+
pushBrowserAssetDuplicateKey(keys, 'id', item.id);
|
|
2827
|
+
return keys;
|
|
2828
|
+
}
|
|
2829
|
+
function pushBrowserAssetDuplicateKey(keys, prefix, value) {
|
|
2830
|
+
const normalized = normalizeBrowserAssetString(value);
|
|
2831
|
+
if (normalized)
|
|
2832
|
+
keys.push(`${prefix}:${normalized}`);
|
|
2833
|
+
}
|
|
2834
|
+
function shouldReplaceBrowserAssetItem(current, candidate) {
|
|
2835
|
+
const currentScore = getBrowserAssetPreferenceScore(current);
|
|
2836
|
+
const candidateScore = getBrowserAssetPreferenceScore(candidate);
|
|
2837
|
+
if (candidateScore !== currentScore)
|
|
2838
|
+
return candidateScore > currentScore;
|
|
2839
|
+
return getBrowserAssetDetailScore(candidate) > getBrowserAssetDetailScore(current);
|
|
2840
|
+
}
|
|
2841
|
+
function getBrowserAssetPreferenceScore(item) {
|
|
2842
|
+
return (item.origin === 'project' ? 1000 : 0)
|
|
2843
|
+
+ (item.guid ? 100 : 0)
|
|
2844
|
+
+ (item.assetId ? 40 : 0)
|
|
2845
|
+
+ (item.placeable === false ? 0 : 10)
|
|
2846
|
+
+ (item.disabled ? 0 : 5);
|
|
2847
|
+
}
|
|
2848
|
+
function getBrowserAssetDetailScore(item) {
|
|
2849
|
+
return [
|
|
2850
|
+
item.label,
|
|
2851
|
+
item.meta,
|
|
2852
|
+
item.kind,
|
|
2853
|
+
item.external?.platformAssetId,
|
|
2854
|
+
item.external?.assetPath,
|
|
2855
|
+
item.external?.assetUrl,
|
|
2856
|
+
].filter(value => typeof value === 'string' && value.trim()).length;
|
|
2857
|
+
}
|
|
2858
|
+
function normalizeBrowserAssetString(value) {
|
|
2859
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
2860
|
+
}
|
|
2515
2861
|
//# sourceMappingURL=local-editor-harness.js.map
|