A composable that dramatically improves raycasting performance by building a Bounding Volume Hierarchy (BVH) for your meshes. This can speed up raycasting by orders of magnitude, especially for complex models with many triangles.
Built on top of by Garrett Johnson.
BVH (Bounding Volume Hierarchy) is a spatial data structure that organizes geometry into a tree of bounding boxes. Instead of testing every triangle during raycasting, the algorithm tests bounding boxes first and only checks triangles in boxes that intersect the ray. This reduces raycasting complexity from O(n) to O(log n).
Use cases:
<script setup lang="ts">
import { useGLTF, useBVH } from '@tresjs/cientos'
const { state: model } = useGLTF('/models/complex-model.glb')
useBVH(() => model.value?.scene)
</script>
<template>
<primitive v-if="model" :object="model.scene" />
</template>
Enable debug mode to visualize the BVH bounding boxes:
<script setup lang="ts">
import { useGLTF, useBVH } from '@tresjs/cientos'
const { state: model } = useGLTF('/models/model.glb')
useBVH(
() => model.value?.scene,
{
debug: true, // Show BVH bounding boxes
}
)
</script>
Control BVH optimization dynamically:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useGLTF, useBVH } from '@tresjs/cientos'
const bvhEnabled = ref(true)
const { state: model } = useGLTF('/models/model.glb')
useBVH(
() => model.value?.scene,
{
enabled: bvhEnabled,
}
)
</script>
<template>
<div>
<button @click="bvhEnabled = !bvhEnabled">
Toggle BVH
</button>
<primitive v-if="model" :object="model.scene" />
</div>
</template>
Options are divided into reactive (can change at runtime) and static (set once at creation - changing requires toggling enabled off/on to rebuild).
| Option | Type | Default | Description |
|---|---|---|---|
| enabled | MaybeRefOrGetter<boolean> | true | Enable/disable BVH optimization. Toggling rebuilds BVH structures. |
| debug | MaybeRefOrGetter<boolean> | false | Show debug visualization of BVH bounding boxes. |
These options configure how the BVH is built. Changing them after creation has no effect - toggle enabled off/on to rebuild with new values.
| Option | Type | Default | Description |
|---|---|---|---|
| firstHitOnly | boolean | false | Use raycastFirst for better performance when only the first hit is needed. |
| splitStrategy | 'CENTER' | 'AVERAGE' | 'SAH' | 'SAH' | BVH build strategy. See section below. |
| maxDepth | number | 40 | Maximum tree depth for the BVH structure. |
| maxLeafSize | number | 10 | Target number of triangles per leaf node. |
| verbose | boolean | false | Print construction warnings and progress to console. |
| setBoundingBox | boolean | true | Automatically set geometry bounding box after BVH construction. |
| indirect | boolean | false | If false, creates and rearranges index buffer for better performance. |
Adjust BVH construction parameters for your use case:
useBVH(
target,
{
splitStrategy: 'SAH', // Best runtime performance
maxDepth: 30, // Shallower tree (faster build)
maxLeafSize: 5, // Smaller leaves (better culling)
verbose: true, // Debug construction
}
)
When you only need the closest intersection (e.g., mouse picking):
useBVH(
target,
{
firstHitOnly: true, // Uses raycastFirst internally
}
)
This is significantly faster than computing all intersections when you only need one.
enabled and debug are reactive. Construction options (splitStrategy, maxDepth, etc.) are static - to apply new values, toggle enabled off/on.Mesh and SkinnedMesh instances.Perfect pairing with useGLTF for optimized model loading:
<script setup lang="ts">
import { useBVH, useGLTF } from '@tresjs/cientos'
const { state: model } = useGLTF('/models/high-poly-model.glb', {
draco: true
})
useBVH(
() => model.value?.scene,
{ splitStrategy: 'SAH' }
)
</script>
verbose: true to see which meshes are skippedObject3D (use primitive in templates)firstHitOnly if you only need one intersectionSAH is usually best)