void DrawFirstSection(GridGraph graph) { float prevRatio = graph.aspectRatio; DrawInspectorMode(graph); Draw2DMode(graph); var normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); var worldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); int newWidth, newDepth; DrawWidthDepthFields(graph, out newWidth, out newDepth); var newNodeSize = EditorGUILayout.FloatField(new GUIContent("Node size", "The size of a single node. The size is the side of the node square in world units"), graph.nodeSize); newNodeSize = newNodeSize <= 0.01F ? 0.01F : newNodeSize; if (graph.inspectorGridMode == InspectorGridMode.IsometricGrid || graph.inspectorGridMode == InspectorGridMode.Advanced) { graph.aspectRatio = EditorGUILayout.FloatField(new GUIContent("Aspect Ratio", "Scaling of the nodes width/depth ratio. Good for isometric games"), graph.aspectRatio); DrawIsometricField(graph); } if ((graph.nodeSize != newNodeSize && locked) || (newWidth != graph.width || newDepth != graph.depth) || prevRatio != graph.aspectRatio) { graph.nodeSize = newNodeSize; graph.SetDimensions(newWidth, newDepth, newNodeSize); normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); var newWorldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); // Move the center so that the pivot point stays at the same point in the world graph.center += worldPoint - newWorldPoint; graph.center = RoundVector3(graph.center); graph.UpdateTransform(); AutoScan(); } if ((graph.nodeSize != newNodeSize && !locked)) { graph.nodeSize = newNodeSize; graph.UpdateTransform(); } DrawPositionField(graph); DrawRotationField(graph); }
/// <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); } } } }
void DrawFirstSection(GridGraph graph) { float prevRatio = graph.aspectRatio; DrawInspectorMode(graph); Draw2DMode(graph); var normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); var worldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); int newWidth, newDepth; DrawWidthDepthFields(graph, out newWidth, out newDepth); EditorGUI.BeginChangeCheck(); float newNodeSize; if (graph.inspectorGridMode == InspectorGridMode.Hexagonal) { graph.inspectorHexagonSizeMode = (InspectorGridHexagonNodeSize)EditorGUILayout.EnumPopup(new GUIContent("Hexagon dimension"), graph.inspectorHexagonSizeMode); float hexagonSize = GridGraph.ConvertNodeSizeToHexagonSize(graph.inspectorHexagonSizeMode, graph.nodeSize); hexagonSize = (float)System.Math.Round(hexagonSize, 5); newNodeSize = GridGraph.ConvertHexagonSizeToNodeSize(graph.inspectorHexagonSizeMode, EditorGUILayout.FloatField(hexagonSizeContents[(int)graph.inspectorHexagonSizeMode], hexagonSize)); } else { newNodeSize = EditorGUILayout.FloatField(new GUIContent("Node size", "The size of a single node. The size is the side of the node square in world units"), graph.nodeSize); } bool nodeSizeChanged = EditorGUI.EndChangeCheck(); newNodeSize = newNodeSize <= 0.01F ? 0.01F : newNodeSize; if (graph.inspectorGridMode == InspectorGridMode.IsometricGrid || graph.inspectorGridMode == InspectorGridMode.Advanced) { graph.aspectRatio = EditorGUILayout.FloatField(new GUIContent("Aspect Ratio", "Scaling of the nodes width/depth ratio. Good for isometric games"), graph.aspectRatio); DrawIsometricField(graph); } if ((nodeSizeChanged && locked) || (newWidth != graph.width || newDepth != graph.depth) || prevRatio != graph.aspectRatio) { graph.nodeSize = newNodeSize; graph.SetDimensions(newWidth, newDepth, newNodeSize); normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); var newWorldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); // Move the center so that the pivot point stays at the same point in the world graph.center += worldPoint - newWorldPoint; graph.center = RoundVector3(graph.center); graph.UpdateTransform(); AutoScan(); } if ((nodeSizeChanged && !locked)) { graph.nodeSize = newNodeSize; graph.UpdateTransform(); } DrawPositionField(graph); DrawRotationField(graph); }
/// <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 = graph.nodes; // Layers are required when handling LayeredGridGraphs int layers = graph.LayerCount; // Create a temporary buffer required for the calculations Memory.Realloc(ref buffer, 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) { // Note: the upper limits are treated as exclusive limits (xmax, ymax) 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; } // 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. // This part is made up of a couple of rows and a couple of columns // So depending on the row we are in (z) we should either iterate over the whole row // or only the relevant columns. 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++) { 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); } // Recalculate the nodes // Take a look at the image in the docs for the UpdateGraph method // to see which nodes are being recalculated. var dependencyTracker = ObjectPool <Jobs.JobDependencyTracker> .Claim(); var updateNodesJob1 = graph.UpdateRegion(new IntRect(0, recalculateRect.ymin, width, recalculateRect.ymax), dependencyTracker, Unity.Collections.Allocator.Persistent); // Note: It is important the the main thread work is completed before the second update begins. // Otherwise if we give updateNodesJob1 as a dependency the code might call .Complete on a the handle and block indefinitely // since it requires work to complete in the main thread. foreach (var _ in updateNodesJob1.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } IntRect job2rect; if (recalculateRect.ymin == 0) { job2rect = new IntRect(recalculateRect.xmin, recalculateRect.ymax, recalculateRect.xmax, depth); } else if (recalculateRect.ymax == depth) { job2rect = new IntRect(recalculateRect.xmin, 0, recalculateRect.xmax, recalculateRect.ymin - 1); } else { throw new System.Exception("Should not happen"); } var updateNodesJob2 = graph.UpdateRegion(job2rect, dependencyTracker, Unity.Collections.Allocator.Persistent); foreach (var _ in updateNodesJob2.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } ObjectPool <Jobs.JobDependencyTracker> .Release(ref dependencyTracker); // 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); } } } yield return(null); } else { var dependencyTracker = ObjectPool <Jobs.JobDependencyTracker> .Claim(); var updateNodesJob = graph.UpdateRegion(new IntRect(0, 0, width, depth), dependencyTracker, Unity.Collections.Allocator.Persistent); foreach (var _ in updateNodesJob.CompleteTimeSliced(MaxMillisPerFrame)) { yield return(null); } ObjectPool <Jobs.JobDependencyTracker> .Release(ref dependencyTracker); } }