From 6ef5c037803ced17d4fe301d348d6582accd92df Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Mon, 21 Sep 2020 09:41:01 +0200 Subject: [PATCH] [orx-keyframer] Add value dicts for per channel easing and duration --- orx-keyframer/README.md | 19 +-- orx-keyframer/src/main/kotlin/Key.kt | 4 + orx-keyframer/src/main/kotlin/Keyframer.kt | 157 +++++++++++++++------ 3 files changed, 118 insertions(+), 62 deletions(-) diff --git a/orx-keyframer/README.md b/orx-keyframer/README.md index b9dbe0ad..4f965edb 100644 --- a/orx-keyframer/README.md +++ b/orx-keyframer/README.md @@ -47,7 +47,10 @@ What this allows you to do: "easing": "cubic-in-out", "x": 100.0, "y": 320.0, - "radius": 40 + "radius": { + "value": 50.0, + "easing": "linear" + } } ] ``` @@ -135,20 +138,6 @@ Supported functions in expressions: [Parameters and prototypes](src/demo/resources/demo-full-01.json) - ## Demos ### DemoFull01 diff --git a/orx-keyframer/src/main/kotlin/Key.kt b/orx-keyframer/src/main/kotlin/Key.kt index 6da2a271..f599cdc8 100644 --- a/orx-keyframer/src/main/kotlin/Key.kt +++ b/orx-keyframer/src/main/kotlin/Key.kt @@ -33,6 +33,10 @@ class KeyframerChannel { return keys.lastOrNull()?.value } + fun lastTime(): Double? { + return keys.lastOrNull()?.time + } + fun duration(): Double { return keys.last().time } diff --git a/orx-keyframer/src/main/kotlin/Keyframer.kt b/orx-keyframer/src/main/kotlin/Keyframer.kt index 8dc1166a..7f84468e 100644 --- a/orx-keyframer/src/main/kotlin/Keyframer.kt +++ b/orx-keyframer/src/main/kotlin/Keyframer.kt @@ -5,6 +5,7 @@ import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken import org.openrndr.color.ColorRGBa import org.openrndr.extras.easing.Easing +import org.openrndr.extras.easing.EasingFunction import org.openrndr.math.Vector2 import org.openrndr.math.Vector3 import org.openrndr.math.Vector4 @@ -12,6 +13,7 @@ import java.io.File import java.lang.IllegalStateException import java.lang.NullPointerException import java.net.URL +import kotlin.math.max import kotlin.math.roundToInt import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 @@ -257,6 +259,45 @@ open class Keyframer { expressionContext["t"] = 0.0 + fun easingFunctionFromName(easingCandidate: String): EasingFunction { + return when (easingCandidate) { + "linear" -> Easing.Linear.function + "back-in" -> Easing.BackIn.function + "back-out" -> Easing.BackOut.function + "back-in-out" -> Easing.BackInOut.function + "bounce-in" -> Easing.BounceIn.function + "bounce-out" -> Easing.BounceOut.function + "bounce-in-out" -> Easing.BackInOut.function + "circ-in" -> Easing.CircIn.function + "circ-out" -> Easing.CircOut.function + "circ-in-out" -> Easing.CircInOut.function + "cubic-in" -> Easing.CubicIn.function + "cubic-out" -> Easing.CubicOut.function + "cubic-in-out" -> Easing.CubicInOut.function + "elastic-in" -> Easing.ElasticIn.function + "elastic-out" -> Easing.ElasticInOut.function + "elastic-in-out" -> Easing.ElasticOut.function + "expo-in" -> Easing.ExpoIn.function + "expo-out" -> Easing.ExpoOut.function + "expo-in-out" -> Easing.ExpoInOut.function + "quad-in" -> Easing.QuadIn.function + "quad-out" -> Easing.QuadOut.function + "quad-in-out" -> Easing.QuadInOut.function + "quart-in" -> Easing.QuartIn.function + "quart-out" -> Easing.QuartOut.function + "quart-in-out" -> Easing.QuartInOut.function + "quint-in" -> Easing.QuintIn.function + "quint-out" -> Easing.QuintOut.function + "quint-in-out" -> Easing.QuintInOut.function + "sine-in" -> Easing.SineIn.function + "sine-out" -> Easing.SineOut.function + "sine-in-out" -> Easing.SineInOut.function + "one" -> Easing.One.function + "zero" -> Easing.Zero.function + else -> error("unknown easing name '$easingCandidate'") + } + } + fun handleKey(key: Map, path: String) { val prototype = (key["prototypes"] as? String)?.let { @@ -298,42 +339,7 @@ open class Keyframer { val easing = try { when (val easingCandidate = computed["easing"]) { null -> Easing.Linear.function - is String -> when (easingCandidate) { - "linear" -> Easing.Linear.function - "back-in" -> Easing.BackIn.function - "back-out" -> Easing.BackOut.function - "back-in-out" -> Easing.BackInOut.function - "bounce-in" -> Easing.BounceIn.function - "bounce-out" -> Easing.BounceOut.function - "bounce-in-out" -> Easing.BackInOut.function - "circ-in" -> Easing.CircIn.function - "circ-out" -> Easing.CircOut.function - "circ-in-out" -> Easing.CircInOut.function - "cubic-in" -> Easing.CubicIn.function - "cubic-out" -> Easing.CubicOut.function - "cubic-in-out" -> Easing.CubicInOut.function - "elastic-in" -> Easing.ElasticIn.function - "elastic-out" -> Easing.ElasticInOut.function - "elastic-in-out" -> Easing.ElasticOut.function - "expo-in" -> Easing.ExpoIn.function - "expo-out" -> Easing.ExpoOut.function - "expo-in-out" -> Easing.ExpoInOut.function - "quad-in" -> Easing.QuadIn.function - "quad-out" -> Easing.QuadOut.function - "quad-in-out" -> Easing.QuadInOut.function - "quart-in" -> Easing.QuartIn.function - "quart-out" -> Easing.QuartOut.function - "quart-in-out" -> Easing.QuartInOut.function - "quint-in" -> Easing.QuintIn.function - "quint-out" -> Easing.QuintOut.function - "quint-in-out" -> Easing.QuintInOut.function - "sine-in" -> Easing.SineIn.function - "sine-out" -> Easing.SineOut.function - "sine-in-out" -> Easing.SineInOut.function - "one" -> Easing.One.function - "zero" -> Easing.Zero.function - else -> error("unknown easing name '$easingCandidate'") - } + is String -> easingFunctionFromName(easingCandidate) else -> error("unknown easing for '$easingCandidate'") } } catch (e: IllegalStateException) { @@ -349,19 +355,76 @@ open class Keyframer { val channel = channels.getOrPut(channelCandidate.key) { KeyframerChannel() } - expressionContext["v"] = channel.lastValue() ?: 0.0 - val value = try { - when (val candidate = channelCandidate.value) { - is Double -> candidate - is String -> evaluateExpression(candidate, expressionContext, functions) - ?: error("unknown value format for key '${channelCandidate.key}' : $candidate") - is Int -> candidate.toDouble() - else -> error("unknown value type for key '${channelCandidate.key}' : $candidate") + + val lastValue = channel.lastValue() ?: 0.0 + expressionContext["v"] = lastValue + + val lastTime = (channel.lastTime()) ?: 0.0 + expressionContext["d"] = time - lastTime + + if (channelCandidate.value is Map<*, *>) { + @Suppress("UNCHECKED_CAST") + val valueMap = channelCandidate.value as Map + + val value = try { + when (val candidate = valueMap["value"]) { + null -> error("no value for '${channelCandidate.key}'") + is Double -> candidate + is String -> evaluateExpression(candidate, expressionContext, functions) + ?: error("unknown value format for key '${channelCandidate.key}' : $candidate") + is Int -> candidate.toDouble() + else -> error("unknown value type for key '${channelCandidate.key}' : $candidate") + } + } catch (e: ExpressionException) { + throw ExpressionException("error in $path.'${channelCandidate.key}': ${e.message ?: ""}") } - } catch (e: ExpressionException) { - throw ExpressionException("error in $path.'${channelCandidate.key}': ${e.message ?: ""}") + + val dictEasing = when (val candidate = valueMap["easing"]) { + null -> easing + is String -> easingFunctionFromName(candidate) + else -> error("unknown easing for '$candidate'") + } + + val dictDuration = try { + when (val candidate = valueMap["duration"]) { + null -> null + is Double -> candidate + is String -> evaluateExpression(candidate, expressionContext, functions) + ?: error("unknown value format for key '${channelCandidate.key}' : $candidate") + is Int -> candidate.toDouble() + else -> error("unknown value type for key '${channelCandidate.key}' : $candidate") + } + } catch (e: ExpressionException) { + throw ExpressionException("error in $path.'${channelCandidate.key}': ${e.message ?: ""}") + } + + if (dictDuration != null) { + if (dictDuration <= 0.0) { + println(lastTime) + println(time + dictDuration) + println(lastValue) + channel.add(max(lastTime, time + dictDuration), lastValue, Easing.Linear.function, hold) + channel.add(time, value, dictEasing, hold) + } else { + channel.add(time, lastValue, Easing.Linear.function, hold) + channel.add(time + dictDuration, value, dictEasing, hold) + } + } + + } else { + val value = try { + when (val candidate = channelCandidate.value) { + is Double -> candidate + is String -> evaluateExpression(candidate, expressionContext, functions) + ?: error("unknown value format for key '${channelCandidate.key}' : $candidate") + is Int -> candidate.toDouble() + else -> error("unknown value type for key '${channelCandidate.key}' : $candidate") + } + } catch (e: ExpressionException) { + throw ExpressionException("error in $path.'${channelCandidate.key}': ${e.message ?: ""}") + } + channel.add(time, value, easing, hold) } - channel.add(time, value, easing, hold) } } lastTime = time + duration