core API

本页按用途分组列出包 Luna-Flow/quaternion(源码根目录 src)的全部公开项。签名以生成的接口文件为准。示例以默认别名 @quaternion 导入本包,并以 @lf_alg 导入 luna-generic:

import {
  "Luna-Flow/quaternion",
  "Luna-Flow/luna-generic" @lf_alg,
  "moonbitlang/core/math",
}

各运算背后的数学推导见 core 设计;core 教程则演示常见任务。

四元数类型

Quaternion

Quaternion[T] 是分量类型为 T 的四元数 w+xi+yj+zkw + xi + yj + zk。

type Quaternion[T] derive(Eq, Hash, @debug.Debug)

一个值由标量(实)部 ww 与向量部 (x,y,z)(x, y, z) 组成。该类型是抽象类型:请用 Quaternion::from_vec、id、Quaternion::zero、Quaternion::one、from_axis_angle 或 from_euler 构造值,并通过 Quaternion::dot、Quaternion::map、to_string 或 Debug 读取。本类型没有分量访问器(参见读取分量)。

类型本身不约束 T。每个函数只要求它用到的 trait:加法需要 Add,Hamilton 积需要 Mul + Sub + Add,luna-generic 代数结构需要 @lf_alg.Ring,凡涉及平方根或三角函数的都需要 DoubleConvert。

派生的 Eq 与 Hash 对分量做精确比较。Debug 显示存储的字段:

test "Quaternion debug form" {
  let q = @quaternion.Quaternion::from_vec((1.0, -2.0, 3.0, 4.5))
  debug_inspect(q, content="{ r: 1, vec: (-2, 3, 4.5) }")
  assert_eq(q, @quaternion.Quaternion::from_vec((1.0, -2.0, 3.0, 4.5)))
}

构造

Quaternion::from_vec

Quaternion::from_vec((w, x, y, z)) 构造 w+xi+yj+zkw + xi + yj + zk。

pub fn[T] Quaternion::from_vec((T, T, T, T)) -> Self[T]

元组的第一个元素是标量部。该函数不要求任何 trait,因此适用于任意分量类型。

test "Quaternion::from_vec" {
  let q = @quaternion.Quaternion::from_vec((1, 2, 3, 4))
  inspect(q, content="1 + 2i + 3j + 4k")
}

id

id() 返回单位元 1+0i+0j+0k1 + 0i + 0j + 0k。

pub fn[T : @luna-generic.Num] id() -> Quaternion[T]

它与 Quaternion::one() 相等,但要求 Num,而 luna-generic 的每个数值类型(Int、Int64、Double 等)都实现了 Num。作为旋转,它是恒等旋转。

Quaternion::zero

Quaternion::zero() 返回加法单位元 0+0i+0j+0k0 + 0i + 0j + 0k。

pub fn[T : @luna-generic.Zero] Quaternion::zero() -> Self[T]

Quaternion::one

Quaternion::one() 返回乘法单位元 1+0i+0j+0k1 + 0i + 0j + 0k。

pub fn[T : @luna-generic.Zero + @luna-generic.One] Quaternion::one() -> Self[T]

zero 与 one 是被提升为静态方法的 luna-generic Zero、One 方法。它们所需的 trait 比 id 少:

test "Quaternion::zero and one" {
  let z : @quaternion.Quaternion[Int] = @quaternion.Quaternion::zero()
  let o : @quaternion.Quaternion[Int] = @quaternion.Quaternion::one()
  inspect(z, content="0 + 0i + 0j + 0k")
  inspect(o, content="1 + 0i + 0j + 0k")
  assert_eq(o, @quaternion.id())
}

from_axis_angle

from_axis_angle(axis, angle) 返回绕 axis 旋转 angle 弧度所对应的单位四元数。

pub fn[T : DoubleConvert + Mul + Add + Div] from_axis_angle((T, T, T), T) -> Quaternion[T]

