przywrócono wyliczanie działek

This commit is contained in:
Kacper Donat 2019-11-16 17:23:03 +01:00
parent c0eda23d16
commit 4361d2b3cd
8 changed files with 163 additions and 66 deletions

View File

@ -155,7 +155,7 @@ MonoBehaviour:
displayBoundary: 1
displayVertices: 0
displayNeighbours: 0
displayLabels: 0
displayLabels: 1
displayEdges: 0
displayLocations: 0
displayLocationCells: 0
@ -164,6 +164,7 @@ MonoBehaviour:
displayLocationPoints: 0
displayFieldBoundaries: 1
displayCityRoads: 0
displayCityFields: 1
size: {x: 250, y: 250}
types:
- name:
@ -179,8 +180,10 @@ MonoBehaviour:
color: {r: 0, g: 1, b: 0.042674065, a: 1}
height: 0
minimumRoadAngle: 30
minimumNudgeDistance: 0.25
maximumNudgeDistance: 0.75
radius: 5
seed: 865432704
seed: 1686660096
--- !u!4 &319467308
Transform:
m_ObjectHideFlags: 0

View File

@ -1,9 +1,12 @@
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
{
@ -12,18 +15,19 @@ namespace Assets.AnnotationPass
private Random _random;
private Graph<Point> _voronoiGraph;
private double _minimumAngle;
public CityFieldsPass(Random random, double minimumAngle = 0.2)
public double MinimumAngle { get; set; }
public double MaxNudgeDistance { get; set; }
public double MinNudgeDistance { get; set; }
public CityFieldsPass(Random random)
{
_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()));
return city.Sites.Any(s => PointUtils.IsPointInside(point, s.Boundary.Select(i => _voronoiGraph[i]).ToArray()));
}
private void AnnotateCity(City city)
@ -31,8 +35,68 @@ namespace Assets.AnnotationPass
CreateRoadGraph(city, _voronoiGraph.Vertices);
RemoveIncorrectEdges(city);
ConnectPointsIntoRoads(city);
FixRoadsDensity(city, _minimumAngle);
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)
@ -47,33 +111,33 @@ namespace Assets.AnnotationPass
private void RemoveIncorrectEdges(City city)
{
var edges = city.roads.Edges.ToList();
var edges = city.Roads.Edges.ToList();
foreach (var (a, b) in edges)
{
var (va, vb) = (city.roads[a], city.roads[b]);
var (va, vb) = (city.Roads[a], city.Roads[b]);
var center = (va + vb) / 2;
if (!IsPointInsideCity(city, center))
city.roads.DeleteEdge(a, b);
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 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.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])) };
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]));
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.
@ -81,7 +145,7 @@ namespace Assets.AnnotationPass
var edges = original.Edges.OrderByDescending(e => original.GetEdgeData(e));
Graph<Point> roads = new Graph<Point>() { Vertices = city.roads.Vertices };
Graph<Point> roads = new Graph<Point>() { Vertices = city.Roads.Vertices };
foreach (var edge in original.Edges)
{
@ -93,30 +157,32 @@ namespace Assets.AnnotationPass
break;
}
city.roads = roads;
city.Roads = roads;
}
private void CreateFieldBoundaries(City city)
{
foreach (var (a, b) in city.roads.Edges)
city.fieldBoundaries.AddEdge(a, b);
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);
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
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]))
.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);
.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);
city.FieldBoundaries.AddEdge(deadEnd, closest);
city.Roads.AddEdge(deadEnd, closest);
}
catch (InvalidOperationException e)
{
@ -136,25 +202,25 @@ namespace Assets.AnnotationPass
// potem według jakiegoś parametru usuń te, które są za bliskie
for (int i = 0; i < city.roads.Vertices.Count(); i++)
for (int i = 0; i < city.Roads.Vertices.Count(); i++)
{
var p = city.roads[i];
if (city.roads.Neighbours(i).Count() <= 2)
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]))
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)
if( PointUtils.AngleBetween(p, city.Roads[a], city.Roads[b]) < angle)
{
city.roads.DeleteEdge(i, a);
city.Roads.DeleteEdge(i, a);
}
}
}

View File

