// Update is called once per frame void Update () { // Calculate the tile the target is standing on Int2 p = new Int2 ( Mathf.RoundToInt ((target.position.x - tileSize*0.5f) / tileSize), Mathf.RoundToInt ((target.position.z - tileSize*0.5f) / tileSize) ); // Clamp range range = range < 1 ? 1 : range; // Remove tiles which are out of range bool changed = true; while ( changed ) { changed = false; foreach (KeyValuePair<Int2,ProceduralTile> pair in tiles ) { if ( Mathf.Abs (pair.Key.x-p.x) > range || Mathf.Abs (pair.Key.y-p.y) > range ) { pair.Value.Destroy (); tiles.Remove ( pair.Key ); changed = true; break; } } } // Enqueue tiles which have come in range // and start calculating them for ( int x = p.x-range; x <= p.x+range; x++ ) { for ( int z = p.y-range; z <= p.y+range; z++ ) { if ( !tiles.ContainsKey ( new Int2(x,z) ) ) { ProceduralTile tile = new ProceduralTile ( this, x, z ); var generator = tile.Generate (); // Tick it one step forward generator.MoveNext (); // Calculate the rest later tileGenerationQueue.Enqueue (generator); tiles.Add ( new Int2(x,z), tile ); } } } // The ones directly adjacent to the current one // should always be completely calculated // make sure they are for ( int x = p.x-1; x <= p.x+1; x++ ) { for ( int z = p.y-1; z <= p.y+1; z++ ) { tiles[new Int2(x,z)].ForceFinish(); } } }
/** Calculates the bounding box in XZ space of all nodes between \a from (inclusive) and \a to (exclusive) */ static IntRect NodeBounds (MeshNode[] nodes, int from, int to) { if (to - from <= 0) throw new ArgumentException(); var first = nodes[from].GetVertex(0); var min = new Int2(first.x,first.z); Int2 max = min; for (int j = from; j < to; j++) { var node = nodes[j]; var nverts = node.GetVertexCount(); for (int i = 0; i < nverts; i++) { var p = node.GetVertex(i); min.x = Math.Min (min.x, p.x); min.y = Math.Min (min.y, p.z); max.x = Math.Max (max.x, p.x); max.y = Math.Max (max.y, p.z); } } return new IntRect (min.x, min.y, max.x, max.y); }
/** Returns a new rect which is offset by the specified amount. */ public IntRect Offset ( Int2 offset ) { return new IntRect ( xmin+offset.x, ymin + offset.y, xmax + offset.x, ymax + offset.y ); }
/** Returns if the line segment \a a2 - \a b2 intersects the line segment \a a - \a b. * If only the endpoints coincide, the result is undefined (may be true or false). */ public static bool Intersects (Int2 a, Int2 b, Int2 a2, Int2 b2) { return Left (a,b,a2) != Left (a,b,b2) && Left (a2,b2,a) != Left (a2,b2,b); }
/** Returns true if the points a in a clockwise order or if they are colinear */ public static bool IsClockwiseMargin (Int2 a, Int2 b, Int2 c) { return Left(a, b, c); }
/** Returns if \a p lies on the left side of the line \a a - \a b. Also returns true if the points are colinear */ public static bool Left (Int2 a, Int2 b, Int2 c) { return (long)(b.x - a.x) * (long)(c.y - a.y) - (long)(c.x - a.x) * (long)(b.y - a.y) <= 0; }
/** Returns if the triangle \a ABC contains the point \a p */ public static bool ContainsPoint (Int2 a, Int2 b, Int2 c, Int2 p) { return Polygon.IsClockwiseMargin (a,b, p) && Polygon.IsClockwiseMargin (b,c, p) && Polygon.IsClockwiseMargin (c,a, p); }
public static int Dot (Int2 a, Int2 b) { return a.x*b.x + a.y*b.y; }
/** Async method for moving the graph */ IEnumerator UpdateGraphCoroutine () { // Find the direction // that we want to move the graph in. // Calcuculate this in graph space (where a distance of one is the size of one node) Vector3 dir = PointToGraphSpace(target.position) - PointToGraphSpace(graph.center); // Snap to a whole number of nodes dir.x = Mathf.Round(dir.x); dir.z = Mathf.Round(dir.z); dir.y = 0; // Nothing do to if ( dir == Vector3.zero ) yield break; // Number of nodes to offset in each direction Int2 offset = new Int2(-Mathf.RoundToInt(dir.x), -Mathf.RoundToInt(dir.z)); // Move the center (this is in world units, so we need to convert it back from graph space) graph.center += graph.matrix.MultiplyVector (dir); graph.GenerateMatrix (); // Create a temporary buffer // required for the calculations if ( tmp == null || tmp.Length != graph.nodes.Length ) { tmp = new GridNode[graph.nodes.Length]; } // Cache some variables for easier access int width = graph.width; int depth = graph.depth; GridNode[] nodes = graph.nodes; // Check if we have moved // less than a whole graph // width in any direction if ( Mathf.Abs(offset.x) <= width && Mathf.Abs(offset.y) <= depth ) { // Offset each node by the #offset variable // nodes which would end up outside the graph // will wrap around to the other side of it for ( int z=0; z < depth; z++ ) { int pz = z*width; int tz = ((z+offset.y + depth)%depth)*width; for ( int x=0; x < width; x++ ) { tmp[tz + ((x+offset.x + width) % width)] = nodes[pz + x]; } } yield return null; // Copy the nodes back to the graph // and set the correct indices for ( int z=0; z < depth; z++ ) { int pz = z*width; for ( int x=0; x < width; x++ ) { GridNode node = tmp[pz + x]; node.NodeInGridIndex = pz + x; nodes[pz + x] = node; } } IntRect r = new IntRect ( 0, 0, offset.x, offset.y ); int minz = r.ymax; int maxz = depth; // If offset.x < 0, adjust the rect if ( r.xmin > r.xmax ) { int tmp2 = r.xmax; r.xmax = width + r.xmin; r.xmin = width + tmp2; } // If offset.y < 0, adjust the rect if ( r.ymin > r.ymax ) { int tmp2 = r.ymax; r.ymax = depth + r.ymin; r.ymin = depth + tmp2; minz = 0; maxz = r.ymin; } // Make sure erosion is taken into account // Otherwise we would end up with ugly artifacts r = r.Expand ( graph.erodeIterations + 1 ); // Makes sure the rect stays inside the grid r = IntRect.Intersection ( r, new IntRect ( 0, 0, width, depth ) ); yield return null; // Update all nodes along one edge of the graph // With the same width as the rect for ( int z = r.ymin; z < r.ymax; z++ ) { for ( int x = 0; x < width; x++ ) { graph.UpdateNodePositionCollision ( nodes[z*width + x], x, z, false ); } } yield return null; // Update all nodes along the other edge of the graph // With the same width as the rect for ( int z = minz; z < maxz; z++ ) { for ( int x = r.xmin; x < r.xmax; x++ ) { graph.UpdateNodePositionCollision ( nodes[z*width + x], x, z, false ); } } yield return null; // Calculate all connections for the nodes // that might have changed for ( int z = r.ymin; z < r.ymax; z++ ) { for ( int x = 0; x < width; x++ ) { graph.CalculateConnections (nodes, x, z, nodes[z*width+x]); } } yield return null; // Calculate all connections for the nodes // that might have changed for ( int z = minz; z < maxz; z++ ) { for ( int x = r.xmin; x < r.xmax; x++ ) { graph.CalculateConnections (nodes, x, z, nodes[z*width+x]); } } yield return null; // Calculate all connections for the nodes along the boundary // of the graph, these always need to be updated /** \todo Optimize to not traverse all nodes in the graph, only those at the edges */ for ( int z = 0; z < depth; z++ ) { for ( int x = 0; x < width; x++ ) { if ( x == 0 || z == 0 || x >= width-1 || z >= depth-1 ) graph.CalculateConnections (nodes, x, z, nodes[z*width+x]); } } } else { // Just update all nodes for ( int z = 0; z < depth; z++ ) { for ( int x = 0; x < width; x++ ) { graph.UpdateNodePositionCollision ( nodes[z*width + x], x, z, false ); } } // Recalculate the connections of all nodes for ( int z = 0; z < depth; z++ ) { for ( int x = 0; x < width; x++ ) { graph.CalculateConnections (nodes, x, z, nodes[z*width+x]); } } } if ( floodFill ) { yield return null; // Make sure the areas for the graph // have been recalculated // not doing this can cause pathfinding to fail AstarPath.active.QueueWorkItemFloodFill (); } }
/** Returns if there is an obstacle between \a _a and \a _b on the graph. * \param [in] _a Point to linecast from * \param [in] _b Point to linecast to * \param [out] hit Contains info on what was hit, see GraphHitInfo * \param [in] hint \deprecated * \param trace If a list is passed, then it will be filled with all nodes the linecast traverses * * This is not the same as Physics.Linecast, this function traverses the graph and looks for collisions. * * It uses a method similar to Bresenham's line algorithm but it has been * extended to allow the start and end points to lie on non-integer coordinates * (which makes the math a bit trickier). * * \see https://en.wikipedia.org/wiki/Bresenham's_line_algorithm * * \version In 3.6.8 this method was rewritten to improve accuracy and performance. * Previously it used a sampling approach which could cut corners of obstacles slightly * and was pretty inefficient. * * \astarpro */ public bool Linecast (Vector3 _a, Vector3 _b, GraphNode hint, out GraphHitInfo hit, List<GraphNode> trace) { hit = new GraphHitInfo (); hit.origin = _a; Vector3 aInGraphSpace = inverseMatrix.MultiplyPoint3x4(_a); Vector3 bInGraphSpace = inverseMatrix.MultiplyPoint3x4(_b); // Clip the line so that the start and end points are on the graph if (!ClipLineSegmentToBounds (aInGraphSpace, bInGraphSpace, out aInGraphSpace, out bInGraphSpace)) { // Line does not intersect the graph // So there are no obstacles we can hit return false; } // Find the closest nodes to the start and end on the part of the segment which is on the graph var n1 = GetNearest (matrix.MultiplyPoint3x4(aInGraphSpace),NNConstraint.None).node as GridNodeBase; var n2 = GetNearest (matrix.MultiplyPoint3x4(bInGraphSpace),NNConstraint.None).node as GridNodeBase; if (!n1.Walkable) { hit.node = n1; // Hit point is the point where the segment intersects with the graph boundary // or just _a if it starts inside the graph hit.point = matrix.MultiplyPoint3x4(aInGraphSpace); hit.tangentOrigin = hit.point; return true; } // Throw away components we don't care about (y) var a = new Vector2(aInGraphSpace.x,aInGraphSpace.z); var b = new Vector2(bInGraphSpace.x,bInGraphSpace.z); // Subtract 0.5 because nodes have an offset of 0.5 (first node is at (0.5,0.5) not at (0,0)) // And it's just more convenient to remove that term here a -= Vector2.one*0.5f; b -= Vector2.one*0.5f; // Couldn't find a valid node // This shouldn't really happen unless there are NO nodes in the graph if (n1 == null || n2 == null) { hit.node = null; hit.point = _a; return true; } var dir = b-a; // Primary direction that we will move in // (e.g up and right or down and left) var sign = new Int2((int)Mathf.Sign(dir.x), (int)Mathf.Sign(dir.y)); // How much further we move away from (or towards) the line when walking along #sign // This isn't an actual distance. It is a signed distance so it can be negative (other side of the line) // Also it includes an additional factor, but the same factor is used everywhere // and we only check for if the signed distance is greater or equal to zero so it is ok var primaryDirectionError = CrossMagnitude(dir, new Vector2(sign.x,sign.y))*0.5f; /* Z * | * | * * 2 * | * -- 3 - X - 1 ----- X * | * 0 * * | * | */ // This is the direction which moves further to the right of the segment (when looking from the start) int directionToReduceError; // This is the direction which moves further to the left of the segment (when looking from the start) int directionToIncreaseError; if (dir.y >= 0) { if (dir.x >= 0) { // First quadrant directionToReduceError = 1; directionToIncreaseError = 2; } else { // Second quadrant directionToReduceError = 2; directionToIncreaseError = 3; } } else { if (dir.x < 0) { // Third quadrant directionToReduceError = 3; directionToIncreaseError = 0; } else { // Fourth quadrant directionToReduceError = 0; directionToIncreaseError = 1; } } // Current node. Start at n1 var current = n1; while (current.NodeInGridIndex != n2.NodeInGridIndex) { // We visited #current so add it to the trace if (trace != null) { trace.Add (current); } // Position of the node in 2D graph/node space // Here the first node in the graph is at (0,0) var p = new Vector2(current.NodeInGridIndex % width, current.NodeInGridIndex / width); // Calculate the error // This is proportional to the distance between the line and the node var error = CrossMagnitude(dir, p-a); // How does the error change we take one step in the primary direction var nerror = error + primaryDirectionError; // Check if we need to reduce or increase the error (we want to keep it near zero) // and pick the appropriate direction to move in int ndir = nerror < 0 ? directionToIncreaseError : directionToReduceError; // Check we can move in that direction var other = GetNeighbourAlongDirection(current, ndir); if (other != null) { current = other; } else { // Hit obstacle // We know from what direction we moved in // so we can calculate the line which we hit // Either X offset is 0 or Z offset is zero since we only move in one of the 4 axis aligned directions // The line we hit will be right between two nodes (so a distance of 0.5 from the current node in graph space) Vector2 lineOrigin = p + new Vector2 (neighbourXOffsets[ndir], neighbourZOffsets[ndir]) * 0.5f; Vector2 lineDirection; if (neighbourXOffsets[ndir] == 0) { // We hit a line parallel to the X axis lineDirection = new Vector2(1,0); } else { // We hit a line parallel to the Z axis lineDirection = new Vector2(0,1); } // Find the intersection var intersection = Polygon.IntersectionPoint (lineOrigin, lineOrigin+lineDirection, a, b); var currentNodePositionInGraphSpace = inverseMatrix.MultiplyPoint3x4((Vector3)current.position); // The intersection is in graph space (with an offset of 0.5) so we need to transform it to world space var intersection3D = new Vector3(intersection.x + 0.5f, currentNodePositionInGraphSpace.y, intersection.y + 0.5f); var lineOrigin3D = new Vector3(lineOrigin.x + 0.5f, currentNodePositionInGraphSpace.y, lineOrigin.y + 0.5f); hit.point = matrix.MultiplyPoint3x4(intersection3D); hit.tangentOrigin = matrix.MultiplyPoint3x4(lineOrigin3D); hit.tangent = matrix.MultiplyVector(new Vector3(lineDirection.x,0,lineDirection.y)); hit.node = current; return true; } } // Enqueue the last node to the trace if (trace != null) { trace.Add (current); } // No obstacles detected if (current == n2) { return false; } // Reached node right above or right below n2 but we cannot reach it hit.point = (Vector3)current.position; hit.tangentOrigin = hit.point; return true; }
public static Int3 ToInt3XZ (Int2 o) { return new Int3 (o.x,0,o.y); }
public static Int2 Max (Int2 a, Int2 b) { return new Int2 (System.Math.Max (a.x,b.x), System.Math.Max (a.y,b.y)); }
/** Returns a new Int2 rotated 90*r degrees around the origin. */ public static Int2 Rotate ( Int2 v, int r ) { r = r % 4; return new Int2 ( v.x*Rotations[r*4+0] + v.y*Rotations[r*4+1], v.x*Rotations[r*4+2] + v.y*Rotations[r*4+3] ); }
public static long DotLong (Int2 a, Int2 b) { return (long)a.x*(long)b.x + (long)a.y*(long)b.y; }
public BBTreeBox (MeshNode node) { this.node = node; var first = node.GetVertex(0); var min = new Int2(first.x,first.z); Int2 max = min; for (int i=1;i<node.GetVertexCount();i++) { var p = node.GetVertex(i); min.x = Math.Min (min.x,p.x); min.y = Math.Min (min.y,p.z); max.x = Math.Max (max.x,p.x); max.y = Math.Max (max.y,p.z); } rect = new IntRect (min.x,min.y,max.x,max.y); left = right = -1; }
/** Factor of the nearest point on the segment. * Returned value is in the range [0,1] if the point lies on the segment otherwise it just lies on the line. * The closest point can be got by (end-start)*factor + start; */ public static float NearestPointFactor (Int2 lineStart, Int2 lineEnd, Int2 point) { Int2 lineDirection = lineEnd-lineStart; double magn = lineDirection.sqrMagnitudeLong; double closestPoint = Int2.DotLong(point-lineStart,lineDirection); //Vector3.Dot(lineDirection,lineDirection); if (magn != 0) closestPoint /= magn; return (float)closestPoint; //return closestPoint / magn; }
/** 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 = ListPool<MeshNode>.Claim (); //new List<MeshNode>(); List<uint> connectionCosts = 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) 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 (); 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 (); } ListPool<MeshNode>.Release (connections); ListPool<uint>.Release (connectionCosts); }