Threejs源码系列- Quaternion

Quaternion 类

js 复制代码
class Quaternion {
    constructor( x = 0, y = 0, z = 0, w = 1 ) {}

    // 用于在两个四元数之间执行球面线性插值(SLERP)
    // 球面线性插值(SLERP)是一种在两个方向四元数(表示旋转 3D 空间中的旋转旋转)之间平滑过渡的方法,相比线性插值(LERP)能保持旋转速度的均匀性,避免插值过程中出现 "加速 - 减速" 的视觉效果。
    static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {}

    // 用于在扁平数组(flat arrays)中计算两个四元数的乘积
    static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {}

    get x() {}
    set x(value) {}

    get y() {}
    set y(value) {}

    get z() {}
    set z(value) {}

    get w() {}
    set w(value) {}

    set(x, y, z, w) {}

    clone() {}

    copy(quaternion) {}

    // 将欧拉角(Euler angles)转换为四元数(Quaternion),是 3D 旋转表示形式转换的核心方法。
    setFromEuler(euler, update = true) {}

    // 将 "轴角表示" 的旋转转换为四元数表示,是 3D 旋转表示形式转换的重要方法。
    setFromAxisAngle(axis, angle) {}

    // 将旋转矩阵(3x3 纯旋转矩阵,通常来自 4x4 变换矩阵的上 3x3 部分)转换为四元数,是 3D 旋转表示形式转换的核心方法。
    // 旋转矩阵通过线性变换描述旋转,而四元数更适合插值和避免万向锁问题,该方法实现了二者的精准转换。
    setFromRotationMatrix(m) {}

    // 计算从一个单位向量 vFrom 旋转到另一个单位向量 vTo 所需的四元数。
    // 该方法高效地将向量方向转换为旋转四元数,避免了欧拉角的万向锁问题,是 3D 方向控制的核心工具。
    setFromUnitVectors(vFrom, vTo) {}

    // 计算当前四元数与目标四元数 q 所表示的旋转之间的角度差(单位为弧度)。
    // 该方法高效地量化了两个旋转的差异,是 3D 旋转比较和过渡控制的重要工具。
    angleTo() {}

    // 将当前四元数向目标四元数 q 旋转指定的最大角度步长 step,确保不会超过目标旋转状态。
    // 通过多次调用 rotateTowards,可实现从当前旋转到目标旋转的平滑过渡,且每次旋转幅度不超过设定的 step。
    rotateTowards(q, step) {}

    // 将当前四元数设置为单位四元数(identity quaternion),即表示 "无旋转" 状态的四元数。
    // 该方法是快速将四元数重置为初始状态的便捷工具,确保旋转操作的基准状态一致。
    identity() {}

    // 计算当前四元数的逆。
    // 调用this.conjugate。
    invert() {}

    // 计算当前四元数的共轭(conjugate)。
    // 该方法是处理旋转反转的核心工具,在 3D 变换中用于实现旋转的抵消或反向操作。
    conjugate() {}

    // 计算当前四元数与另一个四元数 v 的点积(内积)。
    // 该方法是四元数运算的基础工具,为旋转角度计算、插值等高级操作提供核心支持。
    dot(v) {}

    // 计算四元数的平方长度。
    // 该方法是四元数运算中的基础工具,在需要高效处理长度相关计算时非常实用。
    lengthSq() {}

    // 计算四元数的欧氏长度(模长)。
    // 该方法是四元数运算的基础工具,为归一化、长度比较等操作提供核心支持。
    length() {}

    // 归一化(规范化)。
    // 该方法是保证四元数旋转正确性的核心工具,在 3D 变换中被频繁调用。
    normalize() {}

    // 将当前四元数与另一个四元数 q 相乘。
    // 该方法是四元数组合旋转的核心工具,简化了旋转叠加的操作流程。
    multiply(q) {}

    // 将当前四元数与另一个四元数 q 进行前置乘法运算(即 this = q * this)。
    // 该方法是四元数旋转组合中控制顺序的重要工具,与 multiply 方法互补,共同实现灵活的旋转叠加逻辑。
    premultiply(q) {}

    // 计算两个四元数 a 和 b 的乘积。
    // 该方法是四元数旋转组合的核心实现,为 3D 场景中复杂旋转的计算提供了基础。
    multiplyQuaternions(a, b) {}

    // 实现球面线性插值(Spherical Linear Interpolation)
    // 该方法是四元数实现平滑旋转过渡的核心,确保了旋转插值的视觉。自然性和数学正确性。
    slerp(qb, t) {}

    // 在两个四元数 qa(源)和 qb(目标)之间执行球面线性插值(SLERP)。
    // 该方法简化了插值操作的调用流程,是 slerp 方法的常用配套工具。
    slerpQuaternions(qa, qb, t) {}

