TriangleMeshNode.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. using UnityEngine;
  2. namespace PF {
  3. /** Interface for something that holds a triangle based navmesh */
  4. public interface INavmeshHolder : ITransformedGraph, INavmesh {
  5. /** Position of vertex number i in the world */
  6. Int3 GetVertex (int i);
  7. /** Position of vertex number i in coordinates local to the graph.
  8. * The up direction is always the +Y axis for these coordinates.
  9. */
  10. Int3 GetVertexInGraphSpace (int i);
  11. int GetVertexArrayIndex (int index);
  12. /** Transforms coordinates from graph space to world space */
  13. void GetTileCoordinates (int tileIndex, out int x, out int z);
  14. }
  15. /** Node represented by a triangle */
  16. public class TriangleMeshNode : MeshNode {
  17. /** Internal vertex index for the first vertex */
  18. public int v0;
  19. /** Internal vertex index for the second vertex */
  20. public int v1;
  21. /** Internal vertex index for the third vertex */
  22. public int v2;
  23. /** Holds INavmeshHolder references for all graph indices to be able to access them in a performant manner */
  24. protected static INavmeshHolder[] _navmeshHolders = new INavmeshHolder[0];
  25. /** Used for synchronised access to the #_navmeshHolders array */
  26. protected static readonly System.Object lockObject = new System.Object();
  27. public static INavmeshHolder GetNavmeshHolder (uint graphIndex) {
  28. return _navmeshHolders[(int)graphIndex];
  29. }
  30. /** Sets the internal navmesh holder for a given graph index.
  31. * \warning Internal method
  32. */
  33. public static void SetNavmeshHolder (int graphIndex, INavmeshHolder graph) {
  34. // We need to lock to make sure that
  35. // the resize operation is thread safe
  36. lock (lockObject) {
  37. if (graphIndex >= _navmeshHolders.Length) {
  38. var gg = new INavmeshHolder[graphIndex+1];
  39. _navmeshHolders.CopyTo(gg, 0);
  40. _navmeshHolders = gg;
  41. }
  42. _navmeshHolders[graphIndex] = graph;
  43. }
  44. }
  45. /** Set the position of this node to the average of its 3 vertices */
  46. public void UpdatePositionFromVertices () {
  47. Int3 a, b, c;
  48. GetVertices(out a, out b, out c);
  49. position = (a + b + c) * 0.333333f;
  50. }
  51. /** Return a number identifying a vertex.
  52. * This number does not necessarily need to be a index in an array but two different vertices (in the same graph) should
  53. * not have the same vertex numbers.
  54. */
  55. public int GetVertexIndex (int i) {
  56. return i == 0 ? v0 : (i == 1 ? v1 : v2);
  57. }
  58. /** Return a number specifying an index in the source vertex array.
  59. * The vertex array can for example be contained in a recast tile, or be a navmesh graph, that is graph dependant.
  60. * This is slower than GetVertexIndex, if you only need to compare vertices, use GetVertexIndex.
  61. */
  62. public int GetVertexArrayIndex (int i) {
  63. return GetNavmeshHolder(GraphIndex).GetVertexArrayIndex(i == 0 ? v0 : (i == 1 ? v1 : v2));
  64. }
  65. /** Returns all 3 vertices of this node in world space */
  66. public void GetVertices (out Int3 v0, out Int3 v1, out Int3 v2) {
  67. // Get the object holding the vertex data for this node
  68. // This is usually a graph or a recast graph tile
  69. var holder = GetNavmeshHolder(GraphIndex);
  70. v0 = holder.GetVertex(this.v0);
  71. v1 = holder.GetVertex(this.v1);
  72. v2 = holder.GetVertex(this.v2);
  73. }
  74. /** Returns all 3 vertices of this node in graph space */
  75. public void GetVerticesInGraphSpace (out Int3 v0, out Int3 v1, out Int3 v2) {
  76. // Get the object holding the vertex data for this node
  77. // This is usually a graph or a recast graph tile
  78. var holder = GetNavmeshHolder(GraphIndex);
  79. v0 = holder.GetVertexInGraphSpace(this.v0);
  80. v1 = holder.GetVertexInGraphSpace(this.v1);
  81. v2 = holder.GetVertexInGraphSpace(this.v2);
  82. }
  83. public override Int3 GetVertex (int i) {
  84. return GetNavmeshHolder(GraphIndex).GetVertex(GetVertexIndex(i));
  85. }
  86. public Int3 GetVertexInGraphSpace (int i) {
  87. return GetNavmeshHolder(GraphIndex).GetVertexInGraphSpace(GetVertexIndex(i));
  88. }
  89. public override int GetVertexCount () {
  90. // A triangle has 3 vertices
  91. return 3;
  92. }
  93. public override Vector3 ClosestPointOnNode (Vector3 p) {
  94. Int3 a, b, c;
  95. GetVertices(out a, out b, out c);
  96. return Polygon.ClosestPointOnTriangle((Vector3)a, (Vector3)b, (Vector3)c, p);
  97. }
  98. /** Closest point on the node when seen from above.
  99. * This method is mostly for internal use as the #Pathfinding.NavmeshBase.Linecast methods use it.
  100. *
  101. * - The returned point is the closest one on the node to \a p when seen from above (relative to the graph).
  102. * This is important mostly for sloped surfaces.
  103. * - The returned point is an Int3 point in graph space.
  104. * - It is guaranteed to be inside the node, so if you call #ContainsPointInGraphSpace with the return value from this method the result is guaranteed to be true.
  105. *
  106. * This method is slower than e.g #ClosestPointOnNode or #ClosestPointOnNodeXZ.
  107. * However they do not have the same guarantees as this method has.
  108. */
  109. internal Int3 ClosestPointOnNodeXZInGraphSpace (Vector3 p) {
  110. // Get the vertices that make up the triangle
  111. Int3 a, b, c;
  112. GetVerticesInGraphSpace(out a, out b, out c);
  113. // Convert p to graph space
  114. p = GetNavmeshHolder(GraphIndex).transform.InverseTransform(p);
  115. // Find the closest point on the triangle to p when looking at the triangle from above (relative to the graph)
  116. var closest = Polygon.ClosestPointOnTriangleXZ((Vector3)a, (Vector3)b, (Vector3)c, p);
  117. // Make sure the point is actually inside the node
  118. var i3closest = (Int3)closest;
  119. if (ContainsPointInGraphSpace(i3closest)) {
  120. // Common case
  121. return i3closest;
  122. } else {
  123. // Annoying...
  124. // The closest point when converted from floating point coordinates to integer coordinates
  125. // is not actually inside the node. It needs to be inside the node for some methods
  126. // (like for example Linecast) to work properly.
  127. // Try the 8 integer coordinates around the closest point
  128. // and check if any one of them are completely inside the node.
  129. // This will most likely succeed as it should be very close.
  130. for (int dx = -1; dx <= 1; dx++) {
  131. for (int dz = -1; dz <= 1; dz++) {
  132. if ((dx != 0 || dz != 0)) {
  133. var candidate = new Int3(i3closest.x + dx, i3closest.y, i3closest.z + dz);
  134. if (ContainsPointInGraphSpace(candidate)) return candidate;
  135. }
  136. }
  137. }
  138. // Happens veery rarely.
  139. // Pick the closest vertex of the triangle.
  140. // The vertex is guaranteed to be inside the triangle.
  141. var da = (a - i3closest).sqrMagnitudeLong;
  142. var db = (b - i3closest).sqrMagnitudeLong;
  143. var dc = (c - i3closest).sqrMagnitudeLong;
  144. return da < db ? (da < dc ? a : c) : (db < dc ? b : c);
  145. }
  146. }
  147. public override Vector3 ClosestPointOnNodeXZ (Vector3 p) {
  148. // Get all 3 vertices for this node
  149. Int3 tp1, tp2, tp3;
  150. GetVertices(out tp1, out tp2, out tp3);
  151. return Polygon.ClosestPointOnTriangleXZ((Vector3)tp1, (Vector3)tp2, (Vector3)tp3, p);
  152. }
  153. public override bool ContainsPoint (Vector3 p) {
  154. return ContainsPointInGraphSpace((Int3)GetNavmeshHolder(GraphIndex).transform.InverseTransform(p));
  155. }
  156. public override bool ContainsPointInGraphSpace (Int3 p) {
  157. // Get all 3 vertices for this node
  158. Int3 a, b, c;
  159. GetVerticesInGraphSpace(out a, out b, out c);
  160. if ((long)(b.x - a.x) * (long)(p.z - a.z) - (long)(p.x - a.x) * (long)(b.z - a.z) > 0) return false;
  161. if ((long)(c.x - b.x) * (long)(p.z - b.z) - (long)(p.x - b.x) * (long)(c.z - b.z) > 0) return false;
  162. if ((long)(a.x - c.x) * (long)(p.z - c.z) - (long)(p.x - c.x) * (long)(a.z - c.z) > 0) return false;
  163. return true;
  164. // Equivalent code, but the above code is faster
  165. //return Polygon.IsClockwiseMargin (a,b, p) && Polygon.IsClockwiseMargin (b,c, p) && Polygon.IsClockwiseMargin (c,a, p);
  166. //return Polygon.ContainsPoint(g.GetVertex(v0),g.GetVertex(v1),g.GetVertex(v2),p);
  167. }
  168. public override void UpdateRecursiveG (Path path, PathNode pathNode, PathHandler handler) {
  169. pathNode.UpdateG(path);
  170. handler.heap.Add(pathNode);
  171. if (connections == null) return;
  172. for (int i = 0; i < connections.Length; i++) {
  173. GraphNode other = connections[i].node;
  174. PathNode otherPN = handler.GetPathNode(other);
  175. if (otherPN.parent == pathNode && otherPN.pathID == handler.PathID) other.UpdateRecursiveG(path, otherPN, handler);
  176. }
  177. }
  178. public override void Open (Path path, PathNode pathNode, PathHandler handler) {
  179. if (connections == null) return;
  180. // Flag2 indicates if this node needs special treatment
  181. // with regard to connection costs
  182. bool flag2 = pathNode.flag2;
  183. // Loop through all connections
  184. for (int i = connections.Length-1; i >= 0; i--) {
  185. var conn = connections[i];
  186. var other = conn.node;
  187. // Make sure we can traverse the neighbour
  188. if (path.CanTraverse(conn.node)) {
  189. PathNode pathOther = handler.GetPathNode(conn.node);
  190. // Fast path out, worth it for triangle mesh nodes since they usually have degree 2 or 3
  191. if (pathOther == pathNode.parent) {
  192. continue;
  193. }
  194. uint cost = conn.cost;
  195. if (flag2 || pathOther.flag2) {
  196. // Get special connection cost from the path
  197. // This is used by the start and end nodes
  198. cost = path.GetConnectionSpecialCost(this, conn.node, cost);
  199. }
  200. // Test if we have seen the other node before
  201. if (pathOther.pathID != handler.PathID) {
  202. // We have not seen the other node before
  203. // So the path from the start through this node to the other node
  204. // must be the shortest one so far
  205. // Might not be assigned
  206. pathOther.node = conn.node;
  207. pathOther.parent = pathNode;
  208. pathOther.pathID = handler.PathID;
  209. pathOther.cost = cost;
  210. pathOther.H = path.CalculateHScore(other);
  211. pathOther.UpdateG(path);
  212. handler.heap.Add(pathOther);
  213. } else {
  214. // If not we can test if the path from this node to the other one is a better one than the one already used
  215. if (pathNode.G + cost + path.GetTraversalCost(other) < pathOther.G) {
  216. pathOther.cost = cost;
  217. pathOther.parent = pathNode;
  218. other.UpdateRecursiveG(path, pathOther, handler);
  219. }
  220. }
  221. }
  222. }
  223. }
  224. /** Returns the edge which is shared with \a other.
  225. * If no edge is shared, -1 is returned.
  226. * If there is a connection with the other node, but the connection is not marked as using a particular edge of the shape of the node
  227. * then 0xFF will be returned.
  228. *
  229. * The vertices in the edge can be retrieved using
  230. * \code
  231. * var edge = node.SharedEdge(other);
  232. * var a = node.GetVertex(edge);
  233. * var b = node.GetVertex((edge+1) % node.GetVertexCount());
  234. * \endcode
  235. *
  236. * \see #GetPortal which also handles edges that are shared over tile borders and some types of node links
  237. */
  238. public int SharedEdge (GraphNode other) {
  239. var edge = -1;
  240. for (int i = 0; i < connections.Length; i++) {
  241. if (connections[i].node == other) edge = connections[i].shapeEdge;
  242. }
  243. return edge;
  244. }
  245. public override bool GetPortal (GraphNode toNode, System.Collections.Generic.List<Vector3> left, System.Collections.Generic.List<Vector3> right, bool backwards) {
  246. int aIndex, bIndex;
  247. return GetPortal(toNode, left, right, backwards, out aIndex, out bIndex);
  248. }
  249. public bool GetPortal (GraphNode toNode, System.Collections.Generic.List<Vector3> left, System.Collections.Generic.List<Vector3> right, bool backwards, out int aIndex, out int bIndex) {
  250. aIndex = -1;
  251. bIndex = -1;
  252. //If the nodes are in different graphs, this function has no idea on how to find a shared edge.
  253. if (backwards || toNode.GraphIndex != GraphIndex) return false;
  254. // Since the nodes are in the same graph, they are both TriangleMeshNodes
  255. // So we don't need to care about other types of nodes
  256. var toTriNode = toNode as TriangleMeshNode;
  257. var edge = SharedEdge(toTriNode);
  258. // A connection was found, but it specifically didn't use an edge
  259. if (edge == 0xFF) return false;
  260. // No connection was found between the nodes
  261. // Check if there is a node link that connects them
  262. if (edge == -1) {
  263. return false;
  264. }
  265. aIndex = edge;
  266. bIndex = (edge + 1) % GetVertexCount();
  267. // Get the vertices of the shared edge for the first node
  268. Int3 v1a = GetVertex(edge);
  269. Int3 v1b = GetVertex((edge+1) % GetVertexCount());
  270. // Get tile indices
  271. int tileIndex1 = (GetVertexIndex(0) >> NavmeshBase.TileIndexOffset) & NavmeshBase.TileIndexMask;
  272. int tileIndex2 = (toTriNode.GetVertexIndex(0) >> NavmeshBase.TileIndexOffset) & NavmeshBase.TileIndexMask;
  273. if (tileIndex1 != tileIndex2) {
  274. // When the nodes are in different tiles, the edges might not be completely identical
  275. // so another technique is needed.
  276. // Get the tile coordinates, from them we can figure out which edge is going to be shared
  277. int x1, x2, z1, z2, coord;
  278. INavmeshHolder nm = GetNavmeshHolder(GraphIndex);
  279. nm.GetTileCoordinates(tileIndex1, out x1, out z1);
  280. nm.GetTileCoordinates(tileIndex2, out x2, out z2);
  281. if (System.Math.Abs(x1-x2) == 1) coord = 2;
  282. else if (System.Math.Abs(z1-z2) == 1) coord = 0;
  283. else return false; // Tiles are not adjacent. This is likely a custom connection between two nodes.
  284. var otherEdge = toTriNode.SharedEdge(this);
  285. // A connection was found, but it specifically didn't use an edge. This is odd since the connection in the other direction did use an edge
  286. if (otherEdge == 0xFF) throw new System.Exception("Connection used edge in one direction, but not in the other direction. Has the wrong overload of AddConnection been used?");
  287. // If it is -1 then it must be a one-way connection. Fall back to using the whole edge
  288. if (otherEdge != -1) {
  289. // When the nodes are in different tiles, they might not share exactly the same edge
  290. // so we clamp the portal to the segment of the edges which they both have.
  291. int mincoord = System.Math.Min(v1a[coord], v1b[coord]);
  292. int maxcoord = System.Math.Max(v1a[coord], v1b[coord]);
  293. // Get the vertices of the shared edge for the second node
  294. Int3 v2a = toTriNode.GetVertex(otherEdge);
  295. Int3 v2b = toTriNode.GetVertex((otherEdge+1) % toTriNode.GetVertexCount());
  296. mincoord = System.Math.Max(mincoord, System.Math.Min(v2a[coord], v2b[coord]));
  297. maxcoord = System.Math.Min(maxcoord, System.Math.Max(v2a[coord], v2b[coord]));
  298. if (v1a[coord] < v1b[coord]) {
  299. v1a[coord] = mincoord;
  300. v1b[coord] = maxcoord;
  301. } else {
  302. v1a[coord] = maxcoord;
  303. v1b[coord] = mincoord;
  304. }
  305. }
  306. }
  307. if (left != null) {
  308. // All triangles should be laid out in clockwise order so v1b is the rightmost vertex (seen from this node)
  309. left.Add((Vector3)v1a);
  310. right.Add((Vector3)v1b);
  311. }
  312. return true;
  313. }
  314. /** \todo This is the area in XZ space, use full 3D space for higher correctness maybe? */
  315. public override float SurfaceArea () {
  316. var holder = GetNavmeshHolder(GraphIndex);
  317. return System.Math.Abs(VectorMath.SignedTriangleAreaTimes2XZ(holder.GetVertex(v0), holder.GetVertex(v1), holder.GetVertex(v2))) * 0.5f;
  318. }
  319. public override void SerializeNode (GraphSerializationContext ctx) {
  320. base.SerializeNode(ctx);
  321. ctx.writer.Write(v0);
  322. ctx.writer.Write(v1);
  323. ctx.writer.Write(v2);
  324. }
  325. public override void DeserializeNode (GraphSerializationContext ctx) {
  326. base.DeserializeNode(ctx);
  327. v0 = ctx.reader.ReadInt32();
  328. v1 = ctx.reader.ReadInt32();
  329. v2 = ctx.reader.ReadInt32();
  330. }
  331. }
  332. }