@viamrobotics/motion-tools 0.19.2 → 1.0.2

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 (135) hide show
  1. package/README.md +56 -26
  2. package/dist/FrameConfigUpdater.svelte.d.ts +11 -17
  3. package/dist/FrameConfigUpdater.svelte.js +109 -109
  4. package/dist/WorldObject.svelte.js +2 -15
  5. package/dist/common/v1/common_pb.d.ts +950 -0
  6. package/dist/common/v1/common_pb.js +1399 -0
  7. package/dist/components/App.svelte +37 -21
  8. package/dist/components/App.svelte.d.ts +1 -0
  9. package/dist/components/BatchedArrows.svelte +102 -0
  10. package/dist/components/BatchedArrows.svelte.d.ts +3 -0
  11. package/dist/components/CameraControls.svelte +2 -3
  12. package/dist/components/Details.svelte +364 -365
  13. package/dist/components/Entities.svelte +73 -0
  14. package/dist/components/{WorldObjects.svelte.d.ts → Entities.svelte.d.ts} +3 -3
  15. package/dist/components/FileDrop.svelte +9 -23
  16. package/dist/components/Focus.svelte +2 -3
  17. package/dist/components/Frame.svelte +41 -22
  18. package/dist/components/Frame.svelte.d.ts +4 -6
  19. package/dist/components/GLTF.svelte +36 -0
  20. package/dist/components/GLTF.svelte.d.ts +11 -0
  21. package/dist/components/Geometry2.svelte +201 -0
  22. package/dist/components/Geometry2.svelte.d.ts +18 -0
  23. package/dist/components/KeyboardControls.svelte +3 -3
  24. package/dist/components/Line.svelte +10 -13
  25. package/dist/components/Line.svelte.d.ts +2 -2
  26. package/dist/components/LiveUpdatesBanner.svelte +51 -15
  27. package/dist/components/MeasureTool.svelte +4 -5
  28. package/dist/components/Pointcloud.svelte +27 -14
  29. package/dist/components/Pointcloud.svelte.d.ts +2 -2
  30. package/dist/components/PointerMissBox.svelte +3 -3
  31. package/dist/components/Pose.svelte +31 -6
  32. package/dist/components/Pose.svelte.d.ts +2 -2
  33. package/dist/components/Scene.svelte +7 -6
  34. package/dist/components/SceneProviders.svelte +0 -6
  35. package/dist/components/Selected.svelte +22 -16
  36. package/dist/components/StaticGeometries.svelte +51 -27
  37. package/dist/components/Tree/Tree.svelte +28 -22
  38. package/dist/components/Tree/Tree.svelte.d.ts +2 -3
  39. package/dist/components/Tree/TreeContainer.svelte +72 -40
  40. package/dist/components/Tree/Widgets.svelte +2 -5
  41. package/dist/components/Tree/buildTree.d.ts +3 -6
  42. package/dist/components/Tree/buildTree.js +19 -39
  43. package/dist/components/__tests__/__fixtures__/entity.d.ts +2 -0
  44. package/dist/components/__tests__/__fixtures__/entity.js +20 -0
  45. package/dist/components/__tests__/__fixtures__/resource.d.ts +17 -0
  46. package/dist/components/__tests__/__fixtures__/resource.js +13 -0
  47. package/dist/components/dashboard/Dashboard.svelte +5 -3
  48. package/dist/components/dashboard/Dashboard.svelte.d.ts +7 -2
  49. package/dist/components/widgets/ArmPositions.svelte +19 -7
  50. package/dist/draw/v1/drawing_pb.d.ts +341 -0
  51. package/dist/draw/v1/drawing_pb.js +417 -0
  52. package/dist/draw/v1/metadata_pb.d.ts +23 -0
  53. package/dist/draw/v1/metadata_pb.js +39 -0
  54. package/dist/draw/v1/scene_pb.d.ts +230 -0
  55. package/dist/draw/v1/scene_pb.js +298 -0
  56. package/dist/draw/v1/snapshot_pb.d.ts +42 -0
  57. package/dist/draw/v1/snapshot_pb.js +61 -0
  58. package/dist/draw/v1/transforms_pb.d.ts +23 -0
  59. package/dist/draw/v1/transforms_pb.js +39 -0
  60. package/dist/ecs/index.d.ts +4 -0
  61. package/dist/ecs/index.js +4 -0
  62. package/dist/ecs/traits.d.ts +128 -0
  63. package/dist/ecs/traits.js +81 -0
  64. package/dist/ecs/useQuery.svelte.d.ts +4 -0
  65. package/dist/ecs/useQuery.svelte.js +49 -0
  66. package/dist/ecs/useTrait.svelte.d.ts +19 -0
  67. package/dist/ecs/useTrait.svelte.js +40 -0
  68. package/dist/ecs/useWorld.d.ts +4 -0
  69. package/dist/ecs/useWorld.js +10 -0
  70. package/dist/geometry.js +6 -6
  71. package/dist/hooks/__tests__/fixtures/ResizableTestWrapper.svelte +41 -0
  72. package/dist/hooks/__tests__/fixtures/ResizableTestWrapper.svelte.d.ts +6 -0
  73. package/dist/hooks/use3DModels.svelte.js +6 -4
  74. package/dist/hooks/useDrawAPI.svelte.d.ts +0 -10
  75. package/dist/hooks/useDrawAPI.svelte.js +143 -267
  76. package/dist/hooks/useFramelessComponents.svelte.js +1 -1
  77. package/dist/hooks/useFrames.svelte.d.ts +6 -2
  78. package/dist/hooks/useFrames.svelte.js +123 -19
  79. package/dist/hooks/useGeometries.svelte.d.ts +0 -2
  80. package/dist/hooks/useGeometries.svelte.js +49 -25
  81. package/dist/hooks/useObjectEvents.svelte.d.ts +3 -2
  82. package/dist/hooks/useObjectEvents.svelte.js +11 -7
  83. package/dist/hooks/usePartConfig.svelte.d.ts +1 -1
  84. package/dist/hooks/usePartConfig.svelte.js +2 -1
  85. package/dist/hooks/usePointclouds.svelte.d.ts +0 -2
  86. package/dist/hooks/usePointclouds.svelte.js +52 -21
  87. package/dist/hooks/usePose.svelte.js +15 -7
  88. package/dist/hooks/useResizable.svelte.d.ts +12 -0
  89. package/dist/hooks/useResizable.svelte.js +45 -0
  90. package/dist/hooks/useResourceByName.svelte.js +8 -5
  91. package/dist/hooks/useSelection.svelte.d.ts +13 -23
  92. package/dist/hooks/useSelection.svelte.js +45 -65
  93. package/dist/hooks/useVisibility.svelte.d.ts +2 -1
  94. package/dist/hooks/useWeblabs.svelte.d.ts +0 -1
  95. package/dist/hooks/useWeblabs.svelte.js +0 -1
  96. package/dist/hooks/useWorldState.svelte.d.ts +9 -0
  97. package/dist/hooks/useWorldState.svelte.js +158 -107
  98. package/dist/lib.d.ts +1 -0
  99. package/dist/lib.js +2 -0
  100. package/dist/three/BatchedArrow.d.ts +2 -3
  101. package/dist/three/BatchedArrow.js +3 -11
  102. package/dist/three/CapsuleGeometry.d.ts +1 -1
  103. package/dist/three/CapsuleGeometry.js +3 -1
  104. package/dist/transform.js +0 -15
  105. package/package.json +12 -7
  106. package/dist/components/WorldObject.svelte +0 -28
  107. package/dist/components/WorldObject.svelte.d.ts +0 -11
  108. package/dist/components/WorldObjects.svelte +0 -159
  109. package/dist/components/WorldState.svelte +0 -92
  110. package/dist/components/WorldState.svelte.d.ts +0 -7
  111. package/dist/components/__tests__/__fixtures__/worldObject.svelte.d.ts +0 -2
  112. package/dist/components/__tests__/__fixtures__/worldObject.svelte.js +0 -35
  113. package/dist/components/portal/Portal.svelte +0 -25
  114. package/dist/components/portal/Portal.svelte.d.ts +0 -8
  115. package/dist/components/portal/PortalTarget.svelte +0 -18
  116. package/dist/components/portal/PortalTarget.svelte.d.ts +0 -6
  117. package/dist/components/portal/index.d.ts +0 -2
  118. package/dist/components/portal/index.js +0 -2
  119. package/dist/components/portal/usePortalContext.svelte.d.ts +0 -5
  120. package/dist/components/portal/usePortalContext.svelte.js +0 -5
  121. package/dist/hooks/useArrows.svelte.d.ts +0 -3
  122. package/dist/hooks/useArrows.svelte.js +0 -9
  123. package/dist/hooks/useDraggable.svelte.d.ts +0 -10
  124. package/dist/hooks/useDraggable.svelte.js +0 -36
  125. package/dist/hooks/useObjects.svelte.d.ts +0 -7
  126. package/dist/hooks/useObjects.svelte.js +0 -35
  127. package/dist/hooks/usePersistentUUIDs.svelte.d.ts +0 -5
  128. package/dist/hooks/usePersistentUUIDs.svelte.js +0 -13
  129. package/dist/hooks/useResourceByName.svelte.d.ts +0 -7
  130. package/dist/hooks/useStaticGeometries.svelte.d.ts +0 -9
  131. package/dist/hooks/useStaticGeometries.svelte.js +0 -47
  132. package/dist/workers/worldStateWorker.d.ts +0 -1
  133. package/dist/workers/worldStateWorker.js +0 -114
  134. package/dist/world-state-messages.d.ts +0 -23
  135. package/dist/world-state-messages.js +0 -1
