UnityHelper.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. using System;
  2. using System.Collections.Generic;
  3. using Pathfinding.Recast;
  4. using Pathfinding.Util;
  5. using Pathfinding.Voxels;
  6. using PF;
  7. using UnityEngine;
  8. using UnityEngine.Profiling;
  9. using Mathf = UnityEngine.Mathf;
  10. using Matrix4x4 = UnityEngine.Matrix4x4;
  11. using Vector3 = UnityEngine.Vector3;
  12. namespace Pathfinding
  13. {
  14. public static class UnityHelper
  15. {
  16. /** Called for each graph before they are scanned */
  17. public static OnGraphDelegate OnGraphPreScan;
  18. /** Called for each graph after they have been scanned. All other graphs might not have been scanned yet. */
  19. public static OnGraphDelegate OnGraphPostScan;
  20. /** Called before starting the scanning */
  21. public static OnScanDelegate OnPreScan;
  22. /** Called after scanning. This is called before applying links, flood-filling the graphs and other post processing. */
  23. public static OnScanDelegate OnPostScan;
  24. /** Called after scanning has completed fully. This is called as the last thing in the Scan function. */
  25. public static OnScanDelegate OnLatePostScan;
  26. /** Called when any graphs are updated. Register to for example recalculate the path whenever a graph changes. */
  27. public static OnScanDelegate OnGraphsUpdated;
  28. public static void Close()
  29. {
  30. OnGraphPreScan = null;
  31. OnGraphPostScan = null;
  32. OnPreScan = null;
  33. OnPostScan = null;
  34. OnLatePostScan = null;
  35. OnGraphsUpdated = null;
  36. }
  37. public static void OnDrawGizmos (this EuclideanEmbedding embedding) {
  38. if (embedding.pivots != null) {
  39. for (int i = 0; i < embedding.pivots.Length; i++) {
  40. Gizmos.color = new Color(159/255.0f, 94/255.0f, 194/255.0f, 0.8f);
  41. if (embedding.pivots[i] != null && !embedding.pivots[i].Destroyed) {
  42. Gizmos.DrawCube((Vector3)embedding.pivots[i].position, Vector3.one);
  43. }
  44. }
  45. }
  46. }
  47. /** Returns the graph which contains the specified node.
  48. * The graph must be in the #graphs array.
  49. *
  50. * \returns Returns the graph which contains the node. Null if the graph wasn't found
  51. */
  52. public static NavGraph GetGraph (GraphNode node) {
  53. if (node.Destroyed)
  54. {
  55. return null;
  56. }
  57. if (node == null) return null;
  58. AstarPath script = AstarPath.active;
  59. if (script == null) return null;
  60. AstarData data = script.data;
  61. if (data == null || data.graphs == null) return null;
  62. uint graphIndex = node.GraphIndex;
  63. if (graphIndex >= data.graphs.Length) {
  64. return null;
  65. }
  66. return data.graphs[(int)graphIndex];
  67. }
  68. /** Returns a bounds object with the bounding box of a group of tiles.
  69. * The bounding box is defined in world space.
  70. */
  71. public static Bounds GetTileBounds (this NavmeshBase navmeshBase, IntRect rect) {
  72. return navmeshBase.GetTileBounds(rect.xmin, rect.ymin, rect.Width, rect.Height);
  73. }
  74. /** Returns a bounds object with the bounding box of a group of tiles.
  75. * The bounding box is defined in world space.
  76. */
  77. public static Bounds GetTileBounds (this NavmeshBase navmeshBase, int x, int z, int width = 1, int depth = 1) {
  78. return navmeshBase.transform.Transform(navmeshBase.GetTileBoundsInGraphSpace(x, z, width, depth));
  79. }
  80. public static Bounds GetTileBoundsInGraphSpace (this NavmeshBase navmeshBase, IntRect rect) {
  81. return navmeshBase.GetTileBoundsInGraphSpace(rect.xmin, rect.ymin, rect.Width, rect.Height);
  82. }
  83. /** Returns an XZ bounds object with the bounds of a group of tiles in graph space */
  84. public static Bounds GetTileBoundsInGraphSpace (this NavmeshBase navmeshBase, int x, int z, int width = 1, int depth = 1) {
  85. var b = new Bounds();
  86. b.SetMinMax(
  87. new Vector3(x*navmeshBase.TileWorldSizeX, 0, z*navmeshBase.TileWorldSizeZ),
  88. new Vector3((x+width)*navmeshBase.TileWorldSizeX, navmeshBase.forcedBoundsSize.y, (z+depth)*navmeshBase.TileWorldSizeZ)
  89. );
  90. return b;
  91. }
  92. public static void OnDrawGizmos (this NavmeshBase navmeshBase, Pathfinding.Util.RetainedGizmos gizmos, bool drawNodes) {
  93. if (!drawNodes) {
  94. return;
  95. }
  96. using (var helper = gizmos.GetSingleFrameGizmoHelper()) {
  97. var bounds = new Bounds();
  98. bounds.SetMinMax(Vector3.zero, navmeshBase.forcedBoundsSize);
  99. // Draw a write cube using the latest transform
  100. // (this makes the bounds update immediately if some field is changed in the editor)
  101. helper.builder.DrawWireCube(navmeshBase.CalculateTransform(), bounds, Color.white);
  102. }
  103. if (navmeshBase.tiles != null) {
  104. // Update navmesh vizualizations for
  105. // the tiles that have been changed
  106. for (int i = 0; i < navmeshBase.tiles.Length; i++) {
  107. // This may happen if an exception has been thrown when the graph was scanned.
  108. // We don't want the gizmo code to start to throw exceptions as well then as
  109. // that would obscure the actual source of the error.
  110. if (navmeshBase.tiles[i] == null) continue;
  111. // Calculate a hash of the tile
  112. var hasher = new RetainedGizmos.Hasher(AstarPath.active);
  113. hasher.AddHash(navmeshBase.showMeshOutline ? 1 : 0);
  114. hasher.AddHash(navmeshBase.showMeshSurface ? 1 : 0);
  115. hasher.AddHash(navmeshBase.showNodeConnections ? 1 : 0);
  116. var nodes = navmeshBase.tiles[i].nodes;
  117. for (int j = 0; j < nodes.Length; j++) {
  118. hasher.HashNode(nodes[j]);
  119. }
  120. if (!gizmos.Draw(hasher)) {
  121. using (var helper = gizmos.GetGizmoHelper(hasher)) {
  122. if (navmeshBase.showMeshSurface || navmeshBase.showMeshOutline)
  123. navmeshBase.CreateNavmeshSurfaceVisualization(navmeshBase.tiles[i], helper);
  124. if (navmeshBase.showMeshSurface || navmeshBase.showMeshOutline)
  125. CreateNavmeshOutlineVisualization(navmeshBase.tiles[i], helper);
  126. if (navmeshBase.showNodeConnections) {
  127. for (int j = 0; j < nodes.Length; j++) {
  128. helper.DrawConnections(nodes[j]);
  129. }
  130. }
  131. }
  132. }
  133. gizmos.Draw(hasher);
  134. }
  135. }
  136. if (AstarPath.active.showUnwalkableNodes)
  137. navmeshBase.DrawUnwalkableNodes(AstarPath.active.unwalkableNodeDebugSize);
  138. }
  139. /** Creates a mesh of the surfaces of the navmesh for use in OnDrawGizmos in the editor */
  140. public static void CreateNavmeshSurfaceVisualization (this NavmeshBase navmeshBase, NavmeshTile tile, GraphGizmoHelper helper) {
  141. // Vertex array might be a bit larger than necessary, but that's ok
  142. var vertices = ArrayPool<Vector3>.Claim(tile.nodes.Length*3);
  143. var colors = ArrayPool<Color>.Claim(tile.nodes.Length*3);
  144. for (int j = 0; j < tile.nodes.Length; j++) {
  145. var node = tile.nodes[j];
  146. Int3 v0, v1, v2;
  147. node.GetVertices(out v0, out v1, out v2);
  148. vertices[j*3 + 0] = (Vector3)v0;
  149. vertices[j*3 + 1] = (Vector3)v1;
  150. vertices[j*3 + 2] = (Vector3)v2;
  151. var color = helper.NodeColor(node);
  152. colors[j*3 + 0] = colors[j*3 + 1] = colors[j*3 + 2] = color;
  153. }
  154. if (navmeshBase.showMeshSurface) helper.DrawTriangles(vertices, colors, tile.nodes.Length);
  155. if (navmeshBase.showMeshOutline) helper.DrawWireTriangles(vertices, colors, tile.nodes.Length);
  156. // Return lists to the pool
  157. ArrayPool<Vector3>.Release(ref vertices);
  158. ArrayPool<Color>.Release(ref colors);
  159. }
  160. /** Creates an outline of the navmesh for use in OnDrawGizmos in the editor */
  161. public static void CreateNavmeshOutlineVisualization (NavmeshTile tile, GraphGizmoHelper helper) {
  162. var sharedEdges = new bool[3];
  163. for (int j = 0; j < tile.nodes.Length; j++) {
  164. sharedEdges[0] = sharedEdges[1] = sharedEdges[2] = false;
  165. var node = tile.nodes[j];
  166. for (int c = 0; c < node.connections.Length; c++) {
  167. var other = node.connections[c].node as TriangleMeshNode;
  168. // Loop through neighbours to figure out which edges are shared
  169. if (other != null && other.GraphIndex == node.GraphIndex) {
  170. for (int v = 0; v < 3; v++) {
  171. for (int v2 = 0; v2 < 3; v2++) {
  172. if (node.GetVertexIndex(v) == other.GetVertexIndex((v2+1)%3) && node.GetVertexIndex((v+1)%3) == other.GetVertexIndex(v2)) {
  173. // Found a shared edge with the other node
  174. sharedEdges[v] = true;
  175. v = 3;
  176. break;
  177. }
  178. }
  179. }
  180. }
  181. }
  182. var color = helper.NodeColor(node);
  183. for (int v = 0; v < 3; v++) {
  184. if (!sharedEdges[v]) {
  185. helper.builder.DrawLine((Vector3)node.GetVertex(v), (Vector3)node.GetVertex((v+1)%3), color);
  186. }
  187. }
  188. }
  189. }
  190. public static void DrawUnwalkableNodes (this NavmeshBase navmeshBase, float size) {
  191. Gizmos.color = AstarColor.UnwalkableNode;
  192. navmeshBase.GetNodes(node => {
  193. if (!node.Walkable) Gizmos.DrawCube((Vector3)node.position, Vector3.one*size);
  194. });
  195. }
  196. public static IEnumerable<Progress> ScanAllTiles (this RecastGraph self) {
  197. self.transform = self.CalculateTransform();
  198. self.InitializeTileInfo();
  199. // If this is true, just fill the graph with empty tiles
  200. if (self.scanEmptyGraph) {
  201. self.FillWithEmptyTiles();
  202. yield break;
  203. }
  204. // A walkableClimb higher than walkableHeight can cause issues when generating the navmesh since then it can in some cases
  205. // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with navmesh without links)
  206. // The editor scripts also enforce this but we enforce it here too just to be sure
  207. self.walkableClimb = Mathf.Min(self.walkableClimb, self.walkableHeight);
  208. yield return new Progress(0, "Finding Meshes");
  209. var bounds = self.transform.Transform(new Bounds(self.forcedBoundsSize*0.5f, self.forcedBoundsSize));
  210. var meshes = self.CollectMeshes(bounds);
  211. var buckets = self.PutMeshesIntoTileBuckets(meshes);
  212. Queue<Int2> tileQueue = new Queue<Int2>();
  213. // Put all tiles in the queue
  214. for (int z = 0; z < self.tileZCount; z++) {
  215. for (int x = 0; x < self.tileXCount; x++) {
  216. tileQueue.Enqueue(new Int2(x, z));
  217. }
  218. }
  219. var workQueue = new ParallelWorkQueue<Int2>(tileQueue);
  220. // Create the voxelizers and set all settings (one for each thread)
  221. var voxelizers = new Voxelize[workQueue.threadCount];
  222. for (int i = 0; i < voxelizers.Length; i++) voxelizers[i] = new Voxelize(self.CellHeight, self.cellSize, self.walkableClimb, self.walkableHeight, self.maxSlope, self.maxEdgeLength);
  223. workQueue.action = (tile, threadIndex) => {
  224. voxelizers[threadIndex].inputMeshes = buckets[tile.x + tile.y*self.tileXCount];
  225. self.tiles[tile.x + tile.y*self.tileXCount] = self.BuildTileMesh(voxelizers[threadIndex], tile.x, tile.y, threadIndex);
  226. };
  227. // Prioritize responsiveness while playing
  228. // but when not playing prioritize throughput
  229. // (the Unity progress bar is also pretty slow to update)
  230. int timeoutMillis = Application.isPlaying ? 1 : 200;
  231. // Scan all tiles in parallel
  232. foreach (var done in workQueue.Run(timeoutMillis)) {
  233. yield return new Progress(Mathf.Lerp(0.1f, 0.9f, done / (float)self.tiles.Length), "Calculated Tiles: " + done + "/" + self.tiles.Length);
  234. }
  235. yield return new Progress(0.9f, "Assigning Graph Indices");
  236. // Assign graph index to nodes
  237. uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(self);
  238. self.GetNodes(node => node.GraphIndex = graphIndex);
  239. // First connect all tiles with an EVEN coordinate sum
  240. // This would be the white squares on a chess board.
  241. // Then connect all tiles with an ODD coordinate sum (which would be all black squares on a chess board).
  242. // This will prevent the different threads that do all
  243. // this in parallel from conflicting with each other.
  244. // The directions are also done separately
  245. // first they are connected along the X direction and then along the Z direction.
  246. // Looping over 0 and then 1
  247. for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) {
  248. for (int direction = 0; direction <= 1; direction++) {
  249. for (int i = 0; i < self.tiles.Length; i++) {
  250. if ((self.tiles[i].x + self.tiles[i].z) % 2 == coordinateSum) {
  251. tileQueue.Enqueue(new Int2(self.tiles[i].x, self.tiles[i].z));
  252. }
  253. }
  254. workQueue = new ParallelWorkQueue<Int2>(tileQueue);
  255. workQueue.action = (tile, threadIndex) => {
  256. // Connect with tile at (x+1,z) and (x,z+1)
  257. if (direction == 0 && tile.x < self.tileXCount - 1)
  258. self.ConnectTiles(self.tiles[tile.x + tile.y * self.tileXCount], self.tiles[tile.x + 1 + tile.y * self.tileXCount]);
  259. if (direction == 1 && tile.y < self.tileZCount - 1)
  260. self.ConnectTiles(self.tiles[tile.x + tile.y * self.tileXCount], self.tiles[tile.x + (tile.y + 1) * self.tileXCount]);
  261. };
  262. var numTilesInQueue = tileQueue.Count;
  263. // Connect all tiles in parallel
  264. foreach (var done in workQueue.Run(timeoutMillis)) {
  265. yield return new Progress(0.95f, "Connected Tiles " + (numTilesInQueue - done) + "/" + numTilesInQueue + " (Phase " + (direction + 1 + 2*coordinateSum) + " of 4)");
  266. }
  267. }
  268. }
  269. for (int i = 0; i < meshes.Count; i++) meshes[i].Pool();
  270. ListPool<RasterizationMesh>.Release(ref meshes);
  271. // This may be used by the TileHandlerHelper script to update the tiles
  272. // while taking NavmeshCuts into account after the graph has been completely recalculated.
  273. if (self.OnRecalculatedTiles != null) {
  274. self.OnRecalculatedTiles(self.tiles.Clone() as NavmeshTile[]);
  275. }
  276. }
  277. /** Creates a list for every tile and adds every mesh that touches a tile to the corresponding list */
  278. public static List<RasterizationMesh>[] PutMeshesIntoTileBuckets (this RecastGraph self, List<RasterizationMesh> meshes) {
  279. var result = new List<RasterizationMesh>[self.tiles.Length];
  280. var borderExpansion = new Vector3(1, 0, 1)*self.TileBorderSizeInWorldUnits*2;
  281. for (int i = 0; i < result.Length; i++) {
  282. result[i] = ListPool<RasterizationMesh>.Claim();
  283. }
  284. for (int i = 0; i < meshes.Count; i++) {
  285. var mesh = meshes[i];
  286. var bounds = mesh.bounds;
  287. // Expand borderSize voxels on each side
  288. bounds.Expand(borderExpansion);
  289. var rect = self.GetTouchingTiles(bounds);
  290. for (int z = rect.ymin; z <= rect.ymax; z++) {
  291. for (int x = rect.xmin; x <= rect.xmax; x++) {
  292. result[x + z*self.tileXCount].Add(mesh);
  293. }
  294. }
  295. }
  296. return result;
  297. }
  298. public static List<RasterizationMesh> CollectMeshes (this RecastGraph self, Bounds bounds) {
  299. Profiler.BeginSample("Find Meshes for rasterization");
  300. var result = ListPool<RasterizationMesh>.Claim();
  301. var meshGatherer = new RecastMeshGatherer(bounds, self.terrainSampleSize, self.mask, self.tagMask, self.colliderRasterizeDetail);
  302. if (self.rasterizeMeshes) {
  303. Profiler.BeginSample("Find meshes");
  304. meshGatherer.CollectSceneMeshes(result);
  305. Profiler.EndSample();
  306. }
  307. Profiler.BeginSample("Find RecastMeshObj components");
  308. meshGatherer.CollectRecastMeshObjs(result);
  309. Profiler.EndSample();
  310. if (self.rasterizeTerrain) {
  311. Profiler.BeginSample("Find terrains");
  312. // Split terrains up into meshes approximately the size of a single chunk
  313. var desiredTerrainChunkSize = self.cellSize*Math.Max(self.tileSizeX, self.tileSizeZ);
  314. meshGatherer.CollectTerrainMeshes(self.rasterizeTrees, desiredTerrainChunkSize, result);
  315. Profiler.EndSample();
  316. }
  317. if (self.rasterizeColliders) {
  318. Profiler.BeginSample("Find colliders");
  319. meshGatherer.CollectColliderMeshes(result);
  320. Profiler.EndSample();
  321. }
  322. if (result.Count == 0) {
  323. Debug.LogWarning("No MeshFilters were found contained in the layers specified by the 'mask' variables");
  324. }
  325. Profiler.EndSample();
  326. return result;
  327. }
  328. public static Bounds CalculateTileBoundsWithBorder (this RecastGraph self, int x, int z) {
  329. var bounds = new Bounds();
  330. bounds.SetMinMax(new Vector3(x*self.TileWorldSizeX, 0, z*self.TileWorldSizeZ),
  331. new Vector3((x+1)*self.TileWorldSizeX, self.forcedBoundsSize.y, (z+1)*self.TileWorldSizeZ)
  332. );
  333. // Expand borderSize voxels on each side
  334. bounds.Expand(new Vector3(1, 0, 1)*self.TileBorderSizeInWorldUnits*2);
  335. return bounds;
  336. }
  337. public static NavmeshTile BuildTileMesh (this RecastGraph self, Voxelize vox, int x, int z, int threadIndex = 0) {
  338. AstarProfiler.StartProfile("Build Tile");
  339. AstarProfiler.StartProfile("Init");
  340. vox.borderSize = self.TileBorderSizeInVoxels;
  341. vox.forcedBounds = self.CalculateTileBoundsWithBorder(x, z);
  342. vox.width = self.tileSizeX + vox.borderSize*2;
  343. vox.depth = self.tileSizeZ + vox.borderSize*2;
  344. if (!self.useTiles && self.relevantGraphSurfaceMode == RecastGraph.RelevantGraphSurfaceMode.OnlyForCompletelyInsideTile) {
  345. // This best reflects what the user would actually want
  346. vox.relevantGraphSurfaceMode = RecastGraph.RelevantGraphSurfaceMode.RequireForAll;
  347. } else {
  348. vox.relevantGraphSurfaceMode = self.relevantGraphSurfaceMode;
  349. }
  350. vox.minRegionSize = Mathf.RoundToInt(self.minRegionSize / (self.cellSize*self.cellSize));
  351. AstarProfiler.EndProfile("Init");
  352. // Init voxelizer
  353. vox.Init();
  354. vox.VoxelizeInput(self.transform, self.CalculateTileBoundsWithBorder(x, z));
  355. AstarProfiler.StartProfile("Filter Ledges");
  356. vox.FilterLedges(vox.voxelWalkableHeight, vox.voxelWalkableClimb, vox.cellSize, vox.cellHeight);
  357. AstarProfiler.EndProfile("Filter Ledges");
  358. AstarProfiler.StartProfile("Filter Low Height Spans");
  359. vox.FilterLowHeightSpans(vox.voxelWalkableHeight, vox.cellSize, vox.cellHeight);
  360. AstarProfiler.EndProfile("Filter Low Height Spans");
  361. vox.BuildCompactField();
  362. vox.BuildVoxelConnections();
  363. vox.ErodeWalkableArea(self.CharacterRadiusInVoxels);
  364. vox.BuildDistanceField();
  365. vox.BuildRegions();
  366. var cset = new VoxelContourSet();
  367. vox.BuildContours(self.contourMaxError, 1, cset, Voxelize.RC_CONTOUR_TESS_WALL_EDGES | Voxelize.RC_CONTOUR_TESS_TILE_EDGES);
  368. VoxelMesh mesh;
  369. vox.BuildPolyMesh(cset, 3, out mesh);
  370. AstarProfiler.StartProfile("Build Nodes");
  371. // Position the vertices correctly in graph space (all tiles are laid out on the xz plane with the (0,0) tile at the origin)
  372. for (int i = 0; i < mesh.verts.Length; i++) {
  373. mesh.verts[i] *= Int3.Precision;
  374. }
  375. vox.transformVoxel2Graph.Transform(mesh.verts);
  376. NavmeshTile tile = self.CreateTile(vox, mesh, x, z, threadIndex);
  377. AstarProfiler.EndProfile("Build Nodes");
  378. AstarProfiler.EndProfile("Build Tile");
  379. return tile;
  380. }
  381. /** Create a tile at tile index \a x, \a z from the mesh.
  382. * \version Since version 3.7.6 the implementation is thread safe
  383. */
  384. public static NavmeshTile CreateTile (this RecastGraph self, Voxelize vox, VoxelMesh mesh, int x, int z, int threadIndex) {
  385. if (mesh.tris == null) throw new System.ArgumentNullException("mesh.tris");
  386. if (mesh.verts == null) throw new System.ArgumentNullException("mesh.verts");
  387. if (mesh.tris.Length % 3 != 0) throw new System.ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)");
  388. if (mesh.verts.Length >= NavmeshBase.VertexIndexMask) {
  389. if (self.tileXCount*self.tileZCount == 1) {
  390. throw new System.ArgumentException("Too many vertices per tile (more than " + NavmeshBase.VertexIndexMask + ")." +
  391. "\n<b>Try enabling tiling in the recast graph settings.</b>\n");
  392. } else {
  393. throw new System.ArgumentException("Too many vertices per tile (more than " + NavmeshBase.VertexIndexMask + ")." +
  394. "\n<b>Try reducing tile size or enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector</b>");
  395. }
  396. }
  397. // Create a new navmesh tile and assign its settings
  398. var tile = new NavmeshTile {
  399. x = x,
  400. z = z,
  401. w = 1,
  402. d = 1,
  403. tris = mesh.tris,
  404. bbTree = new BBTree(),
  405. graph = self,
  406. };
  407. tile.vertsInGraphSpace = Utility.RemoveDuplicateVertices(mesh.verts, tile.tris);
  408. tile.verts = (Int3[])tile.vertsInGraphSpace.Clone();
  409. self.transform.Transform(tile.verts);
  410. // Here we are faking a new graph
  411. // The tile is not added to any graphs yet, but to get the position queries from the nodes
  412. // to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph
  413. // and direct the position queries directly to the tile
  414. // The thread index is added to make sure that if multiple threads are calculating tiles at the same time
  415. // they will not use the same temporary graph index
  416. uint temporaryGraphIndex = (uint)(AstarPath.active.data.graphs.Length + threadIndex);
  417. if (temporaryGraphIndex > GraphNode.MaxGraphIndex) {
  418. // Multithreaded tile calculations use fake graph indices, see above.
  419. throw new System.Exception("Graph limit reached. Multithreaded recast calculations cannot be done because a few scratch graph indices are required.");
  420. }
  421. TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, tile);
  422. // We need to lock here because creating nodes is not thread safe
  423. // and we may be doing this from multiple threads at the same time
  424. tile.nodes = new TriangleMeshNode[tile.tris.Length/3];
  425. lock (AstarPath.active) {
  426. self.CreateNodes(tile.nodes, tile.tris, x + z*self.tileXCount, temporaryGraphIndex);
  427. }
  428. tile.bbTree.RebuildFrom(tile.nodes);
  429. NavmeshBase.CreateNodeConnections(tile.nodes);
  430. // Remove the fake graph
  431. TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, null);
  432. return tile;
  433. }
  434. /** Changes the bounds of the graph to precisely encapsulate all objects in the scene that can be included in the scanning process based on the settings.
  435. * Which objects are used depends on the settings. If an object would have affected the graph with the current settings if it would have
  436. * been inside the bounds of the graph, it will be detected and the bounds will be expanded to contain that object.
  437. *
  438. * This method corresponds to the 'Snap bounds to scene' button in the inspector.
  439. *
  440. * \see rasterizeMeshes
  441. * \see rasterizeTerrain
  442. * \see rasterizeColliders
  443. * \see mask
  444. * \see tagMask
  445. *
  446. * \see forcedBoundsCenter
  447. * \see forcedBoundsSize
  448. */
  449. public static void SnapForceBoundsToScene (this RecastGraph self) {
  450. var meshes = self.CollectMeshes(new Bounds(Vector3.zero, new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity)));
  451. if (meshes.Count == 0) {
  452. return;
  453. }
  454. var bounds = meshes[0].bounds;
  455. for (int i = 1; i < meshes.Count; i++) {
  456. bounds.Encapsulate(meshes[i].bounds);
  457. meshes[i].Pool();
  458. }
  459. self.forcedBoundsCenter = bounds.center;
  460. self.forcedBoundsSize = bounds.size;
  461. }
  462. public static Bounds Transform (this GraphTransform self, Bounds bounds) {
  463. if (self.onlyTranslational) return new Bounds(bounds.center + self.translation.ToUnityV3(), bounds.size);
  464. var corners = ArrayPool<Vector3>.Claim(8);
  465. var extents = bounds.extents;
  466. corners[0] = self.Transform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
  467. corners[1] = self.Transform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
  468. corners[2] = self.Transform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
  469. corners[3] = self.Transform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
  470. corners[4] = self.Transform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
  471. corners[5] = self.Transform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
  472. corners[6] = self.Transform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
  473. corners[7] = self.Transform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
  474. var min = corners[0];
  475. var max = corners[0];
  476. for (int i = 1; i < 8; i++) {
  477. min = Vector3.Min(min, corners[i]);
  478. max = Vector3.Max(max, corners[i]);
  479. }
  480. ArrayPool<Vector3>.Release(ref corners);
  481. return new Bounds((min+max)*0.5f, max - min);
  482. }
  483. public static Bounds InverseTransform (this GraphTransform self, Bounds bounds) {
  484. if (self.onlyTranslational) return new Bounds(bounds.center - self.translation.ToUnityV3(), bounds.size);
  485. var corners = ArrayPool<Vector3>.Claim(8);
  486. var extents = bounds.extents;
  487. corners[0] = self.InverseTransform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
  488. corners[1] = self.InverseTransform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
  489. corners[2] = self.InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
  490. corners[3] = self.InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
  491. corners[4] = self.InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
  492. corners[5] = self.InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
  493. corners[6] = self.InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
  494. corners[7] = self.InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
  495. var min = corners[0];
  496. var max = corners[0];
  497. for (int i = 1; i < 8; i++) {
  498. min = Vector3.Min(min, corners[i]);
  499. max = Vector3.Max(max, corners[i]);
  500. }
  501. ArrayPool<Vector3>.Release(ref corners);
  502. return new Bounds((min+max)*0.5f, max - min);
  503. }
  504. /** Returns a rect containing the indices of all tiles touching the specified bounds */
  505. public static IntRect GetTouchingTiles (this NavmeshBase self, Bounds bounds) {
  506. bounds = self.transform.InverseTransform(bounds);
  507. // Calculate world bounds of all affected tiles
  508. var r = new IntRect(Mathf.FloorToInt(bounds.min.x / self.TileWorldSizeX), Mathf.FloorToInt(bounds.min.z / self.TileWorldSizeZ), Mathf.FloorToInt(bounds.max.x / self.TileWorldSizeX), Mathf.FloorToInt(bounds.max.z / self.TileWorldSizeZ));
  509. // Clamp to bounds
  510. r = IntRect.Intersection(r, new IntRect(0, 0, self.tileXCount-1, self.tileZCount-1));
  511. return r;
  512. }
  513. /** Returns a rect containing the indices of all tiles touching the specified bounds.
  514. * \param 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)
  515. */
  516. public static IntRect GetTouchingTilesInGraphSpace (this NavmeshBase self, Rect rect) {
  517. // Calculate world bounds of all affected tiles
  518. var r = new IntRect(Mathf.FloorToInt(rect.xMin / self.TileWorldSizeX), Mathf.FloorToInt(rect.yMin / self.TileWorldSizeZ), Mathf.FloorToInt(rect.xMax / self.TileWorldSizeX), Mathf.FloorToInt(rect.yMax / self.TileWorldSizeZ));
  519. // Clamp to bounds
  520. r = IntRect.Intersection(r, new IntRect(0, 0, self.tileXCount-1, self.tileZCount-1));
  521. return r;
  522. }
  523. /** True if the matrix will reverse orientations of faces.
  524. *
  525. * Scaling by a negative value along an odd number of axes will reverse
  526. * the orientation of e.g faces on a mesh. This must be counter adjusted
  527. * by for example the recast rasterization system to be able to handle
  528. * meshes with negative scales properly.
  529. *
  530. * We can find out if they are flipped by finding out how the signed
  531. * volume of a unit cube is transformed when applying the matrix
  532. *
  533. * If the (signed) volume turns out to be negative
  534. * that also means that the orientation of it has been reversed.
  535. *
  536. * \see https://en.wikipedia.org/wiki/Normal_(geometry)
  537. * \see https://en.wikipedia.org/wiki/Parallelepiped
  538. */
  539. public static bool ReversesFaceOrientations (Matrix4x4 matrix) {
  540. var dX = matrix.MultiplyVector(new Vector3(1, 0, 0));
  541. var dY = matrix.MultiplyVector(new Vector3(0, 1, 0));
  542. var dZ = matrix.MultiplyVector(new Vector3(0, 0, 1));
  543. // Calculate the signed volume of the parallelepiped
  544. var volume = Vector3.Dot(Vector3.Cross(dX, dY), dZ);
  545. return volume < 0;
  546. }
  547. /** True if the matrix will reverse orientations of faces in the XZ plane.
  548. * Almost the same as ReversesFaceOrientations, but this method assumes
  549. * that scaling a face with a negative scale along the Y axis does not
  550. * reverse the orientation of the face.
  551. *
  552. * This is used for navmesh cuts.
  553. *
  554. * Scaling by a negative value along one axis or rotating
  555. * it so that it is upside down will reverse
  556. * the orientation of the cut, so we need to be reverse
  557. * it again as a countermeasure.
  558. * However if it is flipped along two axes it does not need to
  559. * be reversed.
  560. * We can handle all these cases by finding out how a unit square formed
  561. * by our forward axis and our rightward axis is transformed in XZ space
  562. * when applying the local to world matrix.
  563. * If the (signed) area of the unit square turns out to be negative
  564. * that also means that the orientation of it has been reversed.
  565. * The signed area is calculated using a cross product of the vectors.
  566. */
  567. public static bool ReversesFaceOrientationsXZ (Matrix4x4 matrix) {
  568. var dX = matrix.MultiplyVector(new Vector3(1, 0, 0));
  569. var dZ = matrix.MultiplyVector(new Vector3(0, 0, 1));
  570. // Take the cross product of the vectors projected onto the XZ plane
  571. var cross = (dX.x*dZ.z - dZ.x*dX.z);
  572. return cross < 0;
  573. }
  574. static int Bit (int a, int b) {
  575. return (a >> b) & 1;
  576. }
  577. public static Color IntToColor (int i, float a) {
  578. int r = Bit(i, 2) + Bit(i, 3) * 2 + 1;
  579. int g = Bit(i, 1) + Bit(i, 4) * 2 + 1;
  580. int b = Bit(i, 0) + Bit(i, 5) * 2 + 1;
  581. return new Color(r*0.25F, g*0.25F, b*0.25F, a);
  582. }
  583. /**
  584. * Converts an HSV color to an RGB color.
  585. * According to the algorithm described at http://en.wikipedia.org/wiki/HSL_and_HSV
  586. *
  587. * @author Wikipedia
  588. * @return the RGB representation of the color.
  589. */
  590. public static Color HSVToRGB (float h, float s, float v) {
  591. float r = 0, g = 0, b = 0;
  592. float Chroma = s * v;
  593. float Hdash = h / 60.0f;
  594. float X = Chroma * (1.0f - System.Math.Abs((Hdash % 2.0f) - 1.0f));
  595. if (Hdash < 1.0f) {
  596. r = Chroma;
  597. g = X;
  598. } else if (Hdash < 2.0f) {
  599. r = X;
  600. g = Chroma;
  601. } else if (Hdash < 3.0f) {
  602. g = Chroma;
  603. b = X;
  604. } else if (Hdash < 4.0f) {
  605. g = X;
  606. b = Chroma;
  607. } else if (Hdash < 5.0f) {
  608. r = X;
  609. b = Chroma;
  610. } else if (Hdash < 6.0f) {
  611. r = Chroma;
  612. b = X;
  613. }
  614. float Min = v - Chroma;
  615. r += Min;
  616. g += Min;
  617. b += Min;
  618. return new Color(r, g, b);
  619. }
  620. }
  621. }