Circle packing with spatial hash
Simple (simpler than a Quadtree) method to reduce collisions checks. Only circles in the same cell are checked for collisions (or neighbouring cells too if the circle straddles more than one, there’s occasional glitches in that scenario which I may or may not find time to investigate).
This Circle
Packing problem is a little different to the usual implementation;
starting with large circles, exhausting available space, then working
down to a minimum radius. Here they instead start with a radius of 1
and grow until they collide with a neighbour or reach their
maxRad
size.
import coracle.*
import coracle.shapes.Circle
import coracle.shapes.Rect
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
class SpatialHashCirclePacking: Drawing() {
private lateinit var spatialHash: SpatialHash
private var maxRad = randomInt(8, 20)
private val backgroundColour = 0xF2EDF2
private val gridColour = 0xC3BDD4
private val defaultColour = 0xA87CAA
private val boundaryColour = 0xC57596
override fun setup() {
(600, 600)
size
val columns = randomInt(3, 8)
val rows = randomInt(3, 8)
= SpatialHash(columns, rows)
spatialHash ("Columns: $columns Rows: $rows Maximum radius: $maxRad")
print}
override fun draw() {
(backgroundColour)
background
spatialHash.addMultiple(20)
.grow()
.checkCells()
.draw()
}
private fun coordWithinCircle(): Coord {
val a = random(0f, 1f) * TWO_PI
val r = ((width/2) * 0.8) * sqrt(random(0f, 1f))
val x = r * cos(a)
val y = r * sin(a)
return Coord(width/2 + x.toFloat(), height/2 + y.toFloat())
}
class GrowingCircle(x: Float, y: Float, radius: Int): Circle(x, y, radius){
inner var growing = true
var colour = defaultColour
fun draw(){
()
noStroke(colour, 1f)
fill(x, y, r)
circle}
}
class SpatialHash(private val columns: Int, private val rows: Int){
inner
private val cellPopulations = HashMap>()
private val cellNeighbours = HashMap>()
private val cellsRects = HashMap()
private val pendingUpdates = mutableListOf>()
private val cellWidth: Int
private val cellHeight: Int
{
init (columns * rows){ index ->
repeat[index] = mutableListOf()
cellPopulations[index] = mutableListOf()
cellNeighbours}
= width/columns
cellWidth = height/rows
cellHeight
var index = -1
(rows){ row ->
repeat(columns){ column ->
repeat++
indexval rect = Rect(column * cellWidth, row * cellHeight, cellWidth, cellHeight)
[index] = mutableListOf()
cellPopulations[index] = rect
cellsRects}
}
}
fun addMultiple(count: Int): SpatialHash{
(count){
repeatval randCoord = coordWithinCircle()
(GrowingCircle(randCoord.x, randCoord.y, 1))
add}
return this
}
fun add(c: GrowingCircle): SpatialHash{
val index = getIndexHash(c.x.toInt(), c.y.toInt())
var collision = false
val population = cellPopulations[index]
?.forEach { e ->
populationif(collision(c, e)) collision = true
}
val neighbours = cellNeighbours[index]
?.forEach { e ->
neighboursif(collision(c, e)) collision = true
}
if(!collision) population?.add(c)
return this
}
fun grow(): SpatialHash {
.forEach { cellCollection ->
cellPopulationsval circles = cellCollection.value
val neighbours = cellNeighbours[cellCollection.key]
.forEach { c ->
circlesif(c.growing && c.r < maxRad){
.r++
cif(collision(c, circles) || collision(c, neighbours ?: emptyList())){
.r--
c.growing = false
c}
}else{
.growing = false
c}
}
}
return this
}
private fun collision(c: Circle, circles: List