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 float r; private double Random(double max, double min = 0) { return Generator.NextDouble() * (max - min) + min; } public PoissonDiskSampler(float r, int k = 30) { this.k = k; this.r = r; } public IEnumerable Generate(double width, double height) { var size = r / Mathf.Sqrt(2); var grid = new Dictionary<(int, int), Point>(); var active = new List(); 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); } } } }