/// <summary>
        /// The actual layout process
        /// </summary>
        protected override void RunInternal()
        {
            if (SingleComponent)
            {
                componentCount = 1;
                LayoutComponent(graph);
            }
            else
            {
                foreach (var c in graph.RootCluster.AllClustersDepthFirst())
                {
                    if (c == graph.RootCluster || c.RectangularBoundary == null)
                    {
                        continue;
                    }
                    c.RectangularBoundary.GenerateFixedConstraints = false;
                }

                var components = graph.GetClusteredConnectedComponents().ToList();

                componentCount = components.Count;

                foreach (var component in components)
                {
                    LayoutComponent(component);
                }

                graph.BoundingBox = MdsGraphLayout.PackGraphs(components, settings);
                this.ProgressComplete();

                // update positions of original graph elements
                foreach (var v in graph.Nodes)
                {
                    var copy = v.AlgorithmData as GraphConnectedComponents.AlgorithmDataNodeWrap;
                    Debug.Assert(copy != null);
                    v.Center = copy.node.Center;
                }

                foreach (var e in graph.Edges)
                {
                    var copy = e.AlgorithmData as Edge;
                    if (copy != null)
                    {
                        e.EdgeGeometry       = copy.EdgeGeometry;
                        e.EdgeGeometry.Curve = copy.Curve;
                    }
                }

                foreach (var c in graph.RootCluster.AllClustersDepthFirst().Where(c => c != graph.RootCluster))
                {
                    var copy        = c.AlgorithmData as GraphConnectedComponents.AlgorithmDataNodeWrap;
                    var copyCluster = copy.node as Cluster;
                    Debug.Assert(copyCluster != null);
                    c.RectangularBoundary = copyCluster.RectangularBoundary;
                    c.RectangularBoundary.GenerateFixedConstraints = c.RectangularBoundary.GenerateFixedConstraintsDefault;
                    c.BoundingBox = c.RectangularBoundary.Rect;
                    c.RaiseLayoutDoneEvent();
                }
            }
        }
        public void GetClusteredConnectedComponentsTest()
        {
            List <GeometryGraph> components = graph.GetClusteredConnectedComponents().ToList();

            List <Cluster> expectedTopLevelClusters = graph.RootCluster.Clusters.ToList();

            Assert.AreEqual(5, components.Count, "Expected 5 connected components");

            Node nodeA = graph.FindNodeByUserData("A");
            Node nodeB = graph.FindNodeByUserData("B");
            Node nodeC = graph.FindNodeByUserData("C");

            // go through each of the components and make sure that it matches the original
            // since the graph is traversed in order of Nodes we know what order the components will be appear in Actual
            GeometryGraph c = components[0];

            Assert.AreEqual(3, c.Nodes.Count, "Component has incorrect node count");
            Assert.IsTrue(c.FindNodeByUserData(nodeA) != null, "Component should contain node A");
            Assert.IsTrue(c.FindNodeByUserData(nodeB) != null, "Component should contain node B");
            Assert.IsTrue(c.FindNodeByUserData(nodeC) != null, "Component should contain node C");
            Assert.AreSame(c.FindNodeByUserData(nodeC).InEdges.First().UserData, nodeC.InEdges.First(), "Edge doesn't match original");

            Assert.AreEqual(1, c.Edges.Count, "Component should contain 1 edge");
            var cluster = c.RootCluster.Clusters.First();

            Assert.AreEqual(2, cluster.Nodes.Count(), "Component should contain 2 nodes");
            Assert.AreSame(expectedTopLevelClusters[0], cluster.UserData, "Component invalid");

            c = components[1];
            Assert.AreEqual(4, c.Nodes.Count, "Component invalid");
            Assert.AreEqual(2, c.Edges.Count, "Component invalid");
            cluster = c.RootCluster.Clusters.First();
            Assert.AreSame(expectedTopLevelClusters[1], cluster.UserData, "Component invalid");
            Assert.AreEqual(1, cluster.Nodes.Count(), "Component invalid");
            Assert.AreEqual("G", ((Node)cluster.Nodes.First().UserData).UserData, "Component invalid"); // double user data lookup since the first user data is the components source node
            var nested = cluster.Clusters.First();

            Assert.AreEqual("E", ((Node)nested.Nodes.First().UserData).UserData, "Component invalid");
            Assert.AreEqual("F", ((Node)nested.Nodes.Last().UserData).UserData, "Component invalid");
            Assert.AreSame(expectedTopLevelClusters[1].Clusters.First(), nested.UserData, "Component invalid");
            Assert.AreEqual(2, nested.Nodes.Count(), "Component invalid");

            c = components[2];
            Assert.AreEqual(2, c.Nodes.Count, "Component invalid");
            Assert.AreEqual(1, c.Edges.Count, "Component invalid");
            Assert.IsFalse(c.RootCluster.Clusters.Any(), "Component invalid");

            c = components[3];
            Assert.AreEqual(1, c.Nodes.Count, "Component invalid");
            Assert.AreEqual(0, c.Edges.Count, "Component invalid");
            Assert.IsFalse(c.RootCluster.Clusters.Any(), "Component invalid");

            c       = components[4];
            cluster = c.RootCluster.Clusters.First();
            Assert.AreEqual("K", ((Node)cluster.Nodes.First().UserData).UserData, "Component invalid");
            Assert.AreEqual("L", ((Node)cluster.Nodes.Last().UserData).UserData, "Component invalid");
            cluster = c.RootCluster.Clusters.Last();
            Assert.AreEqual("M", ((Node)cluster.Nodes.First().UserData).UserData, "Component invalid");
            Assert.AreEqual("N", ((Node)cluster.Nodes.Last().UserData).UserData, "Component invalid");
            Assert.AreEqual(c.Edges.Count, 1, "Component invalid");
        }
