MapboxRoute.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. using UnityEngine;
  2. using UnityEngine.Events;
  3. using System;
  4. using System.Collections.Generic;
  5. namespace ARLocation.MapboxRoutes
  6. {
  7. public class MapboxRoute : MonoBehaviour
  8. {
  9. // ================================================================================ //
  10. // Public Classes //
  11. // ================================================================================ //
  12. [Serializable]
  13. public class MapboxRouteLoadErrorEvent : UnityEvent<string> { }
  14. public enum RouteType
  15. {
  16. Mapbox,
  17. CustomRoute,
  18. }
  19. [Serializable]
  20. public class RouteSettings
  21. {
  22. public RouteType RouteType;
  23. [Tooltip("The route's starting point.")]
  24. public RouteWaypoint From;
  25. [Tooltip("The route's end point.")]
  26. public RouteWaypoint To = new RouteWaypoint { Type = RouteWaypointType.Query };
  27. public CustomRoute CustomRoute;
  28. }
  29. [Serializable]
  30. public class SettingsData
  31. {
  32. [Header("Prefabs")]
  33. [Tooltip("The \"Sign Post\" prefab implements the behaviour of a GameObject which is attached to each route maneuver point. It must contain a component which " +
  34. "implements the abstract class 'AbstractSignPost'.")]
  35. public List<AbstractRouteSignpost> SignpostPrefabs = new List<AbstractRouteSignpost>();
  36. [Tooltip("The \"Path Renderer\" is resposible for drawing the line on the ground showing the user how to follow the route. It must implement the abstrac class 'AbstractRouteRenderer'")]
  37. public AbstractRouteRenderer PathRenderer;
  38. [Tooltip("The \"On Screen Indicator\" is responsable for drawing a 2D icon on the screen showing the direction of the next route target." +
  39. "it must implement the abstract class 'AbstractOnScreenTargetIndicator'.")]
  40. public AbstractOnScreenTargetIndicator OnScreenIndicator;
  41. [Header("Mapbox")]
  42. [Tooltip("The Mapbox API token, used for accessing the Mapbox REST API.")]
  43. public string MapboxToken = "";
  44. [Header("Route")]
  45. public RouteSettings RouteSettings;
  46. [Header("Other settings")]
  47. [Tooltip("If true, load the route as soon as the component's \"Start\" method is called.")]
  48. public bool LoadRouteAtStartup = true;
  49. [Tooltip("The assumed height of the device from the ground.")]
  50. public float GroundHeight = 1.4f;
  51. [Header("Events")]
  52. [Tooltip("Event listener called whenever there is an error loading a route.")]
  53. public MapboxRouteLoadErrorEvent OnMapboxRouteLoadError;
  54. public bool DebugMode;
  55. }
  56. // ================================================================================ //
  57. // Public Properties //
  58. // ================================================================================ //
  59. [Tooltip("Main settings for this component.")]
  60. public SettingsData Settings = new SettingsData();
  61. // ================================================================================ //
  62. // Setters and Getters //
  63. // ================================================================================ //
  64. /// If there was an error when loading the route, this will return a
  65. /// string value with an error message. Otherwise it will return null
  66. /// on success.
  67. public string LoadRouteError => s.LoadRouteError;
  68. /// Returns the number of steps in the current Route.
  69. public int NumberOfSteps => s.RouteSteps.Count;
  70. // Gets/sets the current `RoutePathRenderer`.
  71. public AbstractRouteRenderer RoutePathRenderer
  72. {
  73. get => Settings.PathRenderer;
  74. set
  75. {
  76. if (value != Settings.PathRenderer)
  77. {
  78. Settings.PathRenderer = value;
  79. if (NumberOfSteps > 0)
  80. {
  81. Settings.PathRenderer.Init(createRoutePathRendererArgs());
  82. }
  83. }
  84. }
  85. }
  86. // ================================================================================ //
  87. // Private classes //
  88. // ================================================================================ //
  89. [Serializable]
  90. class State
  91. {
  92. public string LoadRouteError = null;
  93. public List<List<AbstractRouteSignpost>> SignPostInstances = new List<List<AbstractRouteSignpost>>();
  94. public List<PlaceAtLocation> StepsPlaceAtInstances = new List<PlaceAtLocation>();
  95. public List<Route.Step> RouteSteps = new List<Route.Step>();
  96. public float RouteDistance;
  97. public Route.Geometry RouteGeometry;
  98. public int CurrentTargetIndex = -1;
  99. }
  100. // ================================================================================ //
  101. // Private fields //
  102. // ================================================================================ //
  103. private MapboxApi mapbox;
  104. private State s = new State();
  105. // ================================================================================ //
  106. // Monobehaviour methods //
  107. // ================================================================================ //
  108. void Awake()
  109. {
  110. if (Settings.MapboxToken == "")
  111. {
  112. Utils.Logger.WarnFromMethod("MapboxRoute", "Awake",
  113. "Please insert a Mapbox Token on the inspector panel for the 'MapboxRoutes' component!");
  114. }
  115. mapbox = new MapboxApi(Settings.MapboxToken);
  116. }
  117. void Start()
  118. {
  119. if (Settings.LoadRouteAtStartup)
  120. {
  121. ARLocationProvider.Instance.OnEnabled.AddListener(onLocationEnabled);
  122. }
  123. }
  124. void Update()
  125. {
  126. if (NumberOfSteps > 0)
  127. {
  128. bool shouldGotoNextTarget = false;
  129. for (int i = 0; i < NumberOfSteps; i++)
  130. {
  131. var signpostInstances = s.SignPostInstances[i];
  132. var signpostEventArgs = createSignPostEventArgs(i);
  133. for (int j = 0; j < signpostInstances.Count; j++)
  134. {
  135. var instance = signpostInstances[j];
  136. var result = instance.UpdateSignPost(signpostEventArgs);
  137. if (i == s.CurrentTargetIndex && !result)
  138. {
  139. Utils.Logger.LogFromMethod("MapboxRoute", "Update", "NextTarget", Settings.DebugMode);
  140. shouldGotoNextTarget = true;
  141. }
  142. }
  143. }
  144. if (shouldGotoNextTarget)
  145. {
  146. NextTarget();
  147. }
  148. if (Settings.PathRenderer != null)
  149. {
  150. Settings.PathRenderer.OnRouteUpdate(createRoutePathRendererArgs());
  151. }
  152. if (Settings.OnScreenIndicator != null)
  153. {
  154. Settings.OnScreenIndicator.OnRouteUpdate(createSignPostEventArgs(s.CurrentTargetIndex));
  155. }
  156. }
  157. }
  158. // ================================================================================ //
  159. // Private methods //
  160. // ================================================================================ //
  161. private RoutePathRendererArgs createRoutePathRendererArgs()
  162. {
  163. var index = s.CurrentTargetIndex;
  164. var user = Camera.main.transform.position;
  165. var target = s.StepsPlaceAtInstances[index].transform.position;
  166. var distance = MathUtils.HorizontalDistance(user, target);
  167. var positions = new List<Vector3>(NumberOfSteps);
  168. for (int i = 0; i < NumberOfSteps; i++)
  169. {
  170. positions.Add(s.StepsPlaceAtInstances[i].transform.position);
  171. }
  172. return new RoutePathRendererArgs
  173. {
  174. Route = this,
  175. RouteSteps = s.RouteSteps,
  176. RouteGeometry = s.RouteGeometry,
  177. StepIndex = index,
  178. UserPos = user,
  179. TargetPos = target,
  180. Distance = distance,
  181. StepPositions = positions,
  182. };
  183. }
  184. private void onLocationEnabled(Location location)
  185. {
  186. if (Settings.LoadRouteAtStartup)
  187. {
  188. if (Settings.RouteSettings.RouteType == RouteType.CustomRoute)
  189. {
  190. if (Settings.RouteSettings.CustomRoute != null)
  191. {
  192. LoadCustomRoute(Settings.RouteSettings.CustomRoute);
  193. }
  194. else
  195. {
  196. Utils.Logger.ErrorFromMethod("MapboxRoute", "onLocationEnabled", "RouteType is 'Custom Route' but 'CustomRoute' is null; please set the 'Custom Route' property on the inspector panel.");
  197. return;
  198. }
  199. }
  200. else
  201. {
  202. StartCoroutine(LoadRoute());
  203. }
  204. }
  205. }
  206. private void clearRoute()
  207. {
  208. foreach (var signpostInstances in s.SignPostInstances)
  209. {
  210. foreach (var instance in signpostInstances)
  211. {
  212. Destroy(instance.gameObject);
  213. }
  214. }
  215. foreach (var e in s.StepsPlaceAtInstances)
  216. {
  217. Destroy(e.gameObject);
  218. }
  219. s = new State();
  220. }
  221. private SignPostEventArgs createSignPostEventArgs()
  222. {
  223. return createSignPostEventArgs(s.CurrentTargetIndex);
  224. }
  225. private SignPostEventArgs createSignPostEventArgs(int index)
  226. {
  227. var user = Camera.main.transform.position;
  228. var target = s.StepsPlaceAtInstances[index].transform.position;
  229. var instruction = s.RouteSteps[index].maneuver.instruction;
  230. var name = s.RouteSteps[index].name;
  231. return new SignPostEventArgs
  232. {
  233. Route = this,
  234. UserPos = user,
  235. TargetPos = target,
  236. NextTargetPos = (index + 1) < NumberOfSteps ? s.StepsPlaceAtInstances[index + 1].transform.position : (Vector3?)null,
  237. PrevTargetPos = (index) > 0 ? s.StepsPlaceAtInstances[index - 1].transform.position : (Vector3?)null,
  238. Distance = MathUtils.HorizontalDistance(user, target),
  239. IsCurrentTarget = (index == s.CurrentTargetIndex),
  240. StepIndex = index,
  241. Instruction = instruction,
  242. Name = name,
  243. };
  244. }
  245. private bool isValidTargetIndex(int index)
  246. {
  247. return index >= 0 && index < s.RouteSteps.Count;
  248. }
  249. // ================================================================================ //
  250. // Public methods //
  251. // ================================================================================ //
  252. /// <summary>
  253. ///
  254. /// Given a `RouteResponse` form the `MapboxApi` class, builds the
  255. /// corresponding route. By building we mean that it will place all the
  256. /// AR+GPS objects, initialize the path rendering, and so on.
  257. ///
  258. /// </summary>
  259. public bool BuildRoute(RouteResponse result)
  260. {
  261. clearRoute();
  262. if (result.routes.Count == 0)
  263. {
  264. return false;
  265. }
  266. // We only support one route
  267. var route = result.routes[0];
  268. if (route.legs.Count == 0)
  269. {
  270. return false;
  271. }
  272. // Also only one leg. (Leg = a route from A to B)
  273. var leg = route.legs[0];
  274. // Go trough each of the leg's maneuvers
  275. int c = 0;
  276. foreach (var step in leg.steps)
  277. {
  278. var loc = step.maneuver.location;
  279. // Create a PlaceAtLocation gameObject for this step
  280. var go = new GameObject($"PlaceAt_{c}");
  281. var opt = new PlaceAtLocation.PlaceAtOptions { };
  282. opt.MaxNumberOfLocationUpdates = 0;
  283. var placeAt = PlaceAtLocation.AddPlaceAtComponent(go, loc, opt);
  284. s.StepsPlaceAtInstances.Add(placeAt);
  285. s.SignPostInstances.Add(new List<AbstractRouteSignpost>());
  286. // Create a signpost prefab instace for this step
  287. for (var i = 0; i < Settings.SignpostPrefabs.Count; i++)
  288. {
  289. var prefab = Settings.SignpostPrefabs[i];
  290. var signPostInstance = Instantiate(prefab);
  291. signPostInstance.gameObject.SetActive(false);
  292. s.SignPostInstances[c].Add(signPostInstance);
  293. signPostInstance.Init(this);
  294. }
  295. c++;
  296. }
  297. s.RouteSteps = leg.steps;
  298. s.RouteDistance = leg.distance;
  299. s.RouteGeometry = route.geometry;
  300. // Set the first step as the current target
  301. SetTarget(0);
  302. if (Settings.PathRenderer != null)
  303. {
  304. Settings.PathRenderer.Init(createRoutePathRendererArgs());
  305. }
  306. if (Settings.OnScreenIndicator != null)
  307. {
  308. Settings.OnScreenIndicator.Init(this);
  309. }
  310. return true;
  311. }
  312. /// <summary>
  313. /// Sets the current target to `index`.
  314. ///
  315. /// In the context of the `MapboxRoute` class, a "target" is location
  316. /// of the route where a maneuver is expected to happen, e.g. "Turn
  317. /// right", "Keep left", "You have arrived at your destination".
  318. ///
  319. /// </summar>
  320. public void SetTarget(int index)
  321. {
  322. Utils.Logger.LogFromMethod("MapboxRoute", "SetTarget", $"index = {index}", Settings.DebugMode);
  323. if (index == s.CurrentTargetIndex)
  324. {
  325. return;
  326. }
  327. if (isValidTargetIndex(index))
  328. {
  329. SignPostEventArgs args;
  330. if (isValidTargetIndex(s.CurrentTargetIndex))
  331. {
  332. args = createSignPostEventArgs();
  333. foreach (var instance in s.SignPostInstances[s.CurrentTargetIndex])
  334. {
  335. instance.OffCurrentTarget(createSignPostEventArgs());
  336. }
  337. }
  338. s.CurrentTargetIndex = index;
  339. args = createSignPostEventArgs();
  340. foreach (var instance in s.SignPostInstances[s.CurrentTargetIndex])
  341. {
  342. instance.OnCurrentTarget(args);
  343. }
  344. }
  345. }
  346. /// <summary>
  347. /// Sets the current target to be the closest one.
  348. ///
  349. /// To be more precise, this will calculate the distances from the user
  350. /// position to each line segment constructed from one target to the
  351. /// next one: Target0->Target1, Target1->Target2, ..., TargetN-1->TargetN.
  352. ///
  353. /// Then it will pick the closes segment, e.g., TargetK->TargetK+1, and
  354. /// will set the current target to "K+1" (unless the user is positioned
  355. /// before the first target, in which case the target willi be set to "0".)
  356. ///
  357. /// </summary>
  358. public void ClosestTarget()
  359. {
  360. var position = MathUtils.HorizontalVector(Camera.main.transform.position);
  361. MathUtils.PointLineSegmentDistanceResult current = null;
  362. var currentIndex = -1;
  363. for (var i = 0; i < NumberOfSteps - 1; i++)
  364. {
  365. var a = MathUtils.HorizontalVector(s.StepsPlaceAtInstances[i].transform.position);
  366. var b = MathUtils.HorizontalVector(s.StepsPlaceAtInstances[i + 1].transform.position);
  367. var result = MathUtils.PointLineSegmentDistance(position, a, b);
  368. if (current == null || result.Distance < current.Distance)
  369. {
  370. current = result;
  371. currentIndex = i;
  372. }
  373. }
  374. if (currentIndex < 0)
  375. {
  376. Utils.Logger.WarnFromMethod("MapboxRoute", "ClosestTargetByUserPosition", "currentIndex < 0");
  377. return;
  378. }
  379. Utils.Logger.LogFromMethod("MapboxRoute", "ClosestTargetByUserPosition", $"{currentIndex}");
  380. switch (current.Region)
  381. {
  382. case MathUtils.LineSegmentRegion.Start:
  383. if (currentIndex == 0)
  384. {
  385. SetTarget(currentIndex);
  386. }
  387. else
  388. {
  389. SetTarget(currentIndex + 1);
  390. }
  391. break;
  392. case MathUtils.LineSegmentRegion.Middle:
  393. SetTarget(currentIndex + 1);
  394. break;
  395. case MathUtils.LineSegmentRegion.End:
  396. SetTarget(currentIndex + 1);
  397. break;
  398. }
  399. }
  400. /// <summary>
  401. /// Sets the next target as the current one.
  402. /// </summary>
  403. public void NextTarget()
  404. {
  405. SetTarget(s.CurrentTargetIndex + 1);
  406. }
  407. /// <summary>
  408. /// Sets the previous target as the current one.
  409. /// </summary>
  410. public void PrevTarget()
  411. {
  412. SetTarget(s.CurrentTargetIndex - 1);
  413. }
  414. /// <summary>
  415. /// Sets the first/initial route target as the current one.
  416. /// </summary>
  417. public void FirstTarget()
  418. {
  419. SetTarget(0);
  420. }
  421. /// <summary>
  422. /// Sets the final route target as the current one.
  423. /// </summary>
  424. public void LastTarget()
  425. {
  426. SetTarget(NumberOfSteps - 1);
  427. }
  428. /// <summary>
  429. /// Reloads the current route.
  430. /// </summary>
  431. public void ReloadRoute()
  432. {
  433. onLocationEnabled(ARLocationProvider.Instance.CurrentLocation.ToLocation());
  434. }
  435. /// <summary>
  436. /// Loads a custom route defined by a `CustomRoute` ScriptableObject.
  437. /// </summary>
  438. public void LoadCustomRoute(CustomRoute route)
  439. {
  440. var res = new RouteResponse();
  441. res.routes = new List<Route> { route.ToMapboxRoute() };
  442. res.waypoints = route.GetWaypoints();
  443. BuildRoute(res);
  444. }
  445. /// <summary>
  446. /// Loads a route defined a start and end `Waypoint`s, and calls a given callback when the route is loaded.
  447. /// </summary>
  448. public System.Collections.IEnumerator LoadRoute(RouteWaypoint start, RouteWaypoint end, Action<string> callback)
  449. {
  450. yield return LoadRoute(start, end);
  451. callback(s.LoadRouteError);
  452. }
  453. /// <summary>
  454. /// Loads a route defined a start and end `Waypoint`s.
  455. /// </summary>
  456. public System.Collections.IEnumerator LoadRoute(RouteWaypoint start, RouteWaypoint end)
  457. {
  458. Debug.Assert(mapbox != null);
  459. var loader = new RouteLoader(mapbox);
  460. yield return loader.LoadRoute(start, end);
  461. if (loader.Error != null)
  462. {
  463. s.LoadRouteError = loader.Error;
  464. Settings.OnMapboxRouteLoadError?.Invoke(loader.Error);
  465. }
  466. else
  467. {
  468. s.LoadRouteError = null;
  469. BuildRoute(loader.Result);
  470. }
  471. }
  472. /// <summary>
  473. /// Loads a route from the current user location to a given `Waypoint`.
  474. /// </summary>
  475. public System.Collections.IEnumerator LoadRoute(RouteWaypoint routeWaypoint)
  476. {
  477. yield return LoadRoute(new RouteWaypoint { Type = RouteWaypointType.UserLocation }, routeWaypoint);
  478. }
  479. /// <summary>
  480. /// Loads the route defined by the waypoints given in the "RouteSettings".
  481. /// </summary>
  482. public System.Collections.IEnumerator LoadRoute()
  483. {
  484. yield return LoadRoute(Settings.RouteSettings.From, Settings.RouteSettings.To);
  485. }
  486. }
  487. }