    // 生成一个均匀分布的随机单位四元数,可表示三维空间中的随机旋转。
    // 该方法是生成均匀随机旋转的高效实现,广泛用于需要随机性的三维图形应用中。
    random() {}

    // 判断当前四元数与另一个四元数是否完全相等
    equals(quaternion) {}

    // 从数组中读取数据并设置当前四元数的分量值。
    // 该方法是数组与四元数之间数据转换的常用工具,方便在数据存储(如数组)和对象操作(如四元数方法调用)之间切换。
    fromArray(array, offset = 0) {}

    // 将四元数的四个分量(x、y、z、w)写入到数组中,方便数据的存储、传输或后续处理。
    // 该方法是四元数与数组之间数据转换的常用工具,与 fromArray 方法对应,实现了四元数对象和数组格式的双向转换。
    toArray(array = [], offset = 0) {}

    // 用于从 BufferAttribute 中读取数据并设置当前四元数的分量值。
    // 该方法是四元数与缓冲区数据交互的重要接口,尤其在性能敏感的场景中(如大规模渲染),通过缓冲区批量处理数据可提升效率。
    fromBufferAttribute(attribute, index) {}

    toJSON() {}

    _onChange(callback) {}

    _onChangeCallBack() {}

    *[ Symbol.iterator ]() {}
}

angleTo 函数

应用场景
  • 动画系统中判断两个旋转状态的差异,用于控制插值速度(如 rotateTowards 方法中根据角度差决定步长)。
  • 物理模拟中检测物体旋转是否达到目标状态,作为终止条件。
示例
js 复制代码
const q1 = new THREE.Quaternion();
const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.PI/4, 0, 0));
const angleDiff = q1.angleTo(q2); // 计算角度差(结果为 π/4 弧度,即 45°)
if (angleDiff < 0.1) { // 若角度差小于 0.1 弧度(约 5.7°),视为接近
  console.log("Rotations are similar");
}

rotateTowards 函数

应用场景
  • 角色朝向目标的平滑转动(如游戏中角色转向敌人)。
  • 相机视角的渐进式调整(如镜头缓慢对准目标)。
  • 动画系统中旋转状态的过渡控制。
示例
js 复制代码
const currentRot = new THREE.Quaternion(); // 初始旋转(无旋转)
const targetRot = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0)); // 目标旋转(绕Y轴转180度)
const step = Math.PI / 4; // 每次最大旋转90度(π/4弧度)

// 第一次调用:旋转90度(向目标靠近)
currentRot.rotateTowards(targetRot, step);

// 第二次调用:剩余角度为90度,再次旋转90度到达目标
currentRot.rotateTowards(targetRot, step);

identity 函数

应用场景
  • 初始化物体的旋转状态(默认无旋转)。
  • 重置物体的旋转(清除之前的旋转操作)。
示例
js 复制代码
const q = new THREE.Quaternion();
q.identity(); // 将 q 设置为单位四元数 (0, 0, 0, 1)
object.quaternion.copy(q); // 物体恢复无旋转状态

conjugate 函数

应用场景
  • 抵消旋转操作:若对物体应用了四元数 q 表示的旋转,再应用其共轭 conjugate(q) 可将物体恢复到原始姿态。
  • 坐标系转换:在层级变换中,用于计算父坐标系到子坐标系的反向旋转变换。
示例
js 复制代码
const q = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI/2); // 绕Y轴旋转90度
const qConj = q.clone().conjugate(); // 共轭四元数(绕Y轴旋转-90度)

object.quaternion.multiply(q);      // 物体旋转90度
object.quaternion.multiply(qConj);  // 物体旋转-90度(回到初始状态)

dot 函数

应用场景
  • 计算两个旋转之间的角度(如 angleTo 方法中使用 this.dot(q) 计算角度差)。
  • 球面线性插值(SLERP)中判断旋转方向,确保插值走最短路径(如 slerp 方法中根据点积符号决定是否取反目标四元数)。
  • 判断两个旋转是否相似(点积越接近 1,旋转越相似)。
示例
js 复制代码
const q1 = new THREE.Quaternion(); // 单位四元数(无旋转)
const q2 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI/2); // 绕Y轴旋转90度

const dotProduct = q1.dot(q2); 
// 计算结果为 cos(π/4) ≈ 0.707,对应旋转角度为 2 * arccos(0.707) = π/2(90度)

lengthSq 函数

应用场景
  • 性能优化:当只需比较两个四元数的长度大小(而非获取实际长度)时,使用 lengthSq 可避免昂贵的开平方运算,提升效率。
  • 辅助计算:作为 length 方法(计算实际长度)和 normalize 方法(归一化四元数)的内部依赖。

length 函数

应用场景
  • 检查四元数是否为单位四元数(若 length() 接近 1,则为单位四元数)。
  • 作为 normalize 方法的内部依赖(归一化时需用长度的倒数进行缩放)。
  • 计算两个四元数的实际距离(需结合向量距离公式)。

