Example: poisson disc python
import numpy as np
import matplotlib.pyplot as plt
k = 30
r = 1.7
width, height = 60, 45
a = r/np.sqrt(2)
nx, ny = int(width / a) + 1, int(height / a) + 1
coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)]
cells = {coords: None for coords in coords_list}
def get_cell_coords(pt):
"""Get the coordinates of the cell that pt = (x,y) falls in."""
return int(pt[0] // a), int(pt[1] // a)
def get_neighbours(coords):
"""Return the indexes of points in cells neighbouring cell at coords.
For the cell at coords = (x,y), return the indexes of points in the cells
with neighbouring coordinates illustrated below: ie those cells that could
contain points closer than r.
ooo
ooooo
ooXoo
ooooo
ooo
"""
dxdy = [(-1,-2),(0,-2),(1,-2),(-2,-1),(-1,-1),(0,-1),(1,-1),(2,-1),
(-2,0),(-1,0),(1,0),(2,0),(-2,1),(-1,1),(0,1),(1,1),(2,1),
(-1,2),(0,2),(1,2),(0,0)]
neighbours = []
for dx, dy in dxdy:
neighbour_coords = coords[0] + dx, coords[1] + dy
if not (0 <= neighbour_coords[0] < nx and
0 <= neighbour_coords[1] < ny):
continue
neighbour_cell = cells[neighbour_coords]
if neighbour_cell is not None:
neighbours.append(neighbour_cell)
return neighbours
def point_valid(pt):
"""Is pt a valid point to emit as a sample?
It must be no closer than r from any other point: check the cells in its
immediate neighbourhood.
"""
cell_coords = get_cell_coords(pt)
for idx in get_neighbours(cell_coords):
nearby_pt = samples[idx]
distance2 = (nearby_pt[0]-pt[0])**2 + (nearby_pt[1]-pt[1])**2
if distance2 < r**2:
return False
return True
def get_point(k, refpt):
"""Try to find a candidate point relative to refpt to emit in the sample.
We draw up to k points from the annulus of inner radius r, outer radius 2r
around the reference point, refpt. If none of them are suitable (because
they're too close to existing points in the sample), return False.
Otherwise, return the pt.
"""
i = 0
while i < k:
rho, theta = np.random.uniform(r, 2*r), np.random.uniform(0, 2*np.pi)
pt = refpt[0] + rho*np.cos(theta), refpt[1] + rho*np.sin(theta)
if not (0 <= pt[0] < width and 0 <= pt[1] < height):
continue
if point_valid(pt):
return pt
i += 1
return False
pt = (np.random.uniform(0, width), np.random.uniform(0, height))
samples = [pt]
cells[get_cell_coords(pt)] = 0
active = [0]
nsamples = 1
while active:
idx = np.random.choice(active)
refpt = samples[idx]
pt = get_point(k, refpt)
if pt:
samples.append(pt)
nsamples += 1
active.append(len(samples)-1)
cells[get_cell_coords(pt)] = len(samples) - 1
else:
active.remove(idx)
plt.scatter(*zip(*samples), color='r', alpha=0.6, lw=0)
plt.xlim(0, width)
plt.ylim(0, height)
plt.axis('off')
plt.show()