threlte logo
@threlte/xr

useHitTest

Provides a hit test result on each frame during an immersive-ar session.

Hit testing lets you position virtual items in a real-world view.

<script>
  import { useHitTest } from '@threlte/xr'

  let ref

  useHitTest((hitMatrix, hit) => {
    if (!ref) return
  
    if (hit) {
      ref.visible = true
      ref.matrix.copy(hitMatrix)
    } else {
      ref.visible = false
    }
  })
</script>

<T.Mesh bind:ref>
  <T.SphereGeometry args={[0.1]}>
  <T.MeshBasicMaterial />
</T.Mesh>

This hook can optionally specify one of three origins from which to cast the hit test ray: viewer (the default), leftInput or rightInput.

useHitTest((hitMatrix, hit) => {
  // Perform a hit test from the left controller or hand.
}, { source: 'leftInput' })

In the following example, hit testing is set up from both controllers and hands.

<script lang="ts">
	import { Canvas } from '@threlte/core'
	import { ARButton } from '@threlte/xr'
	import Scene from './Scene.svelte'
</script>

<Canvas>
	<Scene />
</Canvas>

<ARButton />
<script lang='ts'>
  import * as THREE from 'three'
  import { T } from '@threlte/core'
  import { XR, Controller, Hand, useHitTest } from '@threlte/xr'
  
  const geometry = new THREE.CylinderGeometry(0.1, 0.1, 0.2, 32).translate(0, 0.1, 0);
  
  let meshes: THREE.Mesh[] = []
  let cursors = { left: undefined! as THREE.Mesh, right: undefined! as THREE.Mesh }

  const hands = ['left', 'right'] as const
  type Hands = (typeof hands)[number]
  
  const handleSelect = (hand: Hands) => () => {
    if (!cursors[hand].visible) return

    const material = new THREE.MeshPhongMaterial({ color: 0xffffff * Math.random() })
    const mesh = new THREE.Mesh(geometry, material)
    cursors[hand].matrix.decompose(mesh.position, mesh.quaternion, mesh.scale)
    mesh.scale.y = Math.random() * 2 + 1
    meshes.push(mesh)
    meshes = meshes
  }

  const handleHitTest = (hand: Hands) => (hitMatrix: THREE.Matrix4, hit: XRHitTestResult | undefined) => {
    if (!cursors[hand]) return
  
    if (hit) {
      cursors[hand].visible = true
      cursors[hand].matrix.copy(hitMatrix)
    } else {
      cursors[hand].visible = false
    }
  }

  useHitTest(handleHitTest('left'), { source: 'leftInput' })
  useHitTest(handleHitTest('right'), { source: 'rightInput' })
</script>

<XR>
  {#each hands as hand}
    <Controller {hand} on:select={handleSelect(hand)} />
    <Hand {hand} on:pinchend={handleSelect(hand)} />

    <T.Mesh
      bind:ref={cursors[hand]}
      matrixAutoUpdate={false}
      visible={false}
    >
      <T.RingGeometry
        args={[0.15, 0.2, 32]}
        on:create={({ ref }) => ref.rotateX(-Math.PI / 2)}
      />
      <T.MeshBasicMaterial />
    </T.Mesh>
  {/each}
</XR>

<T.HemisphereLight
  args={[0xffffff, 0xbbbbff, 1]}
  position={[0.5, 1, 0.25]}
/>

<T.AmbientLight intensity={0.5} />

{#each meshes as mesh, index (index)}
  <T is={mesh} />
{/each}

Signature

useHitTest((hitMatrix: THREE.Matrix4, hit: XRHitTestResult | undefined) => {})