Align Object to Raycast Normal in Three.js

This article explains how to align a 3D object to match the exact surface normal vector at the point of intersection using the Three.js Raycaster. You will learn how to extract the normal vector from raycaster intersection data, convert it if necessary, and use quaternions to smoothly rotate your 3D object so that its base or “up” direction aligns perfectly with the target surface.

Step 1: Perform the Raycast

First, you need to set up a THREE.Raycaster and cast a ray from a source (like the camera or mouse coordinates) into your scene. When the ray intersects a mesh, Three.js returns an intersection object containing valuable spatial data, including the precise point of contact and the normal vector of the intersected face.

const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);

const intersects = raycaster.intersectObjects(scene.children, true);

if (intersects.length > 0) {
    const hit = intersects[0];
    
    // Position of intersection in world space
    const targetPosition = hit.point; 
    
    // Normal vector of the intersected surface in world space
    const targetNormal = hit.normal; 
}

Note: In modern versions of Three.js, intersection.normal is already provided in world space. If you are using an older version, you may need to get the local face normal (hit.face.normal) and apply the intersected mesh’s normal matrix to convert it to world space.

Step 2: Calculate the Rotation with Quaternions

To align your 3D object with the surface normal, you must calculate the rotation difference between the object’s current alignment axis (usually the Y-axis/up vector) and the target normal vector.

The most efficient and gimbal-lock-free way to achieve this in Three.js is using the setFromUnitVectors method of a THREE.Quaternion.

// Define the default local 'up' vector of your 3D object
const localUp = new THREE.Vector3(0, 1, 0);

// Create a quaternion to store the rotation
const rotationQuaternion = new THREE.Quaternion();

// Calculate the rotation from the local up vector to the target normal
rotationQuaternion.setFromUnitVectors(localUp, targetNormal.clone().normalize());

Step 3: Apply Position and Rotation to the Object

Once you have calculated the rotation quaternion and the intersection point, apply them directly to your 3D object.

const myObject = new THREE.Mesh(geometry, material);

// Position the object exactly at the intersection point
myObject.position.copy(targetPosition);

// Apply the calculated rotation
myObject.quaternion.copy(rotationQuaternion);

Resolving Potential Alignment Offsets

If your object aligns incorrectly (for example, lying flat on its side instead of standing perpendicular to the surface), it is because the default orientation of your 3D model does not point up along the Y-axis.

You can resolve this in two ways: 1. Adjust the source vector: Change localUp in your code to match the actual direction vector of your model’s base (e.g., new THREE.Vector3(0, 0, 1) if the model’s top points forward along the Z-axis). 2. Offset the geometry: Adjust the geometry rotation within your 3D modeling software, or rotate the geometry itself in Three.js using mesh.geometry.rotateX() or rotateY() before placing it in the scene.