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 lang="ts"> import Scene from './Scene.svelte' import { Canvas } from '@threlte/core' import { Pane, Checkbox } from 'svelte-tweakpane-ui' import { VRButton } from '@threlte/xr'
let showSurfaces = $state(false) let showBlockers = $state(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 Surfaces from './Surfaces.svelte' import { OrbitControls, Sky, useDraco, useGltf } from '@threlte/extras' import { PointLight } from 'three' import { SimplexNoise } from 'three/examples/jsm/Addons.js' import { T, useTask } from '@threlte/core' import { XR, Controller, Hand } from '@threlte/xr'
type Props = { showBlockers: boolean showSurfaces: boolean }
let { showBlockers, showSurfaces }: Props = $props()
const noise = new SimplexNoise()
const light1 = new PointLight() const light2 = new PointLight()
let torchX = $state(0) let torchZ = $state(0)
const dracoLoader = useDraco() const gltf = useGltf('/models/xr/ruins.glb', { dracoLoader }).then((gltf) => { gltf.scene.traverse((node) => { node.castShadow = true node.receiveShadow = true }) torchX = gltf.nodes.Torch1.position.x torchZ = gltf.nodes.Torch1.position.z
return gltf })
let time = 0
useTask((delta) => { time += delta / 5 const x = noise.noise(time, 0) / 10 const y = noise.noise(0, time) / 10 const lightPositionX = torchX + x const lightPositionZ = torchZ + y light1.position.x = lightPositionX light2.position.x = lightPositionX light1.position.z = lightPositionZ light2.position.z = lightPositionZ })</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>
{#await gltf then { scene, nodes }} <T is={scene} /> <T is={light1} intensity={8} color="red" position.y={nodes.Torch1.position.y + 0.45} />
<T is={light2} intensity={4} color="red" position.y={nodes.Candles1.position.y + 0.45} />{/await}
<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 type { Snippet } from 'svelte' import { T } from '@threlte/core' import { teleportControls } from '@threlte/xr' import { useDraco, useGltf } from '@threlte/extras'
type Props = { children?: Snippet showBlockers: boolean showSurfaces: boolean }
let { children, showBlockers, showSurfaces }: Props = $props()
teleportControls('left') teleportControls('right')
const dracoLoader = useDraco() const gltf = useGltf('/models/xr/ruins.glb', { dracoLoader })</script>
{@render children?.()}
{#await gltf then { nodes }} {#each [1, 2, 3, 4, 5, 6, 7, 8, 9] as n} <T is={nodes[`teleportBlocker${n}`]} visible={showBlockers} teleportBlocker /> {/each}
{#each [1, 2, 3] as n} <T is={nodes[`teleportSurface${n}`]} visible={showSurfaces} teleportSurface /> {/each}{/await}<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.