@weasel-js/kernel3d 1.13.0 → 1.14.1
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/README.md +36 -9
- package/dist/index.d.ts +52 -12
- package/dist/index.js +232 -96
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, invert, transformPoint, normalize,
|
|
2
|
-
import { createSnapOutcomeStore, assertSnapSourceIds, DEFAULT_SNAP_PX, overlaysOf, resolveOverlays, defineTool, snapSourcesFor, resolvedSnapMarks, SNAP_AXIS_STEP, gatherSnapCandidates } from '@weasel-js/core';
|
|
1
|
+
import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, quatMultiply, quatConjugate, quatRotate, sub, invert, transformPoint, normalize, transformPoint4, distance, dot, len, intersectRayPlane, pointOnRay, cross, intersectRayAabb, aabbOfPoints } from '@weasel-js/geom/3d';
|
|
2
|
+
import { scenePoseFrame, createSnapOutcomeStore, assertSnapSourceIds, DEFAULT_SNAP_PX, overlaysOf, resolveOverlays, defineTool, snapSourcesFor, resolvedSnapMarks, SNAP_AXIS_STEP, gatherSnapCandidates, scenePoseAdapter, composeWorldPose } from '@weasel-js/core';
|
|
3
3
|
import { isDev } from '@weasel-js/react';
|
|
4
|
-
import { closestOnFlat, resolveSnap, createBvh, stepLine } from '@weasel-js/geom/nd';
|
|
4
|
+
import { closestOnFlat, resolveSnap, createBvh, stepLine, createSettledCache } from '@weasel-js/geom/nd';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
import { pref } from '@weasel-js/prefs';
|
|
7
7
|
|
|
@@ -94,6 +94,26 @@ function poseMatrix(pose) {
|
|
|
94
94
|
function aabbOfPose(pose, local = UNIT_CUBE) {
|
|
95
95
|
return transformAabb(poseMatrix(pose), local);
|
|
96
96
|
}
|
|
97
|
+
var times = (a, b) => ({ x: a.x * b.x, y: a.y * b.y, z: a.z * b.z });
|
|
98
|
+
var over = (a, s, zero) => ({
|
|
99
|
+
x: s.x === 0 ? zero.x : a.x / s.x,
|
|
100
|
+
y: s.y === 0 ? zero.y : a.y / s.y,
|
|
101
|
+
z: s.z === 0 ? zero.z : a.z / s.z
|
|
102
|
+
});
|
|
103
|
+
var ORIGIN = { x: 0, y: 0, z: 0 };
|
|
104
|
+
var POSE3_COMPOSITION = {
|
|
105
|
+
compose: (parent, child) => ({
|
|
106
|
+
position: add(parent.position, quatRotate(parent.rotation, times(parent.scale, child.position))),
|
|
107
|
+
rotation: quatMultiply(parent.rotation, child.rotation),
|
|
108
|
+
scale: times(parent.scale, child.scale)
|
|
109
|
+
}),
|
|
110
|
+
decompose: (parent, world) => ({
|
|
111
|
+
position: over(quatRotate(quatConjugate(parent.rotation), sub(world.position, parent.position)), parent.scale, ORIGIN),
|
|
112
|
+
rotation: quatMultiply(quatConjugate(parent.rotation), world.rotation),
|
|
113
|
+
scale: over(world.scale, parent.scale, world.scale)
|
|
114
|
+
}),
|
|
115
|
+
closure: "rigid"
|
|
116
|
+
};
|
|
97
117
|
function screenToNdc(point, rect) {
|
|
98
118
|
return {
|
|
99
119
|
x: (point.x - rect.x) / rect.width * 2 - 1,
|
|
@@ -195,21 +215,73 @@ function projectPoint(p, viewport) {
|
|
|
195
215
|
return Number.isFinite(screen.x) && Number.isFinite(screen.y) ? screen : null;
|
|
196
216
|
}
|
|
197
217
|
|
|
198
|
-
// src/
|
|
218
|
+
// src/ground.ts
|
|
219
|
+
function groundAxes(up) {
|
|
220
|
+
if (up.y !== 0) return { u: "x", v: "z", up: "y" };
|
|
221
|
+
if (up.x !== 0) return { u: "y", v: "z", up: "x" };
|
|
222
|
+
return { u: "x", v: "y", up: "z" };
|
|
223
|
+
}
|
|
224
|
+
function groundHit(viewport, point, up) {
|
|
225
|
+
const ray = rayAt(viewport, point);
|
|
226
|
+
const t = ray && intersectRayPlane(ray, up, 0);
|
|
227
|
+
return ray && t !== null ? pointOnRay(ray, t) : null;
|
|
228
|
+
}
|
|
229
|
+
function toAimPoint(p, axes) {
|
|
230
|
+
return { x: p[axes.u], y: p[axes.v], z: p[axes.up] };
|
|
231
|
+
}
|
|
232
|
+
function fromAimPoint(p, axes) {
|
|
233
|
+
const out = { x: 0, y: 0, z: 0 };
|
|
234
|
+
out[axes.u] = p.x;
|
|
235
|
+
out[axes.v] = p.y;
|
|
236
|
+
out[axes.up] = p.z ?? 0;
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
199
239
|
var DEFAULT_UP = { x: 0, y: 1, z: 0 };
|
|
200
|
-
function
|
|
201
|
-
return
|
|
240
|
+
function worldPoseOf(scene, id) {
|
|
241
|
+
return scene.get(id) === void 0 ? void 0 : worldPoses(scene, "shown")(id);
|
|
242
|
+
}
|
|
243
|
+
function worldPoses(scene, reading) {
|
|
244
|
+
const untyped = scene;
|
|
245
|
+
if (reading === "shown") {
|
|
246
|
+
const frame = scenePoseFrame(untyped);
|
|
247
|
+
return (id) => frame.world(id);
|
|
248
|
+
}
|
|
249
|
+
const adapter = scenePoseAdapter(untyped);
|
|
250
|
+
const compose2 = scene.composition.compose;
|
|
251
|
+
return (id) => composeWorldPose(adapter, id, compose2);
|
|
252
|
+
}
|
|
253
|
+
function boxAt(world, node, pose) {
|
|
254
|
+
if (world.bounds) return world.bounds(pose === node.pose ? node : { ...node, pose });
|
|
255
|
+
return node.kind === "group" ? null : aabbOfPose(pose);
|
|
256
|
+
}
|
|
257
|
+
function measuredBoxAt(world, node, pose) {
|
|
258
|
+
return boxAt(world, node, pose) ?? pointBox(pose.position);
|
|
259
|
+
}
|
|
260
|
+
function worldBoxes(world, reading) {
|
|
261
|
+
const poseOf = worldPoses(world.scene, reading);
|
|
262
|
+
return (node) => boxAt(world, node, poseOf(node.id));
|
|
263
|
+
}
|
|
264
|
+
function withDescendants(scene, ids) {
|
|
265
|
+
const out = /* @__PURE__ */ new Set();
|
|
266
|
+
const queue = [...ids];
|
|
267
|
+
while (queue.length > 0) {
|
|
268
|
+
const id = queue.pop();
|
|
269
|
+
if (out.has(id)) continue;
|
|
270
|
+
out.add(id);
|
|
271
|
+
queue.push(...scene.childrenOf(id));
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
function pointBox(p) {
|
|
276
|
+
return { min: p, max: p };
|
|
202
277
|
}
|
|
203
278
|
|
|
204
279
|
// src/pickIndex.ts
|
|
205
280
|
function screenBoxOf(world, id, viewport) {
|
|
206
281
|
const node = world.scene.get(id);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
viewProjectionOf(viewport),
|
|
211
|
-
rectOf(viewport)
|
|
212
|
-
);
|
|
282
|
+
const pose = node && worldPoseOf(world.scene, id);
|
|
283
|
+
const box = node && pose && boxAt(world, node, pose);
|
|
284
|
+
return box ? projectAabbToScreen(box, viewProjectionOf(viewport), rectOf(viewport)) : null;
|
|
213
285
|
}
|
|
214
286
|
function overlaps(a, b) {
|
|
215
287
|
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
@@ -238,14 +310,12 @@ function sidePlanes(vp, rect) {
|
|
|
238
310
|
];
|
|
239
311
|
}
|
|
240
312
|
var BuiltIndex = class {
|
|
241
|
-
constructor(
|
|
242
|
-
this.world = world;
|
|
313
|
+
constructor(order, boxes, tree, always) {
|
|
243
314
|
this.order = order;
|
|
244
315
|
this.boxes = boxes;
|
|
245
316
|
this.tree = tree;
|
|
246
317
|
this.always = always;
|
|
247
318
|
}
|
|
248
|
-
world;
|
|
249
319
|
order;
|
|
250
320
|
boxes;
|
|
251
321
|
tree;
|
|
@@ -255,8 +325,9 @@ var BuiltIndex = class {
|
|
|
255
325
|
let best = -1;
|
|
256
326
|
let bestT = Infinity;
|
|
257
327
|
const consider = (i) => {
|
|
328
|
+
const box = boxes[i];
|
|
258
329
|
if (excluded?.has(order[i].id)) return;
|
|
259
|
-
const t = intersectRayAabb(ray,
|
|
330
|
+
const t = intersectRayAabb(ray, box.min, box.max);
|
|
260
331
|
if (t !== null && (t < bestT || t === bestT && i < best)) {
|
|
261
332
|
bestT = t;
|
|
262
333
|
best = i;
|
|
@@ -278,10 +349,11 @@ var BuiltIndex = class {
|
|
|
278
349
|
});
|
|
279
350
|
candidates.sort((a, b) => a - b);
|
|
280
351
|
const hits = [];
|
|
352
|
+
const m = viewProjectionOf(vp);
|
|
353
|
+
const pane = rectOf(vp);
|
|
281
354
|
for (const i of candidates) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
if (box && overlaps(box, rect)) hits.push(id);
|
|
355
|
+
const box = projectAabbToScreen(this.boxes[i], m, pane);
|
|
356
|
+
if (box && overlaps(box, rect)) hits.push(this.order[i].id);
|
|
285
357
|
}
|
|
286
358
|
return hits;
|
|
287
359
|
}
|
|
@@ -300,14 +372,16 @@ function padded({ min, max }) {
|
|
|
300
372
|
return [min.x - e, min.y - e, min.z - e, max.x + e, max.y + e, max.z + e];
|
|
301
373
|
}
|
|
302
374
|
function build(world, order) {
|
|
303
|
-
const
|
|
375
|
+
const boxOf = worldBoxes(world, "shown");
|
|
376
|
+
const boxes = order.map(boxOf);
|
|
304
377
|
const entries = [];
|
|
305
378
|
const always = [];
|
|
306
379
|
boxes.forEach((b, i) => {
|
|
380
|
+
if (b === null) return;
|
|
307
381
|
if (boxValid(b)) entries.push([i, padded(b)]);
|
|
308
382
|
else always.push(i);
|
|
309
383
|
});
|
|
310
|
-
return new BuiltIndex(
|
|
384
|
+
return new BuiltIndex(order, boxes, createBvh(3, entries), always);
|
|
311
385
|
}
|
|
312
386
|
function worldPickIndex(world) {
|
|
313
387
|
const { scene } = world;
|
|
@@ -338,16 +412,20 @@ function inputFlat3d(c, held, basis, delta0, about) {
|
|
|
338
412
|
return stepLine(tuple(c === "axis" ? ZERO : about), tuple(delta0), [tuple(u), tuple(v)], step);
|
|
339
413
|
}
|
|
340
414
|
function subjectOf(world, ids, up) {
|
|
415
|
+
const { scene } = world;
|
|
416
|
+
const poseOf = worldPoses(scene, "shown");
|
|
417
|
+
const nodeOf = (id) => scene.get(id);
|
|
418
|
+
const lead = ids.find((id) => nodeOf(id) !== void 0);
|
|
419
|
+
if (lead === void 0) return null;
|
|
420
|
+
const leadPose = poseOf(lead);
|
|
421
|
+
const anchor = { box: measuredBoxAt(world, nodeOf(lead), leadPose), height: dot(up, leadPose.position) };
|
|
341
422
|
let box = null;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
const b = boundsOfNode(world, node);
|
|
347
|
-
anchor ??= { box: b, height: dot(up, node.pose.position) };
|
|
348
|
-
box = box ? union(box, b) : b;
|
|
423
|
+
for (const id of withDescendants(scene, ids)) {
|
|
424
|
+
const node = nodeOf(id);
|
|
425
|
+
const b = node && boxAt(world, node, poseOf(node.id));
|
|
426
|
+
if (b) box = box ? union(box, b) : b;
|
|
349
427
|
}
|
|
350
|
-
return box
|
|
428
|
+
return { box: box ?? anchor.box, anchor };
|
|
351
429
|
}
|
|
352
430
|
function union(a, b) {
|
|
353
431
|
return {
|
|
@@ -503,6 +581,10 @@ function onPlane(vp, plane, point) {
|
|
|
503
581
|
const t = intersectRayPlane(ray, plane.normal, plane.offset);
|
|
504
582
|
return t === null ? null : pointOnRay(ray, t);
|
|
505
583
|
}
|
|
584
|
+
function movedOnLevel(pose, delta, upAxis) {
|
|
585
|
+
const position = { ...add(pose.position, delta), [upAxis]: pose.position[upAxis] };
|
|
586
|
+
return { ...pose, position };
|
|
587
|
+
}
|
|
506
588
|
function createNodeAtPoint(world) {
|
|
507
589
|
return (point, exclude) => {
|
|
508
590
|
const ray = rayAt(world.viewport(), point);
|
|
@@ -546,7 +628,7 @@ function createSnap(world, opts = {}) {
|
|
|
546
628
|
}
|
|
547
629
|
function createPoseDescriptor(world) {
|
|
548
630
|
const up = world.up ?? DEFAULT_UP;
|
|
549
|
-
const upAxis = up.
|
|
631
|
+
const upAxis = groundAxes(up).up;
|
|
550
632
|
const specialized = /* @__PURE__ */ new WeakMap();
|
|
551
633
|
let lastCastable = null;
|
|
552
634
|
function castableViewport() {
|
|
@@ -555,14 +637,14 @@ function createPoseDescriptor(world) {
|
|
|
555
637
|
if (rayAt(vp, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 })) lastCastable = vp;
|
|
556
638
|
return lastCastable;
|
|
557
639
|
}
|
|
558
|
-
function
|
|
640
|
+
function build3(worldBox) {
|
|
559
641
|
const boundsIn = (vp, pose) => projectAabbToScreen(worldBox(pose), viewProjectionOf(vp), rectOf(vp));
|
|
560
642
|
const boundsOf = (pose) => boundsIn(world.viewport(), pose);
|
|
561
643
|
const descriptor = {
|
|
562
644
|
forNode(node) {
|
|
563
645
|
let d = specialized.get(node);
|
|
564
646
|
if (!d) {
|
|
565
|
-
d =
|
|
647
|
+
d = build3((pose) => measuredBoxAt(world, node, pose));
|
|
566
648
|
specialized.set(node, d);
|
|
567
649
|
}
|
|
568
650
|
return d;
|
|
@@ -645,31 +727,20 @@ function createPoseDescriptor(world) {
|
|
|
645
727
|
scale: sub(box.max, box.min)
|
|
646
728
|
};
|
|
647
729
|
},
|
|
648
|
-
/**
|
|
649
|
-
|
|
730
|
+
/**
|
|
731
|
+
* Screen-space deltas resolve against the plane the node already sits on. That
|
|
732
|
+
* plane differs per node, so in a move of several each takes the lead's change
|
|
733
|
+
* whole (it kept its height already), and the group moves as one body. */
|
|
734
|
+
translate(pose, dx, dy, lead) {
|
|
735
|
+
if (lead) return { ...pose, position: add(pose.position, sub(lead.to.position, lead.from.position)) };
|
|
650
736
|
const vp = castableViewport();
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (!box) return pose;
|
|
737
|
+
const box = vp && boundsIn(vp, pose);
|
|
738
|
+
if (!vp || !box) return pose;
|
|
654
739
|
const from = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
655
|
-
const
|
|
656
|
-
const
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
const t = intersectRayPlane(ray, up, offset);
|
|
660
|
-
if (t === null) return pose;
|
|
661
|
-
const anchorRay = rayAt(vp, from);
|
|
662
|
-
if (!anchorRay) return pose;
|
|
663
|
-
const anchorT = intersectRayPlane(anchorRay, up, offset);
|
|
664
|
-
if (anchorT === null) return pose;
|
|
665
|
-
const delta = sub(pointOnRay(ray, t), pointOnRay(anchorRay, anchorT));
|
|
666
|
-
const position = {
|
|
667
|
-
x: pose.position.x + delta.x,
|
|
668
|
-
y: pose.position.y + delta.y,
|
|
669
|
-
z: pose.position.z + delta.z
|
|
670
|
-
};
|
|
671
|
-
position[upAxis] = pose.position[upAxis];
|
|
672
|
-
return { ...pose, position };
|
|
740
|
+
const level2 = { normal: up, offset: dot(up, pose.position) };
|
|
741
|
+
const before = onPlane(vp, level2, from);
|
|
742
|
+
const after = onPlane(vp, level2, { x: from.x + dx, y: from.y + dy });
|
|
743
|
+
return before && after ? movedOnLevel(pose, sub(after, before), upAxis) : pose;
|
|
673
744
|
},
|
|
674
745
|
supportsRotation() {
|
|
675
746
|
return false;
|
|
@@ -677,7 +748,7 @@ function createPoseDescriptor(world) {
|
|
|
677
748
|
};
|
|
678
749
|
return descriptor;
|
|
679
750
|
}
|
|
680
|
-
return
|
|
751
|
+
return build3((pose) => aabbOfPose(pose));
|
|
681
752
|
}
|
|
682
753
|
function footprintPoints(bounds, ends, cast) {
|
|
683
754
|
if (ends) {
|
|
@@ -697,18 +768,12 @@ function footprintPoints(bounds, ends, cast) {
|
|
|
697
768
|
function createInsert(opts) {
|
|
698
769
|
const up = opts.up ?? DEFAULT_UP;
|
|
699
770
|
const minExtent = opts.minExtent ?? 0.25;
|
|
700
|
-
const
|
|
771
|
+
const { u, v } = groundAxes(up);
|
|
701
772
|
return {
|
|
702
773
|
commit(bounds, extras, ends) {
|
|
703
774
|
const vp = opts.viewport();
|
|
704
|
-
const
|
|
705
|
-
const ray = rayAt(vp, p);
|
|
706
|
-
const t = ray && intersectRayPlane(ray, up, 0);
|
|
707
|
-
return ray && t !== null ? pointOnRay(ray, t) : null;
|
|
708
|
-
};
|
|
709
|
-
const hits = footprintPoints(bounds, ends, cast);
|
|
775
|
+
const hits = footprintPoints(bounds, ends, (p) => groundHit(vp, p, up));
|
|
710
776
|
if (hits.length === 0) return null;
|
|
711
|
-
const [u, v] = axes;
|
|
712
777
|
let minU = Infinity, maxU = -Infinity, minV = Infinity, maxV = -Infinity;
|
|
713
778
|
for (const h of hits) {
|
|
714
779
|
minU = Math.min(minU, h[u]);
|
|
@@ -730,7 +795,6 @@ function createInsert(opts) {
|
|
|
730
795
|
}
|
|
731
796
|
};
|
|
732
797
|
}
|
|
733
|
-
var GROUND_NORMAL = { x: 0, y: 0, z: 1 };
|
|
734
798
|
function paneCorners(viewport) {
|
|
735
799
|
const { x, y, width, height } = rectOf(viewport);
|
|
736
800
|
return [
|
|
@@ -756,13 +820,24 @@ function frustumOf(viewport) {
|
|
|
756
820
|
far: at(camera.far)
|
|
757
821
|
};
|
|
758
822
|
}
|
|
759
|
-
function
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
823
|
+
function footprint(f, up, axes) {
|
|
824
|
+
const edges = [];
|
|
825
|
+
for (let i = 0; i < 4; i++) {
|
|
826
|
+
const j = (i + 1) % 4;
|
|
827
|
+
edges.push([f.near[i], f.near[j]], [f.far[i], f.far[j]], [f.near[i], f.far[i]]);
|
|
828
|
+
}
|
|
829
|
+
const hits = [];
|
|
830
|
+
for (const [a, b] of edges) {
|
|
831
|
+
const ha = dot(a, up), hb = dot(b, up);
|
|
832
|
+
if (ha > 0 && hb > 0 || ha < 0 && hb < 0 || ha === hb) continue;
|
|
833
|
+
const hit = toAimPoint(add(a, scale(sub(b, a), ha / (ha - hb))), axes);
|
|
834
|
+
hits.push({ x: hit.x, y: hit.y });
|
|
835
|
+
}
|
|
836
|
+
if (hits.length < 3) return null;
|
|
837
|
+
const cx = hits.reduce((s, p) => s + p.x, 0) / hits.length;
|
|
838
|
+
const cy = hits.reduce((s, p) => s + p.y, 0) / hits.length;
|
|
839
|
+
const around = hits.map((p) => ({ p, a: Math.atan2(p.y - cy, p.x - cx) })).sort((u, v) => u.a - v.a).map((u) => u.p).filter((p, i, all) => i === 0 || Math.hypot(p.x - all[i - 1].x, p.y - all[i - 1].y) > 1e-9);
|
|
840
|
+
return around.length >= 3 ? around : null;
|
|
766
841
|
}
|
|
767
842
|
function projectAll(space, points) {
|
|
768
843
|
const out = [];
|
|
@@ -773,7 +848,9 @@ function projectAll(space, points) {
|
|
|
773
848
|
}
|
|
774
849
|
return out;
|
|
775
850
|
}
|
|
776
|
-
function aimOf3d(viewport, viewId) {
|
|
851
|
+
function aimOf3d(viewport, viewId, opts = {}) {
|
|
852
|
+
const up = opts.up ?? DEFAULT_UP;
|
|
853
|
+
const axes = groundAxes(up);
|
|
777
854
|
const extent = frustumOf(viewport);
|
|
778
855
|
return {
|
|
779
856
|
viewId,
|
|
@@ -781,54 +858,113 @@ function aimOf3d(viewport, viewId) {
|
|
|
781
858
|
extent,
|
|
782
859
|
viewport,
|
|
783
860
|
outlineIn(space) {
|
|
784
|
-
if (space.kind === "3d") return projectAll(space, extent.far);
|
|
785
|
-
const onGround =
|
|
786
|
-
|
|
787
|
-
const hit = groundHit(viewport, c);
|
|
788
|
-
if (!hit) return null;
|
|
789
|
-
onGround.push(hit);
|
|
790
|
-
}
|
|
791
|
-
return projectAll(space, onGround);
|
|
861
|
+
if (space.kind === "3d") return projectAll(space, extent.far.map((p) => toAimPoint(p, axes)));
|
|
862
|
+
const onGround = footprint(extent, up, axes);
|
|
863
|
+
return onGround && projectAll(space, onGround);
|
|
792
864
|
},
|
|
793
865
|
pannedIn(space, from, to) {
|
|
794
866
|
const a = space.unproject(from);
|
|
795
867
|
const b = space.unproject(to);
|
|
796
868
|
if (!a || !b) return null;
|
|
797
869
|
const { camera } = viewport;
|
|
798
|
-
const target = add(camera.target, sub(
|
|
799
|
-
return aimOf3d({ ...viewport, camera: { ...camera, target } }, viewId);
|
|
870
|
+
const target = add(camera.target, sub(fromAimPoint(b, axes), fromAimPoint(a, axes)));
|
|
871
|
+
return aimOf3d({ ...viewport, camera: { ...camera, target } }, viewId, opts);
|
|
800
872
|
}
|
|
801
873
|
};
|
|
802
874
|
}
|
|
803
|
-
function aimSpace3d(viewport, viewId) {
|
|
875
|
+
function aimSpace3d(viewport, viewId, opts = {}) {
|
|
876
|
+
const up = opts.up ?? DEFAULT_UP;
|
|
877
|
+
const axes = groundAxes(up);
|
|
804
878
|
const { x: ox, y: oy } = rectOf(viewport);
|
|
805
879
|
return {
|
|
806
880
|
viewId,
|
|
807
881
|
kind: "3d",
|
|
808
882
|
size: { width: viewport.width, height: viewport.height },
|
|
809
883
|
project(p) {
|
|
810
|
-
const s = projectPoint(
|
|
884
|
+
const s = projectPoint(fromAimPoint(p, axes), viewport);
|
|
811
885
|
return s && { x: s.x - ox, y: s.y - oy };
|
|
812
886
|
},
|
|
813
887
|
unproject(px) {
|
|
814
|
-
|
|
888
|
+
const hit = groundHit(viewport, { x: px.x + ox, y: px.y + oy }, up);
|
|
889
|
+
return hit && toAimPoint(hit, axes);
|
|
815
890
|
}
|
|
816
891
|
};
|
|
817
892
|
}
|
|
818
893
|
function cameraOfAim3d(aim) {
|
|
819
894
|
return aim.viewport.camera;
|
|
820
895
|
}
|
|
896
|
+
var CACHE2 = /* @__PURE__ */ new WeakMap();
|
|
897
|
+
function nodesOf(world) {
|
|
898
|
+
return world.scene.renderOrderNodes();
|
|
899
|
+
}
|
|
900
|
+
function boxValid2({ min, max }) {
|
|
901
|
+
return Number.isFinite(min.x) && Number.isFinite(min.y) && Number.isFinite(min.z) && Number.isFinite(max.x) && Number.isFinite(max.y) && Number.isFinite(max.z) && min.x <= max.x && min.y <= max.y && min.z <= max.z;
|
|
902
|
+
}
|
|
903
|
+
function build2(world) {
|
|
904
|
+
const nodes = nodesOf(world);
|
|
905
|
+
const boxes = nodes.map(worldBoxes(world, "document"));
|
|
906
|
+
const entries = [];
|
|
907
|
+
const always = [];
|
|
908
|
+
boxes.forEach((box, i) => {
|
|
909
|
+
if (box === null) return;
|
|
910
|
+
const { min, max } = box;
|
|
911
|
+
if (boxValid2(box)) entries.push([i, [min.x, min.y, min.z, max.x, max.y, max.z]]);
|
|
912
|
+
else always.push(i);
|
|
913
|
+
});
|
|
914
|
+
return { ids: nodes.map((n) => n.id), boxes, tree: createBvh(3, entries), always };
|
|
915
|
+
}
|
|
916
|
+
function current(world) {
|
|
917
|
+
let cache = CACHE2.get(world);
|
|
918
|
+
if (cache === void 0) CACHE2.set(world, cache = createSettledCache());
|
|
919
|
+
return cache.read([world.scene.getVersion(), world.bounds], () => build2(world));
|
|
920
|
+
}
|
|
921
|
+
function padQuery(box) {
|
|
922
|
+
let m = 0;
|
|
923
|
+
for (const v of box) if (Number.isFinite(v)) m = Math.max(m, Math.abs(v));
|
|
924
|
+
const e = 1e-9 * (1 + m);
|
|
925
|
+
return [box[0] - e, box[1] - e, box[2] - e, box[3] + e, box[4] + e, box[5] + e];
|
|
926
|
+
}
|
|
927
|
+
function snapBoxes(world, exclude, near) {
|
|
928
|
+
const excluded = withDescendants(world.scene, exclude);
|
|
929
|
+
const index = near.every((b) => Array.prototype.every.call(b, (v) => !Number.isNaN(v))) ? current(world) : null;
|
|
930
|
+
if (index === null) {
|
|
931
|
+
const boxOf = worldBoxes(world, "document");
|
|
932
|
+
const out2 = [];
|
|
933
|
+
for (const node of nodesOf(world)) {
|
|
934
|
+
const box = excluded.has(node.id) ? null : boxOf(node);
|
|
935
|
+
if (box) out2.push(box);
|
|
936
|
+
}
|
|
937
|
+
return out2;
|
|
938
|
+
}
|
|
939
|
+
const found = new Set(index.always);
|
|
940
|
+
for (const box of near) index.tree.queryBox(box, (i) => {
|
|
941
|
+
found.add(i);
|
|
942
|
+
});
|
|
943
|
+
const out = [];
|
|
944
|
+
for (const i of [...found].sort((a, b) => a - b)) {
|
|
945
|
+
if (!excluded.has(index.ids[i])) out.push(index.boxes[i]);
|
|
946
|
+
}
|
|
947
|
+
return out;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/snap/sources.ts
|
|
821
951
|
var AXES = ["x", "y", "z"];
|
|
822
952
|
var BITS = [1, 2, 4];
|
|
823
953
|
var UNIT = { x: { x: 1, y: 0, z: 0 }, y: { x: 0, y: 1, z: 0 }, z: { x: 0, y: 0, z: 1 } };
|
|
824
954
|
var ACROSS = { x: [UNIT.y, UNIT.z], y: [UNIT.x, UNIT.z], z: [UNIT.x, UNIT.y] };
|
|
825
|
-
function
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
955
|
+
function boxesNear(world, q, r) {
|
|
956
|
+
return snapBoxes(world, q.exclude, q.probes.map(({ at: p }) => padQuery([p.x - r, p.y - r, p.z - r, p.x + r, p.y + r, p.z + r])));
|
|
957
|
+
}
|
|
958
|
+
function boxesInSlabs(world, q, r) {
|
|
959
|
+
const slabs = q.probes.flatMap(({ at: p }) => AXES.map((axis) => {
|
|
960
|
+
const lo = [-Infinity, -Infinity, -Infinity];
|
|
961
|
+
const hi = [Infinity, Infinity, Infinity];
|
|
962
|
+
const k = AXES.indexOf(axis);
|
|
963
|
+
lo[k] = p[axis] - r;
|
|
964
|
+
hi[k] = p[axis] + r;
|
|
965
|
+
return padQuery([...lo, ...hi]);
|
|
966
|
+
}));
|
|
967
|
+
return snapBoxes(world, q.exclude, slabs);
|
|
832
968
|
}
|
|
833
969
|
function distanceToBox(p, box) {
|
|
834
970
|
const d = (a) => Math.max(box.min[a] - p[a], 0, p[a] - box.max[a]);
|
|
@@ -842,7 +978,7 @@ function boxSnapSource(world) {
|
|
|
842
978
|
roles: ["geometry", "pointer"],
|
|
843
979
|
*collect(q) {
|
|
844
980
|
const r = q.reachWorld(q.reachPx);
|
|
845
|
-
for (const box of
|
|
981
|
+
for (const box of boxesNear(world, q, r)) {
|
|
846
982
|
for (const p of q.probes) {
|
|
847
983
|
if (distanceToBox(p.at, box) <= r) yield* boxLoci(p.id, p.at, box, r);
|
|
848
984
|
}
|
|
@@ -894,7 +1030,7 @@ function alignPlaneSource(world) {
|
|
|
894
1030
|
roles: ["box", "pointer"],
|
|
895
1031
|
*collect(q) {
|
|
896
1032
|
const r = q.reachWorld(q.reachPx);
|
|
897
|
-
for (const [axis, planes] of planesOf(
|
|
1033
|
+
for (const [axis, planes] of planesOf(boxesInSlabs(world, q, r))) {
|
|
898
1034
|
for (const [value, ends] of planes) {
|
|
899
1035
|
for (const p of q.probes) {
|
|
900
1036
|
if (Math.abs(p.at[axis] - value) > r) continue;
|
|
@@ -1127,6 +1263,6 @@ var kernel3dKitSettingsSchema = pref.group("3D camera", {
|
|
|
1127
1263
|
})
|
|
1128
1264
|
});
|
|
1129
1265
|
|
|
1130
|
-
export { DEFAULT_ORBIT_LIMITS, MAX_PITCH, UNIT_CUBE, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, homeAction, kernel3dKitSettingsSchema, ndcToScreen, orbitAction, orbitBy, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool };
|
|
1266
|
+
export { DEFAULT_ORBIT_LIMITS, MAX_PITCH, POSE3_COMPOSITION, UNIT_CUBE, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, homeAction, kernel3dKitSettingsSchema, ndcToScreen, orbitAction, orbitBy, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool, worldPoseOf };
|
|
1131
1267
|
//# sourceMappingURL=index.js.map
|
|
1132
1268
|
//# sourceMappingURL=index.js.map
|