NodeLink3.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. using Pathfinding.Util;
  5. public class NodeLink3Node : PointNode {
  6. public NodeLink3 link;
  7. public Vector3 portalA;
  8. public Vector3 portalB;
  9. public NodeLink3Node (AstarPath active) : base(active) {}
  10. public override bool GetPortal (GraphNode other, List<Vector3> left, List<Vector3> right, bool backwards) {
  11. if (this.connections.Length < 2) return false;
  12. if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length);
  13. if (left != null) {
  14. left.Add(portalA);
  15. right.Add(portalB);
  16. }
  17. return true;
  18. }
  19. public GraphNode GetOther (GraphNode a) {
  20. if (this.connections.Length < 2) return null;
  21. if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length);
  22. return a == connections[0].node ? (connections[1].node as NodeLink3Node).GetOtherInternal(this) : (connections[0].node as NodeLink3Node).GetOtherInternal(this);
  23. }
  24. GraphNode GetOtherInternal (GraphNode a) {
  25. if (this.connections.Length < 2) return null;
  26. return a == connections[0].node ? connections[1].node : connections[0].node;
  27. }
  28. }
  29. /// <summary>
  30. /// Connects two TriangleMeshNodes (recast/navmesh graphs) as if they had shared an edge.
  31. /// Note: Usually you do not want to use this type of link, you want to use NodeLink2 or NodeLink (sorry for the not so descriptive names).
  32. /// </summary>
  33. [AddComponentMenu("Pathfinding/Link3")]
  34. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_node_link3.php")]
  35. public class NodeLink3 : GraphModifier {
  36. protected static Dictionary<GraphNode, NodeLink3> reference = new Dictionary<GraphNode, NodeLink3>();
  37. public static NodeLink3 GetNodeLink (GraphNode node) {
  38. NodeLink3 v;
  39. reference.TryGetValue(node, out v);
  40. return v;
  41. }
  42. /// <summary>End position of the link</summary>
  43. public Transform end;
  44. /// <summary>
  45. /// The connection will be this times harder/slower to traverse.
  46. /// Note that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should
  47. /// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether.
  48. /// </summary>
  49. public float costFactor = 1.0f;
  50. /// <summary>Make a one-way connection</summary>
  51. public bool oneWay = false;
  52. public Transform StartTransform {
  53. get { return transform; }
  54. }
  55. public Transform EndTransform {
  56. get { return end; }
  57. }
  58. NodeLink3Node startNode;
  59. NodeLink3Node endNode;
  60. MeshNode connectedNode1, connectedNode2;
  61. Vector3 clamped1, clamped2;
  62. bool postScanCalled = false;
  63. public GraphNode StartNode {
  64. get { return startNode; }
  65. }
  66. public GraphNode EndNode {
  67. get { return endNode; }
  68. }
  69. public override void OnPostScan () {
  70. if (AstarPath.active.isScanning) {
  71. InternalOnPostScan();
  72. } else {
  73. AstarPath.active.AddWorkItem(new AstarWorkItem(force => {
  74. InternalOnPostScan();
  75. return true;
  76. }));
  77. }
  78. }
  79. public void InternalOnPostScan () {
  80. #if !ASTAR_NO_POINT_GRAPH
  81. if (AstarPath.active.data.pointGraph == null) {
  82. AstarPath.active.data.AddGraph(typeof(PointGraph));
  83. }
  84. //Get nearest nodes from the first point graph, assuming both start and end transforms are nodes
  85. startNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)StartTransform.position);
  86. startNode.link = this;
  87. endNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)EndTransform.position);
  88. endNode.link = this;
  89. #else
  90. throw new System.Exception("Point graphs are not included. Check your A* Optimization settings.");
  91. #endif
  92. connectedNode1 = null;
  93. connectedNode2 = null;
  94. if (startNode == null || endNode == null) {
  95. startNode = null;
  96. endNode = null;
  97. return;
  98. }
  99. postScanCalled = true;
  100. reference[startNode] = this;
  101. reference[endNode] = this;
  102. Apply(true);
  103. }
  104. public override void OnGraphsPostUpdate () {
  105. if (!AstarPath.active.isScanning) {
  106. if (connectedNode1 != null && connectedNode1.Destroyed) {
  107. connectedNode1 = null;
  108. }
  109. if (connectedNode2 != null && connectedNode2.Destroyed) {
  110. connectedNode2 = null;
  111. }
  112. if (!postScanCalled) {
  113. OnPostScan();
  114. } else {
  115. //OnPostScan will also call this method
  116. Apply(false);
  117. }
  118. }
  119. }
  120. protected override void OnEnable () {
  121. base.OnEnable();
  122. #if !ASTAR_NO_POINT_GRAPH
  123. if (Application.isPlaying && AstarPath.active != null && AstarPath.active.data != null && AstarPath.active.data.pointGraph != null) {
  124. OnGraphsPostUpdate();
  125. }
  126. #endif
  127. }
  128. protected override void OnDisable () {
  129. base.OnDisable();
  130. postScanCalled = false;
  131. if (startNode != null) reference.Remove(startNode);
  132. if (endNode != null) reference.Remove(endNode);
  133. if (startNode != null && endNode != null) {
  134. startNode.RemoveConnection(endNode);
  135. endNode.RemoveConnection(startNode);
  136. if (connectedNode1 != null && connectedNode2 != null) {
  137. startNode.RemoveConnection(connectedNode1);
  138. connectedNode1.RemoveConnection(startNode);
  139. endNode.RemoveConnection(connectedNode2);
  140. connectedNode2.RemoveConnection(endNode);
  141. }
  142. }
  143. }
  144. void RemoveConnections (GraphNode node) {
  145. //TODO, might be better to replace connection
  146. node.ClearConnections(true);
  147. }
  148. [ContextMenu("Recalculate neighbours")]
  149. void ContextApplyForce () {
  150. if (Application.isPlaying) {
  151. Apply(true);
  152. }
  153. }
  154. public void Apply (bool forceNewCheck) {
  155. //TODO
  156. //This function assumes that connections from the n1,n2 nodes never need to be removed in the future (e.g because the nodes move or something)
  157. NNConstraint nn = NNConstraint.None;
  158. nn.distanceXZ = true;
  159. int graph = (int)startNode.GraphIndex;
  160. //Search all graphs but the one which start and end nodes are on
  161. nn.graphMask = ~(1 << graph);
  162. bool same = true;
  163. {
  164. var info = AstarPath.active.GetNearest(StartTransform.position, nn);
  165. same &= info.node == connectedNode1 && info.node != null;
  166. connectedNode1 = info.node as MeshNode;
  167. clamped1 = info.position;
  168. if (connectedNode1 != null) Debug.DrawRay((Vector3)connectedNode1.position, Vector3.up*5, Color.red);
  169. }
  170. {
  171. var info = AstarPath.active.GetNearest(EndTransform.position, nn);
  172. same &= info.node == connectedNode2 && info.node != null;
  173. connectedNode2 = info.node as MeshNode;
  174. clamped2 = info.position;
  175. if (connectedNode2 != null) Debug.DrawRay((Vector3)connectedNode2.position, Vector3.up*5, Color.cyan);
  176. }
  177. if (connectedNode2 == null || connectedNode1 == null) return;
  178. startNode.SetPosition((Int3)StartTransform.position);
  179. endNode.SetPosition((Int3)EndTransform.position);
  180. if (same && !forceNewCheck) return;
  181. RemoveConnections(startNode);
  182. RemoveConnections(endNode);
  183. uint cost = (uint)Mathf.RoundToInt(((Int3)(StartTransform.position-EndTransform.position)).costMagnitude*costFactor);
  184. startNode.AddConnection(endNode, cost);
  185. endNode.AddConnection(startNode, cost);
  186. Int3 dir = connectedNode2.position - connectedNode1.position;
  187. for (int a = 0; a < connectedNode1.GetVertexCount(); a++) {
  188. Int3 va1 = connectedNode1.GetVertex(a);
  189. Int3 va2 = connectedNode1.GetVertex((a+1) % connectedNode1.GetVertexCount());
  190. if (Int3.DotLong((va2-va1).Normal2D(), dir) > 0) continue;
  191. for (int b = 0; b < connectedNode2.GetVertexCount(); b++) {
  192. Int3 vb1 = connectedNode2.GetVertex(b);
  193. Int3 vb2 = connectedNode2.GetVertex((b+1) % connectedNode2.GetVertexCount());
  194. if (Int3.DotLong((vb2-vb1).Normal2D(), dir) < 0) continue;
  195. if (Int3.Angle((vb2-vb1), (va2-va1)) > (170.0/360.0f)*Mathf.PI*2) {
  196. float t1 = 0;
  197. float t2 = 1;
  198. t2 = System.Math.Min(t2, VectorMath.ClosestPointOnLineFactor(va1, va2, vb1));
  199. t1 = System.Math.Max(t1, VectorMath.ClosestPointOnLineFactor(va1, va2, vb2));
  200. if (t2 < t1) {
  201. Debug.LogError("Something went wrong! " + t1 + " " + t2 + " " + va1 + " " + va2 + " " + vb1 + " " + vb2+"\nTODO, how can this happen?");
  202. } else {
  203. Vector3 pa = (Vector3)(va2-va1)*t1 + (Vector3)va1;
  204. Vector3 pb = (Vector3)(va2-va1)*t2 + (Vector3)va1;
  205. startNode.portalA = pa;
  206. startNode.portalB = pb;
  207. endNode.portalA = pb;
  208. endNode.portalB = pa;
  209. //Add connections between nodes, or replace old connections if existing
  210. connectedNode1.AddConnection(startNode, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
  211. connectedNode2.AddConnection(endNode, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
  212. startNode.AddConnection(connectedNode1, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor));
  213. endNode.AddConnection(connectedNode2, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor));
  214. return;
  215. }
  216. }
  217. }
  218. }
  219. }
  220. private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f);
  221. private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f);
  222. public virtual void OnDrawGizmosSelected () {
  223. OnDrawGizmos(true);
  224. }
  225. public void OnDrawGizmos () {
  226. OnDrawGizmos(false);
  227. }
  228. public void OnDrawGizmos (bool selected) {
  229. Color col = selected ? GizmosColorSelected : GizmosColor;
  230. if (StartTransform != null) {
  231. Draw.Gizmos.CircleXZ(StartTransform.position, 0.4f, col);
  232. }
  233. if (EndTransform != null) {
  234. Draw.Gizmos.CircleXZ(EndTransform.position, 0.4f, col);
  235. }
  236. if (StartTransform != null && EndTransform != null) {
  237. Draw.Gizmos.Bezier(StartTransform.position, EndTransform.position, col);
  238. if (selected) {
  239. Vector3 cross = Vector3.Cross(Vector3.up, (EndTransform.position-StartTransform.position)).normalized;
  240. Draw.Gizmos.Bezier(StartTransform.position+cross*0.1f, EndTransform.position+cross*0.1f, col);
  241. Draw.Gizmos.Bezier(StartTransform.position-cross*0.1f, EndTransform.position-cross*0.1f, col);
  242. }
  243. }
  244. }
  245. }
  246. }