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);
        }
Esempio n. 2
0
        /// <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);
            }
        }