/Swift-Poisson-Disk-Sampling

Swift-Poisson-Disk-Sampling

Primary LanguageSwift

Rudimentay Poisson Disk Sampling

Ruimentary implementation of Poisson Disk Sampling in Swift. I needed to layout nodes in SpriteKit Scene. Using random posistions wasn't cutting it and showed bias to screen width and height.

Props to samgak on StackOverflow that helped me debug.

TODO?

  • Error checking
  • Examples, do some nice layout stuff
  • Examples, will lend it self to generative art
  • add some functions to filter lists

TODO EXPERIMENT

  • Try combine created seeded lists during generation.
  • variable / randomised minimum distance sizes with apprioate stops to avoid infinite loops
import Foundation
import Darwin
import SceneKit

private func distance(p1: CGPoint, p2: CGPoint) -> CGFloat {
   let dx = p1.x - p2.x
   let dy = p1.y - p2.y
   return sqrt(dx * dx + dy * dy)
}

private func generateRandomPointAround(point: CGPoint, minDist: CGFloat) -> CGPoint
{
   
   let rd1 = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
   print(rd1)
   
   let rd2 = CGFloat(Float(arc4random()) / Float(UINT32_MAX))
   
   //random radius
   let radius = minDist * (rd1 + 1)
   //random angle
   let angle = 2 * CGFloat.pi * rd2
   
   //new point is generated around the point (x, y)
   
   let newX = point.x + radius * cos(angle)
   let newY = point.y + radius * sin(angle)
   return CGPoint(x: newX, y: newY)
   
}


func generatePoisson(wh: CGSize, minDist: CGFloat, newPointsCount: Int? = 30) -> [CGPoint] {
   
   let radius = minDist
   
   //point sample in radius
   let lookUpCount = 30
   var grid = [CGPoint?](), validPoints = [CGPoint](), activePoints = [CGPoint]()
   var cellSize = CGFloat()
   var cols = Int()
   var rows = Int()
   let sizeScren = wh
   
   
   //create cell
   cellSize = (CGFloat(Double(radius) / sqrt(2)))
   cols = Int(floor(sizeScren.height / cellSize))
   rows = Int(floor(sizeScren.width / cellSize))
   
   grid = [CGPoint?](repeating: nil, count: (cols * rows))
   
   let x = sizeScren.height / 2
   let y = sizeScren.width / 2
   let i = floor(x / cellSize)
   let j = floor(y / cellSize)
   
   //first posistion
   
   let pos = CGPoint.zero
   let index = Int(i + j * CGFloat(cols))
   grid[index] = pos
   activePoints.append(pos)
   validPoints.append(pos)
   
   
   while (activePoints.count > 0) {
       let randomIndex = Int(arc4random_uniform(UInt32(activePoints.count)))
       let currentPos = activePoints[randomIndex]
       var found = false
       Mainloop: for _ in 0..<Int(lookUpCount) {
           
           let samplePoint = generateRandomPointAround(point: currentPos, minDist: CGFloat(radius))
           
           let col = floor((samplePoint.x + x) / cellSize)
           let row = floor((samplePoint.y + y) / cellSize)
           
           //check neighbouring cells are empty and valid, if have a point in already
           
           let isIndexValid = grid.indices.contains(Int(col + CGFloat(row) * CGFloat(cols)))
           
           if (isIndexValid && (grid[Int(col + CGFloat(row) * CGFloat(cols))] == nil)) {
               var ok = true
               for index1 in -1...1 {
                   for index2 in -1...1 {
                       
                       //break down complexity for swift compiler
                       
                       let part1 = Int(col + CGFloat(index1))
                       let part2 = Int(row + CGFloat(index2))
                       let part3 = part1 + part2 * cols
                       let sampleIndex = part3
                       
                       let isIndexValid = grid.indices.contains(sampleIndex)
                       
                       if isIndexValid {
                           let neighbor = grid[sampleIndex]
                           
                           if neighbor != nil {
                               let distanceAmount = distance(p1: samplePoint, p2: neighbor!)
                               if distanceAmount < CGFloat(radius) {
                                   ok = false
                               }
                           }
                       }
                   }
               }
               
               if (ok == true) {
                   found = true
                   grid[Int(col + row * CGFloat(cols))] = samplePoint
                   activePoints.append(samplePoint)
                   validPoints.append(samplePoint)
                   //                        break MainLoop
               }
           }
       }
       
       if (!found) {
           activePoints.remove(at: randomIndex)
       }
   }
   return validPoints
}