3dtiles-inspector 0.2.2 → 0.2.4
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 +22 -0
- package/README.md +6 -5
- package/dist/inspector-assets/viewer/app.js +1359 -122
- package/package.json +1 -1
- package/src/server/viewerHtml.js +112 -3
- package/src/viewer/app.js +8 -3
- package/src/viewer/navigation/cameraFlyTo.js +730 -0
- package/src/viewer/navigation/flyTo.js +78 -16
- package/src/viewer/scene/cameraController.js +2 -2
- package/src/viewer/scene/sceneSetup.js +3 -6
- 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
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { updateOverlayRect } from './geometry.js';
|
|
2
|
+
|
|
3
|
+
export const SCREEN_EDIT_EDGE_HIT_SIZE = 8;
|
|
4
|
+
export const SCREEN_EDIT_CORNER_HIT_SIZE = 16;
|
|
5
|
+
const SCREEN_EDIT_EDGE_HANDLE_MAX_LENGTH = 26;
|
|
6
|
+
const SCREEN_EDIT_EDGE_HANDLE_MIN_LENGTH = 6;
|
|
7
|
+
const SCREEN_EDIT_EDGE_HANDLE_ACTIVE_SCALE_X = 1.5;
|
|
8
|
+
const SCREEN_EDIT_EDGE_HANDLE_ACTIVE_SCALE_Y = 1.5;
|
|
9
|
+
const SCREEN_EDIT_GRID_DIVISIONS = 8;
|
|
10
|
+
const SCREEN_EDIT_MIN_CONVEX_CROSS_ABS = 1e-3;
|
|
11
|
+
|
|
12
|
+
export const SCREEN_EDIT_HANDLE_PARTS = [
|
|
13
|
+
'top-left',
|
|
14
|
+
'top',
|
|
15
|
+
'top-right',
|
|
16
|
+
'right',
|
|
17
|
+
'bottom-right',
|
|
18
|
+
'bottom',
|
|
19
|
+
'bottom-left',
|
|
20
|
+
'left',
|
|
21
|
+
];
|
|
22
|
+
export const SCREEN_EDIT_CORNER_PARTS = [
|
|
23
|
+
'top-left',
|
|
24
|
+
'top-right',
|
|
25
|
+
'bottom-right',
|
|
26
|
+
'bottom-left',
|
|
27
|
+
];
|
|
28
|
+
export const SCREEN_EDIT_EDGE_PARTS = ['top', 'right', 'bottom', 'left'];
|
|
29
|
+
export const SCREEN_EDIT_PART_POINT_INDICES = {
|
|
30
|
+
'bottom-left': [3],
|
|
31
|
+
'bottom-right': [2],
|
|
32
|
+
bottom: [2, 3],
|
|
33
|
+
left: [3, 0],
|
|
34
|
+
right: [1, 2],
|
|
35
|
+
top: [0, 1],
|
|
36
|
+
'top-left': [0],
|
|
37
|
+
'top-right': [1],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function clampValue(value, min, max) {
|
|
41
|
+
return Math.min(max, Math.max(min, value));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function copyClientPoint(point) {
|
|
45
|
+
return {
|
|
46
|
+
x: Number(point?.x) || 0,
|
|
47
|
+
y: Number(point?.y) || 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function copyClientPoints(points) {
|
|
52
|
+
return points.map(copyClientPoint);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getClientPointsBounds(points) {
|
|
56
|
+
const xs = points.map((point) => point.x);
|
|
57
|
+
const ys = points.map((point) => point.y);
|
|
58
|
+
return {
|
|
59
|
+
maxX: Math.max(...xs),
|
|
60
|
+
maxY: Math.max(...ys),
|
|
61
|
+
minX: Math.min(...xs),
|
|
62
|
+
minY: Math.min(...ys),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getClientRectPoints(rect) {
|
|
67
|
+
return [
|
|
68
|
+
{ x: Number(rect?.minX) || 0, y: Number(rect?.minY) || 0 },
|
|
69
|
+
{ x: Number(rect?.maxX) || 0, y: Number(rect?.minY) || 0 },
|
|
70
|
+
{ x: Number(rect?.maxX) || 0, y: Number(rect?.maxY) || 0 },
|
|
71
|
+
{ x: Number(rect?.minX) || 0, y: Number(rect?.maxY) || 0 },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clampClientPoint(point, domRect) {
|
|
76
|
+
return {
|
|
77
|
+
x: clampValue(point.x, domRect.left, domRect.right),
|
|
78
|
+
y: clampValue(point.y, domRect.top, domRect.bottom),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function clampClientPoints(points, domElement) {
|
|
83
|
+
const domRect = domElement.getBoundingClientRect();
|
|
84
|
+
return points.map((point) => clampClientPoint(point, domRect));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getPointTurnCross(a, b, c) {
|
|
88
|
+
return (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isConvexClientQuad(points) {
|
|
92
|
+
if (!Array.isArray(points) || points.length !== 4) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let turnSign = 0;
|
|
97
|
+
for (let index = 0; index < points.length; index++) {
|
|
98
|
+
const cross = getPointTurnCross(
|
|
99
|
+
points[index],
|
|
100
|
+
points[(index + 1) % points.length],
|
|
101
|
+
points[(index + 2) % points.length],
|
|
102
|
+
);
|
|
103
|
+
if (Math.abs(cross) <= SCREEN_EDIT_MIN_CONVEX_CROSS_ABS) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const nextSign = Math.sign(cross);
|
|
108
|
+
if (turnSign === 0) {
|
|
109
|
+
turnSign = nextSign;
|
|
110
|
+
} else if (nextSign !== turnSign) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getPartPoint(points, part) {
|
|
119
|
+
const indices = SCREEN_EDIT_PART_POINT_INDICES[part] || [];
|
|
120
|
+
if (indices.length === 0) {
|
|
121
|
+
return { x: 0, y: 0 };
|
|
122
|
+
}
|
|
123
|
+
const x =
|
|
124
|
+
indices.reduce((total, index) => total + points[index].x, 0) /
|
|
125
|
+
indices.length;
|
|
126
|
+
const y =
|
|
127
|
+
indices.reduce((total, index) => total + points[index].y, 0) /
|
|
128
|
+
indices.length;
|
|
129
|
+
return { x, y };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getPartAngle(points, part) {
|
|
133
|
+
const indices = SCREEN_EDIT_PART_POINT_INDICES[part] || [];
|
|
134
|
+
if (indices.length !== 2) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const start = points[indices[0]];
|
|
139
|
+
const end = points[indices[1]];
|
|
140
|
+
return Math.atan2(end.y - start.y, end.x - start.x);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getPartLength(points, part) {
|
|
144
|
+
const indices = SCREEN_EDIT_PART_POINT_INDICES[part] || [];
|
|
145
|
+
if (indices.length !== 2) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const start = points[indices[0]];
|
|
150
|
+
const end = points[indices[1]];
|
|
151
|
+
return Math.hypot(end.x - start.x, end.y - start.y);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function interpolatePoint(a, b, t) {
|
|
155
|
+
return {
|
|
156
|
+
x: a.x + (b.x - a.x) * t,
|
|
157
|
+
y: a.y + (b.y - a.y) * t,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function updateScreenEditGrid(grid, localPoints, visible) {
|
|
162
|
+
if (!grid) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
grid.replaceChildren();
|
|
167
|
+
grid.hidden = !visible;
|
|
168
|
+
if (!visible) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const [topLeft, topRight, bottomRight, bottomLeft] = localPoints;
|
|
173
|
+
for (let index = 1; index < SCREEN_EDIT_GRID_DIVISIONS; index++) {
|
|
174
|
+
const t = index / SCREEN_EDIT_GRID_DIVISIONS;
|
|
175
|
+
const top = interpolatePoint(topLeft, topRight, t);
|
|
176
|
+
const bottom = interpolatePoint(bottomLeft, bottomRight, t);
|
|
177
|
+
const left = interpolatePoint(topLeft, bottomLeft, t);
|
|
178
|
+
const right = interpolatePoint(topRight, bottomRight, t);
|
|
179
|
+
const verticalLine = document.createElementNS(
|
|
180
|
+
'http://www.w3.org/2000/svg',
|
|
181
|
+
'line',
|
|
182
|
+
);
|
|
183
|
+
const horizontalLine = document.createElementNS(
|
|
184
|
+
'http://www.w3.org/2000/svg',
|
|
185
|
+
'line',
|
|
186
|
+
);
|
|
187
|
+
verticalLine.setAttribute('x1', String(top.x));
|
|
188
|
+
verticalLine.setAttribute('y1', String(top.y));
|
|
189
|
+
verticalLine.setAttribute('x2', String(bottom.x));
|
|
190
|
+
verticalLine.setAttribute('y2', String(bottom.y));
|
|
191
|
+
horizontalLine.setAttribute('x1', String(left.x));
|
|
192
|
+
horizontalLine.setAttribute('y1', String(left.y));
|
|
193
|
+
horizontalLine.setAttribute('x2', String(right.x));
|
|
194
|
+
horizontalLine.setAttribute('y2', String(right.y));
|
|
195
|
+
grid.append(verticalLine, horizontalLine);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function ensureEditableRectHandles(rectEl) {
|
|
200
|
+
if (!rectEl || rectEl.dataset.editHandlesReady === 'true') {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
205
|
+
const polygon = document.createElementNS(
|
|
206
|
+
'http://www.w3.org/2000/svg',
|
|
207
|
+
'polygon',
|
|
208
|
+
);
|
|
209
|
+
const grid = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
210
|
+
svg.classList.add('screen-selection-edit-svg');
|
|
211
|
+
grid.classList.add('screen-selection-edit-grid');
|
|
212
|
+
polygon.classList.add('screen-selection-edit-polygon');
|
|
213
|
+
svg.append(polygon, grid);
|
|
214
|
+
rectEl.appendChild(svg);
|
|
215
|
+
|
|
216
|
+
SCREEN_EDIT_HANDLE_PARTS.forEach((part) => {
|
|
217
|
+
const handle = document.createElement('span');
|
|
218
|
+
handle.classList.add(
|
|
219
|
+
'screen-selection-edit-handle',
|
|
220
|
+
`screen-selection-edit-${part}`,
|
|
221
|
+
);
|
|
222
|
+
handle.dataset.editPart = part;
|
|
223
|
+
rectEl.appendChild(handle);
|
|
224
|
+
});
|
|
225
|
+
rectEl.dataset.editHandlesReady = 'true';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function pointSegmentDistanceSq(point, start, end) {
|
|
229
|
+
const dx = end.x - start.x;
|
|
230
|
+
const dy = end.y - start.y;
|
|
231
|
+
const lengthSq = dx * dx + dy * dy;
|
|
232
|
+
if (lengthSq <= 1e-12) {
|
|
233
|
+
const px = point.x - start.x;
|
|
234
|
+
const py = point.y - start.y;
|
|
235
|
+
return px * px + py * py;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const t = clampValue(
|
|
239
|
+
((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSq,
|
|
240
|
+
0,
|
|
241
|
+
1,
|
|
242
|
+
);
|
|
243
|
+
const x = start.x + dx * t;
|
|
244
|
+
const y = start.y + dy * t;
|
|
245
|
+
const px = point.x - x;
|
|
246
|
+
const py = point.y - y;
|
|
247
|
+
return px * px + py * py;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function createScreenEditOverlay({ overlayEl, rectEl }) {
|
|
251
|
+
let activePart = null;
|
|
252
|
+
|
|
253
|
+
function applyActivePart() {
|
|
254
|
+
SCREEN_EDIT_HANDLE_PARTS.forEach((part) => {
|
|
255
|
+
const handle = rectEl?.querySelector(`[data-edit-part="${part}"]`);
|
|
256
|
+
handle?.classList.toggle('active', part === activePart);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function render(clientPoints, { showGrid = false } = {}) {
|
|
261
|
+
ensureEditableRectHandles(rectEl);
|
|
262
|
+
rectEl?.classList.add('editable');
|
|
263
|
+
rectEl?.classList.toggle('drawing', showGrid);
|
|
264
|
+
const bounds = getClientPointsBounds(clientPoints);
|
|
265
|
+
updateOverlayRect(overlayEl, rectEl, bounds);
|
|
266
|
+
|
|
267
|
+
const width = Math.max(1, bounds.maxX - bounds.minX);
|
|
268
|
+
const height = Math.max(1, bounds.maxY - bounds.minY);
|
|
269
|
+
const localPoints = clientPoints.map((point) => ({
|
|
270
|
+
x: point.x - bounds.minX,
|
|
271
|
+
y: point.y - bounds.minY,
|
|
272
|
+
}));
|
|
273
|
+
const svg = rectEl?.querySelector('.screen-selection-edit-svg');
|
|
274
|
+
const polygon = rectEl?.querySelector('.screen-selection-edit-polygon');
|
|
275
|
+
const grid = rectEl?.querySelector('.screen-selection-edit-grid');
|
|
276
|
+
svg?.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
277
|
+
polygon?.setAttribute(
|
|
278
|
+
'points',
|
|
279
|
+
localPoints.map((point) => `${point.x},${point.y}`).join(' '),
|
|
280
|
+
);
|
|
281
|
+
updateScreenEditGrid(grid, localPoints, showGrid);
|
|
282
|
+
|
|
283
|
+
SCREEN_EDIT_HANDLE_PARTS.forEach((part) => {
|
|
284
|
+
const point = getPartPoint(localPoints, part);
|
|
285
|
+
const handle = rectEl?.querySelector(`[data-edit-part="${part}"]`);
|
|
286
|
+
if (!handle) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
handle.style.left = `${point.x}px`;
|
|
290
|
+
handle.style.top = `${point.y}px`;
|
|
291
|
+
const angle = getPartAngle(localPoints, part);
|
|
292
|
+
const length = getPartLength(localPoints, part);
|
|
293
|
+
if (length != null) {
|
|
294
|
+
const maxVisualLength = Math.max(0, length - 2);
|
|
295
|
+
const visualLength = Math.max(
|
|
296
|
+
0,
|
|
297
|
+
Math.min(SCREEN_EDIT_EDGE_HANDLE_MAX_LENGTH, maxVisualLength),
|
|
298
|
+
);
|
|
299
|
+
const activeScaleX =
|
|
300
|
+
visualLength > 0
|
|
301
|
+
? Math.min(
|
|
302
|
+
SCREEN_EDIT_EDGE_HANDLE_ACTIVE_SCALE_X,
|
|
303
|
+
Math.max(1, maxVisualLength / visualLength),
|
|
304
|
+
)
|
|
305
|
+
: 1;
|
|
306
|
+
handle.style.width = `${visualLength}px`;
|
|
307
|
+
handle.style.height = `${
|
|
308
|
+
length <= SCREEN_EDIT_EDGE_HANDLE_MIN_LENGTH ? 2 : 4
|
|
309
|
+
}px`;
|
|
310
|
+
handle.style.setProperty(
|
|
311
|
+
'--screen-selection-edit-active-scale-x',
|
|
312
|
+
String(activeScaleX),
|
|
313
|
+
);
|
|
314
|
+
handle.style.setProperty(
|
|
315
|
+
'--screen-selection-edit-active-scale-y',
|
|
316
|
+
String(SCREEN_EDIT_EDGE_HANDLE_ACTIVE_SCALE_Y),
|
|
317
|
+
);
|
|
318
|
+
} else {
|
|
319
|
+
handle.style.width = '';
|
|
320
|
+
handle.style.height = '';
|
|
321
|
+
handle.style.removeProperty('--screen-selection-edit-active-scale-x');
|
|
322
|
+
handle.style.removeProperty('--screen-selection-edit-active-scale-y');
|
|
323
|
+
}
|
|
324
|
+
handle.style.transform =
|
|
325
|
+
angle == null
|
|
326
|
+
? 'translate(-50%, -50%)'
|
|
327
|
+
: `translate(-50%, -50%) rotate(${angle}rad)`;
|
|
328
|
+
});
|
|
329
|
+
applyActivePart();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function clear() {
|
|
333
|
+
activePart = null;
|
|
334
|
+
rectEl?.classList.remove('drawing', 'editable');
|
|
335
|
+
applyActivePart();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function setActivePart(part) {
|
|
339
|
+
activePart = SCREEN_EDIT_HANDLE_PARTS.includes(part) ? part : null;
|
|
340
|
+
applyActivePart();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
clear,
|
|
345
|
+
render,
|
|
346
|
+
setActivePart,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
@@ -44,6 +44,16 @@ function clientPointToNdc(point, domRect) {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function getClientSelectionQuad(start, end) {
|
|
48
|
+
const clientRect = getClientSelectionRect(start, end);
|
|
49
|
+
return [
|
|
50
|
+
new Vector2(clientRect.minX, clientRect.minY),
|
|
51
|
+
new Vector2(clientRect.maxX, clientRect.minY),
|
|
52
|
+
new Vector2(clientRect.maxX, clientRect.maxY),
|
|
53
|
+
new Vector2(clientRect.minX, clientRect.maxY),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
function getNdcSelectionRect(clientRect, domRect) {
|
|
48
58
|
const topLeft = clientPointToNdc(
|
|
49
59
|
new Vector2(clientRect.minX, clientRect.minY),
|
|
@@ -61,6 +71,21 @@ function getNdcSelectionRect(clientRect, domRect) {
|
|
|
61
71
|
};
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
function getNdcSelectionQuad(clientPoints, domRect) {
|
|
75
|
+
return clientPoints.map((point) => clientPointToNdc(point, domRect));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getNdcBounds(points) {
|
|
79
|
+
const xs = points.map((point) => point.x);
|
|
80
|
+
const ys = points.map((point) => point.y);
|
|
81
|
+
return {
|
|
82
|
+
maxX: Math.max(...xs),
|
|
83
|
+
maxY: Math.max(...ys),
|
|
84
|
+
minX: Math.min(...xs),
|
|
85
|
+
minY: Math.min(...ys),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
export function updateOverlayRect(overlayEl, rectEl, clientRect) {
|
|
65
90
|
if (!overlayEl || !rectEl) {
|
|
66
91
|
return;
|
|
@@ -277,10 +302,12 @@ function createFarPlaneData({
|
|
|
277
302
|
};
|
|
278
303
|
}
|
|
279
304
|
|
|
280
|
-
function
|
|
305
|
+
function createFrustumDataFromQuad(camera, quad, depthRange) {
|
|
281
306
|
const { farDepth, nearDepth } = normalizeDepthRange(camera, depthRange);
|
|
282
|
-
const centerX =
|
|
283
|
-
|
|
307
|
+
const centerX =
|
|
308
|
+
quad.reduce((total, point) => total + point.x, 0) / quad.length;
|
|
309
|
+
const centerY =
|
|
310
|
+
quad.reduce((total, point) => total + point.y, 0) / quad.length;
|
|
284
311
|
const nearCenter = createPointAtViewDepth(
|
|
285
312
|
camera,
|
|
286
313
|
centerX,
|
|
@@ -312,56 +339,14 @@ function createFrustumData(camera, rect, depthRange) {
|
|
|
312
339
|
plane.setFromNormalAndCoplanarPoint(selectionForward, farCenter);
|
|
313
340
|
|
|
314
341
|
const farClipPlane = plane.clone();
|
|
315
|
-
const farTopLeft =
|
|
316
|
-
camera,
|
|
317
|
-
rect.minX,
|
|
318
|
-
rect.maxY,
|
|
319
|
-
farClipPlane,
|
|
320
|
-
);
|
|
321
|
-
const farTopRight = createPointOnPlane(
|
|
322
|
-
camera,
|
|
323
|
-
rect.maxX,
|
|
324
|
-
rect.maxY,
|
|
325
|
-
farClipPlane,
|
|
326
|
-
);
|
|
327
|
-
const farBottomRight = createPointOnPlane(
|
|
328
|
-
camera,
|
|
329
|
-
rect.maxX,
|
|
330
|
-
rect.minY,
|
|
331
|
-
farClipPlane,
|
|
332
|
-
);
|
|
333
|
-
const farBottomLeft = createPointOnPlane(
|
|
334
|
-
camera,
|
|
335
|
-
rect.minX,
|
|
336
|
-
rect.minY,
|
|
337
|
-
farClipPlane,
|
|
342
|
+
const [farTopLeft, farTopRight, farBottomRight, farBottomLeft] = quad.map(
|
|
343
|
+
(point) => createPointOnPlane(camera, point.x, point.y, farClipPlane),
|
|
338
344
|
);
|
|
339
345
|
|
|
340
346
|
plane.setFromNormalAndCoplanarPoint(selectionForward, nearCenter);
|
|
341
347
|
const nearPlane = plane.clone();
|
|
342
|
-
const nearTopLeft =
|
|
343
|
-
camera,
|
|
344
|
-
rect.minX,
|
|
345
|
-
rect.maxY,
|
|
346
|
-
nearPlane,
|
|
347
|
-
);
|
|
348
|
-
const nearTopRight = createPointOnPlane(
|
|
349
|
-
camera,
|
|
350
|
-
rect.maxX,
|
|
351
|
-
rect.maxY,
|
|
352
|
-
nearPlane,
|
|
353
|
-
);
|
|
354
|
-
const nearBottomRight = createPointOnPlane(
|
|
355
|
-
camera,
|
|
356
|
-
rect.maxX,
|
|
357
|
-
rect.minY,
|
|
358
|
-
nearPlane,
|
|
359
|
-
);
|
|
360
|
-
const nearBottomLeft = createPointOnPlane(
|
|
361
|
-
camera,
|
|
362
|
-
rect.minX,
|
|
363
|
-
rect.minY,
|
|
364
|
-
nearPlane,
|
|
348
|
+
const [nearTopLeft, nearTopRight, nearBottomRight, nearBottomLeft] = quad.map(
|
|
349
|
+
(point) => createPointOnPlane(camera, point.x, point.y, nearPlane),
|
|
365
350
|
);
|
|
366
351
|
if (
|
|
367
352
|
!farTopLeft ||
|
|
@@ -451,8 +436,22 @@ function createFrustumData(camera, rect, depthRange) {
|
|
|
451
436
|
};
|
|
452
437
|
}
|
|
453
438
|
|
|
439
|
+
function createFrustumData(camera, rect, depthRange) {
|
|
440
|
+
return createFrustumDataFromQuad(
|
|
441
|
+
camera,
|
|
442
|
+
[
|
|
443
|
+
{ x: rect.minX, y: rect.maxY },
|
|
444
|
+
{ x: rect.maxX, y: rect.maxY },
|
|
445
|
+
{ x: rect.maxX, y: rect.minY },
|
|
446
|
+
{ x: rect.minX, y: rect.minY },
|
|
447
|
+
],
|
|
448
|
+
depthRange,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
454
452
|
export function createSelectionData({
|
|
455
453
|
camera,
|
|
454
|
+
clientPoints,
|
|
456
455
|
domElement,
|
|
457
456
|
end,
|
|
458
457
|
getDepthRange,
|
|
@@ -463,14 +462,27 @@ export function createSelectionData({
|
|
|
463
462
|
return null;
|
|
464
463
|
}
|
|
465
464
|
|
|
466
|
-
const
|
|
465
|
+
const hasClientQuad =
|
|
466
|
+
Array.isArray(clientPoints) && clientPoints.length === 4;
|
|
467
|
+
const points = hasClientQuad
|
|
468
|
+
? clientPoints
|
|
469
|
+
: getClientSelectionQuad(start, end);
|
|
470
|
+
const clientRect = {
|
|
471
|
+
maxX: Math.max(...points.map((point) => point.x)),
|
|
472
|
+
maxY: Math.max(...points.map((point) => point.y)),
|
|
473
|
+
minX: Math.min(...points.map((point) => point.x)),
|
|
474
|
+
minY: Math.min(...points.map((point) => point.y)),
|
|
475
|
+
};
|
|
467
476
|
const width = clientRect.maxX - clientRect.minX;
|
|
468
477
|
const height = clientRect.maxY - clientRect.minY;
|
|
469
478
|
if (width * width + height * height < SCREEN_SELECTION_MIN_DRAG_DISTANCE_SQ) {
|
|
470
479
|
return null;
|
|
471
480
|
}
|
|
472
481
|
|
|
473
|
-
const
|
|
482
|
+
const quad = getNdcSelectionQuad(points, domRect);
|
|
483
|
+
const rect = hasClientQuad
|
|
484
|
+
? getNdcBounds(quad)
|
|
485
|
+
: getNdcSelectionRect(clientRect, domRect);
|
|
474
486
|
camera.updateProjectionMatrix();
|
|
475
487
|
camera.updateMatrixWorld(true);
|
|
476
488
|
cameraPosition.copy(camera.position);
|
|
@@ -478,7 +490,9 @@ export function createSelectionData({
|
|
|
478
490
|
const viewProjectionMatrix = new Matrix4()
|
|
479
491
|
.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
|
|
480
492
|
.toArray();
|
|
481
|
-
const frustum =
|
|
493
|
+
const frustum = hasClientQuad
|
|
494
|
+
? createFrustumDataFromQuad(camera, quad, depthRange)
|
|
495
|
+
: createFrustumData(camera, rect, depthRange);
|
|
482
496
|
if (!frustum) {
|
|
483
497
|
return null;
|
|
484
498
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SCREEN_SELECTION_ACTION_EXCLUDE,
|
|
3
|
+
SCREEN_SELECTION_MIN_DEPTH_RANGE,
|
|
3
4
|
cameraPosition,
|
|
4
5
|
copyDepthRange,
|
|
5
6
|
copyFarPlane,
|
|
@@ -87,6 +88,66 @@ export function getScreenSelectionPayload(selection) {
|
|
|
87
88
|
};
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
export function setScreenSelectionShape(
|
|
92
|
+
selection,
|
|
93
|
+
{
|
|
94
|
+
cameraPosition: sourceCameraPosition,
|
|
95
|
+
depthRange,
|
|
96
|
+
farPlane,
|
|
97
|
+
planeMatrices,
|
|
98
|
+
rect,
|
|
99
|
+
selectionForward: sourceSelectionForward,
|
|
100
|
+
viewProjectionMatrix,
|
|
101
|
+
},
|
|
102
|
+
currentTransformMatrix,
|
|
103
|
+
) {
|
|
104
|
+
if (!selection) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const previousFarDepth = Number(selection.depthRange?.farDepth);
|
|
109
|
+
const copiedPlaneMatrices = planeMatrices.map((matrix) => matrix.slice());
|
|
110
|
+
const referenceTransformMatrix = copyMatrix4Array(currentTransformMatrix);
|
|
111
|
+
|
|
112
|
+
selection.basePlaneMatrices = copiedPlaneMatrices.map((matrix) =>
|
|
113
|
+
matrix.slice(),
|
|
114
|
+
);
|
|
115
|
+
selection.cameraPosition = copyVectorArray(sourceCameraPosition);
|
|
116
|
+
selection.currentTransformMatrix = referenceTransformMatrix.slice();
|
|
117
|
+
selection.depthRange = copyDepthRange(depthRange);
|
|
118
|
+
selection.farPlane = copyFarPlane(farPlane);
|
|
119
|
+
selection.planeMatrices = copiedPlaneMatrices;
|
|
120
|
+
selection.referenceTransformMatrix = referenceTransformMatrix;
|
|
121
|
+
selection.rect = copyRect(rect);
|
|
122
|
+
selection.selectionForward = copyVectorArray(sourceSelectionForward, [
|
|
123
|
+
0,
|
|
124
|
+
0,
|
|
125
|
+
-1,
|
|
126
|
+
]);
|
|
127
|
+
selection.viewProjectionMatrix = viewProjectionMatrix.slice();
|
|
128
|
+
|
|
129
|
+
updateScreenSelectionWorldState(selection, currentTransformMatrix);
|
|
130
|
+
|
|
131
|
+
if (!Number.isFinite(previousFarDepth)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const nextFarDepth = Math.min(
|
|
136
|
+
selection.depthRange.maxFarDepth,
|
|
137
|
+
Math.max(
|
|
138
|
+
selection.depthRange.nearDepth + SCREEN_SELECTION_MIN_DEPTH_RANGE,
|
|
139
|
+
previousFarDepth,
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
if (Math.abs(nextFarDepth - selection.depthRange.farDepth) > 1e-9) {
|
|
143
|
+
setScreenSelectionFarDepth(
|
|
144
|
+
selection,
|
|
145
|
+
nextFarDepth,
|
|
146
|
+
currentTransformMatrix,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
90
151
|
export function updateScreenSelectionWorldState(
|
|
91
152
|
selection,
|
|
92
153
|
currentTransformMatrix,
|
|
@@ -11,6 +11,8 @@ export function createScreenSelectionPointerTracker({
|
|
|
11
11
|
camera,
|
|
12
12
|
domElement,
|
|
13
13
|
getDepthRange,
|
|
14
|
+
onOverlayClear,
|
|
15
|
+
onOverlayUpdate,
|
|
14
16
|
onSelectionCreated,
|
|
15
17
|
overlayEl,
|
|
16
18
|
rectEl,
|
|
@@ -20,9 +22,20 @@ export function createScreenSelectionPointerTracker({
|
|
|
20
22
|
|
|
21
23
|
function clearDrag() {
|
|
22
24
|
drag = null;
|
|
25
|
+
onOverlayClear?.();
|
|
23
26
|
clearOverlay(overlayEl, rectEl);
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
function updateDragOverlay() {
|
|
30
|
+
const clientRect = {
|
|
31
|
+
...getClientSelectionRect(drag.start, drag.current),
|
|
32
|
+
};
|
|
33
|
+
if (onOverlayUpdate?.(clientRect) === true) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
updateOverlayRect(overlayEl, rectEl, clientRect);
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
function setActive(nextActive) {
|
|
27
40
|
active = !!nextActive;
|
|
28
41
|
if (!active) {
|
|
@@ -43,11 +56,7 @@ export function createScreenSelectionPointerTracker({
|
|
|
43
56
|
start: dragStart.clone(),
|
|
44
57
|
current: dragCurrent.clone(),
|
|
45
58
|
};
|
|
46
|
-
|
|
47
|
-
overlayEl,
|
|
48
|
-
rectEl,
|
|
49
|
-
getClientSelectionRect(drag.start, drag.current),
|
|
50
|
-
);
|
|
59
|
+
updateDragOverlay();
|
|
51
60
|
domElement.setPointerCapture?.(event.pointerId);
|
|
52
61
|
event.preventDefault();
|
|
53
62
|
event.stopPropagation();
|
|
@@ -61,11 +70,7 @@ export function createScreenSelectionPointerTracker({
|
|
|
61
70
|
|
|
62
71
|
const domRect = domElement.getBoundingClientRect();
|
|
63
72
|
getClampedClientPoint(event, domRect, drag.current);
|
|
64
|
-
|
|
65
|
-
overlayEl,
|
|
66
|
-
rectEl,
|
|
67
|
-
getClientSelectionRect(drag.start, drag.current),
|
|
68
|
-
);
|
|
73
|
+
updateDragOverlay();
|
|
69
74
|
event.preventDefault();
|
|
70
75
|
event.stopPropagation();
|
|
71
76
|
return true;
|
|
@@ -76,6 +81,9 @@ export function createScreenSelectionPointerTracker({
|
|
|
76
81
|
return false;
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
const clientRect = {
|
|
85
|
+
...getClientSelectionRect(drag.start, drag.current),
|
|
86
|
+
};
|
|
79
87
|
const selection = createSelectionData({
|
|
80
88
|
camera,
|
|
81
89
|
domElement,
|
|
@@ -85,7 +93,7 @@ export function createScreenSelectionPointerTracker({
|
|
|
85
93
|
});
|
|
86
94
|
domElement.releasePointerCapture?.(event.pointerId);
|
|
87
95
|
clearDrag();
|
|
88
|
-
onSelectionCreated(selection);
|
|
96
|
+
onSelectionCreated(selection, clientRect);
|
|
89
97
|
event.preventDefault();
|
|
90
98
|
event.stopPropagation();
|
|
91
99
|
return true;
|