Raycasting in R3F

Back to Modules

Raycasting in React Three Fiber

Raycasting in React Three Fiber (R3F) allows you to detect interactions between a virtual "ray" (often originating from the camera and pointing towards the mouse cursor) and objects in your 3D scene. This is fundamental for implementing interactive experiences like clicking on objects, hovering, or dragging.

1. Built-in Pointer Events (Recommended for most cases)

For typical interactions such as onClick, onPointerOver, onPointerOut, onPointerMove, etc., React Three Fiber automatically handles the underlying raycasting. You can attach these event handlers directly to your mesh components, similar to how you would with DOM elements.

The <Canvas /> component in R3F sets up a raycaster by default. This raycaster's origin and direction are automatically updated based on mouse movement and window resizing, and it uses the currently active camera to determine the ray's path.

Example: Interactive Box and Sphere

import { Canvas } from '@react-three/fiber';
import { Box } from '@react-three/drei'; // A helpful component from @react-three/drei

function MyInteractiveScene() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />

      {/* A clickable box */}
      <Box
        args={[1, 1, 1]} // Dimensions: width, height, depth
        position={[0, 0, 0]}
        onClick={(event) => {
          console.log('Box clicked!', event.object.name);
          // You can access properties of the intersected object via event.object
          // event.stopPropagation() can be used to prevent events from bubbling up to parent objects
        }}
        onPointerOver={(event) => {
          console.log('Pointer over box');
          document.body.style.cursor = 'pointer';
        }}
        onPointerOut={(event) => {
          console.log('Pointer out of box');
          document.body.style.cursor = 'auto';
        }}
      >
        <meshStandardMaterial color="hotpink" />
      </Box>

      {/* Another clickable sphere */}
      <mesh
        position={[2, 0, 0]}
        onClick={() => console.log('Sphere clicked!')}
      >
        <sphereGeometry args={[0.7, 32, 32]} />
        <meshStandardMaterial color="royalblue" />
      </mesh>
    </Canvas>
  );
}

export default MyInteractiveScene;

2. Manual Raycasting (For Advanced Use Cases)

While R3F's built-in events are powerful, there might be situations where you need more control, such as:

In these cases, you can directly use Three.js's Raycaster class within your R3F components. You can access the WebGLRenderer (gl), Camera (camera), and Scene (scene) objects from the useThree hook provided by R3F.

Example of Manual Raycasting:

import { Canvas, useThree } from '@react-three/fiber';
import { useEffect } from 'react';
import *s THREE from 'three';

function RaycasterHandler() {
  const { gl, camera, scene } = useThree();

  useEffect(() => {
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2();

    const onCanvasClick = (event) => {
      // Calculate pointer position in normalized device coordinates (-1 to +1)
      pointer.x = (event.clientX / gl.domElement.clientWidth) * 2 - 1;
      pointer.y = -(event.clientY / gl.domElement.clientHeight) * 2 + 1;

      // Update the raycaster with the camera and pointer position
      raycaster.setFromCamera(pointer, camera);

      // Calculate objects intersecting the ray
      // The second argument (true) makes it recursive, checking children of objects
      const intersects = raycaster.intersectObjects(scene.children, true);

      if (intersects.length > 0) {
        console.log('Manual Raycast: Intersected object(s):', intersects);
      } else {
        console.log('Manual Raycast: Clicked on empty space.');
      }
    };

    // Attach the event listener to the canvas DOM element
    gl.domElement.addEventListener('click', onCanvasClick);

    // Clean up the event listener
    return () => {
      gl.domElement.removeEventListener('click', onCanvasClick);
    };
  }, [gl, camera, scene]);

  return null;
}

function MySceneWithManualRaycasting() {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 10, 10]} />

      <mesh position={[0, 0, 0]}>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="orange" />
      </mesh>

      <RaycasterHandler />
    </Canvas>
  );
}