core 设计
本页说明 Luna-Flow/quaternion 所实现的数学,以及其 API 为何是现在的形态。下文每个公式都是 src/quaternion.mbt 中代码实际计算的公式;凡实现与数学不一致之处,都会明确指出。
设计目标
本包为 MoonBit 提供一个四元数类型 Quaternion[T],服务于两类使用者:
代数代码:把 H \mathbb H H 当作精确或近似标量类型上的环,并通过 luna-generic 的 trait 使用它;
几何代码:把单位四元数当作三维旋转——由轴与角或欧拉角构造它们,并对其进行复合、作用、插值与分解。
两者由同一个泛型类型服务。每个运算只要求 T 上它所需的 trait,因此环运算可以在 Int 上精确进行,而需要平方根或三角函数的运算则经由 Double。
数学背景
作为实代数的四元数
四元数 H \mathbb H H 是以 1 , i , j , k 1, i, j, k 1 , i , j , k 为基的 4 维实向量空间,
q = w + x i + y j + z k , w , x , y , z ∈ R , q = w + x\,i + y\,j + z\,k, \qquad w, x, y, z \in \mathbb R, q = w + x i + y j + z k , w , x , y , z ∈ R ,
其双线性乘法由 Hamilton 关系确定1 1 W. R. Hamilton,“On quaternions; or on a new system of imaginaries in algebra”,1843 年。这些关系正是他刻在都柏林布鲁姆桥(Brougham Bridge)上的那几个式子。
i 2 = j 2 = k 2 = i j k = − 1. i^2 = j^2 = k^2 = ijk = -1 . i 2 = j 2 = k 2 = ij k = − 1.
记 q = ( w , u ) q = (w, \mathbf u) q = ( w , u ) ,其中 w w w 为标量部,u = ( x , y , z ) \mathbf u = (x, y, z) u = ( x , y , z ) 为向量部,并把 R 3 \mathbb R^3 R 3 等同于纯四元数 { ( 0 , u ) } \{(0, \mathbf u)\} {( 0 , u )} 。Quaternion[T] 恰好存储这一对:字段 r 存 w w w ,三元组 vec 存 u \mathbf u u 。由于乘法是 R \mathbb R R -双线性的,实数 ( s , 0 ) (s, \mathbf 0) ( s , 0 ) 与一切元素可交换。
基元的乘积
乘积的一切性质都源自这四个关系。在 i j k = − 1 ijk = -1 ij k = − 1 两边右乘 k k k ,并利用 k 2 = − 1 k^2 = -1 k 2 = − 1 :
i j k k = − k − i j = − k i j = k . \begin{aligned}
ijk\,k &= -k \\
-ij &= -k \\
ij &= k .
\end{aligned} ij k k − ij ij = − k = − k = k .
在 i j k = − 1 ijk = -1 ij k = − 1 两边左乘 i i i ,并利用 i 2 = − 1 i^2 = -1 i 2 = − 1 :− j k = − i -jk = -i − j k = − i ,故 j k = i jk = i j k = i 。反向的乘积可由这两式得出:
j i = j ( j k ) = j 2 k = − k , k j = ( i j ) j = i j 2 = − i , i k = i ( i j ) = i 2 j = − j , k i = ( i j ) i = i ( j i ) = − i k = j . \begin{aligned}
ji &= j\,(jk) = j^2 k = -k, \\
kj &= (ij)\,j = i\,j^2 = -i, \\
ik &= i\,(ij) = i^2 j = -j, \\
ki &= (ij)\,i = i\,(ji) = -ik = j .
\end{aligned} j i k j ik k i = j ( j k ) = j 2 k = − k , = ( ij ) j = i j 2 = − i , = i ( ij ) = i 2 j = − j , = ( ij ) i = i ( j i ) = − ik = j .
因此基元的乘法与标准基的叉积相同,只是对角线上多了 − 1 -1 − 1 :
⋅ \cdot ⋅ i i i j j j k k k i i i − 1 -1 − 1 k k k − j -j − j j j j − k -k − k − 1 -1 − 1 i i i k k k j j j − i -i − i − 1 -1 − 1
八个元素 ± 1 , ± i , ± j , ± k \pm 1, \pm i, \pm j, \pm k ± 1 , ± i , ± j , ± k 构成四元数群 Q 8 Q_8 Q 8 ,它可以用 2 × 2 2\times 2 2 × 2 复矩阵实现。2 2 例如 1 ↦ I 1 \mapsto I 1 ↦ I 、i ↦ ( i 0 0 − i ) i \mapsto \begin{pmatrix} i & 0 \\ 0 & -i\end{pmatrix} i ↦ ( i 0 0 − i ) 、j ↦ ( 0 1 − 1 0 ) j \mapsto \begin{pmatrix} 0 & 1 \\ -1 & 0\end{pmatrix} j ↦ ( 0 − 1 1 0 ) 、k ↦ ( 0 i i 0 ) k \mapsto \begin{pmatrix} 0 & i \\ i & 0\end{pmatrix} k ↦ ( 0 i i 0 ) 。这些矩阵满足 Hamilton 关系,而矩阵乘法满足结合律。 Q 8 Q_8 Q 8 的乘法满足结合律,由双线性可知 H \mathbb H H 的乘积也满足结合律。
Hamilton 积
按乘法表把 p = w 1 + x 1 i + y 1 j + z 1 k p = w_1 + x_1 i + y_1 j + z_1 k p = w 1 + x 1 i + y 1 j + z 1 k 与 q = w 2 + x 2 i + y 2 j + z 2 k q = w_2 + x_2 i + y_2 j + z_2 k q = w 2 + x 2 i + y 2 j + z 2 k 逐项展开相乘,得到
p q = ( w 1 w 2 − x 1 x 2 − y 1 y 2 − z 1 z 2 ) + ( w 1 x 2 + x 1 w 2 + y 1 z 2 − z 1 y 2 ) i + ( w 1 y 2 − x 1 z 2 + y 1 w 2 + z 1 x 2 ) j + ( w 1 z 2 + x 1 y 2 − y 1 x 2 + z 1 w 2 ) k . \begin{aligned}
pq ={} & (w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2) \\
&+ (w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2)\, i \\
&+ (w_1 y_2 - x_1 z_2 + y_1 w_2 + z_1 x_2)\, j \\
&+ (w_1 z_2 + x_1 y_2 - y_1 x_2 + z_1 w_2)\, k .
\end{aligned} pq = ( w 1 w 2 − x 1 x 2 − y 1 y 2 − z 1 z 2 ) + ( w 1 x 2 + x 1 w 2 + y 1 z 2 − z 1 y 2 ) i + ( w 1 y 2 − x 1 z 2 + y 1 w 2 + z 1 x 2 ) j + ( w 1 z 2 + x 1 y 2 − y 1 x 2 + z 1 w 2 ) k .
同一乘积用标量–向量形式表达更为清晰。对两个纯四元数 a \mathbf a a 与 b \mathbf b b ,基元的平方贡献 − a 1 b 1 − a 2 b 2 − a 3 b 3 -a_1 b_1 - a_2 b_2 - a_3 b_3 − a 1 b 1 − a 2 b 2 − a 3 b 3 ,混合项两两配对,例如 a 1 b 2 i j + a 2 b 1 j i = ( a 1 b 2 − a 2 b 1 ) k a_1 b_2\, ij + a_2 b_1\, ji = (a_1 b_2 - a_2 b_1)k a 1 b 2 ij + a 2 b 1 j i = ( a 1 b 2 − a 2 b 1 ) k 。因此
a b = − a ⋅ b + a × b . \mathbf a\,\mathbf b = -\,\mathbf a\cdot\mathbf b + \mathbf a\times\mathbf b . a b = − a ⋅ b + a × b .
结合双线性以及标量可交换这一事实,
( s 1 , v 1 ) ( s 2 , v 2 ) = s 1 s 2 + s 1 v 2 + s 2 v 1 + v 1 v 2 = ( s 1 s 2 − v 1 ⋅ v 2 , s 1 v 2 + s 2 v 1 + v 1 × v 2 ) . \begin{aligned}
(s_1, \mathbf v_1)(s_2, \mathbf v_2) &= s_1 s_2 + s_1\mathbf v_2 + s_2\mathbf v_1 + \mathbf v_1\mathbf v_2 \\
&= \big(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\big).
\end{aligned} ( s 1 , v 1 ) ( s 2 , v 2 ) = s 1 s 2 + s 1 v 2 + s 2 v 1 + v 1 v 2 = ( s 1 s 2 − v 1 ⋅ v 2 , s 1 v 2 + s 2 v 1 + v 1 × v 2 ) .
这正是 Mul 的实现:标量部为 self.r * other.r - dot(self.vec, other.vec),向量部为 cross(self.vec, other.vec) 加上两个缩放后的向量。
交换两个因子只会翻转叉积的符号,因此
p q − q p = 2 v 1 × v 2 , pq - qp = 2\,\mathbf v_1 \times \mathbf v_2 , pq − q p = 2 v 1 × v 2 ,
两个四元数可交换当且仅当它们的向量部平行。特别地,H \mathbb H H 不可交换:i j = k ij = k ij = k 而 j i = − k ji = -k j i = − k 。
上述推导用到了分量彼此可交换(s 1 v 2 = v 2 s 1 s_1\mathbf v_2 = \mathbf v_2 s_1 s 1 v 2 = v 2 s 1 等)。因此泛型实现假定 T 的乘法可交换,luna-generic 提供的所有数值类型都满足这一点。
共轭与范数
共轭为 q ˉ = ( w , − u ) \bar q = (w, -\mathbf u) q ˉ = ( w , − u ) 。四元数乘以其共轭得到一个实数,因为平行向量的叉积为零:
q q ˉ = ( w 2 + u ⋅ u , − w u + w u − u × u ) = ( w 2 + x 2 + y 2 + z 2 , 0 ) = ∣ q ∣ 2 , q\bar q = \big(w^2 + \mathbf u\cdot\mathbf u,\ -w\mathbf u + w\mathbf u - \mathbf u\times\mathbf u\big) = \big(w^2 + x^2 + y^2 + z^2,\ \mathbf 0\big) = |q|^2 , q q ˉ = ( w 2 + u ⋅ u , − w u + w u − u × u ) = ( w 2 + x 2 + y 2 + z 2 , 0 ) = ∣ q ∣ 2 ,
同理 q ˉ q = ∣ q ∣ 2 \bar q q = |q|^2 q ˉ q = ∣ q ∣ 2 。Quaternion::square_len 计算的就是这个 ∣ q ∣ 2 |q|^2 ∣ q ∣ 2 ,而 Quaternion::dot 是以它为范数的 R 4 \mathbb R^4 R 4 内积。
共轭会反转乘积顺序。设 p = ( s 1 , v 1 ) p = (s_1, \mathbf v_1) p = ( s 1 , v 1 ) 、q = ( s 2 , v 2 ) q = (s_2, \mathbf v_2) q = ( s 2 , v 2 ) ,
p q ‾ = ( s 1 s 2 − v 1 ⋅ v 2 , − s 1 v 2 − s 2 v 1 − v 1 × v 2 ) , q ˉ p ˉ = ( s 2 s 1 − v 2 ⋅ v 1 , − s 2 v 1 − s 1 v 2 + v 2 × v 1 ) , \begin{aligned}
\overline{pq} &= \big(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\big), \\
\bar q\,\bar p &= \big(s_2 s_1 - \mathbf v_2\cdot\mathbf v_1,\ -s_2\mathbf v_1 - s_1\mathbf v_2 + \mathbf v_2\times\mathbf v_1\big),
\end{aligned} pq q ˉ p ˉ = ( s 1 s 2 − v 1 ⋅ v 2 , − s 1 v 2 − s 2 v 1 − v 1 × v 2 ) , = ( s 2 s 1 − v 2 ⋅ v 1 , − s 2 v 1 − s 1 v 2 + v 2 × v 1 ) ,
由于 v 2 × v 1 = − v 1 × v 2 \mathbf v_2\times\mathbf v_1 = -\mathbf v_1\times\mathbf v_2 v 2 × v 1 = − v 1 × v 2 ,二者相等。所以 p q ‾ = q ˉ p ˉ \overline{pq} = \bar q\,\bar p pq = q ˉ p ˉ 。
范数是可乘的。利用结合律、p q ‾ = q ˉ p ˉ \overline{pq} = \bar q\bar p pq = q ˉ p ˉ ,以及实数 ∣ q ∣ 2 |q|^2 ∣ q ∣ 2 与 p p p 可交换这一事实:
∣ p q ∣ 2 = ( p q ) ( p q ) ‾ = p ( q q ˉ ) p ˉ = p ∣ q ∣ 2 p ˉ = ∣ q ∣ 2 p p ˉ = ∣ p ∣ 2 ∣ q ∣ 2 . \begin{aligned}
|pq|^2 &= (pq)\,\overline{(pq)} = p\,(q\bar q)\,\bar p \\
&= p\,|q|^2\,\bar p = |q|^2\,p\bar p = |p|^2\,|q|^2 .
\end{aligned} ∣ pq ∣ 2 = ( pq ) ( pq ) = p ( q q ˉ ) p ˉ = p ∣ q ∣ 2 p ˉ = ∣ q ∣ 2 p p ˉ = ∣ p ∣ 2 ∣ q ∣ 2 .
在整数上,这就是欧拉四平方恒等式,教程 用 Quaternion[Int] 验证了它。
逆元与两种除法
若 q q q 不为零,则 ∣ q ∣ 2 > 0 |q|^2 > 0 ∣ q ∣ 2 > 0 ,而 q q ˉ = q ˉ q = ∣ q ∣ 2 q\bar q = \bar q q = |q|^2 q q ˉ = q ˉ q = ∣ q ∣ 2 表明
q − 1 = q ˉ ∣ q ∣ 2 q^{-1} = \frac{\bar q}{|q|^2} q − 1 = ∣ q ∣ 2 q ˉ
是双边逆元。这就是 Inverse 的实现:用 one() / square_len() 缩放共轭。每个非零四元数都可逆,因此 H \mathbb H H 是一个除环 (斜域)。由于不可交换,它不是域。
没有交换律时,“q q q 除以 r r r ”有两种含义,取决于未知数位于哪一侧:
x r = q ⟺ x = q r − 1 (right division, q / r ) , r x = q ⟺ x = r − 1 q (left division, q .left_div ( r ) ) . \begin{aligned}
x\,r = q &\iff x = q\,r^{-1} && \text{(right division, } q / r\text{)}, \\
r\,x = q &\iff x = r^{-1} q && \text{(left division, } q\text{.left\_div}(r)\text{)}.
\end{aligned} x r = q r x = q ⟺ x = q r − 1 ⟺ x = r − 1 q (right division, q / r ) , (left division, q .left_div ( r ) ) .
实现避免构造 r − 1 r^{-1} r − 1 ,只在最后做一次除法。设 q = ( s q , v q ) q = (s_q, \mathbf v_q) q = ( s q , v q ) 、r = ( s r , v r ) r = (s_r, \mathbf v_r) r = ( s r , v r ) 、r ˉ = ( s r , − v r ) \bar r = (s_r, -\mathbf v_r) r ˉ = ( s r , − v r ) ,由标量–向量乘积得
q r − 1 = q r ˉ ∣ r ∣ 2 = ( s q s r + v q ⋅ v r , s r v q − s q v r − v q × v r ) ∣ r ∣ 2 , r − 1 q = r ˉ q ∣ r ∣ 2 = ( s q s r + v q ⋅ v r , s r v q − s q v r − v r × v q ) ∣ r ∣ 2 , \begin{aligned}
q\,r^{-1} &= \frac{q\,\bar r}{|r|^2} = \frac{\big(s_q s_r + \mathbf v_q\cdot\mathbf v_r,\ \ s_r\mathbf v_q - s_q\mathbf v_r - \mathbf v_q\times\mathbf v_r\big)}{|r|^2}, \\
r^{-1} q &= \frac{\bar r\,q}{|r|^2} = \frac{\big(s_q s_r + \mathbf v_q\cdot\mathbf v_r,\ \ s_r\mathbf v_q - s_q\mathbf v_r - \mathbf v_r\times\mathbf v_q\big)}{|r|^2},
\end{aligned} q r − 1 r − 1 q = ∣ r ∣ 2 q r ˉ = ∣ r ∣ 2 ( s q s r + v q ⋅ v r , s r v q − s q v r − v q × v r ) , = ∣ r ∣ 2 r ˉ q = ∣ r ∣ 2 ( s q s r + v q ⋅ v r , s r v q − s q v r − v r × v q ) ,
它们分别是 Div 的实现(其中 c = cross(q.vec, r.vec))与 Quaternion::left_div(其中 c = cross(r.vec, self.vec))。两种商的标量部相同,差为
q r − 1 − r − 1 q = − 2 v q × v r ∣ r ∣ 2 . q\,r^{-1} - r^{-1} q = -\frac{2\,\mathbf v_q\times\mathbf v_r}{|r|^2}. q r − 1 − r − 1 q = − ∣ r ∣ 2 2 v q × v r .
示例。 取 q = 1 + 2 i + 3 j + 4 k q = 1 + 2i + 3j + 4k q = 1 + 2 i + 3 j + 4 k 、r = 5 + 6 i + 7 j + 8 k r = 5 + 6i + 7j + 8k r = 5 + 6 i + 7 j + 8 k 。则 ∣ r ∣ 2 = 174 |r|^2 = 174 ∣ r ∣ 2 = 174 ,s q s r + v q ⋅ v r = 5 + 12 + 21 + 32 = 70 s_q s_r + \mathbf v_q\cdot\mathbf v_r = 5 + 12 + 21 + 32 = 70 s q s r + v q ⋅ v r = 5 + 12 + 21 + 32 = 70 ,s r v q − s q v r = ( 4 , 8 , 12 ) s_r\mathbf v_q - s_q\mathbf v_r = (4, 8, 12) s r v q − s q v r = ( 4 , 8 , 12 ) ,v q × v r = ( − 4 , 8 , − 4 ) \mathbf v_q\times\mathbf v_r = (-4, 8, -4) v q × v r = ( − 4 , 8 , − 4 ) ,所以
q / r = 1 174 ( 70 + 8 i + 0 j + 16 k ) ≈ 0.4023 + 0.0460 i + 0.0920 k , q . l e f t _ d i v ( r ) = 1 174 ( 70 + 0 i + 16 j + 8 k ) ≈ 0.4023 + 0.0920 j + 0.0460 k . \begin{aligned}
q / r &= \tfrac{1}{174}\big(70 + 8i + 0j + 16k\big) \approx 0.4023 + 0.0460\,i + 0.0920\,k, \\
q.\mathrm{left\_div}(r) &= \tfrac{1}{174}\big(70 + 0i + 16j + 8k\big) \approx 0.4023 + 0.0920\,j + 0.0460\,k .
\end{aligned} q / r q . left_div ( r ) = 174 1 ( 70 + 8 i + 0 j + 16 k ) ≈ 0.4023 + 0.0460 i + 0.0920 k , = 174 1 ( 70 + 0 i + 16 j + 8 k ) ≈ 0.4023 + 0.0920 j + 0.0460 k .
API 页面 验证了这些数值以及两个定义方程。
单位四元数与旋转
单位四元数(∣ q ∣ = 1 |q| = 1 ∣ q ∣ = 1 )可以写成
q = ( cos θ 2 , sin θ 2 n ) , ∣ n ∣ = 1 , q = \Big(\cos\tfrac{\theta}{2},\ \sin\tfrac{\theta}{2}\,\mathbf n\Big), \qquad |\mathbf n| = 1 , q = ( cos 2 θ , sin 2 θ n ) , ∣ n ∣ = 1 ,
这正是 from_axis_angle(axis, angle) 的返回值,其中 n \mathbf n n 为归一化后的轴。对单位 q q q 有 q − 1 = q ˉ q^{-1} = \bar q q − 1 = q ˉ 。考虑纯四元数上的映射 v ↦ q v q ˉ \mathbf v \mapsto q\,\mathbf v\,\bar q v ↦ q v q ˉ 。记 q = ( w , u ) q = (w, \mathbf u) q = ( w , u ) 。首先,
q v = ( − u ⋅ v , w v + u × v ) . q\,\mathbf v = \big(-\mathbf u\cdot\mathbf v,\ w\mathbf v + \mathbf u\times\mathbf v\big). q v = ( − u ⋅ v , w v + u × v ) .
再右乘 q ˉ = ( w , − u ) \bar q = (w, -\mathbf u) q ˉ = ( w , − u ) ,标量部为
− w u ⋅ v + ( w v + u × v ) ⋅ u = − w u ⋅ v + w v ⋅ u + 0 = 0 , -w\,\mathbf u\cdot\mathbf v + (w\mathbf v + \mathbf u\times\mathbf v)\cdot\mathbf u = -w\,\mathbf u\cdot\mathbf v + w\,\mathbf v\cdot\mathbf u + 0 = 0 , − w u ⋅ v + ( w v + u × v ) ⋅ u = − w u ⋅ v + w v ⋅ u + 0 = 0 ,
因此像仍是纯四元数,其向量部为
v ′ = ( u ⋅ v ) u + w ( w v + u × v ) − ( w v + u × v ) × u = ( u ⋅ v ) u + w 2 v + 2 w u × v + u × ( u × v ) = ( w 2 − ∣ u ∣ 2 ) v + 2 ( u ⋅ v ) u + 2 w u × v , \begin{aligned}
\mathbf v' &= (\mathbf u\cdot\mathbf v)\,\mathbf u + w\,(w\mathbf v + \mathbf u\times\mathbf v) - (w\mathbf v + \mathbf u\times\mathbf v)\times\mathbf u \\
&= (\mathbf u\cdot\mathbf v)\,\mathbf u + w^2\mathbf v + 2w\,\mathbf u\times\mathbf v + \mathbf u\times(\mathbf u\times\mathbf v) \\
&= (w^2 - |\mathbf u|^2)\,\mathbf v + 2(\mathbf u\cdot\mathbf v)\,\mathbf u + 2w\,\mathbf u\times\mathbf v ,
\end{aligned} v ′ = ( u ⋅ v ) u + w ( w v + u × v ) − ( w v + u × v ) × u = ( u ⋅ v ) u + w 2 v + 2 w u × v + u × ( u × v ) = ( w 2 − ∣ u ∣ 2 ) v + 2 ( u ⋅ v ) u + 2 w u × v ,
这里用到 a × u = − u × a \mathbf a\times\mathbf u = -\mathbf u\times\mathbf a a × u = − u × a 与 u × ( u × v ) = ( u ⋅ v ) u − ∣ u ∣ 2 v \mathbf u\times(\mathbf u\times\mathbf v) = (\mathbf u\cdot\mathbf v)\mathbf u - |\mathbf u|^2\mathbf v u × ( u × v ) = ( u ⋅ v ) u − ∣ u ∣ 2 v 。代入 w = cos θ 2 w = \cos\frac\theta2 w = cos 2 θ 、u = sin θ 2 n \mathbf u = \sin\frac\theta2\,\mathbf n u = sin 2 θ n ,并利用半角公式 cos 2 θ 2 − sin 2 θ 2 = cos θ \cos^2\frac\theta2 - \sin^2\frac\theta2 = \cos\theta cos 2 2 θ − sin 2 2 θ = cos θ 、2 sin 2 θ 2 = 1 − cos θ 2\sin^2\frac\theta2 = 1 - \cos\theta 2 sin 2 2 θ = 1 − cos θ 、2 sin θ 2 cos θ 2 = sin θ 2\sin\frac\theta2\cos\frac\theta2 = \sin\theta 2 sin 2 θ cos 2 θ = sin θ :
v ′ = cos θ v + ( 1 − cos θ ) ( n ⋅ v ) n + sin θ ( n × v ) . \mathbf v' = \cos\theta\,\mathbf v + (1 - \cos\theta)(\mathbf n\cdot\mathbf v)\,\mathbf n + \sin\theta\,(\mathbf n\times\mathbf v). v ′ = cos θ v + ( 1 − cos θ ) ( n ⋅ v ) n + sin θ ( n × v ) .
这就是 Rodrigues 旋转公式:v ↦ q v q − 1 \mathbf v \mapsto q\mathbf v q^{-1} v ↦ q v q − 1 是绕 n \mathbf n n 旋转 θ \theta θ ,按右手定则为逆时针方向。由于 ∣ q v q ˉ ∣ = ∣ q ∣ ∣ v ∣ ∣ q ˉ ∣ = ∣ v ∣ |q\mathbf v\bar q| = |q||\mathbf v||\bar q| = |\mathbf v| ∣ q v q ˉ ∣ = ∣ q ∣∣ v ∣∣ q ˉ ∣ = ∣ v ∣ ,该映射是等距映射,正如旋转所应满足的那样。
由此得到三个塑造 API 的结论:
复合即乘法。 p ( q v q − 1 ) p − 1 = ( p q ) v ( p q ) − 1 p\,(q\mathbf v q^{-1})\,p^{-1} = (pq)\,\mathbf v\,(pq)^{-1} p ( q v q − 1 ) p − 1 = ( pq ) v ( pq ) − 1 ,因此“先 q q q 后 p p p ”的旋转是 p * q。
双重覆盖。 ( − q ) v ( − q ) − 1 = q v q − 1 (-q)\,\mathbf v\,(-q)^{-1} = q\mathbf v q^{-1} ( − q ) v ( − q ) − 1 = q v q − 1 ,所以 q q q 与 − q -q − q 是同一个旋转。事实上角度 θ + 2 π \theta + 2\pi θ + 2 π 给出 − q -q − q 。每个旋转恰好对应两个单位四元数,这就是 == 不能用于比较旋转、而 slerp 要翻转某个输入符号的原因。
廉价求值。 Quaternion::rotate 计算 t = 2 u × v \mathbf t = 2\,\mathbf u\times\mathbf v t = 2 u × v 与 v ′ = v + w t + u × t \mathbf v' = \mathbf v + w\,\mathbf t + \mathbf u\times\mathbf t v ′ = v + w t + u × t 。展开得 u × t = 2 ( u ⋅ v ) u − 2 ∣ u ∣ 2 v \mathbf u\times\mathbf t = 2(\mathbf u\cdot\mathbf v)\mathbf u - 2|\mathbf u|^2\mathbf v u × t = 2 ( u ⋅ v ) u − 2∣ u ∣ 2 v ,所以
v + w t + u × t = ( 1 − 2 ∣ u ∣ 2 ) v + 2 ( u ⋅ v ) u + 2 w u × v , \mathbf v + w\,\mathbf t + \mathbf u\times\mathbf t = (1 - 2|\mathbf u|^2)\,\mathbf v + 2(\mathbf u\cdot\mathbf v)\,\mathbf u + 2w\,\mathbf u\times\mathbf v , v + w t + u × t = ( 1 − 2∣ u ∣ 2 ) v + 2 ( u ⋅ v ) u + 2 w u × v ,
它与上面的公式相等,当且仅当 1 − 2 ∣ u ∣ 2 = w 2 − ∣ u ∣ 2 1 - 2|\mathbf u|^2 = w^2 - |\mathbf u|^2 1 − 2∣ u ∣ 2 = w 2 − ∣ u ∣ 2 ,即 w 2 + ∣ u ∣ 2 = 1 w^2 + |\mathbf u|^2 = 1 w 2 + ∣ u ∣ 2 = 1 。这就是 rotate 要求单位四元数的原因:它无需除法,但依赖于范数为 1。
把同一公式作用于基向量,就得到单位四元数的旋转矩阵:
R ( q ) = ( 1 − 2 ( y 2 + z 2 ) 2 ( x y − w z ) 2 ( x z + w y ) 2 ( x y + w z ) 1 − 2 ( x 2 + z 2 ) 2 ( y z − w x ) 2 ( x z − w y ) 2 ( y z + w x ) 1 − 2 ( x 2 + y 2 ) ) . R(q) = \begin{pmatrix}
1 - 2(y^2 + z^2) & 2(xy - wz) & 2(xz + wy) \\
2(xy + wz) & 1 - 2(x^2 + z^2) & 2(yz - wx) \\
2(xz - wy) & 2(yz + wx) & 1 - 2(x^2 + y^2)
\end{pmatrix}. R ( q ) = 1 − 2 ( y 2 + z 2 ) 2 ( x y + w z ) 2 ( x z − w y ) 2 ( x y − w z ) 1 − 2 ( x 2 + z 2 ) 2 ( y z + w x ) 2 ( x z + w y ) 2 ( y z − w x ) 1 − 2 ( x 2 + y 2 ) .
本包不返回这个矩阵,但下文的欧拉角提取会读取它的元素。
欧拉角
记 q X ( α ) q_X(\alpha) q X ( α ) 、q Y ( α ) q_Y(\alpha) q Y ( α ) 、q Z ( α ) q_Z(\alpha) q Z ( α ) 为绕各坐标轴旋转 α \alpha α ,R X , R Y , R Z R_X, R_Y, R_Z R X , R Y , R Z 为对应的矩阵。绕轴 A , B , C A, B, C A , B , C 、角度为 a , b , c a, b, c a , b , c 的三次旋转序列有两种解读:
外旋 (external=true):绕固定轴,先 A A A ,再 B B B ,最后 C C C 。后发生的旋转乘在左边:q = q C ( c ) q B ( b ) q A ( a ) q = q_C(c)\,q_B(b)\,q_A(a) q = q C ( c ) q B ( b ) q A ( a ) 。
内旋 (external=false):绕随物体转动的轴,先 A A A ,再转动后的 B B B ,最后两次转动后的 C C C :q = q A ( a ) q B ( b ) q C ( c ) q = q_A(a)\,q_B(b)\,q_C(c) q = q A ( a ) q B ( b ) q C ( c ) 。
同一乘积的两种解读表明:角度为 ( a , b , c ) (a, b, c) ( a , b , c ) 的内旋 A B C ABC A B C 就是角度为 ( c , b , a ) (c, b, a) ( c , b , a ) 的外旋 C B A CBA C B A 。实现正是这样做的:每个 to_euler_internal_* 函数都调用逆序顺序的外旋函数,再把三元组反转。
提取:外旋 XYZ。 对 R = R Z ( c ) R Y ( b ) R X ( a ) R = R_Z(c)\,R_Y(b)\,R_X(a) R = R Z ( c ) R Y ( b ) R X ( a ) ,把基本矩阵相乘得到
R = ( cos b cos c ⋯ ⋯ cos b sin c ⋯ ⋯ − sin b cos b sin a cos b cos a ) . R = \begin{pmatrix}
\cos b\cos c & \cdots & \cdots \\
\cos b\sin c & \cdots & \cdots \\
-\sin b & \cos b\sin a & \cos b\cos a
\end{pmatrix}. R = cos b cos c cos b sin c − sin b ⋯ ⋯ cos b sin a ⋯ ⋯ cos b cos a .
与 R ( q ) R(q) R ( q ) 逐元素比较:
sin b = − R 31 = 2 ( w y − x z ) , a = atan2 ( R 32 , R 33 ) = atan2 ( 2 ( w x + y z ) , 1 − 2 ( x 2 + y 2 ) ) , c = atan2 ( R 21 , R 11 ) = atan2 ( 2 ( w z + x y ) , 1 − 2 ( y 2 + z 2 ) ) , \begin{aligned}
\sin b &= -R_{31} = 2(wy - xz), \\
a &= \operatorname{atan2}(R_{32}, R_{33}) = \operatorname{atan2}\big(2(wx + yz),\ 1 - 2(x^2 + y^2)\big), \\
c &= \operatorname{atan2}(R_{21}, R_{11}) = \operatorname{atan2}\big(2(wz + xy),\ 1 - 2(y^2 + z^2)\big),
\end{aligned} sin b a c = − R 31 = 2 ( w y − x z ) , = atan2 ( R 32 , R 33 ) = atan2 ( 2 ( w x + y z ) , 1 − 2 ( x 2 + y 2 ) ) , = atan2 ( R 21 , R 11 ) = atan2 ( 2 ( w z + x y ) , 1 − 2 ( y 2 + z 2 ) ) ,
在 cos b > 0 \cos b > 0 cos b > 0 时成立(把 atan2 的两个参数同除以 cos b \cos b cos b 不改变角度)。这些就是 to_euler_external_XYZ 中的表达式,其中 b = arcsin ( ⋅ ) ∈ [ − π / 2 , π / 2 ] b = \arcsin(\cdot) \in [-\pi/2, \pi/2] b = arcsin ( ⋅ ) ∈ [ − π /2 , π /2 ] 。to_euler_external_YZX 与 to_euler_external_ZYX 是把轴置换后的同一推导;非循环置换会翻转反正弦内以及 atan2 分子的符号。模块在提取前会归一化 q,因此 R ( q ) R(q) R ( q ) 对角线所用的恒等式 w 2 + x 2 + y 2 + z 2 = 1 w^2 + x^2 + y^2 + z^2 = 1 w 2 + x 2 + y 2 + z 2 = 1 在舍入误差范围内成立。
万向节锁。 当 cos b = 0 \cos b = 0 cos b = 0 时,元素 R 32 , R 33 , R 21 , R 11 R_{32}, R_{33}, R_{21}, R_{11} R 32 , R 33 , R 21 , R 11 全部为零,第一次与第三次旋转作用于同一个物理轴。在 XYZ 情形下,对 b = π / 2 b = \pi/2 b = π /2 ,
R Z ( c ) R Y ( π 2 ) R X ( a ) = R Y ( π 2 ) R X ( a − c ) , R_Z(c)\,R_Y(\tfrac\pi2)\,R_X(a) = R_Y(\tfrac\pi2)\,R_X(a - c), R Z ( c ) R Y ( 2 π ) R X ( a ) = R Y ( 2 π ) R X ( a − c ) ,
因此只有 a − c a - c a − c 是确定的(b = − π / 2 b = -\pi/2 b = − π /2 时为 a + c a + c a + c )。常规做法是令 c = 0 c = 0 c = 0 ,再从剩余元素恢复 a a a ,此处为 a = atan2 ( R 12 , R 22 ) a = \operatorname{atan2}(R_{12}, R_{22}) a = atan2 ( R 12 , R 22 ) 。实现在 ∣ sin b ∣ ≥ 0.9998 |\sin b| \ge 0.9998 ∣ sin b ∣ ≥ 0.9998 (约 88.85 ∘ 88.85^\circ 88.8 5 ∘ )时切换到特殊分支:打印警告,令 b = ± π / 2 b = \pm\pi/2 b = ± π /2 、c = 0 c = 0 c = 0 ,但对 XYZ 用 atan2 ( R 13 , R 22 ) \operatorname{atan2}(R_{13}, R_{22}) atan2 ( R 13 , R 22 ) 求第一个角,对其他顺序则沿用常规分支的公式(其参数接近零)。两者都没有分离出 a ∓ c a \mp c a ∓ c ,因此该分支目前返回的角不能复现输入;参见已知偏差 。
构造。 默认顺序 "XYZ" 下的 from_euler(roll, pitch, yaw) 以闭式展开 q X ( roll ) q Y ( pitch ) q Z ( yaw ) q_X(\text{roll})\,q_Y(\text{pitch})\,q_Z(\text{yaw}) q X ( roll ) q Y ( pitch ) q Z ( yaw ) 。记 c r = cos roll 2 c_r = \cos\frac{\text{roll}}{2} c r = cos 2 roll 、s r = sin roll 2 s_r = \sin\frac{\text{roll}}{2} s r = sin 2 roll 等,两次应用 Hamilton 积得到
q X q Y q Z = ( c r c p c y − s r s p s y , s r c p c y + c r s p s y , c r s p c y − s r c p s y , c r c p s y + s r s p c y ) , q_X q_Y q_Z = \big(c_r c_p c_y - s_r s_p s_y,\ \ s_r c_p c_y + c_r s_p s_y,\ \ c_r s_p c_y - s_r c_p s_y,\ \ c_r c_p s_y + s_r s_p c_y\big), q X q Y q Z = ( c r c p c y − s r s p s y , s r c p c y + c r s p s y , c r s p c y − s r c p s y , c r c p s y + s r s p c y ) ,
即源码中的表达式。to_euler_internal_XYZ 是它的逆。
球面线性插值
单位四元数构成三维球面 S 3 ⊂ R 4 S^3 \subset \mathbb R^4 S 3 ⊂ R 4 ,两个姿态之间的最短路径是一段大圆弧。设 q 1 , q 2 q_1, q_2 q 1 , q 2 为单位四元数,cos Ω = q 1 ⋅ q 2 \cos\Omega = q_1\cdot q_2 cos Ω = q 1 ⋅ q 2 ,0 < Ω < π 0 < \Omega < \pi 0 < Ω < π 。单位向量
q ⊥ = q 2 − cos Ω q 1 sin Ω q_\perp = \frac{q_2 - \cos\Omega\, q_1}{\sin\Omega} q ⊥ = sin Ω q 2 − cos Ω q 1
与 q 1 q_1 q 1 正交,弧为 γ ( φ ) = cos φ q 1 + sin φ q ⊥ \gamma(\varphi) = \cos\varphi\, q_1 + \sin\varphi\, q_\perp γ ( φ ) = cos φ q 1 + sin φ q ⊥ 。在 φ = t Ω \varphi = t\Omega φ = t Ω 处,
γ ( t Ω ) = sin Ω cos t Ω − cos Ω sin t Ω sin Ω q 1 + sin t Ω sin Ω q 2 = sin ( ( 1 − t ) Ω ) sin Ω q 1 + sin ( t Ω ) sin Ω q 2 , \begin{aligned}
\gamma(t\Omega) &= \frac{\sin\Omega\cos t\Omega - \cos\Omega\sin t\Omega}{\sin\Omega}\,q_1 + \frac{\sin t\Omega}{\sin\Omega}\,q_2 \\
&= \frac{\sin\big((1-t)\Omega\big)}{\sin\Omega}\,q_1 + \frac{\sin(t\Omega)}{\sin\Omega}\,q_2 ,
\end{aligned} γ ( t Ω ) = sin Ω sin Ω cos t Ω − cos Ω sin t Ω q 1 + sin Ω sin t Ω q 2 = sin Ω sin ( ( 1 − t ) Ω ) q 1 + sin Ω sin ( t Ω ) q 2 ,
这就是 slerp 计算的公式。它以恒定角速度运动,旋转角随 t t t 线性增长。由于双重覆盖,当 q 1 ⋅ q 2 < 0 q_1\cdot q_2 < 0 q 1 ⋅ q 2 < 0 时 slerp 先把 q 2 q_2 q 2 替换为 − q 2 -q_2 − q 2 ,使 Ω ≤ π / 2 \Omega \le \pi/2 Ω ≤ π /2 ,旋转走较短的一侧。它在调用 acos 前把 cos Ω \cos\Omega cos Ω 截断到 [ − 1 , 1 ] [-1, 1] [ − 1 , 1 ] ,因为舍入可能使两个单位四元数的点积略微超过 1。
幂
pow_by_int 利用 q 2 m = ( q m ) 2 q^{2m} = (q^m)^2 q 2 m = ( q m ) 2 与 q 2 m + 1 = q ( q m ) 2 q^{2m+1} = q\,(q^m)^2 q 2 m + 1 = q ( q m ) 2 ,只需要结合律;所有因子都是同一个 q q q 的幂,因此彼此可交换。负指数利用 q − n = ( q − 1 ) n q^{-n} = (q^{-1})^n q − n = ( q − 1 ) n 。
pow_by_T 使用极形式。每个非零 q q q 都可写成 q = ∣ q ∣ ( cos φ + n ^ sin φ ) q = |q|\,(\cos\varphi + \hat{\mathbf n}\sin\varphi) q = ∣ q ∣ ( cos φ + n ^ sin φ ) ,其中 φ ∈ [ 0 , π ] \varphi \in [0, \pi] φ ∈ [ 0 , π ] ,n ^ \hat{\mathbf n} n ^ 为单位向量,而 1 1 1 与 n ^ \hat{\mathbf n} n ^ 张成的空间是 C \mathbb C C 的一个副本(因为 n ^ 2 = − 1 \hat{\mathbf n}^2 = -1 n ^ 2 = − 1 )。于是棣莫弗公式定义了
q t = ∣ q ∣ t ( cos t φ + n ^ sin t φ ) . q^t = |q|^t\,\big(\cos t\varphi + \hat{\mathbf n}\sin t\varphi\big). q t = ∣ q ∣ t ( cos tφ + n ^ sin tφ ) .
实现以 φ = arcsin ∣ u ∣ / ∣ q ∣ \varphi = \arcsin\lvert\mathbf u\rvert/|q| φ = arcsin ∣ u ∣ /∣ q ∣ 计算角度,只在 φ ≤ π / 2 \varphi \le \pi/2 φ ≤ π /2 时正确,即标量部非负时;参见已知偏差 。
设计决策
泛型分量与逐函数约束
问题。 四元数在精确整数上(数论、验证恒等式)和在 Double 上(几何)都很有用,而 luna-generic 生态希望由一个类型同时满足两者。
选项。 仅支持 Double 的类型;由单一“实数”trait 参数化的类型;或对 T 不加约束、每个函数只要求自己所用 trait 的类型。
选择。 最后一种。Quaternion[T] 不加约束;+ 需要 Add,* 需要 Mul + Sub + Add,square_len 需要 Mul + Add,只有涉及平方根或三角函数的函数才要求 DoubleConvert。这遵循 Luna-Flow 的原则——代码依赖能表达其需求的最小 trait 组合,也使 Quaternion[Int] 能精确验证代数恒等式。
标量–向量存储
该类型存储 r : T 与 vec : (T, T, T),而不是四个独立字段。上文的乘积、共轭、逆元与旋转都是以标量–向量形式推导的,因此代码读起来就像推导本身(在 vec 上使用 dot、cross、scale)。类型是抽象的,表示形式因而可以自由改变;代价是目前还没有分量访问器。
为什么是 Ring 而不是 Field
luna-generic 的 Field 是 Ring + Inverse + Div,针对它编写的泛型代码有权依赖交换域的定律,例如 a b = b a a b = b a ab = ba 或 a / b ⋅ c = a c / b a / b \cdot c = a c / b a / b ⋅ c = a c / b 。四元数满足所有环公理且有逆元,但不满足交换律,因此声明 Field 会让这类代码悄无声息地算出错误结果。所以本包实现 Zero、One、AddMonoid、MulMonoid、Semiring 与 Ring(对交换的 T,它们在 H \mathbb H H 上都成立),外加运算 trait Inverse 与 Conjugate,到此为止。除法仍可通过 Div 与 left_div 使用,但泛型代码必须显式要求它。
/ 是右除
问题。 乘法不可交换时,q / r 必须选定一侧。0.2.0 之前它计算 r − 1 q r^{-1} q r − 1 q 。
选择。 自 0.2.0 起,q / r 为 q r − 1 q\,r^{-1} q r − 1 ,即 x r = q x r = q x r = q 的解。这是除环的通常约定,并让 Div 与 Inverse 保持与 luna-generic 标量类型相同的一致关系:a / b == a * b.inv()。对旋转而言它也读起来自然:to / from 是在 from 之后施加便得到 to 的旋转。另一种商仍以 left_div 提供,名字表明逆元位于哪一侧。这一变更是破坏性的,已记录在变更日志中。
Double 桥接
平方根与三角函数只存在于 Double 上,而本包只依赖 luna-generic,后者没有分析类 trait。DoubleConvert 是一个双方法 trait,把分量转换到 Double 再转换回来,使 magnitude、normalize、slerp、pow_by_T 与欧拉角转换在签名上保持泛型,同时在 Double 中计算。它为 Double(恒等映射)与 Int(from_double 截断)实现。该 trait 声明为 pub 而非 pub(open),因此其他包无法为自己的类型实现它。
精确的结构相等
Eq 与 Hash 由派生得到,对分量做精确比较。“作为旋转”相等(q ∼ − q q \sim -q q ∼ − q )或“在容差内”相等取决于具体应用,因此交由调用者决定(教程给出了一种做法)。精确相等还使 Eq 与 Hash 保持一致。
MoonBit 0.10 不再把 trait 实现隐式变为方法。src/extends.mbt 提升了运算符、equal、hash、to_string、conjugate、zero、one 与 inv,它们都是四元数上的自然运算。方法形式 not_equal、hash_combine、output、default 与 to_repr 为兼容而保留,但已弃用并隐藏。
正确性与不变量
代数定律
在具有精确算术的交换环 T(Int、BigInt)上,实现满足:
环公理:( H , + , 0 ) (\mathbb H, +, 0) ( H , + , 0 ) 是阿贝尔群,( H , ⋅ , 1 ) (\mathbb H, \cdot, 1) ( H , ⋅ , 1 ) 是幺半群,且 ⋅ \cdot ⋅ 对 + + + 满足双侧分配律;
p q ‾ = q ˉ p ˉ \overline{pq} = \bar q\,\bar p pq = q ˉ p ˉ 、q ˉ ˉ = q \bar{\bar q} = q q ˉ ˉ = q 、q q ˉ = q ˉ q = ∣ q ∣ 2 q\bar q = \bar q q = |q|^2 q q ˉ = q ˉ q = ∣ q ∣ 2 以及 ∣ p q ∣ 2 = ∣ p ∣ 2 ∣ q ∣ 2 |pq|^2 = |p|^2|q|^2 ∣ pq ∣ 2 = ∣ p ∣ 2 ∣ q ∣ 2 ;
对精确除法(T 为域,例如有理数):q q − 1 = q − 1 q = 1 q\,q^{-1} = q^{-1}q = 1 q q − 1 = q − 1 q = 1 、( q / r ) r = q (q / r)\,r = q ( q / r ) r = q 以及 r ( q . l e f t _ d i v ( r ) ) = q r\,(q.\mathrm{left\_div}(r)) = q r ( q . left_div ( r )) = q 。
本包的测试在整数样本上检查环公理、不可交换性以及两个除法恒等式。
浮点误差
在 Double 上,这些定律在舍入误差范围内成立。p q pq pq 的每个分量是 p p p 的分量与 q q q 的分量的四个乘积之和,计算时乘积有四次舍入、加法有三次舍入。对这类内积的标准界3 3 N. J. Higham,Accuracy and Stability of Numerical Algorithms ,第 2 版,SIAM 2002,§3.1:对任意求和顺序,∣ f l ( x T y ) − x T y ∣ ≤ γ n ∣ x ∣ T ∣ y ∣ \lvert\mathrm{fl}(x^{\mathsf T}y) - x^{\mathsf T}y\rvert \le \gamma_n \lvert x\rvert^{\mathsf T}\lvert y\rvert ∣ fl ( x T y ) − x T y ∣ ≤ γ n ∣ x ∣ T ∣ y ∣ 。 逐分量给出
∣ f l ( p q ) m − ( p q ) m ∣ ≤ γ 4 ∑ l = 1 4 ∣ p l ∣ ∣ q σ m ( l ) ∣ ≤ γ 4 ∣ p ∣ ∣ q ∣ , γ n = n u 1 − n u , \lvert \mathrm{fl}(pq)_m - (pq)_m \rvert \le \gamma_4 \sum_{l=1}^{4} \lvert p_l\rvert\,\lvert q_{\sigma_m(l)}\rvert \le \gamma_4\, |p|\,|q|, \qquad \gamma_n = \frac{n u}{1 - n u}, ∣ fl ( pq ) m − ( pq ) m ∣ ≤ γ 4 l = 1 ∑ 4 ∣ p l ∣ ∣ q σ m ( l ) ∣ ≤ γ 4 ∣ p ∣ ∣ q ∣ , γ n = 1 − n u n u ,
其中 σ m \sigma_m σ m 是第 m m m 个分量中出现的 q q q 的分量置换,第二个不等式即 Cauchy–Schwarz 不等式。对四个分量求和得 ∥ f l ( p q ) − p q ∥ ≤ 2 γ 4 ∣ p ∣ ∣ q ∣ \lVert\mathrm{fl}(pq) - pq\rVert \le 2\gamma_4 |p||q| ∥ fl ( pq ) − pq ∥ ≤ 2 γ 4 ∣ p ∣∣ q ∣ ,于是由范数的可乘性
( 1 − 2 γ 4 ) ∣ p ∣ ∣ q ∣ ≤ ∣ f l ( p q ) ∣ ≤ ( 1 + 2 γ 4 ) ∣ p ∣ ∣ q ∣ . (1 - 2\gamma_4)\,|p|\,|q| \le |\mathrm{fl}(pq)| \le (1 + 2\gamma_4)\,|p|\,|q| . ( 1 − 2 γ 4 ) ∣ p ∣ ∣ q ∣ ≤ ∣ fl ( pq ) ∣ ≤ ( 1 + 2 γ 4 ) ∣ p ∣ ∣ q ∣.
因此 n n n 个单位四元数之积的范数为 1 + δ 1 + \delta 1 + δ ,一阶意义下 ∣ δ ∣ ≲ 8 n u |\delta| \lesssim 8nu ∣ δ ∣ ≲ 8 n u ,其中 u = 2 − 53 ≈ 1.1 × 10 − 16 u = 2^{-53} \approx 1.1 \times 10^{-16} u = 2 − 53 ≈ 1.1 × 1 0 − 16 ;实践中误差会部分抵消,漂移更小。由于 rotate 假定 ∣ q ∣ = 1 |q| = 1 ∣ q ∣ = 1 ,漂移后的四元数会把向量缩放约 1 + 2 δ 1 + 2\delta 1 + 2 δ 倍。长的乘积链应定期调用 normalize;其结果的范数与 1 1 1 相差在几个 u u u 之内(例如 1 + 2 i + 3 j + 4 k 1 + 2i + 3j + 4k 1 + 2 i + 3 j + 4 k 归一化后计算得到的范数为 0.9999999999999999 0.9999999999999999 0.9999999999999999 )。
本包不检查输入;退化情形遵循 T 的算术:
输入 DoubleInt除数为零时的 inv、/、left_div NaN 分量 运行时陷阱(整数除以零) 零的 normalize 原样返回零 原样返回零 零的 pow_by_T 原样返回零 原样返回零 轴为零的 from_axis_angle NaN 向量部 运行时陷阱 输入为零的 slerp 零输入未经归一化直接使用 没有意义
在 Int 上,除非 ∣ q ∣ 2 = 1 |q|^2 = 1 ∣ q ∣ 2 = 1 ,inv 均为零,且所有经由 DoubleConvert 的函数都会截断。
阈值
当 cos Ω > 0.9995 \cos\Omega > 0.9995 cos Ω > 0.9995 ,即 Ω < 0.0316 \Omega < 0.0316 Ω < 0.0316 rad 时,slerp 退回归一化线性插值。此时 sin Ω < 0.032 \sin\Omega < 0.032 sin Ω < 0.032 ,除以它会放大权重中的舍入误差;而在阈值处,线性路径与圆弧在 S 3 S^3 S 3 上的偏差小于 5.1 × 10 − 7 5.1 \times 10^{-7} 5.1 × 1 0 − 7 rad(约合旋转角 10 − 6 10^{-6} 1 0 − 6 rad),低于阈值时更小。
欧拉角转换把 ∣ sin b ∣ ≥ 0.9998 |\sin b| \ge 0.9998 ∣ sin b ∣ ≥ 0.9998 视为万向节锁。在此把 b b b 取整为 ± π / 2 \pm\pi/2 ± π /2 会使中间角产生最多 π / 2 − arcsin 0.9998 ≈ 0.020 \pi/2 - \arcsin 0.9998 \approx 0.020 π /2 − arcsin 0.9998 ≈ 0.020 rad 的误差。
复杂度
每个运算都是对四个分量的 O ( 1 ) O(1) O ( 1 ) 操作:一次 Hamilton 积需要 16 次乘法与 12 次加法,rotate 需要 18 次乘法与 12 次加法。pow_by_int(n) 需要 O ( log ∣ n ∣ ) O(\log |n|) O ( log ∣ n ∣ ) 次乘积和同阶的递归深度。
已知偏差
以下是当前实现与上文数学的偏差。它们在此明确记录而非隐藏,并已报告待修复:
"ZYX" 或 "YZX" 下的 from_euler 并不计算轴旋转的乘积;结果不是单位四元数。"ZXY" 按位置取角,而 "YXZ" 按轴取角。
to_euler_external_XZY(以及由此而来的 to_euler_internal_YZX)计算的是内旋 X-Y-Z 的提取,而非所指定的序列。
欧拉角提取的万向节锁分支没有分离出自由角(见上文),并用 println 输出警告。
pow_by_T 用 asin 计算 φ \varphi φ ,其值不超过 π / 2 \pi/2 π /2 ;改用 atan2 ( ∣ u ∣ , w ) \operatorname{atan2}(\lvert\mathbf u\rvert, w) atan2 (∣ u ∣ , w ) 即可覆盖 [ 0 , π ] [0, \pi] [ 0 , π ] 。
被否决的方案
实现 Field。 被否决,因为泛型 Field 代码可能依赖交换律(见上文)。
保留 / 为左除。 它与 a / b == a * b.inv() 以及旋转中 to / from 的通常读法相矛盾。
仅支持 Double 的类型。 它会失去 Int 上的精确算术,以及其他标量类型上的 luna-generic 环结构。
存储四个具名字段或旋转矩阵。 四个字段会掩盖公式所用的标量–向量结构;矩阵有九个元素,需要重新正交化而非廉价的归一化,插值也没那么简单。
在 Eq 中按容差比较。 基于容差的 == 不具有传递性,也无法与 Hash 保持一致。
边界
本包刻意不做以下事情:
提供旋转矩阵、指数与对数映射或四元数微积分(对偶四元数、姿态的导数);
检查输入或返回 Result:退化输入遵循 T 的算术,rotate 相信其接收者是单位四元数;
实现 luna-generic 的 Field 或 Num,或让四元数成为有序类型;
依赖 arithmetic、linear-algebra 或 luna-complex;它唯一的 Luna-Flow 依赖是 luna-generic;
选定比较旋转所用的容差;这留给调用者决定。