@viamrobotics/motion-tools 1.34.5 → 1.34.7

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 (39) hide show
  1. package/dist/components/App.svelte +14 -2
  2. package/dist/components/App.svelte.d.ts +6 -2
  3. package/dist/components/AxesHelper.svelte +3 -1
  4. package/dist/components/AxesHelper.svelte.d.ts +1 -1
  5. package/dist/components/Entities/Arrows/Arrows.svelte +0 -9
  6. package/dist/components/Entities/AxesHelper.svelte +38 -0
  7. package/dist/components/Entities/AxesHelper.svelte.d.ts +8 -0
  8. package/dist/components/Entities/AxesHelpers.svelte +13 -0
  9. package/dist/{plugins/LLMSceneBuilder/AISettings.svelte.d.ts → components/Entities/AxesHelpers.svelte.d.ts} +6 -14
  10. package/dist/components/Entities/Boxes.svelte +290 -0
  11. package/dist/components/Entities/Boxes.svelte.d.ts +14 -0
  12. package/dist/components/Entities/Entities.svelte +10 -5
  13. package/dist/components/Entities/GLTF.svelte +0 -9
  14. package/dist/components/Entities/Line.svelte +0 -9
  15. package/dist/components/Entities/Mesh.svelte +5 -23
  16. package/dist/components/Entities/Points.svelte +1 -9
  17. package/dist/components/Entities/composeBoxMatrix.d.ts +12 -0
  18. package/dist/components/Entities/composeBoxMatrix.js +29 -0
  19. package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +27 -0
  20. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +87 -39
  21. package/dist/components/Scene.svelte +0 -1
  22. package/dist/components/Selected.svelte +14 -3
  23. package/dist/components/SelectedTransformControls.svelte +3 -5
  24. package/dist/components/overlay/Details.svelte +9 -4
  25. package/dist/hooks/plugins/bvh.svelte.js +9 -0
  26. package/dist/hooks/useConfigFrames.svelte.js +5 -3
  27. package/dist/hooks/useFragmentInfo.svelte.d.ts +24 -0
  28. package/dist/hooks/useFragmentInfo.svelte.js +89 -0
  29. package/dist/hooks/useFramelessComponents.svelte.js +3 -1
  30. package/dist/hooks/usePartConfig.svelte.d.ts +0 -6
  31. package/dist/hooks/usePartConfig.svelte.js +5 -60
  32. package/dist/plugins/Focus/FocusBox.svelte +12 -1
  33. package/dist/plugins/LLMSceneBuilder/frameDeltaAdapter.d.ts +9 -2
  34. package/dist/plugins/LLMSceneBuilder/frameDeltaAdapter.js +65 -10
  35. package/dist/plugins/LLMSceneBuilder/useSceneBuilder.svelte.js +29 -5
  36. package/dist/three/OBBHelper.d.ts +8 -1
  37. package/dist/three/OBBHelper.js +11 -1
  38. package/package.json +12 -11
  39. package/dist/plugins/LLMSceneBuilder/AISettings.svelte +0 -0
@@ -10,7 +10,7 @@
10
10
  import { type Component, onMount, type Snippet } from 'svelte'
11
11
  import { ThemeUtils } from 'svelte-tweakpane-ui'
12
12
 
13
- import type { FragmentInfo } from '../hooks/usePartConfig.svelte'
13
+ import type { FragmentInfo } from '../hooks/useFragmentInfo.svelte'
14
14
 
15
15
  import Controls from './overlay/controls/Controls.svelte'
16
16
  import Dashboard from './overlay/dashboard/Dashboard.svelte'
@@ -20,6 +20,7 @@
20
20
  import { provideWorld, traits, useQuery } from '../ecs'
21
21
  import { type CameraPose, provideCameraControls } from '../hooks/useControls.svelte'
22
22
  import { provideEnvironment } from '../hooks/useEnvironment.svelte'
23
+ import { provideFragmentInfo } from '../hooks/useFragmentInfo.svelte'
23
24
  import { providePartConfig } from '../hooks/usePartConfig.svelte'
24
25
  import { createPartIDContext } from '../hooks/usePartID.svelte'
25
26
  import { provideSettings } from '../hooks/useSettings.svelte'