@@ -1,7 +1,7 @@
1
1
  import { useResourceNames } from '@viamrobotics/svelte-sdk';
2
2
  import { getContext, setContext } from 'svelte';
3
- const key = Symbol('resource-by-name-context');
4
- export const provideResourceByName = (partID) => {
3
+ export const RESOURCE_BY_NAME_CONTEXT_KEY = Symbol('resource-by-name-context');
4
+ export const createResourceByName = (partID) => {
5
5
  const resourceNames = useResourceNames(partID);
6
6
  const resourceByName = $derived.by(() => {
7
7
  const results = {};
@@ -10,12 +10,15 @@ export const provideResourceByName = (partID) => {
10
10
  }
11
11
  return results;
12
12
  });
13
- setContext(key, {
13
+ return {
14
14
  get current() {
15
15
  return resourceByName;
16
16
  },
17
- });
17
+ };
18
+ };
19
+ export const provideResourceByName = (partID) => {
20
+ setContext(RESOURCE_BY_NAME_CONTEXT_KEY, createResourceByName(partID));
18
21
  };
19
22
  export const useResourceByName = () => {
20
- return getContext(key);
23
+ return getContext(RESOURCE_BY_NAME_CONTEXT_KEY);
21
24
  };
@@ -1,35 +1,25 @@
1
1
  import { Object3D } from 'three';
2
- import type { WorldObject } from '../WorldObject.svelte';
3
- interface SelectionContext {
4
- readonly current: string | undefined;
5
- set(value?: string): void;
2
+ import type { Entity } from 'koota';
3
+ interface SelectedEntityContext {
4
+ readonly current: Entity | undefined;
5
+ set(entity?: Entity): void;
6
6
  }
7
- interface FocusContext {
8
- readonly current: string | undefined;
9
- set(value?: string): void;
7
+ interface FocusedEntityContext {
8
+ readonly current: Entity | undefined;
9
+ set(entity?: Entity): void;
10
10
  }
11
11
  export declare const provideSelection: () => {
12
12
  selection: {
13
- readonly current: string | undefined;
14
- set(value?: string): void;
13
+ readonly current: Entity | undefined;
14
+ set(entity: Entity): void;
15
15
  };
16
16
  focus: {
17
- readonly current: string | undefined;
18
- set(value?: string): void;
17
+ readonly current: Entity | undefined;
18
+ set(entity: Entity): void;
19
19
  };
20
- hover: {
21
- readonly current: string | undefined;
22
- set(value?: string): void;
23
- };
24
- };
25
- export declare const useSelected: () => SelectionContext;
26
- export declare const useFocused: () => FocusContext;
27
- export declare const useFocusedObject: () => {
28
- current: WorldObject | undefined;
29
- };
30
- export declare const useSelectedObject: () => {
31
- current: WorldObject | undefined;
32
20
  };
21
+ export declare const useFocusedEntity: () => FocusedEntityContext;
22
+ export declare const useSelectedEntity: () => SelectedEntityContext;
33
23
  export declare const useFocusedObject3d: () => {
34
24
  current: Object3D | undefined;
35
25
  };
@@ -1,65 +1,47 @@
1
1
  import { isInstanceOf, useThrelte } from '@threlte/core';
2
2
  import { getContext, setContext } from 'svelte';
3
- import { Matrix4, Object3D } from 'three';
4
- import { useObjects } from './useObjects.svelte';
5
- const hoverKey = Symbol('hover-context');
6
- const selectionKey = Symbol('selection-context');
7
- const focusKey = Symbol('focus-context');
8
- const selectedObjectKey = Symbol('selected-frame-context');
9
- const focusedObjectKey = Symbol('focused-frame-context');
3
+ import { BatchedMesh, Matrix4, Object3D } from 'three';
4
+ import { traits, useTrait, useWorld } from '../ecs';
5
+ const selectedKey = Symbol('selected-frame-context');
6
+ const focusedKey = Symbol('focused-frame-context');
10
7
  const focusedObject3dKey = Symbol('focused-object-3d-context');
11
8
  export const provideSelection = () => {
12
- let selected = $state();
13
- let focused = $state();
14
- let hovered = $state();
15
- const selectionContext = {
9
+ const world = useWorld();
10
+ const { scene } = useThrelte();
11
+ let selected = $state.raw();
12
+ let focused = $state.raw();
13
+ $effect(() => {
14
+ return world.onRemove(traits.Name, (entity) => {
15
+ if (entity === selected)
16
+ selected = undefined;
17
+ if (entity === focused)
18
+ focused = undefined;
19
+ });
20
+ });
21
+ const selectedEntityContext = {
16
22
  get current() {
17
23
  return selected;
18
24
  },
19
- set(value) {
20
- selected = value;
25
+ set(entity) {
26
+ selected = entity;
21
27
  },
22
28
  };
23
- setContext(selectionKey, selectionContext);
24
- const focusContext = {
29
+ setContext(selectedKey, selectedEntityContext);
30
+ const focusedEntityContext = {
25
31
  get current() {
26
32
  return focused;
27
33
  },
28
- set(value) {
29
- focused = value;
30
- },
31
- };
32
- setContext(focusKey, focusContext);
33
- const hoverContext = {
34
- get current() {
35
- return hovered;
36
- },
37
- set(value) {
38
- hovered = value;
39
- },
40
- };
41
- setContext(hoverKey, hoverContext);
42
- const objects = useObjects();
43
- const selectedObject = $derived(objects.current.find((object) => object.uuid === selected));
44
- const selectedObjectContext = {
45
- get current() {
46
- return selectedObject;
34
+ set(entity) {
35
+ focused = entity;
47
36
  },
48
37
  };
49
- setContext(selectedObjectKey, selectedObjectContext);
50
- const focusedObject = $derived(objects.current.find((object) => object.uuid === focused));
51
- const focusedObjectContext = {
52
- get current() {
53
- return focusedObject;
54
- },
55
- };
56
- setContext(focusedObjectKey, focusedObjectContext);
57
- const { scene } = useThrelte();
58
- const uuid = $derived(focusedObject?.uuid);
38
+ setContext(focusedKey, focusedEntityContext);
59
39
  const focusedObject3d = $derived.by(() => {
60
- if (!uuid)
40
+ const name = focused?.get(traits.Name);
41
+ if (!name) {
61
42
  return;
62
- const object = scene.getObjectByProperty('uuid', uuid)?.clone();
43
+ }
44
+ const object = scene.getObjectByName(name)?.clone();
63
45
  object?.traverse((child) => {
64
46
  if (isInstanceOf(child, 'LineSegments')) {
65
47
  child.raycast = () => null;
@@ -73,42 +55,40 @@ export const provideSelection = () => {
73
55
  },
74
56
  });
75
57
  return {
76
- selection: selectionContext,
77
- focus: focusContext,
78
- hover: hoverContext,
58
+ selection: selectedEntityContext,
59
+ focus: focusedEntityContext,
79
60
  };
80
61
  };
81
- export const useSelected = () => {
82
- return getContext(selectionKey);
62
+ export const useFocusedEntity = () => {
63
+ return getContext(focusedKey);
83
64
  };
84
- export const useFocused = () => {
85
- return getContext(focusKey);
86
- };
87
- export const useFocusedObject = () => {
88
- return getContext(focusedObjectKey);
89
- };
90
- export const useSelectedObject = () => {
91
- return getContext(selectedObjectKey);
65
+ export const useSelectedEntity = () => {
66
+ return getContext(selectedKey);
92
67
  };
93
68
  export const useFocusedObject3d = () => {
94
69
  return getContext(focusedObject3dKey);
95
70
  };
96
71
  const matrix = new Matrix4();
97
72
  export const useSelectedObject3d = () => {
98
- const selectedObject = useSelectedObject();
73
+ const selectedEntity = useSelectedEntity();
99
74
  const { scene } = useThrelte();
75
+ const name = useTrait(() => selectedEntity.current, traits.Name);
76
+ const instance = useTrait(() => selectedEntity.current, traits.Instance);
100
77
  const object = $derived.by(() => {
101
- if (!selectedObject.current) {
78
+ if (!selectedEntity.current) {
102
79
  return;
103
80
  }
104
- if (selectedObject.current.metadata.batched) {
81
+ if (instance.current) {
105
82
  const proxy = new Object3D();
106
- const { id, object } = selectedObject.current.metadata.batched;
107
- object.getMatrixAt(id, matrix);
83
+ const mesh = scene.getObjectById(instance.current.meshID);
84
+ mesh?.getMatrixAt(instance.current.instanceID, matrix);
108
85
  proxy.applyMatrix4(matrix);
109
86
  return proxy;
110
87
  }
111
- return scene.getObjectByProperty('uuid', selectedObject.current.uuid);
88
+ if (!name.current) {
89
+ return;
90
+ }
91
+ return scene.getObjectByName(name.current);
112
92
  });
113
93
  return {
114
94
  get current() {
@@ -1,5 +1,6 @@
1
+ import type { Entity } from 'koota';
1
2
  import { SvelteMap } from 'svelte/reactivity';
2
- type Context = SvelteMap<string, boolean>;
3
+ type Context = SvelteMap<Entity, boolean>;
3
4
  export declare const provideVisibility: () => void;
4
5
  export declare const useVisibility: () => Context;
5
6
  export {};
@@ -1,5 +1,4 @@
1
1
  export declare const WEBLABS_EXPERIMENTS: {
2
- readonly MOTION_TOOLS_EDIT_FRAME: "MOTION_TOOLS_EDIT_FRAME";
3
2
  readonly MOTION_TOOLS_RENDER_ARM_MODELS: "MOTION_TOOLS_RENDER_ARM_MODELS";
4
3
  };
5
4
  export declare const WEBLABS_CONTEXT_KEY: unique symbol;
@@ -1,7 +1,6 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  import { SvelteSet } from 'svelte/reactivity';
3
3
  export const WEBLABS_EXPERIMENTS = {
4
- MOTION_TOOLS_EDIT_FRAME: 'MOTION_TOOLS_EDIT_FRAME',
5
4
  MOTION_TOOLS_RENDER_ARM_MODELS: 'MOTION_TOOLS_RENDER_ARM_MODELS',
6
5
  };
7
6
  export const WEBLABS_CONTEXT_KEY = Symbol('weblabs-context');
@@ -0,0 +1,9 @@
1
+ import { type TransformChangeEvent, type TransformWithUUID } from '@viamrobotics/sdk';
2
+ export type ChangeMessage = {
3
+ type: 'change';
4
+ events: TransformChangeEvent[];
5
+ };
6
+ export type TransformEvent = TransformChangeEvent & {
7
+ transform: TransformWithUUID;
8
+ };
9
+ export declare const provideWorldStates: () => void;
@@ -1,85 +1,125 @@
1
- import { WorldStateStoreClient, TransformChangeType, ResourceName, } from '@viamrobotics/sdk';
1
+ import { WorldStateStoreClient, TransformChangeType, } from '@viamrobotics/sdk';
2
2
  import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
3
- import { fromTransform } from '../WorldObject.svelte';
3
+ import { parseMetadata } from '../WorldObject.svelte';
4
4
  import { usePartID } from './usePartID.svelte';
5
- import { setInUnsafe } from '@thi.ng/paths';
6
- import { getContext, setContext } from 'svelte';
7
- const key = Symbol('world-state-context');
8
- const worker = new Worker(new URL('../workers/worldStateWorker', import.meta.url), {
9
- type: 'module',
10
- });
5
+ import { traits, useWorld } from '../ecs';
6
+ import { createPose } from '../transform';
7
+ import { useThrelte } from '@threlte/core';
8
+ import { createBox, createCapsule, createSphere } from '../geometry';
9
+ import { parsePlyInput } from '../ply';
11
10
  export const provideWorldStates = () => {
12
11
  const partID = usePartID();
13
12
  const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
14
- const current = $derived.by(() => Object.fromEntries(resourceNames.current.map(({ name }) => [
15
- name,
16
- createWorldState(() => partID.current, () => name),
17
- ])));
18
- setContext(key, {
19
- get names() {
20
- return resourceNames.current;
21
- },
22
- get current() {
23
- return current;
24
- },
13
+ const clients = $derived(resourceNames.current.map(({ name }) => createResourceClient(WorldStateStoreClient, () => partID.current, () => name)));
14
+ $effect(() => {
15
+ const cleanups = [];
16
+ for (const client of clients) {
17
+ cleanups.push(createWorldState(client));
18
+ }
19
+ return () => {
20
+ for (const cleanup of cleanups) {
21
+ cleanup();
22
+ }
23
+ };
25
24
  });
26
25
  };
27
- export const useWorldStates = () => {
28
- return getContext(key);
29
- };
30
- export const useWorldState = (resourceName) => {
31
- return useWorldStates().current[resourceName()];
32
- };
33
- const createWorldState = (partID, resourceName) => {
34
- const client = createResourceClient(WorldStateStoreClient, partID, resourceName);
35
- let initialized = $state(false);
36
- let transforms = $state.raw({});
37
- const transformsList = $derived.by(() => Object.values(transforms));
38
- const worldObjectsList = $derived.by(() => transformsList.map(fromTransform));
39
- let pendingEvents = [];
26
+ const createWorldState = (client) => {
27
+ const { invalidate } = useThrelte();
28
+ const world = useWorld();
29
+ const entities = new Map();
30
+ const spawnEntity = (transform) => {
31
+ if (entities.has(transform.uuidString)) {
32
+ return;
33
+ }
34
+ const metadata = parseMetadata(transform.metadata?.fields);
35
+ const pose = createPose(transform.poseInObserverFrame?.pose);
36
+ const entityTraits = [];
37
+ const parent = transform.poseInObserverFrame?.referenceFrame;
38
+ if (parent && parent !== 'world') {
39
+ entityTraits.push(traits.Parent(parent));
40
+ }
41
+ if (metadata.color) {
42
+ entityTraits.push(traits.Color(metadata.color));
43
+ }
44
+ if (metadata.colors) {
45
+ entityTraits.push(traits.VertexColors(metadata.colors));
46
+ }
47
+ if (transform.physicalObject) {
48
+ entityTraits.push(traits.Geometry(transform.physicalObject));
49
+ }
50
+ if (metadata.shape === 'line' && metadata.points) {
51
+ entityTraits.push(traits.LineGeometry(metadata.points), traits.DottedLineColor(metadata.lineDotColor));
52
+ }
53
+ if (metadata.gltf) {
54
+ entityTraits.push(traits.GLTF(metadata.gltf));
55
+ }
56
+ if (metadata.shape === 'arrow') {
57
+ entityTraits.push(traits.Arrow);
58
+ }
59
+ entityTraits.push(traits.Name(transform.referenceFrame), traits.Pose(pose), traits.WorldStateStoreAPI);
60
+ const entity = world.spawn(...entityTraits);
61
+ entities.set(transform.uuidString, entity);
62
+ };
63
+ const destroyEntity = (uuid) => {
64
+ const entity = entities.get(uuid);
65
+ if (!entity)
66
+ return;
67
+ entity.destroy();
68
+ entities.delete(uuid);
69
+ };
70
+ const updateEntity = (transform, changes) => {
71
+ const entity = entities.get(transform.uuidString);
72
+ if (!entity)
73
+ return;
74
+ for (const path of changes) {
75
+ if (typeof path === 'string') {
76
+ if (path.startsWith('poseInObserverFrame.pose')) {
77
+ entity.set(traits.Pose, transform.poseInObserverFrame?.pose ?? createPose());
78
+ }
79
+ else if (path.startsWith('physicalObject') && transform.physicalObject) {
80
+ const { geometryType } = transform.physicalObject;
81
+ if (geometryType.case === 'box') {
82
+ entity.set(traits.Box, createBox(geometryType.value));
83
+ }
84
+ else if (geometryType.case === 'capsule') {
85
+ entity.set(traits.Capsule, createCapsule(geometryType.value));
86
+ }
87
+ else if (geometryType.case === 'sphere') {
88
+ entity.set(traits.Sphere, createSphere(geometryType.value));
89
+ }
90
+ else if (geometryType.case === 'mesh') {
91
+ entity.set(traits.BufferGeometry, parsePlyInput(geometryType.value.mesh));
92
+ }
93
+ }
94
+ }
95
+ }
96
+ };
97
+ let initialized = false;
40
98
  let flushScheduled = false;
99
+ let pendingEvents = [];
41
100
  const listUUIDs = createResourceQuery(client, 'listUUIDs');
42
- const getTransforms = $derived(listUUIDs.data?.map((uuid) => {
101
+ const getTransformQueries = $derived(listUUIDs.data?.map((uuid) => {
43
102
  return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
44
103
  }));
45
104
  const changeStream = createResourceStream(client, 'streamTransformChanges', {
46
105
  refetchMode: 'replace',
47
106
  });
48
- const initialize = (initial) => {
49
- const next = { ...transforms };
50
- for (const transform of initial) {
51
- next[transform.uuidString] = transform;
52
- }
53
- transforms = next;
54
- initialized = true;
55
- };
56
107
  const applyEvents = (events) => {
57
- if (events.length === 0)
58
- return;
59
- const next = { ...transforms };
60
108
  for (const event of events) {
61
- switch (event.type) {
62
- case TransformChangeType.ADDED:
63
- next[event.uuidString] = event.transform;
64
- break;
65
- case TransformChangeType.REMOVED:
66
- delete next[event.uuidString];
67
- break;
68
- case TransformChangeType.UPDATED: {
69
- if (event.changes.length === 0)
70
- continue;
71
- let toUpdate = next[event.uuidString];
72
- if (!toUpdate)
73
- continue;
74
- for (const [path, value] of event.changes) {
75
- toUpdate = setInUnsafe(toUpdate, path, value);
76
- }
77
- next[event.uuidString] = toUpdate;
78
- break;
79
- }
109
+ if (event.changeType === TransformChangeType.ADDED) {
110
+ spawnEntity(event.transform);
111
+ }
112
+ else if (event.changeType === TransformChangeType.REMOVED) {
113
+ destroyEntity(event.transform.uuidString);
114
+ }
115
+ else if (event.changeType === TransformChangeType.UPDATED) {
116
+ updateEntity(event.transform, event.updatedFields?.paths ?? []);
117
+ }
118
+ else {
119
+ console.error('Unspecified change type.', event);
80
120
  }
81
121
  }
82
- transforms = next;
122
+ invalidate();
83
123
  };
84
124
  const scheduleFlush = () => {
85
125
  if (flushScheduled)
@@ -87,64 +127,75 @@ const createWorldState = (partID, resourceName) => {
87
127
  flushScheduled = true;
88
128
  requestAnimationFrame(() => {
89
129
  const toApply = pendingEvents;
90
- if (toApply.length === 0)
91
- return;
92
130
  applyEvents(toApply);
93
131
  flushScheduled = false;
94
132
  pendingEvents = [];
95
133
  });
96
134
  };
97
135
  $effect(() => {
98
- if (!getTransforms)
136
+ if (!getTransformQueries)
99
137
  return;
100
138
  if (initialized)
101
139
  return;
102
- if (getTransforms.some((query) => query?.isLoading))
140
+ if (getTransformQueries.some((query) => query?.isLoading))
103
141
  return;
104
- const data = getTransforms
105
- .flatMap((query) => query?.data ?? [])
142
+ const transforms = getTransformQueries
143
+ .flatMap((query) => query?.data)
106
144
  .filter((transform) => transform !== undefined);
107
- if (data.length === 0)
108
- return;
109
- initialize(data);
145
+ for (const transform of transforms) {
146
+ spawnEntity(transform);
147
+ }
148
+ invalidate();
149
+ initialized = true;
110
150
  });
111
151
  $effect(() => {
112
- worker.onmessage = (e) => {
113
- if (e.data.type !== 'process')
114
- return;
115
- const { events } = e.data ?? { events: [] };
116
- if (events.length === 0)
117
- return;
118
- pendingEvents.push(...events);
119
- scheduleFlush();
120
- };
121
- return () => {
122
- worker.terminate();
123
- };
124
- });
125
- $effect.pre(() => {
126
152
  if (changeStream?.data === undefined)
127
153
  return;
128
- const events = changeStream.data.filter((event) => event.transform !== undefined);
129
- if (events.length === 0)
130
- return;
131
- worker.postMessage({ type: 'change', events });
154
+ const eventsByUUID = new Map();
155
+ for (const event of changeStream.data) {
156
+ if (!event.transform) {
157
+ continue;
158
+ }
159
+ const uuid = event.transform.uuidString;
160
+ const existing = eventsByUUID.get(uuid);
161
+ if (!existing) {
162
+ eventsByUUID.set(uuid, event);
163
+ continue;
164
+ }
165
+ switch (event.changeType) {
166
+ case TransformChangeType.REMOVED:
167
+ eventsByUUID.set(uuid, event);
168
+ break;
169
+ case TransformChangeType.ADDED:
170
+ if (existing.changeType !== TransformChangeType.REMOVED) {
171
+ eventsByUUID.set(uuid, event);
172
+ }
173
+ break;
174
+ case TransformChangeType.UPDATED:
175
+ // merge with existing updated event
176
+ if (existing.changeType === TransformChangeType.UPDATED) {
177
+ existing.updatedFields ??= { paths: [] };
178
+ const paths = event.updatedFields?.paths ?? [];
179
+ for (const path of paths) {
180
+ if (existing.updatedFields.paths.includes(path)) {
181
+ continue;
182
+ }
183
+ existing.updatedFields.paths.push(path);
184
+ }
185
+ existing.transform = event.transform;
186
+ }
187
+ else {
188
+ eventsByUUID.set(uuid, event);
189
+ }
190
+ break;
191
+ }
192
+ }
193
+ pendingEvents.push(...eventsByUUID.values());
194
+ scheduleFlush();
132
195
  });
133
- return {
134
- get name() {
135
- return resourceName();
136
- },
137
- get transforms() {
138
- return transformsList;
139
- },
140
- get worldObjects() {
141
- return worldObjectsList;
142
- },
143
- get listUUIDs() {
144
- return listUUIDs;
145
- },
146
- get getTransforms() {
147
- return getTransforms;
148
- },
196
+ return () => {
197
+ for (const [, entity] of entities) {
198
+ entity.destroy();
199
+ }
149
200
  };
150
201
  };
package/dist/lib.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Geometry } from './components/Geometry.svelte';
2
2
  export { default as AxesHelper } from './components/AxesHelper.svelte';
3
+ export { Snapshot } from './draw/v1/snapshot_pb';
3
4
  export { BatchedArrow } from './three/BatchedArrow';
4
5
  export { CapsuleGeometry } from './three/CapsuleGeometry';
5
6
  export { OrientationVector } from './three/OrientationVector';
package/dist/lib.js CHANGED
@@ -3,6 +3,8 @@
3
3
  // ensure you write a corresponding unit test to assert the component works in absence of parent providers in /src/lib/__tests__/PureComponents.svelte.spec.ts
4
4
  export { default as Geometry } from './components/Geometry.svelte';
5
5
  export { default as AxesHelper } from './components/AxesHelper.svelte';
6
+ // Draw
7
+ export { Snapshot } from './draw/v1/snapshot_pb';
6
8
  // Classes
7
9
  export { BatchedArrow } from './three/BatchedArrow';
8
10
  export { CapsuleGeometry } from './three/CapsuleGeometry';
@@ -7,10 +7,9 @@ export declare class BatchedArrow {
7
7
  _ids: Set<number>;
8
8
  _max: number;
9
9
  constructor();
10
- addArrow(direction: Vector3, origin: Vector3, length?: number, color?: Color, arrowHeadAtPose?: boolean): number;
10
+ addArrow(direction: Vector3, origin: Vector3, color?: Color): number;
11
11
  removeArrow(instanceId: number): void;
12
12
  clear(): void;
13
13
  getBoundingBoxAt(instanceId: number, target: OBB): OBB;
14
- updateArrow(instanceId: number, origin: Vector3, direction: Vector3, length: number, color: Color, arrowHeadAtPose: boolean): import("three").Quaternion | undefined;
15
- get object3d(): BatchedMesh;
14
+ updateArrow(instanceId: number, origin: Vector3, direction: Vector3, color?: Color): import("three").Quaternion | undefined;
16
15
  }
@@ -3,7 +3,6 @@ import { createArrowGeometry } from './arrow';
3
3
  const black = new Color('black');
4
4
  const axis = new Vector3();
5
5
  const object3d = new Object3D();
6
- const vec3 = new Vector3();
7
6
  const box1 = new Box3();
8
7
  const col = new Color();
9
8
  let index = 0;
@@ -23,14 +22,14 @@ export class BatchedArrow {
23
22
  this.mesh.frustumCulled = false;
24
23
  this._geometryId = this.mesh.addGeometry(geometry);
25
24
  }
26
- addArrow(direction, origin, length = 0.1, color = black, arrowHeadAtPose = true) {
25
+ addArrow(direction, origin, color = black) {
27
26
  if (this.mesh.instanceCount >= this._max) {
28
27
  this._max += 20_000;
29
28
  this.mesh.setInstanceCount(this._max);
30
29
  }
31
30
  const instanceId = this._pool.pop() ?? this.mesh.addInstance(this._geometryId);
32
31
  this._ids.add(instanceId);
33
- this.updateArrow(instanceId, origin, direction, length, color, arrowHeadAtPose);
32
+ this.updateArrow(instanceId, origin, direction, color);
34
33
  return instanceId;
35
34
  }
36
35
  removeArrow(instanceId) {
@@ -50,11 +49,7 @@ export class BatchedArrow {
50
49
  }
51
50
  return target;
52
51
  }
53
- updateArrow(instanceId, origin, direction, length, color, arrowHeadAtPose) {
54
- if (arrowHeadAtPose) {
55
- // Compute the base position so the arrow ends at the origin
56
- origin.sub(vec3.copy(direction).multiplyScalar(length));
57
- }
52
+ updateArrow(instanceId, origin, direction, color) {
58
53
  direction.normalize();
59
54
  object3d.position.copy(origin);
60
55
  if (direction.y > 0.99999)
@@ -72,7 +67,4 @@ export class BatchedArrow {
72
67
  }
73
68
  this.mesh.setVisibleAt(instanceId, true);
74
69
  }
75
- get object3d() {
76
- return this.mesh;
77
- }
78
70
  }
@@ -6,5 +6,5 @@ import { LatheGeometry } from 'three';
6
6
  */
7
7
  export declare class CapsuleGeometry extends LatheGeometry {
8
8
  type: string;
9
- constructor(radius?: number, length?: number, capSegments?: number, radialSegments?: number);
9
+ constructor(r?: number, l?: number, capSegments?: number, radialSegments?: number);
10
10
  }