PlaceAtLocation.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. using System;
  2. using ARLocation.UI;
  3. using UnityEngine;
  4. using UnityEngine.Events;
  5. namespace ARLocation
  6. {
  7. using Utils;
  8. [Serializable]
  9. public class OverrideAltitudeData
  10. {
  11. [Tooltip("If true, override the LocationData's altitude options.")]
  12. public bool OverrideAltitude;
  13. [Tooltip("The override altitude value.")]
  14. public double Altitude;
  15. [Tooltip("The override altitude mode.")]
  16. public AltitudeMode AltitudeMode = AltitudeMode.GroundRelative;
  17. }
  18. [Serializable]
  19. public class LocationPropertyData
  20. {
  21. [Serializable]
  22. public enum LocationPropertyType
  23. {
  24. Location,
  25. LocationData
  26. }
  27. [Tooltip("The type of location coordinate input used. Either 'Location' to directly input location " +
  28. "coordinates, or 'LocationData' to use a ScriptableObject.")]
  29. public LocationPropertyType LocationInputType = LocationPropertyType.Location;
  30. [Tooltip("A LocationData ScriptableObject storing the desired GPS coordinates to place the object.")]
  31. public LocationData LocationData;
  32. [Tooltip("Input the desired GPS coordinates here.")]
  33. public Location Location = new Location();
  34. [Tooltip("Use this to override the LocationData's altitude options.")]
  35. public OverrideAltitudeData OverrideAltitudeData = new OverrideAltitudeData();
  36. }
  37. /// <summary>
  38. /// Apply to a GameObject to place it at a specified geographic location.
  39. /// </summary>
  40. [AddComponentMenu("AR+GPS/Place At Location")]
  41. [HelpURL("https://http://docs.unity-ar-gps-location.com/guide/#placeatlocation")]
  42. [DisallowMultipleComponent]
  43. public class PlaceAtLocation : MonoBehaviour
  44. {
  45. [Serializable]
  46. public class ObjectUpdatedEvent : UnityEvent<GameObject, Location, int>
  47. {
  48. }
  49. [Serializable]
  50. public class PlaceAtOptions
  51. {
  52. [Tooltip(
  53. "The smoothing factor for movement due to GPS location adjustments; if set to zero it is disabled."),
  54. Range(0, 1)]
  55. public float MovementSmoothing = 0.05f;
  56. [Tooltip(
  57. "The maximum number of times this object will be affected by GPS location updates. Zero means no limits are imposed.")]
  58. public int MaxNumberOfLocationUpdates = 4;
  59. [Tooltip("If true, use a moving average filter.")]
  60. public bool UseMovingAverage;
  61. [Tooltip(
  62. "If true, the object will be hidden until the object is placed at the geolocation. If will enable/disable the MeshRenderer or SkinnedMeshRenderer " +
  63. "when available, and enable/disable all child game objects.")]
  64. public bool HideObjectUntilItIsPlaced = true;
  65. [ConditionalPropertyAttribute("HideObjectUntilItIsPlaced")]
  66. [Tooltip("The number of location updates to wait until the object is shown after being initially " +
  67. "hidden from view. Only works when 'Hide Object Until It Is Placed' is set to true. If this "+
  68. "is set to 0, 'Hide Object Until It Is Placed' will be disabled.")]
  69. public uint ShowObjectAfterThisManyUpdates = 1;
  70. }
  71. [Serializable]
  72. public class LocationSettingsData
  73. {
  74. public LocationPropertyData LocationInput = new LocationPropertyData();
  75. public Location GetLocation()
  76. {
  77. Location location;
  78. if (LocationInput.LocationInputType ==
  79. LocationPropertyData.LocationPropertyType.LocationData)
  80. {
  81. if (LocationInput.LocationData == null)
  82. {
  83. Debug.LogWarning("[AR+GPS][LocationSettingsData#GetLocation]: " +
  84. "Null LocationData; falling back to Location. When using `Location Input Type = Location Data` " +
  85. "make sure you associate a LocationData ScriptableObject to it.");
  86. location = LocationInput.Location.Clone();
  87. }
  88. else
  89. {
  90. location = LocationInput.LocationData.Location.Clone();
  91. if (LocationInput.OverrideAltitudeData.OverrideAltitude)
  92. {
  93. location.Altitude = LocationInput.OverrideAltitudeData.Altitude;
  94. location.AltitudeMode = LocationInput.OverrideAltitudeData.AltitudeMode;
  95. }
  96. }
  97. }
  98. else
  99. {
  100. location = LocationInput.Location.Clone();
  101. }
  102. return location;
  103. }
  104. }
  105. [Serializable]
  106. public class StateData
  107. {
  108. public Location Location;
  109. public uint LocationUpdatedCount;
  110. public uint PositionUpdatedCount;
  111. public bool Paused;
  112. }
  113. public LocationSettingsData LocationOptions = new LocationSettingsData();
  114. [Space(4.0f)] public PlaceAtOptions PlacementOptions = new PlaceAtOptions();
  115. [Space(4.0f)]
  116. [Header("Debug")]
  117. [Tooltip("When debug mode is enabled, this component will print relevant messages to the console. Filter by 'PlateAtLocation' in the log output to see the messages. It will also " +
  118. "display the direction from the user to the object on the screen, as well as a line renderer from the camera to the object location. To customize how this line looks, add " +
  119. "a Line Renderer component to this game object.")]
  120. public bool DebugMode;
  121. [Space(4.0f)]
  122. [Header("Events")]
  123. [Space(4.0f)]
  124. [Tooltip(
  125. "Event called when the object's location is updated. The arguments are the current GameObject, the location, and the number of location updates received " +
  126. "by the object so far.")]
  127. public ObjectUpdatedEvent ObjectLocationUpdated = new ObjectUpdatedEvent();
  128. [Tooltip(
  129. "Event called when the object's position is updated after a location update. " +
  130. "If the Movement Smoothing is larger than 0, this will fire at a later time than the Location Updated event. The arguments are the current GameObject, the location, and the number of position updates received " +
  131. "by the object so far.")]
  132. public ObjectUpdatedEvent ObjectPositionUpdated = new ObjectUpdatedEvent();
  133. public Location Location
  134. {
  135. get => state.Location;
  136. set
  137. {
  138. if (!hasInitialized)
  139. {
  140. LocationOptions.LocationInput.LocationInputType =
  141. LocationPropertyData.LocationPropertyType.Location;
  142. LocationOptions.LocationInput.LocationData = null;
  143. LocationOptions.LocationInput.Location = value.Clone();
  144. return;
  145. }
  146. if (groundHeight != null)
  147. {
  148. groundHeight.Settings.Altitude = (float) value.Altitude;
  149. }
  150. state.Location = value.Clone();
  151. UpdatePosition(locationProvider.CurrentLocation.ToLocation(), true);
  152. }
  153. }
  154. public float SceneDistance
  155. {
  156. get
  157. {
  158. var cameraPos = mainCameraTransform.position;
  159. return MathUtils.HorizontalDistance(cameraPos, transform.position);
  160. }
  161. }
  162. public double RawGpsDistance =>
  163. Location.HorizontalDistance(locationProvider.Provider.CurrentLocationRaw.ToLocation(),
  164. state.Location);
  165. public bool Paused
  166. {
  167. get => state.Paused;
  168. set => state.Paused = value;
  169. }
  170. public bool UseGroundHeight => state.Location.AltitudeMode == AltitudeMode.GroundRelative;
  171. private StateData state = new StateData();
  172. private ARLocationProvider locationProvider;
  173. private Transform arLocationRoot;
  174. private SmoothMove smoothMove;
  175. private MovingAveragePosition movingAverageFilter;
  176. private GameObject debugPanel;
  177. private ARLocationManager arLocationManager;
  178. private Transform mainCameraTransform;
  179. private bool hasInitialized;
  180. private GroundHeight groundHeight;
  181. // Use this for initialization
  182. void Start()
  183. {
  184. locationProvider = ARLocationProvider.Instance;
  185. arLocationManager = ARLocationManager.Instance;
  186. arLocationRoot = arLocationManager.gameObject.transform;
  187. mainCameraTransform = arLocationManager.MainCamera.transform;
  188. if (locationProvider == null)
  189. {
  190. Debug.LogError("[AR+GPS][PlaceAtLocation]: LocationProvider GameObject or Component not found.");
  191. return;
  192. }
  193. Initialize();
  194. hasInitialized = true;
  195. }
  196. public void Restart()
  197. {
  198. Logger.LogFromMethod("PlaceAtLocation", "Restart", $"({gameObject.name}) - Restarting!", DebugMode);
  199. RemoveLocationProviderListeners();
  200. state = new StateData();
  201. Initialize();
  202. if (locationProvider.IsEnabled)
  203. {
  204. locationUpdatedHandler(locationProvider.CurrentLocation, locationProvider.LastLocation);
  205. }
  206. }
  207. void Initialize()
  208. {
  209. state.Location = LocationOptions.GetLocation();
  210. Transform transform1;
  211. (transform1 = transform).SetParent(arLocationRoot.transform, false);
  212. transform1.localPosition = Vector3.zero;
  213. if (!hasInitialized)
  214. {
  215. if (PlacementOptions.HideObjectUntilItIsPlaced)
  216. {
  217. Misc.HideGameObject(gameObject);
  218. }
  219. if (PlacementOptions.MovementSmoothing > 0)
  220. {
  221. smoothMove = SmoothMove.AddSmoothMove(gameObject, PlacementOptions.MovementSmoothing);
  222. }
  223. if (UseGroundHeight)
  224. {
  225. groundHeight = gameObject.AddComponent<GroundHeight>();
  226. groundHeight.Settings.Altitude = (float) state.Location.Altitude;
  227. }
  228. if (PlacementOptions.UseMovingAverage)
  229. {
  230. movingAverageFilter = new MovingAveragePosition
  231. {
  232. aMax = locationProvider.Provider.Options.AccuracyRadius > 0
  233. ? locationProvider.Provider.Options.AccuracyRadius
  234. : 20
  235. };
  236. }
  237. if (DebugMode)
  238. {
  239. gameObject.AddComponent<DebugDistance>();
  240. }
  241. if (PlacementOptions.ShowObjectAfterThisManyUpdates == 0)
  242. {
  243. PlacementOptions.HideObjectUntilItIsPlaced = false;
  244. }
  245. RegisterLocationProviderListeners();
  246. }
  247. Logger.LogFromMethod("PlaceAtLocation", "Initialize", $"({gameObject.name}) initialized object with geo-location {state.Location}", DebugMode);
  248. }
  249. private void RegisterLocationProviderListeners()
  250. {
  251. // NOTE(daniel): `useRawIfEnabled` = true in this call so that when adding objects at runtime it uses the
  252. // best guess for the user location.
  253. locationProvider.OnLocationUpdatedEvent(locationUpdatedHandler, true);
  254. locationProvider.OnProviderRestartEvent(ProviderRestarted);
  255. }
  256. private void RemoveLocationProviderListeners()
  257. {
  258. locationProvider.OnLocationUpdatedDelegate -= locationUpdatedHandler;
  259. locationProvider.OnRestartDelegate -= ProviderRestarted;
  260. }
  261. private void ProviderRestarted()
  262. {
  263. Logger.LogFromMethod("PlaceAtLocation", "ProviderRestarted", $"({gameObject.name})", DebugMode);
  264. state.LocationUpdatedCount = 0;
  265. state.PositionUpdatedCount = 0;
  266. }
  267. private void locationUpdatedHandler(LocationReading currentLocation, LocationReading lastLocation)
  268. {
  269. UpdatePosition(currentLocation.ToLocation());
  270. }
  271. public void UpdatePosition(Location deviceLocation, bool forceUpdate = false)
  272. {
  273. Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Received location update, location = {deviceLocation}", DebugMode);
  274. if (state.Paused)
  275. {
  276. Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Updates are paused; returning", DebugMode);
  277. return;
  278. }
  279. Vector3 targetPosition;
  280. var location = state.Location;
  281. var useSmoothMove = smoothMove != null;
  282. var isHeightRelative = location.AltitudeMode == AltitudeMode.DeviceRelative;
  283. // If we have reached the max number of location updates, do nothing
  284. if ((PlacementOptions.MaxNumberOfLocationUpdates > 0) &&
  285. (state.LocationUpdatedCount >= PlacementOptions.MaxNumberOfLocationUpdates) && !forceUpdate)
  286. {
  287. return;
  288. }
  289. // Calculate the target position where the object will be placed next
  290. if (movingAverageFilter != null)
  291. {
  292. var position = Location.GetGameObjectPositionForLocation(
  293. arLocationRoot, mainCameraTransform, deviceLocation, location, isHeightRelative
  294. );
  295. var accuracy = locationProvider.CurrentLocation.accuracy;
  296. movingAverageFilter.AddEntry(new DVector3(position), accuracy);
  297. targetPosition = movingAverageFilter.CalculateAveragePosition().toVector3();
  298. }
  299. else
  300. {
  301. targetPosition = Location.GetGameObjectPositionForLocation(
  302. arLocationRoot, mainCameraTransform, deviceLocation, location, isHeightRelative
  303. );
  304. }
  305. // If GroundHeight is enabled, don't change the objects position
  306. if (UseGroundHeight)
  307. {
  308. targetPosition.y = transform.position.y;
  309. if (useSmoothMove)
  310. {
  311. smoothMove.SmoothMoveMode = SmoothMove.Mode.Horizontal;
  312. }
  313. }
  314. Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Moving object to target position {targetPosition} ( {location}, {deviceLocation} )", DebugMode);
  315. if (useSmoothMove && state.PositionUpdatedCount > 0)
  316. {
  317. Logger.LogFromMethod("PlaceAtLocation", "UpdatePosition", $"({gameObject.name}): Using smooth move...", DebugMode);
  318. smoothMove.Move(targetPosition, PositionUpdated);
  319. }
  320. else
  321. {
  322. transform.position = targetPosition;
  323. PositionUpdated();
  324. }
  325. state.LocationUpdatedCount++;
  326. ObjectLocationUpdated?.Invoke(gameObject, location, (int) state.LocationUpdatedCount);
  327. }
  328. private void PositionUpdated()
  329. {
  330. if (PlacementOptions.HideObjectUntilItIsPlaced)// && state.PositionUpdatedCount <= 0)
  331. {
  332. if (state.PositionUpdatedCount == (PlacementOptions.ShowObjectAfterThisManyUpdates - 1))
  333. {
  334. Misc.ShowGameObject(gameObject);
  335. }
  336. }
  337. state.PositionUpdatedCount++;
  338. Logger.LogFromMethod("PlaceAtLocation", "PositionUpdated", $"({gameObject.name}): Object position updated! PositionUpdatedCount = {state.PositionUpdatedCount}, transform.position = {transform.position}", DebugMode);
  339. ObjectPositionUpdated?.Invoke(gameObject, state.Location, (int) state.PositionUpdatedCount);
  340. }
  341. public static GameObject CreatePlacedInstance(GameObject go, Location location, PlaceAtOptions options, bool useDebugMode = false)
  342. {
  343. var instance = Instantiate(go, ARLocationManager.Instance.gameObject.transform);
  344. AddPlaceAtComponent(instance, location, options, useDebugMode);
  345. return instance;
  346. }
  347. public static PlaceAtLocation AddPlaceAtComponent(GameObject go, Location location, PlaceAtOptions options,
  348. bool useDebugMode = false)
  349. {
  350. go.SetActive(false);
  351. var placeAt = go.AddComponent<PlaceAtLocation>();
  352. placeAt.PlacementOptions = options;
  353. placeAt.LocationOptions.LocationInput.LocationInputType =
  354. LocationPropertyData.LocationPropertyType.Location;
  355. placeAt.LocationOptions.LocationInput.Location = location.Clone();
  356. placeAt.DebugMode = useDebugMode;
  357. go.SetActive(true);
  358. return placeAt;
  359. }
  360. public static GameObject CreatePlacedInstanceAtWorldPosition(GameObject go, Vector3 worldPosition, PlaceAtOptions options, out Location location, bool useDebugMode = false)
  361. {
  362. location = ARLocationManager.Instance.GetLocationForWorldPosition(worldPosition);
  363. return CreatePlacedInstance(go, location, options, useDebugMode);
  364. }
  365. private void OnDestroy()
  366. {
  367. RemoveLocationProviderListeners();
  368. }
  369. }
  370. }