GroundHeight.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. using System;
  2. using UnityEngine;
  3. #if !ARGPS_USE_VUFORIA
  4. using UnityEngine.XR.ARFoundation;
  5. using UnityEngine.XR.ARSubsystems;
  6. #endif
  7. #pragma warning disable
  8. using Logger = ARLocation.Utils.Logger;
  9. #pragma warning enable
  10. #if ARGPS_USE_VUFORIA
  11. using Vuforia;
  12. #endif
  13. namespace ARLocation
  14. {
  15. /// <summary>
  16. /// This component will change the Y component of the GameObject's position,
  17. /// so that it is set to the level of the nearest detected ground plane.
  18. /// </summary>
  19. [DisallowMultipleComponent]
  20. public class GroundHeight : MonoBehaviour
  21. {
  22. [Serializable]
  23. public class SettingsData
  24. {
  25. [Range(0, 10)]
  26. public float InitialGroundHeightGuess = 1.4f;
  27. [Range(0, 10)]
  28. public float MinGroundHeight = 0.4f;
  29. [Range(0, 10)]
  30. public float MaxGroundHeight = 3.0f;
  31. [Range(0, 1)]
  32. public float Smoothing = 0.05f;
  33. public float Altitude;
  34. public bool DisableUpdate;
  35. [Range(0, 0.1f)]
  36. public float Precision = 0.005f;
  37. public bool UseArLocationConfigSettings = true;
  38. #if ARGPS_USE_VUFORIA
  39. public float MinHitDistance = 0.5f;
  40. #endif
  41. }
  42. [Serializable]
  43. public class StateData
  44. {
  45. public float CurrentGroundY;
  46. public float CurrentPlaneDistance = -1.0f;
  47. public Vector3 CurrentPlaneCenter;
  48. public bool NeedsUpdate = true;
  49. }
  50. public float CurrentGroundY => state.CurrentGroundY;
  51. public SettingsData Settings = new SettingsData();
  52. private readonly StateData state = new StateData();
  53. private Camera mainCamera;
  54. #if !ARGPS_USE_VUFORIA
  55. private ARPlaneManager arPlaneManager;
  56. private float targetY;
  57. void Start()
  58. {
  59. arPlaneManager = FindObjectOfType<ARPlaneManager>();
  60. var arSessionOrigin = FindObjectOfType<ARSessionOrigin>();
  61. mainCamera = ARLocationManager.Instance.MainCamera;
  62. if (arPlaneManager == null)
  63. {
  64. if (arSessionOrigin == null)
  65. {
  66. Debug.LogWarning("[AR+GPS][GroundHeight#Start]: ARSessionOrigin not present in the scene!");
  67. return;
  68. }
  69. arPlaneManager = arSessionOrigin.gameObject.AddComponent<ARPlaneManager>();
  70. Utils.Misc.RequestPlaneDetectionMode(arPlaneManager, PlaneDetectionMode.Horizontal);
  71. }
  72. if (Settings.UseArLocationConfigSettings)
  73. {
  74. Settings.MaxGroundHeight = ARLocation.Config.MaxGroundHeight;
  75. Settings.MinGroundHeight = ARLocation.Config.MinGroundHeight;
  76. Settings.InitialGroundHeightGuess = ARLocation.Config.InitialGroundHeightGuess;
  77. Settings.Smoothing = ARLocation.Config.GroundHeightSmoothingFactor;
  78. }
  79. state.CurrentGroundY = -Settings.InitialGroundHeightGuess;
  80. arPlaneManager.planesChanged += ArPlaneManagerOnPlanesChanged;
  81. UpdateObjectHeight();
  82. }
  83. void OnEnable()
  84. {
  85. if (arPlaneManager)
  86. {
  87. arPlaneManager.planesChanged += ArPlaneManagerOnPlanesChanged;
  88. }
  89. }
  90. void OnDisable()
  91. {
  92. if (arPlaneManager)
  93. {
  94. arPlaneManager.planesChanged -= ArPlaneManagerOnPlanesChanged;
  95. }
  96. }
  97. private void ArPlaneManagerOnPlanesChanged(ARPlanesChangedEventArgs eventArgs)
  98. {
  99. var addedPlanes = eventArgs.added;
  100. var updatedPlanes = eventArgs.updated;
  101. if (addedPlanes.Count <= 0 && updatedPlanes.Count <= 0)
  102. {
  103. // Debug.Log("[AR+GPS][GroundHeight#ArPlaneManagerOnPlanesChanged]: No added or modified planes!");
  104. return;
  105. }
  106. foreach (ARPlane plane in addedPlanes)
  107. {
  108. ProcessPlane(plane);
  109. }
  110. foreach (ARPlane plane in updatedPlanes)
  111. {
  112. ProcessPlane(plane);
  113. }
  114. UpdateObjectHeight();
  115. }
  116. private void ProcessPlane(ARPlane plane)
  117. {
  118. // Debug.Log("[AR+GPS][GroundHeight#ProcessPlane]: Processing plane " + plane.trackableId.subId1 + ", " + plane.trackableId.subId2);
  119. if (plane.alignment != PlaneAlignment.HorizontalDown && plane.alignment != PlaneAlignment.HorizontalUp)
  120. {
  121. // Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Wrong plane alignment!");
  122. return;
  123. }
  124. if (!IsValidHeightForGround(plane.center.y))
  125. {
  126. // Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Invalid plane height!");
  127. return;
  128. }
  129. var distance = MathUtils.HorizontalDistance(transform.position, plane.center);
  130. if (!(state.CurrentPlaneDistance < 0) && (distance >= state.CurrentPlaneDistance))
  131. {
  132. // Debug.LogWarning("[AR+GPS][GroundHeight#ProcessPlane]: Plane too far!");
  133. return;
  134. }
  135. // Debug.Log("[AR+GPS][GroundHeight#ProcessPlane]: New plane Y: " + plane.center.y);
  136. state.CurrentPlaneDistance = distance;
  137. state.CurrentGroundY = plane.center.y;
  138. state.CurrentPlaneCenter = plane.center;
  139. state.NeedsUpdate = true;
  140. }
  141. #else
  142. private PlaneFinderBehaviour planeFinderBehaviour;
  143. private void Start()
  144. {
  145. planeFinderBehaviour = FindObjectOfType<PlaneFinderBehaviour>();
  146. mainCamera = ARLocationManager.Instance.MainCamera;
  147. if (planeFinderBehaviour == null)
  148. {
  149. Logger.WarnFromMethod("VuforiaGroundHeight", "Start", "No planeFinderBehaviour!");
  150. }
  151. if (Settings.UseArLocationConfigSettings)
  152. {
  153. Settings.MaxGroundHeight = ARLocation.Config.MaxGroundHeight;
  154. Settings.MinGroundHeight = ARLocation.Config.MinGroundHeight;
  155. Settings.InitialGroundHeightGuess = ARLocation.Config.InitialGroundHeightGuess;
  156. Settings.MinHitDistance = ARLocation.Config.VuforiaGroundHitTestDistance;
  157. Settings.Smoothing = ARLocation.Config.GroundHeightSmoothingFactor;
  158. }
  159. state.CurrentGroundY = -Settings.InitialGroundHeightGuess;
  160. planeFinderBehaviour.Height = Settings.InitialGroundHeightGuess;
  161. planeFinderBehaviour.HitTestMode = HitTestMode.AUTOMATIC;
  162. planeFinderBehaviour.OnAutomaticHitTest.AddListener(HitTestHandler);
  163. planeFinderBehaviour.OnInteractiveHitTest.AddListener(HitTestHandler);
  164. UpdateObjectHeight();
  165. }
  166. private void HitTestHandler(HitTestResult result)
  167. {
  168. //Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"result.Position = {result.Position}");
  169. // If the ground height is not in range, reject
  170. // var height = -1.0f * result.Position.y;
  171. if (!IsValidHeightForGround(result.Position.y))
  172. {
  173. //Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"Not in range: {result.Position.y} {height}) > {Settings.MinGroundHeight}");
  174. return;
  175. }
  176. var distanceToObject = MathUtils.HorizontalDistance(transform.position, result.Position);
  177. // If hit to close to previous hit do nothing
  178. if (state.CurrentPlaneDistance >= 0 && distanceToObject <= Settings.MinHitDistance)
  179. {
  180. //Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"Too close :{distanceToObject}");
  181. return;
  182. }
  183. // If there is no previous hit, or if the new hit is closes to the object, apply new
  184. // hit point.
  185. if (state.CurrentPlaneDistance < 0 || distanceToObject < state.CurrentPlaneDistance)
  186. {
  187. state.CurrentPlaneDistance = distanceToObject;
  188. state.CurrentGroundY = result.Position.y;
  189. state.NeedsUpdate = true;
  190. UpdateObjectHeight();
  191. // Logger.LogFromMethod("VuforiaGroundHeight", "HitTestHandler", $"New ground Y = {state.CurrentGroundY}");
  192. }
  193. }
  194. #endif
  195. public void UpdateObjectHeight(bool force = false)
  196. {
  197. if (!state.NeedsUpdate && !force) return;
  198. // Debug.Log("[AR+GPS][GroundHeight#UpdateObjectHeight]: Setting Y to " + state.CurrentGroundY);
  199. if (Settings.Smoothing <= 0)
  200. {
  201. transform.position = MathUtils.SetY(transform.position, state.CurrentGroundY + Settings.Altitude);
  202. }
  203. state.NeedsUpdate = false;
  204. }
  205. private bool IsValidHeightForGround(float y)
  206. {
  207. var diff = (mainCamera.transform.position.y - y);
  208. return (diff >= Settings.MinGroundHeight) && (diff <= Settings.MaxGroundHeight);
  209. }
  210. public void Update()
  211. {
  212. if (Settings.Smoothing <= 0 || Settings.DisableUpdate) return;
  213. if (Mathf.Abs(transform.position.y - (state.CurrentGroundY + Settings.Altitude)) <= Settings.Precision)
  214. {
  215. transform.position = MathUtils.SetY(transform.position, (state.CurrentGroundY + Settings.Altitude));
  216. return;
  217. }
  218. var t = 1.0f - Mathf.Pow(Settings.Smoothing, Time.deltaTime);
  219. var position = transform.position;
  220. var value = Mathf.Lerp(position.y, (state.CurrentGroundY + Settings.Altitude), t);
  221. position = MathUtils.SetY(position, value);
  222. transform.position = position;
  223. }
  224. }
  225. }