破解旋转死锁:Threejs 四元数魔法对抗欧拉角困局

欧拉角

欧拉角是一种表示三维空间中旋转的方法,它由三个角度组成,通过设定物体绕 指定顺序 的轴进行旋转,可以直接对物体的 .rotation 属性进行操作。

rotation 属性是一个欧拉角对象,表示物体的旋转角度。欧拉角由三个角度组成,分别是绕 x 轴的旋转角度、绕 y 轴的旋转角度和绕 z 轴的旋转角度。

js 复制代码
// 创建一个立方体
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(cube);

let euler = new THREE.Euler(Math.PI / 2, 0, 0);
cube.rotation.copy(euler);

console.log(cube.rotation);

Euler(x, y, z, order): 创建一个欧拉角对象,其中 x、y、z 分别表示绕 x 轴、y 轴和 z 轴的旋转角度,order 表示旋转顺序。

换算: 欧拉角不能直接使用度数,需要把度数转换为弧度值,弧度 = (Math.PI / 180) * 度数。

简洁写法: cube.rotation.set(x, y, z)。

注意: 欧拉角不能直接赋值,需要使用 .copy() 方法进行赋值。

js 复制代码
// 创建一个立方体
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(cube);

let angle = 0;
function animate() {
  angle += 1;
  cube.rotation.x = (angle * Math.PI) / 180;
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

可以使用 THREE.Euler() 、 .set() 来统一操作 .rotation,也可以通过 .x 、 .y 、 .z 来分别操作。

方法

  • rotateX(angle): 绕 x 轴旋转 angle 弧度。

  • rotateY(angle): 绕 y 轴旋转 angle 弧度。

  • rotateZ(angle): 绕 z 轴旋转 angle 弧度。

区别: rotateX() 、 rotateY() 、 rotateZ() 多次使用,会叠加旋转角度,而 set() 每次使用,都会覆盖旋转角度。

四元数

四元数是一种表示三维空间中旋转的方法,它由四个部分组成,分别是 w、x、y、z。其中 w 是实部,x、y、z 是虚部。

为什么使用四元数: 欧拉角在旋转过程中会出现万向节死锁问题,而四元数不会。

万向节死锁

原理: 当其中两个旋转轴重合时,就会发生万向节死锁。这会致使失去一个自由度,进而难以预测和控制物体的旋转。在欧拉角的表示里,通常是当绕其中一个轴旋转 ±90 度时,另外两个轴会重合,从而出现万向节死锁。

例如: 假设一个物体绕 Y 轴旋转 90 度,此时 X 轴和 Z 轴就会重合。这时候,不管是绕 X 轴旋转还是绕 Z 轴旋转,产生的效果是一样的,这就意味着失去了一个自由度,这种现象就是万向节死锁。

js 复制代码
//万向节死锁示例
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(cube);

//y轴旋转90度
cube.rotation.y = Math.PI / 2;

let angle = 0;
function animate() {
  angle += 1;
  cube.rotation.x = (angle * Math.PI) / 180;
  cube.rotation.z = (angle * Math.PI) / 180;
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

quaternion

在四元数中是通过操作quaternion来实现的,其中quaternion是一个四元数对象,表示物体的旋转。

quaternion 属性是一个四元数对象,表示物体的旋转。四元数由四个部分组成,分别是 w、x、y、z。其中 w 是实部,x、y、z 是虚部。

1. 创建四元数

js 复制代码
const quaternion = new THREE.Quaternion(x, y, z, w);

其中 x、y、z、w 分别是四元数的分量。如果不传入参数,则默认为 (0, 0, 0, 1)。

2. 四元数计算公式

q = (x, y, z, w) = (sin(θ/2) * ux , sin(θ/2) * uy , sin(θ/2) * uz, cos(θ/2))

js 复制代码
const angle = (30 * Math.PI) / 180; // 将角度转换为弧度
const halfAngle = angle / 2;
const sinHalfAngle = Math.sin(halfAngle);
const cosHalfAngle = Math.cos(halfAngle);
const quaternion = new THREE.Quaternion(
  sinHalfAngle * 1, //解释
  sinHalfAngle * 0,
  sinHalfAngle * 0,
  cosHalfAngle
);

绕着哪个轴旋转,哪个轴的分量就为 1,其他轴的分量就为 0。

3. 设置四元数

js 复制代码
cube.quaternion.copy(quaternion);

简洁写法: setFromAxisAngle(axis, angle) 从轴和角度创建四元数。

1. axis: 表示旋转轴的向量,例如 new THREE.Vector3(1, 0, 0) 表示绕 x 轴旋转。

2. angle: 表示旋转角度,单位是弧度。

js 复制代码
const quaternion = new THREE.Quaternion();
const axis = new THREE.Vector3(1, 0, 0); // 绕 x 轴旋转
const angle = (Math.PI / 180) * 90; // 旋转 90 度
quaternion.setFromAxisAngle(axis, angle);

方法

  • setFromEuler(euler): 从欧拉角创建四元数。

  • multiply(quaternion): 将当前四元数与另一个四元数相乘,会改变当前四元数。

  • multiplyQuaternions(a, b): 将两个四元数相乘,返回一个新的四元数。

解决死锁

js 复制代码
// 解决万向节死锁示例
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
scene.add(cube);

//y轴旋转90度
cube.quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);

function animate() {
  // 创建一个四元数qx
  const qx = new THREE.Quaternion();
  // 将qx设置为绕x轴旋转0.01弧度的四元数
  qx.setFromAxisAngle(new THREE.Vector3(1, 0, 0), 0.01);
  // 将cube的旋转四元数与qx相乘
  cube.quaternion.multiplyQuaternions(qx, cube.quaternion);
  // 创建一个四元数qz
  const qz = new THREE.Quaternion();
  // 将qz设置为绕z轴旋转0.01弧度的四元数
  qz.setFromAxisAngle(new THREE.Vector3(0, 0, 1), 0.01);
  // 将cube的旋转四元数与qz相乘
  cube.quaternion.multiplyQuaternions(qz, cube.quaternion);
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

书洞笔记

相关推荐
jktantan9 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
前端·javascript·vue.js
总要学点什么10 小时前
第89篇:三维特效-螺旋圆效果——让螺旋臂卷起一圈圈外扩的涟漪
开发语言·javascript·ecmascript·webgl·cesium·空间分析·1024程序员节
2601_9628857210 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
excel11 小时前
encodeURI 和 encodeURIComponent 有什么区别?
前端
前端精髓11 小时前
调用openrouter的免费API模型
java·前端·javascript
赛博守夜人11 小时前
AI安全攻防与治理之十四:AI 安全测试不能脱离传统安全,Web、API、移动端与模型攻击面的组合拳
前端·人工智能·安全
开开心心_Every11 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
networknomad12 小时前
OpenReplay:自己搭的会话回放工具,给前端排错用的
前端·其他
缓慢更新13 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
陈随易13 小时前
AI 运维服务器,安全又可靠的方法
前端·后端·程序员