using System; using System.Collections.Generic; using System.Linq; using Assets.Cities; using Assets.Common; using Assets.Voronoi; namespace Assets.AnnotationPass { public class CityFieldsPass : IAnnotationPass { private Random _random; private Graph _voronoiGraph; private double _minimumAngle; public CityFieldsPass(Random random, double minimumAngle = 0.2) { _random = random; _minimumAngle = minimumAngle; } private bool IsPointInsideCity(City city, Point point) { return city.sites.Any(s => PointUtils.IsPointInside(point, s.Boundary.Select(i => _voronoiGraph[i]).ToArray())); } private void AnnotateCity(City city) { CreateRoadGraph(city, _voronoiGraph.Vertices); RemoveIncorrectEdges(city); ConnectPointsIntoRoads(city); FixRoadsDensity(city, _minimumAngle); CreateFieldBoundaries(city); } public void Annotate(Map map) { _voronoiGraph = map.Boundaries; var cities = map.Metadata.GetProperty>(LocateCitiesPass.CitiesProperty); foreach (var city in cities) AnnotateCity(city); } private void RemoveIncorrectEdges(City city) { var edges = city.roads.Edges.ToList(); foreach (var (a, b) in edges) { var (va, vb) = (city.roads[a], city.roads[b]); var center = (va + vb) / 2; if (!IsPointInsideCity(city, center)) city.roads.DeleteEdge(a, b); } } private void CreateRoadGraph(City city, IList points) { var pointsForRoads = city.sites.SelectMany(site => site.Boundary.Select(i => (i, points[i])).Append((-1, site.Center))).Distinct().ToList(); var mapping = pointsForRoads.Select((x, i) => x.Item1 == -1 ? (-1, -1) : (i, x.Item1)).Where(x => x.Item1 != -1).ToDictionary(x => x.Item2, x => x.Item1); VoronoiGenerator generator = new VoronoiGenerator(pointsForRoads.Select(x => x.Item2)); generator.Generate(); city.roads = generator.Delaunay.Morph(s => s.Point); city.fieldBoundaries = new Graph { Vertices = city.roads.Vertices, Edges = city.edges.Select(e => (mapping[e.Item1], mapping[e.Item2])) }; } private void ConnectPointsIntoRoads(City city) { var original = city.roads.Morph(s => s, e => Point.Dist(city.roads[e.Item1], city.roads[e.Item2])); //1.Sort all the edges in non - decreasing order of their weight. //2.Pick the smallest edge.Check if it forms a cycle with the spanning tree formed so far. If cycle is not formed, include this edge.Else, discard it. //3.Repeat step#2 until there are (V-1) edges in the spanning tree. var edges = original.Edges.OrderByDescending(e => original.GetEdgeData(e)); Graph roads = new Graph() { Vertices = city.roads.Vertices }; foreach (var edge in original.Edges) { roads.AddEdge(edge.Item1, edge.Item2); if (Graph.HasCycle(roads)) roads.DeleteEdge(edge.Item1, edge.Item2); if (roads.Edges.Count() == roads.Vertices.Count() - 1) break; } city.roads = roads; } private void CreateFieldBoundaries(City city) { foreach (var (a, b) in city.roads.Edges) city.fieldBoundaries.AddEdge(a, b); var deadEnds = city.fieldBoundaries.Vertices.Select((_, i) => i).Where(i => city.fieldBoundaries.Neighbours(i).Count() == 1); foreach (var deadEnd in deadEnds) { try { var neighbour = city.fieldBoundaries.Neighbours(deadEnd).First(); var closest = city.fieldBoundaries.Vertices .Select((_, i) => i) .OrderBy(i => Point.Dist(city.fieldBoundaries[i], city.fieldBoundaries[deadEnd])) .Skip(1) .First(c => c != neighbour && PointUtils.AngleBetween(city.fieldBoundaries[deadEnd], city.fieldBoundaries[c], city.fieldBoundaries[neighbour]) > _minimumAngle); city.fieldBoundaries.AddEdge(deadEnd, closest); city.roads.AddEdge(deadEnd, closest); } catch (InvalidOperationException e) { } } } private void FixRoadsDensity(City city, double angle = 0.25f) { // dla każdego punktu w grafie dróg // chyba że ma 2 to pomiń? // wyznacz punkt 0 // oblicz odległości kątowe do wszystkich punktów // posortuj je // między wszystkimiu parami oblicz kąt // potem według jakiegoś parametru usuń te, które są za bliskie for (int i = 0; i < city.roads.Vertices.Count(); i++) { var p = city.roads[i]; if (city.roads.Neighbours(i).Count() <= 2) continue; var reference = new Point(p.x, p.y + 30); var orderedNeighours = city.roads.Neighbours(i) .Select(n => (Vertex: n, Point: city.roads[n])) .Select(x => (Vertex: x.Vertex, Angle: PointUtils.AngleBetween(p, reference, x.Point))) .OrderBy(x => x.Angle) .Select(x => x.Vertex); foreach (var (a, b) in orderedNeighours.RotateRight(1).Zip(orderedNeighours, (a, b) => (a, b))) { if( PointUtils.AngleBetween(p, city.roads[a], city.roads[b]) < angle) { city.roads.DeleteEdge(i, a); } } } } } }