Writing Processing in Kotlin

Hi there, I just tried running a Processing program written in the Kotlin language, and I thought I would share here the steps in case anyone wants to try.

This may not be very interesting if you are happy using the Processing IDE and your projects don’t grow very large. I wonder how many users in this forum use other editors like IntelliJ Idea… If you already do then you might be curious about trying this:

  • Install IntelliJ Idea community edition (free), Processing and Kotlin.
  • Create a new Kotlin/JVM project
  • Go to Project Structure and add the processing/core/library/ folder to Libraries
  • Create a new Kotlin file inside src/: Main.kt (or whatever you want to call it)
  • Paste this example which is a slightly modified version of code by Donnie Flood
import processing.core.PApplet
import processing.core.PConstants

data class Circle(val radius: Float, val x: Float, val y: Float, var color: Int = 0)

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

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

    private fun randomXY(xMin: Float, xMax: Float, yMin: Float, yMax: Float): Pair<Float, Float> {
        return Pair(random(xMin, xMax), random(yMin, yMax))
    }

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

        val allCircles = mutableListOf<Circle>()

        for (circleSizeCount in circleSizeCounts) {
            val circleSize = circleSizeCount.first
            val circleCount = circleSizeCount.second
            for (i in 1..circleCount) {
                // allow up to 100 collisions
                for (c in 0..1000) {
                    // generate random point
                    // do not allow circles to overlap canvas
                    // val (x, y) = randomXY(0f+circleSize, 500f-circleSize, 0f+circleSize, 500f-circleSize);
                    // allow circles overlapping canvas
                    val (x, y) = randomXY(0f, width.toFloat(), 0f, height.toFloat());
                    val testCircle = Circle(circleSize.toFloat(), x, y)
                    if (!circleOverlaps(allCircles, testCircle)) {
                        // get random color
                        val c = weightedChoice(
                                listOf(
                                        floatArrayOf(0f, 0f, random(90f, 100f)) to 0.6f,
                                        floatArrayOf(random(180f, 220f), 50f, 50f) to 0.3f,
                                        floatArrayOf(random(0f, 20f), 80f, 80f) to 0.1f
                                )
                        )
                        testCircle.color = color(c[0], c[1], c[2])
                        allCircles.add(testCircle)
                        break
                    }
                }
            }
        }

        for (circle in allCircles) {
            fill(circle.color)
            ellipse(circle.x, circle.y, circle.radius * 2, circle.radius * 2)
        }

    }

    override fun draw() {
    }

    private fun circleOverlaps(allCircles: List<Circle>, testCircle: Circle): Boolean {
        return allCircles.asSequence().any {
            val distance = dist(it.x, it.y, testCircle.x, testCircle.y)
            distance <= (it.radius + testCircle.radius)
        }
    }

    private fun weightedChoice(colorsAndWeights: List<Pair<FloatArray, Float>>): FloatArray {
        val weightSum = colorsAndWeights.sumBy { (it.second * 100).toInt() }
        if (weightSum != 100) throw AssertionError("Weights should sum to 1")
        val random = random(0f, 1.0f)
        var weightTotal = 0f
        for (i in colorsAndWeights) {
            if (random >= weightTotal && random <= weightTotal + i.second) {
                return i.first
            }
            weightTotal += i.second
        }
        throw Exception("Should have returned a Weighted Choice...")
    }
}

fun main(args: Array<String>) {
    CirclePacking.run()
}
  • Right click on the Main.kt file on the left and choose Run (green triangle icon).

If everything is set up properly, the program should start after a few seconds.
2018-09-29-183250_500x500_scrot

In the code above you can see some similarities with a Java Processing program (import, class, PApplet, private, override) and some differences (var, val, listOf, no semicolons, types after and not before).

I do like how Kotlin code looks like. I’m concerned about it being young, and that it’s sponsored and developed by one company. On the other hand, it’s very active (multiple releases per day), has many contributors and many Android developers have switched to it.

The only mention to the word Kotlin in this forum was by @cansik in his presentation. What’s your experience with it @cansik? Has anyone else tried working with Kotlin? What are your thoughts on using this language for creative coding? Any advantages or disadvantages on using it? Is the effort of learning one more language worth it?

16 Likes

Almost everything I like about Kotlin is in Java 11, except data types (coming in 12 IIRC) and nullable types, and I really dislike the syntax. Be great for the Processing pre-processor to be updated to support Java 11 though (or the syntax differences with Java ditched)

