@threlte/xr 0.0.12 → 0.0.13
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/ARButton.svelte +1 -1
- package/dist/components/Controller.svelte +24 -83
- package/dist/components/Hand.svelte +27 -64
- package/dist/components/Hand.svelte.d.ts +1 -0
- package/dist/components/XR.svelte +46 -52
- package/dist/components/XRButton.svelte +2 -2
- package/dist/components/internal/PointerCursor.svelte +7 -3
- package/dist/components/internal/TeleportCursor.svelte +7 -3
- package/dist/hooks/useController.js +2 -4
- package/dist/hooks/useHand.js +2 -0
- package/dist/hooks/useHandJoint.d.ts +2 -2
- package/dist/hooks/useHeadset.d.ts +2 -1
- package/dist/hooks/useHeadset.js +1 -1
- package/dist/hooks/useHitTest.d.ts +2 -2
- package/dist/hooks/useHitTest.js +16 -6
- package/dist/hooks/useTeleport.d.ts +2 -2
- package/dist/hooks/useTeleport.js +2 -2
- package/dist/internal/setupControllers.d.ts +1 -0
- package/dist/internal/setupControllers.js +69 -0
- package/dist/internal/setupHands.d.ts +1 -0
- package/dist/internal/setupHands.js +63 -0
- package/dist/internal/setupHeadset.d.ts +3 -0
- package/dist/internal/{headset.js → setupHeadset.js} +2 -3
- package/dist/internal/setupRaf.d.ts +1 -0
- package/dist/internal/{updateRaf.js → setupRaf.js} +2 -2
- package/dist/internal/stores.d.ts +22 -7
- package/dist/internal/stores.js +15 -9
- package/dist/internal/useHandTrackingState.js +1 -1
- package/dist/lib/toggleXRSession.js +3 -3
- package/dist/plugins/pointerControls/types.d.ts +13 -12
- package/dist/plugins/teleportControls/context.d.ts +7 -6
- package/dist/plugins/teleportControls/hook.d.ts +5 -4
- package/dist/plugins/teleportControls/index.d.ts +1 -1
- package/dist/types.d.ts +10 -8
- package/package.json +4 -4
- package/dist/internal/headset.d.ts +0 -3
- package/dist/internal/updateRaf.d.ts +0 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
sessionInit={{
|
|
18
18
|
domOverlay: typeof document !== 'undefined' ? { root: document.body } : undefined,
|
|
19
19
|
requiredFeatures: ['plane-detection'],
|
|
20
|
-
optionalFeatures: ['
|
|
20
|
+
optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking', 'layers', 'hit-test']
|
|
21
21
|
}}
|
|
22
22
|
{...$$restProps}
|
|
23
23
|
mode='immersive-ar'
|
|
@@ -4,76 +4,33 @@
|
|
|
4
4
|
<script
|
|
5
5
|
|
|
6
6
|
context="module"
|
|
7
|
-
>import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { isHandTracking, pointerState, teleportState } from "../internal/stores";
|
|
12
|
-
import { useHandTrackingState } from "../internal/useHandTrackingState";
|
|
7
|
+
>import { writable } from "svelte/store";
|
|
8
|
+
import { T, createRawEventDispatcher } from "@threlte/core";
|
|
9
|
+
import { left as leftStore, right as rightStore } from "../hooks/useController";
|
|
10
|
+
import { isHandTracking, pointerState, teleportState, controllerDispatchers } from "../internal/stores";
|
|
13
11
|
import PointerCursor from "./internal/PointerCursor.svelte";
|
|
14
12
|
import ShortRay from "./internal/ShortRay.svelte";
|
|
15
13
|
import ScenePortal from "./internal/ScenePortal.svelte";
|
|
16
14
|
import TeleportCursor from "./internal/TeleportCursor.svelte";
|
|
17
15
|
import TeleportRay from "./internal/TeleportRay.svelte";
|
|
18
|
-
const factory = new XRControllerModelFactory();
|
|
19
16
|
const stores = {
|
|
20
17
|
left: leftStore,
|
|
21
|
-
right: rightStore
|
|
22
|
-
none: gaze
|
|
18
|
+
right: rightStore
|
|
23
19
|
};
|
|
24
|
-
const events = [
|
|
25
|
-
"select",
|
|
26
|
-
"selectstart",
|
|
27
|
-
"selectend",
|
|
28
|
-
"squeeze",
|
|
29
|
-
"squeezeend",
|
|
30
|
-
"squeezestart"
|
|
31
|
-
];
|
|
32
|
-
const eventMap = /* @__PURE__ */ new WeakMap();
|
|
33
20
|
</script>
|
|
34
21
|
|
|
35
22
|
<script>export let left = void 0;
|
|
36
23
|
export let right = void 0;
|
|
37
24
|
export let hand = void 0;
|
|
38
|
-
$:
|
|
39
|
-
handedness = left ? "left" : right ? "right" : hand;
|
|
40
25
|
const dispatch = createRawEventDispatcher();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
const handleConnected = (event) => {
|
|
49
|
-
const targetData = eventMap.get(event.target);
|
|
50
|
-
if (event.data.handedness !== handedness || !targetData)
|
|
51
|
-
return;
|
|
52
|
-
stores[handedness].set({ ...targetData, inputSource: event.data });
|
|
53
|
-
if (!handTrackingNow()) {
|
|
54
|
-
dispatch("connected", event);
|
|
55
|
-
}
|
|
56
|
-
events.forEach((name) => event.target.addEventListener(name, handleEvent));
|
|
57
|
-
};
|
|
58
|
-
const handleDisconnected = (event) => {
|
|
59
|
-
if (event.data.handedness !== handedness)
|
|
60
|
-
return;
|
|
61
|
-
stores[handedness].set(void 0);
|
|
62
|
-
if (!$isHandTracking) {
|
|
63
|
-
dispatch("disconnected", event);
|
|
64
|
-
}
|
|
65
|
-
events.forEach((name) => event.target.removeEventListener(name, handleEvent));
|
|
66
|
-
};
|
|
67
|
-
for (const index of [0, 1]) {
|
|
68
|
-
const targetRay2 = xr.getController(index);
|
|
69
|
-
const grip2 = xr.getControllerGrip(index);
|
|
70
|
-
const model2 = factory.createControllerModel(grip2);
|
|
71
|
-
eventMap.set(targetRay2, { targetRay: targetRay2, model: model2, grip: grip2 });
|
|
72
|
-
targetRay2.addEventListener("connected", handleConnected);
|
|
73
|
-
targetRay2.addEventListener("disconnected", handleDisconnected);
|
|
74
|
-
}
|
|
26
|
+
const handedness = writable(left ? "left" : right ? "right" : hand);
|
|
27
|
+
$:
|
|
28
|
+
handedness.set(left ? "left" : right ? "right" : hand);
|
|
29
|
+
controllerDispatchers[$handedness].set(dispatch);
|
|
30
|
+
$:
|
|
31
|
+
controllerDispatchers[$handedness].set(dispatch);
|
|
75
32
|
$:
|
|
76
|
-
store = stores[handedness];
|
|
33
|
+
store = stores[$handedness];
|
|
77
34
|
$:
|
|
78
35
|
grip = $store?.grip;
|
|
79
36
|
$:
|
|
@@ -81,27 +38,14 @@ $:
|
|
|
81
38
|
$:
|
|
82
39
|
model = $store?.model;
|
|
83
40
|
$:
|
|
84
|
-
hasPointerControls = $pointerState[handedness].enabled;
|
|
41
|
+
hasPointerControls = $pointerState[$handedness].enabled;
|
|
85
42
|
$:
|
|
86
|
-
hasTeleportControls = $teleportState[handedness].enabled;
|
|
87
|
-
onDestroy(() => {
|
|
88
|
-
for (const index of [0, 1]) {
|
|
89
|
-
const controller2 = xr.getController(index);
|
|
90
|
-
controller2.removeEventListener("connected", handleConnected);
|
|
91
|
-
controller2.removeEventListener("disconnected", handleDisconnected);
|
|
92
|
-
}
|
|
93
|
-
const controller = $store?.targetRay;
|
|
94
|
-
events.forEach((name) => controller?.removeEventListener(name, handleEvent));
|
|
95
|
-
store.set(void 0);
|
|
96
|
-
});
|
|
43
|
+
hasTeleportControls = $teleportState[$handedness].enabled;
|
|
97
44
|
</script>
|
|
98
45
|
|
|
99
46
|
{#if !$isHandTracking}
|
|
100
47
|
{#if grip}
|
|
101
|
-
<T
|
|
102
|
-
is={grip}
|
|
103
|
-
name="XR controller grip {handedness}"
|
|
104
|
-
>
|
|
48
|
+
<T is={grip}>
|
|
105
49
|
<slot>
|
|
106
50
|
<T is={model} />
|
|
107
51
|
</slot>
|
|
@@ -111,19 +55,16 @@ onDestroy(() => {
|
|
|
111
55
|
{/if}
|
|
112
56
|
|
|
113
57
|
{#if targetRay}
|
|
114
|
-
<T
|
|
115
|
-
is={targetRay}
|
|
116
|
-
name="XR controller {handedness}"
|
|
117
|
-
>
|
|
58
|
+
<T is={targetRay}>
|
|
118
59
|
<slot name="target-ray" />
|
|
119
60
|
|
|
120
61
|
{#if hasPointerControls || hasTeleportControls}
|
|
121
62
|
{#if $$slots['pointer-ray']}
|
|
122
|
-
<ShortRay {handedness}>
|
|
63
|
+
<ShortRay handedness={$handedness}>
|
|
123
64
|
<slot name="pointer-ray" />
|
|
124
65
|
</ShortRay>
|
|
125
66
|
{:else}
|
|
126
|
-
<ShortRay {handedness} />
|
|
67
|
+
<ShortRay handedness={$handedness} />
|
|
127
68
|
{/if}
|
|
128
69
|
{/if}
|
|
129
70
|
</T>
|
|
@@ -133,11 +74,11 @@ onDestroy(() => {
|
|
|
133
74
|
<ScenePortal>
|
|
134
75
|
{#if hasPointerControls}
|
|
135
76
|
{#if $$slots['pointer-cursor']}
|
|
136
|
-
<PointerCursor {handedness}>
|
|
77
|
+
<PointerCursor handedness={$handedness}>
|
|
137
78
|
<slot name="pointer-cursor" />
|
|
138
79
|
</PointerCursor>
|
|
139
80
|
{:else}
|
|
140
|
-
<PointerCursor {handedness} />
|
|
81
|
+
<PointerCursor handedness={$handedness} />
|
|
141
82
|
{/if}
|
|
142
83
|
{/if}
|
|
143
84
|
|
|
@@ -145,23 +86,23 @@ onDestroy(() => {
|
|
|
145
86
|
{#if $$slots['teleport-ray']}
|
|
146
87
|
<TeleportRay
|
|
147
88
|
{targetRay}
|
|
148
|
-
{handedness}
|
|
89
|
+
handedness={$handedness}
|
|
149
90
|
>
|
|
150
91
|
<slot name="teleport-ray" />
|
|
151
92
|
</TeleportRay>
|
|
152
93
|
{:else}
|
|
153
94
|
<TeleportRay
|
|
154
95
|
{targetRay}
|
|
155
|
-
{handedness}
|
|
96
|
+
handedness={$handedness}
|
|
156
97
|
/>
|
|
157
98
|
{/if}
|
|
158
99
|
|
|
159
100
|
{#if $$slots['teleport-ray']}
|
|
160
|
-
<TeleportCursor {handedness}>
|
|
101
|
+
<TeleportCursor handedness={$handedness}>
|
|
161
102
|
<slot name="teleport-cursor" />
|
|
162
103
|
</TeleportCursor>
|
|
163
104
|
{:else}
|
|
164
|
-
<TeleportCursor {handedness} />
|
|
105
|
+
<TeleportCursor handedness={$handedness} />
|
|
165
106
|
{/if}
|
|
166
107
|
{/if}
|
|
167
108
|
</ScenePortal>
|
|
@@ -1,50 +1,26 @@
|
|
|
1
|
-
<script context='module'>import {
|
|
2
|
-
import { XRHandModelFactory } from "three/examples/jsm/webxr/XRHandModelFactory";
|
|
1
|
+
<script context='module'>import { Group } from "three";
|
|
3
2
|
import { T, useThrelte, createRawEventDispatcher, useFrame } from "@threlte/core";
|
|
4
|
-
import { isHandTracking } from "../internal/stores";
|
|
5
|
-
import { useHandTrackingState } from "../internal/useHandTrackingState";
|
|
3
|
+
import { isHandTracking, handDispatchers } from "../internal/stores";
|
|
6
4
|
import { left as leftStore, right as rightStore } from "../hooks/useHand";
|
|
7
|
-
|
|
5
|
+
import ScenePortal from "./internal/ScenePortal.svelte";
|
|
6
|
+
import { writable } from "svelte/store";
|
|
8
7
|
const stores = {
|
|
9
8
|
left: leftStore,
|
|
10
9
|
right: rightStore
|
|
11
10
|
};
|
|
12
|
-
const eventMap = /* @__PURE__ */ new WeakMap();
|
|
13
11
|
</script>
|
|
14
12
|
|
|
15
|
-
<script>
|
|
16
|
-
export let left = void 0;
|
|
13
|
+
<script>export let left = void 0;
|
|
17
14
|
export let right = void 0;
|
|
18
15
|
export let hand = void 0;
|
|
19
|
-
const handTrackingNow = useHandTrackingState();
|
|
20
16
|
const dispatch = createRawEventDispatcher();
|
|
21
17
|
const { xr } = useThrelte().renderer;
|
|
22
18
|
const space = xr.getReferenceSpace();
|
|
19
|
+
const handedness = writable(left ? "left" : right ? "right" : hand);
|
|
23
20
|
$:
|
|
24
|
-
handedness
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return;
|
|
28
|
-
stores[handedness].set({ ...eventMap.get(event.target), inputSource: event.data.hand });
|
|
29
|
-
if (handTrackingNow()) {
|
|
30
|
-
dispatch("connected", event);
|
|
31
|
-
}
|
|
32
|
-
event.target.addEventListener("pinchstart", handlePinchEvent);
|
|
33
|
-
event.target.addEventListener("pinchend", handlePinchEvent);
|
|
34
|
-
};
|
|
35
|
-
const handleDisconnected = (event) => {
|
|
36
|
-
if (event.data.handedness !== handedness)
|
|
37
|
-
return;
|
|
38
|
-
stores[handedness].set(void 0);
|
|
39
|
-
if ($isHandTracking) {
|
|
40
|
-
dispatch("disconnected", event);
|
|
41
|
-
}
|
|
42
|
-
event.target.removeEventListener("pinchstart", handlePinchEvent);
|
|
43
|
-
event.target.removeEventListener("pinchend", handlePinchEvent);
|
|
44
|
-
};
|
|
45
|
-
const handlePinchEvent = (event) => {
|
|
46
|
-
dispatch(event.type, event);
|
|
47
|
-
};
|
|
21
|
+
handedness.set(left ? "left" : right ? "right" : hand);
|
|
22
|
+
$:
|
|
23
|
+
handDispatchers[$handedness].set(dispatch);
|
|
48
24
|
let children;
|
|
49
25
|
const { start, stop } = useFrame(() => {
|
|
50
26
|
const frame = xr.getFrame();
|
|
@@ -59,51 +35,38 @@ const { start, stop } = useFrame(() => {
|
|
|
59
35
|
children.quaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
|
|
60
36
|
}, { autostart: false });
|
|
61
37
|
$:
|
|
62
|
-
if (($$slots.wrist || $$slots.default) && inputSource) {
|
|
38
|
+
if ($isHandTracking && ($$slots.wrist || $$slots.default) && inputSource) {
|
|
63
39
|
start();
|
|
64
40
|
} else {
|
|
65
41
|
stop();
|
|
66
42
|
}
|
|
67
43
|
$:
|
|
68
|
-
store = stores[handedness];
|
|
44
|
+
store = stores[$handedness];
|
|
69
45
|
$:
|
|
70
46
|
inputSource = $store?.inputSource;
|
|
71
47
|
$:
|
|
72
48
|
model = $store?.model;
|
|
73
|
-
for (const index of [0, 1]) {
|
|
74
|
-
const hand2 = xr.getHand(index);
|
|
75
|
-
const model2 = factory.createHandModel(hand2, "mesh");
|
|
76
|
-
eventMap.set(hand2, { hand: hand2, model: model2 });
|
|
77
|
-
hand2.addEventListener("connected", handleConnected);
|
|
78
|
-
hand2.addEventListener("disconnected", handleDisconnected);
|
|
79
|
-
}
|
|
80
|
-
onDestroy(() => {
|
|
81
|
-
for (const index of [0, 1]) {
|
|
82
|
-
const hand3 = xr.getHand(index);
|
|
83
|
-
hand3.removeEventListener("connected", handleConnected);
|
|
84
|
-
hand3.removeEventListener("disconnected", handleDisconnected);
|
|
85
|
-
}
|
|
86
|
-
const hand2 = stores[handedness].current?.hand;
|
|
87
|
-
hand2?.removeEventListener("pinchstart", handlePinchEvent);
|
|
88
|
-
hand2?.removeEventListener("pinchend", handlePinchEvent);
|
|
89
|
-
stores[handedness].set(void 0);
|
|
90
|
-
});
|
|
91
49
|
</script>
|
|
92
50
|
|
|
93
|
-
{#if $store?.hand}
|
|
94
|
-
<T
|
|
95
|
-
is={$store.hand}
|
|
96
|
-
name='XR hand {handedness}'
|
|
97
|
-
>
|
|
51
|
+
{#if $store?.hand && $isHandTracking}
|
|
52
|
+
<T is={$store.hand}>
|
|
98
53
|
{#if $$slots.default === undefined}
|
|
99
54
|
<T is={model} />
|
|
100
55
|
{/if}
|
|
101
56
|
</T>
|
|
57
|
+
|
|
58
|
+
{#if $$slots['target-ray'] !== undefined}
|
|
59
|
+
<T is={$store.targetRay}>
|
|
60
|
+
<slot name='target-ray' />
|
|
61
|
+
</T>
|
|
62
|
+
{/if}
|
|
102
63
|
{/if}
|
|
103
64
|
|
|
104
|
-
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</
|
|
65
|
+
{#if $isHandTracking}
|
|
66
|
+
<ScenePortal>
|
|
67
|
+
<T.Group bind:ref={children}>
|
|
68
|
+
<slot name='wrist' />
|
|
69
|
+
<slot />
|
|
70
|
+
</T.Group>
|
|
71
|
+
</ScenePortal>
|
|
72
|
+
{/if}
|
|
@@ -17,33 +17,38 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
-->
|
|
20
|
-
<script>import {
|
|
21
|
-
import { createRawEventDispatcher, useThrelte } from "@threlte/core";
|
|
20
|
+
<script>import { onMount } from "svelte";
|
|
21
|
+
import { createRawEventDispatcher, useThrelte, watch } from "@threlte/core";
|
|
22
22
|
import {
|
|
23
|
-
initialized,
|
|
24
23
|
isHandTracking,
|
|
25
24
|
isPresenting,
|
|
26
25
|
referenceSpaceType,
|
|
27
26
|
session,
|
|
28
27
|
xr as xrStore
|
|
29
28
|
} from "../internal/stores";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
29
|
+
import { setupRaf } from "../internal/setupRaf";
|
|
30
|
+
import { setupHeadset } from "../internal/setupHeadset";
|
|
31
|
+
import { setupControllers } from "../internal/setupControllers";
|
|
32
|
+
import { setupHands } from "../internal/setupHands";
|
|
32
33
|
export let foveation = 1;
|
|
33
34
|
export let frameRate = void 0;
|
|
34
35
|
export let referenceSpace = "local-floor";
|
|
35
36
|
const dispatch = createRawEventDispatcher();
|
|
36
|
-
updateRaf();
|
|
37
37
|
const { renderer, frameloop } = useThrelte();
|
|
38
38
|
const { xr } = renderer;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
let originalFrameloop = $frameloop;
|
|
40
|
+
setupRaf();
|
|
41
|
+
setupHeadset();
|
|
42
|
+
setupControllers();
|
|
43
|
+
setupHands();
|
|
44
|
+
const handleSessionStart = () => {
|
|
45
|
+
isPresenting.set(true);
|
|
46
|
+
dispatch("sessionstart", { type: "sessionstart", target: $session });
|
|
42
47
|
};
|
|
43
|
-
const handleSessionEnd = (
|
|
44
|
-
dispatch("sessionend", {
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
const handleSessionEnd = () => {
|
|
49
|
+
dispatch("sessionend", { type: "sessionend", target: $session });
|
|
50
|
+
isPresenting.set(false);
|
|
51
|
+
session.set(void 0);
|
|
47
52
|
};
|
|
48
53
|
const handleVisibilityChange = (event) => {
|
|
49
54
|
dispatch("visibilitychange", { ...event, target: $session });
|
|
@@ -63,14 +68,7 @@ const updateTargetFrameRate = (frameRate2) => {
|
|
|
63
68
|
} catch {
|
|
64
69
|
}
|
|
65
70
|
};
|
|
66
|
-
|
|
67
|
-
if (currentSession === void 0)
|
|
68
|
-
return;
|
|
69
|
-
currentSession.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
70
|
-
currentSession.removeEventListener("inputsourceschange", handleInputSourcesChange);
|
|
71
|
-
currentSession.removeEventListener("frameratechange", handleFramerateChange);
|
|
72
|
-
};
|
|
73
|
-
const updateSession = async (currentSession) => {
|
|
71
|
+
watch(session, (currentSession) => {
|
|
74
72
|
if (currentSession === void 0)
|
|
75
73
|
return;
|
|
76
74
|
currentSession.addEventListener("visibilitychange", handleVisibilityChange);
|
|
@@ -78,44 +76,40 @@ const updateSession = async (currentSession) => {
|
|
|
78
76
|
currentSession.addEventListener("frameratechange", handleFramerateChange);
|
|
79
77
|
xr.setFoveation(foveation);
|
|
80
78
|
updateTargetFrameRate(frameRate);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
xr.addEventListener("sessionstart", handleSessionStart);
|
|
87
|
-
xr.addEventListener("sessionend", handleSessionEnd);
|
|
88
|
-
useUpdateHeadset();
|
|
89
|
-
onDestroy(() => {
|
|
90
|
-
$initialized = false;
|
|
91
|
-
$xrStore = void 0;
|
|
92
|
-
xr.enabled = false;
|
|
93
|
-
xr.removeEventListener("sessionstart", handleSessionStart);
|
|
94
|
-
xr.removeEventListener("sessionend", handleSessionEnd);
|
|
79
|
+
return () => {
|
|
80
|
+
currentSession.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
81
|
+
currentSession.removeEventListener("inputsourceschange", handleInputSourcesChange);
|
|
82
|
+
currentSession.removeEventListener("frameratechange", handleFramerateChange);
|
|
83
|
+
};
|
|
95
84
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
$:
|
|
101
|
-
if (lastSession !== $session) {
|
|
102
|
-
cleanupSession(lastSession);
|
|
103
|
-
updateSession($session);
|
|
104
|
-
lastSession = $session;
|
|
105
|
-
}
|
|
106
|
-
let originalFrameloop = $frameloop;
|
|
107
|
-
$frameloop = "always";
|
|
108
|
-
$:
|
|
109
|
-
if ($isPresenting) {
|
|
110
|
-
originalFrameloop = $frameloop;
|
|
111
|
-
$frameloop = "always";
|
|
85
|
+
watch(isPresenting, (presenting) => {
|
|
86
|
+
if (presenting) {
|
|
87
|
+
originalFrameloop = frameloop.current;
|
|
88
|
+
frameloop.set("always");
|
|
112
89
|
} else {
|
|
113
|
-
|
|
90
|
+
frameloop.set(originalFrameloop);
|
|
114
91
|
}
|
|
92
|
+
});
|
|
93
|
+
onMount(() => {
|
|
94
|
+
$xrStore = xr;
|
|
95
|
+
xr.enabled = true;
|
|
96
|
+
xr.addEventListener("sessionstart", handleSessionStart);
|
|
97
|
+
xr.addEventListener("sessionend", handleSessionEnd);
|
|
98
|
+
return () => {
|
|
99
|
+
$xrStore = void 0;
|
|
100
|
+
xr.enabled = false;
|
|
101
|
+
xr.removeEventListener("sessionstart", handleSessionStart);
|
|
102
|
+
xr.removeEventListener("sessionend", handleSessionEnd);
|
|
103
|
+
};
|
|
104
|
+
});
|
|
115
105
|
$:
|
|
116
106
|
updateTargetFrameRate(frameRate);
|
|
117
107
|
$:
|
|
118
108
|
xr.setFoveation(foveation);
|
|
109
|
+
$: {
|
|
110
|
+
xr.setReferenceSpaceType(referenceSpace);
|
|
111
|
+
$referenceSpaceType = referenceSpace;
|
|
112
|
+
}
|
|
119
113
|
</script>
|
|
120
114
|
|
|
121
115
|
{#if $isPresenting}
|
|
@@ -21,14 +21,14 @@ display info about your WebXR session. This is aliased by `ARButton` and
|
|
|
21
21
|
<script>import { createEventDispatcher } from "svelte";
|
|
22
22
|
import { getXRSupportState } from "../lib/getXRSupportState";
|
|
23
23
|
import { toggleXRSession } from "../lib/toggleXRSession";
|
|
24
|
-
import { session,
|
|
24
|
+
import { session, xr } from "../internal/stores";
|
|
25
25
|
export let mode;
|
|
26
26
|
export let sessionInit = void 0;
|
|
27
27
|
export let force = void 0;
|
|
28
28
|
export let styled = true;
|
|
29
29
|
const dispatch = createEventDispatcher();
|
|
30
30
|
const handleButtonClick = async (nativeEvent, state) => {
|
|
31
|
-
if (!$
|
|
31
|
+
if (!$xr) {
|
|
32
32
|
throw new Error("The <XR> component was not created. This is required to start an XR session.");
|
|
33
33
|
}
|
|
34
34
|
dispatch("click", { state, nativeEvent });
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
<script>import { Group, Vector3 } from "three";
|
|
1
|
+
<script>import { Group, Vector3, Matrix3 } from "three";
|
|
2
2
|
import { T, useFrame } from "@threlte/core";
|
|
3
3
|
import { pointerIntersection, pointerState } from "../../internal/stores";
|
|
4
4
|
import Cursor from "./Cursor.svelte";
|
|
5
5
|
export let handedness;
|
|
6
6
|
const ref = new Group();
|
|
7
7
|
const vec3 = new Vector3();
|
|
8
|
+
const normalMatrix = new Matrix3();
|
|
9
|
+
const worldNormal = new Vector3();
|
|
8
10
|
$:
|
|
9
11
|
hovering = $pointerState[handedness].hovering;
|
|
10
12
|
$:
|
|
@@ -13,10 +15,12 @@ const { start, stop } = useFrame(
|
|
|
13
15
|
() => {
|
|
14
16
|
if (intersection.current === void 0)
|
|
15
17
|
return;
|
|
16
|
-
const { point, face } = intersection.current;
|
|
18
|
+
const { point, face, object } = intersection.current;
|
|
17
19
|
ref.position.lerp(point, 0.4);
|
|
18
20
|
if (face) {
|
|
19
|
-
|
|
21
|
+
normalMatrix.getNormalMatrix(object.matrixWorld);
|
|
22
|
+
worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize();
|
|
23
|
+
ref.lookAt(vec3.addVectors(point, worldNormal));
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
{
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
<script>import { spring } from "svelte/motion";
|
|
2
|
-
import { Group, Vector3 } from "three";
|
|
2
|
+
import { Group, Matrix3, Vector3 } from "three";
|
|
3
3
|
import { T, useFrame } from "@threlte/core";
|
|
4
4
|
import { teleportIntersection } from "../../internal/stores";
|
|
5
5
|
import Cursor from "./Cursor.svelte";
|
|
6
6
|
export let handedness;
|
|
7
7
|
const ref = new Group();
|
|
8
8
|
const vec3 = new Vector3();
|
|
9
|
+
const normalMatrix = new Matrix3();
|
|
10
|
+
const worldNormal = new Vector3();
|
|
9
11
|
$:
|
|
10
12
|
intersection = teleportIntersection[handedness];
|
|
11
13
|
const { start, stop } = useFrame(
|
|
12
14
|
() => {
|
|
13
15
|
if (intersection.current === void 0)
|
|
14
16
|
return;
|
|
15
|
-
const { point, face } = intersection.current;
|
|
17
|
+
const { point, face, object } = intersection.current;
|
|
16
18
|
ref.position.lerp(point, 0.4);
|
|
17
19
|
if (face) {
|
|
18
|
-
|
|
20
|
+
normalMatrix.getNormalMatrix(object.matrixWorld);
|
|
21
|
+
worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize();
|
|
22
|
+
ref.lookAt(vec3.addVectors(point, worldNormal));
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
{
|
|
@@ -2,10 +2,6 @@ import { currentWritable } from '@threlte/core';
|
|
|
2
2
|
export const left = currentWritable(undefined);
|
|
3
3
|
export const right = currentWritable(undefined);
|
|
4
4
|
export const gaze = currentWritable(undefined);
|
|
5
|
-
const gamepadLeft = currentWritable(undefined);
|
|
6
|
-
const gamepadRight = currentWritable(undefined);
|
|
7
|
-
left.subscribe((value) => gamepadLeft.set(value?.inputSource.gamepad));
|
|
8
|
-
right.subscribe((value) => gamepadRight.set(value?.inputSource.gamepad));
|
|
9
5
|
/**
|
|
10
6
|
* Provides a reference to a current XRController, filtered by handedness.
|
|
11
7
|
*/
|
|
@@ -17,5 +13,7 @@ export const useController = (handedness) => {
|
|
|
17
13
|
return right;
|
|
18
14
|
case 'none':
|
|
19
15
|
return gaze;
|
|
16
|
+
default:
|
|
17
|
+
throw new Error('useController handedness must be left, right, or none.');
|
|
20
18
|
}
|
|
21
19
|
};
|
package/dist/hooks/useHand.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { XRJointSpace } from 'three';
|
|
2
2
|
import type { HandJoints } from '../lib/handJoints';
|
|
3
3
|
/**
|
|
4
4
|
* Provides a reference to a requested hand joint, once available.
|
|
5
5
|
*/
|
|
6
|
-
export declare const useHandJoint: (handedness: 'left' | 'right', joint: HandJoints) => import("@threlte/core").CurrentWritable<
|
|
6
|
+
export declare const useHandJoint: (handedness: 'left' | 'right', joint: HandJoints) => import("@threlte/core").CurrentWritable<XRJointSpace | undefined>;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Group } from 'three';
|
|
2
|
+
export declare const useHeadset: () => Readonly<Group>;
|
package/dist/hooks/useHeadset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="webxr" />
|
|
2
|
-
import
|
|
3
|
-
export type HitTestCallback = (hitMatrix:
|
|
2
|
+
import { Matrix4 } from 'three';
|
|
3
|
+
export type HitTestCallback = (hitMatrix: Matrix4, hit: XRHitTestResult | undefined) => void;
|
|
4
4
|
export type UseHitTestOptions = {
|
|
5
5
|
/**
|
|
6
6
|
* The ray source when performing hit testing.
|
package/dist/hooks/useHitTest.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Matrix4 } from 'three';
|
|
2
2
|
import { useThrelte, useFrame, watch, currentWritable } from '@threlte/core';
|
|
3
3
|
import { useXR } from './useXR';
|
|
4
4
|
import { useController } from './useController';
|
|
@@ -17,12 +17,14 @@ export const useHitTest = (hitTestCallback, options = {}) => {
|
|
|
17
17
|
const source = options.source ?? 'viewer';
|
|
18
18
|
const { xr } = useThrelte().renderer;
|
|
19
19
|
const xrState = useXR();
|
|
20
|
-
const hitMatrix = new
|
|
20
|
+
const hitMatrix = new Matrix4();
|
|
21
21
|
let hitTestSource = currentWritable(undefined);
|
|
22
22
|
if (source === 'viewer') {
|
|
23
23
|
watch(xrState.session, async (session) => {
|
|
24
|
-
if (session === undefined)
|
|
24
|
+
if (session === undefined) {
|
|
25
|
+
hitTestSource.set(undefined);
|
|
25
26
|
return;
|
|
27
|
+
}
|
|
26
28
|
const space = await session.requestReferenceSpace('viewer');
|
|
27
29
|
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
28
30
|
});
|
|
@@ -31,14 +33,18 @@ export const useHitTest = (hitTestCallback, options = {}) => {
|
|
|
31
33
|
const controller = useController(source === 'leftInput' ? 'left' : 'right');
|
|
32
34
|
const hand = useController(source === 'leftInput' ? 'left' : 'right');
|
|
33
35
|
watch([xrState.session, controller], async ([session, input]) => {
|
|
34
|
-
if (input === undefined || session === undefined)
|
|
36
|
+
if (input === undefined || session === undefined) {
|
|
37
|
+
hitTestSource.set(undefined);
|
|
35
38
|
return;
|
|
39
|
+
}
|
|
36
40
|
const space = input.inputSource.targetRaySpace;
|
|
37
41
|
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
38
42
|
});
|
|
39
43
|
watch([xrState.session, hand], async ([session, input]) => {
|
|
40
|
-
if (input === undefined || session === undefined)
|
|
44
|
+
if (input === undefined || session === undefined) {
|
|
45
|
+
hitTestSource.set(undefined);
|
|
41
46
|
return;
|
|
47
|
+
}
|
|
42
48
|
const space = input.inputSource.targetRaySpace;
|
|
43
49
|
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
44
50
|
});
|
|
@@ -56,7 +62,11 @@ export const useHitTest = (hitTestCallback, options = {}) => {
|
|
|
56
62
|
hitMatrix.fromArray(pose.transform.matrix);
|
|
57
63
|
hitTestCallback(hitMatrix, hit);
|
|
58
64
|
}, { autostart: false });
|
|
59
|
-
watch(hitTestSource, (testSource) => {
|
|
65
|
+
watch([xrState.isPresenting, hitTestSource], ([isPresenting, testSource]) => {
|
|
66
|
+
if (!isPresenting) {
|
|
67
|
+
stop();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
60
70
|
if (testSource === undefined) {
|
|
61
71
|
stop();
|
|
62
72
|
// Execute callback one last time to inform consumers of no hits.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Quaternion, type Vector3, type Vector3Tuple } from 'three';
|
|
2
2
|
/**
|
|
3
3
|
* Returns a callback to teleport the player from the world origin to a position and optional orientation.
|
|
4
4
|
*
|
|
@@ -14,4 +14,4 @@ import * as THREE from 'three';
|
|
|
14
14
|
*
|
|
15
15
|
* teleport(vec3, quat)
|
|
16
16
|
*/
|
|
17
|
-
export declare const useTeleport: () => (position:
|
|
17
|
+
export declare const useTeleport: () => (position: Vector3 | Vector3Tuple, orientation?: Quaternion) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Quaternion } from 'three';
|
|
2
2
|
import { useThrelte } from '@threlte/core';
|
|
3
|
-
const quaternion = new
|
|
3
|
+
const quaternion = new Quaternion();
|
|
4
4
|
const offset = { x: 0, y: 0, z: 0 };
|
|
5
5
|
/**
|
|
6
6
|
* Returns a callback to teleport the player from the world origin to a position and optional orientation.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setupControllers: () => void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory';
|
|
2
|
+
import { useThrelte } from '@threlte/core';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { useHandTrackingState } from './useHandTrackingState';
|
|
5
|
+
import { gaze, left, right } from '../hooks/useController';
|
|
6
|
+
import { controllerDispatchers } from './stores';
|
|
7
|
+
export const setupControllers = () => {
|
|
8
|
+
const factory = new XRControllerModelFactory();
|
|
9
|
+
const stores = { left, right, none: gaze };
|
|
10
|
+
const { xr } = useThrelte().renderer;
|
|
11
|
+
const hasHands = useHandTrackingState();
|
|
12
|
+
const controllers = [xr.getController(0), xr.getController(1)];
|
|
13
|
+
const indexMap = new Map();
|
|
14
|
+
controllers.forEach((targetRay, index) => {
|
|
15
|
+
indexMap.set(targetRay, {
|
|
16
|
+
targetRay,
|
|
17
|
+
grip: xr.getControllerGrip(index),
|
|
18
|
+
model: factory.createControllerModel(targetRay)
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
onMount(() => {
|
|
22
|
+
const dispatch = (event) => {
|
|
23
|
+
if (hasHands())
|
|
24
|
+
return;
|
|
25
|
+
const { data } = event;
|
|
26
|
+
controllerDispatchers[data.handedness]?.current?.(event.type, event);
|
|
27
|
+
};
|
|
28
|
+
function handleConnected(event) {
|
|
29
|
+
const { model, targetRay, grip } = indexMap.get(this);
|
|
30
|
+
const { data: inputSource } = event;
|
|
31
|
+
stores[event.data.handedness].set({
|
|
32
|
+
inputSource,
|
|
33
|
+
targetRay,
|
|
34
|
+
grip,
|
|
35
|
+
model
|
|
36
|
+
});
|
|
37
|
+
dispatch(event);
|
|
38
|
+
}
|
|
39
|
+
const handleDisconnected = (event) => {
|
|
40
|
+
dispatch(event);
|
|
41
|
+
stores[event.data.handedness].set(undefined);
|
|
42
|
+
};
|
|
43
|
+
for (const targetRay of controllers) {
|
|
44
|
+
targetRay.addEventListener('connected', handleConnected);
|
|
45
|
+
targetRay.addEventListener('disconnected', handleDisconnected);
|
|
46
|
+
targetRay.addEventListener('select', dispatch);
|
|
47
|
+
targetRay.addEventListener('selectstart', dispatch);
|
|
48
|
+
targetRay.addEventListener('selectend', dispatch);
|
|
49
|
+
targetRay.addEventListener('squeeze', dispatch);
|
|
50
|
+
targetRay.addEventListener('squeezestart', dispatch);
|
|
51
|
+
targetRay.addEventListener('squeezeend', dispatch);
|
|
52
|
+
}
|
|
53
|
+
return () => {
|
|
54
|
+
for (const targetRay of controllers) {
|
|
55
|
+
targetRay.removeEventListener('connected', handleConnected);
|
|
56
|
+
targetRay.removeEventListener('disconnected', handleDisconnected);
|
|
57
|
+
targetRay.removeEventListener('select', dispatch);
|
|
58
|
+
targetRay.removeEventListener('selectstart', dispatch);
|
|
59
|
+
targetRay.removeEventListener('selectend', dispatch);
|
|
60
|
+
targetRay.removeEventListener('squeeze', dispatch);
|
|
61
|
+
targetRay.removeEventListener('squeezestart', dispatch);
|
|
62
|
+
targetRay.removeEventListener('squeezeend', dispatch);
|
|
63
|
+
}
|
|
64
|
+
stores.left.set(undefined);
|
|
65
|
+
stores.right.set(undefined);
|
|
66
|
+
stores.none.set(undefined);
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setupHands: () => void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { XRHandModelFactory } from 'three/examples/jsm/webxr/XRHandModelFactory';
|
|
2
|
+
import { useThrelte } from '@threlte/core';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
import { left, right } from '../hooks/useHand';
|
|
5
|
+
import { useHandTrackingState } from './useHandTrackingState';
|
|
6
|
+
import { handDispatchers } from './stores';
|
|
7
|
+
export const setupHands = () => {
|
|
8
|
+
const factory = new XRHandModelFactory();
|
|
9
|
+
const stores = { left, right };
|
|
10
|
+
const { xr } = useThrelte().renderer;
|
|
11
|
+
const hasHands = useHandTrackingState();
|
|
12
|
+
const handSpaces = [xr.getHand(0), xr.getHand(1)];
|
|
13
|
+
const map = new Map();
|
|
14
|
+
handSpaces.forEach((handSpace, index) => {
|
|
15
|
+
map.set(handSpace, {
|
|
16
|
+
hand: handSpace,
|
|
17
|
+
targetRay: xr.getController(index),
|
|
18
|
+
model: factory.createHandModel(handSpace, 'mesh')
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
onMount(() => {
|
|
22
|
+
const dispatch = (event) => {
|
|
23
|
+
if (!hasHands())
|
|
24
|
+
return;
|
|
25
|
+
const handEvent = event;
|
|
26
|
+
const handedness = ('handedness' in handEvent) ? handEvent.handedness : handEvent.data.handedness;
|
|
27
|
+
handDispatchers[handedness]?.current?.(event.type, event);
|
|
28
|
+
};
|
|
29
|
+
function handleConnected(event) {
|
|
30
|
+
const hand = this;
|
|
31
|
+
const { model, targetRay } = map.get(this);
|
|
32
|
+
const { data } = event;
|
|
33
|
+
const { handedness, hand: inputSource } = data;
|
|
34
|
+
stores[handedness].set({
|
|
35
|
+
hand,
|
|
36
|
+
model,
|
|
37
|
+
inputSource,
|
|
38
|
+
targetRay
|
|
39
|
+
});
|
|
40
|
+
dispatch(event);
|
|
41
|
+
}
|
|
42
|
+
const handleDisconnected = (event) => {
|
|
43
|
+
dispatch(event);
|
|
44
|
+
stores[event.data.handedness].set(undefined);
|
|
45
|
+
};
|
|
46
|
+
for (const handSpace of handSpaces) {
|
|
47
|
+
handSpace.addEventListener('connected', handleConnected);
|
|
48
|
+
handSpace.addEventListener('disconnected', handleDisconnected);
|
|
49
|
+
handSpace.addEventListener('pinchstart', dispatch);
|
|
50
|
+
handSpace.addEventListener('pinchend', dispatch);
|
|
51
|
+
}
|
|
52
|
+
return () => {
|
|
53
|
+
for (const handSpace of handSpaces) {
|
|
54
|
+
handSpace.removeEventListener('connected', handleConnected);
|
|
55
|
+
handSpace.removeEventListener('disconnected', handleDisconnected);
|
|
56
|
+
handSpace.removeEventListener('pinchstart', dispatch);
|
|
57
|
+
handSpace.removeEventListener('pinchend', dispatch);
|
|
58
|
+
}
|
|
59
|
+
stores.left.set(undefined);
|
|
60
|
+
stores.right.set(undefined);
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
};
|
|
@@ -2,9 +2,8 @@ import { Group } from 'three';
|
|
|
2
2
|
import { useThrelte, useFrame, watch } from '@threlte/core';
|
|
3
3
|
import { useXR } from '../hooks/useXR';
|
|
4
4
|
export const headset = new Group();
|
|
5
|
-
export const
|
|
5
|
+
export const setupHeadset = () => {
|
|
6
6
|
const { renderer, camera } = useThrelte();
|
|
7
|
-
const xrState = useXR();
|
|
8
7
|
const { xr } = renderer;
|
|
9
8
|
const immersiveFrame = useFrame(() => {
|
|
10
9
|
const space = xr.getReferenceSpace();
|
|
@@ -23,7 +22,7 @@ export const useUpdateHeadset = () => {
|
|
|
23
22
|
headset.position.copy(camera.current.position);
|
|
24
23
|
headset.quaternion.copy(camera.current.quaternion);
|
|
25
24
|
}, { autostart: false, invalidate: false });
|
|
26
|
-
watch(
|
|
25
|
+
watch(useXR().isPresenting, (isPresenting) => {
|
|
27
26
|
if (isPresenting) {
|
|
28
27
|
immersiveFrame.start();
|
|
29
28
|
nonImmersiveFrame.stop();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setupRaf: () => void;
|
|
@@ -3,7 +3,7 @@ import { set_raf } from 'svelte/internal';
|
|
|
3
3
|
import { onDestroy } from 'svelte';
|
|
4
4
|
import { watch } from '@threlte/core';
|
|
5
5
|
import { session } from './stores';
|
|
6
|
-
export const
|
|
6
|
+
export const setupRaf = () => {
|
|
7
7
|
if (typeof window === 'undefined')
|
|
8
8
|
return;
|
|
9
9
|
const browserRaf = (fn) => requestAnimationFrame(fn);
|
|
@@ -11,7 +11,7 @@ export const updateRaf = () => {
|
|
|
11
11
|
set_raf((fn) => currentRaf.fn(fn));
|
|
12
12
|
watch(session, (session) => {
|
|
13
13
|
if (session) {
|
|
14
|
-
currentRaf.fn = (fn) =>
|
|
14
|
+
currentRaf.fn = (fn) => session.requestAnimationFrame(fn);
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
17
|
currentRaf.fn = browserRaf;
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
/// <reference types="webxr" />
|
|
2
|
-
|
|
3
|
-
export declare const initialized: import("svelte/store").Writable<boolean>;
|
|
2
|
+
import type { WebXRManager, Intersection } from 'three';
|
|
4
3
|
export declare const isPresenting: import("@threlte/core").CurrentWritable<boolean>;
|
|
5
4
|
export declare const isHandTracking: import("@threlte/core").CurrentWritable<boolean>;
|
|
6
5
|
export declare const session: import("@threlte/core").CurrentWritable<XRSession | undefined>;
|
|
7
6
|
export declare const referenceSpaceType: import("@threlte/core").CurrentWritable<XRReferenceSpaceType | undefined>;
|
|
8
|
-
export declare const xr: import("@threlte/core").CurrentWritable<
|
|
7
|
+
export declare const xr: import("@threlte/core").CurrentWritable<WebXRManager | undefined>;
|
|
8
|
+
export declare const controllerDispatchers: {
|
|
9
|
+
left: import("@threlte/core").CurrentWritable<((<Type extends string>(type: Type, payload?: unknown) => void) & {
|
|
10
|
+
hasEventListener: <Type_1 extends string>(type: Type_1) => boolean;
|
|
11
|
+
}) | undefined>;
|
|
12
|
+
right: import("@threlte/core").CurrentWritable<((<Type extends string>(type: Type, payload?: unknown) => void) & {
|
|
13
|
+
hasEventListener: <Type_1 extends string>(type: Type_1) => boolean;
|
|
14
|
+
}) | undefined>;
|
|
15
|
+
};
|
|
16
|
+
export declare const handDispatchers: {
|
|
17
|
+
left: import("@threlte/core").CurrentWritable<((<Type extends string>(type: Type, payload?: unknown) => void) & {
|
|
18
|
+
hasEventListener: <Type_1 extends string>(type: Type_1) => boolean;
|
|
19
|
+
}) | undefined>;
|
|
20
|
+
right: import("@threlte/core").CurrentWritable<((<Type extends string>(type: Type, payload?: unknown) => void) & {
|
|
21
|
+
hasEventListener: <Type_1 extends string>(type: Type_1) => boolean;
|
|
22
|
+
}) | undefined>;
|
|
23
|
+
};
|
|
9
24
|
export declare const teleportState: import("@threlte/core").CurrentWritable<{
|
|
10
25
|
left: {
|
|
11
26
|
enabled: boolean;
|
|
@@ -17,8 +32,8 @@ export declare const teleportState: import("@threlte/core").CurrentWritable<{
|
|
|
17
32
|
};
|
|
18
33
|
}>;
|
|
19
34
|
export declare const teleportIntersection: {
|
|
20
|
-
left: import("@threlte/core").CurrentWritable<
|
|
21
|
-
right: import("@threlte/core").CurrentWritable<
|
|
35
|
+
left: import("@threlte/core").CurrentWritable<Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined>;
|
|
36
|
+
right: import("@threlte/core").CurrentWritable<Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined>;
|
|
22
37
|
};
|
|
23
38
|
export declare const pointerState: import("@threlte/core").CurrentWritable<{
|
|
24
39
|
left: {
|
|
@@ -31,6 +46,6 @@ export declare const pointerState: import("@threlte/core").CurrentWritable<{
|
|
|
31
46
|
};
|
|
32
47
|
}>;
|
|
33
48
|
export declare const pointerIntersection: {
|
|
34
|
-
left: import("@threlte/core").CurrentWritable<
|
|
35
|
-
right: import("@threlte/core").CurrentWritable<
|
|
49
|
+
left: import("@threlte/core").CurrentWritable<Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined>;
|
|
50
|
+
right: import("@threlte/core").CurrentWritable<Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined>;
|
|
36
51
|
};
|
package/dist/internal/stores.js
CHANGED
|
@@ -1,36 +1,42 @@
|
|
|
1
|
-
import { writable } from 'svelte/store';
|
|
2
1
|
import { currentWritable } from '@threlte/core';
|
|
3
|
-
export const initialized = writable(false);
|
|
4
2
|
export const isPresenting = currentWritable(false);
|
|
5
3
|
export const isHandTracking = currentWritable(false);
|
|
6
4
|
export const session = currentWritable(undefined);
|
|
7
5
|
export const referenceSpaceType = currentWritable(undefined);
|
|
8
6
|
export const xr = currentWritable(undefined);
|
|
7
|
+
export const controllerDispatchers = {
|
|
8
|
+
left: currentWritable(undefined),
|
|
9
|
+
right: currentWritable(undefined)
|
|
10
|
+
};
|
|
11
|
+
export const handDispatchers = {
|
|
12
|
+
left: currentWritable(undefined),
|
|
13
|
+
right: currentWritable(undefined)
|
|
14
|
+
};
|
|
9
15
|
export const teleportState = currentWritable({
|
|
10
16
|
left: {
|
|
11
17
|
enabled: false,
|
|
12
|
-
hovering: false
|
|
18
|
+
hovering: false
|
|
13
19
|
},
|
|
14
20
|
right: {
|
|
15
21
|
enabled: false,
|
|
16
|
-
hovering: false
|
|
17
|
-
}
|
|
22
|
+
hovering: false
|
|
23
|
+
}
|
|
18
24
|
});
|
|
19
25
|
export const teleportIntersection = {
|
|
20
26
|
left: currentWritable(undefined),
|
|
21
|
-
right: currentWritable(undefined)
|
|
27
|
+
right: currentWritable(undefined)
|
|
22
28
|
};
|
|
23
29
|
export const pointerState = currentWritable({
|
|
24
30
|
left: {
|
|
25
31
|
enabled: false,
|
|
26
|
-
hovering: false
|
|
32
|
+
hovering: false
|
|
27
33
|
},
|
|
28
34
|
right: {
|
|
29
35
|
enabled: false,
|
|
30
|
-
hovering: false
|
|
36
|
+
hovering: false
|
|
31
37
|
}
|
|
32
38
|
});
|
|
33
39
|
export const pointerIntersection = {
|
|
34
40
|
left: currentWritable(undefined),
|
|
35
|
-
right: currentWritable(undefined)
|
|
41
|
+
right: currentWritable(undefined)
|
|
36
42
|
};
|
|
@@ -7,7 +7,7 @@ export const useHandTrackingState = () => {
|
|
|
7
7
|
const { xr } = useThrelte().renderer;
|
|
8
8
|
return () => {
|
|
9
9
|
let handTracking = false;
|
|
10
|
-
xr.getSession()?.inputSources
|
|
10
|
+
xr.getSession()?.inputSources?.forEach((value) => {
|
|
11
11
|
if (value.hand) {
|
|
12
12
|
handTracking = true;
|
|
13
13
|
}
|
|
@@ -21,12 +21,12 @@ export const toggleXRSession = async (sessionMode, sessionInit, force) => {
|
|
|
21
21
|
session.set(undefined);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
// Otherwise enter a session
|
|
25
|
-
const options = getXRSessionOptions(referenceSpaceType.current, sessionInit);
|
|
26
|
-
const nextSession = await navigator.xr.requestSession(sessionMode, options);
|
|
27
24
|
if (xr.current === undefined) {
|
|
28
25
|
throw new Error('An <XR> component was not created when attempting to toggle a session.');
|
|
29
26
|
}
|
|
27
|
+
// Otherwise enter a session
|
|
28
|
+
const options = getXRSessionOptions(referenceSpaceType.current, sessionInit);
|
|
29
|
+
const nextSession = await navigator.xr.requestSession(sessionMode, options);
|
|
30
30
|
await xr.current.setSession(nextSession);
|
|
31
31
|
session.set(nextSession);
|
|
32
32
|
return nextSession;
|
|
@@ -1,45 +1,46 @@
|
|
|
1
|
+
import type { Intersection as ThreeIntersection, Object3D, Vector3, Ray, Raycaster, Event } from 'three';
|
|
1
2
|
import type { CurrentWritable } from '@threlte/core';
|
|
2
3
|
import type { ComputeFunction } from './compute';
|
|
3
4
|
export type Properties<T> = Pick<T, {
|
|
4
5
|
[K in keyof T]: T[K] extends (_: any) => any ? never : K;
|
|
5
6
|
}[keyof T]>;
|
|
6
|
-
export interface Intersection extends
|
|
7
|
+
export interface Intersection extends ThreeIntersection {
|
|
7
8
|
/** The event source (the object which registered the handler) */
|
|
8
|
-
eventObject:
|
|
9
|
+
eventObject: Object3D;
|
|
9
10
|
}
|
|
10
11
|
export interface IntersectionEvent extends Intersection {
|
|
11
12
|
/** The event source (the object which registered the handler) */
|
|
12
|
-
eventObject:
|
|
13
|
+
eventObject: Object3D;
|
|
13
14
|
/** An array of intersections */
|
|
14
15
|
intersections: Intersection[];
|
|
15
16
|
/** Normalized event coordinates */
|
|
16
|
-
pointer:
|
|
17
|
+
pointer: Vector3;
|
|
17
18
|
/** Delta between first click and this event */
|
|
18
19
|
delta: number;
|
|
19
20
|
/** The ray that pierced it */
|
|
20
|
-
ray:
|
|
21
|
+
ray: Ray;
|
|
21
22
|
/** stopPropagation will stop underlying handlers from firing */
|
|
22
23
|
stopPropagation: () => void;
|
|
23
24
|
/** The original host event */
|
|
24
|
-
nativeEvent:
|
|
25
|
+
nativeEvent: Event | undefined;
|
|
25
26
|
/** If the event was stopped by calling stopPropagation */
|
|
26
27
|
stopped: boolean;
|
|
27
28
|
}
|
|
28
|
-
export type FilterFunction = (items:
|
|
29
|
+
export type FilterFunction = (items: Intersection[], state: ControlsContext, handState: HandContext) => Intersection[];
|
|
29
30
|
export type ControlsContext = {
|
|
30
|
-
interactiveObjects:
|
|
31
|
-
raycaster:
|
|
31
|
+
interactiveObjects: Object3D[];
|
|
32
|
+
raycaster: Raycaster;
|
|
32
33
|
compute: ComputeFunction;
|
|
33
34
|
filter?: FilterFunction | undefined;
|
|
34
35
|
};
|
|
35
36
|
export type HandContext = {
|
|
36
37
|
hand: 'left' | 'right';
|
|
37
38
|
enabled: CurrentWritable<boolean>;
|
|
38
|
-
pointer: CurrentWritable<
|
|
39
|
+
pointer: CurrentWritable<Vector3>;
|
|
39
40
|
pointerOverTarget: CurrentWritable<boolean>;
|
|
40
|
-
lastEvent:
|
|
41
|
+
lastEvent: Event | undefined;
|
|
41
42
|
initialClick: [x: number, y: number, z: number];
|
|
42
|
-
initialHits:
|
|
43
|
+
initialHits: Object3D[];
|
|
43
44
|
hovered: Map<string, IntersectionEvent>;
|
|
44
45
|
};
|
|
45
46
|
export interface PointerCaptureTarget {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import type { Mesh, Raycaster, Intersection } from 'three';
|
|
1
2
|
import type { CurrentWritable, createRawEventDispatcher } from '@threlte/core';
|
|
2
3
|
export type ComputeFunction = (context: Context, handContext: HandContext) => void;
|
|
3
4
|
export interface Context {
|
|
4
|
-
interactiveObjects:
|
|
5
|
-
surfaces: Map<string,
|
|
6
|
-
blockers: Map<string,
|
|
7
|
-
dispatchers: WeakMap<
|
|
8
|
-
raycaster:
|
|
5
|
+
interactiveObjects: Mesh[];
|
|
6
|
+
surfaces: Map<string, Mesh>;
|
|
7
|
+
blockers: Map<string, Mesh>;
|
|
8
|
+
dispatchers: WeakMap<Mesh, ReturnType<typeof createRawEventDispatcher>>;
|
|
9
|
+
raycaster: Raycaster;
|
|
9
10
|
compute: ComputeFunction;
|
|
10
11
|
}
|
|
11
12
|
export interface HandContext {
|
|
12
13
|
hand: 'left' | 'right';
|
|
13
14
|
enabled: CurrentWritable<boolean>;
|
|
14
15
|
active: CurrentWritable<boolean>;
|
|
15
|
-
hovered: CurrentWritable<
|
|
16
|
+
hovered: CurrentWritable<Intersection | undefined>;
|
|
16
17
|
}
|
|
17
18
|
export declare const getHandContext: (hand: 'left' | 'right') => HandContext;
|
|
18
19
|
export declare const setHandContext: (hand: 'left' | 'right', context: HandContext) => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { Mesh } from 'three';
|
|
1
2
|
export declare const useTeleportControls: () => {
|
|
2
|
-
addSurface: (mesh:
|
|
3
|
-
removeSurface: (mesh:
|
|
4
|
-
addBlocker: (mesh:
|
|
5
|
-
removeBlocker: (mesh:
|
|
3
|
+
addSurface: (mesh: Mesh) => void;
|
|
4
|
+
removeSurface: (mesh: Mesh) => void;
|
|
5
|
+
addBlocker: (mesh: Mesh) => void;
|
|
6
|
+
removeBlocker: (mesh: Mesh) => void;
|
|
6
7
|
};
|
|
@@ -14,6 +14,6 @@ export interface TeleportControlsOptions {
|
|
|
14
14
|
}
|
|
15
15
|
export declare const teleportControls: (handedness: 'left' | 'right', options?: TeleportControlsOptions) => {
|
|
16
16
|
enabled: import("@threlte/core").CurrentWritable<boolean>;
|
|
17
|
-
hovered: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").
|
|
17
|
+
hovered: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined>;
|
|
18
18
|
active: import("@threlte/core").CurrentWritable<boolean>;
|
|
19
19
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
/// <reference types="webxr" />
|
|
2
|
+
import type { Event, Group, XRTargetRaySpace, XRGripSpace, XRHandSpace } from 'three';
|
|
2
3
|
import type { XRControllerModel } from 'three/examples/jsm/webxr/XRControllerModelFactory';
|
|
3
4
|
import type { XRHandModel } from 'three/examples/jsm/webxr/XRHandModelFactory';
|
|
4
5
|
export type XRSessionEventType = 'sessionstart' | 'sessionend' | 'visibilitychange' | 'frameratechange';
|
|
5
6
|
export type XRControllerEventType = 'select' | 'selectstart' | 'selectend' | 'squeeze' | 'squeezeend' | 'squeezestart' | 'disconnected' | 'connected';
|
|
6
7
|
export type XRHandEventType = 'pinchstart' | 'pinchend' | 'connected' | 'disconnected';
|
|
7
|
-
export type XRSessionEvent<Type = XRSessionEventType> =
|
|
8
|
+
export type XRSessionEvent<Type = XRSessionEventType> = Event & {
|
|
8
9
|
type: Type;
|
|
9
10
|
target: XRSession;
|
|
10
11
|
};
|
|
11
|
-
export type XRControllerEvent<Type = XRControllerEventType> =
|
|
12
|
+
export type XRControllerEvent<Type = XRControllerEventType> = Event & {
|
|
12
13
|
type: Type;
|
|
13
|
-
target:
|
|
14
|
+
target: Group;
|
|
14
15
|
data: XRInputSource;
|
|
15
16
|
};
|
|
16
17
|
export type XRController = {
|
|
17
|
-
targetRay:
|
|
18
|
-
grip:
|
|
19
|
-
model?: XRControllerModel;
|
|
18
|
+
targetRay: XRTargetRaySpace;
|
|
19
|
+
grip: XRGripSpace;
|
|
20
|
+
model?: XRControllerModel | undefined;
|
|
20
21
|
inputSource: XRInputSource;
|
|
21
22
|
};
|
|
22
23
|
export type XRHand = {
|
|
23
|
-
|
|
24
|
+
targetRay: XRTargetRaySpace;
|
|
25
|
+
hand: XRHandSpace;
|
|
24
26
|
model?: XRHandModel;
|
|
25
27
|
inputSource: globalThis.XRHand;
|
|
26
28
|
};
|
|
27
29
|
export type XRHandEvent<Type = XRHandEventType> = Type extends 'connected' | 'disconnected' ? {
|
|
28
30
|
type: Type;
|
|
29
|
-
target:
|
|
31
|
+
target: XRHandSpace;
|
|
30
32
|
data: XRInputSource;
|
|
31
33
|
} : Type extends 'pinchstart' | 'pinchend' ? {
|
|
32
34
|
type: Type;
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threlte/xr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"author": "Micheal Parks <michealparks1989@gmail.com> (https://parks.lol)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"@sveltejs/adapter-auto": "^2.1.0",
|
|
8
8
|
"@sveltejs/kit": "^1.22.3",
|
|
9
9
|
"@sveltejs/package": "^2.2.0",
|
|
10
|
-
"@types/three": "^0.
|
|
10
|
+
"@types/three": "^0.158.3",
|
|
11
11
|
"autoprefixer": "^10.4.14",
|
|
12
12
|
"postcss": "^8.4.27",
|
|
13
13
|
"publint": "^0.1.16",
|
|
14
14
|
"svelte": "^4.1.1",
|
|
15
15
|
"svelte-check": "^3.4.6",
|
|
16
|
-
"three": "^0.
|
|
16
|
+
"three": "^0.158.0",
|
|
17
17
|
"tslib": "^2.6.0",
|
|
18
18
|
"typescript": "^5.1.6",
|
|
19
19
|
"vite": "^4.4.6",
|
|
20
20
|
"vite-plugin-mkcert": "^1.16.0",
|
|
21
|
-
"@threlte/core": "6.1.
|
|
21
|
+
"@threlte/core": "6.1.1"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"svelte": ">=4",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const updateRaf: () => void;
|