DeviceMotion.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*****************************************************************************
  2. * DeviceMotion.swift
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Carola Nitz <caro # videolan.org>
  9. *
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. import Foundation
  14. import CoreMotion
  15. @objc(VLCDeviceMotionDelegate)
  16. protocol DeviceMotionDelegate:NSObjectProtocol {
  17. func deviceMotionHasAttitude(deviceMotion:DeviceMotion, pitch:Double, yaw:Double, roll:Double)
  18. }
  19. struct EulerAngles {
  20. var yaw: Double = 0
  21. var pitch: Double = 0
  22. var roll: Double = 0
  23. }
  24. @objc(VLCDeviceMotion)
  25. class DeviceMotion:NSObject {
  26. let motion = CMMotionManager()
  27. let sqrt2 = 0.5.squareRoot()
  28. var lastEulerAngle = EulerAngles()
  29. var lastQuaternion: CMQuaternion? = nil
  30. @objc weak var delegate: DeviceMotionDelegate? = nil
  31. private func multQuaternion(q1: CMQuaternion, q2: CMQuaternion) -> CMQuaternion {
  32. var ret = CMQuaternion()
  33. ret.x = q1.x * q2.x - q1.y * q2.y - q1.z * q2.z - q1.w * q2.w
  34. ret.y = q1.x * q2.y + q1.y * q2.x + q1.z * q2.w - q1.w * q2.z
  35. ret.z = q1.x * q2.z + q1.z * q2.x - q1.y * q2.w + q1.w * q2.y
  36. ret.w = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
  37. return ret
  38. }
  39. private func quaternionToEuler(qIn: CMQuaternion) -> EulerAngles {
  40. // Change the axes
  41. var q = CMQuaternion(x:qIn.y, y:qIn.z, z:qIn.x, w:qIn.w)
  42. // Rotation of 90°
  43. let qRot = CMQuaternion(x: 0, y: 0, z: -sqrt2 / 2, w: sqrt2 / 2)
  44. // Perform the rotation
  45. q = multQuaternion(q1:qRot, q2:q)
  46. // Now, we can perform the conversion and manage ourself the singularities
  47. let sqx = q.x * q.x
  48. let sqy = q.y * q.y
  49. let sqz = q.z * q.z
  50. let sqw = q.w * q.w
  51. let unit = sqx + sqy + sqz + sqw // if normalised is one, otherwise is correction factor
  52. let test = q.x * q.y + q.z * q.w
  53. var vp = EulerAngles()
  54. if (test > 0.499 * unit) {
  55. // singularity at north pole
  56. vp.yaw = 2 * atan2(q.x, q.w)
  57. vp.pitch = Double.pi / 2
  58. vp.roll = 0
  59. } else if (test < -0.499 * unit) {
  60. // singularity at south pole
  61. vp.yaw = -2 * atan2(q.x, q.w)
  62. vp.pitch = -Double.pi / 2
  63. vp.roll = 0
  64. } else {
  65. vp.yaw = atan2(2 * q.y * q.w - 2 * q.x * q.z, sqx - sqy - sqz + sqw)
  66. vp.pitch = asin(2 * test / unit)
  67. vp.roll = atan2(2 * q.x * q.w - 2 * q.y * q.z, -sqx + sqy - sqz + sqw)
  68. }
  69. vp.yaw = -vp.yaw * 180 / Double.pi
  70. vp.pitch = vp.pitch * 180 / Double.pi
  71. vp.roll = vp.roll * 180 / Double.pi
  72. return vp
  73. }
  74. @objc func startDeviceMotion() {
  75. if motion.isDeviceMotionAvailable {
  76. motion.gyroUpdateInterval = 1.0 / 60.0 // 60 Hz
  77. motion.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: .main) {
  78. [weak self] (data, error) in
  79. guard let strongSelf = self, let data = data else {
  80. return
  81. }
  82. var euler = strongSelf.quaternionToEuler(qIn: data.attitude.quaternion)
  83. if let lastQuaternion = strongSelf.lastQuaternion {
  84. let lastEuler = strongSelf.quaternionToEuler(qIn: lastQuaternion)
  85. let diffYaw = euler.yaw - lastEuler.yaw
  86. let diffPitch = euler.pitch - lastEuler.pitch
  87. let diffRoll = euler.roll - lastEuler.roll
  88. euler.yaw = strongSelf.lastEulerAngle.yaw + diffYaw
  89. euler.pitch = strongSelf.lastEulerAngle.pitch + diffPitch
  90. euler.pitch = strongSelf.lastEulerAngle.roll + diffRoll
  91. }
  92. strongSelf.delegate?.deviceMotionHasAttitude(deviceMotion:strongSelf, pitch:euler.pitch, yaw:euler.yaw, roll:euler.roll)
  93. }
  94. }
  95. }
  96. @objc func lastAngle(yaw:Double, pitch:Double, roll:Double) {
  97. lastEulerAngle.yaw = yaw
  98. lastEulerAngle.pitch = pitch
  99. lastEulerAngle.roll = roll
  100. }
  101. @objc func stopDeviceMotion() {
  102. if motion.isDeviceMotionActive {
  103. lastQuaternion = motion.deviceMotion?.attitude.quaternion
  104. motion.stopDeviceMotionUpdates()
  105. }
  106. }
  107. }