SignPost.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. using UnityEngine;
  2. using TMPro;
  3. namespace ARLocation.MapboxRoutes
  4. {
  5. public class SignPost : AbstractRouteSignpost
  6. {
  7. // ================================================================================ //
  8. // Public Classes //
  9. // ================================================================================ //
  10. [System.Serializable]
  11. public enum StateType
  12. {
  13. Hidden,
  14. Following,
  15. Idle,
  16. Deactivated,
  17. MapPin,
  18. }
  19. [System.Serializable]
  20. public struct MachineState
  21. {
  22. public StateType Type;
  23. public bool HasArrow;
  24. }
  25. [System.Serializable]
  26. public class SignSettingsData
  27. {
  28. [Tooltip("The container GameObject for the road-sign that displays the route information to the user. If set to 'None' it won't be displayed.")]
  29. public Transform Container;
  30. [Tooltip("The label that displays the maneuver instructions to the user. Should be a child of the road-sign container.")]
  31. public TMP_Text DirectionLabel;
  32. [Tooltip("The label that displays the distance from the user's current location to the next target. Should be a child of the road-sign container.")]
  33. public TMP_Text DistanceLabel;
  34. [Tooltip("The height of the road-sign.")]
  35. public float Height = 2;
  36. [Tooltip("The road-sign always follows ahead of the user at some distance, being placed in a straight line form the user's current position to the next target position."
  37. + " This is settting defines the distance that the road-sign will keep ahead from the user.")]
  38. public float FollowDistance = 10;
  39. }
  40. [System.Serializable]
  41. public class ArrowSettingsData
  42. {
  43. [Tooltip("The container GameObject for the 3D arrow that indicates the maneuver direction to the user. If set to 'None' it won't be displayed.")]
  44. public Transform Container;
  45. [Tooltip("The distance at which the 3D arrow drops/appears. Should be euqal or less than the road-sign FollowDistance, and larger or equal than the DeactivationDistance.")]
  46. public float DropDistance = 5.0f;
  47. [Tooltip("The duration of the drop animation for the 3D arrow. If '0' there is no drop animation.")]
  48. public float DropDuration = 2.0f;
  49. [Tooltip("If true, the arrow will be hidden after the target has been deactivated and the next target becomes active.")]
  50. public bool HideArrowOnNextTarget = false;
  51. }
  52. [System.Serializable]
  53. public class MapPinSettingsData
  54. {
  55. [Tooltip("The container GameObject for the 3D model which will appear at the end of the route, indicating to the user that he has reached his destination. If set to 'None' it won't be displayed.")]
  56. public Transform Container;
  57. [Tooltip("A position offset that will be applied to the Container, relative to the target's position.")]
  58. public Vector3 Offset = new Vector3(0, 0, 0);
  59. [Tooltip("The duration of the drop animation for the finish sign. If '0' there is no drop animation.")]
  60. public float DropDuration = 1.5f;
  61. [Tooltip("The height from which the finish sign is dropped in the drop animation.")]
  62. public float DropHeight = 50.0f;
  63. }
  64. [System.Serializable]
  65. public class SettingsData
  66. {
  67. [Tooltip("The distance at which the target is deactivated and the next target is activated. Should be smaller than the sign-post FollowDistance and the direction arrow DropDistance.")]
  68. public float DeactivationDistance = 10.0f;
  69. }
  70. // ================================================================================ //
  71. // Public Properties //
  72. // ================================================================================ //
  73. [Tooltip("Settings related to the road-sign that shows the user routing information.")]
  74. public SignSettingsData RoadSignSettings;
  75. [Tooltip("Settings related to the 3D arrow that indicates maneuver directions to the user.")]
  76. public ArrowSettingsData DirectionArrowSettings;
  77. [Tooltip("Settings related to 3D model that appears on the end of the route, indicating to the user that he has arrived at his destination.")]
  78. public MapPinSettingsData FinishSignSettings;
  79. public SettingsData OtherSettings;
  80. // ================================================================================ //
  81. // Private Classes //
  82. // ================================================================================ //
  83. [System.Serializable]
  84. private class InputData
  85. {
  86. public bool IsCurrentTarget;
  87. public bool IsLast;
  88. public float Distance;
  89. }
  90. // ================================================================================ //
  91. // Private Fields //
  92. // ================================================================================ //
  93. private InputData input = new InputData();
  94. private MachineState state = new MachineState { Type = StateType.Hidden, HasArrow = false };
  95. private float arrowTime = 0;
  96. private float mapPinTime = 0;
  97. private float L0 => RoadSignSettings.FollowDistance;
  98. private float L1 => DirectionArrowSettings.DropDistance;
  99. private float L2 => OtherSettings.DeactivationDistance;
  100. private bool v0 => input.IsCurrentTarget && (input.Distance >= L0);
  101. private bool v1 => input.IsCurrentTarget && (input.Distance < L0) && (input.Distance >= L1);
  102. private bool v2 => input.IsCurrentTarget && (input.Distance < L1) && (input.Distance >= L2);
  103. private bool v3 => input.IsCurrentTarget && (input.Distance < L2);
  104. private bool v4 => !input.IsCurrentTarget && (input.Distance <= L2);
  105. private bool v5 => !input.IsCurrentTarget && (input.Distance > L2);
  106. private Transform arrowContainer => DirectionArrowSettings.Container;
  107. private Transform signContainer => RoadSignSettings.Container;
  108. private Transform mapPinContainer => FinishSignSettings.Container;
  109. // ================================================================================ //
  110. // Monobehaviour Methods //
  111. // ================================================================================ //
  112. void OnValidate()
  113. {
  114. if (L1 > L0)
  115. {
  116. DirectionArrowSettings.DropDistance = RoadSignSettings.FollowDistance;
  117. }
  118. if (L2 > L1)
  119. {
  120. OtherSettings.DeactivationDistance = DirectionArrowSettings.DropDistance;
  121. }
  122. }
  123. // ================================================================================ //
  124. // AbstractSignPost Methods //
  125. // ================================================================================ //
  126. public override void Init(MapboxRoute route)
  127. {
  128. state = new MachineState { Type = StateType.Hidden, HasArrow = false };
  129. gameObject.SetActive(false);
  130. }
  131. public override void OffCurrentTarget(SignPostEventArgs args) {}
  132. public override void OnCurrentTarget(SignPostEventArgs args) { }
  133. public override bool UpdateSignPost(SignPostEventArgs args)
  134. {
  135. input.IsCurrentTarget = args.IsCurrentTarget;
  136. input.Distance = args.Distance;
  137. input.IsLast = args.StepIndex == (args.Route.NumberOfSteps - 1);
  138. var result = step();
  139. update(args);
  140. return result;
  141. }
  142. // ================================================================================ //
  143. // Private Methods //
  144. // ================================================================================ //
  145. private bool step()
  146. {
  147. bool result = !v3;
  148. if (v0)
  149. {
  150. setState(StateType.Following, false);
  151. }
  152. else if (v1)
  153. {
  154. setState(StateType.Idle, false);
  155. }
  156. else if (v2)
  157. {
  158. if (input.IsLast)
  159. {
  160. setState(StateType.MapPin, false);
  161. }
  162. else
  163. {
  164. setState(StateType.Idle, true);
  165. }
  166. }
  167. else if (v3 | v4 && !input.IsLast)
  168. {
  169. setState(StateType.Deactivated, !DirectionArrowSettings.HideArrowOnNextTarget);
  170. }
  171. else if (!input.IsLast)
  172. {
  173. setState(StateType.Hidden, false);
  174. }
  175. return result;
  176. }
  177. private void setState(StateType type, bool HasArrow)
  178. {
  179. setState(new MachineState { Type = type, HasArrow = HasArrow });
  180. }
  181. private void setState(MachineState next)
  182. {
  183. var ArrowContainer = DirectionArrowSettings.Container;
  184. var MapPinContainer = FinishSignSettings.Container;
  185. // Hidden -> Not Hiddeen transitions
  186. if (state.Type == StateType.Hidden && next.Type != StateType.Hidden)
  187. {
  188. gameObject.SetActive(true);
  189. ArrowContainer?.gameObject.SetActive(false);
  190. MapPinContainer?.gameObject.SetActive(false);
  191. }
  192. else if (state.Type != StateType.Hidden && next.Type == StateType.Hidden)
  193. {
  194. gameObject.SetActive(false);
  195. ArrowContainer?.gameObject.SetActive(false);
  196. MapPinContainer?.gameObject.SetActive(false);
  197. }
  198. // When transitioning from a state without arrow to a state with arrow...
  199. if (next.HasArrow && !state.HasArrow)
  200. {
  201. ArrowContainer?.gameObject.SetActive(true);
  202. arrowTime = 0;
  203. }
  204. // The opposite case...
  205. else if (!next.HasArrow && state.HasArrow)
  206. {
  207. ArrowContainer?.gameObject.SetActive(false);
  208. }
  209. // Map pin transition
  210. if (next.Type == StateType.MapPin && state.Type != StateType.MapPin)
  211. {
  212. MapPinContainer?.gameObject.SetActive(true);
  213. mapPinTime = 0;
  214. }
  215. else if (next.Type != StateType.MapPin && state.Type == StateType.MapPin)
  216. {
  217. MapPinContainer?.gameObject.SetActive(false);
  218. }
  219. state = next;
  220. }
  221. private void update(SignPostEventArgs args)
  222. {
  223. var SignContainer = RoadSignSettings.Container;
  224. var ArrowContainer = DirectionArrowSettings.Container;
  225. var MapPinContainer = FinishSignSettings.Container;
  226. var groundHeight = args.Route.Settings.GroundHeight;
  227. var relative = args.TargetPos - args.UserPos;
  228. relative.y = 0;
  229. var dir = relative.normalized;
  230. transform.position = args.TargetPos;
  231. Utils.Misc.SetTransformPositionY(transform, 0);
  232. if (ArrowContainer != null && ArrowContainer.gameObject.activeSelf)
  233. {
  234. if (args.StepIndex == args.Route.NumberOfSteps - 1)
  235. {
  236. ArrowContainer.gameObject.SetActive(false);
  237. }
  238. float amp = 0.2f;
  239. var dropY = SignContainer == null ? RoadSignSettings.Height : SignContainer.transform.position.y;
  240. DropAndFloatUpdate(ArrowContainer.transform, arrowTime, dropY, Camera.main.transform.position.y, 2.0f, amp, 0.3f, 0.1f, 0.02f);
  241. arrowTime += Time.deltaTime;
  242. // Point it to the next target, if there is one
  243. if (args.NextTargetPos is Vector3 nextTargetPos)
  244. {
  245. var lookAtPos = MathUtils.SetY(nextTargetPos, ArrowContainer.transform.position.y);
  246. ArrowContainer.transform.LookAt(lookAtPos, Vector3.up);
  247. }
  248. else
  249. {
  250. ArrowContainer.gameObject.SetActive(false);
  251. }
  252. }
  253. switch (state.Type)
  254. {
  255. case StateType.Hidden:
  256. break;
  257. case StateType.Following:
  258. if (SignContainer != null)
  259. {
  260. SignContainer.transform.position = MathUtils.SetY(args.UserPos, 0) + L0 * dir;
  261. SignContainer.transform.forward = dir;
  262. }
  263. break;
  264. case StateType.Idle:
  265. break;
  266. case StateType.MapPin:
  267. if (MapPinContainer != null)
  268. {
  269. if (SignContainer != null)
  270. {
  271. var pos = SignContainer.transform.localToWorldMatrix.MultiplyPoint(FinishSignSettings.Offset);
  272. MapPinContainer.transform.position = pos;
  273. }
  274. else
  275. {
  276. MapPinContainer.transform.position = args.TargetPos;
  277. }
  278. float amp = 1.0f;
  279. DropAndFloatUpdate(MapPinContainer.transform, mapPinTime, 50, -groundHeight + amp, 1.5f, amp, 0.3f, 0.1f, 0.02f);
  280. mapPinTime += Time.deltaTime;
  281. }
  282. break;
  283. }
  284. if (SignContainer != null)
  285. {
  286. Utils.Misc.SetTransformPositionY(SignContainer.transform, Camera.main.transform.position.y + RoadSignSettings.Height);
  287. }
  288. if (state.Type != StateType.Hidden)
  289. {
  290. if (RoadSignSettings.DistanceLabel != null)
  291. {
  292. RoadSignSettings.DistanceLabel.text = $"{args.Distance.ToString("0")} m";
  293. }
  294. if (RoadSignSettings.DirectionLabel != null)
  295. {
  296. RoadSignSettings.DirectionLabel.text = args.Instruction;
  297. }
  298. }
  299. }
  300. public static float EaseOutCubic(float start, float end, float value)
  301. {
  302. value--;
  303. end -= start;
  304. return end * (value * value * value + 1) + start;
  305. }
  306. public static void DropAndFloatUpdate(
  307. Transform transform,
  308. float time,
  309. float StartY,
  310. float EndY,
  311. float DropDuration,
  312. float Amplitude,
  313. float Frequency,
  314. float LfoAmp,
  315. float LfoFreq
  316. )
  317. {
  318. bool isDownwards = EndY < StartY;
  319. if (time < DropDuration)
  320. {
  321. float y = EaseOutCubic(StartY, EndY - Amplitude / 2, time / DropDuration);
  322. transform.position = MathUtils.SetY(transform.position, y);
  323. }
  324. else
  325. {
  326. float t = time - DropDuration;
  327. // Add a "wavy" floating movement
  328. float lfo = LfoAmp * Mathf.Sin(2 * Mathf.PI * LfoFreq * t);
  329. float phase = isDownwards ? 1.5f * Mathf.PI : 0;
  330. float dy = (Amplitude + lfo) * Mathf.Sin(t * 2 * Mathf.PI * Frequency + phase);
  331. transform.position = MathUtils.SetY(transform.position, EndY - Amplitude / 2 + (Amplitude + lfo) + dy);
  332. }
  333. }
  334. }
  335. }