inz-00/Assets/Scripts/PointDistribution/PoissonDiskSampler.cs
2019-11-17 20:33:32 +01:00

103 lines
2.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Assets.Common;
using UnityEngine;
namespace Assets.PointDistribution
{
public class PoissonDiskSampler : IPointDistribution
{
public System.Random Generator { get; set; } = new System.Random();
private int k;
private double r;
private double Random(double max, double min = 0)
{
return Generator.NextDouble() * (max - min) + min;
}
public PoissonDiskSampler(double r, int k = 30)
{
this.k = k;
this.r = r;
}
public IEnumerable<Point> Generate(double width, double height)
{
var size = r / Mathf.Sqrt(2);
var grid = new Dictionary<(int, int), Point>();
var active = new List<Point>();
var initial = new Point(Random(width), Random(height));
void AddToGrid(Point point)
{
grid.Add(
((int)Math.Floor(point.x / size), (int)Math.Floor(point.y / size)),
point
);
active.Add(point);
}
bool IsPointOk(Point point)
{
if (point.x < 0 || point.y < 0 || point.x > width || point.y > height)
{
return false;
}
var x = (int)Math.Floor(point.x / size);
var y = (int)Math.Floor(point.y / size);
var neighbours = new List<(int, int)> {
(x - 1, y + 1), (x, y + 1), (x + 1, y + 1),
(x - 1, y), (x, y), (x + 1, y),
(x - 1, y - 1), (x, y - 1), (x + 1, y - 1)
};
return neighbours
.Where(p => grid.ContainsKey(p))
.All(p => Point.Dist(point, grid[p]) > r);
}
Point RandomPoint(Point origin)
{
var angle = Random(Mathf.PI * 2);
var length = Random(2 * r, r);
return origin + new Point(Math.Sin(angle), Math.Cos(angle)) * length;
}
AddToGrid(initial);
yield return initial;
int watchdog = 3000000;
while (active.Count > 0 && watchdog-- > 0)
{
var current = (int)Math.Floor(Random(active.Count));
var point = active[current];
var i = 0;
for (; i < k; i++)
{
var candidate = RandomPoint(point);
if (IsPointOk(candidate))
{
AddToGrid(candidate);
yield return candidate;
break;
}
}
if (i >= k)
active.RemoveAt(current);
}
}
}
}