@threlte/xr
teleportControls
The teleportControls
plugin creates teleportation controls similar to many native XR experiences: pressing the thumbstick forward on a controller will create a visible ray to a teleport destination, and when the the thumbstick is released the user will be teleported to the end of the ray.
<script>
import { teleportControls } from '@threlte/xr'
teleportControls('left' | 'right')
</script>
Any mesh within this component and all child components can now be treated as a navigation mesh to which the user can teleport to.
To register a mesh with teleportControls
, add a teleportSurface
property.
<T.Mesh teleportSurface>
<T.CylinderGeometry args={[20, 0.01]} />
<T.MeshStandardMaterial />
</T.Mesh>
If you wish to add teleport controls for both hands / controllers, simply call the plugin for both hands.
<script>
import { teleportControls } from '@threlte/xr'
teleportControls('left')
teleportControls('right')
</script>
Teleport controls can be enabled or disabled when initialized or during runtime.
<script>
import { teleportControls } from '@threlte/xr'
// "enabled" is a currentWritable
const { enabled } = teleportControls('left', { enabled: false })
// At some later time...
enabled.set(true)
</script>
A mesh can also be registered as a teleportBlocker
, meaning that it will prevent teleportation through it.
This can be useful when creating walls and doors that the user must navigate around.
<T.Mesh teleportBlocker>
<T.BoxGeometry args={[0.8, 2, 0.1]} />
<T.MeshStandardMaterial />
</T.Mesh>
This plugin can be composed with the teleportControls
plugin to allow both teleporting and interaction.
<script>
import { pointerControls, teleportControls } from '@threlte/xr'
teleportControls('left')
pointerControls('right')
</script>
This will result in pointerControls
taking over when pointing at a mesh with events, and teleportControls
taking over otherwise.
<script lang="ts">
import { Canvas } from '@threlte/core'
import { VRButton } from '@threlte/xr'
import Scene from './Scene.svelte'
import { Pane, Checkbox } from 'svelte-tweakpane-ui'
let showSurfaces = false
let showBlockers = false
</script>
<Pane
title="Teleport objects"
position="fixed"
>
<Checkbox
bind:value={showSurfaces}
label="Show teleport surfaces"
/>
<Checkbox
bind:value={showBlockers}
label="Show teleport blockers"
/>
</Pane>
<div>
<Canvas>
<Scene
{showSurfaces}
{showBlockers}
/>
</Canvas>
<VRButton />
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { PointLight } from 'three'
import { T, useTask } from '@threlte/core'
import { OrbitControls, Sky, useGltf } from '@threlte/extras'
import { XR, Controller, Hand } from '@threlte/xr'
import Surfaces from './Surfaces.svelte'
import { createNoise2D } from 'simplex-noise'
export let showSurfaces: boolean
export let showBlockers: boolean
const noise = createNoise2D()
const light1 = new PointLight()
const light2 = new PointLight()
const gltf = useGltf('/models/xr/ruins.glb', {
useDraco: true
})
$: $gltf?.scene.traverse((node) => {
node.castShadow = node.receiveShadow = true
})
let time = 0
$: torchX = $gltf?.nodes.Torch1.position.x ?? 0
$: torchZ = $gltf?.nodes.Torch1.position.z ?? 0
$: candlesX = $gltf?.nodes.Candles1.position.x ?? 0
$: candlesZ = $gltf?.nodes.Candles1.position.z ?? 0
useTask((delta) => {
time += delta / 5
const x = noise(time, 0) / 10
const y = noise(0, time) / 10
light1.position.x = torchX + x
light1.position.z = torchZ + y
light2.position.x = candlesX + x
light2.position.z = candlesZ + y
})
</script>
<XR>
<Controller left />
<Controller right />
<Hand left />
<Hand right />
{#snippet fallback()}
<T.PerspectiveCamera
makeDefault
position.y={1.8}
position.z={1.5}
oncreate={({ ref }) => ref.lookAt(0, 1.8, 0)}
>
<OrbitControls
target={[0, 1.8, 0]}
enablePan={false}
enableZoom={false}
/>
</T.PerspectiveCamera>
{/snippet}
</XR>
{#if $gltf}
<T is={$gltf.scene} />
<T
is={light1}
intensity={8}
color="red"
position.y={$gltf.nodes.Torch1.position.y + 0.45}
/>
<T
is={light2}
intensity={4}
color="red"
position.y={$gltf.nodes.Candles1.position.y + 0.45}
/>
{/if}
<Sky
elevation={-3}
rayleigh={8}
azimuth={-90}
/>
<Surfaces
{showSurfaces}
{showBlockers}
/>
<T.AmbientLight intensity={0.25} />
<T.DirectionalLight
intensity={0.5}
position={[5, 5, 1]}
castShadow
shadow.camera.top={50}
shadow.camera.right={50}
shadow.camera.left={-50}
shadow.camera.bottom={-50}
shadow.mapSize.width={1024}
shadow.mapSize.height={1024}
shadow.camera.far={10}
/>
<script lang="ts">
import { T } from '@threlte/core'
import { teleportControls } from '@threlte/xr'
import { useGltf } from '@threlte/extras'
export let showSurfaces: boolean
export let showBlockers: boolean
teleportControls('left')
teleportControls('right')
const gltf = useGltf('/models/xr/ruins.glb', {
useDraco: true
})
</script>
<slot />
{#if $gltf}
{#each [1, 2, 3, 4, 5, 6, 7, 8, 9] as n}
<T
is={$gltf.nodes[`teleportBlocker${n}`]}
visible={showBlockers}
teleportBlocker
/>
{/each}
{#each [1, 2, 3] as n}
<T
is={$gltf.nodes[`teleportSurface${n}`]}
visible={showSurfaces}
teleportSurface
/>
{/each}
{/if}