3dtiles-inspector 0.2.2 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "3dtiles-inspector",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Inspect, align, and save local 3D Tiles root transforms in an interactive browser session.",
5
5
  "author": "William Liu <lyz15972107087@gmail.com>",
6
6
  "license": "Apache-2.0",
@@ -147,11 +147,120 @@ function buildViewerHtml(viewerConfig) {
147
147
  .screen-selection-rect {
148
148
  position: absolute;
149
149
  border: 1px solid #ffcf33;
150
- background: rgba(255, 207, 51, 0.14);
150
+ background: rgba(255, 207, 51, 0.06);
151
151
  box-shadow:
152
152
  0 8px 24px rgba(120, 82, 0, 0.12);
153
153
  }
154
154
 
155
+ .screen-selection-rect.editable {
156
+ border: 0;
157
+ background: transparent;
158
+ box-shadow: none;
159
+ }
160
+
161
+ .screen-selection-edit-svg {
162
+ position: absolute;
163
+ inset: 0;
164
+ display: none;
165
+ overflow: visible;
166
+ }
167
+
168
+ .screen-selection-rect.editable .screen-selection-edit-svg {
169
+ display: block;
170
+ }
171
+
172
+ .screen-selection-edit-polygon {
173
+ fill: rgba(255, 207, 51, 0.06);
174
+ stroke: #ffcf33;
175
+ stroke-width: 2;
176
+ vector-effect: non-scaling-stroke;
177
+ filter: drop-shadow(0 8px 12px rgba(120, 82, 0, 0.12));
178
+ }
179
+
180
+ .screen-selection-rect.editable.drawing .screen-selection-edit-polygon {
181
+ fill: rgba(255, 207, 51, 0.2);
182
+ }
183
+
184
+ .screen-selection-edit-grid line {
185
+ stroke: rgba(255, 255, 255, 0.72);
186
+ stroke-width: 0.6;
187
+ vector-effect: non-scaling-stroke;
188
+ }
189
+
190
+ .screen-selection-edit-handle {
191
+ position: absolute;
192
+ display: none;
193
+ pointer-events: none;
194
+ transform: translate(-50%, -50%);
195
+ transform-origin: center;
196
+ }
197
+
198
+ .screen-selection-edit-handle::before {
199
+ content: "";
200
+ position: absolute;
201
+ inset: 0;
202
+ display: block;
203
+ box-sizing: border-box;
204
+ transform-origin: center;
205
+ scale: 1;
206
+ transition: scale 80ms ease;
207
+ }
208
+
209
+ .screen-selection-rect.editable .screen-selection-edit-handle {
210
+ display: block;
211
+ }
212
+
213
+ .screen-selection-edit-top-left,
214
+ .screen-selection-edit-top-right,
215
+ .screen-selection-edit-bottom-right,
216
+ .screen-selection-edit-bottom-left {
217
+ width: 10px;
218
+ height: 10px;
219
+ }
220
+
221
+ .screen-selection-edit-top-left::before,
222
+ .screen-selection-edit-top-right::before,
223
+ .screen-selection-edit-bottom-right::before,
224
+ .screen-selection-edit-bottom-left::before {
225
+ border: 2px solid #ffffff;
226
+ border-radius: 50%;
227
+ background: #ffcf33;
228
+ box-shadow: 0 2px 8px rgba(120, 82, 0, 0.28);
229
+ }
230
+
231
+ .screen-selection-edit-top-left.active::before,
232
+ .screen-selection-edit-top-right.active::before,
233
+ .screen-selection-edit-bottom-right.active::before,
234
+ .screen-selection-edit-bottom-left.active::before {
235
+ scale: 1.5;
236
+ }
237
+
238
+ .screen-selection-edit-top,
239
+ .screen-selection-edit-right,
240
+ .screen-selection-edit-bottom,
241
+ .screen-selection-edit-left {
242
+ width: 26px;
243
+ height: 4px;
244
+ }
245
+
246
+ .screen-selection-edit-top::before,
247
+ .screen-selection-edit-right::before,
248
+ .screen-selection-edit-bottom::before,
249
+ .screen-selection-edit-left::before {
250
+ border-radius: 999px;
251
+ background: #ffffff;
252
+ box-shadow: 0 1px 6px rgba(120, 82, 0, 0.24);
253
+ }
254
+
255
+ .screen-selection-edit-top.active::before,
256
+ .screen-selection-edit-right.active::before,
257
+ .screen-selection-edit-bottom.active::before,
258
+ .screen-selection-edit-left.active::before {
259
+ scale:
260
+ var(--screen-selection-edit-active-scale-x, 1.28)
261
+ var(--screen-selection-edit-active-scale-y, 1.5);
262
+ }
263
+
155
264
  .toolbar-dock {
156
265
  position: fixed;
157
266
  top: 14px;
@@ -743,7 +852,7 @@ function buildViewerHtml(viewerConfig) {
743
852
  <p id="crop-count-value" class="toolbar-value">0</p>
744
853
  </div>
745
854
  <div class="coordinate-actions">
746
- <button id="crop-screen-select" class="wide" type="button">Select Region</button>
855
+ <button id="crop-screen-select" class="wide" type="button">Draw Region</button>
747
856
  </div>
748
857
  <div id="crop-list" class="crop-list"></div>
749
858
  <div class="status-actions">
package/src/viewer/app.js CHANGED
@@ -193,7 +193,7 @@ function setSaveUiLocked(locked) {
193
193
  }
194
194
 
195
195
  cameraController.setPointerDownFilter((event) => {
196
- return !(cropController?.getPendingMode() && event.button === 0);
196
+ return !(cropController?.shouldCapturePointerDown(event) ?? false);
197
197
  });
198
198
 
199
199
  const { transformControls, transformControlsHelper } =
@@ -8,12 +8,36 @@ import {
8
8
  disposeScreenSelection,
9
9
  getScreenSelectionFarDepthFromPosition,
10
10
  getScreenSelectionPayload,
11
+ setScreenSelectionShape,
11
12
  setScreenSelectionEditSelection,
12
13
  setScreenSelectionFarDepth,
13
14
  updateScreenSelectionWorldState,
14
15
  } from './index.js';
16
+ import {
17
+ clearOverlay,
18
+ createSelectionData,
19
+ } from './geometry.js';
20
+ import {
21
+ SCREEN_EDIT_CORNER_HIT_SIZE,
22
+ SCREEN_EDIT_CORNER_PARTS,
23
+ SCREEN_EDIT_EDGE_HIT_SIZE,
24
+ SCREEN_EDIT_EDGE_PARTS,
25
+ SCREEN_EDIT_PART_POINT_INDICES,
26
+ clampClientPoints,
27
+ copyClientPoint,
28
+ copyClientPoints,
29
+ createScreenEditOverlay,
30
+ getClientRectPoints,
31
+ getPartPoint,
32
+ isConvexClientQuad,
33
+ pointSegmentDistanceSq,
34
+ } from './editOverlay.js';
15
35
  import { updateCropControls } from '../dom/cropUi.js';
16
36
 
37
+ const CAMERA_POSITION_EPSILON_SQ = 1e-12;
38
+ const CAMERA_QUATERNION_EPSILON = 1e-10;
39
+ const CAMERA_PROJECTION_EPSILON = 1e-10;
40
+
17
41
  export function createCropController({
18
42
  camera,
19
43
  cameraController,
@@ -36,9 +60,13 @@ export function createCropController({
36
60
  let nextSelectionId = 1;
37
61
  let activeSelectionId = null;
38
62
  let pendingMode = false;
63
+ let pendingScreenEdit = null;
64
+ let pendingEditDrag = null;
65
+ let editCursor = '';
39
66
  let hasGaussianSplats = false;
40
67
  const sphere = new Sphere();
41
68
  const cameraForward = new Vector3();
69
+ const screenEditOverlay = createScreenEditOverlay({ overlayEl, rectEl });
42
70
 
43
71
  function getActiveSelection() {
44
72
  if (activeSelectionId == null) {
@@ -57,6 +85,136 @@ export function createCropController({
57
85
  ];
58
86
  }
59
87
 
88
+ function setEditCursor(cursor) {
89
+ const nextCursor = cursor || '';
90
+ if (editCursor === nextCursor) {
91
+ return;
92
+ }
93
+ domElement.style.cursor = nextCursor;
94
+ editCursor = nextCursor;
95
+ }
96
+
97
+ function clearEditCursor() {
98
+ setEditCursor('');
99
+ }
100
+
101
+ function getActivePendingEdit() {
102
+ if (!pendingScreenEdit || activeSelectionId !== pendingScreenEdit.selectionId) {
103
+ return null;
104
+ }
105
+ const match = pendingSelections.find(
106
+ (selection) => selection.id === pendingScreenEdit.selectionId,
107
+ );
108
+ return match ? pendingScreenEdit : null;
109
+ }
110
+
111
+ function clearScreenEditOverlay() {
112
+ screenEditOverlay.clear();
113
+ clearEditCursor();
114
+ }
115
+
116
+ function syncPendingEditOverlay() {
117
+ const edit = getActivePendingEdit();
118
+ if (!edit) {
119
+ clearScreenEditOverlay();
120
+ if (!pendingMode) {
121
+ clearOverlay(overlayEl, rectEl);
122
+ }
123
+ return;
124
+ }
125
+
126
+ screenEditOverlay.render(edit.clientPoints, { showGrid: false });
127
+ }
128
+
129
+ function createCameraPoseSnapshot() {
130
+ camera.updateMatrixWorld(true);
131
+ return {
132
+ position: camera.position.toArray(),
133
+ projectionMatrix: camera.projectionMatrix.toArray(),
134
+ quaternion: camera.quaternion.toArray(),
135
+ };
136
+ }
137
+
138
+ function projectionChanged(source, target) {
139
+ if (!Array.isArray(source) || !Array.isArray(target)) {
140
+ return true;
141
+ }
142
+ return source.some(
143
+ (value, index) =>
144
+ Math.abs(value - target[index]) > CAMERA_PROJECTION_EPSILON,
145
+ );
146
+ }
147
+
148
+ function cameraPoseChanged(cameraPose) {
149
+ if (!cameraPose) {
150
+ return true;
151
+ }
152
+
153
+ camera.updateMatrixWorld(true);
154
+ const dx = camera.position.x - cameraPose.position[0];
155
+ const dy = camera.position.y - cameraPose.position[1];
156
+ const dz = camera.position.z - cameraPose.position[2];
157
+ if (dx * dx + dy * dy + dz * dz > CAMERA_POSITION_EPSILON_SQ) {
158
+ return true;
159
+ }
160
+
161
+ const quaternion = cameraPose.quaternion;
162
+ const quaternionDot = Math.abs(
163
+ camera.quaternion.x * quaternion[0] +
164
+ camera.quaternion.y * quaternion[1] +
165
+ camera.quaternion.z * quaternion[2] +
166
+ camera.quaternion.w * quaternion[3],
167
+ );
168
+ if (1 - Math.min(1, quaternionDot) > CAMERA_QUATERNION_EPSILON) {
169
+ return true;
170
+ }
171
+
172
+ return projectionChanged(
173
+ camera.projectionMatrix.toArray(),
174
+ cameraPose.projectionMatrix,
175
+ );
176
+ }
177
+
178
+ function clearPendingScreenEdit() {
179
+ pendingScreenEdit = null;
180
+ pendingEditDrag = null;
181
+ syncPendingEditOverlay();
182
+ }
183
+
184
+ function freezePendingScreenEdit(showStatus = false) {
185
+ const hadEdit = !!pendingScreenEdit;
186
+ if (!hadEdit) {
187
+ return false;
188
+ }
189
+
190
+ clearPendingScreenEdit();
191
+ if (showStatus) {
192
+ setStatus(
193
+ 'Screen selection shape fixed after camera movement. Drag the 3D far plane, then Confirm or Cancel.',
194
+ );
195
+ }
196
+ return true;
197
+ }
198
+
199
+ function freezePendingScreenEditIfCameraChanged() {
200
+ if (!pendingScreenEdit || pendingEditDrag) {
201
+ return false;
202
+ }
203
+ if (!cameraPoseChanged(pendingScreenEdit.cameraPose)) {
204
+ return false;
205
+ }
206
+ return freezePendingScreenEdit(true);
207
+ }
208
+
209
+ function createPendingScreenEdit(selection, clientRect) {
210
+ pendingScreenEdit = {
211
+ cameraPose: createCameraPoseSnapshot(),
212
+ clientPoints: clampClientPoints(getClientRectPoints(clientRect), domElement),
213
+ selectionId: selection.id,
214
+ };
215
+ syncPendingEditOverlay();
216
+ }
217
+
60
218
  function syncWorldState() {
61
219
  const transform = getCurrentRootTransformArray();
62
220
  getEntries().forEach(({ selection }) => {
@@ -152,11 +310,206 @@ export function createCropController({
152
310
  camera,
153
311
  domElement,
154
312
  getDepthRange,
313
+ onOverlayClear: clearScreenEditOverlay,
314
+ onOverlayUpdate: (clientRect) => {
315
+ screenEditOverlay.render(getClientRectPoints(clientRect), {
316
+ showGrid: true,
317
+ });
318
+ return true;
319
+ },
155
320
  onSelectionCreated: handleSelectionCreated,
156
321
  overlayEl,
157
322
  rectEl,
158
323
  });
159
324
 
325
+ function createEditHit(part) {
326
+ return { cursor: 'grab', part };
327
+ }
328
+
329
+ function getPendingEditHit(event) {
330
+ if (freezePendingScreenEditIfCameraChanged()) {
331
+ return null;
332
+ }
333
+
334
+ const edit = getActivePendingEdit();
335
+ if (!edit) {
336
+ return null;
337
+ }
338
+
339
+ const pointer = { x: event.clientX, y: event.clientY };
340
+ for (const part of SCREEN_EDIT_CORNER_PARTS) {
341
+ const point = getPartPoint(edit.clientPoints, part);
342
+ const dx = pointer.x - point.x;
343
+ const dy = pointer.y - point.y;
344
+ if (dx * dx + dy * dy <= SCREEN_EDIT_CORNER_HIT_SIZE ** 2) {
345
+ return createEditHit(part);
346
+ }
347
+ }
348
+
349
+ for (const part of SCREEN_EDIT_EDGE_PARTS) {
350
+ const [startIndex, endIndex] = SCREEN_EDIT_PART_POINT_INDICES[part];
351
+ if (
352
+ pointSegmentDistanceSq(
353
+ pointer,
354
+ edit.clientPoints[startIndex],
355
+ edit.clientPoints[endIndex],
356
+ ) <=
357
+ SCREEN_EDIT_EDGE_HIT_SIZE ** 2
358
+ ) {
359
+ return createEditHit(part);
360
+ }
361
+ }
362
+
363
+ return null;
364
+ }
365
+
366
+ function getPendingEditDragPoints(event) {
367
+ const { part, startClientX, startClientY, startPoints } = pendingEditDrag;
368
+ const domRect = domElement.getBoundingClientRect();
369
+ const indices = SCREEN_EDIT_PART_POINT_INDICES[part] || [];
370
+ let dx = event.clientX - startClientX;
371
+ let dy = event.clientY - startClientY;
372
+
373
+ indices.forEach((index) => {
374
+ const point = startPoints[index];
375
+ dx = Math.max(dx, domRect.left - point.x);
376
+ dx = Math.min(dx, domRect.right - point.x);
377
+ dy = Math.max(dy, domRect.top - point.y);
378
+ dy = Math.min(dy, domRect.bottom - point.y);
379
+ });
380
+
381
+ return startPoints.map((point, index) =>
382
+ indices.includes(index)
383
+ ? {
384
+ x: point.x + dx,
385
+ y: point.y + dy,
386
+ }
387
+ : copyClientPoint(point),
388
+ );
389
+ }
390
+
391
+ function updatePendingEditSelection(clientPoints) {
392
+ const edit = getActivePendingEdit();
393
+ if (!edit) {
394
+ return false;
395
+ }
396
+
397
+ const match = findSelection(edit.selectionId);
398
+ if (!match) {
399
+ return false;
400
+ }
401
+
402
+ if (!isConvexClientQuad(clientPoints)) {
403
+ return false;
404
+ }
405
+
406
+ const selectionData = createSelectionData({
407
+ camera,
408
+ clientPoints,
409
+ domElement,
410
+ getDepthRange,
411
+ });
412
+ if (!selectionData) {
413
+ return false;
414
+ }
415
+
416
+ edit.clientPoints = copyClientPoints(clientPoints);
417
+ setScreenSelectionShape(
418
+ match.selection,
419
+ selectionData,
420
+ getCurrentRootTransformArray(),
421
+ );
422
+ syncPendingEditOverlay();
423
+ syncTransformControlsState();
424
+ return true;
425
+ }
426
+
427
+ function handlePendingEditPointerDown(event) {
428
+ if (event.button !== 0) {
429
+ return false;
430
+ }
431
+
432
+ const hit = getPendingEditHit(event);
433
+ if (!hit) {
434
+ return false;
435
+ }
436
+
437
+ pendingEditDrag = {
438
+ part: hit.part,
439
+ pointerId: event.pointerId,
440
+ startClientX: event.clientX,
441
+ startClientY: event.clientY,
442
+ startPoints: copyClientPoints(pendingScreenEdit.clientPoints),
443
+ updated: false,
444
+ };
445
+ domElement.setPointerCapture?.(event.pointerId);
446
+ screenEditOverlay.setActivePart(hit.part);
447
+ setEditCursor('grabbing');
448
+ event.preventDefault();
449
+ event.stopPropagation();
450
+ return true;
451
+ }
452
+
453
+ function handlePendingEditPointerMove(event) {
454
+ if (pendingEditDrag) {
455
+ if (event.pointerId !== pendingEditDrag.pointerId) {
456
+ return false;
457
+ }
458
+
459
+ pendingEditDrag.updated =
460
+ updatePendingEditSelection(getPendingEditDragPoints(event)) ||
461
+ pendingEditDrag.updated;
462
+ event.preventDefault();
463
+ event.stopPropagation();
464
+ return true;
465
+ }
466
+
467
+ if (event.buttons) {
468
+ return false;
469
+ }
470
+
471
+ const hit = getPendingEditHit(event);
472
+ screenEditOverlay.setActivePart(hit?.part);
473
+ setEditCursor(hit?.cursor || '');
474
+ return false;
475
+ }
476
+
477
+ function handlePendingEditPointerUp(event) {
478
+ if (!pendingEditDrag || event.pointerId !== pendingEditDrag.pointerId) {
479
+ return false;
480
+ }
481
+
482
+ domElement.releasePointerCapture?.(event.pointerId);
483
+ const updated = pendingEditDrag.updated;
484
+ pendingEditDrag = null;
485
+ const hit = getPendingEditHit(event);
486
+ screenEditOverlay.setActivePart(hit?.part);
487
+ setEditCursor(hit?.cursor || '');
488
+ setStatus(
489
+ updated
490
+ ? 'Updated screen selection convex quadrilateral.'
491
+ : 'Screen selection must stay convex.',
492
+ !updated,
493
+ );
494
+ event.preventDefault();
495
+ event.stopPropagation();
496
+ return true;
497
+ }
498
+
499
+ function handlePendingEditPointerCancel(event) {
500
+ if (!pendingEditDrag || event.pointerId !== pendingEditDrag.pointerId) {
501
+ return false;
502
+ }
503
+
504
+ domElement.releasePointerCapture?.(event.pointerId);
505
+ pendingEditDrag = null;
506
+ screenEditOverlay.setActivePart(null);
507
+ clearEditCursor();
508
+ event.preventDefault();
509
+ event.stopPropagation();
510
+ return true;
511
+ }
512
+
160
513
  function setMode(active) {
161
514
  pendingMode =
162
515
  active && hasGaussianSplats && pendingSelections.length === 0;
@@ -170,6 +523,7 @@ export function createCropController({
170
523
  }
171
524
  cameraController.enabled = !transformControls.dragging;
172
525
  syncTransformControlsState();
526
+ syncPendingEditOverlay();
173
527
  refreshUi();
174
528
  }
175
529
 
@@ -180,7 +534,7 @@ export function createCropController({
180
534
  setMode(false);
181
535
  }
182
536
 
183
- function handleSelectionCreated(selectionData) {
537
+ function handleSelectionCreated(selectionData, clientRect) {
184
538
  if (pendingSelections.length > 0) {
185
539
  setMode(false);
186
540
  setStatus(
@@ -204,12 +558,13 @@ export function createCropController({
204
558
  pendingSelections.push(selection);
205
559
  activeSelectionId = selection.id;
206
560
  setMode(false);
561
+ createPendingScreenEdit(selection, clientRect);
207
562
  syncEditSdfs();
208
563
  syncFarHandles();
209
564
  syncTransformControlsState();
210
565
  refreshUi();
211
566
  setStatus(
212
- 'Added screen exclude selection. Drag the 3D far plane, then Confirm or Cancel before drawing another.',
567
+ 'Added screen exclude selection. Drag corner points or edges into a convex quadrilateral before moving the camera, then adjust the 3D far plane and Confirm or Cancel.',
213
568
  );
214
569
  }
215
570
 
@@ -247,6 +602,7 @@ export function createCropController({
247
602
 
248
603
  const count = pendingSelections.length;
249
604
  selections.push(...pendingSelections);
605
+ clearPendingScreenEdit();
250
606
  pendingSelections = [];
251
607
  activeSelectionId = null;
252
608
  setMode(false);
@@ -270,6 +626,7 @@ export function createCropController({
270
626
  ) {
271
627
  activeSelectionId = null;
272
628
  }
629
+ clearPendingScreenEdit();
273
630
  pendingSelections.forEach(disposeScreenSelection);
274
631
  pendingSelections = [];
275
632
  syncEditSdfs();
@@ -346,11 +703,15 @@ export function createCropController({
346
703
  setTransformMode(null);
347
704
  syncEditSdfs();
348
705
  syncFarHandles();
706
+ syncPendingEditOverlay();
349
707
  refreshUi();
350
708
  syncTransformControlsState();
709
+ const canEditPendingRect = !wasActive && !!getActivePendingEdit();
351
710
  setStatus(
352
711
  wasActive
353
712
  ? 'Screen selection deactivated.'
713
+ : canEditPendingRect
714
+ ? 'Drag corner points or edges into a convex quadrilateral before moving the camera, or drag the 3D far plane to adjust depth.'
354
715
  : 'Drag the 3D far plane handle to adjust screen selection depth.',
355
716
  );
356
717
  }
@@ -378,6 +739,9 @@ export function createCropController({
378
739
  if (Number(selectionId) === activeSelectionId) {
379
740
  activeSelectionId = null;
380
741
  }
742
+ if (Number(selectionId) === pendingScreenEdit?.selectionId) {
743
+ clearPendingScreenEdit();
744
+ }
381
745
  syncEditSdfs();
382
746
  syncFarHandles();
383
747
  refreshUi();
@@ -397,6 +761,7 @@ export function createCropController({
397
761
  selections = [];
398
762
  pendingSelections = [];
399
763
  activeSelectionId = null;
764
+ clearPendingScreenEdit();
400
765
  syncEditSdfs();
401
766
  syncFarHandles();
402
767
  refreshUi();
@@ -412,6 +777,7 @@ export function createCropController({
412
777
  activeSelectionId = null;
413
778
  syncEditSdfs();
414
779
  syncFarHandles();
780
+ syncPendingEditOverlay();
415
781
  cancelMode();
416
782
  refreshUi();
417
783
  }
@@ -423,9 +789,54 @@ export function createCropController({
423
789
  activeSelectionId = null;
424
790
  syncEditSdfs();
425
791
  syncFarHandles();
792
+ syncPendingEditOverlay();
426
793
  refreshUi();
427
794
  }
428
795
 
796
+ function shouldCapturePointerDown(event) {
797
+ return (
798
+ event.button === 0 &&
799
+ (pendingMode || getPendingEditHit(event) !== null)
800
+ );
801
+ }
802
+
803
+ function handlePointerDown(event) {
804
+ if (pointerTracker.handlePointerDown(event)) {
805
+ return true;
806
+ }
807
+ return handlePendingEditPointerDown(event);
808
+ }
809
+
810
+ function handlePointerMove(event) {
811
+ if (pointerTracker.handlePointerMove(event)) {
812
+ return true;
813
+ }
814
+ return handlePendingEditPointerMove(event);
815
+ }
816
+
817
+ function handlePointerUp(event) {
818
+ if (pointerTracker.handlePointerUp(event)) {
819
+ return true;
820
+ }
821
+ return handlePendingEditPointerUp(event);
822
+ }
823
+
824
+ function handlePointerCancel(event) {
825
+ if (pointerTracker.handlePointerCancel(event)) {
826
+ return true;
827
+ }
828
+ return handlePendingEditPointerCancel(event);
829
+ }
830
+
831
+ cameraController.addEventListener(
832
+ 'update',
833
+ freezePendingScreenEditIfCameraChanged,
834
+ );
835
+ cameraController.addEventListener(
836
+ 'finish',
837
+ freezePendingScreenEditIfCameraChanged,
838
+ );
839
+
429
840
  return {
430
841
  cancel,
431
842
  cancelMode,
@@ -435,16 +846,17 @@ export function createCropController({
435
846
  getActiveSelection,
436
847
  getPayload,
437
848
  getPendingMode: () => pendingMode,
438
- handlePointerCancel: pointerTracker.handlePointerCancel,
439
- handlePointerDown: pointerTracker.handlePointerDown,
440
- handlePointerMove: pointerTracker.handlePointerMove,
441
- handlePointerUp: pointerTracker.handlePointerUp,
849
+ handlePointerCancel,
850
+ handlePointerDown,
851
+ handlePointerMove,
852
+ handlePointerUp,
442
853
  handleSelectionRemove,
443
854
  handleSelectionSelect,
444
855
  handleTransformControlObjectChange,
445
856
  hasPendingSelections: () => pendingSelections.length > 0,
446
857
  notifyTransformModeChanged,
447
858
  setHasGaussianSplats,
859
+ shouldCapturePointerDown,
448
860
  syncWorldState,
449
861
  toggle,
450
862
  };