/// <summary>Async method for moving the graph</summary>
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.transform.TransformVector(dir);
graph.UpdateTransform();
// Cache some variables for easier access
int width = graph.width;
int depth = graph.depth;
GridNodeBase[] nodes;
// Layers are required when handling LayeredGridGraphs
int layers = graph.LayerCount;
var layeredGraph = graph as LayerGridGraph;
if (layeredGraph != null)
{
nodes = layeredGraph.nodes;
}
else
{
nodes = graph.nodes;
}
// Create a temporary buffer required for the calculations
if (buffer == null || buffer.Length != width * depth)
{
buffer = new GridNodeBase[width * depth];
}
// Check if we have moved less than a whole graph width all directions
// If we have moved more than this we can just as well recalculate the whole graph
if (Mathf.Abs(offset.x) <= width && Mathf.Abs(offset.y) <= depth)
{
IntRect recalculateRect = new IntRect(0, 0, offset.x, offset.y);
// If offset.x < 0, adjust the rect
if (recalculateRect.xmin > recalculateRect.xmax)
{
int tmp2 = recalculateRect.xmax;
recalculateRect.xmax = width + recalculateRect.xmin;
recalculateRect.xmin = width + tmp2;
}
// If offset.y < 0, adjust the rect
if (recalculateRect.ymin > recalculateRect.ymax)
{
int tmp2 = recalculateRect.ymax;
recalculateRect.ymax = depth + recalculateRect.ymin;
recalculateRect.ymin = depth + tmp2;
}
// Connections need to be recalculated for the neighbours as well, so we need to expand the rect by 1
var connectionRect = recalculateRect.Expand(1);
// Makes sure the rect stays inside the grid
connectionRect = IntRect.Intersection(connectionRect, new IntRect(0, 0, width, 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 l = 0; l < layers; l++)
{
int layerOffset = l * width * depth;
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++)
{
buffer[tz + ((x + offset.x + width) % width)] = nodes[layerOffset + 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++)
{
int newIndex = pz + x;
var node = buffer[newIndex];
if (node != null)
{
node.NodeInGridIndex = newIndex;
}
nodes[layerOffset + newIndex] = node;
}
// Calculate the limits for the region that has been wrapped
// to the other side of the graph
int xmin, xmax;
if (z >= recalculateRect.ymin && z < recalculateRect.ymax)
{
xmin = 0;
xmax = depth;
}
else
{
xmin = recalculateRect.xmin;
xmax = recalculateRect.xmax;
}
for (int x = xmin; x < xmax; x++)
{
var node = buffer[pz + x];
if (node != null)
{
// Clear connections on all nodes that are wrapped and placed on the other side of the graph.
// This is both to clear any custom connections (which do not really make sense after moving the node)
// and to prevent possible exceptions when the node will later (possibly) be destroyed because it was
// not needed anymore (only for layered grid graphs).
node.ClearConnections(false);
}
}
}
yield return(null);
}
// The calculation will only update approximately this number of
// nodes per frame. This is used to keep CPU load per frame low
int yieldEvery = 1000;
// To avoid the update taking too long, make yieldEvery somewhat proportional to the number of nodes that we are going to update
int approxNumNodesToUpdate = Mathf.Max(Mathf.Abs(offset.x), Mathf.Abs(offset.y)) * Mathf.Max(width, depth);
yieldEvery = Mathf.Max(yieldEvery, approxNumNodesToUpdate / 10);
int counter = 0;
// Recalculate the nodes
// Take a look at the image in the docs for the UpdateGraph method
// to see which nodes are being recalculated.
for (int z = 0; z < depth; z++)
{
int xmin, xmax;
if (z >= recalculateRect.ymin && z < recalculateRect.ymax)
{
xmin = 0;
xmax = width;
}
else
{
xmin = recalculateRect.xmin;
xmax = recalculateRect.xmax;
}
for (int x = xmin; x < xmax; x++)
{
graph.RecalculateCell(x, z, false, false);
}
counter += (xmax - xmin);
if (counter > yieldEvery)
{
counter = 0;
yield return(null);
}
}
for (int z = 0; z < depth; z++)
{
int xmin, xmax;
if (z >= connectionRect.ymin && z < connectionRect.ymax)
{
xmin = 0;
xmax = width;
}
else
{
xmin = connectionRect.xmin;
xmax = connectionRect.xmax;
}
for (int x = xmin; x < xmax; x++)
{
graph.CalculateConnections(x, z);
}
counter += (xmax - xmin);
if (counter > yieldEvery)
{
counter = 0;
yield return(null);
}
}
yield return(null);
// Calculate all connections for the nodes along the boundary
// of the graph, these always need to be updated
/// <summary>TODO: Optimize to not traverse all nodes in the graph, only those at the edges</summary>
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(x, z);
}
}
}
}
else
{
// The calculation will only update approximately this number of
// nodes per frame. This is used to keep CPU load per frame low
int yieldEvery = Mathf.Max(depth * width / 20, 1000);
int counter = 0;
// Just update all nodes
for (int z = 0; z < depth; z++)
{
for (int x = 0; x < width; x++)
{
graph.RecalculateCell(x, z);
}
counter += width;
if (counter > yieldEvery)
{
counter = 0;
yield return(null);
}
}
// Recalculate the connections of all nodes
for (int z = 0; z < depth; z++)
{
for (int x = 0; x < width; x++)
{
graph.CalculateConnections(x, z);
}
counter += width;
if (counter > yieldEvery)
{
counter = 0;
yield return(null);
}
}
}
}