NavmeshBase.cs 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079
  1. using System.Collections.Generic;
  2. namespace PF {
  3. using System.IO;
  4. using Math = System.Math;
  5. using System.Linq;
  6. /** Returned by graph ray- or linecasts containing info about the hit.
  7. * This is the return value by the #Pathfinding.IRaycastableGraph.Linecast methods.
  8. * Some members will also be initialized even if nothing was hit, see the individual member descriptions for more info.
  9. *
  10. * \shadowimage{linecast.png}
  11. */
  12. public struct GraphHitInfo {
  13. /** Start of the line/ray.
  14. * Note that the point passed to the Linecast method will be clamped to the closest point on the navmesh.
  15. */
  16. public Vector3 origin;
  17. /** Hit point.
  18. * In case no obstacle was hit then this will be set to the endpoint of the line.
  19. */
  20. public Vector3 point;
  21. /** Node which contained the edge which was hit.
  22. * If the linecast did not hit anything then this will be set to the last node along the line's path (the one which contains the endpoint).
  23. *
  24. * For layered grid graphs the linecast will return true (i.e: no free line of sight) if when walking the graph we ended up at X,Z coordinate for the end node
  25. * but the end node was on a different level (e.g the floor below or above in a building). In this case no node edge was really hit so this field will still be null.
  26. */
  27. public GraphNode node;
  28. /** Where the tangent starts. #tangentOrigin and #tangent together actually describes the edge which was hit.
  29. * \shadowimage{linecast_tangent.png}
  30. */
  31. public Vector3 tangentOrigin;
  32. /** Tangent of the edge which was hit.
  33. * \shadowimage{linecast_tangent.png}
  34. */
  35. public Vector3 tangent;
  36. /** Distance from #origin to #point */
  37. public float distance {
  38. get {
  39. return (point-origin).magnitude;
  40. }
  41. }
  42. public GraphHitInfo (Vector3 point) {
  43. tangentOrigin = Vector3.zero;
  44. origin = Vector3.zero;
  45. this.point = point;
  46. node = null;
  47. tangent = Vector3.zero;
  48. }
  49. }
  50. /** Graph which supports the Linecast method */
  51. public interface IRaycastableGraph {
  52. bool Linecast (Vector3 start, Vector3 end);
  53. bool Linecast (Vector3 start, Vector3 end, GraphNode hint);
  54. bool Linecast (Vector3 start, Vector3 end, GraphNode hint, out GraphHitInfo hit);
  55. bool Linecast (Vector3 start, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace);
  56. }
  57. /** Graph which has a well defined transformation from graph space to world space */
  58. public interface ITransformedGraph {
  59. GraphTransform transform { get; }
  60. }
  61. /** Base class for RecastGraph and NavMeshGraph */
  62. public abstract class NavmeshBase : NavGraph, INavmesh, INavmeshHolder, ITransformedGraph
  63. , IRaycastableGraph {
  64. #if ASTAR_RECAST_LARGER_TILES
  65. // Larger tiles
  66. public const int VertexIndexMask = 0xFFFFF;
  67. public const int TileIndexMask = 0x7FF;
  68. public const int TileIndexOffset = 20;
  69. #else
  70. // Larger worlds
  71. public const int VertexIndexMask = 0xFFF;
  72. public const int TileIndexMask = 0x7FFFF;
  73. public const int TileIndexOffset = 12;
  74. #endif
  75. /** Size of the bounding box. */
  76. [JsonMember]
  77. public Vector3 forcedBoundsSize = new Vector3(100, 40, 100);
  78. /** Size of a tile in world units along the X axis */
  79. public abstract float TileWorldSizeX { get; }
  80. /** Size of a tile in world units along the Z axis */
  81. public abstract float TileWorldSizeZ { get; }
  82. /** Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge.
  83. * When tiles are connected to each other, the nodes sometimes do not line up perfectly
  84. * so some allowance must be made to allow tiles that do not match exactly to be connected with each other.
  85. */
  86. protected abstract float MaxTileConnectionEdgeDistance { get; }
  87. /** Show an outline of the polygons in the Unity Editor */
  88. [JsonMember]
  89. public bool showMeshOutline = true;
  90. /** Show the connections between the polygons in the Unity Editor */
  91. [JsonMember]
  92. public bool showNodeConnections;
  93. /** Show the surface of the navmesh */
  94. [JsonMember]
  95. public bool showMeshSurface;
  96. /** Number of tiles along the X-axis */
  97. public int tileXCount;
  98. /** Number of tiles along the Z-axis */
  99. public int tileZCount;
  100. /** All tiles.
  101. *
  102. * \see #GetTile
  103. */
  104. public NavmeshTile[] tiles;
  105. /** Perform nearest node searches in XZ space only.
  106. * Recomended for single-layered environments. Faster but can be inaccurate esp. in multilayered contexts.
  107. * You should not use this if the graph is rotated since then the XZ plane no longer corresponds to the ground plane.
  108. *
  109. * This can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for:
  110. * \shadowimage{distanceXZ2.png}
  111. *
  112. * You can also control this using a \link Pathfinding.NNConstraint.distanceXZ field on an NNConstraint object\endlink.
  113. */
  114. [JsonMember]
  115. public bool nearestSearchOnlyXZ;
  116. /** Currently updating tiles in a batch */
  117. bool batchTileUpdate;
  118. /** Determines how the graph transforms graph space to world space.
  119. * \see #CalculateTransform
  120. */
  121. public GraphTransform transform = new GraphTransform(Matrix4x4.identity);
  122. GraphTransform ITransformedGraph.transform { get { return transform; } }
  123. /** \copydoc Pathfinding::NavMeshGraph::recalculateNormals */
  124. protected abstract bool RecalculateNormals { get; }
  125. /** Called when tiles have been completely recalculated.
  126. * This is called after scanning the graph and after
  127. * performing graph updates that completely recalculate tiles
  128. * (not ones that simply modify e.g penalties).
  129. * It is not called after NavmeshCut updates.
  130. */
  131. public System.Action<NavmeshTile[]> OnRecalculatedTiles;
  132. /** Tile at the specified x, z coordinate pair.
  133. * The first tile is at (0,0), the last tile at (tileXCount-1, tileZCount-1).
  134. *
  135. * \snippet MiscSnippets.cs NavmeshBase.GetTile
  136. */
  137. public NavmeshTile GetTile (int x, int z) {
  138. return tiles[x + z * tileXCount];
  139. }
  140. /** Vertex coordinate for the specified vertex index.
  141. *
  142. * \throws IndexOutOfRangeException if the vertex index is invalid.
  143. * \throws NullReferenceException if the tile the vertex is in is not calculated.
  144. *
  145. * \see NavmeshTile.GetVertex
  146. */
  147. public Int3 GetVertex (int index) {
  148. int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
  149. return tiles[tileIndex].GetVertex(index);
  150. }
  151. /** Vertex coordinate in graph space for the specified vertex index */
  152. public Int3 GetVertexInGraphSpace (int index) {
  153. int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
  154. return tiles[tileIndex].GetVertexInGraphSpace(index);
  155. }
  156. /** Tile index from a vertex index */
  157. public static int GetTileIndex (int index) {
  158. return (index >> TileIndexOffset) & TileIndexMask;
  159. }
  160. public int GetVertexArrayIndex (int index) {
  161. return index & VertexIndexMask;
  162. }
  163. /** Tile coordinates from a tile index */
  164. public void GetTileCoordinates (int tileIndex, out int x, out int z) {
  165. //z = System.Math.DivRem (tileIndex, tileXCount, out x);
  166. z = tileIndex/tileXCount;
  167. x = tileIndex - z*tileXCount;
  168. }
  169. /** All tiles.
  170. * \warning Do not modify this array
  171. */
  172. public NavmeshTile[] GetTiles () {
  173. return tiles;
  174. }
  175. /** Returns the tile coordinate which contains the specified \a position.
  176. * It is not necessarily a valid tile (i.e it could be out of bounds).
  177. */
  178. public Int2 GetTileCoordinates (Vector3 position) {
  179. position = transform.InverseTransform(position);
  180. position.x /= TileWorldSizeX;
  181. position.z /= TileWorldSizeZ;
  182. return new Int2((int)position.x, (int)position.z);
  183. }
  184. protected override void OnDestroy () {
  185. base.OnDestroy();
  186. #if !SERVER // 服务端不需要释放
  187. // Cleanup
  188. TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), null);
  189. #endif
  190. if (tiles != null) {
  191. for (int i = 0; i < tiles.Length; i++) {
  192. ObjectPool<BBTree>.Release(ref tiles[i].bbTree);
  193. }
  194. }
  195. }
  196. /** Creates a single new empty tile */
  197. protected NavmeshTile NewEmptyTile (int x, int z) {
  198. return new NavmeshTile {
  199. x = x,
  200. z = z,
  201. w = 1,
  202. d = 1,
  203. verts = new Int3[0],
  204. vertsInGraphSpace = new Int3[0],
  205. tris = new int[0],
  206. nodes = new TriangleMeshNode[0],
  207. bbTree = ObjectPool<BBTree>.Claim(),
  208. graph = this,
  209. };
  210. }
  211. public override void GetNodes (System.Action<GraphNode> action) {
  212. if (tiles == null) return;
  213. for (int i = 0; i < tiles.Length; i++) {
  214. if (tiles[i] == null || tiles[i].x+tiles[i].z*tileXCount != i) continue;
  215. TriangleMeshNode[] nodes = tiles[i].nodes;
  216. if (nodes == null) continue;
  217. for (int j = 0; j < nodes.Length; j++) action(nodes[j]);
  218. }
  219. }
  220. protected void ConnectTileWithNeighbours (NavmeshTile tile, bool onlyUnflagged = false) {
  221. if (tile.w != 1 || tile.d != 1) {
  222. throw new System.ArgumentException("Tile widths or depths other than 1 are not supported. The fields exist mainly for possible future expansions.");
  223. }
  224. // Loop through z and x offsets to adjacent tiles
  225. // _ x _
  226. // x _ x
  227. // _ x _
  228. for (int zo = -1; zo <= 1; zo++) {
  229. var z = tile.z + zo;
  230. if (z < 0 || z >= tileZCount) continue;
  231. for (int xo = -1; xo <= 1; xo++) {
  232. var x = tile.x + xo;
  233. if (x < 0 || x >= tileXCount) continue;
  234. // Ignore diagonals and the tile itself
  235. if ((xo == 0) == (zo == 0)) continue;
  236. var otherTile = tiles[x + z*tileXCount];
  237. if (!onlyUnflagged || !otherTile.flag) {
  238. ConnectTiles(otherTile, tile);
  239. }
  240. }
  241. }
  242. }
  243. static readonly NNConstraint NNConstraintDistanceXZ = new NNConstraint { distanceXZ = true };
  244. public override NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint, GraphNode hint) {
  245. return GetNearestForce(position, constraint != null && constraint.distanceXZ ? NNConstraintDistanceXZ : null);
  246. }
  247. public override NNInfoInternal GetNearestForce (Vector3 position, NNConstraint constraint) {
  248. if (tiles == null) return new NNInfoInternal();
  249. var tileCoords = GetTileCoordinates(position);
  250. // Clamp to graph borders
  251. tileCoords.x = Mathf.Clamp(tileCoords.x, 0, tileXCount-1);
  252. tileCoords.y = Mathf.Clamp(tileCoords.y, 0, tileZCount-1);
  253. int wmax = Math.Max(tileXCount, tileZCount);
  254. var best = new NNInfoInternal();
  255. float bestDistance = float.PositiveInfinity;
  256. bool xzSearch = nearestSearchOnlyXZ || (constraint != null && constraint.distanceXZ);
  257. // Search outwards in a diamond pattern from the closest tile
  258. // 2
  259. // 2 1 2
  260. // 2 1 0 1 2 etc.
  261. // 2 1 2
  262. // 2
  263. for (int w = 0; w < wmax; w++) {
  264. // Stop the loop when we can guarantee that no nodes will be closer than the ones we have already searched
  265. if (bestDistance < (w-2)*Math.Max(TileWorldSizeX, TileWorldSizeX)) break;
  266. int zmax = Math.Min(w+tileCoords.y +1, tileZCount);
  267. for (int z = Math.Max(-w+tileCoords.y, 0); z < zmax; z++) {
  268. // Solve for z such that abs(x-tx) + abs(z-tx) == w
  269. // Delta X coordinate
  270. int originalDx = Math.Abs(w - Math.Abs(z-tileCoords.y));
  271. var dx = originalDx;
  272. // Solution is dx + tx and -dx + tx
  273. // This loop will first check +dx and then -dx
  274. // If dx happens to be zero, then it will not run twice
  275. do {
  276. // Absolute x coordinate
  277. int x = -dx + tileCoords.x;
  278. if (x >= 0 && x < tileXCount) {
  279. NavmeshTile tile = tiles[x + z*tileXCount];
  280. if (tile != null) {
  281. if (xzSearch) {
  282. best = tile.bbTree.QueryClosestXZ(position, constraint, ref bestDistance, best);
  283. } else {
  284. best = tile.bbTree.QueryClosest(position, constraint, ref bestDistance, best);
  285. }
  286. }
  287. }
  288. dx = -dx;
  289. } while (dx != originalDx);
  290. }
  291. }
  292. best.node = best.constrainedNode;
  293. best.constrainedNode = null;
  294. best.clampedPosition = best.constClampedPosition;
  295. return best;
  296. }
  297. /** Fills graph with tiles created by NewEmptyTile */
  298. public void FillWithEmptyTiles () {
  299. for (int z = 0; z < tileZCount; z++) {
  300. for (int x = 0; x < tileXCount; x++) {
  301. tiles[z*tileXCount + x] = NewEmptyTile(x, z);
  302. }
  303. }
  304. }
  305. /** Create connections between all nodes.
  306. * \version Since 3.7.6 the implementation is thread safe
  307. */
  308. public static void CreateNodeConnections (TriangleMeshNode[] nodes) {
  309. List<Connection> connections = ListPool<Connection>.Claim();
  310. var nodeRefs = ObjectPoolSimple<Dictionary<Int2, int> >.Claim();
  311. nodeRefs.Clear();
  312. // Build node neighbours
  313. for (int i = 0; i < nodes.Length; i++) {
  314. TriangleMeshNode node = nodes[i];
  315. int av = node.GetVertexCount();
  316. for (int a = 0; a < av; a++) {
  317. // Recast can in some very special cases generate degenerate triangles which are simply lines
  318. // In that case, duplicate keys might be added and thus an exception will be thrown
  319. // It is safe to ignore the second edge though... I think (only found one case where this happens)
  320. var key = new Int2(node.GetVertexIndex(a), node.GetVertexIndex((a+1) % av));
  321. if (!nodeRefs.ContainsKey(key)) {
  322. nodeRefs.Add(key, i);
  323. }
  324. }
  325. }
  326. for (int i = 0; i < nodes.Length; i++) {
  327. TriangleMeshNode node = nodes[i];
  328. connections.Clear();
  329. int av = node.GetVertexCount();
  330. for (int a = 0; a < av; a++) {
  331. int first = node.GetVertexIndex(a);
  332. int second = node.GetVertexIndex((a+1) % av);
  333. int connNode;
  334. if (nodeRefs.TryGetValue(new Int2(second, first), out connNode)) {
  335. TriangleMeshNode other = nodes[connNode];
  336. int bv = other.GetVertexCount();
  337. for (int b = 0; b < bv; b++) {
  338. /** \todo This will fail on edges which are only partially shared */
  339. if (other.GetVertexIndex(b) == second && other.GetVertexIndex((b+1) % bv) == first) {
  340. connections.Add(new Connection(
  341. other,
  342. (uint)(node.position - other.position).costMagnitude,
  343. (byte)a
  344. ));
  345. break;
  346. }
  347. }
  348. }
  349. }
  350. node.connections = connections.ToArrayFromPool();
  351. }
  352. nodeRefs.Clear();
  353. ObjectPoolSimple<Dictionary<Int2, int> >.Release(ref nodeRefs);
  354. ListPool<Connection>.Release(ref connections);
  355. }
  356. /** Generate connections between the two tiles.
  357. * The tiles must be adjacent.
  358. */
  359. public void ConnectTiles (NavmeshTile tile1, NavmeshTile tile2) {
  360. if (tile1 == null || tile2 == null) return;
  361. if (tile1.nodes == null) throw new System.ArgumentException("tile1 does not contain any nodes");
  362. if (tile2.nodes == null) throw new System.ArgumentException("tile2 does not contain any nodes");
  363. int t1x = Mathf.Clamp(tile2.x, tile1.x, tile1.x+tile1.w-1);
  364. int t2x = Mathf.Clamp(tile1.x, tile2.x, tile2.x+tile2.w-1);
  365. int t1z = Mathf.Clamp(tile2.z, tile1.z, tile1.z+tile1.d-1);
  366. int t2z = Mathf.Clamp(tile1.z, tile2.z, tile2.z+tile2.d-1);
  367. int coord, altcoord;
  368. int t1coord, t2coord;
  369. float tileWorldSize;
  370. // Figure out which side that is shared between the two tiles
  371. // and what coordinate index is fixed along that edge (x or z)
  372. if (t1x == t2x) {
  373. coord = 2;
  374. altcoord = 0;
  375. t1coord = t1z;
  376. t2coord = t2z;
  377. tileWorldSize = TileWorldSizeZ;
  378. } else if (t1z == t2z) {
  379. coord = 0;
  380. altcoord = 2;
  381. t1coord = t1x;
  382. t2coord = t2x;
  383. tileWorldSize = TileWorldSizeX;
  384. } else {
  385. throw new System.ArgumentException("Tiles are not adjacent (neither x or z coordinates match)");
  386. }
  387. if (Math.Abs(t1coord-t2coord) != 1) {
  388. throw new System.ArgumentException("Tiles are not adjacent (tile coordinates must differ by exactly 1. Got '" + t1coord + "' and '" + t2coord + "')");
  389. }
  390. // Midpoint between the two tiles
  391. int midpoint = (int)Math.Round((Math.Max(t1coord, t2coord) * tileWorldSize) * Int3.Precision);
  392. #if ASTARDEBUG
  393. Vector3 v1 = new Vector3(-100, 0, -100);
  394. Vector3 v2 = new Vector3(100, 0, 100);
  395. v1[coord] = midpoint*Int3.PrecisionFactor;
  396. v2[coord] = midpoint*Int3.PrecisionFactor;
  397. Debug.DrawLine(v1, v2, Color.magenta);
  398. #endif
  399. TriangleMeshNode[] nodes1 = tile1.nodes;
  400. TriangleMeshNode[] nodes2 = tile2.nodes;
  401. // Find adjacent nodes on the border between the tiles
  402. for (int i = 0; i < nodes1.Length; i++) {
  403. TriangleMeshNode nodeA = nodes1[i];
  404. int aVertexCount = nodeA.GetVertexCount();
  405. // Loop through all *sides* of the node
  406. for (int a = 0; a < aVertexCount; a++) {
  407. // Vertices that the segment consists of
  408. Int3 aVertex1 = nodeA.GetVertexInGraphSpace(a);
  409. Int3 aVertex2 = nodeA.GetVertexInGraphSpace((a+1) % aVertexCount);
  410. // Check if it is really close to the tile border
  411. if (Math.Abs(aVertex1[coord] - midpoint) < 2 && Math.Abs(aVertex2[coord] - midpoint) < 2) {
  412. int minalt = Math.Min(aVertex1[altcoord], aVertex2[altcoord]);
  413. int maxalt = Math.Max(aVertex1[altcoord], aVertex2[altcoord]);
  414. // Degenerate edge
  415. if (minalt == maxalt) continue;
  416. for (int j = 0; j < nodes2.Length; j++) {
  417. TriangleMeshNode nodeB = nodes2[j];
  418. int bVertexCount = nodeB.GetVertexCount();
  419. for (int b = 0; b < bVertexCount; b++) {
  420. Int3 bVertex1 = nodeB.GetVertexInGraphSpace(b);
  421. Int3 bVertex2 = nodeB.GetVertexInGraphSpace((b+1) % aVertexCount);
  422. if (Math.Abs(bVertex1[coord] - midpoint) < 2 && Math.Abs(bVertex2[coord] - midpoint) < 2) {
  423. int minalt2 = Math.Min(bVertex1[altcoord], bVertex2[altcoord]);
  424. int maxalt2 = Math.Max(bVertex1[altcoord], bVertex2[altcoord]);
  425. // Degenerate edge
  426. if (minalt2 == maxalt2) continue;
  427. if (maxalt > minalt2 && minalt < maxalt2) {
  428. // The two nodes seem to be adjacent
  429. // Test shortest distance between the segments (first test if they are equal since that is much faster and pretty common)
  430. if ((aVertex1 == bVertex1 && aVertex2 == bVertex2) || (aVertex1 == bVertex2 && aVertex2 == bVertex1) ||
  431. VectorMath.SqrDistanceSegmentSegment((Vector3)aVertex1, (Vector3)aVertex2, (Vector3)bVertex1, (Vector3)bVertex2) < MaxTileConnectionEdgeDistance*MaxTileConnectionEdgeDistance) {
  432. uint cost = (uint)(nodeA.position - nodeB.position).costMagnitude;
  433. nodeA.AddConnection(nodeB, cost, a);
  434. nodeB.AddConnection(nodeA, cost, b);
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. }
  442. }
  443. }
  444. /** Temporary buffer used in #PrepareNodeRecycling */
  445. Dictionary<int, int> nodeRecyclingHashBuffer = new Dictionary<int, int>();
  446. /** Reuse nodes that keep the exact same vertices after a tile replacement.
  447. * The reused nodes will be added to the \a recycledNodeBuffer array at the index corresponding to the
  448. * indices in the triangle array that its vertices uses.
  449. *
  450. * All connections on the reused nodes will be removed except ones that go to other graphs.
  451. * The reused nodes will be removed from the tile by replacing it with a null slot in the node array.
  452. *
  453. * \see #ReplaceTile
  454. */
  455. void PrepareNodeRecycling (int x, int z, Int3[] verts, int[] tris, TriangleMeshNode[] recycledNodeBuffer) {
  456. NavmeshTile tile = GetTile(x, z);
  457. if (tile == null || tile.nodes.Length == 0) return;
  458. var nodes = tile.nodes;
  459. var recycling = nodeRecyclingHashBuffer;
  460. for (int i = 0, j = 0; i < tris.Length; i += 3, j++) {
  461. recycling[verts[tris[i+0]].GetHashCode() + verts[tris[i+1]].GetHashCode() + verts[tris[i+2]].GetHashCode()] = j;
  462. }
  463. var connectionsToKeep = ListPool<Connection>.Claim();
  464. for (int i = 0; i < nodes.Length; i++) {
  465. var node = nodes[i];
  466. Int3 v0, v1, v2;
  467. node.GetVerticesInGraphSpace(out v0, out v1, out v2);
  468. var hash = v0.GetHashCode() + v1.GetHashCode() + v2.GetHashCode();
  469. int newNodeIndex;
  470. if (recycling.TryGetValue(hash, out newNodeIndex)) {
  471. // Technically we should check for a cyclic permutations of the vertices (e.g node a,b,c could become node b,c,a)
  472. // but in almost all cases the vertices will keep the same order. Allocating one or two extra nodes isn't such a big deal.
  473. if (verts[tris[3*newNodeIndex+0]] == v0 && verts[tris[3*newNodeIndex+1]] == v1 && verts[tris[3*newNodeIndex+2]] == v2) {
  474. recycledNodeBuffer[newNodeIndex] = node;
  475. // Remove the node from the tile
  476. nodes[i] = null;
  477. // Only keep connections to nodes on other graphs
  478. // Usually there are no connections to nodes to other graphs and this is faster than removing all connections them one by one
  479. for (int j = 0; j < node.connections.Length; j++) {
  480. if (node.connections[j].node.GraphIndex != node.GraphIndex) {
  481. connectionsToKeep.Add(node.connections[j]);
  482. }
  483. }
  484. ArrayPool<Connection>.Release(ref node.connections, true);
  485. if (connectionsToKeep.Count > 0) {
  486. node.connections = connectionsToKeep.ToArrayFromPool();
  487. connectionsToKeep.Clear();
  488. }
  489. }
  490. }
  491. }
  492. recycling.Clear();
  493. ListPool<Connection>.Release(ref connectionsToKeep);
  494. }
  495. public void CreateNodes (TriangleMeshNode[] buffer, int[] tris, int tileIndex, uint graphIndex) {
  496. 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");
  497. // This index will be ORed to the triangle indices
  498. tileIndex <<= TileIndexOffset;
  499. // Create nodes and assign vertex indices
  500. for (int i = 0; i < buffer.Length; i++) {
  501. var node = buffer[i];
  502. // Allow the buffer to be partially filled in already to allow for recycling nodes
  503. if (node == null) node = buffer[i] = new TriangleMeshNode();
  504. // Reset all relevant fields on the node (even on recycled nodes to avoid exposing internal implementation details)
  505. node.Walkable = true;
  506. node.Tag = 0;
  507. node.Penalty = initialPenalty;
  508. node.GraphIndex = graphIndex;
  509. // The vertices stored on the node are composed
  510. // out of the triangle index and the tile index
  511. node.v0 = tris[i*3+0] | tileIndex;
  512. node.v1 = tris[i*3+1] | tileIndex;
  513. node.v2 = tris[i*3+2] | tileIndex;
  514. // Make sure the triangle is clockwise in graph space (it may not be in world space since the graphs can be rotated)
  515. if (RecalculateNormals && !VectorMath.IsClockwiseXZ(node.GetVertexInGraphSpace(0), node.GetVertexInGraphSpace(1), node.GetVertexInGraphSpace(2))) {
  516. Memory.Swap(ref node.v0, ref node.v2);
  517. }
  518. node.UpdatePositionFromVertices();
  519. }
  520. }
  521. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  522. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  523. *
  524. * \astarpro
  525. *
  526. * \shadowimage{linecast.png}
  527. */
  528. public bool Linecast (Vector3 origin, Vector3 end) {
  529. return Linecast(origin, end, GetNearest(origin, NNConstraint.None).node);
  530. }
  531. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  532. * \param [in] origin Point to linecast from
  533. * \param [in] end Point to linecast to
  534. * \param [out] hit Contains info on what was hit, see GraphHitInfo
  535. * \param [in] hint You need to pass the node closest to the start point
  536. *
  537. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  538. * \astarpro
  539. *
  540. * \shadowimage{linecast.png}
  541. */
  542. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
  543. return Linecast(this, origin, end, hint, out hit, null);
  544. }
  545. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  546. * \param [in] origin Point to linecast from
  547. * \param [in] end Point to linecast to
  548. * \param [in] hint You need to pass the node closest to the start point
  549. *
  550. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  551. * \astarpro
  552. *
  553. * \shadowimage{linecast.png}
  554. */
  555. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint) {
  556. GraphHitInfo hit;
  557. return Linecast(this, origin, end, hint, out hit, null);
  558. }
  559. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  560. * \param [in] origin Point to linecast from
  561. * \param [in] end Point to linecast to
  562. * \param [out] hit Contains info on what was hit, see GraphHitInfo
  563. * \param [in] hint You need to pass the node closest to the start point
  564. * \param trace If a list is passed, then it will be filled with all nodes the linecast traverses
  565. *
  566. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  567. * \astarpro
  568. *
  569. * \shadowimage{linecast.png}
  570. */
  571. public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace) {
  572. return Linecast(this, origin, end, hint, out hit, trace);
  573. }
  574. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  575. * \param [in] graph The graph to perform the search on
  576. * \param [in] origin Point to start from
  577. * \param [in] end Point to linecast to
  578. * \param [out] hit Contains info on what was hit, see GraphHitInfo
  579. * \param [in] hint You need to pass the node closest to the start point, if null, a search for the closest node will be done
  580. *
  581. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
  582. * \astarpro
  583. *
  584. * \shadowimage{linecast.png}
  585. */
  586. public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
  587. return Linecast(graph, origin, end, hint, out hit, null);
  588. }
  589. /** Cached \link Pathfinding.NNConstraint.None NNConstraint.None\endlink to reduce allocations */
  590. static readonly NNConstraint NNConstraintNone = NNConstraint.None;
  591. /** Used to optimize linecasts by precomputing some values */
  592. static readonly byte[] LinecastShapeEdgeLookup;
  593. static NavmeshBase () {
  594. // Want want to figure out which side of a triangle that a ray exists using.
  595. // There are only 3*3*3 = 27 different options for the [left/right/colinear] options for the 3 vertices of a triangle.
  596. // So we can precompute the result to improve the performance of linecasts.
  597. // For simplicity we reserve 2 bits for each side which means that we have 4*4*4 = 64 entries in the lookup table.
  598. LinecastShapeEdgeLookup = new byte[64];
  599. Side[] sideOfLine = new Side[3];
  600. for (int i = 0; i < LinecastShapeEdgeLookup.Length; i++) {
  601. sideOfLine[0] = (Side)((i >> 0) & 0x3);
  602. sideOfLine[1] = (Side)((i >> 2) & 0x3);
  603. sideOfLine[2] = (Side)((i >> 4) & 0x3);
  604. LinecastShapeEdgeLookup[i] = 0xFF;
  605. // Value 3 is an invalid value. So we just skip it.
  606. if (sideOfLine[0] != (Side)3 && sideOfLine[1] != (Side)3 && sideOfLine[2] != (Side)3) {
  607. // Figure out the side of the triangle that the line exits.
  608. // In case the line passes through one of the vertices of the triangle
  609. // there may be multiple alternatives. In that case pick the edge
  610. // which contains the fewest vertices that lie on the line.
  611. // This prevents a potential infinite loop when a linecast is done colinear
  612. // to the edge of a triangle.
  613. int bestBadness = int.MaxValue;
  614. for (int j = 0; j < 3; j++) {
  615. if ((sideOfLine[j] == Side.Left || sideOfLine[j] == Side.Colinear) && (sideOfLine[(j+1)%3] == Side.Right || sideOfLine[(j+1)%3] == Side.Colinear)) {
  616. var badness = (sideOfLine[j] == Side.Colinear ? 1 : 0) + (sideOfLine[(j+1)%3] == Side.Colinear ? 1 : 0);
  617. if (badness < bestBadness) {
  618. LinecastShapeEdgeLookup[i] = (byte)j;
  619. bestBadness = badness;
  620. }
  621. }
  622. }
  623. }
  624. }
  625. }
  626. /** Returns if there is an obstacle between \a origin and \a end on the graph.
  627. * \param [in] graph The graph to perform the search on
  628. * \param [in] origin Point to start from. This point should be on the navmesh. It will be snapped to the closest point on the navmesh otherwise.
  629. * \param [in] end Point to linecast to
  630. * \param [out] hit Contains info on what was hit, see GraphHitInfo
  631. * \param [in] hint If you already know the node which contains the \a origin point, you may pass it here for slighly improved performance. If null, a search for the closest node will be done.
  632. * \param 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.
  633. *
  634. * This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersections.
  635. *
  636. * \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,
  637. * 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.
  638. *
  639. * \astarpro
  640. *
  641. * \shadowimage{linecast.png}
  642. */
  643. public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace) {
  644. hit = new GraphHitInfo();
  645. if (float.IsNaN(origin.x + origin.y + origin.z)) throw new System.ArgumentException("origin is NaN");
  646. if (float.IsNaN(end.x + end.y + end.z)) throw new System.ArgumentException("end is NaN");
  647. var node = hint as TriangleMeshNode;
  648. if (node == null) {
  649. node = graph.GetNearest(origin, NNConstraintNone).node as TriangleMeshNode;
  650. if (node == null) {
  651. #if !SERVER
  652. UnityEngine.Debug.LogError("Could not find a valid node to start from");
  653. #endif
  654. hit.origin = origin;
  655. hit.point = origin;
  656. return true;
  657. }
  658. }
  659. // Snap the origin to the navmesh
  660. var i3originInGraphSpace = node.ClosestPointOnNodeXZInGraphSpace(origin);
  661. hit.origin = graph.transform.Transform((Vector3)i3originInGraphSpace);
  662. if (!node.Walkable) {
  663. hit.node = node;
  664. hit.point = hit.origin;
  665. hit.tangentOrigin = hit.origin;
  666. return true;
  667. }
  668. var endInGraphSpace = graph.transform.InverseTransform(end);
  669. var i3endInGraphSpace = (Int3)endInGraphSpace;
  670. // Fast early out check
  671. if (i3originInGraphSpace == i3endInGraphSpace) {
  672. hit.point = hit.origin;
  673. hit.node = node;
  674. return false;
  675. }
  676. int counter = 0;
  677. while (true) {
  678. counter++;
  679. if (counter > 2000) {
  680. #if !SERVER
  681. UnityEngine.Debug.LogError("Linecast was stuck in infinite loop. Breaking.");
  682. #endif
  683. return true;
  684. }
  685. if (trace != null) trace.Add(node);
  686. Int3 a0, a1, a2;
  687. node.GetVerticesInGraphSpace(out a0, out a1, out a2);
  688. int sideOfLine = (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a0);
  689. sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a1) << 2;
  690. sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a2) << 4;
  691. // Use a lookup table to figure out which side of this triangle that the ray exits
  692. int shapeEdgeA = (int)LinecastShapeEdgeLookup[sideOfLine];
  693. // The edge consists of the vertex with index 'sharedEdgeA' and the next vertex after that (index '(sharedEdgeA+1)%3')
  694. var sideNodeExit = VectorMath.SideXZ(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2), shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0), i3endInGraphSpace);
  695. if (sideNodeExit != Side.Left) {
  696. // Ray stops before it leaves the current node.
  697. // The endpoint must be inside the current node.
  698. hit.point = end;
  699. hit.node = node;
  700. return false;
  701. }
  702. if (shapeEdgeA == 0xFF) {
  703. // Line does not intersect node at all?
  704. // 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.
  705. #if !SERVER
  706. UnityEngine.Debug.LogError("Line does not intersect node at all");
  707. #endif
  708. hit.node = node;
  709. hit.point = hit.tangentOrigin = hit.origin;
  710. return true;
  711. } else {
  712. bool success = false;
  713. var nodeConnections = node.connections;
  714. for (int i = 0; i < nodeConnections.Length; i++) {
  715. if (nodeConnections[i].shapeEdge == shapeEdgeA) {
  716. // This might be the next node that we enter
  717. var neighbour = nodeConnections[i].node as TriangleMeshNode;
  718. if (neighbour == null || !neighbour.Walkable) continue;
  719. var neighbourConnections = neighbour.connections;
  720. int shapeEdgeB = -1;
  721. for (int j = 0; j < neighbourConnections.Length; j++) {
  722. if (neighbourConnections[j].node == node) {
  723. shapeEdgeB = neighbourConnections[j].shapeEdge;
  724. break;
  725. }
  726. }
  727. if (shapeEdgeB == -1) {
  728. // Connection was mono-directional!
  729. // This shouldn't normally not happen on navmeshes happen on navmeshes (when the shapeEdge matches at least) unless a user has done something strange to the navmesh.
  730. continue;
  731. }
  732. var side1 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace(shapeEdgeB));
  733. var side2 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace((shapeEdgeB+1) % 3));
  734. // Check if the line enters this edge
  735. success = (side1 == Side.Right || side1 == Side.Colinear) && (side2 == Side.Left || side2 == Side.Colinear);
  736. if (!success) continue;
  737. // Ray has entered the neighbouring node.
  738. // After the first node, it is possible to prove the loop invariant that shapeEdgeA will *never* end up as -1 (checked above)
  739. // Since side = Colinear acts essentially as a wildcard. side1 and side2 can be the most restricted if they are side1=right, side2=left.
  740. // 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.
  741. // We are looking for the sequence [Left, Right] (possibly including Colinear as wildcard). We will always find this sequence regardless of the value of *.
  742. node = neighbour;
  743. break;
  744. }
  745. }
  746. if (!success) {
  747. // Node did not enter any neighbours
  748. // It must have hit the border of the navmesh
  749. var hitEdgeStartInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2));
  750. var hitEdgeEndInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0));
  751. var intersectionInGraphSpace = VectorMath.LineIntersectionPointXZ(hitEdgeStartInGraphSpace, hitEdgeEndInGraphSpace, (Vector3)i3originInGraphSpace, (Vector3)i3endInGraphSpace);
  752. hit.point = graph.transform.Transform(intersectionInGraphSpace);
  753. hit.node = node;
  754. var hitEdgeStart = graph.transform.Transform(hitEdgeStartInGraphSpace);
  755. var hitEdgeEnd = graph.transform.Transform(hitEdgeEndInGraphSpace);
  756. hit.tangent = hitEdgeEnd - hitEdgeStart;
  757. hit.tangentOrigin = hitEdgeStart;
  758. return true;
  759. }
  760. }
  761. }
  762. }
  763. /** Serializes Node Info.
  764. * Should serialize:
  765. * - Base
  766. * - Node Flags
  767. * - Node Penalties
  768. * - Node
  769. * - Node Positions (if applicable)
  770. * - Any other information necessary to load the graph in-game
  771. * All settings marked with json attributes (e.g JsonMember) have already been
  772. * saved as graph settings and do not need to be handled here.
  773. *
  774. * It is not necessary for this implementation to be forward or backwards compatible.
  775. *
  776. * \see
  777. */
  778. protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
  779. BinaryWriter writer = ctx.writer;
  780. if (tiles == null) {
  781. writer.Write(-1);
  782. return;
  783. }
  784. writer.Write(tileXCount);
  785. writer.Write(tileZCount);
  786. for (int z = 0; z < tileZCount; z++) {
  787. for (int x = 0; x < tileXCount; x++) {
  788. NavmeshTile tile = tiles[x + z*tileXCount];
  789. if (tile == null) {
  790. throw new System.Exception("NULL Tile");
  791. //writer.Write (-1);
  792. //continue;
  793. }
  794. writer.Write(tile.x);
  795. writer.Write(tile.z);
  796. if (tile.x != x || tile.z != z) continue;
  797. writer.Write(tile.w);
  798. writer.Write(tile.d);
  799. writer.Write(tile.tris.Length);
  800. for (int i = 0; i < tile.tris.Length; i++) writer.Write(tile.tris[i]);
  801. writer.Write(tile.verts.Length);
  802. for (int i = 0; i < tile.verts.Length; i++) {
  803. ctx.SerializeInt3(tile.verts[i]);
  804. }
  805. writer.Write(tile.vertsInGraphSpace.Length);
  806. for (int i = 0; i < tile.vertsInGraphSpace.Length; i++) {
  807. ctx.SerializeInt3(tile.vertsInGraphSpace[i]);
  808. }
  809. writer.Write(tile.nodes.Length);
  810. for (int i = 0; i < tile.nodes.Length; i++) {
  811. tile.nodes[i].SerializeNode(ctx);
  812. }
  813. }
  814. }
  815. }
  816. public abstract GraphTransform CalculateTransform();
  817. protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
  818. BinaryReader reader = ctx.reader;
  819. tileXCount = reader.ReadInt32();
  820. if (tileXCount < 0) return;
  821. tileZCount = reader.ReadInt32();
  822. transform = CalculateTransform();
  823. tiles = new NavmeshTile[tileXCount * tileZCount];
  824. //Make sure mesh nodes can reference this graph
  825. TriangleMeshNode.SetNavmeshHolder((int)ctx.graphIndex, this);
  826. for (int z = 0; z < tileZCount; z++) {
  827. for (int x = 0; x < tileXCount; x++) {
  828. int tileIndex = x + z*tileXCount;
  829. int tx = reader.ReadInt32();
  830. if (tx < 0) throw new System.Exception("Invalid tile coordinates (x < 0)");
  831. int tz = reader.ReadInt32();
  832. if (tz < 0) throw new System.Exception("Invalid tile coordinates (z < 0)");
  833. // This is not the origin of a large tile. Refer back to that tile.
  834. if (tx != x || tz != z) {
  835. tiles[tileIndex] = tiles[tz*tileXCount + tx];
  836. continue;
  837. }
  838. var tile = tiles[tileIndex] = new NavmeshTile {
  839. x = tx,
  840. z = tz,
  841. w = reader.ReadInt32(),
  842. d = reader.ReadInt32(),
  843. bbTree = ObjectPool<BBTree>.Claim(),
  844. graph = this,
  845. };
  846. int trisCount = reader.ReadInt32();
  847. if (trisCount % 3 != 0) throw new System.Exception("Corrupt data. Triangle indices count must be divisable by 3. Read " + trisCount);
  848. tile.tris = new int[trisCount];
  849. for (int i = 0; i < tile.tris.Length; i++) tile.tris[i] = reader.ReadInt32();
  850. tile.verts = new Int3[reader.ReadInt32()];
  851. for (int i = 0; i < tile.verts.Length; i++) {
  852. tile.verts[i] = ctx.DeserializeInt3();
  853. }
  854. if (ctx.meta.version.Major >= 4) {
  855. tile.vertsInGraphSpace = new Int3[reader.ReadInt32()];
  856. if (tile.vertsInGraphSpace.Length != tile.verts.Length) throw new System.Exception("Corrupt data. Array lengths did not match");
  857. for (int i = 0; i < tile.verts.Length; i++) {
  858. tile.vertsInGraphSpace[i] = ctx.DeserializeInt3();
  859. }
  860. } else {
  861. // Compatibility
  862. tile.vertsInGraphSpace = new Int3[tile.verts.Length];
  863. tile.verts.CopyTo(tile.vertsInGraphSpace, 0);
  864. transform.InverseTransform(tile.vertsInGraphSpace);
  865. }
  866. int nodeCount = reader.ReadInt32();
  867. tile.nodes = new TriangleMeshNode[nodeCount];
  868. // Prepare for storing in vertex indices
  869. tileIndex <<= TileIndexOffset;
  870. for (int i = 0; i < tile.nodes.Length; i++) {
  871. var node = new TriangleMeshNode();
  872. tile.nodes[i] = node;
  873. node.DeserializeNode(ctx);
  874. node.v0 = tile.tris[i*3+0] | tileIndex;
  875. node.v1 = tile.tris[i*3+1] | tileIndex;
  876. node.v2 = tile.tris[i*3+2] | tileIndex;
  877. node.UpdatePositionFromVertices();
  878. }
  879. tile.bbTree.RebuildFrom(tile.nodes);
  880. }
  881. }
  882. }
  883. protected override void PostDeserialization (GraphSerializationContext ctx) {
  884. // Compatibility
  885. if (ctx.meta.version < AstarSerializer.V4_1_0 && tiles != null) {
  886. Dictionary<TriangleMeshNode, Connection[]> conns = tiles.SelectMany(s => s.nodes).ToDictionary(n => n, n => n.connections ?? new Connection[0]);
  887. // We need to recalculate all connections when upgrading data from earlier than 4.1.0
  888. // as the connections now need information about which edge was used.
  889. // This may remove connections for e.g off-mesh links.
  890. foreach (var tile in tiles) CreateNodeConnections(tile.nodes);
  891. foreach (var tile in tiles) ConnectTileWithNeighbours(tile);
  892. // Restore any connections that were contained in the serialized file but didn't get added by the method calls above
  893. GetNodes(node => {
  894. var triNode = node as TriangleMeshNode;
  895. foreach (var conn in conns[triNode].Where(conn => !triNode.ContainsConnection(conn.node)).ToList()) {
  896. triNode.AddConnection(conn.node, conn.cost, conn.shapeEdge);
  897. }
  898. });
  899. }
  900. // Make sure that the transform is up to date.
  901. // It is assumed that the current graph settings correspond to the correct
  902. // transform as it is not serialized itself.
  903. transform = CalculateTransform();
  904. }
  905. }
  906. }