@weasel-js/kernel3d 1.17.3 → 1.18.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.d.ts CHANGED
@@ -184,14 +184,14 @@ declare function rayThroughScreenPoint(point: {
184
184
  }, rect: ScreenBox, viewProjection: Mat4): Ray | null;
185
185
  /**
186
186
  * The screen rectangle a world-space box covers — the currency weasel's chrome
187
- * and `PoseDescriptor.getBounds` speak in. `null` when the box is entirely
188
- * behind the near plane.
187
+ * and `PoseDescriptor.getBounds` speak in. `null` when none of the box lies
188
+ * between the near and far planes, which is when the camera draws none of it.
189
189
  *
190
- * Each of the twelve edges is clipped against the near plane before it is
191
- * projected, rather than dropping the corners behind it. Dropping them reports
192
- * a box that is too small for anything straddling the near plane, and the error
193
- * grows as the camera moves in: a solid the pointer is inside of would outline
194
- * as a sliver, or as nothing.
190
+ * Each of the twelve edges is clipped against the near and far planes before
191
+ * it is projected, rather than dropping the corners behind the camera.
192
+ * Dropping them reports a box that is too small for anything straddling the
193
+ * near plane, and the error grows as the camera moves in: a solid the pointer
194
+ * is inside of would outline as a sliver, or as nothing.
195
195
  */
196
196
  declare function projectAabbToScreen(box: Aabb, viewProjection: Mat4, rect: ScreenBox): ScreenBox | null;
197
197
 
