diff --git a/Assets/Scenes/OverworldExample.unity b/Assets/Scenes/OverworldExample.unity index 5340733..d3d0133 100644 --- a/Assets/Scenes/OverworldExample.unity +++ b/Assets/Scenes/OverworldExample.unity @@ -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 diff --git a/Assets/Scripts/AnnotationPass/CityFieldsPass.cs b/Assets/Scripts/AnnotationPass/CityFieldsPass.cs index 01c9add..c39e84c 100644 --- a/Assets/Scripts/AnnotationPass/CityFieldsPass.cs +++ b/Assets/Scripts/AnnotationPass/CityFieldsPass.cs @@ -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 _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 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 { Vertices = city.roads.Vertices, Edges = city.edges.Select(e => (mapping[e.Item1], mapping[e.Item2])) }; + 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])); + 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 roads = new Graph() { Vertices = city.roads.Vertices }; + Graph roads = new Graph() { 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); } } } diff --git a/Assets/Scripts/AnnotationPass/LocateCitiesPass.cs b/Assets/Scripts/AnnotationPass/LocateCitiesPass.cs index 587c8db..abe6fb6 100644 --- a/Assets/Scripts/AnnotationPass/LocateCitiesPass.cs +++ b/Assets/Scripts/AnnotationPass/LocateCitiesPass.cs @@ -22,12 +22,12 @@ namespace Assets.AnnotationPass IEnumerable 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 { site }; diff --git a/Assets/Scripts/Cities/City.cs b/Assets/Scripts/Cities/City.cs index bb8b106..55c9098 100644 --- a/Assets/Scripts/Cities/City.cs +++ b/Assets/Scripts/Cities/City.cs @@ -7,41 +7,36 @@ namespace Assets.Cities { public class City { - public Graph startGraph; - public List sites; + public List Sites { get; set; } = new List(); - public Graph roads = new Graph(); - public Graph fieldBoundaries = new Graph(); + public Graph Roads { get; set; } = new Graph(); + public Graph FieldBoundaries { get; set; } = new Graph(); - public List fields = new List(); + public List Fields { get; } = new List(); - public List<(int, int)> edges; + public List<(int, int)> Edges = new List<(int, int)>(); - public City(Graph startGraph) + public City() { - this.startGraph = startGraph; - - this.sites = new List(); - this.edges = new List<(int, int)>(); } - - public City(Graph startGraph, List sitesList) : this(startGraph) + + public City(List 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(); } } } \ No newline at end of file diff --git a/Assets/Scripts/Cities/CityField.cs b/Assets/Scripts/Cities/CityField.cs index 27b4f7f..c0240f6 100644 --- a/Assets/Scripts/Cities/CityField.cs +++ b/Assets/Scripts/Cities/CityField.cs @@ -5,6 +5,6 @@ namespace Assets.Cities { public class CityField { - public List boundary; + public ICollection Boundary { get; set; } = new List(); } } \ No newline at end of file diff --git a/Assets/Scripts/Common/Graph.cs b/Assets/Scripts/Common/Graph.cs index 45c3e8d..40117de 100644 --- a/Assets/Scripts/Common/Graph.cs +++ b/Assets/Scripts/Common/Graph.cs @@ -112,10 +112,10 @@ namespace Assets.Common { if (!_edges.ContainsKey(vertex)) return Enumerable.Empty(); - return _edges[vertex]; + return _edges[vertex].Distinct(); } - virtual public object Clone() + public virtual object Clone() { return Morph(l => l); } diff --git a/Assets/Scripts/Common/Point.cs b/Assets/Scripts/Common/Point.cs index b25a061..8916cf0 100644 --- a/Assets/Scripts/Common/Point.cs +++ b/Assets/Scripts/Common/Point.cs @@ -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); - - } } \ No newline at end of file diff --git a/Assets/Scripts/LandmassGenerator.cs b/Assets/Scripts/LandmassGenerator.cs index 302fa05..aa1f9e4 100644 --- a/Assets/Scripts/LandmassGenerator.cs +++ b/Assets/Scripts/LandmassGenerator.cs @@ -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 types = new List(); [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()); + } + } } } }