Yesterday, the OPENRNDR starting project ran on my machine. Today, before studying the system, let’s take a look at the code and tweak it a bit.

I’ve disconnected my project from the origin. It seems that their instructions, while simple, lave your project set with origin as the template. I don’t know whether GitHub would let me commit to it, but I’m sure that now, even if I do hit the push button (haha) it won’t mess up OPENRNDR for the world.

As one does, I plan to review the code they’ve provided and tweak it a bit, just to get a sense of how it all works. Because that’s the kind of person I am, I’ll read the documents before doing much. Here’s one of the two demos their starting template provides:

fun main() = application {
    configure {
        width = 768
        height = 576
    }

    program {
        val image = loadImage("data/images/pm5544.png")
        val font = loadFont("data/fonts/default.otf", 64.0)

        extend {
            drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade(0.2))
            drawer.image(image)

            drawer.fill = ColorRGBa.PINK
            drawer.circle(cos(seconds) * width / 2.0 + width / 2.0, sin(0.5 * seconds) * height / 2.0 + height / 2.0, 140.0)

            drawer.fontMap = font
            drawer.fill = ColorRGBa.WHITE
            drawer.text("OPENRNDR", width / 2.0, height / 2.0)
        }
    }
}

My past experience with drawing programs is such as to tell me these things:

  1. They’ve done a Kotlin-style DSL thing here, with the top level function being application, with at least two subordinates, configure and program.

  2. It seems quite likely that configure sets up configuration matters, including the width and height of a window that’s going to be created1.

  3. I’d bet a few Linden dollars that the program bit is called repeatedly, perhaps at some fixed rate like 30 or 60 times per second.

  4. The drawer is some kind of object that does drawing2, sort of an object that draws on a panel or pane or chunk of paper, and it clearly has a cubic bunload of convenient functions one can call.

The app displays a window that looks like this:

window with dark patterned background, large pink circle, and OPENRNDR in white text

The pink circle is moving in a sinusoidal fashion from top left to middle right to bottom left. What is most interesting about this fact is that the program bit does not include any obvious “move this thing” code. It does, however, include this:

drawer.circle(
	cos(seconds) * width / 2.0 + width / 2.0, 
	sin(0.5 * seconds) * height / 2.0 + height / 2.0, 
	140.0)

From this we surmise that every time program is executed, the value seconds includes a time value, probably an accumulating value. (An alternative would be delta-seconds since last call, but if that were the case, the circle wouldn’t move as it does. Also seconds would be a uniquely bad name for deltaSeconds.)

So as time increases, the x and y of the circle follow, yep, sinusoidal curves. since sin and cos both go from -1 to 1, out of phase with each other, x goes smoothly from 0 to width, y from 0 to height. All the rigmarole with cos*width/2.0 + width/2.0 is just coordinate transforms to keep us inside the window.

It’s time to mess with this.

drawer.circle(
	cos(seconds) * width / 4.0 + width / 2.0, 
	sin(seconds) * height / 4.0 + height / 2.0, 
	140.0)

Now the pink disc rotates in a circle around the center of the screen. Why? Well, because x=cos(theta), y=sin(theta) is the way you express a circle in polar coordinates. One of the things one learns in a lifetime of learning useless things.

There’s not a lot more to learn hear. One interesting thing is the display of the background pattern:

drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade(0.2))
drawer.image(image)

If we comment out that colorMatrix line, we see the background for what it truly is:

window with gaudy pattern background, pink disc, OPENRNDR test

I hope that didn’t hurt your eyes. The basic pattern is, um vivid. For the details of that colorMatrix stuff, we’ll have to read the documents, I’m afraid. Well, I’m not afraid of reading the documents, I just mean there is a limit to what we can best learn by experimentation.

I am minded to try one little thing, though. In a real graphics program, I like to have objects that know how to draw themselves. (My colleagues often add in another layer, winding up with objects that know where they are and have a view that draws them. We may explore this alternative if and when our joint project starts coming together.)

So let’s create a Disc class and use it. A bit of fiddling and we have this:

fun main() = application {
    val disc = Disc(100.0)
    configure {
        width = 768
        height = 576
    }

    program {
        val image = loadImage("data/images/pm5544.png")
        val font = loadFont("data/fonts/default.otf", 64.0)

        extend {
            drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade(0.2))
            drawer.image(image)

            disc.draw(drawer,seconds)
            
            drawer.fontMap = font
            drawer.fill = ColorRGBa.WHITE
            drawer.text("OPENRNDR", width / 2.0, height / 2.0)
        }
    }
}

class Disc(private val radius: Double) {
    fun draw(drawer: Drawer, seconds: Double) {
        val xMul = drawer.width/4.0
        val yMul = drawer.height/4.0
        val xCenter = drawer.width/2.0
        val yCenter = drawer.height/2.0
        val x = cos(seconds)*xMul + xCenter
        val y = sin(seconds)*yMul + yCenter
        val rad = radius*cos(seconds)
        drawer.fill = ColorRGBa.MEDIUM_SLATE_BLUE
        drawer.circle(x,y,rad)
    }
}

It took me a tiny bit of guessing to find out that drawer knows width and height It makes sense that one can find out all the interesting facts from it. So now the program draws a slate blue disc that appears to approach and recede as it circles around. It looks like this:

movie showing blue disc growing and shrinking as it circles inside the window

Summary

That should be close to what we can learn here. One more thing comes to mind, making sure that my tests are hooked up correctly. That will require a bit more configuration knowledge than I have just now, so I’ll do that offline.

For now, we’ve managed to understand a basic OPENRNDR program, and get a bit of comfort from using it. To me, that’s one of the early things one does. And giving it a better design, by pulling out a class? Chef’s kiss.

See you next time!



  1. Hey, this guy must be some kind of wizard to figure all that out from just application, configure, width and height! 

  2. Wow, he is just getting smarter and smarter! A thing called drawer that draws. Hold him back, he’s racing ahead of us mortals!