@viamrobotics/motion-tools 1.32.0 → 1.33.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.
Files changed (132) hide show
  1. package/dist/components/App.svelte +17 -11
  2. package/dist/components/App.svelte.d.ts +14 -7
  3. package/dist/components/Entities/Entities.svelte +18 -25
  4. package/dist/components/Entities/Entities.svelte.d.ts +2 -17
  5. package/dist/components/Entities/Label.svelte +79 -13
  6. package/dist/components/Entities/Label.svelte.d.ts +2 -1
  7. package/dist/components/Entities/Labels.svelte +36 -0
  8. package/dist/components/Entities/Labels.svelte.d.ts +3 -0
  9. package/dist/components/Entities/LineDots.svelte +8 -3
  10. package/dist/components/Entities/labelLayout/applyTeleports.d.ts +9 -0
  11. package/dist/components/Entities/labelLayout/applyTeleports.js +39 -0
  12. package/dist/components/Entities/labelLayout/buildNeighborhood.d.ts +8 -0
  13. package/dist/components/Entities/labelLayout/buildNeighborhood.js +26 -0
  14. package/dist/components/Entities/labelLayout/cameraHash.d.ts +8 -0
  15. package/dist/components/Entities/labelLayout/cameraHash.js +25 -0
  16. package/dist/components/Entities/labelLayout/cost.d.ts +44 -0
  17. package/dist/components/Entities/labelLayout/cost.js +126 -0
  18. package/dist/components/Entities/labelLayout/createLabelLayout.d.ts +27 -0
  19. package/dist/components/Entities/labelLayout/createLabelLayout.js +194 -0
  20. package/dist/components/Entities/labelLayout/geometry.d.ts +20 -0
  21. package/dist/components/Entities/labelLayout/geometry.js +151 -0
  22. package/dist/components/Entities/labelLayout/labelStore.svelte.d.ts +17 -0
  23. package/dist/components/Entities/labelLayout/labelStore.svelte.js +28 -0
  24. package/dist/components/Entities/labelLayout/measure.d.ts +13 -0
  25. package/dist/components/Entities/labelLayout/measure.js +42 -0
  26. package/dist/components/Entities/labelLayout/slots.d.ts +11 -0
  27. package/dist/components/Entities/labelLayout/slots.js +47 -0
  28. package/dist/components/Entities/labelLayout/solve.d.ts +11 -0
  29. package/dist/components/Entities/labelLayout/solve.js +93 -0
  30. package/dist/components/Entities/labelLayout/spatialHash.d.ts +15 -0
  31. package/dist/components/Entities/labelLayout/spatialHash.js +53 -0
  32. package/dist/components/Entities/labelLayout/types.d.ts +105 -0
  33. package/dist/components/Entities/labelLayout/types.js +19 -0
  34. package/dist/components/Entities/labelLayout/writeBack.d.ts +20 -0
  35. package/dist/components/Entities/labelLayout/writeBack.js +51 -0
  36. package/dist/components/Scene.svelte +42 -48
  37. package/dist/components/SceneProviders.svelte +0 -3
  38. package/dist/components/SelectedTransformControls.svelte +65 -47
  39. package/dist/components/overlay/Details.svelte +198 -224
  40. package/dist/components/overlay/Details.svelte.d.ts +1 -1
  41. package/dist/components/overlay/Popover.svelte +6 -4
  42. package/dist/components/overlay/Popover.svelte.d.ts +6 -2
  43. package/dist/components/overlay/dashboard/Button.svelte +7 -2
  44. package/dist/components/overlay/dashboard/Button.svelte.d.ts +2 -1
  45. package/dist/components/overlay/details/AxesHelperDetails.svelte +32 -0
  46. package/dist/components/overlay/details/AxesHelperDetails.svelte.d.ts +7 -0
  47. package/dist/components/overlay/details/ColorDetails.svelte +35 -0
  48. package/dist/components/overlay/details/ColorDetails.svelte.d.ts +7 -0
  49. package/dist/components/overlay/details/GeometryDetails.svelte +104 -0
  50. package/dist/components/overlay/details/GeometryDetails.svelte.d.ts +7 -0
  51. package/dist/components/overlay/details/LineDetails/LineDetails.svelte +196 -0
  52. package/dist/components/overlay/details/LineDetails/LineDetails.svelte.d.ts +7 -0
  53. package/dist/components/overlay/details/LineDetails/linePositions.d.ts +3 -0
  54. package/dist/components/overlay/details/LineDetails/linePositions.js +30 -0
  55. package/dist/components/overlay/details/OpacityDetails.svelte +44 -0
  56. package/dist/components/overlay/details/OpacityDetails.svelte.d.ts +7 -0
  57. package/dist/components/overlay/details/PoseDetails.svelte +189 -0
  58. package/dist/components/overlay/details/PoseDetails.svelte.d.ts +14 -0
  59. package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
  60. package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
  61. package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
  62. package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
  63. package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
  64. package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
  65. package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
  66. package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
  67. package/dist/components/overlay/settings/Settings.svelte +27 -312
  68. package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
  69. package/dist/components/overlay/settings/Tabs.svelte +5 -3
  70. package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
  71. package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
  72. package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
  73. package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
  74. package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
  75. package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
  76. package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
  77. package/dist/components/overlay/widgets/FramePov.svelte +1 -12
  78. package/dist/ecs/traits.d.ts +1 -1
  79. package/dist/ecs/traits.js +1 -1
  80. package/dist/hooks/useWorldState.svelte.js +39 -50
  81. package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
  82. package/dist/plugins/XR/DebugPanel.svelte +29 -0
  83. package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
  84. package/dist/plugins/XR/OriginMarker.svelte +341 -0
  85. package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
  86. package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
  87. package/dist/plugins/XR/WristDisplay.svelte +60 -0
  88. package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
  89. package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
  90. package/dist/plugins/XR/XRPlugins.svelte +9 -0
  91. package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
  92. package/dist/plugins/XR/XRSettings.svelte +240 -0
  93. package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
  94. package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
  95. package/dist/plugins/XR/debug.svelte.d.ts +7 -0
  96. package/dist/plugins/XR/debug.svelte.js +13 -0
  97. package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
  98. package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
  99. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
  100. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
  101. package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
  102. package/dist/plugins/XR/useOrigin.svelte.js +50 -0
  103. package/dist/plugins/index.d.ts +2 -0
  104. package/dist/plugins/index.js +2 -0
  105. package/dist/three/OBBHelper.js +1 -0
  106. package/package.json +3 -1
  107. package/dist/components/xr/OriginMarker.svelte +0 -151
  108. package/dist/components/xr/XRControllerSettings.svelte +0 -242
  109. package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
  110. package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
  111. package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
  112. package/dist/components/xr/useOrigin.svelte.js +0 -27
  113. /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
  114. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
  115. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
  116. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
  117. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
  118. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
  119. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
  120. /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
  121. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
  122. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
  123. /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
  124. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
  125. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
  126. /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
  127. /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
  128. /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
  129. /package/dist/{components/xr → plugins/XR}/math.js +0 -0
  130. /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
  131. /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
  132. /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Deterministic slot assignment via priority-ordered conflict-graph local search.