结果为 cos⁡θ2+sin⁡θ2 n^\cos\frac{\theta}{2} + \sin\frac{\theta}{2}\,\hat n,其中 n^=axis/∣axis∣\hat n = \text{axis}/|\text{axis}|。轴不必归一化,但不能为零:零轴会导致除以零,对 Double 得到 NaN 分量。沿轴向原点看去,旋转为逆时针方向(右手定则)。正弦、余弦与轴长都在 Double 中计算,因此 T 需要 DoubleConvert。

test "from_axis_angle" {
  let q = @quaternion.from_axis_angle((0.0, 0.0, 2.0), @math.PI / 2.0)
  inspect(q, content="0.7071067811865476 + 0i + 0j + 0.7071067811865475k")
}

from_euler

from_euler(roll, pitch, yaw, order~) 由三个以弧度表示的角构造四元数。

pub fn[T : DoubleConvert + Mul + Add + Sub] from_euler(T, T, T, order? : String) -> Quaternion[T]

order 默认为 "XYZ"。记 qX(α)q_X(\alpha)、qY(α)q_Y(\alpha)、qZ(α)q_Z(\alpha) 为绕各坐标轴旋转 α\alpha。可接受的顺序计算如下:

order结果含义
"XYZ"(默认)qX(roll) qY(pitch) qZ(yaw)q_X(\text{roll})\,q_Y(\text{pitch})\,q_Z(\text{yaw})内旋 X-Y-Z,等同于外旋 Z-Y-X
"YXZ"qY(pitch) qX(roll) qZ(yaw)q_Y(\text{pitch})\,q_X(\text{roll})\,q_Z(\text{yaw})内旋 Y-X-Z
"ZXY"qZ(roll) qX(pitch) qY(yaw)q_Z(\text{roll})\,q_X(\text{pitch})\,q_Y(\text{yaw})内旋 Z-X-Y,角度按位置取用
"ZYX", "YZX"见下方警告

其他字符串会以 order error. Please check input format and retry 中止。

test "from_euler" {
  let q = @quaternion.from_euler(0.1, 0.2, 0.3)
  let qx = @quaternion.from_axis_angle((1.0, 0.0, 0.0), 0.1)
  let qy = @quaternion.from_axis_angle((0.0, 1.0, 0.0), 0.2)
  let qz = @quaternion.from_axis_angle((0.0, 0.0, 1.0), 0.3)
  assert_true((q - qx * qy * qz).square_len() < 1.0e-30)
}

Quaternion::default(已弃用的方法形式)

Default::default() 返回 id();其方法形式已弃用,参见已弃用。

算术

Quaternion::add

q + r 按分量相加。

pub fn[T : Add] Quaternion::add(Self[T], Self[T]) -> Self[T]

Quaternion::sub

q - r 按分量相减,计算方式为 q + -r。

pub fn[T : Neg + Add] Quaternion::sub(Self[T], Self[T]) -> Self[T]

Quaternion::neg

-q 对每个分量取负。

pub fn[T : Neg] Quaternion::neg(Self[T]) -> Self[T]

Quaternion::mul

q * r 是 Hamilton 积。

pub fn[T : Mul + Sub + Add] Quaternion::mul(Self[T], Self[T]) -> Self[T]

写成标量–向量形式为 (s1,v1)(s2,v2)=(s1s2−v1⋅v2, s1v2+s2v1+v1×v2)(s_1, \mathbf v_1)(s_2, \mathbf v_2) = (s_1 s_2 - \mathbf v_1\cdot\mathbf v_2,\ s_1\mathbf v_2 + s_2\mathbf v_1 + \mathbf v_1\times\mathbf v_2)。该乘积满足结合律并对 + 满足分配律,但不满足交换律:qr−rq=2 vq×vrqr - rq = 2\,\mathbf v_q \times \mathbf v_r。对旋转而言,q * r 先施加 r,再施加 q。

这四个运算符也可以作为方法调用(q.add(r)、q.mul(r) 等):

test "operators" {
  let q = @quaternion.Quaternion::from_vec((1, 2, 3, 4))
  let r = @quaternion.Quaternion::from_vec((5, 6, 7, 8))
  inspect(q + r, content="6 + 8i + 10j + 12k")
  inspect(q - r, content="-4 + -4i + -4j + -4k")
  inspect(-q, content="-1 + -2i + -3j + -4k")
  inspect(q * r, content="-60 + 12i + 30j + 24k")
  inspect(r * q, content="-60 + 20i + 14j + 32k")
  assert_eq(q.mul(r), q * r)
}

