ARKit や SceneKit で使用している simd について - クォータニオン編 -
今回はクォータニオンについて。
確かこの Blog で SCNQuaternion もちゃんと紹介していないので、まずはクォータニオンについてのご説明。
クォータニオンのざっくり説明
クォータニオンは四元数と呼ばれ、スカラー(実数)部分と3つの虚数部分をまとめたベクトルによって定義され、
3次元でオブジェクトの回転の方法としてグラフィックプログラミングでよく使用される。
最大の利点は、行列によるオイラー角の回転ではジンバルロックが起る可能性があり、クォータニオンでは起きないところ。
simd でのクォータニオンの型と使用方法
simd では float の simd_quatf、double の simd_quatd が用意されていて以下のように書くことができる。
simd_quatf や simd_quatd は実部や虚部を返す型となっている。
let ix:Float = 1.0 let iy:Float = 4.0 let iz:Float = 8.0 let r:Float = 9.0 let q = simd_quatf(ix: ix, iy: iy, iz: iz, r: r) // simd_quatf(real: 9.0, imag: float3(1.0, 4.0, 8.0))
SceneKit でクォータニオンを渡す場合は、単位クォータニオンとして設定する必要がある。
単位クォータニオンとは長さが1となるもので、以下の方法で計算できる。
sqrt(ix * ix + iy * iy + iz * iz + r * r)
ちなみに、SCNQuaternion の中身は SCNVector4 で、xx + yy + zz + ww == 1 となる必要がある。
クォータニオンは、通常の行列による回転よりいくつか利点があり、
float の 3×3 行列は 48 バイト使用するが、クォータニオンは 16 バイトになる。
また、クォータニオンでは1回の回転は計算が複雑であるため、行列を使用した場合よりも少し遅くなるが、アクションを組み合わせると四元数は最大 30% 速くなる。
以下のコードはクォータニオンの一般的な使用例。
axis で y 方向に回転する軸を設定して、angle からラジアンで π 分(180度)回転させている。
axis は normalize して正規化しないとジオメトリがぺったんこになるよ。
let axis = simd_float3(x: 0, y: 20, z: 0) let q = simd_quatf(angle: .pi, axis: simd_normalize(axis)) // retrieve the ship node let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! ship.simdLocalRotate(by: q)
初期化
すでにいくつか紹介しているが設定できるものをまとめてみる。
let ix:Float = 0.0 let iy:Float = 2.0 let iz:Float = 0.0 let r:Float = 7.54979e-08 let i = simd_normalize(simd_float3(x: ix, y: iy, z: iz)) let f4x4 = simd_float4x4([ float4(-1.0, 0.0, -1.50996e-07, 0.0), float4(0.0, 1.0, 0.0, 0.0), float4(1.50996e-07, 0.0, -1.0, 0.0), float4(0.0, 0.0, 0.0, 1.0) ]) let f3x3 = simd_float3x3([ float3(-1.0, 0.0, -1.50996e-07), float3(0.0, 1.0, 0.0), float3(1.50996e-07, 0.0, -1.0) ]) let f3_1 = float3(0.0, 0.0, 1.0) let f3_2 = float3(1.0, 0.0, 0.0) // ------------------- // 初期化設定 // ------------------- // 回転: 0 度 let q0 = simd_quatf() // 回転: 180 度 let q1 = simd_quatf(vector: float4(0.0, 1.0, 0.0, r)) let q2 = simd_quatf(f4x4) let q3 = simd_quatf(f3x3) let q4 = simd_quatf(angle: .pi, axis: i) let q5 = simd_quatf(ix: 0.0, iy: 1.0, iz: 0.0, r: r) let q6 = simd_quatf(real: r, imag: i) // 回転: 90 度 // (原点を中心に2点間から角度を取りクォータニオンを設定する) let q7 = simd_quatf(from:f3_1, to:f3_2)
まとめ
SCNQuaternion の場合は値を計算するのが面倒だが、 simd_quatf や simd_quatd の場合は初期化をわりと簡単に設定できるものが用意されているので使用する機会があるのではないかと思っている。
気力があれば、いつか simd のクォータニオンで使用できる関数の方も紹介する。