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/CHANGELOG.md +6 -0
- package/README.md +6 -5
- package/dist/inspector-assets/viewer/app.js +764 -71
- package/package.json +1 -1
- package/src/server/viewerHtml.js +111 -2
- package/src/viewer/app.js +1 -1
- package/src/viewer/screenSelection/cropController.js +418 -6
- package/src/viewer/screenSelection/editOverlay.js +348 -0
- package/src/viewer/screenSelection/geometry.js +66 -52
- package/src/viewer/screenSelection/index.js +61 -0
- package/src/viewer/screenSelection/pointerTracker.js +19 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "3dtiles-inspector",
|
|
3
|
-
"version": "0.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",
|
package/src/server/viewerHtml.js
CHANGED
|
@@ -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.
|
|
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">
|
|
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?.
|
|
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
|
|
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
|
|
439
|
-
handlePointerDown
|
|
440
|
-
handlePointerMove
|
|
441
|
-
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
|
};
|