示例

js 复制代码
const q = new THREE.Quaternion(1, 0, 0, 0);
console.log(q.length()); // 输出 1(因为 1² + 0² + 0² + 0² = 1,平方根为 1)

const q2 = new THREE.Quaternion(1, 1, 1, 1);
console.log(q2.length()); // 输出 2(因为 1+1+1+1=4,平方根为 2)
q2.normalize(); // 归一化后长度为 1
与 lengthSq 方法的关系
  • lengthSq 方法返回平方和(x² + y² + z² + w²),避免了开平方运算,性能更高,适合仅需比较长度大小的场景。
  • length 方法则在 lengthSq 的基础上多了一步开平方,得到实际长度,适合需要精确长度值的场景(如归一化)。

normalize 函数

应用场景
  • 维持旋转有效性:旋转四元数必须是单位四元数,否则会导致物体缩放或变形。例如,多次旋转叠加后四元数可能偏离单位长度,需通过 normalize 修正。
  • 确保运算正确性:四元数的逆、点积、SLERP 插值等运算均依赖单位四元数的特性,归一化是这些操作的前置步骤。
  • 修复数值误差:浮点运算误差可能导致四元数模长偏离 1,normalize 可用于修正此类误差。
示例
js 复制代码
const q = new THREE.Quaternion(1, 1, 1, 1); // 初始模长为 2(非单位四元数)
q.normalize(); // 归一化后模长为 1,分量变为 (0.5, 0.5, 0.5, 0.5)

multiply 函数

应用场景
  • 组合多个旋转操作(例如,角色先抬头再转身,可通过四元数相乘得到最终姿态)。
  • 更新物体的累积旋转状态(如动画中逐帧叠加旋转)。
示例
js 复制代码
const q1 = new THREE.Quaternion().setFromAxisAngle(xAxis, 90); // 绕X轴旋转90度
const q2 = new THREE.Quaternion().setFromAxisAngle(yAxis, 90); // 绕Y轴旋转90度

q1.multiply(q2); // q1 变为 "先绕X轴转90度,再绕Y轴转90度" 的组合旋转
与 premultiply 的区别
  • multiply(q):计算 this = this * q(当前四元数在后,q 在前,即先应用 this 的旋转,再应用 q 的旋转)。
  • premultiply(q):计算 this = q * this(q 在后,当前四元数在前,即先应用 q 的旋转,再应用 this 的旋转)

premultiply 函数

应用场景
  • 调整旋转顺序:当需要先应用新的旋转(q),再叠加当前已有的旋转时,使用 premultiply。
  • 累积外部旋转:例如在层级变换中,父节点的旋转需要前置作用于子节点的旋转时。
示例
js 复制代码
const q1 = new THREE.Quaternion().setFromAxisAngle(xAxis, 90); // 绕X轴旋转90度
const q2 = new THREE.Quaternion().setFromAxisAngle(yAxis, 90); // 绕Y轴旋转90度

q1.premultiply(q2); // q1 变为 "先绕Y轴转90度,再绕X轴转90度" 的组合旋转

multiplyQuaternions 函数

应用场景
  • 组合多个旋转操作(例如,先绕 X 轴旋转,再绕 Y 轴旋转,通过四元数乘法得到最终旋转)。
  • 作为 multiplypremultiply 方法的底层实现(multiply(q) 调用 multiplyQuaternions(this, q)premultiply(q) 调用 multiplyQuaternions(q, this))。
示例
js 复制代码
const q1 = new THREE.Quaternion().setFromAxisAngle(xAxis, 90); // 绕X轴旋转90度
const q2 = new THREE.Quaternion().setFromAxisAngle(yAxis, 90); // 绕Y轴旋转90度
const result = new THREE.Quaternion();

result.multiplyQuaternions(q1, q2); // 计算 q1 * q2,得到组合旋转

slerp 函数

应用场景
  • 动画过渡:物体在两个姿态之间平滑旋转(如角色转身、相机视角切换)。
  • 关键帧插值:在关键帧动画中,基于时间因子生成中间帧的旋转状态。
  • 姿态平滑修正:如机器人关节旋转的平滑控制。
示例
js 复制代码
const qStart = new THREE.Quaternion().setFromAxisAngle(xAxis, 0); // 初始姿态(无旋转)
const qEnd = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI); // 目标姿态(绕X轴旋转180度)
const qResult = new THREE.Quaternion().copy(qStart);

qResult.slerp(qEnd, 0.5); // 插值到中间状态(绕X轴旋转90度)

slerpQuaternions 函数

应用场景
  • 当需要直接基于两个独立的四元数计算插值结果时,使用 slerpQuaternions 更简洁。
