@threlte/xr 1.5.5 → 1.6.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/Controller.svelte +59 -60
- package/dist/components/Hand.svelte +21 -29
- package/dist/components/XR.svelte +146 -16
- package/dist/components/XR.svelte.d.ts +20 -0
- package/dist/components/XROrigin.svelte +82 -0
- package/dist/components/XROrigin.svelte.d.ts +33 -0
- package/dist/components/internal/TeleportRay.svelte +15 -3
- package/dist/hooks/currentReadable.svelte.d.ts +28 -1
- package/dist/hooks/currentReadable.svelte.js +36 -9
- package/dist/hooks/useController.svelte.d.ts +3 -3
- package/dist/hooks/useController.svelte.js +30 -7
- package/dist/hooks/useHand.svelte.d.ts +2 -2
- package/dist/hooks/useHand.svelte.js +26 -5
- package/dist/hooks/useHandJoint.svelte.js +6 -5
- package/dist/hooks/useHitTest.svelte.js +56 -12
- package/dist/hooks/useTeleport.d.ts +11 -9
- package/dist/hooks/useTeleport.js +62 -14
- package/dist/hooks/useXR.js +5 -5
- package/dist/hooks/useXROrigin.svelte.d.ts +10 -0
- package/dist/hooks/useXROrigin.svelte.js +11 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/internal/inputSources.svelte.d.ts +84 -0
- package/dist/internal/inputSources.svelte.js +91 -0
- package/dist/internal/setupHeadset.svelte.js +18 -6
- package/dist/internal/setupInputSources.d.ts +4 -0
- package/dist/internal/setupInputSources.js +319 -0
- package/dist/internal/state.svelte.d.ts +10 -12
- package/dist/internal/state.svelte.js +9 -3
- package/dist/lib/getXRSessionOptions.d.ts +1 -1
- package/dist/lib/getXRSessionOptions.js +8 -7
- package/dist/lib/toggleXRSession.d.ts +1 -1
- package/dist/lib/toggleXRSession.js +20 -5
- package/dist/plugins/pointerControls/compute.js +14 -9
- package/dist/plugins/pointerControls/context.d.ts +3 -3
- package/dist/plugins/pointerControls/context.js +12 -6
- package/dist/plugins/pointerControls/index.d.ts +4 -3
- package/dist/plugins/pointerControls/index.js +63 -29
- package/dist/plugins/pointerControls/setup.svelte.js +64 -44
- package/dist/plugins/pointerControls/types.d.ts +14 -3
- package/dist/plugins/teleportControls/compute.d.ts +1 -1
- package/dist/plugins/teleportControls/compute.js +11 -8
- package/dist/plugins/teleportControls/context.d.ts +4 -4
- package/dist/plugins/teleportControls/context.js +1 -4
- package/dist/plugins/teleportControls/index.js +7 -4
- package/dist/plugins/teleportControls/setup.svelte.js +10 -9
- package/dist/plugins/touchControls/compute.d.ts +3 -0
- package/dist/plugins/touchControls/compute.js +13 -0
- package/dist/plugins/touchControls/context.d.ts +12 -0
- package/dist/plugins/touchControls/context.js +27 -0
- package/dist/plugins/touchControls/hook.d.ts +5 -0
- package/dist/plugins/touchControls/hook.js +26 -0
- package/dist/plugins/touchControls/index.d.ts +33 -0
- package/dist/plugins/touchControls/index.js +41 -0
- package/dist/plugins/touchControls/plugin.svelte.d.ts +1 -0
- package/dist/plugins/touchControls/plugin.svelte.js +24 -0
- package/dist/plugins/touchControls/setup.svelte.d.ts +2 -0
- package/dist/plugins/touchControls/setup.svelte.js +247 -0
- package/dist/plugins/touchControls/types.d.ts +62 -0
- package/dist/plugins/touchControls/types.js +11 -0
- package/dist/types.d.ts +1 -1
- package/package.json +3 -3
- package/dist/internal/setupControllers.d.ts +0 -2
- package/dist/internal/setupControllers.js +0 -73
- package/dist/internal/setupHands.d.ts +0 -2
- package/dist/internal/setupHands.js +0 -67
- package/dist/internal/useHandTrackingState.d.ts +0 -5
- package/dist/internal/useHandTrackingState.js +0 -20
|
@@ -2,4 +2,31 @@ import { type Readable } from 'svelte/store';
|
|
|
2
2
|
export type CurrentReadable<T> = Readable<T> & {
|
|
3
3
|
current: T;
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* ### `runeToCurrentReadable`
|
|
7
|
+
*
|
|
8
|
+
* Bridges a Svelte 5 `$state` or `$derived` rune into the `CurrentReadable`
|
|
9
|
+
* interface, allowing rune-backed reactive values to be consumed as read-only
|
|
10
|
+
* stores by legacy or store-aware code.
|
|
11
|
+
*
|
|
12
|
+
* Pass the getter of a `$state` or `$derived` variable:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* let count = $state(0)
|
|
16
|
+
* const doubled = $derived(count * 2)
|
|
17
|
+
*
|
|
18
|
+
* const store = runeToCurrentReadable(() => doubled)
|
|
19
|
+
*
|
|
20
|
+
* store.subscribe((v) => console.log(v)) // reacts to rune changes
|
|
21
|
+
* console.log(store.current) // synchronous read, no overhead
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Use this over `runeToCurrentWritable` when the value is derived or when
|
|
25
|
+
* external writes should not be allowed — for example, renderer settings
|
|
26
|
+
* that are owned by `<Canvas>` props.
|
|
27
|
+
*
|
|
28
|
+
* The `.current` property reads the rune directly and is always in sync.
|
|
29
|
+
* `.subscribe` is powered by `toStore`, which tracks the getter reactively
|
|
30
|
+
* via `$effect`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const runeToCurrentReadable: <T>(get: () => T) => CurrentReadable<T>;
|
|
@@ -1,11 +1,38 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
1
2
|
import { toStore } from 'svelte/store';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
/**
|
|
4
|
+
* ### `runeToCurrentReadable`
|
|
5
|
+
*
|
|
6
|
+
* Bridges a Svelte 5 `$state` or `$derived` rune into the `CurrentReadable`
|
|
7
|
+
* interface, allowing rune-backed reactive values to be consumed as read-only
|
|
8
|
+
* stores by legacy or store-aware code.
|
|
9
|
+
*
|
|
10
|
+
* Pass the getter of a `$state` or `$derived` variable:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* let count = $state(0)
|
|
14
|
+
* const doubled = $derived(count * 2)
|
|
15
|
+
*
|
|
16
|
+
* const store = runeToCurrentReadable(() => doubled)
|
|
17
|
+
*
|
|
18
|
+
* store.subscribe((v) => console.log(v)) // reacts to rune changes
|
|
19
|
+
* console.log(store.current) // synchronous read, no overhead
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Use this over `runeToCurrentWritable` when the value is derived or when
|
|
23
|
+
* external writes should not be allowed — for example, renderer settings
|
|
24
|
+
* that are owned by `<Canvas>` props.
|
|
25
|
+
*
|
|
26
|
+
* The `.current` property reads the rune directly and is always in sync.
|
|
27
|
+
* `.subscribe` is powered by `toStore`, which tracks the getter reactively
|
|
28
|
+
* via `$effect`.
|
|
29
|
+
*/
|
|
30
|
+
export const runeToCurrentReadable = (get) => {
|
|
31
|
+
const { subscribe } = toStore(get);
|
|
32
|
+
return {
|
|
33
|
+
subscribe,
|
|
34
|
+
get current() {
|
|
35
|
+
return untrack(get);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
11
38
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { XRController } from '../types.js';
|
|
2
2
|
import { type CurrentReadable } from './currentReadable.svelte.js';
|
|
3
3
|
declare class Controllers {
|
|
4
|
-
left: XRController | undefined;
|
|
5
|
-
right: XRController | undefined;
|
|
6
|
-
none: XRController | undefined;
|
|
4
|
+
get left(): XRController | undefined;
|
|
5
|
+
get right(): XRController | undefined;
|
|
6
|
+
get none(): XRController | undefined;
|
|
7
7
|
}
|
|
8
8
|
export declare const controllers: Controllers;
|
|
9
9
|
/**
|
|
@@ -1,8 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getControllerState } from '../internal/inputSources.svelte.js';
|
|
2
|
+
import { runeToCurrentReadable } from './currentReadable.svelte.js';
|
|
3
|
+
const controllerObjects = new WeakMap();
|
|
4
|
+
const toXRController = (state) => {
|
|
5
|
+
if (state === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
let controller = controllerObjects.get(state);
|
|
8
|
+
if (controller !== undefined)
|
|
9
|
+
return controller;
|
|
10
|
+
controller = {
|
|
11
|
+
inputSource: state.inputSource,
|
|
12
|
+
targetRay: state.targetRay,
|
|
13
|
+
grip: state.grip,
|
|
14
|
+
model: state.model
|
|
15
|
+
};
|
|
16
|
+
controllerObjects.set(state, controller);
|
|
17
|
+
return controller;
|
|
18
|
+
};
|
|
2
19
|
class Controllers {
|
|
3
|
-
left
|
|
4
|
-
|
|
5
|
-
|
|
20
|
+
get left() {
|
|
21
|
+
return toXRController(getControllerState('left'));
|
|
22
|
+
}
|
|
23
|
+
get right() {
|
|
24
|
+
return toXRController(getControllerState('right'));
|
|
25
|
+
}
|
|
26
|
+
get none() {
|
|
27
|
+
return toXRController(getControllerState('none'));
|
|
28
|
+
}
|
|
6
29
|
}
|
|
7
30
|
export const controllers = new Controllers();
|
|
8
31
|
/**
|
|
@@ -11,11 +34,11 @@ export const controllers = new Controllers();
|
|
|
11
34
|
export const useController = (handedness) => {
|
|
12
35
|
switch (handedness) {
|
|
13
36
|
case 'left':
|
|
14
|
-
return
|
|
37
|
+
return runeToCurrentReadable(() => controllers.left);
|
|
15
38
|
case 'right':
|
|
16
|
-
return
|
|
39
|
+
return runeToCurrentReadable(() => controllers.right);
|
|
17
40
|
case 'none':
|
|
18
|
-
return
|
|
41
|
+
return runeToCurrentReadable(() => controllers.none);
|
|
19
42
|
default:
|
|
20
43
|
throw new Error('useController handedness must be left, right, or none.');
|
|
21
44
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { XRHandObject } from '../types.js';
|
|
2
2
|
import { type CurrentReadable } from './currentReadable.svelte.js';
|
|
3
3
|
declare class Hands {
|
|
4
|
-
left: XRHandObject | undefined;
|
|
5
|
-
right: XRHandObject | undefined;
|
|
4
|
+
get left(): XRHandObject | undefined;
|
|
5
|
+
get right(): XRHandObject | undefined;
|
|
6
6
|
}
|
|
7
7
|
export declare const hands: Hands;
|
|
8
8
|
/**
|
|
@@ -1,7 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getHandState } from '../internal/inputSources.svelte.js';
|
|
2
|
+
import { runeToCurrentReadable } from './currentReadable.svelte.js';
|
|
3
|
+
const handObjects = new WeakMap();
|
|
4
|
+
const toXRHandObject = (state) => {
|
|
5
|
+
if (state === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
let hand = handObjects.get(state);
|
|
8
|
+
if (hand !== undefined)
|
|
9
|
+
return hand;
|
|
10
|
+
hand = {
|
|
11
|
+
targetRay: state.targetRay,
|
|
12
|
+
hand: state.hand,
|
|
13
|
+
model: state.model,
|
|
14
|
+
inputSource: state.inputSource.hand
|
|
15
|
+
};
|
|
16
|
+
handObjects.set(state, hand);
|
|
17
|
+
return hand;
|
|
18
|
+
};
|
|
2
19
|
class Hands {
|
|
3
|
-
left
|
|
4
|
-
|
|
20
|
+
get left() {
|
|
21
|
+
return toXRHandObject(getHandState('left'));
|
|
22
|
+
}
|
|
23
|
+
get right() {
|
|
24
|
+
return toXRHandObject(getHandState('right'));
|
|
25
|
+
}
|
|
5
26
|
}
|
|
6
27
|
export const hands = new Hands();
|
|
7
28
|
/**
|
|
@@ -10,9 +31,9 @@ export const hands = new Hands();
|
|
|
10
31
|
export const useHand = (handedness) => {
|
|
11
32
|
switch (handedness) {
|
|
12
33
|
case 'left':
|
|
13
|
-
return
|
|
34
|
+
return runeToCurrentReadable(() => hands.left);
|
|
14
35
|
case 'right':
|
|
15
|
-
return
|
|
36
|
+
return runeToCurrentReadable(() => hands.right);
|
|
16
37
|
default:
|
|
17
38
|
throw new Error('useHand handedness must be left or right.');
|
|
18
39
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { useTask, useThrelte } from '@threlte/core';
|
|
2
|
-
import {
|
|
2
|
+
import { useHand } from './useHand.svelte.js';
|
|
3
3
|
import { isPresenting } from '../internal/state.svelte.js';
|
|
4
|
-
import {
|
|
4
|
+
import { runeToCurrentReadable } from './currentReadable.svelte.js';
|
|
5
|
+
import { fromStore } from 'svelte/store';
|
|
5
6
|
/**
|
|
6
7
|
* Provides a reference to a requested hand joint, once available.
|
|
7
8
|
*/
|
|
8
9
|
export const useHandJoint = (handedness, joint) => {
|
|
9
10
|
const { invalidate } = useThrelte();
|
|
10
|
-
const
|
|
11
|
+
const hand = fromStore(useHand(handedness));
|
|
11
12
|
let jointSpace = $state.raw();
|
|
12
13
|
useTask(() => {
|
|
13
|
-
const space =
|
|
14
|
+
const space = hand.current?.hand.joints[joint];
|
|
14
15
|
// The joint radius is a good indicator that the joint is ready.
|
|
15
16
|
// Re-check each frame so we pick up reconnects and clear on disconnect.
|
|
16
17
|
if (space?.jointRadius !== undefined) {
|
|
@@ -24,5 +25,5 @@ export const useHandJoint = (handedness, joint) => {
|
|
|
24
25
|
invalidate();
|
|
25
26
|
}
|
|
26
27
|
}, { running: () => isPresenting.current });
|
|
27
|
-
return
|
|
28
|
+
return runeToCurrentReadable(() => jointSpace);
|
|
28
29
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Matrix4 } from 'three';
|
|
2
2
|
import { useThrelte, useTask } from '@threlte/core';
|
|
3
|
-
import {
|
|
3
|
+
import { useController } from './useController.svelte.js';
|
|
4
|
+
import { useXROrigin } from './useXROrigin.svelte.js';
|
|
4
5
|
import { isPresenting, session } from '../internal/state.svelte.js';
|
|
6
|
+
import { fromStore } from 'svelte/store';
|
|
5
7
|
/**
|
|
6
8
|
* Use this hook to perform a hit test per frame in an AR environment.
|
|
7
9
|
*
|
|
@@ -16,25 +18,62 @@ import { isPresenting, session } from '../internal/state.svelte.js';
|
|
|
16
18
|
export const useHitTest = (hitTestCallback, options = {}) => {
|
|
17
19
|
const source = options.source ?? 'viewer';
|
|
18
20
|
const { xr } = useThrelte().renderer;
|
|
21
|
+
const xrOrigin = useXROrigin();
|
|
19
22
|
const hitMatrix = new Matrix4();
|
|
20
23
|
let hitTestSource = $state.raw();
|
|
21
|
-
const getHitTestSource = async (space) => {
|
|
22
|
-
if (space === undefined) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
hitTestSource = await session.current?.requestHitTestSource?.({ space });
|
|
26
|
-
};
|
|
27
24
|
if (source === 'viewer') {
|
|
28
25
|
$effect.pre(() => {
|
|
29
|
-
session.current
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const currentSession = session.current;
|
|
27
|
+
if (currentSession === undefined)
|
|
28
|
+
return;
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
let created;
|
|
31
|
+
currentSession
|
|
32
|
+
.requestReferenceSpace('viewer')
|
|
33
|
+
.then((space) => currentSession.requestHitTestSource?.({ space }))
|
|
34
|
+
.then((src) => {
|
|
35
|
+
if (cancelled || src === undefined) {
|
|
36
|
+
src?.cancel();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
created = src;
|
|
40
|
+
hitTestSource = src;
|
|
41
|
+
})
|
|
42
|
+
.catch(console.error);
|
|
43
|
+
return () => {
|
|
44
|
+
cancelled = true;
|
|
45
|
+
created?.cancel();
|
|
46
|
+
if (hitTestSource === created)
|
|
47
|
+
hitTestSource = undefined;
|
|
48
|
+
};
|
|
32
49
|
});
|
|
33
50
|
}
|
|
34
51
|
else {
|
|
35
|
-
const controller =
|
|
52
|
+
const controller = fromStore(useController(source === 'leftInput' ? 'left' : 'right'));
|
|
36
53
|
$effect.pre(() => {
|
|
37
|
-
|
|
54
|
+
const currentSession = session.current;
|
|
55
|
+
const space = controller.current?.inputSource.targetRaySpace;
|
|
56
|
+
if (currentSession === undefined || space === undefined)
|
|
57
|
+
return;
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
let created;
|
|
60
|
+
currentSession
|
|
61
|
+
.requestHitTestSource?.({ space })
|
|
62
|
+
?.then((src) => {
|
|
63
|
+
if (cancelled || src === undefined) {
|
|
64
|
+
src?.cancel();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
created = src;
|
|
68
|
+
hitTestSource = src;
|
|
69
|
+
})
|
|
70
|
+
.catch(console.error);
|
|
71
|
+
return () => {
|
|
72
|
+
cancelled = true;
|
|
73
|
+
created?.cancel();
|
|
74
|
+
if (hitTestSource === created)
|
|
75
|
+
hitTestSource = undefined;
|
|
76
|
+
};
|
|
38
77
|
});
|
|
39
78
|
}
|
|
40
79
|
useTask(() => {
|
|
@@ -48,6 +87,11 @@ export const useHitTest = (hitTestCallback, options = {}) => {
|
|
|
48
87
|
return hitTestCallback(hitMatrix, undefined);
|
|
49
88
|
}
|
|
50
89
|
hitMatrix.fromArray(pose.transform.matrix);
|
|
90
|
+
const currentOrigin = xrOrigin.current;
|
|
91
|
+
if (currentOrigin !== undefined) {
|
|
92
|
+
currentOrigin.updateWorldMatrix(true, false);
|
|
93
|
+
hitMatrix.premultiply(currentOrigin.matrixWorld);
|
|
94
|
+
}
|
|
51
95
|
hitTestCallback(hitMatrix, hit);
|
|
52
96
|
}, { running: () => isPresenting.current && hitTestSource !== undefined });
|
|
53
97
|
$effect.pre(() => {
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { Quaternion,
|
|
1
|
+
import { Quaternion, Vector3, type Vector3Tuple } from 'three';
|
|
2
2
|
/**
|
|
3
|
-
* Returns a callback
|
|
3
|
+
* Returns a callback that teleports the player to a target position and optional orientation.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* const vec3 = new THREE.Vector3()
|
|
5
|
+
* When used inside `<XROrigin>`, the origin group is translated directly — the
|
|
6
|
+
* user's feet end up at the target, and their room-scale offset from the origin is preserved.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* When used outside `<XROrigin>`, the underlying `XRReferenceSpace` is mutated to compensate
|
|
9
|
+
* for the viewer's current position so the feet end up at the target regardless of where the
|
|
10
|
+
* user has walked in their physical space.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
+
* @example
|
|
13
|
+
* const teleport = useTeleport()
|
|
14
|
+
* teleport([5, 0, 5])
|
|
12
15
|
*
|
|
13
16
|
* const quat = new THREE.Quaternion()
|
|
14
|
-
*
|
|
15
|
-
* teleport(vec3, quat)
|
|
17
|
+
* teleport(new THREE.Vector3(5, 0, 5), quat)
|
|
16
18
|
*/
|
|
17
19
|
export declare const useTeleport: () => (position: Vector3 | Vector3Tuple, orientation?: Quaternion) => void;
|
|
@@ -1,28 +1,76 @@
|
|
|
1
|
-
import { Quaternion } from 'three';
|
|
1
|
+
import { Matrix4, Quaternion, Vector3 } from 'three';
|
|
2
2
|
import { useThrelte } from '@threlte/core';
|
|
3
|
-
|
|
3
|
+
import { useXROrigin } from './useXROrigin.svelte.js';
|
|
4
|
+
const defaultOrientation = new Quaternion();
|
|
4
5
|
const offset = { x: 0, y: 0, z: 0 };
|
|
6
|
+
const targetPosition = new Vector3();
|
|
7
|
+
const localOffset = new Vector3();
|
|
8
|
+
const worldOffset = new Vector3();
|
|
9
|
+
const originWorldPosition = new Vector3();
|
|
10
|
+
const parentWorldQuaternion = new Quaternion();
|
|
11
|
+
const localQuaternion = new Quaternion();
|
|
12
|
+
const inverseParentQuaternion = new Quaternion();
|
|
13
|
+
const localBasis = new Matrix4();
|
|
14
|
+
const worldBasis = new Matrix4();
|
|
15
|
+
const inverseParentMatrix = new Matrix4();
|
|
5
16
|
/**
|
|
6
|
-
* Returns a callback
|
|
17
|
+
* Returns a callback that teleports the player to a target position and optional orientation.
|
|
7
18
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* const vec3 = new THREE.Vector3()
|
|
19
|
+
* When used inside `<XROrigin>`, the origin group is translated directly — the
|
|
20
|
+
* user's feet end up at the target, and their room-scale offset from the origin is preserved.
|
|
11
21
|
*
|
|
12
|
-
*
|
|
22
|
+
* When used outside `<XROrigin>`, the underlying `XRReferenceSpace` is mutated to compensate
|
|
23
|
+
* for the viewer's current position so the feet end up at the target regardless of where the
|
|
24
|
+
* user has walked in their physical space.
|
|
13
25
|
*
|
|
14
|
-
*
|
|
26
|
+
* @example
|
|
27
|
+
* const teleport = useTeleport()
|
|
28
|
+
* teleport([5, 0, 5])
|
|
15
29
|
*
|
|
16
30
|
* const quat = new THREE.Quaternion()
|
|
17
|
-
*
|
|
18
|
-
* teleport(vec3, quat)
|
|
31
|
+
* teleport(new THREE.Vector3(5, 0, 5), quat)
|
|
19
32
|
*/
|
|
20
33
|
export const useTeleport = () => {
|
|
21
34
|
const { xr } = useThrelte().renderer;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
const xrOrigin = useXROrigin();
|
|
36
|
+
return (position, orientation = defaultOrientation) => {
|
|
37
|
+
const currentOrigin = xrOrigin.current;
|
|
38
|
+
if (currentOrigin !== undefined) {
|
|
39
|
+
if (Array.isArray(position)) {
|
|
40
|
+
targetPosition.set(position[0], position[1], position[2]);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
targetPosition.copy(position);
|
|
44
|
+
}
|
|
45
|
+
const parent = currentOrigin.parent;
|
|
46
|
+
const space = xr.getReferenceSpace();
|
|
47
|
+
const pose = space === null ? undefined : xr.getFrame()?.getViewerPose(space);
|
|
48
|
+
localOffset.set(0, 0, 0);
|
|
49
|
+
if (pose !== undefined && pose !== null) {
|
|
50
|
+
localOffset.set(pose.transform.position.x, 0, pose.transform.position.z);
|
|
51
|
+
}
|
|
52
|
+
if (parent === null) {
|
|
53
|
+
localQuaternion.copy(orientation);
|
|
54
|
+
worldBasis.compose(originWorldPosition.set(0, 0, 0), localQuaternion, currentOrigin.scale);
|
|
55
|
+
worldOffset.copy(localOffset).applyMatrix4(worldBasis);
|
|
56
|
+
currentOrigin.position.copy(targetPosition).sub(worldOffset);
|
|
57
|
+
currentOrigin.quaternion.copy(localQuaternion);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
parent.updateWorldMatrix(true, false);
|
|
61
|
+
parent.getWorldQuaternion(parentWorldQuaternion);
|
|
62
|
+
inverseParentQuaternion.copy(parentWorldQuaternion).invert();
|
|
63
|
+
localQuaternion.copy(inverseParentQuaternion).multiply(orientation);
|
|
64
|
+
localBasis.compose(originWorldPosition.set(0, 0, 0), localQuaternion, currentOrigin.scale);
|
|
65
|
+
worldBasis.copy(parent.matrixWorld).multiply(localBasis);
|
|
66
|
+
worldOffset.copy(localOffset).applyMatrix4(worldBasis);
|
|
67
|
+
originWorldPosition.copy(targetPosition).sub(worldOffset);
|
|
68
|
+
inverseParentMatrix.copy(parent.matrixWorld).invert();
|
|
69
|
+
currentOrigin.position.copy(originWorldPosition.applyMatrix4(inverseParentMatrix));
|
|
70
|
+
currentOrigin.quaternion.copy(localQuaternion);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
26
74
|
const space = xr.getReferenceSpace();
|
|
27
75
|
if (space === null)
|
|
28
76
|
return;
|
package/dist/hooks/useXR.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isPresenting, isHandTracking, session, xr } from '../internal/state.svelte.js';
|
|
2
|
-
import {
|
|
2
|
+
import { runeToCurrentReadable } from './currentReadable.svelte.js';
|
|
3
3
|
/**
|
|
4
4
|
* Provides access to context related to `<XR />`.
|
|
5
5
|
*/
|
|
6
6
|
export const useXR = () => {
|
|
7
7
|
return {
|
|
8
|
-
isPresenting:
|
|
9
|
-
isHandTracking:
|
|
10
|
-
session:
|
|
11
|
-
xr:
|
|
8
|
+
isPresenting: runeToCurrentReadable(() => isPresenting.current),
|
|
9
|
+
isHandTracking: runeToCurrentReadable(() => isHandTracking.current),
|
|
10
|
+
session: runeToCurrentReadable(() => session.current),
|
|
11
|
+
xr: runeToCurrentReadable(() => xr.current)
|
|
12
12
|
};
|
|
13
13
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Group } from 'three';
|
|
2
|
+
interface Context {
|
|
3
|
+
current: Group | undefined;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns XR-scoped origin state for the current `<XR>` tree. `current` is the
|
|
7
|
+
* mounted `<XROrigin>` group when present, otherwise `undefined`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useXROrigin: () => Context;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class XROriginState {
|
|
2
|
+
current = $state.raw();
|
|
3
|
+
}
|
|
4
|
+
const origin = new XROriginState();
|
|
5
|
+
/**
|
|
6
|
+
* Returns XR-scoped origin state for the current `<XR>` tree. `current` is the
|
|
7
|
+
* mounted `<XROrigin>` group when present, otherwise `undefined`.
|
|
8
|
+
*/
|
|
9
|
+
export const useXROrigin = () => {
|
|
10
|
+
return origin;
|
|
11
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,13 @@ export { default as Controller } from './components/Controller.svelte';
|
|
|
5
5
|
export { default as Hand } from './components/Hand.svelte';
|
|
6
6
|
export { default as Headset } from './components/Headset.svelte';
|
|
7
7
|
export { default as XR } from './components/XR.svelte';
|
|
8
|
+
export { default as XROrigin } from './components/XROrigin.svelte';
|
|
8
9
|
export { getXRSupportState } from './lib/getXRSupportState.js';
|
|
9
10
|
export { toggleXRSession } from './lib/toggleXRSession.js';
|
|
10
11
|
export { handJoints } from './lib/handJoints.js';
|
|
11
12
|
export { pointerControls } from './plugins/pointerControls/index.js';
|
|
12
13
|
export { teleportControls } from './plugins/teleportControls/index.js';
|
|
14
|
+
export { touchControls } from './plugins/touchControls/index.js';
|
|
13
15
|
export { useController } from './hooks/useController.svelte.js';
|
|
14
16
|
export { useHand } from './hooks/useHand.svelte.js';
|
|
15
17
|
export { useHandJoint } from './hooks/useHandJoint.svelte.js';
|
|
@@ -17,4 +19,6 @@ export { useHeadset } from './hooks/useHeadset.js';
|
|
|
17
19
|
export { useHitTest } from './hooks/useHitTest.svelte.js';
|
|
18
20
|
export { useTeleport } from './hooks/useTeleport.js';
|
|
19
21
|
export { useXR } from './hooks/useXR.js';
|
|
22
|
+
export { useXROrigin } from './hooks/useXROrigin.svelte.js';
|
|
20
23
|
export type { XRSessionEventType, XRControllerEventType, XRHandEventType, XRControllerEvent, XRController, XRHandObject, XRHandEvent } from './types.js';
|
|
24
|
+
export type { HandJoints } from './lib/handJoints.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { default as Controller } from './components/Controller.svelte';
|
|
|
6
6
|
export { default as Hand } from './components/Hand.svelte';
|
|
7
7
|
export { default as Headset } from './components/Headset.svelte';
|
|
8
8
|
export { default as XR } from './components/XR.svelte';
|
|
9
|
+
export { default as XROrigin } from './components/XROrigin.svelte';
|
|
9
10
|
// Utilities
|
|
10
11
|
export { getXRSupportState } from './lib/getXRSupportState.js';
|
|
11
12
|
export { toggleXRSession } from './lib/toggleXRSession.js';
|
|
@@ -13,6 +14,7 @@ export { handJoints } from './lib/handJoints.js';
|
|
|
13
14
|
// Plugins
|
|
14
15
|
export { pointerControls } from './plugins/pointerControls/index.js';
|
|
15
16
|
export { teleportControls } from './plugins/teleportControls/index.js';
|
|
17
|
+
export { touchControls } from './plugins/touchControls/index.js';
|
|
16
18
|
// Hooks
|
|
17
19
|
export { useController } from './hooks/useController.svelte.js';
|
|
18
20
|
export { useHand } from './hooks/useHand.svelte.js';
|
|
@@ -21,3 +23,4 @@ export { useHeadset } from './hooks/useHeadset.js';
|
|
|
21
23
|
export { useHitTest } from './hooks/useHitTest.svelte.js';
|
|
22
24
|
export { useTeleport } from './hooks/useTeleport.js';
|
|
23
25
|
export { useXR } from './hooks/useXR.js';
|
|
26
|
+
export { useXROrigin } from './hooks/useXROrigin.svelte.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { XRGripSpace, XRHandSpace, XRTargetRaySpace } from 'three';
|
|
2
|
+
import type { XRControllerModel } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
|
|
3
|
+
import type { XRHandModel } from 'three/examples/jsm/webxr/XRHandModelFactory.js';
|
|
4
|
+
import type { XRControllerEvents, XRHandEvents } from '../types.js';
|
|
5
|
+
export type XRHandInputSource = XRInputSource & {
|
|
6
|
+
hand: XRHand;
|
|
7
|
+
};
|
|
8
|
+
export type XRInputSourceStateBase = {
|
|
9
|
+
id: string;
|
|
10
|
+
inputSource: XRInputSource;
|
|
11
|
+
handedness: XRHandedness;
|
|
12
|
+
isPrimary: boolean;
|
|
13
|
+
targetRay: XRTargetRaySpace;
|
|
14
|
+
};
|
|
15
|
+
export type XRControllerSourceState = XRInputSourceStateBase & {
|
|
16
|
+
type: 'controller';
|
|
17
|
+
grip: XRGripSpace;
|
|
18
|
+
model: XRControllerModel;
|
|
19
|
+
};
|
|
20
|
+
export type XRHandSourceState = Omit<XRInputSourceStateBase, 'inputSource'> & {
|
|
21
|
+
type: 'hand';
|
|
22
|
+
inputSource: XRHandInputSource;
|
|
23
|
+
hand: XRHandSpace;
|
|
24
|
+
model: XRHandModel;
|
|
25
|
+
};
|
|
26
|
+
export type XRGazeSourceState = XRInputSourceStateBase & {
|
|
27
|
+
type: 'gaze';
|
|
28
|
+
};
|
|
29
|
+
export type XRTransientPointerSourceState = XRInputSourceStateBase & {
|
|
30
|
+
type: 'transientPointer';
|
|
31
|
+
};
|
|
32
|
+
export type XRScreenInputSourceState = XRInputSourceStateBase & {
|
|
33
|
+
type: 'screenInput';
|
|
34
|
+
};
|
|
35
|
+
export type XRInputSourceState = XRControllerSourceState | XRHandSourceState | XRGazeSourceState | XRTransientPointerSourceState | XRScreenInputSourceState;
|
|
36
|
+
declare class InputSourcesState {
|
|
37
|
+
current: readonly XRInputSourceState[];
|
|
38
|
+
}
|
|
39
|
+
export declare const inputSources: InputSourcesState;
|
|
40
|
+
export type ControllerSubscriber = {
|
|
41
|
+
type: 'controller';
|
|
42
|
+
handedness: XRHandedness;
|
|
43
|
+
callbacks: XRControllerEvents;
|
|
44
|
+
};
|
|
45
|
+
export type HandSubscriber = {
|
|
46
|
+
type: 'hand';
|
|
47
|
+
handedness: 'left' | 'right';
|
|
48
|
+
callbacks: XRHandEvents;
|
|
49
|
+
};
|
|
50
|
+
export type Subscriber = ControllerSubscriber | HandSubscriber;
|
|
51
|
+
/**
|
|
52
|
+
* Registers callbacks with the module-level XR input-source dispatcher.
|
|
53
|
+
*
|
|
54
|
+
* This does not subscribe to a specific `XRInputSource`, `XRSession`, or
|
|
55
|
+
* Three.js object. Instead, the subscriber is stored in the internal
|
|
56
|
+
* `subscribers` set and receives events for whichever current input-source
|
|
57
|
+
* state matches its `type` and `handedness`.
|
|
58
|
+
*
|
|
59
|
+
* For example, a `{ type: 'controller', handedness: 'left' }` subscriber will
|
|
60
|
+
* receive forwarded events for the current left controller, even if the
|
|
61
|
+
* underlying `XRInputSource` instance disconnects and reconnects.
|
|
62
|
+
*
|
|
63
|
+
* Returns a cleanup function that removes the subscriber from the dispatcher.
|
|
64
|
+
*/
|
|
65
|
+
export declare const addSubscriber: (sub: Subscriber) => () => void;
|
|
66
|
+
export declare const dispatchEvent: (state: XRInputSourceState, eventType: string, event: unknown) => void;
|
|
67
|
+
export declare const createInputSourceEvent: (state: XRInputSourceState, type: string, extra?: Record<string, unknown>) => {
|
|
68
|
+
type: string;
|
|
69
|
+
data: XRInputSource | XRHandInputSource;
|
|
70
|
+
inputSource: XRInputSource | XRHandInputSource;
|
|
71
|
+
target: XRTargetRaySpace | XRHandSpace;
|
|
72
|
+
};
|
|
73
|
+
export declare const dispatchInputSourceStateEvent: (state: XRInputSourceState, eventType: string, event: unknown, options?: {
|
|
74
|
+
dispatchSpaces?: boolean;
|
|
75
|
+
}) => void;
|
|
76
|
+
type ResolveOptions = {
|
|
77
|
+
isPrimary?: boolean;
|
|
78
|
+
};
|
|
79
|
+
export declare const getInputSourceState: (inputSource: XRInputSource, options?: ResolveOptions) => XRInputSourceState | undefined;
|
|
80
|
+
export declare const getControllerState: (handedness: XRHandedness, options?: ResolveOptions) => XRControllerSourceState | undefined;
|
|
81
|
+
export declare const getHandState: (handedness: "left" | "right", options?: ResolveOptions) => XRHandSourceState | undefined;
|
|
82
|
+
export declare const dispatchInputSourceEvent: (inputSource: XRInputSource, eventType: string, event: unknown) => void;
|
|
83
|
+
export declare const dispatchXRInputSourceEvent: (event: XRInputSourceEvent) => void;
|
|
84
|
+
export {};
|