@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.
Files changed (111) hide show
  1. package/dist/local-editor-harness.d.ts +52 -4
  2. package/dist/local-editor-harness.d.ts.map +1 -1
  3. package/dist/local-editor-harness.js +437 -91
  4. package/dist/local-editor-harness.js.map +1 -1
  5. package/dist/local-editor-scene-render-scheduler.d.ts +33 -0
  6. package/dist/local-editor-scene-render-scheduler.d.ts.map +1 -0
  7. package/dist/local-editor-scene-render-scheduler.js +160 -0
  8. package/dist/local-editor-scene-render-scheduler.js.map +1 -0
  9. package/dist/local-editor-scene-view-interaction-runtime.d.ts +22 -0
  10. package/dist/local-editor-scene-view-interaction-runtime.d.ts.map +1 -0
  11. package/dist/local-editor-scene-view-interaction-runtime.js +80 -0
  12. package/dist/local-editor-scene-view-interaction-runtime.js.map +1 -0
  13. package/dist/local-editor-viewport-render-coordinator.d.ts +17 -0
  14. package/dist/local-editor-viewport-render-coordinator.d.ts.map +1 -0
  15. package/dist/local-editor-viewport-render-coordinator.js +102 -0
  16. package/dist/local-editor-viewport-render-coordinator.js.map +1 -0
  17. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -0
  18. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
  19. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +9 -3
  20. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
  21. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
  22. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +2 -0
  23. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
  24. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +1 -0
  25. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  26. package/node_modules/@fps-games/editor-babylon/dist/index.js +1 -0
  27. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  28. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +1 -0
  29. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -1
  30. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +335 -32
  31. package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
  32. package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts +25 -0
  33. package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.d.ts.map +1 -0
  34. package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js +162 -0
  35. package/node_modules/@fps-games/editor-babylon/dist/main-camera-preview.js.map +1 -0
  36. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +28 -1
  37. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  38. package/node_modules/@fps-games/editor-babylon/dist/projection.js +169 -25
  39. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  40. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts +2 -22
  41. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.d.ts.map +1 -1
  42. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js +1 -121
  43. package/node_modules/@fps-games/editor-babylon/dist/scene-camera-preview.js.map +1 -1
  44. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +4 -0
  45. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts.map +1 -1
  46. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js +37 -23
  47. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js.map +1 -1
  48. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts +1 -0
  49. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.d.ts.map +1 -1
  50. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js +100 -8
  51. package/node_modules/@fps-games/editor-babylon/dist/scene-view-input-controller.js.map +1 -1
  52. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts +6 -1
  53. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.d.ts.map +1 -1
  54. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js +172 -106
  55. package/node_modules/@fps-games/editor-babylon/dist/transform-gizmo-controller.js.map +1 -1
  56. package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts +30 -0
  57. package/node_modules/@fps-games/editor-babylon/dist/transform-solver.d.ts.map +1 -0
  58. package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js +120 -0
  59. package/node_modules/@fps-games/editor-babylon/dist/transform-solver.js.map +1 -0
  60. package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +2 -0
  61. package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
  62. package/node_modules/@fps-games/editor-babylon/package.json +4 -4
  63. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts +2 -1
  64. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
  65. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +167 -34
  66. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
  67. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -1
  68. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  69. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +105 -1
  70. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  71. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts +3 -1
  72. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
  73. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +49 -4
  74. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
  75. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts +4 -1
  76. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.d.ts.map +1 -1
  77. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js +12 -3
  78. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-input-router.js.map +1 -1
  79. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +1 -0
  80. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  81. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +96 -14
  82. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  83. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +1 -1
  84. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
  85. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.d.ts.map +1 -1
  86. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js +7 -6
  87. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shortcuts.js.map +1 -1
  88. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-tooltip.js +1 -1
  89. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +59 -2
  90. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  91. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +1 -0
  92. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
  93. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +3 -0
  94. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
  95. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +5 -2
  96. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  97. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +260 -40
  98. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  99. package/node_modules/@fps-games/editor-browser/package.json +2 -2
  100. package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts +7 -0
  101. package/node_modules/@fps-games/editor-core/dist/scene-view-input.d.ts.map +1 -1
  102. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts +22 -0
  103. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.d.ts.map +1 -1
  104. package/node_modules/@fps-games/editor-core/dist/transform-gizmo.js.map +1 -1
  105. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +6 -6
  106. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +3 -3
  107. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
  108. package/node_modules/@fps-games/editor-core/package.json +2 -2
  109. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  110. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  111. package/package.json +6 -6
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, normalizeEditorTransformConstraint, snapEditorTransformSnapshot, } from '@fps-games/editor-core';
2
+ import { createBabylonTransformSolver } from './transform-solver';
2
3
  export function resolveBabylonTransformHandleConstraint(tool, handleKey) {
3
4
  if (tool === 'move') {
4
5
  if (handleKey === 'xPlaneGizmo' || handleKey === 'yPlaneGizmo' || handleKey === 'zPlaneGizmo')
@@ -18,13 +19,10 @@ export function resolveBabylonTransformHandleConstraint(tool, handleKey) {
18
19
  return null;
19
20
  }
20
21
  export function createBabylonTransformGizmoController(options) {
21
- const GizmoManager = options.babylon.GizmoManager;
22
- if (!GizmoManager)
22
+ const GizmoManagerCtor = options.babylon.GizmoManager;
23
+ if (!GizmoManagerCtor)
23
24
  throw new Error('Babylon runtime missing GizmoManager');
24
- const manager = new GizmoManager(options.scene);
25
- manager.usePointerToAttachGizmos = false;
26
- manager.clearGizmoOnEmptyPointerEvent = false;
27
- manager.boundingBoxGizmoEnabled = false;
25
+ const transformSolver = createBabylonTransformSolver({ babylon: options.babylon });
28
26
  let tool = options.initialTool ?? 'select';
29
27
  let space = options.initialSpace ?? 'world';
30
28
  let constraint = normalizeConstraintForTool(tool, 'axis') ?? 'axis';
@@ -40,7 +38,17 @@ export function createBabylonTransformGizmoController(options) {
40
38
  let freeMoveHandleMaterial = null;
41
39
  let placementMarker = null;
42
40
  let placementMarkerMaterial = null;
41
+ let manager = null;
43
42
  const canvas = options.scene.getEngine?.().getRenderingCanvas?.();
43
+ function ensureGizmoManager() {
44
+ if (manager)
45
+ return manager;
46
+ manager = new GizmoManagerCtor(options.scene);
47
+ manager.usePointerToAttachGizmos = false;
48
+ manager.clearGizmoOnEmptyPointerEvent = false;
49
+ manager.boundingBoxGizmoEnabled = false;
50
+ return manager;
51
+ }
44
52
  function activeTransformTool() {
45
53
  return tool === 'select' ? null : tool;
46
54
  }
@@ -116,25 +124,31 @@ export function createBabylonTransformGizmoController(options) {
116
124
  if (!activeConstraint)
117
125
  continue;
118
126
  const behavior = gizmo?.[handleKey]?.dragBehavior;
119
- addObserver(behavior?.onDragStartObservable, () => beginDrag(activeConstraint));
127
+ addObserver(behavior?.onDragStartObservable, () => beginDrag(activeConstraint, handleKey));
120
128
  addObserver(behavior?.onDragObservable, updateDrag);
121
129
  addObserver(behavior?.onDragEndObservable, endDrag);
122
130
  }
123
131
  }
124
132
  function registerDragObservers() {
125
133
  clearDragObservers();
126
- registerDragObserversFor('move', manager.gizmos?.positionGizmo);
127
- registerDragObserversFor('rotate', manager.gizmos?.rotationGizmo);
128
- registerDragObserversFor('scale', manager.gizmos?.scaleGizmo);
134
+ const activeManager = manager;
135
+ if (!activeManager)
136
+ return;
137
+ registerDragObserversFor('move', activeManager.gizmos?.positionGizmo);
138
+ registerDragObserversFor('rotate', activeManager.gizmos?.rotationGizmo);
139
+ registerDragObserversFor('scale', activeManager.gizmos?.scaleGizmo);
129
140
  }
130
141
  function applySpacePreference() {
131
- const matchAttachedMesh = space === 'local';
142
+ const activeManager = manager;
143
+ if (!activeManager)
144
+ return;
132
145
  const gizmos = [
133
- manager.gizmos?.positionGizmo,
134
- manager.gizmos?.rotationGizmo,
135
- manager.gizmos?.scaleGizmo,
146
+ { gizmo: activeManager.gizmos?.positionGizmo },
147
+ { gizmo: activeManager.gizmos?.rotationGizmo },
148
+ { gizmo: activeManager.gizmos?.scaleGizmo },
136
149
  ];
137
- for (const gizmo of gizmos) {
150
+ for (const { gizmo } of gizmos) {
151
+ const matchAttachedMesh = shouldMatchAttachedMeshForGizmo();
138
152
  try {
139
153
  if ('updateGizmoRotationToMatchAttachedMesh' in (gizmo ?? {})) {
140
154
  gizmo.updateGizmoRotationToMatchAttachedMesh = matchAttachedMesh;
@@ -159,8 +173,11 @@ export function createBabylonTransformGizmoController(options) {
159
173
  }
160
174
  }
161
175
  }
176
+ function shouldMatchAttachedMeshForGizmo() {
177
+ return space === 'local';
178
+ }
162
179
  function applyHandlePreference() {
163
- const positionGizmo = manager.gizmos?.positionGizmo;
180
+ const positionGizmo = manager?.gizmos?.positionGizmo;
164
181
  if (positionGizmo) {
165
182
  try {
166
183
  positionGizmo.planarGizmoEnabled = tool === 'move';
@@ -169,26 +186,43 @@ export function createBabylonTransformGizmoController(options) {
169
186
  }
170
187
  }
171
188
  function attachCurrentSelection() {
189
+ clearDragObservers();
172
190
  const activeTool = activeTransformTool();
173
- manager.positionGizmoEnabled = activeTool === 'move';
174
- manager.rotationGizmoEnabled = activeTool === 'rotate';
175
- manager.scaleGizmoEnabled = activeTool === 'scale';
191
+ if (!activeTool) {
192
+ if (manager) {
193
+ manager.positionGizmoEnabled = false;
194
+ manager.rotationGizmoEnabled = false;
195
+ manager.scaleGizmoEnabled = false;
196
+ try {
197
+ manager.attachToNode?.(null);
198
+ }
199
+ catch { }
200
+ }
201
+ updateFreeMoveHandle();
202
+ return;
203
+ }
204
+ const activeManager = ensureGizmoManager();
205
+ if (!activeManager) {
206
+ updateFreeMoveHandle();
207
+ return;
208
+ }
209
+ activeManager.positionGizmoEnabled = activeTool === 'move';
210
+ activeManager.rotationGizmoEnabled = activeTool === 'rotate';
211
+ activeManager.scaleGizmoEnabled = activeTool === 'scale';
176
212
  applyHandlePreference();
177
213
  applySpacePreference();
178
214
  registerDragObservers();
179
215
  let target = null;
180
- if (activeTool) {
181
- target = attachNativeTransformProxy(getCurrentTransformTargetIds());
182
- }
216
+ target = attachNativeTransformProxy(getCurrentTransformTargetIds());
183
217
  try {
184
- manager.attachToNode?.(target ?? null);
218
+ activeManager.attachToNode?.(target ?? null);
185
219
  }
186
220
  catch (error) {
187
221
  options.logger?.warn?.('[BabylonTransformGizmoController] failed to attach gizmo', error);
188
222
  }
189
223
  updateFreeMoveHandle();
190
224
  }
191
- function beginDrag(activeConstraint = constraint) {
225
+ function beginDrag(activeConstraint = constraint, handleKey) {
192
226
  if (activeDrag)
193
227
  return;
194
228
  const activeTool = activeTransformTool();
@@ -197,7 +231,7 @@ export function createBabylonTransformGizmoController(options) {
197
231
  const duplicate = pendingDuplicateDrag;
198
232
  pendingDuplicateDrag = false;
199
233
  const targetIds = resolveDragTargetIds(activeTool, getCurrentTransformTargetIds(), activeConstraint, duplicate);
200
- const drag = createActiveDrag(activeTool, targetIds, activeConstraint, duplicate);
234
+ const drag = createActiveDrag(activeTool, targetIds, activeConstraint, duplicate, handleKey);
201
235
  if (!drag)
202
236
  return;
203
237
  activeDrag = drag;
@@ -232,8 +266,11 @@ export function createBabylonTransformGizmoController(options) {
232
266
  const target = attachNativeTransformProxy(targetIds);
233
267
  if (!target)
234
268
  return false;
269
+ const activeManager = ensureGizmoManager();
270
+ if (!activeManager)
271
+ return false;
235
272
  try {
236
- manager.attachToNode?.(target);
273
+ activeManager.attachToNode?.(target);
237
274
  updateFreeMoveHandle();
238
275
  return true;
239
276
  }
@@ -265,7 +302,7 @@ export function createBabylonTransformGizmoController(options) {
265
302
  }
266
303
  : null;
267
304
  }
268
- function createActiveDrag(activeTool, targetIds, activeConstraint, duplicate = false) {
305
+ function createActiveDrag(activeTool, targetIds, activeConstraint, duplicate = false, handleKey) {
269
306
  if (targetIds.length === 0)
270
307
  return null;
271
308
  const beforeTransforms = options.projection.readNodeTransforms(targetIds);
@@ -292,13 +329,19 @@ export function createBabylonTransformGizmoController(options) {
292
329
  before,
293
330
  beforeTransforms,
294
331
  duplicate,
332
+ handleKey,
295
333
  proxyStart: readNativeProxyTransform(pivot),
296
334
  };
297
335
  }
298
336
  function updateDrag() {
299
337
  if (!activeDrag)
300
338
  return;
301
- const currentTransforms = previewBatchTransform(activeDrag);
339
+ const preview = previewBatchTransform(activeDrag);
340
+ if (!preview.ok) {
341
+ blockDrag(activeDrag, preview.reason, preview.failedTargetId);
342
+ return;
343
+ }
344
+ const currentTransforms = preview.transforms;
302
345
  const current = activeDrag.activeId
303
346
  ? currentTransforms[activeDrag.activeId] ?? Object.values(currentTransforms)[0] ?? null
304
347
  : Object.values(currentTransforms)[0] ?? null;
@@ -315,10 +358,32 @@ export function createBabylonTransformGizmoController(options) {
315
358
  const drag = activeDrag;
316
359
  if (!drag)
317
360
  return;
318
- const afterTransforms = previewBatchTransform(drag);
361
+ const preview = previewBatchTransform(drag);
362
+ if (!preview.ok) {
363
+ blockDrag(drag, preview.reason, preview.failedTargetId);
364
+ return;
365
+ }
366
+ activeDrag = null;
367
+ emitTransformCommit(drag, preview.transforms);
368
+ attachCurrentSelection();
369
+ }
370
+ function blockDrag(drag, reason, failedTargetId) {
319
371
  activeDrag = null;
320
- emitTransformCommit(drag, afterTransforms);
372
+ restoreDragPreview(drag);
321
373
  attachCurrentSelection();
374
+ options.onDragBlocked?.({
375
+ ...drag,
376
+ reason,
377
+ failedTargetId,
378
+ });
379
+ }
380
+ function restoreDragPreview(drag) {
381
+ if (drag.targetIds.length > 1) {
382
+ options.projection.setNodeTransformsPreview(drag.beforeTransforms);
383
+ }
384
+ else if (drag.nodeId && drag.before) {
385
+ options.projection.setNodeTransformPreview(drag.nodeId, drag.before);
386
+ }
322
387
  }
323
388
  function emitTransformCommit(drag, afterTransforms) {
324
389
  if (drag.targetIds.length === 1) {
@@ -367,15 +432,15 @@ export function createBabylonTransformGizmoController(options) {
367
432
  scale: { x: 1, y: 1, z: 1 },
368
433
  };
369
434
  if (drag.tool === 'move') {
370
- return previewMoveWithDelta(drag, subtractVec3(pivotTransform.position, proxyStart.position));
435
+ return { ok: true, transforms: previewMoveWithDelta(drag, subtractVec3(pivotTransform.position, proxyStart.position)) };
371
436
  }
372
437
  if (drag.tool === 'rotate') {
373
- return previewRotateWithDelta(drag, subtractVec3(pivotTransform.rotation, proxyStart.rotation));
438
+ return previewRotateWithDelta(drag, resolveRotationDelta(drag, pivotTransform, proxyStart));
374
439
  }
375
440
  if (drag.tool === 'scale') {
376
441
  return previewScaleWithDelta(drag, divideVec3(pivotTransform.scale, proxyStart.scale));
377
442
  }
378
- return {};
443
+ return { ok: true, transforms: {} };
379
444
  }
380
445
  function previewMoveWithDelta(drag, delta) {
381
446
  const transforms = {};
@@ -383,34 +448,54 @@ export function createBabylonTransformGizmoController(options) {
383
448
  const before = drag.beforeTransforms[nodeId];
384
449
  if (!before)
385
450
  continue;
386
- transforms[nodeId] = applySnapToTransform(drag, nodeId, translateTransform(before, delta));
451
+ const result = transformSolver.solveMove({ before, delta });
452
+ if (!result.ok)
453
+ continue;
454
+ transforms[nodeId] = applySnapToTransform(drag, nodeId, result.transform);
387
455
  }
388
456
  options.projection.setNodeTransformsPreview(transforms);
389
457
  return transforms;
390
458
  }
391
- function translateTransform(transform, delta) {
392
- return {
393
- position: addVec3(transform.position, delta),
394
- rotation: transform.rotation,
395
- scale: transform.scale,
396
- };
397
- }
398
459
  function previewRotateWithDelta(drag, rotationDelta) {
399
460
  const transforms = {};
400
461
  for (const nodeId of drag.targetIds) {
401
462
  const before = drag.beforeTransforms[nodeId];
402
463
  if (!before)
403
464
  continue;
404
- const offset = subtractVec3(before.position, drag.pivot.position);
405
- transforms[nodeId] = {
406
- position: addVec3(drag.pivot.position, rotateVec3Euler(offset, rotationDelta)),
407
- rotation: addVec3(before.rotation, rotationDelta),
408
- scale: before.scale,
409
- };
410
- transforms[nodeId] = applySnapToTransform(drag, nodeId, transforms[nodeId]);
465
+ const result = transformSolver.solveRotate({
466
+ before,
467
+ pivot: drag.pivot.position,
468
+ delta: rotationDelta,
469
+ space: drag.space,
470
+ });
471
+ if (!result.ok) {
472
+ return { ok: false, reason: result.reason, failedTargetId: nodeId };
473
+ }
474
+ transforms[nodeId] = applySnapToTransform(drag, nodeId, result.transform);
411
475
  }
412
476
  options.projection.setNodeTransformsPreview(transforms);
413
- return transforms;
477
+ return { ok: true, transforms };
478
+ }
479
+ function resolveRotationDelta(drag, pivotTransform, proxyStart) {
480
+ const angleDelta = readRotationHandleAngle(drag.handleKey);
481
+ if (angleDelta != null)
482
+ return rotationDeltaFromHandleAngle(drag.handleKey, angleDelta);
483
+ return subtractVec3(pivotTransform.rotation, proxyStart.rotation);
484
+ }
485
+ function readRotationHandleAngle(handleKey) {
486
+ if (handleKey !== 'xGizmo' && handleKey !== 'yGizmo' && handleKey !== 'zGizmo')
487
+ return null;
488
+ const angle = manager?.gizmos?.rotationGizmo?.[handleKey]?.angle;
489
+ return Number.isFinite(angle) ? Number(angle) : null;
490
+ }
491
+ function rotationDeltaFromHandleAngle(handleKey, angle) {
492
+ if (handleKey === 'xGizmo')
493
+ return { x: angle, y: 0, z: 0 };
494
+ if (handleKey === 'yGizmo')
495
+ return { x: 0, y: angle, z: 0 };
496
+ if (handleKey === 'zGizmo')
497
+ return { x: 0, y: 0, z: angle };
498
+ return { x: 0, y: 0, z: 0 };
414
499
  }
415
500
  function previewScaleWithDelta(drag, scaleDelta) {
416
501
  const transforms = {};
@@ -423,16 +508,19 @@ export function createBabylonTransformGizmoController(options) {
423
508
  const before = drag.beforeTransforms[nodeId];
424
509
  if (!before)
425
510
  continue;
426
- const offset = subtractVec3(before.position, drag.pivot.position);
427
- transforms[nodeId] = {
428
- position: addVec3(drag.pivot.position, multiplyVec3(offset, safeScale)),
429
- rotation: before.rotation,
430
- scale: multiplyVec3(before.scale, safeScale),
431
- };
432
- transforms[nodeId] = applySnapToTransform(drag, nodeId, transforms[nodeId]);
511
+ const result = transformSolver.solveScale({
512
+ before,
513
+ pivot: drag.pivot.position,
514
+ delta: safeScale,
515
+ space: drag.space,
516
+ });
517
+ if (!result.ok) {
518
+ return { ok: false, reason: result.reason, failedTargetId: nodeId };
519
+ }
520
+ transforms[nodeId] = applySnapToTransform(drag, nodeId, result.transform);
433
521
  }
434
522
  options.projection.setNodeTransformsPreview(transforms);
435
- return transforms;
523
+ return { ok: true, transforms };
436
524
  }
437
525
  function applySnapToTransform(drag, nodeId, after) {
438
526
  if (!operationSettings.snap.enabled)
@@ -490,13 +578,6 @@ export function createBabylonTransformGizmoController(options) {
490
578
  function dotVec3(left, right) {
491
579
  return left.x * right.x + left.y * right.y + left.z * right.z;
492
580
  }
493
- function multiplyVec3(left, right) {
494
- return {
495
- x: left.x * right.x,
496
- y: left.y * right.y,
497
- z: left.z * right.z,
498
- };
499
- }
500
581
  function divideVec3(left, right) {
501
582
  return {
502
583
  x: Math.abs(right.x) > 0.000001 ? left.x / right.x : 1,
@@ -504,36 +585,6 @@ export function createBabylonTransformGizmoController(options) {
504
585
  z: Math.abs(right.z) > 0.000001 ? left.z / right.z : 1,
505
586
  };
506
587
  }
507
- function rotateVec3Euler(value, rotation) {
508
- return rotateZ(rotateY(rotateX(value, rotation.x), rotation.y), rotation.z);
509
- }
510
- function rotateX(value, angle) {
511
- const cos = Math.cos(angle);
512
- const sin = Math.sin(angle);
513
- return {
514
- x: value.x,
515
- y: value.y * cos - value.z * sin,
516
- z: value.y * sin + value.z * cos,
517
- };
518
- }
519
- function rotateY(value, angle) {
520
- const cos = Math.cos(angle);
521
- const sin = Math.sin(angle);
522
- return {
523
- x: value.x * cos + value.z * sin,
524
- y: value.y,
525
- z: -value.x * sin + value.z * cos,
526
- };
527
- }
528
- function rotateZ(value, angle) {
529
- const cos = Math.cos(angle);
530
- const sin = Math.sin(angle);
531
- return {
532
- x: value.x * cos - value.y * sin,
533
- y: value.x * sin + value.y * cos,
534
- z: value.z,
535
- };
536
- }
537
588
  function isViewPlaneMoveCandidate(event) {
538
589
  if (disposed || activeDrag || tool !== 'move' || event.button !== 0)
539
590
  return false;
@@ -720,7 +771,8 @@ export function createBabylonTransformGizmoController(options) {
720
771
  const MeshBuilder = options.babylon.MeshBuilder;
721
772
  const StandardMaterial = options.babylon.StandardMaterial;
722
773
  const Color3 = options.babylon.Color3;
723
- const utilityScene = manager.utilityLayer?.utilityLayerScene;
774
+ const activeManager = ensureGizmoManager();
775
+ const utilityScene = activeManager?.utilityLayer?.utilityLayerScene;
724
776
  if (!MeshBuilder?.CreateSphere || !utilityScene)
725
777
  return null;
726
778
  placementMarker = MeshBuilder.CreateSphere('editor.placement.marker', { diameter: 0.32, segments: 16 }, utilityScene);
@@ -767,7 +819,7 @@ export function createBabylonTransformGizmoController(options) {
767
819
  const rect = canvas.getBoundingClientRect();
768
820
  const x = clientX - rect.left;
769
821
  const y = clientY - rect.top;
770
- const utilityScene = manager.utilityLayer?.utilityLayerScene;
822
+ const utilityScene = manager?.utilityLayer?.utilityLayerScene;
771
823
  const utilityPick = typeof utilityScene?.pick === 'function' ? utilityScene.pick(x, y) : null;
772
824
  if (utilityPick?.hit && utilityPick.pickedMesh)
773
825
  return utilityPick.pickedMesh;
@@ -780,7 +832,7 @@ export function createBabylonTransformGizmoController(options) {
780
832
  const rect = canvas.getBoundingClientRect();
781
833
  const x = clientX - rect.left;
782
834
  const y = clientY - rect.top;
783
- const utilityScene = manager.utilityLayer?.utilityLayerScene;
835
+ const utilityScene = manager?.utilityLayer?.utilityLayerScene;
784
836
  const pick = typeof utilityScene?.pick === 'function'
785
837
  ? utilityScene.pick(x, y, (mesh) => isFreeMoveHandleNode(mesh))
786
838
  : null;
@@ -792,7 +844,8 @@ export function createBabylonTransformGizmoController(options) {
792
844
  const MeshBuilder = options.babylon.MeshBuilder;
793
845
  const StandardMaterial = options.babylon.StandardMaterial;
794
846
  const Color3 = options.babylon.Color3;
795
- const utilityScene = manager.utilityLayer?.utilityLayerScene;
847
+ const activeManager = ensureGizmoManager();
848
+ const utilityScene = activeManager?.utilityLayer?.utilityLayerScene;
796
849
  if (!MeshBuilder?.CreateSphere || !utilityScene)
797
850
  return null;
798
851
  freeMoveHandle = MeshBuilder.CreateSphere('editor.transform.freeMoveHandle', { diameter: 0.22, segments: 12 }, utilityScene);
@@ -880,13 +933,16 @@ export function createBabylonTransformGizmoController(options) {
880
933
  }
881
934
  function collectCurrentGizmoRoots() {
882
935
  const roots = new Set();
936
+ const activeManager = manager;
937
+ if (!activeManager)
938
+ return roots;
883
939
  const activeTool = activeTransformTool();
884
940
  const gizmo = activeTool === 'move'
885
- ? manager.gizmos?.positionGizmo
941
+ ? activeManager.gizmos?.positionGizmo
886
942
  : activeTool === 'rotate'
887
- ? manager.gizmos?.rotationGizmo
943
+ ? activeManager.gizmos?.rotationGizmo
888
944
  : activeTool === 'scale'
889
- ? manager.gizmos?.scaleGizmo
945
+ ? activeManager.gizmos?.scaleGizmo
890
946
  : null;
891
947
  collectGizmoRoots(gizmo, roots);
892
948
  return roots;
@@ -919,8 +975,11 @@ export function createBabylonTransformGizmoController(options) {
919
975
  setTool(nextTool) {
920
976
  if (disposed)
921
977
  return;
922
- if (tool === nextTool)
978
+ if (tool === nextTool) {
979
+ if (!activeDrag)
980
+ attachCurrentSelection();
923
981
  return;
982
+ }
924
983
  controller.cancelDrag();
925
984
  tool = nextTool;
926
985
  constraint = normalizeConstraintForTool(tool, constraint) ?? 'axis';
@@ -929,8 +988,11 @@ export function createBabylonTransformGizmoController(options) {
929
988
  setSpace(nextSpace) {
930
989
  if (disposed)
931
990
  return;
932
- if (space === nextSpace)
991
+ if (space === nextSpace) {
992
+ if (!activeDrag)
993
+ attachCurrentSelection();
933
994
  return;
995
+ }
934
996
  controller.cancelDrag();
935
997
  space = nextSpace;
936
998
  attachCurrentSelection();
@@ -939,8 +1001,11 @@ export function createBabylonTransformGizmoController(options) {
939
1001
  if (disposed)
940
1002
  return;
941
1003
  const normalized = normalizeConstraintForTool(tool, nextConstraint) ?? 'axis';
942
- if (constraint === normalized)
1004
+ if (constraint === normalized) {
1005
+ if (!activeDrag)
1006
+ attachCurrentSelection();
943
1007
  return;
1008
+ }
944
1009
  controller.cancelDrag();
945
1010
  constraint = normalized;
946
1011
  attachCurrentSelection();
@@ -1016,11 +1081,11 @@ export function createBabylonTransformGizmoController(options) {
1016
1081
  controller.cancelDrag();
1017
1082
  clearDragObservers();
1018
1083
  try {
1019
- manager.attachToNode?.(null);
1084
+ manager?.attachToNode?.(null);
1020
1085
  }
1021
1086
  catch { }
1022
1087
  try {
1023
- manager.dispose?.();
1088
+ manager?.dispose?.();
1024
1089
  }
1025
1090
  catch { }
1026
1091
  try {
@@ -1048,6 +1113,7 @@ export function createBabylonTransformGizmoController(options) {
1048
1113
  freeMoveHandleMaterial = null;
1049
1114
  placementMarker = null;
1050
1115
  placementMarkerMaterial = null;
1116
+ manager = null;
1051
1117
  disposed = true;
1052
1118
  },
1053
1119
  };