Can you use Java 11 features with Processing in an external IDE?

Answering my own question: yes, at least with OpenJDK 10. Only this warning is there:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jogamp.common.os.NativeLibrary$3 (file:/usr/share/processing/core/library/gluegen-rt.jar) to method java.lang.ClassLoader.findLibrary(java.lang.String)
WARNING: Please consider reporting this to the maintainers of com.jogamp.common.os.NativeLibrary$3
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
3 Likes

Very interesting share @hamoid. It could be an excellent introduction step towards the recently released and quite promising OPENRDNR framework.

4 Likes

Oooh, thanks for that. Not seen it before. Looks promising. May have to see how easy that is to work with / live code from the Java side.

1 Like

That was my secret goal :slight_smile:

2 Likes

Looking forward to hearing your thoughts on the frameworks (how does it perform compared to Processing, etc…)

Bear in mind that’s going to be difficult to answer. In the past I wrote a partial 2D renderer on top of libGDX, and this performed noticeably better. At the same time, Processing tends towards accuracy over brute speed. You need to compare like with like. There’s a pull request somewhere that could improve speed using methodology from libGDX.

At some point Processing will need to move on from JOGL, and while that might be on top of LWJGL directly, I personally think there’s merit in building on top of another system like this. At the same time, a key problem would be Pi support, although seems to be on their roadmap.

For me is not about performance, but more about what ideas can I express with that syntax. Can I create data structures that would be harder to work with in Java? Can I make large changes easily? Are programs easier to maintain? For high frame rate I use OpenFrameworks (or shaders while avoiding sending data to the GPU).

BTW thanks for mentioning Java 11: I just replaced

for (Map.Entry<String, Layer> entry : layers.entrySet()) {

by

for (var entry : layers.entrySet()) {

in my program :slight_smile:

1 Like

The joy of Java 8+ (with/without Kotlin) is internal iteration. Your example would be even better (and perhaps perform better) as -

layers.entrySet().forEach(e -> ... )

There’s no reason in theory that Processing couldn’t be as performant as OpenFrameworks.

Thanks, yes, I use .forEach in other places, this case needs access to an integer counter that is increased inside the loop. That seems to be not allowed with .forEach.

ps. I remember why I thought I could not use more recent versions of Java with Processing: I tried doing that with my VideoExport library, but then it was no longer usable from the Processing IDE.

At some point Processing will need to move on from JOGL.

Why do you say that?

JOGL is practically dead and there have had to be a few hacks to keep it working. Unless someone takes on active development really soon then there will have to be a move to something else.

Operator overloading

After watching this Kotlin tutorial I’m now following this course: Coursera | Online Courses & Credentials From Top Educators. Join for Free | Coursera and I just learned something very cool. Since Kotlin has operator overloading, I can write something like this

        val a = PVector(1.0f, 0.0f)
        val b = PVector(0.2f, 0.3f)
        val c = a * 3.0f + b // <--- nice
        println(c)

if on the same file I declare these

private operator fun PVector.times(mag: Float): PVector {
    return PVector.mult(this, mag)
}
private operator fun PVector.plus(other: PVector): PVector {
    return PVector.add(this, other)
}

What’s cool about it is that this line

val c = a * 3.0f + b

In Processing would be

PVector c = PVector.mult(a, 3).add(b);

which I find more tedious to type and harder to read.

ps. What was also very cool is that I didn’t know how to do this. I just typed a * 3.0f + b in IntelliJ, and * and + were marked as errors. I hit Alt+Enter and the IDE suggested to implement those two methods, which I then had to populate. Very smart IDE.

5 Likes

Python’s got that too: :snake:

3 Likes

Nice :slight_smile: Is it also possible in Python to add new methods to classes like PVector or String from user code?

1 Like

Well, Python Mode’s class PVector is actually a subclass of Processing’s class processing.core.PVector: :coffee:

1 Like

Sure but that wasn’t the question :slight_smile: Apparently in Kotlin you can add methods to any class directly from your code.

Even Java itself can add new methods (even new fields and constructors too) to a class via subclassing w/ extends.

There’s also anonymous instantiation. Which customizes an individual object creation just like a subclass.

Kotlin, similar to C# and Gosu, provides the ability to extend a class with new functionality without having to inherit from the class or use any type of design pattern such as Decorator. This is done via special declarations called extensions . Kotlin supports extension functions and extension properties .

See https://kotlinlang.org/docs/reference/extensions.html