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);x: The current input value you want to map.xMin/xMax: The minimum and maximum limits of the input data.yMin/yMax: The minimum and maximum limits of the output data (in this case, your rotation limits).
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.