@threlte/xr 0.0.11 → 0.0.12

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.
Files changed (63) hide show
  1. package/dist/components/Controller.svelte +62 -13
  2. package/dist/components/Controller.svelte.d.ts +4 -0
  3. package/dist/components/Hand.svelte +11 -7
  4. package/dist/components/Headset.svelte +4 -7
  5. package/dist/components/internal/Cursor.svelte +51 -0
  6. package/dist/components/internal/Cursor.svelte.d.ts +19 -0
  7. package/dist/components/internal/PointerCursor.svelte +42 -0
  8. package/dist/components/internal/PointerCursor.svelte.d.ts +18 -0
  9. package/dist/components/internal/ScenePortal.svelte +10 -0
  10. package/dist/components/internal/ScenePortal.svelte.d.ts +16 -0
  11. package/dist/components/internal/ShortRay.svelte +45 -0
  12. package/dist/components/{ShortRay.svelte.d.ts → internal/ShortRay.svelte.d.ts} +4 -2
  13. package/dist/components/internal/TeleportCursor.svelte +47 -0
  14. package/dist/components/internal/TeleportCursor.svelte.d.ts +18 -0
  15. package/dist/components/internal/TeleportRay.svelte +71 -0
  16. package/dist/components/internal/TeleportRay.svelte.d.ts +20 -0
  17. package/dist/hooks/useController.d.ts +0 -4
  18. package/dist/hooks/useController.js +0 -11
  19. package/dist/index.d.ts +10 -3
  20. package/dist/index.js +13 -2
  21. package/dist/internal/headset.js +1 -1
  22. package/dist/internal/stores.d.ts +28 -2
  23. package/dist/internal/stores.js +28 -2
  24. package/dist/internal/useFixed.d.ts +11 -0
  25. package/dist/internal/useFixed.js +17 -0
  26. package/dist/plugins/pointerControls/compute.d.ts +3 -0
  27. package/dist/plugins/pointerControls/compute.js +14 -0
  28. package/dist/plugins/pointerControls/context.d.ts +12 -0
  29. package/dist/plugins/pointerControls/context.js +27 -0
  30. package/dist/plugins/pointerControls/hook.d.ts +5 -0
  31. package/dist/plugins/pointerControls/hook.js +24 -0
  32. package/dist/plugins/pointerControls/index.d.ts +27 -0
  33. package/dist/plugins/pointerControls/index.js +54 -0
  34. package/dist/plugins/pointerControls/plugin.d.ts +1 -0
  35. package/dist/plugins/pointerControls/plugin.js +28 -0
  36. package/dist/plugins/pointerControls/setup.d.ts +2 -0
  37. package/dist/plugins/pointerControls/setup.js +203 -0
  38. package/dist/plugins/pointerControls/types.d.ts +61 -0
  39. package/dist/plugins/pointerControls/types.js +11 -0
  40. package/dist/plugins/pointerControls/useComponentEventHandlers.d.ts +4 -0
  41. package/dist/plugins/pointerControls/useComponentEventHandlers.js +15 -0
  42. package/dist/plugins/teleportControls/compute.d.ts +3 -0
  43. package/dist/plugins/teleportControls/compute.js +14 -0
  44. package/dist/plugins/teleportControls/context.d.ts +20 -0
  45. package/dist/plugins/teleportControls/context.js +18 -0
  46. package/dist/plugins/teleportControls/hook.d.ts +6 -0
  47. package/dist/plugins/teleportControls/hook.js +40 -0
  48. package/dist/plugins/teleportControls/index.d.ts +19 -0
  49. package/dist/plugins/teleportControls/index.js +54 -0
  50. package/dist/plugins/teleportControls/plugin.d.ts +4 -0
  51. package/dist/plugins/teleportControls/plugin.js +54 -0
  52. package/dist/plugins/teleportControls/setup.d.ts +2 -0
  53. package/dist/plugins/teleportControls/setup.js +62 -0
  54. package/package.json +2 -2
  55. package/dist/components/Ray.svelte +0 -23
  56. package/dist/components/Ray.svelte.d.ts +0 -18
  57. package/dist/components/ShortRay.svelte +0 -32
  58. package/dist/components/TeleportControls.svelte +0 -136
  59. package/dist/components/TeleportControls.svelte.d.ts +0 -54
  60. package/dist/hooks/index.d.ts +0 -7
  61. package/dist/hooks/index.js +0 -7
  62. package/dist/plugins/teleportPlugin.d.ts +0 -1
  63. package/dist/plugins/teleportPlugin.js +0 -41
