Apple Engine

Apple, iPhone, iOS, その周辺のことについて

ARKit や SceneKit で使用している simd について - クォータニオンでの関数編 その2 -

今回はクォータニオンで使用する線形補間の関数について。
基本的には球状での回転の補間となる。

用意されている関数は以下のもの。

  • simd_slerp
  • simd_slerp_longest
  • simd_spline
  • simd_bezier

 

ちなみに線形補間に関しては iOS でサンプルアプリがあるのでそちらを参考に。

Rotating a Cube by Transforming Its Vertices | Apple Developer Documentation

 

ちゃんとした説明は WWDC 2018 でも行なっているのでそちらを参照。

developer.apple.com

 

以下、コードを書いていく。
macOS の Swift Playground で試していたので、iOS で使用する場合は NSColor を UIColor に変更するべし。

 

simd_slerp

クォータニオン q0 と q1 の間の最短の弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032736p:plain
simd_slerp

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_slerp_longest

クォータニオン q0 と q1 の間からもっとも遠い弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032817p:plain
simd_slerp_longest (正面だとわかりづらいので斜めに始点移動)

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp_longest(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_spline

クォータニオン q1 と q2 の間を補間し、q0 は始点であり、q3 は終点。
回転するシーケンス間をスムーズに補間する場合は slerp ではなくこちらを使う。

f:id:x67x6fx74x6f:20190105032905p:plain
simd_spline

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
               axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 1 ... rotations.count - 3 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_spline(rotations[i - 1],
                            rotations[i],
                            rotations[i + 1],
                            rotations[i + 2],
                            t)

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_bezier

De Casteljau アルゴリズムを使用しクォータニオン q0、q3 をコントロールポイントとして扱う3次ベジェ曲線の補間を行う。
補間の終点が q0 か q3 になり、曲線は q1 または q2 を通過しない。

標準的なベジェ曲線のように凸包のプロパティが球上には保持されないので注意。

f:id:x67x6fx74x6f:20190105032932p:plain
simd_bezier

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0)))
]

for t: Float in stride(from: 0, to: 1, by: 0.001) {
    let q = simd_bezier(rotations[0],
                        rotations[1],
                        rotations[2],
                        rotations[3],
                        t)

    let node = SCNNode()
    let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
    sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
    node.addChildNode(sphere)
    node.simdPosition = q.act(float3(0,0,5))
    scene.rootNode.addChildNode(node)
}

 

まとめ

クォータニオンでの線形補間を使用した回転は行列での回転より早く処理できる可能性があるため、状況によっては積極的に使用すべきだろう。

 

simd に関しては一通り説明が終わった。
ARKit のアンカーや SceneKit のノードで多くのものを素早く変更する場合に有用な機能ではある。

また、Float、Double など、型の値の取り出しが可能であるため、ARKit や SceneKit 以外にも行列の処理で使用できる状況はあると思われる。