threlte logo
@threlte/xr

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>

<Canvas>
  <World gravity={[0, 0, 0]}>
    <Scene />
  </World>
</Canvas>

<VRButton />
<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 { useFrame } from '@threlte/core'
  import { handJoints, useHandJoint } from '@threlte/xr'
  import type { RigidBody as RapierRigidBody } from '@dimforge/rapier3d-compat'
  import { Collider, RigidBody } from '@threlte/rapier'

  export let jointIndex: number
  export let hand: 'left' | 'right'

  let body: RapierRigidBody

  const joint = useHandJoint(hand, handJoints[jointIndex]!)

  const { start, stop } = useFrame(() => {
    if (joint.current === undefined || body === undefined) return

    const { x, y, z } = joint.current.position
    body.setNextKinematicTranslation({ x, y, z })
  }, { autostart: false })

  $: radius = $joint?.jointRadius

  $: 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 on:pinchend={() => (debug = !debug)} />
  <Hand right on:pinchend={() => (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]}
  on:create={({ 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]}
/>