@viamrobotics/motion-tools 1.16.0 → 1.18.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 (77) hide show
  1. package/dist/attribute.d.ts +3 -2
  2. package/dist/attribute.js +24 -16
  3. package/dist/buf/draw/v1/drawing_pb.d.ts +33 -16
  4. package/dist/buf/draw/v1/drawing_pb.js +35 -17
  5. package/dist/buf/draw/v1/metadata_pb.d.ts +44 -3
  6. package/dist/buf/draw/v1/metadata_pb.js +54 -3
  7. package/dist/buf/draw/v1/scene_pb.d.ts +6 -6
  8. package/dist/buf/draw/v1/scene_pb.js +7 -7
  9. package/dist/buffer.d.ts +54 -45
  10. package/dist/buffer.js +91 -57
  11. package/dist/color.d.ts +1 -2
  12. package/dist/color.js +5 -12
  13. package/dist/components/App.svelte +18 -3
  14. package/dist/components/App.svelte.d.ts +15 -2
  15. package/dist/components/Entities/Arrows/ArrowGroups.svelte +5 -6
  16. package/dist/components/Entities/Arrows/Arrows.svelte +9 -0
  17. package/dist/components/Entities/Entities.svelte +18 -1
  18. package/dist/components/Entities/Frame.svelte +7 -1
  19. package/dist/components/Entities/GLTF.svelte +13 -2
  20. package/dist/components/Entities/Line.svelte +46 -18
  21. package/dist/components/Entities/LineDots.svelte +38 -8
  22. package/dist/components/Entities/LineDots.svelte.d.ts +2 -2
  23. package/dist/components/Entities/LineGeometry.svelte +2 -1
  24. package/dist/components/Entities/LineGeometry.svelte.d.ts +2 -0
  25. package/dist/components/Entities/Mesh.svelte +8 -1
  26. package/dist/components/Entities/Points.svelte +22 -11
  27. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +6 -2
  28. package/dist/components/FileDrop/FileDrop.svelte +5 -1
  29. package/dist/components/KeyboardControls.svelte +2 -10
  30. package/dist/components/PCD.svelte +11 -4
  31. package/dist/components/PCD.svelte.d.ts +3 -1
  32. package/dist/components/SceneProviders.svelte +2 -0
  33. package/dist/components/Selected.svelte +2 -12
  34. package/dist/components/Selection/Ellipse.svelte +2 -0
  35. package/dist/components/Selection/Lasso.svelte +2 -0
  36. package/dist/components/Selection/Tool.svelte +7 -56
  37. package/dist/components/Selection/Tool.svelte.d.ts +2 -2
  38. package/dist/components/Selection/useSelectionPlugin.svelte.d.ts +8 -0
  39. package/dist/components/Selection/useSelectionPlugin.svelte.js +24 -0
  40. package/dist/components/Snapshot.svelte +4 -2
  41. package/dist/components/overlay/AddRelationship.svelte +1 -2
  42. package/dist/components/overlay/AddRelationship.svelte.d.ts +1 -1
  43. package/dist/components/overlay/Details.svelte +12 -12
  44. package/dist/components/overlay/Details.svelte.d.ts +8 -1
  45. package/dist/components/overlay/settings/Settings.svelte +8 -1
  46. package/dist/components/xr/XR.svelte +1 -1
  47. package/dist/draw.d.ts +13 -0
  48. package/dist/draw.js +428 -0
  49. package/dist/ecs/traits.d.ts +31 -13
  50. package/dist/ecs/traits.js +25 -8
  51. package/dist/geometry.js +3 -0
  52. package/dist/hooks/useDrawAPI.svelte.js +61 -24
  53. package/dist/hooks/useDrawService.svelte.d.ts +12 -0
  54. package/dist/hooks/useDrawService.svelte.js +240 -0
  55. package/dist/hooks/usePointcloudObjects.svelte.js +7 -2
  56. package/dist/hooks/usePointclouds.svelte.js +7 -2
  57. package/dist/hooks/useSettings.svelte.d.ts +2 -1
  58. package/dist/hooks/useSettings.svelte.js +1 -1
  59. package/dist/hooks/useWorldState.svelte.js +5 -52
  60. package/dist/index.d.ts +8 -0
  61. package/dist/index.js +9 -0
  62. package/dist/lib.d.ts +2 -0
  63. package/dist/lib.js +2 -0
  64. package/dist/loaders/pcd/index.d.ts +1 -1
  65. package/dist/loaders/pcd/messages.d.ts +2 -2
  66. package/dist/loaders/pcd/worker.inline.d.ts +1 -1
  67. package/dist/loaders/pcd/worker.inline.js +229 -187
  68. package/dist/loaders/pcd/worker.js +2 -2
  69. package/dist/metadata.d.ts +9 -15
  70. package/dist/metadata.js +45 -9
  71. package/dist/plugins/bvh.svelte.js +6 -2
  72. package/dist/snapshot.d.ts +3 -9
  73. package/dist/snapshot.js +11 -204
  74. package/dist/three/InstancedArrows/InstancedArrows.js +3 -2
  75. package/package.json +14 -11
  76. package/dist/components/xr/Hands.svelte +0 -23
  77. package/dist/components/xr/Hands.svelte.d.ts +0 -18
