@threlte/xr 1.0.7 → 1.0.8

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.
@@ -5,14 +5,12 @@
5
5
  lang="ts"
6
6
  module
7
7
  >
8
- import { writable } from 'svelte/store'
9
8
  import { T } from '@threlte/core'
10
9
  import { left as leftStore, right as rightStore } from '../hooks/useController'
11
10
  import { isHandTracking, pointerState, teleportState, controllerEvents } from '../internal/stores'
12
11
  import type { XRControllerEvents } from '../types'
13
12
  import PointerCursor from './internal/PointerCursor.svelte'
14
13
  import ShortRay from './internal/ShortRay.svelte'
15
- import ScenePortal from './internal/ScenePortal.svelte'
16
14
  import TeleportCursor from './internal/TeleportCursor.svelte'
17
15
  import TeleportRay from './internal/TeleportRay.svelte'
18
16
  import type { Snippet } from 'svelte'
@@ -77,11 +75,10 @@
77
75
  teleportCursor: teleportCursorSnippet
78
76
  }: Props = $props()
79
77
 
80
- const handedness = writable<'left' | 'right'>(left ? 'left' : right ? 'right' : hand)
81
- $effect.pre(() => handedness.set(left ? 'left' : right ? 'right' : (hand as 'left' | 'right')))
78
+ const handedness = $derived<'left' | 'right'>(left ? 'left' : right ? 'right' : hand ?? 'left')
82
79
 
