inz-00/Assets/Scripts/GraphGenerator.cs
2019-08-29 21:41:21 +02:00

336 lines
11 KiB
C#

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<Vector3> _points = new List<Vector3>();
private List<Location> _locations = new List<Location>();
private Graph<(Point, Location)> _locationGraph;
public List<LocationType> types = new List<LocationType>();
[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<Vector3>();
_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<Location>() { 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<int> { 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<Point> 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<Point> 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<Point> 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));
}
}
}