@@ -5,6 +5,32 @@ export declare const isPresenting: import("@threlte/core").CurrentWritable<boole
5
5
  export declare const isHandTracking: import("@threlte/core").CurrentWritable<boolean>;
6
6
  export declare const session: import("@threlte/core").CurrentWritable<XRSession | undefined>;
7
7
  export declare const referenceSpaceType: import("@threlte/core").CurrentWritable<XRReferenceSpaceType | undefined>;
8
- export declare const activeTeleportController: import("@threlte/core").CurrentWritable<import("three").XRTargetRaySpace | undefined>;
9
- export declare const pendingTeleportDestination: import("@threlte/core").CurrentWritable<import("three").Vector3 | undefined>;
10
8
  export declare const xr: import("@threlte/core").CurrentWritable<import("three").WebXRManager | undefined>;
9
+ export declare const teleportState: import("@threlte/core").CurrentWritable<{
10
+ left: {
11
+ enabled: boolean;
12
+ hovering: boolean;
13
+ };
14
+ right: {
15
+ enabled: boolean;
16
+ hovering: boolean;
17
+ };
18
+ }>;
19
+ export declare const teleportIntersection: {
20
+ left: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Event>> | undefined>;
21
+ right: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Event>> | undefined>;
22
+ };
23
+ export declare const pointerState: import("@threlte/core").CurrentWritable<{
24
+ left: {
25
+ enabled: boolean;
26
+ hovering: boolean;
27
+ };
28
+ right: {
29
+ enabled: boolean;
30
+ hovering: boolean;
31
+ };
32
+ }>;
33
+ export declare const pointerIntersection: {
34
+ left: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Event>> | undefined>;
35
+ right: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Event>> | undefined>;
36
+ };
@@ -5,6 +5,32 @@ export const isPresenting = currentWritable(false);
5
5
  export const isHandTracking = currentWritable(false);
6
6
  export const session = currentWritable(undefined);
7
7
  export const referenceSpaceType = currentWritable(undefined);
8
- export const activeTeleportController = currentWritable(undefined);
9
- export const pendingTeleportDestination = currentWritable(undefined);
10
8
  export const xr = currentWritable(undefined);
