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()
}
}
}
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 aroundhold()
- 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
-
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")}
-
Once it has been registered, the tracker can be consumed in the rendering loop with
isHolding
andholdProgress
.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
.