threejs-core-raycaster
threejs-core-raycaster
Quick Reference
Raycaster Constructor
import * as THREE from 'three';
const raycaster = new THREE.Raycaster(
origin, // Vector3 — default: (0, 0, 0)
direction, // Vector3 — MUST be normalized, default: (0, 0, -1)
near, // number — minimum distance, default: 0
far // number — maximum distance, default: Infinity
);
Properties
| Property | Type | Default | Description |
|---|---|---|---|
ray |
Ray |
-- | Underlying Ray object (origin + direction) |
near |
number |
0 |
Minimum intersection distance |
far |
number |
Infinity |
Maximum intersection distance |
camera |
Camera |
-- | Required for raycasting against Sprite objects |
layers |
Layers |
layer 0 | Layer mask -- only objects on matching layers are tested |
params |
object |
see below | Per-type intersection thresholds |
Params Defaults
raycaster.params = {
Mesh: {},
Line: { threshold: 1 }, // world units distance for line hits
LOD: {},
Points: { threshold: 1 }, // world units distance for point hits
Sprite: {}
};
Critical Warnings
NEVER raycast on every mousemove event without throttling -- this causes severe frame drops on complex scenes. ALWAYS throttle to the render loop via requestAnimationFrame or limit to 30-60 checks per second.
NEVER pass recursive: true on large scene graphs when you have a flat array of target objects -- ALWAYS maintain a flat array of selectable objects and pass recursive: false to avoid unnecessary tree traversal.
NEVER forget to normalize the direction vector when using raycaster.set() -- an unnormalized direction produces incorrect distance values in all intersection results.
NEVER allocate a new results array every frame -- ALWAYS reuse an array via the optionalTarget parameter and clear it with array.length = 0 after processing.
NEVER raycast against the entire scene.children when only a subset of objects is interactive -- ALWAYS maintain a separate array of selectable objects or use layers for filtering.
NEVER omit raycaster.camera when raycasting against Sprite objects -- the raycaster requires the camera reference to compute sprite screen-space bounds.
Core Methods
set(origin, direction)
Manually sets the ray origin and direction. Direction MUST be normalized.
const origin = new THREE.Vector3(0, 1, 0);
const direction = new THREE.Vector3(0, -1, 0); // already normalized
raycaster.set(origin, direction);
setFromCamera(coords, camera)
Sets the ray from normalized device coordinates (NDC) and a camera. This is the standard mouse-picking method.
coords:Vector2with x and y in range [-1, +1]camera:PerspectiveCameraproduces a ray from the camera through the point;OrthographicCameraproduces a parallel ray
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersectObject(object, recursive?, optionalTarget?)
Tests a single object (and optionally its descendants) for intersections.
recursive(default:true): whentrue, tests all descendantsoptionalTarget: reusable array to avoid allocation- Returns:
Intersection[]ALWAYS sorted by distance (nearest first)
intersectObjects(objects, recursive?, optionalTarget?)
Tests an array of objects for intersections.
recursive(default:true): whentrue, tests descendants of each objectoptionalTarget: reusable array to avoid allocation- Returns:
Intersection[]ALWAYS sorted by distance (nearest first)
Intersection Object Format
Every intersection in the returned array has this structure:
interface Intersection {
distance: number; // distance from ray origin to hit point
point: Vector3; // hit point in world space
face: Face | null; // hit face ({a, b, c} vertex indices + normal)
faceIndex: number; // index of hit face in the geometry
object: Object3D; // the intersected object reference
uv?: Vector2; // UV coordinates at intersection point
uv1?: Vector2; // second UV set (if available)
normal?: Vector3; // interpolated surface normal at hit point
instanceId?: number; // instance index (only for InstancedMesh)
}
ALWAYS check intersects.length > 0 before accessing intersects[0]. The array is empty when no objects are hit.
NDC Conversion (Normalized Device Coordinates)
Converting DOM mouse coordinates to NDC is required for setFromCamera. The NDC space ranges from -1 (left/bottom) to +1 (right/top).
// For fullscreen canvas (canvas fills entire window)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// For non-fullscreen canvas (canvas has offset within page)
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
ALWAYS use getBoundingClientRect() when the canvas does NOT fill the entire viewport. Using window.innerWidth/Height on a partial canvas produces incorrect NDC values and misaligned picking.
InstancedMesh Picking
When raycasting against InstancedMesh, the intersection result includes instanceId identifying which instance was hit:
import * as THREE from 'three';
const intersects = raycaster.intersectObject(instancedMesh);
if (intersects.length > 0) {
const instanceId = intersects[0].instanceId;
// Retrieve the instance's transform matrix
const matrix = new THREE.Matrix4();
instancedMesh.getMatrixAt(instanceId, matrix);
// Example: change the color of the hit instance
const color = new THREE.Color();
instancedMesh.getColorAt(instanceId, color);
instancedMesh.setColorAt(instanceId, new THREE.Color(0xff0000));
instancedMesh.instanceColor.needsUpdate = true;
}
ALWAYS check that instanceId !== undefined before using it -- non-InstancedMesh objects do not have this property.
Layer Filtering
Raycaster respects the Layers system. Only objects whose layers overlap with raycaster.layers are tested.
// Assign objects to layers
selectableMesh.layers.set(1); // exclusively on layer 1
decorationMesh.layers.set(2); // exclusively on layer 2
// Configure raycaster to only test layer 1
raycaster.layers.set(1);
// Now intersectObjects skips all objects NOT on layer 1
// Enable multiple layers
raycaster.layers.enable(1);
raycaster.layers.enable(2);
// Now tests objects on layer 1 OR layer 2
// Reset to default (layer 0 only)
raycaster.layers.set(0);
Use layers to create picking groups: interactive objects on layer 1, non-interactive decoration on layer 2, helpers/gizmos on layer 3.
Performance Guidelines
-
Throttle mousemove raycasting -- NEVER raycast on every
mousemoveevent. Use a flag checked in the render loop:let needsRaycast = false; canvas.addEventListener('pointermove', (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; needsRaycast = true; }); function animate() { if (needsRaycast) { raycaster.setFromCamera(mouse, camera); // perform intersection tests needsRaycast = false; } renderer.render(scene, camera); requestAnimationFrame(animate); } -
Use layers -- assign interactive objects to a specific layer and set
raycaster.layersaccordingly to skip non-interactive geometry entirely. -
Maintain a flat selectable array -- instead of raycasting against
scenewithrecursive: true, keep a separateselectableObjects[]array and passrecursive: false. -
Reuse the intersections array -- pass
optionalTargetto avoid garbage collection:const intersections = []; raycaster.intersectObjects(selectableObjects, false, intersections); // process results... intersections.length = 0; -
Bounding sphere pre-check -- Three.js automatically tests bounding spheres before triangle intersection. Ensure
geometry.computeBoundingSphere()has been called (it is called automatically on first render, but manual call is needed if raycasting before first render). -
Limit
near/far-- narrow the ray range when you know the expected intersection distance to skip distant objects early.
Reference Links
- references/methods.md -- Complete Raycaster API signatures
- references/examples.md -- Working code examples for picking, hover, and InstancedMesh
- references/anti-patterns.md -- What NOT to do with raycasting