Spatial Hash reverse circle packing
A version of the Circle packing with spatial hash drawing but with random coordinates being chosen from the outside edge then moving towards the origin.
import coracle.*
import coracle.Math.map
import coracle.shapes.Circle
import coracle.shapes.Rect
import kotlin.math.sqrt
class SpatialHashCircleReverse: Drawing() {
private lateinit var spatialHash: SpatialHash
private var maxRad = randomInt(8, 20)
private val backgroundColour = 0xffffff
private var colourA = Colour.random()
private var colourB = Colour.random()
private var startRad = 600f
override fun setup() {
(600, 600)
size= (width/2 * 0.95f)
startRad val columns = randomInt(3, 8)
val rows = randomInt(3, 8)
= SpatialHash(columns, rows)
spatialHash }
override fun draw() {
(backgroundColour)
background
spatialHash.addMultiple(20)
.grow()
.checkCells()
.draw()
when {
> 10 -> startRad -= 10
startRad else -> startRad = (width/2 * 0.95f)
}
}
private fun coordWithinCircleReverse(): Coord?{
if(startRad - 20 < 0) return null
val c = randomCircleCoord(startRad - 20, startRad)
.x += width/2
c.y += height/2
creturn c
}
class GrowingCircle(x: Float, y: Float, radius: Int): Circle(x, y, radius){
inner var growing = true
fun draw(){
()
noStroke(calculateColour())
fill(x, y, r)
circle}
private fun calculateColour(): Int{
val dx = x - width/2f
val dy = y - height/2f
val distance = sqrt(dx * dx + dy * dy)
return Color.lerp(colourA, colourB, map(distance, 0f, width/2f, 0f, 1f)).c
}
}
class SpatialHash(private val columns: Int, private val rows: Int){
inner private val cellPopulations = HashMap<Int, MutableList<GrowingCircle>>()
private val cellNeighbours = HashMap<Int, MutableList<GrowingCircle>>()
private val cellsRects = HashMap<Int, Rect>()
private val pendingUpdates = mutableListOf<Pair<Int, GrowingCircle>>()
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 = coordWithinCircleReverse()
?.let{
randCoord(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<Circle>): Boolean{
.forEach { o ->
circlesif(c != o){
if(collision(c, o)) return true
}
}
return false
}
private fun collision(a: Circle, b: Circle): Boolean{
return Vector(a.x, a.y).distance(Vector(b.x, b.y)) <= a.r + b.r
}
private fun getIndexHash(x: Int, y: Int): Int {
val col = (x * columns / (width + 1))
val row = (y * rows / (height + 1))
return row * columns + col
}
/*
As the circles grow check if they encroach on neighbouring cells
*/
fun checkCells(): SpatialHash {
.clear()
pendingUpdates.forEach { cellCollection ->
cellPopulationsval key = cellCollection.key
val circles = cellCollection.value
val nativeRect = cellsRects[key]
.forEach { c ->
circlesif(!fullyEnclosed(c, nativeRect!!)){
(key, c)
addToNeighbouringCells}
}
}
.forEach { u ->
pendingUpdatesval key = u.first
if(cellPopulations.containsKey(key)){
val neighbours = cellNeighbours[key]
if(!neighbours!!.contains(u.second)) neighbours.add(u.second)
}
}
return this
}
private fun fullyEnclosed(c: Circle, r: Rect): Boolean =
.x - c.r > r.x && c.x + c.r < r.x + r.width && c.y - c.r > r.y && c.y + c.r < r.y + r.height
cprivate fun addToNeighbouringCells(nativeKey: Int, c: GrowingCircle){
val nativeRect = cellsRects[nativeKey]!!
if(c.x - c.r <= nativeRect.x){
val leftIndex = getIndexHash((c.x - c.r).toInt(), c.y.toInt())
.add(Pair(leftIndex, c))
pendingUpdateswhen {
.y - c.r <= nativeRect.y -> {
cval aboveLeftIndex = getIndexHash((c.x - c.r).toInt(), (c.y - c.r).toInt())
.add(Pair(aboveLeftIndex, c))
pendingUpdates}
.y + c.r >= nativeRect.y + nativeRect.height -> {
cval belowLeftIndex = getIndexHash((c.x - c.r).toInt(), (c.y + c.r).toInt())
.add(Pair(belowLeftIndex, c))
pendingUpdates}
}
}
if(c.x + c.r >= nativeRect.x + nativeRect.width){
val rightIndex = getIndexHash((c.x + c.r).toInt(), c.y.toInt())
.add(Pair(rightIndex, c))
pendingUpdateswhen {
.y - c.r <= nativeRect.y -> {
cval aboveRightIndex = getIndexHash((c.x + c.r).toInt(), (c.y - c.r).toInt())
.add(Pair(aboveRightIndex, c))
pendingUpdates}
.y + c.r >= nativeRect.y + nativeRect.height -> {
cval belowRightIndex = getIndexHash((c.x + c.r).toInt(), (c.y + c.r).toInt())
.add(Pair(belowRightIndex, c))
pendingUpdates}
}
}
if(c.y - c.r <= nativeRect.y){
val aboveIndex = getIndexHash(c.x.toInt(), (c.y - c.r).toInt())//Above
.add(Pair(aboveIndex, c))
pendingUpdates}
if(c.y + c.r >= nativeRect.y + nativeRect.height){
val belowIndex = getIndexHash(c.x.toInt(), (c.y + c.r).toInt())//Below
.add(Pair(belowIndex, c))
pendingUpdates}
}
fun draw(){
.forEach { cellCollection ->
cellPopulations.value.forEach { c -> c.draw() }
cellCollection}
}
fun svg(svg: SVG){
.forEach { cellCollection ->
cellPopulations.value.forEach { c ->
cellCollectionval colour = Colour(calculateColour(c))
.addLine(c.toSVG(colour.toHexString()))
svg}
}
}
private fun calculateColour(circle: Circle): Int{
val dx = circle.x - width/2f
val dy = circle.y - height/2f
val distance = sqrt(dx * dx + dy * dy)
return Color.lerp(colourA, colourB, map(distance, 0f, width/2f, 0f, 1f)).c
}
}
}