package/dist/buffer.js CHANGED
@@ -1,57 +1,61 @@
1
- /**
2
- * Zero-copy buffer utilities for converting protobuf bytes to Three.js typed arrays.
3
- *
4
- * Proto messages pack float32 data as `Uint8Array` (bytes fields). These utilities
5
- * provide efficient conversion to `Float32Array` for Three.js BufferAttributes.
6
- */
7
1
  import { Color } from 'three';
8
- /**
9
- * Stride constants for proto binary data formats.
10
- * Each value represents the number of float32 elements per item.
11
- */
2
+ import { ColorFormat } from './buf/draw/v1/metadata_pb';
12
3
  export const STRIDE = {
13
- /** Arrows: [x, y, z, ox, oy, oz] per arrow */
4
+ /** Arrows: [x, y, z, ox, oy, oz, ...] */
14
5
  ARROWS: 6,
15
- /** Line/Points: [x, y, z] per point */
6
+ /** Line/Points: [x, y, z, ...] */
16
7
  POSITIONS: 3,
17
- /** Nurbs control points: [x, y, z, ox, oy, oz, theta] per point */
8
+ /** Nurbs control points: [x, y, z, ox, oy, oz, theta, ...] */
18
9
  NURBS_CONTROL_POINTS: 7,
19
- /** Nurbs knots/weights: single float per element */
10
+ /** Nurbs knots/weights: [w, ...] */
20
11
  NURBS_KNOTS: 1,
21
- /** Colors: [r, g, b, a] per color (uint8) */
22
- COLORS_RGBA: 4,
23
- /** Colors: [r, g, b] */
12
+ /** Colors: [r, g, b, ...] */
24
13
  COLORS_RGB: 3,
25
14
  };
26
15
  /**
27
16
  * Creates a Float32Array view over a Uint8Array without copying data.
28
17
  * Falls back to a copy if the buffer is not 4-byte aligned (rare with protobuf).
29
18
  *
19
+ * An optional `transform` applies a per-element function during conversion.
20
+ *
30
21
  * @param bytes - The raw bytes from a protobuf bytes field
22
+ * @param transform - Optional function applied to every float element
31
23
  * @returns A Float32Array view or copy of the data
32
24
  *
33
25
  * @example
34
26
  * ```ts
35
27
  * const positions = asFloat32Array(line.positions)
36
- * geometry.setAttribute('position', new BufferAttribute(positions, 3))
28
+ * const meterPositions = asFloat32Array(line.positions, inMeters)
37
29
  * ```
38
30
  */
