@viamrobotics/motion-tools 1.11.1 → 1.12.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/components/App.svelte +3 -0
- package/dist/components/CameraControls.svelte +1 -1
- package/dist/components/Geometry.svelte +13 -12
- package/dist/components/Lasso/Debug.svelte +72 -0
- package/dist/components/Lasso/Debug.svelte.d.ts +8 -0
- package/dist/components/Lasso/Lasso.svelte +238 -92
- package/dist/components/Lasso/Tool.svelte +94 -0
- package/dist/components/Lasso/Tool.svelte.d.ts +9 -0
- package/dist/components/Lasso/traits.d.ts +21 -0
- package/dist/components/Lasso/traits.js +16 -0
- package/dist/components/LineGeometry.svelte +20 -0
- package/dist/components/{Lasso/Line.svelte.d.ts → LineGeometry.svelte.d.ts} +4 -3
- package/dist/components/MeasureTool/MeasureTool.svelte +2 -2
- package/dist/components/PCD.svelte +34 -0
- package/dist/components/PCD.svelte.d.ts +6 -0
- package/dist/components/PointerMissBox.svelte +1 -1
- package/dist/components/Scene.svelte +7 -2
- package/dist/components/overlay/FloatingPanel.svelte +17 -10
- package/dist/components/overlay/FloatingPanel.svelte.d.ts +5 -0
- package/dist/ecs/traits.d.ts +8 -0
- package/dist/ecs/traits.js +8 -0
- package/dist/hooks/useControls.svelte.d.ts +2 -1
- package/dist/hooks/useControls.svelte.js +7 -2
- package/dist/hooks/usePartConfig.svelte.js +1 -1
- package/dist/hooks/useSettings.svelte.d.ts +1 -1
- package/dist/hooks/useSettings.svelte.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/pcd.d.ts +1 -0
- package/dist/pcd.js +44 -0
- package/dist/plugins/bvh.svelte.js +6 -1
- package/dist/test/createRandomPcdBinary.d.ts +1 -1
- package/dist/test/createRandomPcdBinary.js +14 -27
- package/package.json +1 -1
- package/dist/components/Lasso/Line.svelte +0 -44
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
3
|
import { Canvas } from '@threlte/core'
|
|
4
|
+
import { PortalTarget } from '@threlte/extras'
|
|
4
5
|
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
|
|
5
6
|
import { provideToast, ToastContainer } from '@viamrobotics/prime-core'
|
|
6
7
|
import type { Struct } from '@viamrobotics/sdk'
|
|
@@ -155,6 +156,8 @@
|
|
|
155
156
|
<Camera name={cameraName} />
|
|
156
157
|
{/each}
|
|
157
158
|
{/if}
|
|
159
|
+
|
|
160
|
+
<PortalTarget id="dom" />
|
|
158
161
|
</div>
|
|
159
162
|
{/snippet}
|
|
160
163
|
</SceneProviders>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { type Snippet } from 'svelte'
|
|
4
4
|
import { meshBounds } from '@threlte/extras'
|
|
5
5
|
import { BufferGeometry, Color, DoubleSide, FrontSide, Group, Mesh } from 'three'
|
|
6
|
-
import { Line2,
|
|
6
|
+
import { Line2, LineMaterial } from 'three/examples/jsm/Addons.js'
|
|
7
7
|
import { CapsuleGeometry } from '../three/CapsuleGeometry'
|
|
8
8
|
import { colors, darkenColor } from '../color'
|
|
9
9
|
import AxesHelper from './AxesHelper.svelte'
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { traits, useTrait } from '../ecs'
|
|
12
12
|
import { poseToObject3d } from '../transform'
|
|
13
13
|
import type { Pose } from '@viamrobotics/sdk'
|
|
14
|
+
import LineGeometry from './LineGeometry.svelte'
|
|
14
15
|
|
|
15
16
|
interface Props extends ThrelteProps<Group> {
|
|
16
17
|
entity: Entity
|
|
@@ -47,6 +48,8 @@
|
|
|
47
48
|
const lineWidth = useTrait(() => entity, traits.LineWidth)
|
|
48
49
|
const center = useTrait(() => entity, traits.Center)
|
|
49
50
|
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
51
|
+
const materialProps = useTrait(() => entity, traits.Material)
|
|
52
|
+
const renderOrder = useTrait(() => entity, traits.RenderOrder)
|
|
50
53
|
|
|
51
54
|
const geometryType = $derived.by(() => {
|
|
52
55
|
if (box.current) return 'box'
|
|
@@ -136,6 +139,7 @@
|
|
|
136
139
|
is={mesh}
|
|
137
140
|
name={entity}
|
|
138
141
|
userData.name={name}
|
|
142
|
+
renderOrder={renderOrder.current}
|
|
139
143
|
>
|
|
140
144
|
{#if model && renderMode.includes('model')}
|
|
141
145
|
<T is={model} />
|
|
@@ -143,14 +147,7 @@
|
|
|
143
147
|
|
|
144
148
|
{#if !model || renderMode.includes('colliders')}
|
|
145
149
|
{#if linePositions.current}
|
|
146
|
-
<
|
|
147
|
-
is={LineGeometry}
|
|
148
|
-
oncreate={(ref) => {
|
|
149
|
-
if (linePositions.current) {
|
|
150
|
-
ref.setPositions(linePositions.current)
|
|
151
|
-
}
|
|
152
|
-
}}
|
|
153
|
-
/>
|
|
150
|
+
<LineGeometry positions={linePositions.current} />
|
|
154
151
|
{:else if box.current}
|
|
155
152
|
{@const { x, y, z } = box.current ?? { x: 0, y: 0, z: 0 }}
|
|
156
153
|
<T.BoxGeometry
|
|
@@ -178,16 +175,20 @@
|
|
|
178
175
|
is={LineMaterial}
|
|
179
176
|
{color}
|
|
180
177
|
width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
|
|
178
|
+
depthTest={materialProps.current?.depthTest}
|
|
181
179
|
/>
|
|
182
180
|
{:else}
|
|
181
|
+
{@const currentOpacity = opacity.current ?? 0.7}
|
|
183
182
|
<T.MeshToonMaterial
|
|
184
183
|
{color}
|
|
185
184
|
side={geometryType === 'buffer' ? DoubleSide : FrontSide}
|
|
186
|
-
transparent={
|
|
187
|
-
|
|
185
|
+
transparent={currentOpacity < 1}
|
|
186
|
+
depthWrite={currentOpacity === 1}
|
|
187
|
+
opacity={currentOpacity}
|
|
188
|
+
depthTest={materialProps.current?.depthTest}
|
|
188
189
|
/>
|
|
189
190
|
|
|
190
|
-
{#if geo && renderMode.includes('colliders')}
|
|
191
|
+
{#if geo && (renderMode.includes('colliders') || !model)}
|
|
191
192
|
<T.LineSegments
|
|
192
193
|
raycast={() => null}
|
|
193
194
|
bvh={{ enabled: false }}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
|
|
4
|
+
Shows all steps for querying points within a lasso selection
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { T } from '@threlte/core'
|
|
8
|
+
import { Box3, BufferAttribute, BufferGeometry, Vector3 } from 'three'
|
|
9
|
+
import { useTrait, traits } from '../../ecs'
|
|
10
|
+
import type { Entity } from 'koota'
|
|
11
|
+
import * as lassoTraits from './traits'
|
|
12
|
+
|
|
13
|
+
const box3 = new Box3()
|
|
14
|
+
const min = new Vector3()
|
|
15
|
+
const max = new Vector3()
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
lasso: Entity
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { lasso }: Props = $props()
|
|
22
|
+
|
|
23
|
+
const indices = useTrait(() => lasso, lassoTraits.Indices)
|
|
24
|
+
const positions = useTrait(() => lasso, traits.LinePositions)
|
|
25
|
+
const box = useTrait(() => lasso, lassoTraits.Box)
|
|
26
|
+
const boxes = useTrait(() => lasso, lassoTraits.Boxes)
|
|
27
|
+
|
|
28
|
+
const geometry = new BufferGeometry()
|
|
29
|
+
|
|
30
|
+
$effect(() => {
|
|
31
|
+
if (indices.current) {
|
|
32
|
+
geometry.setIndex(new BufferAttribute(indices.current, 1))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (positions.current) {
|
|
36
|
+
geometry.setAttribute('position', new BufferAttribute(positions.current, 3))
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
{#if positions.current && indices.current}
|
|
42
|
+
<T.Mesh>
|
|
43
|
+
<T is={geometry} />
|
|
44
|
+
<T.MeshBasicMaterial
|
|
45
|
+
wireframe
|
|
46
|
+
color="green"
|
|
47
|
+
/>
|
|
48
|
+
</T.Mesh>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
{#if boxes.current}
|
|
52
|
+
{#each boxes.current as box (box)}
|
|
53
|
+
<T.Box3Helper
|
|
54
|
+
args={[
|
|
55
|
+
new Box3().set(min.set(box.minX, box.minY, 0), max.set(box.maxX, box.maxY, 0)),
|
|
56
|
+
'lightgreen',
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
{/each}
|
|
60
|
+
{/if}
|
|
61
|
+
|
|
62
|
+
{#if box.current}
|
|
63
|
+
<T.Box3Helper
|
|
64
|
+
args={[
|
|
65
|
+
box3.set(
|
|
66
|
+
min.set(box.current.minX, box.current.minY, 0),
|
|
67
|
+
max.set(box.current.maxX, box.current.maxY, 0)
|
|
68
|
+
),
|
|
69
|
+
'red',
|
|
70
|
+
]}
|
|
71
|
+
/>
|
|
72
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Entity } from 'koota';
|
|
2
|
+
interface Props {
|
|
3
|
+
lasso: Entity;
|
|
4
|
+
}
|
|
5
|
+
/** Shows all steps for querying points within a lasso selection */
|
|
6
|
+
declare const Debug: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type Debug = ReturnType<typeof Debug>;
|
|
8
|
+
export default Debug;
|
|
@@ -1,153 +1,299 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { Raycaster, Box3, Vector3, Vector2, Plane, Triangle } from 'three'
|
|
3
|
+
import { useThrelte } from '@threlte/core'
|
|
4
|
+
import { Not } from 'koota'
|
|
5
5
|
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
6
6
|
import earcut from 'earcut'
|
|
7
|
-
import {
|
|
7
|
+
import { traits, useQuery, useWorld } from '../../ecs'
|
|
8
|
+
import type { ShapecastCallbacks } from 'three-mesh-bvh'
|
|
9
|
+
import { createBufferGeometry } from '../../attribute'
|
|
10
|
+
import * as lassoTraits from './traits'
|
|
11
|
+
import Debug from './Debug.svelte'
|
|
8
12
|
|
|
9
13
|
interface Props {
|
|
10
14
|
debug?: boolean
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
let { debug =
|
|
17
|
+
let { debug = false }: Props = $props()
|
|
14
18
|
|
|
19
|
+
const world = useWorld()
|
|
15
20
|
const controls = useCameraControls()
|
|
21
|
+
const { scene, dom, camera } = useThrelte()
|
|
16
22
|
|
|
17
23
|
const box3 = new Box3()
|
|
24
|
+
const min = new Vector3()
|
|
25
|
+
const max = new Vector3()
|
|
26
|
+
|
|
27
|
+
const triangle = new Triangle()
|
|
28
|
+
const triangleBox = new Box3()
|
|
18
29
|
const a = new Vector3()
|
|
19
30
|
const b = new Vector3()
|
|
20
31
|
const c = new Vector3()
|
|
21
32
|
|
|
33
|
+
let frameScheduled = false
|
|
22
34
|
let drawing = false
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
indices: Uint16Array
|
|
29
|
-
boxes: Box3[]
|
|
30
|
-
min: { x: number; y: number }
|
|
31
|
-
max: { x: number; y: number }
|
|
32
|
-
}[]
|
|
33
|
-
>([])
|
|
34
|
-
|
|
35
|
-
const onpointerdown = (event: IntersectionEvent<PointerEvent>) => {
|
|
36
|
-
drawing = true
|
|
36
|
+
const raycaster = new Raycaster()
|
|
37
|
+
const mouse = new Vector2()
|
|
38
|
+
const plane = new Plane(new Vector3(0, 0, 1), 0)
|
|
39
|
+
const point = new Vector3()
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
const raycast = (event: PointerEvent) => {
|
|
42
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1
|
|
43
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
raycaster.setFromCamera(mouse, camera.current)
|
|
46
|
+
raycaster.ray.intersectPlane(plane, point)
|
|
47
|
+
return point
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const onpointerdown = (event: PointerEvent) => {
|
|
51
|
+
if (!event.shiftKey) return
|
|
52
|
+
|
|
53
|
+
const { x, y } = raycast(event)
|
|
54
|
+
|
|
55
|
+
drawing = true
|
|
56
|
+
|
|
57
|
+
world.spawn(
|
|
58
|
+
traits.LinePositions(new Float32Array([x, y, 0])),
|
|
59
|
+
traits.LineWidth(1.5),
|
|
60
|
+
traits.RenderOrder(999),
|
|
61
|
+
traits.Material({ depthTest: false }),
|
|
62
|
+
traits.Color({ r: 1, g: 0, b: 0 }),
|
|
63
|
+
lassoTraits.Box({ minX: x, minY: y, maxX: x, maxY: y }),
|
|
64
|
+
lassoTraits.Lasso
|
|
65
|
+
)
|
|
47
66
|
|
|
48
67
|
if (controls.current) {
|
|
49
68
|
controls.current.enabled = false
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
|
|
53
|
-
const onpointermove = (event:
|
|
54
|
-
event.point.toArray(position)
|
|
55
|
-
|
|
72
|
+
const onpointermove = (event: PointerEvent) => {
|
|
56
73
|
if (!drawing) return
|
|
57
74
|
|
|
58
|
-
let
|
|
75
|
+
let lasso = world.query(lassoTraits.Lasso).at(-1)
|
|
76
|
+
|
|
77
|
+
if (!lasso) return
|
|
78
|
+
|
|
79
|
+
if (frameScheduled) return
|
|
80
|
+
|
|
81
|
+
frameScheduled = true
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* pointermove can execute at a rate much higher than screen
|
|
85
|
+
* refresh, creating huge polygon vertex counts, so we cap it.
|
|
86
|
+
*/
|
|
87
|
+
requestAnimationFrame(() => {
|
|
88
|
+
frameScheduled = false
|
|
89
|
+
|
|
90
|
+
const { x, y } = raycast(event)
|
|
91
|
+
const positions = lasso.get(traits.LinePositions)
|
|
92
|
+
const box = lasso.get(lassoTraits.Box)
|
|
59
93
|
|
|
60
|
-
|
|
94
|
+
if (!positions || !box) return
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
const nextPositions = new Float32Array(positions.length + 3)
|
|
97
|
+
nextPositions.set(positions)
|
|
98
|
+
nextPositions[positions.length] = x
|
|
99
|
+
nextPositions[positions.length + 1] = y
|
|
100
|
+
lasso.set(traits.LinePositions, nextPositions)
|
|
64
101
|
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
if (x < box.minX) box.minX = x
|
|
103
|
+
else if (x > box.maxX) box.maxX = x
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
if (y < box.minY) box.minY = y
|
|
106
|
+
else if (y > box.maxY) box.maxY = y
|
|
107
|
+
|
|
108
|
+
lasso.set(lassoTraits.Box, box)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const onpointerleave = () => {
|
|
113
|
+
if (!drawing) return
|
|
114
|
+
|
|
115
|
+
onpointerup()
|
|
70
116
|
}
|
|
71
117
|
|
|
72
118
|
const onpointerup = () => {
|
|
119
|
+
if (!drawing) return
|
|
120
|
+
|
|
73
121
|
drawing = false
|
|
74
122
|
|
|
75
|
-
let lasso =
|
|
123
|
+
let lasso = world.query(lassoTraits.Lasso).at(-1)
|
|
76
124
|
|
|
77
125
|
if (!lasso) return
|
|
78
126
|
|
|
79
|
-
|
|
127
|
+
let positions = lasso.get(traits.LinePositions)
|
|
128
|
+
|
|
129
|
+
if (!positions) return
|
|
130
|
+
|
|
131
|
+
const [startX, startY] = positions
|
|
80
132
|
|
|
81
133
|
if (controls.current) {
|
|
82
134
|
controls.current.enabled = true
|
|
83
135
|
}
|
|
84
136
|
|
|
85
137
|
// Close the loop
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
138
|
+
const nextPositions = new Float32Array(positions.length + 3)
|
|
139
|
+
nextPositions.set(positions)
|
|
140
|
+
nextPositions[positions.length] = startX
|
|
141
|
+
nextPositions[positions.length + 1] = startY
|
|
142
|
+
lasso.set(traits.LinePositions, nextPositions)
|
|
143
|
+
positions = nextPositions
|
|
89
144
|
|
|
90
145
|
const indices = earcut(positions, undefined, 3)
|
|
91
|
-
|
|
146
|
+
if (debug) {
|
|
147
|
+
lasso.add(lassoTraits.Indices(new Uint16Array(indices)))
|
|
148
|
+
}
|
|
92
149
|
|
|
93
|
-
|
|
150
|
+
const getTriangleFromIndex = (i: number, triangle: Triangle) => {
|
|
94
151
|
const stride = 3
|
|
95
152
|
const ia = indices[i + 0] * stride
|
|
96
153
|
const ib = indices[i + 1] * stride
|
|
97
154
|
const ic = indices[i + 2] * stride
|
|
98
|
-
|
|
99
155
|
a.set(positions[ia + 0], positions[ia + 1], positions[ia + 2])
|
|
100
156
|
b.set(positions[ib + 0], positions[ib + 1], positions[ib + 2])
|
|
101
157
|
c.set(positions[ic + 0], positions[ic + 1], positions[ic + 2])
|
|
102
|
-
|
|
158
|
+
triangle.set(a, b, c)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const boxes: lassoTraits.AABB[] = []
|
|
162
|
+
for (let i = 0, l = indices.length; i < l; i += 3) {
|
|
163
|
+
getTriangleFromIndex(i, triangle)
|
|
164
|
+
box3.setFromPoints([triangle.a, triangle.b, triangle.c])
|
|
165
|
+
boxes.push({ minX: box3.min.x, minY: box3.min.y, maxX: box3.max.x, maxY: box3.max.y })
|
|
166
|
+
}
|
|
167
|
+
if (debug) {
|
|
168
|
+
lasso.add(lassoTraits.Boxes(boxes))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lassoBox = lasso.get(lassoTraits.Box)
|
|
172
|
+
|
|
173
|
+
if (!lassoBox) return
|
|
174
|
+
|
|
175
|
+
min.set(lassoBox.minX, lassoBox.minY, Number.NEGATIVE_INFINITY)
|
|
176
|
+
max.set(lassoBox.maxX, lassoBox.maxY, Number.POSITIVE_INFINITY)
|
|
177
|
+
box3.set(min, max)
|
|
178
|
+
|
|
179
|
+
const enclosedPoints: number[] = []
|
|
180
|
+
|
|
181
|
+
for (const pointsEntity of world.query(traits.Points, Not(lassoTraits.LassoEnclosedPoints))) {
|
|
182
|
+
const geometry = pointsEntity.get(traits.BufferGeometry)
|
|
183
|
+
|
|
184
|
+
if (!geometry) return
|
|
103
185
|
|
|
104
|
-
|
|
186
|
+
const points = scene.getObjectByName(pointsEntity as unknown as string)
|
|
187
|
+
|
|
188
|
+
if (!points) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
geometry.boundsTree?.shapecast({
|
|
193
|
+
intersectsBounds: (box) => {
|
|
194
|
+
return box.intersectsBox(box3)
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
intersectsPoint: (point: Vector3) => {
|
|
198
|
+
for (let i = 0, j = 0, l = indices.length; i < l; i += 3, j += 1) {
|
|
199
|
+
const { minX, minY, maxX, maxY } = boxes[j]
|
|
200
|
+
|
|
201
|
+
min.set(minX, minY, Number.NEGATIVE_INFINITY)
|
|
202
|
+
max.set(maxX, maxY, Number.POSITIVE_INFINITY)
|
|
203
|
+
triangleBox.set(min, max)
|
|
204
|
+
|
|
205
|
+
if (triangleBox.containsPoint(point)) {
|
|
206
|
+
getTriangleFromIndex(i, triangle)
|
|
207
|
+
|
|
208
|
+
if (triangle.containsPoint(point)) {
|
|
209
|
+
enclosedPoints.push(point.x, point.y, point.z)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
// intersectsPoint is not yet in typedef, this can be removed when it is added
|
|
215
|
+
} as ShapecastCallbacks)
|
|
105
216
|
}
|
|
217
|
+
|
|
218
|
+
const lassoResultGeometry = createBufferGeometry(new Float32Array(enclosedPoints))
|
|
219
|
+
|
|
220
|
+
world.spawn(
|
|
221
|
+
traits.Name('Lasso result'),
|
|
222
|
+
traits.BufferGeometry(lassoResultGeometry),
|
|
223
|
+
traits.Color({ r: 1, g: 0, b: 0 }),
|
|
224
|
+
traits.RenderOrder(999),
|
|
225
|
+
traits.Material({ depthTest: false }),
|
|
226
|
+
traits.Points,
|
|
227
|
+
traits.Removable,
|
|
228
|
+
lassoTraits.LassoEnclosedPoints,
|
|
229
|
+
lassoTraits.PointsCapturedBy(lasso)
|
|
230
|
+
)
|
|
106
231
|
}
|
|
232
|
+
|
|
233
|
+
const onkeydown = (event: KeyboardEvent) => {
|
|
234
|
+
if (event.key === 'Shift') {
|
|
235
|
+
dom.style.cursor = 'crosshair'
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const onkeyup = (event: KeyboardEvent) => {
|
|
240
|
+
if (event.key === 'Shift') {
|
|
241
|
+
dom.style.removeProperty('cursor')
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$effect(() => {
|
|
246
|
+
window.addEventListener('keydown', onkeydown)
|
|
247
|
+
window.addEventListener('keyup', onkeyup)
|
|
248
|
+
dom.addEventListener('pointerdown', onpointerdown)
|
|
249
|
+
dom.addEventListener('pointermove', onpointermove)
|
|
250
|
+
dom.addEventListener('pointerup', onpointerup)
|
|
251
|
+
dom.addEventListener('pointerleave', onpointerleave)
|
|
252
|
+
|
|
253
|
+
return () => {
|
|
254
|
+
window.removeEventListener('keydown', onkeydown)
|
|
255
|
+
window.removeEventListener('keyup', onkeyup)
|
|
256
|
+
dom.removeEventListener('pointerdown', onpointerdown)
|
|
257
|
+
dom.removeEventListener('pointermove', onpointermove)
|
|
258
|
+
dom.removeEventListener('pointerup', onpointerup)
|
|
259
|
+
dom.removeEventListener('pointerleave', onpointerleave)
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const lassos = useQuery(lassoTraits.Lasso)
|
|
264
|
+
|
|
265
|
+
$effect(() => {
|
|
266
|
+
if (!controls.current) return
|
|
267
|
+
|
|
268
|
+
const currentControls = controls.current
|
|
269
|
+
|
|
270
|
+
const { minPolarAngle, maxPolarAngle } = currentControls
|
|
271
|
+
|
|
272
|
+
// Locks the camera to top down while this component is mounted
|
|
273
|
+
currentControls.polarAngle = 0
|
|
274
|
+
currentControls.minPolarAngle = 0
|
|
275
|
+
currentControls.maxPolarAngle = 0
|
|
276
|
+
|
|
277
|
+
return () => {
|
|
278
|
+
currentControls.minPolarAngle = minPolarAngle
|
|
279
|
+
currentControls.maxPolarAngle = maxPolarAngle
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// On unmount, destroy all lasso related entities
|
|
284
|
+
$effect(() => {
|
|
285
|
+
return () => {
|
|
286
|
+
for (const entity of world.query(lassoTraits.LassoEnclosedPoints)) {
|
|
287
|
+
if (world.has(entity)) {
|
|
288
|
+
entity.destroy()
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
107
293
|
</script>
|
|
108
294
|
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
<T.PlaneGeometry args={[7, 7, 10, 10]} />
|
|
115
|
-
<T.MeshBasicMaterial
|
|
116
|
-
wireframe
|
|
117
|
-
color="blue"
|
|
118
|
-
transparent
|
|
119
|
-
opacity={debug ? 1 : 0}
|
|
120
|
-
/>
|
|
121
|
-
</T.Mesh>
|
|
122
|
-
|
|
123
|
-
{#each lassos as lasso (lasso)}
|
|
124
|
-
<Line positions={lasso.positions} />
|
|
125
|
-
|
|
126
|
-
{#if debug}
|
|
127
|
-
{#if lasso.indices.length > 0}
|
|
128
|
-
<T.Mesh>
|
|
129
|
-
<T.BufferGeometry
|
|
130
|
-
oncreate={(ref) => {
|
|
131
|
-
ref.setIndex(new BufferAttribute(lasso.indices, 1))
|
|
132
|
-
ref.setAttribute('position', new BufferAttribute(new Float32Array(lasso.positions), 3))
|
|
133
|
-
}}
|
|
134
|
-
/>
|
|
135
|
-
<T.MeshBasicMaterial
|
|
136
|
-
wireframe
|
|
137
|
-
color="green"
|
|
138
|
-
/>
|
|
139
|
-
</T.Mesh>
|
|
140
|
-
{/if}
|
|
141
|
-
|
|
142
|
-
{#each lasso.boxes as box (box)}
|
|
143
|
-
<T.Box3Helper args={[box, 'lightgreen']} />
|
|
144
|
-
{/each}
|
|
145
|
-
|
|
146
|
-
<T.Box3Helper
|
|
147
|
-
args={[
|
|
148
|
-
new Box3(a.set(lasso.min.x, lasso.min.y, 0), b.set(lasso.max.x, lasso.max.y, 0)),
|
|
149
|
-
'red',
|
|
150
|
-
]}
|
|
151
|
-
/>
|
|
152
|
-
{/if}
|
|
153
|
-
{/each}
|
|
295
|
+
{#if debug}
|
|
296
|
+
{#each lassos.current as lasso (lasso)}
|
|
297
|
+
<Debug {lasso} />
|
|
298
|
+
{/each}
|
|
299
|
+
{/if}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Portal } from '@threlte/extras'
|
|
3
|
+
import { Button } from '@viamrobotics/prime-core'
|
|
4
|
+
import Lasso from './Lasso.svelte'
|
|
5
|
+
import DashboardButton from '../overlay/dashboard/Button.svelte'
|
|
6
|
+
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
7
|
+
import FloatingPanel from '../overlay/FloatingPanel.svelte'
|
|
8
|
+
import { traits, useWorld } from '../../ecs'
|
|
9
|
+
import * as lassoTraits from './traits'
|
|
10
|
+
import { BufferGeometryUtils } from 'three/examples/jsm/Addons.js'
|
|
11
|
+
import { createBinaryPCD } from '../../pcd'
|
|
12
|
+
import type { BufferGeometry } from 'three'
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Whether to auto-enable lasso mode when the component mounts */
|
|
16
|
+
enabled?: boolean
|
|
17
|
+
|
|
18
|
+
/** Fires when the user has committed to a lasso selection */
|
|
19
|
+
onSelection: (pcd: Blob) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { enabled = false, onSelection }: Props = $props()
|
|
23
|
+
|
|
24
|
+
const world = useWorld()
|
|
25
|
+
const settings = useSettings()
|
|
26
|
+
const isLassoMode = $derived(settings.current.interactionMode === 'lasso')
|
|
27
|
+
|
|
28
|
+
const onCommitClick = () => {
|
|
29
|
+
const entities = world.query(lassoTraits.LassoEnclosedPoints)
|
|
30
|
+
|
|
31
|
+
const geometries: BufferGeometry[] = []
|
|
32
|
+
for (const entity of entities) {
|
|
33
|
+
const geometry = entity.get(traits.BufferGeometry)
|
|
34
|
+
|
|
35
|
+
if (geometry) {
|
|
36
|
+
geometries.push(geometry)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries)
|
|
41
|
+
const positions = mergedGeometry.getAttribute('position').array as Float32Array
|
|
42
|
+
|
|
43
|
+
const pcd = createBinaryPCD(positions)
|
|
44
|
+
|
|
45
|
+
onSelection(pcd)
|
|
46
|
+
|
|
47
|
+
for (const entity of entities) {
|
|
48
|
+
if (world.has(entity)) {
|
|
49
|
+
entity.destroy()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
$effect(() => {
|
|
55
|
+
if (enabled) {
|
|
56
|
+
settings.current.interactionMode = 'lasso'
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Portal id="dashboard">
|
|
62
|
+
<fieldset>
|
|
63
|
+
<DashboardButton
|
|
64
|
+
active={isLassoMode}
|
|
65
|
+
icon="selection-drag"
|
|
66
|
+
description="{isLassoMode ? 'Disable' : 'Enable'} lasso selection"
|
|
67
|
+
onclick={() => {
|
|
68
|
+
settings.current.interactionMode = isLassoMode ? 'navigate' : 'lasso'
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
</fieldset>
|
|
72
|
+
</Portal>
|
|
73
|
+
|
|
74
|
+
{#if isLassoMode}
|
|
75
|
+
<Lasso />
|
|
76
|
+
|
|
77
|
+
<Portal id="dom">
|
|
78
|
+
<FloatingPanel
|
|
79
|
+
isOpen
|
|
80
|
+
exitable={false}
|
|
81
|
+
title="Lasso"
|
|
82
|
+
defaultSize={{ width: 445, height: 100 }}
|
|
83
|
+
defaultPosition={{ x: window.innerWidth / 2 - 200, y: window.innerHeight - 10 - 100 }}
|
|
84
|
+
>
|
|
85
|
+
<div class="flex items-center gap-4 p-4 text-xs">
|
|
86
|
+
Shift + click and drag to make a lasso selection.
|
|
87
|
+
<Button
|
|
88
|
+
onclick={onCommitClick}
|
|
89
|
+
variant="success">Commit selection</Button
|
|
90
|
+
>
|
|
91
|
+
</div>
|
|
92
|
+
</FloatingPanel>
|
|
93
|
+
</Portal>
|
|
94
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Whether to auto-enable lasso mode when the component mounts */
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/** Fires when the user has committed to a lasso selection */
|
|
5
|
+
onSelection: (pcd: Blob) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const Tool: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Tool = ReturnType<typeof Tool>;
|
|
9
|
+
export default Tool;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const Lasso: import("koota").Trait<() => boolean>;
|
|
2
|
+
export declare const LassoEnclosedPoints: import("koota").Trait<() => boolean>;
|
|
3
|
+
/**
|
|
4
|
+
* Captured points are removable, so we want to also destroy
|
|
5
|
+
* the source lasso every time a user deletes one.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PointsCapturedBy: import("koota").Relation<import("koota").Trait<Record<string, never>>>;
|
|
8
|
+
export interface AABB {
|
|
9
|
+
minX: number;
|
|
10
|
+
minY: number;
|
|
11
|
+
maxX: number;
|
|
12
|
+
maxY: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const Box: import("koota").Trait<{
|
|
15
|
+
minX: number;
|
|
16
|
+
minY: number;
|
|
17
|
+
maxX: number;
|
|
18
|
+
maxY: number;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const Indices: import("koota").Trait<() => Uint16Array<ArrayBuffer>>;
|
|
21
|
+
export declare const Boxes: import("koota").Trait<() => AABB[]>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { relation, trait } from 'koota';
|
|
2
|
+
export const Lasso = trait(() => true);
|
|
3
|
+
export const LassoEnclosedPoints = trait(() => true);
|
|
4
|
+
/**
|
|
5
|
+
* Captured points are removable, so we want to also destroy
|
|
6
|
+
* the source lasso every time a user deletes one.
|
|
7
|
+
*/
|
|
8
|
+
export const PointsCapturedBy = relation({ autoDestroy: 'target' });
|
|
9
|
+
export const Box = trait({
|
|
10
|
+
minX: 0,
|
|
11
|
+
minY: 0,
|
|
12
|
+
maxX: 0,
|
|
13
|
+
maxY: 0,
|
|
14
|
+
});
|
|
15
|
+
export const Indices = trait(() => new Uint16Array());
|
|
16
|
+
export const Boxes = trait(() => []);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { LineGeometry } from 'three/examples/jsm/Addons.js'
|
|
3
|
+
import { T } from '@threlte/core'
|
|
4
|
+
import { untrack } from 'svelte'
|
|
5
|
+
|
|
6
|
+
let { positions } = $props()
|
|
7
|
+
|
|
8
|
+
let geometry = $state.raw(new LineGeometry())
|
|
9
|
+
|
|
10
|
+
$effect(() => {
|
|
11
|
+
if (positions) {
|
|
12
|
+
untrack(() => {
|
|
13
|
+
geometry = new LineGeometry()
|
|
14
|
+
geometry.setPositions(positions)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<T is={geometry} />
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export default
|
|
2
|
-
type
|
|
1
|
+
export default LineGeometry;
|
|
2
|
+
type LineGeometry = {
|
|
3
3
|
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
4
|
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
5
|
};
|
|
6
|
-
declare const
|
|
6
|
+
declare const LineGeometry: import("svelte").Component<{
|
|
7
7
|
positions: any;
|
|
8
8
|
}, {}, "">;
|
|
9
|
+
import { LineGeometry } from 'three/examples/jsm/Addons.js';
|
|
9
10
|
type $$ComponentProps = {
|
|
10
11
|
positions: any;
|
|
11
12
|
};
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
let p1 = $state.raw<Vector3>()
|
|
22
22
|
let p2 = $state.raw<Vector3>()
|
|
23
23
|
|
|
24
|
-
const enabled = $derived(settings.current.
|
|
24
|
+
const enabled = $derived(settings.current.interactionMode === 'measure')
|
|
25
25
|
|
|
26
26
|
const { onclick, onmove, raycaster } = useMouseRaycaster(() => ({
|
|
27
27
|
enabled,
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
icon="ruler"
|
|
86
86
|
description="{enabled ? 'Disable' : 'Enable'} measurement"
|
|
87
87
|
onclick={() => {
|
|
88
|
-
settings.current.
|
|
88
|
+
settings.current.interactionMode = enabled ? 'navigate' : 'measure'
|
|
89
89
|
}}
|
|
90
90
|
/>
|
|
91
91
|
<Popover>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parsePcdInWorker } from '../lib'
|
|
3
|
+
import { traits, useWorld } from '../ecs'
|
|
4
|
+
import { createBufferGeometry } from '../attribute'
|
|
5
|
+
import type { Entity } from 'koota'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
data: Uint8Array
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { data }: Props = $props()
|
|
12
|
+
|
|
13
|
+
const world = useWorld()
|
|
14
|
+
|
|
15
|
+
let entity: Entity
|
|
16
|
+
|
|
17
|
+
$effect(() => {
|
|
18
|
+
parsePcdInWorker(data).then(({ positions, colors }) => {
|
|
19
|
+
const geometry = createBufferGeometry(positions, colors)
|
|
20
|
+
|
|
21
|
+
entity = world.spawn(
|
|
22
|
+
traits.Name('Random points'),
|
|
23
|
+
traits.Points,
|
|
24
|
+
traits.BufferGeometry(geometry)
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
if (entity && world.has(entity)) {
|
|
30
|
+
entity.destroy()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const transformControls = useTransformControls()
|
|
13
13
|
const cameraDown = new Vector3()
|
|
14
14
|
|
|
15
|
-
const enabled = $derived(
|
|
15
|
+
const enabled = $derived(settings.current.interactionMode === 'navigate')
|
|
16
16
|
|
|
17
17
|
const size = 1_000
|
|
18
18
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Vector3 } from 'three'
|
|
2
|
+
import { ShaderMaterial, Vector3 } from 'three'
|
|
3
3
|
import { T } from '@threlte/core'
|
|
4
4
|
import { Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
|
|
5
5
|
import Entities from './Entities.svelte'
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
$effect(() => {
|
|
43
|
-
enabled.set(
|
|
43
|
+
enabled.set(settings.current.interactionMode === 'navigate')
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
bvh(raycaster, () => ({ helper: false }))
|
|
@@ -76,11 +76,16 @@
|
|
|
76
76
|
|
|
77
77
|
{#if !$isPresenting && settings.current.grid}
|
|
78
78
|
<Grid
|
|
79
|
+
oncreate={(ref) => {
|
|
80
|
+
const material = ref.material as ShaderMaterial
|
|
81
|
+
material.depthWrite = false
|
|
82
|
+
}}
|
|
79
83
|
raycast={() => null}
|
|
80
84
|
bvh={{ enabled: false }}
|
|
81
85
|
plane="xy"
|
|
82
86
|
sectionColor="#333"
|
|
83
87
|
infiniteGrid
|
|
88
|
+
renderOrder={999}
|
|
84
89
|
cellSize={settings.current.gridCellSize}
|
|
85
90
|
sectionSize={settings.current.gridSectionSize}
|
|
86
91
|
fadeOrigin={new Vector3()}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
interface Props {
|
|
9
9
|
title?: string
|
|
10
10
|
defaultSize?: { width: number; height: number }
|
|
11
|
+
defaultPosition?: { x: number; y: number }
|
|
12
|
+
exitable?: boolean
|
|
11
13
|
isOpen?: boolean
|
|
12
14
|
children: Snippet
|
|
13
15
|
}
|
|
@@ -15,6 +17,8 @@
|
|
|
15
17
|
let {
|
|
16
18
|
title = '',
|
|
17
19
|
defaultSize = { width: 700, height: 500 },
|
|
20
|
+
defaultPosition,
|
|
21
|
+
exitable = true,
|
|
18
22
|
isOpen = $bindable(false),
|
|
19
23
|
children,
|
|
20
24
|
}: Props = $props()
|
|
@@ -23,6 +27,7 @@
|
|
|
23
27
|
const floatingPanelService = useMachine(floatingPanel.machine, () => ({
|
|
24
28
|
id,
|
|
25
29
|
defaultSize,
|
|
30
|
+
defaultPosition,
|
|
26
31
|
resizable: false,
|
|
27
32
|
allowOverflow: false,
|
|
28
33
|
open: isOpen,
|
|
@@ -54,17 +59,19 @@
|
|
|
54
59
|
{title}
|
|
55
60
|
</p>
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<button
|
|
62
|
-
aria-label="Close connection configs panel"
|
|
63
|
-
onclick={() => (isOpen = false)}
|
|
62
|
+
{#if exitable}
|
|
63
|
+
<div
|
|
64
|
+
{...api.getControlProps()}
|
|
65
|
+
class="flex gap-3"
|
|
64
66
|
>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
<button
|
|
68
|
+
aria-label="Close connection configs panel"
|
|
69
|
+
onclick={() => (isOpen = false)}
|
|
70
|
+
>
|
|
71
|
+
<Icon name="close" />
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
68
75
|
</div>
|
|
69
76
|
</div>
|
|
70
77
|
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export declare const Instance: import("koota").Trait<{
|
|
|
59
59
|
meshID: number;
|
|
60
60
|
instanceID: number;
|
|
61
61
|
}>;
|
|
62
|
+
export declare const RenderOrder: import("koota").Trait<() => number>;
|
|
62
63
|
export declare const Opacity: import("koota").Trait<() => number>;
|
|
63
64
|
/**
|
|
64
65
|
* The color of an object
|
|
@@ -69,6 +70,13 @@ export declare const Color: import("koota").Trait<{
|
|
|
69
70
|
g: number;
|
|
70
71
|
b: number;
|
|
71
72
|
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Material properties
|
|
75
|
+
*/
|
|
76
|
+
export declare const Material: import("koota").Trait<{
|
|
77
|
+
depthTest: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
export declare const DepthTest: import("koota").Trait<() => boolean>;
|
|
72
80
|
export declare const Arrow: import("koota").Trait<() => boolean>;
|
|
73
81
|
export declare const Positions: import("koota").Trait<() => Float32Array<ArrayBuffer>>;
|
|
74
82
|
export declare const Colors: import("koota").Trait<() => Uint8Array<ArrayBuffer>>;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -37,12 +37,20 @@ export const Instance = trait({
|
|
|
37
37
|
meshID: -1,
|
|
38
38
|
instanceID: -1,
|
|
39
39
|
});
|
|
40
|
+
export const RenderOrder = trait(() => 0);
|
|
40
41
|
export const Opacity = trait(() => 1);
|
|
41
42
|
/**
|
|
42
43
|
* The color of an object
|
|
43
44
|
* @default { r: 1, g: 0, b: 0 }
|
|
44
45
|
*/
|
|
45
46
|
export const Color = trait({ r: 0, g: 0, b: 0 });
|
|
47
|
+
/**
|
|
48
|
+
* Material properties
|
|
49
|
+
*/
|
|
50
|
+
export const Material = trait({
|
|
51
|
+
depthTest: false,
|
|
52
|
+
});
|
|
53
|
+
export const DepthTest = trait(() => true);
|
|
46
54
|
export const Arrow = trait(() => true);
|
|
47
55
|
export const Positions = trait(() => new Float32Array());
|
|
48
56
|
export const Colors = trait(() => new Uint8Array());
|
|
@@ -8,8 +8,9 @@ interface CameraControlsContext {
|
|
|
8
8
|
current: CameraControlsRef | undefined;
|
|
9
9
|
set(current: CameraControlsRef): void;
|
|
10
10
|
setPose(pose: CameraPose, animate?: boolean): void;
|
|
11
|
+
setInitialPose(): void;
|
|
11
12
|
}
|
|
12
|
-
export declare const provideCameraControls: (
|
|
13
|
+
export declare const provideCameraControls: (initialCameraPose: () => CameraPose | undefined) => void;
|
|
13
14
|
export declare const useCameraControls: () => CameraControlsContext;
|
|
14
15
|
interface TransformControlsContext {
|
|
15
16
|
active: boolean;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
2
|
const TRANSFORM_CONTROLS_KEY = Symbol('tranform-controls-context');
|
|
3
3
|
const CAMERA_CONTROLS_KEY = Symbol('camera-controls-context');
|
|
4
|
-
export const provideCameraControls = (
|
|
4
|
+
export const provideCameraControls = (initialCameraPose) => {
|
|
5
5
|
let controls = $state.raw();
|
|
6
6
|
const setPose = (pose, animate = false) => {
|
|
7
7
|
const [x, y, z] = pose.position;
|
|
@@ -9,8 +9,12 @@ export const provideCameraControls = (cameraPose) => {
|
|
|
9
9
|
controls?.setPosition(x, y, z, animate);
|
|
10
10
|
controls?.setLookAt(x, y, z, lookAtX, lookAtY, lookAtZ, animate);
|
|
11
11
|
};
|
|
12
|
+
const setInitialPose = () => {
|
|
13
|
+
const pose = initialCameraPose();
|
|
14
|
+
setPose(pose ?? { position: [3, 3, 3], lookAt: [0, 0, 0] }, true);
|
|
15
|
+
};
|
|
12
16
|
$effect(() => {
|
|
13
|
-
const pose =
|
|
17
|
+
const pose = initialCameraPose();
|
|
14
18
|
if (pose) {
|
|
15
19
|
setPose(pose);
|
|
16
20
|
}
|
|
@@ -23,6 +27,7 @@ export const provideCameraControls = (cameraPose) => {
|
|
|
23
27
|
controls = current;
|
|
24
28
|
},
|
|
25
29
|
setPose,
|
|
30
|
+
setInitialPose,
|
|
26
31
|
});
|
|
27
32
|
};
|
|
28
33
|
export const useCameraControls = () => {
|
|
@@ -277,7 +277,7 @@ class StandalonePartConfig {
|
|
|
277
277
|
if (!fragmentId) {
|
|
278
278
|
continue;
|
|
279
279
|
}
|
|
280
|
-
const components = fragmentResponse?.fragment?.fields['components']
|
|
280
|
+
const components = fragmentResponse?.fragment?.fields['components']?.kind;
|
|
281
281
|
if (components?.case === 'listValue') {
|
|
282
282
|
for (const component of components.value.values) {
|
|
283
283
|
if (component.kind.case === 'structValue') {
|
|
@@ -12,7 +12,7 @@ export interface Settings {
|
|
|
12
12
|
pointColor: string;
|
|
13
13
|
lineWidth: number;
|
|
14
14
|
lineDotSize: number;
|
|
15
|
-
|
|
15
|
+
interactionMode: 'navigate' | 'measure' | 'lasso';
|
|
16
16
|
enableMeasureAxisX: boolean;
|
|
17
17
|
enableMeasureAxisY: boolean;
|
|
18
18
|
enableMeasureAxisZ: boolean;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/pcd.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createBinaryPCD: (positions: Float32Array, colors?: Uint8Array) => Blob;
|
package/dist/pcd.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const createBinaryPCD = (positions, colors) => {
|
|
2
|
+
const numPoints = positions.length / 3;
|
|
3
|
+
const hasColor = !!colors;
|
|
4
|
+
if (hasColor && colors.length !== numPoints * 3) {
|
|
5
|
+
throw new Error('Color array length must be numPoints * 3');
|
|
6
|
+
}
|
|
7
|
+
// 12 bytes xyz + optional 4 byte packed rgb
|
|
8
|
+
const stride = hasColor ? 16 : 12;
|
|
9
|
+
const header = `# .PCD v0.7 - Point Cloud Data file format
|
|
10
|
+
VERSION 0.7
|
|
11
|
+
FIELDS x y z${hasColor ? ' rgb' : ''}
|
|
12
|
+
SIZE 4 4 4${hasColor ? ' 4' : ''}
|
|
13
|
+
TYPE F F F${hasColor ? ' F' : ''}
|
|
14
|
+
COUNT 1 1 1${hasColor ? ' 1' : ''}
|
|
15
|
+
WIDTH ${numPoints}
|
|
16
|
+
HEIGHT 1
|
|
17
|
+
VIEWPOINT 0 0 0 1 0 0 0
|
|
18
|
+
POINTS ${numPoints}
|
|
19
|
+
DATA binary
|
|
20
|
+
`;
|
|
21
|
+
const headerBytes = new TextEncoder().encode(header);
|
|
22
|
+
const bodyBuffer = new ArrayBuffer(numPoints * stride);
|
|
23
|
+
const view = new DataView(bodyBuffer);
|
|
24
|
+
for (let i = 0; i < numPoints; i++) {
|
|
25
|
+
const offset = i * stride;
|
|
26
|
+
const pi = i * 3;
|
|
27
|
+
// XYZ
|
|
28
|
+
view.setFloat32(offset + 0, positions[pi + 0], true);
|
|
29
|
+
view.setFloat32(offset + 4, positions[pi + 1], true);
|
|
30
|
+
view.setFloat32(offset + 8, positions[pi + 2], true);
|
|
31
|
+
if (hasColor) {
|
|
32
|
+
const r = colors[pi + 0];
|
|
33
|
+
const g = colors[pi + 1];
|
|
34
|
+
const b = colors[pi + 2];
|
|
35
|
+
// pack into uint32
|
|
36
|
+
const packed = (r << 16) | (g << 8) | b;
|
|
37
|
+
// write as float32
|
|
38
|
+
view.setUint32(offset + 12, packed, true);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return new Blob([headerBytes, bodyBuffer], {
|
|
42
|
+
type: 'application/octet-stream',
|
|
43
|
+
});
|
|
44
|
+
};
|
|
@@ -50,7 +50,12 @@ export const bvh = (raycaster, options) => {
|
|
|
50
50
|
ref.remove(helper);
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
else if (isInstanceOf(ref, 'Mesh')
|
|
53
|
+
else if (isInstanceOf(ref, 'Mesh') &&
|
|
54
|
+
/**
|
|
55
|
+
* (mp) Line2s sort of suck. Their buffer attribute design internally is much different
|
|
56
|
+
* but they give no indication other than this that they are different.
|
|
57
|
+
*/
|
|
58
|
+
ref.geometry.attributes.position) {
|
|
54
59
|
ref.geometry.computeBoundsTree = computeBoundsTree;
|
|
55
60
|
ref.geometry.disposeBoundsTree = disposeBoundsTree;
|
|
56
61
|
ref.raycast = acceleratedRaycast;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const createRandomPcdBinary: (numPoints?: number, scale?: number, axes?: string) => Uint8Array
|
|
1
|
+
export declare const createRandomPcdBinary: (numPoints?: number, scale?: number, axes?: string) => Promise<Uint8Array>;
|
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# .PCD v0.7 - Point Cloud Data file format
|
|
4
|
-
VERSION 0.7
|
|
5
|
-
FIELDS x y z rgb
|
|
6
|
-
SIZE 4 4 4 4
|
|
7
|
-
TYPE F F F F
|
|
8
|
-
COUNT 1 1 1
|
|
9
|
-
WIDTH ${numPoints}
|
|
10
|
-
HEIGHT 1
|
|
11
|
-
VIEWPOINT 0 0 0 1 0 0 0
|
|
12
|
-
POINTS ${numPoints}
|
|
13
|
-
DATA ascii
|
|
14
|
-
`.trim();
|
|
1
|
+
import { createBinaryPCD } from '../pcd';
|
|
2
|
+
export const createRandomPcdBinary = async (numPoints = 200, scale = 1, axes = 'xyz') => {
|
|
15
3
|
const doX = axes.includes('x');
|
|
16
4
|
const doY = axes.includes('y');
|
|
17
5
|
const doZ = axes.includes('z');
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return encoder.encode(`${header}\n${points.join('\n')}`);
|
|
6
|
+
const positions = new Float32Array(numPoints * 3);
|
|
7
|
+
const colors = new Uint8Array(numPoints * 3);
|
|
8
|
+
for (let i = 0, l = positions.length; i < l; i += 1) {
|
|
9
|
+
positions[i] = doX ? (Math.random() - 0.5) * scale : 0;
|
|
10
|
+
positions[i + 1] = doY ? (Math.random() - 0.5) * scale : 0;
|
|
11
|
+
positions[i + 2] = doZ ? (Math.random() - 0.5) * scale : 0;
|
|
12
|
+
colors[i] = Math.floor(Math.random() * 256);
|
|
13
|
+
colors[i + 1] = Math.floor(Math.random() * 256);
|
|
14
|
+
colors[i + 2] = Math.floor(Math.random() * 256);
|
|
15
|
+
}
|
|
16
|
+
const buffer = await createBinaryPCD(positions, colors).arrayBuffer();
|
|
17
|
+
return new Uint8Array(buffer);
|
|
31
18
|
};
|
package/package.json
CHANGED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { T, useThrelte } from '@threlte/core'
|
|
3
|
-
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
|
|
4
|
-
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
|
|
5
|
-
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
|
|
6
|
-
import { untrack } from 'svelte'
|
|
7
|
-
|
|
8
|
-
let { positions } = $props()
|
|
9
|
-
|
|
10
|
-
let geometry = $state.raw(new LineGeometry())
|
|
11
|
-
|
|
12
|
-
const material = new LineMaterial()
|
|
13
|
-
const line = new Line2()
|
|
14
|
-
material.linewidth = 1
|
|
15
|
-
material.color.set('red')
|
|
16
|
-
material.depthTest = false
|
|
17
|
-
material.depthWrite = false
|
|
18
|
-
|
|
19
|
-
const { invalidate } = useThrelte()
|
|
20
|
-
|
|
21
|
-
$effect(() => {
|
|
22
|
-
untrack(() => {
|
|
23
|
-
geometry = new LineGeometry()
|
|
24
|
-
invalidate()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
if (!positions || positions.length === 0) {
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
untrack(() => {
|
|
32
|
-
geometry.setPositions(positions)
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
<T
|
|
38
|
-
is={line}
|
|
39
|
-
frustumCulled={false}
|
|
40
|
-
renderOrder={Number.POSITIVE_INFINITY}
|
|
41
|
-
>
|
|
42
|
-
<T is={geometry} />
|
|
43
|
-
<T is={material} />
|
|
44
|
-
</T>
|