NavmeshBase.cs 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using UnityEngine.Profiling;
  4. namespace Pathfinding {
  5. using System.IO;
  6. using Pathfinding.Util;
  7. using Pathfinding.Serialization;
  8. using Math = System.Math;
  9. using System.Linq;
  10. /// <summary>Base class for <see cref="RecastGraph"/> and <see cref="NavMeshGraph"/></summary>
  11. public abstract class NavmeshBase : NavGraph, INavmesh, INavmeshHolder, ITransformedGraph
  12. , IRaycastableGraph {
  13. #if ASTAR_RECAST_LARGER_TILES
  14. // Larger tiles
  15. public const int VertexIndexMask = 0xFFFFF;
  16. public const int TileIndexMask = 0x7FF;
  17. public const int TileIndexOffset = 20;
  18. #else
  19. // Larger worlds
  20. public const int VertexIndexMask = 0xFFF;
  21. public const int TileIndexMask = 0x7FFFF;
  22. public const int TileIndexOffset = 12;
  23. #endif
  24. /// <summary>Size of the bounding box.</summary>
  25. [JsonMember]
  26. public Vector3 forcedBoundsSize = new Vector3(100, 40, 100);
  27. /// <summary>Size of a tile in world units along the X axis</summary>
  28. public abstract float TileWorldSizeX { get; }
  29. /// <summary>Size of a tile in world units along the Z axis</summary>
  30. public abstract float TileWorldSizeZ { get; }
  31. /// <summary>
  32. /// Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge.
  33. /// When tiles are connected to each other, the nodes sometimes do not line up perfectly
  34. /// so some allowance must be made to allow tiles that do not match exactly to be connected with each other.
  35. /// </summary>
  36. protected abstract float MaxTileConnectionEdgeDistance { get; }
  37. /// <summary>Show an outline of the polygons in the Unity Editor</summary>
  38. [JsonMember]
  39. public bool showMeshOutline = true;
  40. /// <summary>Show the connections between the polygons in the Unity Editor</summary>
  41. [JsonMember]
  42. public bool showNodeConnections;
  43. /// <summary>Show the surface of the navmesh</summary>
  44. [JsonMember]
  45. public bool showMeshSurface = true;
  46. /// <summary>Number of tiles along the X-axis</summary>
  47. public int tileXCount;
  48. /// <summary>Number of tiles along the Z-axis</summary>
  49. public int tileZCount;
  50. /// <summary>
  51. /// All tiles.
  52. ///
  53. /// See: <see cref="GetTile"/>
  54. /// </summary>
  55. protected NavmeshTile[] tiles;
  56. /// <summary>
  57. /// Perform nearest node searches in XZ space only.
  58. /// Recomended for single-layered environments. Faster but can be inaccurate esp. in multilayered contexts.
  59. /// You should not use this if the graph is rotated since then the XZ plane no longer corresponds to the ground plane.
  60. ///
  61. /// This can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for:
  62. /// [Open online documentation to see images]
  63. ///
  64. /// You can also control this using a <see cref="Pathfinding.NNConstraint.distanceXZ field on an NNConstraint"/>.
  65. /// </summary>
  66. [JsonMember]
  67. public bool nearestSearchOnlyXZ;
  68. /// <summary>
  69. /// Should navmesh cuts affect this graph.
  70. /// See: <see cref="navmeshUpdateData"/>
  71. /// </summary>
  72. [JsonMember]
  73. public bool enableNavmeshCutting = true;
  74. /// <summary>
  75. /// Handles navmesh cutting.
  76. /// See: <see cref="enableNavmeshCutting"/>
  77. /// See: <see cref="Pathfinding.NavmeshUpdates"/>
  78. /// </summary>
  79. internal readonly NavmeshUpdates.NavmeshUpdateSettings navmeshUpdateData;
  80. /// <summary>Currently updating tiles in a batch</summary>
  81. bool batchTileUpdate;
  82. /// <summary>List of tiles updating during batch</summary>
  83. List<int> batchUpdatedTiles = new List<int>();
  84. /// <summary>List of nodes that are going to be destroyed as part of a batch update</summary>
  85. List<MeshNode> batchNodesToDestroy = new List<MeshNode>();
  86. /// <summary>
  87. /// Determines how the graph transforms graph space to world space.
  88. /// See: <see cref="CalculateTransform"/>
  89. /// </summary>
  90. public GraphTransform transform = new GraphTransform(Matrix4x4.identity);
  91. GraphTransform ITransformedGraph.transform { get { return transform; } }
  92. /// <summary>\copydoc Pathfinding::NavMeshGraph::recalculateNormals</summary>
  93. protected abstract bool RecalculateNormals { get; }
  94. /// <summary>
  95. /// Returns a new transform which transforms graph space to world space.
  96. /// Does not update the <see cref="transform"/> field.
  97. /// See: <see cref="RelocateNodes(GraphTransform)"/>
  98. /// </summary>
  99. public abstract GraphTransform CalculateTransform();
  100. /// <summary>
  101. /// Called when tiles have been completely recalculated.
  102. /// This is called after scanning the graph and after
  103. /// performing graph updates that completely recalculate tiles
  104. /// (not ones that simply modify e.g penalties).
  105. /// It is not called after NavmeshCut updates.
  106. /// </summary>
  107. public System.Action<NavmeshTile[]> OnRecalculatedTiles;
  108. /// <summary>
  109. /// Tile at the specified x, z coordinate pair.
  110. /// The first tile is at (0,0), the last tile at (tileXCount-1, tileZCount-1).
  111. ///
  112. /// <code>
  113. /// var graph = AstarPath.active.data.recastGraph;
  114. /// int tileX = 5;
  115. /// int tileZ = 8;
  116. /// NavmeshTile tile = graph.GetTile(tileX, tileZ);
  117. ///
  118. /// for (int i = 0; i < tile.nodes.Length; i++) {
  119. /// // ...
  120. /// }
  121. /// // or you can access the nodes like this:
  122. /// tile.GetNodes(node => {
  123. /// // ...
  124. /// });
  125. /// </code>
  126. /// </summary>
  127. public NavmeshTile GetTile (int x, int z) {
  128. return tiles[x + z * tileXCount];
  129. }
  130. /// <summary>
  131. /// Vertex coordinate for the specified vertex index.
  132. ///
  133. /// Throws: IndexOutOfRangeException if the vertex index is invalid.
  134. /// Throws: NullReferenceException if the tile the vertex is in is not calculated.
  135. ///
  136. /// See: NavmeshTile.GetVertex
  137. /// </summary>
  138. public Int3 GetVertex (int index) {
  139. int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
  140. return tiles[tileIndex].GetVertex(index);
  141. }
  142. /// <summary>Vertex coordinate in graph space for the specified vertex index</summary>
  143. public Int3 GetVertexInGraphSpace (int index) {
  144. int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
  145. return tiles[tileIndex].GetVertexInGraphSpace(index);
  146. }
  147. /// <summary>Tile index from a vertex index</summary>
  148. public static int GetTileIndex (int index) {
  149. return (index >> TileIndexOffset) & TileIndexMask;
  150. }
  151. public int GetVertexArrayIndex (int index) {
  152. return index & VertexIndexMask;
  153. }
  154. /// <summary>Tile coordinates from a tile index</summary>
  155. public void GetTileCoordinates (int tileIndex, out int x, out int z) {
  156. //z = System.Math.DivRem (tileIndex, tileXCount, out x);
  157. z = tileIndex/tileXCount;
  158. x = tileIndex - z*tileXCount;
  159. }
  160. /// <summary>
  161. /// All tiles.
  162. /// Warning: Do not modify this array
  163. /// </summary>
  164. public NavmeshTile[] GetTiles () {
  165. return tiles;
  166. }
  167. /// <summary>
  168. /// Returns a bounds object with the bounding box of a group of tiles.
  169. /// The bounding box is defined in world space.
  170. /// </summary>
  171. public Bounds GetTileBounds (IntRect rect) {
  172. return GetTileBounds(rect.xmin, rect.ymin, rect.Width, rect.Height);
  173. }
  174. /// <summary>
  175. /// Returns a bounds object with the bounding box of a group of tiles.
  176. /// The bounding box is defined in world space.
  177. /// </summary>
  178. public Bounds GetTileBounds (int x, int z, int width = 1, int depth = 1) {
  179. return transform.Transform(GetTileBoundsInGraphSpace(x, z, width, depth));
  180. }
  181. public Bounds GetTileBoundsInGraphSpace (IntRect rect) {
  182. return GetTileBoundsInGraphSpace(rect.xmin, rect.ymin, rect.Width, rect.Height);
  183. }
  184. /// <summary>Returns an XZ bounds object with the bounds of a group of tiles in graph space</summary>
  185. public Bounds GetTileBoundsInGraphSpace (int x, int z, int width = 1, int depth = 1) {
  186. var b = new Bounds();
  187. b.SetMinMax(
  188. new Vector3(x*TileWorldSizeX, 0, z*TileWorldSizeZ),
  189. new Vector3((x+width)*TileWorldSizeX, forcedBoundsSize.y, (z+depth)*TileWorldSizeZ)
  190. );
  191. return b;
  192. }
  193. /// <summary>
  194. /// Returns the tile coordinate which contains the specified position.
  195. /// It is not necessarily a valid tile (i.e it could be out of bounds).
  196. /// </summary>
  197. public Int2 GetTileCoordinates (Vector3 position) {
  198. position = transform.InverseTransform(position);
  199. position.x /= TileWorldSizeX;
  200. position.z /= TileWorldSizeZ;
  201. return new Int2((int)position.x, (int)position.z);
  202. }
  203. protected override void OnDestroy () {
  204. base.OnDestroy();
  205. // Cleanup
  206. TriangleMeshNode.SetNavmeshHolder(active.data.GetGraphIndex(this), null);
  207. if (tiles != null) {
  208. for (int i = 0; i < tiles.Length; i++) {
  209. Pathfinding.Util.ObjectPool<BBTree>.Release(ref tiles[i].bbTree);
  210. }
  211. }
  212. }
  213. public override void RelocateNodes (Matrix4x4 deltaMatrix) {
  214. RelocateNodes(deltaMatrix * transform);
  215. }
  216. /// <summary>
  217. /// Moves the nodes in this graph.
  218. /// Moves all the nodes in such a way that the specified transform is the new graph space to world space transformation for the graph.
  219. /// You usually use this together with the <see cref="CalculateTransform"/> method.
  220. ///
  221. /// So for example if you want to move and rotate all your nodes in e.g a recast graph you can do
  222. /// <code>
  223. /// var graph = AstarPath.data.recastGraph;
  224. /// graph.rotation = new Vector3(45, 0, 0);
  225. /// graph.forcedBoundsCenter = new Vector3(20, 10, 10);
  226. /// var transform = graph.CalculateTransform();
  227. /// graph.RelocateNodes(transform);
  228. /// </code>
  229. /// This will move all the nodes to new positions as if the new graph settings had been there from the start.
  230. ///
  231. /// Note: RelocateNodes(deltaMatrix) is not equivalent to RelocateNodes(new GraphTransform(deltaMatrix)).
  232. /// The overload which takes a matrix multiplies all existing node positions with the matrix while this
  233. /// overload does not take into account the current positions of the nodes.
  234. ///
  235. /// See: <see cref="CalculateTransform"/>
  236. /// </summary>
  237. public void RelocateNodes (GraphTransform newTransform) {
  238. transform = newTransform;
  239. if (tiles != null) {
  240. // Move all the vertices in each tile
  241. for (int tileIndex = 0; tileIndex < tiles.Length; tileIndex++) {
  242. var tile = tiles[tileIndex];
  243. if (tile != null) {
  244. tile.vertsInGraphSpace.CopyTo(tile.verts, 0);
  245. // Transform the graph space vertices to world space
  246. transform.Transform(tile.verts);
  247. for (int nodeIndex = 0; nodeIndex < tile.nodes.Length; nodeIndex++) {
  248. tile.nodes[nodeIndex].UpdatePositionFromVertices();
  249. }
  250. tile.bbTree.RebuildFrom(tile.nodes);
  251. }
  252. }
  253. }
  254. }
  255. /// <summary>Creates a single new empty tile</summary>
  256. protected NavmeshTile NewEmptyTile (int x, int z) {
  257. return new NavmeshTile {
  258. x = x,
  259. z = z,
  260. w = 1,
  261. d = 1,
  262. verts = new Int3[0],
  263. vertsInGraphSpace = new Int3[0],
  264. tris = new int[0],
  265. nodes = new TriangleMeshNode[0],
  266. bbTree = ObjectPool<BBTree>.Claim(),
  267. graph = this,
  268. };
  269. }
  270. public override void GetNodes (System.Action<GraphNode> action) {
  271. if (tiles == null) return;
  272. for (int i = 0; i < tiles.Length; i++) {
  273. if (tiles[i] == null || tiles[i].x+tiles[i].z*tileXCount != i) continue;
  274. TriangleMeshNode[] nodes = tiles[i].nodes;
  275. if (nodes == null) continue;
  276. for (int j = 0; j < nodes.Length; j++) action(nodes[j]);
  277. }
  278. }
  279. /// <summary>
  280. /// Returns a rect containing the indices of all tiles touching the specified bounds.
  281. /// If a margin is passed, the bounding box in graph space is expanded by that amount in every direction.
  282. /// </summary>
  283. public IntRect GetTouchingTiles (Bounds bounds, float margin = 0) {
  284. bounds = transform.InverseTransform(bounds);
  285. // Calculate world bounds of all affected tiles
  286. var r = new IntRect(Mathf.FloorToInt((bounds.min.x - margin) / TileWorldSizeX), Mathf.FloorToInt((bounds.min.z - margin) / TileWorldSizeZ), Mathf.FloorToInt((bounds.max.x + margin) / TileWorldSizeX), Mathf.FloorToInt((bounds.max.z + margin) / TileWorldSizeZ));
  287. // Clamp to bounds
  288. r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
  289. return r;
  290. }
  291. /// <summary>Returns a rect containing the indices of all tiles touching the specified bounds.</summary>
  292. /// <param name="rect">Graph space rectangle (in graph space all tiles are on the XZ plane regardless of graph rotation and other transformations, the first tile has a corner at the origin)</param>
  293. public IntRect GetTouchingTilesInGraphSpace (Rect rect) {
  294. // Calculate world bounds of all affected tiles
  295. var r = new IntRect(Mathf.FloorToInt(rect.xMin / TileWorldSizeX), Mathf.FloorToInt(rect.yMin / TileWorldSizeZ), Mathf.FloorToInt(rect.xMax / TileWorldSizeX), Mathf.FloorToInt(rect.yMax / TileWorldSizeZ));
  296. // Clamp to bounds
  297. r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
  298. return r;
  299. }
  300. /// <summary>
  301. /// Returns a rect containing the indices of all tiles by rounding the specified bounds to tile borders.
  302. /// This is different from GetTouchingTiles in that the tiles inside the rectangle returned from this method
  303. /// may not contain the whole bounds, while that is guaranteed for GetTouchingTiles.
  304. /// </summary>
  305. public IntRect GetTouchingTilesRound (Bounds bounds) {
  306. bounds = transform.InverseTransform(bounds);
  307. //Calculate world bounds of all affected tiles
  308. var r = new IntRect(Mathf.RoundToInt(bounds.min.x / TileWorldSizeX), Mathf.RoundToInt(bounds.min.z / TileWorldSizeZ), Mathf.RoundToInt(bounds.max.x / TileWorldSizeX)-1, Mathf.RoundToInt(bounds.max.z / TileWorldSizeZ)-1);
  309. //Clamp to bounds
  310. r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
  311. return r;
  312. }
  313. protected void ConnectTileWithNeighbours (NavmeshTile tile, bool onlyUnflagged = false) {
  314. if (tile.w != 1 || tile.d != 1) {
  315. throw new System.ArgumentException("Tile widths or depths other than 1 are not supported. The fields exist mainly for possible future expansions.");
  316. }
  317. // Loop through z and x offsets to adjacent tiles
  318. // _ x _
  319. // x _ x
  320. // _ x _
  321. for (int zo = -1; zo <= 1; zo++) {
  322. var z = tile.z + zo;
  323. if (z < 0 || z >= tileZCount) continue;
  324. for (int xo = -1; xo <= 1; xo++) {
  325. var x = tile.x + xo;
  326. if (x < 0 || x >= tileXCount) continue;
  327. // Ignore diagonals and the tile itself
  328. if ((xo == 0) == (zo == 0)) continue;
  329. var otherTile = tiles[x + z*tileXCount];
  330. if (!onlyUnflagged || !otherTile.flag) {
  331. ConnectTiles(otherTile, tile);
  332. }
  333. }
  334. }
  335. }
  336. protected void RemoveConnectionsFromTile (NavmeshTile tile) {
  337. if (tile.x > 0) {
  338. int x = tile.x-1;
  339. for (int z = tile.z; z < tile.z+tile.d; z++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
  340. }
  341. if (tile.x+tile.w < tileXCount) {
  342. int x = tile.x+tile.w;
  343. for (int z = tile.z; z < tile.z+tile.d; z++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
  344. }
  345. if (tile.z > 0) {
  346. int z = tile.z-1;
  347. for (int x = tile.x; x < tile.x+tile.w; x++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
  348. }
  349. if (tile.z+tile.d < tileZCount) {
  350. int z = tile.z+tile.d;
  351. for (int x = tile.x; x < tile.x+tile.w; x++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
  352. }
  353. }
  354. protected void RemoveConnectionsFromTo (NavmeshTile a, NavmeshTile b) {
  355. if (a == null || b == null) return;
  356. //Same tile, possibly from a large tile (one spanning several x,z tile coordinates)
  357. if (a == b) return;
  358. int tileIdx = b.x + b.z*tileXCount;
  359. for (int i = 0; i < a.nodes.Length; i++) {
  360. TriangleMeshNode node = a.nodes[i];
  361. if (node.connections == null) continue;
  362. for (int j = 0;; j++) {
  363. //Length will not be constant if connections are removed
  364. if (j >= node.connections.Length) break;
  365. var other = node.connections[j].node as TriangleMeshNode;
  366. //Only evaluate TriangleMeshNodes
  367. if (other == null) continue;
  368. int tileIdx2 = other.GetVertexIndex(0);
  369. tileIdx2 = (tileIdx2 >> TileIndexOffset) & TileIndexMask;
  370. if (tileIdx2 == tileIdx) {
  371. node.RemoveConnection(node.connections[j].node);
  372. j--;
  373. }
  374. }
  375. }
  376. }
  377. static readonly NNConstraint NNConstraintDistanceXZ = new NNConstraint { distanceXZ = true };
  378. public override NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint, GraphNode hint) {
  379. return GetNearestForce(position, constraint != null && constraint.distanceXZ ? NNConstraintDistanceXZ : null);
  380. }
  381. public override NNInfoInternal GetNearestForce (Vector3 position, NNConstraint constraint) {
  382. if (tiles == null) return new NNInfoInternal();
  383. var tileCoords = GetTileCoordinates(position);
  384. // Clamp to graph borders
  385. tileCoords.x = Mathf.Clamp(tileCoords.x, 0, tileXCount-1);
  386. tileCoords.y = Mathf.Clamp(tileCoords.y, 0, tileZCount-1);
  387. int wmax = Math.Max(tileXCount, tileZCount);
  388. var best = new NNInfoInternal();
  389. float bestDistance = float.PositiveInfinity;
  390. bool xzSearch = nearestSearchOnlyXZ || (constraint != null && constraint.distanceXZ);
  391. // Search outwards in a diamond pattern from the closest tile
  392. // 2
  393. // 2 1 2
  394. // 2 1 0 1 2 etc.
  395. // 2 1 2
  396. // 2
  397. for (int w = 0; w < wmax; w++) {
  398. // Stop the loop when we can guarantee that no nodes will be closer than the ones we have already searched
  399. if (bestDistance < (w-2)*Math.Max(TileWorldSizeX, TileWorldSizeX)) break;
  400. int zmax = Math.Min(w+tileCoords.y +1, tileZCount);
  401. for (int z = Math.Max(-w+tileCoords.y, 0); z < zmax; z++) {
  402. // Solve for z such that abs(x-tx) + abs(z-tx) == w
  403. // Delta X coordinate
  404. int originalDx = Math.Abs(w - Math.Abs(z-tileCoords.y));
  405. var dx = originalDx;
  406. // Solution is dx + tx and -dx + tx
  407. // This loop will first check +dx and then -dx
  408. // If dx happens to be zero, then it will not run twice
  409. do {
  410. // Absolute x coordinate
  411. int x = -dx + tileCoords.x;
  412. if (x >= 0 && x < tileXCount) {
  413. NavmeshTile tile = tiles[x + z*tileXCount];
  414. if (tile != null) {
  415. if (xzSearch) {
  416. best = tile.bbTree.QueryClosestXZ(position, constraint, ref bestDistance, best);
  417. } else {
  418. best = tile.bbTree.QueryClosest(position, constraint, ref bestDistance, best);
  419. }
  420. }
  421. }
  422. dx = -dx;
  423. } while (dx != originalDx);
  424. }
  425. }
  426. best.node = best.constrainedNode;
  427. best.constrainedNode = null;
  428. best.clampedPosition = best.constClampedPosition;
  429. return best;
  430. }
  431. /// <summary>
  432. /// Finds the first node which contains position.
  433. /// "Contains" is defined as position is inside the triangle node when seen from above. So only XZ space matters.
  434. /// In case of a multilayered environment, which node of the possibly several nodes
  435. /// containing the point is undefined.
  436. ///
  437. /// Returns null if there was no node containing the point. This serves as a quick
  438. /// check for "is this point on the navmesh or not".
  439. ///
  440. /// Note that the behaviour of this method is distinct from the GetNearest method.
  441. /// The GetNearest method will return the closest node to a point,
  442. /// which is not necessarily the one which contains it in XZ space.
  443. ///
  444. /// See: GetNearest
  445. /// </summary>
  446. public GraphNode PointOnNavmesh (Vector3 position, NNConstraint constraint) {
  447. if (tiles == null) return null;
  448. var tileCoords = GetTileCoordinates(position);
  449. // Graph borders
  450. if (tileCoords.x < 0 || tileCoords.y < 0 || tileCoords.x >= tileXCount || tileCoords.y >= tileZCount) return null;
  451. NavmeshTile tile = GetTile(tileCoords.x, tileCoords.y);
  452. if (tile != null) {
  453. GraphNode node = tile.bbTree.QueryInside(position, constraint);
  454. return node;
  455. }
  456. return null;
  457. }
  458. /// <summary>Fills graph with tiles created by NewEmptyTile</summary>
  459. protected void FillWithEmptyTiles () {
  460. for (int z = 0; z < tileZCount; z++) {
  461. for (int x = 0; x < tileXCount; x++) {
  462. tiles[z*tileXCount + x] = NewEmptyTile(x, z);
  463. }
  464. }
  465. }
  466. /// <summary>
  467. /// Create connections between all nodes.
  468. /// Version: Since 3.7.6 the implementation is thread safe
  469. /// </summary>
  470. protected static void CreateNodeConnections (TriangleMeshNode[] nodes) {
  471. List<Connection> connections = ListPool<Connection>.Claim();
  472. var nodeRefs = ObjectPoolSimple<Dictionary<Int2, int> >.Claim();
  473. nodeRefs.Clear();
  474. // Build node neighbours
  475. for (int i = 0; i < nodes.Length; i++) {
  476. TriangleMeshNode node = nodes[i];
  477. int av = node.GetVertexCount();
  478. for (int a = 0; a < av; a++) {
  479. // Recast can in some very special cases generate degenerate triangles which are simply lines
  480. // In that case, duplicate keys might be added and thus an exception will be thrown
  481. // It is safe to ignore the second edge though... I think (only found one case where this happens)
  482. var key = new Int2(node.GetVertexIndex(a), node.GetVertexIndex((a+1) % av));
  483. if (!nodeRefs.ContainsKey(key)) {
  484. nodeRefs.Add(key, i);
  485. }
  486. }
  487. }
  488. for (int i = 0; i < nodes.Length; i++) {
  489. TriangleMeshNode node = nodes[i];
  490. connections.Clear();
  491. int av = node.GetVertexCount();
  492. for (int a = 0; a < av; a++) {
  493. int first = node.GetVertexIndex(a);
  494. int second = node.GetVertexIndex((a+1) % av);
  495. int connNode;
  496. if (nodeRefs.TryGetValue(new Int2(second, first), out connNode)) {
  497. TriangleMeshNode other = nodes[connNode];
  498. int bv = other.GetVertexCount();
  499. for (int b = 0; b < bv; b++) {
  500. /// <summary>TODO: This will fail on edges which are only partially shared</summary>
  501. if (other.GetVertexIndex(b) == second && other.GetVertexIndex((b+1) % bv) == first) {
  502. connections.Add(new Connection(
  503. other,
  504. (uint)(node.position - other.position).costMagnitude,
  505. (byte)a
  506. ));
  507. break;
  508. }
  509. }
  510. }
  511. }
  512. node.connections = connections.ToArrayFromPool();
  513. node.SetConnectivityDirty();
  514. }
  515. nodeRefs.Clear();
  516. ObjectPoolSimple<Dictionary<Int2, int> >.Release(ref nodeRefs);
  517. ListPool<Connection>.Release(ref connections);
  518. }
  519. /// <summary>
  520. /// Generate connections between the two tiles.
  521. /// The tiles must be adjacent.
  522. /// </summary>
  523. protected void ConnectTiles (NavmeshTile tile1, NavmeshTile tile2) {
  524. if (tile1 == null || tile2 == null) return;
  525. if (tile1.nodes == null) throw new System.ArgumentException("tile1 does not contain any nodes");
  526. if (tile2.nodes == null) throw new System.ArgumentException("tile2 does not contain any nodes");
  527. int t1x = Mathf.Clamp(tile2.x, tile1.x, tile1.x+tile1.w-1);
  528. int t2x = Mathf.Clamp(tile1.x, tile2.x, tile2.x+tile2.w-1);
  529. int t1z = Mathf.Clamp(tile2.z, tile1.z, tile1.z+tile1.d-1);
  530. int t2z = Mathf.Clamp(tile1.z, tile2.z, tile2.z+tile2.d-1);
  531. int coord, altcoord;
  532. int t1coord, t2coord;
  533. float tileWorldSize;
  534. // Figure out which side that is shared between the two tiles
  535. // and what coordinate index is fixed along that edge (x or z)
  536. if (t1x == t2x) {
  537. coord = 2;
  538. altcoord = 0;
  539. t1coord = t1z;
  540. t2coord = t2z;
  541. tileWorldSize = TileWorldSizeZ;
  542. } else if (t1z == t2z) {
  543. coord = 0;
  544. altcoord = 2;
  545. t1coord = t1x;
  546. t2coord = t2x;
  547. tileWorldSize = TileWorldSizeX;
  548. } else {
  549. throw new System.ArgumentException("Tiles are not adjacent (neither x or z coordinates match)");
  550. }
  551. if (Math.Abs(t1coord-t2coord) != 1) {
  552. throw new System.ArgumentException("Tiles are not adjacent (tile coordinates must differ by exactly 1. Got '" + t1coord + "' and '" + t2coord + "')");
  553. }
  554. // Midpoint between the two tiles
  555. int midpoint = (int)Math.Round((Math.Max(t1coord, t2coord) * tileWorldSize) * Int3.Precision);
  556. #if ASTARDEBUG
  557. Vector3 v1 = new Vector3(-100, 0, -100);
  558. Vector3 v2 = new Vector3(100, 0, 100);
  559. v1[coord] = midpoint*Int3.PrecisionFactor;
  560. v2[coord] = midpoint*Int3.PrecisionFactor;
  561. Debug.DrawLine(v1, v2, Color.magenta);
  562. #endif
  563. TriangleMeshNode[] nodes1 = tile1.nodes;
  564. TriangleMeshNode[] nodes2 = tile2.nodes;
  565. // Find all nodes of the second tile which are adjacent to the border between the tiles.
  566. // This is used to speed up the matching process (the impact can be very significant for large tiles, but is insignificant for small ones).
  567. TriangleMeshNode[] closeToEdge = ArrayPool<TriangleMeshNode>.Claim(nodes2.Length);
  568. int numCloseToEdge = 0;
  569. for (int j = 0; j < nodes2.Length; j++) {
  570. TriangleMeshNode nodeB = nodes2[j];
  571. int bVertexCount = nodeB.GetVertexCount();
  572. for (int b = 0; b < bVertexCount; b++) {
  573. Int3 bVertex1 = nodeB.GetVertexInGraphSpace(b);
  574. Int3 bVertex2 = nodeB.GetVertexInGraphSpace((b+1) % bVertexCount);
  575. if (Math.Abs(bVertex1[coord] - midpoint) < 2 && Math.Abs(bVertex2[coord] - midpoint) < 2) {
  576. closeToEdge[numCloseToEdge] = nodes2[j];
  577. numCloseToEdge++;
  578. break;
  579. }
  580. }
  581. }
  582. // Find adjacent nodes on the border between the tiles
  583. for (int i = 0; i < nodes1.Length; i++) {
  584. TriangleMeshNode nodeA = nodes1[i];
  585. int aVertexCount = nodeA.GetVertexCount();
  586. // Loop through all *sides* of the node
  587. for (int a = 0; a < aVertexCount; a++) {
  588. // Vertices that the segment consists of
  589. Int3 aVertex1 = nodeA.GetVertexInGraphSpace(a);
  590. Int3 aVertex2 = nodeA.GetVertexInGraphSpace((a+1) % aVertexCount);
  591. // Check if it is really close to the tile border
  592. if (Math.Abs(aVertex1[coord] - midpoint) < 2 && Math.Abs(aVertex2[coord] - midpoint) < 2) {
  593. int minalt = Math.Min(aVertex1[altcoord], aVertex2[altcoord]);
  594. int maxalt = Math.Max(aVertex1[altcoord], aVertex2[altcoord]);
  595. // Degenerate edge
  596. if (minalt == maxalt) continue;
  597. for (int j = 0; j < numCloseToEdge; j++) {
  598. TriangleMeshNode nodeB = closeToEdge[j];
  599. int bVertexCount = nodeB.GetVertexCount();
  600. for (int b = 0; b < bVertexCount; b++) {
  601. Int3 bVertex1 = nodeB.GetVertexInGraphSpace(b);
  602. Int3 bVertex2 = nodeB.GetVertexInGraphSpace((b+1) % bVertexCount);
  603. if (Math.Abs(bVertex1[coord] - midpoint) < 2 && Math.Abs(bVertex2[coord] - midpoint) < 2) {
  604. int minalt2 = Math.Min(bVertex1[altcoord], bVertex2[altcoord]);
  605. int maxalt2 = Math.Max(bVertex1[altcoord], bVertex2[altcoord]);
  606. // Degenerate edge
  607. if (minalt2 == maxalt2) continue;
  608. if (maxalt > minalt2 && minalt < maxalt2) {
  609. // The two nodes seem to be adjacent
  610. // Test shortest distance between the segments (first test if they are equal since that is much faster and pretty common)
  611. if ((aVertex1 == bVertex1 && aVertex2 == bVertex2) || (aVertex1 == bVertex2 && aVertex2 == bVertex1) ||
  612. VectorMath.SqrDistanceSegmentSegment((Vector3)aVertex1, (Vector3)aVertex2, (Vector3)bVertex1, (Vector3)bVertex2) < MaxTileConnectionEdgeDistance*MaxTileConnectionEdgeDistance) {
  613. uint cost = (uint)(nodeA.position - nodeB.position).costMagnitude;
  614. nodeA.AddConnection(nodeB, cost, (byte)a);
  615. nodeB.AddConnection(nodeA, cost, (byte)b);
  616. }
  617. }
  618. }
  619. }
  620. }
  621. }
  622. }
  623. }
  624. ArrayPool<TriangleMeshNode>.Release(ref closeToEdge);
  625. }
  626. /// <summary>
  627. /// Start batch updating of tiles.
  628. /// During batch updating, tiles will not be connected if they are updating with ReplaceTile.
  629. /// When ending batching, all affected tiles will be connected.
  630. /// This is faster than not using batching.
  631. /// </summary>
  632. public void StartBatchTileUpdate () {
  633. if (batchTileUpdate) throw new System.InvalidOperationException("Calling StartBatchLoad when batching is already enabled");
  634. batchTileUpdate = true;
  635. }
  636. /// <summary>
  637. /// Destroy several nodes simultaneously.
  638. /// This is faster than simply looping through the nodes and calling the node.Destroy method because some optimizations
  639. /// relating to how connections are removed can be optimized.
  640. /// </summary>
  641. void DestroyNodes (List<MeshNode> nodes) {
  642. for (int i = 0; i < batchNodesToDestroy.Count; i++) {
  643. batchNodesToDestroy[i].TemporaryFlag1 = true;
  644. }
  645. for (int i = 0; i < batchNodesToDestroy.Count; i++) {
  646. var node = batchNodesToDestroy[i];
  647. for (int j = 0; j < node.connections.Length; j++) {
  648. var neighbour = node.connections[j].node;
  649. if (!neighbour.TemporaryFlag1) {
  650. neighbour.RemoveConnection(node);
  651. }
  652. }
  653. // Remove the connections array explicitly for performance.
  654. // Otherwise the Destroy method will try to remove the connections in both directions one by one which is slow.
  655. ArrayPool<Connection>.Release(ref node.connections, true);
  656. node.Destroy();
  657. }
  658. }
  659. void TryConnect (int tileIdx1, int tileIdx2) {
  660. // If both tiles were flagged, then only connect if tileIdx1 < tileIdx2 to make sure we don't connect the tiles twice
  661. // as this method will be called with swapped arguments as well.
  662. if (tiles[tileIdx1].flag && tiles[tileIdx2].flag && tileIdx1 >= tileIdx2) return;
  663. ConnectTiles(tiles[tileIdx1], tiles[tileIdx2]);
  664. }
  665. /// <summary>
  666. /// End batch updating of tiles.
  667. /// During batch updating, tiles will not be connected if they are updating with ReplaceTile.
  668. /// When ending batching, all affected tiles will be connected.
  669. /// This is faster than not using batching.
  670. /// </summary>
  671. public void EndBatchTileUpdate () {
  672. if (!batchTileUpdate) throw new System.InvalidOperationException("Calling EndBatchTileUpdate when batching had not yet been started");
  673. batchTileUpdate = false;
  674. DestroyNodes(batchNodesToDestroy);
  675. batchNodesToDestroy.ClearFast();
  676. for (int i = 0; i < batchUpdatedTiles.Count; i++) tiles[batchUpdatedTiles[i]].flag = true;
  677. for (int i = 0; i < batchUpdatedTiles.Count; i++) {
  678. int x = batchUpdatedTiles[i] % tileXCount, z = batchUpdatedTiles[i] / tileXCount;
  679. if (x > 0) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] - 1);
  680. if (x < tileXCount - 1) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] + 1);
  681. if (z > 0) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] - tileXCount);
  682. if (z < tileZCount - 1) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] + tileXCount);
  683. }
  684. for (int i = 0; i < batchUpdatedTiles.Count; i++) tiles[batchUpdatedTiles[i]].flag = false;
  685. batchUpdatedTiles.ClearFast();
  686. }
  687. /// <summary>
  688. /// Clear the tile at the specified coordinate.
  689. /// Must be called during a batch update, see <see cref="StartBatchTileUpdate"/>.
  690. /// </summary>
  691. protected void ClearTile (int x, int z) {
  692. if (!batchTileUpdate) throw new System.Exception("Must be called during a batch update. See StartBatchTileUpdate");
  693. var tile = GetTile(x, z);
  694. if (tile == null) return;
  695. var nodes = tile.nodes;
  696. for (int i = 0; i < nodes.Length; i++) {
  697. if (nodes[i] != null) batchNodesToDestroy.Add(nodes[i]);
  698. }
  699. ObjectPool<BBTree>.Release(ref tile.bbTree);
  700. // TODO: Pool tile object and various arrays in it?
  701. tiles[x + z*tileXCount] = null;
  702. }
  703. /// <summary>Temporary buffer used in <see cref="PrepareNodeRecycling"/></summary>
  704. Dictionary<int, int> nodeRecyclingHashBuffer = new Dictionary<int, int>();
  705. /// <summary>
  706. /// Reuse nodes that keep the exact same vertices after a tile replacement.
  707. /// The reused nodes will be added to the recycledNodeBuffer array at the index corresponding to the
  708. /// indices in the triangle array that its vertices uses.
  709. ///
  710. /// All connections on the reused nodes will be removed except ones that go to other graphs.
  711. /// The reused nodes will be removed from the tile by replacing it with a null slot in the node array.
  712. ///
  713. /// See: <see cref="ReplaceTile"/>
  714. /// </summary>
  715. void PrepareNodeRecycling (int x, int z, Int3[] verts, int[] tris, TriangleMeshNode[] recycledNodeBuffer) {
  716. NavmeshTile tile = GetTile(x, z);
  717. if (tile == null || tile.nodes.Length == 0) return;
  718. var nodes = tile.nodes;
  719. var recycling = nodeRecyclingHashBuffer;
  720. for (int i = 0, j = 0; i < tris.Length; i += 3, j++) {
  721. recycling[verts[tris[i+0]].GetHashCode() + verts[tris[i+1]].GetHashCode() + verts[tris[i+2]].GetHashCode()] = j;
  722. }
  723. var connectionsToKeep = ListPool<Connection>.Claim();
  724. for (int i = 0; i < nodes.Length; i++) {
  725. var node = nodes[i];
  726. Int3 v0, v1, v2;
  727. node.GetVerticesInGraphSpace(out v0, out v1, out v2);
  728. var hash = v0.GetHashCode() + v1.GetHashCode() + v2.GetHashCode();
  729. int newNodeIndex;
  730. if (recycling.TryGetValue(hash, out newNodeIndex)) {
  731. // Technically we should check for a cyclic permutations of the vertices (e.g node a,b,c could become node b,c,a)
  732. // but in almost all cases the vertices will keep the same order. Allocating one or two extra nodes isn't such a big deal.
  733. if (verts[tris[3*newNodeIndex+0]] == v0 && verts[tris[3*newNodeIndex+1]] == v1 && verts[tris[3*newNodeIndex+2]] == v2) {
  734. recycledNodeBuffer[newNodeIndex] = node;
  735. // Remove the node from the tile
  736. nodes[i] = null;
  737. // Only keep connections to nodes on other graphs
  738. // Usually there are no connections to nodes to other graphs and this is faster than removing all connections them one by one
  739. for (int j = 0; j < node.connections.Length; j++) {
  740. if (node.connections[j].node.GraphIndex != node.GraphIndex) {
  741. connectionsToKeep.Add(node.connections[j]);
  742. }
  743. }
  744. ArrayPool<Connection>.Release(ref node.connections, true);
  745. if (connectionsToKeep.Count > 0) {
  746. node.connections = connectionsToKeep.ToArrayFromPool();
  747. node.SetConnectivityDirty();
  748. connectionsToKeep.Clear();
  749. }
  750. }
  751. }
  752. }
  753. recycling.Clear();
  754. ListPool<Connection>.Release(ref connectionsToKeep);
  755. }
  756. /// <summary>
  757. /// Replace tile at index with nodes created from specified navmesh.
  758. /// This will create new nodes and link them to the adjacent tile (unless batching has been started in which case that will be done when batching ends).
  759. ///
  760. /// The vertices are assumed to be in 'tile space', that is being in a rectangle with
  761. /// one corner at the origin and one at (<see cref="TileWorldSizeX"/>, 0, <see cref="TileWorldSizeZ)"/>.
  762. ///
  763. /// Note: The vertex and triangle arrays may be modified and will be stored with the tile data.
  764. /// do not modify them after this method has been called.
  765. ///
  766. /// See: <see cref="StartBatchTileUpdate"/>
  767. /// </summary>
  768. public void ReplaceTile (int x, int z, Int3[] verts, int[] tris) {
  769. int w = 1, d = 1;
  770. if (x + w > tileXCount || z+d > tileZCount || x < 0 || z < 0) {
  771. throw new System.ArgumentException("Tile is placed at an out of bounds position or extends out of the graph bounds ("+x+", " + z + " [" + w + ", " + d+ "] " + tileXCount + " " + tileZCount + ")");
  772. }
  773. if (tris.Length % 3 != 0) throw new System.ArgumentException("Triangle array's length must be a multiple of 3 (tris)");
  774. if (verts.Length > VertexIndexMask) {
  775. Debug.LogError("Too many vertices in the tile (" + verts.Length + " > " + VertexIndexMask +")\nYou can enable ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector to raise this limit. Or you can use a smaller tile size to reduce the likelihood of this happening.");
  776. verts = new Int3[0];
  777. tris = new int[0];
  778. }
  779. var wasNotBatching = !batchTileUpdate;
  780. if (wasNotBatching) StartBatchTileUpdate();
  781. Profiler.BeginSample("Tile Initialization");
  782. //Create a new navmesh tile and assign its settings
  783. var tile = new NavmeshTile {
  784. x = x,
  785. z = z,
  786. w = w,
  787. d = d,
  788. tris = tris,
  789. bbTree = ObjectPool<BBTree>.Claim(),
  790. graph = this,
  791. };
  792. if (!Mathf.Approximately(x*TileWorldSizeX*Int3.FloatPrecision, (float)Math.Round(x*TileWorldSizeX*Int3.FloatPrecision))) Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize");
  793. if (!Mathf.Approximately(z*TileWorldSizeZ*Int3.FloatPrecision, (float)Math.Round(z*TileWorldSizeZ*Int3.FloatPrecision))) Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize");
  794. var offset = (Int3) new Vector3((x * TileWorldSizeX), 0, (z * TileWorldSizeZ));
  795. for (int i = 0; i < verts.Length; i++) {
  796. verts[i] += offset;
  797. }
  798. tile.vertsInGraphSpace = verts;
  799. tile.verts = (Int3[])verts.Clone();
  800. transform.Transform(tile.verts);
  801. Profiler.BeginSample("Clear Previous Tiles");
  802. // Create a backing array for the new nodes
  803. var nodes = tile.nodes = new TriangleMeshNode[tris.Length/3];
  804. // Recycle any nodes that are in the exact same spot after replacing the tile.
  805. // This also keeps e.g penalties and tags and other connections which might be useful.
  806. // It also avoids trashing the paths for the RichAI component (as it will have to immediately recalculate its path
  807. // if it discovers that its path contains destroyed nodes).
  808. PrepareNodeRecycling(x, z, tile.vertsInGraphSpace, tris, tile.nodes);
  809. // Remove previous tiles (except the nodes that were recycled above)
  810. ClearTile(x, z);
  811. Profiler.EndSample();
  812. Profiler.EndSample();
  813. Profiler.BeginSample("Assign Node Data");
  814. // Set tile
  815. tiles[x + z*tileXCount] = tile;
  816. batchUpdatedTiles.Add(x + z*tileXCount);
  817. // Create nodes and assign triangle indices
  818. CreateNodes(nodes, tile.tris, x + z*tileXCount, (uint)active.data.GetGraphIndex(this));
  819. Profiler.EndSample();
  820. Profiler.BeginSample("AABBTree Rebuild");
  821. tile.bbTree.RebuildFrom(nodes);
  822. Profiler.EndSample();
  823. Profiler.BeginSample("Create Node Connections");
  824. CreateNodeConnections(tile.nodes);
  825. Profiler.EndSample();
  826. Profiler.BeginSample("Connect With Neighbours");
  827. if (wasNotBatching) EndBatchTileUpdate();
  828. Profiler.EndSample();
  829. }
  830. protected void CreateNodes (TriangleMeshNode[] buffer, int[] tris, int tileIndex, uint graphIndex) {
  831. if (buffer == null || buffer.Length < tris.Length/3) throw new System.ArgumentException("buffer must be non null and at least as large as tris.Length/3");
  832. // This index will be ORed to the triangle indices
  833. tileIndex <<= TileIndexOffset;
  834. // Create nodes and assign vertex indices
  835. for (int i = 0; i < buffer.Length; i++) {
  836. var node = buffer[i];
  837. // Allow the buffer to be partially filled in already to allow for recycling nodes
  838. if (node == null) node = buffer[i] = new TriangleMeshNode(active);
  839. // Reset all relevant fields on the node (even on recycled nodes to avoid exposing internal implementation details)
  840. node.Walkable = true;
  841. node.Tag = 0;
  842. node.Penalty = initialPenalty;
  843. node.GraphIndex = graphIndex;
  844. // The vertices stored on the node are composed
  845. // out of the triangle index and the tile index
  846. node.v0 = tris[i*3+0] | tileIndex;
  847. node.v1 = tris[i*3+1] | tileIndex;
  848. node.v2 = tris[i*3+2] | tileIndex;
  849. // Make sure the triangle is clockwise in graph space (it may not be in world space since the graphs can be rotated)
  850. // Note that we also modify the original triangle array because if the graph is cached then we will re-initialize the nodes from that array and assume all triangles are clockwise.
  851. if (RecalculateNormals && !VectorMath.IsClockwiseXZ(node.GetVertexInGraphSpace(0), node.GetVertexInGraphSpace(1), node.GetVertexInGraphSpace(2))) {
  852. Memory.Swap(ref tris[i*3+0], ref tris[i*3+2]);
  853. Memory.Swap(ref node.v0, ref node.v2);
  854. }
  855. node.UpdatePositionFromVertices();
  856. }
  857. }
  858. public NavmeshBase () {
  859. navmeshUpdateData = new NavmeshUpdates.NavmeshUpdateSettings(this);
  860. }
  861. /// <summary>
  862. /// Returns if there is an obstacle between origin and end on the graph.
  863. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  864. ///
  865. /// [Open online documentation to see images]
  866. /// </summary>
  867. public bool Linecast (Vector3 origin, Vector3 end) {
  868. return Linecast(origin, end, null);
  869. }
  870. /// <summary>
  871. /// Returns if there is an obstacle between origin and end on the graph.
  872. ///
  873. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  874. ///
  875. /// [Open online documentation to see images]
  876. /// </summary>
  877. /// <param name="origin">Point to linecast from.</param>
  878. /// <param name="end">Point to linecast to.</param>
  879. /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
  880. /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
  881. /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
  882. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
  883. return Linecast(this, origin, end, hint, out hit, null);
  884. }
  885. /// <summary>
  886. /// Returns if there is an obstacle between origin and end on the graph.
  887. ///
  888. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  889. ///
  890. /// [Open online documentation to see images]
  891. /// </summary>
  892. /// <param name="origin">Point to linecast from.</param>
  893. /// <param name="end">Point to linecast to.</param>
  894. /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
  895. /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
  896. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint) {
  897. GraphHitInfo hit;
  898. return Linecast(this, origin, end, hint, out hit, null);
  899. }
  900. /// <summary>
  901. /// Returns if there is an obstacle between origin and end on the graph.
  902. ///
  903. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  904. ///
  905. /// [Open online documentation to see images]
  906. /// </summary>
  907. /// <param name="origin">Point to linecast from.</param>
  908. /// <param name="end">Point to linecast to.</param>
  909. /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
  910. /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
  911. /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
  912. /// <param name="trace">If a list is passed, then it will be filled with all nodes the linecast traverses.</param>
  913. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace) {
  914. return Linecast(this, origin, end, hint, out hit, trace);
  915. }
  916. /// <summary>
  917. /// Returns if there is an obstacle between origin and end on the graph.
  918. ///
  919. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  920. ///
  921. /// [Open online documentation to see images]
  922. /// </summary>
  923. /// <param name="origin">Point to linecast from.</param>
  924. /// <param name="end">Point to linecast to.</param>
  925. /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
  926. /// <param name="trace">If a list is passed, then it will be filled with all nodes the linecast traverses.</param>
  927. /// <param name="filter">If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
  928. /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.</param>
  929. public bool Linecast (Vector3 origin, Vector3 end, out GraphHitInfo hit, List<GraphNode> trace, System.Func<GraphNode, bool> filter) {
  930. return Linecast(this, origin, end, null, out hit, trace, filter);
  931. }
  932. /// <summary>
  933. /// Returns if there is an obstacle between origin and end on the graph.
  934. ///
  935. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  936. ///
  937. /// [Open online documentation to see images]
  938. /// </summary>
  939. /// <param name="graph">The graph to perform the search on.</param>
  940. /// <param name="origin">Point to start from.</param>
  941. /// <param name="end">Point to linecast to.</param>
  942. /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
  943. /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
  944. /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
  945. public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
  946. return Linecast(graph, origin, end, hint, out hit, null);
  947. }
  948. /// <summary>Cached <see cref="Pathfinding.NNConstraint.None"/> with distanceXZ=true to reduce allocations</summary>
  949. static readonly NNConstraint NNConstraintNoneXZ = new NNConstraint {
  950. constrainWalkability = false,
  951. constrainArea = false,
  952. constrainTags = false,
  953. constrainDistance = false,
  954. graphMask = -1,
  955. distanceXZ = true,
  956. };
  957. /// <summary>Used to optimize linecasts by precomputing some values</summary>
  958. static readonly byte[] LinecastShapeEdgeLookup;
  959. static NavmeshBase () {
  960. // Want want to figure out which side of a triangle that a ray exists using.
  961. // There are only 3*3*3 = 27 different options for the [left/right/colinear] options for the 3 vertices of a triangle.
  962. // So we can precompute the result to improve the performance of linecasts.
  963. // For simplicity we reserve 2 bits for each side which means that we have 4*4*4 = 64 entries in the lookup table.
  964. LinecastShapeEdgeLookup = new byte[64];
  965. Side[] sideOfLine = new Side[3];
  966. for (int i = 0; i < LinecastShapeEdgeLookup.Length; i++) {
  967. sideOfLine[0] = (Side)((i >> 0) & 0x3);
  968. sideOfLine[1] = (Side)((i >> 2) & 0x3);
  969. sideOfLine[2] = (Side)((i >> 4) & 0x3);
  970. LinecastShapeEdgeLookup[i] = 0xFF;
  971. // Value 3 is an invalid value. So we just skip it.
  972. if (sideOfLine[0] != (Side)3 && sideOfLine[1] != (Side)3 && sideOfLine[2] != (Side)3) {
  973. // Figure out the side of the triangle that the line exits.
  974. // In case the line passes through one of the vertices of the triangle
  975. // there may be multiple alternatives. In that case pick the edge
  976. // which contains the fewest vertices that lie on the line.
  977. // This prevents a potential infinite loop when a linecast is done colinear
  978. // to the edge of a triangle.
  979. int bestBadness = int.MaxValue;
  980. for (int j = 0; j < 3; j++) {
  981. if ((sideOfLine[j] == Side.Left || sideOfLine[j] == Side.Colinear) && (sideOfLine[(j+1)%3] == Side.Right || sideOfLine[(j+1)%3] == Side.Colinear)) {
  982. var badness = (sideOfLine[j] == Side.Colinear ? 1 : 0) + (sideOfLine[(j+1)%3] == Side.Colinear ? 1 : 0);
  983. if (badness < bestBadness) {
  984. LinecastShapeEdgeLookup[i] = (byte)j;
  985. bestBadness = badness;
  986. }
  987. }
  988. }
  989. }
  990. }
  991. }
  992. /// <summary>
  993. /// Returns if there is an obstacle between origin and end on the graph.
  994. ///
  995. /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersections.
  996. ///
  997. /// Note: This method only makes sense for graphs in which there is a definite 'up' direction. For example it does not make sense for e.g spherical graphs,
  998. /// navmeshes in which characters can walk on walls/ceilings or other curved worlds. If you try to use this method on such navmeshes it may output nonsense.
  999. ///
  1000. /// [Open online documentation to see images]
  1001. /// </summary>
  1002. /// <param name="graph">The graph to perform the search on</param>
  1003. /// <param name="origin">Point to start from. This point should be on the navmesh. It will be snapped to the closest point on the navmesh otherwise.</param>
  1004. /// <param name="end">Point to linecast to</param>
  1005. /// <param name="hit">Contains info on what was hit, see GraphHitInfo</param>
  1006. /// <param name="hint">If you already know the node which contains the origin point, you may pass it here for slighly improved performance. If null, a search for the closest node will be done.</param>
  1007. /// <param name="trace">If a list is passed, then it will be filled with all nodes along the line up until it hits an obstacle or reaches the end.</param>
  1008. /// <param name="filter">If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
  1009. /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.</param>
  1010. public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace, System.Func<GraphNode, bool> filter = null) {
  1011. hit = new GraphHitInfo();
  1012. if (float.IsNaN(origin.x + origin.y + origin.z)) throw new System.ArgumentException("origin is NaN");
  1013. if (float.IsNaN(end.x + end.y + end.z)) throw new System.ArgumentException("end is NaN");
  1014. var node = hint as TriangleMeshNode;
  1015. if (node == null) {
  1016. node = graph.GetNearest(origin, NNConstraintNoneXZ).node as TriangleMeshNode;
  1017. if (node == null) {
  1018. Debug.LogError("Could not find a valid node to start from");
  1019. hit.origin = origin;
  1020. hit.point = origin;
  1021. return true;
  1022. }
  1023. }
  1024. // Snap the origin to the navmesh
  1025. var i3originInGraphSpace = node.ClosestPointOnNodeXZInGraphSpace(origin);
  1026. hit.origin = graph.transform.Transform((Vector3)i3originInGraphSpace);
  1027. if (!node.Walkable || (filter != null && !filter(node))) {
  1028. hit.node = node;
  1029. hit.point = hit.origin;
  1030. hit.tangentOrigin = hit.origin;
  1031. return true;
  1032. }
  1033. var endInGraphSpace = graph.transform.InverseTransform(end);
  1034. var i3endInGraphSpace = (Int3)endInGraphSpace;
  1035. // Fast early out check
  1036. if (i3originInGraphSpace == i3endInGraphSpace) {
  1037. hit.point = hit.origin;
  1038. hit.node = node;
  1039. if (trace != null) trace.Add(node);
  1040. return false;
  1041. }
  1042. int counter = 0;
  1043. while (true) {
  1044. counter++;
  1045. if (counter > 2000) {
  1046. Debug.LogError("Linecast was stuck in infinite loop. Breaking.");
  1047. return true;
  1048. }
  1049. if (trace != null) trace.Add(node);
  1050. Int3 a0, a1, a2;
  1051. node.GetVerticesInGraphSpace(out a0, out a1, out a2);
  1052. int sideOfLine = (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a0);
  1053. sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a1) << 2;
  1054. sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a2) << 4;
  1055. // Use a lookup table to figure out which side of this triangle that the ray exits
  1056. int shapeEdgeA = (int)LinecastShapeEdgeLookup[sideOfLine];
  1057. // The edge consists of the vertex with index 'sharedEdgeA' and the next vertex after that (index '(sharedEdgeA+1)%3')
  1058. var sideNodeExit = VectorMath.SideXZ(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2), shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0), i3endInGraphSpace);
  1059. if (sideNodeExit != Side.Left) {
  1060. // Ray stops before it leaves the current node.
  1061. // The endpoint must be inside the current node.
  1062. hit.point = end;
  1063. hit.node = node;
  1064. var endNode = graph.GetNearest(end, NNConstraintNoneXZ).node as TriangleMeshNode;
  1065. if (endNode == node || endNode == null) {
  1066. // We ended up at the right node.
  1067. // If endNode == null we also take this branch.
  1068. // That case may happen if a linecast is made to a point, but the point way a very large distance straight up into the air.
  1069. // The linecast may indeed reach the right point, but it's so far away up into the air that the GetNearest method will stop searching.
  1070. return false;
  1071. } else {
  1072. // The closest node to the end point was not the node we ended up at.
  1073. // This can happen if a linecast is done between two floors of a building.
  1074. // The linecast may reach the right location when seen from above
  1075. // but it will have ended up on the wrong floor of the building.
  1076. // This indicates that the start and end points cannot be connected by a valid straight line on the navmesh.
  1077. return true;
  1078. }
  1079. }
  1080. if (shapeEdgeA == 0xFF) {
  1081. // Line does not intersect node at all?
  1082. // This may theoretically happen if the origin was not properly snapped to the inside of the triangle, but is instead a tiny distance outside the node.
  1083. Debug.LogError("Line does not intersect node at all");
  1084. hit.node = node;
  1085. hit.point = hit.tangentOrigin = hit.origin;
  1086. return true;
  1087. } else {
  1088. bool success = false;
  1089. var nodeConnections = node.connections;
  1090. // Check all node connetions to see which one is the next node along the ray's path
  1091. for (int i = 0; i < nodeConnections.Length; i++) {
  1092. if (nodeConnections[i].shapeEdge == shapeEdgeA) {
  1093. // This might be the next node that we enter
  1094. var neighbour = nodeConnections[i].node as TriangleMeshNode;
  1095. if (neighbour == null || !neighbour.Walkable || (filter != null && !filter(neighbour))) continue;
  1096. var neighbourConnections = neighbour.connections;
  1097. int shapeEdgeB = -1;
  1098. for (int j = 0; j < neighbourConnections.Length; j++) {
  1099. if (neighbourConnections[j].node == node) {
  1100. shapeEdgeB = neighbourConnections[j].shapeEdge;
  1101. break;
  1102. }
  1103. }
  1104. if (shapeEdgeB == -1) {
  1105. // Connection was mono-directional!
  1106. // This shouldn't normally happen on navmeshes (when the shapeEdge matches at least) unless a user has done something strange to the navmesh.
  1107. continue;
  1108. }
  1109. var side1 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace(shapeEdgeB));
  1110. var side2 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace((shapeEdgeB+1) % 3));
  1111. // Check if the line enters this edge
  1112. success = (side1 == Side.Right || side1 == Side.Colinear) && (side2 == Side.Left || side2 == Side.Colinear);
  1113. if (!success) continue;
  1114. // Ray has entered the neighbouring node.
  1115. // After the first node, it is possible to prove the loop invariant that shapeEdgeA will *never* end up as -1 (checked above)
  1116. // Since side = Colinear acts essentially as a wildcard. side1 and side2 can be the most restricted if they are side1=right, side2=left.
  1117. // Then when we get to the next node we know that the sideOfLine array is either [*, Right, Left], [Left, *, Right] or [Right, Left, *], where * is unknown.
  1118. // We are looking for the sequence [Left, Right] (possibly including Colinear as wildcard). We will always find this sequence regardless of the value of *.
  1119. node = neighbour;
  1120. break;
  1121. }
  1122. }
  1123. if (!success) {
  1124. // Node did not enter any neighbours
  1125. // It must have hit the border of the navmesh
  1126. var hitEdgeStartInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2));
  1127. var hitEdgeEndInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0));
  1128. var intersectionInGraphSpace = VectorMath.LineIntersectionPointXZ(hitEdgeStartInGraphSpace, hitEdgeEndInGraphSpace, (Vector3)i3originInGraphSpace, (Vector3)i3endInGraphSpace);
  1129. hit.point = graph.transform.Transform(intersectionInGraphSpace);
  1130. hit.node = node;
  1131. var hitEdgeStart = graph.transform.Transform(hitEdgeStartInGraphSpace);
  1132. var hitEdgeEnd = graph.transform.Transform(hitEdgeEndInGraphSpace);
  1133. hit.tangent = hitEdgeEnd - hitEdgeStart;
  1134. hit.tangentOrigin = hitEdgeStart;
  1135. return true;
  1136. }
  1137. }
  1138. }
  1139. }
  1140. public override void OnDrawGizmos (Pathfinding.Util.RetainedGizmos gizmos, bool drawNodes) {
  1141. if (!drawNodes) {
  1142. return;
  1143. }
  1144. using (var helper = gizmos.GetSingleFrameGizmoHelper(active)) {
  1145. var bounds = new Bounds();
  1146. bounds.SetMinMax(Vector3.zero, forcedBoundsSize);
  1147. // Draw a write cube using the latest transform
  1148. // (this makes the bounds update immediately if some field is changed in the editor)
  1149. helper.builder.DrawWireCube(CalculateTransform(), bounds, Color.white);
  1150. }
  1151. if (tiles != null && (showMeshSurface || showMeshOutline || showNodeConnections)) {
  1152. var baseHasher = new RetainedGizmos.Hasher(active);
  1153. baseHasher.AddHash(showMeshOutline ? 1 : 0);
  1154. baseHasher.AddHash(showMeshSurface ? 1 : 0);
  1155. baseHasher.AddHash(showNodeConnections ? 1 : 0);
  1156. int startTileIndex = 0;
  1157. var hasher = baseHasher;
  1158. var hashedNodes = 0;
  1159. // Update navmesh vizualizations for
  1160. // the tiles that have been changed
  1161. for (int i = 0; i < tiles.Length; i++) {
  1162. // This may happen if an exception has been thrown when the graph was scanned.
  1163. // We don't want the gizmo code to start to throw exceptions as well then as
  1164. // that would obscure the actual source of the error.
  1165. if (tiles[i] == null) continue;
  1166. // Calculate a hash of the tile
  1167. var nodes = tiles[i].nodes;
  1168. for (int j = 0; j < nodes.Length; j++) {
  1169. hasher.HashNode(nodes[j]);
  1170. }
  1171. hashedNodes += nodes.Length;
  1172. // Note: do not batch more than some large number of nodes at a time.
  1173. // Also do not batch more than a single "row" of the graph at once
  1174. // because otherwise a small change in one part of the graph could invalidate
  1175. // the caches almost everywhere else.
  1176. // When restricting the caches to row by row a change in a row
  1177. // will never invalidate the cache in another row.
  1178. if (hashedNodes > 1024 || (i % tileXCount) == tileXCount - 1 || i == tiles.Length - 1) {
  1179. if (!gizmos.Draw(hasher)) {
  1180. using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
  1181. if (showMeshSurface || showMeshOutline) {
  1182. CreateNavmeshSurfaceVisualization(tiles, startTileIndex, i + 1, helper);
  1183. CreateNavmeshOutlineVisualization(tiles, startTileIndex, i + 1, helper);
  1184. }
  1185. if (showNodeConnections) {
  1186. for (int ti = startTileIndex; ti <= i; ti++) {
  1187. if (tiles[ti] == null) continue;
  1188. var tileNodes = tiles[ti].nodes;
  1189. for (int j = 0; j < tileNodes.Length; j++) {
  1190. helper.DrawConnections(tileNodes[j]);
  1191. }
  1192. }
  1193. }
  1194. }
  1195. }
  1196. gizmos.Draw(hasher);
  1197. startTileIndex = i + 1;
  1198. hasher = baseHasher;
  1199. hashedNodes = 0;
  1200. }
  1201. }
  1202. }
  1203. if (active.showUnwalkableNodes) DrawUnwalkableNodes(active.unwalkableNodeDebugSize);
  1204. }
  1205. /// <summary>Creates a mesh of the surfaces of the navmesh for use in OnDrawGizmos in the editor</summary>
  1206. void CreateNavmeshSurfaceVisualization (NavmeshTile[] tiles, int startTile, int endTile, GraphGizmoHelper helper) {
  1207. int numNodes = 0;
  1208. for (int i = startTile; i < endTile; i++) if (tiles[i] != null) numNodes += tiles[i].nodes.Length;
  1209. // Vertex array might be a bit larger than necessary, but that's ok
  1210. var vertices = ArrayPool<Vector3>.Claim(numNodes*3);
  1211. var colors = ArrayPool<Color>.Claim(numNodes*3);
  1212. int offset = 0;
  1213. for (int i = startTile; i < endTile; i++) {
  1214. var tile = tiles[i];
  1215. if (tile == null) continue;
  1216. for (int j = 0; j < tile.nodes.Length; j++) {
  1217. var node = tile.nodes[j];
  1218. Int3 v0, v1, v2;
  1219. node.GetVertices(out v0, out v1, out v2);
  1220. int index = offset + j*3;
  1221. vertices[index + 0] = (Vector3)v0;
  1222. vertices[index + 1] = (Vector3)v1;
  1223. vertices[index + 2] = (Vector3)v2;
  1224. var color = helper.NodeColor(node);
  1225. colors[index + 0] = colors[index + 1] = colors[index + 2] = color;
  1226. }
  1227. offset += tile.nodes.Length * 3;
  1228. }
  1229. if (showMeshSurface) helper.DrawTriangles(vertices, colors, numNodes);
  1230. if (showMeshOutline) helper.DrawWireTriangles(vertices, colors, numNodes);
  1231. // Return lists to the pool
  1232. ArrayPool<Vector3>.Release(ref vertices);
  1233. ArrayPool<Color>.Release(ref colors);
  1234. }
  1235. /// <summary>Creates an outline of the navmesh for use in OnDrawGizmos in the editor</summary>
  1236. static void CreateNavmeshOutlineVisualization (NavmeshTile[] tiles, int startTile, int endTile, GraphGizmoHelper helper) {
  1237. var sharedEdges = new bool[3];
  1238. for (int i = startTile; i < endTile; i++) {
  1239. var tile = tiles[i];
  1240. if (tile == null) continue;
  1241. for (int j = 0; j < tile.nodes.Length; j++) {
  1242. sharedEdges[0] = sharedEdges[1] = sharedEdges[2] = false;
  1243. var node = tile.nodes[j];
  1244. for (int c = 0; c < node.connections.Length; c++) {
  1245. var other = node.connections[c].node as TriangleMeshNode;
  1246. // Loop through neighbours to figure out which edges are shared
  1247. if (other != null && other.GraphIndex == node.GraphIndex) {
  1248. for (int v = 0; v < 3; v++) {
  1249. for (int v2 = 0; v2 < 3; v2++) {
  1250. if (node.GetVertexIndex(v) == other.GetVertexIndex((v2+1)%3) && node.GetVertexIndex((v+1)%3) == other.GetVertexIndex(v2)) {
  1251. // Found a shared edge with the other node
  1252. sharedEdges[v] = true;
  1253. v = 3;
  1254. break;
  1255. }
  1256. }
  1257. }
  1258. }
  1259. }
  1260. var color = helper.NodeColor(node);
  1261. for (int v = 0; v < 3; v++) {
  1262. if (!sharedEdges[v]) {
  1263. helper.builder.DrawLine((Vector3)node.GetVertex(v), (Vector3)node.GetVertex((v+1)%3), color);
  1264. }
  1265. }
  1266. }
  1267. }
  1268. }
  1269. /// <summary>
  1270. /// Serializes Node Info.
  1271. /// Should serialize:
  1272. /// - Base
  1273. /// - Node Flags
  1274. /// - Node Penalties
  1275. /// - Node
  1276. /// - Node Positions (if applicable)
  1277. /// - Any other information necessary to load the graph in-game
  1278. /// All settings marked with json attributes (e.g JsonMember) have already been
  1279. /// saved as graph settings and do not need to be handled here.
  1280. ///
  1281. /// It is not necessary for this implementation to be forward or backwards compatible.
  1282. ///
  1283. /// See:
  1284. /// </summary>
  1285. protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
  1286. BinaryWriter writer = ctx.writer;
  1287. if (tiles == null) {
  1288. writer.Write(-1);
  1289. return;
  1290. }
  1291. writer.Write(tileXCount);
  1292. writer.Write(tileZCount);
  1293. for (int z = 0; z < tileZCount; z++) {
  1294. for (int x = 0; x < tileXCount; x++) {
  1295. NavmeshTile tile = tiles[x + z*tileXCount];
  1296. if (tile == null) {
  1297. throw new System.Exception("NULL Tile");
  1298. //writer.Write (-1);
  1299. //continue;
  1300. }
  1301. writer.Write(tile.x);
  1302. writer.Write(tile.z);
  1303. if (tile.x != x || tile.z != z) continue;
  1304. writer.Write(tile.w);
  1305. writer.Write(tile.d);
  1306. writer.Write(tile.tris.Length);
  1307. for (int i = 0; i < tile.tris.Length; i++) writer.Write(tile.tris[i]);
  1308. writer.Write(tile.verts.Length);
  1309. for (int i = 0; i < tile.verts.Length; i++) {
  1310. ctx.SerializeInt3(tile.verts[i]);
  1311. }
  1312. writer.Write(tile.vertsInGraphSpace.Length);
  1313. for (int i = 0; i < tile.vertsInGraphSpace.Length; i++) {
  1314. ctx.SerializeInt3(tile.vertsInGraphSpace[i]);
  1315. }
  1316. writer.Write(tile.nodes.Length);
  1317. for (int i = 0; i < tile.nodes.Length; i++) {
  1318. tile.nodes[i].SerializeNode(ctx);
  1319. }
  1320. }
  1321. }
  1322. }
  1323. protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
  1324. BinaryReader reader = ctx.reader;
  1325. tileXCount = reader.ReadInt32();
  1326. if (tileXCount < 0) return;
  1327. tileZCount = reader.ReadInt32();
  1328. transform = CalculateTransform();
  1329. tiles = new NavmeshTile[tileXCount * tileZCount];
  1330. //Make sure mesh nodes can reference this graph
  1331. TriangleMeshNode.SetNavmeshHolder((int)ctx.graphIndex, this);
  1332. for (int z = 0; z < tileZCount; z++) {
  1333. for (int x = 0; x < tileXCount; x++) {
  1334. int tileIndex = x + z*tileXCount;
  1335. int tx = reader.ReadInt32();
  1336. if (tx < 0) throw new System.Exception("Invalid tile coordinates (x < 0)");
  1337. int tz = reader.ReadInt32();
  1338. if (tz < 0) throw new System.Exception("Invalid tile coordinates (z < 0)");
  1339. // This is not the origin of a large tile. Refer back to that tile.
  1340. if (tx != x || tz != z) {
  1341. tiles[tileIndex] = tiles[tz*tileXCount + tx];
  1342. continue;
  1343. }
  1344. var tile = tiles[tileIndex] = new NavmeshTile {
  1345. x = tx,
  1346. z = tz,
  1347. w = reader.ReadInt32(),
  1348. d = reader.ReadInt32(),
  1349. bbTree = ObjectPool<BBTree>.Claim(),
  1350. graph = this,
  1351. };
  1352. int trisCount = reader.ReadInt32();
  1353. if (trisCount % 3 != 0) throw new System.Exception("Corrupt data. Triangle indices count must be divisable by 3. Read " + trisCount);
  1354. tile.tris = new int[trisCount];
  1355. for (int i = 0; i < tile.tris.Length; i++) tile.tris[i] = reader.ReadInt32();
  1356. tile.verts = new Int3[reader.ReadInt32()];
  1357. for (int i = 0; i < tile.verts.Length; i++) {
  1358. tile.verts[i] = ctx.DeserializeInt3();
  1359. }
  1360. if (ctx.meta.version.Major >= 4) {
  1361. tile.vertsInGraphSpace = new Int3[reader.ReadInt32()];
  1362. if (tile.vertsInGraphSpace.Length != tile.verts.Length) throw new System.Exception("Corrupt data. Array lengths did not match");
  1363. for (int i = 0; i < tile.verts.Length; i++) {
  1364. tile.vertsInGraphSpace[i] = ctx.DeserializeInt3();
  1365. }
  1366. } else {
  1367. // Compatibility
  1368. tile.vertsInGraphSpace = new Int3[tile.verts.Length];
  1369. tile.verts.CopyTo(tile.vertsInGraphSpace, 0);
  1370. transform.InverseTransform(tile.vertsInGraphSpace);
  1371. }
  1372. int nodeCount = reader.ReadInt32();
  1373. tile.nodes = new TriangleMeshNode[nodeCount];
  1374. // Prepare for storing in vertex indices
  1375. tileIndex <<= TileIndexOffset;
  1376. for (int i = 0; i < tile.nodes.Length; i++) {
  1377. var node = new TriangleMeshNode(active);
  1378. tile.nodes[i] = node;
  1379. node.DeserializeNode(ctx);
  1380. node.v0 = tile.tris[i*3+0] | tileIndex;
  1381. node.v1 = tile.tris[i*3+1] | tileIndex;
  1382. node.v2 = tile.tris[i*3+2] | tileIndex;
  1383. node.UpdatePositionFromVertices();
  1384. }
  1385. tile.bbTree.RebuildFrom(tile.nodes);
  1386. }
  1387. }
  1388. }
  1389. protected override void PostDeserialization (GraphSerializationContext ctx) {
  1390. // Compatibility
  1391. if (ctx.meta.version < AstarSerializer.V4_1_0 && tiles != null) {
  1392. Dictionary<TriangleMeshNode, Connection[]> conns = tiles.SelectMany(s => s.nodes).ToDictionary(n => n, n => n.connections ?? new Connection[0]);
  1393. // We need to recalculate all connections when upgrading data from earlier than 4.1.0
  1394. // as the connections now need information about which edge was used.
  1395. // This may remove connections for e.g off-mesh links.
  1396. foreach (var tile in tiles) CreateNodeConnections(tile.nodes);
  1397. foreach (var tile in tiles) ConnectTileWithNeighbours(tile);
  1398. // Restore any connections that were contained in the serialized file but didn't get added by the method calls above
  1399. GetNodes(node => {
  1400. var triNode = node as TriangleMeshNode;
  1401. foreach (var conn in conns[triNode].Where(conn => !triNode.ContainsConnection(conn.node)).ToList()) {
  1402. triNode.AddConnection(conn.node, conn.cost, conn.shapeEdge);
  1403. }
  1404. });
  1405. }
  1406. // Make sure that the transform is up to date.
  1407. // It is assumed that the current graph settings correspond to the correct
  1408. // transform as it is not serialized itself.
  1409. transform = CalculateTransform();
  1410. }
  1411. }
  1412. }