@threlte/xr 1.5.3 → 1.5.4
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 +3 -2
- package/dist/components/Hand.svelte +8 -5
- package/dist/components/XR.svelte +22 -9
- package/dist/components/XR.svelte.d.ts +3 -1
- package/dist/hooks/useHandJoint.svelte.js +10 -3
- package/dist/internal/setupHands.js +4 -1
- package/dist/internal/setupHeadset.svelte.js +3 -3
- package/dist/plugins/pointerControls/hook.js +2 -0
- package/dist/plugins/pointerControls/plugin.svelte.js +1 -5
- package/dist/plugins/teleportControls/context.js +4 -0
- package/package.json +2 -2
|
@@ -75,7 +75,8 @@
|
|
|
75
75
|
const handedness = $derived<'left' | 'right'>(left ? 'left' : right ? 'right' : (hand ?? 'left'))
|
|
76
76
|
|
|
77
77
|
$effect.pre(() => {
|
|
78
|
-
|
|
78
|
+
const key = handedness
|
|
79
|
+
controllerEvents[key] = {
|
|
79
80
|
onconnected,
|
|
80
81
|
ondisconnected,
|
|
81
82
|
onselect,
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
return () => {
|
|
90
|
-
controllerEvents[
|
|
91
|
+
controllerEvents[key] = undefined
|
|
91
92
|
}
|
|
92
93
|
})
|
|
93
94
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Group } from 'three'
|
|
3
|
-
import { T, useThrelte, useTask } from '@threlte/core'
|
|
3
|
+
import { T, useThrelte, useTask, useStage } from '@threlte/core'
|
|
4
4
|
import type { XRHandEvents } from '../types.js'
|
|
5
5
|
import { isHandTracking, handEvents } from '../internal/state.svelte.js'
|
|
6
6
|
import { hands } from '../hooks/useHand.svelte.js'
|
|
@@ -45,12 +45,13 @@
|
|
|
45
45
|
wrist
|
|
46
46
|
}: Props = $props()
|
|
47
47
|
|
|
48
|
-
const { scene, renderer,
|
|
48
|
+
const { scene, renderer, renderStage } = useThrelte()
|
|
49
49
|
|
|
50
50
|
const handedness = $derived<'left' | 'right'>(left ? 'left' : right ? 'right' : (hand ?? 'left'))
|
|
51
51
|
|
|
52
52
|
$effect.pre(() => {
|
|
53
|
-
|
|
53
|
+
const key = handedness
|
|
54
|
+
handEvents[key] = {
|
|
54
55
|
onconnected,
|
|
55
56
|
ondisconnected,
|
|
56
57
|
onpinchend,
|
|
@@ -58,10 +59,12 @@
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
return () => {
|
|
61
|
-
handEvents[
|
|
62
|
+
handEvents[key] = undefined
|
|
62
63
|
}
|
|
63
64
|
})
|
|
64
65
|
|
|
66
|
+
const stage = useStage(Symbol('xr-hand-stage'), { before: renderStage })
|
|
67
|
+
|
|
65
68
|
const group = new Group()
|
|
66
69
|
|
|
67
70
|
/**
|
|
@@ -90,7 +93,7 @@
|
|
|
90
93
|
group.quaternion.set(orientation.x, orientation.y, orientation.z, orientation.w)
|
|
91
94
|
},
|
|
92
95
|
{
|
|
93
|
-
stage
|
|
96
|
+
stage,
|
|
94
97
|
running: () =>
|
|
95
98
|
isHandTracking.current &&
|
|
96
99
|
(wrist !== undefined || children !== undefined) &&
|
|
@@ -21,7 +21,7 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
21
21
|
import type { EventListener, WebXRManager, Event as ThreeEvent } from 'three'
|
|
22
22
|
import type { XRHandModelFactory } from 'three/examples/jsm/webxr/XRHandModelFactory.js'
|
|
23
23
|
import type { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js'
|
|
24
|
-
import type
|
|
24
|
+
import { untrack, type Snippet } from 'svelte'
|
|
25
25
|
import { useThrelte } from '@threlte/core'
|
|
26
26
|
import {
|
|
27
27
|
isHandTracking,
|
|
@@ -78,6 +78,9 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
78
78
|
|
|
79
79
|
/** Called when available inputsources change */
|
|
80
80
|
oninputsourceschange?: (event: XRSessionEvent) => void
|
|
81
|
+
|
|
82
|
+
/** Called when the session frame rate changes. */
|
|
83
|
+
onframeratechange?: (event: XRSessionEvent) => void
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
let {
|
|
@@ -88,6 +91,7 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
88
91
|
onsessionend,
|
|
89
92
|
onvisibilitychange,
|
|
90
93
|
oninputsourceschange,
|
|
94
|
+
onframeratechange,
|
|
91
95
|
fallback,
|
|
92
96
|
children,
|
|
93
97
|
handFactory,
|
|
@@ -96,8 +100,6 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
96
100
|
|
|
97
101
|
const { renderer, renderMode } = useThrelte()
|
|
98
102
|
|
|
99
|
-
let originalRenderMode = $renderMode
|
|
100
|
-
|
|
101
103
|
setupRaf()
|
|
102
104
|
setupHeadset()
|
|
103
105
|
setupControllers(controllerFactory)
|
|
@@ -105,12 +107,19 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
105
107
|
|
|
106
108
|
const handleSessionStart: EventListener<object, 'sessionstart', WebXRManager> = (event) => {
|
|
107
109
|
isPresenting.current = true
|
|
110
|
+
const currentSession = renderer.xr.getSession()
|
|
111
|
+
if (currentSession !== null) {
|
|
112
|
+
isHandTracking.current = Array.from(currentSession.inputSources).some(
|
|
113
|
+
(source) => source.hand !== undefined
|
|
114
|
+
)
|
|
115
|
+
}
|
|
108
116
|
onsessionstart?.(event)
|
|
109
117
|
}
|
|
110
118
|
|
|
111
119
|
const handleSessionEnd = (event: XRSessionEvent) => {
|
|
112
120
|
onsessionend?.(event)
|
|
113
121
|
isPresenting.current = false
|
|
122
|
+
isHandTracking.current = false
|
|
114
123
|
session.current = undefined
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -124,7 +133,7 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
const handleFramerateChange = (event: XRSessionEvent) => {
|
|
127
|
-
|
|
136
|
+
onframeratechange?.(event)
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
$effect(() => {
|
|
@@ -148,11 +157,15 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
148
157
|
})
|
|
149
158
|
|
|
150
159
|
$effect.pre(() => {
|
|
151
|
-
if (isPresenting.current)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
if (!isPresenting.current) return
|
|
161
|
+
|
|
162
|
+
// Capture the mode from before we forced 'always' so it survives
|
|
163
|
+
// any manual renderMode changes made during the session.
|
|
164
|
+
const saved = untrack(() => renderMode.current)
|
|
165
|
+
renderMode.set('always')
|
|
166
|
+
|
|
167
|
+
return () => {
|
|
168
|
+
renderMode.set(saved)
|
|
156
169
|
}
|
|
157
170
|
})
|
|
158
171
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WebXRManager, Event as ThreeEvent } from 'three';
|
|
2
2
|
import type { XRHandModelFactory } from 'three/examples/jsm/webxr/XRHandModelFactory.js';
|
|
3
3
|
import type { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type Snippet } from 'svelte';
|
|
5
5
|
interface Props {
|
|
6
6
|
/**
|
|
7
7
|
* Enables foveated rendering. Default is `1`, the three.js default.
|
|
@@ -36,6 +36,8 @@ interface Props {
|
|
|
36
36
|
onvisibilitychange?: (event: XRSessionEvent) => void;
|
|
37
37
|
/** Called when available inputsources change */
|
|
38
38
|
oninputsourceschange?: (event: XRSessionEvent) => void;
|
|
39
|
+
/** Called when the session frame rate changes. */
|
|
40
|
+
onframeratechange?: (event: XRSessionEvent) => void;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
43
|
* `<XR />` is a WebXR manager that configures your scene for XR rendering and interaction.
|
|
@@ -10,11 +10,18 @@ export const useHandJoint = (handedness, joint) => {
|
|
|
10
10
|
let jointSpace = $state.raw();
|
|
11
11
|
useTask(() => {
|
|
12
12
|
const space = xrhand?.hand.joints[joint];
|
|
13
|
-
// The joint radius is a good indicator that the joint is ready
|
|
13
|
+
// The joint radius is a good indicator that the joint is ready.
|
|
14
|
+
// Re-check each frame so we pick up reconnects and clear on disconnect.
|
|
14
15
|
if (space?.jointRadius !== undefined) {
|
|
15
|
-
jointSpace
|
|
16
|
+
if (jointSpace !== space) {
|
|
17
|
+
jointSpace = space;
|
|
18
|
+
invalidate();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (jointSpace !== undefined) {
|
|
22
|
+
jointSpace = undefined;
|
|
16
23
|
invalidate();
|
|
17
24
|
}
|
|
18
|
-
}
|
|
25
|
+
});
|
|
19
26
|
return toCurrentReadable(() => jointSpace);
|
|
20
27
|
};
|
|
@@ -42,7 +42,10 @@ export const setupHands = (factory) => {
|
|
|
42
42
|
}
|
|
43
43
|
const handleDisconnected = (event) => {
|
|
44
44
|
dispatch(event);
|
|
45
|
-
|
|
45
|
+
const { handedness } = event.data;
|
|
46
|
+
if (handedness === 'left' || handedness === 'right') {
|
|
47
|
+
hands[handedness] = undefined;
|
|
48
|
+
}
|
|
46
49
|
};
|
|
47
50
|
for (const handSpace of handSpaces) {
|
|
48
51
|
handSpace.addEventListener('connected', handleConnected);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Group } from 'three';
|
|
2
|
-
import { useThrelte, useTask } from '@threlte/core';
|
|
2
|
+
import { useThrelte, useTask, useStage } from '@threlte/core';
|
|
3
3
|
import { isPresenting } from './state.svelte.js';
|
|
4
4
|
export const headset = new Group();
|
|
5
5
|
export const setupHeadset = () => {
|
|
6
|
-
const { renderer, camera,
|
|
6
|
+
const { renderer, camera, renderStage } = useThrelte();
|
|
7
|
+
const stage = useStage(Symbol('xr-headset-stage'), { before: renderStage });
|
|
7
8
|
const { xr } = renderer;
|
|
8
|
-
const stage = scheduler.createStage(Symbol('xr-headset-stage'), { before: renderStage });
|
|
9
9
|
useTask(() => {
|
|
10
10
|
const space = xr.getReferenceSpace();
|
|
11
11
|
if (space === null)
|
|
@@ -15,6 +15,8 @@ export const usePointerControls = () => {
|
|
|
15
15
|
};
|
|
16
16
|
const removeInteractiveObject = (object) => {
|
|
17
17
|
const index = context.interactiveObjects.indexOf(object);
|
|
18
|
+
if (index === -1)
|
|
19
|
+
return;
|
|
18
20
|
context.interactiveObjects.splice(index, 1);
|
|
19
21
|
dispatchers.delete(object);
|
|
20
22
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { injectPlugin, isInstanceOf
|
|
1
|
+
import { injectPlugin, isInstanceOf } from '@threlte/core';
|
|
2
2
|
import { usePointerControls } from './hook.js';
|
|
3
3
|
import { events } from './types.js';
|
|
4
4
|
export const injectPointerControlsPlugin = () => {
|
|
@@ -22,10 +22,6 @@ export const injectPointerControlsPlugin = () => {
|
|
|
22
22
|
removeInteractiveObject(ref);
|
|
23
23
|
};
|
|
24
24
|
});
|
|
25
|
-
observe.pre(() => [args.ref], ([ref]) => {
|
|
26
|
-
addInteractiveObject(ref, args.props);
|
|
27
|
-
return () => removeInteractiveObject(ref);
|
|
28
|
-
});
|
|
29
25
|
return {
|
|
30
26
|
pluginProps: events
|
|
31
27
|
};
|
|
@@ -27,6 +27,8 @@ export const createTeleportContext = (compute) => {
|
|
|
27
27
|
};
|
|
28
28
|
const removeSurface = (mesh) => {
|
|
29
29
|
const index = context.interactiveObjects.indexOf(mesh);
|
|
30
|
+
if (index === -1)
|
|
31
|
+
return;
|
|
30
32
|
context.interactiveObjects.splice(index, 1);
|
|
31
33
|
context.surfaces.delete(mesh.uuid);
|
|
32
34
|
context.dispatchers.delete(mesh);
|
|
@@ -41,6 +43,8 @@ export const createTeleportContext = (compute) => {
|
|
|
41
43
|
};
|
|
42
44
|
const removeBlocker = (mesh) => {
|
|
43
45
|
const index = context.interactiveObjects.indexOf(mesh);
|
|
46
|
+
if (index === -1)
|
|
47
|
+
return;
|
|
44
48
|
context.interactiveObjects.splice(index, 1);
|
|
45
49
|
context.blockers.delete(mesh.uuid);
|
|
46
50
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threlte/xr",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"author": "Micheal Parks <michealparks1989@gmail.com> (https://parks.lol)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Tools to more easily create VR and AR experiences with Threlte",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"typescript-eslint": "^8.32.0",
|
|
29
29
|
"vite": "^7.1.4",
|
|
30
30
|
"vite-plugin-mkcert": "^1.17.5",
|
|
31
|
-
"@threlte/core": "8.5.
|
|
31
|
+
"@threlte/core": "8.5.9"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"svelte": ">=5",
|