Quaternion::div

q / r 是右除:q r−1q\,r^{-1},即方程 x r=qx\,r = q 的解 xx。

pub fn[T : Add + Mul + Div + Sub] Quaternion::div(Self[T], Self[T]) -> Self[T]

它计算 q rˉ/∣r∣2q\,\bar r / |r|^2,每个分量只做一次除法,不构造中间的逆元。除以零四元数时会在 T 中除以零:Double 得到 NaN 分量,Int 则触发运行时陷阱。对 Int,每个分量都会被整数除法截断,因此只有在除法恰好整除时结果才有意义。

Quaternion::left_div

q.left_div(r) 是左除:r−1qr^{-1} q,即方程 r x=qr\,x = q 的解 xx。

pub fn[T : Add + Mul + Div + Sub] Quaternion::left_div(Self[T], Self[T]) -> Self[T]

它计算 rˉ q/∣r∣2\bar r\,q / |r|^2。两种商相差 2 (vq×vr)/∣r∣22\,(\mathbf v_q \times \mathbf v_r)/|r|^2,因此恰在向量部平行时二者相等。

test "right and left division" {
  let q = @quaternion.Quaternion::from_vec((1.0, 2.0, 3.0, 4.0))
  let r = @quaternion.Quaternion::from_vec((5.0, 6.0, 7.0, 8.0))
  inspect(q / r, content="0.40229885057471265 + 0.04597701149425287i + 0j + 0.09195402298850575k")
  inspect(q.left_div(r), content="0.40229885057471265 + 0i + 0.09195402298850575j + 0.04597701149425287k")
  assert_true((q / r * r - q).square_len() < 1.0e-24)
  assert_true((r * q.left_div(r) - q).square_len() < 1.0e-24)
}

Quaternion::scale

q.scale(s) 将每个分量乘以标量 s。

pub fn[T : Mul] Quaternion::scale(Self[T], T) -> Self[T]

标量位于 H\mathbb H 的中心,因此把 s 看作实四元数时,q.scale(s) 既等于 q * s 也等于 s * q。

Quaternion::conjugate

q.conjugate() 返回 qˉ=w−xi−yj−zk\bar q = w - xi - yj - zk。

pub fn[T : Neg] Quaternion::conjugate(Self[T]) -> Self[T]

它是被提升为方法的 luna-generic Conjugate 方法。共轭会反转乘积顺序:qr‾=rˉ qˉ\overline{qr} = \bar r\,\bar q。

Quaternion::inv

q.inv() 返回双边逆元 q−1=qˉ/∣q∣2q^{-1} = \bar q / |q|^2。

pub fn[T : Div + @luna-generic.Num] Quaternion::inv(Self[T]) -> Self[T]

它是被提升为方法的 luna-generic Inverse 方法;泛型代码以 @lf_alg.Inverse::inv(q) 调用它。单位四元数的逆就是它的共轭。零四元数没有逆元:在 Double 上结果为 NaN 分量。在 Int 上因子 1 / |q|^2 会被截断,因此除非 ∣q∣2=1|q|^2 = 1,inv 都返回零。

test "conjugate and inv" {
  let q = @quaternion.Quaternion::from_vec((1.0, 2.0, 3.0, 4.0))
  inspect(q.conjugate(), content="1 + -2i + -3j + -4k")
  inspect(q.inv(), content="0.03333333333333333 + -0.06666666666666667i + -0.1j + -0.13333333333333333k")
  assert_true((q * q.inv() - @quaternion.id()).square_len() < 1.0e-30)
}

Quaternion::pow_by_int

q.pow_by_int(n) 返回整数指数 Int 的幂 qnq^n。

pub fn[T : @luna-generic.Num + Mul + Add + Sub + Div] Quaternion::pow_by_int(Self[T], Int) -> Self[T]

