@threlte/xr
<Hand>
<Hands />
instantiates XRHand inputs for devices that allow hand tracking.
<Hand left />
<Hand right />
It will by default load a hand model.
Default hand models are fetched from the immersive web group’s webxr input profile repo. If you are developing an offline app, you should download and provide any anticipated models.
<Hand>
can accept a slot to replace the default model.
<Hand left>
<T.Mesh>
<T.IcosahedronGeometry args={[0.2]} />
<T.MeshStandardMaterial color='turquoise' />
</T.Mesh>
</Hand>
A named slot, wrist
, will place any children within the wrist space of the hand:
<Hand left>
<T.Mesh slot='wrist'>
<T.IcosahedronGeometry args={[0.2]} />
<T.MeshStandardMaterial color='hotpink' />
</T.Mesh>
</Hand>
To trigger reactive changes based on whether hand input is or is not present, the useXR
hook provides a currentWritable
store:
const { isHandTracking } = useXR()
Hand tracking can serve as a powerful input device, as any joint position, and not just the wrist, can be read from in real time:
<script lang="ts">
import { Canvas } from '@threlte/core'
import { VRButton } from '@threlte/xr'
import Scene from './Scene.svelte'
</script>
<div>
<Canvas>
<Scene />
</Canvas>
<VRButton />
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import * as THREE from 'three'
import { T } from '@threlte/core'
import { XR, Hand, Controller, type XRHandEvent, type XRControllerEvent } from '@threlte/xr'
let boxes: THREE.Object3D[] = []
const handleEvent = (event: XRHandEvent) => {
console.log('Hand', event)
}
const handleControllerEvent = (event: XRControllerEvent) => {
console.log('Controller', event)
}
const handlePinchStart = (event: XRHandEvent) => {
const controller = event.target
const size = 0.05
const geometry = new THREE.BoxGeometry(size, size, size)
const material = new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff })
const spawn = new THREE.Mesh(geometry, material)
const indexTip = controller.joints['index-finger-tip']
spawn.position.copy(indexTip.position)
spawn.quaternion.copy(indexTip.quaternion)
boxes.push(spawn)
boxes = boxes
}
const hands = ['left', 'right'] as const
</script>
<XR>
{#each hands as hand (hand)}
<Hand
{hand}
on:pinchstart={handlePinchStart}
on:connected={handleEvent}
on:disconnected={handleEvent}
on:pinchstart={handleEvent}
on:pinchend={handleEvent}
/>
<Controller
{hand}
on:connected={handleControllerEvent}
on:disconnected={handleControllerEvent}
on:select={handleControllerEvent}
on:squeeze={handleControllerEvent}
on:selectstart={handleControllerEvent}
on:selectend={handleControllerEvent}
on:squeezestart={handleControllerEvent}
on:squeezeend={handleControllerEvent}
/>
{/each}
</XR>
<T.Mesh rotation={[-Math.PI / 2, 0, 0]}>
<T.CircleGeometry args={[1]} />
<T.MeshBasicMaterial />
</T.Mesh>
<T.PerspectiveCamera
makeDefault
position={[0, 1.8, 1]}
on:create={({ ref }) => ref.lookAt(0, 1.8, 0)}
/>
<T.AmbientLight />
<T.DirectionalLight />
{#each boxes as box (box.uuid)}
<T is={box} />
{/each}