diff --git a/orx-easing/README.md b/orx-easing/README.md index 9428d9d6..3337564f 100644 --- a/orx-easing/README.md +++ b/orx-easing/README.md @@ -4,37 +4,99 @@ Provides easing functions for smooth animation or non-linear interpolation. Similar to those on https://easings.net - - `easeLinear` - - `easeBackIn`, `easeBackInOut`, `easeBackOut` - - `easeBounceIn`, `easeBounceInOut`, `easeBounceOut` - - `easeCircIn`, `easeCircInOut`, `easeCircOut` - - `easeCubicIn`, `easeCubicInOut` `easeCubicOut` - - `easeElasticIn`, `easeElasticInOut`, `easeElasticOut` - - `easeExpoIn`, `easeExpoInOut`, `easeExpoOut` - - `easeQuadIn`, `easeQuadInOut`, `easeQuadOut` - - `easeQuartIn`, `easeQuartInOut`, `easeQuartOut` - - `easeQuintIn`, `easeQuintInOut`, `easeQuintOut` - - `easeSineIn`, `easeSineInOut`, `easeSineOut` +| type | | +|:-----------|:-------------| +| linear | `easeLinear` | +| constant 0 | `easeZero` | +| constant 1 | `easeOne` | -## usage +| type | in | in out | out | +|---------|----------------:|-------------------:|-----------------:| +| quad | `easeQuadIn` | `easeQuadInOut` | `easeQuadOut` | +| cubic | `easeCubicIn` | `easeCubicInOut` | `easeCubicOut` | +| quart | `easeQuartIn` | `easeQuartInOut` | `easeQuartOut` | +| quint | `easeQuintIn` | `easeQuintInOut` | `easeQuintOut` | +| circ | `easeCircIn` | `easeCircInOut` | `easeCircOut` | +| expo | `easeExpoIn` | `easeExpoInOut` | `easeExpoOut` | +| sine | `easeSineIn` | `easeSineInOut` | `easeSineOut` | +| back | `easeBackIn` | `easeBackInOut` | `easeBackOut` | +| bounce | `easeBounceIn` | `easeBounceInOut` | `easeBounceOut` | +| elastic | `easeElasticIn` | `easeElasticInOut` | `easeElasticOut` | -`fun easeX(time: Double, bias: Double = 0.0, scale: Double = 1.0, duration : Double = 1.0)` +## Usage -```kotlin -// -- when t is in [0, 1] -val et = easeQuadIn(t) -val et = easeQuadIn(t, 0.0, 1.0, 10.0) +``` +fun easeX( + t: Double, // current time + b: Double = 0.0, // beginning (output value when t is 0.0) + c: Double = 1.0, // change (output delta) + d: Double = 1.0 // duration = end time +) ``` -Using the `Easing` enumeration +The most common usage involves repeatedly calling the easing function increasing +the `t` argument while keeping other arguments unchanged. When `t` increases from 0.0 up to `d`, the returned value slides from `b` to `b + c`. + +### Example + +For accelerating from 40.0 down to 10.0 in 10 steps: + +```kotlin +repeat(10) { + val y = easeQuadIn(it.toDouble(), 40.0, -30.0, 9.0) + println("$it -> $y") +} +``` + +Outputs + +``` +0 -> 40.0 +1 -> 39.629629629629626 +2 -> 38.51851851851852 +3 -> 36.666666666666664 +4 -> 34.074074074074076 +5 -> 30.74074074074074 +6 -> 26.666666666666668 +7 -> 21.85185185185185 +8 -> 16.2962962962963 +9 -> 10.0 +``` + +Note how most result values are closer to 40.0 than to 10.0, due to the usage of +an `In` easing function. `easeCubicIn`, `easeQuartIn` and `easeQuinticIn` functions would make this even more obvious. + +### Default arguments + +When `t` is in `[0, 1]` we can omit most arguments + +```kotlin +val e0 = easeQuadIn(t, 0.0, 1.0, 1.0) +val e1 = easeQuadIn(t) +``` + +### Using the `Easing` enumeration + +The `Easing` enum contains all easing functions. ```kotlin val et = Easing.QuadIn.function(t, 0.0, 1.0, 1.0) + +// list all easing function names +Easing.values().forEach { easing -> + println(easing.name) +} + +// find out how many easing functions are available +println(Easing.values().size) ``` + ## Demos + ### DemoEasings01 + [source code](src/demo/kotlin/DemoEasings01.kt) ![DemoEasings01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-easing/images/DemoEasings01Kt.png) diff --git a/orx-easing/build.gradle.kts b/orx-easing/build.gradle.kts index ba68a86c..c9606aba 100644 --- a/orx-easing/build.gradle.kts +++ b/orx-easing/build.gradle.kts @@ -25,6 +25,7 @@ kotlin { kotlin.srcDir("src/demo") dependencies { implementation(project(":orx-camera")) + implementation(project(":orx-shapes")) implementation("org.openrndr:openrndr-application:$openrndrVersion") implementation("org.openrndr:openrndr-extensions:$openrndrVersion") runtimeOnly("org.openrndr:openrndr-gl3:$openrndrVersion") diff --git a/orx-easing/src/commonMain/kotlin/Easing.kt b/orx-easing/src/commonMain/kotlin/Easing.kt index 4ce588b4..3895f846 100644 --- a/orx-easing/src/commonMain/kotlin/Easing.kt +++ b/orx-easing/src/commonMain/kotlin/Easing.kt @@ -4,6 +4,15 @@ import kotlin.math.* typealias EasingFunction = (Double, Double, Double, Double) -> Double +/** + * # Easing function arguments + * + * @param t current Time + * @param b Beginning value + * @param c Change in value (the final value is `b+c`) + * @param d Duration (maximum time) + */ + fun easeLinear(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0) = c * (t / d) + b // -- constant @@ -261,11 +270,9 @@ fun easeQuintIn(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0): D } fun easeQuintInOut(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0): Double { - val t2 = t * 2.0 - val t22 = t2 - 2.0 - return if (t < 0.5) 0.5 * t2 * t2 * t2 * t2 * t2 else { - 0.5 * (t22 * t22 * t22 * t22 * t22 + 2.0) - } + val td = t / (d / 2) + val td2 = td - 2.0 + return if (td < 1) c / 2 * td * td * td * td * td + b else c / 2 * (td2 * td2 * td2 * td2 * td2 + 2) + b } fun easeQuintOut(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0): Double { @@ -284,7 +291,12 @@ fun easeSineOut(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0): D fun easeSineInOut(t: Double, b: Double = 0.0, c: Double = 1.0, d: Double = 1.0): Double = -c / 2 * (cos(PI * t / d) - 1) + b - +/** + * Enum containing all easing functions + * + * Use the `Easing.values()` list to iterate over available functions, + * query its `.size` property or get functions by index. + */ enum class Easing(val function: EasingFunction) { Linear(::easeLinear), @@ -331,5 +343,3 @@ enum class Easing(val function: EasingFunction) { SineInOut(::easeSineInOut), SineOut(::easeSineOut), } - - diff --git a/orx-easing/src/demo/kotlin/DemoEasings01.kt b/orx-easing/src/demo/kotlin/DemoEasings01.kt index 5c8b42d1..e4ee8b77 100644 --- a/orx-easing/src/demo/kotlin/DemoEasings01.kt +++ b/orx-easing/src/demo/kotlin/DemoEasings01.kt @@ -1,9 +1,17 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa -import org.openrndr.extensions.SingleScreenshot -import org.openrndr.extras.easing.* +import org.openrndr.draw.loadFont +import org.openrndr.extra.shapes.grid +import org.openrndr.extras.easing.Easing import org.openrndr.math.Vector2 +import org.openrndr.math.map +/** + * # Visualizes Easing types as a graph and as motion. + * + * [grid] is used to layout graphs on rows and columns. + * + */ fun main() { application { configure { @@ -11,63 +19,60 @@ fun main() { height = 1080 } program { - fun drawEasing(f: EasingFunction) { - drawer.stroke = ColorRGBa.PINK - val points = mutableListOf() - for (i in 0 .. 40) { - val y = 40.0 - f(i / 40.0, 0.0, 1.0, 1.0) * 40.0 - points.add(Vector2(i*10.0, y)) - } - drawer.lineStrip(points) - drawer.stroke = ColorRGBa.GRAY - drawer.lineSegment(0.0, 40.0, 400.0, 40.0) - drawer.lineSegment(0.0, 20.0, 400.0, 20.0) - } - extend { - drawer.stroke = ColorRGBa.WHITE + val font = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 20.0) - val functions = listOf( - ::easeLinear, - ::easeQuadIn, - ::easeQuadOut, - ::easeQuadInOut, - ::easeCubicIn, - ::easeCubicOut, - ::easeCubicInOut, - ::easeCircIn, - ::easeCircOut, - ::easeCircInOut, - ::easeQuartIn, - ::easeQuartOut, - ::easeQuartInOut, - ::easeExpoIn, - ::easeExpoOut, - ::easeExpoInOut, - ::easeQuintIn, - ::easeQuintOut, - ::easeQuintInOut, - ::easeSineIn, - ::easeSineOut, - ::easeSineInOut, - ::easeBackIn, - ::easeBackOut, - ::easeBackInOut, - ::easeElasticIn, - ::easeElasticOut, - ::easeElasticInOut, - ::easeBounceIn, - ::easeBounceOut, - ::easeBounceInOut - ) - var i = 0 - for (f in functions) { - drawEasing(f) - drawer.translate(0.0, 50.0) - i ++ - if (i > 19) { - drawer.translate(450.0, -20 * 50.0) - i = 0 + // grid `columns * rows` must be >= Easing.values().size + val grid = drawer.bounds.grid( + 3, 11, 10.0, 10.0, 10.0, 10.0 + ).flatten() + + // make pairs of (easing function, grid rectangle) + val pairs = Easing.values() zip grid + + extend { + // ~4 seconds animation loop + val animT = (frameCount % 240) / 60.0 + + pairs.forEach { (easing, gridRect) -> + + // background rectangle + drawer.stroke = null + drawer.fill = ColorRGBa.WHITE.opacify(0.3) + drawer.rectangle(gridRect) + + // graph + drawer.stroke = ColorRGBa.PINK + val points = List(40) { + val curveT = it / 39.0 + gridRect.position( + curveT, easing.function(curveT, 1.0, -1.0, 1.0) + ) } + drawer.lineStrip(points) + + // label + drawer.fill = ColorRGBa.WHITE + drawer.stroke = null + drawer.fontMap = font + drawer.text( + easing.name, + // text position rounded for crisp font rendering + gridRect.position(0.02, 0.25).toInt().vector2 + ) + + // animation + drawer.fill = ColorRGBa.WHITE.opacify( + when { // 4-stage opacity + animT > 3.0 -> 0.0 // invisible + animT > 2.0 -> 3.0 - animT // fade-out + animT < 1.0 -> animT // fade-in + else -> 1.0 // visible + } + ) + // move only while visible (when loop time in 1.0..2.0) + val t = animT.map(1.0, 2.0, 0.0, 1.0, true) + val xy = Vector2(1.0, easing.function(t, 1.0, -1.0, 1.0)) + drawer.circle(gridRect.position(xy), 5.0) } } }