n = 0 得到单位元,负的 n 得到 (q−1)∣n∣(q^{-1})^{|n|}。幂通过反复平方计算,需要 O(log⁡∣n∣)O(\log |n|) 次乘法。同一个四元数的各次幂彼此可交换,因此因子顺序无关紧要。

Quaternion::pow_by_T

q.pow_by_T(t) 通过极形式返回实数次幂 qtq^t。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Div + Eq] Quaternion::pow_by_T(Self[T], T) -> Self[T]

记 q=∣q∣(cos⁡φ+n^sin⁡φ)q = |q|(\cos\varphi + \hat n \sin\varphi),结果为 ∣q∣t(cos⁡tφ+n^sin⁡tφ)|q|^t(\cos t\varphi + \hat n\sin t\varphi)。零四元数原样返回。实四元数(x=y=z=0x = y = z = 0)对其标量部使用 @math.pow,因此负实部配合非整数指数会得到 NaN。

test "powers" {
  let q = @quaternion.Quaternion::from_vec((2.0, 2.0, 3.0, 4.0))
  inspect(q.pow_by_int(2), content="-25 + 8i + 12j + 16k")
  inspect(q.pow_by_int(-1), content="0.06060606060606061 + -0.06060606060606061i + -0.09090909090909091j + -0.12121212121212122k")
  inspect(q.pow_by_T(0.5), content="1.967811302759747 + 0.508178806879275i + 0.7622682103189125j + 1.01635761375855k")
}

范数与内积

Quaternion::dot

q.dot(r) 是四个分量的欧氏内积 wqwr+xqxr+yqyr+zqzrw_q w_r + x_q x_r + y_q y_r + z_q z_r。

pub fn[T : Mul + Add] Quaternion::dot(Self[T], Self[T]) -> T

对单位四元数,∣q⋅r∣=cos⁡(θ/2)|q\cdot r| = \cos(\theta/2),其中 θ\theta 是把一个姿态转到另一个姿态的旋转角。

Quaternion::square_len

q.square_len() 返回 ∣q∣2=w2+x2+y2+z2=q qˉ|q|^2 = w^2 + x^2 + y^2 + z^2 = q\,\bar q。

pub fn[T : Mul + Add] Quaternion::square_len(Self[T]) -> T

它只需要 Mul + Add,因此在 Int 与 BigInt 上是精确的。平方范数是可乘的:∣qr∣2=∣q∣2∣r∣2|qr|^2 = |q|^2|r|^2。

Quaternion::magnitude

q.magnitude() 返回 ∣q∣|q|。

pub fn[T : @luna-generic.Num + DoubleConvert] Quaternion::magnitude(Self[T]) -> T

平方根在 Double 中计算,再用 DoubleConvert::from_double 转换回来,对 Int 会截断。

Quaternion::normalize

q.normalize() 返回 q/∣q∣q / |q|,即与 q 同方向的单位四元数。

pub fn[T : @luna-generic.Num + DoubleConvert + Div + Eq] Quaternion::normalize(Self[T]) -> Self[T]

零四元数原样返回。结果的范数在舍入误差范围内为 1。在 Int 上计算会被截断,没有意义。

test "norms" {
  let q = @quaternion.Quaternion::from_vec((1.0, 2.0, 3.0, 4.0))
  inspect(q.dot(q), content="30")
  inspect(q.square_len(), content="30")
  inspect(q.magnitude(), content="5.477225575051661")
  inspect(q.normalize(), content="0.18257418583505536 + 0.3651483716701107i + 0.5477225575051661j + 0.7302967433402214k")
  inspect(q.normalize().magnitude(), content="0.9999999999999999")
}

旋转

Quaternion::rotate

q.rotate(v) 用单位四元数 q 旋转三维向量 v,即计算 q v q−1q\,v\,q^{-1} 的向量部。

pub fn[T : @luna-generic.Num + Sub] Quaternion::rotate(Self[T], (T, T, T)) -> (T, T, T)

对 q=(w,u)q = (w, \mathbf u),实现使用 t=2 u×v\mathbf t = 2\,\mathbf u \times \mathbf v 与 v′=v+w t+u×t\mathbf v' = \mathbf v + w\,\mathbf t + \mathbf u \times \mathbf t,无需除法。该公式仅在 ∣q∣=1|q| = 1 时等于 qvq−1q v q^{-1};rotate 不会替你归一化。旋转方向遵循绕 q 的轴的右手定则。

