using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Assets.Cities; using Assets.Common; using Assets.Voronoi; using UnityEngine.SocialPlatforms.GameCenter; using UnityEngine.UIElements; namespace Assets.AnnotationPass { public class CityFieldsPass : IAnnotationPass { private Random _random; private Graph _voronoiGraph; public double MinimumAngle { get; set; } public double MaxNudgeDistance { get; set; } public double MinNudgeDistance { get; set; } public CityFieldsPass(Random random) { _random = random; } 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); CreateCityFields(city); NudgeCityFields(city); } private void NudgeCityFields(City city) { foreach (var field in city.Fields) { var center = PointUtils.Mean(field.Boundary); var nudge = _random.NextDouble() * (MaxNudgeDistance - MinNudgeDistance) + MinNudgeDistance; field.Boundary = field.Boundary.Select(p => p - (p - center).Direction * nudge).ToList(); } } private void CreateCityFields(City city) { var edges = city.FieldBoundaries.Edges.Distinct().ToList(); edges = edges.Concat(edges.Select(tuple => (tuple.Item2, tuple.Item1))).ToList(); int GetNext(int current, int previous) { var center = city.FieldBoundaries[current]; var reference = center + new Point(0, 10); var neighbours = city.FieldBoundaries .Neighbours(current) .Select(n => (Vertex: n, Angle: PointUtils.AngleBetween(center, reference, city.FieldBoundaries[n]))) .OrderBy(tuple => tuple.Angle) .Select(tuple => tuple.Vertex) .ToList(); return neighbours[(neighbours.FindIndex(n => n == previous) + 1) % neighbours.Count]; } CityField CreateField(int start, int next) { CityField field = new CityField(); field.Boundary.Add(city.FieldBoundaries[start]); int watchdog = 100; int current = start; do { field.Boundary.Add(city.FieldBoundaries[current].Clone() as Point); edges.Remove((current, next)); (current, next) = (next, GetNext(next, current)); } while (current != start && watchdog-- > 0); return field; } while (edges.Count > 0) { var (a, b) = edges[0]; edges.RemoveAt(0); city.Fields.Add(CreateField(a, b)); } } 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); } } } } } }