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

View File

@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Assets.Cities; using Assets.Cities;
using Assets.Common; using Assets.Common;
using Assets.Voronoi; using Assets.Voronoi;
using UnityEngine.SocialPlatforms.GameCenter;
using UnityEngine.UIElements;
namespace Assets.AnnotationPass namespace Assets.AnnotationPass
{ {
@ -13,17 +16,18 @@ namespace Assets.AnnotationPass
private Graph<Point> _voronoiGraph; private Graph<Point> _voronoiGraph;
private double _minimumAngle; public double MinimumAngle { get; set; }
public double MaxNudgeDistance { get; set; }
public double MinNudgeDistance { get; set; }
public CityFieldsPass(Random random, double minimumAngle = 0.2) public CityFieldsPass(Random random)
{ {
_random = random; _random = random;
_minimumAngle = minimumAngle;
} }
private bool IsPointInsideCity(City city, Point point) 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) private void AnnotateCity(City city)
@ -31,8 +35,68 @@ namespace Assets.AnnotationPass
CreateRoadGraph(city, _voronoiGraph.Vertices); CreateRoadGraph(city, _voronoiGraph.Vertices);
RemoveIncorrectEdges(city); RemoveIncorrectEdges(city);
ConnectPointsIntoRoads(city); ConnectPointsIntoRoads(city);
FixRoadsDensity(city, _minimumAngle); FixRoadsDensity(city, MinimumAngle);
CreateFieldBoundaries(city); 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) public void Annotate(Map map)
@ -47,33 +111,33 @@ namespace Assets.AnnotationPass
private void RemoveIncorrectEdges(City city) private void RemoveIncorrectEdges(City city)
{ {
var edges = city.roads.Edges.ToList(); var edges = city.Roads.Edges.ToList();
foreach (var (a, b) in edges) 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; var center = (va + vb) / 2;
if (!IsPointInsideCity(city, center)) if (!IsPointInsideCity(city, center))
city.roads.DeleteEdge(a, b); city.Roads.DeleteEdge(a, b);
} }
} }
private void CreateRoadGraph(City city, IList<Point> points) 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); 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)); VoronoiGenerator generator = new VoronoiGenerator(pointsForRoads.Select(x => x.Item2));
generator.Generate(); 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) 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. //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. //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)); 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) foreach (var edge in original.Edges)
{ {
@ -93,30 +157,32 @@ namespace Assets.AnnotationPass
break; break;
} }
city.roads = roads; city.Roads = roads;
} }
private void CreateFieldBoundaries(City city) private void CreateFieldBoundaries(City city)
{ {
foreach (var (a, b) in city.roads.Edges) foreach (var (a, b) in city.Roads.Edges)
city.fieldBoundaries.AddEdge(a, b); 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) foreach (var deadEnd in deadEnds)
{ {
try try
{ {
var neighbour = city.fieldBoundaries.Neighbours(deadEnd).First(); var neighbour = city.FieldBoundaries.Neighbours(deadEnd).First();
var closest = city.fieldBoundaries.Vertices var closest = city.FieldBoundaries.Vertices
.Select((_, i) => i) .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) .Skip(1)
.First(c => c != neighbour && PointUtils.AngleBetween(city.fieldBoundaries[deadEnd], .First(c =>
city.fieldBoundaries[c], city.fieldBoundaries[neighbour]) > _minimumAngle); c != neighbour &&
PointUtils.AngleBetween(city.FieldBoundaries[deadEnd],city.FieldBoundaries[c], city.FieldBoundaries[neighbour]) > MinimumAngle
);
city.fieldBoundaries.AddEdge(deadEnd, closest); city.FieldBoundaries.AddEdge(deadEnd, closest);
city.roads.AddEdge(deadEnd, closest); city.Roads.AddEdge(deadEnd, closest);
} }
catch (InvalidOperationException e) catch (InvalidOperationException e)
{ {
@ -136,25 +202,25 @@ namespace Assets.AnnotationPass
// potem według jakiegoś parametru usuń te, które są za bliskie // 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]; var p = city.Roads[i];
if (city.roads.Neighbours(i).Count() <= 2) if (city.Roads.Neighbours(i).Count() <= 2)
continue; continue;
var reference = new Point(p.x, p.y + 30); var reference = new Point(p.x, p.y + 30);
var orderedNeighours = city.roads.Neighbours(i) var orderedNeighours = city.Roads.Neighbours(i)
.Select(n => (Vertex: n, Point: city.roads[n])) .Select(n => (Vertex: n, Point: city.Roads[n]))
.Select(x => (Vertex: x.Vertex, Angle: PointUtils.AngleBetween(p, reference, x.Point))) .Select(x => (Vertex: x.Vertex, Angle: PointUtils.AngleBetween(p, reference, x.Point)))
.OrderBy(x => x.Angle) .OrderBy(x => x.Angle)
.Select(x => x.Vertex); .Select(x => x.Vertex);
foreach (var (a, b) in orderedNeighours.RotateRight(1).Zip(orderedNeighours, (a, b) => (a, b))) 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() IEnumerable<int> LocateCities()
{ {
return ChoosePoints(20); return ChoosePoints(5);
} }
City CreateCity(int vertex, int size) City CreateCity(int vertex, int size)
{ {
City newCity = new City(_basicGraph); City newCity = new City();
var site = _basicGraph.Vertices[vertex]; var site = _basicGraph.Vertices[vertex];
var sites = new List<MapSite> { site }; var sites = new List<MapSite> { site };

View File

@ -7,41 +7,36 @@ namespace Assets.Cities
{ {
public class City public class City
{ {
public Graph<MapSite> startGraph; public List<MapSite> Sites { get; set; } = new List<MapSite>();
public List<MapSite> sites;
public Graph<Point> roads = new Graph<Point>(); public Graph<Point> Roads { get; set; } = new Graph<Point>();
public Graph<Point> fieldBoundaries = 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) public void AddSite(MapSite site)
{ {
sites.Add(site); Sites.Add(site);
FixBoundaryEdges(site); FixBoundaryEdges(site);
} }
private void FixBoundaryEdges(MapSite 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)); 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 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>(); 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); return Morph(l => l);
} }

View File

@ -3,11 +3,12 @@ using System.Collections.Generic;
namespace Assets.Common namespace Assets.Common
{ {
public class Point public class Point : ICloneable
{ {
public double x; public double x;
public double y; public double y;
public double Length => Math.Sqrt(x*x+y*y); public double Length => Math.Sqrt(x*x+y*y);
public Point Direction => this / Length;
public Point(double x, double y) public Point(double x, double y)
{ {
@ -28,6 +29,10 @@ namespace Assets.Common
return hashCode; 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, Point b) => new Point(a.x + b.x, a.y + b.y);
public static Point operator +(Point a) => a; 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) => 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);
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 displayLocationPoints = true;
public bool displayFieldBoundaries = true; public bool displayFieldBoundaries = true;
public bool displayCityRoads = true; public bool displayCityRoads = true;
public bool displayCityFields = true;
[NonSerialized] public float generationTime = 0.0f; [NonSerialized] public float generationTime = 0.0f;
} }
@ -58,7 +59,12 @@ namespace Assets
public List<LocationType> types = new List<LocationType>(); public List<LocationType> types = new List<LocationType>();
[Range(0f, 360f)] [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)] [Range(2.0f, 64.0f)]
public float radius = 8; 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 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 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; return generator;
} }
@ -110,12 +121,12 @@ namespace Assets
{ {
foreach (City city in cities) 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()); 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); Gizmos.DrawSphere(a.Center.ToVector3(), 1);
} }
} }
@ -224,7 +235,10 @@ namespace Assets
Gizmos.color = Color.white; Gizmos.color = Color.white;
foreach (var city in cities) foreach (var city in cities)
DebugUtils.DisplayGraphEdges(city.fieldBoundaries); {
DebugUtils.DisplayGraphEdges(city.FieldBoundaries);
DebugUtils.DisplayGraphVertices(city.Roads, displayLabels: debug.displayLabels);
}
} }
if (debug.displayCityRoads) if (debug.displayCityRoads)
@ -233,8 +247,24 @@ namespace Assets
foreach (var city in cities) foreach (var city in cities)
{ {
DebugUtils.DisplayGraphEdges(city.roads); DebugUtils.DisplayGraphEdges(city.Roads);
DebugUtils.DisplayGraphVertices(city.roads, displayLabels: debug.displayLabels); 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());
}
}
} }
} }
} }