test "rotate" {
  let q = @quaternion.from_axis_angle((0.0, 0.0, 1.0), @math.PI / 2.0)
  let (x, y, z) = q.rotate((1.0, 0.0, 0.0))
  assert_true((x - 0.0).abs() < 1.0e-15)
  assert_true((y - 1.0).abs() < 1.0e-15)
  assert_true(z == 0.0)
}

slerp

slerp(q1, q2, t) 在旋转 q1 与 q2 之间做球面插值。

pub fn[T : DoubleConvert + @luna-generic.Num + Mul + Add + Div + Eq] slerp(Quaternion[T], Quaternion[T], Double) -> Quaternion[T]

两个输入先被归一化。若它们的点积为负,则用 -q2(同一旋转)替换 q2,使插值走较短的弧。记 cos⁡Ω=q1⋅q2\cos\Omega = q_1\cdot q_2,结果为

slerp⁡(q1,q2;t)=sin⁡((1−t)Ω)sin⁡Ω q1+sin⁡(tΩ)sin⁡Ω q2.\operatorname{slerp}(q_1, q_2; t) = \frac{\sin\big((1-t)\Omega\big)}{\sin\Omega}\,q_1 + \frac{\sin(t\Omega)}{\sin\Omega}\,q_2 .

当 cos⁡Ω>0.9995\cos\Omega > 0.9995 时,函数改用归一化的线性插值,以避免除以很小的 sin⁡Ω\sin\Omega。t = 0 给出(归一化后的)q1,t = 1 给出 q2 或 -q2。[0,1][0, 1] 之外的 t 会沿同一大圆外推。

test "slerp" {
  let a = @quaternion.id()
  let b = @quaternion.from_axis_angle((0.0, 0.0, 1.0), @math.PI / 2.0)
  let half = @quaternion.slerp(a, b, 0.5)
  let expected = @quaternion.from_axis_angle((0.0, 0.0, 1.0), @math.PI / 4.0)
  assert_true((half - expected).square_len() < 1.0e-30)
}

Quaternion::to_euler

q.to_euler(order~, external~) 将 q 的旋转分解为三个以弧度表示的角。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler(Self[T], order? : String, external? : Bool) -> (T, T, T)

q 会先被归一化。order 指定三个不同的轴,默认为 "ZYX";external 默认为 true。返回的第 k 个角属于 order 的第 k 个轴:

  • 外旋(external=true),顺序 ABCABC、角度 (a,b,c)(a, b, c):先绕固定轴 AA 转 aa,再绕固定轴 BB 转 bb,最后绕固定轴 CC 转 cc,因此 q=± qC(c) qB(b) qA(a)q = \pm\,q_C(c)\,q_B(b)\,q_A(a);
  • 内旋(external=false):先绕 AA 转,再绕转动后的 BB,最后绕再次转动后的 CC,因此 q=± qA(a) qB(b) qC(c)q = \pm\,q_A(a)\,q_B(b)\,q_C(c)。

支持的顺序为 "XYZ"、"XZY"、"YZX" 与 "ZYX";其他字符串会以 order error. Please check input format and retry 中止。中间角位于 [−π/2,π/2][-\pi/2, \pi/2],两端的角位于 (−π,π](-\pi, \pi]。

当中间角的正弦达到 ∣sin⁡b∣≥0.9998|\sin b| \ge 0.9998(约 88.85∘88.85^\circ)时,分解接近万向节锁:函数向标准输出打印一行 UserWarning: Gimbal lock detected...,把中间角精确设为 ±π/2\pm\pi/2,并把第三个角设为 00。

test "to_euler" {
  let q = @quaternion.from_euler(0.1, 0.2, 0.3) // q_X(0.1) q_Y(0.2) q_Z(0.3)
  let (x, y, z) = q.to_euler(order="XYZ", external=false) // about X, Y, Z
  assert_true((x - 0.1).abs() < 1.0e-12 && (y - 0.2).abs() < 1.0e-12 && (z - 0.3).abs() < 1.0e-12)
  let (z2, y2, x2) = q.to_euler() // extrinsic ZYX: about Z, Y, X
  assert_true((z2 - 0.3).abs() < 1.0e-12 && (y2 - 0.2).abs() < 1.0e-12 && (x2 - 0.1).abs() < 1.0e-12)
}

Quaternion::to_euler_external_XYZ

q.to_euler_external_XYZ() 即 q.to_euler(order="XYZ", external=true):绕 X、Y、Z 的角 (a,b,c)(a, b, c),满足 q=±qZ(c) qY(b) qX(a)q = \pm q_Z(c)\,q_Y(b)\,q_X(a)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_external_XYZ(Self[T]) -> (T, T, T)

Quaternion::to_euler_external_XZY

q.to_euler_external_XZY() 即 q.to_euler(order="XZY", external=true)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_external_XZY(Self[T]) -> (T, T, T)

它目前返回内旋 X-Y-Z 的角 (a,b,c)(a, b, c),满足 q=±qX(a) qY(b) qZ(c)q = \pm q_X(a)\,q_Y(b)\,q_Z(c),而不是 X-Z-Y 分解(见上方警告)。

Quaternion::to_euler_external_YZX

q.to_euler_external_YZX() 即 q.to_euler(order="YZX", external=true):绕 Y、Z、X 的角,满足 q=±qX(c) qZ(b) qY(a)q = \pm q_X(c)\,q_Z(b)\,q_Y(a)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_external_YZX(Self[T]) -> (T, T, T)

Quaternion::to_euler_external_ZYX

q.to_euler_external_ZYX() 即 q.to_euler():绕 Z、Y、X 的角,满足 q=±qX(c) qY(b) qZ(a)q = \pm q_X(c)\,q_Y(b)\,q_Z(a)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_external_ZYX(Self[T]) -> (T, T, T)

Quaternion::to_euler_internal_XYZ

q.to_euler_internal_XYZ() 即 q.to_euler(order="XYZ", external=false):绕 X、Y、Z 的角,满足 q=±qX(a) qY(b) qZ(c)q = \pm q_X(a)\,q_Y(b)\,q_Z(c)。它是默认顺序下 from_euler(a, b, c) 的逆。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_internal_XYZ(Self[T]) -> (T, T, T)

Quaternion::to_euler_internal_XZY

q.to_euler_internal_XZY() 即 q.to_euler(order="XZY", external=false):绕 X、Z、Y 的角,满足 q=±qX(a) qZ(b) qY(c)q = \pm q_X(a)\,q_Z(b)\,q_Y(c)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_internal_XZY(Self[T]) -> (T, T, T)

Quaternion::to_euler_internal_YZX

q.to_euler_internal_YZX() 即 q.to_euler(order="YZX", external=false)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_internal_YZX(Self[T]) -> (T, T, T)

它目前返回 (c,b,a)(c, b, a),满足 q=±qX(a) qY(b) qZ(c)q = \pm q_X(a)\,q_Y(b)\,q_Z(c),即逆序的内旋 X-Y-Z 角,而不是 Y-Z-X 分解(见上方警告)。

Quaternion::to_euler_internal_ZYX

q.to_euler_internal_ZYX() 即 q.to_euler(order="ZYX", external=false):绕 Z、Y、X 的角,满足 q=±qZ(a) qY(b) qX(c)q = \pm q_Z(a)\,q_Y(b)\,q_X(c)。

pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_internal_ZYX(Self[T]) -> (T, T, T)

每个内旋变体都是逆序顺序的外旋变体再把结果反转,因为 qA(a) qB(b) qC(c)q_A(a)\,q_B(b)\,q_C(c) 既是内旋 ABCABC 也是外旋 CBACBA。

通过 Double 转换

DoubleConvert

DoubleConvert 在分量类型与 Double 之间相互转换。

pub trait DoubleConvert {
  fn to_double(Self) -> Double
  fn from_double(Double) -> Self
}
pub impl DoubleConvert for Int
pub impl DoubleConvert for Double

