Writing Processing in Kotlin

Hi :slight_smile: After learning more about Kotlin I’m concerned that the original example is a bit verbose so I decided to share an updated version with some changes:

  • No need to name companion object
  • Use PVector, PVector.dist instead of x, y
  • Use Int for colors, no need of floatArrayOf
  • Use destructuring declaration with circleSizeCounts map
  • Replace circleOverlaps() by .all {}
  • Improve weightedChoice:
    • No need for weights to sum 1.0
    • Accept any collection as input, not only colors
  • Use circle() instead of ellipse()

Updated Kotlin + Processing code

import processing.core.PApplet
import processing.core.PConstants
import processing.core.PVector

data class Circle(val radius: Float, val pos: PVector, var color: Int = 0)

fun main() {
    CirclePacking.start()
}

class CirclePacking : PApplet() {
    companion object {
        fun start() {
            var art = CirclePacking()
            art.setSize(500, 500)
            art.runSketch()
        }
    }

    private val circleSizeCounts = listOf(
            65f to 19,
            37f to 38,
            20f to 75,
            7f to 150,
            3f to 300
    )

    override fun setup() {
        colorMode(PConstants.HSB, 360f, 100f, 100f)
        noStroke()
        background(70)

        val circles = mutableListOf<Circle>()

        for ((circleRadius, circleCount) in circleSizeCounts) {
            for (i in 1..circleCount) {
                // allow up to 1000 collisions
                for (c in 0..1000) {
                    // generate random point

                    // A. do not allow circles to overlap canvas
                    // val pos = PVector(random(circleRadius, width -
                    // circleRadius), random(circleRadius, height - circleRadius))

                    // B. allow circles overlapping canvas
                    val pos = PVector(random(width * 1f), random(height * 1f))

                    val newCircle = Circle(circleRadius, pos)
                    if (circles.all { otherCircle ->
                                newCircle.pos.dist(otherCircle.pos) >
                                        newCircle.radius + otherCircle.radius
                            }) {
                        // get random color
                        newCircle.color = weightedChoice(
                                listOf(
                                        color(0f, 0f, random(90f, 100f)),
                                        color(random(180f, 220f), 50f, 50f),
                                        color(random(0f, 20f), 80f, 80f)
                                ), listOf(0.6f, 0.3f, 0.1f)
                        )
                        circles.add(newCircle)
                        break
                    }
                }
            }
        }

        circles.forEach {
            fill(it.color)
            circle(it.pos.x, it.pos.y, it.radius * 2)
        }
    }

    override fun draw() {
    }

    private fun <T> weightedChoice(coll: Collection<T>, weights: Collection<Float>): T {
        if (coll.size != weights.size) {
            error("weightedChoice() requires two collections with the same number of elements")
        }
        val rnd = random(weights.sum())
        var sum = 0.0
        val index = weights.indexOfFirst { sum += it; sum > rnd }
        return coll.toList()[index]
    }
}

Same program written with OPENRNDR here.

3 Likes