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 的灵活性,让四元数的分量获取更简洁,尤其在需要批量处理分量时(如转换格式、传递参数等)非常方便。

相关推荐
sixgod_h5 小时前
Threejs源码系列- MathUtils(2)
前端·webgl
sixgod_h1 天前
Threejs源码系列- MathUtils(1)
前端·webgl
入秋1 天前
2025年项目中是怎么初始化Three.js三维场景的
前端·three.js
烛阴2 天前
Dot
前端·webgl
刘皇叔code2 天前
PBR学习笔记与Three.js中MeshPhysicalMaterial源码阅读
webgl·three.js
答案answer2 天前
1K+star,回顾一下我的开源之路
前端·程序员·three.js
烛阴3 天前
Vector Normaliztion -- 向量归一化
前端·webgl
allenjiao3 天前
Cesium粒子系统模拟风场动态效果
javascript·arcgis·gis·webgl·cesium·三维·风场
康康的幸福生活3 天前
webgl2 方法解析: uniformBlockBinding()
前端·javascript·webgl