core API

このページでは、パッケージ Luna-Flow/quaternion(ソースルート src)のすべての公開項目を用途別にまとめます。シグネチャは生成されたインターフェースが正となります。例では本パッケージを既定の別名 @quaternion で、luna-generic を @lf_alg でインポートします。

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 を要求します。Num は luna-generic のすべての数値型(Int、Int64、Double など)が実装しています。回転としては恒等回転です。

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 のメソッドです。id より少ない trait で済みます。

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~) は、ラジアン単位の 3 つの角から四元数を作ります。

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

order の既定値は "XYZ" です。座標軸の周りに α\alpha だけ回転する四元数を qX(α)q_X(\alpha)、qY(α)q_Y(\alpha)、qZ(α)q_Z(\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 を適用します。

4 つの演算子はメソッドとしても使えます(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 を、中間の逆元を作らず成分ごとに 1 回の除算で計算します。零四元数で割ると 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 つの商の差は 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) は 4 成分のユークリッド内積 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 で 3 次元ベクトル 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 を使い、除算を必要としません。この式が qvq−1q v q^{-1} と等しいのは ∣q∣=1|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 の回転をラジアン単位の 3 つの角に分解します。

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 は互いに異なる 3 軸を指定し、既定値は "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、3 番目の角を 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(open) ではなく pub として宣言されているため、このパッケージ外のコードは実装できません。上記の関数で使える成分型は 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)(== 演算子)は 4 つの成分を厳密に比較します。

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

Double の成分では IEEE の等価性です:0.0 == -0.0 であり、NaN 成分を持つ四元数は自分自身と等しくありません。

Quaternion::hash

q.hash() は equal と整合するように 4 つの成分をハッシュします。

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

trait の実装

Quaternion[T] は次の trait を実装します。

TraitT への要件意味
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 積が結合的なのは、スカラーが可換環をなす場合だけです。四元数の乗法は可換でなく、luna-generic の Field は可換体を想定しているため、@lf_alg.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)