@threlte/xr 0.0.10 → 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.
- package/dist/components/Controller.svelte +62 -13
- package/dist/components/Controller.svelte.d.ts +4 -0
- package/dist/components/Hand.svelte +11 -7
- package/dist/components/Headset.svelte +4 -7
- package/dist/components/XR.svelte +2 -0
- package/dist/components/internal/Cursor.svelte +51 -0
- package/dist/components/internal/Cursor.svelte.d.ts +19 -0
- package/dist/components/internal/PointerCursor.svelte +42 -0
- package/dist/components/internal/PointerCursor.svelte.d.ts +18 -0
- package/dist/components/internal/ScenePortal.svelte +10 -0
- package/dist/components/internal/ScenePortal.svelte.d.ts +16 -0
- package/dist/components/internal/ShortRay.svelte +45 -0
- package/dist/components/{ShortRay.svelte.d.ts → internal/ShortRay.svelte.d.ts} +4 -2
- package/dist/components/internal/TeleportCursor.svelte +47 -0
- package/dist/components/internal/TeleportCursor.svelte.d.ts +18 -0
- package/dist/components/internal/TeleportRay.svelte +71 -0
- package/dist/components/internal/TeleportRay.svelte.d.ts +20 -0
- package/dist/hooks/useController.d.ts +0 -4
- package/dist/hooks/useController.js +0 -11
- package/dist/index.d.ts +10 -3
- package/dist/index.js +13 -2
- package/dist/internal/headset.js +1 -1
- package/dist/internal/stores.d.ts +28 -2
- package/dist/internal/stores.js +28 -2
- package/dist/internal/updateRaf.d.ts +1 -0
- package/dist/internal/updateRaf.js +21 -0
- package/dist/internal/useFixed.d.ts +11 -0
- package/dist/internal/useFixed.js +17 -0
- package/dist/plugins/pointerControls/compute.d.ts +3 -0
- package/dist/plugins/pointerControls/compute.js +14 -0
- package/dist/plugins/pointerControls/context.d.ts +12 -0
- package/dist/plugins/pointerControls/context.js +27 -0
- package/dist/plugins/pointerControls/hook.d.ts +5 -0
- package/dist/plugins/pointerControls/hook.js +24 -0
- package/dist/plugins/pointerControls/index.d.ts +27 -0
- package/dist/plugins/pointerControls/index.js +54 -0
- package/dist/plugins/pointerControls/plugin.d.ts +1 -0
- package/dist/plugins/pointerControls/plugin.js +28 -0
- package/dist/plugins/pointerControls/setup.d.ts +2 -0
- package/dist/plugins/pointerControls/setup.js +203 -0
- package/dist/plugins/pointerControls/types.d.ts +61 -0
- package/dist/plugins/pointerControls/types.js +11 -0
- package/dist/plugins/pointerControls/useComponentEventHandlers.d.ts +4 -0
- package/dist/plugins/pointerControls/useComponentEventHandlers.js +15 -0
- package/dist/plugins/teleportControls/compute.d.ts +3 -0
- package/dist/plugins/teleportControls/compute.js +14 -0
- package/dist/plugins/teleportControls/context.d.ts +20 -0
- package/dist/plugins/teleportControls/context.js +18 -0
- package/dist/plugins/teleportControls/hook.d.ts +6 -0
- package/dist/plugins/teleportControls/hook.js +40 -0
- package/dist/plugins/teleportControls/index.d.ts +19 -0
- package/dist/plugins/teleportControls/index.js +54 -0
- package/dist/plugins/teleportControls/plugin.d.ts +4 -0
- package/dist/plugins/teleportControls/plugin.js +54 -0
- package/dist/plugins/teleportControls/setup.d.ts +2 -0
- package/dist/plugins/teleportControls/setup.js +62 -0
- package/package.json +2 -2
- package/dist/components/Ray.svelte +0 -23
- package/dist/components/Ray.svelte.d.ts +0 -18
- package/dist/components/ShortRay.svelte +0 -32
- package/dist/components/TeleportControls.svelte +0 -136
- package/dist/components/TeleportControls.svelte.d.ts +0 -54
- package/dist/hooks/index.d.ts +0 -7
- package/dist/hooks/index.js +0 -7
- package/dist/plugins/teleportPlugin.d.ts +0 -1
- package/dist/plugins/teleportPlugin.js +0 -41
|
@@ -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;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const handContextKeys = {
|
|
3
|
+
left: Symbol('teleport-controls-context-left-hand'),
|
|
4
|
+
right: Symbol('teleport-controls-context-right-hand')
|
|
5
|
+
};
|
|
6
|
+
const contextKey = Symbol('teleport-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 getTeleportContext = () => {
|
|
14
|
+
return getContext(contextKey);
|
|
15
|
+
};
|
|
16
|
+
export const setTeleportContext = (context) => {
|
|
17
|
+
setContext(contextKey, context);
|
|
18
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRawEventDispatcher } from '@threlte/core';
|
|
2
|
+
import { getTeleportContext } from './context';
|
|
3
|
+
export const useTeleportControls = () => {
|
|
4
|
+
const context = getTeleportContext();
|
|
5
|
+
const eventDispatcher = createRawEventDispatcher();
|
|
6
|
+
const addSurface = (mesh) => {
|
|
7
|
+
// check if the object is already in the list
|
|
8
|
+
if (context.interactiveObjects.indexOf(mesh) > -1) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
context.interactiveObjects.push(mesh);
|
|
12
|
+
context.surfaces.set(mesh.uuid, mesh);
|
|
13
|
+
context.dispatchers.set(mesh, eventDispatcher);
|
|
14
|
+
};
|
|
15
|
+
const removeSurface = (mesh) => {
|
|
16
|
+
const index = context.interactiveObjects.indexOf(mesh);
|
|
17
|
+
context.interactiveObjects.splice(index, 1);
|
|
18
|
+
context.surfaces.delete(mesh.uuid);
|
|
19
|
+
context.dispatchers.delete(mesh);
|
|
20
|
+
};
|
|
21
|
+
const addBlocker = (mesh) => {
|
|
22
|
+
// check if the object is already in the list
|
|
23
|
+
if (context.interactiveObjects.indexOf(mesh) > -1) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
context.interactiveObjects.push(mesh);
|
|
27
|
+
context.blockers.set(mesh.uuid, mesh);
|
|
28
|
+
};
|
|
29
|
+
const removeBlocker = (mesh) => {
|
|
30
|
+
const index = context.interactiveObjects.indexOf(mesh);
|
|
31
|
+
context.interactiveObjects.splice(index, 1);
|
|
32
|
+
context.blockers.delete(mesh.uuid);
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
addSurface,
|
|
36
|
+
removeSurface,
|
|
37
|
+
addBlocker,
|
|
38
|
+
removeBlocker
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ComputeFunction } from './context';
|
|
2
|
+
export interface TeleportControlsOptions {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The compute function is responsible for updating the state of the pointerControls plugin.
|
|
6
|
+
* It needs to set up the raycaster and the pointer vector. If no compute function is provided,
|
|
7
|
+
* the plugin will use the default compute function.
|
|
8
|
+
*/
|
|
9
|
+
compute?: ComputeFunction;
|
|
10
|
+
/**
|
|
11
|
+
* @default 1 / 30
|
|
12
|
+
*/
|
|
13
|
+
fixedStep?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const teleportControls: (handedness: 'left' | 'right', options?: TeleportControlsOptions) => {
|
|
16
|
+
enabled: import("@threlte/core").CurrentWritable<boolean>;
|
|
17
|
+
hovered: import("@threlte/core").CurrentWritable<import("three").Intersection<import("three").Object3D<import("three").Event>> | undefined>;
|
|
18
|
+
active: import("@threlte/core").CurrentWritable<boolean>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Raycaster } from 'three';
|
|
2
|
+
import { currentWritable, watch } from '@threlte/core';
|
|
3
|
+
import { setTeleportContext, getTeleportContext, getHandContext } from './context';
|
|
4
|
+
import { injectTeleportControlsPlugin } from './plugin';
|
|
5
|
+
import { defaultComputeFunction } from './compute';
|
|
6
|
+
import { setHandContext } from './context';
|
|
7
|
+
import { setupTeleportControls } from './setup';
|
|
8
|
+
import { teleportState } from '../../internal/stores';
|
|
9
|
+
let controlsCounter = 0;
|
|
10
|
+
export const teleportControls = (handedness, options) => {
|
|
11
|
+
if (getTeleportContext() === undefined) {
|
|
12
|
+
injectTeleportControlsPlugin();
|
|
13
|
+
setTeleportContext({
|
|
14
|
+
interactiveObjects: [],
|
|
15
|
+
surfaces: new Map(),
|
|
16
|
+
blockers: new Map(),
|
|
17
|
+
dispatchers: new WeakMap(),
|
|
18
|
+
raycaster: new Raycaster(),
|
|
19
|
+
compute: options?.compute ?? defaultComputeFunction
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const context = getTeleportContext();
|
|
23
|
+
if (getHandContext(handedness) === undefined) {
|
|
24
|
+
const enabled = options?.enabled ?? true;
|
|
25
|
+
controlsCounter += enabled ? 1 : -1;
|
|
26
|
+
const ctx = {
|
|
27
|
+
hand: handedness,
|
|
28
|
+
active: currentWritable(false),
|
|
29
|
+
enabled: currentWritable(enabled),
|
|
30
|
+
hovered: currentWritable(undefined)
|
|
31
|
+
};
|
|
32
|
+
setHandContext(handedness, ctx);
|
|
33
|
+
setupTeleportControls(context, ctx, options?.fixedStep);
|
|
34
|
+
}
|
|
35
|
+
const handContext = getHandContext(handedness);
|
|
36
|
+
watch(handContext.enabled, (enabled) => {
|
|
37
|
+
controlsCounter += enabled ? 1 : -1;
|
|
38
|
+
teleportState.update((value) => {
|
|
39
|
+
value[handedness].enabled = controlsCounter > 0;
|
|
40
|
+
return value;
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
watch(handContext.active, (hovering) => {
|
|
44
|
+
teleportState.update((value) => {
|
|
45
|
+
value[handedness].hovering = hovering;
|
|
46
|
+
return value;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
enabled: handContext.enabled,
|
|
51
|
+
hovered: handContext.hovered,
|
|
52
|
+
active: handContext.active
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
import { watch } from '@threlte/core';
|
|
3
|
+
import { injectPlugin } from '@threlte/core';
|
|
4
|
+
import { useTeleportControls } from './hook';
|
|
5
|
+
/**
|
|
6
|
+
* Registers T components with "teleportSurface" or "teleportBlocker" attributes.
|
|
7
|
+
*/
|
|
8
|
+
export const injectTeleportControlsPlugin = () => {
|
|
9
|
+
const noop = () => { };
|
|
10
|
+
injectPlugin('threlte-teleport-controls', ({ ref, props }) => {
|
|
11
|
+
if (!ref.isMesh)
|
|
12
|
+
return;
|
|
13
|
+
const isSurface = 'teleportSurface' in props;
|
|
14
|
+
const isBlocker = 'teleportBlocker' in props;
|
|
15
|
+
if (!isSurface && !isBlocker)
|
|
16
|
+
return;
|
|
17
|
+
const { addBlocker, addSurface, removeBlocker, removeSurface } = useTeleportControls();
|
|
18
|
+
const refStore = writable(ref);
|
|
19
|
+
const propsStore = writable(props);
|
|
20
|
+
watch([refStore, propsStore], ([nextRef, nextProps]) => {
|
|
21
|
+
if (isSurface) {
|
|
22
|
+
if (nextProps.teleportSurface === false) {
|
|
23
|
+
removeSurface(nextRef);
|
|
24
|
+
return noop;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
addSurface(nextRef);
|
|
28
|
+
return () => removeSurface(nextRef);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (isBlocker) {
|
|
32
|
+
if (props.teleportBlocker === false) {
|
|
33
|
+
removeBlocker(nextRef);
|
|
34
|
+
return noop;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
addBlocker(nextRef);
|
|
38
|
+
return () => removeBlocker(nextRef);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return noop;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
onRefChange(ref) {
|
|
47
|
+
refStore.set(ref);
|
|
48
|
+
},
|
|
49
|
+
onPropsChange(props) {
|
|
50
|
+
propsStore.set(props);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { watch } from '@threlte/core';
|
|
2
|
+
import { useXR } from '../../hooks/useXR';
|
|
3
|
+
import { useController } from '../../hooks/useController';
|
|
4
|
+
import { useTeleport } from '../../hooks/useTeleport';
|
|
5
|
+
import { useFixed } from '../../internal/useFixed';
|
|
6
|
+
import { teleportIntersection } from '../../internal/stores';
|
|
7
|
+
export const setupTeleportControls = (context, handContext, fixedStep = 1 / 40) => {
|
|
8
|
+
const handedness = handContext.hand;
|
|
9
|
+
const controller = useController(handedness);
|
|
10
|
+
const teleport = useTeleport();
|
|
11
|
+
const handleHoverEnd = () => {
|
|
12
|
+
handContext.hovered.set(undefined);
|
|
13
|
+
teleportIntersection[handedness].set(undefined);
|
|
14
|
+
};
|
|
15
|
+
const { start, stop } = useFixed(() => {
|
|
16
|
+
const gamepad = controller.current?.inputSource.gamepad;
|
|
17
|
+
if (gamepad === undefined) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const selecting = (gamepad.axes[3] ?? 0) < -0.8;
|
|
21
|
+
if (handContext.active.current && !selecting) {
|
|
22
|
+
handContext.active.set(false);
|
|
23
|
+
}
|
|
24
|
+
else if (!handContext.active.current && selecting) {
|
|
25
|
+
handContext.active.set(true);
|
|
26
|
+
}
|
|
27
|
+
if (!handContext.active.current) {
|
|
28
|
+
if (handContext.hovered.current !== undefined) {
|
|
29
|
+
teleport(handContext.hovered.current.point);
|
|
30
|
+
handleHoverEnd();
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
context.compute(context, handContext);
|
|
35
|
+
const [intersect] = context.raycaster.intersectObjects(context.interactiveObjects, true);
|
|
36
|
+
if (intersect === undefined) {
|
|
37
|
+
if (handContext.hovered.current !== undefined) {
|
|
38
|
+
handleHoverEnd();
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (intersect !== undefined && context.blockers.has(intersect.object.uuid)) {
|
|
43
|
+
if (handContext.hovered.current !== undefined) {
|
|
44
|
+
handleHoverEnd();
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
teleportIntersection[handedness].set(intersect);
|
|
49
|
+
handContext.hovered.set(intersect);
|
|
50
|
+
}, {
|
|
51
|
+
fixedStep,
|
|
52
|
+
autostart: false
|
|
53
|
+
});
|
|
54
|
+
watch([useXR().isPresenting, handContext.enabled], ([isPresenting, enabled]) => {
|
|
55
|
+
if (isPresenting && enabled) {
|
|
56
|
+
start();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
stop();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threlte/xr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"author": "Micheal Parks <michealparks1989@gmail.com> (https://parks.lol)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"devDependencies": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"typescript": "^5.1.6",
|
|
19
19
|
"vite": "^4.4.6",
|
|
20
20
|
"vite-plugin-mkcert": "^1.16.0",
|
|
21
|
-
"@threlte/core": "6.0
|
|
21
|
+
"@threlte/core": "6.1.0"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"svelte": ">=4",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script>import { T } from "@threlte/core";
|
|
2
|
-
import { Line2 } from "three/examples/jsm/lines/Line2";
|
|
3
|
-
import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
|
|
4
|
-
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
|
|
5
|
-
export let positions;
|
|
6
|
-
export let color = "white";
|
|
7
|
-
let lineGeometry = new LineGeometry();
|
|
8
|
-
$:
|
|
9
|
-
if (positions) {
|
|
10
|
-
lineGeometry.setPositions(positions);
|
|
11
|
-
}
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<T is={Line2}
|
|
15
|
-
{...$$restProps}
|
|
16
|
-
position.z={-0.01}
|
|
17
|
-
>
|
|
18
|
-
<T is={lineGeometry} />
|
|
19
|
-
<T is={LineMaterial}
|
|
20
|
-
{color}
|
|
21
|
-
linewidth={0.004}
|
|
22
|
-
/>
|
|
23
|
-
</T>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { SvelteComponent } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
[x: string]: any;
|
|
5
|
-
positions: Float32Array | undefined;
|
|
6
|
-
color?: string;
|
|
7
|
-
};
|
|
8
|
-
events: {
|
|
9
|
-
[evt: string]: CustomEvent<any>;
|
|
10
|
-
};
|
|
11
|
-
slots: {};
|
|
12
|
-
};
|
|
13
|
-
export type RayProps = typeof __propDef.props;
|
|
14
|
-
export type RayEvents = typeof __propDef.events;
|
|
15
|
-
export type RaySlots = typeof __propDef.slots;
|
|
16
|
-
export default class Ray extends SvelteComponent<RayProps, RayEvents, RaySlots> {
|
|
17
|
-
}
|
|
18
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<script>import { T } from "@threlte/core";
|
|
2
|
-
const vertexShader = `
|
|
3
|
-
uniform mat4 modelViewMatrix;
|
|
4
|
-
uniform mat4 projectionMatrix;
|
|
5
|
-
attribute vec2 uv;
|
|
6
|
-
attribute vec3 position;
|
|
7
|
-
varying vec2 vUv;
|
|
8
|
-
void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.); }
|
|
9
|
-
`;
|
|
10
|
-
const fragmentShader = `
|
|
11
|
-
varying mediump vec2 vUv;
|
|
12
|
-
void main() { gl_FragColor = vec4(1., 1., 1., pow(vUv.y - 1., 2.)); }
|
|
13
|
-
`;
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
|
-
<T.Mesh
|
|
17
|
-
{...$$restProps}
|
|
18
|
-
rotation.x={-Math.PI / 2}
|
|
19
|
-
position.z={-0.1}
|
|
20
|
-
>
|
|
21
|
-
{@const radius = 0.002}
|
|
22
|
-
{@const height = 0.2}
|
|
23
|
-
{@const radialSegments = 16}
|
|
24
|
-
{@const heightSegments = 1}
|
|
25
|
-
{@const openEnded = false}
|
|
26
|
-
<T.CylinderGeometry args={[radius, radius, height, radialSegments, heightSegments, openEnded]} />
|
|
27
|
-
<T.RawShaderMaterial
|
|
28
|
-
transparent
|
|
29
|
-
{vertexShader}
|
|
30
|
-
{fragmentShader}
|
|
31
|
-
/>
|
|
32
|
-
</T.Mesh>
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@component
|
|
3
|
-
|
|
4
|
-
`<TeleportControls />` creates a teleportation experience similar to that on the Quest home environment.
|
|
5
|
-
|
|
6
|
-
@param handedness - Which hands to allow teleportation from.
|
|
7
|
-
|
|
8
|
-
@param maxDistance - The maximum radial teleporting distance from the user's current origin, in meters.
|
|
9
|
-
|
|
10
|
-
@event teleport - Fires after a teleport event.
|
|
11
|
-
|
|
12
|
-
```svelte
|
|
13
|
-
<TeleportControls
|
|
14
|
-
handedness={'left' | 'right'}
|
|
15
|
-
maxDistance={10}
|
|
16
|
-
on:teleport={(event) => {}}
|
|
17
|
-
>
|
|
18
|
-
<T.Mesh teleportSurface>
|
|
19
|
-
...
|
|
20
|
-
</T.Mesh>
|
|
21
|
-
</TeleportControls>
|
|
22
|
-
```
|
|
23
|
-
-->
|
|
24
|
-
|
|
25
|
-
<script>import * as THREE from "three";
|
|
26
|
-
import { T, useFrame, createRawEventDispatcher } from "@threlte/core";
|
|
27
|
-
import { activeTeleportController, pendingTeleportDestination } from "../internal/stores";
|
|
28
|
-
import { useTeleport, useController, useGamepad } from "../hooks";
|
|
29
|
-
import Ray from "../components/Ray.svelte";
|
|
30
|
-
import { teleportPlugin } from "../plugins/teleportPlugin";
|
|
31
|
-
export let raycaster = new THREE.Raycaster();
|
|
32
|
-
export let handedness = "right";
|
|
33
|
-
export let maxDistance = 20;
|
|
34
|
-
const dispatch = createRawEventDispatcher();
|
|
35
|
-
let destination;
|
|
36
|
-
let activeController;
|
|
37
|
-
const teleport = useTeleport();
|
|
38
|
-
const navMeshes = [];
|
|
39
|
-
const controllerPosition = new THREE.Vector3();
|
|
40
|
-
const matrix4 = new THREE.Matrix4();
|
|
41
|
-
const rayMidpoint = new THREE.Vector3();
|
|
42
|
-
const rayDivisions = 40;
|
|
43
|
-
const positions = new Float32Array(rayDivisions * 3);
|
|
44
|
-
const curve = new THREE.QuadraticBezierCurve3();
|
|
45
|
-
const curvePoint = new THREE.Vector3();
|
|
46
|
-
$:
|
|
47
|
-
raycaster.far = maxDistance;
|
|
48
|
-
$:
|
|
49
|
-
teleportController = useController(handedness);
|
|
50
|
-
$:
|
|
51
|
-
teleportGamepad = useGamepad(handedness);
|
|
52
|
-
const calculateRayMidpoint = (vector1, vector2) => {
|
|
53
|
-
rayMidpoint.x = (vector1.x + vector2.x) / 2;
|
|
54
|
-
rayMidpoint.y = (vector1.y + vector2.y) / 2;
|
|
55
|
-
rayMidpoint.z = (vector1.z + vector2.z) / 2;
|
|
56
|
-
};
|
|
57
|
-
const { start, stop } = useFrame(() => {
|
|
58
|
-
matrix4.identity().extractRotation(activeController.matrixWorld);
|
|
59
|
-
raycaster.ray.origin.setFromMatrixPosition(activeController.matrixWorld);
|
|
60
|
-
raycaster.ray.direction.set(0, 0, -1).applyMatrix4(matrix4);
|
|
61
|
-
const [intersect] = raycaster.intersectObjects(navMeshes, true);
|
|
62
|
-
if (intersect === void 0) {
|
|
63
|
-
destination = void 0;
|
|
64
|
-
$pendingTeleportDestination = void 0;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
destination = intersect.point;
|
|
68
|
-
$pendingTeleportDestination = destination;
|
|
69
|
-
activeController.getWorldPosition(controllerPosition);
|
|
70
|
-
calculateRayMidpoint(controllerPosition, destination);
|
|
71
|
-
rayMidpoint.y += 0.8;
|
|
72
|
-
curve.v0.copy(controllerPosition);
|
|
73
|
-
curve.v1.copy(rayMidpoint);
|
|
74
|
-
curve.v2.copy(intersect.point);
|
|
75
|
-
for (let i = 0, j = 0; i < rayDivisions; i += 1, j += 3) {
|
|
76
|
-
const t = i / rayDivisions;
|
|
77
|
-
curve.getPoint(t, curvePoint);
|
|
78
|
-
positions[j + 0] = curvePoint.x;
|
|
79
|
-
positions[j + 1] = curvePoint.y;
|
|
80
|
-
positions[j + 2] = curvePoint.z;
|
|
81
|
-
}
|
|
82
|
-
}, { autostart: false });
|
|
83
|
-
const handleSelectStart = (controller) => {
|
|
84
|
-
activeController = controller;
|
|
85
|
-
$activeTeleportController = controller;
|
|
86
|
-
start();
|
|
87
|
-
};
|
|
88
|
-
const handleSelectEnd = () => {
|
|
89
|
-
stop();
|
|
90
|
-
activeController = void 0;
|
|
91
|
-
$activeTeleportController = void 0;
|
|
92
|
-
if (destination !== void 0) {
|
|
93
|
-
teleport(destination);
|
|
94
|
-
dispatch("teleport", destination);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
useFrame(() => {
|
|
98
|
-
const selecting = (teleportGamepad.current?.axes[3] ?? 0) < -0.9;
|
|
99
|
-
if (selecting && activeController === void 0) {
|
|
100
|
-
handleSelectStart(teleportController.current.targetRay);
|
|
101
|
-
} else if (!selecting && activeController !== void 0) {
|
|
102
|
-
handleSelectEnd();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
teleportPlugin(navMeshes);
|
|
106
|
-
</script>
|
|
107
|
-
|
|
108
|
-
<slot />
|
|
109
|
-
|
|
110
|
-
<slot name='ray'>
|
|
111
|
-
<Ray
|
|
112
|
-
visible={activeController !== undefined && destination !== undefined}
|
|
113
|
-
positions={activeController !== undefined && destination !== undefined ? positions : undefined}
|
|
114
|
-
/>
|
|
115
|
-
</slot>
|
|
116
|
-
|
|
117
|
-
<slot name='cursor'>
|
|
118
|
-
<T.Mesh
|
|
119
|
-
visible={activeController !== undefined && destination !== undefined}
|
|
120
|
-
position.x={destination?.x}
|
|
121
|
-
position.y={destination?.y}
|
|
122
|
-
position.z={destination?.z}
|
|
123
|
-
>
|
|
124
|
-
{@const innerRadius = 0.175}
|
|
125
|
-
{@const outerRadius = 0.2}
|
|
126
|
-
{@const thetaSegments = 32}
|
|
127
|
-
<T.RingGeometry
|
|
128
|
-
args={[innerRadius, outerRadius, thetaSegments]}
|
|
129
|
-
on:create={({ ref }) => ref.rotateX(-Math.PI / 2)}
|
|
130
|
-
/>
|
|
131
|
-
<T.MeshBasicMaterial
|
|
132
|
-
polygonOffset
|
|
133
|
-
polygonOffsetFactor={-1}
|
|
134
|
-
/>
|
|
135
|
-
</T.Mesh>
|
|
136
|
-
</slot>
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { SvelteComponent } from "svelte";
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
declare const __propDef: {
|
|
4
|
-
props: {
|
|
5
|
-
/**
|
|
6
|
-
* The raycaster used for teleportation.
|
|
7
|
-
* @default new THREE.Raycaster()
|
|
8
|
-
*/ raycaster?: THREE.Raycaster;
|
|
9
|
-
/**
|
|
10
|
-
* The controller handedness that the teleport controls is linked to.
|
|
11
|
-
* @default 'right'
|
|
12
|
-
*/ handedness?: 'left' | 'right';
|
|
13
|
-
/**
|
|
14
|
-
* The maximum radial teleporting distance from the user's current origin, in meters.
|
|
15
|
-
* @default 20
|
|
16
|
-
*/ maxDistance?: number;
|
|
17
|
-
};
|
|
18
|
-
slots: {
|
|
19
|
-
default: {};
|
|
20
|
-
ray: {};
|
|
21
|
-
cursor: {};
|
|
22
|
-
};
|
|
23
|
-
events: {
|
|
24
|
-
/** Fired after a teleportation occurs with the new location as the payload. */
|
|
25
|
-
teleport: THREE.Vector3;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
export type TeleportControlsProps = typeof __propDef.props;
|
|
29
|
-
export type TeleportControlsEvents = typeof __propDef.events;
|
|
30
|
-
export type TeleportControlsSlots = typeof __propDef.slots;
|
|
31
|
-
/**
|
|
32
|
-
* `<TeleportControls />` creates a teleportation experience similar to that on the Quest home environment.
|
|
33
|
-
*
|
|
34
|
-
* @param handedness - Which hands to allow teleportation from.
|
|
35
|
-
*
|
|
36
|
-
* @param maxDistance - The maximum radial teleporting distance from the user's current origin, in meters.
|
|
37
|
-
*
|
|
38
|
-
* @event teleport - Fires after a teleport event.
|
|
39
|
-
*
|
|
40
|
-
* ```svelte
|
|
41
|
-
* <TeleportControls
|
|
42
|
-
* handedness={'left' | 'right'}
|
|
43
|
-
* maxDistance={10}
|
|
44
|
-
* on:teleport={(event) => {}}
|
|
45
|
-
* >
|
|
46
|
-
* <T.Mesh teleportSurface>
|
|
47
|
-
* ...
|
|
48
|
-
* </T.Mesh>
|
|
49
|
-
* </TeleportControls>
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
export default class TeleportControls extends SvelteComponent<TeleportControlsProps, TeleportControlsEvents, TeleportControlsSlots> {
|
|
53
|
-
}
|
|
54
|
-
export {};
|
package/dist/hooks/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { useController, useGamepad } from './useController';
|
|
2
|
-
export { useHand } from './useHand';
|
|
3
|
-
export { useHandJoint } from './useHandJoint';
|
|
4
|
-
export { useHeadset } from './useHeadset';
|
|
5
|
-
export { useHitTest } from './useHitTest';
|
|
6
|
-
export { useTeleport } from './useTeleport';
|
|
7
|
-
export { useXR } from './useXR';
|
package/dist/hooks/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { useController, useGamepad } from './useController';
|
|
2
|
-
export { useHand } from './useHand';
|
|
3
|
-
export { useHandJoint } from './useHandJoint';
|
|
4
|
-
export { useHeadset } from './useHeadset';
|
|
5
|
-
export { useHitTest } from './useHitTest';
|
|
6
|
-
export { useTeleport } from './useTeleport';
|
|
7
|
-
export { useXR } from './useXR';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const teleportPlugin: (navMeshes: THREE.Mesh[]) => void;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { injectPlugin } from '@threlte/core';
|
|
2
|
-
export const teleportPlugin = (navMeshes) => injectPlugin('teleport-controls', ({ ref, props }) => {
|
|
3
|
-
let currentRef = ref;
|
|
4
|
-
let currentProps = props;
|
|
5
|
-
if (!currentRef.isMesh)
|
|
6
|
-
return;
|
|
7
|
-
if (!('teleportSurface' in props))
|
|
8
|
-
return;
|
|
9
|
-
const addSurface = (mesh) => {
|
|
10
|
-
const registered = navMeshes.some((navMesh) => navMesh.uuid === mesh.uuid);
|
|
11
|
-
if (!registered) {
|
|
12
|
-
navMeshes.push(mesh);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
const removeSurface = (mesh) => {
|
|
16
|
-
const index = navMeshes.indexOf(mesh);
|
|
17
|
-
if (index > -1) {
|
|
18
|
-
navMeshes.splice(index, 1);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
return {
|
|
22
|
-
pluginProps: ['teleportSurface'],
|
|
23
|
-
onRefChange(ref) {
|
|
24
|
-
removeSurface(currentRef);
|
|
25
|
-
currentRef = ref;
|
|
26
|
-
if (currentProps.teleportSurface) {
|
|
27
|
-
addSurface(currentRef);
|
|
28
|
-
}
|
|
29
|
-
return () => removeSurface(currentRef);
|
|
30
|
-
},
|
|
31
|
-
onPropsChange(props) {
|
|
32
|
-
currentProps = props;
|
|
33
|
-
if (currentProps.teleportSurface) {
|
|
34
|
-
addSurface(currentRef);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
removeSurface(currentRef);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
});
|