Using Three.js MathUtils.mapLinear for Rotation Ranges

In Three.js, translating arbitrary input data—such as mouse coordinates, scroll positions, or sensor data—into controlled 3D rotations can be challenging. The MathUtils.mapLinear function simplifies this process by taking an input value from a known range and mapping it proportionally to a target rotational range (typically defined in radians). This article explains how MathUtils.mapLinear works, its mathematical underpinnings, and how to implement it to achieve precise rotation limits in your 3D scenes.

Understanding MathUtils.mapLinear

The MathUtils.mapLinear function is a linear interpolation utility. It projects a value from an initial range onto a completely different target range.

The syntax for the function is:

THREE.MathUtils.mapLinear(x, xMin, xMax, yMin, yMax);

Mathematically, the function calculates where x lies relative to the interval [xMin, xMax] as a percentage, and then applies that same percentage to the interval [yMin, yMax].

Normalizing Inputs to Rotational Radians

Three.js measures rotations in radians rather than degrees. When creating interactive elements—like a camera that pans as the mouse moves, or a character model that faces the cursor—you must map input values (like screen pixels or normalized device coordinates) into a specific range of radians.

For example, if you want a 3D object to rotate on its Y-axis up to 45 degrees left or right based on the mouse horizontal position, you can map the screen coordinate inputs directly to the desired radian bounds.

Code Implementation

Here is how you can implement this in your Three.js project:

import * as THREE from 'three';

// 1. Define the input range (e.g., normalized mouse coordinates from -1 to 1)
const inputMin = -1;
const inputMax = 1;

// 2. Define the output rotation range in radians (-45 degrees to 45 degrees)
const maxRotationRad = THREE.MathUtils.degToRad(45); // Approx 0.785
const rotationMin = -maxRotationRad;
const rotationMax = maxRotationRad;

// 3. Set up an event listener to capture mouse movement
window.addEventListener('mousemove', (event) => {
    // Normalize mouse x position to be between -1 and 1
    const mouseX = (event.clientX / window.innerWidth) * 2 - 1;

    // Map the input to the target rotational range
    const targetRotationY = THREE.MathUtils.mapLinear(
        mouseX,
        inputMin,
        inputMax,
        rotationMin,
        rotationMax
    );

    // Apply the mapped rotation to the 3D mesh
    myMesh.rotation.y = targetRotationY;
});

Handling Out-of-Bounds Values

It is important to note that MathUtils.mapLinear does not automatically clamp values. If your input x exceeds the specified xMin or xMax boundaries, the resulting output will also exceed the yMin or yMax boundaries.

To prevent your 3D objects from over-rotating when inputs go out of bounds, you should wrap the mapped value in MathUtils.clamp:

const mappedRotation = THREE.MathUtils.mapLinear(mouseX, -1, 1, -Math.PI / 4, Math.PI / 4);

// Clamp the output to ensure it never exceeds the rotation limits
myMesh.rotation.y = THREE.MathUtils.clamp(mappedRotation, -Math.PI / 4, Math.PI / 4);

By combining mapLinear and clamp, you ensure smooth, predictable, and bounded rotations regardless of how erratic the user input may be.