ARLocationOrientation.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Events;
  5. // ReSharper disable UnusedMember.Global
  6. namespace ARLocation
  7. {
  8. using Utils;
  9. /// <summary>
  10. /// This component should be placed on the "ARLocationRoot" GameObject (which should be a child of the
  11. /// "AR Session Origin") for correctly aligning the coordinate system to the north/east geographical lines.
  12. /// </summary>
  13. [DisallowMultipleComponent]
  14. [HelpURL("https://http://docs.unity-ar-gps-location.com/guide/#arlocationorientation")]
  15. public class ARLocationOrientation : Singleton<ARLocationOrientation>
  16. {
  17. [Serializable]
  18. public class OnBeforeOrientationUpdatedEvent : UnityEvent<float> {}
  19. [Header("Update Settings")]
  20. [Tooltip("The maximum number of orientation updates. The updates will be paused after this amount. Zero means there is no limit and " +
  21. "the updates won't be paused automatically.")]
  22. public uint MaxNumberOfUpdates = 4;
  23. /// <summary>
  24. /// Only update after measuring the heading N times, and take the average.
  25. /// </summary>
  26. [Tooltip("Only update after measuring the heading N times, and take the average."), Range(1, 500)]
  27. [Header("Averaging")]
  28. public int AverageCount = 150;
  29. /// <summary>
  30. /// If set to true, use raw heading values until measuring the first average.
  31. /// </summary>
  32. [Tooltip("If set to true, use raw heading values until measuring the first average.")]
  33. public bool UseRawUntilFirstAverage = true;
  34. /// <summary>
  35. /// The smoothing factor. Zero means disabled. Values around 100 seem to give good results.
  36. /// </summary>
  37. [Tooltip("The smoothing factor. Zero means disabled.")]
  38. [Header("Smoothing")]
  39. [Range(0.0f, 1.0f)]
  40. public float MovementSmoothingFactor = 0.015f;
  41. /// <summary>
  42. /// A custom offset to the device-calculated true north direction.
  43. /// </summary>
  44. [Tooltip("A custom offset to the device-calculated true north direction. When set to a value other than zero, the device's true north will be ignored, and replaced by the " +
  45. "magnetic heading added to this offset.")]
  46. [Header("Calibration")]
  47. public float TrueNorthOffset;
  48. [Tooltip("If true, apply a tilt-compensation algorithm on Android devices. Only disable this if you run into any issues.")]
  49. public bool ApplyCompassTiltCompensationOnAndroid = true;
  50. [Tooltip("This is the low pass filter factor applied to the heading values to reduce jitter. A zero value disables the low-pass filter, while a value" +
  51. " of 1 will make the filter block all value changes. Not applied on iOS, only on Android when the tilt-compensation is enabled.")]
  52. [Range(0, 1)]
  53. public double LowPassFilterFactor = 0.9;
  54. [Header("Events")]
  55. [Tooltip("Called after the orientation has been updated.")]
  56. public UnityEvent OnOrientationUpdated = new UnityEvent();
  57. [Tooltip("Called just before the orientation has been updated.")]
  58. public OnBeforeOrientationUpdatedEvent OnBeforeOrientationUpdated = new OnBeforeOrientationUpdatedEvent();
  59. ARLocationProvider locationProvider;
  60. private int updateCounter;
  61. private List<float> values = new List<float>();
  62. private bool isFirstAverage = true;
  63. private float targetAngle;
  64. private bool isChangingOrientation;
  65. private Transform mainCameraTransform;
  66. private bool waitingForARTracking;
  67. /// <summary>
  68. /// Restarts the orientation tracking.
  69. /// </summary>
  70. public void Restart()
  71. {
  72. isFirstAverage = true;
  73. updateCounter = 0;
  74. values = new List<float>();
  75. targetAngle = 0;
  76. isChangingOrientation = false;
  77. targetAngle = mainCameraTransform ?
  78. mainCameraTransform.rotation.eulerAngles.y : 0;
  79. }
  80. // Use this for initialization
  81. void Start()
  82. {
  83. // Look for the LocationProvider
  84. locationProvider = ARLocationProvider.Instance;
  85. mainCameraTransform = ARLocationManager.Instance.MainCamera.transform;
  86. targetAngle = mainCameraTransform.rotation.eulerAngles.y;
  87. if (LowPassFilterFactor > 0)
  88. {
  89. locationProvider.Provider.SetCompassLowPassFactor(LowPassFilterFactor);
  90. }
  91. locationProvider.Provider.ApplyCompassTiltCompensationOnAndroid = ApplyCompassTiltCompensationOnAndroid;
  92. if (ARLocationManager.Instance.WaitForARTrackingToStart)
  93. {
  94. waitingForARTracking = true;
  95. ARLocationManager.Instance.OnARTrackingStarted(() =>
  96. {
  97. waitingForARTracking = false;
  98. });
  99. }
  100. // Register compass update delegate
  101. locationProvider.OnCompassUpdatedEvent(OnCompassUpdatedHandler);
  102. }
  103. private void OnCompassUpdatedHandler(HeadingReading newHeading, HeadingReading lastReading)
  104. {
  105. if (waitingForARTracking) return;
  106. if (!newHeading.isMagneticHeadingAvailable)
  107. {
  108. Debug.LogWarning("[AR+GPS][ARLocationOrientation]: Magnetic heading data not available.");
  109. return;
  110. }
  111. if (MaxNumberOfUpdates > 0 && updateCounter >= MaxNumberOfUpdates)
  112. {
  113. return;
  114. }
  115. var trueHeading = (Mathf.Abs(TrueNorthOffset) > 0.000001f) ? newHeading.magneticHeading + TrueNorthOffset : newHeading.heading;
  116. float currentCameraHeading = mainCameraTransform.rotation.eulerAngles.y;
  117. float value = Misc.GetNormalizedDegrees(currentCameraHeading - ((float)trueHeading));
  118. if (Mathf.Abs(value) < 0.0000001f)
  119. {
  120. return;
  121. }
  122. // If averaging is not enabled
  123. if (AverageCount <= 1)
  124. {
  125. if (updateCounter == 0)
  126. {
  127. transform.localRotation = Quaternion.AngleAxis(value, Vector3.up);
  128. TrySetOrientation(value, true);
  129. }
  130. else
  131. {
  132. TrySetOrientation(value);
  133. }
  134. return;
  135. }
  136. values.Add(value);
  137. if (updateCounter == 0 && values.Count == 1)
  138. {
  139. TrySetOrientation(value, true);
  140. return;
  141. }
  142. if (isFirstAverage && UseRawUntilFirstAverage)
  143. {
  144. TrySetOrientation(value, true);
  145. return;
  146. }
  147. if (values.Count >= AverageCount)
  148. {
  149. if (isFirstAverage)
  150. {
  151. isFirstAverage = false;
  152. }
  153. var average = Misc.FloatListAverage(values);
  154. values.Clear();
  155. TrySetOrientation(average);
  156. }
  157. }
  158. private void TrySetOrientation(float angle, bool isFirstUpdate = false)
  159. {
  160. if (isFirstUpdate)
  161. {
  162. targetAngle = angle;
  163. OnBeforeOrientationUpdated?.Invoke(targetAngle);
  164. transform.localRotation = Quaternion.AngleAxis(angle, Vector3.up);
  165. OnOrientationUpdated?.Invoke();
  166. updateCounter++;
  167. return;
  168. }
  169. if (MaxNumberOfUpdates > 0 && updateCounter >= MaxNumberOfUpdates)
  170. {
  171. return;
  172. }
  173. targetAngle = angle;
  174. OnBeforeOrientationUpdated?.Invoke(targetAngle);
  175. isChangingOrientation = true;
  176. updateCounter++;
  177. }
  178. private void Update()
  179. {
  180. if (locationProvider.Provider == null || !locationProvider.Provider.IsCompassEnabled)
  181. {
  182. return;
  183. }
  184. if (Mathf.Abs(transform.rotation.eulerAngles.y - targetAngle) <= 0.001f)
  185. {
  186. if (isChangingOrientation)
  187. {
  188. isChangingOrientation = false;
  189. OnOrientationUpdated?.Invoke();
  190. }
  191. return;
  192. }
  193. var t = 1.0f - Mathf.Pow(MovementSmoothingFactor, Time.deltaTime);
  194. var value = Mathf.LerpAngle(transform.rotation.eulerAngles.y, targetAngle, t);
  195. transform.localRotation = Quaternion.AngleAxis(value, Vector3.up);
  196. }
  197. private void OnDestroy()
  198. {
  199. locationProvider.OnCompassUpdateDelegate -= OnCompassUpdatedHandler;
  200. }
  201. }
  202. }