@threlte/xr

pointerControls

The pointerControls plugin adds pointer events to an immersive XR session. This means that pointing at any mesh with your hand or a controller will trigger DOM-like pointer events.

To get started, import and call the plugin in a component within your app.

<script>
  import { pointerControls } from '@threlte/xr'
  pointerControls('left' | 'right')
</script>

Any mesh within this component and all child components will now receive events if the controller or hand with the specified handedness points at it.

<T.Mesh
  onclick={() => {
    console.log('clicked')
  }}
>
  <T.BoxGeometry />
  <T.MeshStandardMaterial color="red" />
</T.Mesh>

If you wish to add pointer controls for both hands / controllers, simply call the plugin for both hands.

<script>
  import { pointerControls } from '@threlte/xr'
  pointerControls('left')
  pointerControls('right')
</script>

Pointer controls can be enabled or disabled when initialized or during runtime.

<script>
  import { pointerControls } from '@threlte/xr'
  // "enabled" is a currentWritable
  const { enabled } = pointerControls('left', { enabled: false })

  // At some later time...
  enabled.set(true)
</script>

Available Events

The following events are available:

<T.Mesh
  onclick={(e) => console.log('click')}
  onpointerup={(e) => console.log('up')}
  onpointerdown={(e) => console.log('down')}
  onpointerover={(e) => console.log('over')}
  onpointerout={(e) => console.log('out')}
  onpointerenter={(e) => console.log('enter')}
  onpointerleave={(e) => console.log('leave')}
  onpointermove={(e) => console.log('move')}
/>

While a controller or hand is pointed at this mesh…

  • click fires when a user selects the primary action input. This usually means pulling a primary trigger with a controller or pinching with a hand.
  • pointerdown fires when a primary action begins, and pointerup fires when it ends.
  • pointerover and pointerout fire when the ray of the pointing device is moved onto an object, or onto one of its children. It bubbles, meaning it can trigger on the object that the pointer is over or any of its ancestor objects.
  • pointerenter and pointerleave fire when the ray of the pointing device enters / leaves the boundaries of an object, and does not bubble. It only triggers on the exact element the pointer has entered / left.

To replace the default ray and cursor that are created by the plugin, the following snippets can be added to a <Controller> or a <Hand>:

<script>
  import { Hand, Controller } from '@threlte/xr'
  import CustomRay from './CustomRay.svelte'
  import CustomCursor from './CustomCursor.svelte'
</script>

<Controller left>
  {#snippet pointerRay()}
    <CustomRay>
  {/snippet}

  {#snippet pointerCursor()}
    <CustomCursor>
  {/snippet}
</Controller>

This plugin can be used with the teleportControls plugin to allow both teleporting and interaction.

<script>
  import { pointerControls, teleportControls } from '@threlte/xr'
  teleportControls('left')
  pointerControls('right')
</script>

Since the default behavior of pointer and teleport controls have no overlap, they can be added to the same hand.

If these two plugins are added to the same hand, pointerControls will take over when pointing at a mesh with events, and teleportControls will take over otherwise.

pointerControls can also be used with interactivity to allow pointer events within and outside an immersive session.

<script>
  import { interactivity } from '@threlte/extras'
  import { pointerControls, teleportControls } from '@threlte/xr'
  interactivity()
  pointerControls('left')
</script>

The will be a few subtle differences when events are fired within an immersive session:

  • Pointers / cursors will be THREE.Vector3s instead of THREE.Vector2s. In XR, the cursor that intersects with the object that you interact with can be anywhere within a 3d space.
  • There will be no camera property on the event, since raycasting will originate from hands or controllers.
  • The nativeEvent property on event objects will be a XRControllerEvent or XRHandEvent rather than a DomEvent. In the case of hover events such as pointerMove, there will be no native event.