inz-00/Assets/Scripts/AnnotationPass/CityFieldsPass.cs

163 lines
6.0 KiB
C#

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<Point> _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<List<City>>(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<Point> 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<Point> { 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<Point> roads = new Graph<Point>() { Vertices = city.roads.Vertices };
foreach (var edge in original.Edges)
{
roads.AddEdge(edge.Item1, edge.Item2);
if (Graph<Point>.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);
}
}
}
}
}
}