threlte logo
@threlte/extras

meshBounds

A raycast function that will first check for collision with the object’s bounding sphere, then it will check against the object’s bounding box but only if the bounding box has been previously computed. You can use this function if you want to trade pointer precision for an increase in performance.

Usage

Basic Usage

meshBounds can simply be passed to an object’s raycast prop.

<script>
  import { meshBounds } from '@threlte/extras'
</script>

<T.Mesh raycast={meshBounds}>
  <T.BoxGeometry />
</T.Mesh>

If your meshes or models aren’t very complex in terms of geometry, meshBounds won’t provide much of a performance boost.

<script lang="ts">
  import Scene from './Scene.svelte'
  import { Canvas } from '@threlte/core'
  import { Checkbox, Pane } from 'svelte-tweakpane-ui'

  let showBounds = $state(false)
</script>

<Pane
  title="meshBounds"
  position="fixed"
>
  <Checkbox
    bind:value={showBounds}
    label="show bounds"
  />
</Pane>

<div>
  <Canvas>
    <Scene {showBounds} />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
import type { Vector3Tuple } from 'three'

export class BoundsMesh {
  wireframe = $state(true)
  constructor(readonly position: Vector3Tuple) {}
}
<script lang="ts">
  import { BoundsMesh } from './BoundsMesh.svelte.ts'
  import { OrbitControls } from '@threlte/extras'
  import { T } from '@threlte/core'
  import { interactivity, meshBounds } from '@threlte/extras'
  import type { Vector3Tuple } from 'three'

  type Props = {
    showBounds: boolean
  }

  let { showBounds }: Props = $props()

  interactivity()

  const positions: Vector3Tuple[] = [
    [0, 1, 0],
    [1, -1, 0],
    [-1, -1, 0]
  ]

  const meshes = positions.map((position) => new BoundsMesh(position))

  const size = 1
  // half of the box's diagonal === radius of the bounding sphere
  const radius = 0.5 * size * Math.sqrt(3)
</script>

<T.AmbientLight />

<T.PerspectiveCamera
  makeDefault
  position.z={5}
>
  <OrbitControls />
</T.PerspectiveCamera>

{#each meshes as mesh}
  <T.Mesh
    raycast={meshBounds}
    onpointerenter={() => {
      mesh.wireframe = false
    }}
    onpointerleave={() => {
      mesh.wireframe = true
    }}
    position={mesh.position}
  >
    <T.BoxGeometry args={[size, size, size]} />
    <T.MeshStandardMaterial
      color="hotpink"
      wireframe={mesh.wireframe}
    />
  </T.Mesh>
{/each}

<T.Group visible={showBounds}>
  {#each meshes as { position }}
    <T.Mesh {position}>
      <T.SphereGeometry args={[radius]} />
      <T.MeshStandardMaterial
        transparent
        opacity={0.25}
      />
    </T.Mesh>
  {/each}
</T.Group>

Creating a plugin

Instead of manually applying the meshBounds raycast function to each mesh, you can use a Threlte plugin to automatically apply the meshBounds raycast function to all meshes in your scene.

Scene.svelte
<script>
  import { T, injectPlugin, isInstanceOf } from '@threlte/core'
  import { meshBounds } from '@threlte/extras'

  injectPlugin('mesh-bounds-plugin', (args) => {
    if (isInstanceOf(args.ref, 'Mesh')) {
      args.ref.raycast = meshBounds
    }
  })
</script>

<!-- No need to manually apply the meshBounds raycast function -->
<T.Mesh>
  <T.MeshBasicMaterial color="hotpink" />
  <T.BoxGeometry />
</T.Mesh>

To selectively apply the meshBounds raycast function to only certain meshes, you can listen to a prop (e.g. raycastMeshBounds) and conditionally apply the function.

Scene.svelte
<script>
  import { T, injectPlugin, isInstanceOf } from '@threlte/core'
  import { meshBounds } from '@threlte/extras'
  import { onDestroy } from 'svelte'

  injectPlugin('mesh-bounds-plugin', (args) => {
    if (!isInstanceOf(args.ref, 'Mesh')) return

    const originalRaycast = args.ref.raycast

    $effect(() => {
      if (!!args.props.raycastMeshBounds) {
        args.ref.raycast = meshBounds
      } else {
        args.ref.raycast = originalRaycast
      }
    })

    onDestroy(() => {
      args.ref.raycast = originalRaycast
    })

    return {
      pluginProps: ['raycastMeshBounds']
    }
  })
</script>

<!-- Regular raycasting -->
<T.Mesh>
  <T.MeshBasicMaterial color="hotpink" />
  <T.BoxGeometry />
</T.Mesh>

<!-- meshBounds raycasting -->
<T.Mesh raycastMeshBounds>
  <T.MeshBasicMaterial color="hotpink" />
  <T.BoxGeometry />
</T.Mesh>