@threlte/xr 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ARButton.svelte.d.ts +11 -24
- package/dist/components/Controller.svelte +31 -27
- package/dist/components/Controller.svelte.d.ts +7 -5
- package/dist/components/Hand.svelte +24 -28
- package/dist/components/Hand.svelte.d.ts +6 -4
- package/dist/components/Headset.svelte.d.ts +4 -2
- package/dist/components/VRButton.svelte.d.ts +5 -18
- package/dist/components/XR.svelte +44 -43
- package/dist/components/XR.svelte.d.ts +24 -22
- package/dist/components/XRButton.svelte +3 -3
- package/dist/components/XRButton.svelte.d.ts +26 -23
- package/dist/components/internal/Cursor.svelte.d.ts +4 -2
- package/dist/components/internal/PointerCursor.svelte +8 -7
- package/dist/components/internal/PointerCursor.svelte.d.ts +5 -3
- package/dist/components/internal/ShortRay.svelte +3 -3
- package/dist/components/internal/ShortRay.svelte.d.ts +5 -3
- package/dist/components/internal/TeleportCursor.svelte +8 -7
- package/dist/components/internal/TeleportCursor.svelte.d.ts +5 -3
- package/dist/components/internal/TeleportRay.svelte +5 -5
- package/dist/components/internal/TeleportRay.svelte.d.ts +5 -3
- package/dist/hooks/currentReadable.svelte.d.ts +5 -0
- package/dist/hooks/currentReadable.svelte.js +11 -0
- package/dist/hooks/useController.svelte.d.ts +13 -0
- package/dist/hooks/useController.svelte.js +22 -0
- package/dist/hooks/useHand.svelte.d.ts +12 -0
- package/dist/hooks/{useHand.js → useHand.svelte.js} +8 -5
- package/dist/hooks/{useHandJoint.d.ts → useHandJoint.svelte.d.ts} +1 -1
- package/dist/hooks/useHandJoint.svelte.js +21 -0
- package/dist/hooks/useHeadset.js +1 -1
- package/dist/hooks/useHitTest.svelte.js +67 -0
- package/dist/hooks/useTeleport.js +1 -2
- package/dist/hooks/useXR.d.ts +6 -7
- package/dist/hooks/useXR.js +10 -12
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/raf.d.ts +1 -0
- package/dist/internal/raf.js +2 -0
- package/dist/internal/setupControllers.js +13 -14
- package/dist/internal/setupHands.js +8 -9
- package/dist/internal/{setupHeadset.js → setupHeadset.svelte.js} +6 -6
- package/dist/internal/setupRaf.svelte.js +17 -0
- package/dist/internal/state.svelte.d.ts +51 -0
- package/dist/internal/state.svelte.js +40 -0
- package/dist/internal/useHandTrackingState.js +10 -7
- package/dist/lib/getXRSessionOptions.d.ts +1 -1
- package/dist/lib/toggleXRSession.d.ts +3 -3
- package/dist/lib/toggleXRSession.js +3 -3
- package/dist/plugins/pointerControls/compute.js +2 -6
- package/dist/plugins/pointerControls/index.js +4 -10
- package/dist/plugins/pointerControls/{setup.js → setup.svelte.js} +18 -19
- package/dist/plugins/teleportControls/compute.js +2 -6
- package/dist/plugins/teleportControls/index.js +4 -10
- package/dist/plugins/teleportControls/{setup.js → setup.svelte.js} +9 -10
- package/package.json +8 -8
- package/dist/hooks/useController.d.ts +0 -9
- package/dist/hooks/useController.js +0 -19
- package/dist/hooks/useHand.d.ts +0 -8
- package/dist/hooks/useHandJoint.js +0 -20
- package/dist/hooks/useHitTest.js +0 -79
- package/dist/internal/setupRaf.js +0 -16
- package/dist/internal/stores.d.ts +0 -43
- package/dist/internal/stores.js +0 -42
- /package/dist/hooks/{useHitTest.d.ts → useHitTest.svelte.d.ts} +0 -0
- /package/dist/internal/{setupHeadset.d.ts → setupHeadset.svelte.d.ts} +0 -0
- /package/dist/internal/{setupRaf.d.ts → setupRaf.svelte.d.ts} +0 -0
- /package/dist/plugins/pointerControls/{setup.d.ts → setup.svelte.d.ts} +0 -0
- /package/dist/plugins/teleportControls/{setup.d.ts → setup.svelte.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Group } from 'three';
|
|
2
|
-
import { useThrelte, useTask
|
|
3
|
-
import {
|
|
2
|
+
import { useThrelte, useTask } from '@threlte/core';
|
|
3
|
+
import { isPresenting } from './state.svelte';
|
|
4
4
|
export const headset = new Group();
|
|
5
5
|
export const setupHeadset = () => {
|
|
6
6
|
const { renderer, camera, scheduler, renderStage } = useThrelte();
|
|
@@ -16,8 +16,8 @@ export const setupHeadset = () => {
|
|
|
16
16
|
if (pose === undefined || pose === null)
|
|
17
17
|
return;
|
|
18
18
|
const { position, orientation } = pose.transform;
|
|
19
|
-
headset.position.
|
|
20
|
-
headset.quaternion.
|
|
19
|
+
headset.position.copy(position);
|
|
20
|
+
headset.quaternion.copy(orientation);
|
|
21
21
|
}, {
|
|
22
22
|
autoStart: false,
|
|
23
23
|
autoInvalidate: false,
|
|
@@ -31,8 +31,8 @@ export const setupHeadset = () => {
|
|
|
31
31
|
autoInvalidate: false,
|
|
32
32
|
stage
|
|
33
33
|
});
|
|
34
|
-
|
|
35
|
-
if (
|
|
34
|
+
$effect.pre(() => {
|
|
35
|
+
if (isPresenting.current) {
|
|
36
36
|
immersiveFrame.start();
|
|
37
37
|
nonImmersiveFrame.stop();
|
|
38
38
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { session } from './state.svelte';
|
|
2
|
+
import { raf } from './raf';
|
|
3
|
+
export const setupRaf = () => {
|
|
4
|
+
if (typeof window === 'undefined')
|
|
5
|
+
return;
|
|
6
|
+
$effect.pre(() => {
|
|
7
|
+
const currentSession = session.current;
|
|
8
|
+
if (currentSession === undefined) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const originalTick = raf.tick;
|
|
12
|
+
raf.tick = (fn) => currentSession.requestAnimationFrame(fn);
|
|
13
|
+
return () => {
|
|
14
|
+
raf.tick = originalTick;
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { WebXRManager, Intersection } from 'three';
|
|
2
|
+
import type { XRControllerEvents, XRHandEvents } from '../types';
|
|
3
|
+
interface ControllerEvents {
|
|
4
|
+
left?: XRControllerEvents;
|
|
5
|
+
right?: XRControllerEvents;
|
|
6
|
+
}
|
|
7
|
+
interface HandEvents {
|
|
8
|
+
left?: XRHandEvents;
|
|
9
|
+
right?: XRHandEvents;
|
|
10
|
+
}
|
|
11
|
+
declare class Presenting {
|
|
12
|
+
current: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare class IsHandTracking {
|
|
15
|
+
current: boolean;
|
|
16
|
+
}
|
|
17
|
+
declare class Session {
|
|
18
|
+
current: XRSession | undefined;
|
|
19
|
+
}
|
|
20
|
+
declare class ReferenceSpaceType {
|
|
21
|
+
current: XRReferenceSpaceType | undefined;
|
|
22
|
+
}
|
|
23
|
+
declare class XR {
|
|
24
|
+
current: WebXRManager | undefined;
|
|
25
|
+
}
|
|
26
|
+
declare class PointerState {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
hovering: boolean;
|
|
29
|
+
}
|
|
30
|
+
declare class IntersectionState {
|
|
31
|
+
left: Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined;
|
|
32
|
+
right: Intersection<import("three").Object3D<import("three").Object3DEventMap>> | undefined;
|
|
33
|
+
}
|
|
34
|
+
export declare const isPresenting: Presenting;
|
|
35
|
+
export declare const isHandTracking: IsHandTracking;
|
|
36
|
+
export declare const session: Session;
|
|
37
|
+
export declare const referenceSpaceType: ReferenceSpaceType;
|
|
38
|
+
export declare const xr: XR;
|
|
39
|
+
export declare const controllerEvents: ControllerEvents;
|
|
40
|
+
export declare const handEvents: HandEvents;
|
|
41
|
+
export declare const teleportState: {
|
|
42
|
+
left: PointerState;
|
|
43
|
+
right: PointerState;
|
|
44
|
+
};
|
|
45
|
+
export declare const pointerState: {
|
|
46
|
+
left: PointerState;
|
|
47
|
+
right: PointerState;
|
|
48
|
+
};
|
|
49
|
+
export declare const teleportIntersection: IntersectionState;
|
|
50
|
+
export declare const pointerIntersection: IntersectionState;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class Presenting {
|
|
2
|
+
current = $state(false);
|
|
3
|
+
}
|
|
4
|
+
class IsHandTracking {
|
|
5
|
+
current = $state(false);
|
|
6
|
+
}
|
|
7
|
+
class Session {
|
|
8
|
+
current = $state.raw();
|
|
9
|
+
}
|
|
10
|
+
class ReferenceSpaceType {
|
|
11
|
+
current = $state.raw();
|
|
12
|
+
}
|
|
13
|
+
class XR {
|
|
14
|
+
current = $state.raw();
|
|
15
|
+
}
|
|
16
|
+
class PointerState {
|
|
17
|
+
enabled = $state(false);
|
|
18
|
+
hovering = $state(false);
|
|
19
|
+
}
|
|
20
|
+
class IntersectionState {
|
|
21
|
+
left = $state.raw();
|
|
22
|
+
right = $state.raw();
|
|
23
|
+
}
|
|
24
|
+
export const isPresenting = new Presenting();
|
|
25
|
+
export const isHandTracking = new IsHandTracking();
|
|
26
|
+
export const session = new Session();
|
|
27
|
+
export const referenceSpaceType = new ReferenceSpaceType();
|
|
28
|
+
export const xr = new XR();
|
|
29
|
+
export const controllerEvents = {};
|
|
30
|
+
export const handEvents = {};
|
|
31
|
+
export const teleportState = {
|
|
32
|
+
left: new PointerState(),
|
|
33
|
+
right: new PointerState()
|
|
34
|
+
};
|
|
35
|
+
export const pointerState = {
|
|
36
|
+
left: new PointerState(),
|
|
37
|
+
right: new PointerState()
|
|
38
|
+
};
|
|
39
|
+
export const teleportIntersection = new IntersectionState();
|
|
40
|
+
export const pointerIntersection = new IntersectionState();
|
|
@@ -4,14 +4,17 @@ import { useThrelte } from '@threlte/core';
|
|
|
4
4
|
* connection or disconnection event. This is the way to do that.
|
|
5
5
|
*/
|
|
6
6
|
export const useHandTrackingState = () => {
|
|
7
|
-
const {
|
|
7
|
+
const { renderer } = useThrelte();
|
|
8
8
|
return () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const sources = renderer.xr.getSession()?.inputSources;
|
|
10
|
+
if (sources === undefined) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
for (const source of sources) {
|
|
14
|
+
if (source.hand !== undefined) {
|
|
15
|
+
return true;
|
|
13
16
|
}
|
|
14
|
-
}
|
|
15
|
-
return
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
16
19
|
};
|
|
17
20
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const getXRSessionOptions: (referenceSpaceType
|
|
1
|
+
export declare const getXRSessionOptions: (referenceSpaceType?: XRReferenceSpaceType, sessionInit?: XRSessionInit) => XRSessionInit | undefined;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* @param force Whether this button should only enter / exit an `XRSession`. Default is to toggle both ways
|
|
7
7
|
* @returns
|
|
8
8
|
*/
|
|
9
|
-
export declare const toggleXRSession: (sessionMode: XRSessionMode, sessionInit?:
|
|
9
|
+
export declare const toggleXRSession: (sessionMode: XRSessionMode, sessionInit?: XRSessionInit & {
|
|
10
10
|
domOverlay?: {
|
|
11
11
|
root: HTMLElement;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
12
|
+
};
|
|
13
|
+
}, force?: "enter" | "exit") => Promise<XRSession | undefined>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { session, referenceSpaceType, xr } from '../internal/
|
|
1
|
+
import { session, referenceSpaceType, xr } from '../internal/state.svelte';
|
|
2
2
|
import { getXRSessionOptions } from './getXRSessionOptions';
|
|
3
3
|
/**
|
|
4
4
|
* Starts / ends an XR session.
|
|
@@ -18,7 +18,7 @@ export const toggleXRSession = async (sessionMode, sessionInit, force) => {
|
|
|
18
18
|
// Exit a session if entered
|
|
19
19
|
if (hasSession) {
|
|
20
20
|
await currentSession.end();
|
|
21
|
-
session.
|
|
21
|
+
session.current = undefined;
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (xr.current === undefined) {
|
|
@@ -28,6 +28,6 @@ export const toggleXRSession = async (sessionMode, sessionInit, force) => {
|
|
|
28
28
|
const options = getXRSessionOptions(referenceSpaceType.current, sessionInit);
|
|
29
29
|
const nextSession = await navigator.xr.requestSession(sessionMode, options);
|
|
30
30
|
await xr.current.setSession(nextSession);
|
|
31
|
-
session.
|
|
31
|
+
session.current = nextSession;
|
|
32
32
|
return nextSession;
|
|
33
33
|
};
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { Vector3 } from 'three';
|
|
2
|
-
import {
|
|
3
|
-
const controllers = {
|
|
4
|
-
left: useController('left'),
|
|
5
|
-
right: useController('right')
|
|
6
|
-
};
|
|
2
|
+
import { controllers } from '../../hooks/useController.svelte';
|
|
7
3
|
const forward = new Vector3();
|
|
8
4
|
export const defaultComputeFunction = (context, handContext) => {
|
|
9
|
-
const targetRay = controllers[handContext.hand]
|
|
5
|
+
const targetRay = controllers[handContext.hand]?.targetRay;
|
|
10
6
|
if (targetRay === undefined)
|
|
11
7
|
return;
|
|
12
8
|
forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
|
|
@@ -2,9 +2,9 @@ import { Raycaster, Vector3 } from 'three';
|
|
|
2
2
|
import { currentWritable, watch } from '@threlte/core';
|
|
3
3
|
import { defaultComputeFunction } from './compute';
|
|
4
4
|
import { injectPointerControlsPlugin } from './plugin.svelte';
|
|
5
|
-
import { setupPointerControls } from './setup';
|
|
5
|
+
import { setupPointerControls } from './setup.svelte';
|
|
6
6
|
import { getControlsContext, getHandContext, setControlsContext, setHandContext, setInternalContext } from './context';
|
|
7
|
-
import { pointerState } from '../../internal/
|
|
7
|
+
import { pointerState } from '../../internal/state.svelte';
|
|
8
8
|
let controlsCounter = 0;
|
|
9
9
|
export const pointerControls = (handedness, options) => {
|
|
10
10
|
if (getControlsContext() === undefined) {
|
|
@@ -36,16 +36,10 @@ export const pointerControls = (handedness, options) => {
|
|
|
36
36
|
const handContext = getHandContext(handedness);
|
|
37
37
|
watch(handContext.enabled, (enabled) => {
|
|
38
38
|
controlsCounter += enabled ? 1 : -1;
|
|
39
|
-
pointerState.
|
|
40
|
-
value[handedness].enabled = controlsCounter > 0;
|
|
41
|
-
return value;
|
|
42
|
-
});
|
|
39
|
+
pointerState[handedness].enabled = controlsCounter > 0;
|
|
43
40
|
});
|
|
44
41
|
watch(handContext.pointerOverTarget, (hovering) => {
|
|
45
|
-
pointerState.
|
|
46
|
-
value[handedness].hovering = hovering;
|
|
47
|
-
return value;
|
|
48
|
-
});
|
|
42
|
+
pointerState[handedness].hovering = hovering;
|
|
49
43
|
});
|
|
50
44
|
return {
|
|
51
45
|
enabled: handContext.enabled,
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Vector3 } from 'three';
|
|
2
|
-
import { watch } from '@threlte/core';
|
|
2
|
+
import { observe, watch } from '@threlte/core';
|
|
3
3
|
import { getInternalContext } from './context';
|
|
4
|
-
import {
|
|
5
|
-
import { useHand } from '../../hooks/useHand';
|
|
6
|
-
import { useXR } from '../../hooks/useXR';
|
|
4
|
+
import { controllers } from '../../hooks/useController.svelte';
|
|
5
|
+
import { useHand } from '../../hooks/useHand.svelte';
|
|
7
6
|
import { useFixed } from '../../internal/useFixed';
|
|
8
|
-
import { pointerIntersection } from '../../internal/
|
|
7
|
+
import { isPresenting, pointerIntersection } from '../../internal/state.svelte';
|
|
9
8
|
const getIntersectionId = (intersection) => {
|
|
10
9
|
return `${(intersection.eventObject || intersection.object).uuid}/${intersection.index}${intersection.instanceId ?? ''}`;
|
|
11
10
|
};
|
|
12
11
|
const EPSILON = 0.0001;
|
|
13
12
|
export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) => {
|
|
14
13
|
const handedness = handContext.hand;
|
|
15
|
-
const controller =
|
|
14
|
+
const controller = $derived(controllers[handedness]);
|
|
16
15
|
const hand = useHand(handedness);
|
|
17
16
|
const { dispatchers } = getInternalContext();
|
|
18
17
|
let hits = [];
|
|
@@ -64,7 +63,7 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
64
63
|
const intersections = [];
|
|
65
64
|
const hits = context.raycaster.intersectObjects(context.interactiveObjects, true);
|
|
66
65
|
const filtered = context.filter === undefined ? hits : context.filter(hits, context, handContext);
|
|
67
|
-
pointerIntersection[handedness]
|
|
66
|
+
pointerIntersection[handedness] = filtered[0];
|
|
68
67
|
// Bubble up the events, find the event source (eventObject)
|
|
69
68
|
for (const hit of filtered) {
|
|
70
69
|
let eventObject = hit.object;
|
|
@@ -158,7 +157,7 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
158
157
|
};
|
|
159
158
|
const { start, stop } = useFixed(() => {
|
|
160
159
|
hits = processHits();
|
|
161
|
-
const targetRay = controller
|
|
160
|
+
const targetRay = controller?.targetRay;
|
|
162
161
|
if (targetRay === undefined)
|
|
163
162
|
return;
|
|
164
163
|
if (targetRay.position.distanceTo(lastPosition) > EPSILON) {
|
|
@@ -169,18 +168,18 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
169
168
|
fixedStep,
|
|
170
169
|
autoStart: false
|
|
171
170
|
});
|
|
172
|
-
|
|
173
|
-
if (
|
|
171
|
+
observe.pre(() => [controller, handContext.enabled], ([controller, $enabled]) => {
|
|
172
|
+
if (controller === undefined)
|
|
174
173
|
return;
|
|
175
174
|
const removeHandlers = () => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
controller.targetRay.removeEventListener('selectstart', handlePointerDown);
|
|
176
|
+
controller.targetRay.removeEventListener('selectend', handlePointerUp);
|
|
177
|
+
controller.targetRay.removeEventListener('select', handleClick);
|
|
179
178
|
};
|
|
180
|
-
if (enabled) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
if ($enabled) {
|
|
180
|
+
controller.targetRay.addEventListener('selectstart', handlePointerDown);
|
|
181
|
+
controller.targetRay.addEventListener('selectend', handlePointerUp);
|
|
182
|
+
controller.targetRay.addEventListener('select', handleClick);
|
|
184
183
|
return removeHandlers;
|
|
185
184
|
}
|
|
186
185
|
else {
|
|
@@ -207,8 +206,8 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
207
206
|
return;
|
|
208
207
|
}
|
|
209
208
|
});
|
|
210
|
-
|
|
211
|
-
if (isPresenting && enabled) {
|
|
209
|
+
observe.pre(() => [isPresenting.current, handContext.enabled], ([isPresenting, $enabled]) => {
|
|
210
|
+
if (isPresenting && $enabled) {
|
|
212
211
|
start();
|
|
213
212
|
}
|
|
214
213
|
else {
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { Vector3 } from 'three';
|
|
2
|
-
import {
|
|
3
|
-
const controllers = {
|
|
4
|
-
left: useController('left'),
|
|
5
|
-
right: useController('right')
|
|
6
|
-
};
|
|
2
|
+
import { controllers } from '../../hooks/useController.svelte';
|
|
7
3
|
const forward = new Vector3();
|
|
8
4
|
export const defaultComputeFunction = (context, handContext) => {
|
|
9
|
-
const targetRay = controllers[handContext.hand]
|
|
5
|
+
const targetRay = controllers[handContext.hand]?.targetRay;
|
|
10
6
|
if (targetRay === undefined)
|
|
11
7
|
return;
|
|
12
8
|
forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
|
|
@@ -2,8 +2,8 @@ import { currentWritable, watch } from '@threlte/core';
|
|
|
2
2
|
import { createTeleportContext, useTeleportControls, getHandContext } from './context';
|
|
3
3
|
import { injectTeleportControlsPlugin } from './plugin.svelte';
|
|
4
4
|
import { setHandContext } from './context';
|
|
5
|
-
import { setupTeleportControls } from './setup';
|
|
6
|
-
import { teleportState } from '../../internal/
|
|
5
|
+
import { setupTeleportControls } from './setup.svelte';
|
|
6
|
+
import { teleportState } from '../../internal/state.svelte';
|
|
7
7
|
let controlsCounter = 0;
|
|
8
8
|
export const teleportControls = (handedness, options) => {
|
|
9
9
|
if (useTeleportControls() === undefined) {
|
|
@@ -26,16 +26,10 @@ export const teleportControls = (handedness, options) => {
|
|
|
26
26
|
const handContext = getHandContext(handedness);
|
|
27
27
|
watch(handContext.enabled, (enabled) => {
|
|
28
28
|
controlsCounter += enabled ? 1 : -1;
|
|
29
|
-
teleportState.
|
|
30
|
-
value[handedness].enabled = controlsCounter > 0;
|
|
31
|
-
return value;
|
|
32
|
-
});
|
|
29
|
+
teleportState[handedness].enabled = controlsCounter > 0;
|
|
33
30
|
});
|
|
34
31
|
watch(handContext.active, (hovering) => {
|
|
35
|
-
teleportState.
|
|
36
|
-
value[handedness].hovering = hovering;
|
|
37
|
-
return value;
|
|
38
|
-
});
|
|
32
|
+
teleportState[handedness].hovering = hovering;
|
|
39
33
|
});
|
|
40
34
|
return {
|
|
41
35
|
enabled: handContext.enabled,
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { useController } from '../../hooks/useController';
|
|
1
|
+
import { observe } from '@threlte/core';
|
|
2
|
+
import { controllers } from '../../hooks/useController.svelte';
|
|
4
3
|
import { useTeleport } from '../../hooks/useTeleport';
|
|
5
4
|
import { useFixed } from '../../internal/useFixed';
|
|
6
|
-
import { teleportIntersection } from '../../internal/
|
|
5
|
+
import { isPresenting, teleportIntersection } from '../../internal/state.svelte';
|
|
7
6
|
export const setupTeleportControls = (context, handContext, fixedStep = 1 / 40) => {
|
|
8
7
|
const handedness = handContext.hand;
|
|
9
|
-
const controller =
|
|
8
|
+
const controller = $derived(controllers[handedness]);
|
|
10
9
|
const teleport = useTeleport();
|
|
11
10
|
const handleHoverEnd = () => {
|
|
12
11
|
handContext.hovered.set(undefined);
|
|
13
|
-
teleportIntersection[handedness]
|
|
12
|
+
teleportIntersection[handedness] = undefined;
|
|
14
13
|
};
|
|
15
14
|
const { start, stop } = useFixed(() => {
|
|
16
|
-
const gamepad = controller
|
|
15
|
+
const gamepad = controller?.inputSource.gamepad;
|
|
17
16
|
if (gamepad === undefined) {
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
@@ -45,14 +44,14 @@ export const setupTeleportControls = (context, handContext, fixedStep = 1 / 40)
|
|
|
45
44
|
}
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
|
-
teleportIntersection[handedness]
|
|
47
|
+
teleportIntersection[handedness] = intersect;
|
|
49
48
|
handContext.hovered.set(intersect);
|
|
50
49
|
}, {
|
|
51
50
|
fixedStep,
|
|
52
51
|
autoStart: false
|
|
53
52
|
});
|
|
54
|
-
|
|
55
|
-
if (isPresenting && enabled) {
|
|
53
|
+
observe.pre(() => [isPresenting.current, handContext.enabled], ([isPresenting, $enabled]) => {
|
|
54
|
+
if (isPresenting && $enabled) {
|
|
56
55
|
start();
|
|
57
56
|
}
|
|
58
57
|
else {
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threlte/xr",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@eslint/js": "^9.26.0",
|
|
9
|
-
"@sveltejs/adapter-auto": "^
|
|
10
|
-
"@sveltejs/kit": "^2.
|
|
9
|
+
"@sveltejs/adapter-auto": "^6.1.0",
|
|
10
|
+
"@sveltejs/kit": "^2.37.0",
|
|
11
11
|
"@sveltejs/package": "^2.3.7",
|
|
12
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
12
|
+
"@sveltejs/vite-plugin-svelte": "^6.1.4",
|
|
13
13
|
"@types/three": "^0.175.0",
|
|
14
14
|
"@types/webxr": "^0.5.22",
|
|
15
15
|
"autoprefixer": "^10.4.19",
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"globals": "^16.1.0",
|
|
19
19
|
"postcss": "^8.4.38",
|
|
20
20
|
"publint": "^0.2.7",
|
|
21
|
-
"svelte": "
|
|
22
|
-
"svelte-check": "^4.1
|
|
21
|
+
"svelte": "5.26.2",
|
|
22
|
+
"svelte-check": "^4.3.1",
|
|
23
23
|
"three": "^0.175.0",
|
|
24
24
|
"tslib": "^2.6.2",
|
|
25
25
|
"typescript": "^5.6.3",
|
|
26
26
|
"typescript-eslint": "^8.32.0",
|
|
27
|
-
"vite": "^
|
|
27
|
+
"vite": "^7.1.4",
|
|
28
28
|
"vite-plugin-mkcert": "^1.17.5",
|
|
29
|
-
"@threlte/core": "8.
|
|
29
|
+
"@threlte/core": "8.1.5"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"svelte": ">=5",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type CurrentWritable } from '@threlte/core';
|
|
2
|
-
import type { XRController } from '../types';
|
|
3
|
-
export declare const left: CurrentWritable<XRController | undefined>;
|
|
4
|
-
export declare const right: CurrentWritable<XRController | undefined>;
|
|
5
|
-
export declare const gaze: CurrentWritable<XRController | undefined>;
|
|
6
|
-
/**
|
|
7
|
-
* Provides a reference to a current XRController, filtered by handedness.
|
|
8
|
-
*/
|
|
9
|
-
export declare const useController: (handedness: XRHandedness) => CurrentWritable<XRController | undefined>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { currentWritable } from '@threlte/core';
|
|
2
|
-
export const left = currentWritable(undefined);
|
|
3
|
-
export const right = currentWritable(undefined);
|
|
4
|
-
export const gaze = currentWritable(undefined);
|
|
5
|
-
/**
|
|
6
|
-
* Provides a reference to a current XRController, filtered by handedness.
|
|
7
|
-
*/
|
|
8
|
-
export const useController = (handedness) => {
|
|
9
|
-
switch (handedness) {
|
|
10
|
-
case 'left':
|
|
11
|
-
return left;
|
|
12
|
-
case 'right':
|
|
13
|
-
return right;
|
|
14
|
-
case 'none':
|
|
15
|
-
return gaze;
|
|
16
|
-
default:
|
|
17
|
-
throw new Error('useController handedness must be left, right, or none.');
|
|
18
|
-
}
|
|
19
|
-
};
|
package/dist/hooks/useHand.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type CurrentWritable } from '@threlte/core';
|
|
2
|
-
import type { XRHand } from '../types';
|
|
3
|
-
export declare const left: CurrentWritable<XRHand | undefined>;
|
|
4
|
-
export declare const right: CurrentWritable<XRHand | undefined>;
|
|
5
|
-
/**
|
|
6
|
-
* Provides a reference to a current XRHand, filtered by handedness.
|
|
7
|
-
*/
|
|
8
|
-
export declare const useHand: (handedness: "left" | "right") => CurrentWritable<undefined | XRHand>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { currentWritable, useTask, useThrelte } from '@threlte/core';
|
|
2
|
-
import { useHand } from './useHand';
|
|
3
|
-
/**
|
|
4
|
-
* Provides a reference to a requested hand joint, once available.
|
|
5
|
-
*/
|
|
6
|
-
export const useHandJoint = (handedness, joint) => {
|
|
7
|
-
const { invalidate } = useThrelte();
|
|
8
|
-
const jointSpaceStore = currentWritable(undefined);
|
|
9
|
-
const xrhand = useHand(handedness);
|
|
10
|
-
const { stop } = useTask(() => {
|
|
11
|
-
const jointSpace = xrhand.current?.hand.joints[joint];
|
|
12
|
-
// The joint radius is a good indicator that the joint is ready
|
|
13
|
-
if (jointSpace?.jointRadius !== undefined) {
|
|
14
|
-
jointSpaceStore.set(jointSpace);
|
|
15
|
-
invalidate();
|
|
16
|
-
stop();
|
|
17
|
-
}
|
|
18
|
-
}, { autoInvalidate: false });
|
|
19
|
-
return jointSpaceStore;
|
|
20
|
-
};
|
package/dist/hooks/useHitTest.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { Matrix4 } from 'three';
|
|
2
|
-
import { useThrelte, useTask, watch, currentWritable } from '@threlte/core';
|
|
3
|
-
import { useXR } from './useXR';
|
|
4
|
-
import { useController } from './useController';
|
|
5
|
-
/**
|
|
6
|
-
* Use this hook to perform a hit test per frame in an AR environment.
|
|
7
|
-
*
|
|
8
|
-
* ```ts
|
|
9
|
-
* useHitTest((hitMatrix, hit) => {
|
|
10
|
-
* mesh.matrix.copy(hitMatrix)
|
|
11
|
-
* }, {
|
|
12
|
-
* source: 'viewer' | 'leftInput' | 'rightInput' // Default 'viewer'
|
|
13
|
-
* })
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
export const useHitTest = (hitTestCallback, options = {}) => {
|
|
17
|
-
const source = options.source ?? 'viewer';
|
|
18
|
-
const { xr } = useThrelte().renderer;
|
|
19
|
-
const xrState = useXR();
|
|
20
|
-
const hitMatrix = new Matrix4();
|
|
21
|
-
const hitTestSource = currentWritable(undefined);
|
|
22
|
-
if (source === 'viewer') {
|
|
23
|
-
watch(xrState.session, async (session) => {
|
|
24
|
-
if (session === undefined) {
|
|
25
|
-
hitTestSource.set(undefined);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const space = await session.requestReferenceSpace('viewer');
|
|
29
|
-
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
const controller = useController(source === 'leftInput' ? 'left' : 'right');
|
|
34
|
-
const hand = useController(source === 'leftInput' ? 'left' : 'right');
|
|
35
|
-
watch([xrState.session, controller], async ([session, input]) => {
|
|
36
|
-
if (input === undefined || session === undefined) {
|
|
37
|
-
hitTestSource.set(undefined);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const space = input.inputSource.targetRaySpace;
|
|
41
|
-
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
42
|
-
});
|
|
43
|
-
watch([xrState.session, hand], async ([session, input]) => {
|
|
44
|
-
if (input === undefined || session === undefined) {
|
|
45
|
-
hitTestSource.set(undefined);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const space = input.inputSource.targetRaySpace;
|
|
49
|
-
hitTestSource.set(await session.requestHitTestSource?.({ space }));
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
const { start, stop } = useTask(() => {
|
|
53
|
-
const referenceSpace = xr.getReferenceSpace();
|
|
54
|
-
if (referenceSpace === null || hitTestSource.current === undefined) {
|
|
55
|
-
return hitTestCallback(hitMatrix, undefined);
|
|
56
|
-
}
|
|
57
|
-
const [hit] = xr.getFrame().getHitTestResults(hitTestSource.current);
|
|
58
|
-
const pose = hit?.getPose(referenceSpace);
|
|
59
|
-
if (pose === undefined) {
|
|
60
|
-
return hitTestCallback(hitMatrix, undefined);
|
|
61
|
-
}
|
|
62
|
-
hitMatrix.fromArray(pose.transform.matrix);
|
|
63
|
-
hitTestCallback(hitMatrix, hit);
|
|
64
|
-
}, { autoStart: false });
|
|
65
|
-
watch([xrState.isPresenting, hitTestSource], ([isPresenting, testSource]) => {
|
|
66
|
-
if (!isPresenting) {
|
|
67
|
-
stop();
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (testSource === undefined) {
|
|
71
|
-
stop();
|
|
72
|
-
// Execute callback one last time to inform consumers of no hits.
|
|
73
|
-
hitTestCallback(hitMatrix, undefined);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
start();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { onDestroy } from 'svelte';
|
|
2
|
-
import { raf } from 'svelte/internal/client';
|
|
3
|
-
import { watch } from '@threlte/core';
|
|
4
|
-
import { session } from './stores';
|
|
5
|
-
export const setupRaf = () => {
|
|
6
|
-
if (typeof window === 'undefined')
|
|
7
|
-
return;
|
|
8
|
-
const originalTick = raf.tick;
|
|
9
|
-
watch(session, (session) => {
|
|
10
|
-
raf.tick =
|
|
11
|
-
session === undefined
|
|
12
|
-
? originalTick
|
|
13
|
-
: (fn) => session.requestAnimationFrame(fn);
|
|
14
|
-
});
|
|
15
|
-
onDestroy(() => (raf.tick = originalTick));
|
|
16
|
-
};
|