DeviceMotion.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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? = nil
  29. var beginningQuaternion: 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. //get the first quaternion that we started with
  83. if strongSelf.beginningQuaternion == nil {
  84. strongSelf.beginningQuaternion = data.attitude.quaternion
  85. }
  86. var currentEuler = strongSelf.quaternionToEuler(qIn: data.attitude.quaternion)
  87. // if we panned we will have a lastEuler value that we need to take as beginning angle
  88. if let lastEulerAngle = strongSelf.lastEulerAngle {
  89. //we get the devicemotion diff between start and currentangle
  90. let beginningEuler = strongSelf.quaternionToEuler(qIn: strongSelf.beginningQuaternion!)
  91. let diffYaw = currentEuler.yaw - beginningEuler.yaw
  92. let diffPitch = currentEuler.pitch - beginningEuler.pitch
  93. let diffRoll = currentEuler.roll - beginningEuler.roll
  94. //and add that to the angle that we had after we lifted our finger
  95. currentEuler.yaw = lastEulerAngle.yaw + diffYaw
  96. currentEuler.pitch = lastEulerAngle.pitch + diffPitch
  97. currentEuler.roll = lastEulerAngle.roll + diffRoll
  98. }
  99. strongSelf.delegate?.deviceMotionHasAttitude(deviceMotion:strongSelf, pitch:currentEuler.pitch, yaw:currentEuler.yaw, roll:currentEuler.roll)
  100. }
  101. }
  102. }
  103. @objc func lastAngle(yaw: Double, pitch: Double, roll: Double) {
  104. if lastEulerAngle == nil {
  105. lastEulerAngle = EulerAngles()
  106. }
  107. lastEulerAngle?.yaw = yaw
  108. lastEulerAngle?.pitch = pitch
  109. lastEulerAngle?.roll = roll
  110. }
  111. @objc func stopDeviceMotion() {
  112. if motion.isDeviceMotionActive {
  113. beginningQuaternion = nil
  114. lastEulerAngle = nil
  115. motion.stopDeviceMotionUpdates()
  116. }
  117. }
  118. }