@threlte/xr 0.0.3 → 0.0.5
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 +38 -44
- package/dist/components/Controller.svelte.d.ts +14 -2
- package/dist/components/Hand.svelte +35 -47
- package/dist/components/Hand.svelte.d.ts +18 -6
- package/dist/components/Headset.svelte +44 -0
- package/dist/components/Headset.svelte.d.ts +16 -0
- package/dist/components/ShortRay.svelte +6 -2
- package/dist/components/TeleportControls.svelte +2 -2
- package/dist/components/XR.svelte +14 -7
- package/dist/components/XR.svelte.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/useTeleport.d.ts +6 -2
- package/dist/hooks/useTeleport.js +19 -25
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/useHandTrackingState.d.ts +5 -0
- package/dist/internal/useHandTrackingState.js +17 -0
- package/dist/types.d.ts +11 -6
- package/package.json +1 -1
- package/dist/components/Hands.svelte +0 -35
- package/dist/components/Hands.svelte.d.ts +0 -24
- package/dist/hooks/useEvent.d.ts +0 -14
- package/dist/hooks/useEvent.js +0 -34
- package/dist/internal/events.d.ts +0 -5
- package/dist/internal/events.js +0 -26
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@component
|
|
3
|
-
`<Controller />` represents a THREE.XRTargetRaySpace, a THREE.XRGripSpace, and a controller model.
|
|
2
|
+
@component `<Controller />` represents a THREE.XRTargetRaySpace, a THREE.XRGripSpace, and a controller model.
|
|
4
3
|
-->
|
|
5
4
|
<script
|
|
6
5
|
|
|
@@ -8,15 +7,10 @@
|
|
|
8
7
|
>import { T, createRawEventDispatcher, useThrelte } from "@threlte/core";
|
|
9
8
|
import { onDestroy } from "svelte";
|
|
10
9
|
import { XRControllerModelFactory } from "three/examples/jsm/webxr/XRControllerModelFactory";
|
|
11
|
-
import ShortRay from "
|
|
10
|
+
import ShortRay from "./ShortRay.svelte";
|
|
12
11
|
import { gaze, left as leftStore, right as rightStore } from "../hooks/useController";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
activeTeleportController,
|
|
17
|
-
isHandTracking,
|
|
18
|
-
pendingTeleportDestination
|
|
19
|
-
} from "../internal/stores";
|
|
12
|
+
import { activeTeleportController, pendingTeleportDestination, isHandTracking } from "../internal/stores";
|
|
13
|
+
import { useHandTrackingState } from "../internal/useHandTrackingState";
|
|
20
14
|
const factory = new XRControllerModelFactory();
|
|
21
15
|
const stores = {
|
|
22
16
|
left: leftStore,
|
|
@@ -34,63 +28,63 @@ const events = [
|
|
|
34
28
|
const eventMap = /* @__PURE__ */ new WeakMap();
|
|
35
29
|
</script>
|
|
36
30
|
|
|
37
|
-
<script>export let left =
|
|
38
|
-
export let right =
|
|
31
|
+
<script>export let left = void 0;
|
|
32
|
+
export let right = void 0;
|
|
33
|
+
export let hand = void 0;
|
|
39
34
|
$:
|
|
40
|
-
|
|
41
|
-
throw new Error("A <Controller> component can only specify one hand.");
|
|
42
|
-
}
|
|
43
|
-
$:
|
|
44
|
-
if (!left && !right) {
|
|
45
|
-
throw new Error("A <Controller> component must specify a hand.");
|
|
46
|
-
}
|
|
47
|
-
$:
|
|
48
|
-
handedness = left ? "left" : "right";
|
|
35
|
+
handedness = left ? "left" : right ? "right" : hand;
|
|
49
36
|
const dispatch = createRawEventDispatcher();
|
|
50
37
|
const { xr } = useThrelte().renderer;
|
|
38
|
+
const handTrackingNow = useHandTrackingState();
|
|
39
|
+
const handleEvent = (event) => {
|
|
40
|
+
if (!handTrackingNow()) {
|
|
41
|
+
dispatch(event.type, event);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
51
44
|
const handleConnected = (event) => {
|
|
52
|
-
const
|
|
53
|
-
if (data.handedness !== handedness)
|
|
45
|
+
const targetData = eventMap.get(event.target);
|
|
46
|
+
if (event.data.handedness !== handedness || !targetData)
|
|
54
47
|
return;
|
|
55
|
-
stores[
|
|
56
|
-
|
|
48
|
+
stores[handedness].set({ ...targetData, inputSource: event.data });
|
|
49
|
+
if (!handTrackingNow()) {
|
|
50
|
+
dispatch("connected", event);
|
|
51
|
+
}
|
|
52
|
+
events.forEach((name) => event.target.addEventListener(name, handleEvent));
|
|
57
53
|
};
|
|
58
54
|
const handleDisconnected = (event) => {
|
|
59
55
|
if (event.data.handedness !== handedness)
|
|
60
56
|
return;
|
|
61
|
-
stores[
|
|
62
|
-
|
|
57
|
+
stores[handedness].set(void 0);
|
|
58
|
+
if (!$isHandTracking) {
|
|
59
|
+
dispatch("disconnected", event);
|
|
60
|
+
}
|
|
61
|
+
events.forEach((name) => event.target.removeEventListener(name, handleEvent));
|
|
63
62
|
};
|
|
64
|
-
const handleEvent = (event) => fire(event.type, event);
|
|
65
63
|
for (const index of [0, 1]) {
|
|
66
|
-
const
|
|
64
|
+
const controller = xr.getController(index);
|
|
67
65
|
const grip2 = xr.getControllerGrip(index);
|
|
68
66
|
const model2 = factory.createControllerModel(grip2);
|
|
69
|
-
eventMap.set(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
events.forEach((name) => controller2.addEventListener(name, handleEvent));
|
|
67
|
+
eventMap.set(controller, { targetRay: controller, model: model2, grip: grip2 });
|
|
68
|
+
controller.addEventListener("connected", handleConnected);
|
|
69
|
+
controller.addEventListener("disconnected", handleDisconnected);
|
|
73
70
|
}
|
|
74
71
|
$:
|
|
75
|
-
store =
|
|
72
|
+
store = stores[handedness];
|
|
76
73
|
$:
|
|
77
74
|
grip = $store?.grip;
|
|
78
75
|
$:
|
|
79
|
-
|
|
76
|
+
targetRay = $store?.targetRay;
|
|
80
77
|
$:
|
|
81
78
|
model = $store?.model;
|
|
82
|
-
for (const type of ["connected", "disconnected", ...events]) {
|
|
83
|
-
useControllerEvent(type, (event) => dispatch(type, event), {
|
|
84
|
-
handedness: left ? "left" : "right"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
79
|
onDestroy(() => {
|
|
88
80
|
for (const index of [0, 1]) {
|
|
89
81
|
const controller2 = xr.getController(index);
|
|
90
82
|
controller2.removeEventListener("connected", handleConnected);
|
|
91
83
|
controller2.removeEventListener("disconnected", handleDisconnected);
|
|
92
|
-
events.forEach((name) => controller2.removeEventListener(name, handleEvent));
|
|
93
84
|
}
|
|
85
|
+
const controller = $store?.targetRay;
|
|
86
|
+
events.forEach((name) => controller?.removeEventListener(name, handleEvent));
|
|
87
|
+
store.set(void 0);
|
|
94
88
|
});
|
|
95
89
|
</script>
|
|
96
90
|
|
|
@@ -108,15 +102,15 @@ onDestroy(() => {
|
|
|
108
102
|
</T>
|
|
109
103
|
{/if}
|
|
110
104
|
|
|
111
|
-
{#if
|
|
105
|
+
{#if targetRay}
|
|
112
106
|
<T
|
|
113
|
-
is={
|
|
107
|
+
is={targetRay}
|
|
114
108
|
name="XR controller {handedness}"
|
|
115
109
|
visible={!$isHandTracking}
|
|
116
110
|
>
|
|
117
111
|
<slot name="target-ray" />
|
|
118
112
|
<ShortRay
|
|
119
|
-
visible={$activeTeleportController ===
|
|
113
|
+
visible={$activeTeleportController === targetRay &&
|
|
120
114
|
$pendingTeleportDestination === undefined}
|
|
121
115
|
/>
|
|
122
116
|
</T>
|
|
@@ -2,8 +2,20 @@ import { SvelteComponent } from "svelte";
|
|
|
2
2
|
import type { XRControllerEvent } from '../types';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
|
-
/** Whether the controller should be matched with the left hand. */
|
|
6
|
-
|
|
5
|
+
/** Whether the controller should be matched with the left hand. */
|
|
6
|
+
left: true;
|
|
7
|
+
right?: undefined;
|
|
8
|
+
hand?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
/** Whether the controller should be matched with the right hand. */
|
|
11
|
+
right: true;
|
|
12
|
+
left?: undefined;
|
|
13
|
+
hand?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
/** Whether the controller should be matched with the left or right hand. */
|
|
16
|
+
hand: 'left' | 'right';
|
|
17
|
+
left?: undefined;
|
|
18
|
+
right?: undefined;
|
|
7
19
|
};
|
|
8
20
|
slots: {
|
|
9
21
|
default: {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script context='module'>import { T, useThrelte, createRawEventDispatcher, useFrame } from "@threlte/core";
|
|
2
2
|
import { XRHandModelFactory } from "three/examples/jsm/webxr/XRHandModelFactory";
|
|
3
|
-
import {
|
|
3
|
+
import { isHandTracking } from "../internal/stores";
|
|
4
|
+
import { useHandTrackingState } from "../internal/useHandTrackingState";
|
|
4
5
|
import { left as leftStore, right as rightStore } from "../hooks/useHand";
|
|
5
|
-
import { useHandEvent } from "../hooks/useEvent";
|
|
6
6
|
import { onDestroy } from "svelte";
|
|
7
7
|
const factory = new XRHandModelFactory();
|
|
8
8
|
const stores = {
|
|
@@ -12,48 +12,38 @@ const stores = {
|
|
|
12
12
|
const eventMap = /* @__PURE__ */ new WeakMap();
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
|
-
<script>export let left =
|
|
16
|
-
export let right =
|
|
15
|
+
<script>export let left = void 0;
|
|
16
|
+
export let right = void 0;
|
|
17
|
+
export let hand = void 0;
|
|
18
|
+
const handTrackingNow = useHandTrackingState();
|
|
17
19
|
const dispatch = createRawEventDispatcher();
|
|
18
20
|
const { xr } = useThrelte().renderer;
|
|
19
21
|
const space = xr.getReferenceSpace();
|
|
20
22
|
$:
|
|
21
|
-
|
|
22
|
-
throw new Error("A <Hand> component can only specify one hand.");
|
|
23
|
-
}
|
|
24
|
-
$:
|
|
25
|
-
if (!left && !right) {
|
|
26
|
-
throw new Error("A <Hand> component must specify a hand.");
|
|
27
|
-
}
|
|
28
|
-
$:
|
|
29
|
-
handedness = left ? "left" : "right";
|
|
23
|
+
handedness = left ? "left" : right ? "right" : hand;
|
|
30
24
|
const handleConnected = (event) => {
|
|
31
|
-
|
|
32
|
-
const eventHandedness = event.data.handedness;
|
|
33
|
-
if (eventHandedness !== handedness)
|
|
25
|
+
if (event.data.handedness !== handedness)
|
|
34
26
|
return;
|
|
35
|
-
stores[handedness].set({ ...eventMap.get(event.target), inputSource:
|
|
36
|
-
|
|
27
|
+
stores[handedness].set({ ...eventMap.get(event.target), inputSource: event.data.hand });
|
|
28
|
+
if (handTrackingNow()) {
|
|
29
|
+
dispatch("connected", event);
|
|
30
|
+
}
|
|
31
|
+
event.target.addEventListener("pinchstart", handlePinchEvent);
|
|
32
|
+
event.target.addEventListener("pinchend", handlePinchEvent);
|
|
37
33
|
};
|
|
38
34
|
const handleDisconnected = (event) => {
|
|
39
|
-
|
|
40
|
-
if (eventHandedness !== handedness)
|
|
35
|
+
if (event.data.handedness !== handedness)
|
|
41
36
|
return;
|
|
42
37
|
stores[handedness].set(void 0);
|
|
43
|
-
|
|
38
|
+
if ($isHandTracking) {
|
|
39
|
+
dispatch("disconnected", event);
|
|
40
|
+
}
|
|
41
|
+
event.target.removeEventListener("pinchstart", handlePinchEvent);
|
|
42
|
+
event.target.removeEventListener("pinchend", handlePinchEvent);
|
|
44
43
|
};
|
|
45
44
|
const handlePinchEvent = (event) => {
|
|
46
|
-
|
|
45
|
+
dispatch(event.type, event);
|
|
47
46
|
};
|
|
48
|
-
for (const index of [0, 1]) {
|
|
49
|
-
const hand2 = xr.getHand(index);
|
|
50
|
-
const model2 = factory.createHandModel(hand2, "mesh");
|
|
51
|
-
eventMap.set(hand2, { hand: hand2, model: model2 });
|
|
52
|
-
hand2.addEventListener("connected", handleConnected);
|
|
53
|
-
hand2.addEventListener("disconnected", handleDisconnected);
|
|
54
|
-
hand2.addEventListener("pinchstart", handlePinchEvent);
|
|
55
|
-
hand2.addEventListener("pinchend", handlePinchEvent);
|
|
56
|
-
}
|
|
57
47
|
let children;
|
|
58
48
|
const { start, stop } = useFrame(() => {
|
|
59
49
|
const frame = xr.getFrame();
|
|
@@ -74,32 +64,30 @@ $:
|
|
|
74
64
|
stop();
|
|
75
65
|
}
|
|
76
66
|
$:
|
|
77
|
-
store =
|
|
67
|
+
store = stores[handedness];
|
|
78
68
|
$:
|
|
79
69
|
hand = $store?.hand;
|
|
80
70
|
$:
|
|
81
71
|
inputSource = $store?.inputSource;
|
|
82
72
|
$:
|
|
83
73
|
model = $store?.model;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
for (const name of handEvents) {
|
|
91
|
-
useHandEvent(name, (event) => dispatch(name, event), {
|
|
92
|
-
handedness: left ? "left" : "right"
|
|
93
|
-
});
|
|
74
|
+
for (const index of [0, 1]) {
|
|
75
|
+
const hand2 = xr.getHand(index);
|
|
76
|
+
const model2 = factory.createHandModel(hand2, "mesh");
|
|
77
|
+
eventMap.set(hand2, { hand: hand2, model: model2 });
|
|
78
|
+
hand2.addEventListener("connected", handleConnected);
|
|
79
|
+
hand2.addEventListener("disconnected", handleDisconnected);
|
|
94
80
|
}
|
|
95
81
|
onDestroy(() => {
|
|
96
82
|
for (const index of [0, 1]) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
hand2.removeEventListener("pinchstart", handlePinchEvent);
|
|
101
|
-
hand2.removeEventListener("pinchend", handlePinchEvent);
|
|
83
|
+
const hand3 = xr.getHand(index);
|
|
84
|
+
hand3.removeEventListener("connected", handleConnected);
|
|
85
|
+
hand3.removeEventListener("disconnected", handleDisconnected);
|
|
102
86
|
}
|
|
87
|
+
const hand2 = stores[handedness].current?.hand;
|
|
88
|
+
hand2?.removeEventListener("pinchstart", handlePinchEvent);
|
|
89
|
+
hand2?.removeEventListener("pinchend", handlePinchEvent);
|
|
90
|
+
stores[handedness].set(void 0);
|
|
103
91
|
});
|
|
104
92
|
</script>
|
|
105
93
|
|
|
@@ -2,18 +2,30 @@ import { SvelteComponent } from "svelte";
|
|
|
2
2
|
import type { XRHandEvent } from '../types';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
|
-
/** Whether the XRHand should be matched with the left hand. */
|
|
6
|
-
|
|
5
|
+
/** Whether the XRHand should be matched with the left hand. */
|
|
6
|
+
left: true;
|
|
7
|
+
right?: undefined;
|
|
8
|
+
hand?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
/** Whether the XRHand should be matched with the right hand. */
|
|
11
|
+
right: true;
|
|
12
|
+
left?: undefined;
|
|
13
|
+
hand?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
/** Whether the XRHand should be matched with the left or right hand. */
|
|
16
|
+
hand: 'left' | 'right';
|
|
17
|
+
left?: undefined;
|
|
18
|
+
right?: undefined;
|
|
7
19
|
};
|
|
8
20
|
slots: {
|
|
9
21
|
wrist: {};
|
|
10
22
|
default: {};
|
|
11
23
|
};
|
|
12
24
|
events: {
|
|
13
|
-
connected: XRHandEvent<'connected'
|
|
14
|
-
disconnected: XRHandEvent<'disconnected'
|
|
15
|
-
pinchstart: XRHandEvent<'pinchstart'
|
|
16
|
-
pinchend: XRHandEvent<'pinchend'
|
|
25
|
+
connected: XRHandEvent<'connected'>;
|
|
26
|
+
disconnected: XRHandEvent<'disconnected'>;
|
|
27
|
+
pinchstart: XRHandEvent<'pinchstart'>;
|
|
28
|
+
pinchend: XRHandEvent<'pinchend'>;
|
|
17
29
|
};
|
|
18
30
|
};
|
|
19
31
|
export type HandProps = typeof __propDef.props;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script>import { T, HierarchicalObject, useFrame, useThrelte } from "@threlte/core";
|
|
2
|
+
import { Group } from "three";
|
|
3
|
+
import { useXR } from "../hooks";
|
|
4
|
+
const { isPresenting } = useXR();
|
|
5
|
+
const { renderer, scene, camera } = useThrelte();
|
|
6
|
+
const { xr } = renderer;
|
|
7
|
+
const group = new Group();
|
|
8
|
+
const immersiveFrame = useFrame(() => {
|
|
9
|
+
const space = xr.getReferenceSpace();
|
|
10
|
+
if (space === null)
|
|
11
|
+
return;
|
|
12
|
+
const pose = xr.getFrame().getViewerPose(space);
|
|
13
|
+
if (pose === void 0)
|
|
14
|
+
return;
|
|
15
|
+
const { position, orientation } = pose.transform;
|
|
16
|
+
group.position.set(position.x, position.y, position.z);
|
|
17
|
+
group.quaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
|
|
18
|
+
}, { autostart: false });
|
|
19
|
+
$:
|
|
20
|
+
if ($isPresenting) {
|
|
21
|
+
immersiveFrame.start();
|
|
22
|
+
} else {
|
|
23
|
+
immersiveFrame.stop();
|
|
24
|
+
}
|
|
25
|
+
const nonImmersiveFrame = useFrame(() => {
|
|
26
|
+
group.position.copy(camera.current.position);
|
|
27
|
+
group.quaternion.copy(camera.current.quaternion);
|
|
28
|
+
}, { autostart: false });
|
|
29
|
+
$:
|
|
30
|
+
if ($isPresenting === false) {
|
|
31
|
+
nonImmersiveFrame.start();
|
|
32
|
+
} else {
|
|
33
|
+
nonImmersiveFrame.stop();
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<HierarchicalObject
|
|
38
|
+
onChildMount={(child) => { scene.add(child) }}
|
|
39
|
+
onChildDestroy={(child) => { scene.remove(child) }}
|
|
40
|
+
>
|
|
41
|
+
<T is={group}>
|
|
42
|
+
<slot />
|
|
43
|
+
</T>
|
|
44
|
+
</HierarchicalObject>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: Record<string, never>;
|
|
4
|
+
events: {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
};
|
|
7
|
+
slots: {
|
|
8
|
+
default: {};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type HeadsetProps = typeof __propDef.props;
|
|
12
|
+
export type HeadsetEvents = typeof __propDef.events;
|
|
13
|
+
export type HeadsetSlots = typeof __propDef.slots;
|
|
14
|
+
export default class Headset extends SvelteComponent<HeadsetProps, HeadsetEvents, HeadsetSlots> {
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>import { T } from "@threlte/core";
|
|
2
2
|
const vertexShader = `
|
|
3
|
-
uniform mat4 modelViewMatrix;
|
|
3
|
+
uniform mat4 modelViewMatrix;
|
|
4
4
|
uniform mat4 projectionMatrix;
|
|
5
5
|
attribute vec2 uv;
|
|
6
6
|
attribute vec3 position;
|
|
@@ -24,5 +24,9 @@ const fragmentShader = `
|
|
|
24
24
|
{@const heightSegments = 1}
|
|
25
25
|
{@const openEnded = false}
|
|
26
26
|
<T.CylinderGeometry args={[radius, radius, height, radialSegments, heightSegments, openEnded]} />
|
|
27
|
-
<T.RawShaderMaterial
|
|
27
|
+
<T.RawShaderMaterial
|
|
28
|
+
transparent
|
|
29
|
+
{vertexShader}
|
|
30
|
+
{fragmentShader}
|
|
31
|
+
/>
|
|
28
32
|
</T.Mesh>
|
|
@@ -97,7 +97,7 @@ const handleSelectEnd = () => {
|
|
|
97
97
|
useFrame(() => {
|
|
98
98
|
const selecting = (teleportGamepad.current?.axes[3] ?? 0) < -0.9;
|
|
99
99
|
if (selecting && activeController === void 0) {
|
|
100
|
-
handleSelectStart(teleportController.current.
|
|
100
|
+
handleSelectStart(teleportController.current.targetRay);
|
|
101
101
|
} else if (!selecting && activeController !== void 0) {
|
|
102
102
|
handleSelectEnd();
|
|
103
103
|
}
|
|
@@ -133,4 +133,4 @@ teleportPlugin(navMeshes);
|
|
|
133
133
|
polygonOffsetFactor={-1}
|
|
134
134
|
/>
|
|
135
135
|
</T.Mesh>
|
|
136
|
-
</slot>
|
|
136
|
+
</slot>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
|
|
3
|
-
@component
|
|
4
|
-
`<XR />` is a WebXR manager that configures your scene for XR rendering and interaction.
|
|
3
|
+
@component `<XR />` is a WebXR manager that configures your scene for XR rendering and interaction.
|
|
5
4
|
|
|
6
5
|
This should be placed within a Threlte `<Canvas />`.
|
|
7
6
|
|
|
@@ -18,10 +17,16 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
-->
|
|
21
|
-
|
|
22
20
|
<script>import { onDestroy } from "svelte";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
21
|
+
import { createRawEventDispatcher, useThrelte } from "@threlte/core";
|
|
22
|
+
import {
|
|
23
|
+
initialized,
|
|
24
|
+
isHandTracking,
|
|
25
|
+
isPresenting,
|
|
26
|
+
referenceSpaceType,
|
|
27
|
+
session,
|
|
28
|
+
xr as xrStore
|
|
29
|
+
} from "../internal/stores";
|
|
25
30
|
export let foveation = 1;
|
|
26
31
|
export let frameRate = void 0;
|
|
27
32
|
export let referenceSpace = "local-floor";
|
|
@@ -41,7 +46,7 @@ const handleVisibilityChange = (event) => {
|
|
|
41
46
|
dispatch("visibilitychange", { ...event, target: $session });
|
|
42
47
|
};
|
|
43
48
|
const handleInputSourcesChange = (event) => {
|
|
44
|
-
$isHandTracking = Object.values(
|
|
49
|
+
$isHandTracking = Object.values(event.session.inputSources).some((source) => source.hand);
|
|
45
50
|
dispatch("inputsourceschange", { ...event, target: $session });
|
|
46
51
|
};
|
|
47
52
|
const handleFramerateChange = (event) => {
|
|
@@ -111,4 +116,6 @@ $:
|
|
|
111
116
|
|
|
112
117
|
{#if $isPresenting}
|
|
113
118
|
<slot />
|
|
114
|
-
{
|
|
119
|
+
{:else}
|
|
120
|
+
<slot name="fallback" />
|
|
121
|
+
{/if}
|
package/dist/hooks/index.d.ts
CHANGED
package/dist/hooks/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
/**
|
|
3
|
-
* Returns a callback to teleport the player to a position.
|
|
3
|
+
* Returns a callback to teleport the player from the world origin to a position and optional orientation.
|
|
4
4
|
*
|
|
5
5
|
* @example
|
|
6
6
|
* const teleport = useTeleport()
|
|
@@ -9,5 +9,9 @@ import * as THREE from 'three';
|
|
|
9
9
|
* vec3.set(5, 0, 5)
|
|
10
10
|
*
|
|
11
11
|
* teleport(vec3)
|
|
12
|
+
*
|
|
13
|
+
* const quat = new THREE.Quaternion()
|
|
14
|
+
*
|
|
15
|
+
* teleport(vec3, quat)
|
|
12
16
|
*/
|
|
13
|
-
export declare const useTeleport: () => (position: THREE.Vector3 | THREE.Vector3Tuple,
|
|
17
|
+
export declare const useTeleport: () => (position: THREE.Vector3 | THREE.Vector3Tuple, orientation?: THREE.Quaternion) => void;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { useThrelte } from '@threlte/core';
|
|
3
|
-
import { session } from '../internal/stores';
|
|
4
|
-
import { onDestroy } from 'svelte';
|
|
5
3
|
const quaternion = new THREE.Quaternion();
|
|
6
4
|
const offset = { x: 0, y: 0, z: 0 };
|
|
7
5
|
/**
|
|
8
|
-
* Returns a callback to teleport the player to a position.
|
|
6
|
+
* Returns a callback to teleport the player from the world origin to a position and optional orientation.
|
|
9
7
|
*
|
|
10
8
|
* @example
|
|
11
9
|
* const teleport = useTeleport()
|
|
@@ -14,41 +12,37 @@ const offset = { x: 0, y: 0, z: 0 };
|
|
|
14
12
|
* vec3.set(5, 0, 5)
|
|
15
13
|
*
|
|
16
14
|
* teleport(vec3)
|
|
15
|
+
*
|
|
16
|
+
* const quat = new THREE.Quaternion()
|
|
17
|
+
*
|
|
18
|
+
* teleport(vec3, quat)
|
|
17
19
|
*/
|
|
18
20
|
export const useTeleport = () => {
|
|
19
21
|
const { xr } = useThrelte().renderer;
|
|
20
|
-
let
|
|
21
|
-
const unsub = session.subscribe((value) => {
|
|
22
|
-
if (value === undefined)
|
|
23
|
-
return;
|
|
24
|
-
baseReferenceSpace = xr.getReferenceSpace();
|
|
25
|
-
});
|
|
26
|
-
onDestroy(unsub);
|
|
22
|
+
let space = xr.getReferenceSpace();
|
|
27
23
|
/**
|
|
28
|
-
* Teleports a player to a position
|
|
24
|
+
* Teleports a player from the world origin to a position and optional orientation.
|
|
29
25
|
*/
|
|
30
|
-
return (position,
|
|
31
|
-
|
|
26
|
+
return (position, orientation = quaternion) => {
|
|
27
|
+
space ??= xr.getReferenceSpace();
|
|
28
|
+
if (space === null)
|
|
32
29
|
return;
|
|
33
|
-
let x = 0, y = 0, z = 0;
|
|
34
30
|
if (Array.isArray(position)) {
|
|
35
|
-
;
|
|
36
|
-
|
|
31
|
+
offset.x = -position[0];
|
|
32
|
+
offset.y = -position[1];
|
|
33
|
+
offset.z = -position[2];
|
|
37
34
|
}
|
|
38
35
|
else {
|
|
39
|
-
x = position.x;
|
|
40
|
-
y = position.y;
|
|
41
|
-
z = position.z;
|
|
36
|
+
offset.x = -position.x;
|
|
37
|
+
offset.y = -position.y;
|
|
38
|
+
offset.z = -position.z;
|
|
42
39
|
}
|
|
43
|
-
|
|
44
|
-
offset.y = -y;
|
|
45
|
-
offset.z = -z;
|
|
46
|
-
const pose = xr.getFrame().getViewerPose(baseReferenceSpace);
|
|
40
|
+
const pose = xr.getFrame()?.getViewerPose(space);
|
|
47
41
|
if (pose !== undefined) {
|
|
48
42
|
offset.x += pose.transform.position.x;
|
|
49
43
|
offset.z += pose.transform.position.z;
|
|
50
44
|
}
|
|
51
|
-
const teleportOffset = new XRRigidTransform(offset,
|
|
52
|
-
xr.setReferenceSpace(
|
|
45
|
+
const teleportOffset = new XRRigidTransform(offset, orientation);
|
|
46
|
+
xr.setReferenceSpace(space.getOffsetReferenceSpace(teleportOffset));
|
|
53
47
|
};
|
|
54
48
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { default as VRButton } from './components/VRButton.svelte';
|
|
|
3
3
|
export { default as XRButton } from './components/XRButton.svelte';
|
|
4
4
|
export { default as Controller } from './components/Controller.svelte';
|
|
5
5
|
export { default as Hand } from './components/Hand.svelte';
|
|
6
|
+
export { default as Headset } from './components/Headset.svelte';
|
|
6
7
|
export { default as TeleportControls } from './components/TeleportControls.svelte';
|
|
7
8
|
export { default as XR } from './components/XR.svelte';
|
|
8
9
|
export { getXRSupportState } from './lib/getXRSupportState';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { default as VRButton } from './components/VRButton.svelte';
|
|
|
3
3
|
export { default as XRButton } from './components/XRButton.svelte';
|
|
4
4
|
export { default as Controller } from './components/Controller.svelte';
|
|
5
5
|
export { default as Hand } from './components/Hand.svelte';
|
|
6
|
+
export { default as Headset } from './components/Headset.svelte';
|
|
6
7
|
export { default as TeleportControls } from './components/TeleportControls.svelte';
|
|
7
8
|
export { default as XR } from './components/XR.svelte';
|
|
8
9
|
export { getXRSupportState } from './lib/getXRSupportState';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useThrelte } from '@threlte/core';
|
|
2
|
+
/**
|
|
3
|
+
* There are some cases where we need to know if hand tracking is now active before an input source
|
|
4
|
+
* connection or disconnection event. This is the way to do that.
|
|
5
|
+
*/
|
|
6
|
+
export const useHandTrackingState = () => {
|
|
7
|
+
const { xr } = useThrelte().renderer;
|
|
8
|
+
return () => {
|
|
9
|
+
let handTracking = false;
|
|
10
|
+
xr.getSession()?.inputSources.forEach((value) => {
|
|
11
|
+
if (value.hand) {
|
|
12
|
+
handTracking = true;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return handTracking;
|
|
16
|
+
};
|
|
17
|
+
};
|
package/dist/types.d.ts
CHANGED
|
@@ -10,11 +10,11 @@ export type XRSessionEvent<Type = XRSessionEventType> = THREE.Event & {
|
|
|
10
10
|
};
|
|
11
11
|
export type XRControllerEvent<Type = XRControllerEventType> = THREE.Event & {
|
|
12
12
|
type: Type;
|
|
13
|
-
target:
|
|
14
|
-
data
|
|
13
|
+
target: THREE.Group;
|
|
14
|
+
data: XRInputSource;
|
|
15
15
|
};
|
|
16
16
|
export type XRController = {
|
|
17
|
-
|
|
17
|
+
targetRay: THREE.XRTargetRaySpace;
|
|
18
18
|
grip: THREE.XRGripSpace;
|
|
19
19
|
model?: XRControllerModel;
|
|
20
20
|
inputSource: XRInputSource;
|
|
@@ -24,8 +24,13 @@ export type XRHand = {
|
|
|
24
24
|
model?: XRHandModel;
|
|
25
25
|
inputSource: globalThis.XRHand;
|
|
26
26
|
};
|
|
27
|
-
export type XRHandEvent<Type = XRHandEventType
|
|
27
|
+
export type XRHandEvent<Type = XRHandEventType> = Type extends 'connected' | 'disconnected' ? {
|
|
28
28
|
type: Type;
|
|
29
|
-
target:
|
|
30
|
-
|
|
29
|
+
target: THREE.XRHandSpace;
|
|
30
|
+
data: XRInputSource;
|
|
31
|
+
} : Type extends 'pinchstart' | 'pinchend' ? {
|
|
32
|
+
type: Type;
|
|
33
|
+
handedness: 'left' | 'right';
|
|
34
|
+
target: null;
|
|
35
|
+
} : never;
|
|
31
36
|
export type HitTestCallback = (hitMatrix: THREE.Matrix4, hit: XRHitTestResult) => void;
|
package/package.json
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
Creates XRHand inputs for devices that allow hand tracking.
|
|
4
|
-
-->
|
|
5
|
-
<script>import { createRawEventDispatcher } from "@threlte/core";
|
|
6
|
-
import Hand from "./Hand.svelte";
|
|
7
|
-
export let profile = "mesh";
|
|
8
|
-
const dispatch = createRawEventDispatcher();
|
|
9
|
-
const handedness = [void 0, void 0];
|
|
10
|
-
const setHandedness = (index, event) => {
|
|
11
|
-
if (event.data) {
|
|
12
|
-
handedness[index] = event.data.handedness;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
{#each [0, 1] as index (index)}
|
|
18
|
-
<Hand
|
|
19
|
-
{index}
|
|
20
|
-
{profile}
|
|
21
|
-
on:connected={(event) => {
|
|
22
|
-
setHandedness(index, event)
|
|
23
|
-
dispatch(event.type, event)
|
|
24
|
-
}}
|
|
25
|
-
on:disconnected
|
|
26
|
-
on:pinchstart
|
|
27
|
-
on:pinchend
|
|
28
|
-
>
|
|
29
|
-
{#if handedness[index] === 'left'}
|
|
30
|
-
<slot name='left' />
|
|
31
|
-
{:else if handedness[index] === 'right'}
|
|
32
|
-
<slot name='right' />
|
|
33
|
-
{/if}
|
|
34
|
-
</Hand>
|
|
35
|
-
{/each}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { SvelteComponent } from "svelte";
|
|
2
|
-
import type { XRHandEvent } from '../types';
|
|
3
|
-
declare const __propDef: {
|
|
4
|
-
props: {
|
|
5
|
-
profile?: 'mesh' | 'spheres' | 'boxes' | 'none';
|
|
6
|
-
};
|
|
7
|
-
slots: {
|
|
8
|
-
left: {};
|
|
9
|
-
right: {};
|
|
10
|
-
};
|
|
11
|
-
events: {
|
|
12
|
-
connected: XRHandEvent<'connected', null>;
|
|
13
|
-
disconnected: XRHandEvent<'disconnected', null>;
|
|
14
|
-
pinchstart: XRHandEvent<'pinchstart', THREE.XRHandSpace>;
|
|
15
|
-
pinchend: XRHandEvent<'pinchend', THREE.XRHandSpace>;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
export type HandsProps = typeof __propDef.props;
|
|
19
|
-
export type HandsEvents = typeof __propDef.events;
|
|
20
|
-
export type HandsSlots = typeof __propDef.slots;
|
|
21
|
-
/** Creates XRHand inputs for devices that allow hand tracking. */
|
|
22
|
-
export default class Hands extends SvelteComponent<HandsProps, HandsEvents, HandsSlots> {
|
|
23
|
-
}
|
|
24
|
-
export {};
|
package/dist/hooks/useEvent.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/// <reference types="webxr" />
|
|
2
|
-
import type { XRControllerEventType, XRHandEventType, XRControllerEvent, XRHandEvent } from '../types';
|
|
3
|
-
/**
|
|
4
|
-
* Adds listeners for controller events.
|
|
5
|
-
*/
|
|
6
|
-
export declare const useControllerEvent: (event: XRControllerEventType, handler: (event: XRControllerEvent) => void, { handedness }?: {
|
|
7
|
-
handedness?: XRHandedness;
|
|
8
|
-
}) => void;
|
|
9
|
-
/**
|
|
10
|
-
* Adds listeners for hand events.
|
|
11
|
-
*/
|
|
12
|
-
export declare const useHandEvent: (event: XRHandEventType, handler: (event: XRHandEvent<XRHandEventType, null | THREE.XRHandSpace>) => void, { handedness }?: {
|
|
13
|
-
handedness?: 'left' | 'right';
|
|
14
|
-
}) => void;
|
package/dist/hooks/useEvent.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { onDestroy } from 'svelte';
|
|
2
|
-
import { isHandTracking } from '../internal/stores';
|
|
3
|
-
import { on, off } from '../internal/events';
|
|
4
|
-
/**
|
|
5
|
-
* Adds listeners for controller events.
|
|
6
|
-
*/
|
|
7
|
-
export const useControllerEvent = (event, handler, { handedness } = {}) => {
|
|
8
|
-
const listener = (event, metadata) => {
|
|
9
|
-
if (metadata?.input === 'hand')
|
|
10
|
-
return;
|
|
11
|
-
if (handedness !== undefined && event.data?.handedness !== handedness) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
handler(event);
|
|
15
|
-
};
|
|
16
|
-
on(event, listener);
|
|
17
|
-
onDestroy(() => off(event, listener));
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Adds listeners for hand events.
|
|
21
|
-
*/
|
|
22
|
-
export const useHandEvent = (event, handler, { handedness } = {}) => {
|
|
23
|
-
const listener = (event, metadata) => {
|
|
24
|
-
if (metadata?.input === 'controller')
|
|
25
|
-
return;
|
|
26
|
-
const eventHandedness = event.handedness ?? event.data?.handedness;
|
|
27
|
-
if (handedness !== undefined && eventHandedness !== handedness) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
handler(event);
|
|
31
|
-
};
|
|
32
|
-
on(event, listener);
|
|
33
|
-
onDestroy(() => off(event, listener));
|
|
34
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
type Callback<T = any, O = any> = (event: T, metadata?: O) => void;
|
|
2
|
-
export declare const on: <T, O = any>(name: string, cb: Callback<T, O>) => (() => void);
|
|
3
|
-
export declare const off: <T>(name: string, cb: Callback<T, any>) => void;
|
|
4
|
-
export declare const fire: <T, O = any>(name: string, payload: T, metadata?: O | undefined) => void;
|
|
5
|
-
export {};
|
package/dist/internal/events.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const events = {};
|
|
2
|
-
export const on = (name, cb) => {
|
|
3
|
-
const fns = events[name];
|
|
4
|
-
if (fns === undefined) {
|
|
5
|
-
events[name] = [cb];
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
fns.push(cb);
|
|
9
|
-
}
|
|
10
|
-
return () => off(name, cb);
|
|
11
|
-
};
|
|
12
|
-
export const off = (name, cb) => {
|
|
13
|
-
const arr = events[name];
|
|
14
|
-
if (arr === undefined)
|
|
15
|
-
return;
|
|
16
|
-
arr.splice(arr.indexOf(cb), 1);
|
|
17
|
-
if (arr.length === 0)
|
|
18
|
-
delete events[name];
|
|
19
|
-
};
|
|
20
|
-
export const fire = (name, payload, metadata) => {
|
|
21
|
-
const fns = events[name];
|
|
22
|
-
if (fns === undefined)
|
|
23
|
-
return;
|
|
24
|
-
for (const fn of fns)
|
|
25
|
-
fn(payload, metadata);
|
|
26
|
-
};
|