@viamrobotics/motion-tools 1.32.0 → 1.33.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 +17 -11
- package/dist/components/App.svelte.d.ts +14 -7
- package/dist/components/Scene.svelte +40 -47
- package/dist/components/SceneProviders.svelte +0 -3
- package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
- package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
- package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
- package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
- package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
- package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/Settings.svelte +27 -312
- package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
- package/dist/components/overlay/settings/Tabs.svelte +5 -3
- package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
- package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
- package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
- package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
- package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/widgets/FramePov.svelte +1 -12
- package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
- package/dist/plugins/XR/DebugPanel.svelte +29 -0
- package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
- package/dist/plugins/XR/OriginMarker.svelte +341 -0
- package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
- package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
- package/dist/plugins/XR/WristDisplay.svelte +60 -0
- package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
- package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
- package/dist/plugins/XR/XRPlugins.svelte +9 -0
- package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
- package/dist/plugins/XR/XRSettings.svelte +240 -0
- package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
- package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
- package/dist/plugins/XR/debug.svelte.d.ts +7 -0
- package/dist/plugins/XR/debug.svelte.js +13 -0
- package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
- package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
- package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
- package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
- package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
- package/dist/plugins/XR/useOrigin.svelte.js +50 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +2 -0
- package/dist/three/OBBHelper.js +1 -0
- package/package.json +1 -1
- package/dist/components/xr/OriginMarker.svelte +0 -151
- package/dist/components/xr/XRControllerSettings.svelte +0 -242
- package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
- package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
- package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
- package/dist/components/xr/useOrigin.svelte.js +0 -27
- /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
- /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/math.js +0 -0
- /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { IntersectionEvent } from '@threlte/extras'
|
|
3
|
+
import type { XRTargetRaySpace } from 'three'
|
|
4
|
+
|
|
5
|
+
import { T, useTask, useThrelte } from '@threlte/core'
|
|
6
|
+
import { Controller, useController, useHeadset } from '@threlte/xr'
|
|
7
|
+
import { onDestroy } from 'svelte'
|
|
8
|
+
import { MathUtils, Vector3 } from 'three'
|
|
9
|
+
import { TransformControls } from 'three/addons/controls/TransformControls.js'
|
|
10
|
+
import { Text } from 'threlte-uikit'
|
|
11
|
+
import { Button, ButtonIcon, ButtonLabel, Panel } from 'threlte-uikit/horizon'
|
|
12
|
+
import { Icon, Locate, Move3d, Plus, Rotate3d, Scale3d } from 'threlte-uikit/lucide'
|
|
13
|
+
|
|
14
|
+
import { traits, useQuery, useTrait } from '../../../ecs'
|
|
15
|
+
import { FrameConfigUpdater } from '../../../FrameConfigUpdater.svelte'
|
|
16
|
+
import { useTransformControls } from '../../../hooks/useControls.svelte'
|
|
17
|
+
import { useFramelessComponents } from '../../../hooks/useFramelessComponents.svelte'
|
|
18
|
+
import { usePartConfig } from '../../../hooks/usePartConfig.svelte'
|
|
19
|
+
import { OrientationVector } from '../../../three/OrientationVector'
|
|
20
|
+
|
|
21
|
+
import { useOrigin } from '../useOrigin.svelte'
|
|
22
|
+
import WristDisplay from '../WristDisplay.svelte'
|
|
23
|
+
|
|
24
|
+
type Mode = 'translate' | 'rotate' | 'scale'
|
|
25
|
+
type Handedness = 'left' | 'right'
|
|
26
|
+
|
|
27
|
+
type BoxBase = { type: 'box'; x: number; y: number; z: number }
|
|
28
|
+
type SphereBase = { type: 'sphere'; r: number }
|
|
29
|
+
type CapsuleBase = { type: 'capsule'; r: number; l: number }
|
|
30
|
+
type GeometryBase = BoxBase | SphereBase | CapsuleBase
|
|
31
|
+
|
|
32
|
+
const { camera, renderer, scene } = useThrelte()
|
|
33
|
+
|
|
34
|
+
const partConfig = usePartConfig()
|
|
35
|
+
const transformControls = useTransformControls()
|
|
36
|
+
const framelessComponents = useFramelessComponents()
|
|
37
|
+
const leftController = useController('left')
|
|
38
|
+
const rightController = useController('right')
|
|
39
|
+
const headset = useHeadset()
|
|
40
|
+
const origin = useOrigin()
|
|
41
|
+
const selected = useQuery(traits.Selected)
|
|
42
|
+
|
|
43
|
+
const selectedEntity = $derived(selected.current[0])
|
|
44
|
+
const selectedObject3d = $derived(scene.getObjectByName(`${selectedEntity}`))
|
|
45
|
+
const framesAPI = useTrait(() => selectedEntity, traits.FramesAPI)
|
|
46
|
+
|
|
47
|
+
const resetForward = new Vector3()
|
|
48
|
+
const resetHead = new Vector3()
|
|
49
|
+
const resetOrigin = () => {
|
|
50
|
+
resetForward.set(0, 0, -1).applyQuaternion(headset.quaternion)
|
|
51
|
+
resetForward.z = 0
|
|
52
|
+
if (resetForward.lengthSq() < 1e-6) {
|
|
53
|
+
resetForward.set(0, 1, 0)
|
|
54
|
+
} else {
|
|
55
|
+
resetForward.normalize()
|
|
56
|
+
}
|
|
57
|
+
// headset.position/quaternion live in the composed XR reference space;
|
|
58
|
+
// convert into zUp before writing to origin.
|
|
59
|
+
origin.toZUpPos(resetHead, headset.position)
|
|
60
|
+
origin.toZUpDir(resetForward)
|
|
61
|
+
origin.set([resetHead.x + resetForward.x, resetHead.y + resetForward.y, 0], 0)
|
|
62
|
+
origin.commit()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const updater = new FrameConfigUpdater(partConfig.updateFrame, partConfig.deleteFrame)
|
|
66
|
+
|
|
67
|
+
const box = useTrait(() => selectedEntity, traits.Box)
|
|
68
|
+
const sphere = useTrait(() => selectedEntity, traits.Sphere)
|
|
69
|
+
const capsule = useTrait(() => selectedEntity, traits.Capsule)
|
|
70
|
+
|
|
71
|
+
const geometryType = $derived.by<'box' | 'sphere' | 'capsule' | undefined>(() => {
|
|
72
|
+
if (box.current) return 'box'
|
|
73
|
+
if (sphere.current) return 'sphere'
|
|
74
|
+
if (capsule.current) return 'capsule'
|
|
75
|
+
return undefined
|
|
76
|
+
})
|
|
77
|
+
const hasGeometry = $derived(geometryType !== undefined)
|
|
78
|
+
|
|
79
|
+
const controls = new TransformControls(camera.current, renderer.domElement)
|
|
80
|
+
controls.setSize(0.25)
|
|
81
|
+
|
|
82
|
+
const helper = controls.getHelper()
|
|
83
|
+
const raycaster = controls.getRaycaster()
|
|
84
|
+
|
|
85
|
+
let mode = $state<Mode>('translate')
|
|
86
|
+
let activeHandedness = $state.raw<Handedness>()
|
|
87
|
+
let attached = $state(false)
|
|
88
|
+
let addingFrame = $state(false)
|
|
89
|
+
|
|
90
|
+
// Snapshot of the geometry dims at drag start. Used in scale mode to compute
|
|
91
|
+
// `newDims = base * absoluteScaleFactor` each frame; cleared on drag end.
|
|
92
|
+
let geometryBase: GeometryBase | undefined
|
|
93
|
+
|
|
94
|
+
const getRay = (handedness: Handedness | undefined): XRTargetRaySpace | undefined => {
|
|
95
|
+
if (handedness === 'left') return leftController.current?.targetRay
|
|
96
|
+
if (handedness === 'right') return rightController.current?.targetRay
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const detectHandedness = (event: IntersectionEvent<PointerEvent>): Handedness | undefined => {
|
|
101
|
+
// nativeEvent is three's selectstart/selectend event whose `target` is the XRTargetRaySpace.
|
|
102
|
+
const target = (event.nativeEvent as { target?: unknown } | undefined)?.target
|
|
103
|
+
if (target && target === leftController.current?.targetRay) return 'left'
|
|
104
|
+
if (target && target === rightController.current?.targetRay) return 'right'
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$effect(() => {
|
|
109
|
+
controls.setMode(mode)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Constrain which axes the gizmo exposes while in scale mode:
|
|
113
|
+
// box: all three (width/height/depth)
|
|
114
|
+
// capsule: X (radius) + Z (length); hide Y since capsules are radially symmetric
|
|
115
|
+
// sphere: X only; a single handle drives the radius
|
|
116
|
+
// Translate/rotate always show all three.
|
|
117
|
+
$effect(() => {
|
|
118
|
+
if (mode !== 'scale') {
|
|
119
|
+
controls.showX = true
|
|
120
|
+
controls.showY = true
|
|
121
|
+
controls.showZ = true
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
switch (geometryType) {
|
|
125
|
+
case 'box': {
|
|
126
|
+
controls.showX = true
|
|
127
|
+
controls.showY = true
|
|
128
|
+
controls.showZ = true
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
case 'capsule': {
|
|
132
|
+
controls.showX = true
|
|
133
|
+
controls.showY = false
|
|
134
|
+
controls.showZ = true
|
|
135
|
+
break
|
|
136
|
+
}
|
|
137
|
+
case 'sphere': {
|
|
138
|
+
controls.showX = true
|
|
139
|
+
controls.showY = false
|
|
140
|
+
controls.showZ = false
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
default: {
|
|
144
|
+
controls.showX = false
|
|
145
|
+
controls.showY = false
|
|
146
|
+
controls.showZ = false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// selectedObject3d resolves to the named Mesh from Mesh.svelte; the Group that
|
|
152
|
+
// carries the frame's pose is its parent (set up in Frame.svelte).
|
|
153
|
+
$effect(() => {
|
|
154
|
+
if (!framesAPI.current || !partConfig.hasEditPermissions) {
|
|
155
|
+
controls.detach()
|
|
156
|
+
attached = false
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const target = selectedObject3d?.parent
|
|
161
|
+
|
|
162
|
+
if (target && target !== scene) {
|
|
163
|
+
controls.attach(target)
|
|
164
|
+
attached = true
|
|
165
|
+
} else {
|
|
166
|
+
controls.detach()
|
|
167
|
+
attached = false
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// `Raycaster.setFromXRController` reads `ray.matrixWorld`. When hand-tracking
|
|
172
|
+
// is active in the session, `@threlte/xr` does not attach the controller's
|
|
173
|
+
// targetRay to the scene graph (see `Controller.svelte`'s
|
|
174
|
+
// `{#if !isHandTracking.current}`), so scene traversal never refreshes its
|
|
175
|
+
// matrixWorld. We have to call `updateMatrixWorld()` ourselves or the ray
|
|
176
|
+
// aims wherever the controller was last attached.
|
|
177
|
+
const setRaycasterFromRay = (ray: XRTargetRaySpace) => {
|
|
178
|
+
ray.updateMatrixWorld(true)
|
|
179
|
+
raycaster.setFromXRController(ray)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const onPointerDown = (event: IntersectionEvent<PointerEvent>) => {
|
|
183
|
+
activeHandedness = detectHandedness(event)
|
|
184
|
+
const ray = getRay(activeHandedness)
|
|
185
|
+
if (!ray) return
|
|
186
|
+
setRaycasterFromRay(ray)
|
|
187
|
+
controls.pointerHover(null)
|
|
188
|
+
controls.pointerDown(null)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const onPointerUp = () => {
|
|
192
|
+
const ray = getRay(activeHandedness)
|
|
193
|
+
if (ray) setRaycasterFromRay(ray)
|
|
194
|
+
controls.pointerUp(null)
|
|
195
|
+
activeHandedness = undefined
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Per-frame: while dragging, keep pointerMove firing even if the active ray
|
|
199
|
+
// wobbles off the gizmo. Otherwise, drive pointerHover for axis highlight.
|
|
200
|
+
useTask(() => {
|
|
201
|
+
if (!controls.object) return
|
|
202
|
+
|
|
203
|
+
if (controls.dragging) {
|
|
204
|
+
const ray = getRay(activeHandedness)
|
|
205
|
+
if (!ray) return
|
|
206
|
+
setRaycasterFromRay(ray)
|
|
207
|
+
controls.pointerMove(null)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const rays = [rightController.current?.targetRay, leftController.current?.targetRay]
|
|
212
|
+
for (const ray of rays) {
|
|
213
|
+
if (!ray) continue
|
|
214
|
+
setRaycasterFromRay(ray)
|
|
215
|
+
controls.pointerHover(null)
|
|
216
|
+
if (controls.axis !== null) return
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const ov = new OrientationVector()
|
|
221
|
+
|
|
222
|
+
// Snapshot the current geometry dims when a scale-mode drag begins so every
|
|
223
|
+
// frame can compute `newDims = base * controls.object.scale` from a stable
|
|
224
|
+
// baseline, and clear the snapshot on drag end.
|
|
225
|
+
controls.addEventListener('mouseDown', () => {
|
|
226
|
+
transformControls.setActive(true)
|
|
227
|
+
if (mode !== 'scale') return
|
|
228
|
+
if (box.current) {
|
|
229
|
+
geometryBase = { type: 'box', x: box.current.x, y: box.current.y, z: box.current.z }
|
|
230
|
+
} else if (sphere.current) {
|
|
231
|
+
geometryBase = { type: 'sphere', r: sphere.current.r }
|
|
232
|
+
} else if (capsule.current) {
|
|
233
|
+
geometryBase = { type: 'capsule', r: capsule.current.r, l: capsule.current.l }
|
|
234
|
+
} else {
|
|
235
|
+
geometryBase = undefined
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
controls.addEventListener('mouseUp', () => {
|
|
240
|
+
transformControls.setActive(false)
|
|
241
|
+
geometryBase = undefined
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
controls.addEventListener('objectChange', () => {
|
|
245
|
+
const target = controls.object
|
|
246
|
+
if (!selectedEntity || !target) return
|
|
247
|
+
|
|
248
|
+
if (mode === 'translate') {
|
|
249
|
+
// three.js scene is in meters; FrameConfigUpdater stores mm.
|
|
250
|
+
updater.updateLocalPosition(selectedEntity, {
|
|
251
|
+
x: target.position.x * 1000,
|
|
252
|
+
y: target.position.y * 1000,
|
|
253
|
+
z: target.position.z * 1000,
|
|
254
|
+
})
|
|
255
|
+
} else if (mode === 'rotate') {
|
|
256
|
+
ov.setFromQuaternion(target.quaternion)
|
|
257
|
+
updater.updateLocalOrientation(selectedEntity, {
|
|
258
|
+
oX: ov.x,
|
|
259
|
+
oY: ov.y,
|
|
260
|
+
oZ: ov.z,
|
|
261
|
+
theta: MathUtils.radToDeg(ov.th),
|
|
262
|
+
})
|
|
263
|
+
} else if (geometryBase) {
|
|
264
|
+
// Scale mode: `target.scale` is the absolute factor since drag start
|
|
265
|
+
// because TC's internal `_scaleStart` was `(1,1,1)`. Apply it to the
|
|
266
|
+
// baseline dims, write back through the trait so the mesh regenerates
|
|
267
|
+
// at the new size, then snap the group's scale back to 1 so the next
|
|
268
|
+
// frame's regenerated geometry isn't re-scaled visually.
|
|
269
|
+
const s = target.scale
|
|
270
|
+
if (geometryBase.type === 'box') {
|
|
271
|
+
updater.updateGeometry(selectedEntity, {
|
|
272
|
+
type: 'box',
|
|
273
|
+
x: geometryBase.x * s.x,
|
|
274
|
+
y: geometryBase.y * s.y,
|
|
275
|
+
z: geometryBase.z * s.z,
|
|
276
|
+
})
|
|
277
|
+
} else if (geometryBase.type === 'sphere') {
|
|
278
|
+
updater.updateGeometry(selectedEntity, {
|
|
279
|
+
type: 'sphere',
|
|
280
|
+
r: geometryBase.r * s.x,
|
|
281
|
+
})
|
|
282
|
+
} else {
|
|
283
|
+
updater.updateGeometry(selectedEntity, {
|
|
284
|
+
type: 'capsule',
|
|
285
|
+
r: geometryBase.r * s.x,
|
|
286
|
+
l: geometryBase.l * s.z,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
target.scale.set(1, 1, 1)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
onDestroy(() => {
|
|
294
|
+
transformControls.setActive(false)
|
|
295
|
+
controls.detach()
|
|
296
|
+
controls.dispose()
|
|
297
|
+
})
|
|
298
|
+
</script>
|
|
299
|
+
|
|
300
|
+
<Controller left />
|
|
301
|
+
<Controller right />
|
|
302
|
+
|
|
303
|
+
<T
|
|
304
|
+
is={helper}
|
|
305
|
+
onpointerdown={onPointerDown as never}
|
|
306
|
+
onpointerup={onPointerUp as never}
|
|
307
|
+
/>
|
|
308
|
+
|
|
309
|
+
{#if partConfig.hasEditPermissions}
|
|
310
|
+
<WristDisplay position={[0, 0.005, 0.04]}>
|
|
311
|
+
<Panel
|
|
312
|
+
flexDirection="row"
|
|
313
|
+
padding={8}
|
|
314
|
+
gap={8}
|
|
315
|
+
backgroundColor="#111"
|
|
316
|
+
borderRadius={16}
|
|
317
|
+
>
|
|
318
|
+
<Button
|
|
319
|
+
icon
|
|
320
|
+
size="sm"
|
|
321
|
+
variant="tertiary"
|
|
322
|
+
onclick={resetOrigin}
|
|
323
|
+
>
|
|
324
|
+
<ButtonIcon>
|
|
325
|
+
<Icon is={Locate} />
|
|
326
|
+
</ButtonIcon>
|
|
327
|
+
</Button>
|
|
328
|
+
<Button
|
|
329
|
+
icon
|
|
330
|
+
size="sm"
|
|
331
|
+
variant={addingFrame ? 'primary' : 'tertiary'}
|
|
332
|
+
onclick={() => (addingFrame = !addingFrame)}
|
|
333
|
+
>
|
|
334
|
+
<ButtonIcon>
|
|
335
|
+
<Icon is={Plus} />
|
|
336
|
+
</ButtonIcon>
|
|
337
|
+
</Button>
|
|
338
|
+
{#if attached}
|
|
339
|
+
<Button
|
|
340
|
+
icon
|
|
341
|
+
size="sm"
|
|
342
|
+
variant={mode === 'translate' ? 'primary' : 'tertiary'}
|
|
343
|
+
onclick={() => (mode = 'translate')}
|
|
344
|
+
>
|
|
345
|
+
<ButtonIcon>
|
|
346
|
+
<Icon is={Move3d} />
|
|
347
|
+
</ButtonIcon>
|
|
348
|
+
</Button>
|
|
349
|
+
<Button
|
|
350
|
+
icon
|
|
351
|
+
size="sm"
|
|
352
|
+
variant={mode === 'rotate' ? 'primary' : 'tertiary'}
|
|
353
|
+
onclick={() => (mode = 'rotate')}
|
|
354
|
+
>
|
|
355
|
+
<ButtonIcon>
|
|
356
|
+
<Icon is={Rotate3d} />
|
|
357
|
+
</ButtonIcon>
|
|
358
|
+
</Button>
|
|
359
|
+
<Button
|
|
360
|
+
icon
|
|
361
|
+
size="sm"
|
|
362
|
+
disabled={!hasGeometry}
|
|
363
|
+
variant={mode === 'scale' ? 'primary' : 'tertiary'}
|
|
364
|
+
onclick={() => (mode = 'scale')}
|
|
365
|
+
>
|
|
366
|
+
<ButtonIcon>
|
|
367
|
+
<Icon is={Scale3d} />
|
|
368
|
+
</ButtonIcon>
|
|
369
|
+
</Button>
|
|
370
|
+
{/if}
|
|
371
|
+
</Panel>
|
|
372
|
+
</WristDisplay>
|
|
373
|
+
|
|
374
|
+
{#if addingFrame}
|
|
375
|
+
<WristDisplay position={[0, 0.005, 0.14]}>
|
|
376
|
+
<Panel
|
|
377
|
+
flexDirection="column"
|
|
378
|
+
padding={12}
|
|
379
|
+
gap={6}
|
|
380
|
+
backgroundColor="#111"
|
|
381
|
+
borderRadius={16}
|
|
382
|
+
minWidth={320}
|
|
383
|
+
>
|
|
384
|
+
{#if framelessComponents.current.length > 0}
|
|
385
|
+
{#each framelessComponents.current as component (component)}
|
|
386
|
+
<Button
|
|
387
|
+
size="sm"
|
|
388
|
+
variant="tertiary"
|
|
389
|
+
onclick={() => {
|
|
390
|
+
partConfig.createFrame(component)
|
|
391
|
+
addingFrame = false
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
<ButtonLabel>
|
|
395
|
+
<Text
|
|
396
|
+
text={component}
|
|
397
|
+
fontSize={14}
|
|
398
|
+
color="#ffffff"
|
|
399
|
+
/>
|
|
400
|
+
</ButtonLabel>
|
|
401
|
+
</Button>
|
|
402
|
+
{/each}
|
|
403
|
+
{:else}
|
|
404
|
+
<Text
|
|
405
|
+
text="No components without frames"
|
|
406
|
+
fontSize={14}
|
|
407
|
+
color="#ffffff"
|
|
408
|
+
/>
|
|
409
|
+
{/if}
|
|
410
|
+
</Panel>
|
|
411
|
+
</WristDisplay>
|
|
412
|
+
{/if}
|
|
413
|
+
{/if}
|
|
@@ -3,6 +3,10 @@ interface Context {
|
|
|
3
3
|
createAnchor: (position: Vector3, orientation: Quaternion) => Promise<XRAnchor> | undefined;
|
|
4
4
|
bindAnchorObject: (anchor: XRAnchor, object: Object3D) => void;
|
|
5
5
|
unbindAnchorObject: (anchor: XRAnchor) => void;
|
|
6
|
+
persist: (anchor: XRAnchor) => Promise<string | undefined>;
|
|
7
|
+
restore: (uuid: string) => Promise<XRAnchor | undefined>;
|
|
8
|
+
remove: (uuid: string) => Promise<void>;
|
|
9
|
+
getAnchorPose: (anchor: XRAnchor) => XRPose | undefined;
|
|
6
10
|
}
|
|
7
11
|
export declare const provideAnchors: () => void;
|
|
8
12
|
export declare const useAnchors: () => Context;
|
|
@@ -23,6 +23,24 @@ export const provideAnchors = () => {
|
|
|
23
23
|
const unbindAnchorObject = (anchor) => {
|
|
24
24
|
map.delete(anchor);
|
|
25
25
|
};
|
|
26
|
+
const persist = async (anchor) => {
|
|
27
|
+
return anchor.requestPersistentHandle?.();
|
|
28
|
+
};
|
|
29
|
+
const restore = async (uuid) => {
|
|
30
|
+
const session = renderer.xr.getSession();
|
|
31
|
+
return session?.restorePersistentAnchor?.(uuid);
|
|
32
|
+
};
|
|
33
|
+
const remove = async (uuid) => {
|
|
34
|
+
const session = renderer.xr.getSession();
|
|
35
|
+
await session?.deletePersistentAnchor?.(uuid);
|
|
36
|
+
};
|
|
37
|
+
const getAnchorPose = (anchor) => {
|
|
38
|
+
const space = renderer.xr.getReferenceSpace();
|
|
39
|
+
const frame = renderer.xr.getFrame();
|
|
40
|
+
if (!space || !frame)
|
|
41
|
+
return;
|
|
42
|
+
return frame.getPose(anchor.anchorSpace, space);
|
|
43
|
+
};
|
|
26
44
|
useTask(() => {
|
|
27
45
|
const space = renderer.xr.getReferenceSpace();
|
|
28
46
|
const frame = renderer.xr.getFrame();
|
|
@@ -48,6 +66,10 @@ export const provideAnchors = () => {
|
|
|
48
66
|
createAnchor,
|
|
49
67
|
bindAnchorObject,
|
|
50
68
|
unbindAnchorObject,
|
|
69
|
+
persist,
|
|
70
|
+
restore,
|
|
71
|
+
remove,
|
|
72
|
+
getAnchorPose,
|
|
51
73
|
});
|
|
52
74
|
};
|
|
53
75
|
export const useAnchors = () => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Vector3, Vector3Like, Vector3Tuple } from 'three';
|
|
2
|
+
interface Context {
|
|
3
|
+
position: Vector3Tuple;
|
|
4
|
+
rotation: number;
|
|
5
|
+
set: (pos?: Vector3Tuple, rot?: number) => void;
|
|
6
|
+
/** Request persistence of the current origin (creates/updates the XR anchor). */
|
|
7
|
+
commit: () => void;
|
|
8
|
+
/** Called once by OriginMarker to wire the commit implementation. */
|
|
9
|
+
registerCommit: (fn: () => void) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Convert a position reported in the composed XR reference space
|
|
12
|
+
* (zUp offset by the current origin) into the zUp frame that
|
|
13
|
+
* `position`/`rotation` are stored in. Writes into `out` and returns it.
|
|
14
|
+
*/
|
|
15
|
+
toZUpPos: (out: Vector3, p: Vector3Like) => Vector3;
|
|
16
|
+
/**
|
|
17
|
+
* Rotate a direction from the composed XR reference space into zUp
|
|
18
|
+
* (no translation). Mutates `v` in place and returns it.
|
|
19
|
+
*/
|
|
20
|
+
toZUpDir: (v: Vector3) => Vector3;
|
|
21
|
+
}
|
|
22
|
+
export declare const provideOrigin: () => Context;
|
|
23
|
+
export declare const useOrigin: () => Context;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const key = Symbol('origin-context');
|
|
3
|
+
export const provideOrigin = () => {
|
|
4
|
+
const position = $state([0, 0, 0]);
|
|
5
|
+
let rotation = $state(0);
|
|
6
|
+
let commitFn = () => { };
|
|
7
|
+
const context = {
|
|
8
|
+
get position() {
|
|
9
|
+
return position;
|
|
10
|
+
},
|
|
11
|
+
get rotation() {
|
|
12
|
+
return rotation;
|
|
13
|
+
},
|
|
14
|
+
set(pos, rot) {
|
|
15
|
+
if (pos) {
|
|
16
|
+
position[0] = pos[0];
|
|
17
|
+
position[1] = pos[1];
|
|
18
|
+
position[2] = pos[2];
|
|
19
|
+
}
|
|
20
|
+
if (rot !== undefined) {
|
|
21
|
+
rotation = rot;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
commit() {
|
|
25
|
+
commitFn();
|
|
26
|
+
},
|
|
27
|
+
registerCommit(fn) {
|
|
28
|
+
commitFn = fn;
|
|
29
|
+
},
|
|
30
|
+
toZUpPos(out, p) {
|
|
31
|
+
const cos = Math.cos(rotation);
|
|
32
|
+
const sin = Math.sin(rotation);
|
|
33
|
+
out.set(position[0] + cos * p.x - sin * p.y, position[1] + sin * p.x + cos * p.y, position[2] + p.z);
|
|
34
|
+
return out;
|
|
35
|
+
},
|
|
36
|
+
toZUpDir(v) {
|
|
37
|
+
const cos = Math.cos(rotation);
|
|
38
|
+
const sin = Math.sin(rotation);
|
|
39
|
+
const x = v.x;
|
|
40
|
+
v.x = cos * x - sin * v.y;
|
|
41
|
+
v.y = sin * x + cos * v.y;
|
|
42
|
+
return v;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
setContext(key, context);
|
|
46
|
+
return context;
|
|
47
|
+
};
|
|
48
|
+
export const useOrigin = () => {
|
|
49
|
+
return getContext(key);
|
|
50
|
+
};
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export { default as DrawService } from './DrawService/DrawService.svelte';
|
|
|
7
7
|
export { default as Skybox } from './Skybox/Skybox.svelte';
|
|
8
8
|
export { default as Debug } from './Debug/Debug.svelte';
|
|
9
9
|
export { default as Focus } from './Focus/Focus.svelte';
|
|
10
|
+
export { default as XR } from './XR/XR.svelte';
|
|
11
|
+
export { default as XRSettings } from './XR/XRSettings.svelte';
|
package/dist/plugins/index.js
CHANGED
|
@@ -11,3 +11,5 @@ export { default as Skybox } from './Skybox/Skybox.svelte';
|
|
|
11
11
|
// Debug
|
|
12
12
|
export { default as Debug } from './Debug/Debug.svelte';
|
|
13
13
|
export { default as Focus } from './Focus/Focus.svelte';
|
|
14
|
+
export { default as XR } from './XR/XR.svelte';
|
|
15
|
+
export { default as XRSettings } from './XR/XRSettings.svelte';
|
package/dist/three/OBBHelper.js
CHANGED