예제 #3
0
    public void GenerateTechs()
    {
        foreach (var instance in _ringInstances)
        {
            instance.ReturnToPool();
        }
        _ringInstances.Clear();

        foreach (var instance in _techInstances)
        {
            instance.ReturnToPool();
        }
        _techInstances.Clear();

        foreach (var instance in _linkInstances)
        {
            instance.ReturnToPool();
        }
        _linkInstances.Clear();

        foreach (var instance in _arrowInstances)
        {
            instance.ReturnToPool();
        }
        _arrowInstances.Clear();

        var graph = new GeometryGraph();

        var settings = new SugiyamaLayoutSettings();

        if (TestMode)
        {
            for (int i = 0; i < TechCount; i++)
            {
                var node = NewNode(i);
                graph.Nodes.Add(node);
                if (i > SeedTechs)
                {
                    graph.Edges.Add(new Edge(graph.Nodes[Random.Range(max(0, i - RollingWindowSize), i - 1)], node));
                }
            }
            foreach (var vertex in graph.Nodes.Where(v => !graph.Edges.Any(e => e.Source == v || e.Target == v)))
            {
                graph.Edges.Add(new Edge(vertex, graph.Nodes[Random.Range(0, TechCount - 1)]));
            }
        }
        else
        {
            var nodeMap = new Dictionary <BlueprintData, Node>();
            foreach (var blueprint in Blueprints)
            {
                var node = NewNode(blueprint);
                nodeMap[blueprint] = node;
                graph.Nodes.Add(node);
            }

            foreach (var targetBlueprint in Blueprints)
            {
                foreach (var sourceBlueprint in Blueprints.Where(sb =>
                                                                 targetBlueprint.Dependencies.Any(dep => sb.ID == dep)))
                {
                    var edge = new Edge(nodeMap[sourceBlueprint], nodeMap[targetBlueprint]);
                    graph.Edges.Add(edge);
                    var splitName = sourceBlueprint.Name.Split(' ');
                    if (splitName.Length > 1)
                    {
                        var nameStart = string.Join(" ", splitName.Take(splitName.Length - 1));
                        if (targetBlueprint.Name.StartsWith(nameStart))
                        {
                            edge.Weight *= 10;
                        }
                    }
                }
            }
        }

        settings.BrandesThreshold = 9999;

        var islands   = graph.GetClusteredConnectedComponents();
        var islandMap =
            graph.Nodes.ToDictionary(n => n, n => islands.Find(c => c.Nodes.Any(cn => cn.UserData == n)));

        if (TestMode)
        {
            foreach (var island in islands)
            {
                foreach (var node in island.Nodes)
                {
                    var region = GetRegion(island, node, DependencyAncestorDepth, DependencyDepth);
                    while (Random.value < MultipleDependencyProbability && region.Count > 0)
                    {
                        var dependency = region[Random.Range(0, region.Count)];
                        graph.Edges.Add(new Edge(dependency.UserData as Node, node.UserData as Node));
                        region.Remove(dependency);
                    }
                }
            }
        }

        var islandsBySize = islands.OrderByDescending(i => i.Nodes.Count);
        var largestIsland = islandsBySize.First();

        foreach (var island in islandsBySize.Skip(1))
        {
            settings.VerticalConstraints.SameLayerConstraints.Insert(new Tuple <Node, Node>(
                                                                         largestIsland.Nodes[Random.Range(0, largestIsland.Nodes.Count)].UserData as Node, island.Nodes[Random.Range(0, island.Nodes.Count)].UserData as Node));
        }

        settings.PackingMethod  = PackingMethod;
        settings.Transformation = PlaneTransformation.Rotation(PI);
        var layout = new LayeredLayout(graph, settings);

        layout.Run();

        var positions     = graph.Nodes.ToDictionary(n => n, n => (float2)n.Center);
        var islandCenters = islands.ToDictionary(i => i,
                                                 i => i.Nodes.Aggregate(float2(0, 0), (total, v) => total + positions[v.UserData as Node]) / i.Nodes.Count);

        Rect bounds = Rect.MinMaxRect(
            positions.Values.Min(v => v.x),
            positions.Values.Min(v => v.y),
            positions.Values.Max(v => v.x),
            positions.Values.Max(v => v.y));

        var tiers =
            Mathf.RoundToInt(graph.Nodes.Max(n => Rect.PointToNormalized(bounds, n.Center).y) /
                             graph.Nodes.Min(n =>
        {
            var normalized = Rect.PointToNormalized(bounds, n.Center);
            return(normalized.y > .001f ? normalized.y : 1);
        }));

        // var islandsBySize = islands.OrderByDescending(i => i.Nodes.Count);
        // var largestIsland = islandsBySize.First();
        // foreach(var island in islandsBySize.Skip(1))
        //     settings.AddSameLayerNeighbors(largestIsland.Nodes.RandomElement().UserData as Node, island.Nodes.RandomElement().UserData as Node);
        //
        // layout = new LayeredLayout(graph, settings);
        // layout.Run();

        positions     = graph.Nodes.ToDictionary(n => n, n => (float2)n.Center);
        islandCenters = islands.ToDictionary(i => i,
                                             i => i.Nodes.Aggregate(float2(0, 0), (total, v) => total + positions[v.UserData as Node]) / i.Nodes.Count);

        bounds = Rect.MinMaxRect(
            positions.Values.Min(v => v.x),
            positions.Values.Min(v => v.y),
            positions.Values.Max(v => v.x),
            positions.Values.Max(v => v.y));

        Debug.Log($"Generated {tiers} tiers of techs!");

        var islandColors = islandCenters.ToDictionary(
            i => i.Key,
            i => Color.HSVToRGB(lerp(HueMin, HueMax, Rect.PointToNormalized(bounds, i.Value).x), Saturation, Brightness));

        var islandFillMaterials = islandColors.ToDictionary(i => i.Key, i =>
        {
            var mat = new Material(TechFillMaterial);
            var col = i.Value;
            col.a   = FillOpacity;
            mat.SetColor("_TintColor", col);
            return(mat);
        });

        var islandGlowMaterials = islandColors.ToDictionary(i => i.Key, i =>
        {
            var mat = new Material(TechGlowMaterial);
            var col = i.Value;
            col.a   = GlowOpacity;
            mat.SetColor("_TintColor", col);
            return(mat);
        });

        var islandArrowMaterials = islandColors.ToDictionary(i => i.Key, i =>
        {
            var mat = new Material(TechArrowMaterial);
            var col = i.Value;
            col.a   = 1;
            mat.SetColor("_TintColor", col);
            return(mat);
        });

        var islandLinkMaterials = islandColors.ToDictionary(i => i.Key, i =>
        {
            var mat = new Material(TechLinkMaterial);
            var col = i.Value;
            col.a   = 1;
            mat.SetColor("_TintColor", col);
            return(mat);
        });

        if (Radial)
        {
            positions = positions.ToDictionary(
                kvp => kvp.Key,
                kvp =>
            {
                var normalized = Rect.PointToNormalized(bounds, kvp.Value);
                var rads       = normalized.x * (PI * 2 - PaddingRadians) + PaddingRadians / 2;
                return(float2(sin(rads), -cos(rads)) * ((normalized.y * tiers + StartTier + .5f) * LayerDistance) * Scale);
            });
        }
        else
        {
            positions = positions.ToDictionary(
                kvp => kvp.Key,
                kvp =>
            {
                var normalized = Rect.PointToNormalized(bounds, kvp.Value);
                return(float2(
                           normalized.x * LayerDistance * tiers * (bounds.width / bounds.height),
                           (normalized.y * tiers / 2 + StartTier + .5f) * LayerDistance));
            });
        }

        foreach (var vertex in graph.Nodes.Where(positions.ContainsKey))
        {
            var tech = Tech.Instantiate <TechNode>();
            tech.transform.position = (Vector2)positions[vertex];
            tech.Label.text         = $"{(TestMode ? ((int) vertex.UserData).ToString() : ((BlueprintData) vertex.UserData).ID.ToString().Substring(0,2))}";
            var gradient = tech.Label.colorGradient;
            gradient.bottomLeft      = gradient.bottomRight = islandColors[islandMap[vertex]];
            tech.Label.colorGradient = gradient;
            tech.Icon.material.SetTexture("_MainTex", _icons[Random.Range(0, _icons.Length - 1)]);
            tech.Fill.material = islandFillMaterials[islandMap[vertex]];
            tech.Glow.material = islandGlowMaterials[islandMap[vertex]];
            if (!TestMode)
            {
                tech.Fill.GetComponent <ClickableCollider>().OnClick += (collider, data) =>
                {
                    PropertiesPanel.Clear();
                    var blueprint = (BlueprintData)vertex.UserData;
                    PropertiesPanel.Title.text = blueprint.Name;
                    PropertiesPanel.AddProperty("Research Time", () => $"{blueprint.ResearchTime:0.##} MH");
                    PropertiesPanel.AddProperty("Produces", () => $"{Context.Cache.Get<ItemData>(blueprint.Item).Name}");
                    PropertiesPanel.AddProperty("Production Quality", () => $"{Mathf.RoundToInt(blueprint.Quality * 100)}%");
                    PropertiesPanel.AddProperty("Production Time", () => $"{blueprint.ProductionTime:0.##} MH");

                    var ingredientsList = PropertiesPanel.AddList("Ingredients");
                    foreach (var ingredient in blueprint.Ingredients)
                    {
                        var ingredientData = Context.Cache.Get <ItemData>(ingredient.Key);
                        ingredientsList.AddProperty(ingredientData.Name, () => ingredient.Value.ToString());
                    }
                    ingredientsList.SetExpanded(true, true);

                    var dependenciesList = PropertiesPanel.AddList("Dependencies");
                    foreach (var dependency in blueprint.Dependencies)
                    {
                        var dependencyBlueprint = Context.Cache.Get <BlueprintData>(dependency);
                        dependenciesList.AddProperty(dependencyBlueprint.Name);
                    }
                    dependenciesList.SetExpanded(true, true);

                    var descendantsList = PropertiesPanel.AddList("Descendants");
                    foreach (var descendant in Blueprints.Where(bp => bp.Dependencies.Any(dep => blueprint.ID == dep)))
                    {
                        descendantsList.AddProperty(descendant.Name);
                    }
                    descendantsList.SetExpanded(true, true);
                }
            }
            ;
            _techInstances.Add(tech.GetComponent <Prototype>());
        }

        foreach (var edge in graph.Edges)
        {
            var link = RadialLink.Instantiate <LineRenderer>();
            link.material      = islandLinkMaterials[islandMap[edge.Source]];
            link.positionCount = LinkPoints;
            var p1r      = length(positions[edge.Source]);
            var p2r      = length(positions[edge.Target]);
            var p1a      = (atan2(positions[edge.Source].y, positions[edge.Source].x) + PI * 2.5f) % (PI * 2);
            var p2a      = (atan2(positions[edge.Target].y, positions[edge.Target].x) + PI * 2.5f) % (PI * 2);
            var p1       = float2(sin(p1a) * p1r, -cos(p1a) * p1r);
            var p2       = float2(sin(p2a) * p2r, -cos(p2a) * p2r);
            var dist     = length(p2 - p1);
            var distProp = (dist - LinkTargetDistance) / dist;
            var dir      = new float2();
            var lastPos  = new float2();
            link.SetPositions(Enumerable.Range(0, LinkPoints).Select(i =>
            {
                var lerp   = (float)i / (LinkPoints - 1) * distProp;
                var angle  = math.lerp(p1a, p2a, (lerp));
                var radius = math.lerp(p1r, p2r, lerp);
                var pos    = Radial ? float2(sin(angle) * radius, -cos(angle) * radius) : math.lerp(positions[edge.Source], positions[edge.Target], lerp);
                dir        = normalize(pos - lastPos);
                lastPos    = pos;
                return((Vector3)float3(lastPos, LinkDepth));
            }).ToArray());
            _linkInstances.Add(link.GetComponent <Prototype>());

            var arrow = Arrow.Instantiate <TechArrow>();
            arrow.transform.position = new Vector3(lastPos.x, lastPos.y, ArrowDepth);
            arrow.transform.rotation = Quaternion.Euler(0, 0, atan2(dir.y, dir.x) * Mathf.Rad2Deg);
            arrow.Icon.material      = islandArrowMaterials[islandMap[edge.Source]];
            _arrowInstances.Add(arrow.GetComponent <Prototype>());
        }

        if (Radial)
        {
            for (int i = 1; i <= tiers; i++)
            {
                var ring          = Ring.Instantiate <LineRenderer>();
                var ringPositions = new Vector3[Sections];
                for (int s = 0; s < Sections; s++)
                {
                    var rads = ((float)s / (Sections - 1)) * (PI * 2 - PaddingRadians) + PaddingRadians / 2;
                    ringPositions[s]   = new Vector3(sin(rads), -cos(rads), 0) * ((i + StartTier) * (LayerDistance) * Scale);
                    ringPositions[s].z = RingDepth;
                }

                ring.positionCount = Sections;
                ring.SetPositions(ringPositions);
                var tierLerp = (float)i / (tiers - 1);
                ring.colorGradient = new Gradient
                {
                    alphaKeys = new [] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) },
                    colorKeys = Enumerable.Range(0, RingColorKeys).Select(x =>
                    {
                        var ringLerp = (float)x / (RingColorKeys - 1);
                        return(new GradientColorKey(
                                   Color.HSVToRGB(HueMin + (HueMax - HueMin) * ringLerp,
                                                  lerp(RingInnerSaturation, RingOuterSaturation, tierLerp),
                                                  lerp(RingInnerBrightness, RingOuterBrightness, tierLerp)), ringLerp));
                    }).ToArray()
                };

                _ringInstances.Add(ring.GetComponent <Prototype>());
            }
        }
    }
}