NavMeshGenerator.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. using Pathfinding.Util;
  5. using Pathfinding.Serialization;
  6. public interface INavmesh {
  7. void GetNodes(System.Action<GraphNode> del);
  8. }
  9. /// <summary>
  10. /// Generates graphs based on navmeshes.
  11. /// [Open online documentation to see images]
  12. ///
  13. /// Navmeshes are meshes in which each triangle defines a walkable area.
  14. /// These are great because the AI can get so much more information on how it can walk.
  15. /// Polygons instead of points mean that the <see cref="FunnelModifier"/> can produce really nice looking paths, and the graphs are also really fast to search
  16. /// and have a low memory footprint because fewer nodes are usually needed to describe the same area compared to grid graphs.
  17. ///
  18. /// The navmesh graph requires that you create a navmesh manually. The package also has support for generating navmeshes automatically using the <see cref="RecastGraph"/>.
  19. ///
  20. /// For a tutorial on how to configure a navmesh graph, take a look at getstarted2 (view in online documentation for working links).
  21. ///
  22. /// [Open online documentation to see images]
  23. /// [Open online documentation to see images]
  24. ///
  25. /// See: Pathfinding.RecastGraph
  26. /// </summary>
  27. [JsonOptIn]
  28. [Pathfinding.Util.Preserve]
  29. public class NavMeshGraph : NavmeshBase, IUpdatableGraph {
  30. /// <summary>Mesh to construct navmesh from</summary>
  31. [JsonMember]
  32. public Mesh sourceMesh;
  33. /// <summary>Offset in world space</summary>
  34. [JsonMember]
  35. public Vector3 offset;
  36. /// <summary>Rotation in degrees</summary>
  37. [JsonMember]
  38. public Vector3 rotation;
  39. /// <summary>Scale of the graph</summary>
  40. [JsonMember]
  41. public float scale = 1;
  42. /// <summary>
  43. /// Determines how normals are calculated.
  44. /// Disable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.
  45. ///
  46. /// By default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction.
  47. /// The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way.
  48. /// This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh.
  49. /// If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.
  50. ///
  51. /// If you for example take a look at the image below. In the upper case then the nodes on the bottom half of the
  52. /// mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been
  53. /// modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on
  54. /// the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option
  55. /// is set to false instead the nodes are connected properly as in the original mesh all normals point outwards.
  56. /// [Open online documentation to see images]
  57. ///
  58. /// The default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that
  59. /// has all normals pointing downwards and this option is false, then some methods like <see cref="PointOnNavmesh"/> will not work correctly
  60. /// as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway
  61. /// as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction.
  62. /// </summary>
  63. [JsonMember]
  64. public bool recalculateNormals = true;
  65. /// <summary>
  66. /// Cached bounding box minimum of <see cref="sourceMesh"/>.
  67. /// This is important when the graph has been saved to a file and is later loaded again, but the original mesh does not exist anymore (or has been moved).
  68. /// In that case we still need to be able to find the bounding box since the <see cref="CalculateTransform"/> method uses it.
  69. /// </summary>
  70. [JsonMember]
  71. Vector3 cachedSourceMeshBoundsMin;
  72. protected override bool RecalculateNormals { get { return recalculateNormals; } }
  73. public override float TileWorldSizeX {
  74. get {
  75. return forcedBoundsSize.x;
  76. }
  77. }
  78. public override float TileWorldSizeZ {
  79. get {
  80. return forcedBoundsSize.z;
  81. }
  82. }
  83. protected override float MaxTileConnectionEdgeDistance {
  84. get {
  85. // Tiles are not supported, so this is irrelevant
  86. return 0f;
  87. }
  88. }
  89. public override GraphTransform CalculateTransform () {
  90. return new GraphTransform(Matrix4x4.TRS(offset, Quaternion.Euler(rotation), Vector3.one) * Matrix4x4.TRS(sourceMesh != null ? sourceMesh.bounds.min * scale : cachedSourceMeshBoundsMin * scale, Quaternion.identity, Vector3.one));
  91. }
  92. GraphUpdateThreading IUpdatableGraph.CanUpdateAsync (GraphUpdateObject o) {
  93. return GraphUpdateThreading.UnityThread;
  94. }
  95. void IUpdatableGraph.UpdateAreaInit (GraphUpdateObject o) {}
  96. void IUpdatableGraph.UpdateAreaPost (GraphUpdateObject o) {}
  97. void IUpdatableGraph.UpdateArea (GraphUpdateObject o) {
  98. UpdateArea(o, this);
  99. }
  100. public static void UpdateArea (GraphUpdateObject o, INavmeshHolder graph) {
  101. Bounds bounds = graph.transform.InverseTransform(o.bounds);
  102. // Bounding rectangle with integer coordinates
  103. var irect = new IntRect(
  104. Mathf.FloorToInt(bounds.min.x*Int3.Precision),
  105. Mathf.FloorToInt(bounds.min.z*Int3.Precision),
  106. Mathf.CeilToInt(bounds.max.x*Int3.Precision),
  107. Mathf.CeilToInt(bounds.max.z*Int3.Precision)
  108. );
  109. // Corners of the bounding rectangle
  110. var a = new Int3(irect.xmin, 0, irect.ymin);
  111. var b = new Int3(irect.xmin, 0, irect.ymax);
  112. var c = new Int3(irect.xmax, 0, irect.ymin);
  113. var d = new Int3(irect.xmax, 0, irect.ymax);
  114. var ymin = ((Int3)bounds.min).y;
  115. var ymax = ((Int3)bounds.max).y;
  116. // Loop through all nodes and check if they intersect the bounding box
  117. graph.GetNodes(_node => {
  118. var node = _node as TriangleMeshNode;
  119. bool inside = false;
  120. int allLeft = 0;
  121. int allRight = 0;
  122. int allTop = 0;
  123. int allBottom = 0;
  124. // Check bounding box rect in XZ plane
  125. for (int v = 0; v < 3; v++) {
  126. Int3 p = node.GetVertexInGraphSpace(v);
  127. if (irect.Contains(p.x, p.z)) {
  128. inside = true;
  129. break;
  130. }
  131. if (p.x < irect.xmin) allLeft++;
  132. if (p.x > irect.xmax) allRight++;
  133. if (p.z < irect.ymin) allTop++;
  134. if (p.z > irect.ymax) allBottom++;
  135. }
  136. if (!inside && (allLeft == 3 || allRight == 3 || allTop == 3 || allBottom == 3)) {
  137. return;
  138. }
  139. // Check if the polygon edges intersect the bounding rect
  140. for (int v = 0; v < 3; v++) {
  141. int v2 = v > 1 ? 0 : v+1;
  142. Int3 vert1 = node.GetVertexInGraphSpace(v);
  143. Int3 vert2 = node.GetVertexInGraphSpace(v2);
  144. if (VectorMath.SegmentsIntersectXZ(a, b, vert1, vert2)) { inside = true; break; }
  145. if (VectorMath.SegmentsIntersectXZ(a, c, vert1, vert2)) { inside = true; break; }
  146. if (VectorMath.SegmentsIntersectXZ(c, d, vert1, vert2)) { inside = true; break; }
  147. if (VectorMath.SegmentsIntersectXZ(d, b, vert1, vert2)) { inside = true; break; }
  148. }
  149. // Check if the node contains any corner of the bounding rect
  150. if (inside || node.ContainsPointInGraphSpace(a) || node.ContainsPointInGraphSpace(b) || node.ContainsPointInGraphSpace(c) || node.ContainsPointInGraphSpace(d)) {
  151. inside = true;
  152. }
  153. if (!inside) {
  154. return;
  155. }
  156. int allAbove = 0;
  157. int allBelow = 0;
  158. // Check y coordinate
  159. for (int v = 0; v < 3; v++) {
  160. Int3 p = node.GetVertexInGraphSpace(v);
  161. if (p.y < ymin) allBelow++;
  162. if (p.y > ymax) allAbove++;
  163. }
  164. // Polygon is either completely above the bounding box or completely below it
  165. if (allBelow == 3 || allAbove == 3) return;
  166. // Triangle is inside the bounding box!
  167. // Update it!
  168. o.WillUpdateNode(node);
  169. o.Apply(node);
  170. });
  171. }
  172. protected override IEnumerable<Progress> ScanInternal () {
  173. cachedSourceMeshBoundsMin = sourceMesh != null ? sourceMesh.bounds.min : Vector3.zero;
  174. transform = CalculateTransform();
  175. tileZCount = tileXCount = 1;
  176. tiles = new NavmeshTile[tileZCount*tileXCount];
  177. TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this);
  178. if (sourceMesh == null) {
  179. FillWithEmptyTiles();
  180. yield break;
  181. }
  182. yield return new Progress(0.0f, "Transforming Vertices");
  183. forcedBoundsSize = sourceMesh.bounds.size * scale;
  184. Vector3[] vectorVertices = sourceMesh.vertices;
  185. var intVertices = ListPool<Int3>.Claim(vectorVertices.Length);
  186. var matrix = Matrix4x4.TRS(-sourceMesh.bounds.min * scale, Quaternion.identity, Vector3.one * scale);
  187. // Convert the vertices to integer coordinates and also position them in graph space
  188. // so that the minimum of the bounding box of the mesh is at the origin
  189. // (the vertices will later be transformed to world space)
  190. for (int i = 0; i < vectorVertices.Length; i++) {
  191. intVertices.Add((Int3)matrix.MultiplyPoint3x4(vectorVertices[i]));
  192. }
  193. yield return new Progress(0.1f, "Compressing Vertices");
  194. // Remove duplicate vertices
  195. Int3[] compressedVertices = null;
  196. int[] compressedTriangles = null;
  197. Polygon.CompressMesh(intVertices, new List<int>(sourceMesh.triangles), out compressedVertices, out compressedTriangles);
  198. ListPool<Int3>.Release(ref intVertices);
  199. yield return new Progress(0.2f, "Building Nodes");
  200. ReplaceTile(0, 0, compressedVertices, compressedTriangles);
  201. // Signal that tiles have been recalculated to the navmesh cutting system.
  202. navmeshUpdateData.OnRecalculatedTiles(tiles);
  203. if (OnRecalculatedTiles != null) OnRecalculatedTiles(tiles.Clone() as NavmeshTile[]);
  204. }
  205. protected override void DeserializeSettingsCompatibility (GraphSerializationContext ctx) {
  206. base.DeserializeSettingsCompatibility(ctx);
  207. sourceMesh = ctx.DeserializeUnityObject() as Mesh;
  208. offset = ctx.DeserializeVector3();
  209. rotation = ctx.DeserializeVector3();
  210. scale = ctx.reader.ReadSingle();
  211. nearestSearchOnlyXZ = !ctx.reader.ReadBoolean();
  212. }
  213. }
  214. }