@viamrobotics/motion-tools 0.14.11 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WorldObject.svelte.d.ts +9 -2
- package/dist/color.js +2 -2
- package/dist/components/Details.svelte +8 -7
- package/dist/components/FileDrop.svelte +57 -20
- package/dist/components/Geometry.svelte +6 -1
- package/dist/components/Tree/TreeContainer.svelte +7 -3
- package/dist/components/Tree/buildTree.js +4 -1
- package/dist/components/WorldObjects.svelte +2 -1
- package/dist/components/WorldState.svelte +36 -25
- package/dist/hooks/useDrawAPI.svelte.d.ts +2 -1
- package/dist/hooks/useDrawAPI.svelte.js +7 -10
- package/dist/hooks/useEnvironment.svelte.d.ts +2 -0
- package/dist/hooks/useEnvironment.svelte.js +8 -4
- package/dist/hooks/useSettings.svelte.js +5 -1
- package/dist/hooks/useWeblabs.svelte.d.ts +3 -0
- package/dist/hooks/useWeblabs.svelte.js +3 -0
- package/dist/three/BatchedArrow.d.ts +11 -27
- package/dist/three/BatchedArrow.js +48 -112
- package/dist/three/arrow.d.ts +7 -0
- package/dist/three/arrow.js +27 -0
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Geometry, PlainMessage, Pose, Struct, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
|
-
import { BatchedMesh, Color, Object3D, Vector3 } from 'three';
|
|
2
|
+
import { BatchedMesh, Color, Object3D, Vector3, type BufferGeometry } from 'three';
|
|
3
3
|
import type { ValueOf } from 'type-fest';
|
|
4
4
|
import type { OBB } from 'three/addons/math/OBB.js';
|
|
5
5
|
export type PointsGeometry = {
|
|
@@ -16,7 +16,14 @@ export type LinesGeometry = {
|
|
|
16
16
|
value: Float32Array;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
-
export type
|
|
19
|
+
export type ThreeBufferGeometry = {
|
|
20
|
+
center: undefined;
|
|
21
|
+
geometryType: {
|
|
22
|
+
case: 'bufferGeometry';
|
|
23
|
+
value: BufferGeometry;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type Geometries = Geometry | PointsGeometry | LinesGeometry | ThreeBufferGeometry;
|
|
20
27
|
export declare const SupportedShapes: {
|
|
21
28
|
readonly points: "points";
|
|
22
29
|
readonly line: "line";
|
package/dist/color.js
CHANGED
|
@@ -102,8 +102,8 @@ export const isRGB = (color) => {
|
|
|
102
102
|
};
|
|
103
103
|
export const parseRGB = (color, defaultColor = { r: 0, g: 0, b: 0 }) => {
|
|
104
104
|
if (!isRGB(color))
|
|
105
|
-
return new Color().setRGB(defaultColor.r, defaultColor.g, defaultColor.b);
|
|
106
|
-
return new Color().setRGB(color.r, color.g, color.b);
|
|
105
|
+
return new Color().setRGB(defaultColor.r > 1 ? defaultColor.r / 255 : defaultColor.r, defaultColor.g > 1 ? defaultColor.g / 255 : defaultColor.g, defaultColor.b > 1 ? defaultColor.b / 255 : defaultColor.b);
|
|
106
|
+
return new Color().setRGB(color.r > 1 ? color.r / 255 : color.r, color.g > 1 ? color.g / 255 : color.g, color.b > 1 ? color.b / 255 : color.b);
|
|
107
107
|
};
|
|
108
108
|
export const parseOpacity = (opacity, defaultOpacity = 1) => {
|
|
109
109
|
if (!isNumber(opacity))
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
import { usePartConfig } from '../hooks/usePartConfig.svelte'
|
|
28
28
|
import { FrameConfigUpdater } from '../FrameConfigUpdater.svelte'
|
|
29
29
|
import { useWeblabs } from '../hooks/useWeblabs.svelte'
|
|
30
|
-
|
|
30
|
+
import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
|
|
31
|
+
import { useEnvironment } from '../hooks/useEnvironment.svelte'
|
|
31
32
|
const { ...rest } = $props()
|
|
32
33
|
|
|
33
34
|
const focused = useFocused()
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
const selectedObject = useSelectedObject()
|
|
39
40
|
const selectedObject3d = useSelectedObject3d()
|
|
40
41
|
const weblab = useWeblabs()
|
|
41
|
-
|
|
42
|
+
const environment = useEnvironment()
|
|
42
43
|
const object = $derived(focusedObject.current ?? selectedObject.current)
|
|
43
44
|
const object3d = $derived(focusedObject3d.current ?? selectedObject3d.current)
|
|
44
45
|
const worldPosition = $state({ x: 0, y: 0, z: 0 })
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
})
|
|
105
106
|
|
|
106
107
|
const getCopyClipboardText = () => {
|
|
107
|
-
if (weblab.isActive(
|
|
108
|
+
if (weblab.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME)) {
|
|
108
109
|
return JSON.stringify(
|
|
109
110
|
{
|
|
110
111
|
worldPosition: worldPosition,
|
|
@@ -303,7 +304,7 @@
|
|
|
303
304
|
</div>
|
|
304
305
|
{/if}
|
|
305
306
|
|
|
306
|
-
<WeblabActive experiment=
|
|
307
|
+
<WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME}>
|
|
307
308
|
{@const ParentFrame = showEditFrameOptions ? DropDownField : ImmutableField}
|
|
308
309
|
|
|
309
310
|
<div>
|
|
@@ -487,7 +488,7 @@
|
|
|
487
488
|
</WeblabActive>
|
|
488
489
|
|
|
489
490
|
<WeblabActive
|
|
490
|
-
experiment=
|
|
491
|
+
experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME}
|
|
491
492
|
renderIfActive={false}
|
|
492
493
|
>
|
|
493
494
|
{#if object.geometry}
|
|
@@ -563,8 +564,8 @@
|
|
|
563
564
|
</Button>
|
|
564
565
|
{/if}
|
|
565
566
|
|
|
566
|
-
<WeblabActive experiment=
|
|
567
|
-
{#if showEditFrameOptions}
|
|
567
|
+
<WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME}>
|
|
568
|
+
{#if showEditFrameOptions && environment.current.isStandalone}
|
|
568
569
|
<Button
|
|
569
570
|
variant="danger"
|
|
570
571
|
class="mt-2 w-full"
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { useDrawAPI } from '../hooks/useDrawAPI.svelte'
|
|
3
3
|
import { parsePcdInWorker, WorldObject } from '../lib'
|
|
4
4
|
import { useToast, ToastVariant } from '@viamrobotics/prime-core'
|
|
5
|
+
import { PLYLoader } from 'three/examples/jsm/Addons.js'
|
|
5
6
|
|
|
6
7
|
let { ...rest } = $props()
|
|
7
8
|
|
|
8
|
-
const { addPoints } = useDrawAPI()
|
|
9
|
+
const { addPoints, addMesh } = useDrawAPI()
|
|
9
10
|
|
|
10
11
|
type DropStates = 'inactive' | 'hovering' | 'loading'
|
|
11
12
|
|
|
@@ -31,6 +32,12 @@
|
|
|
31
32
|
|
|
32
33
|
const toast = useToast()
|
|
33
34
|
|
|
35
|
+
const extensions = {
|
|
36
|
+
PCD: 'pcd',
|
|
37
|
+
PLY: 'ply',
|
|
38
|
+
}
|
|
39
|
+
const supportedFiles = [extensions.PCD, extensions.PLY]
|
|
40
|
+
|
|
34
41
|
const ondrop = (event: DragEvent) => {
|
|
35
42
|
event.preventDefault()
|
|
36
43
|
|
|
@@ -45,9 +52,18 @@
|
|
|
45
52
|
for (const file of files) {
|
|
46
53
|
const ext = file.name.split('.').at(-1)
|
|
47
54
|
|
|
48
|
-
if (ext
|
|
55
|
+
if (!ext) {
|
|
56
|
+
toast({
|
|
57
|
+
message: `Could not determine file extension.`,
|
|
58
|
+
variant: ToastVariant.Danger,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!supportedFiles.includes(ext)) {
|
|
49
65
|
toast({
|
|
50
|
-
message: `.${
|
|
66
|
+
message: `Only ${supportedFiles.map((file) => `.${file}`).join(', ')} files are supported.`,
|
|
51
67
|
variant: ToastVariant.Danger,
|
|
52
68
|
})
|
|
53
69
|
|
|
@@ -65,34 +81,55 @@
|
|
|
65
81
|
})
|
|
66
82
|
|
|
67
83
|
reader.addEventListener('error', () => {
|
|
68
|
-
toast({
|
|
84
|
+
toast({
|
|
85
|
+
message: `${file.name} failed to load.`,
|
|
86
|
+
variant: ToastVariant.Danger,
|
|
87
|
+
})
|
|
69
88
|
})
|
|
70
89
|
|
|
71
90
|
reader.addEventListener('load', async (event) => {
|
|
72
91
|
const arrayBuffer = event.target?.result
|
|
73
92
|
|
|
74
93
|
if (!arrayBuffer || typeof arrayBuffer === 'string') {
|
|
94
|
+
toast({
|
|
95
|
+
message: `${file.name} failed to load.`,
|
|
96
|
+
variant: ToastVariant.Danger,
|
|
97
|
+
})
|
|
98
|
+
|
|
75
99
|
return
|
|
76
100
|
}
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
if (ext === extensions.PCD) {
|
|
103
|
+
const result = await parsePcdInWorker(new Uint8Array(arrayBuffer))
|
|
104
|
+
|
|
105
|
+
addPoints(
|
|
106
|
+
new WorldObject(
|
|
107
|
+
file.name,
|
|
108
|
+
undefined,
|
|
109
|
+
undefined,
|
|
110
|
+
{
|
|
111
|
+
center: undefined,
|
|
112
|
+
geometryType: {
|
|
113
|
+
case: 'points',
|
|
114
|
+
value: result.positions,
|
|
115
|
+
},
|
|
90
116
|
},
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
result.colors ? { colors: result.colors } : undefined
|
|
118
|
+
)
|
|
93
119
|
)
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
|
|
121
|
+
toast({ message: `Loaded ${file.name}`, variant: ToastVariant.Success })
|
|
122
|
+
} else if (ext === extensions.PLY) {
|
|
123
|
+
const result = new PLYLoader().parse(arrayBuffer)
|
|
124
|
+
const worldObject = new WorldObject(file.name, undefined, undefined, {
|
|
125
|
+
center: undefined,
|
|
126
|
+
geometryType: { case: 'bufferGeometry', value: result },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
addMesh(worldObject)
|
|
130
|
+
|
|
131
|
+
toast({ message: `Loaded ${file.name}`, variant: ToastVariant.Success })
|
|
132
|
+
}
|
|
96
133
|
})
|
|
97
134
|
|
|
98
135
|
reader.readAsArrayBuffer(file)
|
|
@@ -103,7 +103,12 @@
|
|
|
103
103
|
{uuid}
|
|
104
104
|
bvh={{ enabled: false }}
|
|
105
105
|
>
|
|
106
|
-
{#if geometry.geometryType.case === '
|
|
106
|
+
{#if geometry.geometryType.case === 'bufferGeometry'}
|
|
107
|
+
<T
|
|
108
|
+
is={geometry.geometryType.value}
|
|
109
|
+
{oncreate}
|
|
110
|
+
/>
|
|
111
|
+
{:else if geometry.geometryType.case === 'mesh'}
|
|
107
112
|
{@const mesh = geometry.geometryType.value.mesh}
|
|
108
113
|
{@const meshGeometry = parsePlyInput(mesh)}
|
|
109
114
|
<T
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
16
16
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
17
17
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
18
|
+
import WeblabActive from '../weblab/WeblabActive.svelte'
|
|
19
|
+
import { WEBLABS_EXPERIMENTS } from '../../hooks/useWeblabs.svelte'
|
|
18
20
|
const { ...rest } = $props()
|
|
19
21
|
|
|
20
22
|
provideTreeExpandedContext()
|
|
@@ -60,9 +62,11 @@
|
|
|
60
62
|
/>
|
|
61
63
|
{/key}
|
|
62
64
|
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
<WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME}>
|
|
66
|
+
{#if environment.current.isStandalone && partID.current && partConfig.hasEditPermissions}
|
|
67
|
+
<AddFrames />
|
|
68
|
+
{/if}
|
|
69
|
+
</WeblabActive>
|
|
66
70
|
|
|
67
71
|
<Logs />
|
|
68
72
|
<Settings />
|
|
@@ -39,8 +39,11 @@ export const buildTreeNodes = (objects, worldStates) => {
|
|
|
39
39
|
children: [],
|
|
40
40
|
href: `/world-state/${worldState.name}/${object.name}`,
|
|
41
41
|
};
|
|
42
|
+
const parentNode = object.referenceFrame && nodeMap.has(object.referenceFrame)
|
|
43
|
+
? nodeMap.get(object.referenceFrame)
|
|
44
|
+
: node;
|
|
42
45
|
nodeMap.set(object.name, child);
|
|
43
|
-
|
|
46
|
+
parentNode.children?.push(child);
|
|
44
47
|
}
|
|
45
48
|
nodeMap.set(worldState.name, node);
|
|
46
49
|
rootNodes.push(node);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { useWeblabs } from '../hooks/useWeblabs.svelte'
|
|
19
19
|
import type { WorldObject } from '../WorldObject.svelte'
|
|
20
20
|
import type { Pose as ViamPose } from '@viamrobotics/sdk'
|
|
21
|
+
import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
|
|
21
22
|
const points = usePointClouds()
|
|
22
23
|
const drawAPI = useDrawAPI()
|
|
23
24
|
const frames = useFrames()
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
const weblabs = useWeblabs()
|
|
28
29
|
|
|
29
30
|
const weblabedDeterminePose = (object: WorldObject, pose: ViamPose | undefined) => {
|
|
30
|
-
if (weblabs.isActive(
|
|
31
|
+
if (weblabs.isActive(WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME)) {
|
|
31
32
|
return determinePose(object, pose)
|
|
32
33
|
}
|
|
33
34
|
return pose ?? object.pose
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import Portal from './portal/Portal.svelte'
|
|
7
7
|
import PortalTarget from './portal/PortalTarget.svelte'
|
|
8
8
|
import { WorldObject } from '../WorldObject.svelte'
|
|
9
|
-
import { useArrows } from '../hooks/useArrows.svelte'
|
|
10
9
|
import { poseToDirection } from '../transform'
|
|
10
|
+
import { BatchedArrow } from '../three/BatchedArrow'
|
|
11
|
+
import { T } from '@threlte/core'
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
worldObjects: WorldObject[]
|
|
@@ -15,38 +16,36 @@
|
|
|
15
16
|
|
|
16
17
|
let { worldObjects }: Props = $props()
|
|
17
18
|
|
|
18
|
-
const batchedArrow = useArrows()
|
|
19
19
|
const currentArrows: Record<string, { id: number; arrow: WorldObject }> = {}
|
|
20
|
+
const arrowBatches = $state<Record<string, BatchedArrow>>({})
|
|
20
21
|
|
|
21
22
|
const arrows = $derived(worldObjects.filter((object) => object.metadata?.shape === 'arrow'))
|
|
22
23
|
const objects = $derived(worldObjects.filter((object) => object.metadata?.shape !== 'arrow'))
|
|
23
24
|
|
|
24
25
|
const getArrows = () => ({ ...currentArrows })
|
|
25
|
-
const getArrow = (uuid: string) =>
|
|
26
|
-
|
|
26
|
+
const getArrow = (referenceFrame: string, uuid: string) =>
|
|
27
|
+
currentArrows[`${referenceFrame}:${uuid}`]
|
|
28
|
+
|
|
29
|
+
const removeArrow = (referenceFrame: string, uuid: string) => {
|
|
30
|
+
delete currentArrows[`${referenceFrame}:${uuid}`]
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const setArrow = (arrow: WorldObject) => {
|
|
28
|
-
const
|
|
34
|
+
const referenceFrame = arrow.referenceFrame ?? 'world'
|
|
35
|
+
const currentArrow = getArrow(referenceFrame, arrow.uuid)
|
|
29
36
|
const color = arrow.metadata?.color ?? new Color('yellow')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
0.1,
|
|
36
|
-
color,
|
|
37
|
-
true
|
|
38
|
-
)
|
|
37
|
+
const direction = poseToDirection(arrow.pose)
|
|
38
|
+
const position = new Vector3(arrow.pose.x, arrow.pose.y, arrow.pose.z)
|
|
39
|
+
|
|
40
|
+
arrowBatches[referenceFrame] ??= new BatchedArrow()
|
|
41
|
+
const batchedArrow = arrowBatches[referenceFrame]
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
if (currentArrow) {
|
|
44
|
+
batchedArrow.updateArrow(currentArrow.id, direction, position, 0.1, color, true)
|
|
45
|
+
currentArrows[`${referenceFrame}:${arrow.uuid}`] = { id: currentArrow.id, arrow }
|
|
41
46
|
} else {
|
|
42
|
-
const id = batchedArrow.addArrow(
|
|
43
|
-
|
|
44
|
-
new Vector3(arrow.pose.x, arrow.pose.y, arrow.pose.z),
|
|
45
|
-
0.1,
|
|
46
|
-
color,
|
|
47
|
-
true
|
|
48
|
-
)
|
|
49
|
-
currentArrows[arrow.uuid] = { id, arrow }
|
|
47
|
+
const id = batchedArrow.addArrow(direction, position, 0.1, color, true)
|
|
48
|
+
currentArrows[`${referenceFrame}:${arrow.uuid}`] = { id, arrow }
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -58,12 +57,24 @@
|
|
|
58
57
|
})
|
|
59
58
|
|
|
60
59
|
Object.values(toRemove).forEach(({ id, arrow }) => {
|
|
61
|
-
|
|
62
|
-
removeArrow(
|
|
60
|
+
const referenceFrame = arrow.referenceFrame ?? 'world'
|
|
61
|
+
arrowBatches[referenceFrame].removeArrow(id)
|
|
62
|
+
removeArrow(referenceFrame, arrow.uuid)
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
|
+
{#each Object.entries(arrowBatches) as [referenceFrame, batch] (referenceFrame)}
|
|
68
|
+
<Portal id={referenceFrame}>
|
|
69
|
+
<T
|
|
70
|
+
name={batch.object3d.name}
|
|
71
|
+
is={batch.object3d}
|
|
72
|
+
dispose={false}
|
|
73
|
+
bvh={{ enabled: false }}
|
|
74
|
+
/>
|
|
75
|
+
</Portal>
|
|
76
|
+
{/each}
|
|
77
|
+
|
|
67
78
|
{#each objects as object (object.uuid)}
|
|
68
79
|
<Portal id={object.referenceFrame}>
|
|
69
80
|
<Frame
|
|
@@ -2,7 +2,6 @@ import { Vector3 } from 'three';
|
|
|
2
2
|
import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
|
|
3
3
|
type ConnectionStatus = 'connecting' | 'open' | 'closed';
|
|
4
4
|
interface Context {
|
|
5
|
-
addPoints(worldObject: WorldObject<PointsGeometry>): void;
|
|
6
5
|
points: WorldObject<PointsGeometry>[];
|
|
7
6
|
frames: WorldObject[];
|
|
8
7
|
lines: WorldObject[];
|
|
@@ -16,6 +15,8 @@ interface Context {
|
|
|
16
15
|
lookAt: Vector3;
|
|
17
16
|
animate: boolean;
|
|
18
17
|
} | undefined;
|
|
18
|
+
addPoints(worldObject: WorldObject<PointsGeometry>): void;
|
|
19
|
+
addMesh(worldObject: WorldObject): void;
|
|
19
20
|
clearCamera: () => void;
|
|
20
21
|
}
|
|
21
22
|
export declare const provideDrawAPI: () => void;
|
|
@@ -112,13 +112,7 @@ export const provideDrawAPI = () => {
|
|
|
112
112
|
result.pose = data.center;
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
const geometry =
|
|
116
|
-
label: data.label,
|
|
117
|
-
center: undefined,
|
|
118
|
-
geometryType: {
|
|
119
|
-
case: undefined,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
115
|
+
const geometry = createGeometry();
|
|
122
116
|
if ('mesh' in data) {
|
|
123
117
|
geometry.geometryType.case = 'mesh';
|
|
124
118
|
geometry.geometryType.value = data.mesh;
|
|
@@ -443,9 +437,6 @@ export const provideDrawAPI = () => {
|
|
|
443
437
|
get points() {
|
|
444
438
|
return points;
|
|
445
439
|
},
|
|
446
|
-
addPoints(worldObject) {
|
|
447
|
-
points.push(worldObject);
|
|
448
|
-
},
|
|
449
440
|
get lines() {
|
|
450
441
|
return lines;
|
|
451
442
|
},
|
|
@@ -467,6 +458,12 @@ export const provideDrawAPI = () => {
|
|
|
467
458
|
get camera() {
|
|
468
459
|
return camera;
|
|
469
460
|
},
|
|
461
|
+
addPoints(worldObject) {
|
|
462
|
+
points.push(worldObject);
|
|
463
|
+
},
|
|
464
|
+
addMesh(worldObject) {
|
|
465
|
+
meshes.push(worldObject);
|
|
466
|
+
},
|
|
470
467
|
clearCamera: () => {
|
|
471
468
|
camera = undefined;
|
|
472
469
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const ENVIRONMENT_CONTEXT_KEY: unique symbol;
|
|
1
2
|
interface Environemnt {
|
|
2
3
|
viewerMode: 'edit' | 'monitor';
|
|
3
4
|
isStandalone: boolean;
|
|
@@ -5,6 +6,7 @@ interface Environemnt {
|
|
|
5
6
|
interface Context {
|
|
6
7
|
current: Environemnt;
|
|
7
8
|
}
|
|
9
|
+
export declare const createEnvironment: () => Context;
|
|
8
10
|
export declare const provideEnvironment: () => Context;
|
|
9
11
|
export declare const useEnvironment: () => Context;
|
|
10
12
|
export {};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
|
-
const
|
|
2
|
+
export const ENVIRONMENT_CONTEXT_KEY = Symbol('environment');
|
|
3
3
|
const defaults = () => ({
|
|
4
4
|
viewerMode: 'monitor',
|
|
5
5
|
isStandalone: true,
|
|
6
6
|
});
|
|
7
|
-
export const
|
|
7
|
+
export const createEnvironment = () => {
|
|
8
8
|
const environment = $state(defaults());
|
|
9
9
|
const context = {
|
|
10
10
|
get current() {
|
|
11
11
|
return environment;
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
return context;
|
|
15
|
+
};
|
|
16
|
+
export const provideEnvironment = () => {
|
|
17
|
+
const context = createEnvironment();
|
|
18
|
+
setContext(ENVIRONMENT_CONTEXT_KEY, context);
|
|
15
19
|
return context;
|
|
16
20
|
};
|
|
17
21
|
export const useEnvironment = () => {
|
|
18
|
-
return getContext(
|
|
22
|
+
return getContext(ENVIRONMENT_CONTEXT_KEY);
|
|
19
23
|
};
|
|
@@ -24,13 +24,17 @@ const defaults = () => ({
|
|
|
24
24
|
});
|
|
25
25
|
export const provideSettings = () => {
|
|
26
26
|
let settings = $state(defaults());
|
|
27
|
+
let settingsLoaded = $state(false);
|
|
27
28
|
get('motion-tools-settings').then((response) => {
|
|
28
29
|
if (response) {
|
|
29
30
|
settings = { ...settings, ...response };
|
|
30
31
|
}
|
|
32
|
+
settingsLoaded = true;
|
|
31
33
|
});
|
|
32
34
|
$effect(() => {
|
|
33
|
-
|
|
35
|
+
if (settingsLoaded) {
|
|
36
|
+
set('motion-tools-settings', $state.snapshot(settings));
|
|
37
|
+
}
|
|
34
38
|
});
|
|
35
39
|
const context = {
|
|
36
40
|
get current() {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
import { SvelteSet } from 'svelte/reactivity';
|
|
3
|
+
export const WEBLABS_EXPERIMENTS = {
|
|
4
|
+
MOTION_TOOLS_EDIT_FRAME: 'MOTION_TOOLS_EDIT_FRAME',
|
|
5
|
+
};
|
|
3
6
|
export const WEBLABS_CONTEXT_KEY = Symbol('weblabs-context');
|
|
4
7
|
const getCookie = (name) => {
|
|
5
8
|
const value = `; ${document.cookie}`;
|
|
@@ -1,33 +1,17 @@
|
|
|
1
|
-
import { BatchedMesh,
|
|
1
|
+
import { BatchedMesh, Vector3, Color } from 'three';
|
|
2
2
|
import type { OBB } from 'three/addons/math/OBB.js';
|
|
3
|
-
interface Arrow {
|
|
4
|
-
shaftId: number;
|
|
5
|
-
headId: number;
|
|
6
|
-
}
|
|
7
3
|
export declare class BatchedArrow {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
_idCounter: number;
|
|
16
|
-
constructor({ maxArrows, shaftWidth, material, }?: {
|
|
17
|
-
maxArrows?: number | undefined;
|
|
18
|
-
shaftWidth?: number | undefined;
|
|
19
|
-
material?: MeshBasicMaterial | undefined;
|
|
20
|
-
});
|
|
4
|
+
mesh: BatchedMesh;
|
|
5
|
+
_geometryId: number;
|
|
6
|
+
_pool: number[];
|
|
7
|
+
_ids: Set<number>;
|
|
8
|
+
_id: number;
|
|
9
|
+
_max: number;
|
|
10
|
+
constructor();
|
|
21
11
|
addArrow(direction: Vector3, origin: Vector3, length?: number, color?: Color, arrowHeadAtPose?: boolean): number;
|
|
22
|
-
|
|
23
|
-
getBoundingBoxAt(arrowId: number, target: OBB): any;
|
|
24
|
-
removeArrow(arrowId: number): void;
|
|
25
|
-
updateArrow(arrowId: number, direction: Vector3, origin: Vector3, length?: number, color?: Color, arrowHeadAtPose?: boolean): void;
|
|
12
|
+
removeArrow(instanceId: number): void;
|
|
26
13
|
clear(): void;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
_computeTransform(origin: Vector3, dir: Vector3, lengthY: number, scaleXZ?: number): Matrix4;
|
|
30
|
-
_quaternionFromDirection(dir: Vector3): import("three").Quaternion;
|
|
14
|
+
getBoundingBoxAt(instanceId: number, target: OBB): OBB;
|
|
15
|
+
updateArrow(instanceId: number, origin: Vector3, direction: Vector3, length: number, color: Color, arrowHeadAtPose: boolean): import("three").Quaternion | undefined;
|
|
31
16
|
get object3d(): BatchedMesh;
|
|
32
17
|
}
|
|
33
|
-
export {};
|
|
@@ -1,143 +1,79 @@
|
|
|
1
|
-
import { BatchedMesh,
|
|
1
|
+
import { BatchedMesh, MeshBasicMaterial, Object3D, Vector3, Color, Box3 } from 'three';
|
|
2
|
+
import { createArrowGeometry } from './arrow';
|
|
2
3
|
const black = new Color('black');
|
|
3
4
|
const axis = new Vector3();
|
|
4
5
|
const object3d = new Object3D();
|
|
5
6
|
const vec3 = new Vector3();
|
|
6
7
|
const box1 = new Box3();
|
|
7
|
-
const box2 = new Box3();
|
|
8
|
-
const box3 = new Box3();
|
|
9
|
-
const mat4_1 = new Matrix4();
|
|
10
|
-
const mat4_2 = new Matrix4();
|
|
11
8
|
const col = new Color();
|
|
12
9
|
let index = 0;
|
|
13
10
|
export class BatchedArrow {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
coneGeoId = -1;
|
|
17
|
-
shaftWidth = 0;
|
|
18
|
-
_arrows = new Map(); // arrowId -> { shaftId, headId }
|
|
19
|
-
_idToArrowId = new Map();
|
|
11
|
+
mesh;
|
|
12
|
+
_geometryId;
|
|
20
13
|
_pool = [];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.batchedMesh = new BatchedMesh(maxArrows * 2, maxVertexCount, maxIndexCount, material);
|
|
34
|
-
this.batchedMesh.name = `batched arrows ${++index}`;
|
|
35
|
-
this.batchedMesh.frustumCulled = false;
|
|
36
|
-
this.shaftWidth = shaftWidth;
|
|
37
|
-
this.shaftGeoId = this.batchedMesh.addGeometry(shaftGeo);
|
|
38
|
-
this.coneGeoId = this.batchedMesh.addGeometry(coneGeo);
|
|
14
|
+
_ids = new Set();
|
|
15
|
+
_id = 0;
|
|
16
|
+
_max = 20_000;
|
|
17
|
+
constructor() {
|
|
18
|
+
const material = new MeshBasicMaterial({ color: 0xffffff, toneMapped: false });
|
|
19
|
+
const geometry = createArrowGeometry();
|
|
20
|
+
const vertexCount = geometry.getAttribute('position').count;
|
|
21
|
+
const indexCount = geometry.index?.count ?? vertexCount;
|
|
22
|
+
this.mesh = new BatchedMesh(this._max, vertexCount, indexCount, material);
|
|
23
|
+
this.mesh.name = `Batched arrows ${++index}`;
|
|
24
|
+
this.mesh.frustumCulled = false;
|
|
25
|
+
this._geometryId = this.mesh.addGeometry(geometry);
|
|
39
26
|
}
|
|
40
27
|
addArrow(direction, origin, length = 0.1, color = black, arrowHeadAtPose = true) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (instance) {
|
|
45
|
-
;
|
|
46
|
-
({ shaftId, headId } = instance);
|
|
28
|
+
if (this.mesh.instanceCount >= this._max) {
|
|
29
|
+
this._max += 20_000;
|
|
30
|
+
this.mesh.setInstanceCount(this._max);
|
|
47
31
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this._drawArrow(shaftId, headId, direction, origin, length, color, arrowHeadAtPose);
|
|
53
|
-
const arrowId = this._idCounter++;
|
|
54
|
-
this._arrows.set(arrowId, { shaftId, headId });
|
|
55
|
-
this._idToArrowId.set(shaftId, arrowId);
|
|
56
|
-
this._idToArrowId.set(headId, arrowId);
|
|
57
|
-
return arrowId;
|
|
58
|
-
}
|
|
59
|
-
getArrowId(instanceId) {
|
|
60
|
-
return this._idToArrowId.get(instanceId);
|
|
61
|
-
}
|
|
62
|
-
getBoundingBoxAt(arrowId, target) {
|
|
63
|
-
const arrow = this._arrows.get(arrowId);
|
|
64
|
-
if (arrow) {
|
|
65
|
-
const headBox = this.batchedMesh.getBoundingBoxAt(this.coneGeoId, box1);
|
|
66
|
-
const tailBox = this.batchedMesh.getBoundingBoxAt(this.shaftGeoId, box2);
|
|
67
|
-
if (headBox && tailBox) {
|
|
68
|
-
this.batchedMesh.getMatrixAt(arrow.headId, mat4_1);
|
|
69
|
-
this.batchedMesh.getMatrixAt(arrow.shaftId, mat4_2);
|
|
70
|
-
box3.copy(headBox.applyMatrix4(mat4_1)).union(tailBox.applyMatrix4(mat4_2));
|
|
71
|
-
target.fromBox3(box3);
|
|
72
|
-
return target;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
removeArrow(arrowId) {
|
|
77
|
-
const arrow = this._arrows.get(arrowId);
|
|
78
|
-
if (!arrow)
|
|
79
|
-
return;
|
|
80
|
-
this.batchedMesh.setVisibleAt(arrow.shaftId, false);
|
|
81
|
-
this.batchedMesh.setVisibleAt(arrow.headId, false);
|
|
82
|
-
this._pool.push(arrow);
|
|
83
|
-
this._arrows.delete(arrowId);
|
|
32
|
+
const instanceId = this._pool.pop() ?? this.mesh.addInstance(this._geometryId);
|
|
33
|
+
this._ids.add(instanceId);
|
|
34
|
+
this.updateArrow(instanceId, origin, direction, length, color, arrowHeadAtPose);
|
|
35
|
+
return instanceId;
|
|
84
36
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this._drawArrow(arrow.shaftId, arrow.headId, direction, origin, length, color, arrowHeadAtPose);
|
|
37
|
+
removeArrow(instanceId) {
|
|
38
|
+
this._ids.delete(instanceId);
|
|
39
|
+
this.mesh.setVisibleAt(instanceId, false);
|
|
40
|
+
this._pool.push(instanceId);
|
|
90
41
|
}
|
|
91
42
|
clear() {
|
|
92
|
-
for (const id of this.
|
|
43
|
+
for (const id of this._ids) {
|
|
93
44
|
this.removeArrow(id);
|
|
94
45
|
}
|
|
95
46
|
}
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
47
|
+
getBoundingBoxAt(instanceId, target) {
|
|
48
|
+
const box = this.mesh.getBoundingBoxAt(instanceId, box1);
|
|
49
|
+
if (box) {
|
|
50
|
+
target.fromBox3(box);
|
|
51
|
+
}
|
|
52
|
+
return target;
|
|
100
53
|
}
|
|
101
|
-
|
|
54
|
+
updateArrow(instanceId, origin, direction, length, color, arrowHeadAtPose) {
|
|
102
55
|
if (arrowHeadAtPose) {
|
|
103
56
|
// Compute the base position so the arrow ends at the origin
|
|
104
57
|
origin.sub(vec3.copy(direction).multiplyScalar(length));
|
|
105
58
|
}
|
|
106
59
|
direction.normalize();
|
|
107
|
-
const headLength = length * 0.2;
|
|
108
|
-
const headWidth = headLength * 0.2;
|
|
109
|
-
// Apply shaft transform
|
|
110
|
-
const shaftMatrix = this._computeTransform(origin, direction, length - headLength, this.shaftWidth);
|
|
111
|
-
this.batchedMesh.setMatrixAt(shaftId, shaftMatrix);
|
|
112
|
-
// Compute cone position = origin + dir * length
|
|
113
|
-
const coneOrigin = vec3.copy(direction).multiplyScalar(length).add(origin);
|
|
114
|
-
const coneMatrix = this._computeTransform(coneOrigin, direction, headLength, headWidth * 4);
|
|
115
|
-
this.batchedMesh.setMatrixAt(headId, coneMatrix);
|
|
116
|
-
if (color) {
|
|
117
|
-
col.set(color);
|
|
118
|
-
this.batchedMesh.setColorAt(shaftId, col);
|
|
119
|
-
this.batchedMesh.setColorAt(headId, col);
|
|
120
|
-
}
|
|
121
|
-
this.batchedMesh.setVisibleAt(shaftId, true);
|
|
122
|
-
this.batchedMesh.setVisibleAt(headId, true);
|
|
123
|
-
}
|
|
124
|
-
_computeTransform(origin, dir, lengthY, scaleXZ = 1) {
|
|
125
60
|
object3d.position.copy(origin);
|
|
126
|
-
|
|
127
|
-
object3d.scale.set(scaleXZ, lengthY, scaleXZ);
|
|
128
|
-
object3d.updateMatrix();
|
|
129
|
-
return object3d.matrix.clone();
|
|
130
|
-
}
|
|
131
|
-
_quaternionFromDirection(dir) {
|
|
132
|
-
if (dir.y > 0.99999)
|
|
61
|
+
if (direction.y > 0.99999)
|
|
133
62
|
return object3d.quaternion.set(0, 0, 0, 1);
|
|
134
|
-
if (
|
|
63
|
+
if (direction.y < -0.99999)
|
|
135
64
|
return object3d.quaternion.set(1, 0, 0, 0);
|
|
136
|
-
axis.set(
|
|
137
|
-
const radians = Math.acos(
|
|
138
|
-
|
|
65
|
+
axis.set(direction.z, 0, -direction.x).normalize();
|
|
66
|
+
const radians = Math.acos(direction.y);
|
|
67
|
+
object3d.quaternion.setFromAxisAngle(axis, radians);
|
|
68
|
+
object3d.updateMatrix();
|
|
69
|
+
this.mesh.setMatrixAt(instanceId, object3d.matrix);
|
|
70
|
+
if (color) {
|
|
71
|
+
col.set(color);
|
|
72
|
+
this.mesh.setColorAt(instanceId, col);
|
|
73
|
+
}
|
|
74
|
+
this.mesh.setVisibleAt(instanceId, true);
|
|
139
75
|
}
|
|
140
76
|
get object3d() {
|
|
141
|
-
return this.
|
|
77
|
+
return this.mesh;
|
|
142
78
|
}
|
|
143
79
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BoxGeometry, ConeGeometry } from 'three';
|
|
2
|
+
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns one merged geometry for an arrow (box tail + cone head)
|
|
5
|
+
*
|
|
6
|
+
* Arrow points along +Y with its base at y = 0
|
|
7
|
+
*/
|
|
8
|
+
export const createArrowGeometry = () => {
|
|
9
|
+
const length = 0.1;
|
|
10
|
+
const headLength = length * 0.2;
|
|
11
|
+
const headWidth = headLength * 0.3;
|
|
12
|
+
const tailLength = length - headLength;
|
|
13
|
+
const tailWidth = 0.001;
|
|
14
|
+
// Tail: box translated so base starts at y = 0
|
|
15
|
+
const tailGeometry = new BoxGeometry(tailWidth, tailLength, tailWidth);
|
|
16
|
+
tailGeometry.translate(0, tailLength * 0.5, 0);
|
|
17
|
+
// Head: cone centered at origin spanning [-h/2, +h/2] in y
|
|
18
|
+
const radialSegments = 5;
|
|
19
|
+
const headGeo = new ConeGeometry(headWidth * 0.5, headLength, radialSegments, 1, false);
|
|
20
|
+
// Place its center at y = shaftLength + headLength/2 so tip lands at y = shaftLength + headLength
|
|
21
|
+
headGeo.translate(0, tailLength + headLength * 0.5, 0);
|
|
22
|
+
const merged = mergeGeometries([tailGeometry, headGeo], true);
|
|
23
|
+
merged.computeVertexNormals();
|
|
24
|
+
merged.computeBoundingBox();
|
|
25
|
+
merged.computeBoundingSphere();
|
|
26
|
+
return merged;
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"svelte-check": "4.3.1",
|
|
61
61
|
"svelte-virtuallists": "1.4.2",
|
|
62
62
|
"tailwindcss": "4.1.13",
|
|
63
|
-
"three": "0.
|
|
63
|
+
"three": "0.180.0",
|
|
64
64
|
"three-mesh-bvh": "^0.9.1",
|
|
65
65
|
"threlte-uikit": "1.2.1",
|
|
66
66
|
"tsx": "4.20.5",
|