3
+ *
4
+ * Warm-started from each node's previously committed slot (set by the engine
5
+ * before this runs), it repeatedly moves the worst-conflict node to its lowest
6
+ * cost slot, re-scoring only the affected neighbourhood, until no node has a
7
+ * conflict or the move budget is spent. A best-total snapshot is restored at the
8
+ * end so a re-solve can never regress below where it started.
9
+ */
10
+ import type { LabelNode, SolverConfig } from './types';
11
+ export declare const solve: (nodes: LabelNode[], config: SolverConfig, bestSnap: Int16Array) => void;
@@ -0,0 +1,93 @@
1
+ import { evalConflict, placementBias, W } from './cost';
2
+ /** Conflicts below this are treated as resolved (floating-point slack). */
3
+ const RESOLVED = 0.5;
4
+ /**
5
+ * The slot that minimizes conflict (crossings/overlaps), breaking ties by the
6
+ * tidiness bias. Selection and the caller's acceptance gate share the conflict
7
+ * objective, so a node is never locked at a placement another slot would
8
+ * improve. The baseCost early-out applies only once conflict is already
9
+ * resolved — while a node still conflicts, every slot is considered.
10
+ */
11
+ const bestSlot = (node, config) => {
12
+ const neighbors = node.neighbors;
13
+ let index = node.slotIndex;
14
+ let conflict = evalConflict(node, node.slotIndex, neighbors, config);
15
+ let bias = placementBias(node, node.slotIndex);
16
+ for (let s = 0; s < node.slots.length; s++) {
17
+ if (s === node.slotIndex)
18
+ continue;
19
+ // Slots are sorted ascending by baseCost; once conflict is resolved only
20
+ // tidiness remains, so no farther slot can beat the incumbent's bias.
21
+ if (conflict <= RESOLVED && node.slots[s].baseCost - W.stick >= bias)
22
+ break;
23
+ const c = evalConflict(node, s, neighbors, config);
24
+ const b = placementBias(node, s);
25
+ if (c < conflict - 1e-6 || (c <= conflict + 1e-6 && b < bias)) {
26
+ index = s;
27
+ conflict = c;
28
+ bias = b;
29
+ }
30
+ }
31
+ return { index, conflict };
32
+ };
33
+ export const solve = (nodes, config, bestSnap) => {
34
+ const n = nodes.length;
35
+ if (n === 0)
36
+ return;
37
+ for (const node of nodes)
38
+ node.prevSlotIndex = node.slotIndex;
39
+ const order = nodes.toSorted((a, b) => b.w * b.h - a.w * a.h || a.slots.length - b.slots.length || a.idHash - b.idHash);
40
+ let total = 0;
41
+ for (let i = 0; i < n; i++) {
42
+ const node = nodes[i];
43
+ node.conflict = evalConflict(node, node.slotIndex, node.neighbors, config);
44
+ node.locked = false;
45
+ total += node.conflict;
46
+ }
47
+ let bestE = total;
48
+ for (let i = 0; i < n; i++)
49
+ bestSnap[i] = nodes[i].slotIndex;
50
+ let budget = config.polishBudget;
51
+ while (budget > 0) {
52
+ let worst;
53
+ let worstConflict = RESOLVED;
54
+ for (let i = 0; i < n; i++) {
55
+ const node = order[i];
56
+ if (!node.locked && node.conflict > worstConflict) {
57
+ worstConflict = node.conflict;
58
+ worst = node;
59
+ }
60
+ }
61
+ if (!worst)
62
+ break;
63
+ const result = bestSlot(worst, config);
64
+ if (result.index !== worst.slotIndex && result.conflict < worst.conflict - RESOLVED) {
65
+ total += result.conflict - worst.conflict;
66
+ worst.slotIndex = result.index;
67
+ worst.conflict = result.conflict;
68
+ budget--;
69
+ // Moving `worst` changes the pairwise cost of its neighbors only.
70
+ for (const m of worst.neighbors) {
71
+ const before = m.conflict;
72
+ m.conflict = evalConflict(m, m.slotIndex, m.neighbors, config);
73
+ total += m.conflict - before;
74
+ m.locked = false;
75
+ }
76
+ if (total < bestE) {
77
+ bestE = total;
78
+ for (let i = 0; i < n; i++)
79
+ bestSnap[i] = nodes[i].slotIndex;
80
+ }
81
+ }
82
+ else {
83
+ worst.locked = true;
84
+ }
85
+ }
86
+ for (let i = 0; i < n; i++) {
87
+ const node = nodes[i];
88
+ node.slotIndex = bestSnap[i];
89
+ const slot = node.slots[node.slotIndex];
90
+ node.tx = node.ax + slot.dx;
91
+ node.ty = node.ay + slot.dy;
92
+ }
93
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Uniform grid over label anchors, used to prune the O(n^2) cost evaluation to a
3
+ * bounded neighborhood. The cell size is chosen so any two labels whose boxes
4
+ * could possibly interact share or border a cell, so scanning the 3x3 block
5
+ * around a node finds every candidate.
6
+ */
7
+ import type { LabelNode } from './types';
8
+ export declare class SpatialHash {
9
+ private cell;
10
+ private readonly buckets;
11
+ private static key;
12
+ build(nodes: LabelNode[], cell: number): void;
13
+ /** Nearest `max` nodes (by anchor distance) in the 3x3 cell block around `node`, excluding itself. */
14
+ queryNeighbors(node: LabelNode, max: number): LabelNode[];
15
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Uniform grid over label anchors, used to prune the O(n^2) cost evaluation to a
3
+ * bounded neighborhood. The cell size is chosen so any two labels whose boxes
4
+ * could possibly interact share or border a cell, so scanning the 3x3 block
5
+ * around a node finds every candidate.
6
+ */
7
+ const KEY_OFFSET = 2048;
8
+ const KEY_STRIDE = 4096;
9
+ export class SpatialHash {
10
+ cell = 1;
11
+ buckets = new Map();
12
+ static key(gx, gy) {
13
+ return (gx + KEY_OFFSET) * KEY_STRIDE + (gy + KEY_OFFSET);
14
+ }
15
+ build(nodes, cell) {
16
+ this.cell = Math.max(cell, 1);
17
+ this.buckets.clear();
18
+ for (const node of nodes) {
19
+ const k = SpatialHash.key(Math.floor(node.ax / this.cell), Math.floor(node.ay / this.cell));
20
+ const bucket = this.buckets.get(k);
21
+ if (bucket)
22
+ bucket.push(node);
23
+ else
24
+ this.buckets.set(k, [node]);
25
+ }
26
+ }
27
+ /** Nearest `max` nodes (by anchor distance) in the 3x3 cell block around `node`, excluding itself. */
28
+ queryNeighbors(node, max) {
29
+ const gx = Math.floor(node.ax / this.cell);
30
+ const gy = Math.floor(node.ay / this.cell);
31
+ const found = [];
32
+ for (let ox = -1; ox <= 1; ox++) {
33
+ for (let oy = -1; oy <= 1; oy++) {
34
+ const bucket = this.buckets.get(SpatialHash.key(gx + ox, gy + oy));
35
+ if (!bucket)
36
+ continue;
37
+ for (const other of bucket) {
38
+ if (other !== node)
39
+ found.push(other);
40
+ }
41
+ }
42
+ }
43
+ if (found.length <= max)
44
+ return found;
45
+ found.sort((a, b) => {
46
+ const da = (a.ax - node.ax) ** 2 + (a.ay - node.ay) ** 2;
47
+ const db = (b.ax - node.ax) ** 2 + (b.ay - node.ay) ** 2;
48
+ return da - db;
49
+ });
50
+ found.length = max;
51
+ return found;
52
+ }
53
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Shared types for the label layout engine.
3
+ *
4
+ * All spatial fields are in viewport pixels (one shared screen-space for every
5
+ * label) except where noted. Slots store dot-relative offsets so a camera pan
6
+ * never invalidates them.
7
+ */
8
+ /** Axis-aligned rectangle as center + half-extents (viewport px). */
9
+ export interface Rect {
10
+ cx: number;
11
+ cy: number;
12
+ hw: number;
13
+ hh: number;
14
+ }
15
+ /** Line segment (viewport px). */
16
+ export interface Segment {
17
+ x1: number;
18
+ y1: number;
19
+ x2: number;
20
+ y2: number;
21
+ }
22
+ /** A candidate placement for a label box, expressed as the box-center offset from the dot. */
23
+ export interface Slot {
24
+ /** Box-center offset from the dot, viewport px. */
25
+ dx: number;
26
+ dy: number;
27
+ /** Direction of the slot from the dot, radians. */
28
+ angle: number;
29
+ /** Distance from the dot to the box center, viewport px. */
30
+ radius: number;
31
+ /** Ring index (0 = innermost). */
32
+ ring: number;
33
+ /** Length-only cost (`W.len * radius`); slots are sorted ascending by this for early-out. */
34
+ baseCost: number;
35
+ }
36
+ export interface LabelNode {
37
+ /** Stable per-element id, assigned once. */
38
+ id: string;
39
+ /** FNV hash of `id`, used for deterministic tie-breaks and per-node slot phase. */
40
+ idHash: number;
41
+ ax: number;
42
+ ay: number;
43
+ dotR: number;
44
+ w: number;
45
+ h: number;
46
+ /** Per-island CSS scale (screenPx / localPx). */
47
+ scale: number;
48
+ /** Cached computed CSS width of the dot (local px); read once. */
49
+ cssDotW: number;
50
+ /** Dot center relative to the island origin, island-local px. Fixed CSS offset, measured once (NaN until then). */
51
+ dotLocalX: number;
52
+ dotLocalY: number;
53
+ slots: Slot[];
54
+ /** Hash of the box/dot geometry; slots regenerate only when this or `crowded` changes. */
55
+ geomKey: string;
56
+ crowded: boolean;
57
+ /** Committed slot (target). -1 before first placement. */
58
+ slotIndex: number;
59
+ /** Slot committed by the previous solve, for stickiness. */
60
+ prevSlotIndex: number;
61
+ cx: number;
62
+ cy: number;
63
+ tx: number;
64
+ ty: number;
65
+ settled: boolean;
66
+ /** Conflict cost of the current slot — the local-search loop key. */
67
+ conflict: number;
68
+ /** Locked at a local optimum for the current solve. */
69
+ locked: boolean;
70
+ /** Local cluster centroid (self + neighbors) for the outward-fan term. */
71
+ centroidX: number;
72
+ centroidY: number;
73
+ /** Anchor at the previous solve, for teleport detection. NaN before first solve. */
74
+ prevAx: number;
75
+ prevAy: number;
76
+ /** Pruned interaction set for the current solve (symmetric). */
77
+ neighbors: LabelNode[];
78
+ labelEl: HTMLElement;
79
+ textEl: HTMLElement;
80
+ dotEl: HTMLElement;
81
+ lineEl: SVGLineElement;
82
+ }
83
+ export interface SolverConfig {
84
+ /** Label-label clearance (viewport px). */
85
+ labelPadding: number;
86
+ /** Dot clearance (viewport px). */
87
+ dotPadding: number;
88
+ /** Candidate angles per ring (doubled for crowded nodes). */
89
+ anglesPerRing: number;
90
+ /** Ring radius multipliers for sparse nodes. */
91
+ ringRadii: number[];
92
+ /** Ring radius multipliers for crowded nodes (more escape room). */
93
+ ringRadiiCrowded: number[];
94
+ /** Max accepted local-search moves per solve. */
95
+ polishBudget: number;
96
+ /** Max neighbors considered per node (caps cost in dense clusters). */
97
+ maxNeighbors: number;
98
+ /** Distance (viewport px) under which a box is considered arrived. */
99
+ settleEps: number;
100
+ /** Neighbor count above which a node is "crowded". */
101
+ crowdedThreshold: number;
102
+ /** Fraction of the viewport diagonal that counts as a camera teleport. */
103
+ teleportFrac: number;
104
+ }
105
+ export declare const defaultSolverConfig: SolverConfig;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared types for the label layout engine.
3
+ *
4
+ * All spatial fields are in viewport pixels (one shared screen-space for every
5
+ * label) except where noted. Slots store dot-relative offsets so a camera pan
6
+ * never invalidates them.
7
+ */
8
+ export const defaultSolverConfig = {
9
+ labelPadding: 6,
10
+ dotPadding: 6,
11
+ anglesPerRing: 12,
12
+ ringRadii: [1, 1.55, 2.2],
13
+ ringRadiiCrowded: [1, 1.45, 1.95, 2.6],
14
+ polishBudget: 240,
15
+ maxNeighbors: 24,
16
+ settleEps: 0.4,
17
+ crowdedThreshold: 8,
18
+ teleportFrac: 0.5,
19
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Per-frame animation and DOM write-back. The solver produces a target box
3
+ * center; the animated center eases toward it (framerate-independent), and the
4
+ * eased position is written to the label as a scale-corrected local transform
5
+ * plus leader-line endpoints — the same coordinate handling the old engine used.
6
+ */
7
+ import type { LabelNode } from './types';
8
+ /**
9
+ * Ease `node`'s animated center toward its target. Returns true while still
10
+ * moving, false once arrived (and snapped). `delta` is seconds, pre-clamped by
11
+ * the engine so a long idle gap can't produce an overshoot.
12
+ */
13
+ export declare function lerpStep(node: LabelNode, delta: number, settleEps: number): boolean;
14
+ /**
15
+ * Write the eased position to the DOM. The label island is positioned at the dot
16
+ * by Threlte's <HTML>, so we work in island-local px: convert the viewport-space
17
+ * offset by the island's CSS scale, place the text box, and draw the leader from
18
+ * the dot's island-local position to the box center.
19
+ */
20
+ export declare const writeBack: (node: LabelNode) => void;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Per-frame animation and DOM write-back. The solver produces a target box
3
+ * center; the animated center eases toward it (framerate-independent), and the
4
+ * eased position is written to the label as a scale-corrected local transform
5
+ * plus leader-line endpoints — the same coordinate handling the old engine used.
6
+ */
7
+ /** Easing time constant (seconds): ~3-4 frames to arrive at 60fps. */
8
+ const TAU = 0.08;
9
+ /**
10
+ * Ease `node`'s animated center toward its target. Returns true while still
11
+ * moving, false once arrived (and snapped). `delta` is seconds, pre-clamped by
12
+ * the engine so a long idle gap can't produce an overshoot.
13
+ */
14
+ export function lerpStep(node, delta, settleEps) {
15
+ const dx = node.tx - node.cx;
16
+ const dy = node.ty - node.cy;
17
+ if (dx * dx + dy * dy <= settleEps * settleEps) {
18
+ node.cx = node.tx;
19
+ node.cy = node.ty;
20
+ node.settled = true;
21
+ return false;
22
+ }
23
+ const alpha = 1 - Math.exp(-delta / TAU);
24
+ node.cx += dx * alpha;
25
+ node.cy += dy * alpha;
26
+ node.settled = false;
27
+ return true;
28
+ }
29
+ /**
30
+ * Write the eased position to the DOM. The label island is positioned at the dot
31
+ * by Threlte's <HTML>, so we work in island-local px: convert the viewport-space
32
+ * offset by the island's CSS scale, place the text box, and draw the leader from
33
+ * the dot's island-local position to the box center.
34
+ */
35
+ export const writeBack = (node) => {
36
+ const inv = 1 / node.scale;
37
+ // Box-center offset from the dot, in island-local px.
38
+ const dx = (node.cx - node.ax) * inv;
39
+ const dy = (node.cy - node.ay) * inv;
40
+ const wL = node.w * inv;
41
+ const hL = node.h * inv;
42
+ // The dot may not sit at the island origin, so anchor everything at the dot's
43
+ // measured local position rather than assuming (0, 0).
44
+ const ox = node.dotLocalX;
45
+ const oy = node.dotLocalY;
46
+ node.textEl.style.transform = `translate(${ox + dx - wL / 2}px, ${oy + dy - hL / 2}px)`;
47
+ node.lineEl.setAttribute('x1', `${ox}`);
48
+ node.lineEl.setAttribute('y1', `${oy}`);
49
+ node.lineEl.setAttribute('x2', `${ox + dx}`);
50
+ node.lineEl.setAttribute('y2', `${oy + dy}`);
51
+ };
@@ -19,7 +19,6 @@
19
19
  import CameraControls from './CameraControls.svelte'
20
20
  import KeyboardBindings from './KeyboardBindings.svelte'
21
21
  import PointerMissBox from './PointerMissBox.svelte'
22
- import { useOrigin } from './xr/useOrigin.svelte'
23
22
 
24
23
  interface Props {
25
24
  children?: Snippet
@@ -29,7 +28,6 @@
29
28
 
30
29
  const threlte = useThrelte()
31
30
  const settings = useSettings()
32
- const origin = useOrigin()
33
31
 
34
32
  // @ts-expect-error This is for debugging
35
33
  globalThis.__threlte__ = threlte
@@ -51,7 +49,8 @@
51
49
  const bvhEnabled = $derived(
52
50
  settings.current.renderSubEntityHoverDetail ||
53
51
  settings.current.interactionMode === 'measure' ||
54
- settings.current.interactionMode === 'select'
52
+ settings.current.interactionMode === 'select' ||
53
+ settings.current.interactionMode === 'gizmo'
55
54
  )
56
55
 
57
56
  bvh(raycaster, () => ({ helper: false, enabled: bvhEnabled }))
@@ -66,48 +65,43 @@
66
65
  <KeyboardBindings />
67
66
  <Environment url={hdrImage} />
68
67
 
69
- <T.Group
70
- position={origin.position}
71
- rotation.z={origin.rotation}
72
- >
73
- <PointerMissBox />
74
- <SelectedTransformControls />
75
-
76
- {#if !$isPresenting && settings.current.grid}
77
- <Grid
78
- oncreate={(ref) => {
79
- const material = ref.material as ShaderMaterial
80
- material.depthWrite = false
81
- }}
82
- raycast={() => null}
83
- bvh={{ enabled: false }}
84
- plane="xy"
85
- sectionColor="#333"
86
- infiniteGrid
87
- renderOrder={999}
88
- cellSize={settings.current.gridCellSize}
89
- sectionSize={settings.current.gridSectionSize}
90
- fadeOrigin={[0, 0, 0]}
91
- fadeDistance={settings.current.gridFadeDistance}
92
- />
93
- {/if}
94
-
95
- {#if !$isPresenting}
96
- <Camera position={[3, 3, 3]}>
97
- <CameraControls />
98
- </Camera>
99
- {/if}
100
-
101
- <StaticGeometries />
102
- <Selected />
103
-
104
- <PortalTarget />
105
-
106
- <Entities />
107
- <BatchedArrows />
108
-
109
- {@render children?.()}
110
-
111
- <T.DirectionalLight position={[3, 3, 3]} />
112
- <T.AmbientLight />
113
- </T.Group>
68
+ <PointerMissBox />
69
+ <SelectedTransformControls />
70
+
71
+ {#if !$isPresenting && settings.current.grid}
72
+ <Grid
73
+ oncreate={(ref) => {
74
+ const material = ref.material as ShaderMaterial
75
+ material.depthWrite = false
76
+ }}
77
+ raycast={() => null}
78
+ bvh={{ enabled: false }}
79
+ plane="xy"
80
+ sectionColor="#333"
81
+ infiniteGrid
82
+ renderOrder={999}
83
+ cellSize={settings.current.gridCellSize}
84
+ sectionSize={settings.current.gridSectionSize}
85
+ fadeOrigin={[0, 0, 0]}
86
+ fadeDistance={settings.current.gridFadeDistance}
87
+ />
88
+ {/if}
89
+
90
+ {#if !$isPresenting}
91
+ <Camera position={[3, 3, 3]}>
92
+ <CameraControls />
93
+ </Camera>
94
+ {/if}
95
+
96
+ <StaticGeometries />
97
+ <Selected />
98
+
99
+ <PortalTarget />
100
+
101
+ <Entities />
102
+ <BatchedArrows />
103
+
104
+ {@render children?.()}
105
+
106
+ <T.DirectionalLight position={[3, 3, 3]} />
107
+ <T.AmbientLight />
@@ -21,8 +21,6 @@
21
21
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
22
22
  import { provideWorldStates } from '../hooks/useWorldState.svelte'
23
23
 
24
- import { provideOrigin } from './xr/useOrigin.svelte'
25
-
26
24
  interface Props {
27
25
  children: Snippet
28
26
  }
@@ -37,7 +35,6 @@
37
35
  provideHierarchy()
38
36
  provideWorldMatrix()
39
37
  provideInheritedInvisible()
40
- provideOrigin()
41
38
 
42
39
  provideRelationships()
43
40