您的位置:

欧拉角和四元数的介绍及应用

一、欧拉角

欧拉角是用于描述物体在三维空间中旋转的方法之一。它由三个连续旋转组成,分别是绕x轴旋转角度φ、绕y轴旋转角度θ和绕z轴旋转角度ψ,其旋转顺序可以是xyz、xzy、yxz、yzx、zxy、zyx等。

欧拉角的优点是直观易懂,可以通过简单的三个角度来描述旋转情况。但缺点也是很明显的,就是在某些情况下会出现万向锁问题。另外,由于欧拉角旋转的过程是连续的,一旦多次旋转累积起来,会导致误差的积累。

//欧拉角转为旋转矩阵
function euler2mat(phi, theta, psi) {
  const cosPhi = Math.cos(phi);
  const sinPhi = Math.sin(phi);
  const cosTheta = Math.cos(theta);
  const sinTheta = Math.sin(theta);
  const cosPsi = Math.cos(psi);
  const sinPsi = Math.sin(psi);
  const mat = [
    [cosTheta * cosPsi, -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi, sinPhi * sinPsi + cosPhi * sinTheta * cosPsi],
    [cosTheta * sinPsi, cosPhi * cosPsi + sinPhi * sinTheta * sinPsi, -sinPhi * cosPsi + cosPhi * sinTheta * sinPsi],
    [-sinTheta, sinPhi * cosTheta, cosPhi * cosTheta]
  ];
  return mat;
}

二、四元数

四元数是一种扩展了复数的数学结构,它由一个实部和三个虚部构成。可以用于表示旋转操作和空间旋转中的旋转向量,同时解决了欧拉角的两个问题,即避免万向锁和误差积累。

四元数的乘法可以看作是两个旋转的合成,因此可以很方便地进行多次旋转的叠加。而且只需要4个数就可以表示旋转状态,比欧拉角更加紧凑和高效。

//四元数旋转矩阵转欧拉角
function mat2euler(mat) {
  const theta1 = -Math.asin(mat[2][0]);
  const cosTheta1 = Math.cos(theta1);
  const phi1 = Math.atan2(mat[2][1] / cosTheta1, mat[2][2] / cosTheta1);
  const psi1 = Math.atan2(mat[1][0] / cosTheta1, mat[0][0] / cosTheta1);
  return [phi1, theta1, psi1];
}

//四元数类
class Quaternion {
  constructor(w, x, y, z) {
    this.w = w || 1.0;
    this.x = x || 0.0;
    this.y = y || 0.0;
    this.z = z || 0.0;
  }
  normalize() {
    const norm = Math.sqrt(this.w ** 2 + this.x ** 2 + this.y ** 2 + this.z ** 2);
    if (norm === 0) return;
    this.w /= norm;
    this.x /= norm;
    this.y /= norm;
    this.z /= norm;
  }
  multiply(q) {
    const w1 = this.w,
      x1 = this.x,
      y1 = this.y,
      z1 = this.z;
    const w2 = q.w,
      x2 = q.x,
      y2 = q.y,
      z2 = q.z;
    this.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
    this.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
    this.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
    this.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
    this.normalize();
  }
}

三、应用实例

在游戏开发中,我们通常会使用三维渲染引擎来展现一个三维场景。在这个场景中,有很多需要旋转的物体,比如角色、相机和物体等。下面我们以相机为例,展示欧拉角和四元数在相机旋转中的应用。

1、欧拉角控制相机旋转

在WebGL中,我们可以使用欧拉角来控制相机的旋转。假设我们有一个相机对象,可以通过鼠标控制其绕x和y轴的旋转角度,代码如下:

