useHandJoint
Provides a reference to a requested hand joint, once available.
<script> import { useHandJoint } from '@threlte/xr'
const wristJoint = useHandJoint('left', 'wrist')</script>Reading hand joint positions in real time can be very useful, for example in providing rigid bodies for hands:
<script lang="ts"> import { Canvas } from '@threlte/core' import { World } from '@threlte/rapier' import { VRButton } from '@threlte/xr' import Scene from './Scene.svelte'</script>
<div> <Canvas> <World gravity={[0, 0, 0]}> <Scene /> </World> </Canvas> <VRButton /></div>
<style> div { height: 100%; }</style><script lang="ts"> import { T } from '@threlte/core' import { InstancedMesh, Instance } from '@threlte/extras' import { Collider, RigidBody } from '@threlte/rapier'
const size = 0.02 const limit = 100</script>
<T.Group position={[0, 1.7, 0]}> <InstancedMesh {limit}> <T.BoxGeometry args={[size, size, size]} /> <T.MeshStandardMaterial roughness={0} metalness={0.2} />
{#each { length: limit } as _, index (index)} <RigidBody> <Collider shape="cuboid" args={[size / 2, size / 2, size / 2]} /> <Instance color="hotpink" /> </RigidBody> {/each} </InstancedMesh></T.Group><script lang="ts"> import { useTask } from '@threlte/core' import { handJoints, useHandJoint } from '@threlte/xr' import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat' import { Collider, RigidBody } from '@threlte/rapier'
interface Props { jointIndex: number hand: 'left' | 'right' }
let { jointIndex, hand }: Props = $props()
let body = $state.raw<RapierRigidBody>()
const joint = useHandJoint(hand, handJoints[jointIndex]!)
const { start, stop } = useTask( () => { if (joint.current === undefined || body === undefined) return
const { x, y, z } = joint.current.position body.setNextKinematicTranslation({ x, y, z }) }, { autoStart: false } )
let radius = $derived($joint?.jointRadius)
$effect.pre(() => { if (body && radius && $joint) { start() } else { stop() } })</script>
{#if radius} <RigidBody bind:rigidBody={body} type="kinematicPosition" > <Collider shape="ball" args={[radius]} /> </RigidBody>{/if}<script lang="ts"> import { T } from '@threlte/core' import { Hand, XR, useXR } from '@threlte/xr' import { Text } from '@threlte/extras' import { Attractor, Debug } from '@threlte/rapier' import JointCollider from './JointBody.svelte' import Cubes from './Cube.svelte'
const { isHandTracking } = useXR()
let debug = false</script>
{#if debug} <Debug />{/if}
<XR> <Hand left onpinchend={() => (debug = !debug)} /> <Hand right onpinchend={() => (debug = !debug)} />
{#if $isHandTracking} {#each { length: 25 } as _, jointIndex} <JointCollider {jointIndex} hand="left" /> <JointCollider {jointIndex} hand="right" /> {/each} {/if}
<Text position={[0, 1.7, -1]} text="Pinch to toggle physics debug." /></XR>
<Cubes />
<T.PerspectiveCamera makeDefault position={[0, 1, 1]} oncreate={(ref) => ref.lookAt(0, 1.8, 0)}/>
<T.AmbientLight />
<T.SpotLight position={[1, 8, 1]} angle={0.3} penumbra={1} intensity={30} castShadow target.x={0} target.y={1.8} target.z={0}/>
<Attractor range={50} strength={0.000001} position={[0, 1.7, 0]}/>