123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677 |
- using UnityEngine;
- using System.Collections.Generic;
- using UnityEngine.Profiling;
- namespace Pathfinding {
- using System.IO;
- using Pathfinding.Util;
- using Pathfinding.Serialization;
- using Math = System.Math;
- using System.Linq;
- /// <summary>Base class for <see cref="RecastGraph"/> and <see cref="NavMeshGraph"/></summary>
- public abstract class NavmeshBase : NavGraph, INavmesh, INavmeshHolder, ITransformedGraph
- , IRaycastableGraph {
- #if ASTAR_RECAST_LARGER_TILES
- // Larger tiles
- public const int VertexIndexMask = 0xFFFFF;
- public const int TileIndexMask = 0x7FF;
- public const int TileIndexOffset = 20;
- #else
- // Larger worlds
- public const int VertexIndexMask = 0xFFF;
- public const int TileIndexMask = 0x7FFFF;
- public const int TileIndexOffset = 12;
- #endif
- /// <summary>Size of the bounding box.</summary>
- [JsonMember]
- public Vector3 forcedBoundsSize = new Vector3(100, 40, 100);
- /// <summary>Size of a tile in world units along the X axis</summary>
- public abstract float TileWorldSizeX { get; }
- /// <summary>Size of a tile in world units along the Z axis</summary>
- public abstract float TileWorldSizeZ { get; }
- /// <summary>
- /// Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge.
- /// When tiles are connected to each other, the nodes sometimes do not line up perfectly
- /// so some allowance must be made to allow tiles that do not match exactly to be connected with each other.
- /// </summary>
- protected abstract float MaxTileConnectionEdgeDistance { get; }
- /// <summary>Show an outline of the polygons in the Unity Editor</summary>
- [JsonMember]
- public bool showMeshOutline = true;
- /// <summary>Show the connections between the polygons in the Unity Editor</summary>
- [JsonMember]
- public bool showNodeConnections;
- /// <summary>Show the surface of the navmesh</summary>
- [JsonMember]
- public bool showMeshSurface = true;
- /// <summary>Number of tiles along the X-axis</summary>
- public int tileXCount;
- /// <summary>Number of tiles along the Z-axis</summary>
- public int tileZCount;
- /// <summary>
- /// All tiles.
- ///
- /// See: <see cref="GetTile"/>
- /// </summary>
- protected NavmeshTile[] tiles;
- /// <summary>
- /// Perform nearest node searches in XZ space only.
- /// Recomended for single-layered environments. Faster but can be inaccurate esp. in multilayered contexts.
- /// You should not use this if the graph is rotated since then the XZ plane no longer corresponds to the ground plane.
- ///
- /// This can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for:
- /// [Open online documentation to see images]
- ///
- /// You can also control this using a <see cref="Pathfinding.NNConstraint.distanceXZ field on an NNConstraint"/>.
- /// </summary>
- [JsonMember]
- public bool nearestSearchOnlyXZ;
- /// <summary>
- /// Should navmesh cuts affect this graph.
- /// See: <see cref="navmeshUpdateData"/>
- /// </summary>
- [JsonMember]
- public bool enableNavmeshCutting = true;
- /// <summary>
- /// Handles navmesh cutting.
- /// See: <see cref="enableNavmeshCutting"/>
- /// See: <see cref="Pathfinding.NavmeshUpdates"/>
- /// </summary>
- internal readonly NavmeshUpdates.NavmeshUpdateSettings navmeshUpdateData;
- /// <summary>Currently updating tiles in a batch</summary>
- bool batchTileUpdate;
- /// <summary>List of tiles updating during batch</summary>
- List<int> batchUpdatedTiles = new List<int>();
- /// <summary>List of nodes that are going to be destroyed as part of a batch update</summary>
- List<MeshNode> batchNodesToDestroy = new List<MeshNode>();
- /// <summary>
- /// Determines how the graph transforms graph space to world space.
- /// See: <see cref="CalculateTransform"/>
- /// </summary>
- public GraphTransform transform = new GraphTransform(Matrix4x4.identity);
- GraphTransform ITransformedGraph.transform { get { return transform; } }
- /// <summary>\copydoc Pathfinding::NavMeshGraph::recalculateNormals</summary>
- protected abstract bool RecalculateNormals { get; }
- /// <summary>
- /// Returns a new transform which transforms graph space to world space.
- /// Does not update the <see cref="transform"/> field.
- /// See: <see cref="RelocateNodes(GraphTransform)"/>
- /// </summary>
- public abstract GraphTransform CalculateTransform();
- /// <summary>
- /// Called when tiles have been completely recalculated.
- /// This is called after scanning the graph and after
- /// performing graph updates that completely recalculate tiles
- /// (not ones that simply modify e.g penalties).
- /// It is not called after NavmeshCut updates.
- /// </summary>
- public System.Action<NavmeshTile[]> OnRecalculatedTiles;
- /// <summary>
- /// Tile at the specified x, z coordinate pair.
- /// The first tile is at (0,0), the last tile at (tileXCount-1, tileZCount-1).
- ///
- /// <code>
- /// var graph = AstarPath.active.data.recastGraph;
- /// int tileX = 5;
- /// int tileZ = 8;
- /// NavmeshTile tile = graph.GetTile(tileX, tileZ);
- ///
- /// for (int i = 0; i < tile.nodes.Length; i++) {
- /// // ...
- /// }
- /// // or you can access the nodes like this:
- /// tile.GetNodes(node => {
- /// // ...
- /// });
- /// </code>
- /// </summary>
- public NavmeshTile GetTile (int x, int z) {
- return tiles[x + z * tileXCount];
- }
- /// <summary>
- /// Vertex coordinate for the specified vertex index.
- ///
- /// Throws: IndexOutOfRangeException if the vertex index is invalid.
- /// Throws: NullReferenceException if the tile the vertex is in is not calculated.
- ///
- /// See: NavmeshTile.GetVertex
- /// </summary>
- public Int3 GetVertex (int index) {
- int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
- return tiles[tileIndex].GetVertex(index);
- }
- /// <summary>Vertex coordinate in graph space for the specified vertex index</summary>
- public Int3 GetVertexInGraphSpace (int index) {
- int tileIndex = (index >> TileIndexOffset) & TileIndexMask;
- return tiles[tileIndex].GetVertexInGraphSpace(index);
- }
- /// <summary>Tile index from a vertex index</summary>
- public static int GetTileIndex (int index) {
- return (index >> TileIndexOffset) & TileIndexMask;
- }
- public int GetVertexArrayIndex (int index) {
- return index & VertexIndexMask;
- }
- /// <summary>Tile coordinates from a tile index</summary>
- public void GetTileCoordinates (int tileIndex, out int x, out int z) {
- //z = System.Math.DivRem (tileIndex, tileXCount, out x);
- z = tileIndex/tileXCount;
- x = tileIndex - z*tileXCount;
- }
- /// <summary>
- /// All tiles.
- /// Warning: Do not modify this array
- /// </summary>
- public NavmeshTile[] GetTiles () {
- return tiles;
- }
- /// <summary>
- /// Returns a bounds object with the bounding box of a group of tiles.
- /// The bounding box is defined in world space.
- /// </summary>
- public Bounds GetTileBounds (IntRect rect) {
- return GetTileBounds(rect.xmin, rect.ymin, rect.Width, rect.Height);
- }
- /// <summary>
- /// Returns a bounds object with the bounding box of a group of tiles.
- /// The bounding box is defined in world space.
- /// </summary>
- public Bounds GetTileBounds (int x, int z, int width = 1, int depth = 1) {
- return transform.Transform(GetTileBoundsInGraphSpace(x, z, width, depth));
- }
- public Bounds GetTileBoundsInGraphSpace (IntRect rect) {
- return GetTileBoundsInGraphSpace(rect.xmin, rect.ymin, rect.Width, rect.Height);
- }
- /// <summary>Returns an XZ bounds object with the bounds of a group of tiles in graph space</summary>
- public Bounds GetTileBoundsInGraphSpace (int x, int z, int width = 1, int depth = 1) {
- var b = new Bounds();
- b.SetMinMax(
- new Vector3(x*TileWorldSizeX, 0, z*TileWorldSizeZ),
- new Vector3((x+width)*TileWorldSizeX, forcedBoundsSize.y, (z+depth)*TileWorldSizeZ)
- );
- return b;
- }
- /// <summary>
- /// Returns the tile coordinate which contains the specified position.
- /// It is not necessarily a valid tile (i.e it could be out of bounds).
- /// </summary>
- public Int2 GetTileCoordinates (Vector3 position) {
- position = transform.InverseTransform(position);
- position.x /= TileWorldSizeX;
- position.z /= TileWorldSizeZ;
- return new Int2((int)position.x, (int)position.z);
- }
- protected override void OnDestroy () {
- base.OnDestroy();
- // Cleanup
- TriangleMeshNode.SetNavmeshHolder(active.data.GetGraphIndex(this), null);
- if (tiles != null) {
- for (int i = 0; i < tiles.Length; i++) {
- Pathfinding.Util.ObjectPool<BBTree>.Release(ref tiles[i].bbTree);
- }
- }
- }
- public override void RelocateNodes (Matrix4x4 deltaMatrix) {
- RelocateNodes(deltaMatrix * transform);
- }
- /// <summary>
- /// Moves the nodes in this graph.
- /// Moves all the nodes in such a way that the specified transform is the new graph space to world space transformation for the graph.
- /// You usually use this together with the <see cref="CalculateTransform"/> method.
- ///
- /// So for example if you want to move and rotate all your nodes in e.g a recast graph you can do
- /// <code>
- /// var graph = AstarPath.data.recastGraph;
- /// graph.rotation = new Vector3(45, 0, 0);
- /// graph.forcedBoundsCenter = new Vector3(20, 10, 10);
- /// var transform = graph.CalculateTransform();
- /// graph.RelocateNodes(transform);
- /// </code>
- /// This will move all the nodes to new positions as if the new graph settings had been there from the start.
- ///
- /// Note: RelocateNodes(deltaMatrix) is not equivalent to RelocateNodes(new GraphTransform(deltaMatrix)).
- /// The overload which takes a matrix multiplies all existing node positions with the matrix while this
- /// overload does not take into account the current positions of the nodes.
- ///
- /// See: <see cref="CalculateTransform"/>
- /// </summary>
- public void RelocateNodes (GraphTransform newTransform) {
- transform = newTransform;
- if (tiles != null) {
- // Move all the vertices in each tile
- for (int tileIndex = 0; tileIndex < tiles.Length; tileIndex++) {
- var tile = tiles[tileIndex];
- if (tile != null) {
- tile.vertsInGraphSpace.CopyTo(tile.verts, 0);
- // Transform the graph space vertices to world space
- transform.Transform(tile.verts);
- for (int nodeIndex = 0; nodeIndex < tile.nodes.Length; nodeIndex++) {
- tile.nodes[nodeIndex].UpdatePositionFromVertices();
- }
- tile.bbTree.RebuildFrom(tile.nodes);
- }
- }
- }
- }
- /// <summary>Creates a single new empty tile</summary>
- protected NavmeshTile NewEmptyTile (int x, int z) {
- return new NavmeshTile {
- x = x,
- z = z,
- w = 1,
- d = 1,
- verts = new Int3[0],
- vertsInGraphSpace = new Int3[0],
- tris = new int[0],
- nodes = new TriangleMeshNode[0],
- bbTree = ObjectPool<BBTree>.Claim(),
- graph = this,
- };
- }
- public override void GetNodes (System.Action<GraphNode> action) {
- if (tiles == null) return;
- for (int i = 0; i < tiles.Length; i++) {
- if (tiles[i] == null || tiles[i].x+tiles[i].z*tileXCount != i) continue;
- TriangleMeshNode[] nodes = tiles[i].nodes;
- if (nodes == null) continue;
- for (int j = 0; j < nodes.Length; j++) action(nodes[j]);
- }
- }
- /// <summary>
- /// Returns a rect containing the indices of all tiles touching the specified bounds.
- /// If a margin is passed, the bounding box in graph space is expanded by that amount in every direction.
- /// </summary>
- public IntRect GetTouchingTiles (Bounds bounds, float margin = 0) {
- bounds = transform.InverseTransform(bounds);
- // Calculate world bounds of all affected tiles
- var r = new IntRect(Mathf.FloorToInt((bounds.min.x - margin) / TileWorldSizeX), Mathf.FloorToInt((bounds.min.z - margin) / TileWorldSizeZ), Mathf.FloorToInt((bounds.max.x + margin) / TileWorldSizeX), Mathf.FloorToInt((bounds.max.z + margin) / TileWorldSizeZ));
- // Clamp to bounds
- r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
- return r;
- }
- /// <summary>Returns a rect containing the indices of all tiles touching the specified bounds.</summary>
- /// <param name="rect">Graph space rectangle (in graph space all tiles are on the XZ plane regardless of graph rotation and other transformations, the first tile has a corner at the origin)</param>
- public IntRect GetTouchingTilesInGraphSpace (Rect rect) {
- // Calculate world bounds of all affected tiles
- var r = new IntRect(Mathf.FloorToInt(rect.xMin / TileWorldSizeX), Mathf.FloorToInt(rect.yMin / TileWorldSizeZ), Mathf.FloorToInt(rect.xMax / TileWorldSizeX), Mathf.FloorToInt(rect.yMax / TileWorldSizeZ));
- // Clamp to bounds
- r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
- return r;
- }
- /// <summary>
- /// Returns a rect containing the indices of all tiles by rounding the specified bounds to tile borders.
- /// This is different from GetTouchingTiles in that the tiles inside the rectangle returned from this method
- /// may not contain the whole bounds, while that is guaranteed for GetTouchingTiles.
- /// </summary>
- public IntRect GetTouchingTilesRound (Bounds bounds) {
- bounds = transform.InverseTransform(bounds);
- //Calculate world bounds of all affected tiles
- var r = new IntRect(Mathf.RoundToInt(bounds.min.x / TileWorldSizeX), Mathf.RoundToInt(bounds.min.z / TileWorldSizeZ), Mathf.RoundToInt(bounds.max.x / TileWorldSizeX)-1, Mathf.RoundToInt(bounds.max.z / TileWorldSizeZ)-1);
- //Clamp to bounds
- r = IntRect.Intersection(r, new IntRect(0, 0, tileXCount-1, tileZCount-1));
- return r;
- }
- protected void ConnectTileWithNeighbours (NavmeshTile tile, bool onlyUnflagged = false) {
- if (tile.w != 1 || tile.d != 1) {
- throw new System.ArgumentException("Tile widths or depths other than 1 are not supported. The fields exist mainly for possible future expansions.");
- }
- // Loop through z and x offsets to adjacent tiles
- // _ x _
- // x _ x
- // _ x _
- for (int zo = -1; zo <= 1; zo++) {
- var z = tile.z + zo;
- if (z < 0 || z >= tileZCount) continue;
- for (int xo = -1; xo <= 1; xo++) {
- var x = tile.x + xo;
- if (x < 0 || x >= tileXCount) continue;
- // Ignore diagonals and the tile itself
- if ((xo == 0) == (zo == 0)) continue;
- var otherTile = tiles[x + z*tileXCount];
- if (!onlyUnflagged || !otherTile.flag) {
- ConnectTiles(otherTile, tile);
- }
- }
- }
- }
- protected void RemoveConnectionsFromTile (NavmeshTile tile) {
- if (tile.x > 0) {
- int x = tile.x-1;
- for (int z = tile.z; z < tile.z+tile.d; z++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
- }
- if (tile.x+tile.w < tileXCount) {
- int x = tile.x+tile.w;
- for (int z = tile.z; z < tile.z+tile.d; z++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
- }
- if (tile.z > 0) {
- int z = tile.z-1;
- for (int x = tile.x; x < tile.x+tile.w; x++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
- }
- if (tile.z+tile.d < tileZCount) {
- int z = tile.z+tile.d;
- for (int x = tile.x; x < tile.x+tile.w; x++) RemoveConnectionsFromTo(tiles[x + z*tileXCount], tile);
- }
- }
- protected void RemoveConnectionsFromTo (NavmeshTile a, NavmeshTile b) {
- if (a == null || b == null) return;
- //Same tile, possibly from a large tile (one spanning several x,z tile coordinates)
- if (a == b) return;
- int tileIdx = b.x + b.z*tileXCount;
- for (int i = 0; i < a.nodes.Length; i++) {
- TriangleMeshNode node = a.nodes[i];
- if (node.connections == null) continue;
- for (int j = 0;; j++) {
- //Length will not be constant if connections are removed
- if (j >= node.connections.Length) break;
- var other = node.connections[j].node as TriangleMeshNode;
- //Only evaluate TriangleMeshNodes
- if (other == null) continue;
- int tileIdx2 = other.GetVertexIndex(0);
- tileIdx2 = (tileIdx2 >> TileIndexOffset) & TileIndexMask;
- if (tileIdx2 == tileIdx) {
- node.RemoveConnection(node.connections[j].node);
- j--;
- }
- }
- }
- }
- static readonly NNConstraint NNConstraintDistanceXZ = new NNConstraint { distanceXZ = true };
- public override NNInfoInternal GetNearest (Vector3 position, NNConstraint constraint, GraphNode hint) {
- return GetNearestForce(position, constraint != null && constraint.distanceXZ ? NNConstraintDistanceXZ : null);
- }
- public override NNInfoInternal GetNearestForce (Vector3 position, NNConstraint constraint) {
- if (tiles == null) return new NNInfoInternal();
- var tileCoords = GetTileCoordinates(position);
- // Clamp to graph borders
- tileCoords.x = Mathf.Clamp(tileCoords.x, 0, tileXCount-1);
- tileCoords.y = Mathf.Clamp(tileCoords.y, 0, tileZCount-1);
- int wmax = Math.Max(tileXCount, tileZCount);
- var best = new NNInfoInternal();
- float bestDistance = float.PositiveInfinity;
- bool xzSearch = nearestSearchOnlyXZ || (constraint != null && constraint.distanceXZ);
- // Search outwards in a diamond pattern from the closest tile
- // 2
- // 2 1 2
- // 2 1 0 1 2 etc.
- // 2 1 2
- // 2
- for (int w = 0; w < wmax; w++) {
- // Stop the loop when we can guarantee that no nodes will be closer than the ones we have already searched
- if (bestDistance < (w-2)*Math.Max(TileWorldSizeX, TileWorldSizeX)) break;
- int zmax = Math.Min(w+tileCoords.y +1, tileZCount);
- for (int z = Math.Max(-w+tileCoords.y, 0); z < zmax; z++) {
- // Solve for z such that abs(x-tx) + abs(z-tx) == w
- // Delta X coordinate
- int originalDx = Math.Abs(w - Math.Abs(z-tileCoords.y));
- var dx = originalDx;
- // Solution is dx + tx and -dx + tx
- // This loop will first check +dx and then -dx
- // If dx happens to be zero, then it will not run twice
- do {
- // Absolute x coordinate
- int x = -dx + tileCoords.x;
- if (x >= 0 && x < tileXCount) {
- NavmeshTile tile = tiles[x + z*tileXCount];
- if (tile != null) {
- if (xzSearch) {
- best = tile.bbTree.QueryClosestXZ(position, constraint, ref bestDistance, best);
- } else {
- best = tile.bbTree.QueryClosest(position, constraint, ref bestDistance, best);
- }
- }
- }
- dx = -dx;
- } while (dx != originalDx);
- }
- }
- best.node = best.constrainedNode;
- best.constrainedNode = null;
- best.clampedPosition = best.constClampedPosition;
- return best;
- }
- /// <summary>
- /// Finds the first node which contains position.
- /// "Contains" is defined as position is inside the triangle node when seen from above. So only XZ space matters.
- /// In case of a multilayered environment, which node of the possibly several nodes
- /// containing the point is undefined.
- ///
- /// Returns null if there was no node containing the point. This serves as a quick
- /// check for "is this point on the navmesh or not".
- ///
- /// Note that the behaviour of this method is distinct from the GetNearest method.
- /// The GetNearest method will return the closest node to a point,
- /// which is not necessarily the one which contains it in XZ space.
- ///
- /// See: GetNearest
- /// </summary>
- public GraphNode PointOnNavmesh (Vector3 position, NNConstraint constraint) {
- if (tiles == null) return null;
- var tileCoords = GetTileCoordinates(position);
- // Graph borders
- if (tileCoords.x < 0 || tileCoords.y < 0 || tileCoords.x >= tileXCount || tileCoords.y >= tileZCount) return null;
- NavmeshTile tile = GetTile(tileCoords.x, tileCoords.y);
- if (tile != null) {
- GraphNode node = tile.bbTree.QueryInside(position, constraint);
- return node;
- }
- return null;
- }
- /// <summary>Fills graph with tiles created by NewEmptyTile</summary>
- protected void FillWithEmptyTiles () {
- for (int z = 0; z < tileZCount; z++) {
- for (int x = 0; x < tileXCount; x++) {
- tiles[z*tileXCount + x] = NewEmptyTile(x, z);
- }
- }
- }
- /// <summary>
- /// Create connections between all nodes.
- /// Version: Since 3.7.6 the implementation is thread safe
- /// </summary>
- protected static void CreateNodeConnections (TriangleMeshNode[] nodes) {
- List<Connection> connections = ListPool<Connection>.Claim();
- var nodeRefs = ObjectPoolSimple<Dictionary<Int2, int> >.Claim();
- nodeRefs.Clear();
- // Build node neighbours
- for (int i = 0; i < nodes.Length; i++) {
- TriangleMeshNode node = nodes[i];
- int av = node.GetVertexCount();
- for (int a = 0; a < av; a++) {
- // Recast can in some very special cases generate degenerate triangles which are simply lines
- // In that case, duplicate keys might be added and thus an exception will be thrown
- // It is safe to ignore the second edge though... I think (only found one case where this happens)
- var key = new Int2(node.GetVertexIndex(a), node.GetVertexIndex((a+1) % av));
- if (!nodeRefs.ContainsKey(key)) {
- nodeRefs.Add(key, i);
- }
- }
- }
- for (int i = 0; i < nodes.Length; i++) {
- TriangleMeshNode node = nodes[i];
- connections.Clear();
- int av = node.GetVertexCount();
- for (int a = 0; a < av; a++) {
- int first = node.GetVertexIndex(a);
- int second = node.GetVertexIndex((a+1) % av);
- int connNode;
- if (nodeRefs.TryGetValue(new Int2(second, first), out connNode)) {
- TriangleMeshNode other = nodes[connNode];
- int bv = other.GetVertexCount();
- for (int b = 0; b < bv; b++) {
- /// <summary>TODO: This will fail on edges which are only partially shared</summary>
- if (other.GetVertexIndex(b) == second && other.GetVertexIndex((b+1) % bv) == first) {
- connections.Add(new Connection(
- other,
- (uint)(node.position - other.position).costMagnitude,
- (byte)a
- ));
- break;
- }
- }
- }
- }
- node.connections = connections.ToArrayFromPool();
- node.SetConnectivityDirty();
- }
- nodeRefs.Clear();
- ObjectPoolSimple<Dictionary<Int2, int> >.Release(ref nodeRefs);
- ListPool<Connection>.Release(ref connections);
- }
- /// <summary>
- /// Generate connections between the two tiles.
- /// The tiles must be adjacent.
- /// </summary>
- protected void ConnectTiles (NavmeshTile tile1, NavmeshTile tile2) {
- if (tile1 == null || tile2 == null) return;
- if (tile1.nodes == null) throw new System.ArgumentException("tile1 does not contain any nodes");
- if (tile2.nodes == null) throw new System.ArgumentException("tile2 does not contain any nodes");
- int t1x = Mathf.Clamp(tile2.x, tile1.x, tile1.x+tile1.w-1);
- int t2x = Mathf.Clamp(tile1.x, tile2.x, tile2.x+tile2.w-1);
- int t1z = Mathf.Clamp(tile2.z, tile1.z, tile1.z+tile1.d-1);
- int t2z = Mathf.Clamp(tile1.z, tile2.z, tile2.z+tile2.d-1);
- int coord, altcoord;
- int t1coord, t2coord;
- float tileWorldSize;
- // Figure out which side that is shared between the two tiles
- // and what coordinate index is fixed along that edge (x or z)
- if (t1x == t2x) {
- coord = 2;
- altcoord = 0;
- t1coord = t1z;
- t2coord = t2z;
- tileWorldSize = TileWorldSizeZ;
- } else if (t1z == t2z) {
- coord = 0;
- altcoord = 2;
- t1coord = t1x;
- t2coord = t2x;
- tileWorldSize = TileWorldSizeX;
- } else {
- throw new System.ArgumentException("Tiles are not adjacent (neither x or z coordinates match)");
- }
- if (Math.Abs(t1coord-t2coord) != 1) {
- throw new System.ArgumentException("Tiles are not adjacent (tile coordinates must differ by exactly 1. Got '" + t1coord + "' and '" + t2coord + "')");
- }
- // Midpoint between the two tiles
- int midpoint = (int)Math.Round((Math.Max(t1coord, t2coord) * tileWorldSize) * Int3.Precision);
- #if ASTARDEBUG
- Vector3 v1 = new Vector3(-100, 0, -100);
- Vector3 v2 = new Vector3(100, 0, 100);
- v1[coord] = midpoint*Int3.PrecisionFactor;
- v2[coord] = midpoint*Int3.PrecisionFactor;
- Debug.DrawLine(v1, v2, Color.magenta);
- #endif
- TriangleMeshNode[] nodes1 = tile1.nodes;
- TriangleMeshNode[] nodes2 = tile2.nodes;
- // Find all nodes of the second tile which are adjacent to the border between the tiles.
- // This is used to speed up the matching process (the impact can be very significant for large tiles, but is insignificant for small ones).
- TriangleMeshNode[] closeToEdge = ArrayPool<TriangleMeshNode>.Claim(nodes2.Length);
- int numCloseToEdge = 0;
- for (int j = 0; j < nodes2.Length; j++) {
- TriangleMeshNode nodeB = nodes2[j];
- int bVertexCount = nodeB.GetVertexCount();
- for (int b = 0; b < bVertexCount; b++) {
- Int3 bVertex1 = nodeB.GetVertexInGraphSpace(b);
- Int3 bVertex2 = nodeB.GetVertexInGraphSpace((b+1) % bVertexCount);
- if (Math.Abs(bVertex1[coord] - midpoint) < 2 && Math.Abs(bVertex2[coord] - midpoint) < 2) {
- closeToEdge[numCloseToEdge] = nodes2[j];
- numCloseToEdge++;
- break;
- }
- }
- }
- // Find adjacent nodes on the border between the tiles
- for (int i = 0; i < nodes1.Length; i++) {
- TriangleMeshNode nodeA = nodes1[i];
- int aVertexCount = nodeA.GetVertexCount();
- // Loop through all *sides* of the node
- for (int a = 0; a < aVertexCount; a++) {
- // Vertices that the segment consists of
- Int3 aVertex1 = nodeA.GetVertexInGraphSpace(a);
- Int3 aVertex2 = nodeA.GetVertexInGraphSpace((a+1) % aVertexCount);
- // Check if it is really close to the tile border
- if (Math.Abs(aVertex1[coord] - midpoint) < 2 && Math.Abs(aVertex2[coord] - midpoint) < 2) {
- int minalt = Math.Min(aVertex1[altcoord], aVertex2[altcoord]);
- int maxalt = Math.Max(aVertex1[altcoord], aVertex2[altcoord]);
- // Degenerate edge
- if (minalt == maxalt) continue;
- for (int j = 0; j < numCloseToEdge; j++) {
- TriangleMeshNode nodeB = closeToEdge[j];
- int bVertexCount = nodeB.GetVertexCount();
- for (int b = 0; b < bVertexCount; b++) {
- Int3 bVertex1 = nodeB.GetVertexInGraphSpace(b);
- Int3 bVertex2 = nodeB.GetVertexInGraphSpace((b+1) % bVertexCount);
- if (Math.Abs(bVertex1[coord] - midpoint) < 2 && Math.Abs(bVertex2[coord] - midpoint) < 2) {
- int minalt2 = Math.Min(bVertex1[altcoord], bVertex2[altcoord]);
- int maxalt2 = Math.Max(bVertex1[altcoord], bVertex2[altcoord]);
- // Degenerate edge
- if (minalt2 == maxalt2) continue;
- if (maxalt > minalt2 && minalt < maxalt2) {
- // The two nodes seem to be adjacent
- // Test shortest distance between the segments (first test if they are equal since that is much faster and pretty common)
- if ((aVertex1 == bVertex1 && aVertex2 == bVertex2) || (aVertex1 == bVertex2 && aVertex2 == bVertex1) ||
- VectorMath.SqrDistanceSegmentSegment((Vector3)aVertex1, (Vector3)aVertex2, (Vector3)bVertex1, (Vector3)bVertex2) < MaxTileConnectionEdgeDistance*MaxTileConnectionEdgeDistance) {
- uint cost = (uint)(nodeA.position - nodeB.position).costMagnitude;
- nodeA.AddConnection(nodeB, cost, (byte)a);
- nodeB.AddConnection(nodeA, cost, (byte)b);
- }
- }
- }
- }
- }
- }
- }
- }
- ArrayPool<TriangleMeshNode>.Release(ref closeToEdge);
- }
- /// <summary>
- /// Start batch updating of tiles.
- /// During batch updating, tiles will not be connected if they are updating with ReplaceTile.
- /// When ending batching, all affected tiles will be connected.
- /// This is faster than not using batching.
- /// </summary>
- public void StartBatchTileUpdate () {
- if (batchTileUpdate) throw new System.InvalidOperationException("Calling StartBatchLoad when batching is already enabled");
- batchTileUpdate = true;
- }
- /// <summary>
- /// Destroy several nodes simultaneously.
- /// This is faster than simply looping through the nodes and calling the node.Destroy method because some optimizations
- /// relating to how connections are removed can be optimized.
- /// </summary>
- void DestroyNodes (List<MeshNode> nodes) {
- for (int i = 0; i < batchNodesToDestroy.Count; i++) {
- batchNodesToDestroy[i].TemporaryFlag1 = true;
- }
- for (int i = 0; i < batchNodesToDestroy.Count; i++) {
- var node = batchNodesToDestroy[i];
- for (int j = 0; j < node.connections.Length; j++) {
- var neighbour = node.connections[j].node;
- if (!neighbour.TemporaryFlag1) {
- neighbour.RemoveConnection(node);
- }
- }
- // Remove the connections array explicitly for performance.
- // Otherwise the Destroy method will try to remove the connections in both directions one by one which is slow.
- ArrayPool<Connection>.Release(ref node.connections, true);
- node.Destroy();
- }
- }
- void TryConnect (int tileIdx1, int tileIdx2) {
- // If both tiles were flagged, then only connect if tileIdx1 < tileIdx2 to make sure we don't connect the tiles twice
- // as this method will be called with swapped arguments as well.
- if (tiles[tileIdx1].flag && tiles[tileIdx2].flag && tileIdx1 >= tileIdx2) return;
- ConnectTiles(tiles[tileIdx1], tiles[tileIdx2]);
- }
- /// <summary>
- /// End batch updating of tiles.
- /// During batch updating, tiles will not be connected if they are updating with ReplaceTile.
- /// When ending batching, all affected tiles will be connected.
- /// This is faster than not using batching.
- /// </summary>
- public void EndBatchTileUpdate () {
- if (!batchTileUpdate) throw new System.InvalidOperationException("Calling EndBatchTileUpdate when batching had not yet been started");
- batchTileUpdate = false;
- DestroyNodes(batchNodesToDestroy);
- batchNodesToDestroy.ClearFast();
- for (int i = 0; i < batchUpdatedTiles.Count; i++) tiles[batchUpdatedTiles[i]].flag = true;
- for (int i = 0; i < batchUpdatedTiles.Count; i++) {
- int x = batchUpdatedTiles[i] % tileXCount, z = batchUpdatedTiles[i] / tileXCount;
- if (x > 0) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] - 1);
- if (x < tileXCount - 1) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] + 1);
- if (z > 0) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] - tileXCount);
- if (z < tileZCount - 1) TryConnect(batchUpdatedTiles[i], batchUpdatedTiles[i] + tileXCount);
- }
- for (int i = 0; i < batchUpdatedTiles.Count; i++) tiles[batchUpdatedTiles[i]].flag = false;
- batchUpdatedTiles.ClearFast();
- }
- /// <summary>
- /// Clear the tile at the specified coordinate.
- /// Must be called during a batch update, see <see cref="StartBatchTileUpdate"/>.
- /// </summary>
- protected void ClearTile (int x, int z) {
- if (!batchTileUpdate) throw new System.Exception("Must be called during a batch update. See StartBatchTileUpdate");
- var tile = GetTile(x, z);
- if (tile == null) return;
- var nodes = tile.nodes;
- for (int i = 0; i < nodes.Length; i++) {
- if (nodes[i] != null) batchNodesToDestroy.Add(nodes[i]);
- }
- ObjectPool<BBTree>.Release(ref tile.bbTree);
- // TODO: Pool tile object and various arrays in it?
- tiles[x + z*tileXCount] = null;
- }
- /// <summary>Temporary buffer used in <see cref="PrepareNodeRecycling"/></summary>
- Dictionary<int, int> nodeRecyclingHashBuffer = new Dictionary<int, int>();
- /// <summary>
- /// Reuse nodes that keep the exact same vertices after a tile replacement.
- /// The reused nodes will be added to the recycledNodeBuffer array at the index corresponding to the
- /// indices in the triangle array that its vertices uses.
- ///
- /// All connections on the reused nodes will be removed except ones that go to other graphs.
- /// The reused nodes will be removed from the tile by replacing it with a null slot in the node array.
- ///
- /// See: <see cref="ReplaceTile"/>
- /// </summary>
- void PrepareNodeRecycling (int x, int z, Int3[] verts, int[] tris, TriangleMeshNode[] recycledNodeBuffer) {
- NavmeshTile tile = GetTile(x, z);
- if (tile == null || tile.nodes.Length == 0) return;
- var nodes = tile.nodes;
- var recycling = nodeRecyclingHashBuffer;
- for (int i = 0, j = 0; i < tris.Length; i += 3, j++) {
- recycling[verts[tris[i+0]].GetHashCode() + verts[tris[i+1]].GetHashCode() + verts[tris[i+2]].GetHashCode()] = j;
- }
- var connectionsToKeep = ListPool<Connection>.Claim();
- for (int i = 0; i < nodes.Length; i++) {
- var node = nodes[i];
- Int3 v0, v1, v2;
- node.GetVerticesInGraphSpace(out v0, out v1, out v2);
- var hash = v0.GetHashCode() + v1.GetHashCode() + v2.GetHashCode();
- int newNodeIndex;
- if (recycling.TryGetValue(hash, out newNodeIndex)) {
- // Technically we should check for a cyclic permutations of the vertices (e.g node a,b,c could become node b,c,a)
- // but in almost all cases the vertices will keep the same order. Allocating one or two extra nodes isn't such a big deal.
- if (verts[tris[3*newNodeIndex+0]] == v0 && verts[tris[3*newNodeIndex+1]] == v1 && verts[tris[3*newNodeIndex+2]] == v2) {
- recycledNodeBuffer[newNodeIndex] = node;
- // Remove the node from the tile
- nodes[i] = null;
- // Only keep connections to nodes on other graphs
- // Usually there are no connections to nodes to other graphs and this is faster than removing all connections them one by one
- for (int j = 0; j < node.connections.Length; j++) {
- if (node.connections[j].node.GraphIndex != node.GraphIndex) {
- connectionsToKeep.Add(node.connections[j]);
- }
- }
- ArrayPool<Connection>.Release(ref node.connections, true);
- if (connectionsToKeep.Count > 0) {
- node.connections = connectionsToKeep.ToArrayFromPool();
- node.SetConnectivityDirty();
- connectionsToKeep.Clear();
- }
- }
- }
- }
- recycling.Clear();
- ListPool<Connection>.Release(ref connectionsToKeep);
- }
- /// <summary>
- /// Replace tile at index with nodes created from specified navmesh.
- /// This will create new nodes and link them to the adjacent tile (unless batching has been started in which case that will be done when batching ends).
- ///
- /// The vertices are assumed to be in 'tile space', that is being in a rectangle with
- /// one corner at the origin and one at (<see cref="TileWorldSizeX"/>, 0, <see cref="TileWorldSizeZ)"/>.
- ///
- /// Note: The vertex and triangle arrays may be modified and will be stored with the tile data.
- /// do not modify them after this method has been called.
- ///
- /// See: <see cref="StartBatchTileUpdate"/>
- /// </summary>
- public void ReplaceTile (int x, int z, Int3[] verts, int[] tris) {
- int w = 1, d = 1;
- if (x + w > tileXCount || z+d > tileZCount || x < 0 || z < 0) {
- throw new System.ArgumentException("Tile is placed at an out of bounds position or extends out of the graph bounds ("+x+", " + z + " [" + w + ", " + d+ "] " + tileXCount + " " + tileZCount + ")");
- }
- if (tris.Length % 3 != 0) throw new System.ArgumentException("Triangle array's length must be a multiple of 3 (tris)");
- if (verts.Length > VertexIndexMask) {
- Debug.LogError("Too many vertices in the tile (" + verts.Length + " > " + VertexIndexMask +")\nYou can enable ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector to raise this limit. Or you can use a smaller tile size to reduce the likelihood of this happening.");
- verts = new Int3[0];
- tris = new int[0];
- }
- var wasNotBatching = !batchTileUpdate;
- if (wasNotBatching) StartBatchTileUpdate();
- Profiler.BeginSample("Tile Initialization");
- //Create a new navmesh tile and assign its settings
- var tile = new NavmeshTile {
- x = x,
- z = z,
- w = w,
- d = d,
- tris = tris,
- bbTree = ObjectPool<BBTree>.Claim(),
- graph = this,
- };
- if (!Mathf.Approximately(x*TileWorldSizeX*Int3.FloatPrecision, (float)Math.Round(x*TileWorldSizeX*Int3.FloatPrecision))) Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize");
- if (!Mathf.Approximately(z*TileWorldSizeZ*Int3.FloatPrecision, (float)Math.Round(z*TileWorldSizeZ*Int3.FloatPrecision))) Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize");
- var offset = (Int3) new Vector3((x * TileWorldSizeX), 0, (z * TileWorldSizeZ));
- for (int i = 0; i < verts.Length; i++) {
- verts[i] += offset;
- }
- tile.vertsInGraphSpace = verts;
- tile.verts = (Int3[])verts.Clone();
- transform.Transform(tile.verts);
- Profiler.BeginSample("Clear Previous Tiles");
- // Create a backing array for the new nodes
- var nodes = tile.nodes = new TriangleMeshNode[tris.Length/3];
- // Recycle any nodes that are in the exact same spot after replacing the tile.
- // This also keeps e.g penalties and tags and other connections which might be useful.
- // It also avoids trashing the paths for the RichAI component (as it will have to immediately recalculate its path
- // if it discovers that its path contains destroyed nodes).
- PrepareNodeRecycling(x, z, tile.vertsInGraphSpace, tris, tile.nodes);
- // Remove previous tiles (except the nodes that were recycled above)
- ClearTile(x, z);
- Profiler.EndSample();
- Profiler.EndSample();
- Profiler.BeginSample("Assign Node Data");
- // Set tile
- tiles[x + z*tileXCount] = tile;
- batchUpdatedTiles.Add(x + z*tileXCount);
- // Create nodes and assign triangle indices
- CreateNodes(nodes, tile.tris, x + z*tileXCount, (uint)active.data.GetGraphIndex(this));
- Profiler.EndSample();
- Profiler.BeginSample("AABBTree Rebuild");
- tile.bbTree.RebuildFrom(nodes);
- Profiler.EndSample();
- Profiler.BeginSample("Create Node Connections");
- CreateNodeConnections(tile.nodes);
- Profiler.EndSample();
- Profiler.BeginSample("Connect With Neighbours");
- if (wasNotBatching) EndBatchTileUpdate();
- Profiler.EndSample();
- }
- protected void CreateNodes (TriangleMeshNode[] buffer, int[] tris, int tileIndex, uint graphIndex) {
- 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");
- // This index will be ORed to the triangle indices
- tileIndex <<= TileIndexOffset;
- // Create nodes and assign vertex indices
- for (int i = 0; i < buffer.Length; i++) {
- var node = buffer[i];
- // Allow the buffer to be partially filled in already to allow for recycling nodes
- if (node == null) node = buffer[i] = new TriangleMeshNode(active);
- // Reset all relevant fields on the node (even on recycled nodes to avoid exposing internal implementation details)
- node.Walkable = true;
- node.Tag = 0;
- node.Penalty = initialPenalty;
- node.GraphIndex = graphIndex;
- // The vertices stored on the node are composed
- // out of the triangle index and the tile index
- node.v0 = tris[i*3+0] | tileIndex;
- node.v1 = tris[i*3+1] | tileIndex;
- node.v2 = tris[i*3+2] | tileIndex;
- // Make sure the triangle is clockwise in graph space (it may not be in world space since the graphs can be rotated)
- // Note that we also modify the original triangle array because if the graph is cached then we will re-initialize the nodes from that array and assume all triangles are clockwise.
- if (RecalculateNormals && !VectorMath.IsClockwiseXZ(node.GetVertexInGraphSpace(0), node.GetVertexInGraphSpace(1), node.GetVertexInGraphSpace(2))) {
- Memory.Swap(ref tris[i*3+0], ref tris[i*3+2]);
- Memory.Swap(ref node.v0, ref node.v2);
- }
- node.UpdatePositionFromVertices();
- }
- }
- public NavmeshBase () {
- navmeshUpdateData = new NavmeshUpdates.NavmeshUpdateSettings(this);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- public bool Linecast (Vector3 origin, Vector3 end) {
- return Linecast(origin, end, null);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="origin">Point to linecast from.</param>
- /// <param name="end">Point to linecast to.</param>
- /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
- /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
- /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
- public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
- return Linecast(this, origin, end, hint, out hit, null);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="origin">Point to linecast from.</param>
- /// <param name="end">Point to linecast to.</param>
- /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
- /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
- public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint) {
- GraphHitInfo hit;
- return Linecast(this, origin, end, hint, out hit, null);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="origin">Point to linecast from.</param>
- /// <param name="end">Point to linecast to.</param>
- /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
- /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
- /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
- /// <param name="trace">If a list is passed, then it will be filled with all nodes the linecast traverses.</param>
- public bool Linecast (Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace) {
- return Linecast(this, origin, end, hint, out hit, trace);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="origin">Point to linecast from.</param>
- /// <param name="end">Point to linecast to.</param>
- /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
- /// <param name="trace">If a list is passed, then it will be filled with all nodes the linecast traverses.</param>
- /// <param name="filter">If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
- /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.</param>
- public bool Linecast (Vector3 origin, Vector3 end, out GraphHitInfo hit, List<GraphNode> trace, System.Func<GraphNode, bool> filter) {
- return Linecast(this, origin, end, null, out hit, trace, filter);
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersection.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="graph">The graph to perform the search on.</param>
- /// <param name="origin">Point to start from.</param>
- /// <param name="end">Point to linecast to.</param>
- /// <param name="hit">Contains info on what was hit, see GraphHitInfo.</param>
- /// <param name="hint">You may pass the node closest to the start point if you already know it for a minor performance boost.
- /// If null, a search for the closest node will be done. This parameter is mostly deprecated and should be avoided. Pass null instead.</param>
- public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit) {
- return Linecast(graph, origin, end, hint, out hit, null);
- }
- /// <summary>Cached <see cref="Pathfinding.NNConstraint.None"/> with distanceXZ=true to reduce allocations</summary>
- static readonly NNConstraint NNConstraintNoneXZ = new NNConstraint {
- constrainWalkability = false,
- constrainArea = false,
- constrainTags = false,
- constrainDistance = false,
- graphMask = -1,
- distanceXZ = true,
- };
- /// <summary>Used to optimize linecasts by precomputing some values</summary>
- static readonly byte[] LinecastShapeEdgeLookup;
- static NavmeshBase () {
- // Want want to figure out which side of a triangle that a ray exists using.
- // There are only 3*3*3 = 27 different options for the [left/right/colinear] options for the 3 vertices of a triangle.
- // So we can precompute the result to improve the performance of linecasts.
- // For simplicity we reserve 2 bits for each side which means that we have 4*4*4 = 64 entries in the lookup table.
- LinecastShapeEdgeLookup = new byte[64];
- Side[] sideOfLine = new Side[3];
- for (int i = 0; i < LinecastShapeEdgeLookup.Length; i++) {
- sideOfLine[0] = (Side)((i >> 0) & 0x3);
- sideOfLine[1] = (Side)((i >> 2) & 0x3);
- sideOfLine[2] = (Side)((i >> 4) & 0x3);
- LinecastShapeEdgeLookup[i] = 0xFF;
- // Value 3 is an invalid value. So we just skip it.
- if (sideOfLine[0] != (Side)3 && sideOfLine[1] != (Side)3 && sideOfLine[2] != (Side)3) {
- // Figure out the side of the triangle that the line exits.
- // In case the line passes through one of the vertices of the triangle
- // there may be multiple alternatives. In that case pick the edge
- // which contains the fewest vertices that lie on the line.
- // This prevents a potential infinite loop when a linecast is done colinear
- // to the edge of a triangle.
- int bestBadness = int.MaxValue;
- for (int j = 0; j < 3; j++) {
- if ((sideOfLine[j] == Side.Left || sideOfLine[j] == Side.Colinear) && (sideOfLine[(j+1)%3] == Side.Right || sideOfLine[(j+1)%3] == Side.Colinear)) {
- var badness = (sideOfLine[j] == Side.Colinear ? 1 : 0) + (sideOfLine[(j+1)%3] == Side.Colinear ? 1 : 0);
- if (badness < bestBadness) {
- LinecastShapeEdgeLookup[i] = (byte)j;
- bestBadness = badness;
- }
- }
- }
- }
- }
- }
- /// <summary>
- /// Returns if there is an obstacle between origin and end on the graph.
- ///
- /// This is not the same as Physics.Linecast, this function traverses the \b graph and looks for collisions instead of checking for collider intersections.
- ///
- /// 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,
- /// 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.
- ///
- /// [Open online documentation to see images]
- /// </summary>
- /// <param name="graph">The graph to perform the search on</param>
- /// <param name="origin">Point to start from. This point should be on the navmesh. It will be snapped to the closest point on the navmesh otherwise.</param>
- /// <param name="end">Point to linecast to</param>
- /// <param name="hit">Contains info on what was hit, see GraphHitInfo</param>
- /// <param name="hint">If you already know the node which contains the origin point, you may pass it here for slighly improved performance. If null, a search for the closest node will be done.</param>
- /// <param name="trace">If a list is passed, then it will be filled with all nodes along the line up until it hits an obstacle or reaches the end.</param>
- /// <param name="filter">If not null then the delegate will be called for each node and if it returns false the node will be treated as unwalkable and a hit will be returned.
- /// Note that unwalkable nodes are always treated as unwalkable regardless of what this filter returns.</param>
- public static bool Linecast (NavmeshBase graph, Vector3 origin, Vector3 end, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace, System.Func<GraphNode, bool> filter = null) {
- hit = new GraphHitInfo();
- if (float.IsNaN(origin.x + origin.y + origin.z)) throw new System.ArgumentException("origin is NaN");
- if (float.IsNaN(end.x + end.y + end.z)) throw new System.ArgumentException("end is NaN");
- var node = hint as TriangleMeshNode;
- if (node == null) {
- node = graph.GetNearest(origin, NNConstraintNoneXZ).node as TriangleMeshNode;
- if (node == null) {
- Debug.LogError("Could not find a valid node to start from");
- hit.origin = origin;
- hit.point = origin;
- return true;
- }
- }
- // Snap the origin to the navmesh
- var i3originInGraphSpace = node.ClosestPointOnNodeXZInGraphSpace(origin);
- hit.origin = graph.transform.Transform((Vector3)i3originInGraphSpace);
- if (!node.Walkable || (filter != null && !filter(node))) {
- hit.node = node;
- hit.point = hit.origin;
- hit.tangentOrigin = hit.origin;
- return true;
- }
- var endInGraphSpace = graph.transform.InverseTransform(end);
- var i3endInGraphSpace = (Int3)endInGraphSpace;
- // Fast early out check
- if (i3originInGraphSpace == i3endInGraphSpace) {
- hit.point = hit.origin;
- hit.node = node;
- if (trace != null) trace.Add(node);
- return false;
- }
- int counter = 0;
- while (true) {
- counter++;
- if (counter > 2000) {
- Debug.LogError("Linecast was stuck in infinite loop. Breaking.");
- return true;
- }
- if (trace != null) trace.Add(node);
- Int3 a0, a1, a2;
- node.GetVerticesInGraphSpace(out a0, out a1, out a2);
- int sideOfLine = (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a0);
- sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a1) << 2;
- sideOfLine |= (byte)VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, a2) << 4;
- // Use a lookup table to figure out which side of this triangle that the ray exits
- int shapeEdgeA = (int)LinecastShapeEdgeLookup[sideOfLine];
- // The edge consists of the vertex with index 'sharedEdgeA' and the next vertex after that (index '(sharedEdgeA+1)%3')
- var sideNodeExit = VectorMath.SideXZ(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2), shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0), i3endInGraphSpace);
- if (sideNodeExit != Side.Left) {
- // Ray stops before it leaves the current node.
- // The endpoint must be inside the current node.
- hit.point = end;
- hit.node = node;
- var endNode = graph.GetNearest(end, NNConstraintNoneXZ).node as TriangleMeshNode;
- if (endNode == node || endNode == null) {
- // We ended up at the right node.
- // If endNode == null we also take this branch.
- // That case may happen if a linecast is made to a point, but the point way a very large distance straight up into the air.
- // The linecast may indeed reach the right point, but it's so far away up into the air that the GetNearest method will stop searching.
- return false;
- } else {
- // The closest node to the end point was not the node we ended up at.
- // This can happen if a linecast is done between two floors of a building.
- // The linecast may reach the right location when seen from above
- // but it will have ended up on the wrong floor of the building.
- // This indicates that the start and end points cannot be connected by a valid straight line on the navmesh.
- return true;
- }
- }
- if (shapeEdgeA == 0xFF) {
- // Line does not intersect node at all?
- // 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.
- Debug.LogError("Line does not intersect node at all");
- hit.node = node;
- hit.point = hit.tangentOrigin = hit.origin;
- return true;
- } else {
- bool success = false;
- var nodeConnections = node.connections;
- // Check all node connetions to see which one is the next node along the ray's path
- for (int i = 0; i < nodeConnections.Length; i++) {
- if (nodeConnections[i].shapeEdge == shapeEdgeA) {
- // This might be the next node that we enter
- var neighbour = nodeConnections[i].node as TriangleMeshNode;
- if (neighbour == null || !neighbour.Walkable || (filter != null && !filter(neighbour))) continue;
- var neighbourConnections = neighbour.connections;
- int shapeEdgeB = -1;
- for (int j = 0; j < neighbourConnections.Length; j++) {
- if (neighbourConnections[j].node == node) {
- shapeEdgeB = neighbourConnections[j].shapeEdge;
- break;
- }
- }
- if (shapeEdgeB == -1) {
- // Connection was mono-directional!
- // This shouldn't normally happen on navmeshes (when the shapeEdge matches at least) unless a user has done something strange to the navmesh.
- continue;
- }
- var side1 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace(shapeEdgeB));
- var side2 = VectorMath.SideXZ(i3originInGraphSpace, i3endInGraphSpace, neighbour.GetVertexInGraphSpace((shapeEdgeB+1) % 3));
- // Check if the line enters this edge
- success = (side1 == Side.Right || side1 == Side.Colinear) && (side2 == Side.Left || side2 == Side.Colinear);
- if (!success) continue;
- // Ray has entered the neighbouring node.
- // After the first node, it is possible to prove the loop invariant that shapeEdgeA will *never* end up as -1 (checked above)
- // Since side = Colinear acts essentially as a wildcard. side1 and side2 can be the most restricted if they are side1=right, side2=left.
- // 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.
- // We are looking for the sequence [Left, Right] (possibly including Colinear as wildcard). We will always find this sequence regardless of the value of *.
- node = neighbour;
- break;
- }
- }
- if (!success) {
- // Node did not enter any neighbours
- // It must have hit the border of the navmesh
- var hitEdgeStartInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a0 : (shapeEdgeA == 1 ? a1 : a2));
- var hitEdgeEndInGraphSpace = (Vector3)(shapeEdgeA == 0 ? a1 : (shapeEdgeA == 1 ? a2 : a0));
- var intersectionInGraphSpace = VectorMath.LineIntersectionPointXZ(hitEdgeStartInGraphSpace, hitEdgeEndInGraphSpace, (Vector3)i3originInGraphSpace, (Vector3)i3endInGraphSpace);
- hit.point = graph.transform.Transform(intersectionInGraphSpace);
- hit.node = node;
- var hitEdgeStart = graph.transform.Transform(hitEdgeStartInGraphSpace);
- var hitEdgeEnd = graph.transform.Transform(hitEdgeEndInGraphSpace);
- hit.tangent = hitEdgeEnd - hitEdgeStart;
- hit.tangentOrigin = hitEdgeStart;
- return true;
- }
- }
- }
- }
- public override void OnDrawGizmos (Pathfinding.Util.RetainedGizmos gizmos, bool drawNodes) {
- if (!drawNodes) {
- return;
- }
- using (var helper = gizmos.GetSingleFrameGizmoHelper(active)) {
- var bounds = new Bounds();
- bounds.SetMinMax(Vector3.zero, forcedBoundsSize);
- // Draw a write cube using the latest transform
- // (this makes the bounds update immediately if some field is changed in the editor)
- helper.builder.DrawWireCube(CalculateTransform(), bounds, Color.white);
- }
- if (tiles != null && (showMeshSurface || showMeshOutline || showNodeConnections)) {
- var baseHasher = new RetainedGizmos.Hasher(active);
- baseHasher.AddHash(showMeshOutline ? 1 : 0);
- baseHasher.AddHash(showMeshSurface ? 1 : 0);
- baseHasher.AddHash(showNodeConnections ? 1 : 0);
- int startTileIndex = 0;
- var hasher = baseHasher;
- var hashedNodes = 0;
- // Update navmesh vizualizations for
- // the tiles that have been changed
- for (int i = 0; i < tiles.Length; i++) {
- // This may happen if an exception has been thrown when the graph was scanned.
- // We don't want the gizmo code to start to throw exceptions as well then as
- // that would obscure the actual source of the error.
- if (tiles[i] == null) continue;
- // Calculate a hash of the tile
- var nodes = tiles[i].nodes;
- for (int j = 0; j < nodes.Length; j++) {
- hasher.HashNode(nodes[j]);
- }
- hashedNodes += nodes.Length;
- // Note: do not batch more than some large number of nodes at a time.
- // Also do not batch more than a single "row" of the graph at once
- // because otherwise a small change in one part of the graph could invalidate
- // the caches almost everywhere else.
- // When restricting the caches to row by row a change in a row
- // will never invalidate the cache in another row.
- if (hashedNodes > 1024 || (i % tileXCount) == tileXCount - 1 || i == tiles.Length - 1) {
- if (!gizmos.Draw(hasher)) {
- using (var helper = gizmos.GetGizmoHelper(active, hasher)) {
- if (showMeshSurface || showMeshOutline) {
- CreateNavmeshSurfaceVisualization(tiles, startTileIndex, i + 1, helper);
- CreateNavmeshOutlineVisualization(tiles, startTileIndex, i + 1, helper);
- }
- if (showNodeConnections) {
- for (int ti = startTileIndex; ti <= i; ti++) {
- if (tiles[ti] == null) continue;
- var tileNodes = tiles[ti].nodes;
- for (int j = 0; j < tileNodes.Length; j++) {
- helper.DrawConnections(tileNodes[j]);
- }
- }
- }
- }
- }
- gizmos.Draw(hasher);
- startTileIndex = i + 1;
- hasher = baseHasher;
- hashedNodes = 0;
- }
- }
- }
- if (active.showUnwalkableNodes) DrawUnwalkableNodes(active.unwalkableNodeDebugSize);
- }
- /// <summary>Creates a mesh of the surfaces of the navmesh for use in OnDrawGizmos in the editor</summary>
- void CreateNavmeshSurfaceVisualization (NavmeshTile[] tiles, int startTile, int endTile, GraphGizmoHelper helper) {
- int numNodes = 0;
- for (int i = startTile; i < endTile; i++) if (tiles[i] != null) numNodes += tiles[i].nodes.Length;
- // Vertex array might be a bit larger than necessary, but that's ok
- var vertices = ArrayPool<Vector3>.Claim(numNodes*3);
- var colors = ArrayPool<Color>.Claim(numNodes*3);
- int offset = 0;
- for (int i = startTile; i < endTile; i++) {
- var tile = tiles[i];
- if (tile == null) continue;
- for (int j = 0; j < tile.nodes.Length; j++) {
- var node = tile.nodes[j];
- Int3 v0, v1, v2;
- node.GetVertices(out v0, out v1, out v2);
- int index = offset + j*3;
- vertices[index + 0] = (Vector3)v0;
- vertices[index + 1] = (Vector3)v1;
- vertices[index + 2] = (Vector3)v2;
- var color = helper.NodeColor(node);
- colors[index + 0] = colors[index + 1] = colors[index + 2] = color;
- }
- offset += tile.nodes.Length * 3;
- }
- if (showMeshSurface) helper.DrawTriangles(vertices, colors, numNodes);
- if (showMeshOutline) helper.DrawWireTriangles(vertices, colors, numNodes);
- // Return lists to the pool
- ArrayPool<Vector3>.Release(ref vertices);
- ArrayPool<Color>.Release(ref colors);
- }
- /// <summary>Creates an outline of the navmesh for use in OnDrawGizmos in the editor</summary>
- static void CreateNavmeshOutlineVisualization (NavmeshTile[] tiles, int startTile, int endTile, GraphGizmoHelper helper) {
- var sharedEdges = new bool[3];
- for (int i = startTile; i < endTile; i++) {
- var tile = tiles[i];
- if (tile == null) continue;
- for (int j = 0; j < tile.nodes.Length; j++) {
- sharedEdges[0] = sharedEdges[1] = sharedEdges[2] = false;
- var node = tile.nodes[j];
- for (int c = 0; c < node.connections.Length; c++) {
- var other = node.connections[c].node as TriangleMeshNode;
- // Loop through neighbours to figure out which edges are shared
- if (other != null && other.GraphIndex == node.GraphIndex) {
- for (int v = 0; v < 3; v++) {
- for (int v2 = 0; v2 < 3; v2++) {
- if (node.GetVertexIndex(v) == other.GetVertexIndex((v2+1)%3) && node.GetVertexIndex((v+1)%3) == other.GetVertexIndex(v2)) {
- // Found a shared edge with the other node
- sharedEdges[v] = true;
- v = 3;
- break;
- }
- }
- }
- }
- }
- var color = helper.NodeColor(node);
- for (int v = 0; v < 3; v++) {
- if (!sharedEdges[v]) {
- helper.builder.DrawLine((Vector3)node.GetVertex(v), (Vector3)node.GetVertex((v+1)%3), color);
- }
- }
- }
- }
- }
- /// <summary>
- /// Serializes Node Info.
- /// Should serialize:
- /// - Base
- /// - Node Flags
- /// - Node Penalties
- /// - Node
- /// - Node Positions (if applicable)
- /// - Any other information necessary to load the graph in-game
- /// All settings marked with json attributes (e.g JsonMember) have already been
- /// saved as graph settings and do not need to be handled here.
- ///
- /// It is not necessary for this implementation to be forward or backwards compatible.
- ///
- /// See:
- /// </summary>
- protected override void SerializeExtraInfo (GraphSerializationContext ctx) {
- BinaryWriter writer = ctx.writer;
- if (tiles == null) {
- writer.Write(-1);
- return;
- }
- writer.Write(tileXCount);
- writer.Write(tileZCount);
- for (int z = 0; z < tileZCount; z++) {
- for (int x = 0; x < tileXCount; x++) {
- NavmeshTile tile = tiles[x + z*tileXCount];
- if (tile == null) {
- throw new System.Exception("NULL Tile");
- //writer.Write (-1);
- //continue;
- }
- writer.Write(tile.x);
- writer.Write(tile.z);
- if (tile.x != x || tile.z != z) continue;
- writer.Write(tile.w);
- writer.Write(tile.d);
- writer.Write(tile.tris.Length);
- for (int i = 0; i < tile.tris.Length; i++) writer.Write(tile.tris[i]);
- writer.Write(tile.verts.Length);
- for (int i = 0; i < tile.verts.Length; i++) {
- ctx.SerializeInt3(tile.verts[i]);
- }
- writer.Write(tile.vertsInGraphSpace.Length);
- for (int i = 0; i < tile.vertsInGraphSpace.Length; i++) {
- ctx.SerializeInt3(tile.vertsInGraphSpace[i]);
- }
- writer.Write(tile.nodes.Length);
- for (int i = 0; i < tile.nodes.Length; i++) {
- tile.nodes[i].SerializeNode(ctx);
- }
- }
- }
- }
- protected override void DeserializeExtraInfo (GraphSerializationContext ctx) {
- BinaryReader reader = ctx.reader;
- tileXCount = reader.ReadInt32();
- if (tileXCount < 0) return;
- tileZCount = reader.ReadInt32();
- transform = CalculateTransform();
- tiles = new NavmeshTile[tileXCount * tileZCount];
- //Make sure mesh nodes can reference this graph
- TriangleMeshNode.SetNavmeshHolder((int)ctx.graphIndex, this);
- for (int z = 0; z < tileZCount; z++) {
- for (int x = 0; x < tileXCount; x++) {
- int tileIndex = x + z*tileXCount;
- int tx = reader.ReadInt32();
- if (tx < 0) throw new System.Exception("Invalid tile coordinates (x < 0)");
- int tz = reader.ReadInt32();
- if (tz < 0) throw new System.Exception("Invalid tile coordinates (z < 0)");
- // This is not the origin of a large tile. Refer back to that tile.
- if (tx != x || tz != z) {
- tiles[tileIndex] = tiles[tz*tileXCount + tx];
- continue;
- }
- var tile = tiles[tileIndex] = new NavmeshTile {
- x = tx,
- z = tz,
- w = reader.ReadInt32(),
- d = reader.ReadInt32(),
- bbTree = ObjectPool<BBTree>.Claim(),
- graph = this,
- };
- int trisCount = reader.ReadInt32();
- if (trisCount % 3 != 0) throw new System.Exception("Corrupt data. Triangle indices count must be divisable by 3. Read " + trisCount);
- tile.tris = new int[trisCount];
- for (int i = 0; i < tile.tris.Length; i++) tile.tris[i] = reader.ReadInt32();
- tile.verts = new Int3[reader.ReadInt32()];
- for (int i = 0; i < tile.verts.Length; i++) {
- tile.verts[i] = ctx.DeserializeInt3();
- }
- if (ctx.meta.version.Major >= 4) {
- tile.vertsInGraphSpace = new Int3[reader.ReadInt32()];
- if (tile.vertsInGraphSpace.Length != tile.verts.Length) throw new System.Exception("Corrupt data. Array lengths did not match");
- for (int i = 0; i < tile.verts.Length; i++) {
- tile.vertsInGraphSpace[i] = ctx.DeserializeInt3();
- }
- } else {
- // Compatibility
- tile.vertsInGraphSpace = new Int3[tile.verts.Length];
- tile.verts.CopyTo(tile.vertsInGraphSpace, 0);
- transform.InverseTransform(tile.vertsInGraphSpace);
- }
- int nodeCount = reader.ReadInt32();
- tile.nodes = new TriangleMeshNode[nodeCount];
- // Prepare for storing in vertex indices
- tileIndex <<= TileIndexOffset;
- for (int i = 0; i < tile.nodes.Length; i++) {
- var node = new TriangleMeshNode(active);
- tile.nodes[i] = node;
- node.DeserializeNode(ctx);
- node.v0 = tile.tris[i*3+0] | tileIndex;
- node.v1 = tile.tris[i*3+1] | tileIndex;
- node.v2 = tile.tris[i*3+2] | tileIndex;
- node.UpdatePositionFromVertices();
- }
- tile.bbTree.RebuildFrom(tile.nodes);
- }
- }
- }
- protected override void PostDeserialization (GraphSerializationContext ctx) {
- // Compatibility
- if (ctx.meta.version < AstarSerializer.V4_1_0 && tiles != null) {
- Dictionary<TriangleMeshNode, Connection[]> conns = tiles.SelectMany(s => s.nodes).ToDictionary(n => n, n => n.connections ?? new Connection[0]);
- // We need to recalculate all connections when upgrading data from earlier than 4.1.0
- // as the connections now need information about which edge was used.
- // This may remove connections for e.g off-mesh links.
- foreach (var tile in tiles) CreateNodeConnections(tile.nodes);
- foreach (var tile in tiles) ConnectTileWithNeighbours(tile);
- // Restore any connections that were contained in the serialized file but didn't get added by the method calls above
- GetNodes(node => {
- var triNode = node as TriangleMeshNode;
- foreach (var conn in conns[triNode].Where(conn => !triNode.ContainsConnection(conn.node)).ToList()) {
- triNode.AddConnection(conn.node, conn.cost, conn.shapeEdge);
- }
- });
- }
- // Make sure that the transform is up to date.
- // It is assumed that the current graph settings correspond to the correct
- // transform as it is not serialized itself.
- transform = CalculateTransform();
- }
- }
- }
|