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

const worldPosition = new THREE.Vector3();
target.getWorldPosition(worldPosition);
tracker.lookAt(worldPosition);