Editor/PoissonDiscSamples.cs
using System;
using System.Threading.Tasks;
using Sandbox;
using System.Collections.Generic;
using System.Linq;
public class PoissonDiscSamples
{
	public float Width { get; set; }
	public float Height { get; set; }
	private float Radius { get; set; }
	private int K { get; set; }
	private float CellSize { get; set; }
	private int CellsWidth => (int)Math.Ceiling( Width / CellSize ) + 1;
	private int CellsHeight => (int)Math.Ceiling( Height / CellSize ) + 1;
	private Vector2[,] Grid { get; set; }

	public PoissonDiscSamples( float width, float height, float radius, int k )
	{
		Width = width;
		Height = height;
		Radius = radius;
		CellSize = Radius / (float)Math.Sqrt( 2 );
		K = k;
	}

	public List<Vector2> GetPoints()
	{
		Grid = new Vector2[CellsWidth, CellsHeight];

		for ( int i = 0; i < CellsWidth; i++ )
		{
			for ( int j = 0; j < CellsHeight; j++ )
			{
				Grid[i,j] = new Vector2( -1, -1 );
			}
		}

		// Keep Track of Points Added to the Grid
		List<Vector2> points = new List<Vector2>();
		List<Vector2> active = new List<Vector2>();

		// Add Initial Point 
		Vector2 initialPoint = new Vector2( Game.Random.Float(0, Width ), Game.Random.Float( 0, Height ) );
		
		InsertPoint(initialPoint );

		points.Add( initialPoint );
		active.Add( initialPoint );
		
		while (active.Count() > 0)
		{
			// Get a random point from the active list
			Vector2 point = active[Game.Random.Int( 0, active.Count - 1 )];

			//Generate up to K Points around random point until a suitable new point is found
			bool found = false;

			for(int attempts = 0; attempts < K; attempts++ )
			{
				
				var newPoint = GetRadiusPoint( point );

				if ( !IsValidPoint(newPoint) )
					continue;

				InsertPoint(newPoint );

				points.Add( newPoint );
				active.Add( newPoint );

				found = true;

				break;
			}

			// If No Points are found, remove this point from the active list
			if(!found)
			{
				active.Remove( point );
			}

		}
		
		return points;
	}

	private Vector2 GetRadiusPoint(Vector2 point)
	{
		float theta = Game.Random.Float( 360 ) * ((float)Math.PI / 180);
		float radius = Game.Random.Float( Radius, 2 * Radius );

		float x = point.x + radius * MathF.Cos( theta );
		float y = point.y + radius * MathF.Sin( theta );

		return new Vector2( x, y );
	}

	private void InsertPoint(Vector2 point)
	{
		int xIndex = (int)Math.Floor( point.x / CellSize );
		int yIndex = (int)Math.Floor( point.y / CellSize );

		Grid[xIndex, yIndex] = point;
	}

	private bool IsValidPoint( Vector2 point)
	{
		if(point.x < 0 || point.x >= Width || point.y < 0 || point.y >= Height )
			return false;

		int xIndex = (int)Math.Floor( point.x / CellSize );
		int yIndex = (int)Math.Floor( point.y / CellSize );

		int i0 = Math.Max( xIndex - 1, 0 );
		int i1 = Math.Min( xIndex + 1, CellsWidth - 1);
		int j0 = Math.Max( yIndex - 1, 0 );
		int j1 = Math.Min( yIndex + 1, CellsHeight - 1 );

		for ( int i = i0; i <= i1; i++ )
		{
			for(int j = j0; j <= j1; j++ )
			{
				Vector2 gridPoint = Grid[i,j];

				if ( gridPoint != new Vector2(-1, -1) || Vector2.Distance( point, gridPoint ) < Radius )
					return false;
			}
		}

		return true;
	}
}