39
- export const asFloat32Array = (bytes) => {
31
+ export const asFloat32Array = (bytes, transform) => {
40
32
  if (bytes.length === 0) {
41
33
  return new Float32Array(0);
42
34
  }
43
35
  if (bytes.byteOffset % 4 === 0 && bytes.byteLength % 4 === 0) {
44
- return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
36
+ const view = new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
37
+ if (transform) {
38
+ for (let i = 0; i < view.length; i++)
39
+ view[i] = transform(view[i]);
40
+ }
41
+ return view;
45
42
  }
46
43
  const aligned = new Float32Array(bytes.byteLength / 4);
47
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
48
- for (let i = 0; i < aligned.length; i++) {
49
- aligned[i] = view.getFloat32(i * 4, true); // little-endian
44
+ const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
45
+ if (transform) {
46
+ for (let i = 0; i < aligned.length; i++) {
47
+ aligned[i] = transform(dataView.getFloat32(i * 4, true));
48
+ }
49
+ }
50
+ else {
51
+ for (let i = 0; i < aligned.length; i++) {
52
+ aligned[i] = dataView.getFloat32(i * 4, true);
53
+ }
50
54
  }
51
55
  return aligned;
52
56
  };
53
57
  /**
54
- * Sets a Three.js Color from 3 bytes of a uint8 color array starting at `offset`.
58
+ * Sets a Three.js Color from 3 bytes of a uint8 RGB color array starting at `offset`.
55
59
  * Mutates and returns `target` — pass a pre-allocated Color to avoid allocations
56
60
  * in hot paths.
57
61
  *
@@ -63,7 +67,7 @@ export const asFloat32Array = (bytes) => {
63
67
  * @example
64
68
  * ```ts
65
69
  * asColor(colors.current, material.color)
66
- * asColor(colors.current, pointColorUtil, stride) // read second color
70
+ * asColor(colors.current, pointColorUtil, STRIDE.COLORS_RGB) // read second color
67
71
  * ```
68
72
  */
69
73
  export const asColor = (bytes, target, offset = 0) => {
@@ -71,59 +75,89 @@ export const asColor = (bytes, target, offset = 0) => {
71
75
  return target.setRGB(0, 0, 0);
72
76
  return target.setRGB(bytes[offset] / 255, bytes[offset + 1] / 255, bytes[offset + 2] / 255);
73
77
  };
78
+ export const asRGB = (bytes, target, offset = 0) => {
79
+ target.r = (bytes[offset] ?? 0) / 255;
80
+ target.g = (bytes[offset + 1] ?? 0) / 255;
81
+ target.b = (bytes[offset + 2] ?? 0) / 255;
82
+ return target;
83
+ };
74
84
  /**
75
- * Creates a Uint8Array from a Three.js Color.
85
+ * Reads a byte from a uint8 opacities array at `index` and normalizes it to 0-1.
86
+ * Returns `fallback` when the array is absent or shorter than `index + 1`.
76
87
  *
77
- * @param color - The Three.js Color to convert
78
- * @returns A Uint8Array with the RGBA values
88
+ * @param opacities - Uint8Array of opacity values (0-255). Length 1 = uniform. Length N = per-vertex.
89
+ * @param fallback - Value to return when no opacity byte is available (default 1)
90
+ * @param index - Index into the opacities array (default 0)
91
+ * @returns Normalized opacity in 0-1 range, or the fallback value
79
92
  *
80
93
  * @example
81
94
  * ```ts
82
- * const color = fromColor(new Color(0, 1, 0))
95
+ * material.opacity = asOpacity(metadata.opacities)
83
96
  * ```
84
97
  */
85
- export const fromColor = (color) => {
86
- return new Uint8Array([
87
- Math.round(color.r * 255),
88
- Math.round(color.g * 255),
89
- Math.round(color.b * 255),
90
- ]);
98
+ export const asOpacity = (opacities, fallback = 1, index = 0) => {
99
+ if (!opacities || opacities.length === 0)
100
+ return fallback;
101
+ // If only one opacity byte, it is the uniform opacity regardless of index
102
+ const i = opacities.length === 1 ? 0 : index;
103
+ if (opacities.length <= i)
104
+ return fallback;
105
+ return opacities[i] / 255;
91
106
  };
92
107
  /**
93
- * Reads a byte from a uint8 color array at `offset` and normalizes it to 0-1.
94
- * Returns `fallback` when the array has fewer than `offset + 1` elements.
95
- *
96
- * @param bytes - ArrayLike of uint8 color values
97
- * @param fallback - Value to return when no alpha byte is present (default 1)
98
- * @param offset - Byte index to read from (default 3, the alpha channel of the first color)
99
- * @returns Normalized opacity in 0-1 range, or the fallback value
108
+ * Returns true when `colors` contains exactly one color (RGB or RGBA),
109
+ * as opposed to a per-vertex color array.
100
110
  *
101
111
  * @example
102
112
  * ```ts
103
- * material.opacity = asOpacity(colors.current)
104
- * material.opacity = asOpacity(colors.current, 1, stride + 3) // alpha of second color
113
+ * if (isSingleColor(colors)) {
114
+ * material.color = asColor(colors, colorUtil)
115
+ * }
105
116
  * ```
106
117
  */
107
- export const asOpacity = (bytes, fallback = 1, offset = 3) => {
108
- if (bytes.length < offset + 1)
109
- return fallback;
110
- return bytes[offset] / 255;
118
+ export const isSingleColor = (colors) => {
119
+ if (!colors)
120
+ return false;
121
+ return colors.length === STRIDE.COLORS_RGB;
111
122
  };
112
123
  /**
113
- * Returns true when `colors` contains exactly one color entry per point (RGB or RGBA).
114
- * Use this to distinguish per-vertex color buffers from a single uniform color.
124
+ * Returns true when `colors` contains per-vertex color data rather than a
125
+ * single uniform color.
115
126
  *
116
- * @param colors - Uint8Array of packed color bytes
117
- * @param numVertex - Number of points/vertices the color buffer should cover
127
+ * @param colors - Uint8Array of packed RGB bytes (stride of 3)
118
128
  *
119
129
  * @example
120
130
  * ```ts
121
- * if (isPerVertexColors(colors, positions.length / STRIDE.POSITIONS)) {
131
+ * if (isVertexColors(colors, positions.length / 3)) {
122
132
  * // treat as per-vertex
123
- * } else {
124
- * addColorTraits(entityTraits, colors)
125
133
  * }
126
134
  * ```
127
135
  */
128
- export const isPerVertexColors = (colors, numVertex) => colors.length === numVertex * STRIDE.COLORS_RGB ||
129
- colors.length === numVertex * STRIDE.COLORS_RGBA;
136
+ export const isVertexColors = (colors) => {
137
+ if (!colors || colors.length === 0)
138
+ return false;
139
+ if (isSingleColor(colors))
140
+ return false;
141
+ return colors.length % STRIDE.COLORS_RGB === 0;
142
+ };
143
+ /**
144
+ * Per-element transform that converts a millimeter value to meters.
145
+ * Pass to {@link asFloat32Array} to fuse the conversion into a single pass.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const positions = asFloat32Array(line.positions, inMeters)
150
+ * ```
151
+ */
152
+ export const inMeters = (v) => v * 0.001;
153
+ /** Returns the byte stride for a given color format. */
154
+ export const colorStride = (format) => {
155
+ switch (format) {
156
+ case ColorFormat.RGB: {
157
+ return STRIDE.COLORS_RGB;
158
+ }
159
+ default: {
160
+ return 0;
161
+ }
162
+ }
163
+ };
package/dist/color.d.ts CHANGED
@@ -34,5 +34,4 @@ export declare const parseColor: (color: unknown, defaultColor?: ColorRepresenta
34
34
  export declare const isRGB: (color: unknown) => color is RGB;
35
35
  export declare const parseRGB: (color: unknown, defaultColor?: RGB) => Color;
36
36
  export declare const parseOpacity: (opacity: unknown, defaultOpacity?: number) => number;
37
- export declare const rgbaToHex: (rgba: Uint8Array) => string;
38
- export declare const rgbaBytesToFloat32: (bytes: Uint8Array<ArrayBuffer>) => Float32Array<ArrayBuffer>;
37
+ export declare const rgbToHex: (rgb: Uint8Array) => string;
package/dist/color.js CHANGED
@@ -141,18 +141,11 @@ const isColorHex = (color) => {
141
141
  }
142
142
  return false;
143
143
  };
144
- export const rgbaToHex = (rgba) => {
145
- if (rgba.length < 3)
144
+ export const rgbToHex = (rgb) => {
145
+ if (rgb.length < 3)
146
146
  return '#333333';
147
- const r = rgba[0].toString(16).padStart(2, '0');
148
- const g = rgba[1].toString(16).padStart(2, '0');
149
- const b = rgba[2].toString(16).padStart(2, '0');
147
+ const r = rgb[0].toString(16).padStart(2, '0');
148
+ const g = rgb[1].toString(16).padStart(2, '0');
149
+ const b = rgb[2].toString(16).padStart(2, '0');
150
150
  return `#${r}${g}${b}`;
151
151
  };
152
- export const rgbaBytesToFloat32 = (bytes) => {
153
- const out = new Float32Array(bytes.length);
154
- for (let i = 0; i < bytes.length; i++) {
155
- out[i] = bytes[i] / 255;
156
- }
157
- return out;
158
- };
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Struct } from '@viamrobotics/sdk'
3
+ import type { Entity } from 'koota'
3
4
  import type { Snippet } from 'svelte'
4
5
 
5
6
  import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
@@ -47,11 +48,24 @@
47
48
  interface Props {
48
49
  partID?: string
49
50
  enableKeybindings?: boolean
50
- children?: Snippet
51
- dashboard?: Snippet
52
51
  localConfigProps?: LocalConfigProps
53
52
  drawConnectionConfig?: DrawConnectionConfig
54
53
 
54
+ /**
55
+ * Snippet for THREE objects
56
+ */
57
+ children?: Snippet
58
+
59
+ /**
60
+ * Snippet to inject items in the top middle dashboard
61
+ */
62
+ dashboard?: Snippet
63
+
64
+ /**
65
+ * Snippet to inject items into the details panel
66
+ */
67
+ details?: Snippet<[{ entity: Entity }]>
68
+
55
69
  /**
56
70
  * Allows setting the initial camera pose
57
71
  */
@@ -66,6 +80,7 @@
66
80
  drawConnectionConfig,
67
81
  children: appChildren,
68
82
  dashboard,
83
+ details,
69
84
  }: Props = $props()
70
85
 
71
86
  provideWorld()
@@ -121,7 +136,7 @@
121
136
  <div {@attach domPortal(root)}>
122
137
  <FileDrop />
123
138
  <Dashboard {dashboard} />
124
- <Details />
139
+ <Details {details} />
125
140
 
126
141
  {#if environment.current.isStandalone}
127
142
  <LiveUpdatesBanner />
@@ -1,4 +1,5 @@
1
1
  import type { Struct } from '@viamrobotics/sdk';
2
+ import type { Entity } from 'koota';
2
3
  import type { Snippet } from 'svelte';
3
4
  import type { CameraPose } from '../hooks/useControls.svelte';
4
5
  import { type DrawConnectionConfig } from '../hooks/useDrawConnectionConfig.svelte';
@@ -11,10 +12,22 @@ interface LocalConfigProps {
11
12
  interface Props {
12
13
  partID?: string;
13
14
  enableKeybindings?: boolean;
14
- children?: Snippet;
15
- dashboard?: Snippet;
16
15
  localConfigProps?: LocalConfigProps;
17
16
  drawConnectionConfig?: DrawConnectionConfig;
17
+ /**
18
+ * Snippet for THREE objects
19
+ */
20
+ children?: Snippet;
21
+ /**
22
+ * Snippet to inject items in the top middle dashboard
23
+ */
24
+ dashboard?: Snippet;
25
+ /**
26
+ * Snippet to inject items into the details panel
27
+ */
28
+ details?: Snippet<[{
29
+ entity: Entity;
30
+ }]>;
18
31
  /**
19
32
  * Allows setting the initial camera pose
20
33
  */
@@ -14,21 +14,20 @@
14
14
 
15
15
  const map = new SvelteMap<Entity, InstancedArrows>()
16
16
 
17
+ const colorUtil = new Color()
18
+
17
19
  const onAdd = (entity: Entity) => {
18
20
  const poses = entity.get(traits.Positions)
21
+ const color = entity.get(traits.Color)
19
22
  const colors = entity.get(traits.Colors)
20
23
  const { headAtPose } = entity.get(traits.Arrows) ?? {}
21
24
 
22
25
  if (!poses) return
23
26
 
24
27
  const total = poses.length / STRIDE.ARROWS
25
- const alpha = colors && colors.length / STRIDE.COLORS_RGBA === total
26
- const uniformColor =
27
- colors && (colors.length === 3 || colors.length === 4)
28
- ? new Color(colors[0], colors[1], colors[2])
29
- : undefined
28
+ const uniformColor = color ? colorUtil.setRGB(color.r, color.g, color.b) : undefined
30
29
 
31
- const arrows = new InstancedArrows({ count: total, alpha, uniformColor })
30
+ const arrows = new InstancedArrows({ count: total, uniformColor })
32
31
  map.set(entity, arrows)
33
32
  arrows.update({ poses, colors, headAtPose })
34
33
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { InstancedArrows } from '../../../three/InstancedArrows/InstancedArrows'
8
8
 
9
+ import AxesHelper from '../../AxesHelper.svelte'
9
10
  import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
10
11
  import { traits, useTrait } from '../../../ecs'
11
12
  import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte'
@@ -20,6 +21,7 @@
20
21
 
21
22
  const parent = useTrait(() => entity, traits.Parent)
22
23
  const invisible = useTrait(() => entity, traits.Invisible)
24
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
23
25
 
24
26
  const events = useEntityEvents(() => entity)
25
27
  const selectedEntity = useSelectedEntity()
@@ -53,5 +55,12 @@
53
55
  bvh={{ enabled: false }}
54
56
  raycast={() => null}
55
57
  />
58
+ {#if showAxesHelper.current}
59
+ <AxesHelper
60
+ name={entity}
61
+ width={3}
62
+ length={0.1}
63
+ />
64
+ {/if}
56
65
  </T>
57
66
  </Portal>
@@ -27,13 +27,23 @@
27
27
 
28
28
  /**
29
29
  * Geometries from the world state API are bucketed into their own query
30
- * to avoid thrashing other query results due to them being potentially polled at 60fps.
30
+ * to avoid thrashing other query results due to them being streamed.
31
31
  */
32
32
  const worldStateEntities = useQuery(
33
33
  traits.WorldStateStoreAPI,
34
34
  Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
35
35
  )
36
36
 
37
+ /**
38
+ * Entities from the draw service API are bucketed into their own query
39
+ * to avoid thrashing other query results due to them being streamed.
40
+ */
41
+ const drawServiceEntities = useQuery(
42
+ traits.DrawServiceAPI,
43
+ Not(traits.Points, traits.LinePositions, traits.GLTF),
44
+ Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
45
+ )
46
+
37
47
  /**
38
48
  * All remaining meshes can be bucketed into a query due to lower frequency updates.
39
49
  */
@@ -41,6 +51,7 @@
41
51
  Not(traits.FramesAPI),
42
52
  Not(traits.GeometriesAPI),
43
53
  Not(traits.WorldStateStoreAPI),
54
+ Not(traits.DrawServiceAPI),
44
55
  Not(traits.Points),
45
56
  Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
46
57
  )
@@ -75,6 +86,12 @@
75
86
  </Frame>
76
87
  {/each}
77
88
 
89
+ {#each drawServiceEntities.current as entity (entity)}
90
+ <Frame {entity}>
91
+ <Label text={entity.get(traits.Name)} />
92
+ </Frame>
93
+ {/each}
94
+
78
95
  {#each meshEntities.current as entity (entity)}
79
96
  <Frame {entity}>
80
97
  <Label text={entity.get(traits.Name)} />
@@ -18,6 +18,7 @@ Renders a Viam Frame object
18
18
  import { Portal, PortalTarget } from '@threlte/extras'
19
19
  import { Group, type Object3D } from 'three'
20
20
 
21
+ import { asColor } from '../../buffer'
21
22
  import { colors, resourceColors } from '../../color'
22
23
  import { traits, useTrait } from '../../ecs'
23
24
  import { useResourceByName } from '../../hooks/useResourceByName.svelte'
@@ -39,6 +40,7 @@ Renders a Viam Frame object
39
40
 
40
41
  const name = useTrait(() => entity, traits.Name)
41
42
  const parent = useTrait(() => entity, traits.Parent)
43
+ const entityColors = useTrait(() => entity, traits.Colors)
42
44
  const entityColor = useTrait(() => entity, traits.Color)
43
45
  const entityPose = useTrait(() => entity, traits.Pose)
44
46
  const center = useTrait(() => entity, traits.Center)
@@ -47,8 +49,12 @@ Renders a Viam Frame object
47
49
  const events = useEntityEvents(() => entity)
48
50
 
49
51
  const color = $derived.by(() => {
52
+ if (entityColors.current) {
53
+ return `#${asColor(entityColors.current, colorUtil).getHexString()}`
54
+ }
55
+
50
56
  if (entityColor.current) {
51
- return `#${colorUtil.set(entityColor.current.r, entityColor.current.g, entityColor.current.b).getHexString()}`
57
+ return `#${colorUtil.setRGB(entityColor.current.r, entityColor.current.g, entityColor.current.b).getHexString()}`
52
58
  }
53
59
 
54
60
  const subtype = resourceByName.current[name.current ?? '']?.subtype
@@ -22,6 +22,7 @@
22
22
  import { traits, useTrait } from '../../ecs'
23
23
  import { poseToObject3d } from '../../transform'
24
24
 
25
+ import AxesHelper from '../AxesHelper.svelte'
25
26
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
26
27
 
27
28
  interface Props extends ThrelteProps<Object3D> {
@@ -39,6 +40,7 @@
39
40
  const gltfTrait = useTrait(() => entity, traits.GLTF)
40
41
  const scale = useTrait(() => entity, traits.Scale)
41
42
  const invisible = useTrait(() => entity, traits.Invisible)
43
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
42
44
  const events = useEntityEvents(() => entity)
43
45
 
44
46
  const animationName = $derived(gltfTrait.current?.animationName)
@@ -65,7 +67,7 @@
65
67
  const buffer = source.glb.buffer.slice(
66
68
  source.glb.byteOffset,
67
69
  source.glb.byteOffset + source.glb.byteLength
68
- )
70
+ ) as ArrayBuffer
69
71
  $gltf = (await gltfLoader.parseAsync(buffer, '')) as ThrelteGltf
70
72
  } else if ('gltf' in source) {
71
73
  $gltf = source.gltf as ThrelteGltf
@@ -84,6 +86,13 @@
84
86
 
85
87
  <Portal id={parent.current}>
86
88
  <T is={group}>
89
+ {#if showAxesHelper.current}
90
+ <AxesHelper
91
+ name={entity}
92
+ width={3}
93
+ length={0.1}
94
+ />
95
+ {/if}
87
96
  {#if $gltf}
88
97
  <T
89
98
  is={$gltf.scene as Object3D}
@@ -95,7 +104,9 @@
95
104
  >
96
105
  {@render children?.()}
97
106
 
98
- <PortalTarget id={name.current} />
107
+ {#if name.current}
108
+ <PortalTarget id={name.current} />
109
+ {/if}
99
110
  </T>
100
111
  {/if}
101
112
  </T>
@@ -1,9 +1,3 @@
1
- <script module>
2
- import { Color } from 'three'
3
-
4
- const colorUtil = new Color()
5
- </script>
6
-
7
1
  <script lang="ts">
8
2
  import type { Entity } from 'koota'
9
3
  import type { Snippet } from 'svelte'
@@ -12,10 +6,11 @@
12
6
  import { meshBounds, Portal, PortalTarget } from '@threlte/extras'
13
7
  import { Line2, LineMaterial } from 'three/examples/jsm/Addons.js'
14
8
 
15
- import { darkenColor } from '../../color'
9
+ import { isVertexColors, STRIDE } from '../../buffer'
16
10
  import { traits, useTrait } from '../../ecs'
17
11
  import { poseToObject3d } from '../../transform'
18
12
 
13
+ import AxesHelper from '../AxesHelper.svelte'
19
14
  import { useEntityEvents } from './hooks/useEntityEvents.svelte'
20
15
  import LineDots from './LineDots.svelte'
21
16
  import LineGeometry from './LineGeometry.svelte'
@@ -32,16 +27,39 @@
32
27
  const parent = useTrait(() => entity, traits.Parent)
33
28
  const pose = useTrait(() => entity, traits.Pose)
34
29
  const color = useTrait(() => entity, traits.Color)
35
- const pointSize = useTrait(() => entity, traits.PointSize)
30
+ const colors = useTrait(() => entity, traits.Colors)
31
+ const dotColors = useTrait(() => entity, traits.DotColors)
32
+ const dotSize = useTrait(() => entity, traits.DotSize)
36
33
  const linePositions = useTrait(() => entity, traits.LinePositions)
37
34
  const lineWidth = useTrait(() => entity, traits.LineWidth)
38
- const opacity = useTrait(() => entity, traits.Opacity)
39
35
  const materialProps = useTrait(() => entity, traits.Material)
40
36
  const renderOrder = useTrait(() => entity, traits.RenderOrder)
37
+ const opacity = useTrait(() => entity, traits.Opacity)
38
+ const screenSpace = useTrait(() => entity, traits.ScreenSpace)
41
39
  const invisible = useTrait(() => entity, traits.Invisible)
40
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
42
41
 
43
42
  const events = useEntityEvents(() => entity)
44
43
 
44
+ const hasVertexColors = $derived(isVertexColors(colors.current))
45
+
46
+ const lineColor = $derived.by<[number, number, number]>(() => {
47
+ if (color.current) return [color.current.r, color.current.g, color.current.b]
48
+ return [0, 0, 1]
49
+ })
50
+
51
+ const lineColors = $derived.by<Float32Array | undefined>(() => {
52
+ if (!colors.current) return undefined
53
+ const numColors = colors.current.length / STRIDE.COLORS_RGB
54
+ const rgb = new Float32Array(numColors * 3)
55
+ for (let i = 0; i < numColors; i++) {
56
+ rgb[i * 3] = colors.current[i * STRIDE.COLORS_RGB]! / 255
57
+ rgb[i * 3 + 1] = colors.current[i * STRIDE.COLORS_RGB + 1]! / 255
58
+ rgb[i * 3 + 2] = colors.current[i * STRIDE.COLORS_RGB + 2]! / 255
59
+ }
60
+ return rgb
61
+ })
62
+
45
63
  const currentOpacity = $derived(opacity.current ?? 0.7)
46
64
 
47
65
  const mesh = new Line2()
@@ -64,26 +82,36 @@
64
82
  visible={invisible.current !== true}
65
83
  {...events}
66
84
  >
67
- <LineGeometry positions={linePositions.current} />
85
+ <LineGeometry
86
+ positions={linePositions.current}
87
+ colors={lineColors}
88
+ />
68
89
  <T
69
90
  is={LineMaterial}
70
- color={[color.current?.r ?? 1, color.current?.g ?? 0, color.current?.b ?? 0]}
91
+ color={hasVertexColors ? [1, 1, 1] : lineColor}
92
+ vertexColors={hasVertexColors}
71
93
  transparent={currentOpacity < 1}
72
94
  depthWrite={currentOpacity === 1}
73
95
  opacity={currentOpacity}
74
- width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
96
+ worldUnits={!screenSpace.current}
97
+ linewidth={(lineWidth.current ?? 5) * (screenSpace.current ? 1 : 0.001)}
75
98
  depthTest={materialProps.current?.depthTest ?? true}
76
99
  />
100
+ {#if showAxesHelper.current}
101
+ <AxesHelper
102
+ name={entity}
103
+ width={3}
104
+ length={0.1}
105
+ />
106
+ {/if}
77
107
  </T>
78
108
 
79
- {#if linePositions.current && pointSize.current}
109
+ {#if linePositions.current && dotSize.current}
80
110
  <LineDots
81
- color={darkenColor(
82
- colorUtil.setRGB(color.current?.r ?? 1, color.current?.g ?? 0, color.current?.b ?? 0),
83
- 10
84
- )}
111
+ colors={dotColors.current ?? new Uint8Array()}
112
+ opacity={currentOpacity}
85
113
  positions={linePositions.current}
86
- scale={pointSize.current * 0.001}
114
+ scale={dotSize.current * 0.001}
87
115
  />
88
116
  {/if}
89
117