Three.js: Track a Moving Object with Object3D.lookAt
In Three.js, orienting a 3D object to face another moving target is a
fundamental task for creating dynamic scenes, interactive cameras, or
responsive character behaviors. This article provides a straightforward
guide on how to use the Object3D.lookAt method within the
animation loop to continuously track a moving object’s position in
real-time.
Understanding Object3D.lookAt
The lookAt method rotates an object so that its local
front facing side (by default, the positive Z-axis) points directly
toward a specified target position in 3D space. The method accepts
either a THREE.Vector3 object or three individual
coordinate numbers (x, y, z) representing the target’s
position.
// Syntax using a Vector3
myObject.lookAt(targetVector3);
// Syntax using coordinates
myObject.lookAt(x, y, z);Implementation for Moving Targets
Because the target object is moving, calling lookAt only
once during setup is not enough. You must continuously update the
orientation of the tracking object inside your render or animation
loop.
Here is a practical, step-by-step code implementation:
import * as THREE from 'three';
// 1. Setup scene, camera, and renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 2. Create the tracking object (e.g., a cone pointing along its local Z-axis)
const trackerGeometry = new THREE.ConeGeometry(1, 4, 32);
// By default, Three.js cones point up (Y-axis).
// Rotate the geometry so the tip points along the positive Z-axis.
trackerGeometry.rotateX(Math.PI / 2);
const trackerMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const tracker = new THREE.Mesh(trackerGeometry, trackerMaterial);
scene.add(tracker);
// 3. Create the moving target (e.g., a green sphere)
const targetGeometry = new THREE.SphereGeometry(1, 32, 32);
const targetMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const target = new THREE.Mesh(targetGeometry, targetMaterial);
scene.add(target);
camera.position.z = 20;
// 4. The Animation Loop
function animate(time) {
requestAnimationFrame(animate);
// Convert time to seconds for smooth motion
const seconds = time * 0.001;
// Move the target sphere in a circular path
target.position.x = Math.sin(seconds * 2) * 8;
target.position.y = Math.cos(seconds * 2) * 8;
// Force the tracker to look at the target's updated position
tracker.lookAt(target.position);
renderer.render(scene, camera);
}
animate();Important Considerations
- Local Forward Axis:
lookAtrotates the object’s local positive Z-axis to point at the target. If your 3D model or geometry points in a different direction (like the Y-axis for default cones or cylinders), you must pre-rotate the geometry (usinggeometry.rotateX()orgeometry.rotateY()) so that its forward-facing side aligns with the positive Z-axis. - Nested Objects (Parenting): If either the tracker
or the target is a child of another rotated object,
tracker.lookAt(target.position)may fail becausepositionrefers to local coordinates. To track objects inside nested hierarchies, retrieve the target’s absolute world position first:
const worldPosition = new THREE.Vector3();
target.getWorldPosition(worldPosition);
tracker.lookAt(worldPosition);