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>

<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'

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

  let body: 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 }
  )

  $: 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
    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]}
/>