Interactive animations

Animatable

The OPENRNDR Animatable class provides animation logic.

Displayed below is a very simple animation setup in which we animate a circle from left to right. We do this by animating the x property of our animation object.

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    program {
        // -- create an animation object
        val animation = object : Animatable() {
            var x = 0.0
            var y = 360.0
        }

        animation.apply {
            ::x.animate(width.toDouble(), 5000)
        }

        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

By using .complete() we can create sequences of property animations.

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }

        animation.apply {
            ::x.animate(width.toDouble(), 5000)
            ::x.complete()
            ::y.animate(height.toDouble(), 5000)
        }

        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

If we leave out that ::x.complete() line we will see that animations for x and y run simultaneously.

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }

        animation.apply {
            ::x.animate(width.toDouble(), 5000)
            ::y.animate(height.toDouble(), 5000)
        }

        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

For those wondering where that ::x.animate() notation comes from, those are Kotlin’s property references.

Easing

A simple trick for making animations less stiff is to specify an easing.

To demonstrate we take one of the previously shown animations and add easings.

Available Easings

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }

        animation.apply {
            ::x.animate(width.toDouble(), 5000, Easing.CubicInOut)
            ::y.animate(height.toDouble(), 5000, Easing.CubicInOut)
        }

        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

Behavioral animation

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    program {
        class AnimatedCircle : Animatable() {
            var x = 0.0
            var y = 0.0
            var radius = 100.0
            var latch = 0.0

            fun shrink() {
                // -- first stop any running animations for the radius property
                ::radius.cancel()
                ::radius.animate(10.0, 200, Easing.CubicInOut)
            }

            fun grow() {
                ::radius.cancel()
                ::radius.animate(Double.uniform(60.0, 90.0), 200, Easing.CubicInOut)
            }

            fun jump() {
                ::x.cancel()
                ::y.cancel()
                ::x.animate(Double.uniform(0.0, width.toDouble()), 400, Easing.CubicInOut)
                ::y.animate(Double.uniform(0.0, height.toDouble()), 400, Easing.CubicInOut)
            }

            fun update() {
                updateAnimation()
                if (!::latch.hasAnimations) {
                    val duration = Double.uniform(100.0, 700.0).toLong()
                    ::latch.animate(1.0, duration).completed.listen {
                        val action = listOf(::shrink, ::grow, ::jump).random()
                        action()
                    }
                }
            }
        }

        val animatedCircles = List(5) { AnimatedCircle() }
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            for (ac in animatedCircles) {
                ac.update()
                drawer.circle(ac.x, ac.y, ac.radius)
            }
        }
    }
}

Link to the full example

Looping animations

While Animatable doesn’t provide explicit support for looping animations. They can be achieved through the following pattern:

import org.openrndr.animatable.Animatable
import org.openrndr.animatable.easing.Easing
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.uniform
import org.openrndr.math.Vector2

fun main() = application {
    val animation = object : Animatable() {
        var x: Double = 0.0
    }

    program {
        extend {
            animation.updateAnimation()
            if (!animation.hasAnimations()) {
                animation.apply {
                    ::x.animate(width.toDouble(), 1000, Easing.CubicInOut)
                    ::x.complete()
                    ::x.animate(0.0, 1000, Easing.CubicInOut)
                    ::x.complete()
                }
            }
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, height / 2.0, 100.0)
        }
    }
}

Link to the full example

Animatable properties

Thus far we have only worked with Double properties in our animations. However, animation is not limited to Doubles.

Any property that is a LinearType can be animated through Animatable.

fun main() = application {
    program {
        val animation = object : Animatable() {
            var color = ColorRGBa.WHITE
            var position = Vector2.ZERO
        }
        animation.apply {
            ::color.animate(ColorRGBa.PINK, 5000)
            ::position.animate(Vector2(width.toDouble(), height.toDouble()), 5000)
        }
    }
}

edit on GitHub


This site uses Just the Docs, a documentation theme for Jekyll.