Writing Processing in Kotlin

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

Now I’ve got it! You want to edit classes in such a way that all past & future instances of them have new members, right? :thinking:

Indeed Java can’t do that! Creating a custom subclass doesn’t replace the original. :disappointed:

Nor does it force other places instantiating the original class to use our own “hacked” version. Even less can it upgrade already existing object instances. :sob:

1st languages that come to mind which can easily edit classes & objects on-the-fly are JavaScript & Lua. :first_quarter_moon_with_face:

Lua I barely know anything about it. But in JS we merely append new properties on a constructor’s prototype{} object. :money_mouth_face:

After that, all object instances of that constructor immediately receive those new added properties. :partying_face:

Here’s an example sketch that adds a new method called zero() to the p5js’ p5.Vector class: :zero:

function setup() {
  noCanvas();

  const vec = p5.Vector.random3D();
  print(vec);
  print(`Method zero() exists in PVector: ${'zero' in vec}\n`);

  p5.Vector.prototype.zero = function () {
    this.x = this.y = this.z = 0;
    return this;
  };

  vec.zero();
  print(vec);
  print('Method zero() exists in PVector: ' + ('zero' in vec));
}

Just copy & paste the sketch above on OpenProcessing.org in order to run it btW: :wink:
www.OpenProcessing.org/sketch/create

Of course there are many more other languages which can edit classes & objects on-the-fly as well. :airplane:

And among those, Python of course: :snake:

vec = PVector.random3D(this)
print vec
print 'Method zero() exists in PVector: %s\n' % hasattr(vec, 'zero')

def zero(v):
    v.x = v.y = v.z = 0
    return v

setattr(PVector, zero.__name__, zero)

vec.zero()
print vec
print 'Method zero() exists in PVector: ' + `hasattr(vec, 'zero')`

exit()
1 Like

Hi @hamoid

Yes I am using Kotlin as main language for my processing projects. I really love the syntactic sugar it offers (extension methods, optional parameters, type inference …) and the null-safety, coroutines, a simpler functional interface for lists (more like LINQ from C#) and much more.

But I mainly use it for larger projects, where I need a good software architecture. You can find some of my projects here:

  • led-forest3 - very adaptive interactive installation
  • realsense-processing - smaller sketch as a proof of concept
  • anna - based on led-forest3 but another installation (then one of my presentation)

Extension methods are a great too to polish an API which adds too much clutter to your code. For example, I use the following method a lot which gives you the security that you won’t ever forget the endDraw():

fun PGraphics.draw(block: (g: PGraphics) -> Unit) {
    this.beginDraw()
    block(this)
    this.endDraw()
}

And you find many more here:

But really the most interesting part of kotlin is, that you do not use nullable types. So usually you can be sure that no variable is null. That leads to another way of how you write your code. Mostly processing uses the nullability, so sometimes you have to work around a bit.

For me kotlin is not “better” or “faster” then java, just more fun to use and in some ways helps me to write cleaner code.

2 Likes

5 posts were split to a new topic: Getting the depth image from a Real Sense camera

Thank you @cansik for posting your code! Very useful to learn this new approach :slight_smile:

Question: I’m trying to inherit from PGraphics3D but apparently when I call LCircle.draw() the PGraphics has not been initialized (NPE at clear()). Any ideas?

package com.hamoid

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

class Basic : PApplet() {
    lateinit var layer : LCircle
    companion object Factory {
        fun run() {
            val art = Basic()
            //art.setSize(700, 500)
            art.runSketch()
        }
    }

    override fun settings() {
        size(700, 500, PConstants.P3D)
    }

    override fun setup() {
        background(70.0f, 20.0f, 200.0f)
        layer = LCircle(this, 500, 500)
    }

    override fun draw() {
        line(0.0f, 0.0f, width * 1.0f, height * 1.0f);
        layer.draw()
    }
}

fun main() {
    Basic.run()
}

This is an example “hello world” layer:

package com.hamoid

import processing.core.PApplet
import processing.opengl.PGraphics3D

class LCircle(p5: PApplet, w: Int, h: Int) : PGraphics3D() {
    init {
        setParent(p5)
        setPrimary(false)
        setSize(w, h)
    }

    fun draw() {
        clear()
        fill(255)
        circle(width* 0.5f, height * 0.5f, 100.0f)
    }
}

Update: the thing that was missing was a beginDraw() / endDraw() inside fun draw(). The clean syntax (without the myPG. prefix) made me forget I was inside a PGraphics.

There is also Sam Pottingers fork works great for me:-

2 Likes

Thanks! Yes, been keeping an eye on it. Although Processing core generally works fine with OpenJDK 11 as is. Was about to ship PraxisLIVE v4.3 with Java 11 but having some testing issues with crashes when closing windows on macOS. This is a bug in JOGL unfortunately so will still be there in Sam’s fork.

Is it even possible to initialise a valid PGraphics without using the createGraphics(width, height) method? I have never inherited from PGraphics3D, I always decorate it.

Ah yes, I found the solution. I added it to my question :slight_smile: It works :slight_smile:

ps. I found it thanks to the source code linked by @neilcsmith at Is it possible to extend the classes PGraphics/ PGraphics3D?

2 Likes

Very cool. I didn’t realize that extending PGraphics in that way was possible.

I was having some trouble getting the processing library to be picked up by Intellij so here’s what I added in the end to my build.gradle (Mac OS X example):

repositories {
    mavenCentral()
    flatDir {
        dirs '/Applications/Processing.app/Contents/Java/core/library'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    testCompile group: 'junit', name: 'junit', version: '4.12'

    compile group: 'org.processing', name: 'core', version: '3.5.4'
    compile name: 'jogl-all', ext:'jar'
    compile name: 'jogl-all-natives-macosx-universal', ext:'jar'
    compile name: 'gluegen-rt', ext:'jar'
    compile name: 'gluegen-rt-natives-macosx-universal', ext:'jar'
}
1 Like

Hi @iamsaitam :slight_smile:

Could you describe the steps to use Gradle with Processing and Kotlin? Assuming you have Idea, Gradle and Processing installed, what’s next? Do you go to File > New > Gradle Kotlin JVM project? And then edit build.gradle to include the Processing related stuff as you just posted?

2 Likes

Yes, that’s basically what I did :slight_smile:

I’ve been using OPENRNDR for a few months and I’m really enjoying it.
Having previous experience with Processing and computer graphics in general helps.

I’m writing some posts comparing both frameworks, in case anyone is curious.

1 Like
val closest = points.minBy { (mouse.position - it).squaredLength }

ouch… this is neat !

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

Be interesting to compare that with an idiomatic Java 11 (or 14) version.

1 Like

It would! I’m not up to date on them. Anyone?