9
+ export const teleportState = currentWritable({
10
+ left: {
11
+ enabled: false,
12
+ hovering: false,
13
+ },
14
+ right: {
15
+ enabled: false,
16
+ hovering: false,
17
+ },
18
+ });
19
+ export const teleportIntersection = {
20
+ left: currentWritable(undefined),
21
+ right: currentWritable(undefined),
22
+ };
23
+ export const pointerState = currentWritable({
24
+ left: {
25
+ enabled: false,
26
+ hovering: false,
27
+ },
28
+ right: {
29
+ enabled: false,
30
+ hovering: false,
31
+ }
32
+ });
33
+ export const pointerIntersection = {
34
+ left: currentWritable(undefined),
35
+ right: currentWritable(undefined),
36
+ };
@@ -0,0 +1,11 @@
1
+ import { type ThrelteUseFrameOptions, type ThrelteContext } from '@threlte/core';
2
+ type UseFixedOptions = ThrelteUseFrameOptions & {
3
+ fixedStep?: number;
4
+ };
5
+ /**
6
+ * A fixed useFrame, based on https://github.com/threlte/threlte/pull/654
7
+ *
8
+ * @Todo Can be removed if this or a similar feature is merged.
9
+ */
10
+ export declare const useFixed: (fn: (ctx: ThrelteContext, delta: number) => void, options: UseFixedOptions) => import("@threlte/core/dist/hooks/useFrame").ThrelteUseFrame;
11
+ export {};
@@ -0,0 +1,17 @@
1
+ import { useFrame } from '@threlte/core';
2
+ /**
3
+ * A fixed useFrame, based on https://github.com/threlte/threlte/pull/654
4
+ *
5
+ * @Todo Can be removed if this or a similar feature is merged.
6
+ */
7
+ export const useFixed = (fn, options) => {
8
+ let fixedStepTimeAccumulator = 0;
9
+ let fixedStep = options.fixedStep ?? 1 / 60;
10
+ return useFrame((ctx, delta) => {
11
+ fixedStepTimeAccumulator += delta;
12
+ while (fixedStepTimeAccumulator >= fixedStep) {
13
+ fixedStepTimeAccumulator -= fixedStep;
14
+ fn(ctx, fixedStep);
15
+ }
16
+ }, options);
17
+ };
@@ -0,0 +1,3 @@
1
+ import type { ControlsContext, HandContext } from './types';
2
+ export type ComputeFunction = (state: ControlsContext, handState: HandContext) => void;
3
+ export declare const defaultComputeFunction: ComputeFunction;
@@ -0,0 +1,14 @@
1
+ import { Vector3 } from 'three';
2
+ import { useController } from '../../hooks/useController';
3
+ const controllers = {
4
+ left: useController('left'),
5
+ right: useController('right')
6
+ };
7
+ const forward = new Vector3();
8
+ export const defaultComputeFunction = (context, handContext) => {
9
+ const targetRay = controllers[handContext.hand].current?.targetRay;
10
+ if (targetRay === undefined)
11
+ return;
12
+ forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
13
+ context.raycaster.set(targetRay.position, forward);
14
+ };
@@ -0,0 +1,12 @@
1
+ import type { createRawEventDispatcher } from '@threlte/core';
2
+ import type { ControlsContext, HandContext, ThrelteXREvents } from './types';
3
+ export declare const getHandContext: (hand: 'left' | 'right') => HandContext;
4
+ export declare const setHandContext: (hand: 'left' | 'right', context: HandContext) => void;
5
+ export declare const getControlsContext: () => ControlsContext;
6
+ export declare const setControlsContext: (context: ControlsContext) => void;
7
+ interface InternalContext {
8
+ dispatchers: WeakMap<THREE.Object3D, ReturnType<typeof createRawEventDispatcher<ThrelteXREvents>>>;
9
+ }
10
+ export declare const getInternalContext: () => InternalContext;
11
+ export declare const setInternalContext: () => void;
12
+ export {};
@@ -0,0 +1,27 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const handContextKeys = {
3
+ left: Symbol('pointer-controls-context-left'),
4
+ right: Symbol('pointer-controls-context-right')
5
+ };
6
+ const contextKey = Symbol('pointer-controls-context');
7
+ export const getHandContext = (hand) => {
8
+ return getContext(handContextKeys[hand]);
9
+ };
10
+ export const setHandContext = (hand, context) => {
11
+ setContext(handContextKeys[hand], context);
12
+ };
13
+ export const getControlsContext = () => {
14
+ return getContext(contextKey);
15
+ };
16
+ export const setControlsContext = (context) => {
17
+ setContext(contextKey, context);
18
+ };
19
+ const internalContextKey = Symbol('pointer-controls-internal-context');
20
+ export const getInternalContext = () => {
21
+ return getContext(internalContextKey);
22
+ };
23
+ export const setInternalContext = () => {
24
+ setContext(internalContextKey, {
25
+ dispatchers: new WeakMap()
26
+ });
27
+ };
@@ -0,0 +1,5 @@
1
+ import type { Object3D } from 'three';
2
+ export declare const usePointerControls: () => {
3
+ addInteractiveObject: (object: Object3D) => void;
4
+ removeInteractiveObject: (object: Object3D) => void;
5
+ };
@@ -0,0 +1,24 @@
1
+ import { createRawEventDispatcher } from '@threlte/core';
2
+ import { getControlsContext, getInternalContext } from './context';
3
+ export const usePointerControls = () => {
4
+ const { dispatchers } = getInternalContext();
5
+ const context = getControlsContext();
6
+ const eventDispatcher = createRawEventDispatcher();
7
+ const addInteractiveObject = (object) => {
8
+ // check if the object is already in the list
9
+ if (context.interactiveObjects.indexOf(object) > -1) {
10
+ return;
11
+ }
12
+ dispatchers.set(object, eventDispatcher);
13
+ context.interactiveObjects.push(object);
14
+ };
15
+ const removeInteractiveObject = (object) => {
16
+ const index = context.interactiveObjects.indexOf(object);
17
+ context.interactiveObjects.splice(index, 1);
18
+ dispatchers.delete(object);
19
+ };
20
+ return {
21
+ addInteractiveObject,
22
+ removeInteractiveObject
23
+ };
24
+ };
@@ -0,0 +1,27 @@
1
+ import { type ComputeFunction } from './compute';
2
+ import type { FilterFunction } from './types';
3
+ export type PointerControlsOptions = {
4
+ enabled?: boolean;
5
+ /**
6
+ * The compute function is responsible for updating the state of the pointerControls plugin.
7
+ * It needs to set up the raycaster and the pointer vector. If no compute function is provided,
8
+ * the plugin will use the default compute function.
9
+ */
10
+ compute?: ComputeFunction;
11
+ /**
12
+ * The filter function is responsible for filtering and sorting the
13
+ * intersections. By default, the intersections are sorted by distance. If no
14
+ * filter function is provided, the plugin will use the default filter function.
15
+ */
16
+ filter?: FilterFunction;
17
+ /**
18
+ * Sets the interval at which raycasting occurs.
19
+ *
20
+ * @default 1 / 40
21
+ */
22
+ fixedStep?: number;
23
+ };
24
+ export declare const pointerControls: (handedness: 'left' | 'right', options?: PointerControlsOptions) => {
25
+ enabled: import("@threlte/core").CurrentWritable<boolean>;
26
+ hovered: Map<string, import("./types").IntersectionEvent>;
27
+ };
@@ -0,0 +1,54 @@
1
+ import { Raycaster, Vector3 } from 'three';
2
+ import { currentWritable, watch } from '@threlte/core';
3
+ import { defaultComputeFunction } from './compute';
4
+ import { injectPointerControlsPlugin } from './plugin';
5
+ import { setupPointerControls } from './setup';
6
+ import { getControlsContext, getHandContext, setControlsContext, setHandContext, setInternalContext } from './context';
7
+ import { pointerState } from '../../internal/stores';
8
+ let controlsCounter = 0;
9
+ export const pointerControls = (handedness, options) => {
10
+ if (getControlsContext() === undefined) {
11
+ injectPointerControlsPlugin();
12
+ setInternalContext();
13
+ setControlsContext({
14
+ interactiveObjects: [],
15
+ raycaster: new Raycaster(),
16
+ compute: options?.compute ?? defaultComputeFunction,
17
+ filter: options?.filter,
18
+ });
19
+ }
20
+ const context = getControlsContext();
21
+ if (getHandContext(handedness) === undefined) {
22
+ const enabled = options?.enabled ?? true;
23
+ const ctx = {
24
+ hand: handedness,
25
+ enabled: currentWritable(enabled),
26
+ pointer: currentWritable(new Vector3()),
27
+ pointerOverTarget: currentWritable(false),
28
+ lastEvent: undefined,
29
+ initialClick: [0, 0, 0],
30
+ initialHits: [],
31
+ hovered: new Map(),
32
+ };
33
+ setHandContext(handedness, ctx);
34
+ setupPointerControls(context, ctx, options?.fixedStep);
35
+ }
36
+ const handContext = getHandContext(handedness);
37
+ watch(handContext.enabled, (enabled) => {
38
+ controlsCounter += (enabled ? 1 : -1);
39
+ pointerState.update((value) => {
40
+ value[handedness].enabled = controlsCounter > 0;
41
+ return value;
42
+ });
43
+ });
44
+ watch(handContext.pointerOverTarget, (hovering) => {
45
+ pointerState.update((value) => {
46
+ value[handedness].hovering = hovering;
47
+ return value;
48
+ });
49
+ });
50
+ return {
51
+ enabled: handContext.enabled,
52
+ hovered: handContext.hovered,
53
+ };
54
+ };
@@ -0,0 +1 @@
1
+ export declare const injectPointerControlsPlugin: () => void;
@@ -0,0 +1,28 @@
1
+ import { injectPlugin, watch } from '@threlte/core';
2
+ import { writable } from 'svelte/store';
3
+ import { usePointerControls } from './hook';
4
+ import { useComponentHasEventHandlers } from './useComponentEventHandlers';
5
+ export const injectPointerControlsPlugin = () => {
6
+ injectPlugin('threlte-pointer-controls', ({ ref }) => {
7
+ if (ref.isMesh !== true)
8
+ return;
9
+ const { addInteractiveObject, removeInteractiveObject } = usePointerControls();
10
+ const refStore = writable(ref);
11
+ const { hasEventHandlers } = useComponentHasEventHandlers();
12
+ watch([hasEventHandlers, refStore], ([hasEventHandlers, ref]) => {
13
+ // Because hasEventHandlers will only be set from false to true in the
14
+ // lifecycle of the component, we can safely assume that we do not need to
15
+ // remove the object from the list of interactive objects when
16
+ // hasEventHandlers is false.
17
+ if (!hasEventHandlers)
18
+ return;
19
+ addInteractiveObject(ref);
20
+ return () => removeInteractiveObject(ref);
21
+ });
22
+ return {
23
+ onRefChange(ref) {
24
+ refStore.set(ref);
25
+ }
26
+ };
27
+ });
28
+ };
@@ -0,0 +1,2 @@
1
+ import type { ControlsContext, HandContext } from './types';
2
+ export declare const setupPointerControls: (context: ControlsContext, handContext: HandContext, fixedStep?: number) => void;
@@ -0,0 +1,203 @@
1
+ import { Vector3 } from 'three';
2
+ import { watch } from '@threlte/core';
3
+ import { getInternalContext } from './context';
4
+ import { useController } from '../../hooks/useController';
5
+ import { useHand } from '../../hooks/useHand';
6
+ import { useXR } from '../../hooks/useXR';
7
+ import { useFixed } from '../../internal/useFixed';
8
+ import { pointerIntersection } from '../../internal/stores';
9
+ const getIntersectionId = (intersection) => {
10
+ return `${(intersection.eventObject || intersection.object).uuid}/${intersection.index}${intersection.instanceId ?? ''}`;
11
+ };
12
+ const EPSILON = 0.0001;
13
+ export const setupPointerControls = (context, handContext, fixedStep = 1 / 40) => {
14
+ const handedness = handContext.hand;
15
+ const controller = useController(handedness);
16
+ const hand = useHand(handedness);
17
+ const { dispatchers } = getInternalContext();
18
+ let hits = [];
19
+ let lastPosition = new Vector3();
20
+ const handlePointerDown = (event) => {
21
+ // Save initial coordinates on pointer-down
22
+ const [hit] = hits;
23
+ if (!hit)
24
+ return;
25
+ handContext.initialClick = [hit.point.x, hit.point.y, hit.point.z];
26
+ handContext.initialHits = hits.map((hit) => hit.eventObject);
27
+ handleEvent('pointerdown', event);
28
+ };
29
+ const handlePointerUp = (event) => {
30
+ handleEvent('pointerup', event);
31
+ };
32
+ const handleClick = (event) => {
33
+ // If a click yields no results, pass it back to the user as a miss
34
+ // Missed events have to come first in order to establish user-land side-effect clean up
35
+ if (hits.length === 0) {
36
+ pointerMissed(context.interactiveObjects, event);
37
+ }
38
+ handleEvent('click', event);
39
+ };
40
+ function cancelPointer(intersections) {
41
+ for (const [, hoveredObj] of handContext.hovered) {
42
+ // When no objects were hit or the the hovered object wasn't found underneath the cursor
43
+ // we call pointerout and delete the object from the hovered elements map
44
+ if (intersections.length === 0 ||
45
+ !intersections.some((hit) => hit.object === hoveredObj.object &&
46
+ hit.index === hoveredObj.index &&
47
+ hit.instanceId === hoveredObj.instanceId)) {
48
+ const { eventObject } = hoveredObj;
49
+ handContext.hovered.delete(getIntersectionId(hoveredObj));
50
+ const eventDispatcher = dispatchers.get(eventObject);
51
+ if (eventDispatcher) {
52
+ // Clear out intersects, they are outdated by now
53
+ const data = { ...hoveredObj, intersections };
54
+ eventDispatcher('pointerout', data);
55
+ eventDispatcher('pointerleave', data);
56
+ // Deal with cancelation
57
+ handContext.pointerOverTarget.set(false);
58
+ cancelPointer([]);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ const getHits = () => {
64
+ const intersections = [];
65
+ const hits = context.raycaster.intersectObjects(context.interactiveObjects, true);
66
+ const filtered = context.filter === undefined ? hits : context.filter(hits, context, handContext);
67
+ pointerIntersection[handedness].set(filtered[0]);
68
+ // Bubble up the events, find the event source (eventObject)
69
+ for (const hit of filtered) {
70
+ let eventObject = hit.object;
71
+ // Bubble event up
72
+ while (eventObject) {
73
+ if (dispatchers.has(eventObject)) {
74
+ intersections.push({ ...hit, eventObject });
75
+ }
76
+ eventObject = eventObject.parent;
77
+ }
78
+ }
79
+ return intersections;
80
+ };
81
+ function pointerMissed(objects, event) {
82
+ for (const object of objects) {
83
+ dispatchers.get(object)?.('pointermissed', event);
84
+ }
85
+ }
86
+ function processHits() {
87
+ context.compute(context, handContext);
88
+ return getHits();
89
+ }
90
+ const handleEvent = (name, event) => {
91
+ const isPointerMove = name === 'pointermove';
92
+ const isClickEvent = name === 'click' || name === 'contextmenu';
93
+ // Take care of unhover
94
+ if (isPointerMove)
95
+ cancelPointer(hits);
96
+ let stopped = false;
97
+ // loop through all hits and dispatch events
98
+ dispatchEvents: for (const hit of hits) {
99
+ const intersectionEvent = {
100
+ stopped,
101
+ ...hit,
102
+ intersections: hits,
103
+ stopPropagation() {
104
+ stopped = true;
105
+ intersectionEvent.stopped = true;
106
+ if (handContext.hovered.size > 0 &&
107
+ Array.from(handContext.hovered.values()).some((i) => i.eventObject === hit.eventObject)) {
108
+ // Objects cannot flush out higher up objects that have already caught the event
109
+ const higher = hits.slice(0, hits.indexOf(hit));
110
+ cancelPointer([...higher, hit]);
111
+ }
112
+ },
113
+ delta: 0,
114
+ nativeEvent: event,
115
+ pointer: handContext.pointer.current,
116
+ ray: context.raycaster.ray
117
+ };
118
+ const eventDispatcher = dispatchers.get(hit.eventObject);
119
+ if (eventDispatcher === undefined)
120
+ return;
121
+ if (isPointerMove) {
122
+ // Move event ...
123
+ handContext.pointer.update((value) => value.copy(intersectionEvent.point));
124
+ if (eventDispatcher.hasEventListener('pointerover') ||
125
+ eventDispatcher.hasEventListener('pointerenter') ||
126
+ eventDispatcher.hasEventListener('pointerout') ||
127
+ eventDispatcher.hasEventListener('pointerleave')) {
128
+ const id = getIntersectionId(intersectionEvent);
129
+ const hoveredItem = handContext.hovered.get(id);
130
+ if (hoveredItem === undefined) {
131
+ // If the object wasn't previously hovered, book it and call its handler
132
+ handContext.hovered.set(id, intersectionEvent);
133
+ eventDispatcher('pointerover', intersectionEvent);
134
+ eventDispatcher('pointerenter', intersectionEvent);
135
+ handContext.pointerOverTarget.set(true);
136
+ }
137
+ else if (hoveredItem.stopped) {
138
+ // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
139
+ intersectionEvent.stopPropagation();
140
+ }
141
+ }
142
+ // Call pointer move
143
+ eventDispatcher('pointermove', intersectionEvent);
144
+ }
145
+ else if ((!isClickEvent || handContext.initialHits.includes(hit.eventObject)) &&
146
+ eventDispatcher.hasEventListener(name)) {
147
+ // Missed events have to come first
148
+ pointerMissed(context.interactiveObjects.filter((object) => !handContext.initialHits.includes(object)), event);
149
+ // Call the event
150
+ eventDispatcher(name, intersectionEvent);
151
+ }
152
+ else if (isClickEvent && handContext.initialHits.includes(hit.eventObject)) {
153
+ pointerMissed(context.interactiveObjects.filter((object) => !handContext.initialHits.includes(object)), event);
154
+ }
155
+ if (stopped)
156
+ break dispatchEvents;
157
+ }
158
+ };
159
+ const { start, stop } = useFixed(() => {
160
+ hits = processHits();
161
+ const targetRay = controller.current?.targetRay;
162
+ if (targetRay === undefined)
163
+ return;
164
+ if (targetRay.position.distanceTo(lastPosition) > EPSILON) {
165
+ handleEvent('pointermove');
166
+ }
167
+ lastPosition.copy(targetRay.position);
168
+ }, {
169
+ fixedStep,
170
+ autostart: false
171
+ });
172
+ watch(controller, (input) => {
173
+ if (input === undefined)
174
+ return;
175
+ input.targetRay.addEventListener('selectstart', handlePointerDown);
176
+ input.targetRay.addEventListener('selectend', handlePointerUp);
177
+ input.targetRay.addEventListener('select', handleClick);
178
+ return () => {
179
+ input.targetRay.removeEventListener('selectstart', handlePointerDown);
180
+ input.targetRay.removeEventListener('selectend', handlePointerUp);
181
+ input.targetRay.removeEventListener('select', handleClick);
182
+ };
183
+ });
184
+ // watch(hand, (input) => {
185
+ // if (input === undefined) return
186
+ // input.hand.addEventListener('pinchstart', handlePointerDown)
187
+ // input.hand.addEventListener('pinchend', handlePointerUp)
188
+ // input.hand.addEventListener('pinchend', handleClick)
189
+ // return () => {
190
+ // input.hand.removeEventListener('pinchstart', handlePointerDown)
191
+ // input.hand.removeEventListener('pinchend', handlePointerUp)
192
+ // input.hand.removeEventListener('pinchend', handleClick)
193
+ // }
194
+ // })
195
+ watch([useXR().isPresenting, handContext.enabled], ([isPresenting, enabled]) => {
196
+ if (isPresenting && enabled) {
197
+ start();
198
+ }
199
+ else {
200
+ stop();
201
+ }
202
+ });
203
+ };
@@ -0,0 +1,61 @@
1
+ import type { CurrentWritable } from '@threlte/core';
2
+ import type { ComputeFunction } from './compute';
3
+ export type Properties<T> = Pick<T, {
4
+ [K in keyof T]: T[K] extends (_: any) => any ? never : K;
5
+ }[keyof T]>;
6
+ export interface Intersection extends THREE.Intersection {
7
+ /** The event source (the object which registered the handler) */
8
+ eventObject: THREE.Object3D;
9
+ }
10
+ export interface IntersectionEvent extends Intersection {
11
+ /** The event source (the object which registered the handler) */
12
+ eventObject: THREE.Object3D;
13
+ /** An array of intersections */
14
+ intersections: Intersection[];
15
+ /** Normalized event coordinates */
16
+ pointer: THREE.Vector3;
17
+ /** Delta between first click and this event */
18
+ delta: number;
19
+ /** The ray that pierced it */
20
+ ray: THREE.Ray;
21
+ /** stopPropagation will stop underlying handlers from firing */
22
+ stopPropagation: () => void;
23
+ /** The original host event */
24
+ nativeEvent: THREE.Event | undefined;
25
+ /** If the event was stopped by calling stopPropagation */
26
+ stopped: boolean;
27
+ }
28
+ export type FilterFunction = (items: THREE.Intersection[], state: ControlsContext, handState: HandContext) => THREE.Intersection[];
29
+ export type ControlsContext = {
30
+ interactiveObjects: THREE.Object3D[];
31
+ raycaster: THREE.Raycaster;
32
+ compute: ComputeFunction;
33
+ filter?: FilterFunction | undefined;
34
+ };
35
+ export type HandContext = {
36
+ hand: 'left' | 'right';
37
+ enabled: CurrentWritable<boolean>;
38
+ pointer: CurrentWritable<THREE.Vector3>;
39
+ pointerOverTarget: CurrentWritable<boolean>;
40
+ lastEvent: THREE.Event | undefined;
41
+ initialClick: [x: number, y: number, z: number];
42
+ initialHits: THREE.Object3D[];
43
+ hovered: Map<string, IntersectionEvent>;
44
+ };
45
+ export interface PointerCaptureTarget {
46
+ intersection: Intersection;
47
+ target: Element;
48
+ }
49
+ export type ThrelteXREvents = {
50
+ click: IntersectionEvent;
51
+ contextmenu: IntersectionEvent;
52
+ pointerup: IntersectionEvent;
53
+ pointerdown: IntersectionEvent;
54
+ pointerover: IntersectionEvent;
55
+ pointerout: IntersectionEvent;
56
+ pointerenter: IntersectionEvent;
57
+ pointerleave: IntersectionEvent;
58
+ pointermove: IntersectionEvent;
59
+ pointermissed: IntersectionEvent;
60
+ };
61
+ export declare const events: (keyof ThrelteXREvents)[];
@@ -0,0 +1,11 @@
1
+ export const events = [
2
+ 'click',
3
+ 'contextmenu',
4
+ 'pointerup',
5
+ 'pointerdown',
6
+ 'pointerover',
7
+ 'pointerout',
8
+ 'pointerenter',
9
+ 'pointerleave',
10
+ 'pointermove'
11
+ ];
@@ -0,0 +1,4 @@
1
+ /// <reference types="svelte" />
2
+ export declare const useComponentHasEventHandlers: () => {
3
+ hasEventHandlers: import("svelte/store").Writable<boolean>;
4
+ };
@@ -0,0 +1,15 @@
1
+ import { onMount } from 'svelte';
2
+ import { get_current_component } from 'svelte/internal';
3
+ import { writable } from 'svelte/store';
4
+ import { events } from './types';
5
+ export const useComponentHasEventHandlers = () => {
6
+ const component = get_current_component();
7
+ const hasEventHandlers = writable(false);
8
+ onMount(() => {
9
+ const match = Object.keys(component.$$.callbacks).some((callback) => events.includes(callback));
10
+ hasEventHandlers.set(match);
11
+ });
12
+ return {
13
+ hasEventHandlers
14
+ };
15
+ };
@@ -0,0 +1,3 @@
1
+ import type { Context, HandContext } from './context';
2
+ export type ComputeFunction = (context: Context, handContext: HandContext) => void;
3
+ export declare const defaultComputeFunction: (context: Context, handContext: HandContext) => void;
@@ -0,0 +1,14 @@
1
+ import { Vector3 } from 'three';
2
+ import { useController } from '../../hooks/useController';
3
+ const controllers = {
4
+ left: useController('left'),
5
+ right: useController('right')
6
+ };
7
+ const forward = new Vector3();
8
+ export const defaultComputeFunction = (context, handContext) => {
9
+ const targetRay = controllers[handContext.hand].current?.targetRay;
10
+ if (targetRay === undefined)
11
+ return;
12
+ forward.set(0, 0, -1).applyQuaternion(targetRay.quaternion);
13
+ context.raycaster.set(targetRay.position, forward);
14
+ };
@@ -0,0 +1,20 @@
1
+ import type { CurrentWritable, createRawEventDispatcher } from '@threlte/core';
2
+ export type ComputeFunction = (context: Context, handContext: HandContext) => void;
3
+ export interface Context {
4
+ interactiveObjects: THREE.Mesh[];
5
+ surfaces: Map<string, THREE.Mesh>;
6
+ blockers: Map<string, THREE.Mesh>;
7
+ dispatchers: WeakMap<THREE.Mesh, ReturnType<typeof createRawEventDispatcher>>;
8
+ raycaster: THREE.Raycaster;
9
+ compute: ComputeFunction;
10
+ }
11
+ export interface HandContext {
12
+ hand: 'left' | 'right';
13
+ enabled: CurrentWritable<boolean>;
14
+ active: CurrentWritable<boolean>;
15
+ hovered: CurrentWritable<THREE.Intersection | undefined>;
16
+ }
17
+ export declare const getHandContext: (hand: 'left' | 'right') => HandContext;
18
+ export declare const setHandContext: (hand: 'left' | 'right', context: HandContext) => void;
19
+ export declare const getTeleportContext: () => Context;
20
+ export declare const setTeleportContext: (context: Context) => void;