Poisson disk samples generated via Bridson algorithm

In [1]:
using PlotlyJS
import StaticArrays:SVector, @SVector

The WebIO Jupyter extension was not detected. See the WebIO Jupyter integration documentation for more information.

In [2]:
function distsq(A::SVector{2, T}, B::SVector{2, T}) where T <: Real
    return (A[1]-B[1])^2 + (A[2]-B[2])^2
end

function grid_idxs(point:: SVector{2, T}, cellsize::T) where T <: Real 
    """ Returns cell indices, (i,j)=(row, col) for  `point` """
    return ceil(Int, point[2] / cellsize) + 1, ceil(Int, point[1] / cellsize) + 1
end

function ispoint_inrectangle(point::SVector{2, T}, rlims::Tuple{T, T}) where T <: Real
    """ 
    Check whether a point is within the rectangle 
    [0, rlims[1]] x [0, rlims[2]];
    here rlims =(width, height)
    """
    return (0 <= point[1] <= rlims[1]) && (0 <= point[2] <= rlims[2])
end    

function unif_sampling_annulus(center::SVector{2, T}, inner_rad::T, outer_rad::T; 
                               k=30) where T <: Real
    """ 
    Samples  uniformly k points from the annulus of center, `center`, 
    and radii, inner_radius, outer_radius
    """
    rad = sqrt.(inner_rad^2  .+ (outer_rad^2 - inner_rad^2)*rand(k)) 
    angle = 2π * rand(k)  
    x, y = center[1] .+ rad .* cos.(angle), center[2] .+ rad .*sin.(angle) 
    return [@SVector([xi, yi]) for (xi, yi) in zip(x,y)]
end
Out[2]:
unif_sampling_annulus (generic function with 1 method)
In [3]:
function Poisson_disk_sampling(;width =1.0, height=1.0, radius=0.05, k=30)
    """ 
    Bridson Algorithm to generate  Poisson disk samples 
    within a 2D rectangle, [0, width] x [0, height]
    Ref: R Bridson, Fast Poisson disk sampling in arbitrary dimensions,
    in  SIGGRAPH '07: ACM SIGGRAPH 2007 sketches
    https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
    """
    function  is_accepted(point:: SVector{2, T})  where T <: Real
        """ 
        Check whether the point, `point` can be added to the active_list
        """
        i, j = grid_idxs(point, cellsize)
        m, n = maximum([i-2, 1]), maximum([j-2, 1])
        p, q = minimum([i+2, grows]), minimum([j+2, gcols])
        for k in n:q
            for l in m:p
                gpoint = grid[(k-1) * grows + l]
                if ismissing(gpoint)
                    continue
                end
                if distsq(point, gpoint) <= r2
                    return false
                end
            end
        end              
        return true
    end
    
    cellsize = radius/sqrt(2)
    gcols = ceil(Int, width / cellsize) + 1
    grows = ceil(Int, height / cellsize) + 1
  
    grid = Vector{Any}(missing, grows*gcols)
    r2 = radius*radius
    active_list = SVector{2, Float64}[]
    
    #generate a random point within the rectangle of interest
    # and  insert it into the active_list and grid
    point = @SVector([width*rand(1)[1], height*rand(1)[1]])
    push!(active_list, point)
    i, j = grid_idxs(point, cellsize)
    grid[(j-1)*grows+i] = point
    
    while length(active_list) > 0
        i = rand(1:length(active_list), 1)[1]
        pt = active_list[i] 
        active_list  =  active_list[1:end .!= i] #removes the point in pos i
        rpoints = unif_sampling_annulus(pt, radius, 2*radius; k=k)
        for rp in rpoints
            if !ispoint_inrectangle(rp, (width, height)) || !is_accepted(rp)
                continue
            end    
            push!(active_list, rp)    
            i, j = grid_idxs(rp, cellsize)
            grid[(j-1)*grows+i] = rp
        end
    end
    return filter(x->!ismissing(x), grid)
end
Out[3]:
Poisson_disk_sampling (generic function with 1 method)
In [4]:
vector_of_samples = Poisson_disk_sampling(;width =5.0, height=3.0, radius=0.045, k=30)
xcoords = [p[1] for p in vector_of_samples]
ycoords = [p[2] for p in vector_of_samples]
fig1 = Plot(scatter(x=xcoords, y=ycoords, mode="markers", marker_size=3), 
            Layout(width=600, height=400, 
                   xaxis_showgrid=false, yaxis_showgrid=false))
display(fig1)
In [5]:
vector_of_samples = Poisson_disk_sampling(;width=4.0, height=4.0, radius=0.05, k=30)
#perform a translation of the default rectangle [0, width] x [0, height] to [3, 3+]x  [-2, -2+height]
xcoords = [p[1] for p in vector_of_samples] .+ 3.0
ycoords = [p[2] for p in vector_of_samples] .- 2.0  
fig2 = Plot(scatter(x=xcoords, y=ycoords, mode="markers", marker_size=3), 
            Layout(width=500, height=500, 
                   xaxis_showgrid=false, yaxis_showgrid=false,
                   xaxis_zeroline=false, yaxis_zeroline=false ))
display(fig2)
In [ ]: