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 的四元数 。
type Quaternion[T] derive(Eq, Hash, @debug.Debug)
一个值由标量(实)部 与向量部 组成。该类型是抽象类型:请用 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)) 构造 。
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() 返回单位元 。
pub fn[T : @luna-generic.Num] id() -> Quaternion[T]
它与 Quaternion::one() 相等,但要求 Num,而 luna-generic 的每个数值类型(Int、Int64、Double 等)都实现了 Num。作为旋转,它是恒等旋转。
Quaternion::zero
Quaternion::zero() 返回加法单位元 。
pub fn[T : @luna-generic.Zero] Quaternion::zero() -> Self[T]
Quaternion::one
Quaternion::one() 返回乘法单位元 。
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]
结果为 ,其中 。轴不必归一化,但不能为零:零轴会导致除以零,对 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"。记 、、 为绕各坐标轴旋转 。可接受的顺序计算如下:
order | 结果 | 含义 |
|---|---|---|
"XYZ"(默认) | 内旋 X-Y-Z,等同于外旋 Z-Y-X | |
"YXZ" | 内旋 Y-X-Z | |
"ZXY" | 内旋 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]
写成标量–向量形式为 。该乘积满足结合律并对 + 满足分配律,但不满足交换律:。对旋转而言,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 是右除:,即方程 的解 。
pub fn[T : Add + Mul + Div + Sub] Quaternion::div(Self[T], Self[T]) -> Self[T]
它计算 ,每个分量只做一次除法,不构造中间的逆元。除以零四元数时会在 T 中除以零:Double 得到 NaN 分量,Int 则触发运行时陷阱。对 Int,每个分量都会被整数除法截断,因此只有在除法恰好整除时结果才有意义。
Quaternion::left_div
q.left_div(r) 是左除:,即方程 的解 。
pub fn[T : Add + Mul + Div + Sub] Quaternion::left_div(Self[T], Self[T]) -> Self[T]
它计算 。两种商相差 ,因此恰在向量部平行时二者相等。
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]
标量位于 的中心,因此把 s 看作实四元数时,q.scale(s) 既等于 q * s 也等于 s * q。
Quaternion::conjugate
q.conjugate() 返回 。
pub fn[T : Neg] Quaternion::conjugate(Self[T]) -> Self[T]
它是被提升为方法的 luna-generic Conjugate 方法。共轭会反转乘积顺序:。
Quaternion::inv
q.inv() 返回双边逆元 。
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 会被截断,因此除非 ,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 的幂 。
pub fn[T : @luna-generic.Num + Mul + Add + Sub + Div] Quaternion::pow_by_int(Self[T], Int) -> Self[T]
n = 0 得到单位元,负的 n 得到 。幂通过反复平方计算,需要 次乘法。同一个四元数的各次幂彼此可交换,因此因子顺序无关紧要。
Quaternion::pow_by_T
q.pow_by_T(t) 通过极形式返回实数次幂 。
pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Div + Eq] Quaternion::pow_by_T(Self[T], T) -> Self[T]
记 ,结果为 。零四元数原样返回。实四元数()对其标量部使用 @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) 是四个分量的欧氏内积 。
pub fn[T : Mul + Add] Quaternion::dot(Self[T], Self[T]) -> T
对单位四元数,,其中 是把一个姿态转到另一个姿态的旋转角。
Quaternion::square_len
q.square_len() 返回 。
pub fn[T : Mul + Add] Quaternion::square_len(Self[T]) -> T
它只需要 Mul + Add,因此在 Int 与 BigInt 上是精确的。平方范数是可乘的:。
Quaternion::magnitude
q.magnitude() 返回 。
pub fn[T : @luna-generic.Num + DoubleConvert] Quaternion::magnitude(Self[T]) -> T
平方根在 Double 中计算,再用 DoubleConvert::from_double 转换回来,对 Int 会截断。
Quaternion::normalize
q.normalize() 返回 ,即与 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,即计算 的向量部。
pub fn[T : @luna-generic.Num + Sub] Quaternion::rotate(Self[T], (T, T, T)) -> (T, T, T)
对 ,实现使用 与 ,无需除法。该公式仅在 时等于 ;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,使插值走较短的弧。记 ,结果为
当 时,函数改用归一化的线性插值,以避免除以很小的 。t = 0 给出(归一化后的)q1,t = 1 给出 q2 或 -q2。 之外的 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),顺序 、角度 :先绕固定轴 转 ,再绕固定轴 转 ,最后绕固定轴 转 ,因此 ; - 内旋(
external=false):先绕 转,再绕转动后的 ,最后绕再次转动后的 ,因此 。
支持的顺序为 "XYZ"、"XZY"、"YZX" 与 "ZYX";其他字符串会以 order error. Please check input format and retry 中止。中间角位于 ,两端的角位于 。
当中间角的正弦达到 (约 )时,分解接近万向节锁:函数向标准输出打印一行 UserWarning: Gimbal lock detected...,把中间角精确设为 ,并把第三个角设为 。
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 的角 ,满足 。
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 的角 ,满足 ,而不是 X-Z-Y 分解(见上方警告)。
Quaternion::to_euler_external_YZX
q.to_euler_external_YZX() 即 q.to_euler(order="YZX", external=true):绕 Y、Z、X 的角,满足 。
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 的角,满足 。
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 的角,满足 。它是默认顺序下 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 的角,满足 。
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)
它目前返回 ,满足 ,即逆序的内旋 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 的角,满足 。
pub fn[T : @luna-generic.Num + DoubleConvert + Mul + Add + Sub + Div + Eq] Quaternion::to_euler_internal_ZYX(Self[T]) -> (T, T, T)
每个内旋变体都是逆序顺序的外旋变体再把结果反转,因为 既是内旋 也是外旋 。
通过 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, Neg | Add; Neg + Add; Neg | 按分量 |
Mul | Mul + Sub + Add | Hamilton 积 |
Div | Add + Mul + Div + Sub | 右除 |
Eq, Hash, @debug.Debug | 同一 trait | 派生,按分量 |
Show | Show | w + xi + yj + zk |
Default | @lf_alg.Num | id() |
@lf_alg.Zero, @lf_alg.One | Zero; Zero + One | 与 |
@lf_alg.AddMonoid | AddMonoid | |
@lf_alg.MulMonoid, @lf_alg.Semiring, @lf_alg.Ring | Ring | |
@lf_alg.Conjugate | Neg | |
@lf_alg.Inverse | Div + Num |
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) |