[Swift]端末のPitch角を-180°~180°で取得する
はじめに
SwiftでCoreMotionを使用すると、Pitch角(端末のx軸回りの回転)が取得可能なのですが、-90°~90°の範囲でしか取得できず困ったので、Quaternionを使用して-180°~180°の範囲でPitch角を取得しました。
環境
- Xcode 14.0.1
- Swift 5.7
問題
前提
端末の角度を取得したい場面があり、以下の公式ドキュメントなどを参考にして端末の角度を取得してみました。
ただし、Yawはそこまで精度が高くないと思われる(Yawは地磁気から推定可能だが、地磁気以外にも残留磁気などが存在し、地磁気のみを正確に取得できないから?)ため、今回はYawについては触れません。
実装してみる
CoreMotionを使用して、端末の角度を取得してみます。以下のコードは、CoreMotionからPitch、Rollを取得するコードの一部です。
ゆっきぶろぐさんのサイトが大変参考になりました。
let motionManager = CMMotionManager()
func start() {
if motionManager.isDeviceMotionAvailable {
motionManager.deviceMotionUpdateInterval = 1 //最大値100Hz(1 / 100)
motionManager.startDeviceMotionUpdates(using: [.xMagneticNorthZVertical], to: OperationQueue.current!, withHandler: {(motion:CMDeviceMotion?, error:Error?) in
self.updateMotionData(deviceMotion: motion)
})
}
}
private func updateMotionData(deviceMotion: CMDeviceMotion?) {
guard let attitude = deviceMotion?.attitude else { return }
//radian -> degree
let pitch = attitude.pitch * 180 / Double.pi //x
let roll = attitude.roll * 180 / Double.pi //y
//let yaw = attitude.yaw * 180 / Double.pi //z
}
取得してみる
Rollについては、-180°~180°の範囲で取得することができましたが、Pitchについては-90°~90°(0°~90°、90°~0°、0°~-90°、-90°~0°)の範囲でしか取得できないことがわかりました。
これはオイラー角のジンバルロックが関係していそうですが詳しくわかりません。。。
とにかく結果としてCMDeviceMotion.attitude.pitch
を使用するとPitchを-180°~180°の範囲で取得できないことがわかりました。
解決策
クォータニオンを使用する
そこで、クォータニオンからPitchを取得してみます。クォータニオンの説明については省略するので、詳しく知りたい方は調べてみてください。
クォータニオンからPitchへの変換は、式(1)で変換することができます。(参考:Conversion between quaternions and Euler angles)
また、arctanは
実装してみる
クォータニオンは、Pitch、Roll、Yawと同様にdeviceMotion.attitude
から取得する可能です。
guard let attitude = deviceMotion?.attitude else { return }
let qw = attitude.quaternion.w
let qx = attitude.quaternion.x
let qy = attitude.quaternion.y
let qz = attitude.quaternion.z
let qpitch = atan2((2 * (qw * qx + qy * qz)), 1 - 2 * (qx * qx + qy * qy))
終わり
クォータニオンを使用することで無事Pitchを-180°~180°の範囲で取得できるようになりました!
Discussion