using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Assets.Generators { public class PoissonDiskSampler { public System.Random Generator { get; set; } = new System.Random(); private int k; private float r; private float Random(float max, float min = 0) { return (float)Generator.NextDouble() * (max - min) + min; } public PoissonDiskSampler(float r, int k = 30) { this.k = k; this.r = r; } public IEnumerable Generate(float width, float height) { var size = r / Mathf.Sqrt(2); var grid = new Dictionary<(int, int), Vector3>(); var active = new List(); var initial = new Vector3(Random(width), Random(height)); void AddToGrid(Vector3 point) { grid.Add( (Mathf.FloorToInt(point.x / size), Mathf.FloorToInt(point.y / size)), point ); active.Add(point); } bool IsPointOk(Vector3 point) { if (point.x < 0 || point.y < 0 || point.x > width || point.y > height) { return false; } var x = Mathf.FloorToInt(point.x / size); var y = Mathf.FloorToInt(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 => Vector3.Distance(point, grid[p]) > r); } Vector3 RandomPoint(Vector3 origin) { var angle = Random(Mathf.PI * 2); var length = Random(2 * r, r); return origin + new Vector3(Mathf.Sin(angle), Mathf.Cos(angle)) * length; } AddToGrid(initial); yield return initial; int watchdog = 3000000; while (active.Count > 0 && watchdog-- > 0) { var current = Mathf.FloorToInt(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); } } } } }