83
80
  $effect.pre(() =>
84
- controllerEvents[$handedness].set({
81
+ controllerEvents[handedness].set({
85
82
  onconnected,
86
83
  ondisconnected,
87
84
  onselect,
@@ -93,12 +90,12 @@
93
90
  })
94
91
  )
95
92
 
96
- let store = $derived(stores[$handedness])
97
- let grip = $derived($store?.grip)
98
- let targetRay = $derived($store?.targetRay)
99
- let model = $derived($store?.model)
100
- let hasPointerControls = $derived($pointerState[$handedness].enabled)
101
- let hasTeleportControls = $derived($teleportState[$handedness].enabled)
93
+ const store = $derived(stores[handedness])
94
+ const grip = $derived($store?.grip)
95
+ const targetRay = $derived($store?.targetRay)
96
+ const model = $derived($store?.model)
97
+ const hasPointerControls = $derived($pointerState[handedness].enabled)
98
+ const hasTeleportControls = $derived($teleportState[handedness].enabled)
102
99
  </script>
103
100
 
104
101
  {#if !$isHandTracking}
@@ -120,7 +117,7 @@
120
117
 
121
118
  {#if hasPointerControls || hasTeleportControls}
122
119
  <ShortRay
123
- handedness={$handedness}
120
+ {handedness}
124
121
  children={pointerRaySnippet}
125
122
  />
126
123
  {/if}
@@ -128,24 +125,22 @@
128
125
  {/if}
129
126
  {/if}
130
127
 
131
- <ScenePortal>
132
- {#if hasPointerControls}
133
- <PointerCursor
134
- handedness={$handedness}
135
- children={pointerCursorSnippet}
136
- />
137
- {/if}
128
+ {#if hasPointerControls}
129
+ <PointerCursor
130
+ {handedness}
131
+ children={pointerCursorSnippet}
132
+ />
133
+ {/if}
138
134
 
139
- {#if hasTeleportControls && targetRay !== undefined}
140
- <TeleportRay
141
- {targetRay}
142
- handedness={$handedness}
143
- children={teleportRaySnippet}
144
- />
135
+ {#if hasTeleportControls && targetRay !== undefined}
136
+ <TeleportRay
137
+ {targetRay}
138
+ {handedness}
139
+ children={teleportRaySnippet}
140
+ />
145
141
 
146
- <TeleportCursor
147
- handedness={$handedness}
148
- children={teleportCursorSnippet}
149
- />
150
- {/if}
151
- </ScenePortal>
142
+ <TeleportCursor
143
+ {handedness}
144
+ children={teleportCursorSnippet}
145
+ />
146
+ {/if}
@@ -7,8 +7,6 @@
7
7
  import type { XRHandEvents } from '../types'
8
8
  import { isHandTracking, handEvents } from '../internal/stores'
9
9
  import { left as leftStore, right as rightStore } from '../hooks/useHand'
10
- import ScenePortal from './internal/ScenePortal.svelte'
11
- import { writable } from 'svelte/store'
12
10
  import type { Snippet } from 'svelte'
13
11
 
14
12
  const stores = {
@@ -44,7 +42,7 @@
44
42
  }
45
43
  )
46
44
 
47
- let {
45
+ const {
48
46
  left,
49
47
  right,
50
48
  hand,
@@ -57,15 +55,14 @@
57
55
  wrist
58
56
  }: Props = $props()
59
57
 
60
- const { renderer, scheduler, renderStage } = useThrelte()
58
+ const { scene, renderer, scheduler, renderStage } = useThrelte()
61
59
  const { xr } = renderer
62
60
  const space = xr.getReferenceSpace()
63
61
 
64
- const handedness = writable<'left' | 'right'>(left ? 'left' : right ? 'right' : hand)
65
- $effect.pre(() => handedness.set(left ? 'left' : right ? 'right' : (hand as 'left' | 'right')))
62
+ const handedness = $derived<'left' | 'right'>(left ? 'left' : right ? 'right' : hand ?? 'left')
66
63
 
67
64
  $effect.pre(() =>
68
- handEvents[$handedness].set({
65
+ handEvents[handedness].set({
69
66
  onconnected,
70
67
  ondisconnected,
71
68
  onpinchend,
@@ -73,7 +70,7 @@
73
70
  })
74
71
  )
75
72
 
76
- let group = new Group()
73
+ const group = new Group()
77
74
 
78
75
  /**
79
76
  * Currently children of a hand XRSpace or model will not
@@ -113,9 +110,9 @@
113
110
  }
114
111
  })
115
112
 
116
- let store = $derived(stores[$handedness])
117
- let inputSource = $derived($store?.inputSource)
118
- let model = $derived($store?.model)
113
+ const store = $derived(stores[handedness])
114
+ const inputSource = $derived($store?.inputSource)
115
+ const model = $derived($store?.model)
119
116
  </script>
120
117
 
121
118
  {#if $store?.hand && $isHandTracking}
@@ -133,10 +130,11 @@
133
130
  {/if}
134
131
 
135
132
  {#if $isHandTracking}
136
- <ScenePortal>
137
- <T is={group}>
138
- {@render wrist?.()}
139
- {@render children?.()}
140
- </T>
141
- </ScenePortal>
133
+ <T
134
+ is={group}
135
+ attach={scene}
136
+ >
137
+ {@render wrist?.()}
138
+ {@render children?.()}
139
+ </T>
142
140
  {/if}
@@ -1,13 +1,22 @@
1
1
  <script lang="ts">
2
- import { T } from '@threlte/core'
2
+ import type { Snippet } from 'svelte'
3
+ import type { Group } from 'three'
4
+ import { T, useThrelte } from '@threlte/core'
3
5
  import { useHeadset } from '../hooks/useHeadset'
4
- import ScenePortal from './internal/ScenePortal.svelte'
5
6
 
7
+ interface Props {
8
+ children?: Snippet<[{ ref: Group }]>
9
+ }
10
+
11
+ const { children }: Props = $props()
12
+
13
+ const { scene } = useThrelte()
6
14
  const headset = useHeadset()
7
15
  </script>
8
16
 
9
- <ScenePortal>
10
- <T is={headset}>
11
- <slot />
12
- </T>
13
- </ScenePortal>
17
+ <T
18
+ is={headset}
19
+ attach={scene}
20
+ >
21
+ {@render children?.({ ref: headset })}
22
+ </T>
@@ -1,20 +1,8 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const Headset: $$__sveltets_2_IsomorphicComponent<any, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {
17
- default: {};
18
- }, {}, string>;
19
- type Headset = InstanceType<typeof Headset>;
1
+ import type { Snippet } from 'svelte';
2
+ import type { Group } from 'three';
3
+ declare const Headset: import("svelte").Component<{
4
+ children?: Snippet<[{
5
+ ref: Group;
6
+ }]>;
7
+ }, {}, "">;
20
8
  export default Headset;
@@ -18,7 +18,7 @@ This should be placed within a Threlte `<Canvas />`.
18
18
 
19
19
  -->
20
20
  <script lang="ts">
21
- import { onDestroy, onMount, type Snippet } from 'svelte'
21
+ import { onMount, type Snippet } from 'svelte'
22
22
  import { useThrelte, watch } from '@threlte/core'
23
23
  import type { XRSessionEvent } from '../types'
24
24
  import {
@@ -135,10 +135,6 @@ This should be placed within a Threlte `<Canvas />`.
135
135
  currentSession.addEventListener('inputsourceschange', handleInputSourcesChange)
136
136
  currentSession.addEventListener('frameratechange', handleFramerateChange)
137
137
 
138
- xr.setFoveation(foveation)
139
-
140
- updateTargetFrameRate(frameRate)
141
-
142
138
  return () => {
143
139
  currentSession.removeEventListener('visibilitychange', handleVisibilityChange)
144
140
  currentSession.removeEventListener('inputsourceschange', handleInputSourcesChange)
@@ -166,17 +162,20 @@ This should be placed within a Threlte `<Canvas />`.
166
162
  xr.enabled = false
167
163
  xr.removeEventListener('sessionstart', handleSessionStart)
168
164
  xr.removeEventListener('sessionend', handleSessionEnd)
165
+
166
+ // if unmounted while presenting (e.g. due to sveltekit navigation), end the session
167
+ session.current?.end()
169
168
  }
170
169
  })
171
- onDestroy(() => {
172
- // if unmounted while presenting (e.g. due to sveltekit navigation), end the session
173
- if (session.current) {
174
- session.current.end()
175
- }
170
+
171
+ $effect.pre(() => {
172
+ updateTargetFrameRate(frameRate)
173
+ })
174
+
175
+ $effect.pre(() => {
176
+ xr.setFoveation(foveation)
176
177
  })
177
178
 
178
- $effect.pre(() => updateTargetFrameRate(frameRate))
179
- $effect.pre(() => xr.setFoveation(foveation))
180
179
  $effect.pre(() => {
181
180
  xr.setReferenceSpaceType(referenceSpace)
182
181
  $referenceSpaceType = referenceSpace
@@ -75,7 +75,7 @@ display info about your WebXR session. This is aliased by `ARButton` and
75
75
  }
76
76
  }
77
77
 
78
- let modeText = $derived(
78
+ const modeText = $derived(
79
79
  {
80
80
  'immersive-vr': 'VR',
81
81
  'immersive-ar': 'AR',
@@ -83,7 +83,7 @@ display info about your WebXR session. This is aliased by `ARButton` and
83
83
  }[mode]
84
84
  )
85
85
 
86
- let style = $derived(
86
+ const style = $derived(
87
87
  styled
88
88
  ? `
89
89
  position: absolute;
@@ -111,7 +111,7 @@ display info about your WebXR session. This is aliased by `ARButton` and
111
111
  {style}
112
112
  >
113
113
  {#if children}
114
- {@render children?.({ state })}
114
+ {@render children({ state })}
115
115
  {:else if state === 'unsupported'}
116
116
  {modeText} unsupported
117
117
  {:else if state === 'insecure'}
@@ -8,7 +8,7 @@
8
8
  thickness?: number
9
9
  }
10
10
 
11
- let { color = new Color('white'), size = 0.03, thickness = 0.035 }: Props = $props()
11
+ const { color = new Color('white'), size = 0.03, thickness = 0.035 }: Props = $props()
12
12
 
13
13
  const vertexShader = `
14
14
  uniform mat4 projectionMatrix;
@@ -52,6 +52,7 @@
52
52
  $effect.pre(() => {
53
53
  uniforms.thickness.value = thickness
54
54
  })
55
+
55
56
  $effect.pre(() => {
56
57
  uniforms.color.value = color
57
58
  })
@@ -1,6 +1,16 @@
1
- <script lang="ts">
1
+ <script
2
+ module
3
+ lang="ts"
4
+ >
2
5
  import { Group, Vector3, Matrix3 } from 'three'
3
- import { T, useTask } from '@threlte/core'
6
+
7
+ const vec3 = new Vector3()
8
+ const normalMatrix = new Matrix3()
9
+ const worldNormal = new Vector3()
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import { T, useTask, useThrelte } from '@threlte/core'
4
14
  import { pointerIntersection, pointerState } from '../../internal/stores'
5
15
  import Cursor from './Cursor.svelte'
6
16
  import type { Snippet } from 'svelte'
@@ -10,27 +20,29 @@
10
20
  children?: Snippet
11
21
  }
12
22
 
13
- let { handedness, children }: Props = $props()
23
+ const { handedness, children }: Props = $props()
14
24
 
25
+ const { scene } = useThrelte()
15
26
  const ref = new Group()
16
- const vec3 = new Vector3()
17
- const normalMatrix = new Matrix3()
18
- const worldNormal = new Vector3()
19
-
20
- let hovering = $derived($pointerState[handedness].hovering)
21
- let intersection = $derived(pointerIntersection[handedness])
27
+ const hovering = $derived($pointerState[handedness].hovering)
28
+ const intersection = $derived(pointerIntersection[handedness])
22
29
 
23
30
  const { start, stop } = useTask(
24
31
  () => {
25
- if (intersection.current === undefined) return
32
+ if (intersection.current === undefined) {
33
+ return
34
+ }
35
+
26
36
  const { point, face, object } = intersection.current
27
37
  ref.position.lerp(point, 0.4)
28
38
 
29
- if (face) {
30
- normalMatrix.getNormalMatrix(object.matrixWorld)
31
- worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize()
32
- ref.lookAt(vec3.addVectors(point, worldNormal))
39
+ if (face === null || face === undefined) {
40
+ return
33
41
  }
42
+
43
+ normalMatrix.getNormalMatrix(object.matrixWorld)
44
+ worldNormal.copy(face.normal).applyMatrix3(normalMatrix).normalize()
45
+ ref.lookAt(vec3.addVectors(point, worldNormal))
34
46
  },
35
47
  {
36
48
  autoStart: false
@@ -38,8 +50,8 @@
38
50
  )
39
51
 
40
52
  $effect.pre(() => {
41
- if (hovering) {
42
- ref.position.copy(intersection.current!.point)
53
+ if (hovering && intersection.current) {
54
+ ref.position.copy(intersection.current?.point)
43
55
  start()
44
56
  } else {
45
57
  stop()
@@ -49,6 +61,7 @@
49
61
 
50
62
  <T
51
63
  is={ref}
64
+ attach={scene}
52
65
  visible={hovering}
53
66
  >
54
67
  {#if children}
@@ -8,11 +8,11 @@
8
8
  children?: Snippet
9
9
  }
10
10
 
11
- let { handedness, children }: Props = $props()
11
+ const { handedness, children }: Props = $props()
12
12
 
13
- let hovering = $derived($teleportState[handedness].hovering)
14
- let intersection = $derived(teleportIntersection[handedness])
15
- let visible = $derived(
13
+ const hovering = $derived($teleportState[handedness].hovering)
14
+ const intersection = $derived(teleportIntersection[handedness])
15
+ const visible = $derived(
16
16
  $pointerState[handedness].enabled || (hovering && $intersection === undefined)
17
17
  )
18
18
 
@@ -1,7 +1,17 @@
1
- <script lang="ts">
2
- import { spring } from 'svelte/motion'
1
+ <script
2
+ module
3
+ lang="ts"
4
+ >
3
5
  import { Group, Matrix3, Vector3 } from 'three'
4
- import { T, useTask } from '@threlte/core'
6
+
7
+ const vec3 = new Vector3()
8
+ const normalMatrix = new Matrix3()
9
+ const worldNormal = new Vector3()
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import { Spring } from 'svelte/motion'
14
+ import { T, useTask, useThrelte } from '@threlte/core'
5
15
  import { teleportIntersection } from '../../internal/stores'
6
16
  import Cursor from './Cursor.svelte'
7
17
  import type { Snippet } from 'svelte'
@@ -11,18 +21,17 @@
11
21
  children?: Snippet
12
22
  }
13
23
 
14
- let { handedness, children }: Props = $props()
24
+ const { handedness, children }: Props = $props()
15
25
 
26
+ const { scene } = useThrelte()
16
27
  const ref = new Group()
17
- const vec3 = new Vector3()
18
- const normalMatrix = new Matrix3()
19
- const worldNormal = new Vector3()
20
-
21
- let intersection = $derived(teleportIntersection[handedness])
28
+ const intersection = $derived(teleportIntersection[handedness])
22
29
 
23
30
  const { start, stop } = useTask(
24
31
  () => {
25
- if (intersection.current === undefined) return
32
+ if (intersection.current === undefined) {
33
+ return
34
+ }
26
35
 
27
36
  const { point, face, object } = intersection.current
28
37
  ref.position.lerp(point, 0.4)
@@ -38,7 +47,7 @@
38
47
  }
39
48
  )
40
49
 
41
- const size = spring(0.1, { stiffness: 0.2 })
50
+ const size = new Spring(0.1, { stiffness: 0.2 })
42
51
 
43
52
  $effect.pre(() => {
44
53
  if ($intersection === undefined) {
@@ -54,13 +63,14 @@
54
63
 
55
64
  <T
56
65
  is={ref}
66
+ attach={scene}
57
67
  visible={$intersection !== undefined}
58
68
  >
59
69
  {#if children}
60
70
  {@render children()}
61
71
  {:else}
62
72
  <Cursor
63
- size={$size}
73
+ size={size.current}
64
74
  thickness={0.015}
65
75
  />
66
76
  {/if}
@@ -1,11 +1,24 @@
1
- <script lang="ts">
1
+ <script
2
+ module
3
+ lang="ts"
4
+ >
2
5
  import { Vector3, QuadraticBezierCurve3, type XRTargetRaySpace, Vector2 } from 'three'
6
+
7
+ const rayStart = new Vector3()
8
+ const rayMidpoint = new Vector3()
9
+ const curve = new QuadraticBezierCurve3()
10
+ const vec3 = new Vector3()
11
+ const v2_1 = new Vector2()
12
+ const v2_2 = new Vector2()
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import type { Snippet } from 'svelte'
3
17
  import { Line2 } from 'three/examples/jsm/lines/Line2.js'
4
18
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
5
19
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
6
- import { T, useTask } from '@threlte/core'
20
+ import { T, useTask, useThrelte } from '@threlte/core'
7
21
  import { teleportIntersection } from '../../internal/stores'
8
- import type { Snippet } from 'svelte'
9
22
 
10
23
  interface Props {
11
24
  handedness: 'left' | 'right'
@@ -13,21 +26,13 @@
13
26
  children?: Snippet
14
27
  }
15
28
 
16
- let { handedness, targetRay, children }: Props = $props()
17
-
18
- let lineGeometry = new LineGeometry()
29
+ const { handedness, targetRay, children }: Props = $props()
19
30
 
20
- const rayStart = new Vector3()
21
- const rayMidpoint = new Vector3()
22
- const curve = new QuadraticBezierCurve3()
31
+ const { scene } = useThrelte()
23
32
  const rayDivisions = 40
24
33
  const positions = new Float32Array(rayDivisions * 3)
25
- const vec3 = new Vector3()
26
-
27
- const v2_1 = new Vector2()
28
- const v2_2 = new Vector2()
29
-
30
- let intersection = $derived(teleportIntersection[handedness])
34
+ const lineGeometry = new LineGeometry()
35
+ const intersection = $derived(teleportIntersection[handedness])
31
36
 
32
37
  const setCurvePoints = (alpha = 0.3) => {
33
38
  if (intersection.current === undefined) return
@@ -83,6 +88,7 @@
83
88
  {:else}
84
89
  <T
85
90
  is={Line2}
91
+ attach={scene}
86
92
  visible={$intersection !== undefined}
87
93
  position.z={-0.01}
88
94
  >
@@ -96,6 +96,9 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
96
96
  let stopped = false;
97
97
  // loop through all hits and dispatch events
98
98
  dispatchEvents: for (const hit of hits) {
99
+ const events = dispatchers.get(hit.eventObject);
100
+ if (events === undefined)
101
+ continue;
99
102
  const intersectionEvent = {
100
103
  stopped,
101
104
  ...hit,
@@ -115,9 +118,6 @@ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) =
115
118
  pointer: handContext.pointer.current,
116
119
  ray: context.raycaster.ray
117
120
  };
118
- const events = dispatchers.get(hit.eventObject);
119
- if (events === undefined)
120
- return;
121
121
  if (isPointerMove) {
122
122
  // Move event ...
123
123
  handContext.pointer.update((value) => value.copy(intersectionEvent.point));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threlte/xr",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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",
@@ -1,23 +0,0 @@
1
- <script lang="ts">
2
- import { T, useThrelte } from '@threlte/core'
3
- import { Object3D } from 'three'
4
-
5
- const { scene } = useThrelte()
6
-
7
- const proxy = new Object3D()
8
- proxy.add = (child) => {
9
- scene.add(child)
10
- return child
11
- }
12
- proxy.remove = (child) => {
13
- scene.remove(child)
14
- return child
15
- }
16
- </script>
17
-
18
- <T
19
- is={proxy}
20
- attach={false}
21
- >
22
- <slot />
23
- </T>
@@ -1,20 +0,0 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const ScenePortal: $$__sveltets_2_IsomorphicComponent<any, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {
17
- default: {};
18
- }, {}, string>;
19
- type ScenePortal = InstanceType<typeof ScenePortal>;
20
- export default ScenePortal;