using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using Assets.Common; using Assets.Generators; using Assets.Map; using Assets.Voronoi; using UnityEditor; using UnityEngine; using Random = System.Random; namespace Assets { [Serializable] public class GraphGeneratorDebug { public bool enabled = true; public bool displayPoints = true; public bool displayBoundary = true; public bool displayBeachLine = true; public bool displayParabolas = false; public bool displayVertices = true; public bool displayNeighbours = false; public bool displayLabels = false; public bool displayHalfEdges = false; public bool displayEdges = true; public bool displayLocations = true; public bool displayLocationCells = true; public bool displayLocationEdges = true; public bool displayLocationPoints = true; [NonSerialized] public float generationTime = 0.0f; } [Serializable] public class LocationType { public string name; public Color color; public float height = 0.0f; } public enum GenerationStage { Start, Voronoi, Location, Done }; [ExecuteInEditMode] public class GraphGenerator : MonoBehaviour { private PoissonDiskSampler _sampler; private Random _random; private VoronoiGenerator _voronoiGenerator; private LocationGenerator _locationGenerator; private List _points = new List(); private List _locations = new List(); private Graph<(Point, Location)> _locationGraph; public List types = new List(); [Range(2.0f, 64.0f)] public float radius = 8; public int seed = Environment.TickCount; public Vector2 size = new Vector2(); public GraphGeneratorDebug debug; public GenerationStage Stage { get; private set; } = GenerationStage.Start; public VoronoiGenerator VoronoiGenerator => _voronoiGenerator; public LocationGenerator LocationGenerator => _locationGenerator; void Start() { Reset(); } public void Reset() { _random = new System.Random(seed); _sampler = new PoissonDiskSampler(radius); _sampler.Generator = _random; _points = new List(); _points.Add(new Vector3(-size.x/8, size.y / 2 + 0.1f)); _points.Add(new Vector3(size.x * 1.125f, size.y / 2 - 0.1f)); _points.Add(new Vector3(size.x / 2 - 0.1f, -size.y/8)); _points.Add(new Vector3(size.x / 2 + 0.1f, size.y * 1.125f)); _points.AddRange(_sampler.Generate(size.x, size.y)); Stage = GenerationStage.Voronoi; _voronoiGenerator = new VoronoiGenerator(_points.Select(x => new Point(x.x, x.y)).ToList()); _locationGenerator = null; _locationGraph = null; _locations = new List() { new Location() { Type = new LocationType() { color = Color.clear, name = "Ocean", height = -1 } } }; _locations.AddRange(types.Select(type => new Location { Type = type })); } public void Generate() { var stopwatch = new Stopwatch(); stopwatch.Start(); debug.generationTime = 0; while (Stage != GenerationStage.Done) { Step(); debug.generationTime = stopwatch.ElapsedMilliseconds / 1000.0f; } stopwatch.Stop(); debug.generationTime = stopwatch.ElapsedMilliseconds / 1000.0f; } public void Step() { if (Stage == GenerationStage.Voronoi) { _voronoiGenerator.Step(); if (_voronoiGenerator.Done) { Stage = GenerationStage.Location; _locationGenerator = new LocationGenerator(_locations, _voronoiGenerator.Delaunay, _random); var initial = from location in _locations select (_random.Next(_voronoiGenerator.Delaunay.Vertices.Count), location); initial = initial.Concat(new List { 0, 1, 2, 3 }.Select(i => (i, _locations[0]))); foreach (var (vertex, location) in initial) _locationGenerator.SetLocation(vertex, location); } } else if (Stage == GenerationStage.Location) { _locationGenerator.Step(); if (_locationGenerator.Done) { Stage = GenerationStage.Done; RecalculateLocationGraph(); } } } void OnDrawGizmos() { if (debug.displayBoundary) { Gizmos.color = Color.gray; Gizmos.DrawLine(new Vector3(0, 0), new Vector3(size.x, 0)); Gizmos.DrawLine(new Vector3(0, 0), new Vector3(0, size.y)); Gizmos.DrawLine(new Vector3(size.x, size.y), new Vector3(size.x, 0)); Gizmos.DrawLine(new Vector3(size.x, size.y), new Vector3(0, size.y)); } if (!debug.enabled || _voronoiGenerator == null) return; if (debug.displayPoints) { Gizmos.color = Color.white; DisplayGraphVertices(_voronoiGenerator.Delaunay.Morph(x => x.Point)); } if (debug.displayNeighbours) { Gizmos.color = Color.yellow; DisplayGraphEdges(_voronoiGenerator.Delaunay.Morph(x => x.Point)); } if (debug.displayVertices) { Gizmos.color = Color.blue; DisplayGraphVertices(_voronoiGenerator.Voronoi); } if (debug.displayHalfEdges) { Gizmos.color = Color.green; foreach (var edge in _voronoiGenerator.HalfEdges) { Gizmos.DrawLine(edge.Start.ToVector3(), edge.End.ToVector3()); } } if (debug.displayEdges) { Gizmos.color = Color.white; DisplayGraphEdges(_voronoiGenerator.Voronoi); } Gizmos.color = Color.red; Gizmos.DrawLine(new Vector3(0, (float)_voronoiGenerator.Line.Directrix), new Vector3(size.x, (float)_voronoiGenerator.Line.Directrix)); Gizmos.color = Color.blue; Vector3 start; if (debug.displayParabolas) { foreach (var parabola in _voronoiGenerator.Line.Parabolas) { start = new Vector3(-size.x, (float)_voronoiGenerator.Line.EvalParabola(parabola.Site.Point, -size.x)); for (var x = -size.x + 0.1f; x < size.x * 2; x += 0.1f) { var point = new Vector3(x, (float)_voronoiGenerator.Line.EvalParabola(parabola.Site.Point, x)); Gizmos.DrawLine(start, point); start = point; } } } if (debug.displayBeachLine) { Gizmos.color = Color.white; start = new Vector3(-size.x, (float)_voronoiGenerator.Line.Eval(-size.x)); for (var x = -size.x + 0.1f; x < size.x * 2; x += 0.1f) { var point = new Vector3(x, (float)_voronoiGenerator.Line.Eval(x)); Gizmos.DrawLine(start, point); start = point; } } if (LocationGenerator == null) return; if (debug.displayLocationCells) { foreach (var location in LocationGenerator.Sites.Vertices.Where(l => l.Location != null && l.Location != _locations[0])) { Gizmos.color = location.Location.Type.color; Gizmos.DrawSphere(location.Site.Point.ToVector3(), 2); } } if (debug.displayLocations && _locationGraph != null) { DisplayGraphEdges(_locationGraph.Morph(a => a.Item1)); } if (debug.displayLocationPoints) { foreach (var location in _locations.Skip(1)) { Gizmos.color = location.Type.color; foreach (var point in location.BoundaryPoints) { Gizmos.DrawSphere(VoronoiGenerator.Voronoi.Vertices[point].ToVector3(), 1); } } } if (debug.displayLocationEdges) { foreach (var location in _locations.Skip(1)) { Gizmos.color = location.Type.color; foreach (var (a, b) in location.BoundaryEdges) { Gizmos.DrawLine(VoronoiGenerator.Voronoi.Vertices[a].ToVector3(), VoronoiGenerator.Voronoi.Vertices[b].ToVector3()); } } } } public void Rewind(float y) { while (_voronoiGenerator.Line.Directrix < y && !_voronoiGenerator.Done) { _voronoiGenerator.Step(); } } private void DisplayGraphVertices(Graph graph) { var vertices = graph.Vertices.Select(p => p.ToVector3()); var offset = Vector3.right; foreach (var (v, i) in vertices.Select((x, i) => (x, i))) { Gizmos.DrawSphere(v, 1); if (debug.displayLabels) Handles.Label(v + offset, $"{i} at {v.x:F2}, {v.y:f2}"); } } private void DisplayGraphEdges(Graph graph) { var edges = graph.Edges .Select(edge => (graph.Vertices[edge.Item1], graph.Vertices[edge.Item2])) .Select(edge => (edge.Item1.ToVector3(), edge.Item2.ToVector3())); foreach (var (s, e) in edges) Gizmos.DrawLine(s, e); } private void RecalculateLocationGraph() { Point Mean(IEnumerable points) { var result = new Point(0, 0); var i = 0; foreach (var point in points) { result.x = (result.x * i + point.x) / (i + 1); result.y = (result.y * i + point.y) / (i + 1); i++; } return result; } _locationGraph = LocationGenerator.Result.Morph(location => (Mean(location.Sites.Select(s => s.Site.Point)), location)); } } }