Three.js 角度单位介绍
1. 概述
Three.js 中角度单位的使用遵循一个明确的规则:大部分情况下使用弧度(radians),只有少数特定场景使用度(degrees)。
2. 角度单位转换常量
2.1 转换常量定义
核心实现:
javascript
// src/math/MathUtils.js
const DEG2RAD = Math.PI / 180;
const RAD2DEG = 180 / Math.PI;
/**
* Converts degrees to radians.
* @param {number} degrees - A value in degrees.
* @return {number} The converted value in radians.
*/
function degToRad(degrees) {
return degrees * DEG2RAD;
}
/**
* Converts radians to degrees.
* @param {number} radians - A value in radians.
* @return {number} The converted value in degrees.
*/
function radToDeg(radians) {
return radians * RAD2DEG;
}
转换关系:
DEG2RAD = Math.PI / 180 ≈ 0.017453292519943295
RAD2DEG = 180 / Math.PI ≈ 57.29577951308232
3. 使用弧度的场景
3.1 欧拉角 (Euler)
核心实现:
javascript
// src/math/Euler.js
class Euler {
/**
* Constructs a new euler instance.
* @param {number} [x=0] - The angle of the x axis in radians.
* @param {number} [y=0] - The angle of the y axis in radians.
* @param {number} [z=0] - The angle of the z axis in radians.
* @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied.
*/
constructor(x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER) {
this._x = x;
this._y = y;
this._z = z;
this._order = order;
}
/**
* The angle of the x axis in radians.
* @type {number}
* @default 0
*/
get x() {
return this._x;
}
/**
* The angle of the y axis in radians.
* @type {number}
* @default 0
*/
get y() {
return this._y;
}
/**
* The angle of the z axis in radians.
* @type {number}
* @default 0
*/
get z() {
return this._z;
}
}
使用示例:
javascript
// 创建欧拉角(使用弧度)
const euler = new Euler(0, Math.PI / 4, Math.PI / 2);
// 设置旋转(使用弧度)
mesh.rotation.set(0, Math.PI / 4, 0);
// 从度转换为弧度
const angleInRadians = THREE.MathUtils.degToRad(45);
mesh.rotation.y = angleInRadians;
3.2 四元数 (Quaternion)
核心实现:
javascript
// src/math/Quaternion.js
class Quaternion {
/**
* Sets this quaternion from the given axis and angle.
* @param {Vector3} axis - The normalized axis.
* @param {number} angle - The angle in radians.
* @return {Quaternion} A reference to this quaternion.
*/
setFromAxisAngle(axis, angle) {
const halfAngle = angle / 2, s = Math.sin(halfAngle);
this._x = axis.x * s;
this._y = axis.y * s;
this._z = axis.z * s;
this._w = Math.cos(halfAngle);
this._onChangeCallback();
return this;
}
/**
* Returns the angle between this quaternion and the given one in radians.
* @param {Quaternion} q - The quaternion to compute the angle with.
* @return {number} The angle in radians.
*/
angleTo(q) {
return 2 * Math.acos(Math.abs(clamp(this.dot(q), -1, 1)));
}
/**
* Rotates this quaternion by a given angular step to the given quaternion.
* @param {Quaternion} q - The target quaternion.
* @param {number} step - The angular step in radians.
* @return {Quaternion} A reference to this quaternion.
*/
rotateTowards(q, step) {
const angle = this.angleTo(q);
if (angle === 0) return this;
const t = Math.min(1, step / angle);
this.slerp(q, t);
return this;
}
}
使用示例:
javascript
// 从轴角创建四元数(使用弧度)
const quaternion = new Quaternion();
quaternion.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / 4);
// 从度转换为弧度
const angleInRadians = THREE.MathUtils.degToRad(90);
quaternion.setFromAxisAngle(new Vector3(0, 1, 0), angleInRadians);
3.3 矩阵旋转
核心实现:
javascript
// src/math/Matrix4.js
class Matrix4 {
/**
* Sets this matrix as a rotational transformation around the X axis by the given angle.
* @param {number} theta - The rotation in radians.
* @return {Matrix4} A reference to this matrix.
*/
makeRotationX(theta) {
const c = Math.cos(theta), s = Math.sin(theta);
this.set(
1, 0, 0, 0,
0, c, -s, 0,
0, s, c, 0,
0, 0, 0, 1
);
return this;
}
/**
* Sets this matrix as a rotational transformation around the Y axis by the given angle.
* @param {number} theta - The rotation in radians.
* @return {Matrix4} A reference to this matrix.
*/
makeRotationY(theta) {
const c = Math.cos(theta), s = Math.sin(theta);
this.set(
c, 0, s, 0,
0, 1, 0, 0,
-s, 0, c, 0,
0, 0, 0, 1
);
return this;
}
/**
* Sets this matrix as a rotational transformation around the Z axis by the given angle.
* @param {number} theta - The rotation in radians.
* @return {Matrix4} A reference to this matrix.
*/
makeRotationZ(theta) {
const c = Math.cos(theta), s = Math.sin(theta);
this.set(
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
return this;
}
}
使用示例:
javascript
// 创建旋转矩阵(使用弧度)
const matrix = new Matrix4();
matrix.makeRotationY(Math.PI / 4);
// 从度转换为弧度
const angleInRadians = THREE.MathUtils.degToRad(45);
matrix.makeRotationY(angleInRadians);
3.4 球面坐标 (Spherical)
核心实现:
javascript
// src/math/Spherical.js
class Spherical {
/**
* Constructs a new spherical.
* @param {number} [radius=1] - The radius, or the Euclidean distance from the point to the origin.
* @param {number} [phi=0] - The polar angle in radians from the y (up) axis.
* @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis.
*/
constructor(radius = 1, phi = 0, theta = 0) {
/**
* The polar angle in radians from the y (up) axis.
* @type {number}
* @default 0
*/
this.phi = phi;
/**
* The equator/azimuthal angle in radians around the y (up) axis.
* @type {number}
* @default 0
*/
this.theta = theta;
}
}
使用示例:
javascript
// 创建球面坐标(使用弧度)
const spherical = new Spherical(10, Math.PI / 4, Math.PI / 6);
// 从度转换为弧度
const phiInRadians = THREE.MathUtils.degToRad(45);
const thetaInRadians = THREE.MathUtils.degToRad(30);
const spherical = new Spherical(10, phiInRadians, thetaInRadians);
3.5 柱面坐标 (Cylindrical)
核心实现:
javascript
// src/math/Cylindrical.js
class Cylindrical {
/**
* Constructs a new cylindrical.
* @param {number} [radius=1] - The distance from the origin to a point in the x-z plane.
* @param {number} [theta=0] - A counterclockwise angle in the x-z plane measured in radians from the positive z-axis.
* @param {number} [y=0] - The height above the x-z plane.
*/
constructor(radius = 1, theta = 0, y = 0) {
/**
* A counterclockwise angle in the x-z plane measured in radians from the positive z-axis.
* @type {number}
* @default 0
*/
this.theta = theta;
}
}
使用示例:
javascript
// 创建柱面坐标(使用弧度)
const cylindrical = new Cylindrical(5, Math.PI / 3, 3);
// 从度转换为弧度
const thetaInRadians = THREE.MathUtils.degToRad(60);
const cylindrical = new Cylindrical(5, thetaInRadians, 3);
3.6 向量角度计算
核心实现:
javascript
// src/math/Vector3.js
class Vector3 {
/**
* Returns the angle between the given vector and this instance in radians.
* @param {Vector3} v - The vector to compute the angle with.
* @return {number} The angle in radians.
*/
angleTo(v) {
const denominator = Math.sqrt(this.lengthSq() * v.lengthSq());
if (denominator === 0) return Math.PI / 2;
const theta = this.dot(v) / denominator;
// clamp, to handle numerical problems
return Math.acos(clamp(theta, -1, 1));
}
}
使用示例:
javascript
// 计算向量间角度(返回弧度)
const v1 = new Vector3(1, 0, 0);
const v2 = new Vector3(0, 1, 0);
const angleInRadians = v1.angleTo(v2); // Math.PI / 2
// 转换为度
const angleInDegrees = THREE.MathUtils.radToDeg(angleInRadians); // 90
3.7 纹理旋转
核心实现:
javascript
// src/textures/Texture.js
class Texture extends EventDispatcher {
/**
* How much the texture is rotated around the center point, in radians.
* @type {number}
* @default 0
*/
this.rotation = 0;
}
使用示例:
javascript
// 设置纹理旋转(使用弧度)
texture.rotation = Math.PI / 4;
// 从度转换为弧度
const rotationInRadians = THREE.MathUtils.degToRad(45);
texture.rotation = rotationInRadians;
3.8 场景背景旋转
核心实现:
javascript
// src/scenes/Scene.js
class Scene extends Object3D {
/**
* The rotation of the background in radians. Only influences environment maps
* @type {number}
* @default 0
*/
this.backgroundRotation = 0;
/**
* The rotation of the environment map in radians. Only influences physical materials
* @type {number}
* @default 0
*/
this.environmentRotation = 0;
}
使用示例:
javascript
// 设置背景旋转(使用弧度)
scene.backgroundRotation = Math.PI / 6;
// 从度转换为弧度
const rotationInRadians = THREE.MathUtils.degToRad(30);
scene.backgroundRotation = rotationInRadians;
4. 使用度的场景
4.1 透视相机视场角 (FOV)
核心实现:
javascript
// src/cameras/PerspectiveCamera.js
class PerspectiveCamera extends Camera {
/**
* Constructs a new perspective camera.
* @param {number} [fov=50] - The vertical field of view.
* @param {number} [aspect=1] - The aspect ratio.
* @param {number} [near=0.1] - The camera's near plane.
* @param {number} [far=2000] - The camera's far plane.
*/
constructor(fov = 50, aspect = 1, near = 0.1, far = 2000) {
/**
* The vertical field of view, from bottom to top of view, in degrees.
* @type {number}
* @default 50
*/
this.fov = fov;
}
updateProjectionMatrix() {
const near = this.near;
const top = near * Math.tan(THREE.MathUtils.DEG2RAD * 0.5 * this.fov) / this.zoom;
const height = 2 * top;
const width = this.aspect * height;
const left = -0.5 * width;
const right = left + width;
const bottom = top - height;
this.projectionMatrix.makePerspective(left, right, top, bottom, near, this.far);
this.projectionMatrixInverse.copy(this.projectionMatrix).invert();
}
}
使用示例:
javascript
// 创建透视相机(FOV 使用度)
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置视场角(使用度)
camera.fov = 60;
// 更新投影矩阵
camera.updateProjectionMatrix();
4.2 WebXR 管理器中的 FOV 转换
核心实现:
javascript
// src/renderers/webxr/WebXRManager.js
import { RAD2DEG } from '../../math/MathUtils.js';
// 从投影矩阵计算 FOV(转换为度)
camera.fov = RAD2DEG * 2 * Math.atan(1 / camera.projectionMatrix.elements[5]);
4.3 XR 管理器中的 FOV 转换
核心实现:
javascript
// src/renderers/common/XRManager.js
import { RAD2DEG } from '../../math/MathUtils.js';
// 从投影矩阵计算 FOV(转换为度)
camera.fov = RAD2DEG * 2 * Math.atan(1 / camera.projectionMatrix.elements[5]);
5. 角度单位转换工具
5.1 转换函数
核心实现:
javascript
// src/math/MathUtils.js
/**
* Converts degrees to radians.
* @param {number} degrees - A value in degrees.
* @return {number} The converted value in radians.
*/
function degToRad(degrees) {
return degrees * DEG2RAD;
}
/**
* Converts radians to degrees.
* @param {number} radians - A value in radians.
* @return {number} The converted value in degrees.
*/
function radToDeg(radians) {
return radians * RAD2DEG;
}
使用示例:
javascript
// 度转弧度
const radians = THREE.MathUtils.degToRad(45); // Math.PI / 4
// 弧度转度
const degrees = THREE.MathUtils.radToDeg(Math.PI / 4); // 45
// 设置旋转
mesh.rotation.y = THREE.MathUtils.degToRad(90);
// 获取旋转角度
const angleInDegrees = THREE.MathUtils.radToDeg(mesh.rotation.y);
5.2 常用角度值
核心实现:
javascript
// 常用角度值(弧度)
const PI = Math.PI;
const HALF_PI = Math.PI / 2;
const QUARTER_PI = Math.PI / 4;
const TWO_PI = Math.PI * 2;
// 常用角度值(度)
const DEG_0 = 0;
const DEG_45 = 45;
const DEG_90 = 90;
const DEG_180 = 180;
const DEG_360 = 360;
使用示例:
javascript
// 使用预定义角度值
mesh.rotation.y = Math.PI / 2; // 90度
mesh.rotation.y = Math.PI / 4; // 45度
mesh.rotation.y = Math.PI; // 180度
mesh.rotation.y = Math.PI * 2; // 360度
// 使用转换函数
mesh.rotation.y = THREE.MathUtils.degToRad(90);
mesh.rotation.y = THREE.MathUtils.degToRad(45);
mesh.rotation.y = THREE.MathUtils.degToRad(180);
mesh.rotation.y = THREE.MathUtils.degToRad(360);
6. 实际应用示例
6.1 对象旋转
javascript
// 方法1:直接使用弧度
mesh.rotation.y = Math.PI / 4; // 45度
// 方法2:使用转换函数
mesh.rotation.y = THREE.MathUtils.degToRad(45);
// 方法3:使用四元数
const quaternion = new Quaternion();
quaternion.setFromAxisAngle(new Vector3(0, 1, 0), THREE.MathUtils.degToRad(45));
mesh.quaternion.copy(quaternion);
6.2 相机控制
javascript
// 透视相机 FOV(使用度)
const camera = new PerspectiveCamera(75, aspect, 0.1, 1000);
// 相机旋转(使用弧度)
camera.rotation.y = THREE.MathUtils.degToRad(30);
camera.rotation.x = THREE.MathUtils.degToRad(-15);
6.3 动画
javascript
// 旋转动画(使用弧度)
function animate() {
requestAnimationFrame(animate);
// 每帧旋转 1 度
mesh.rotation.y += THREE.MathUtils.degToRad(1);
renderer.render(scene, camera);
}
6.4 球面坐标
javascript
// 球面坐标(使用弧度)
const spherical = new Spherical();
spherical.radius = 10;
spherical.phi = THREE.MathUtils.degToRad(45); // 极角
spherical.theta = THREE.MathUtils.degToRad(30); // 方位角
const position = new Vector3();
position.setFromSpherical(spherical);
mesh.position.copy(position);
7. 最佳实践
7.1 角度单位选择
推荐做法:
javascript
// 1. 内部计算使用弧度
const angleInRadians = Math.PI / 4;
// 2. 用户输入使用度,然后转换
const userInput = 45; // 度
const angleInRadians = THREE.MathUtils.degToRad(userInput);
// 3. 显示给用户时转换为度
const displayAngle = THREE.MathUtils.radToDeg(mesh.rotation.y);
console.log(`Rotation: ${displayAngle} degrees`);
7.2 常量定义
javascript
// 定义常用角度常量
const ANGLES = {
DEG_0: 0,
DEG_45: THREE.MathUtils.degToRad(45),
DEG_90: THREE.MathUtils.degToRad(90),
DEG_180: THREE.MathUtils.degToRad(180),
DEG_360: THREE.MathUtils.degToRad(360)
};
// 使用常量
mesh.rotation.y = ANGLES.DEG_90;
7.3 角度验证
javascript
// 角度范围验证
function validateAngle(angle, min = 0, max = Math.PI * 2) {
return Math.max(min, Math.min(max, angle));
}
// 使用验证
mesh.rotation.y = validateAngle(THREE.MathUtils.degToRad(45));
8. 常见错误
8.1 角度单位混淆
错误示例:
javascript
// 错误:直接使用度
mesh.rotation.y = 45; // 这会被当作弧度,实际是 45 弧度!
// 正确:转换为弧度
mesh.rotation.y = THREE.MathUtils.degToRad(45);
8.2 FOV 单位混淆
错误示例:
javascript
// 错误:FOV 使用弧度
const camera = new PerspectiveCamera(Math.PI / 4, aspect, 0.1, 1000); // 错误!
// 正确:FOV 使用度
const camera = new PerspectiveCamera(45, aspect, 0.1, 1000); // 正确!
9. 总结
9.1 角度单位规则
-
使用弧度的场景:
- 欧拉角 (
Euler
) - 四元数 (
Quaternion
) - 矩阵旋转 (
Matrix4
) - 球面坐标 (
Spherical
) - 柱面坐标 (
Cylindrical
) - 向量角度计算 (
Vector3.angleTo()
) - 纹理旋转 (
Texture.rotation
) - 场景背景旋转 (
Scene.backgroundRotation
)
- 欧拉角 (
-
使用度的场景:
- 透视相机视场角 (
PerspectiveCamera.fov
) - WebXR 管理器中的 FOV 计算
- XR 管理器中的 FOV 计算
- 透视相机视场角 (
9.2 转换工具
THREE.MathUtils.degToRad(degrees)
- 度转弧度THREE.MathUtils.radToDeg(radians)
- 弧度转度DEG2RAD
- 度转弧度常量RAD2DEG
- 弧度转度常量
9.3 最佳实践
- 内部计算使用弧度
- 用户输入使用度,然后转换
- 显示给用户时转换为度
- 定义常用角度常量
- 进行角度范围验证
通过理解这些角度单位的使用规则,开发者可以避免常见的角度单位混淆错误,正确使用 three.js 中的各种角度相关功能。