示例
js 复制代码
const qa = new THREE.Quaternion().setFromAxisAngle(xAxis, 0); // 初始姿态
const qb = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI); // 目标姿态
const result = new THREE.Quaternion();

result.slerpQuaternions(qa, qb, 0.5); // 直接计算 qa 到 qb 的中间插值(90度旋转)
与 slerp 方法的关系
  • slerp(qb, t):以当前四元数为起点,插值到 qb(需确保当前四元数已被正确初始化)。
  • slerpQuaternions(qa, qb, t):直接以 qa 为起点,插值到 qb(内部自动完成 "复制起点" 的步骤)。

random 函数

应用场景
  • 生成随机朝向的物体(如粒子系统中随机旋转的粒子)。
  • 蒙特卡洛模拟中需要随机旋转的场景(如光线追踪的随机反射方向)。
  • 测试旋转相关功能时生成随机测试用例。
示例
js 复制代码
const randomQuat = new THREE.Quaternion().random();
// randomQuat 是一个随机单位四元数,可用于旋转物体
mesh.quaternion.copy(randomQuat); // 物体应用随机旋转

fromArray 函数

应用场景
  • 当需要从数组中快速初始化或更新四元数时使用
示例
js 复制代码
const quat = new THREE.Quaternion();
const arr = [0.1, 0.2, 0.3, 0.9];
quat.fromArray(arr); // 从数组 arr 读取数据,quat 的分量变为 (0.1, 0.2, 0.3, 0.9)

// 带偏移量的使用
const arr2 = [10, 20, 0.4, 0.5, 0.6, 0.7];
quat.fromArray(arr2, 2); // 从 arr2[2] 开始读取,quat 的分量变为 (0.4, 0.5, 0.6, 0.7)

toArray 函数

应用场景
  • 将四元数数据转换为数组格式,用于存储(如序列化)、传递给需要数组作为输入的 API,或批量处理多个四元数数据。
示例
js 复制代码
const quat = new THREE.Quaternion(0.1, 0.2, 0.3, 0.9);

// 使用默认空数组
const arr1 = quat.toArray(); 
// arr1 结果:[0.1, 0.2, 0.3, 0.9]

// 传入已有数组和偏移量
const arr2 = [10, 20, 30, 40, 50];
quat.toArray(arr2, 1); 
// arr2 结果:[10, 0.1, 0.2, 0.3, 0.9](从索引1开始写入)

fromBufferAttribute 函数

应用场景
  • 在处理批量数据(如粒子系统、骨骼动画等)时,BufferAttribute 通常用于高效存储大量四元数数据。fromBufferAttribute 方法提供了从这类缓冲区中快速初始化或更新单个四元数的途径。
示例
js 复制代码
// 假设 bufferAttribute 存储了多个四元数数据
const quat = new THREE.Quaternion();
quat.fromBufferAttribute(bufferAttribute, 5); // 读取第5个索引处的四元数数据

*[ Symbol.iterator ]迭代器方法

通过 ES6 的 Symbol.iterator 实现,使得四元数实例可以被迭代(即可用在 for...of 循环、扩展运算符等场景中)

js 复制代码
*[ Symbol.iterator ]() {

    yield this._x;
    yield this._y;
    yield this._z;
    yield this._w;

}
作用与使用场景

通过实现迭代器,Quaternion 实例可以像数组一样被迭代或解构,例如:

js 复制代码
const quat = new Quaternion(1, 2, 3, 4);

// 使用 for...of 循环迭代
for (const component of quat) {
  console.log(component); // 依次输出 1, 2, 3, 4
}

// 解构赋值
const [x, y, z, w] = quat;
console.log(x, y, z, w); // 输出 1 2 3 4

// 转换为数组
const arr = [...quat];
console.log(arr); // 输出 [1, 2, 3, 4]

这种设计增强了 API 的灵活性,让四元数的分量获取更简洁,尤其在需要批量处理分量时(如转换格式、传递参数等)非常方便。

相关推荐
小猪努力学前端3 天前
基于PixiJS的小游戏广告开发
前端·webgl·游戏开发
光影少年4 天前
WebGIS 和GIS学习路线图
学习·前端框架·webgl
DBBH4 天前
Cesium源码分析之渲染3DTile的一点思考
图形渲染·webgl·cesium.js
Robet5 天前
TS2d渲染引擎
webgl
Robet5 天前
WebGL2D渲染引擎
webgl
goodName6 天前
如何实现精准操控?Cesium模型移动旋转控件实现
webgl·cesium
一千柯橘7 天前
从摄影新手到三维光影师:Three.js 核心要素的故事
前端·three.js
big男孩8 天前
OrbitControls 的完整原理
three.js
丫丫7237349 天前
Three.js 模型树结构与节点查询学习笔记
javascript·webgl
答案answer9 天前
一些经典的3D编辑器开源项目
前端·开源·three.js