class Camera {
  constructor() {
    this.position = [0, 0, 5];
    this.target = [0, 0, 0];
    this.up = [0, 1, 0];
    this.dist = Math.sqrt(this.position[0] ** 2 + this.position[1] ** 2 + this.position[2] ** 2);
    this.phi = Math.atan2(this.position[1], this.position[0]);
    this.theta = Math.acos(this.position[2] / this.dist);
  }
  rotate(deltaPhi, deltaTheta) {
    this.phi += deltaPhi;
    this.theta += deltaTheta;
    if (this.theta < 0.01) this.theta = 0.01;
    if (this.theta > Math.PI - 0.01) this.theta = Math.PI - 0.01;

    const x = this.dist * Math.sin(this.theta) * Math.cos(this.phi);
    const y = this.dist * Math.sin(this.theta) * Math.sin(this.phi);
    const z = this.dist * Math.cos(this.theta);

    this.position = [x, y, z];
  }
  getViewMatrix() {
    const mat = euler2mat(this.theta, this.phi, 0);
    const zAxis = [mat[0][2], mat[1][2], mat[2][2]];
    const xAxis = [mat[0][0], mat[1][0], mat[2][0]];
    const yAxis = [mat[0][1], mat[1][1], mat[2][1]];

    const tx = -xAxis[0] * this.position[0] - xAxis[1] * this.position[1] - xAxis[2] * this.position[2];
    const ty = -yAxis[0] * this.position[0] - yAxis[1] * this.position[1] - yAxis[2] * this.position[2];
    const tz = -zAxis[0] * this.position[0] - zAxis[1] * this.position[1] - zAxis[2] * this.position[2];

    return [
      xAxis[0], yAxis[0], zAxis[0], 0,
      xAxis[1], yAxis[1], zAxis[1], 0,
      xAxis[2], yAxis[2], zAxis[2], 0,
      tx, ty, tz, 1
    ];
  }
}

2、四元数控制相机旋转

除了使用欧拉角之外,我们还可以使用四元数来控制相机的旋转。这里假设我们有一个相机对象和一个旋转四元数q,可以通过鼠标操作旋转四元数,然后计算出相机的旋转矩阵,代码如下:

class Camera {
  constructor() {
    this.position = [0, 0, 5];
    this.target = [0, 0, 0];
    this.up = [0, 1, 0];
    this.dist = Math.sqrt(this.position[0] ** 2 + this.position[1] ** 2 + this.position[2] ** 2);
    this.phi = Math.atan2(this.position[1], this.position[0]);
    this.theta = Math.acos(this.position[2] / this.dist);
    this.quaternion = new Quaternion();
  }
  rotate(deltaPhi, deltaTheta) {
    this.phi += deltaPhi;
    this.theta += deltaTheta;
    if (this.theta < 0.01) this.theta = 0.01;
    if (this.theta > Math.PI - 0.01) this.theta = Math.PI - 0.01;

    const x = this.dist * Math.sin(this.theta) * Math.cos(this.phi);
    const y = this.dist * Math.sin(this.theta) * Math.sin(this.phi);
    const z = this.dist * Math.cos(this.theta);

    this.position = [x, y, z];
    const mat = euler2mat(this.theta, this.phi, 0);
    this.quaternion = new Quaternion().fromMat(mat);
  }
  getViewMatrix() {
    const mat = this.quaternion.toMat();
    const zAxis = [mat[0][2], mat[1][2], mat[2][2]];
    const xAxis = [mat[0][0], mat[1][0], mat[2][0]];
    const yAxis = [mat[0][1], mat[1][1], mat[2][1]];

    const tx = -xAxis[0] * this.position[0] - xAxis[1] * this.position[1] - xAxis[2] * this.position[2];
    const ty = -yAxis[0] * this.position[0] - yAxis[1] * this.position[1] - yAxis[2] * this.position[2];
    const tz = -zAxis[0] * this.position[0] - zAxis[1] * this.position[1] - zAxis[2] * this.position[2];

    return [
      xAxis[0], yAxis[0], zAxis[0], 0,
      xAxis[1], yAxis[1], zAxis[1], 0,
      xAxis[2], yAxis[2], zAxis[2], 0,
      tx, ty, tz, 1
    ];
  }
}

