TileHandler.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using Pathfinding.ClipperLib;
  5. using UnityEngine.Profiling;
  6. namespace Pathfinding.Util {
  7. using Pathfinding;
  8. using Pathfinding.Poly2Tri;
  9. /// <summary>
  10. /// Utility class for updating tiles of navmesh/recast graphs.
  11. ///
  12. /// Most operations that this class does are asynchronous.
  13. /// They will be added as work items to the AstarPath class
  14. /// and executed when the pathfinding threads have finished
  15. /// calculating their current paths.
  16. ///
  17. /// See: navmeshcutting (view in online documentation for working links)
  18. /// See: <see cref="Pathfinding.NavmeshUpdates"/>
  19. /// </summary>
  20. public class TileHandler {
  21. /// <summary>The underlaying graph which is handled by this instance</summary>
  22. public readonly NavmeshBase graph;
  23. /// <summary>Number of tiles along the x axis</summary>
  24. readonly int tileXCount;
  25. /// <summary>Number of tiles along the z axis</summary>
  26. readonly int tileZCount;
  27. /// <summary>Handles polygon clipping operations</summary>
  28. readonly Clipper clipper = new Clipper();
  29. /// <summary>Cached dictionary to avoid excessive allocations</summary>
  30. readonly Dictionary<Int2, int> cached_Int2_int_dict = new Dictionary<Int2, int>();
  31. /// <summary>
  32. /// Which tile type is active on each tile index.
  33. /// This array will be tileXCount*tileZCount elements long.
  34. /// </summary>
  35. readonly TileType[] activeTileTypes;
  36. /// <summary>Rotations of the active tiles</summary>
  37. readonly int[] activeTileRotations;
  38. /// <summary>Offsets along the Y axis of the active tiles</summary>
  39. readonly int[] activeTileOffsets;
  40. /// <summary>A flag for each tile that is set to true if it has been reloaded while batching is in progress</summary>
  41. readonly bool[] reloadedInBatch;
  42. /// <summary>
  43. /// NavmeshCut and NavmeshAdd components registered to this tile handler.
  44. /// This is updated by the <see cref="Pathfinding.NavmeshUpdates"/> class.
  45. /// See: <see cref="Pathfinding.NavmeshUpdates"/>
  46. /// </summary>
  47. public readonly GridLookup<NavmeshClipper> cuts;
  48. /// <summary>
  49. /// Positive while batching tile updates.
  50. /// Batching tile updates has a positive effect on performance
  51. /// </summary>
  52. int batchDepth;
  53. /// <summary>
  54. /// True while batching tile updates.
  55. /// Batching tile updates has a positive effect on performance
  56. /// </summary>
  57. bool isBatching { get { return batchDepth > 0; } }
  58. /// <summary>
  59. /// Utility for clipping polygons to rectangles.
  60. /// Implemented as a struct and not a bunch of static methods
  61. /// because it needs some buffer arrays that are best cached
  62. /// to avoid excessive allocations
  63. /// </summary>
  64. readonly Pathfinding.Voxels.Int3PolygonClipper simpleClipper;
  65. /// <summary>
  66. /// True if the tile handler still has the same number of tiles and tile layout as the graph.
  67. /// If the graph is rescanned the tile handler will get out of sync and needs to be recreated.
  68. /// </summary>
  69. public bool isValid {
  70. get {
  71. return graph != null && graph.exists && tileXCount == graph.tileXCount && tileZCount == graph.tileZCount;
  72. }
  73. }
  74. public TileHandler (NavmeshBase graph) {
  75. if (graph == null) throw new ArgumentNullException("graph");
  76. if (graph.GetTiles() == null) Debug.LogWarning("Creating a TileHandler for a graph with no tiles. Please scan the graph before creating a TileHandler");
  77. tileXCount = graph.tileXCount;
  78. tileZCount = graph.tileZCount;
  79. activeTileTypes = new TileType[tileXCount*tileZCount];
  80. activeTileRotations = new int[activeTileTypes.Length];
  81. activeTileOffsets = new int[activeTileTypes.Length];
  82. reloadedInBatch = new bool[activeTileTypes.Length];
  83. cuts = new GridLookup<NavmeshClipper>(new Int2(tileXCount, tileZCount));
  84. this.graph = graph;
  85. }
  86. /// <summary>
  87. /// Call to update the specified tiles with new information based on the navmesh/recast graph.
  88. /// This is usually called right after a navmesh/recast graph has recalculated some tiles
  89. /// and thus some calculations need to be done to take navmesh cutting into account
  90. /// as well.
  91. ///
  92. /// Will reload all tiles in the list.
  93. /// </summary>
  94. public void OnRecalculatedTiles (NavmeshTile[] recalculatedTiles) {
  95. for (int i = 0; i < recalculatedTiles.Length; i++) {
  96. UpdateTileType(recalculatedTiles[i]);
  97. }
  98. StartBatchLoad();
  99. for (int i = 0; i < recalculatedTiles.Length; i++) {
  100. ReloadTile(recalculatedTiles[i].x, recalculatedTiles[i].z);
  101. }
  102. EndBatchLoad();
  103. }
  104. /// <summary>
  105. /// Rotation of the specified tile relative to the original rotation of the tile type.
  106. /// A result N means that the tile has been rotated N*90 degrees.
  107. /// </summary>
  108. public int GetActiveRotation (Int2 p) {
  109. return activeTileRotations[p.x + p.y*tileXCount];
  110. }
  111. /// <summary>A template for a single tile in a navmesh/recast graph</summary>
  112. public class TileType {
  113. Int3[] verts;
  114. int[] tris;
  115. Int3 offset;
  116. int lastYOffset;
  117. int lastRotation;
  118. int width;
  119. int depth;
  120. public int Width {
  121. get {
  122. return width;
  123. }
  124. }
  125. public int Depth {
  126. get {
  127. return depth;
  128. }
  129. }
  130. /// <summary>
  131. /// Matrices for rotation.
  132. /// Each group of 4 elements is a 2x2 matrix.
  133. /// The XZ position is multiplied by this.
  134. /// So
  135. /// <code>
  136. /// //A rotation by 90 degrees clockwise, second matrix in the array
  137. /// (5,2) * ((0, 1), (-1, 0)) = (2,-5)
  138. /// </code>
  139. /// </summary>
  140. private static readonly int[] Rotations = {
  141. 1, 0, // Identity matrix
  142. 0, 1,
  143. 0, 1,
  144. -1, 0,
  145. -1, 0,
  146. 0, -1,
  147. 0, -1,
  148. 1, 0
  149. };
  150. public TileType (Int3[] sourceVerts, int[] sourceTris, Int3 tileSize, Int3 centerOffset, int width = 1, int depth = 1) {
  151. if (sourceVerts == null) throw new ArgumentNullException("sourceVerts");
  152. if (sourceTris == null) throw new ArgumentNullException("sourceTris");
  153. tris = new int[sourceTris.Length];
  154. for (int i = 0; i < tris.Length; i++) tris[i] = sourceTris[i];
  155. verts = new Int3[sourceVerts.Length];
  156. for (int i = 0; i < sourceVerts.Length; i++) {
  157. verts[i] = sourceVerts[i] + centerOffset;
  158. }
  159. offset = tileSize/2;
  160. offset.x *= width;
  161. offset.z *= depth;
  162. offset.y = 0;
  163. for (int i = 0; i < sourceVerts.Length; i++) {
  164. verts[i] = verts[i] + offset;
  165. }
  166. lastRotation = 0;
  167. lastYOffset = 0;
  168. this.width = width;
  169. this.depth = depth;
  170. }
  171. /// <summary>
  172. /// Create a new TileType.
  173. /// First all vertices of the source mesh are offseted by the centerOffset.
  174. /// The source mesh is assumed to be centered (after offsetting). Corners of the tile should be at tileSize*0.5 along all axes.
  175. /// When width or depth is not 1, the tileSize param should not change, but corners of the tile are assumed to lie further out.
  176. /// </summary>
  177. /// <param name="source">The navmesh as a unity Mesh</param>
  178. /// <param name="width">The number of base tiles this tile type occupies on the x-axis</param>
  179. /// <param name="depth">The number of base tiles this tile type occupies on the z-axis</param>
  180. /// <param name="tileSize">Size of a single tile, the y-coordinate will be ignored.</param>
  181. /// <param name="centerOffset">This offset will be added to all vertices</param>
  182. public TileType (Mesh source, Int3 tileSize, Int3 centerOffset, int width = 1, int depth = 1) {
  183. if (source == null) throw new ArgumentNullException("source");
  184. Vector3[] vectorVerts = source.vertices;
  185. tris = source.triangles;
  186. verts = new Int3[vectorVerts.Length];
  187. for (int i = 0; i < vectorVerts.Length; i++) {
  188. verts[i] = (Int3)vectorVerts[i] + centerOffset;
  189. }
  190. offset = tileSize/2;
  191. offset.x *= width;
  192. offset.z *= depth;
  193. offset.y = 0;
  194. for (int i = 0; i < vectorVerts.Length; i++) {
  195. verts[i] = verts[i] + offset;
  196. }
  197. lastRotation = 0;
  198. lastYOffset = 0;
  199. this.width = width;
  200. this.depth = depth;
  201. }
  202. /// <summary>
  203. /// Load a tile, result given by the vert and tris array.
  204. /// Warning: For performance and memory reasons, the returned arrays are internal arrays, so they must not be modified in any way or
  205. /// subsequent calls to Load may give corrupt output. The contents of the verts array is only valid until the next call to Load since
  206. /// different rotations and y offsets can be applied.
  207. /// If you need persistent arrays, please copy the returned ones.
  208. /// </summary>
  209. public void Load (out Int3[] verts, out int[] tris, int rotation, int yoffset) {
  210. //Make sure it is a number 0 <= x < 4
  211. rotation = ((rotation % 4) + 4) % 4;
  212. //Figure out relative rotation (relative to previous rotation that is, since that is still applied to the verts array)
  213. int tmp = rotation;
  214. rotation = (rotation - (lastRotation % 4) + 4) % 4;
  215. lastRotation = tmp;
  216. verts = this.verts;
  217. int relYOffset = yoffset - lastYOffset;
  218. lastYOffset = yoffset;
  219. if (rotation != 0 || relYOffset != 0) {
  220. for (int i = 0; i < verts.Length; i++) {
  221. Int3 op = verts[i] - offset;
  222. Int3 p = op;
  223. p.y += relYOffset;
  224. p.x = op.x * Rotations[rotation*4 + 0] + op.z * Rotations[rotation*4 + 1];
  225. p.z = op.x * Rotations[rotation*4 + 2] + op.z * Rotations[rotation*4 + 3];
  226. verts[i] = p + offset;
  227. }
  228. }
  229. tris = this.tris;
  230. }
  231. }
  232. /// <summary>
  233. /// Register that a tile can be loaded from source.
  234. ///
  235. /// Returns: Identifier for loading that tile type
  236. /// </summary>
  237. /// <param name="centerOffset">Assumes that the mesh has its pivot point at the center of the tile.
  238. /// If it has not, you can supply a non-zero centerOffset to offset all vertices.</param>
  239. /// <param name="width">width of the tile. In base tiles, not world units.</param>
  240. /// <param name="depth">depth of the tile. In base tiles, not world units.</param>
  241. /// <param name="source">Source mesh, must be readable.</param>
  242. public TileType RegisterTileType (Mesh source, Int3 centerOffset, int width = 1, int depth = 1) {
  243. return new TileType(source, (Int3) new Vector3(graph.TileWorldSizeX, 0, graph.TileWorldSizeZ), centerOffset, width, depth);
  244. }
  245. public void CreateTileTypesFromGraph () {
  246. NavmeshTile[] tiles = graph.GetTiles();
  247. if (tiles == null)
  248. return;
  249. if (!isValid) {
  250. throw new InvalidOperationException("Graph tiles are invalid (number of tiles is not equal to width*depth of the graph). You need to create a new tile handler if you have changed the graph.");
  251. }
  252. for (int z = 0; z < tileZCount; z++) {
  253. for (int x = 0; x < tileXCount; x++) {
  254. NavmeshTile tile = tiles[x + z*tileXCount];
  255. UpdateTileType(tile);
  256. }
  257. }
  258. }
  259. void UpdateTileType (NavmeshTile tile) {
  260. int x = tile.x;
  261. int z = tile.z;
  262. Int3 size = (Int3) new Vector3(graph.TileWorldSizeX, 0, graph.TileWorldSizeZ);
  263. Bounds b = graph.GetTileBoundsInGraphSpace(x, z);
  264. var centerOffset = -((Int3)b.min + new Int3(size.x*tile.w/2, 0, size.z*tile.d/2));
  265. var tileType = new TileType(tile.vertsInGraphSpace, tile.tris, size, centerOffset, tile.w, tile.d);
  266. int index = x + z*tileXCount;
  267. activeTileTypes[index] = tileType;
  268. activeTileRotations[index] = 0;
  269. activeTileOffsets[index] = 0;
  270. }
  271. /// <summary>
  272. /// Start batch loading.
  273. /// Every call to this method must be matched by exactly one call to EndBatchLoad.
  274. /// </summary>
  275. public void StartBatchLoad () {
  276. batchDepth++;
  277. if (batchDepth > 1) return;
  278. AstarPath.active.AddWorkItem(new AstarWorkItem(force => {
  279. graph.StartBatchTileUpdate();
  280. return true;
  281. }));
  282. }
  283. public void EndBatchLoad () {
  284. if (batchDepth <= 0) throw new Exception("Ending batching when batching has not been started");
  285. batchDepth--;
  286. for (int i = 0; i < reloadedInBatch.Length; i++) reloadedInBatch[i] = false;
  287. AstarPath.active.AddWorkItem(new AstarWorkItem((ctx, force) => {
  288. Profiler.BeginSample("Apply Tile Modifications");
  289. graph.EndBatchTileUpdate();
  290. // Trigger post update event
  291. // This can trigger for example recalculation of navmesh links
  292. ctx.SetGraphDirty(graph);
  293. Profiler.EndSample();
  294. return true;
  295. }));
  296. }
  297. [Flags]
  298. public enum CutMode {
  299. /// <summary>Cut holes in the navmesh</summary>
  300. CutAll = 1,
  301. /// <summary>Cut the navmesh but do not remove the interior of the cuts</summary>
  302. CutDual = 2,
  303. /// <summary>Also cut using the extra shape that was provided</summary>
  304. CutExtra = 4
  305. }
  306. /// <summary>Internal class describing a single NavmeshCut</summary>
  307. class Cut {
  308. /// <summary>Bounds in XZ space</summary>
  309. public IntRect bounds;
  310. /// <summary>X is the lower bound on the y axis, Y is the upper bounds on the Y axis</summary>
  311. public Int2 boundsY;
  312. public bool isDual;
  313. public bool cutsAddedGeom;
  314. public List<IntPoint> contour;
  315. }
  316. /// <summary>Internal class representing a mesh which is the result of the CutPoly method</summary>
  317. struct CuttingResult {
  318. public Int3[] verts;
  319. public int[] tris;
  320. }
  321. /// <summary>
  322. /// Cuts a piece of navmesh using navmesh cuts.
  323. ///
  324. /// Note: I am sorry for the really messy code in this method.
  325. /// It really needs to be refactored.
  326. ///
  327. /// See: NavmeshBase.transform
  328. /// See: CutMode
  329. /// </summary>
  330. /// <param name="verts">Vertices that are going to be cut. Should be in graph space.</param>
  331. /// <param name="tris">Triangles describing a mesh using the vertices.</param>
  332. /// <param name="extraShape">If supplied the resulting mesh will be the intersection of the input mesh and this mesh.</param>
  333. /// <param name="graphTransform">Transform mapping graph space to world space.</param>
  334. /// <param name="tiles">Tiles in the recast graph which the mesh covers.</param>
  335. /// <param name="mode"></param>
  336. /// <param name="perturbate">Move navmesh cuts around randomly a bit, the larger the value the more they are moved around.
  337. /// Used to prevent edge cases that can cause the clipping to fail.</param>
  338. CuttingResult CutPoly (Int3[] verts, int[] tris, Int3[] extraShape, GraphTransform graphTransform, IntRect tiles, CutMode mode = CutMode.CutAll | CutMode.CutDual, int perturbate = -1) {
  339. // Nothing to do here
  340. if (verts.Length == 0 || tris.Length == 0) {
  341. return new CuttingResult {
  342. verts = ArrayPool<Int3>.Claim(0),
  343. tris = ArrayPool<int>.Claim(0)
  344. };
  345. }
  346. if (perturbate > 10) {
  347. Debug.LogError("Too many perturbations aborting.\n" +
  348. "This may cause a tile in the navmesh to become empty. " +
  349. "Try to see see if any of your NavmeshCut or NavmeshAdd components use invalid custom meshes.");
  350. return new CuttingResult {
  351. verts = verts,
  352. tris = tris
  353. };
  354. }
  355. List<IntPoint> extraClipShape = null;
  356. // Do not cut with extra shape if there is no extra shape
  357. if (extraShape == null && (mode & CutMode.CutExtra) != 0) {
  358. throw new Exception("extraShape is null and the CutMode specifies that it should be used. Cannot use null shape.");
  359. }
  360. // Calculate tile bounds so that the correct cutting offset can be used
  361. // The tile will be cut in local space (i.e it is at the world origin) so cuts need to be translated
  362. // to that point from their world space coordinates
  363. var graphSpaceBounds = graph.GetTileBoundsInGraphSpace(tiles);
  364. var cutOffset = graphSpaceBounds.min;
  365. var transform = graphTransform * Matrix4x4.TRS(cutOffset, Quaternion.identity, Vector3.one);
  366. // cutRegionSize The cutting region is a rectangle with one corner at the origin and one at the coordinates of cutRegionSize
  367. // NavmeshAdd components will be clipped against this rectangle. It is assumed that the input vertices do not extend outside the region.
  368. // For navmesh tiles, cutRegionSize is set to the size of a single tile.
  369. var cutRegionSize = new Vector2(graphSpaceBounds.size.x, graphSpaceBounds.size.z);
  370. if ((mode & CutMode.CutExtra) != 0) {
  371. extraClipShape = ListPool<IntPoint>.Claim(extraShape.Length);
  372. for (int i = 0; i < extraShape.Length; i++) {
  373. var p = transform.InverseTransform(extraShape[i]);
  374. extraClipShape.Add(new IntPoint(p.x, p.z));
  375. }
  376. }
  377. var bounds = new IntRect(verts[0].x, verts[0].z, verts[0].x, verts[0].z);
  378. // Expand bounds to contain all vertices
  379. for (int i = 0; i < verts.Length; i++) {
  380. bounds = bounds.ExpandToContain(verts[i].x, verts[i].z);
  381. }
  382. // Find all NavmeshCut components that could be inside these bounds
  383. List<NavmeshCut> navmeshCuts;
  384. if (mode == CutMode.CutExtra) {
  385. // Not needed when only cutting extra
  386. navmeshCuts = ListPool<NavmeshCut>.Claim();
  387. } else {
  388. navmeshCuts = cuts.QueryRect<NavmeshCut>(tiles);
  389. }
  390. // Find all NavmeshAdd components that could be inside the bounds
  391. List<NavmeshAdd> navmeshAdds = cuts.QueryRect<NavmeshAdd>(tiles);
  392. var intersectingCuts = ListPool<int>.Claim();
  393. var cutInfos = PrepareNavmeshCutsForCutting(navmeshCuts, transform, bounds, perturbate, navmeshAdds.Count > 0);
  394. var outverts = ListPool<Int3>.Claim(verts.Length*2);
  395. var outtris = ListPool<int>.Claim(tris.Length);
  396. if (navmeshCuts.Count == 0 && navmeshAdds.Count == 0 && (mode & ~(CutMode.CutAll | CutMode.CutDual)) == 0 && (mode & CutMode.CutAll) != 0) {
  397. // Fast path for the common case, no cuts or adds to the navmesh, so we just copy the vertices
  398. CopyMesh(verts, tris, outverts, outtris);
  399. } else {
  400. var poly = ListPool<IntPoint>.Claim();
  401. var point2Index = new Dictionary<TriangulationPoint, int>();
  402. var polypoints = ListPool<Poly2Tri.PolygonPoint>.Claim();
  403. var clipResult = new Pathfinding.ClipperLib.PolyTree();
  404. var intermediateClipResult = ListPool<List<IntPoint> >.Claim();
  405. var polyCache = StackPool<Poly2Tri.Polygon>.Claim();
  406. // If we failed the previous iteration
  407. // use a higher quality cutting
  408. // this is heavier on the CPU, so only use it in special cases
  409. clipper.StrictlySimple = perturbate > -1;
  410. clipper.ReverseSolution = true;
  411. Int3[] clipIn = null;
  412. Int3[] clipOut = null;
  413. Int2 clipSize = new Int2();
  414. if (navmeshAdds.Count > 0) {
  415. clipIn = new Int3[7];
  416. clipOut = new Int3[7];
  417. // TODO: What if the size is odd?
  418. // Convert cutRegionSize to an Int2 (all the casting is used to scale it appropriately, Int2 does not have an explicit conversion)
  419. clipSize = new Int2(((Int3)(Vector3)cutRegionSize).x, ((Int3)(Vector3)cutRegionSize).y);
  420. }
  421. // Iterate over all meshes that will make up the navmesh surface
  422. Int3[] vertexBuffer = null;
  423. for (int meshIndex = -1; meshIndex < navmeshAdds.Count; meshIndex++) {
  424. // Current array of vertices and triangles that are being processed
  425. Int3[] cverts;
  426. int[] ctris;
  427. if (meshIndex == -1) {
  428. cverts = verts;
  429. ctris = tris;
  430. } else {
  431. navmeshAdds[meshIndex].GetMesh(ref vertexBuffer, out ctris, transform);
  432. cverts = vertexBuffer;
  433. }
  434. for (int tri = 0; tri < ctris.Length; tri += 3) {
  435. Int3 tp1 = cverts[ctris[tri + 0]];
  436. Int3 tp2 = cverts[ctris[tri + 1]];
  437. Int3 tp3 = cverts[ctris[tri + 2]];
  438. if (VectorMath.IsColinearXZ(tp1, tp2, tp3)) {
  439. Debug.LogWarning("Skipping degenerate triangle.");
  440. continue;
  441. }
  442. var triBounds = new IntRect(tp1.x, tp1.z, tp1.x, tp1.z);
  443. triBounds = triBounds.ExpandToContain(tp2.x, tp2.z);
  444. triBounds = triBounds.ExpandToContain(tp3.x, tp3.z);
  445. // Upper and lower bound on the Y-axis, the above bounds do not have Y axis information
  446. int tpYMin = Math.Min(tp1.y, Math.Min(tp2.y, tp3.y));
  447. int tpYMax = Math.Max(tp1.y, Math.Max(tp2.y, tp3.y));
  448. intersectingCuts.Clear();
  449. bool hasDual = false;
  450. for (int i = 0; i < cutInfos.Count; i++) {
  451. int ymin = cutInfos[i].boundsY.x;
  452. int ymax = cutInfos[i].boundsY.y;
  453. if (IntRect.Intersects(triBounds, cutInfos[i].bounds) && !(ymax< tpYMin || ymin > tpYMax) && (cutInfos[i].cutsAddedGeom || meshIndex == -1)) {
  454. Int3 p1 = tp1;
  455. p1.y = ymin;
  456. Int3 p2 = tp1;
  457. p2.y = ymax;
  458. intersectingCuts.Add(i);
  459. hasDual |= cutInfos[i].isDual;
  460. }
  461. }
  462. // Check if this is just a simple triangle which no navmesh cuts intersect and
  463. // there are no other special things that should be done
  464. if (intersectingCuts.Count == 0 && (mode & CutMode.CutExtra) == 0 && (mode & CutMode.CutAll) != 0 && meshIndex == -1) {
  465. // Just add the triangle and be done with it
  466. // Refers to vertices to be added a few lines below
  467. outtris.Add(outverts.Count + 0);
  468. outtris.Add(outverts.Count + 1);
  469. outtris.Add(outverts.Count + 2);
  470. outverts.Add(tp1);
  471. outverts.Add(tp2);
  472. outverts.Add(tp3);
  473. continue;
  474. }
  475. // Add current triangle as subject polygon for cutting
  476. poly.Clear();
  477. if (meshIndex == -1) {
  478. // Geometry from a tile mesh is assumed to be completely inside the tile
  479. poly.Add(new IntPoint(tp1.x, tp1.z));
  480. poly.Add(new IntPoint(tp2.x, tp2.z));
  481. poly.Add(new IntPoint(tp3.x, tp3.z));
  482. } else {
  483. // Added geometry must be clipped against the tile bounds
  484. clipIn[0] = tp1;
  485. clipIn[1] = tp2;
  486. clipIn[2] = tp3;
  487. int ct = ClipAgainstRectangle(clipIn, clipOut, clipSize);
  488. // Check if triangle was completely outside the tile
  489. if (ct == 0) {
  490. continue;
  491. }
  492. for (int q = 0; q < ct; q++)
  493. poly.Add(new IntPoint(clipIn[q].x, clipIn[q].z));
  494. }
  495. point2Index.Clear();
  496. // Loop through all possible modes (just 4 at the moment, so < 4 could be used actually)
  497. for (int cmode = 0; cmode < 16; cmode++) {
  498. // Ignore modes which are not active
  499. if ((((int)mode >> cmode) & 0x1) == 0)
  500. continue;
  501. if (1 << cmode == (int)CutMode.CutAll) {
  502. CutAll(poly, intersectingCuts, cutInfos, clipResult);
  503. } else if (1 << cmode == (int)CutMode.CutDual) {
  504. // No duals, don't bother processing this
  505. if (!hasDual)
  506. continue;
  507. CutDual(poly, intersectingCuts, cutInfos, hasDual, intermediateClipResult, clipResult);
  508. } else if (1 << cmode == (int)CutMode.CutExtra) {
  509. CutExtra(poly, extraClipShape, clipResult);
  510. }
  511. for (int exp = 0; exp < clipResult.ChildCount; exp++) {
  512. PolyNode node = clipResult.Childs[exp];
  513. List<IntPoint> outer = node.Contour;
  514. List<PolyNode> holes = node.Childs;
  515. if (holes.Count == 0 && outer.Count == 3 && meshIndex == -1) {
  516. for (int i = 0; i < 3; i++) {
  517. var p = new Int3((int)outer[i].X, 0, (int)outer[i].Y);
  518. p.y = Pathfinding.Polygon.SampleYCoordinateInTriangle(tp1, tp2, tp3, p);
  519. outtris.Add(outverts.Count);
  520. outverts.Add(p);
  521. }
  522. } else {
  523. Poly2Tri.Polygon polygonToTriangulate = null;
  524. // Loop over outer and all holes
  525. int hole = -1;
  526. List<IntPoint> contour = outer;
  527. while (contour != null) {
  528. polypoints.Clear();
  529. for (int i = 0; i < contour.Count; i++) {
  530. // Create a new point
  531. var pp = new PolygonPoint(contour[i].X, contour[i].Y);
  532. // Add the point to the polygon
  533. polypoints.Add(pp);
  534. var p = new Int3((int)contour[i].X, 0, (int)contour[i].Y);
  535. p.y = Pathfinding.Polygon.SampleYCoordinateInTriangle(tp1, tp2, tp3, p);
  536. // Prepare a lookup table for pp -> vertex index
  537. point2Index[pp] = outverts.Count;
  538. // Add to resulting vertex list
  539. outverts.Add(p);
  540. }
  541. Poly2Tri.Polygon contourPolygon = null;
  542. if (polyCache.Count > 0) {
  543. contourPolygon = polyCache.Pop();
  544. contourPolygon.AddPoints(polypoints);
  545. } else {
  546. contourPolygon = new Poly2Tri.Polygon(polypoints);
  547. }
  548. // Since the outer contour is the first to be processed, polygonToTriangle will be null
  549. // Holes are processed later, when polygonToTriangle is not null
  550. if (hole == -1) {
  551. polygonToTriangulate = contourPolygon;
  552. } else {
  553. polygonToTriangulate.AddHole(contourPolygon);
  554. }
  555. hole++;
  556. contour = hole < holes.Count ? holes[hole].Contour : null;
  557. }
  558. // Triangulate the polygon with holes
  559. try {
  560. P2T.Triangulate(polygonToTriangulate);
  561. } catch (Poly2Tri.PointOnEdgeException) {
  562. Debug.LogWarning("PointOnEdgeException, perturbating vertices slightly.\nThis is usually fine. It happens sometimes because of rounding errors. Cutting will be retried a few more times.");
  563. return CutPoly(verts, tris, extraShape, graphTransform, tiles, mode, perturbate + 1);
  564. }
  565. try {
  566. for (int i = 0; i < polygonToTriangulate.Triangles.Count; i++) {
  567. Poly2Tri.DelaunayTriangle t = polygonToTriangulate.Triangles[i];
  568. // Add the triangle with the correct indices (using the previously built lookup table)
  569. outtris.Add(point2Index[t.Points._0]);
  570. outtris.Add(point2Index[t.Points._1]);
  571. outtris.Add(point2Index[t.Points._2]);
  572. }
  573. } catch (System.Collections.Generic.KeyNotFoundException) {
  574. Debug.LogWarning("KeyNotFoundException, perturbating vertices slightly.\nThis is usually fine. It happens sometimes because of rounding errors. Cutting will be retried a few more times.");
  575. return CutPoly(verts, tris, extraShape, graphTransform, tiles, mode, perturbate + 1);
  576. }
  577. PoolPolygon(polygonToTriangulate, polyCache);
  578. }
  579. }
  580. }
  581. }
  582. }
  583. if (vertexBuffer != null) ArrayPool<Int3>.Release(ref vertexBuffer);
  584. StackPool<Poly2Tri.Polygon>.Release(polyCache);
  585. ListPool<List<IntPoint> >.Release(ref intermediateClipResult);
  586. ListPool<IntPoint>.Release(ref poly);
  587. ListPool<Poly2Tri.PolygonPoint>.Release(ref polypoints);
  588. }
  589. // This next step will remove all duplicate vertices in the data (of which there are quite a few)
  590. // and output the final vertex and triangle arrays to the outVertsArr and outTrisArr variables
  591. var result = new CuttingResult();
  592. Pathfinding.Polygon.CompressMesh(outverts, outtris, out result.verts, out result.tris);
  593. // Notify the navmesh cuts that they were used
  594. for (int i = 0; i < navmeshCuts.Count; i++) {
  595. navmeshCuts[i].UsedForCut();
  596. }
  597. // Release back to pools
  598. ListPool<Int3>.Release(ref outverts);
  599. ListPool<int>.Release(ref outtris);
  600. ListPool<int>.Release(ref intersectingCuts);
  601. for (int i = 0; i < cutInfos.Count; i++) {
  602. ListPool<IntPoint>.Release(cutInfos[i].contour);
  603. }
  604. ListPool<Cut>.Release(ref cutInfos);
  605. ListPool<NavmeshCut>.Release(ref navmeshCuts);
  606. return result;
  607. }
  608. /// <summary>
  609. /// Generates a list of cuts from the navmesh cut components.
  610. /// Each cut has a single contour (NavmeshCut components may contain multiple).
  611. ///
  612. /// transform should transform a point from cut space to world space.
  613. /// </summary>
  614. static List<Cut> PrepareNavmeshCutsForCutting (List<NavmeshCut> navmeshCuts, GraphTransform transform, IntRect cutSpaceBounds, int perturbate, bool anyNavmeshAdds) {
  615. System.Random rnd = null;
  616. if (perturbate > 0) {
  617. rnd = new System.Random();
  618. }
  619. var contourBuffer = ListPool<List<Vector3> >.Claim();
  620. var result = ListPool<Cut>.Claim();
  621. for (int i = 0; i < navmeshCuts.Count; i++) {
  622. // Generate random perturbation for this obstacle if required
  623. Int2 perturbation = new Int2(0, 0);
  624. if (perturbate > 0) {
  625. // Create a perturbation vector, choose a point with coordinates in the set [-3*perturbate,3*perturbate]
  626. // makes sure none of the coordinates are zero
  627. perturbation.x = (rnd.Next() % 6*perturbate) - 3*perturbate;
  628. if (perturbation.x >= 0) perturbation.x++;
  629. perturbation.y = (rnd.Next() % 6*perturbate) - 3*perturbate;
  630. if (perturbation.y >= 0) perturbation.y++;
  631. }
  632. contourBuffer.Clear();
  633. navmeshCuts[i].GetContour(contourBuffer);
  634. var y0 = (int)(navmeshCuts[i].GetY(transform) * Int3.FloatPrecision);
  635. for (int j = 0; j < contourBuffer.Count; j++) {
  636. List<Vector3> worldContour = contourBuffer[j];
  637. if (worldContour.Count == 0) {
  638. Debug.LogError("A NavmeshCut component had a zero length contour. Ignoring that contour.");
  639. continue;
  640. }
  641. // TODO: transform should include cutting offset
  642. List<IntPoint> contour = ListPool<IntPoint>.Claim(worldContour.Count);
  643. for (int q = 0; q < worldContour.Count; q++) {
  644. var p = (Int3)transform.InverseTransform(worldContour[q]);
  645. if (perturbate > 0) {
  646. p.x += perturbation.x;
  647. p.z += perturbation.y;
  648. }
  649. contour.Add(new IntPoint(p.x, p.z));
  650. }
  651. IntRect contourBounds = new IntRect((int)contour[0].X, (int)contour[0].Y, (int)contour[0].X, (int)contour[0].Y);
  652. for (int q = 0; q < contour.Count; q++) {
  653. IntPoint p = contour[q];
  654. contourBounds = contourBounds.ExpandToContain((int)p.X, (int)p.Y);
  655. }
  656. Cut cut = new Cut();
  657. // Calculate bounds on the y axis
  658. int halfHeight = (int)(navmeshCuts[i].height * 0.5f * Int3.Precision);
  659. cut.boundsY = new Int2(y0 - halfHeight, y0 + halfHeight);
  660. cut.bounds = contourBounds;
  661. cut.isDual = navmeshCuts[i].isDual;
  662. cut.cutsAddedGeom = navmeshCuts[i].cutsAddedGeom;
  663. cut.contour = contour;
  664. result.Add(cut);
  665. }
  666. }
  667. ListPool<List<Vector3> >.Release(ref contourBuffer);
  668. return result;
  669. }
  670. static void PoolPolygon (Poly2Tri.Polygon polygon, Stack<Poly2Tri.Polygon> pool) {
  671. if (polygon.Holes != null)
  672. for (int i = 0; i < polygon.Holes.Count; i++) {
  673. polygon.Holes[i].Points.Clear();
  674. polygon.Holes[i].ClearTriangles();
  675. if (polygon.Holes[i].Holes != null)
  676. polygon.Holes[i].Holes.Clear();
  677. pool.Push(polygon.Holes[i]);
  678. }
  679. polygon.ClearTriangles();
  680. if (polygon.Holes != null)
  681. polygon.Holes.Clear();
  682. polygon.Points.Clear();
  683. pool.Push(polygon);
  684. }
  685. void CutAll (List<IntPoint> poly, List<int> intersectingCutIndices, List<Cut> cuts, Pathfinding.ClipperLib.PolyTree result) {
  686. clipper.Clear();
  687. clipper.AddPolygon(poly, PolyType.ptSubject);
  688. // Add all holes (cuts) as clip polygons
  689. // TODO: AddPolygon allocates quite a lot, modify ClipperLib to use object pooling
  690. for (int i = 0; i < intersectingCutIndices.Count; i++) {
  691. clipper.AddPolygon(cuts[intersectingCutIndices[i]].contour, PolyType.ptClip);
  692. }
  693. result.Clear();
  694. clipper.Execute(ClipType.ctDifference, result, PolyFillType.pftNonZero, PolyFillType.pftNonZero);
  695. }
  696. void CutDual (List<IntPoint> poly, List<int> tmpIntersectingCuts, List<Cut> cuts, bool hasDual, List<List<IntPoint> > intermediateResult, Pathfinding.ClipperLib.PolyTree result) {
  697. // First calculate
  698. // a = original intersection dualCuts
  699. // then
  700. // b = a difference normalCuts
  701. // then process b as normal
  702. clipper.Clear();
  703. clipper.AddPolygon(poly, PolyType.ptSubject);
  704. // Add all holes (cuts) as clip polygons
  705. for (int i = 0; i < tmpIntersectingCuts.Count; i++) {
  706. if (cuts[tmpIntersectingCuts[i]].isDual) {
  707. clipper.AddPolygon(cuts[tmpIntersectingCuts[i]].contour, PolyType.ptClip);
  708. }
  709. }
  710. clipper.Execute(ClipType.ctIntersection, intermediateResult, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero);
  711. clipper.Clear();
  712. if (intermediateResult != null) {
  713. for (int i = 0; i < intermediateResult.Count; i++) {
  714. clipper.AddPolygon(intermediateResult[i], Pathfinding.ClipperLib.Clipper.Orientation(intermediateResult[i]) ? PolyType.ptClip : PolyType.ptSubject);
  715. }
  716. }
  717. for (int i = 0; i < tmpIntersectingCuts.Count; i++) {
  718. if (!cuts[tmpIntersectingCuts[i]].isDual) {
  719. clipper.AddPolygon(cuts[tmpIntersectingCuts[i]].contour, PolyType.ptClip);
  720. }
  721. }
  722. result.Clear();
  723. clipper.Execute(ClipType.ctDifference, result, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero);
  724. }
  725. void CutExtra (List<IntPoint> poly, List<IntPoint> extraClipShape, Pathfinding.ClipperLib.PolyTree result) {
  726. clipper.Clear();
  727. clipper.AddPolygon(poly, PolyType.ptSubject);
  728. clipper.AddPolygon(extraClipShape, PolyType.ptClip);
  729. result.Clear();
  730. clipper.Execute(ClipType.ctIntersection, result, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero);
  731. }
  732. /// <summary>
  733. /// Clips the input polygon against a rectangle with one corner at the origin and one at size in XZ space.
  734. ///
  735. /// Returns: Number of output vertices
  736. /// </summary>
  737. /// <param name="clipIn">Input vertices</param>
  738. /// <param name="clipOut">Output vertices. This buffer must be large enough to contain all output vertices.</param>
  739. /// <param name="size">The clipping rectangle has one corner at the origin and one at this position in XZ space.</param>
  740. int ClipAgainstRectangle (Int3[] clipIn, Int3[] clipOut, Int2 size) {
  741. int ct;
  742. ct = simpleClipper.ClipPolygon(clipIn, 3, clipOut, 1, 0, 0);
  743. if (ct == 0)
  744. return ct;
  745. ct = simpleClipper.ClipPolygon(clipOut, ct, clipIn, -1, size.x, 0);
  746. if (ct == 0)
  747. return ct;
  748. ct = simpleClipper.ClipPolygon(clipIn, ct, clipOut, 1, 0, 2);
  749. if (ct == 0)
  750. return ct;
  751. ct = simpleClipper.ClipPolygon(clipOut, ct, clipIn, -1, size.y, 2);
  752. return ct;
  753. }
  754. /// <summary>Copy mesh from (vertices, triangles) to (outVertices, outTriangles)</summary>
  755. static void CopyMesh (Int3[] vertices, int[] triangles, List<Int3> outVertices, List<int> outTriangles) {
  756. outTriangles.Capacity = Math.Max(outTriangles.Capacity, triangles.Length);
  757. outVertices.Capacity = Math.Max(outVertices.Capacity, vertices.Length);
  758. for (int i = 0; i < vertices.Length; i++) {
  759. outVertices.Add(vertices[i]);
  760. }
  761. for (int i = 0; i < triangles.Length; i++) {
  762. outTriangles.Add(triangles[i]);
  763. }
  764. }
  765. /// <summary>
  766. /// Refine a mesh using delaunay refinement.
  767. /// Loops through all pairs of neighbouring triangles and check if it would be better to flip the diagonal joining them
  768. /// using the delaunay criteria.
  769. ///
  770. /// Does not require triangles to be clockwise, triangles will be checked for if they are clockwise and made clockwise if not.
  771. /// The resulting mesh will have all triangles clockwise.
  772. ///
  773. /// See: https://en.wikipedia.org/wiki/Delaunay_triangulation
  774. /// </summary>
  775. void DelaunayRefinement (Int3[] verts, int[] tris, ref int tCount, bool delaunay, bool colinear) {
  776. if (tCount % 3 != 0) throw new System.ArgumentException("Triangle array length must be a multiple of 3");
  777. Dictionary<Int2, int> lookup = cached_Int2_int_dict;
  778. lookup.Clear();
  779. for (int i = 0; i < tCount; i += 3) {
  780. if (!VectorMath.IsClockwiseXZ(verts[tris[i]], verts[tris[i+1]], verts[tris[i+2]])) {
  781. int tmp = tris[i];
  782. tris[i] = tris[i+2];
  783. tris[i+2] = tmp;
  784. }
  785. lookup[new Int2(tris[i+0], tris[i+1])] = i+2;
  786. lookup[new Int2(tris[i+1], tris[i+2])] = i+0;
  787. lookup[new Int2(tris[i+2], tris[i+0])] = i+1;
  788. }
  789. for (int i = 0; i < tCount; i += 3) {
  790. for (int j = 0; j < 3; j++) {
  791. int opp;
  792. if (lookup.TryGetValue(new Int2(tris[i+((j+1)%3)], tris[i+((j+0)%3)]), out opp)) {
  793. // The vertex which we are using as the viewpoint
  794. Int3 po = verts[tris[i+((j+2)%3)]];
  795. // Right vertex of the edge
  796. Int3 pr = verts[tris[i+((j+1)%3)]];
  797. // Left vertex of the edge
  798. Int3 pl = verts[tris[i+((j+3)%3)]];
  799. // Opposite vertex (in the other triangle)
  800. Int3 popp = verts[tris[opp]];
  801. po.y = 0;
  802. pr.y = 0;
  803. pl.y = 0;
  804. popp.y = 0;
  805. bool noDelaunay = false;
  806. if (!VectorMath.RightOrColinearXZ(po, pl, popp) || VectorMath.RightXZ(po, pr, popp)) {
  807. if (colinear) {
  808. noDelaunay = true;
  809. } else {
  810. continue;
  811. }
  812. }
  813. if (colinear) {
  814. const int MaxError = 3 * 3;
  815. // Check if op - right shared - opposite in other - is colinear
  816. // and if the edge right-op is not shared and if the edge opposite in other - right shared is not shared
  817. if (VectorMath.SqrDistancePointSegmentApproximate(po, popp, pr) < MaxError &&
  818. !lookup.ContainsKey(new Int2(tris[i+((j+2)%3)], tris[i+((j+1)%3)])) &&
  819. !lookup.ContainsKey(new Int2(tris[i+((j+1)%3)], tris[opp]))) {
  820. tCount -= 3;
  821. int root = (opp/3)*3;
  822. // Move right vertex to the other triangle's opposite
  823. tris[i+((j+1)%3)] = tris[opp];
  824. // Move last triangle to delete
  825. if (root != tCount) {
  826. tris[root+0] = tris[tCount+0];
  827. tris[root+1] = tris[tCount+1];
  828. tris[root+2] = tris[tCount+2];
  829. lookup[new Int2(tris[root+0], tris[root+1])] = root+2;
  830. lookup[new Int2(tris[root+1], tris[root+2])] = root+0;
  831. lookup[new Int2(tris[root+2], tris[root+0])] = root+1;
  832. tris[tCount+0] = 0;
  833. tris[tCount+1] = 0;
  834. tris[tCount+2] = 0;
  835. } else {
  836. tCount += 3;
  837. }
  838. // Since the above mentioned edges are not shared, we don't need to bother updating them
  839. // However some need to be updated
  840. // left - new right (previously opp) should have opposite vertex po
  841. //lookup[new Int2(tris[i+((j+3)%3)],tris[i+((j+1)%3)])] = i+((j+2)%3);
  842. lookup[new Int2(tris[i+0], tris[i+1])] = i+2;
  843. lookup[new Int2(tris[i+1], tris[i+2])] = i+0;
  844. lookup[new Int2(tris[i+2], tris[i+0])] = i+1;
  845. continue;
  846. }
  847. }
  848. if (delaunay && !noDelaunay) {
  849. float beta = Int3.Angle(pr-po, pl-po);
  850. float alpha = Int3.Angle(pr-popp, pl-popp);
  851. if (alpha > (2*Mathf.PI - 2*beta)) {
  852. // Denaunay condition not holding, refine please
  853. tris[i+((j+1)%3)] = tris[opp];
  854. int root = (opp/3)*3;
  855. int off = opp-root;
  856. tris[root+((off-1+3) % 3)] = tris[i+((j+2)%3)];
  857. lookup[new Int2(tris[i+0], tris[i+1])] = i+2;
  858. lookup[new Int2(tris[i+1], tris[i+2])] = i+0;
  859. lookup[new Int2(tris[i+2], tris[i+0])] = i+1;
  860. lookup[new Int2(tris[root+0], tris[root+1])] = root+2;
  861. lookup[new Int2(tris[root+1], tris[root+2])] = root+0;
  862. lookup[new Int2(tris[root+2], tris[root+0])] = root+1;
  863. }
  864. }
  865. }
  866. }
  867. }
  868. }
  869. /// <summary>Clear the tile at the specified tile coordinates</summary>
  870. public void ClearTile (int x, int z) {
  871. if (AstarPath.active == null) return;
  872. if (x < 0 || z < 0 || x >= tileXCount || z >= tileZCount) return;
  873. AstarPath.active.AddWorkItem(new AstarWorkItem((context, force) => {
  874. //Replace the tile using the final vertices and triangles
  875. graph.ReplaceTile(x, z, new Int3[0], new int[0]);
  876. activeTileTypes[x + z*tileXCount] = null;
  877. if (!isBatching) {
  878. // Trigger post update event
  879. // This can trigger for example recalculation of navmesh links
  880. context.SetGraphDirty(graph);
  881. }
  882. return true;
  883. }));
  884. }
  885. /// <summary>Reloads all tiles intersecting with the specified bounds</summary>
  886. public void ReloadInBounds (Bounds bounds) {
  887. ReloadInBounds(graph.GetTouchingTiles(bounds));
  888. }
  889. /// <summary>Reloads all tiles specified by the rectangle</summary>
  890. public void ReloadInBounds (IntRect tiles) {
  891. // Make sure the rect is inside graph bounds
  892. tiles = IntRect.Intersection(tiles, new IntRect(0, 0, tileXCount-1, tileZCount-1));
  893. if (!tiles.IsValid()) return;
  894. for (int z = tiles.ymin; z <= tiles.ymax; z++) {
  895. for (int x = tiles.xmin; x <= tiles.xmax; x++) {
  896. ReloadTile(x, z);
  897. }
  898. }
  899. }
  900. /// <summary>
  901. /// Reload tile at tile coordinate.
  902. /// The last tile loaded at that position will be reloaded (e.g to account for moved NavmeshCut components)
  903. /// </summary>
  904. public void ReloadTile (int x, int z) {
  905. if (x < 0 || z < 0 || x >= tileXCount || z >= tileZCount) return;
  906. int index = x + z*tileXCount;
  907. if (activeTileTypes[index] != null) LoadTile(activeTileTypes[index], x, z, activeTileRotations[index], activeTileOffsets[index]);
  908. }
  909. /// <summary>Load a tile at tile coordinate x, z.</summary>
  910. /// <param name="tile">Tile type to load</param>
  911. /// <param name="x">Tile x coordinate (first tile is at (0,0), second at (1,0) etc.. ).</param>
  912. /// <param name="z">Tile z coordinate.</param>
  913. /// <param name="rotation">Rotate tile by 90 degrees * value.</param>
  914. /// <param name="yoffset">Offset Y coordinates by this amount. In Int3 space, so if you have a world space
  915. /// offset, multiply by Int3.Precision and round to the nearest integer before calling this function.</param>
  916. public void LoadTile (TileType tile, int x, int z, int rotation, int yoffset) {
  917. if (tile == null) throw new ArgumentNullException("tile");
  918. if (AstarPath.active == null) return;
  919. int index = x + z*tileXCount;
  920. rotation = rotation % 4;
  921. // If loaded during this batch with the same settings, skip it
  922. if (isBatching && reloadedInBatch[index] && activeTileOffsets[index] == yoffset && activeTileRotations[index] == rotation && activeTileTypes[index] == tile) {
  923. return;
  924. }
  925. reloadedInBatch[index] |= isBatching;
  926. activeTileOffsets[index] = yoffset;
  927. activeTileRotations[index] = rotation;
  928. activeTileTypes[index] = tile;
  929. // Add a work item
  930. // This will pause pathfinding as soon as possible
  931. // and call the delegate when it is safe to update graphs
  932. AstarPath.active.AddWorkItem(new AstarWorkItem((context, force) => {
  933. // If this was not the correct settings to load with, ignore
  934. if (!(activeTileOffsets[index] == yoffset && activeTileRotations[index] == rotation && activeTileTypes[index] == tile)) return true;
  935. GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate);
  936. Int3[] verts;
  937. int[] tris;
  938. tile.Load(out verts, out tris, rotation, yoffset);
  939. Profiler.BeginSample("Cut Poly");
  940. // Cut the polygon
  941. var cuttingResult = CutPoly(verts, tris, null, graph.transform, new IntRect(x, z, x + tile.Width - 1, z + tile.Depth - 1));
  942. Profiler.EndSample();
  943. Profiler.BeginSample("Delaunay Refinement");
  944. // Refine to tweak bad triangles
  945. var tCount = cuttingResult.tris.Length;
  946. DelaunayRefinement(cuttingResult.verts, cuttingResult.tris, ref tCount, true, false);
  947. Profiler.EndSample();
  948. if (tCount != cuttingResult.tris.Length) cuttingResult.tris = Memory.ShrinkArray(cuttingResult.tris, tCount);
  949. // Rotate the mask correctly
  950. // and update width and depth to match rotation
  951. // (width and depth will swap if rotated 90 or 270 degrees )
  952. int newWidth = rotation % 2 == 0 ? tile.Width : tile.Depth;
  953. int newDepth = rotation % 2 == 0 ? tile.Depth : tile.Width;
  954. if (newWidth != 1 || newDepth != 1) throw new System.Exception("Only tiles of width = depth = 1 are supported at this time");
  955. Profiler.BeginSample("ReplaceTile");
  956. // Replace the tile using the final vertices and triangles
  957. // The vertices are still in local space
  958. graph.ReplaceTile(x, z, cuttingResult.verts, cuttingResult.tris);
  959. Profiler.EndSample();
  960. if (!isBatching) {
  961. // Trigger post update event
  962. // This can trigger for example recalculation of navmesh links
  963. // TODO: Does this need to be inside an if statement?
  964. context.SetGraphDirty(graph);
  965. }
  966. return true;
  967. }));
  968. }
  969. }
  970. }