Skip to content

EulerAngles

欧拉角使用heading-pitch-bank约定,没有实现加、减、标量乘等运算,如果保存的不是方位而是角速度或变化率时才会需要用到这些

ts
import { kPi, kPiOver2, wrapPi } from './MathUtil';
import { Quaternion } from './Quaternion';
import Matrix4x3 from './Matrix4x3';
import { RotationMatrix } from './RotationMatrix';

export class EulerAngles {
  heading: number;
  pitch: number;
  bank: number;

  constructor(heading: number, pitch: number, bank: number) {
    this.heading = heading;
    this.pitch = pitch;
    this.bank = bank;
  }

  //   置零
  identity() {
    this.heading = this.pitch = this.bank = 0;
  }

  //   变为“限制集”欧拉角,角度约定在规定范围内
  canonize() {
    this.pitch = wrapPi(this.pitch);

    // 将pitch 变换到-pi/2到pi/2之间
    if (this.pitch < -kPiOver2) {
      this.pitch = -kPi - this.pitch;
      this.heading += kPi;
      this.bank += kPi;
    } else if (this.pitch > kPiOver2) {
      this.pitch = kPi - this.pitch;
      this.heading += kPi;
      this.bank += kPi;
    }

    //   现在检查万向锁,并且允许存在一定的误差
    if (Math.abs(this.pitch) > kPiOver2 - 1e-4) {
      // 万向锁,将所有绕垂直轴的旋转赋给heading
      this.heading += this.bank;
      this.bank = 0;
    } else {
      // 非万向锁,将bank转换到限制集中
      this.bank = wrapPi(this.bank);
    }

    // 将heading转换到限制集中
    this.heading = wrapPi(this.heading);
  }

  //   四元数、欧拉角之间转换
  //   输入的四元数假设为物体-惯性或惯性-物体四元数
  fromObjectToInertialQuaternion(q: Quaternion) {
    const sp = -2 * (q.y * q.z - q.w * q.x);
    // 检查万向锁,允许误差
    if (Math.abs(sp) > 0.9999) {
      // 从正上方或正下方看
      this.pitch = kPiOver2 * sp;
      // bank置零,计算heading
      this.heading = Math.atan2(-q.x * q.z + q.w + q.y, 0.5 - q.y * q.y - q.z * q.z);
      this.bank = 0;
    } else {
      this.pitch = Math.asin(sp);
      this.heading = Math.atan2(q.x * q.z + q.w * q.y, 0.5 - q.x * q.x - q.y * q.y);
      this.bank = Math.atan2(q.x * q.y + q.w * q.z, 0.5 - q.x * q.x - q.z * q.z);
    }
  }

  // 从惯性-物体四元数到欧拉角
  fromInertialQuaternionToObject(q: Quaternion) {
    const sp = -2 * (q.y * q.z + q.w * q.x);
    // 检查万向锁,允许误差
    if (Math.abs(sp) > 0.9999) {
      // 从正上方或正下方看
      this.pitch = kPiOver2 * sp;
      this.heading = Math.atan2(-q.x * q.z - q.w * q.y, 0.5 - q.y * q.y - q.z * q.z);
      this.bank = 0;
    } else {
      this.pitch = Math.asin(sp);
      this.heading = Math.atan2(q.x * q.z - q.w * q.y, 0.5 - q.x * q.x - q.y * q.y);
      this.bank = Math.atan2(q.x * q.y - q.w * q.z, 0.5 - q.x * q.x - q.z * q.z);
    }
  }

  //   矩阵、欧拉角之间转换
  //   输入的矩阵假设为物体-世界或世界-物体转换矩阵
  //   平移部分被省略,并且假设矩阵是正交的
  fromObjectToWorldMatrix(m: Matrix4x3) {
    const sp = -m.m32;

    // 检查万向锁,允许误差
    if (Math.abs(sp) > 9.99999) {
      // 从正上方或正下方看
      this.pitch = kPiOver2 * sp;
      this.heading = Math.atan2(-m.m23, m.m11);
      this.bank = 0;
    } else {
      // 计算角度
      this.heading = Math.atan2(m.m31, m.m33);
      this.pitch = Math.asin(sp);
      this.bank = Math.atan2(m.m12, m.m22);
    }
  }

  /**
   * 从世界-物体坐标系变换矩阵到欧拉角
   *
   * 假设矩阵是正交的,忽略平移部分
   * @param m
   */
  fromWorldToObjectMatrix(m: Matrix4x3) {
    const sp = -m.m32;

    // 检查万向锁,允许误差
    if (Math.abs(sp) > 9.99999) {
      // 从正上方或正下方看
      this.pitch = kPiOver2 * sp;
      this.heading = Math.atan2(-m.m31, m.m11);
      this.bank = 0;
    } else {
      // 计算角度
      this.heading = Math.atan2(m.m13, m.m33);
      this.pitch = Math.asin(sp);
      this.bank = Math.atan2(m.m21, m.m22);
    }
  }

  //    从旋转矩阵转换到欧拉角
  fromRotationMatrix(m: RotationMatrix) {
    const sp = -m.m32;

    if (Math.abs(sp) > 9.99999) {
      this.pitch = kPiOver2 * sp;
      this.heading = Math.atan2(-m.m31, m.m11);
      this.bank = 0;
    } else {
      this.heading = Math.atan2(m.m13, m.m33);
      this.pitch = Math.asin(sp);
      this.bank = Math.atan2(m.m21, m.m22);
    }
  }
}

// 全局"单位"欧拉角
export const kRulerAnglesIdentity: EulerAngles = new EulerAngles(0, 0, 0);