@ -22,12 +22,12 @@ namespace Assets.AnnotationPass
IEnumerable<int> LocateCities()
{
return ChoosePoints(20);
return ChoosePoints(5);
}
City CreateCity(int vertex, int size)
{
City newCity = new City(_basicGraph);
City newCity = new City();
var site = _basicGraph.Vertices[vertex];
var sites = new List<MapSite> { site };

View File

@ -7,41 +7,36 @@ namespace Assets.Cities
{
public class City
{
public Graph<MapSite> startGraph;
public List<MapSite> sites;
public List<MapSite> Sites { get; set; } = new List<MapSite>();
public Graph<Point> roads = new Graph<Point>();
public Graph<Point> fieldBoundaries = new Graph<Point>();
public Graph<Point> Roads { get; set; } = new Graph<Point>();
public Graph<Point> FieldBoundaries { get; set; } = new Graph<Point>();
public List<CityField> fields = new List<CityField>();
public List<CityField> Fields { get; } = new List<CityField>();
public List<(int, int)> edges;
public List<(int, int)> Edges = new List<(int, int)>();
public City(Graph<MapSite> startGraph)
public City()
{
this.startGraph = startGraph;
this.sites = new List<MapSite>();
this.edges = new List<(int, int)>();
}
public City(Graph<MapSite> startGraph, List<MapSite> sitesList) : this(startGraph)
public City(List<MapSite> sites)
{
this.sites = sitesList;
Sites = sites;
}
public void AddSite(MapSite site)
{
sites.Add(site);
Sites.Add(site);
FixBoundaryEdges(site);
}
private void FixBoundaryEdges(MapSite site)
{
var a = edges;
var a = Edges;
var b = site.Edges.Select(x => x.Item1 < x.Item2 ? x : (x.Item2, x.Item1));
edges = a.Union(b).Except(a.Intersect(b)).ToList();
Edges = a.Union(b).Except(a.Intersect(b)).ToList();
}
}
}

View File

@ -5,6 +5,6 @@ namespace Assets.Cities
{
public class CityField
{
public List<Point> boundary;
public ICollection<Point> Boundary { get; set; } = new List<Point>();
}
}

View File

@ -112,10 +112,10 @@ namespace Assets.Common
{
if (!_edges.ContainsKey(vertex)) return Enumerable.Empty<int>();
return _edges[vertex];
return _edges[vertex].Distinct();
}
virtual public object Clone()
public virtual object Clone()
{
return Morph(l => l);
}

View File

@ -3,11 +3,12 @@ using System.Collections.Generic;
namespace Assets.Common
{
public class Point
public class Point : ICloneable
{
public double x;
public double y;
public double Length => Math.Sqrt(x*x+y*y);
public Point Direction => this / Length;
public Point(double x, double y)
{
@ -28,6 +29,10 @@ namespace Assets.Common
return hashCode;
}
public object Clone()
{
return new Point(x, y);
}
public static Point operator +(Point a, Point b) => new Point(a.x + b.x, a.y + b.y);
public static Point operator +(Point a) => a;
@ -35,7 +40,5 @@ namespace Assets.Common
public static Point operator -(Point a) => new Point(-a.x, -a.y);
public static Point operator *(Point a, double b) => new Point(a.x * b, a.y * b);
public static Point operator /(Point a, double b) => new Point(a.x / b, a.y / b);
}
}

View File

@ -34,6 +34,7 @@ namespace Assets
public bool displayLocationPoints = true;
public bool displayFieldBoundaries = true;
public bool displayCityRoads = true;
public bool displayCityFields = true;
[NonSerialized] public float generationTime = 0.0f;
}
@ -58,7 +59,12 @@ namespace Assets
public List<LocationType> types = new List<LocationType>();
[Range(0f, 360f)]
public double minimumRoadAngle = 30f;
public float minimumRoadAngle = 30f;
[Range(0f, 4f)]
public float minimumNudgeDistance = 0.5f;
[Range(0f, 4f)]
public float maximumNudgeDistance = 1f;
[Range(2.0f, 64.0f)]
public float radius = 8;
@ -96,7 +102,12 @@ namespace Assets
generator.AddAnnotationPass(new LandmassPass(types.Prepend(new LocationType { name = "Ocean", height = -1 }).Select(t => new Location { Type = t }), new Random(seed)));
generator.AddAnnotationPass(new LocateCitiesPass(new Random(seed)));
generator.AddAnnotationPass(new CityFieldsPass(new Random(seed), Mathf.Deg2Rad * minimumRoadAngle));
generator.AddAnnotationPass(new CityFieldsPass(new Random(seed))
{
MinimumAngle = Mathf.Deg2Rad * minimumRoadAngle,
MaxNudgeDistance = maximumNudgeDistance,
MinNudgeDistance = minimumNudgeDistance,
});
return generator;
}
@ -110,12 +121,12 @@ namespace Assets
{
foreach (City city in cities)
{
foreach (var (a, b) in city.edges)
foreach (var (a, b) in city.Edges)
{
Gizmos.DrawLine(Map.Boundaries[a].ToVector3(), Map.Boundaries[b].ToVector3());
}
foreach (var a in city.sites)
foreach (var a in city.Sites)
Gizmos.DrawSphere(a.Center.ToVector3(), 1);
}
}
@ -224,7 +235,10 @@ namespace Assets
Gizmos.color = Color.white;
foreach (var city in cities)
DebugUtils.DisplayGraphEdges(city.fieldBoundaries);
{
DebugUtils.DisplayGraphEdges(city.FieldBoundaries);
DebugUtils.DisplayGraphVertices(city.Roads, displayLabels: debug.displayLabels);
}
}
if (debug.displayCityRoads)
@ -233,8 +247,24 @@ namespace Assets
foreach (var city in cities)
{
DebugUtils.DisplayGraphEdges(city.roads);
DebugUtils.DisplayGraphVertices(city.roads, displayLabels: debug.displayLabels);
DebugUtils.DisplayGraphEdges(city.Roads);
DebugUtils.DisplayGraphVertices(city.Roads, displayLabels: debug.displayLabels);
}
}
if (debug.displayCityFields)
{
Gizmos.color = Color.green;
foreach (var city in cities)
{
foreach (var field in city.Fields)
{
foreach (var (a, b) in field.Boundary.RotateRight().Zip(field.Boundary, (a, b) => (a, b)))
{
Gizmos.DrawLine(a.ToVector3(), b.ToVector3());
}
}
}
}
}