Open8

Generative Artをやってみる(Processing + Kotlin編)

ぺい / パンケーキガメぺい / パンケーキガメ

以前はNext.jsをよく触っていたので、↓のスクラップを作りp5.jsで遊んでいた。
https://zenn.dev/pay_hayashi/scraps/bd4e1b9360b85d

しかし、Typescript+Next.jsでp5.jsを動かそうと思うと型定義周りが弱いのと、ラッパーとProcessingの境目がわかりづらかったりとで結構しんどかった。
とはいえ素直にProcessingで遊ぼうと思うと、IDEまで提供されてしまっているせいで使い慣れたエディタで開発できないのが辛いという気持ちもあった。

そこでよくよく考えるとProcessingってJavaをベースに作られてるわけだからJVM言語で開発できるんじゃない?と思い、今の業務領域であるKotlinで実行するところまで遊んでみた。

ぺい / パンケーキガメぺい / パンケーキガメ

MavenRepositoryにProcessingがあるので、上記記事のように素直に依存に入れるだけで動きそう。
https://mvnrepository.com/artifact/org.processing/core

上記記事ではProcessing3でJava8でないと動かないとあるが、今はProcessing4が登場しており、状況が違っている。
公式ページは専用IDEの説明しかなく、ライブラリとしての要件がパッと探した限りは見つからない。
GitHubのrevisions.mdを見たところJava17に対応したとの文言があるので、ライブラリとしてもJava17以上で使えるだろうと予想。
https://github.com/processing/processing4/blob/main/build/shared/revisions.md

ぺい / パンケーキガメぺい / パンケーキガメ

新しく生のKotlinプロジェクトを作るにあたり、普段IntelliJを使っているのであまり考えずにIntelliJの機能を使うのが楽かなーと思い適当に作成。
JDKは9以降なら後方互換性結構あるやろともう一段上のLTSである21を雑に指定。

build.gradle.ktsにライブラリとしてのProcessingを追加。

gradle.kts
dependencies {
    // ↓追加
    // https://mvnrepository.com/artifact/org.processing/core
    implementation("org.processing:core:4.3.1")

    testImplementation(kotlin("test"))
}

PAppletクラスを継承したSketchクラスを作成。

TestSketch.kt
import processing.core.PApplet

class TestSketch : PApplet() {
    override fun settings() {
        size(400, 400)
    }

    override fun setup() {
        background(255)
    }

    override fun draw() {
        fill(0)
        ellipse(width / 2f, height / 2f, 100f, 100f)
    }
}

Main.ktで PApplet.main() にTestSketchを指定して、実行。

Main.kt
fun main() {
    PApplet.main(TestSketch::class.java)
}

無事に起動できた!!

ぺい / パンケーキガメぺい / パンケーキガメ

Processingの関数やら変数がimportしてないのに動くのはなんでだ?と思い、仕組みを少々調査。

PAppletクラス内にそれらのプロパティ・メソッドが定義されているため、子クラスから親クラスのそれらを参照する、という動きのようだ。
https://github.com/processing/processing4/blob/main/core/src/processing/core/PApplet.java

まあ考えたら当たり前ではあり、レンダリングした内容についての状態は常にPAppletが管理しているわけで、そうでなければ画面幅とかのプロパティを参照できない。

これはほぼ生のProcessingを触っているようにリファレンスを見ながら開発する分には非常に強力で楽だが、Util的な関数を作ろうとすると途端にしんどくなる。。。
全然関係ない関数から描画に関する状態を把握できないので、わざわざSketchのインスタンスをutil関数に渡す必要がある。
例えば、map()noise() を入れるケースが結構ありutil化しようとしたが、noise() はPAppletが持つseedの情報を知るためにsketchを参照する必要があるため、このような実装になる。(map() は単なる数学的な変換なのでstatic関数としてimportできる)

fun mappedNoise(sketch: PApplet, value: Float, min: Float, max: Float): Float {
    return map(sketch.noise(value), 0f, 1f, min, max)
}
ぺい / パンケーキガメぺい / パンケーキガメ

util関数を定義しているのにわざわざインスタンスを渡すのは結局記述量が増えて億劫だし、複数のPAppletを起動して互いの状態を知る必要が出るケースはそうそうないと思われるので、PAppletをラップしたSketch抽象クラスを作ってしまう。このSketchクラスを継承すれば、その手のutil関数をProcessingがもともと提供している関数のように扱うことができる。

Sketch.kt

open class Sketch : PApplet() {
    protected fun mappedNoise(value: Float, min: Float, max: Float): Float {
        return map(noise(value), 0f, 1f, min, max)
    }
}
TestSketch.kt
import processing.core.PApplet

class TestSketch : Sketch() {
    override fun draw() {
        // 略
        mappedNoise(tick * 0.01f + seed, -10, 25)
    }
}
ぺい / パンケーキガメぺい / パンケーキガメ

複数の作品を作成して起動するものを都度変えたいと思ったので、CLIから起動するSketchのクラスを指定できるようにした。
他にも画面サイズなど場合によって引数を追加したい場合もあるかと思い、Apache Commons CLIを使用してパースすることにした。

Main.kt
import org.apache.commons.cli.Option
import processing.core.PApplet

val optionList = listOf(
    Option.builder()
        .longOpt("sketch")
        .hasArg()
        .desc("The name of the sketch to run")
        .required()
        .build()
)

fun main(argsArray: Array<String>) {
    val args = getCliParser(optionList)(argsArray)

    val sketchName = args.getOptionValue("sketch")
    println("Running sketch: $sketchName")

    try {
        // Sketchをおいてあるパッケージのパスを書いておく
        val sketchClass = Class.forName("hoge.fuga.$sketchName")
        PApplet.main(sketchClass)
    } catch (e: ClassNotFoundException) {
        println("Sketch not found: $sketchName")
    }
}

IntelliJの実行構成で引数を指定することでクラスを変更できる。(黒塗りがだるいので適当なパスに変えている)

ぺい / パンケーキガメぺい / パンケーキガメ

ほぼほぼ生のProcessingの感覚で開発できるのでめちゃくちゃ体験がいい。
以前にp5.jsで書いた改造WaveClockもサクッと書けた。
というかProcessing4が結構開発体験がいいという説もある。(3以前を使ったことがないのでわからないけど。)