@weasel-js/kernel3d 1.13.0-rc.0 → 1.14.0

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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, invert, transformPoint, normalize, sub, transformPoint4, distance, dot, len, intersectRayPlane, pointOnRay, cross, intersectRayAabb, aabbOfPoints } from '@weasel-js/geom/3d';
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/world.ts
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 boundsOfNode(world, node) {
201
- return world.bounds ? world.bounds(node) : aabbOfPose(node.pose);
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
- if (!node) return null;
208
- return projectAabbToScreen(
209
- boundsOfNode(world, node),
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(world, order, boxes, tree, always) {
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, boxes[i].min, boxes[i].max);
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 id = this.order[i].id;
283
- const box = screenBoxOf(this.world, id, vp);
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 boxes = order.map((node) => boundsOfNode(world, node));
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(world, order, boxes, createBvh(3, entries), always);
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
- let anchor = null;
343
- for (const id of ids) {
344
- const node = world.scene.get(id);
345
- if (!node) continue;
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 && anchor ? { box, anchor } : null;
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.y !== 0 ? "y" : up.x !== 0 ? "x" : "z";
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 build2(worldBox) {
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 = build2((pose) => boundsOfNode(world, { ...node, pose }));
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
- /** Screen-space deltas resolve against the plane the node already sits on. */
649
- translate(pose, dx, dy) {
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
- if (!vp) return pose;
652
- const box = boundsIn(vp, pose);
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 to = { x: from.x + dx, y: from.y + dy };
656
- const offset = dot(up, pose.position);
657
- const ray = rayAt(vp, to);
658
- if (!ray) return pose;
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 build2((pose) => aabbOfPose(pose));
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 axes = up.y !== 0 ? ["x", "z"] : up.x !== 0 ? ["y", "z"] : ["x", "y"];
771
+ const { u, v } = groundAxes(up);
701
772
  return {
702
773
  commit(bounds, extras, ends) {
703
774
  const vp = opts.viewport();
704
- const cast = (p) => {
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 groundHit(viewport, point) {
760
- const ray = rayAt(viewport, point);
761
- const t = ray && intersectRayPlane(ray, GROUND_NORMAL, 0);
762
- return ray && t !== null ? pointOnRay(ray, t) : null;
763
- }
764
- function toZ(p) {
765
- return { x: p.x, y: p.y, z: p.z ?? 0 };
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
- for (const c of paneCorners(viewport)) {
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(toZ(b), toZ(a)));
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(toZ(p), viewport);
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
- return groundHit(viewport, { x: px.x + ox, y: px.y + oy });
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 boxesOf(world, q) {
826
- const excluded = new Set(q.exclude);
827
- const out = [];
828
- for (const node of world.scene.renderOrderNodes()) {
829
- if (!excluded.has(node.id)) out.push(boundsOfNode(world, node));
830
- }
831
- return out;
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 boxesOf(world, q)) {
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(boxesOf(world, q))) {
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