How to Use Three.js Ray Without Raycaster

In Three.js, while the Raycaster class is the standard tool for mouse picking and object collision, you can perform lower-level, high-performance manual intersection calculations using the lightweight Ray class. This article explains how to instantiate a THREE.Ray object with an origin and a direction, and demonstrates how to manually calculate intersection points with geometric primitives like planes, spheres, and triangles.

Creating a THREE.Ray Object

To manually calculate intersections, you must first construct a THREE.Ray. The Ray constructor requires two parameters: an origin point and a direction vector. Both must be instances of THREE.Vector3. Crucially, the direction vector must be normalized (have a length of 1) for the mathematical calculations to work correctly.

import * as THREE from 'three';

// Define the starting point of the ray
const origin = new THREE.Vector3(0, 5, 0);

// Define the direction (e.g., pointing straight down the Y-axis)
const direction = new THREE.Vector3(0, -1, 0).normalize();

// Construct the Ray
const ray = new THREE.Ray(origin, direction);

Calculating Intersections manually

Once your Ray is defined, you can use its built-in methods to check for intersections against various mathematical shapes. To prevent garbage collection overhead, these methods require a “target” Vector3 variable where the resulting intersection coordinates will be stored.

1. Intersecting with a Plane

To find where a ray intersects an infinite flat surface, use THREE.Plane and the intersectPlane method.

// Create a plane facing upwards, positioned at y = 0
const planeNormal = new THREE.Vector3(0, 1, 0);
const plane = new THREE.Plane(planeNormal, 0);

// Create a target vector to store the result
const intersectionPoint = new THREE.Vector3();

// Calculate the intersection
const result = ray.intersectPlane(plane, intersectionPoint);

if (result !== null) {
    console.log("Intersection point:", intersectionPoint); 
    // Output: Vector3 {x: 0, y: 0, z: 0}
} else {
    console.log("The ray does not intersect the plane.");
}

2. Intersecting with a Sphere

To detect if and where a ray hits a bounding sphere, use THREE.Sphere and the intersectSphere method.

// Create a sphere at center (0, 0, 0) with a radius of 2
const sphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 2);
const intersectionPoint = new THREE.Vector3();

const result = ray.intersectSphere(sphere, intersectionPoint);

if (result !== null) {
    console.log("Sphere intersection point:", intersectionPoint);
    // Output: Vector3 {x: 0, y: 2, z: 0} (where the ray hits the top of the sphere)
}

3. Intersecting with a Triangle

For polygon-level precision (such as manual terrain clamping or custom mesh collisions), you can check intersections against individual triangles using intersectTriangle.

// Define the three vertices of a triangle
const vA = new THREE.Vector3(-1, 0, -1);
const vB = new THREE.Vector3(1, 0, -1);
const vC = new THREE.Vector3(0, 0, 1);

const backfaceCulling = true;
const intersectionPoint = new THREE.Vector3();

const result = ray.intersectTriangle(vA, vB, vC, backfaceCulling, intersectionPoint);

if (result !== null) {
    console.log("Triangle intersection point:", intersectionPoint);
}

By using the THREE.Ray class directly, you bypass the overhead of traversing the entire 3D scene graph, making it an ideal choice for physics engines, custom pathfinding, and fast-running gameplay calculations.