class Quaternion {
  constructor(w, x, y, z) {
    this.w = w || 1.0;
    this.x = x || 0.0;
    this.y = y || 0.0;
    this.z = z || 0.0;
  }
  normalize() {
    const norm = Math.sqrt(this.w ** 2 + this.x ** 2 + this.y ** 2 + this.z ** 2);
    if (norm === 0) return;
    this.w /= norm;
    this.x /= norm;
    this.y /= norm;
    this.z /= norm;
  }
  multiply(q) {
    const w1 = this.w,
      x1 = this.x,
      y1 = this.y,
      z1 = this.z;
    const w2 = q.w,
      x2 = q.x,
      y2 = q.y,
      z2 = q.z;
    this.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
    this.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
    this.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
    this.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
    this.normalize();
  }
  fromMat(mat) {
    const trace = mat[0][0] + mat[1][1] + mat[2][2];
    if (trace >= 0) {
      const s = Math.sqrt(trace + 1) * 2;
      const w = 0.25 * s;
      const x = (mat[2][1] - mat[1][2]) / s;
      const y = (mat[0][2] - mat[2][0]) / s;
      const z = (mat[1][0] - mat[0][1]) / s;
      this.w = w;
      this.x = x;
      this.y = y;
      this.z = z;
    } else if (mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) {
      const s = Math.sqrt(1 + mat[0][0] - mat[1][1] - mat[2][2]) * 2;
      const w = (mat[2][1] - mat[1][2]) / s;
      const x = 0.25 * s;
      const y = (mat[0][1] + mat[1][0]) / s;
      const z = (mat[0][2] + mat[2][0]) / s;
      this.w = w;
      this.x = x;
      this.y = y;
      this.z = z;
    } else if            
欧拉角和四元数的介绍及应用

2023-05-24
欧拉角和四元数的区别

2023-05-20
四元数与欧拉角关系

2022-11-23
四元数转欧拉角详解

2023-05-17
欧拉角转换旋转矩阵

2023-05-17
深入探究单位四元数

2023-05-22
欧拉定理证明详解

2023-05-20
cosx+jsinx的简单介绍

本文目录一览: 1、sinx和e指数的关系 2、虚函数含有能否求导或积分?如jsinx,j cosx等?? 3、如何快速记住三角函数的格种公式 4、后边两步完全看不懂,求高手解答 5、sinx和cos

2023-12-08
数据库的笔记mysql,数据库管理系统笔记

2022-11-24
js待办事项列表添加删除代码的简单介绍

本文目录一览: 1、“点击此处可添加笔记”的代码怎么写 2、js动态添加、删除html代码 3、vivo手机便签怎么一起删 “点击此处可添加笔记”的代码怎么写 输入符号就可以了第一步打开手机,点击备忘

2023-12-08
python使用笔记23的简单介绍

2022-11-10
全能笔记应用Leanote在Docker环境下的部署和使用

2023-05-20
英语四级在线练习系统php的简单介绍

2022-11-09
cosxjsinx的模的简单介绍

本文目录一览: 1、e^jx的无穷次方为有限值吗?为什么e^jx/3整体的无穷次方为0?x为常数 2、e的jwt次方的模为什么是1,j是复数,w是角速度,t是时间,电路相量法中 3、后边两步完全看不懂

2023-12-08
postman上传文件和json参数的简单介绍

本文目录一览: 1、postman怎么发送json参数 2、postman如何导入json文件 3、json和普通字段怎么一起传 4、postman 怎么提交json数据 5、postman上传文件应

2023-12-08
java笔记,大学java笔记

2022-11-28
印象笔记记录java学习(Java成长笔记)

2022-11-12
java客户端学习笔记(java开发笔记)

2022-11-14
c到c语言笔记的简单介绍

2022-11-24
java学习笔记(java初学笔记)

2022-11-14