public override void CreateNodes (int number) { TriangleMeshNode[] tmp = new TriangleMeshNode[number]; for (int i=0;i<number;i++) { tmp[i] = new TriangleMeshNode (active); tmp[i].Penalty = initialPenalty; } }
public override IEnumerable <Progress> ScanInternal() { TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this); if (!Application.isPlaying) { RelevantGraphSurface.FindAllGraphSurfaces(); } RelevantGraphSurface.UpdateAllPositions(); foreach (Progress progress in this.ScanAllTiles()) { yield return(progress); } IEnumerator <Progress> enumerator = null; yield break; yield break; yield break; }
public override void ScanInternal(OnScanStatus statusCallback) { if (sourceMesh == null) { return; } GenerateMatrix(); //float startTime = 0;//Time.realtimeSinceStartup; Vector3[] vectorVertices = sourceMesh.vertices; triangles = sourceMesh.triangles; TriangleMeshNode.SetNavmeshHolder(active.astarData.GetGraphIndex(this), this); GenerateNodes(vectorVertices, triangles, out originalVertices, out _vertices); }
public bool IsVertex(Int3 p, out int index) { INavmeshHolder navmeshHolder = TriangleMeshNode.GetNavmeshHolder(base.GraphIndex); index = -1; if (navmeshHolder.GetVertex(this.v0).IsEqualXZ(ref p)) { index = 0; } else if (navmeshHolder.GetVertex(this.v1).IsEqualXZ(ref p)) { index = 1; } else if (navmeshHolder.GetVertex(this.v2).IsEqualXZ(ref p)) { index = 2; } return(index != -1); }
// Token: 0x060025F4 RID: 9716 RVA: 0x001A3488 File Offset: 0x001A1688 private void SearchBoxClosestXZ(int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) { BBTree.BBTreeBox bbtreeBox = this.tree[boxi]; if (bbtreeBox.IsLeaf) { TriangleMeshNode[] array = this.nodeLookup; for (int i = 0; i < 4; i++) { if (array[bbtreeBox.nodeOffset + i] == null) { return; } TriangleMeshNode triangleMeshNode = array[bbtreeBox.nodeOffset + i]; if (constraint == null || constraint.Suitable(triangleMeshNode)) { Vector3 vector = triangleMeshNode.ClosestPointOnNodeXZ(p); float num = (vector.x - p.x) * (vector.x - p.x) + (vector.z - p.z) * (vector.z - p.z); if (nnInfo.constrainedNode == null || num < closestSqrDist - 1E-06f || (num <= closestSqrDist + 1E-06f && Mathf.Abs(vector.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) { nnInfo.constrainedNode = triangleMeshNode; nnInfo.constClampedPosition = vector; closestSqrDist = num; } } } } else { int left = bbtreeBox.left; int right = bbtreeBox.right; float num2; float num3; this.GetOrderedChildren(ref left, ref right, out num2, out num3, p); if (num2 <= closestSqrDist) { this.SearchBoxClosestXZ(left, p, ref closestSqrDist, constraint, ref nnInfo); } if (num3 <= closestSqrDist) { this.SearchBoxClosestXZ(right, p, ref closestSqrDist, constraint, ref nnInfo); } } }
private TriangleMeshNode checkObjIntersects(ref int edge, Int3 start, Int3 end, int gridX, int gridY) { List <object> objs = this.rasterizer.GetObjs(gridX, gridY); if (objs == null || objs.Count == 0) { return(null); } Int3[] array = new Int3[3]; TriangleMeshNode triangleMeshNode = null; int num = -1; long num2 = 9223372036854775807L; for (int i = 0; i < objs.Count; i++) { TriangleMeshNode triangleMeshNode2 = objs[i] as TriangleMeshNode; triangleMeshNode2.GetPoints(out array[0], out array[1], out array[2]); for (int j = 0; j < 3; j++) { int num3 = j; int num4 = (j + 1) % 3; if (Polygon.Intersects(array[num3], array[num4], start, end)) { bool flag; Int3 vInt = Polygon.IntersectionPoint(ref array[num3], ref array[num4], ref start, ref end, out flag); long num5 = start.XZSqrMagnitude(ref vInt); if (num5 < num2) { num2 = num5; triangleMeshNode = triangleMeshNode2; num = j; } } } } if (num != -1 && triangleMeshNode != null) { edge = num; return(triangleMeshNode); } return(null); }
public static void BuildFunnelCorridor(INavmesh graph, List <GraphNode> path, int startIndex, int endIndex, List <Vector3> left, List <Vector3> right) { if (graph == null) { Debug.LogError("Couldn't cast graph to the appropriate type (graph isn't a Navmesh type graph, it doesn't implement the INavmesh interface)"); return; } for (int i = startIndex; i < endIndex; i++) { TriangleMeshNode triangleMeshNode = path[i] as TriangleMeshNode; TriangleMeshNode triangleMeshNode2 = path[i + 1] as TriangleMeshNode; bool flag = true; int j; for (j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { if (triangleMeshNode.GetVertexIndex(j) == triangleMeshNode2.GetVertexIndex((k + 1) % 3) && triangleMeshNode.GetVertexIndex((j + 1) % 3) == triangleMeshNode2.GetVertexIndex(k)) { flag = false; break; } } if (!flag) { break; } } if (j == 3) { left.Add((Vector3)triangleMeshNode.position); right.Add((Vector3)triangleMeshNode.position); left.Add((Vector3)triangleMeshNode2.position); right.Add((Vector3)triangleMeshNode2.position); } else { left.Add((Vector3)triangleMeshNode.GetVertex(j)); right.Add((Vector3)triangleMeshNode.GetVertex((j + 1) % 3)); } } }
private TriangleMeshNode SearchBoxInside(int boxi, Vector3 p, NNConstraint constraint) { BBTree.BBTreeBox bbtreeBox = this.tree[boxi]; if (bbtreeBox.IsLeaf) { TriangleMeshNode[] array = this.nodeLookup; int num = 0; while (num < 4 && array[bbtreeBox.nodeOffset + num] != null) { TriangleMeshNode triangleMeshNode = array[bbtreeBox.nodeOffset + num]; if (triangleMeshNode.ContainsPoint((Int3)p)) { if (constraint == null || constraint.Suitable(triangleMeshNode)) { return(triangleMeshNode); } } num++; } } else { if (this.tree[bbtreeBox.left].Contains(p)) { TriangleMeshNode triangleMeshNode2 = this.SearchBoxInside(bbtreeBox.left, p, constraint); if (triangleMeshNode2 != null) { return(triangleMeshNode2); } } if (this.tree[bbtreeBox.right].Contains(p)) { TriangleMeshNode triangleMeshNode3 = this.SearchBoxInside(bbtreeBox.right, p, constraint); if (triangleMeshNode3 != null) { return(triangleMeshNode3); } } } return(null); }
private void SearchBoxClosest(int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) { BBTree.BBTreeBox bbtreeBox = this.tree[boxi]; if (bbtreeBox.IsLeaf) { TriangleMeshNode[] array = this.nodeLookup; int num = 0; while (num < 4 && array[bbtreeBox.nodeOffset + num] != null) { TriangleMeshNode triangleMeshNode = array[bbtreeBox.nodeOffset + num]; Vector3 vector = triangleMeshNode.ClosestPointOnNode(p); float sqrMagnitude = (vector - p).sqrMagnitude; if (sqrMagnitude < closestSqrDist) { if (constraint == null || constraint.Suitable(triangleMeshNode)) { nnInfo.constrainedNode = triangleMeshNode; nnInfo.constClampedPosition = vector; closestSqrDist = sqrMagnitude; } } num++; } } else { int left = bbtreeBox.left; int right = bbtreeBox.right; float num2; float num3; this.GetOrderedChildren(ref left, ref right, out num2, out num3, p); if (num2 < closestSqrDist) { this.SearchBoxClosest(left, p, ref closestSqrDist, constraint, ref nnInfo); } if (num3 < closestSqrDist) { this.SearchBoxClosest(right, p, ref closestSqrDist, constraint, ref nnInfo); } } }
private void SearchBoxClosestXZ(int boxi, Vector3 p, ref float closestSqrDist, NNConstraint constraint, ref NNInfoInternal nnInfo) { BBTree.BBTreeBox bbtreeBox = this.tree[boxi]; if (bbtreeBox.IsLeaf) { TriangleMeshNode[] array = this.nodeLookup; int num = 0; while (num < 4 && array[bbtreeBox.nodeOffset + num] != null) { TriangleMeshNode triangleMeshNode = array[bbtreeBox.nodeOffset + num]; if (constraint == null || constraint.Suitable(triangleMeshNode)) { Vector3 constClampedPosition = triangleMeshNode.ClosestPointOnNodeXZ(p); float num2 = (constClampedPosition.x - p.x) * (constClampedPosition.x - p.x) + (constClampedPosition.z - p.z) * (constClampedPosition.z - p.z); if (nnInfo.constrainedNode == null || num2 < closestSqrDist - 1E-06f || (num2 <= closestSqrDist + 1E-06f && Mathf.Abs(constClampedPosition.y - p.y) < Mathf.Abs(nnInfo.constClampedPosition.y - p.y))) { nnInfo.constrainedNode = triangleMeshNode; nnInfo.constClampedPosition = constClampedPosition; closestSqrDist = num2; } } num++; } } else { int left = bbtreeBox.left; int right = bbtreeBox.right; float num3; float num4; this.GetOrderedChildren(ref left, ref right, out num3, out num4, p); if (num3 <= closestSqrDist) { this.SearchBoxClosestXZ(left, p, ref closestSqrDist, constraint, ref nnInfo); } if (num4 <= closestSqrDist) { this.SearchBoxClosestXZ(right, p, ref closestSqrDist, constraint, ref nnInfo); } } }
private TriangleMeshNode checkObjIntersects(ref int edge, VInt3 start, VInt3 end, int gridX, int gridY) { List <object> objs = this.rasterizer.GetObjs(gridX, gridY); if ((objs != null) && (objs.Count != 0)) { VInt3[] numArray = new VInt3[3]; TriangleMeshNode node = null; int num = -1; long num2 = 0x7fffffffffffffffL; for (int i = 0; i < objs.Count; i++) { TriangleMeshNode node2 = objs[i] as TriangleMeshNode; node2.GetPoints(out numArray[0], out numArray[1], out numArray[2]); for (int j = 0; j < 3; j++) { int index = j; int num6 = (j + 1) % 3; if (Polygon.Intersects(numArray[index], numArray[num6], start, end)) { bool flag; VInt3 rhs = Polygon.IntersectionPoint(ref numArray[index], ref numArray[num6], ref start, ref end, out flag); DebugHelper.Assert(flag); long num8 = start.XZSqrMagnitude(ref rhs); if (num8 < num2) { num2 = num8; node = node2; num = j; } } } } if ((num != -1) && (node != null)) { edge = num; return(node); } } return(null); }
public override IEnumerable <Progress> ScanInternal() { this.transform = this.CalculateTransform(); this.tileZCount = (this.tileXCount = 1); this.tiles = new NavmeshTile[this.tileZCount * this.tileXCount]; TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this); if (this.sourceMesh == null) { base.FillWithEmptyTiles(); yield break; } yield return(new Progress(0f, "Transforming Vertices")); this.forcedBoundsSize = this.sourceMesh.bounds.size * this.scale; Vector3[] vertices = this.sourceMesh.vertices; List <Int3> intVertices = ListPool <Int3> .Claim(vertices.Length); Matrix4x4 matrix4x = Matrix4x4.TRS(-this.sourceMesh.bounds.min * this.scale, Quaternion.identity, Vector3.one * this.scale); for (int i = 0; i < vertices.Length; i++) { intVertices.Add((Int3)matrix4x.MultiplyPoint3x4(vertices[i])); } yield return(new Progress(0.1f, "Compressing Vertices")); Int3[] compressedVertices = null; int[] compressedTriangles = null; Polygon.CompressMesh(intVertices, new List <int>(this.sourceMesh.triangles), out compressedVertices, out compressedTriangles); ListPool <Int3> .Release(intVertices); yield return(new Progress(0.2f, "Building Nodes")); base.ReplaceTile(0, 0, compressedVertices, compressedTriangles); if (this.OnRecalculatedTiles != null) { this.OnRecalculatedTiles(this.tiles.Clone() as NavmeshTile[]); } yield break; yield break; }
// Token: 0x060025F9 RID: 9721 RVA: 0x001A379C File Offset: 0x001A199C private TriangleMeshNode SearchBoxInside(int boxi, Vector3 p, NNConstraint constraint) { BBTree.BBTreeBox bbtreeBox = this.tree[boxi]; if (bbtreeBox.IsLeaf) { TriangleMeshNode[] array = this.nodeLookup; for (int i = 0; i < 4; i++) { if (array[bbtreeBox.nodeOffset + i] == null) { break; } TriangleMeshNode triangleMeshNode = array[bbtreeBox.nodeOffset + i]; if (triangleMeshNode.ContainsPoint((Int3)p) && (constraint == null || constraint.Suitable(triangleMeshNode))) { return(triangleMeshNode); } } } else { if (this.tree[bbtreeBox.left].Contains(p)) { TriangleMeshNode triangleMeshNode2 = this.SearchBoxInside(bbtreeBox.left, p, constraint); if (triangleMeshNode2 != null) { return(triangleMeshNode2); } } if (this.tree[bbtreeBox.right].Contains(p)) { TriangleMeshNode triangleMeshNode3 = this.SearchBoxInside(bbtreeBox.right, p, constraint); if (triangleMeshNode3 != null) { return(triangleMeshNode3); } } } return(null); }
public bool CheckSegmentIntersects(Int3 start, Int3 end, int gridX, int gridY, out Int3 outPoint, out TriangleMeshNode nearestNode) { List <object> objs = this.rasterizer.GetObjs(gridX, gridY); outPoint = end; nearestNode = null; if (objs == null || objs.Count == 0) { return(false); } Int3[] array = new Int3[3]; bool result = false; long num = 9223372036854775807L; for (int i = 0; i < objs.Count; i++) { TriangleMeshNode triangleMeshNode = objs[i] as TriangleMeshNode; triangleMeshNode.GetPoints(out array[0], out array[1], out array[2]); for (int j = 0; j < 3; j++) { int num2 = j; int num3 = (j + 1) % 3; bool flag = false; Int3 vInt = Polygon.SegmentIntersectionPoint(array[num2], array[num3], start, end, out flag); if (flag) { long num4 = start.XZSqrMagnitude(ref vInt); if (num4 < num) { nearestNode = triangleMeshNode; num = num4; outPoint = vInt; result = true; } } } } return(result); }
protected override IEnumerable <Progress> ScanInternal() { TriangleMeshNode.SetNavmeshHolder(AstarPath.active.data.GetGraphIndex(this), this); if (!Application.isPlaying) { RelevantGraphSurface.FindAllGraphSurfaces(); } RelevantGraphSurface.UpdateAllPositions(); foreach (var progress in ScanAllTiles()) { yield return(progress); } #if DEBUG_REPLAY DebugReplay.WriteToFile(); #endif }
public TriangleMeshNode GetNearestByRasterizer(Int3 position, out Int3 clampedPosition) { clampedPosition = Int3.zero; if (this.rasterizer == null) { return(null); } TriangleMeshNode triangleMeshNode = this.GetLocatedByRasterizer(position); if (triangleMeshNode != null) { clampedPosition = position; return(triangleMeshNode); } triangleMeshNode = this.FindNearestByRasterizer(position, -1); if (triangleMeshNode == null) { return(null); } clampedPosition = triangleMeshNode.ClosestPointOnNodeXZ(position); return(triangleMeshNode); }
public override void DeserializeExtraInfo(GraphSerializationContext ctx) { uint graphIndex = ctx.graphIndex; TriangleMeshNode.SetNavmeshHolder((int)graphIndex, this); int nodeCount = ctx.reader.ReadInt32(); int vertexCount = ctx.reader.ReadInt32(); if (nodeCount == -1) { nodes = new TriangleMeshNode[0]; _vertices = new Int3[0]; originalVertices = new Vector3[0]; return; } nodes = new TriangleMeshNode[nodeCount]; _vertices = new Int3[vertexCount]; originalVertices = new Vector3[vertexCount]; for (int i = 0; i < vertexCount; i++) { _vertices[i] = ctx.DeserializeInt3(); originalVertices[i] = ctx.DeserializeVector3(); } bbTree = new BBTree(); for (int i = 0; i < nodeCount; i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i]; node.DeserializeNode(ctx); node.UpdatePositionFromVertices(); } bbTree.RebuildFrom(nodes); }
//These functions are for serialization, the static ones are there so other graphs using mesh nodes can serialize them more easily public static byte[] SerializeMeshNodes(NavMeshGraph graph, GraphNode[] nodes) { System.IO.MemoryStream mem = new System.IO.MemoryStream(); System.IO.BinaryWriter stream = new System.IO.BinaryWriter(mem); for (int i = 0; i < nodes.Length; i++) { TriangleMeshNode node = nodes[i] as TriangleMeshNode; if (node == null) { Debug.LogError("Serialization Error : Couldn't cast the node to the appropriate type - NavMeshGenerator. Omitting node data."); return(null); } stream.Write(node.v0); stream.Write(node.v1); stream.Write(node.v2); } Int3[] vertices = graph.vertices; if (vertices == null) { vertices = new Int3[0]; } stream.Write(vertices.Length); for (int i = 0; i < vertices.Length; i++) { stream.Write(vertices[i].x); stream.Write(vertices[i].y); stream.Write(vertices[i].z); } stream.Close(); return(mem.ToArray()); }
private void UpdateFunnelCorridor(int splitIndex, TriangleMeshNode prefix) { if (splitIndex > 0) { this.nodes.RemoveRange(0, splitIndex - 1); this.nodes[0] = prefix; } else { this.nodes.Insert(0, prefix); } this.left.Clear(); this.right.Clear(); this.left.Add(this.exactStart); this.right.Add(this.exactStart); for (int i = 0; i < this.nodes.Count - 1; i++) { this.nodes[i].GetPortal(this.nodes[i + 1], this.left, this.right, false); } this.left.Add(this.exactEnd); this.right.Add(this.exactEnd); }
public TriangleMeshNode GetNeighborByEdge(int edge, out int otherEdge) { otherEdge = -1; if (edge < 0 || edge > 2 || this.connections == null) { return(null); } int vertexIndex = this.GetVertexIndex(edge % 3); int vertexIndex2 = this.GetVertexIndex((edge + 1) % 3); TriangleMeshNode result = null; for (int i = 0; i < this.connections.Length; i++) { TriangleMeshNode triangleMeshNode = this.connections[i] as TriangleMeshNode; if (triangleMeshNode != null && triangleMeshNode.GraphIndex == base.GraphIndex) { if (triangleMeshNode.v1 == vertexIndex && triangleMeshNode.v0 == vertexIndex2) { otherEdge = 0; } else if (triangleMeshNode.v2 == vertexIndex && triangleMeshNode.v1 == vertexIndex2) { otherEdge = 1; } else if (triangleMeshNode.v0 == vertexIndex && triangleMeshNode.v2 == vertexIndex2) { otherEdge = 2; } if (otherEdge != -1) { result = triangleMeshNode; break; } } } return(result); }
public override void DeserializeExtraInfo(GraphSerializationContext ctx) { uint graphIndex = (uint)ctx.graphIndex; TriangleMeshNode.SetNavmeshHolder((int)graphIndex, this); int c1 = ctx.reader.ReadInt32(); int c2 = ctx.reader.ReadInt32(); if (c1 == -1) { nodes = new TriangleMeshNode[0]; _vertices = new Int3[0]; originalVertices = new Vector3[0]; } nodes = new TriangleMeshNode[c1]; _vertices = new Int3[c2]; originalVertices = new Vector3[c2]; for (int i = 0; i < c2; i++) { _vertices[i] = new Int3(ctx.reader.ReadInt32(), ctx.reader.ReadInt32(), ctx.reader.ReadInt32()); originalVertices[i] = new Vector3(ctx.reader.ReadSingle(), ctx.reader.ReadSingle(), ctx.reader.ReadSingle()); } bbTree = new BBTree(this); for (int i = 0; i < c1; i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i]; node.DeserializeNode(ctx); node.UpdatePositionFromVertices(); bbTree.Insert(node); } }
public override void DeserializeExtraInfo(GraphSerializationContext ctx) { var graphIndex = (uint)active.astarData.GetGraphIndex(this); TriangleMeshNode.SetNavmeshHolder((int)graphIndex, this); var c1 = ctx.reader.ReadInt32(); var c2 = ctx.reader.ReadInt32(); if (c1 == -1) { nodes = new TriangleMeshNode[0]; _vertices = new Int3[0]; originalVertices = new Vector3[0]; } nodes = new TriangleMeshNode[c1]; _vertices = new Int3[c2]; originalVertices = new Vector3[c2]; for (var i = 0; i < c2; i++) { _vertices[i] = new Int3(ctx.reader.ReadInt32(), ctx.reader.ReadInt32(), ctx.reader.ReadInt32()); originalVertices[i] = new Vector3(ctx.reader.ReadSingle(), ctx.reader.ReadSingle(), ctx.reader.ReadSingle()); } for (var i = 0; i < c1; i++) { nodes[i] = new TriangleMeshNode(active); var node = nodes[i]; node.DeserializeNode(ctx); node.GraphIndex = graphIndex; node.UpdatePositionFromVertices(); } }
/** Generates a navmesh. Based on the supplied vertices and triangles */ void GenerateNodes (Vector3[] vectorVertices, int[] triangles, out Vector3[] originalVertices, out Int3[] vertices) { Profiler.BeginSample ("Init"); if (vectorVertices.Length == 0 || triangles.Length == 0) { originalVertices = vectorVertices; vertices = new Int3[0]; nodes = new TriangleMeshNode[0]; return; } vertices = new Int3[vectorVertices.Length]; int c = 0; for (int i=0;i<vertices.Length;i++) { vertices[i] = (Int3)matrix.MultiplyPoint3x4 (vectorVertices[i]); } var hashedVerts = new Dictionary<Int3,int> (); var newVertices = new int[vertices.Length]; Profiler.EndSample (); Profiler.BeginSample ("Hashing"); for (int i=0;i<vertices.Length;i++) { if (!hashedVerts.ContainsKey (vertices[i])) { newVertices[c] = i; hashedVerts.Add (vertices[i], c); c++; } } for (int x=0;x<triangles.Length;x++) { Int3 vertex = vertices[triangles[x]]; triangles[x] = hashedVerts[vertex]; } Int3[] totalIntVertices = vertices; vertices = new Int3[c]; originalVertices = new Vector3[c]; for (int i=0;i<c;i++) { vertices[i] = totalIntVertices[newVertices[i]]; originalVertices[i] = vectorVertices[newVertices[i]]; } Profiler.EndSample (); Profiler.BeginSample ("Constructing Nodes"); nodes = new TriangleMeshNode[triangles.Length/3]; int graphIndex = active.astarData.GetGraphIndex(this); // Does not have to set this, it is set in ScanInternal //TriangleMeshNode.SetNavmeshHolder ((int)graphIndex,this); for (int i=0;i<nodes.Length;i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i];//new MeshNode (); node.GraphIndex = (uint)graphIndex; node.Penalty = initialPenalty; node.Walkable = true; node.v0 = triangles[i*3]; node.v1 = triangles[i*3+1]; node.v2 = triangles[i*3+2]; if (!Polygon.IsClockwise (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { //Debug.DrawLine (vertices[node.v0],vertices[node.v1],Color.red); //Debug.DrawLine (vertices[node.v1],vertices[node.v2],Color.red); //Debug.DrawLine (vertices[node.v2],vertices[node.v0],Color.red); int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } if (Polygon.IsColinear (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.red); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.red); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.red); } // Make sure position is correctly set node.UpdatePositionFromVertices(); } Profiler.EndSample (); var sides = new Dictionary<Int2, TriangleMeshNode>(); for (int i=0, j=0;i<triangles.Length; j+=1, i+=3) { sides[new Int2(triangles[i+0],triangles[i+1])] = nodes[j]; sides[new Int2(triangles[i+1],triangles[i+2])] = nodes[j]; sides[new Int2(triangles[i+2],triangles[i+0])] = nodes[j]; } Profiler.BeginSample ("Connecting Nodes"); var connections = new List<MeshNode> (); var connectionCosts = new List<uint> (); for (int i=0, j = 0; i < triangles.Length; j+=1, i+=3) { connections.Clear (); connectionCosts.Clear (); TriangleMeshNode node = nodes[j]; for ( int q = 0; q < 3; q++ ) { TriangleMeshNode other; if (sides.TryGetValue ( new Int2 (triangles[i+((q+1)%3)], triangles[i+q]), out other ) ) { connections.Add (other); connectionCosts.Add ((uint)(node.position-other.position).costMagnitude); } } node.connections = connections.ToArray (); node.connectionCosts = connectionCosts.ToArray (); } Profiler.EndSample (); Profiler.BeginSample ("Rebuilding BBTree"); RebuildBBTree (this); Profiler.EndSample (); #if ASTARDEBUG for (int i=0;i<nodes.Length;i++) { TriangleMeshNode node = nodes[i] as TriangleMeshNode; float a1 = Polygon.TriangleArea2 ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],(Vector3)vertices[node.v2]); long a2 = Polygon.TriangleArea2 (vertices[node.v0],vertices[node.v1],vertices[node.v2]); if (a1 * a2 < 0) Debug.LogError (a1+ " " + a2); if (Polygon.IsClockwise (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.green); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.green); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.green); } else { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.red); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.red); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.red); } } #endif }
/** Returns if the point is inside the node in XZ space */ public static bool ContainsPoint (TriangleMeshNode node, Vector3 pos, Int3[] vertices) { if (!Polygon.IsClockwiseMargin ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1], (Vector3)vertices[node.v2])) { Debug.LogError ("Noes!"); } if ( Polygon.IsClockwiseMargin ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1], pos) && Polygon.IsClockwiseMargin ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2], pos) && Polygon.IsClockwiseMargin ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0], pos)) { return true; } return false; }
/** Generates a navmesh. Based on the supplied vertices and triangles. Memory usage is about O(n) */ public void GenerateNodes (Vector3[] vectorVertices, int[] triangles, out Vector3[] originalVertices, out Int3[] vertices) { if (vectorVertices.Length == 0 || triangles.Length == 0) { originalVertices = vectorVertices; vertices = new Int3[0]; //graph.CreateNodes (0); nodes = new TriangleMeshNode[0]; return; } vertices = new Int3[vectorVertices.Length]; //Backup the original vertices //for (int i=0;i<vectorVertices.Length;i++) { // vectorVertices[i] = graph.matrix.MultiplyPoint (vectorVertices[i]); //} int c = 0; /*int maxX = 0; int maxZ = 0; //Almost infinity int minX = 0xFFFFFFF; int minZ = 0xFFFFFFF;*/ for (int i=0;i<vertices.Length;i++) { vertices[i] = (Int3)matrix.MultiplyPoint3x4 (vectorVertices[i]); /*maxX = Mathfx.Max (vertices[i].x, maxX); maxZ = Mathfx.Max (vertices[i].z, maxZ); minX = Mathfx.Min (vertices[i].x, minX); minZ = Mathfx.Min (vertices[i].z, minZ);*/ } //maxX = maxX-minX; //maxZ = maxZ-minZ; Dictionary<Int3,int> hashedVerts = new Dictionary<Int3,int> (); int[] newVertices = new int[vertices.Length]; for (int i=0;i<vertices.Length-1;i++) { //int hash = Mathfx.ComputeVertexHash (vertices[i].x,vertices[i].y,vertices[i].z); //(vertices[i].x-minX)+(vertices[i].z-minX)*maxX+vertices[i].y*maxX*maxZ; //if (sortedVertices[i] != sortedVertices[i+1]) { if (!hashedVerts.ContainsKey (vertices[i])) { newVertices[c] = i; hashedVerts.Add (vertices[i], c); c++; }// else { //Debug.Log ("Hash Duplicate "+hash+" "+vertices[i].ToString ()); //} } newVertices[c] = vertices.Length-1; //int hash2 = (newVertices[c].x-minX)+(newVertices[c].z-minX)*maxX+newVertices[c].y*maxX*maxZ; //int hash2 = Mathfx.ComputeVertexHash (newVertices[c].x,newVertices[c].y,newVertices[c].z); if (!hashedVerts.ContainsKey (vertices[newVertices[c]])) { hashedVerts.Add (vertices[newVertices[c]], c); c++; } for (int x=0;x<triangles.Length;x++) { Int3 vertex = vertices[triangles[x]]; //int hash3 = (vertex.x-minX)+(vertex.z-minX)*maxX+vertex.y*maxX*maxZ; //int hash3 = Mathfx.ComputeVertexHash (vertex.x,vertex.y,vertex.z); //for (int y=0;y<newVertices.Length;y++) { triangles[x] = hashedVerts[vertex]; } /*for (int i=0;i<triangles.Length;i += 3) { Vector3 offset = Vector3.forward*i*0.01F; Debug.DrawLine (newVertices[triangles[i]]+offset,newVertices[triangles[i+1]]+offset,Color.blue); Debug.DrawLine (newVertices[triangles[i+1]]+offset,newVertices[triangles[i+2]]+offset,Color.blue); Debug.DrawLine (newVertices[triangles[i+2]]+offset,newVertices[triangles[i]]+offset,Color.blue); }*/ //Debug.Log ("NavMesh - Old vertice count "+vertices.Length+", new vertice count "+c+" "+maxX+" "+maxZ+" "+maxX*maxZ); Int3[] totalIntVertices = vertices; vertices = new Int3[c]; originalVertices = new Vector3[c]; for (int i=0;i<c;i++) { vertices[i] = totalIntVertices[newVertices[i]];//(Int3)graph.matrix.MultiplyPoint (vectorVertices[i]); originalVertices[i] = (Vector3)vectorVertices[newVertices[i]];//vectorVertices[newVertices[i]]; } //graph.CreateNodes (triangles.Length/3);//new Node[triangles.Length/3]; nodes = new TriangleMeshNode[triangles.Length/3]; for (int i=0;i<nodes.Length;i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i];//new MeshNode (); node.Penalty = initialPenalty; node.Walkable = true; node.v0 = triangles[i*3]; node.v1 = triangles[i*3+1]; node.v2 = triangles[i*3+2]; if (!Polygon.IsClockwise (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { //Debug.DrawLine (vertices[node.v0],vertices[node.v1],Color.red); //Debug.DrawLine (vertices[node.v1],vertices[node.v2],Color.red); //Debug.DrawLine (vertices[node.v2],vertices[node.v0],Color.red); int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } if (Polygon.IsColinear (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.red); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.red); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.red); } // Make sure position is correctly set node.UpdatePositionFromVertices(); } List<MeshNode> connections = new List<MeshNode> (); List<uint> connectionCosts = new List<uint> (); int identicalError = 0; for (int i=0;i<triangles.Length;i+=3) { connections.Clear (); connectionCosts.Clear (); //Int3 indices = new Int3(triangles[i],triangles[i+1],triangles[i+2]); TriangleMeshNode node = nodes[i/3]; for (int x=0;x<triangles.Length;x+=3) { if (x == i) { continue; } int count = 0; if (triangles[x] == triangles[i]) { count++; } if (triangles[x+1] == triangles[i]) { count++; } if (triangles[x+2] == triangles[i]) { count++; } if (triangles[x] == triangles[i+1]) { count++; } if (triangles[x+1] == triangles[i+1]) { count++; } if (triangles[x+2] == triangles[i+1]) { count++; } if (triangles[x] == triangles[i+2]) { count++; } if (triangles[x+1] == triangles[i+2]) { count++; } if (triangles[x+2] == triangles[i+2]) { count++; } if (count >= 3) { identicalError++; Debug.DrawLine ((Vector3)vertices[triangles[x]],(Vector3)vertices[triangles[x+1]],Color.red); Debug.DrawLine ((Vector3)vertices[triangles[x]],(Vector3)vertices[triangles[x+2]],Color.red); Debug.DrawLine ((Vector3)vertices[triangles[x+2]],(Vector3)vertices[triangles[x+1]],Color.red); } if (count == 2) { GraphNode other = nodes[x/3]; connections.Add (other as MeshNode); connectionCosts.Add ((uint)(node.position-other.position).costMagnitude); } } node.connections = connections.ToArray (); node.connectionCosts = connectionCosts.ToArray (); } if (identicalError > 0) { Debug.LogError ("One or more triangles are identical to other triangles, this is not a good thing to have in a navmesh\nIncreasing the scale of the mesh might help\nNumber of triangles with error: "+identicalError+"\n"); } RebuildBBTree (this); #if ASTARDEBUG for (int i=0;i<nodes.Length;i++) { TriangleMeshNode node = nodes[i] as TriangleMeshNode; float a1 = Polygon.TriangleArea2 ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],(Vector3)vertices[node.v2]); long a2 = Polygon.TriangleArea2 (vertices[node.v0],vertices[node.v1],vertices[node.v2]); if (a1 * a2 < 0) Debug.LogError (a1+ " " + a2); if (Polygon.IsClockwise (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.green); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.green); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.green); } else { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.red); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.red); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.red); } } #endif //Debug.Log ("Graph Generation - NavMesh - Time to compute graph "+((Time.realtimeSinceStartup-startTime)*1000F).ToString ("0")+"ms"); }
/** Returns the closest point of the node */ public static Vector3 ClosestPointOnNode(TriangleMeshNode node, Int3[] vertices, Vector3 pos) { return(Polygon.ClosestPointOnTriangle((Vector3)vertices[node.v0], (Vector3)vertices[node.v1], (Vector3)vertices[node.v2], pos)); }
/** Returns if the point is inside the node in XZ space */ public bool ContainsPoint (TriangleMeshNode node, Vector3 pos) { if ( Polygon.IsClockwise ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1], pos) && Polygon.IsClockwise ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2], pos) && Polygon.IsClockwise ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0], pos)) { return true; } return false; }
public bool ContainsPoint(TriangleMeshNode node, Vector3 pos) { return Polygon.IsClockwise((Vector3)this.GetVertex(node.v0), (Vector3)this.GetVertex(node.v1), pos) && Polygon.IsClockwise((Vector3)this.GetVertex(node.v1), (Vector3)this.GetVertex(node.v2), pos) && Polygon.IsClockwise((Vector3)this.GetVertex(node.v2), (Vector3)this.GetVertex(node.v0), pos); }
private RecastGraph.NavmeshTile CreateTile(Voxelize vox, VoxelMesh mesh, int x, int z) { if (mesh.tris == null) { throw new ArgumentNullException("mesh.tris"); } if (mesh.verts == null) { throw new ArgumentNullException("mesh.verts"); } RecastGraph.NavmeshTile navmeshTile = new RecastGraph.NavmeshTile(); navmeshTile.x = x; navmeshTile.z = z; navmeshTile.w = 1; navmeshTile.d = 1; navmeshTile.tris = mesh.tris; navmeshTile.verts = mesh.verts; navmeshTile.bbTree = new BBTree(); if (navmeshTile.tris.Length % 3 != 0) { throw new ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)"); } if (navmeshTile.verts.Length >= 4095) { throw new ArgumentException("Too many vertices per tile (more than " + 4095 + ").\nTry enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector"); } Dictionary<Int3, int> dictionary = this.cachedInt3_int_dict; dictionary.Clear(); int[] array = new int[navmeshTile.verts.Length]; int num = 0; for (int i = 0; i < navmeshTile.verts.Length; i++) { if (!dictionary.ContainsKey(navmeshTile.verts[i])) { dictionary.Add(navmeshTile.verts[i], num); array[i] = num; navmeshTile.verts[num] = navmeshTile.verts[i]; num++; } else { array[i] = dictionary[navmeshTile.verts[i]]; } } for (int j = 0; j < navmeshTile.tris.Length; j++) { navmeshTile.tris[j] = array[navmeshTile.tris[j]]; } Int3[] array2 = new Int3[num]; for (int k = 0; k < num; k++) { array2[k] = navmeshTile.verts[k]; } navmeshTile.verts = array2; TriangleMeshNode[] array3 = new TriangleMeshNode[navmeshTile.tris.Length / 3]; navmeshTile.nodes = array3; int graphIndex = AstarPath.active.astarData.graphs.Length; TriangleMeshNode.SetNavmeshHolder(graphIndex, navmeshTile); int num2 = x + z * this.tileXCount; num2 <<= 12; for (int l = 0; l < array3.Length; l++) { TriangleMeshNode triangleMeshNode = new TriangleMeshNode(this.active); array3[l] = triangleMeshNode; triangleMeshNode.GraphIndex = (uint)graphIndex; triangleMeshNode.v0 = (navmeshTile.tris[l * 3] | num2); triangleMeshNode.v1 = (navmeshTile.tris[l * 3 + 1] | num2); triangleMeshNode.v2 = (navmeshTile.tris[l * 3 + 2] | num2); if (!Polygon.IsClockwise(triangleMeshNode.GetVertex(0), triangleMeshNode.GetVertex(1), triangleMeshNode.GetVertex(2))) { int v = triangleMeshNode.v0; triangleMeshNode.v0 = triangleMeshNode.v2; triangleMeshNode.v2 = v; } triangleMeshNode.Walkable = true; triangleMeshNode.Penalty = this.initialPenalty; triangleMeshNode.UpdatePositionFromVertices(); } navmeshTile.bbTree.RebuildFrom(array3); this.CreateNodeConnections(navmeshTile.nodes); TriangleMeshNode.SetNavmeshHolder(graphIndex, null); return navmeshTile; }
/** Split funnel at node index \a splitIndex and throw the nodes up to that point away and replace with \a prefix. * Used when the AI has happened to get sidetracked and entered a node outside the funnel. */ public void UpdateFunnelCorridor (int splitIndex, TriangleMeshNode prefix) { if (splitIndex > 0) { nodes.RemoveRange(0,splitIndex-1); //This is a node which should be removed, we replace it with the prefix nodes[0] = prefix; } else { nodes.Insert(0,prefix); } left.Clear(); right.Clear(); left.Add(exactStart); right.Add(exactStart); for (int i=0;i<nodes.Count-1;i++) { //NOTE should use return value in future versions nodes[i].GetPortal (nodes[i+1],left,right,false); } left.Add(exactEnd); right.Add(exactEnd); }
/** Generates a navmesh. Based on the supplied vertices and triangles. Memory usage is about O(n) */ public void GenerateNodes (Vector3[] vectorVertices, int[] triangles, out Vector3[] originalVertices, out Int3[] vertices) { Profiler.BeginSample ("Init"); if (vectorVertices.Length == 0 || triangles.Length == 0) { originalVertices = vectorVertices; vertices = new Int3[0]; //graph.CreateNodes (0); nodes = new TriangleMeshNode[0]; return; } vertices = new Int3[vectorVertices.Length]; //Backup the original vertices //for (int i=0;i<vectorVertices.Length;i++) { // vectorVertices[i] = graph.matrix.MultiplyPoint (vectorVertices[i]); //} int c = 0; for (int i=0;i<vertices.Length;i++) { vertices[i] = (Int3)matrix.MultiplyPoint3x4 (vectorVertices[i]); } Dictionary<Int3,int> hashedVerts = new Dictionary<Int3,int> (); int[] newVertices = new int[vertices.Length]; Profiler.EndSample (); Profiler.BeginSample ("Hashing"); for (int i=0;i<vertices.Length;i++) { if (!hashedVerts.ContainsKey (vertices[i])) { newVertices[c] = i; hashedVerts.Add (vertices[i], c); c++; }// else { //Debug.Log ("Hash Duplicate "+hash+" "+vertices[i].ToString ()); //} } /*newVertices[c] = vertices.Length-1; if (!hashedVerts.ContainsKey (vertices[newVertices[c]])) { hashedVerts.Add (vertices[newVertices[c]], c); c++; }*/ for (int x=0;x<triangles.Length;x++) { Int3 vertex = vertices[triangles[x]]; triangles[x] = hashedVerts[vertex]; } /*for (int i=0;i<triangles.Length;i += 3) { Vector3 offset = Vector3.forward*i*0.01F; Debug.DrawLine (newVertices[triangles[i]]+offset,newVertices[triangles[i+1]]+offset,Color.blue); Debug.DrawLine (newVertices[triangles[i+1]]+offset,newVertices[triangles[i+2]]+offset,Color.blue); Debug.DrawLine (newVertices[triangles[i+2]]+offset,newVertices[triangles[i]]+offset,Color.blue); }*/ Int3[] totalIntVertices = vertices; vertices = new Int3[c]; originalVertices = new Vector3[c]; for (int i=0;i<c;i++) { vertices[i] = totalIntVertices[newVertices[i]];//(Int3)graph.matrix.MultiplyPoint (vectorVertices[i]); originalVertices[i] = (Vector3)vectorVertices[newVertices[i]];//vectorVertices[newVertices[i]]; } Profiler.EndSample (); Profiler.BeginSample ("Constructing Nodes"); //graph.CreateNodes (triangles.Length/3);//new Node[triangles.Length/3]; nodes = new TriangleMeshNode[triangles.Length/3]; for (int i=0;i<nodes.Length;i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i];//new MeshNode (); node.Penalty = initialPenalty; node.Walkable = true; node.v0 = triangles[i*3]; node.v1 = triangles[i*3+1]; node.v2 = triangles[i*3+2]; if (!Polygon.IsClockwise (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { //Debug.DrawLine (vertices[node.v0],vertices[node.v1],Color.red); //Debug.DrawLine (vertices[node.v1],vertices[node.v2],Color.red); //Debug.DrawLine (vertices[node.v2],vertices[node.v0],Color.red); int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } if (Polygon.IsColinear (vertices[node.v0],vertices[node.v1],vertices[node.v2])) { Debug.DrawLine ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],Color.red); Debug.DrawLine ((Vector3)vertices[node.v1],(Vector3)vertices[node.v2],Color.red); Debug.DrawLine ((Vector3)vertices[node.v2],(Vector3)vertices[node.v0],Color.red); } // Make sure position is correctly set node.UpdatePositionFromVertices(); } Profiler.EndSample (); Dictionary<Int2,TriangleMeshNode> sides = new Dictionary<Int2, TriangleMeshNode>(); for (int i=0, j=0;i<triangles.Length; j+=1, i+=3) { sides[new Int2(triangles[i+0],triangles[i+1])] = nodes[j]; sides[new Int2(triangles[i+1],triangles[i+2])] = nodes[j]; sides[new Int2(triangles[i+2],triangles[i+0])] = nodes[j]; } Profiler.BeginSample ("Connecting Nodes"); List<MeshNode> connections = new List<MeshNode> (); List<uint> connectionCosts = new List<uint> (); int identicalError = 0; for (int i=0, j=0;i<triangles.Length; j+=1, i+=3) { connections.Clear (); connectionCosts.Clear (); //Int3 indices = new Int3(triangles[i],triangles[i+1],triangles[i+2]); TriangleMeshNode node = nodes[j]; for ( int q = 0; q < 3; q++ ) { TriangleMeshNode other; if (sides.TryGetValue ( new Int2 (triangles[i+((q+1)%3)], triangles[i+q]), out other ) ) { connections.Add (other); connectionCosts.Add ((uint)(node.position-other.position).costMagnitude); } } node.connections = connections.ToArray (); node.connectionCosts = connectionCosts.ToArray (); } if (identicalError > 0) { Debug.LogError ("One or more triangles are identical to other triangles, this is not a good thing to have in a navmesh\nIncreasing the scale of the mesh might help\nNumber of triangles with error: "+identicalError+"\n"); } Profiler.EndSample (); Profiler.BeginSample ("Rebuilding BBTree"); RebuildBBTree (this); Profiler.EndSample (); //Debug.Log ("Graph Generation - NavMesh - Time to compute graph "+((Time.realtimeSinceStartup-startTime)*1000F).ToString ("0")+"ms"); }
/** Returns the closest point of the node */ public Vector3 ClosestPointOnNode (TriangleMeshNode node, Vector3 pos) { return Polygon.ClosestPointOnTriangle ((Vector3)GetVertex(node.v0),(Vector3)GetVertex(node.v1),(Vector3)GetVertex(node.v2),pos); }
/** Returns if the point is inside the node in XZ space */ public bool ContainsPoint (TriangleMeshNode node, Vector3 pos) { if ( Polygon.IsClockwise ((Vector3)GetVertex(node.v0),(Vector3)GetVertex(node.v1), pos) && Polygon.IsClockwise ((Vector3)GetVertex(node.v1),(Vector3)GetVertex(node.v2), pos) && Polygon.IsClockwise ((Vector3)GetVertex(node.v2),(Vector3)GetVertex(node.v0), pos)) { return true; } return false; }
private void CreateNodeConnections(TriangleMeshNode[] nodes) { List<MeshNode> list = ListPool<MeshNode>.Claim(); List<uint> list2 = ListPool<uint>.Claim(); Dictionary<Int2, int> dictionary = this.cachedInt2_int_dict; dictionary.Clear(); for (int i = 0; i < nodes.Length; i++) { TriangleMeshNode triangleMeshNode = nodes[i]; int vertexCount = triangleMeshNode.GetVertexCount(); for (int j = 0; j < vertexCount; j++) { Int2 key = new Int2(triangleMeshNode.GetVertexIndex(j), triangleMeshNode.GetVertexIndex((j + 1) % vertexCount)); if (!dictionary.ContainsKey(key)) { dictionary.Add(key, i); } } } for (int k = 0; k < nodes.Length; k++) { TriangleMeshNode triangleMeshNode2 = nodes[k]; list.Clear(); list2.Clear(); int vertexCount2 = triangleMeshNode2.GetVertexCount(); for (int l = 0; l < vertexCount2; l++) { int vertexIndex = triangleMeshNode2.GetVertexIndex(l); int vertexIndex2 = triangleMeshNode2.GetVertexIndex((l + 1) % vertexCount2); int num; if (dictionary.TryGetValue(new Int2(vertexIndex2, vertexIndex), out num)) { TriangleMeshNode triangleMeshNode3 = nodes[num]; int vertexCount3 = triangleMeshNode3.GetVertexCount(); for (int m = 0; m < vertexCount3; m++) { if (triangleMeshNode3.GetVertexIndex(m) == vertexIndex2 && triangleMeshNode3.GetVertexIndex((m + 1) % vertexCount3) == vertexIndex) { uint costMagnitude = (uint)(triangleMeshNode2.position - triangleMeshNode3.position).costMagnitude; list.Add(triangleMeshNode3); list2.Add(costMagnitude); break; } } } } triangleMeshNode2.connections = list.ToArray(); triangleMeshNode2.connectionCosts = list2.ToArray(); } ListPool<MeshNode>.Release(list); ListPool<uint>.Release(list2); }
public Vector3 Update(Vector3 position, List <Vector3> buffer, int numCorners, out bool lastCorner, out bool requiresRepath) { lastCorner = false; requiresRepath = false; var i3Pos = (Int3)position; if (nodes[currentNode].Destroyed) { requiresRepath = true; lastCorner = false; buffer.Add(position); return(position); } // Check if we are in the same node as we were in during the last frame if (nodes[currentNode].ContainsPoint(i3Pos)) { // Only check for destroyed nodes every 10 frames if (checkForDestroyedNodesCounter >= 10) { checkForDestroyedNodesCounter = 0; // Loop through all nodes and check if they are destroyed // If so, we really need a recalculation of our path quickly // since there might be an obstacle blocking our path after // a graph update or something similar for (int i = 0, t = nodes.Count; i < t; i++) { if (nodes[i].Destroyed) { requiresRepath = true; break; } } } else { checkForDestroyedNodesCounter++; } } else { // This part of the code is relatively seldom called // Most of the time we are still on the same node as during the previous frame // Otherwise check the 2 nodes ahead and 2 nodes back // If they contain the node in XZ space, then we probably moved into those nodes bool found = false; // 2 nodes ahead for (int i = currentNode + 1, t = System.Math.Min(currentNode + 3, nodes.Count); i < t && !found; i++) { // If the node is destroyed, make sure we recalculate a new path quickly if (nodes[i].Destroyed) { requiresRepath = true; lastCorner = false; buffer.Add(position); return(position); } // We found a node which contains our current position in XZ space if (nodes[i].ContainsPoint(i3Pos)) { currentNode = i; found = true; } } // 2 nodes behind for (int i = currentNode - 1, t = System.Math.Max(currentNode - 3, 0); i > t && !found; i--) { if (nodes[i].Destroyed) { requiresRepath = true; lastCorner = false; buffer.Add(position); return(position); } if (nodes[i].ContainsPoint(i3Pos)) { currentNode = i; found = true; } } if (!found) { int closestNodeInPath = 0; int closestIsNeighbourOf = 0; float closestDist = float.PositiveInfinity; bool closestIsInPath = false; TriangleMeshNode closestNode = null; int containingIndex = nodes.Count - 1; // If we still couldn't find a good node // Check all nodes in the whole path // We are checking for if any node is destroyed in the loop // So we can reset this counter checkForDestroyedNodesCounter = 0; for (int i = 0, t = nodes.Count; i < t; i++) { if (nodes[i].Destroyed) { requiresRepath = true; lastCorner = false; buffer.Add(position); return(position); } Vector3 close = nodes[i].ClosestPointOnNode(position); float d = (close - position).sqrMagnitude; if (d < closestDist) { closestDist = d; closestNodeInPath = i; closestNode = nodes[i]; closestIsInPath = true; } } // Loop through all neighbours of all nodes in the path // and find the closet point on them // We cannot just look on the ones in the path since it is impossible // to know if we are outside the navmesh completely or if we have just // stepped in to an adjacent node // Need to make a copy here, the JIT will move this variable to the heap // because it is used inside a delegate, if we didn't make a copy here // we would *always* allocate 24 bytes (sizeof(position)) on the heap every time // this method was called // now we only do it when this IF statement is executed var posCopy = position; GraphNodeDelegate del = node => { // Check so that this neighbour we are processing is neither the node after the current node or the node before the current node in the path // This is done for optimization, we have already checked those nodes earlier if (!(containingIndex > 0 && node == nodes[containingIndex - 1]) && !(containingIndex < nodes.Count - 1 && node == nodes[containingIndex + 1])) { // Check if the neighbour was a mesh node var mn = node as TriangleMeshNode; if (mn != null) { // Find the distance to the closest point on it from our current position var close = mn.ClosestPointOnNode(posCopy); float d = (close - posCopy).sqrMagnitude; // Is that distance better than the best distance seen so far if (d < closestDist) { closestDist = d; closestIsNeighbourOf = containingIndex; closestNode = mn; closestIsInPath = false; } } } }; // Loop through all the nodes in the path in reverse order // The callback needs to know about the index, so we store it // in a local variable which it can read for (; containingIndex >= 0; containingIndex--) { // Loop through all neighbours of the node nodes[containingIndex].GetConnections(del); } // Check if the closest node // was on the path already or if we need to adjust it if (closestIsInPath) { // If we have found a node // Snap to the closest point in XZ space (keep the Y coordinate) // If we would have snapped to the closest point in 3D space, the agent // might slow down when traversing slopes currentNode = closestNodeInPath; position = nodes[closestNodeInPath].ClosestPointOnNodeXZ(position); } else { // Snap to the closest point in XZ space on the node position = closestNode.ClosestPointOnNodeXZ(position); // We have found a node containing the position, but it is outside the funnel // Recalculate the funnel to include this node exactStart = position; UpdateFunnelCorridor(closestIsNeighbourOf, closestNode); // Restart from the first node in the updated path currentNode = 0; } } } currentPosition = position; if (!FindNextCorners(position, currentNode, buffer, numCorners, out lastCorner)) { Debug.LogError("Oh oh"); buffer.Add(position); return(position); } return(position); }
/** Generates a navmesh. Based on the supplied vertices and triangles. Memory usage is about O(n) */ void GenerateNodes(Vector3[] vectorVertices, int[] triangles, out Vector3[] originalVertices, out Int3[] vertices) { Profiler.BeginSample("Init"); if (vectorVertices.Length == 0 || triangles.Length == 0) { originalVertices = vectorVertices; vertices = new Int3[0]; //graph.CreateNodes (0); nodes = new TriangleMeshNode[0]; return; } vertices = new Int3[vectorVertices.Length]; //Backup the original vertices //for (int i=0;i<vectorVertices.Length;i++) { // vectorVertices[i] = graph.matrix.MultiplyPoint (vectorVertices[i]); //} int c = 0; for (int i = 0; i < vertices.Length; i++) { vertices[i] = (Int3)matrix.MultiplyPoint3x4(vectorVertices[i]); } Dictionary <Int3, int> hashedVerts = new Dictionary <Int3, int> (); int[] newVertices = new int[vertices.Length]; Profiler.EndSample(); Profiler.BeginSample("Hashing"); for (int i = 0; i < vertices.Length; i++) { if (!hashedVerts.ContainsKey(vertices[i])) { newVertices[c] = i; hashedVerts.Add(vertices[i], c); c++; } // else { //Debug.Log ("Hash Duplicate "+hash+" "+vertices[i].ToString ()); //} } /*newVertices[c] = vertices.Length-1; * * if (!hashedVerts.ContainsKey (vertices[newVertices[c]])) { * * hashedVerts.Add (vertices[newVertices[c]], c); * c++; * }*/ for (int x = 0; x < triangles.Length; x++) { Int3 vertex = vertices[triangles[x]]; triangles[x] = hashedVerts[vertex]; } /*for (int i=0;i<triangles.Length;i += 3) { * * Vector3 offset = Vector3.forward*i*0.01F; * Debug.DrawLine (newVertices[triangles[i]]+offset,newVertices[triangles[i+1]]+offset,Color.blue); * Debug.DrawLine (newVertices[triangles[i+1]]+offset,newVertices[triangles[i+2]]+offset,Color.blue); * Debug.DrawLine (newVertices[triangles[i+2]]+offset,newVertices[triangles[i]]+offset,Color.blue); * }*/ Int3[] totalIntVertices = vertices; vertices = new Int3[c]; originalVertices = new Vector3[c]; for (int i = 0; i < c; i++) { vertices[i] = totalIntVertices[newVertices[i]]; //(Int3)graph.matrix.MultiplyPoint (vectorVertices[i]); originalVertices[i] = (Vector3)vectorVertices[newVertices[i]]; //vectorVertices[newVertices[i]]; } Profiler.EndSample(); Profiler.BeginSample("Constructing Nodes"); //graph.CreateNodes (triangles.Length/3);//new Node[triangles.Length/3]; nodes = new TriangleMeshNode[triangles.Length / 3]; int graphIndex = active.astarData.GetGraphIndex(this); // Does not have to set this, it is set in ScanInternal //TriangleMeshNode.SetNavmeshHolder ((int)graphIndex,this); for (int i = 0; i < nodes.Length; i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i]; //new MeshNode (); node.GraphIndex = (uint)graphIndex; node.Penalty = initialPenalty; node.Walkable = true; node.v0 = triangles[i * 3]; node.v1 = triangles[i * 3 + 1]; node.v2 = triangles[i * 3 + 2]; if (!Polygon.IsClockwise(vertices[node.v0], vertices[node.v1], vertices[node.v2])) { //Debug.DrawLine (vertices[node.v0],vertices[node.v1],Color.red); //Debug.DrawLine (vertices[node.v1],vertices[node.v2],Color.red); //Debug.DrawLine (vertices[node.v2],vertices[node.v0],Color.red); int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } if (Polygon.IsColinear(vertices[node.v0], vertices[node.v1], vertices[node.v2])) { Debug.DrawLine((Vector3)vertices[node.v0], (Vector3)vertices[node.v1], Color.red); Debug.DrawLine((Vector3)vertices[node.v1], (Vector3)vertices[node.v2], Color.red); Debug.DrawLine((Vector3)vertices[node.v2], (Vector3)vertices[node.v0], Color.red); } // Make sure position is correctly set node.UpdatePositionFromVertices(); } Profiler.EndSample(); Dictionary <Int2, TriangleMeshNode> sides = new Dictionary <Int2, TriangleMeshNode>(); for (int i = 0, j = 0; i < triangles.Length; j += 1, i += 3) { sides[new Int2(triangles[i + 0], triangles[i + 1])] = nodes[j]; sides[new Int2(triangles[i + 1], triangles[i + 2])] = nodes[j]; sides[new Int2(triangles[i + 2], triangles[i + 0])] = nodes[j]; } Profiler.BeginSample("Connecting Nodes"); List <MeshNode> connections = new List <MeshNode> (); List <uint> connectionCosts = new List <uint> (); int identicalError = 0; for (int i = 0, j = 0; i < triangles.Length; j += 1, i += 3) { connections.Clear(); connectionCosts.Clear(); //Int3 indices = new Int3(triangles[i],triangles[i+1],triangles[i+2]); TriangleMeshNode node = nodes[j]; for (int q = 0; q < 3; q++) { TriangleMeshNode other; if (sides.TryGetValue(new Int2(triangles[i + ((q + 1) % 3)], triangles[i + q]), out other)) { connections.Add(other); connectionCosts.Add((uint)(node.position - other.position).costMagnitude); } } node.connections = connections.ToArray(); node.connectionCosts = connectionCosts.ToArray(); } if (identicalError > 0) { Debug.LogError("One or more triangles are identical to other triangles, this is not a good thing to have in a navmesh\nIncreasing the scale of the mesh might help\nNumber of triangles with error: " + identicalError + "\n"); } Profiler.EndSample(); Profiler.BeginSample("Rebuilding BBTree"); RebuildBBTree(this); Profiler.EndSample(); //Debug.Log ("Graph Generation - NavMesh - Time to compute graph "+((Time.realtimeSinceStartup-startTime)*1000F).ToString ("0")+"ms"); }
public void ReplaceTile(int x, int z, int w, int d, Int3[] verts, int[] tris, bool worldSpace) { if (x + w > this.tileXCount || z + d > this.tileZCount || x < 0 || z < 0) { throw new ArgumentException(string.Concat(new object[] { "Tile is placed at an out of bounds position or extends out of the graph bounds (", x, ", ", z, " [", w, ", ", d, "] ", this.tileXCount, " ", this.tileZCount, ")" })); } if (w < 1 || d < 1) { throw new ArgumentException(string.Concat(new object[] { "width and depth must be greater or equal to 1. Was ", w, ", ", d })); } for (int i = z; i < z + d; i++) { for (int j = x; j < x + w; j++) { RecastGraph.NavmeshTile navmeshTile = this.tiles[j + i * this.tileXCount]; if (navmeshTile != null) { this.RemoveConnectionsFromTile(navmeshTile); for (int k = 0; k < navmeshTile.nodes.Length; k++) { navmeshTile.nodes[k].Destroy(); } for (int l = navmeshTile.z; l < navmeshTile.z + navmeshTile.d; l++) { for (int m = navmeshTile.x; m < navmeshTile.x + navmeshTile.w; m++) { RecastGraph.NavmeshTile navmeshTile2 = this.tiles[m + l * this.tileXCount]; if (navmeshTile2 == null || navmeshTile2 != navmeshTile) { throw new Exception("This should not happen"); } if (l < z || l >= z + d || m < x || m >= x + w) { this.tiles[m + l * this.tileXCount] = RecastGraph.NewEmptyTile(m, l); if (this.batchTileUpdate) { this.batchUpdatedTiles.Add(m + l * this.tileXCount); } } else { this.tiles[m + l * this.tileXCount] = null; } } } } } } RecastGraph.NavmeshTile navmeshTile3 = new RecastGraph.NavmeshTile(); navmeshTile3.x = x; navmeshTile3.z = z; navmeshTile3.w = w; navmeshTile3.d = d; navmeshTile3.tris = tris; navmeshTile3.verts = verts; navmeshTile3.bbTree = new BBTree(); if (navmeshTile3.tris.Length % 3 != 0) { throw new ArgumentException("Triangle array's length must be a multiple of 3 (tris)"); } if (navmeshTile3.verts.Length > 65535) { throw new ArgumentException("Too many vertices per tile (more than 65535)"); } if (!worldSpace) { if (!Mathf.Approximately((float)(x * this.tileSizeX) * this.cellSize * 1000f, (float)Math.Round((double)((float)(x * this.tileSizeX) * this.cellSize * 1000f)))) { UnityEngine.Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize"); } if (!Mathf.Approximately((float)(z * this.tileSizeZ) * this.cellSize * 1000f, (float)Math.Round((double)((float)(z * this.tileSizeZ) * this.cellSize * 1000f)))) { UnityEngine.Debug.LogWarning("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize"); } Int3 rhs = (Int3)(new Vector3((float)(x * this.tileSizeX) * this.cellSize, 0f, (float)(z * this.tileSizeZ) * this.cellSize) + this.forcedBounds.min); for (int n = 0; n < verts.Length; n++) { verts[n] += rhs; } } TriangleMeshNode[] array = new TriangleMeshNode[navmeshTile3.tris.Length / 3]; navmeshTile3.nodes = array; int graphIndex = AstarPath.active.astarData.graphs.Length; TriangleMeshNode.SetNavmeshHolder(graphIndex, navmeshTile3); int num = x + z * this.tileXCount; num <<= 12; for (int num2 = 0; num2 < array.Length; num2++) { TriangleMeshNode triangleMeshNode = new TriangleMeshNode(this.active); array[num2] = triangleMeshNode; triangleMeshNode.GraphIndex = (uint)graphIndex; triangleMeshNode.v0 = (navmeshTile3.tris[num2 * 3] | num); triangleMeshNode.v1 = (navmeshTile3.tris[num2 * 3 + 1] | num); triangleMeshNode.v2 = (navmeshTile3.tris[num2 * 3 + 2] | num); if (!Polygon.IsClockwise(triangleMeshNode.GetVertex(0), triangleMeshNode.GetVertex(1), triangleMeshNode.GetVertex(2))) { int v = triangleMeshNode.v0; triangleMeshNode.v0 = triangleMeshNode.v2; triangleMeshNode.v2 = v; } triangleMeshNode.Walkable = true; triangleMeshNode.Penalty = this.initialPenalty; triangleMeshNode.UpdatePositionFromVertices(); } navmeshTile3.bbTree.RebuildFrom(array); this.CreateNodeConnections(navmeshTile3.nodes); for (int num3 = z; num3 < z + d; num3++) { for (int num4 = x; num4 < x + w; num4++) { this.tiles[num4 + num3 * this.tileXCount] = navmeshTile3; } } if (this.batchTileUpdate) { this.batchUpdatedTiles.Add(x + z * this.tileXCount); } else { this.ConnectTileWithNeighbours(navmeshTile3); } TriangleMeshNode.SetNavmeshHolder(graphIndex, null); graphIndex = AstarPath.active.astarData.GetGraphIndex(this); for (int num5 = 0; num5 < array.Length; num5++) { array[num5].GraphIndex = (uint)graphIndex; } }
public override void DeserializeExtraInfo (GraphSerializationContext ctx) { uint graphIndex = (uint)active.astarData.GetGraphIndex(this); TriangleMeshNode.SetNavmeshHolder ((int)graphIndex,this); int c1 = ctx.reader.ReadInt32(); int c2 = ctx.reader.ReadInt32(); if (c1 == -1) { nodes = new TriangleMeshNode[0]; _vertices = new Int3[0]; originalVertices = new Vector3[0]; } nodes = new TriangleMeshNode[c1]; _vertices = new Int3[c2]; originalVertices = new Vector3[c2]; for (int i=0;i<c2;i++) { _vertices[i] = new Int3(ctx.reader.ReadInt32(), ctx.reader.ReadInt32(), ctx.reader.ReadInt32()); originalVertices[i] = new Vector3(ctx.reader.ReadSingle(), ctx.reader.ReadSingle(), ctx.reader.ReadSingle()); } bbTree = new BBTree(this); for (int i=0;i<c1;i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i]; node.DeserializeNode(ctx); node.GraphIndex = graphIndex; node.UpdatePositionFromVertices(); bbTree.Insert (node); } }
public override void DeserializeExtraInfo(GraphSerializationContext ctx) { BinaryReader reader = ctx.reader; this.tileXCount = reader.ReadInt32(); if (this.tileXCount < 0) { return; } this.tileZCount = reader.ReadInt32(); this.tiles = new RecastGraph.NavmeshTile[this.tileXCount * this.tileZCount]; TriangleMeshNode.SetNavmeshHolder(ctx.graphIndex, this); for (int i = 0; i < this.tileZCount; i++) { for (int j = 0; j < this.tileXCount; j++) { int num = j + i * this.tileXCount; int num2 = reader.ReadInt32(); if (num2 < 0) { throw new Exception("Invalid tile coordinates (x < 0)"); } int num3 = reader.ReadInt32(); if (num3 < 0) { throw new Exception("Invalid tile coordinates (z < 0)"); } if (num2 != j || num3 != i) { this.tiles[num] = this.tiles[num3 * this.tileXCount + num2]; } else { RecastGraph.NavmeshTile navmeshTile = new RecastGraph.NavmeshTile(); navmeshTile.x = num2; navmeshTile.z = num3; navmeshTile.w = reader.ReadInt32(); navmeshTile.d = reader.ReadInt32(); navmeshTile.bbTree = new BBTree(); this.tiles[num] = navmeshTile; int num4 = reader.ReadInt32(); if (num4 % 3 != 0) { throw new Exception("Corrupt data. Triangle indices count must be divisable by 3. Got " + num4); } navmeshTile.tris = new int[num4]; for (int k = 0; k < navmeshTile.tris.Length; k++) { navmeshTile.tris[k] = reader.ReadInt32(); } navmeshTile.verts = new Int3[reader.ReadInt32()]; for (int l = 0; l < navmeshTile.verts.Length; l++) { navmeshTile.verts[l] = new Int3(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); } int num5 = reader.ReadInt32(); navmeshTile.nodes = new TriangleMeshNode[num5]; num <<= 12; for (int m = 0; m < navmeshTile.nodes.Length; m++) { TriangleMeshNode triangleMeshNode = new TriangleMeshNode(this.active); navmeshTile.nodes[m] = triangleMeshNode; triangleMeshNode.DeserializeNode(ctx); triangleMeshNode.v0 = (navmeshTile.tris[m * 3] | num); triangleMeshNode.v1 = (navmeshTile.tris[m * 3 + 1] | num); triangleMeshNode.v2 = (navmeshTile.tris[m * 3 + 2] | num); triangleMeshNode.UpdatePositionFromVertices(); } navmeshTile.bbTree.RebuildFrom(navmeshTile.nodes); } } } }
public override void DeserializeExtraInfo (GraphSerializationContext ctx) { uint graphIndex = (uint)ctx.graphIndex; TriangleMeshNode.SetNavmeshHolder ((int)graphIndex,this); int nodeCount = ctx.reader.ReadInt32(); int vertexCount = ctx.reader.ReadInt32(); if (nodeCount == -1) { nodes = new TriangleMeshNode[0]; _vertices = new Int3[0]; originalVertices = new Vector3[0]; } nodes = new TriangleMeshNode[nodeCount]; _vertices = new Int3[vertexCount]; originalVertices = new Vector3[vertexCount]; for (int i=0;i<vertexCount;i++) { _vertices[i] = new Int3(ctx.reader.ReadInt32(), ctx.reader.ReadInt32(), ctx.reader.ReadInt32()); originalVertices[i] = new Vector3(ctx.reader.ReadSingle(), ctx.reader.ReadSingle(), ctx.reader.ReadSingle()); } bbTree = new BBTree(); for (int i = 0; i < nodeCount;i++) { nodes[i] = new TriangleMeshNode(active); TriangleMeshNode node = nodes[i]; node.DeserializeNode(ctx); node.UpdatePositionFromVertices(); } bbTree.RebuildFrom (nodes); }
public bool ContainsPoint(TriangleMeshNode node, Vector3 pos) { return(node.ContainsPoint((Int3)pos)); }
/** Returns the closest point of the node */ public static Vector3 ClosestPointOnNode (TriangleMeshNode node, Int3[] vertices, Vector3 pos) { return Polygon.ClosestPointOnTriangle ((Vector3)vertices[node.v0],(Vector3)vertices[node.v1],(Vector3)vertices[node.v2],pos); }
/** Create a tile at tile index \a x, \a z from the mesh. * \version Since version 3.7.6 the implementation is thread safe */ NavmeshTile CreateTile(Voxelize vox, VoxelMesh mesh, int x, int z, int threadIndex) { if (mesh.tris == null) { throw new System.ArgumentNullException("mesh.tris"); } if (mesh.verts == null) { throw new System.ArgumentNullException("mesh.verts"); } if (mesh.tris.Length % 3 != 0) { throw new System.ArgumentException("Indices array's length must be a multiple of 3 (mesh.tris)"); } if (mesh.verts.Length >= VertexIndexMask) { if (tileXCount * tileZCount == 1) { throw new System.ArgumentException("Too many vertices per tile (more than " + VertexIndexMask + ")." + "\n<b>Try enabling tiling in the recast graph settings.</b>\n"); } else { throw new System.ArgumentException("Too many vertices per tile (more than " + VertexIndexMask + ")." + "\n<b>Try reducing tile size or enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector</b>"); } } // Create a new navmesh tile and assign its settings var tile = new NavmeshTile { x = x, z = z, w = 1, d = 1, tris = mesh.tris, bbTree = new BBTree(), graph = this, }; tile.vertsInGraphSpace = Utility.RemoveDuplicateVertices(mesh.verts, tile.tris); tile.verts = (Int3[])tile.vertsInGraphSpace.Clone(); transform.Transform(tile.verts); // Here we are faking a new graph // The tile is not added to any graphs yet, but to get the position queries from the nodes // to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph // and direct the position queries directly to the tile // The thread index is added to make sure that if multiple threads are calculating tiles at the same time // they will not use the same temporary graph index uint temporaryGraphIndex = (uint)(active.data.graphs.Length + threadIndex); if (temporaryGraphIndex > GraphNode.MaxGraphIndex) { // Multithreaded tile calculations use fake graph indices, see above. throw new System.Exception("Graph limit reached. Multithreaded recast calculations cannot be done because a few scratch graph indices are required."); } TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, tile); // We need to lock here because creating nodes is not thread safe // and we may be doing this from multiple threads at the same time tile.nodes = new TriangleMeshNode[tile.tris.Length / 3]; lock (active) { CreateNodes(tile.nodes, tile.tris, x + z * tileXCount, temporaryGraphIndex); } tile.bbTree.RebuildFrom(tile.nodes); CreateNodeConnections(tile.nodes); // Remove the fake graph TriangleMeshNode.SetNavmeshHolder((int)temporaryGraphIndex, null); return(tile); }
/** This performs a linear search through all polygons returning the closest one. * This will fill the NNInfo with .node for the closest node not necessarily complying with the NNConstraint, and .constrainedNode with the closest node * complying with the NNConstraint. * \see GetNearestForce(Node[],Int3[],Vector3,NNConstraint,bool) */ public static NNInfo GetNearestForceBoth(NavGraph graph, INavmeshHolder navmesh, Vector3 position, NNConstraint constraint, bool accurateNearestNode) { Int3 pos = (Int3)position; float minDist = -1; GraphNode minNode = null; float minConstDist = -1; GraphNode minConstNode = null; float maxDistSqr = constraint.constrainDistance ? AstarPath.active.maxNearestNodeDistanceSqr : float.PositiveInfinity; GraphNodeDelegateCancelable del = delegate(GraphNode _node) { TriangleMeshNode node = _node as TriangleMeshNode; if (accurateNearestNode) { Vector3 closest = node.ClosestPointOnNode(position); float dist = ((Vector3)pos - closest).sqrMagnitude; if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable(node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } else { if (!node.ContainsPoint((Int3)position)) { float dist = (node.position - pos).sqrMagnitude; if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable(node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } else { int dist = AstarMath.Abs(node.position.y - pos.y); if (minNode == null || dist < minDist) { minDist = dist; minNode = node; } if (dist < maxDistSqr && constraint.Suitable(node)) { if (minConstNode == null || dist < minConstDist) { minConstDist = dist; minConstNode = node; } } } } return(true); }; graph.GetNodes(del); NNInfo nninfo = new NNInfo(minNode); //Find the point closest to the nearest triangle if (nninfo.node != null) { TriangleMeshNode node = nninfo.node as TriangleMeshNode; //minNode2 as MeshNode; Vector3 clP = node.ClosestPointOnNode(position); nninfo.clampedPosition = clP; } nninfo.constrainedNode = minConstNode; if (nninfo.constrainedNode != null) { TriangleMeshNode node = nninfo.constrainedNode as TriangleMeshNode; //minNode2 as MeshNode; Vector3 clP = node.ClosestPointOnNode(position); nninfo.constClampedPosition = clP; } return(nninfo); }
public static void UpdateArea(GraphUpdateObject o, INavmesh graph) { //System.DateTime startTime = System.DateTime.UtcNow; Bounds bounds = o.bounds; Rect r = Rect.MinMaxRect(bounds.min.x, bounds.min.z, bounds.max.x, bounds.max.z); IntRect r2 = new IntRect( Mathf.FloorToInt(bounds.min.x * Int3.Precision), Mathf.FloorToInt(bounds.min.z * Int3.Precision), Mathf.FloorToInt(bounds.max.x * Int3.Precision), Mathf.FloorToInt(bounds.max.z * Int3.Precision) ); Int3 a = new Int3(r2.xmin, 0, r2.ymin); Int3 b = new Int3(r2.xmin, 0, r2.ymax); Int3 c = new Int3(r2.xmax, 0, r2.ymin); Int3 d = new Int3(r2.xmax, 0, r2.ymax); Int3 ia = (Int3)a; Int3 ib = (Int3)b; Int3 ic = (Int3)c; Int3 id = (Int3)d; //for (int i=0;i<nodes.Length;i++) { graph.GetNodes(delegate(GraphNode _node) { TriangleMeshNode node = _node as TriangleMeshNode; bool inside = false; int allLeft = 0; int allRight = 0; int allTop = 0; int allBottom = 0; for (int v = 0; v < 3; v++) { Int3 p = node.GetVertex(v); Vector3 vert = (Vector3)p; //Vector2 vert2D = new Vector2 (vert.x,vert.z); if (r2.Contains(p.x, p.z)) { //Debug.DrawRay (vert,Vector3.up*10,Color.yellow); inside = true; break; } if (vert.x < r.xMin) { allLeft++; } if (vert.x > r.xMax) { allRight++; } if (vert.z < r.yMin) { allTop++; } if (vert.z > r.yMax) { allBottom++; } } if (!inside) { if (allLeft == 3 || allRight == 3 || allTop == 3 || allBottom == 3) { return(true); } } //Debug.DrawLine ((Vector3)node.GetVertex(0),(Vector3)node.GetVertex(1),Color.yellow); //Debug.DrawLine ((Vector3)node.GetVertex(1),(Vector3)node.GetVertex(2),Color.yellow); //Debug.DrawLine ((Vector3)node.GetVertex(2),(Vector3)node.GetVertex(0),Color.yellow); for (int v = 0; v < 3; v++) { int v2 = v > 1 ? 0 : v + 1; Int3 vert1 = node.GetVertex(v); Int3 vert2 = node.GetVertex(v2); if (Polygon.Intersects(a, b, vert1, vert2)) { inside = true; break; } if (Polygon.Intersects(a, c, vert1, vert2)) { inside = true; break; } if (Polygon.Intersects(c, d, vert1, vert2)) { inside = true; break; } if (Polygon.Intersects(d, b, vert1, vert2)) { inside = true; break; } } if (node.ContainsPoint(ia) || node.ContainsPoint(ib) || node.ContainsPoint(ic) || node.ContainsPoint(id)) { inside = true; } if (!inside) { return(true); } o.WillUpdateNode(node); o.Apply(node); /*Debug.DrawLine ((Vector3)node.GetVertex(0),(Vector3)node.GetVertex(1),Color.blue); * Debug.DrawLine ((Vector3)node.GetVertex(1),(Vector3)node.GetVertex(2),Color.blue); * Debug.DrawLine ((Vector3)node.GetVertex(2),(Vector3)node.GetVertex(0),Color.blue); * Debug.Break ();*/ return(true); }); //System.DateTime endTime = System.DateTime.UtcNow; //float theTime = (endTime-startTime).Ticks*0.0001F; //Debug.Log ("Intersecting bounds with navmesh took "+theTime.ToString ("0.000")+" ms"); }
/** Create a tile at tile index \a x , \a z from the mesh. * \warning This implementation is not thread safe. It uses cached variables to improve performance */ NavmeshTile CreateTile (Voxelize vox, VoxelMesh mesh, int x, int z) { if (mesh.tris == null) throw new System.ArgumentNullException ("The mesh must be valid. tris is null."); if (mesh.verts == null) throw new System.ArgumentNullException ("The mesh must be valid. verts is null."); //Create a new navmesh tile and assign its settings NavmeshTile tile = new NavmeshTile(); tile.x = x; tile.z = z; tile.w = 1; tile.d = 1; tile.tris = mesh.tris; tile.verts = mesh.verts; tile.bbTree = new BBTree(tile); if (tile.tris.Length % 3 != 0) throw new System.ArgumentException ("Indices array's length must be a multiple of 3 (mesh.tris)"); if (tile.verts.Length >= VertexIndexMask) throw new System.ArgumentException ("Too many vertices per tile (more than "+VertexIndexMask+")." + "\nTry enabling ASTAR_RECAST_LARGER_TILES under the 'Optimizations' tab in the A* Inspector"); //Dictionary<Int3, int> firstVerts = new Dictionary<Int3, int> (); Dictionary<Int3, int> firstVerts = cachedInt3_int_dict; firstVerts.Clear(); int[] compressedPointers = new int[tile.verts.Length]; int count = 0; for (int i=0;i<tile.verts.Length;i++) { try { firstVerts.Add (tile.verts[i], count); compressedPointers[i] = count; tile.verts[count] = tile.verts[i]; count++; } catch { //There are some cases, rare but still there, that vertices are identical compressedPointers[i] = firstVerts[tile.verts[i]]; } } for (int i=0;i<tile.tris.Length;i++) { tile.tris[i] = compressedPointers[tile.tris[i]]; } Int3[] compressed = new Int3[count]; for (int i=0;i<count;i++) compressed[i] = tile.verts[i]; tile.verts = compressed; TriangleMeshNode[] nodes = new TriangleMeshNode[tile.tris.Length/3]; tile.nodes = nodes; //Here we are faking a new graph //The tile is not added to any graphs yet, but to get the position querys from the nodes //to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph //and direct the position queries directly to the tile int graphIndex = AstarPath.active.astarData.graphs.Length; TriangleMeshNode.SetNavmeshHolder (graphIndex, tile); //This index will be ORed to the triangle indices int tileIndex = x + z*tileXCount; tileIndex <<= TileIndexOffset; //Create nodes and assign triangle indices for (int i=0;i<nodes.Length;i++) { TriangleMeshNode node = new TriangleMeshNode(active); nodes[i] = node; node.GraphIndex = (uint)graphIndex; node.v0 = tile.tris[i*3+0] | tileIndex; node.v1 = tile.tris[i*3+1] | tileIndex; node.v2 = tile.tris[i*3+2] | tileIndex; //Degenerate triangles might ocurr, but they will not cause any large troubles anymore //if (Polygon.IsColinear (node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { // Debug.Log ("COLINEAR!!!!!!"); //} //Make sure the triangle is clockwise if (!Polygon.IsClockwise (node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } node.Walkable = true; node.Penalty = initialPenalty; node.UpdatePositionFromVertices(); tile.bbTree.Insert (node); } CreateNodeConnections (tile.nodes); //Remove the fake graph TriangleMeshNode.SetNavmeshHolder (graphIndex, null); return tile; }
/** Create connections between all nodes. * \warning This implementation is not thread safe. It uses cached variables to improve performance */ void CreateNodeConnections (TriangleMeshNode[] nodes) { List<MeshNode> connections = Pathfinding.Util.ListPool<MeshNode>.Claim (); //new List<MeshNode>(); List<uint> connectionCosts = Pathfinding.Util.ListPool<uint>.Claim (); //new List<uint>(); Dictionary<Int2,int> nodeRefs = cachedInt2_int_dict; 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) try { nodeRefs.Add (new Int2 (node.GetVertexIndex(a), node.GetVertexIndex ((a+1) % av)), i); } catch (System.Exception) { } } } for (int i=0;i<nodes.Length;i++) { TriangleMeshNode node = nodes[i]; connections.Clear (); connectionCosts.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++) { /** \todo This will fail on edges which are only partially shared */ if (other.GetVertexIndex (b) == second && other.GetVertexIndex ((b+1) % bv) == first) { uint cost = (uint)(node.position - other.position).costMagnitude; connections.Add (other); connectionCosts.Add (cost); break; } } } } node.connections = connections.ToArray (); node.connectionCosts = connectionCosts.ToArray (); } Pathfinding.Util.ListPool<MeshNode>.Release (connections); Pathfinding.Util.ListPool<uint>.Release (connectionCosts); }
public void ReplaceTile (int x, int z, int w, int d, Int3[] verts, int[] tris, bool worldSpace) { 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 (w < 1 || d < 1) throw new System.ArgumentException ("width and depth must be greater or equal to 1"); //Remove previous tiles for (int cz=z; cz < z+d;cz++) { for (int cx=x; cx < x+w;cx++) { NavmeshTile otile = tiles[cx + cz*tileXCount]; if (otile == null) continue; //Remove old tile connections RemoveConnectionsFromTile (otile); for (int i=0;i<otile.nodes.Length;i++) { otile.nodes[i].Destroy(); } for (int qz=otile.z; qz < otile.z+otile.d;qz++) { for (int qx=otile.x; qx < otile.x+otile.w;qx++) { NavmeshTile qtile = tiles[qx + qz*tileXCount]; if (qtile == null || qtile != otile) throw new System.Exception("This should not happen"); if (qz < z || qz >= z+d || qx < x || qx >= x+w) { //if out of this tile's bounds, replace with empty tile tiles[qx + qz*tileXCount] = NewEmptyTile(qx,qz); if (batchTileUpdate) { batchUpdatedTiles.Add (qx + qz*tileXCount); } } else { //Will be replaced by the new tile tiles[qx + qz*tileXCount] = null; } } } } } //Create a new navmesh tile and assign its settings NavmeshTile tile = new NavmeshTile(); tile.x = x; tile.z = z; tile.w = w; tile.d = d; tile.tris = tris; tile.verts = verts; tile.bbTree = new BBTree(tile); if (tile.tris.Length % 3 != 0) throw new System.ArgumentException ("Triangle array's length must be a multiple of 3 (tris)"); if (tile.verts.Length > 0xFFFF) throw new System.ArgumentException ("Too many vertices per tile (more than 65535)"); if (!worldSpace) { if (!Mathf.Approximately (x*tileSizeX*cellSize*Int3.FloatPrecision, (float)System.Math.Round(x*tileSizeX*cellSize*Int3.FloatPrecision))) Debug.LogWarning ("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize"); if (!Mathf.Approximately (z*tileSizeZ*cellSize*Int3.FloatPrecision, (float)System.Math.Round(z*tileSizeZ*cellSize*Int3.FloatPrecision))) Debug.LogWarning ("Possible numerical imprecision. Consider adjusting tileSize and/or cellSize"); Int3 offset = (Int3)(new Vector3((x * tileSizeX * cellSize),0,(z * tileSizeZ * cellSize)) + forcedBounds.min); for (int i=0;i<verts.Length;i++) { verts[i] += offset; } } TriangleMeshNode[] nodes = new TriangleMeshNode[tile.tris.Length/3]; tile.nodes = nodes; //Here we are faking a new graph //The tile is not added to any graphs yet, but to get the position querys from the nodes //to work correctly (not throw exceptions because the tile is not calculated) we fake a new graph //and direct the position queries directly to the tile int graphIndex = AstarPath.active.astarData.graphs.Length; TriangleMeshNode.SetNavmeshHolder (graphIndex, tile); //This index will be ORed to the triangle indices int tileIndex = x + z*tileXCount; tileIndex <<= TileIndexOffset; //Create nodes and assign triangle indices for (int i=0;i<nodes.Length;i++) { TriangleMeshNode node = new TriangleMeshNode(active); nodes[i] = node; node.GraphIndex = (uint)graphIndex; node.v0 = tile.tris[i*3+0] | tileIndex; node.v1 = tile.tris[i*3+1] | tileIndex; node.v2 = tile.tris[i*3+2] | tileIndex; //Degenerate triangles might ocurr, but they will not cause any large troubles anymore //if (Polygon.IsColinear (node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { // Debug.Log ("COLINEAR!!!!!!"); //} //Make sure the triangle is clockwise if (!Polygon.IsClockwise (node.GetVertex(0), node.GetVertex(1), node.GetVertex(2))) { int tmp = node.v0; node.v0 = node.v2; node.v2 = tmp; } node.Walkable = true; node.Penalty = initialPenalty; node.UpdatePositionFromVertices(); tile.bbTree.Insert (node); } CreateNodeConnections (tile.nodes); //Set tile for (int cz=z; cz < z+d;cz++) { for (int cx=x; cx < x+w;cx++) { tiles[cx + cz*tileXCount] = tile; } } if (batchTileUpdate) { batchUpdatedTiles.Add (x + z*tileXCount); } else { ConnectTileWithNeighbours(tile); /*if (x > 0) ConnectTiles (tiles[(x-1) + z*tileXCount], tile); if (z > 0) ConnectTiles (tiles[x + (z-1)*tileXCount], tile); if (x < tileXCount-1) ConnectTiles (tiles[(x+1) + z*tileXCount], tile); if (z < tileZCount-1) ConnectTiles (tiles[x + (z+1)*tileXCount], tile);*/ } //Remove the fake graph TriangleMeshNode.SetNavmeshHolder (graphIndex, null); //Real graph index //TODO, could this step be changed for this function, is a fake index required? graphIndex = AstarPath.active.astarData.GetGraphIndex (this); for (int i=0;i<nodes.Length;i++) nodes[i].GraphIndex = (uint)graphIndex; }
public override void OnDrawGizmos(bool drawNodes) { if (!drawNodes) { return; } Matrix4x4 preMatrix = matrix; GenerateMatrix(); if (nodes == null) { //Scan (); } if (nodes == null) { return; } if (bbTree != null) { bbTree.OnDrawGizmos(); } if (preMatrix != matrix) { //Debug.Log ("Relocating Nodes"); RelocateNodes(preMatrix, matrix); } PathHandler debugData = AstarPath.active.debugPathData; for (int i = 0; i < nodes.Length; i++) { TriangleMeshNode node = (TriangleMeshNode)nodes[i]; Gizmos.color = NodeColor(node, AstarPath.active.debugPathData); if (node.Walkable) { if (AstarPath.active.showSearchTree && debugData != null && debugData.GetPathNode(node).parent != null) { Gizmos.DrawLine((Vector3)node.position, (Vector3)debugData.GetPathNode(node).parent.node.position); } else { for (int q = 0; q < node.connections.Length; q++) { Gizmos.DrawLine((Vector3)node.position, Vector3.Lerp((Vector3)node.position, (Vector3)node.connections[q].position, 0.45f)); } } Gizmos.color = AstarColor.MeshEdgeColor; } else { Gizmos.color = Color.red; } Gizmos.DrawLine((Vector3)vertices[node.v0], (Vector3)vertices[node.v1]); Gizmos.DrawLine((Vector3)vertices[node.v1], (Vector3)vertices[node.v2]); Gizmos.DrawLine((Vector3)vertices[node.v2], (Vector3)vertices[node.v0]); } }
public override void DeserializeExtraInfo (GraphSerializationContext ctx) { //NavMeshGraph.DeserializeMeshNodes (this,nodes,bytes); System.IO.BinaryReader reader = ctx.reader; tileXCount = reader.ReadInt32(); if (tileXCount < 0) return; tileZCount = reader.ReadInt32(); tiles = new NavmeshTile[tileXCount * tileZCount]; //Make sure mesh nodes can reference this graph TriangleMeshNode.SetNavmeshHolder (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; } NavmeshTile tile = new NavmeshTile (); tile.x = tx; tile.z = tz; tile.w = reader.ReadInt32(); tile.d = reader.ReadInt32(); tile.bbTree = new BBTree (tile); tiles[tileIndex] = tile; int trisCount = reader.ReadInt32 (); if (trisCount % 3 != 0) throw new System.Exception ("Corrupt data. Triangle indices count must be divisable by 3. Got " + 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] = new Int3 (reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); } 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++) { TriangleMeshNode node = new TriangleMeshNode (active); tile.nodes[i] = node; node.GraphIndex = (uint)ctx.graphIndex; 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.Insert (node); } } } }
void FindWalls(int nodeIndex, List <Vector3> wallBuffer, Vector3 position, float range) { if (range <= 0) { return; } bool negAbort = false; bool posAbort = false; range *= range; position.y = 0; //Looping as 0,-1,1,-2,2,-3,3,-4,4 etc. Avoids code duplication by keeping it to one loop instead of two for (int i = 0; !negAbort || !posAbort; i = i < 0 ? -i : -i - 1) { if (i < 0 && negAbort) { continue; } if (i > 0 && posAbort) { continue; } if (i < 0 && nodeIndex + i < 0) { negAbort = true; continue; } if (i > 0 && nodeIndex + i >= nodes.Count) { posAbort = true; continue; } TriangleMeshNode prev = nodeIndex + i - 1 < 0 ? null : nodes[nodeIndex + i - 1]; TriangleMeshNode node = nodes[nodeIndex + i]; TriangleMeshNode next = nodeIndex + i + 1 >= nodes.Count ? null : nodes[nodeIndex + i + 1]; if (node.Destroyed) { break; } if ((node.ClosestPointOnNodeXZ(position) - position).sqrMagnitude > range) { if (i < 0) { negAbort = true; } else { posAbort = true; } continue; } for (int j = 0; j < 3; j++) { triBuffer[j] = 0; } for (int j = 0; j < node.connections.Length; j++) { var other = node.connections[j] as TriangleMeshNode; if (other == null) { continue; } int va = -1; for (int a = 0; a < 3; a++) { for (int b = 0; b < 3; b++) { if (node.GetVertex(a) == other.GetVertex((b + 1) % 3) && node.GetVertex((a + 1) % 3) == other.GetVertex(b)) { va = a; a = 3; break; } } } if (va == -1) { //No direct connection } else { triBuffer[va] = other == prev || other == next ? 2 : 1; } } for (int j = 0; j < 3; j++) { //Tribuffer values // 0 : Navmesh border, outer edge // 1 : Inner edge, to node inside funnel // 2 : Inner edge, to node outside funnel if (triBuffer[j] == 0) { //Add edge to list of walls wallBuffer.Add((Vector3)node.GetVertex(j)); wallBuffer.Add((Vector3)node.GetVertex((j + 1) % 3)); } } } }
public void UpdateFunnelCorridor(int splitIndex, TriangleMeshNode prefix) { if (splitIndex > 0) { this.nodes.RemoveRange(0, splitIndex - 1); this.nodes[0] = prefix; } else { this.nodes.Insert(0, prefix); } this.left.Clear(); this.right.Clear(); this.left.Add(this.exactStart); this.right.Add(this.exactStart); for (int i = 0; i < this.nodes.Count - 1; i++) { this.nodes[i].GetPortal(this.nodes[i + 1], this.left, this.right, false); } this.left.Add(this.exactEnd); this.right.Add(this.exactEnd); }