[orx-keyframer] Add envelopes, remove repetitions
This commit is contained in:
@@ -20,8 +20,8 @@ DECIMAL : 'Decimal';
|
||||
STRING : 'String';
|
||||
|
||||
// Identifiers
|
||||
ID : [_]*[a-zA-Z][A-Za-z0-9_]* ;
|
||||
FUNCTION_ID : [_]*[a-z][A-Za-z0-9_]* ;
|
||||
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* ;
|
||||
FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ;
|
||||
|
||||
// Literals
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.openrndr.extra.keyframer
|
||||
import org.antlr.v4.runtime.*
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
import org.openrndr.extra.keyframer.antlr.*
|
||||
import org.openrndr.extra.keyframer.antlr.KeyLangLexer
|
||||
import org.openrndr.extra.keyframer.antlr.KeyLangParser
|
||||
import org.openrndr.extra.keyframer.antlr.KeyLangParserBaseListener
|
||||
import org.openrndr.extra.noise.uniform
|
||||
import org.openrndr.math.*
|
||||
import java.util.*
|
||||
@@ -357,20 +359,6 @@ fun evaluateExpression(
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||
): Double? {
|
||||
val lexer = KeyLangLexer(CharStreams.fromString(input))
|
||||
//
|
||||
// lexer.removeErrorListeners()
|
||||
// lexer.addErrorListener(object : BaseErrorListener() {
|
||||
// override fun syntaxError(
|
||||
// recognizer: Recognizer<*, *>?,
|
||||
// offendingSymbol: Any?,
|
||||
// line: Int,
|
||||
// charPositionInLine: Int,
|
||||
// msg: String?,
|
||||
// e: RecognitionException?
|
||||
// ) {
|
||||
// println("syntax error!")
|
||||
// }
|
||||
// })
|
||||
val parser = KeyLangParser(CommonTokenStream(lexer))
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(object : BaseErrorListener() {
|
||||
|
||||
@@ -2,14 +2,12 @@ package org.openrndr.extra.keyframer
|
||||
|
||||
import org.openrndr.extras.easing.Easing
|
||||
import org.openrndr.extras.easing.EasingFunction
|
||||
import org.openrndr.math.map
|
||||
|
||||
class Key(val time: Double, val value: Double, val easing: EasingFunction)
|
||||
internal val defaultEnvelope = doubleArrayOf(0.0, 1.0)
|
||||
|
||||
class Key(val time: Double, val value: Double, val easing: EasingFunction, val envelope: DoubleArray = defaultEnvelope)
|
||||
|
||||
enum class Hold {
|
||||
HoldNone,
|
||||
HoldSet,
|
||||
HoldAll
|
||||
}
|
||||
|
||||
class KeyframerChannel {
|
||||
val keys = mutableListOf<Key>()
|
||||
@@ -18,14 +16,17 @@ class KeyframerChannel {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fun add(time: Double, value: Double?, easing: EasingFunction = Easing.Linear.function, jump: Hold = Hold.HoldNone) {
|
||||
if (jump == Hold.HoldAll || (jump == Hold.HoldSet && value != null)) {
|
||||
lastValue()?.let {
|
||||
keys.add(Key(time, it, Easing.Linear.function))
|
||||
}
|
||||
fun add(
|
||||
time: Double,
|
||||
value: Double?,
|
||||
easing: EasingFunction = Easing.Linear.function,
|
||||
envelope: DoubleArray = defaultEnvelope
|
||||
) {
|
||||
require(envelope.size >= 2) {
|
||||
"envelope should contain at least 2 entries"
|
||||
}
|
||||
value?.let {
|
||||
keys.add(Key(time, it, easing))
|
||||
keys.add(Key(time, it, easing, envelope))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +66,8 @@ class KeyframerChannel {
|
||||
val rightKey = keys[rightIndex]
|
||||
val leftKey = keys[leftIndex]
|
||||
val t0 = (time - leftKey.time) / (rightKey.time - leftKey.time)
|
||||
val e0 = rightKey.easing(t0, 0.0, 1.0, 1.0)
|
||||
val te = t0.map(rightKey.envelope[0], rightKey.envelope[1], 0.0, 1.0, clamp = true)
|
||||
val e0 = rightKey.easing(te, 0.0, 1.0, 1.0)
|
||||
leftKey.value * (1.0 - e0) + rightKey.value * (e0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,8 @@ class KeyframerChannelQuaternion {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fun add(time: Double, value: Quaternion?, easing: EasingFunction = Easing.Linear.function, jump: Hold = Hold.HoldNone) {
|
||||
if (jump == Hold.HoldAll || (jump == Hold.HoldSet && value != null)) {
|
||||
lastValue()?.let {
|
||||
keys.add(KeyQuaternion(time, it, Easing.Linear.function))
|
||||
}
|
||||
}
|
||||
fun add(time: Double, value: Quaternion?, easing: EasingFunction = Easing.Linear.function) {
|
||||
|
||||
value?.let {
|
||||
keys.add(KeyQuaternion(time, it, easing))
|
||||
}
|
||||
|
||||
@@ -13,12 +13,7 @@ class KeyframerChannelVector3 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fun add(time: Double, value: Vector3?, easing: EasingFunction = Easing.Linear.function, jump: Hold = Hold.HoldNone) {
|
||||
if (jump == Hold.HoldAll || (jump == Hold.HoldSet && value != null)) {
|
||||
lastValue()?.let {
|
||||
keys.add(KeyVector3(time, it, Easing.Linear.function))
|
||||
}
|
||||
}
|
||||
fun add(time: Double, value: Vector3?, easing: EasingFunction = Easing.Linear.function) {
|
||||
value?.let {
|
||||
keys.add(KeyVector3(time, it, easing))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ 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
|
||||
import kotlin.reflect.full.memberProperties
|
||||
@@ -258,7 +257,6 @@ open class Keyframer {
|
||||
expressionContext.putAll(parameters)
|
||||
expressionContext["t"] = 0.0
|
||||
|
||||
|
||||
fun easingFunctionFromName(easingCandidate: String): EasingFunction {
|
||||
return when (easingCandidate) {
|
||||
"linear" -> Easing.Linear.function
|
||||
@@ -346,9 +344,20 @@ open class Keyframer {
|
||||
throw ExpressionException("error in $path.'easing': ${e.message ?: ""}")
|
||||
}
|
||||
|
||||
val hold = Hold.HoldNone
|
||||
val envelope = try {
|
||||
when (val candidate = computed["envelope"]) {
|
||||
null -> defaultEnvelope
|
||||
is DoubleArray -> candidate
|
||||
is List<*> -> candidate.map { it.toString().toDouble() }.toDoubleArray()
|
||||
is Array<*> -> candidate.map { it.toString().toDouble() }.toDoubleArray()
|
||||
else -> error("unknown envelope for '$candidate")
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
throw ExpressionException("error in $path.'envelope': ${e.message ?: ""}")
|
||||
}
|
||||
|
||||
val reservedKeys = setOf("time", "easing", "hold")
|
||||
|
||||
val reservedKeys = setOf("time", "easing", "envelope")
|
||||
|
||||
for (channelCandidate in computed.filter { it.key !in reservedKeys }) {
|
||||
if (channelCandidate.key in channelKeys) {
|
||||
@@ -385,6 +394,14 @@ open class Keyframer {
|
||||
else -> error("unknown easing for '$candidate'")
|
||||
}
|
||||
|
||||
val dictEnvelope = when (val candidate = valueMap["envelope"]) {
|
||||
null -> envelope
|
||||
is DoubleArray -> candidate
|
||||
is List<*> -> candidate.map { it.toString().toDouble() }.toDoubleArray()
|
||||
is Array<*> -> candidate.map { it.toString().toDouble() }.toDoubleArray()
|
||||
else -> error("unknown envelope for '$candidate")
|
||||
|
||||
}
|
||||
val dictDuration = try {
|
||||
when (val candidate = valueMap["duration"]) {
|
||||
null -> null
|
||||
@@ -400,12 +417,14 @@ open class Keyframer {
|
||||
|
||||
if (dictDuration != null) {
|
||||
if (dictDuration <= 0.0) {
|
||||
channel.add(max(lastTime, time + dictDuration), lastValue, Easing.Linear.function, hold)
|
||||
channel.add(time, value, dictEasing, hold)
|
||||
channel.add(max(lastTime, time + dictDuration), lastValue, Easing.Linear.function, defaultEnvelope)
|
||||
channel.add(time, value, dictEasing, dictEnvelope)
|
||||
} else {
|
||||
channel.add(time, lastValue, Easing.Linear.function, hold)
|
||||
channel.add(time + dictDuration, value, dictEasing, hold)
|
||||
channel.add(time, lastValue, Easing.Linear.function, defaultEnvelope)
|
||||
channel.add(time + dictDuration, value, dictEasing, dictEnvelope)
|
||||
}
|
||||
} else {
|
||||
channel.add(time, value, dictEasing, dictEnvelope)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -420,39 +439,12 @@ open class Keyframer {
|
||||
} catch (e: ExpressionException) {
|
||||
throw ExpressionException("error in $path.'${channelCandidate.key}': ${e.message ?: ""}")
|
||||
}
|
||||
channel.add(time, value, easing, hold)
|
||||
channel.add(time, value, easing, envelope)
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTime = time + duration
|
||||
expressionContext["t"] = lastTime
|
||||
|
||||
if (computed.containsKey("repeat")) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val repeatObject = computed["repeat"] as? Map<String, Any> ?: error("'repeat' should be a map")
|
||||
val count = try {
|
||||
when (val candidate = repeatObject["count"]) {
|
||||
null -> 1
|
||||
is Int -> candidate
|
||||
is Double -> candidate.toInt()
|
||||
is String -> evaluateExpression(candidate, expressionContext, functions)?.roundToInt()
|
||||
?: error("cannot evaluate expression for count: '$candidate'")
|
||||
else -> error("unknown value type for count: '$candidate")
|
||||
}
|
||||
} catch (e: ExpressionException) {
|
||||
throw ExpressionException("error in $path.repeat.'count': ${e.message ?: ""}")
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val repeatKeys = repeatObject["keys"] as? List<Map<String, Any>> ?: error("no repeat keys")
|
||||
|
||||
for (i in 0 until count) {
|
||||
expressionContext["rep"] = i.toDouble()
|
||||
for (repeatKey in repeatKeys) {
|
||||
handleKey(repeatKey, "$path.repeat")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((index, key) in keys.withIndex()) {
|
||||
|
||||
Reference in New Issue
Block a user