@viamrobotics/motion-tools 1.1.6 → 1.2.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.js +23 -0
- package/dist/buffer.d.ts +50 -0
- package/dist/buffer.js +69 -0
- package/dist/color.d.ts +2 -0
- package/dist/color.js +20 -3
- package/dist/components/BatchedArrows.svelte +2 -2
- package/dist/components/Entities.svelte +52 -37
- package/dist/components/FileDrop/FileDrop.svelte +18 -38
- package/dist/components/GLTF.svelte +76 -15
- package/dist/components/GLTF.svelte.d.ts +1 -1
- package/dist/components/Geometry2.svelte +20 -10
- package/dist/components/Line.svelte +27 -28
- package/dist/components/LineDots.svelte +45 -0
- package/dist/components/LineDots.svelte.d.ts +9 -0
- package/dist/components/{Pointcloud.svelte → Points.svelte} +41 -6
- package/dist/components/Points.svelte.d.ts +10 -0
- package/dist/components/Scene.svelte +0 -1
- package/dist/components/Snapshot.svelte +60 -0
- package/dist/components/Snapshot.svelte.d.ts +21 -0
- package/dist/ecs/traits.d.ts +30 -12
- package/dist/ecs/traits.js +22 -13
- package/dist/hooks/useDrawAPI.svelte.js +21 -11
- package/dist/hooks/usePointclouds.svelte.js +2 -2
- package/dist/hooks/useSettings.svelte.d.ts +1 -1
- package/dist/hooks/useSettings.svelte.js +3 -0
- package/dist/hooks/useWorldState.svelte.js +9 -2
- package/dist/lib.d.ts +2 -1
- package/dist/lib.js +3 -2
- package/dist/snapshot.d.ts +7 -0
- package/dist/snapshot.js +255 -0
- package/package.json +1 -1
- package/dist/components/Pointcloud.svelte.d.ts +0 -9
|
@@ -98,6 +98,29 @@ export const parseMetadata = (fields = {}) => {
|
|
|
98
98
|
case 'lineDotColor':
|
|
99
99
|
json[k] = readColor(unwrappedValue);
|
|
100
100
|
break;
|
|
101
|
+
case 'colors': {
|
|
102
|
+
let colorBytes;
|
|
103
|
+
// Handle base64-encoded string (from protobuf Struct)
|
|
104
|
+
if (typeof unwrappedValue === 'string') {
|
|
105
|
+
const binary = atob(unwrappedValue);
|
|
106
|
+
colorBytes = new Uint8Array(binary.length);
|
|
107
|
+
for (let i = 0; i < binary.length; i++) {
|
|
108
|
+
colorBytes[i] = binary.charCodeAt(i);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (Array.isArray(unwrappedValue)) {
|
|
112
|
+
colorBytes = unwrappedValue;
|
|
113
|
+
}
|
|
114
|
+
if (colorBytes && colorBytes.length >= 3) {
|
|
115
|
+
const r = (colorBytes[0] ?? 0) / 255;
|
|
116
|
+
const g = (colorBytes[1] ?? 0) / 255;
|
|
117
|
+
const b = (colorBytes[2] ?? 0) / 255;
|
|
118
|
+
const a = colorBytes.length >= 4 ? (colorBytes[3] ?? 255) / 255 : 1;
|
|
119
|
+
json.color = new Color(r, g, b);
|
|
120
|
+
json.opacity = a;
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
101
124
|
case 'opacity':
|
|
102
125
|
json[k] = parseOpacity(unwrappedValue);
|
|
103
126
|
break;
|
package/dist/buffer.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Stride constants for proto binary data formats.
|
|
9
|
+
* Each value represents the number of float32 elements per item.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STRIDE: {
|
|
12
|
+
/** Arrows: [x, y, z, ox, oy, oz] per arrow */
|
|
13
|
+
readonly ARROWS: 6;
|
|
14
|
+
/** Line/Points: [x, y, z] per point */
|
|
15
|
+
readonly POSITIONS: 3;
|
|
16
|
+
/** Nurbs control points: [x, y, z, ox, oy, oz, theta] per point */
|
|
17
|
+
readonly NURBS_CONTROL_POINTS: 7;
|
|
18
|
+
/** Nurbs knots/weights: single float per element */
|
|
19
|
+
readonly NURBS_KNOTS: 1;
|
|
20
|
+
/** Colors: [r, g, b, a] per color (uint8) */
|
|
21
|
+
readonly COLORS_RGBA: 4;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Creates a Float32Array view over a Uint8Array without copying data.
|
|
25
|
+
* Falls back to a copy if the buffer is not 4-byte aligned (rare with protobuf).
|
|
26
|
+
*
|
|
27
|
+
* @param bytes - The raw bytes from a protobuf bytes field
|
|
28
|
+
* @returns A Float32Array view or copy of the data
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const positions = asFloat32Array(line.positions)
|
|
33
|
+
* geometry.setAttribute('position', new BufferAttribute(positions, 3))
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const asFloat32Array: (bytes: Uint8Array<ArrayBuffer>) => Float32Array<ArrayBuffer>;
|
|
37
|
+
/**
|
|
38
|
+
* Converts uint8 RGBA colors to normalized float32 colors (0-1 range).
|
|
39
|
+
* Three.js expects colors in 0-1 range for BufferAttributes.
|
|
40
|
+
*
|
|
41
|
+
* @param colors - Uint8Array of RGBA color data [r, g, b, a, ...]
|
|
42
|
+
* @returns Float32Array with normalized color values
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const colors = normalizeColorsRGBA(metadata.colors)
|
|
47
|
+
* geometry.setAttribute('color', new BufferAttribute(colors, 4))
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare const normalizeColorsRGBA: (colors: Float32Array) => Float32Array;
|
package/dist/buffer.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Stride constants for proto binary data formats.
|
|
9
|
+
* Each value represents the number of float32 elements per item.
|
|
10
|
+
*/
|
|
11
|
+
export const STRIDE = {
|
|
12
|
+
/** Arrows: [x, y, z, ox, oy, oz] per arrow */
|
|
13
|
+
ARROWS: 6,
|
|
14
|
+
/** Line/Points: [x, y, z] per point */
|
|
15
|
+
POSITIONS: 3,
|
|
16
|
+
/** Nurbs control points: [x, y, z, ox, oy, oz, theta] per point */
|
|
17
|
+
NURBS_CONTROL_POINTS: 7,
|
|
18
|
+
/** Nurbs knots/weights: single float per element */
|
|
19
|
+
NURBS_KNOTS: 1,
|
|
20
|
+
/** Colors: [r, g, b, a] per color (uint8) */
|
|
21
|
+
COLORS_RGBA: 4,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Creates a Float32Array view over a Uint8Array without copying data.
|
|
25
|
+
* Falls back to a copy if the buffer is not 4-byte aligned (rare with protobuf).
|
|
26
|
+
*
|
|
27
|
+
* @param bytes - The raw bytes from a protobuf bytes field
|
|
28
|
+
* @returns A Float32Array view or copy of the data
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const positions = asFloat32Array(line.positions)
|
|
33
|
+
* geometry.setAttribute('position', new BufferAttribute(positions, 3))
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const asFloat32Array = (bytes) => {
|
|
37
|
+
if (bytes.length === 0) {
|
|
38
|
+
return new Float32Array(0);
|
|
39
|
+
}
|
|
40
|
+
if (bytes.byteOffset % 4 === 0 && bytes.byteLength % 4 === 0) {
|
|
41
|
+
return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
|
|
42
|
+
}
|
|
43
|
+
const aligned = new Float32Array(bytes.byteLength / 4);
|
|
44
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
45
|
+
for (let i = 0; i < aligned.length; i++) {
|
|
46
|
+
aligned[i] = view.getFloat32(i * 4, true); // little-endian
|
|
47
|
+
}
|
|
48
|
+
return aligned;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Converts uint8 RGBA colors to normalized float32 colors (0-1 range).
|
|
52
|
+
* Three.js expects colors in 0-1 range for BufferAttributes.
|
|
53
|
+
*
|
|
54
|
+
* @param colors - Uint8Array of RGBA color data [r, g, b, a, ...]
|
|
55
|
+
* @returns Float32Array with normalized color values
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const colors = normalizeColorsRGBA(metadata.colors)
|
|
60
|
+
* geometry.setAttribute('color', new BufferAttribute(colors, 4))
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export const normalizeColorsRGBA = (colors) => {
|
|
64
|
+
const normalized = new Float32Array(colors.length);
|
|
65
|
+
for (let i = 0; i < colors.length; i++) {
|
|
66
|
+
normalized[i] = colors[i] / 255;
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
};
|
package/dist/color.d.ts
CHANGED
|
@@ -34,3 +34,5 @@ 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>;
|
package/dist/color.js
CHANGED
|
@@ -40,6 +40,8 @@ const oklchToHex = (raw) => {
|
|
|
40
40
|
const b = Math.max(0, Math.min(1, linearToSrgb(b_linear)));
|
|
41
41
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
42
42
|
};
|
|
43
|
+
const original = new Color();
|
|
44
|
+
const hsl = { h: 0, s: 0, l: 0 };
|
|
43
45
|
/**
|
|
44
46
|
* Darkens a THREE.Color by a given percentage while preserving hue.
|
|
45
47
|
* @param color The original THREE.Color instance.
|
|
@@ -47,17 +49,17 @@ const oklchToHex = (raw) => {
|
|
|
47
49
|
* @returns A new THREE.Color instance with the darkened color.
|
|
48
50
|
*/
|
|
49
51
|
export const darkenColor = (value, percent) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
original.set(value);
|
|
53
|
+
original.getHSL(hsl);
|
|
52
54
|
hsl.l = Math.max(0, hsl.l * (1 - percent / 100));
|
|
53
55
|
return new Color().setHSL(hsl.h, hsl.s, hsl.l);
|
|
54
56
|
};
|
|
55
|
-
const darkness = '600';
|
|
56
57
|
export const resourceNameToColor = (resourceName) => {
|
|
57
58
|
return resourceName
|
|
58
59
|
? new Color(resourceColors[resourceName.subtype])
|
|
59
60
|
: undefined;
|
|
60
61
|
};
|
|
62
|
+
const darkness = '600';
|
|
61
63
|
export const colors = {
|
|
62
64
|
default: oklchToHex(twColors.red[darkness]),
|
|
63
65
|
};
|
|
@@ -139,3 +141,18 @@ const isColorHex = (color) => {
|
|
|
139
141
|
}
|
|
140
142
|
return false;
|
|
141
143
|
};
|
|
144
|
+
export const rgbaToHex = (rgba) => {
|
|
145
|
+
if (rgba.length < 3)
|
|
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');
|
|
150
|
+
return `#${r}${g}${b}`;
|
|
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
|
+
};
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
const instanceID = batched.addArrow(
|
|
30
30
|
direction.set(pose?.oX ?? 0, pose?.oY ?? 0, pose?.oZ ?? 0),
|
|
31
|
-
origin.set(pose?.x ?? 0, pose?.y ?? 0, pose?.z ?? 0),
|
|
31
|
+
origin.set(pose?.x ?? 0, pose?.y ?? 0, pose?.z ?? 0).multiplyScalar(0.001),
|
|
32
32
|
colorRGB ? color.set(colorRGB.r, colorRGB.g, colorRGB.b) : color.set('yellow')
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
batch?.updateArrow(
|
|
48
48
|
instanceID,
|
|
49
49
|
direction.set(pose.oX, pose.oY, pose.oZ),
|
|
50
|
-
origin.set(pose.x, pose.y, pose.z)
|
|
50
|
+
origin.set(pose.x, pose.y, pose.z).multiplyScalar(0.001)
|
|
51
51
|
)
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -1,70 +1,85 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Pose from './Pose.svelte'
|
|
3
3
|
import Frame from './Frame.svelte'
|
|
4
|
-
import Line from './Line.svelte'
|
|
5
|
-
import Pointcloud from './Pointcloud.svelte'
|
|
6
4
|
import GLTF from './GLTF.svelte'
|
|
7
5
|
import Label from './Label.svelte'
|
|
6
|
+
import Line from './Line.svelte'
|
|
7
|
+
import Points from './Points.svelte'
|
|
8
8
|
import { traits, useQuery } from '../ecs'
|
|
9
|
-
import { Or } from 'koota'
|
|
9
|
+
import { Not, Or } from 'koota'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Frames from a live machine are bucketed into their own query
|
|
13
|
+
* due to needing to call `getPose` on each one
|
|
14
|
+
*/
|
|
15
|
+
const machineFramesEntities = useQuery(traits.FramesAPI)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Geometries from a live machine are bucketed into their own query
|
|
19
|
+
* to avoid thrashing other query results due to them being
|
|
20
|
+
* potentially being polled at 30/60fps.
|
|
21
|
+
*/
|
|
22
|
+
const resourceGeometriesEntities = useQuery(traits.GeometriesAPI)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Geometries from the world state API are bucketed into their own query
|
|
26
|
+
* to avoid thrashing other query results due to them being potentially polled at 60fps.
|
|
27
|
+
*/
|
|
28
|
+
const worldStateEntities = useQuery(
|
|
29
|
+
traits.WorldStateStoreAPI,
|
|
19
30
|
Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
20
31
|
)
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* All remaining meshes can be bucketed into a query due to lower frequency updates.
|
|
35
|
+
*/
|
|
36
|
+
const meshEntities = useQuery(
|
|
37
|
+
Not(traits.FramesAPI),
|
|
38
|
+
Not(traits.GeometriesAPI),
|
|
39
|
+
Not(traits.WorldStateStoreAPI),
|
|
23
40
|
Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
24
41
|
)
|
|
42
|
+
|
|
43
|
+
const points = useQuery(traits.PointsPositions)
|
|
44
|
+
const lines = useQuery(traits.LinePositions)
|
|
45
|
+
const gltfs = useQuery(traits.GLTF)
|
|
25
46
|
</script>
|
|
26
47
|
|
|
27
|
-
{#each
|
|
48
|
+
{#each machineFramesEntities.current as entity (entity)}
|
|
49
|
+
<Pose {entity}>
|
|
50
|
+
{#snippet children({ pose })}
|
|
51
|
+
<Frame
|
|
52
|
+
{pose}
|
|
53
|
+
{entity}
|
|
54
|
+
>
|
|
55
|
+
<Label text={entity.get(traits.Name)} />
|
|
56
|
+
</Frame>
|
|
57
|
+
{/snippet}
|
|
58
|
+
</Pose>
|
|
59
|
+
{/each}
|
|
60
|
+
|
|
61
|
+
{#each resourceGeometriesEntities.current as entity (entity)}
|
|
28
62
|
<Frame {entity}>
|
|
29
63
|
<Label text={entity.get(traits.Name)} />
|
|
30
64
|
</Frame>
|
|
31
65
|
{/each}
|
|
32
66
|
|
|
33
|
-
{#each
|
|
67
|
+
{#each worldStateEntities.current as entity (entity)}
|
|
34
68
|
<Frame {entity}>
|
|
35
69
|
<Label text={entity.get(traits.Name)} />
|
|
36
70
|
</Frame>
|
|
37
71
|
{/each}
|
|
38
72
|
|
|
39
|
-
{#each
|
|
73
|
+
{#each meshEntities.current as entity (entity)}
|
|
40
74
|
<Frame {entity}>
|
|
41
75
|
<Label text={entity.get(traits.Name)} />
|
|
42
76
|
</Frame>
|
|
43
77
|
{/each}
|
|
44
78
|
|
|
45
79
|
{#each points.current as entity (entity)}
|
|
46
|
-
<
|
|
80
|
+
<Points {entity}>
|
|
47
81
|
<Label text={entity.get(traits.Name)} />
|
|
48
|
-
</
|
|
49
|
-
{/each}
|
|
50
|
-
|
|
51
|
-
{#each frames.current as entity (entity)}
|
|
52
|
-
<Pose {entity}>
|
|
53
|
-
{#snippet children({ pose })}
|
|
54
|
-
<Frame
|
|
55
|
-
{pose}
|
|
56
|
-
{entity}
|
|
57
|
-
>
|
|
58
|
-
<Label text={entity.get(traits.Name)} />
|
|
59
|
-
</Frame>
|
|
60
|
-
{/snippet}
|
|
61
|
-
</Pose>
|
|
62
|
-
{/each}
|
|
63
|
-
|
|
64
|
-
{#each geometries.current as entity (entity)}
|
|
65
|
-
<Frame {entity}>
|
|
66
|
-
<Label text={entity.get(traits.Name)} />
|
|
67
|
-
</Frame>
|
|
82
|
+
</Points>
|
|
68
83
|
{/each}
|
|
69
84
|
|
|
70
85
|
{#each lines.current as entity (entity)}
|
|
@@ -5,59 +5,39 @@
|
|
|
5
5
|
import { useWorld } from '../../ecs/useWorld'
|
|
6
6
|
import type { FileDropperSuccess } from './file-dropper'
|
|
7
7
|
import { traits } from '../../ecs'
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { spawnSnapshotEntities } from '../../snapshot'
|
|
9
|
+
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
10
10
|
|
|
11
11
|
const props: HTMLAttributes<HTMLDivElement> = $props()
|
|
12
12
|
|
|
13
13
|
const world = useWorld()
|
|
14
14
|
const toast = useToast()
|
|
15
|
-
|
|
16
|
-
const addSnapshotToWorld = (snapshot: Snapshot) => {
|
|
17
|
-
for (const transform of snapshot.transforms) {
|
|
18
|
-
const entity = world.spawn(
|
|
19
|
-
traits.Name(transform.referenceFrame),
|
|
20
|
-
traits.Pose(transform.poseInObserverFrame?.pose),
|
|
21
|
-
traits.Parent(transform.poseInObserverFrame?.referenceFrame)
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
if (transform.physicalObject) {
|
|
25
|
-
entity.add(traits.Geometry(transform.physicalObject))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (transform.metadata) {
|
|
29
|
-
const metadata = parseMetadata(transform.metadata.fields)
|
|
30
|
-
if (metadata.color) {
|
|
31
|
-
entity.add(traits.Color(metadata.color))
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const drawing of snapshot.drawings) {
|
|
37
|
-
world.spawn(
|
|
38
|
-
traits.Name(drawing.referenceFrame),
|
|
39
|
-
traits.Pose(drawing.poseInObserverFrame?.pose),
|
|
40
|
-
traits.Parent(drawing.poseInObserverFrame?.referenceFrame)
|
|
41
|
-
// TODO: Add shape
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
if (drawing.metadata) {
|
|
45
|
-
// add shape colors
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
15
|
+
const cameraControls = useCameraControls()
|
|
49
16
|
|
|
50
17
|
const fileDrop = useFileDrop(
|
|
51
18
|
(result: FileDropperSuccess) => {
|
|
52
19
|
switch (result.type) {
|
|
53
20
|
case 'snapshot': {
|
|
54
|
-
|
|
21
|
+
spawnSnapshotEntities(world, result.snapshot)
|
|
22
|
+
|
|
23
|
+
const { sceneCamera } = result.snapshot.sceneMetadata ?? {}
|
|
24
|
+
|
|
25
|
+
if (sceneCamera) {
|
|
26
|
+
const { x = 0, y = 0, z = 0 } = sceneCamera.position ?? {}
|
|
27
|
+
const { x: lx = 0, y: ly = 0, z: lz = 0 } = sceneCamera.lookAt ?? {}
|
|
28
|
+
|
|
29
|
+
cameraControls.setPose({
|
|
30
|
+
position: [x * 0.001, y * 0.001, z * 0.001],
|
|
31
|
+
lookAt: [lx * 0.001, ly * 0.001, lz * 0.001],
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
55
35
|
break
|
|
56
36
|
}
|
|
57
37
|
case 'pcd':
|
|
58
38
|
world.spawn(
|
|
59
39
|
traits.Name(result.name),
|
|
60
|
-
traits.
|
|
40
|
+
traits.PointsPositions(result.pcd.positions),
|
|
61
41
|
result.pcd.colors ? traits.VertexColors(result.pcd.colors) : traits.Color,
|
|
62
42
|
traits.DroppedFile
|
|
63
43
|
)
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
<script
|
|
2
|
+
module
|
|
3
|
+
lang="ts"
|
|
4
|
+
>
|
|
5
|
+
import { GLTFLoader, DRACOLoader } from 'three/examples/jsm/Addons.js'
|
|
6
|
+
|
|
7
|
+
const dracoLoader = new DRACOLoader()
|
|
8
|
+
const gltfLoader = new GLTFLoader()
|
|
9
|
+
|
|
10
|
+
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/')
|
|
11
|
+
gltfLoader.setDRACOLoader(dracoLoader)
|
|
12
|
+
</script>
|
|
13
|
+
|
|
1
14
|
<script lang="ts">
|
|
2
15
|
import { T, type Props as ThrelteProps } from '@threlte/core'
|
|
3
|
-
import { Portal, PortalTarget } from '@threlte/extras'
|
|
16
|
+
import { Portal, PortalTarget, useGltfAnimations, type ThrelteGltf } from '@threlte/extras'
|
|
4
17
|
import type { Snippet } from 'svelte'
|
|
5
|
-
import type
|
|
18
|
+
import { Group, type Object3D } from 'three'
|
|
6
19
|
import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
|
|
7
20
|
import type { Entity } from 'koota'
|
|
8
21
|
import { traits, useTrait } from '../ecs'
|
|
22
|
+
import { poseToObject3d } from '../transform'
|
|
9
23
|
|
|
10
24
|
interface Props extends ThrelteProps<Object3D> {
|
|
11
25
|
entity: Entity
|
|
@@ -14,23 +28,70 @@
|
|
|
14
28
|
|
|
15
29
|
let { entity, children, ...rest }: Props = $props()
|
|
16
30
|
|
|
31
|
+
const { gltf, actions } = useGltfAnimations()
|
|
32
|
+
|
|
17
33
|
const name = useTrait(() => entity, traits.Name)
|
|
18
34
|
const parent = useTrait(() => entity, traits.Parent)
|
|
19
|
-
const
|
|
35
|
+
const pose = useTrait(() => entity, traits.Pose)
|
|
36
|
+
const gltfTrait = useTrait(() => entity, traits.GLTF)
|
|
37
|
+
const scale = useTrait(() => entity, traits.Scale)
|
|
20
38
|
const objectProps = useObjectEvents(() => entity)
|
|
39
|
+
|
|
40
|
+
const animationName = $derived(gltfTrait.current?.animationName)
|
|
41
|
+
|
|
42
|
+
const group = new Group()
|
|
43
|
+
|
|
44
|
+
$effect.pre(() => {
|
|
45
|
+
if (pose.current) {
|
|
46
|
+
poseToObject3d(pose.current, group)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
$effect.pre(() => {
|
|
51
|
+
if (!gltfTrait.current) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { source } = gltfTrait.current
|
|
56
|
+
|
|
57
|
+
const load = async () => {
|
|
58
|
+
if ('url' in source) {
|
|
59
|
+
$gltf = (await gltfLoader.loadAsync(source.url)) as ThrelteGltf
|
|
60
|
+
} else if ('glb' in source) {
|
|
61
|
+
const buffer = source.glb.buffer.slice(
|
|
62
|
+
source.glb.byteOffset,
|
|
63
|
+
source.glb.byteOffset + source.glb.byteLength
|
|
64
|
+
)
|
|
65
|
+
$gltf = (await gltfLoader.parseAsync(buffer, '')) as ThrelteGltf
|
|
66
|
+
} else if ('gltf' in source) {
|
|
67
|
+
$gltf = source.gltf as ThrelteGltf
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
load()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
$effect.pre(() => {
|
|
75
|
+
if (animationName) {
|
|
76
|
+
$actions[animationName]?.play()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
21
79
|
</script>
|
|
22
80
|
|
|
23
81
|
<Portal id={parent.current}>
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
82
|
+
<T is={group}>
|
|
83
|
+
{#if $gltf}
|
|
84
|
+
<T
|
|
85
|
+
is={$gltf.scene as Object3D}
|
|
86
|
+
scale={[scale.current?.x ?? 1, scale.current?.y ?? 1, scale.current?.z ?? 1]}
|
|
87
|
+
name={name.current}
|
|
88
|
+
{...objectProps}
|
|
89
|
+
{...rest}
|
|
90
|
+
>
|
|
91
|
+
{@render children?.()}
|
|
92
|
+
|
|
93
|
+
<PortalTarget id={name.current} />
|
|
94
|
+
</T>
|
|
95
|
+
{/if}
|
|
96
|
+
</T>
|
|
36
97
|
</Portal>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Props as ThrelteProps } from '@threlte/core';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import type
|
|
3
|
+
import { type Object3D } from 'three';
|
|
4
4
|
import type { Entity } from 'koota';
|
|
5
5
|
interface Props extends ThrelteProps<Object3D> {
|
|
6
6
|
entity: Entity;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { T, useThrelte, type Props as ThrelteProps } from '@threlte/core'
|
|
3
3
|
import { type Snippet } from 'svelte'
|
|
4
|
-
import { meshBounds
|
|
4
|
+
import { meshBounds } from '@threlte/extras'
|
|
5
5
|
import { BufferGeometry, Color, DoubleSide, FrontSide, Group, Mesh } from 'three'
|
|
6
|
+
import { Line2, LineGeometry, LineMaterial } from 'three/examples/jsm/Addons.js'
|
|
6
7
|
import { CapsuleGeometry } from '../three/CapsuleGeometry'
|
|
7
8
|
import { colors, darkenColor } from '../color'
|
|
8
9
|
import AxesHelper from './AxesHelper.svelte'
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
const capsule = useTrait(() => entity, traits.Capsule)
|
|
43
44
|
const sphere = useTrait(() => entity, traits.Sphere)
|
|
44
45
|
const bufferGeometry = useTrait(() => entity, traits.BufferGeometry)
|
|
45
|
-
const
|
|
46
|
+
const linePositions = useTrait(() => entity, traits.LinePositions)
|
|
47
|
+
const lineWidth = useTrait(() => entity, traits.LineWidth)
|
|
46
48
|
const center = useTrait(() => entity, traits.Center)
|
|
47
49
|
|
|
48
50
|
const geometryType = $derived.by(() => {
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
if (capsule.current) return 'capsule'
|
|
51
53
|
if (sphere.current) return 'sphere'
|
|
52
54
|
if (bufferGeometry.current) return 'buffer'
|
|
53
|
-
if (
|
|
55
|
+
if (linePositions.current) return 'line'
|
|
54
56
|
})
|
|
55
57
|
|
|
56
58
|
const color = $derived.by(() => {
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
return
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
const result = new Mesh()
|
|
78
|
+
const result = geometryType === 'line' ? new Line2() : new Mesh()
|
|
77
79
|
|
|
78
80
|
if (geometryType === 'line') {
|
|
79
81
|
result.raycast = meshBounds
|
|
@@ -137,8 +139,15 @@
|
|
|
137
139
|
{/if}
|
|
138
140
|
|
|
139
141
|
{#if !model || renderMode.includes('colliders')}
|
|
140
|
-
{#if
|
|
141
|
-
<
|
|
142
|
+
{#if linePositions.current}
|
|
143
|
+
<T
|
|
144
|
+
is={LineGeometry}
|
|
145
|
+
oncreate={(ref) => {
|
|
146
|
+
if (linePositions.current) {
|
|
147
|
+
ref.setPositions(linePositions.current)
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
142
151
|
{:else if box.current}
|
|
143
152
|
{@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
|
|
144
153
|
<T.BoxGeometry
|
|
@@ -161,16 +170,17 @@
|
|
|
161
170
|
{/if}
|
|
162
171
|
{/if}
|
|
163
172
|
|
|
164
|
-
{#if
|
|
165
|
-
<
|
|
173
|
+
{#if linePositions.current}
|
|
174
|
+
<T
|
|
175
|
+
is={LineMaterial}
|
|
166
176
|
{color}
|
|
167
|
-
width={0.
|
|
177
|
+
width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
|
|
168
178
|
/>
|
|
169
179
|
{:else}
|
|
170
180
|
<T.MeshToonMaterial
|
|
171
181
|
{color}
|
|
172
182
|
side={geometryType === 'buffer' ? DoubleSide : FrontSide}
|
|
173
|
-
transparent
|
|
183
|
+
transparent={(opacity.current ?? 0.7) < 1}
|
|
174
184
|
opacity={opacity.current ?? 0.7}
|
|
175
185
|
/>
|
|
176
186
|
|