diff --git a/Assets/Scripts/AnnotationPass/CityFieldsPass.cs b/Assets/Scripts/AnnotationPass/CityFieldsPass.cs index a9a4787..1e40aff 100644 --- a/Assets/Scripts/AnnotationPass/CityFieldsPass.cs +++ b/Assets/Scripts/AnnotationPass/CityFieldsPass.cs @@ -1,12 +1,9 @@ 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 { @@ -17,8 +14,10 @@ namespace Assets.AnnotationPass private Graph _voronoiGraph; public double MinimumAngle { get; set; } - public double MaxNudgeDistance { get; set; } - public double MinNudgeDistance { get; set; } + public double MaxNudgeDistance { get; set; } = .25; + public double MinNudgeDistance { get; set; } = .75; + + public double MinimumRoadLength { get; set; } = 2; public CityFieldsPass(Random random) { @@ -38,16 +37,52 @@ namespace Assets.AnnotationPass 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(), (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 center = PointUtils.Mean(field.Boundary); - var nudge = _random.NextDouble() * (MaxNudgeDistance - MinNudgeDistance) + MinNudgeDistance; - field.Boundary = field.Boundary.Select(p => p - (p - center).Direction * nudge).ToList(); + 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(); } } @@ -74,8 +109,6 @@ namespace Assets.AnnotationPass CityField CreateField(int start, int next) { CityField field = new CityField(); - - field.Boundary.Add(city.FieldBoundaries[start]); int watchdog = 100; int current = start; @@ -99,7 +132,11 @@ namespace Assets.AnnotationPass } // remove outside field - city.Fields.Remove(city.Fields.OrderByDescending(field => field.Boundary.Count).First()); + city.Fields.RemoveAll(field => + { + var points = field.Boundary.Take(3).ToArray(); + return !PointUtils.IsClockwise(points[0], points[1], points[2]); + }); } public void Annotate(Map map) diff --git a/Assets/Scripts/Cities/City.cs b/Assets/Scripts/Cities/City.cs index 55c9098..3ab326f 100644 --- a/Assets/Scripts/Cities/City.cs +++ b/Assets/Scripts/Cities/City.cs @@ -12,7 +12,7 @@ namespace Assets.Cities public Graph Roads { get; set; } = new Graph(); public Graph FieldBoundaries { get; set; } = new Graph(); - public List Fields { get; } = new List(); + public List Fields { get; set; } = new List(); public List<(int, int)> Edges = new List<(int, int)>(); diff --git a/Assets/Scripts/Cities/CityField.cs b/Assets/Scripts/Cities/CityField.cs index c0240f6..cd5baef 100644 --- a/Assets/Scripts/Cities/CityField.cs +++ b/Assets/Scripts/Cities/CityField.cs @@ -5,6 +5,6 @@ namespace Assets.Cities { public class CityField { - public ICollection Boundary { get; set; } = new List(); + public IList Boundary { get; set; } = new List(); } } \ No newline at end of file diff --git a/Assets/Scripts/Common/Extensions.cs b/Assets/Scripts/Common/Extensions.cs index f6cfb98..a65335a 100644 --- a/Assets/Scripts/Common/Extensions.cs +++ b/Assets/Scripts/Common/Extensions.cs @@ -29,6 +29,16 @@ namespace Assets.Common return tail.Concat(head); } + + public static IEnumerable RotateLeft(this IEnumerable collection, int count = 1) + { + var diff = collection.Count() - count; + + var head = collection.Take(count); + var tail = collection.Skip(count).Take(diff); + + return tail.Concat(head); + } } public static class HasMetadataExtensions diff --git a/Assets/Scripts/Common/PointUtils.cs b/Assets/Scripts/Common/PointUtils.cs index bf66492..1a15d2e 100644 --- a/Assets/Scripts/Common/PointUtils.cs +++ b/Assets/Scripts/Common/PointUtils.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Assets.Common { @@ -21,6 +23,20 @@ namespace Assets.Common return result; } + public static IEnumerable CalculateNormals(IEnumerable points) + { + var pairs = points.RotateRight().Zip(points, (a, b) => (a, b)); + var thirds = pairs.Zip(points.RotateLeft(), (pair, c) => (pair.a, pair.b, c)); + + return thirds.Select(third => + { + var left = (third.a - third.b).Direction; + var right = (third.c - third.b).Direction; + + return ((left + right) / 2).Direction * (IsClockwise(third.a, third.b, third.c) ? -1 : 1); + }); + } + public static double AngleBetween(Point center, Point a, Point b) { a = a - center; diff --git a/Assets/Scripts/LandmassGenerator.cs b/Assets/Scripts/LandmassGenerator.cs index 164dfee..ca9755a 100644 --- a/Assets/Scripts/LandmassGenerator.cs +++ b/Assets/Scripts/LandmassGenerator.cs @@ -65,6 +65,8 @@ namespace Assets public float minimumNudgeDistance = 0.5f; [Range(0f, 4f)] public float maximumNudgeDistance = 1f; + [Range(0f, 4f)] + public float minimumRoadLength = 1f; [Range(2.0f, 64.0f)] public float radius = 8; @@ -113,6 +115,7 @@ namespace Assets MinimumAngle = Mathf.Deg2Rad * minimumRoadAngle, MaxNudgeDistance = maximumNudgeDistance, MinNudgeDistance = minimumNudgeDistance, + MinimumRoadLength = minimumRoadLength }); return generator; @@ -267,9 +270,10 @@ namespace Assets 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()); - } + + foreach (var tuple in field.Boundary.Zip(PointUtils.CalculateNormals(field.Boundary), (a, b) => (Point: a, Normal: a + b))) + Gizmos.DrawLine(tuple.Point.ToVector3(), tuple.Normal.ToVector3()); } } } diff --git a/Assets/Scripts/MapRenderer.cs b/Assets/Scripts/MapRenderer.cs index 71c0c55..606c049 100644 --- a/Assets/Scripts/MapRenderer.cs +++ b/Assets/Scripts/MapRenderer.cs @@ -18,7 +18,8 @@ namespace Assets private IList _renderers = new List { - new LandmassRenderer() + new LandmassRenderer(), + new CityRenderer(), }; public void GenerateRandom() diff --git a/Assets/Scripts/RenderPass/CityRenderer.cs b/Assets/Scripts/RenderPass/CityRenderer.cs new file mode 100644 index 0000000..29f6007 --- /dev/null +++ b/Assets/Scripts/RenderPass/CityRenderer.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using Assets.AnnotationPass; +using Assets.Cities; +using Assets.Common; +using UnityEngine; + +namespace Assets.RenderPass +{ + public class CityRenderer : IRenderer + { + public void Render(Map map, Component component) + { + var go = new GameObject(); + go.AddComponent(); + go.AddComponent(); + + var cities = map.GetProperty>(LocateCitiesPass.CitiesProperty); + var meshes = cities.Select(city => new CombineInstance { mesh = CreateCityMesh(city), transform = component.transform.localToWorldMatrix }); + + var mesh = new Mesh(); + mesh.CombineMeshes(meshes.ToArray()); + + go.GetComponent().sharedMesh = mesh; + } + + private Mesh CreateCityMesh(City city) + { + List vertices = new List(); + List normals = new List(); + List triangles = new List(); + + int start = 0, n = 0; + foreach (var field in city.Fields) + { + start = vertices.Count; + n = field.Boundary.Count; + + vertices.AddRange(field.Boundary.Select(p => p.ToVector3() + Vector3.up * 5)); + normals.AddRange(field.Boundary.Select(v => Vector3.up)); + triangles.AddRange(Triangulate(field).Select(x => x + start)); + + start = vertices.Count; + + vertices.AddRange(field.Boundary.Select(p => p.ToVector3() + Vector3.up * 5)); + normals.AddRange(PointUtils.CalculateNormals(field.Boundary).Select(p => p.ToVector3())); + vertices.AddRange(field.Boundary.Select(p => p.ToVector3())); + normals.AddRange(PointUtils.CalculateNormals(field.Boundary).Select(p => p.ToVector3())); + + for (int i = 0; i < n; i++) + { + triangles.AddRange(new []{ start + i, start + n + i, start + n + (i + 1) % n }); + triangles.AddRange(new []{ start + i, start + n + (i + 1) % n, start + (i + 1) % n }); + } + } + + return new Mesh + { + vertices = vertices.ToArray(), + normals = normals.ToArray(), + triangles = triangles.ToArray(), + }; + } + + private static IEnumerable Triangulate(CityField field) + { + return new Triangulator(field.Boundary.Select(p => new Vector2((float)p.x, (float)p.y)).ToArray()).Triangulate(); + } + + public void DrawGizmos(Map map, Component component) + { + + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/RenderPass/CityRenderer.cs.meta b/Assets/Scripts/RenderPass/CityRenderer.cs.meta new file mode 100644 index 0000000..e0759da --- /dev/null +++ b/Assets/Scripts/RenderPass/CityRenderer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9804b7a1c403484e9a6c51c550be879b +timeCreated: 1573942634 \ No newline at end of file