# BVH

Shows how to form self-contained components with their own state and user interaction.

Demo: https://pmndrs.github.io/examples/examples/bvh
Source: https://github.com/pmndrs/examples/tree/main/examples/bvh
Scaffold: npx degit pmndrs/examples/examples/bvh
Published: 2023-01-25
Authors: Paul Henschel
Tags: bvh, raycast
Ported from: https://codesandbox.io/s/txzeq8
Dependencies: @react-three/drei@10.7.8, @react-three/fiber@9.6.1, leva@^0.10.1, r3f-perf@^7.2.3, react@19.2.8, react-dom@19.2.8, three@0.165.0, three-mesh-bvh@0.7.8

## src/App.tsx

```tsx
import { useRef } from "react";
import * as THREE from "three";
import { Canvas, useFrame, type ThreeElements } from "@react-three/fiber";
import { Bvh, OrbitControls } from "@react-three/drei";
import { Perf } from "r3f-perf";
import { useControls } from "leva";
import { Rays } from "./Rays";

function Torus(props: ThreeElements["mesh"]) {
  const mesh = useRef<THREE.Mesh>(null!);
  const sphere = useRef<THREE.Mesh>(null!);
  useFrame(
    (state, delta) =>
      (mesh.current.rotation.x = mesh.current.rotation.y += delta),
  );
  return (
    <mesh
      ref={mesh}
      {...props}
      onPointerMove={(e) =>
        sphere.current.position.copy(mesh.current.worldToLocal(e.point))
      }
      onPointerOver={() => (sphere.current.visible = true)}
      onPointerOut={() => (sphere.current.visible = false)}
    >
      <torusKnotGeometry args={[1, 0.4, 200, 50]} />
      <meshNormalMaterial />
      <mesh raycast={() => null} ref={sphere} visible={false}>
        <sphereGeometry args={[0.2]} />
        <meshBasicMaterial color="orange" toneMapped={false} />
      </mesh>
    </mesh>
  );
}

export default function App() {
  const { enabled } = useControls({ enabled: true });
  return (
    <Canvas camera-position-z={40} camera-far={100}>
      <color attach="background" args={["#202025"]} />
      <Perf position="bottom-right" style={{ margin: 10 }} />
      {/** Anything that Bvh wraps is getting three-mesh-bvh's acceleratedRaycast.
           Click on "enabled" to see what normal raycast performance in threejs looks like. */}
      <Bvh firstHitOnly enabled={enabled}>
        <Rays>
          <Torus />
        </Rays>
      </Bvh>
      <OrbitControls />
    </Canvas>
  );
}
```

## src/Rays.tsx

```tsx
import * as THREE from "three";
import { useRef, useEffect, type RefObject } from "react";
import { useFrame, type ThreeElements } from "@react-three/fiber";
import { useControls } from "leva";

const pointDist = 25;
const raycaster = new THREE.Raycaster();
const origVec = new THREE.Vector3();
const dirVec = new THREE.Vector3();
const cyl = new THREE.CylinderGeometry(0.02, 0.02);
const sph = new THREE.SphereGeometry(0.25, 20, 20);
const bas = new THREE.MeshBasicMaterial();
const tra = new THREE.MeshBasicMaterial({ transparent: true, opacity: 0.25 });

export const Rays = (props: ThreeElements["group"]) => {
  const ref = useRef<THREE.Group>(null!);
  const { count } = useControls({ count: { value: 100, min: 0, max: 500 } });
  return (
    <>
      <group ref={ref} {...props} />
      {Array.from({ length: count }, (_, id) => {
        return <Ray key={id} target={ref} />;
      })}
    </>
  );
};

const Ray = ({ target }: { target: RefObject<THREE.Group> }) => {
  const objRef = useRef<THREE.Group>(null!);
  const origMesh = useRef<THREE.Mesh>(null!);
  const hitMesh = useRef<THREE.Mesh>(null!);
  const cylinderMesh = useRef<THREE.Mesh>(null!);

  useEffect(() => {
    hitMesh.current.scale.multiplyScalar(0.5);
    origMesh.current.position.set(pointDist, 0, 0);
    objRef.current.rotation.x = Math.random() * 10;
    objRef.current.rotation.y = Math.random() * 10;
  }, []);

  const xDir = Math.random() - 0.5;
  const yDir = Math.random() - 0.5;

  useFrame((state, delta) => {
    const obj = objRef.current;
    obj.rotation.x += xDir * delta;
    obj.rotation.y += yDir * delta;
    origMesh.current.updateMatrixWorld();
    origVec.setFromMatrixPosition(origMesh.current.matrixWorld);
    dirVec.copy(origVec).multiplyScalar(-1).normalize();
    raycaster.set(origVec, dirVec);
    raycaster.firstHitOnly = true;
    const res = raycaster.intersectObject(target.current, true);
    const length = res.length ? res[0].distance : pointDist;
    hitMesh.current.position.set(pointDist - length, 0, 0);
    cylinderMesh.current.position.set(pointDist - length / 2, 0, 0);
    cylinderMesh.current.scale.set(1, length, 1);
    cylinderMesh.current.rotation.z = Math.PI / 2;
  });

  return (
    <group ref={objRef}>
      <mesh ref={origMesh} geometry={sph} material={bas} />
      <mesh ref={hitMesh} geometry={sph} material={bas} />
      <mesh ref={cylinderMesh} geometry={cyl} material={tra} />
    </group>
  );
};
```

## src/index.tsx

```tsx
import { createRoot } from "react-dom/client";
import "./styles.css";
import App from "./App";

createRoot(document.getElementById("root")!).render(<App />);
```

## src/styles.css

```css
* {
  box-sizing: border-box;
}

html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background: #202025;
}
```

## src/vite-env.d.ts

```ts
/// <reference types="vite/client" />
```