平方根、三角函数与 @math.pow 只对 Double 可用,因此 magnitude、normalize、from_axis_angle、from_euler、slerp、pow_by_T 以及欧拉角转换都经由此 trait。对 Double,两个方向都是恒等映射。对 Int,from_double 即 Double::to_int,向零截断,因此这些函数在 Int 上的结果会被截断。

该 trait 声明为 pub 而非 pub(open),因此本包之外的代码无法实现它:Int 与 Double 是上述函数仅能使用的分量类型。不过仍可直接调用它的方法:

test "DoubleConvert" {
  let n : Int = @quaternion.DoubleConvert::from_double(2.9)
  inspect(n, content="2")
  inspect(@quaternion.DoubleConvert::to_double(7), content="7")
}

其他方法

Quaternion::map

q.map(f) 对每个分量应用 f,分量类型可以随之改变。

pub fn[T, U] Quaternion::map(Self[T], (T) -> U) -> Self[U]
test "map" {
  let q = @quaternion.Quaternion::from_vec((1, 2, 3, 4))
  inspect(q.map(Int::to_double).scale(0.5), content="0.5 + 1i + 1.5j + 2k")
}

Quaternion::to_string

q.to_string() 使用分量的 Show 把 q 渲染为 w + xi + yj + zk。

pub fn[T : Show] Quaternion::to_string(Self[T]) -> String

负分量在加号后保留其符号,例如 1 + -2i + 3j + 4k。字符串插值 "\{q}" 与 inspect 使用同样的文本。

Quaternion::equal

q.equal(r)(即 == 运算符)精确比较四个分量。

pub fn[T : Eq] Quaternion::equal(Self[T], Self[T]) -> Bool

对 Double 分量,这是 IEEE 相等:0.0 == -0.0,而含 NaN 分量的四元数不等于自身。

Quaternion::hash

q.hash() 对四个分量求哈希,与 equal 保持一致。

pub fn[T : Hash] Quaternion::hash(Self[T]) -> Int

trait 实现

Quaternion[T] 实现了以下 trait:

Trait对 T 的要求含义
Add, Sub, NegAdd; Neg + Add; Neg按分量
MulMul + Sub + AddHamilton 积
DivAdd + Mul + Div + Sub右除 qr−1q r^{-1}
Eq, Hash, @debug.Debug同一 trait派生,按分量
ShowShoww + xi + yj + zk
Default@lf_alg.Numid()
@lf_alg.Zero, @lf_alg.OneZero; Zero + One00 与 11
@lf_alg.AddMonoidAddMonoid(H,+,0)(\mathbb H, +, 0)
@lf_alg.MulMonoid, @lf_alg.Semiring, @lf_alg.RingRing(H,+,⋅,0,1)(\mathbb H, +, \cdot, 0, 1)
@lf_alg.ConjugateNegqˉ\bar q
@lf_alg.InverseDiv + Numqˉ/∣q∣2\bar q / \lvert q\rvert^2

Ring 实例假定 T 的乘法可交换,Int、Int64、BigInt、Float 与 Double 都满足这一点;只有在标量构成交换环时,Hamilton 积才满足结合律。本包刻意不实现 @lf_alg.Field,因为四元数乘法不可交换,而 luna-generic 的 Field 面向交换域。@lf_alg.Num 同样未实现:四元数没有符合 Num 预期含义的 signum/abs。

///|
fn[R : @lf_alg.Ring] square_plus_one(x : R) -> R {
  x * x + @lf_alg.One::one()
}

test "generic Ring code" {
  let q = @quaternion.Quaternion::from_vec((0, 1, 0, 0)) // i
  inspect(square_plus_one(q), content="0 + 0i + 0j + 0k") // i^2 + 1 = 0
}

已弃用

这些方法形式来自 trait 实现,为兼容而保留。它们已被弃用并从文档索引中隐藏;trait 本身仍然实现。

已弃用的方法替代用法
q.not_equal(r)q != r
q.hash_combine(hasher)Hash::hash_combine(q, hasher)
q.output(logger)q.to_string() 或 "\{q}"
Quaternion::default()id() 或 Default::default()
q.to_repr()Repr(q) 或 @debug.Debug::to_repr(q)