@@ -342,7 +342,7 @@ interface Footprint {
342
342
  /**
343
343
  * A drag becomes a footprint on the ground plane: its ends, or without them
344
344
  * its box's four corners, are cast onto the plane and the hit points give the
345
- * extent. What node that implies is the consumer's answer — the kernel does
345
+ * extent. One that misses the plane, past the horizon, declines the insert. What node that implies is the consumer's answer — the kernel does
346
346
  * not know what it is inserting. The depth the 2D contract cannot express
347
347
  * comes from the plane, not from the caller.
348
348
  */
package/dist/index.js CHANGED
@@ -139,9 +139,10 @@ function rayThroughScreenPoint(point, rect, viewProjection) {
139
139
  if (unit.x === 0 && unit.y === 0 && unit.z === 0) return null;
140
140
  return { origin: near, direction };
141
141
  }
142
- function nearSide(p) {
143
- return p[2] + p[3];
144
- }
142
+ var CLIP_SIDES = [
143
+ (p) => p[2] + p[3],
144
+ (p) => p[3] - p[2]
145
+ ];
145
146
  function lerpClip(a, b, t) {
146
147
  return [
147
148
  a[0] + (b[0] - a[0]) * t,
@@ -178,15 +179,24 @@ function projectAabbToScreen(box, viewProjection, rect) {
178
179
  for (let i = 0; i < 8; i++) {
179
180
  for (const bit of EDGE_BITS) {
180
181
  if (i & bit) continue;
181
- let a = corners[i];
182
- let b = corners[i | bit];
183
- const da = nearSide(a);
184
- const db = nearSide(b);
185
- if (da < 0 && db < 0) continue;
186
- if (da < 0) a = lerpClip(a, b, da / (da - db));
187
- else if (db < 0) b = lerpClip(b, a, db / (db - da));
188
- extend(a);
189
- extend(b);
182
+ const a = corners[i];
183
+ const b = corners[i | bit];
184
+ let t0 = 0;
185
+ let t1 = 1;
186
+ for (const side of CLIP_SIDES) {
187
+ const da = side(a);
188
+ const db = side(b);
189
+ if (da < 0 && db < 0) {
190
+ t0 = 1;
191
+ t1 = 0;
192
+ break;
193
+ }
194
+ if (da < 0) t0 = Math.max(t0, da / (da - db));
195
+ else if (db < 0) t1 = Math.min(t1, da / (da - db));
196
+ }
197
+ if (t0 > t1) continue;
198
+ extend(t0 === 0 ? a : lerpClip(a, b, t0));
199
+ extend(t1 === 1 ? b : lerpClip(a, b, t1));
190
200
  }
191
201
  }
192
202
  if (!any) return null;
@@ -272,10 +282,65 @@ function withDescendants(scene, ids) {
272
282
  }
273
283
  return out;
274
284
  }
285
+ function boxIndexable({ min, max }) {
286
+ 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;
287
+ }
275
288
  function pointBox(p) {
276
289
  return { min: p, max: p };
277
290
  }
278
291
 
292
+ // src/snap/boxIndex.ts
293
+ var CACHE = /* @__PURE__ */ new WeakMap();
294
+ function nodesOf(world) {
295
+ return world.scene.renderOrderNodes();
296
+ }
297
+ function build(world) {
298
+ const nodes = nodesOf(world);
299
+ const boxes = nodes.map(worldBoxes(world, "document"));
300
+ const entries = [];
301
+ const always = [];
302
+ boxes.forEach((box, i) => {
303
+ if (box === null) return;
304
+ const { min, max } = box;
305
+ if (boxIndexable(box)) entries.push([i, [min.x, min.y, min.z, max.x, max.y, max.z]]);
306
+ else always.push(i);
307
+ });
308
+ return { ids: nodes.map((n) => n.id), boxes, tree: createBvh(3, entries), always };
309
+ }
310
+ function current(world) {
311
+ let cache = CACHE.get(world);
312
+ if (cache === void 0) CACHE.set(world, cache = createSettledCache());
313
+ return cache.read([world.scene.getVersion(), world.bounds], () => build(world));
314
+ }
315
+ function padQuery(box) {
316
+ let m = 0;
317
+ for (const v of box) if (Number.isFinite(v)) m = Math.max(m, Math.abs(v));
318
+ const e = 1e-9 * (1 + m);
319
+ return [box[0] - e, box[1] - e, box[2] - e, box[3] + e, box[4] + e, box[5] + e];
320
+ }
321
+ function snapBoxes(world, exclude, near) {
322
+ const excluded = withDescendants(world.scene, exclude);
323
+ const index = near.every((b) => Array.prototype.every.call(b, (v) => !Number.isNaN(v))) ? current(world) : null;
324
+ if (index === null) {
325
+ const boxOf = worldBoxes(world, "document");
326
+ const out2 = [];
327
+ for (const node of nodesOf(world)) {
328
+ const box = excluded.has(node.id) ? null : boxOf(node);
329
+ if (box) out2.push(box);
330
+ }
331
+ return out2;
332
+ }
333
+ const found = new Set(index.always);
334
+ for (const box of near) index.tree.queryBox(box, (i) => {
335
+ found.add(i);
336
+ });
337
+ const out = [];
338
+ for (const i of [...found].sort((a, b) => a - b)) {
339
+ if (!excluded.has(index.ids[i])) out.push(index.boxes[i]);
340
+ }
341
+ return out;
342
+ }
343
+
279
344
  // src/pickIndex.ts
280
345
  function screenBoxOf(world, id, viewport) {
281
346
  const node = world.scene.get(id);
@@ -283,9 +348,6 @@ function screenBoxOf(world, id, viewport) {
283
348
  const box = node && pose && boxAt(world, node, pose);
284
349
  return box ? projectAabbToScreen(box, viewProjectionOf(viewport), rectOf(viewport)) : null;
285
350
  }
286
- function boxValid({ min, max }) {
287
- 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;
288
- }
289
351
  var NDC_SLACK = 1e-9;
290
352
  function sidePlanes(vp, rect) {
291
353
  const m = viewProjectionOf(vp);
@@ -355,27 +417,15 @@ var BuiltIndex = class {
355
417
  return hits;
356
418
  }
357
419
  };
358
- var CACHE = /* @__PURE__ */ new WeakMap();
359
- function padded({ min, max }) {
360
- const m = Math.max(
361
- Math.abs(min.x),
362
- Math.abs(min.y),
363
- Math.abs(min.z),
364
- Math.abs(max.x),
365
- Math.abs(max.y),
366
- Math.abs(max.z)
367
- );
368
- const e = 1e-9 * (1 + m);
369
- return [min.x - e, min.y - e, min.z - e, max.x + e, max.y + e, max.z + e];
370
- }
371
- function build(world, order) {
420
+ var CACHE2 = /* @__PURE__ */ new WeakMap();
421
+ function build2(world, order) {
372
422
  const boxOf = worldBoxes(world, "shown");
373
423
  const boxes = order.map(boxOf);
374
424
  const entries = [];
375
425
  const always = [];
376
426
  boxes.forEach((b, i) => {
377
427
  if (b === null) return;
378
- if (boxValid(b)) entries.push([i, padded(b)]);
428
+ if (boxIndexable(b)) entries.push([i, padQuery([b.min.x, b.min.y, b.min.z, b.max.x, b.max.y, b.max.z])]);
379
429
  else always.push(i);
380
430
  });
381
431
  return new BuiltIndex(order, boxes, createBvh(3, entries), always);
@@ -385,12 +435,12 @@ function worldPickIndex(world) {
385
435
  const version = scene.getVersion();
386
436
  const overrides = scene.overrides.getGeneration();
387
437
  const order = scene.renderOrderNodes();
388
- const hit = CACHE.get(world);
438
+ const hit = CACHE2.get(world);
389
439
  if (hit && hit.version === version && hit.overrides === overrides && hit.bounds === world.bounds && hit.order === order) {
390
440
  return hit.index;
391
441
  }
392
- const index = build(world, order);
393
- CACHE.set(world, { version, overrides, bounds: world.bounds, order, index });
442
+ const index = build2(world, order);
443
+ CACHE2.set(world, { version, overrides, bounds: world.bounds, order, index });
394
444
  return index;
395
445
  }
396
446
  var ZERO = { x: 0, y: 0, z: 0 };
@@ -544,7 +594,7 @@ function candidatesOf(inputs, sources, probes, delta0, vp, origin, up) {
544
594
  gesture: req.gesture,
545
595
  probes: offered,
546
596
  delta0,
547
- exclude: req.ids ?? [],
597
+ exclude: req.exclude ?? req.ids ?? [],
548
598
  ...req.ids ? { ids: req.ids } : {},
549
599
  ...req.parent !== void 0 ? { parent: req.parent } : {},
550
600
  ...origin ? { origin } : {},
@@ -760,7 +810,13 @@ function footprintPoints(bounds, ends, cast) {
760
810
  { x: bounds.x, y: bounds.y + bounds.height },
761
811
  { x: bounds.x + bounds.width, y: bounds.y + bounds.height }
762
812
  ];
763
- return corners.map(cast).filter((h) => h !== null);
813
+ const hits = [];
814
+ for (const corner of corners) {
815
+ const hit = cast(corner);
816
+ if (!hit) return [];
817
+ hits.push(hit);
818
+ }
819
+ return hits;
764
820
  }
765
821
  function createInsert(opts) {
766
822
  const up = opts.up ?? DEFAULT_UP;
@@ -891,61 +947,6 @@ function aimSpace3d(viewport, viewId, opts = {}) {
891
947
  function cameraOfAim3d(aim) {
892
948
  return aim.viewport.camera;
893
949
  }
894
- var CACHE2 = /* @__PURE__ */ new WeakMap();
895
- function nodesOf(world) {
896
- return world.scene.renderOrderNodes();
897
- }
898
- function boxValid2({ min, max }) {
899
- 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;
900
- }
901
- function build2(world) {
902
- const nodes = nodesOf(world);
903
- const boxes = nodes.map(worldBoxes(world, "document"));
904
- const entries = [];
905
- const always = [];
906
- boxes.forEach((box, i) => {
907
- if (box === null) return;
908
- const { min, max } = box;
909
- if (boxValid2(box)) entries.push([i, [min.x, min.y, min.z, max.x, max.y, max.z]]);
910
- else always.push(i);
911
- });
912
- return { ids: nodes.map((n) => n.id), boxes, tree: createBvh(3, entries), always };
913
- }
914
- function current(world) {
915
- let cache = CACHE2.get(world);
916
- if (cache === void 0) CACHE2.set(world, cache = createSettledCache());
917
- return cache.read([world.scene.getVersion(), world.bounds], () => build2(world));
918
- }
919
- function padQuery(box) {
920
- let m = 0;
921
- for (const v of box) if (Number.isFinite(v)) m = Math.max(m, Math.abs(v));
922
- const e = 1e-9 * (1 + m);
923
- return [box[0] - e, box[1] - e, box[2] - e, box[3] + e, box[4] + e, box[5] + e];
924
- }
925
- function snapBoxes(world, exclude, near) {
926
- const excluded = withDescendants(world.scene, exclude);
927
- const index = near.every((b) => Array.prototype.every.call(b, (v) => !Number.isNaN(v))) ? current(world) : null;
928
- if (index === null) {
929
- const boxOf = worldBoxes(world, "document");
930
- const out2 = [];
931
- for (const node of nodesOf(world)) {
932
- const box = excluded.has(node.id) ? null : boxOf(node);
933
- if (box) out2.push(box);
934
- }
935
- return out2;
936
- }
937
- const found = new Set(index.always);
938
- for (const box of near) index.tree.queryBox(box, (i) => {
939
- found.add(i);
940
- });
941
- const out = [];
942
- for (const i of [...found].sort((a, b) => a - b)) {
943
- if (!excluded.has(index.ids[i])) out.push(index.boxes[i]);
944
- }
945
- return out;
946
- }
947
-
948
- // src/snap/sources.ts
949
950
  var AXES = ["x", "y", "z"];
950
951
  var BITS = [1, 2, 4];
951
952
  var UNIT = { x: { x: 1, y: 0, z: 0 }, y: { x: 0, y: 1, z: 0 }, z: { x: 0, y: 0, z: 1 } };
@@ -1467,15 +1468,32 @@ var DOLLY_RATE = 15e-4;
1467
1468
  function screenDelta(move) {
1468
1469
  return move.drag?.screenDelta ?? move.drag?.delta;
1469
1470
  }
1471
+ function cameraStepper(dep, zero, relative, step) {
1472
+ let origin = dep?.get();
1473
+ let from = zero;
1474
+ let reached = zero;
1475
+ let written = origin;
1476
+ let prior = origin;
1477
+ return (at) => {
1478
+ if (!dep || !origin) return;
1479
+ const now = dep.get();
1480
+ if (now !== written && now !== prior) {
1481
+ origin = now;
1482
+ from = reached;
1483
+ }
1484
+ prior = now;
1485
+ written = step(origin, relative(at, from), dep);
1486
+ reached = at;
1487
+ dep.set(written);
1488
+ };
1489
+ }
1470
1490
  function dragCamera(ctx, kind, step) {
1471
- const dep = ctx.deps.camera3d;
1472
- const origin = dep?.get();
1491
+ const apply = cameraStepper(ctx.deps.camera3d, { x: 0, y: 0 }, (a, b) => ({ x: a.x - b.x, y: a.y - b.y }), step);
1473
1492
  return {
1474
1493
  kind,
1475
1494
  onMove(move) {
1476
1495
  const delta = screenDelta(move);
1477
- if (!dep || !origin || !delta) return;
1478
- dep.set(step(origin, delta, dep));
1496
+ if (delta) apply(delta);
1479
1497
  }
1480
1498
  };
1481
1499
  }
@@ -1552,16 +1570,18 @@ var pinchDollyAction = {
1552
1570
  timing: "ongoing",
1553
1571
  start(ctx) {
1554
1572
  const dep = ctx.deps.camera3d;
1555
- const origin = dep?.get();
1556
1573
  const startSpread = ctx.multiTouch?.spread ?? 0;
1557
1574
  const at = ctx.multiTouch?.centroid;
1575
+ const apply = cameraStepper(dep, 1, (a, b) => a / b, (origin, factor, d) => {
1576
+ const pane = d.size();
1577
+ return dollyToward(origin, factor, paneAnchor(at, d, pane), pane, d.limits?.());
1578
+ });
1558
1579
  return {
1559
1580
  kind: "pinch",
1560
1581
  onMove(move) {
1561
1582
  const spread = move.multiTouch?.pinch?.currentSpread ?? 0;
1562
- if (!dep || !origin || !(startSpread > 0) || !(spread > 0)) return;
1563
- const pane = dep.size();
1564
- dep.set(dollyToward(origin, startSpread / spread, paneAnchor(at, dep, pane), pane, dep.limits?.()));
1583
+ if (!(startSpread > 0) || !(spread > 0)) return;
1584
+ apply(startSpread / spread);
1565
1585
  }
1566
1586
  };
1567
1587
  }