@threlte/xr 1.5.4 → 1.5.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 +4 -1
- package/dist/components/Hand.svelte +4 -1
- package/dist/components/XR.svelte +1 -1
- package/dist/components/internal/Cursor.svelte +5 -10
- package/dist/components/internal/PointerCursor.svelte +18 -4
- package/dist/components/internal/TeleportCursor.svelte +4 -1
- package/dist/hooks/useHandJoint.svelte.js +2 -1
- package/dist/internal/setupControllers.js +8 -3
- package/dist/internal/setupHands.js +2 -2
- package/dist/lib/toggleXRSession.js +2 -2
- package/dist/plugins/pointerControls/compute.js +4 -0
- package/dist/plugins/pointerControls/index.js +1 -3
- package/dist/plugins/pointerControls/plugin.svelte.js +0 -5
- package/dist/plugins/pointerControls/setup.svelte.js +40 -46
- package/dist/plugins/pointerControls/types.d.ts +2 -0
- package/dist/plugins/pointerControls/types.js +2 -1
- package/dist/plugins/teleportControls/compute.js +4 -0
- package/dist/plugins/teleportControls/index.js +1 -4
- package/package.json +3 -2
|
@@ -128,7 +128,7 @@ This should be placed within a Threlte `<Canvas />`.
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const handleInputSourcesChange = (event: XRInputSourcesChangeEvent) => {
|
|
131
|
-
isHandTracking.current =
|
|
131
|
+
isHandTracking.current = Array.from(event.session.inputSources).some((source) => source.hand)
|
|
132
132
|
oninputsourceschange?.(event)
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Color, DoubleSide,
|
|
2
|
+
import { Color, DoubleSide, ShaderMaterial, type ColorRepresentation } from 'three'
|
|
3
3
|
import { T } from '@threlte/core'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
const { color = new Color('white'), size = 0.03, thickness = 0.035 }: Props = $props()
|
|
12
12
|
|
|
13
13
|
const vertexShader = `
|
|
14
|
-
uniform mat4 projectionMatrix;
|
|
15
|
-
uniform mat4 modelViewMatrix;
|
|
16
|
-
attribute vec2 uv;
|
|
17
|
-
attribute vec3 position;
|
|
18
14
|
varying vec2 vUv;
|
|
19
15
|
void main() {
|
|
20
16
|
vUv = uv;
|
|
@@ -23,14 +19,13 @@
|
|
|
23
19
|
`
|
|
24
20
|
|
|
25
21
|
const fragmentShader = `
|
|
26
|
-
precision mediump float;
|
|
27
22
|
uniform float thickness;
|
|
28
23
|
uniform vec3 color;
|
|
29
24
|
varying vec2 vUv;
|
|
30
25
|
void main() {
|
|
31
|
-
float
|
|
32
|
-
float
|
|
33
|
-
float alpha = 1.0 -
|
|
26
|
+
float d = abs(distance(vUv, vec2(0.5)) - 0.25);
|
|
27
|
+
float edge = fwidth(d);
|
|
28
|
+
float alpha = 1.0 - smoothstep(thickness - edge, thickness + edge, d);
|
|
34
29
|
gl_FragColor = vec4(color, alpha);
|
|
35
30
|
}
|
|
36
31
|
`
|
|
@@ -40,7 +35,7 @@
|
|
|
40
35
|
color: { value: color }
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
const shaderMaterial = new
|
|
38
|
+
const shaderMaterial = new ShaderMaterial({
|
|
44
39
|
vertexShader,
|
|
45
40
|
fragmentShader,
|
|
46
41
|
uniforms,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<script lang="ts">
|
|
13
13
|
import { T, useTask, useThrelte } from '@threlte/core'
|
|
14
|
+
import { untrack } from 'svelte'
|
|
14
15
|
import { pointerIntersection, pointerState } from '../../internal/state.svelte.js'
|
|
15
16
|
import Cursor from './Cursor.svelte'
|
|
16
17
|
import type { Snippet } from 'svelte'
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
|
|
29
30
|
const ref = new Group()
|
|
30
31
|
|
|
32
|
+
const SURFACE_OFFSET = 0.002
|
|
33
|
+
|
|
31
34
|
useTask(
|
|
32
35
|
() => {
|
|
33
36
|
if (intersection === undefined) {
|
|
@@ -43,17 +46,28 @@
|
|
|
43
46
|
|
|
44
47
|
normalMatrix.getNormalMatrix(object.matrixWorld)
|
|
45
48
|
worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize()
|
|
46
|
-
|
|
49
|
+
|
|
50
|
+
// Float the reticle just above the surface so it doesn't z-fight
|
|
51
|
+
// with the coplanar face underneath.
|
|
52
|
+
ref.position.addScaledVector(worldNormal, SURFACE_OFFSET)
|
|
53
|
+
|
|
54
|
+
ref.lookAt(vec3.addVectors(ref.position, worldNormal))
|
|
47
55
|
},
|
|
48
56
|
{
|
|
49
57
|
running: () => hovering && intersection !== undefined
|
|
50
58
|
}
|
|
51
59
|
)
|
|
52
60
|
|
|
61
|
+
// Snap to the hit point on hover entry so the reticle doesn't visibly
|
|
62
|
+
// fly in from its previous location. `intersection` is read untracked
|
|
63
|
+
// so this only reruns on hover transitions, not every frame.
|
|
53
64
|
$effect.pre(() => {
|
|
54
|
-
if (hovering
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
if (!hovering) return
|
|
66
|
+
untrack(() => {
|
|
67
|
+
if (intersection) {
|
|
68
|
+
ref.position.copy(intersection.point)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
57
71
|
})
|
|
58
72
|
</script>
|
|
59
73
|
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
if (face) {
|
|
41
41
|
normalMatrix.getNormalMatrix(object.matrixWorld)
|
|
42
42
|
worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize()
|
|
43
|
-
|
|
43
|
+
// lookAt from the lerped position (matches PointerCursor) — using the
|
|
44
|
+
// raw hit point here causes orientation to wobble while position is
|
|
45
|
+
// still easing toward the target.
|
|
46
|
+
ref.lookAt(vec3.addVectors(ref.position, worldNormal))
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
49
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useTask, useThrelte } from '@threlte/core';
|
|
2
2
|
import { hands } from './useHand.svelte.js';
|
|
3
|
+
import { isPresenting } from '../internal/state.svelte.js';
|
|
3
4
|
import { toCurrentReadable } from './currentReadable.svelte.js';
|
|
4
5
|
/**
|
|
5
6
|
* Provides a reference to a requested hand joint, once available.
|
|
@@ -22,6 +23,6 @@ export const useHandJoint = (handedness, joint) => {
|
|
|
22
23
|
jointSpace = undefined;
|
|
23
24
|
invalidate();
|
|
24
25
|
}
|
|
25
|
-
});
|
|
26
|
+
}, { running: () => isPresenting.current });
|
|
26
27
|
return toCurrentReadable(() => jointSpace);
|
|
27
28
|
};
|
|
@@ -9,12 +9,12 @@ export const setupControllers = (factory) => {
|
|
|
9
9
|
const hasHands = useHandTrackingState();
|
|
10
10
|
const targetRaySpaces = [xr.getController(0), xr.getController(1)];
|
|
11
11
|
const indexMap = new Map();
|
|
12
|
+
const modelFactory = factory ?? new XRControllerModelFactory();
|
|
12
13
|
targetRaySpaces.forEach((targetRay, index) => {
|
|
13
|
-
const model = (factory ?? new XRControllerModelFactory()).createControllerModel(targetRay);
|
|
14
14
|
indexMap.set(targetRay, {
|
|
15
15
|
targetRay,
|
|
16
16
|
grip: xr.getControllerGrip(index),
|
|
17
|
-
model
|
|
17
|
+
model: modelFactory.createControllerModel(targetRay)
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
20
|
onMount(() => {
|
|
@@ -25,8 +25,13 @@ export const setupControllers = (factory) => {
|
|
|
25
25
|
controllerEvents[data.handedness]?.[`on${event.type}`]?.(event);
|
|
26
26
|
};
|
|
27
27
|
function handleConnected(event) {
|
|
28
|
-
const { model, targetRay, grip } = indexMap.get(this);
|
|
29
28
|
const { data: inputSource } = event;
|
|
29
|
+
// The targetRaySpace 'connected' event fires for both controller and
|
|
30
|
+
// hand-tracking input sources. The controllers slot represents a physical
|
|
31
|
+
// controller — setupHands handles the hand-tracking side.
|
|
32
|
+
if (inputSource.hand)
|
|
33
|
+
return;
|
|
34
|
+
const { model, targetRay, grip } = indexMap.get(this);
|
|
30
35
|
controllers[event.data.handedness] = {
|
|
31
36
|
inputSource,
|
|
32
37
|
targetRay,
|
|
@@ -9,12 +9,12 @@ export const setupHands = (factory) => {
|
|
|
9
9
|
const hasHands = useHandTrackingState();
|
|
10
10
|
const handSpaces = [xr.getHand(0), xr.getHand(1)];
|
|
11
11
|
const map = new Map();
|
|
12
|
+
const modelFactory = factory ?? new XRHandModelFactory();
|
|
12
13
|
handSpaces.forEach((handSpace, index) => {
|
|
13
|
-
const model = (factory ?? new XRHandModelFactory()).createHandModel(handSpace, 'mesh');
|
|
14
14
|
map.set(handSpace, {
|
|
15
15
|
hand: handSpace,
|
|
16
16
|
targetRay: xr.getController(index),
|
|
17
|
-
model
|
|
17
|
+
model: modelFactory.createHandModel(handSpace, 'mesh')
|
|
18
18
|
});
|
|
19
19
|
});
|
|
20
20
|
onMount(() => {
|
|
@@ -15,10 +15,10 @@ export const toggleXRSession = async (sessionMode, sessionInit, force) => {
|
|
|
15
15
|
return currentSession;
|
|
16
16
|
if (force === 'exit' && !hasSession)
|
|
17
17
|
return;
|
|
18
|
-
// Exit a session if entered
|
|
18
|
+
// Exit a session if entered. `session.current` is cleared by XR.svelte's
|
|
19
|
+
// `handleSessionEnd` when the 'end' event fires — don't duplicate that here.
|
|
19
20
|
if (hasSession) {
|
|
20
21
|
await currentSession.end();
|
|
21
|
-
session.current = undefined;
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (xr.current === undefined) {
|
|
@@ -5,6 +5,10 @@ export const defaultComputeFunction = (context, handContext) => {
|
|
|
5
5
|
const targetRay = controllers[handContext.hand]?.targetRay;
|
|
6
6
|
if (targetRay === undefined)
|
|
7
7
|
return;
|
|
8
|
+
// `<Controller>` attaches targetRay to the scene root so local === world;
|
|
9
|
+
// we can read `.position`/`.quaternion` directly without a matrixWorld
|
|
10
|
+
// roundtrip (which would force-recompose the matrix three.js writes from
|
|
11
|
+
// the XR pose and introduce a drift against the visible render).
|
|
8
12
|
forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
|
|
9
13
|
context.raycaster.set(targetRay.position, forward);
|
|
10
14
|
};
|
|
@@ -5,7 +5,6 @@ import { injectPointerControlsPlugin } from './plugin.svelte.js';
|
|
|
5
5
|
import { setupPointerControls } from './setup.svelte.js';
|
|
6
6
|
import { getControlsContext, getHandContext, setControlsContext, setHandContext, setInternalContext } from './context.js';
|
|
7
7
|
import { pointerState } from '../../internal/state.svelte.js';
|
|
8
|
-
let controlsCounter = 0;
|
|
9
8
|
export const pointerControls = (handedness, options) => {
|
|
10
9
|
if (getControlsContext() === undefined) {
|
|
11
10
|
injectPointerControlsPlugin();
|
|
@@ -35,8 +34,7 @@ export const pointerControls = (handedness, options) => {
|
|
|
35
34
|
}
|
|
36
35
|
const handContext = getHandContext(handedness);
|
|
37
36
|
observe.pre(() => [handContext.enabled], ([enabled]) => {
|
|
38
|
-
|
|
39
|
-
pointerState[handedness].enabled = controlsCounter > 0;
|
|
37
|
+
pointerState[handedness].enabled = enabled;
|
|
40
38
|
});
|
|
41
39
|
observe.pre(() => [handContext.pointerOverTarget], ([hovering]) => {
|
|
42
40
|
pointerState[handedness].hovering = hovering;
|
|
@@ -5,11 +5,6 @@ export const injectPointerControlsPlugin = () => {
|
|
|
5
5
|
injectPlugin('threlte-pointer-controls', (args) => {
|
|
6
6
|
if (!isInstanceOf(args.ref, 'Object3D'))
|
|
7
7
|
return;
|
|
8
|
-
const hasEventHandlers = Object.entries(args.props).some(([key, value]) => {
|
|
9
|
-
return value !== undefined && events.includes(key);
|
|
10
|
-
});
|
|
11
|
-
if (!hasEventHandlers)
|
|
12
|
-
return;
|
|
13
8
|
const { addInteractiveObject, removeInteractiveObject } = usePointerControls();
|
|
14
9
|
$effect.pre(() => {
|
|
15
10
|
const ref = args.ref;
|
|
@@ -5,8 +5,19 @@ import { controllers } from '../../hooks/useController.svelte.js';
|
|
|
5
5
|
import { useHand } from '../../hooks/useHand.svelte.js';
|
|
6
6
|
import { useFixed } from '../../internal/useFixed.js';
|
|
7
7
|
import { isPresenting, pointerIntersection } from '../../internal/state.svelte.js';
|
|
8
|
+
// Hover identity must match the dedup key used in `getHits`, otherwise the ID
|
|
9
|
+
// changes mid-hover (e.g. the hit's face index changes as the ray sweeps a
|
|
10
|
+
// plain mesh) and the object flickers between pointerout/pointerenter every
|
|
11
|
+
// frame.
|
|
8
12
|
const getIntersectionId = (intersection) => {
|
|
9
|
-
|
|
13
|
+
const target = intersection.eventObject ?? intersection.object;
|
|
14
|
+
if (intersection.instanceId !== undefined) {
|
|
15
|
+
return `${target.uuid}|${intersection.instanceId}`;
|
|
16
|
+
}
|
|
17
|
+
if (intersection.object.isPoints) {
|
|
18
|
+
return `${target.uuid}|${intersection.index}`;
|
|
19
|
+
}
|
|
20
|
+
return target.uuid;
|
|
10
21
|
};
|
|
11
22
|
const EPSILON = 0.0001;
|
|
12
23
|
export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) => {
|
|
@@ -29,24 +40,18 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
29
40
|
handleEvent('onpointerup', event);
|
|
30
41
|
};
|
|
31
42
|
const handleClick = (event) => {
|
|
32
|
-
// If a click yields no results, pass it back to the user as a miss
|
|
33
|
-
// Missed events have to come first in order to establish user-land side-effect clean up
|
|
34
|
-
if (hits.length === 0) {
|
|
35
|
-
pointerMissed(context.interactiveObjects, event);
|
|
36
|
-
}
|
|
37
43
|
handleEvent('onclick', event);
|
|
38
44
|
};
|
|
39
45
|
function cancelPointer(intersections) {
|
|
40
46
|
if (handContext.hovered.size === 0)
|
|
41
47
|
return;
|
|
48
|
+
const currentIds = new Set();
|
|
49
|
+
for (const hit of intersections) {
|
|
50
|
+
currentIds.add(getIntersectionId(hit));
|
|
51
|
+
}
|
|
42
52
|
const toRemove = [];
|
|
43
53
|
for (const [id, hoveredObj] of handContext.hovered) {
|
|
44
|
-
|
|
45
|
-
// we call pointerout and delete the object from the hovered elements map
|
|
46
|
-
if (intersections.length === 0 ||
|
|
47
|
-
!intersections.some((hit) => hit.object === hoveredObj.object &&
|
|
48
|
-
hit.index === hoveredObj.index &&
|
|
49
|
-
hit.instanceId === hoveredObj.instanceId)) {
|
|
54
|
+
if (!currentIds.has(id)) {
|
|
50
55
|
toRemove.push([id, hoveredObj]);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -114,7 +119,13 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
114
119
|
const handleEvent = (name, event) => {
|
|
115
120
|
const isPointerMove = name === 'onpointermove';
|
|
116
121
|
const isClickEvent = name === 'onclick' || name === 'oncontextmenu';
|
|
117
|
-
//
|
|
122
|
+
// Fire pointermissed for objects that were not under the pointer at pointerdown.
|
|
123
|
+
// Must come before the dispatch loop so user-land cleanup runs first.
|
|
124
|
+
if (isClickEvent) {
|
|
125
|
+
pointerMissed(context.interactiveObjects.filter((object) => !handContext.initialHits.includes(object)), event);
|
|
126
|
+
}
|
|
127
|
+
// Update hover state before dispatch so that pointerout/pointerleave fire
|
|
128
|
+
// before pointerover/pointerenter on newly hit objects.
|
|
118
129
|
if (isPointerMove)
|
|
119
130
|
cancelPointer(hits);
|
|
120
131
|
let stopped = false;
|
|
@@ -127,6 +138,7 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
127
138
|
stopped,
|
|
128
139
|
...hit,
|
|
129
140
|
intersections: hits,
|
|
141
|
+
handedness,
|
|
130
142
|
stopPropagation() {
|
|
131
143
|
stopped = true;
|
|
132
144
|
intersectionEvent.stopped = true;
|
|
@@ -166,15 +178,11 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
166
178
|
// Call pointer move
|
|
167
179
|
events.onpointermove?.(intersectionEvent);
|
|
168
180
|
}
|
|
169
|
-
else if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
events[name]?.(intersectionEvent);
|
|
175
|
-
}
|
|
176
|
-
else if (isClickEvent && handContext.initialHits.includes(hit.eventObject)) {
|
|
177
|
-
pointerMissed(context.interactiveObjects.filter((object) => !handContext.initialHits.includes(object)), event);
|
|
181
|
+
else if (events[name] !== undefined) {
|
|
182
|
+
// All other events
|
|
183
|
+
if (!isClickEvent || handContext.initialHits.includes(hit.eventObject)) {
|
|
184
|
+
events[name]?.(intersectionEvent);
|
|
185
|
+
}
|
|
178
186
|
}
|
|
179
187
|
if (stopped)
|
|
180
188
|
break dispatchEvents;
|
|
@@ -194,42 +202,28 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
|
|
|
194
202
|
autoStart: false
|
|
195
203
|
});
|
|
196
204
|
observe.pre(() => [controller, handContext.enabled], ([controller, $enabled]) => {
|
|
197
|
-
if (controller === undefined)
|
|
205
|
+
if (controller === undefined || !$enabled)
|
|
198
206
|
return;
|
|
199
|
-
|
|
207
|
+
controller.targetRay.addEventListener('selectstart', handlePointerDown);
|
|
208
|
+
controller.targetRay.addEventListener('selectend', handlePointerUp);
|
|
209
|
+
controller.targetRay.addEventListener('select', handleClick);
|
|
210
|
+
return () => {
|
|
200
211
|
controller.targetRay.removeEventListener('selectstart', handlePointerDown);
|
|
201
212
|
controller.targetRay.removeEventListener('selectend', handlePointerUp);
|
|
202
213
|
controller.targetRay.removeEventListener('select', handleClick);
|
|
203
214
|
};
|
|
204
|
-
if ($enabled) {
|
|
205
|
-
controller.targetRay.addEventListener('selectstart', handlePointerDown);
|
|
206
|
-
controller.targetRay.addEventListener('selectend', handlePointerUp);
|
|
207
|
-
controller.targetRay.addEventListener('select', handleClick);
|
|
208
|
-
return removeHandlers;
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
removeHandlers();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
215
|
});
|
|
215
216
|
observe.pre(() => [hand, handContext.enabled], ([input, enabled]) => {
|
|
216
|
-
if (input === undefined)
|
|
217
|
+
if (input === undefined || !enabled)
|
|
217
218
|
return;
|
|
218
|
-
|
|
219
|
+
input.hand.addEventListener('pinchstart', handlePointerDown);
|
|
220
|
+
input.hand.addEventListener('pinchend', handlePointerUp);
|
|
221
|
+
input.hand.addEventListener('pinchend', handleClick);
|
|
222
|
+
return () => {
|
|
219
223
|
input.hand.removeEventListener('pinchstart', handlePointerDown);
|
|
220
224
|
input.hand.removeEventListener('pinchend', handlePointerUp);
|
|
221
225
|
input.hand.removeEventListener('pinchend', handleClick);
|
|
222
226
|
};
|
|
223
|
-
if (enabled) {
|
|
224
|
-
input.hand.addEventListener('pinchstart', handlePointerDown);
|
|
225
|
-
input.hand.addEventListener('pinchend', handlePointerUp);
|
|
226
|
-
input.hand.addEventListener('pinchend', handleClick);
|
|
227
|
-
return removeHandlers;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
removeHandlers();
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
227
|
});
|
|
234
228
|
observe.pre(() => [isPresenting.current, handContext.enabled], ([isPresenting, $enabled]) => {
|
|
235
229
|
if (isPresenting && $enabled) {
|
|
@@ -13,6 +13,8 @@ export interface IntersectionEvent extends Intersection {
|
|
|
13
13
|
eventObject: Object3D;
|
|
14
14
|
/** An array of intersections */
|
|
15
15
|
intersections: Intersection[];
|
|
16
|
+
/** Which hand dispatched this event. Each controller/hand fires enter/leave/etc. independently. */
|
|
17
|
+
handedness: 'left' | 'right';
|
|
16
18
|
/** Normalized event coordinates */
|
|
17
19
|
pointer: Vector3;
|
|
18
20
|
/** Delta between first click and this event */
|
|
@@ -5,6 +5,10 @@ export const defaultComputeFunction = (context, handContext) => {
|
|
|
5
5
|
const targetRay = controllers[handContext.hand]?.targetRay;
|
|
6
6
|
if (targetRay === undefined)
|
|
7
7
|
return;
|
|
8
|
+
// `<Controller>` attaches targetRay to the scene root so local === world;
|
|
9
|
+
// we can read `.position`/`.quaternion` directly without a matrixWorld
|
|
10
|
+
// roundtrip (which would force-recompose the matrix three.js writes from
|
|
11
|
+
// the XR pose and introduce a drift against the visible render).
|
|
8
12
|
forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
|
|
9
13
|
context.raycaster.set(targetRay.position, forward);
|
|
10
14
|
};
|
|
@@ -4,7 +4,6 @@ import { injectTeleportControlsPlugin } from './plugin.svelte.js';
|
|
|
4
4
|
import { setHandContext } from './context.js';
|
|
5
5
|
import { setupTeleportControls } from './setup.svelte.js';
|
|
6
6
|
import { teleportState } from '../../internal/state.svelte.js';
|
|
7
|
-
let controlsCounter = 0;
|
|
8
7
|
export const teleportControls = (handedness, options) => {
|
|
9
8
|
if (useTeleportControls() === undefined) {
|
|
10
9
|
injectTeleportControlsPlugin();
|
|
@@ -13,7 +12,6 @@ export const teleportControls = (handedness, options) => {
|
|
|
13
12
|
const context = useTeleportControls();
|
|
14
13
|
if (getHandContext(handedness) === undefined) {
|
|
15
14
|
const enabled = options?.enabled ?? true;
|
|
16
|
-
controlsCounter += enabled ? 1 : -1;
|
|
17
15
|
const ctx = {
|
|
18
16
|
hand: handedness,
|
|
19
17
|
active: currentWritable(false),
|
|
@@ -25,8 +23,7 @@ export const teleportControls = (handedness, options) => {
|
|
|
25
23
|
}
|
|
26
24
|
const handContext = getHandContext(handedness);
|
|
27
25
|
observe.pre(() => [handContext.enabled], ([enabled]) => {
|
|
28
|
-
|
|
29
|
-
teleportState[handedness].enabled = controlsCounter > 0;
|
|
26
|
+
teleportState[handedness].enabled = enabled;
|
|
30
27
|
});
|
|
31
28
|
observe.pre(() => [handContext.active], ([hovering]) => {
|
|
32
29
|
teleportState[handedness].hovering = hovering;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threlte/xr",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
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,8 @@
|
|
|
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.9"
|
|
31
|
+
"@threlte/core": "8.5.9",
|
|
32
|
+
"@threlte/extras": "9.14.9"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"svelte": ">=5",
|