@@ -39,7 +40,6 @@
39
40
  interface LocalConfigProps {
40
41
  current: Struct
41
42
  isDirty: boolean
42
- componentNameToFragmentInfo: Record<string, FragmentInfo>
43
43
  setLocalPartConfig: (config: Struct) => void
44
44
  }
45
45
 
@@ -48,6 +48,12 @@
48
48
  inputBindingsEnabled?: boolean
49
49
  localConfigProps?: LocalConfigProps
50
50
 
51
+ /**
52
+ * Maps a component name to the fragment that defines it. Embedded hosts
53
+ * supply this; in standalone it is computed from fragment queries (omit).
54
+ */
55
+ componentNameToFragmentInfo?: Record<string, FragmentInfo>
56
+
51
57
  /**
52
58
  * Allows adding additional tabs to the settings panel
53
59
  */
@@ -81,6 +87,7 @@
81
87
  partID = '',
82
88
  inputBindingsEnabled = true,
83
89
  localConfigProps,
90
+ componentNameToFragmentInfo,
84
91
  cameraPose,
85
92
  settingsTabs,
86
93
  children: appChildren,
@@ -104,6 +111,11 @@
104
111
 
105
112
  let root = $state.raw<HTMLElement>()
106
113
 
114
+ provideFragmentInfo(
115
+ () => partID,
116
+ () => componentNameToFragmentInfo
117
+ )
118
+
107
119
  providePartConfig(
108
120
  () => partID,
109
121
  () => localConfigProps
@@ -1,18 +1,22 @@
1
1
  import type { Struct } from '@viamrobotics/sdk';
2
2
  import type { Entity } from 'koota';
3
3
  import { type Component, type Snippet } from 'svelte';
4
- import type { FragmentInfo } from '../hooks/usePartConfig.svelte';
4
+ import type { FragmentInfo } from '../hooks/useFragmentInfo.svelte';
5
5
  import { type CameraPose } from '../hooks/useControls.svelte';
6
6
  interface LocalConfigProps {
7
7
  current: Struct;
8
8
  isDirty: boolean;
9
- componentNameToFragmentInfo: Record<string, FragmentInfo>;
10
9
  setLocalPartConfig: (config: Struct) => void;
11
10
  }
12
11
  interface Props {
13
12
  partID?: string;
14
13
  inputBindingsEnabled?: boolean;
15
14
  localConfigProps?: LocalConfigProps;
15
+ /**
16
+ * Maps a component name to the fragment that defines it. Embedded hosts
17
+ * supply this; in standalone it is computed from fragment queries (omit).
18
+ */
19
+ componentNameToFragmentInfo?: Record<string, FragmentInfo>;
16
20
  /**
17
21
  * Allows adding additional tabs to the settings panel
18
22
  */
@@ -12,11 +12,12 @@
12
12
  depthTest?: boolean
13
13
  }
14
14
 
15
- const {
15
+ let {
16
16
  length = 1,
17
17
  width = 0.1,
18
18
  axesColors = ['red', 'green', 'blue'],
19
19
  depthTest = true,
20
+ ref = $bindable(),
20
21
  ...rest
21
22
  }: Props = $props()
22
23
 
@@ -66,6 +67,7 @@
66
67
  is={line}
67
68
  {...rest}
68
69
  bvh={{ enabled: false }}
70
+ bind:ref
69
71
  >
70
72
  <T is={geometry} />
71
73
  <T
@@ -6,6 +6,6 @@ interface Props extends ThrelteProps<Line2> {
6
6
  axesColors?: [x: string, y: string, z: string];
7
7
  depthTest?: boolean;
8
8
  }
9
- declare const AxesHelper: import("svelte").Component<Props, {}, "">;
9
+ declare const AxesHelper: import("svelte").Component<Props, {}, "ref">;
10
10
  type AxesHelper = ReturnType<typeof AxesHelper>;
11
11
  export default AxesHelper;
@@ -5,7 +5,6 @@
5
5
 
6
6
  import type { InstancedArrows } from '../../../three/InstancedArrows/InstancedArrows'
7
7
 
8
- import AxesHelper from '../../AxesHelper.svelte'
9
8
  import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
10
9
  import { traits, useTag, useTrait } from '../../../ecs'
11
10
  import { meshBoundsRaycast, raycast } from '../../../three/InstancedArrows/raycast'
@@ -21,7 +20,6 @@
21
20
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
22
21
  const selected = useTag(() => entity, traits.Selected)
23
22
  const invisible = useTrait(() => entity, traits.InheritedInvisible)
24
- const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
25
23
 
26
24
  const events = useEntityEvents(() => entity)
27
25
 
@@ -59,11 +57,4 @@
59
57
  bvh={{ enabled: false }}
60
58
  raycast={() => null}
61
59
  />
62
- {#if showAxesHelper.current}
63
- <AxesHelper
64
- name={entity}
65
- width={3}
66
- length={0.1}
67
- />
68
- {/if}
69
60
  </T>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import type { Entity } from 'koota'
3
+ import type { Line2 } from 'three/addons'
4
+
5
+ import { useThrelte } from '@threlte/core'
6
+
7
+ import { traits, useTrait } from '../../ecs'
8
+
9
+ import AxesHelper from '../AxesHelper.svelte'
10
+
11
+ interface Props {
12
+ entity: Entity
13
+ }
14
+
15
+ let { entity }: Props = $props()
16
+
17
+ const { invalidate } = useThrelte()
18
+
19
+ let ref = $state.raw<Line2>()
20
+
21
+ const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
22
+
23
+ $effect(() => {
24
+ if (worldMatrix.current) {
25
+ ref?.matrix.copy(worldMatrix.current)
26
+ ref?.updateMatrixWorld()
27
+ invalidate()
28
+ }
29
+ })
30
+ </script>
31
+
32
+ <AxesHelper
33
+ bind:ref
34
+ name={entity}
35
+ width={3}
36
+ length={0.1}
37
+ matrixAutoUpdate={false}
38
+ />
@@ -0,0 +1,8 @@
1
+ import type { Entity } from 'koota';
2
+ import AxesHelper from '../AxesHelper.svelte';
3
+ interface Props {
4
+ entity: Entity;
5
+ }
6
+ declare const AxesHelper: import("svelte").Component<Props, {}, "">;
7
+ type AxesHelper = ReturnType<typeof AxesHelper>;
8
+ export default AxesHelper;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { Not } from 'koota'
3
+
4
+ import { traits, useQuery } from '../../ecs'
5
+
6
+ import AxesHelper from './AxesHelper.svelte'
7
+
8
+ const entities = useQuery(traits.ShowAxesHelper, Not(traits.Invisible))
9
+ </script>
10
+
11
+ {#each entities.current as entity (entity)}
12
+ <AxesHelper {entity} />
13
+ {/each}
@@ -1,18 +1,5 @@
1
- export default AISettings;
2
- type AISettings = SvelteComponent<{
3
- [x: string]: never;
4
- }, {
5
- [evt: string]: CustomEvent<any>;
6
- }, {}> & {
7
- $$bindings?: string | undefined;
8
- };
9
- declare const AISettings: $$__sveltets_2_IsomorphicComponent<{
10
- [x: string]: never;
11
- }, {
12
- [evt: string]: CustomEvent<any>;
13
- }, {}, {}, string>;
14
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
- new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
16
3
  $$bindings?: Bindings;
17
4
  } & Exports;
18
5
  (internal: unknown, props: {
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
24
11
  };
25
12
  z_$$bindings?: Bindings;
26
13
  }
14
+ declare const AxesHelpers: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type AxesHelpers = InstanceType<typeof AxesHelpers>;
18
+ export default AxesHelpers;
@@ -0,0 +1,290 @@
1
+ <!--
2
+ @component
3
+
4
+ Renders every entity with `Box` + `WorldMatrix` traits as one pair of
5
+ instanced draw calls (toon-shaded faces + edge lines) instead of a mesh
6
+ per box. Trait events are coalesced into a microtask flush, mirroring
7
+ the `WorldMatrix` system, so a burst of changes (one reconcile tick)
8
+ becomes a single batch of instance writes and one `invalidate()`.
9
+
10
+ The faces mesh is also the pointer-interaction surface: `InstancedMesh2`
11
+ raycasts per instance (skipping invisible ones) and stamps `instanceId`
12
+ on each hit, which `useInstancedEntityEvents` maps back to the entity.
13
+ -->
14
+ <script lang="ts">
15
+ import type { Entity } from 'koota'
16
+
17
+ import { createRadixSort, InstancedMesh2 } from '@three.ez/instanced-mesh'
18
+ import { T, useThrelte } from '@threlte/core'
19
+ import {
20
+ BoxGeometry,
21
+ Color,
22
+ EdgesGeometry,
23
+ LineBasicMaterial,
24
+ Matrix4,
25
+ MeshToonMaterial,
26
+ Sphere,
27
+ Vector3,
28
+ } from 'three'
29
+
30
+ import { asColor } from '../../buffer'
31
+ import { colors, darkenColor, subtypeToColor } from '../../color'
32
+ import { traits, useWorld } from '../../ecs'
33
+ import { useResourceByName } from '../../hooks/useResourceByName.svelte'
34
+
35
+ import { composeBoxMatrix } from './composeBoxMatrix'
36
+ import { useInstancedEntityEvents } from './hooks/useEntityEvents.svelte'
37
+
38
+ const { invalidate, renderer } = useThrelte()
39
+ const world = useWorld()
40
+ const resourceByName = useResourceByName()
41
+
42
+ /**
43
+ * Shared unit geometries — every instance references these and sets its
44
+ * dimensions through the per-instance matrix scale, so resizing never
45
+ * rebuilds GPU buffers.
46
+ */
47
+ const unitBox = new BoxGeometry(1, 1, 1)
48
+ const unitBoxEdges = new EdgesGeometry(unitBox, 0)
49
+
50
+ /**
51
+ * Box meshes render transparent by default (`Opacity` trait absent → 0.7,
52
+ * depth write off — same as `Mesh.svelte`); per-instance alpha is written
53
+ * via `setOpacityAt`. The base color stays white so per-instance colors
54
+ * aren't tinted. Whole-object culling is disabled because the library
55
+ * culls per instance against a bounding sphere it derives from each
56
+ * instance matrix.
57
+ */
58
+ const instancedBoxes = new InstancedMesh2(
59
+ unitBox,
60
+ new MeshToonMaterial({ transparent: true, depthWrite: false }),
61
+ { renderer }
62
+ )
63
+ instancedBoxes.sortObjects = true
64
+ instancedBoxes.customSort = createRadixSort(instancedBoxes)
65
+ instancedBoxes.frustumCulled = false
66
+
67
+ /**
68
+ * Keep raycasts on the library's linear (non-BVH) path, but neutralize
69
+ * its gate: the whole-object bounding sphere is computed once on the
70
+ * first raycast (usually before any boxes have streamed in) and never
71
+ * invalidated, leaving instances unhittable. Pin it open and let the
72
+ * per-instance early-outs do the pruning — for an always-animating
73
+ * scene this beats `computeBVH()`, which would re-insert every moving
74
+ * box into the tree on every kinematics tick.
75
+ */
76
+ instancedBoxes.boundingSphere = new Sphere(new Vector3(), Infinity)
77
+
78
+ const instancedBoxEdges = new InstancedMesh2(unitBoxEdges, new LineBasicMaterial(), {
79
+ renderer,
80
+ })
81
+ instancedBoxEdges.frustumCulled = false
82
+
83
+ /**
84
+ * `InstancedMesh2` extends `Mesh`, so on its own it would draw the edge
85
+ * geometry as triangles. Re-tagging the object makes the renderer emit
86
+ * `gl.LINES`; the library's instancing shader patch still applies because
87
+ * `LineBasicMaterial` compiles from the same chunk-based `basic` program
88
+ * its patched shader chunks target.
89
+ *
90
+ * @three.ez/instanced-mesh ^0.3.15 — patches the 'basic' shader chunks shared
91
+ * by MeshBasicMaterial and LineBasicMaterial. Re-validate if upgrading the library.
92
+ */
93
+ Object.assign(instancedBoxEdges, { isMesh: false, isLine: true, isLineSegments: true })
94
+
95
+ /**
96
+ * Faces and edges are separate meshes with independent free lists, so each
97
+ * entity tracks its faces id and edges id separately. `entityByInstanceId`
98
+ * is keyed by faces id — only the faces mesh raycasts (edges set
99
+ * `raycast={() => null}`), so a hit's `instanceId` is always a faces id.
100
+ */
101
+ type InstanceIds = { face: number; edge: number }
102
+ const instanceIdByEntity = new Map<Entity, InstanceIds>()
103
+ const entityByInstanceId = new Map<number, Entity>()
104
+
105
+ const events = useInstancedEntityEvents((event) =>
106
+ event.instanceId === undefined ? undefined : entityByInstanceId.get(event.instanceId)
107
+ )
108
+
109
+ const matrix = new Matrix4()
110
+ const colorUtil = new Color()
111
+
112
+ /** Same resolution order as `Frame.svelte`. */
113
+ const resolveColor = (entity: Entity): Color => {
114
+ const vertexColors = entity.get(traits.Colors)
115
+ if (vertexColors && vertexColors.length >= 3) {
116
+ return asColor(vertexColors, colorUtil)
117
+ }
118
+
119
+ const color = entity.get(traits.Color)
120
+ if (color) {
121
+ return colorUtil.setRGB(color.r, color.g, color.b)
122
+ }
123
+
124
+ const subtype = resourceByName.current[entity.get(traits.Name) ?? '']?.subtype
125
+ return subtypeToColor(subtype) ?? colorUtil.set(colors.default)
126
+ }
127
+
128
+ const writeAppearance = (entity: Entity, ids: InstanceIds) => {
129
+ const color = resolveColor(entity)
130
+ const visible = !entity.has(traits.InheritedInvisible)
131
+
132
+ instancedBoxes.setColorAt(ids.face, color)
133
+ instancedBoxes.setOpacityAt(ids.face, entity.get(traits.Opacity) ?? 0.7)
134
+ instancedBoxes.setVisibilityAt(ids.face, visible)
135
+
136
+ instancedBoxEdges.setColorAt(ids.edge, darkenColor(color, 10))
137
+ instancedBoxEdges.setVisibilityAt(ids.edge, visible)
138
+
139
+ /**
140
+ * Mirrors `useEntityEvents`' invisibility watcher: an instance that
141
+ * vanishes under a motionless cursor gets no pointerleave until the
142
+ * pointer moves, so drop its hover state here.
143
+ */
144
+ if (!visible && entity.has(traits.Hovered)) {
145
+ entity.remove(traits.Hovered)
146
+ }
147
+ }
148
+
149
+ /** Caller composes the instance transform into `matrix` first. */
150
+ const addInstance = (entity: Entity) => {
151
+ let face = -1
152
+ instancedBoxes.addInstances(1, (_obj, index) => {
153
+ face = index
154
+ })
155
+ instancedBoxes.setMatrixAt(face, matrix)
156
+
157
+ let edge = -1
158
+ instancedBoxEdges.addInstances(1, (_obj, index) => {
159
+ edge = index
160
+ })
161
+ instancedBoxEdges.setMatrixAt(edge, matrix)
162
+
163
+ const ids = { face, edge }
164
+ instanceIdByEntity.set(entity, ids)
165
+ entityByInstanceId.set(face, entity)
166
+ writeAppearance(entity, ids)
167
+ }
168
+
169
+ const removeInstance = (entity: Entity, ids: InstanceIds) => {
170
+ instanceIdByEntity.delete(entity)
171
+ entityByInstanceId.delete(ids.face)
172
+ instancedBoxes.removeInstances(ids.face)
173
+ instancedBoxEdges.removeInstances(ids.edge)
174
+ }
175
+
176
+ /**
177
+ * Transform work (matrix/dimension changes, adds, removes) is tracked
178
+ * separately from appearance work (color/opacity/visibility) so a robot
179
+ * in motion only rewrites matrices, not the color texture.
180
+ */
181
+ const dirtyTransform = new Set<Entity>()
182
+ const dirtyAppearance = new Set<Entity>()
183
+ let scheduled = false
184
+
185
+ const flush = () => {
186
+ if (dirtyTransform.size === 0 && dirtyAppearance.size === 0) {
187
+ return
188
+ }
189
+
190
+ for (const entity of dirtyTransform) {
191
+ const ids = instanceIdByEntity.get(entity)
192
+
193
+ if (entity.isAlive() && composeBoxMatrix(entity, matrix)) {
194
+ if (ids === undefined) {
195
+ addInstance(entity)
196
+ } else {
197
+ instancedBoxes.setMatrixAt(ids.face, matrix)
198
+ instancedBoxEdges.setMatrixAt(ids.edge, matrix)
199
+ }
200
+ } else if (ids !== undefined) {
201
+ removeInstance(entity, ids)
202
+ }
203
+ }
204
+
205
+ for (const entity of dirtyAppearance) {
206
+ const ids = instanceIdByEntity.get(entity)
207
+ if (ids !== undefined && entity.isAlive()) {
208
+ writeAppearance(entity, ids)
209
+ }
210
+ }
211
+
212
+ dirtyTransform.clear()
213
+ dirtyAppearance.clear()
214
+ invalidate()
215
+ }
216
+
217
+ const schedule = () => {
218
+ if (scheduled) return
219
+ scheduled = true
220
+ queueMicrotask(() => {
221
+ scheduled = false
222
+ flush()
223
+ })
224
+ }
225
+
226
+ /**
227
+ * `WorldMatrix` changes fire for every entity on every kinematics tick —
228
+ * filter to box entities before touching the dirty sets. `instanceIdByEntity`
229
+ * catches entities whose `Box` trait was just removed.
230
+ */
231
+ const enqueue = (dirty: Set<Entity>) => (entity: Entity) => {
232
+ if (!entity.has(traits.Box) && !instanceIdByEntity.has(entity)) return
233
+ dirty.add(entity)
234
+ schedule()
235
+ }
236
+
237
+ const enqueueTransform = enqueue(dirtyTransform)
238
+ const enqueueAppearance = enqueue(dirtyAppearance)
239
+
240
+ $effect(() => {
241
+ // Initial sync: existing boxes need both an instance allocated (transform)
242
+ // and appearance written once. At runtime the sets diverge — motion enqueues
243
+ // transform alone, so appearance buffers aren't rewritten per kinematics tick.
244
+ for (const entity of world.query(traits.Box)) {
245
+ dirtyTransform.add(entity)
246
+ dirtyAppearance.add(entity)
247
+ }
248
+ if (dirtyTransform.size > 0) schedule()
249
+
250
+ const unsubs = [
251
+ world.onAdd(traits.Box, enqueueTransform),
252
+ world.onChange(traits.Box, enqueueTransform),
253
+ world.onRemove(traits.Box, enqueueTransform),
254
+ world.onAdd(traits.WorldMatrix, enqueueTransform),
255
+ world.onChange(traits.WorldMatrix, enqueueTransform),
256
+ world.onRemove(traits.WorldMatrix, enqueueTransform),
257
+ world.onAdd(traits.Center, enqueueTransform),
258
+ world.onChange(traits.Center, enqueueTransform),
259
+ world.onRemove(traits.Center, enqueueTransform),
260
+
261
+ world.onAdd(traits.Color, enqueueAppearance),
262
+ world.onChange(traits.Color, enqueueAppearance),
263
+ world.onRemove(traits.Color, enqueueAppearance),
264
+ world.onAdd(traits.Colors, enqueueAppearance),
265
+ world.onChange(traits.Colors, enqueueAppearance),
266
+ world.onRemove(traits.Colors, enqueueAppearance),
267
+ world.onAdd(traits.Opacity, enqueueAppearance),
268
+ world.onChange(traits.Opacity, enqueueAppearance),
269
+ world.onRemove(traits.Opacity, enqueueAppearance),
270
+ world.onAdd(traits.InheritedInvisible, enqueueAppearance),
271
+ world.onRemove(traits.InheritedInvisible, enqueueAppearance),
272
+ ]
273
+
274
+ return () => {
275
+ for (const unsub of unsubs) unsub()
276
+ dirtyTransform.clear()
277
+ dirtyAppearance.clear()
278
+ }
279
+ })
280
+ </script>
281
+
282
+ <T
283
+ is={instancedBoxes}
284
+ {...events}
285
+ />
286
+
287
+ <T
288
+ is={instancedBoxEdges}
289
+ raycast={() => null}
290
+ />
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Renders every entity with `Box` + `WorldMatrix` traits as one pair of
3
+ * instanced draw calls (toon-shaded faces + edge lines) instead of a mesh
4
+ * per box. Trait events are coalesced into a microtask flush, mirroring
5
+ * the `WorldMatrix` system, so a burst of changes (one reconcile tick)
6
+ * becomes a single batch of instance writes and one `invalidate()`.
7
+ *
8
+ * The faces mesh is also the pointer-interaction surface: `InstancedMesh2`
9
+ * raycasts per instance (skipping invisible ones) and stamps `instanceId`
10
+ * on each hit, which `useInstancedEntityEvents` maps back to the entity.
11
+ */
12
+ declare const Boxes: import("svelte").Component<Record<string, never>, {}, "">;
13
+ type Boxes = ReturnType<typeof Boxes>;
14
+ export default Boxes;
@@ -5,6 +5,8 @@
5
5
  import { useSettings } from '../../hooks/useSettings.svelte'
6
6
 
7
7
  import Arrows from './Arrows/ArrowGroups.svelte'
8
+ import AxesHelpers from './AxesHelpers.svelte'
9
+ import Boxes from './Boxes.svelte'
8
10
  import Frame from './Frame.svelte'
9
11
  import Geometry from './Geometry.svelte'
10
12
  import GLTF from './GLTF.svelte'
@@ -32,8 +34,8 @@
32
34
  */
33
35
  const worldStateEntities = useQuery(
34
36
  traits.WorldStateStoreAPI,
35
- Not(traits.Points, traits.LinePositions, traits.GLTF),
36
- Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
37
+ Not(traits.Points, traits.Box, traits.LinePositions, traits.GLTF),
38
+ Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
37
39
  )
38
40
 
39
41
  /**
@@ -42,8 +44,8 @@
42
44
  */
43
45
  const drawServiceEntities = useQuery(
44
46
  traits.DrawServiceAPI,
45
- Not(traits.Points, traits.LinePositions, traits.GLTF),
46
- Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
47
+ Not(traits.Points, traits.Box, traits.LinePositions, traits.GLTF),
48
+ Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
47
49
  )
48
50
 
49
51
  /**
@@ -55,7 +57,8 @@
55
57
  Not(traits.WorldStateStoreAPI),
56
58
  Not(traits.DrawServiceAPI),
57
59
  Not(traits.Points),
58
- Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
60
+ Not(traits.Box),
61
+ Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
59
62
  )
60
63
 
61
64
  const points = useQuery(traits.Points)
@@ -102,6 +105,8 @@
102
105
  {/each}
103
106
 
104
107
  <Arrows />
108
+ <AxesHelpers />
109
+ <Boxes />
105
110
 
106
111
  {#if enableLabels}
107
112
  <Labels />
@@ -21,7 +21,6 @@
21
21
 
22
22
  import { traits, useTrait } from '../../ecs'
23
23
 
24
- import AxesHelper from '../AxesHelper.svelte'
25
24
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
26
25
 
27
26
  interface Props extends ThrelteProps<Object3D> {
@@ -36,7 +35,6 @@
36
35
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
37
36
  const gltfTrait = useTrait(() => entity, traits.GLTF)
38
37
  const invisible = useTrait(() => entity, traits.InheritedInvisible)
39
- const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
40
38
  const events = useEntityEvents(() => entity)
41
39
 
42
40
  const animationName = $derived(gltfTrait.current?.animationName)
@@ -83,13 +81,6 @@
83
81
  </script>
84
82
 
85
83
  <T is={group}>
86
- {#if showAxesHelper.current}
87
- <AxesHelper
88
- name={entity}
89
- width={3}
90
- length={0.1}
91
- />
92
- {/if}
93
84
  {#if $gltf}
94
85
  <T
95
86
  is={$gltf.scene as Object3D}
@@ -9,7 +9,6 @@
9
9
  import { isVertexColors, STRIDE } from '../../buffer'
10
10
  import { traits, useTrait } from '../../ecs'
11
11
 
12
- import AxesHelper from '../AxesHelper.svelte'
13
12
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
14
13
  import LineDots from './LineDots.svelte'
15
14
  import LineGeometry from './LineGeometry.svelte'
@@ -35,7 +34,6 @@
35
34
  const opacity = useTrait(() => entity, traits.Opacity)
36
35
  const screenSpace = useTrait(() => entity, traits.ScreenSpace)
37
36
  const invisible = useTrait(() => entity, traits.InheritedInvisible)
38
- const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
39
37
 
40
38
  const events = useEntityEvents(() => entity)
41
39
 
@@ -96,13 +94,6 @@
96
94
  linewidth={(lineWidth.current ?? 5) * (screenSpace.current ? 1 : 0.001)}
97
95
  depthTest={materialProps.current?.depthTest ?? true}
98
96
  />
99
- {#if showAxesHelper.current}
100
- <AxesHelper
101
- name={entity}
102
- width={3}
103
- length={0.1}
104
- />
105
- {/if}
106
97
 
107
98
  {#if linePositions.current && dotSize.current}
108
99
  <LineDots