Skip to content

Process Animation

Animation is a powerful tool to debug, test and demonstrate simulations.

It is possible use shapes (lines, rectangles, circles, etc), texts as well image to visualize the state of a simulation model. Statistical properties may be animated by showing the current value against the time.

Process animations can be

  • Synchronized with the simulation clock and run in real time (synchronized)
  • Advanced per simulation event (non-synchronized)

How to get started?

All it takes is a single dependency

dependencies {
    api("com.github.holgerbrandl:kalasim-animation:0.7.97")
}

The dependency pull everything you need to animate simulations.

For fully worked out examples, have a look at the lunar mining or the office tower.

If you're not sure how to configure gradle, you could also start with the provided processes animation template project.

Under the hood

OPENRNDR is an open source framework for creative coding, written in Kotlin that simplifies writing real-time interactive software.

For more details see https://openrndr.org/

Process animation with kalasim is using OPENRNDR as backend and rendering engine. Animation is not part of the core API of kalasim, but support is provided by a decorator types (extending their respective base-type)

  • Component -> AnimationComponent
  • Resource -> AnimationResource
  • ComponentQueue -> AnimationResource

These components are worked out below.

Animation Template

The basic structure of a process animation is as follows

// package org.kalasim.animation

import kotlinx.coroutines.*
import org.kalasim.ClockSync
import org.kalasim.Environment
import org.kalasim.misc.DependencyContext
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.loadFont
import org.openrndr.draw.loadImage
import java.awt.geom.Point2D
import java.lang.Thread.sleep
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit

fun main() {
    application {
        // setup simulation model
        val sim = object : Environment(tickDurationUnit = DurationUnit.SECONDS) {
            init {
                ClockSync(tickDuration = 10.milliseconds, syncsPerTick = 100)
            }

            // instantiate components (not fully worked out here)
            val worker = AnimationComponent(Point2D.Double(1.0, 3.0))
        }

        // configure the window
        configure {
            width = 1024
            height = 800
            windowResizable = true
            title = "Simulation Name"
        }

        var frameCounter = 0

        program {
            // load resources such as images
            val image = loadImage("src/main/resources/1024px-Phlegra_Montes_on_Mars_ESA211127.jpg")
//            val truck = loadSVG("src/main/resources/tractor-svgrepo-com.svg")
            val font = loadFont("file:IBM_Plex_Mono/IBMPlexMono-Bold.ttf", 24.0)

            // optionally enable video recording
//            extend(ScreenRecorder())

            extend {
                // draw background
                drawer.image(image, 0.0, 0.0, width.toDouble(), height.toDouble())

                // visualize simulation entities
                with(drawer) {
                    val workerPosition = sim.worker.currentPosition
                    circle(workerPosition.x, workerPosition.y, 10.0)
                }


                // draw info & statistics
                drawer.defaults()
                drawer.fill = ColorRGBa.WHITE
                drawer.fontMap = font
                drawer.text("NOW: ${sim.now}", width - 150.0, height - 30.0)
                drawer.text("Frame: ${frameCounter++}", width - 150.0, height - 50.0)
            }
        }

        // Start simulation model
        CoroutineScope(Dispatchers.Default).launch {
            //rewire koin context for dependency injection to async execution context
            DependencyContext.setKoin(sim.getKoin())
            // wait because Openrndr needs a second to warm up
            sleep(3000)
            sim.run()
        }
    }
}
Templates including gradle build files) sources can be found in the repo. F

For an in-depth walkthrough of the elements the an animation, see https://guide.openrndr.org/

Animating Components

By changing the base class of a component from Component to org.kalasim.animation.AnimationComponent, we decorate the original with the following features

  • Instances can have an initial position (modelled as Point2D)
  • With moveTo(newLocation:Point2D) the API provides suspendable wrapper around hold()
  • While being on hold, an animation can always request the current position with c.currentPosition. Positions are linearly interpolated.

Animating hold() Interactions

An animation can track the status hold() interaction with holdProgress. It's a 2 step process

  1. First, we need to register what type of holds we would like to monitor

    val UNLOADING = "Unloading"
    val c: Component = Component()
    
    c.registerHoldTracker(UNLOADING) { it.description.startsWith("unloading")}
    

  2. Once it has been registered, the tracker can be consumed in the rendering loop with isHolding and holdProgress.

    if(c.isHolding(UNLOADING)) {
        drawer.contour(contour.sub(0.0, (1 - c.holdProgress(UNLOADING)!!)))
    }
    

For a fully worked out example, see how the mining process is animated in the lunar mining demo.

Animating Resources

Dedicated support for resource rendering is coming soon. See lunar mining to see how it's done.

Animating States

Dedicated support for state rendering is coming soon.

Animating Queues & Collections

Dedicated support for collection rendering is coming soon.

Other animation frontends

The animation support API does not bind to a particular rendering engine. However, until now only https://openrndr.org/ has been explored for process animation with kalasim.