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 である四元数 です。
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 を要求します。Num は luna-generic のすべての数値型(Int、Int64、Double など)が実装しています。回転としては恒等回転です。
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 のメソッドです。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]
結果は ()です。軸は正規化されていなくても構いませんが、零であってはいけません。零の軸はゼロ除算となり、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" です。座標軸の周りに だけ回転する四元数を 、、 と書きます。受け付ける順序は次を計算します。
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 を適用します。
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 は右除算です:、すなわち の解 。
pub fn[T : Add + Mul + Div + Sub] Quaternion::div(Self[T], Self[T]) -> Self[T]
を、中間の逆元を作らず成分ごとに 1 回の除算で計算します。零四元数で割ると 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]
を計算します。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]
スカラーは の中心に属するため、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) は 4 成分のユークリッド内積 です。
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 で 3 次元ベクトル 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 の回転をラジアン単位の 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)、順序 、角 :固定軸 の周りに 、次に固定軸 の周りに 、最後に固定軸 の周りに 回転するので、; - 内因的(
external=false): の周り、次に回転後の の周り、最後に回転後の の周りに回転するので、。
対応する順序は "XYZ"、"XZY"、"YZX"、"ZYX" です。それ以外の文字列は order error. Please check input format and retry で中断します。中央の角は 、両端の角は に入ります。
中央の角の正弦が (約 )に達すると、分解はジンバルロックに近い状態です。関数は標準出力に UserWarning: Gimbal lock detected... という行を出力し、中央の角をちょうど 、3 番目の角を に設定します。
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(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 を実装します。
| 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 積が結合的なのは、スカラーが可換環をなす場合だけです。四元数の乗法は可換でなく、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) |