291 lines
10 KiB
C#
291 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Assets.Cities;
|
|
using Assets.Common;
|
|
using System;
|
|
using Assets.Voronoi;
|
|
using UnityEngine;
|
|
using Random = System.Random;
|
|
|
|
namespace Assets.AnnotationPass
|
|
{
|
|
public class CityFieldsPass : IAnnotationPass
|
|
{
|
|
private Random _random;
|
|
|
|
private Graph<Point> _voronoiGraph;
|
|
|
|
public double MinimumAngle { get; set; }
|
|
public double MaxNudgeDistance { get; set; } = .25;
|
|
public double MinNudgeDistance { get; set; } = .75;
|
|
|
|
public double MinimumRoadLength { get; set; } = 2;
|
|
public double BusinessCityFields { get; set; } = 0.3;
|
|
|
|
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);
|
|
RemoveTooShortRoads(city);
|
|
NudgeCityFields(city);
|
|
}
|
|
|
|
private void RemoveTooShortRoads(City city)
|
|
{
|
|
foreach (var field in city.Fields)
|
|
{
|
|
field.Boundary = field.Boundary.Aggregate(new List<Point>(), (boundary, point) =>
|
|
{
|
|
if (boundary.Count == 0)
|
|
{
|
|
boundary.Add(point);
|
|
}
|
|
else
|
|
{
|
|
var last = boundary[boundary.Count - 1];
|
|
|
|
if (Point.Dist(last, point) > MinimumRoadLength)
|
|
boundary.Add(point);
|
|
else
|
|
boundary[boundary.Count - 1] = (point + last) / 2;
|
|
}
|
|
|
|
return boundary;
|
|
});
|
|
|
|
var f = field.Boundary.First();
|
|
var l = field.Boundary.Last();
|
|
|
|
if (Point.Dist(l, f) < MinimumRoadLength)
|
|
{
|
|
field.Boundary[0] = (f + l) / 2;
|
|
field.Boundary.Remove(l);
|
|
}
|
|
}
|
|
|
|
city.Fields = city.Fields.Where(field => field.Boundary.Count > 2).ToList();
|
|
}
|
|
|
|
private void NudgeCityFields(City city)
|
|
{
|
|
foreach (var field in city.Fields)
|
|
{
|
|
var nudge = _random.NextDouble() * MaxNudgeDistance + MinNudgeDistance;
|
|
field.Boundary = field.Boundary.Zip(PointUtils.CalculateNormals(field.Boundary), (a, b) => (Point: a, Normal: b)).Select(tuple => tuple.Point - tuple.Normal * 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();
|
|
|
|
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);
|
|
|
|
field.Center = PointUtils.Mean(field.Boundary);
|
|
|
|
return field;
|
|
}
|
|
|
|
while (edges.Count > 0)
|
|
{
|
|
var (a, b) = edges[0];
|
|
edges.RemoveAt(0);
|
|
|
|
city.Fields.Add(CreateField(a, b));
|
|
}
|
|
|
|
// remove outside field
|
|
city.Fields.RemoveAll(field => !PointUtils.IsClockwise(field.Boundary));
|
|
|
|
// add field types
|
|
var orderedFields = city.Fields.OrderBy(cf => Point.Dist(cf.Center, city.Center)).ToList();
|
|
|
|
orderedFields.First().Type = FieldType.MainSquare;
|
|
|
|
|
|
int businessCityFields = (int)Math.Ceiling((BusinessCityFields*orderedFields.Count()));
|
|
|
|
foreach (var cityField in orderedFields.Skip(1).Take(businessCityFields))
|
|
{
|
|
cityField.Type = FieldType.Business;
|
|
}
|
|
|
|
|
|
foreach (var cityField in orderedFields.Skip(1+businessCityFields))
|
|
{
|
|
cityField.Type = FieldType.Living;
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |