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;
}
}