Three.js 角度单位介绍

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 角度单位规则

  1. 使用弧度的场景:

    • 欧拉角 (Euler)
    • 四元数 (Quaternion)
    • 矩阵旋转 (Matrix4)
    • 球面坐标 (Spherical)
    • 柱面坐标 (Cylindrical)
    • 向量角度计算 (Vector3.angleTo())
    • 纹理旋转 (Texture.rotation)
    • 场景背景旋转 (Scene.backgroundRotation)
  2. 使用度的场景:

    • 透视相机视场角 (PerspectiveCamera.fov)
    • WebXR 管理器中的 FOV 计算
    • XR 管理器中的 FOV 计算

9.2 转换工具

  • THREE.MathUtils.degToRad(degrees) - 度转弧度
  • THREE.MathUtils.radToDeg(radians) - 弧度转度
  • DEG2RAD - 度转弧度常量
  • RAD2DEG - 弧度转度常量

9.3 最佳实践

  1. 内部计算使用弧度
  2. 用户输入使用度,然后转换
  3. 显示给用户时转换为度
  4. 定义常用角度常量
  5. 进行角度范围验证

通过理解这些角度单位的使用规则,开发者可以避免常见的角度单位混淆错误,正确使用 three.js 中的各种角度相关功能。

相关推荐
傻梦兽2 小时前
用 scheduler.yield() 让你的网页应用飞起来⚡
前端·javascript
然我2 小时前
搞定异步任务依赖:Promise.all 与拓扑排序的妙用
前端·javascript·算法
usagisah2 小时前
为 CSS-IN-JS 正个名,被潮流抛弃并不代表无用,与原子类相比仍有一战之力
前端·javascript·css
fox_2 小时前
JS:手搓一份防抖和节流函数
javascript
bug_kada4 小时前
Js 的事件循环(Event Loop)机制以及面试题讲解
前端·javascript
bug_kada4 小时前
深入理解 JavaScript 可选链操作符
前端·javascript
LuckySusu20 小时前
【js篇】JavaScript 对象创建的 6 种方式:从基础到高级
前端·javascript
LuckySusu20 小时前
【js篇】async/await 的五大核心优势:让异步代码像同步一样清晰
前端·javascript
艾雅法拉拉20 小时前
JS知识点回顾(1)
前端·javascript·面试