public static Graph Defleshed(this Graph graph, Graph xray, GraphUtility.Metric metric) { Dictionary <Node, Node> xray_analogs = new Dictionary <Node, Node>(); foreach (Node node in xray.Nodes) { xray_analogs[node] = graph.GetNearestNode(node.GetPosition()); } HashSet <Edge> skeleton = new HashSet <Edge>(); foreach (Edge bone in xray.Edges) { Path path = xray_analogs[bone.A].GetPathTo(xray_analogs[bone.B], metric); for (int i = 0; i < path.Count - 1; i++) { skeleton.Add(new Edge(path[i], path[i + 1])); } } return(GraphUtility.CreateGraph(skeleton)); }
public static Graph MinimumSpanned(this Graph graph, GraphUtility.Metric metric) { if (graph.Nodes.Count() == 0) { return(new Graph()); } HashSet <Node> open_set = new HashSet <Node>(graph.WithoutHiddenNodes().Nodes); IEnumerable <Edge> edges = new Graph(open_set).Edges; foreach (Node node in open_set) { node.ClearNeighbors(); } Graph mst = new Graph(); mst.AddNode(open_set.First()); open_set.Remove(open_set.First()); while (open_set.Count > 0) { Edge minimum_edge = edges .Where(edge => mst.Nodes.Contains(edge.A) && open_set.Contains(edge.B)) .MinElement(edge => metric(edge.A, edge.B)); minimum_edge.A.AddNeighbor(minimum_edge.B); minimum_edge.B.AddNeighbor(minimum_edge.A); open_set.Remove(minimum_edge.B); mst.AddNode(minimum_edge.B); } return(mst); }
public static float GetCost(this Path path, GraphUtility.Metric metric, Node start, Node end = null) { int start_index = path.IndexOf(start); int end_index; if (end != null) { end_index = path.IndexOf(end); } else { end_index = path.Count - 1; } float cost_sum = 0; for (int i = start_index; i < end_index; i++) { cost_sum += metric(path[i], path[i + 1]); } return(cost_sum); }
public static Node GetNearestNode(this Path path, Vector3 position, GraphUtility.Metric metric = null, System.Func <Node, bool> predicate = null) { if (metric == null) { metric = GraphUtility.EuclideanMetric; } if (predicate == null) { predicate = node => true; } return(path .Where(predicate) .MinElement(node => metric(node, GraphUtility.CreatePositionNode(position)))); }
public HighwaySystem(Terrain terrain, IEnumerable <Vector3> cities) { Vector3 terrain_size = terrain.terrainData.size; Vector3 terrain_center = terrain.GetPosition() + terrain_size / 2; Graph terrain_graph = GraphUtility.CreateGrid(terrain, TerrainGridResolution); Graph city_graph = GraphUtility.CreateHairball(cities).MinimumSpanned_Obstacle(); //Select capital Node capital = city_graph.Nodes .Where(node => node.Neighbors.Count() == 1) .RandomElement(); //Get edges leaving the capital HashSet <Edge> left_edges = new HashSet <Edge>(); foreach (Node node in city_graph.Nodes) { if (node != capital) { left_edges.UnionWith(capital.GetPathTo_Euclidean(node).Edges); } } //Embed left_city_graph within terrain_graph Graph left_city_graph = GraphUtility.CreateGraph(left_edges); Graph left_lanes = terrain_graph.Defleshed(left_city_graph, GraphUtility.ObstacleMetric); //Do the same for right_city_graph, but alter the cost metric //so the right_lanes prefer a margin to exist between //opposing lanes of traffic. Graph right_city_graph = GraphUtility.CreateGraph( left_edges.Select(edge => new Edge(edge.B, edge.A))); foreach (Node node in right_city_graph.Nodes) { Edge edge; if (node.GetPosition() == capital.GetPosition()) { edge = right_city_graph.Edges.FirstOrDefault(edge_ => edge_.B == node); } else { edge = right_city_graph.Edges.FirstOrDefault(edge_ => edge_.A == node); } Vector3 offset = (edge.A.GetPosition() - edge.B.GetPosition()) .Crossed(new Vector3(0, 1, 0)).normalized *OpposingTrafficMargin; node.SetPosition(node.GetPosition() + offset); } GraphUtility.Metric opposing_traffic_metric = GraphUtility.CreateBakedMetric( delegate(Node a, Node b) { System.Func <Vector3, float> get_height = position => Mathf.Max(0, (OpposingTrafficMargin - left_lanes.Distance(position)) / OpposingTrafficMargin); return(GraphUtility.CreateHeightMetric( get_height, height => height * 2, slope => 0, TerrainGridResolution * Mathf.Sqrt(2))(a, b) + GraphUtility.ObstacleMetric(a, b));; }); GraphUtility.Heuristics[opposing_traffic_metric] = GraphUtility.EuclideanMetric; Graph right_lanes = terrain_graph .Defleshed(right_city_graph, opposing_traffic_metric); //Merge duplicate nodes foreach (Node node in left_lanes.Nodes.ToList()) { foreach (Node other_node in right_lanes.Nodes) { if (node.GetPosition() != other_node.GetPosition()) { continue; } left_lanes.RemoveNode(node); left_lanes.AddNode(other_node); foreach (Node neighbor in node.Neighbors) { other_node.AddNeighbor(neighbor); } IEnumerable <Node> neighbors = left_lanes.Nodes .Where(potential_neighbor => potential_neighbor.Neighbors.Contains(node)); foreach (Node neighbor in neighbors.ToList()) { neighbor.RemoveNeighbor(node); neighbor.AddNeighbor(other_node); } } } //Create "bridges" from one highway system to the other. int bridge_count = 0; bool is_forward_edge = true; foreach (Node node in left_lanes.Nodes) { if ((bridge_count++ % 4) == 0) { Node other_node = right_lanes.GetNearestNode( other_node_ => is_forward_edge ? GraphUtility.ObstacleMetric(node, other_node_) : GraphUtility.ObstacleMetric(other_node_, node), other_node_ => node != other_node_ && !node.Neighbors.Contains(other_node_) && !other_node_.Neighbors.Contains(node)); if (is_forward_edge) { node.AddNeighbor(other_node); } else { other_node.AddNeighbor(node); } is_forward_edge = !is_forward_edge; } } Highways = GraphUtility.CreateGraph( left_lanes.Edges.Union(right_lanes.Edges)); //Fully connect the endpoints of paths because their connectivity //tends to be problematic. HashSet <Node> endpoints = new HashSet <Node>( left_city_graph.Nodes.Union(right_city_graph.Nodes) .Select(node => Highways.GetNearestNode(node))); foreach (Node node in endpoints) { foreach (Node other_node in Highways.Nodes) { if (node != other_node && node.Distance(other_node) < 1.2f * Mathf.Sqrt(2) * TerrainGridResolution) { node.AddNeighbor(other_node); } } } }
//Assumes admissible, consistant heuristic public static Path GetPathTo(this Node source, Node destination, GraphUtility.Metric metric) { //"Guest" destinations aren't actually in the graph, but we can //still path to them by pretending they are everyone's neighbor bool destination_is_guest = !source.IsConnectedTo(destination); Dictionary <Node, Node> previous_node = new Dictionary <Node, Node>(); Dictionary <Node, float> cost_to_source = new Dictionary <Node, float>(); Dictionary <Node, float> cost_to_destination_estimate = new Dictionary <Node, float>(); System.Func <Node, float> GetPathCostEstimate = node => cost_to_source[node] + cost_to_destination_estimate[node]; SortedSet <Node> open_set = new SortedSet <Node>( Comparer <Node> .Create((a, b) => GetPathCostEstimate(a).CompareTo(GetPathCostEstimate(b)))); HashSet <Node> closed_set = new HashSet <Node>(); open_set.Add(source); previous_node[source] = null; cost_to_source[source] = 0; cost_to_destination_estimate[source] = 0; while (open_set.Count > 0) { Node node = open_set.First(); open_set.Remove(node); closed_set.Add(node); if (node == destination) { break; } IEnumerable <Node> neighbors = node.Neighbors; if (destination_is_guest) { neighbors = neighbors.Union(Utility.List(destination)); } foreach (Node neighbor in neighbors) { if (closed_set.Contains(neighbor)) { continue; } float neighbor_to_source_cost = metric(node, neighbor) + cost_to_source[node]; bool contains = cost_to_source.ContainsKey(neighbor); if (cost_to_source.ContainsKey(neighbor)) { if (neighbor_to_source_cost > cost_to_source[neighbor]) { continue; } else { open_set.Remove(neighbor); } } previous_node[neighbor] = node; cost_to_source[neighbor] = neighbor_to_source_cost; cost_to_destination_estimate[neighbor] = GraphUtility.Heuristics[metric](neighbor, destination); open_set.Add(neighbor); } } if (!previous_node.ContainsKey(destination)) { return(null); } return(new Path(Utility.List(destination, node => previous_node[node]).Reversed())); }