[orx-jvm] Move panel, gui, dnk3, keyframer, triangulation to orx-jvm

This commit is contained in:
Edwin Jakobs
2021-06-27 21:32:24 +02:00
parent 5814acef8f
commit 874d49779f
159 changed files with 22 additions and 21 deletions

View File

@@ -0,0 +1,386 @@
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.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.*
import kotlin.math.*
typealias Function0 = () -> Double
typealias Function1 = (Double) -> Double
typealias Function2 = (Double, Double) -> Double
typealias Function3 = (Double, Double, Double) -> Double
typealias Function4 = (Double, Double, Double, Double) -> Double
typealias Function5 = (Double, Double, Double, Double, Double) -> Double
class FunctionExtensions(
val functions0: Map<String, Function0> = emptyMap(),
val functions1: Map<String, Function1> = emptyMap(),
val functions2: Map<String, Function2> = emptyMap(),
val functions3: Map<String, Function3> = emptyMap(),
val functions4: Map<String, Function4> = emptyMap(),
val functions5: Map<String, Function5> = emptyMap()
) {
companion object {
val EMPTY = FunctionExtensions()
}
}
internal enum class IDType {
VARIABLE,
FUNCTION0,
FUNCTION1,
FUNCTION2,
FUNCTION3,
FUNCTION4,
FUNCTION5
}
internal class ExpressionListener(val functions: FunctionExtensions = FunctionExtensions.EMPTY) :
KeyLangParserBaseListener() {
val doubleStack = Stack<Double>()
val functionStack = Stack<(DoubleArray) -> Double>()
val variables = mutableMapOf<String, Double>()
val idTypeStack = Stack<IDType>()
var lastExpressionResult: Double? = null
val exceptionStack = Stack<ExpressionException>()
override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) {
ifError {
throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}")
}
val result = doubleStack.pop()
lastExpressionResult = result
}
override fun exitAssignment(ctx: KeyLangParser.AssignmentContext) {
val value = doubleStack.pop()
variables[ctx.ID()?.text ?: error("buh")] = value
}
override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) {
val op = doubleStack.pop()
doubleStack.push(-op)
}
override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) {
ifError {
pushError(it.message ?: "")
return
}
val right = doubleStack.pop()
val left = doubleStack.pop()
val result = when (val operator = ctx.operator?.type) {
KeyLangParser.PLUS -> left + right
KeyLangParser.MINUS -> left - right
KeyLangParser.ASTERISK -> left * right
KeyLangParser.DIVISION -> left / right
KeyLangParser.PERCENTAGE -> mod(left, right)
else -> error("operator '$operator' not implemented")
}
doubleStack.push(result)
}
override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) {
ifError {
pushError(it.message ?: "")
return
}
val left = doubleStack.pop()
val right = doubleStack.pop()
val result = when (val operator = ctx.operator?.type) {
KeyLangParser.PLUS -> left + right
KeyLangParser.MINUS -> right - left
KeyLangParser.ASTERISK -> left * right
KeyLangParser.DIVISION -> left / right
else -> error("operator '$operator' not implemented")
}
doubleStack.push(result)
}
override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) {
idTypeStack.push(IDType.VARIABLE)
}
override fun enterFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) {
idTypeStack.push(IDType.FUNCTION0)
}
override fun exitFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val result = function.invoke(doubleArrayOf())
doubleStack.push(result)
}
override fun enterFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) {
idTypeStack.push(IDType.FUNCTION1)
}
override fun exitFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val argument = doubleStack.pop()
val result = function.invoke(doubleArrayOf(argument))
doubleStack.push(result)
}
override fun enterFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) {
idTypeStack.push(IDType.FUNCTION2)
}
override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val argument1 = doubleStack.pop()
val argument0 = doubleStack.pop()
val result = function.invoke(doubleArrayOf(argument0, argument1))
doubleStack.push(result)
}
override fun enterFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) {
idTypeStack.push(IDType.FUNCTION3)
}
override fun exitFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val argument2 = doubleStack.pop()
val argument1 = doubleStack.pop()
val argument0 = doubleStack.pop()
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2))
doubleStack.push(result)
}
override fun enterFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) {
idTypeStack.push(IDType.FUNCTION4)
}
override fun exitFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val argument3 = doubleStack.pop()
val argument2 = doubleStack.pop()
val argument1 = doubleStack.pop()
val argument0 = doubleStack.pop()
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2, argument3))
doubleStack.push(result)
}
override fun enterFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) {
idTypeStack.push(IDType.FUNCTION5)
}
override fun exitFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) {
ifError {
pushError(it.message ?: "")
return
}
val function = functionStack.pop()
val argument4 = doubleStack.pop()
val argument3 = doubleStack.pop()
val argument2 = doubleStack.pop()
val argument1 = doubleStack.pop()
val argument0 = doubleStack.pop()
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2, argument3, argument4))
doubleStack.push(result)
}
private fun <T> errorValue(message: String, value: T): T {
pushError(message)
return value
}
private fun pushError(message: String) {
exceptionStack.push(ExpressionException(message))
}
private inline fun ifError(f: (e: Throwable) -> Unit) {
if (exceptionStack.isNotEmpty()) {
val e = exceptionStack.pop()
f(e)
}
}
override fun visitTerminal(node: TerminalNode) {
val type = node.symbol?.type
if (type == KeyLangParser.INTLIT) {
doubleStack.push(node.text.toDouble())
}
if (type == KeyLangParser.DECLIT) {
doubleStack.push(node.text.toDouble())
}
if (type == KeyLangParser.ID) {
@Suppress("DIVISION_BY_ZERO")
when (val idType = idTypeStack.pop()) {
IDType.VARIABLE -> doubleStack.push(
when (val name = node.text) {
"PI" -> PI
else -> variables[name] ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0)
}
)
IDType.FUNCTION0 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
"random" -> { _ -> Double.uniform(0.0, 1.0) }
else -> functions.functions0[candidate]?.let { { _: DoubleArray -> it.invoke() } }
?: errorValue(
"unresolved function: '${candidate}()'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
IDType.FUNCTION1 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
"sqrt" -> { x -> sqrt(x[0]) }
"radians" -> { x -> Math.toRadians(x[0]) }
"degrees" -> { x -> Math.toDegrees(x[0]) }
"cos" -> { x -> cos(x[0]) }
"sin" -> { x -> sin(x[0]) }
"tan" -> { x -> tan(x[0]) }
"atan" -> { x -> atan(x[0]) }
"acos" -> { x -> acos(x[0]) }
"asin" -> { x -> asin(x[0]) }
"exp" -> { x -> exp(x[0]) }
"abs" -> { x -> abs(x[0]) }
"floor" -> { x -> floor(x[0]) }
"ceil" -> { x -> ceil(x[0]) }
"saturate" -> { x -> x[0].coerceIn(0.0, 1.0) }
else -> functions.functions1[candidate]?.let { { x: DoubleArray -> it.invoke(x[0]) } }
?: errorValue(
"unresolved function: '${candidate}(x0)'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
IDType.FUNCTION2 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
"max" -> { x -> max(x[0], x[1]) }
"min" -> { x -> min(x[0], x[1]) }
"pow" -> { x -> x[0].pow(x[1]) }
"atan2" -> { x -> atan2(x[0], x[1]) }
"random" -> { x -> Double.uniform(x[0], x[1]) }
"length" -> { x -> Vector2(x[0], x[1]).length }
else -> functions.functions2[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1]) } }
?: errorValue(
"unresolved function: '${candidate}(x0, x1)'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
IDType.FUNCTION3 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
"mix" -> { x -> mix(x[0], x[1], x[2]) }
"smoothstep" -> { x -> smoothstep(x[0], x[1], x[2]) }
"length" -> { x -> Vector3(x[0], x[1], x[2]).length }
else -> functions.functions3[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2]) } }
?: errorValue(
"unresolved function: '${candidate}(x0, x1, x2)'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
IDType.FUNCTION4 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
else -> functions.functions4[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3]) } }
?: errorValue(
"unresolved function: '${candidate}(x0, x1, x2, x3)'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
IDType.FUNCTION5 -> {
val function: (DoubleArray) -> Double =
when (val candidate = node.text) {
"map" -> { x -> map(x[0], x[1], x[2], x[3], x[4]) }
else -> functions.functions5[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3], x[4]) } }
?: errorValue(
"unresolved function: '${candidate}(x0, x1, x2, x3, x4)'"
) { _ -> error("this is the error function") }
}
functionStack.push(function)
}
else -> error("unsupported id-type $idType")
}
}
}
}
class ExpressionException(message: String) : RuntimeException(message)
fun evaluateExpression(
input: String,
variables: Map<String, Double> = emptyMap(),
functions: FunctionExtensions = FunctionExtensions.EMPTY
): Double? {
val lexer = KeyLangLexer(CharStreams.fromString(input))
val parser = KeyLangParser(CommonTokenStream(lexer))
parser.removeErrorListeners()
parser.addErrorListener(object : BaseErrorListener() {
override fun syntaxError(
recognizer: Recognizer<*, *>?,
offendingSymbol: Any?,
line: Int,
charPositionInLine: Int,
msg: String?,
e: RecognitionException?
) {
throw ExpressionException("parser error in expression: '$input'; [line: $line, character: $charPositionInLine ${offendingSymbol?.let { ", near: $it" } ?: ""} ]")
}
})
val root = parser.miniCalcFile()
val listener = ExpressionListener(functions)
listener.variables.putAll(variables)
try {
ParseTreeWalker.DEFAULT.walk(listener, root)
} catch (e: ExpressionException) {
throw ExpressionException(e.message ?: "")
}
return listener.lastExpressionResult
}

View File

@@ -0,0 +1,75 @@
package org.openrndr.extra.keyframer
import org.openrndr.extras.easing.Easing
import org.openrndr.extras.easing.EasingFunction
import org.openrndr.math.map
internal val defaultEnvelope = doubleArrayOf(0.0, 1.0)
class Key(val time: Double, val value: Double, val easing: EasingFunction, val envelope: DoubleArray = defaultEnvelope)
class KeyframerChannel {
val keys = mutableListOf<Key>()
operator fun invoke() : Double {
return 0.0
}
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, envelope))
}
}
fun lastValue(): Double? {
return keys.lastOrNull()?.value
}
fun lastTime(): Double? {
return keys.lastOrNull()?.time
}
fun duration(): Double {
return keys.last().time
}
fun value(time: Double): Double? {
if (keys.size == 0) {
return null
}
if (keys.size == 1) {
return if (time < keys.first().time) {
null
} else {
keys[0].value
}
}
if (time < keys.first().time) {
return null
}
val rightIndex = keys.indexOfFirst { it.time > time }
return if (rightIndex == -1) {
keys.last().value
} else {
val leftIndex = (rightIndex - 1).coerceAtLeast(0)
val rightKey = keys[rightIndex]
val leftKey = keys[leftIndex]
val t0 = (time - leftKey.time) / (rightKey.time - leftKey.time)
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)
}
}
}

View File

@@ -0,0 +1,60 @@
package org.openrndr.extra.keyframer
import org.openrndr.extras.easing.Easing
import org.openrndr.extras.easing.EasingFunction
import org.openrndr.math.Quaternion
import org.openrndr.math.slerp
class KeyQuaternion(val time: Double, val value: Quaternion, val easing: EasingFunction)
class KeyframerChannelQuaternion {
val keys = mutableListOf<KeyQuaternion>()
operator fun invoke() : Double {
return 0.0
}
fun add(time: Double, value: Quaternion?, easing: EasingFunction = Easing.Linear.function) {
value?.let {
keys.add(KeyQuaternion(time, it, easing))
}
}
fun lastValue(): Quaternion? {
return keys.lastOrNull()?.value
}
fun duration(): Double {
return keys.last().time
}
fun value(time: Double): Quaternion? {
if (keys.size == 0) {
return null
}
if (keys.size == 1) {
return if (time < keys.first().time) {
keys[0].value.normalized
} else {
keys[0].value.normalized
}
}
if (time < keys.first().time) {
return null
}
val rightIndex = keys.indexOfFirst { it.time > time }
return if (rightIndex == -1) {
keys.last().value.normalized
} else {
val leftIndex = (rightIndex - 1).coerceAtLeast(0)
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)
slerp(leftKey.value, rightKey.value, e0).normalized
}
}
}

View File

@@ -0,0 +1,58 @@
package org.openrndr.extra.keyframer
import org.openrndr.extras.easing.Easing
import org.openrndr.extras.easing.EasingFunction
import org.openrndr.math.Vector3
class KeyVector3(val time: Double, val value: Vector3, val easing: EasingFunction)
class KeyframerChannelVector3 {
val keys = mutableListOf<KeyVector3>()
operator fun invoke() : Double {
return 0.0
}
fun add(time: Double, value: Vector3?, easing: EasingFunction = Easing.Linear.function) {
value?.let {
keys.add(KeyVector3(time, it, easing))
}
}
fun lastValue(): Vector3? {
return keys.lastOrNull()?.value
}
fun duration(): Double {
return keys.last().time
}
fun value(time: Double): Vector3? {
if (keys.size == 0) {
return null
}
if (keys.size == 1) {
return if (time < keys.first().time) {
null
} else {
keys[0].value
}
}
if (time < keys.first().time) {
return null
}
val rightIndex = keys.indexOfFirst { it.time > time }
return if (rightIndex == -1) {
keys.last().value
} else {
val leftIndex = (rightIndex - 1).coerceAtLeast(0)
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)
leftKey.value * (1.0 - e0) + rightKey.value * (e0)
}
}
}

View File

@@ -0,0 +1,454 @@
package org.openrndr.extra.keyframer
import com.google.gson.Gson
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
import java.io.File
import java.lang.IllegalStateException
import java.lang.NullPointerException
import java.net.URL
import kotlin.math.max
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
enum class KeyframerFormat {
SIMPLE,
FULL
}
open class Keyframer {
private var currentTime = 0.0
operator fun invoke(time: Double) {
currentTime = time
}
open inner class CompoundChannel(val keys: Array<String>, private val defaultValues: Array<Double>) {
private var channelTimes: Array<Double> = Array(keys.size) { Double.NEGATIVE_INFINITY }
private var compoundChannels: Array<KeyframerChannel?> = Array(keys.size) { null }
private var cachedValues: Array<Double?> = Array(keys.size) { null }
open fun reset() {
for (i in channelTimes.indices) {
channelTimes[i] = Double.NEGATIVE_INFINITY
}
}
fun getValue(compound: Int): Double {
if (compoundChannels[compound] == null) {
compoundChannels[compound] = channels[keys[compound]]
}
return if (compoundChannels[compound] != null) {
if (channelTimes[compound] == currentTime && cachedValues[compound] != null) {
cachedValues[compound] ?: defaultValues[compound]
} else {
val value = compoundChannels[compound]?.value(currentTime) ?: defaultValues[compound]
cachedValues[compound] = value
value
}
} else {
defaultValues[compound]
}
}
}
val duration: Double
get() = channels.values.maxByOrNull { it.duration() }?.duration() ?: 0.0
inner class DoubleChannel(key: String, defaultValue: Double = 0.0) :
CompoundChannel(arrayOf(key), arrayOf(defaultValue)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): Double = getValue(0)
}
inner class Vector2Channel(keys: Array<String>, defaultValue: Vector2 = Vector2.ZERO) :
CompoundChannel(keys, arrayOf(defaultValue.x, defaultValue.y)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): Vector2 = Vector2(getValue(0), getValue(1))
}
inner class Vector3Channel(keys: Array<String>, defaultValue: Vector3 = Vector3.ZERO) :
CompoundChannel(keys, arrayOf(defaultValue.x, defaultValue.y, defaultValue.z)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): Vector3 =
Vector3(getValue(0), getValue(1), getValue(2))
}
inner class Vector4Channel(keys: Array<String>, defaultValue: Vector4 = Vector4.ZERO) :
CompoundChannel(keys, arrayOf(defaultValue.x, defaultValue.y, defaultValue.z, defaultValue.w)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): Vector4 =
Vector4(getValue(0), getValue(1), getValue(2), getValue(3))
}
inner class RGBaChannel(keys: Array<String>, defaultValue: ColorRGBa = ColorRGBa.WHITE) :
CompoundChannel(keys, arrayOf(defaultValue.r, defaultValue.g, defaultValue.b, defaultValue.a)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): ColorRGBa =
ColorRGBa(getValue(0), getValue(1), getValue(2), getValue(3))
}
inner class RGBChannel(keys: Array<String>, defaultValue: ColorRGBa = ColorRGBa.WHITE) :
CompoundChannel(keys, arrayOf(defaultValue.r, defaultValue.g, defaultValue.b)) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): ColorRGBa =
ColorRGBa(getValue(0), getValue(1), getValue(2))
}
inner class DoubleArrayChannel(keys: Array<String>, defaultValue: DoubleArray = DoubleArray(keys.size)) :
CompoundChannel(keys, defaultValue.toTypedArray()) {
operator fun getValue(keyframer: Keyframer, property: KProperty<*>): DoubleArray {
val result = DoubleArray(keys.size)
for (i in keys.indices) {
result[i] = getValue(i)
}
return result
}
}
val channels = mutableMapOf<String, KeyframerChannel>()
fun loadFromJson(
file: File,
format: KeyframerFormat = KeyframerFormat.SIMPLE,
parameters: Map<String, Double> = emptyMap(),
functions: FunctionExtensions = FunctionExtensions.EMPTY
) {
require(file.exists()) {
"failed to load keyframer from json: '${file.absolutePath}' does not exist."
}
try {
loadFromJsonString(file.readText(), format, parameters, functions)
} catch (e: ExpressionException) {
throw ExpressionException("Error loading from '${file.path}': ${e.message ?: ""}")
}
}
fun loadFromJson(
url: URL,
format: KeyframerFormat = KeyframerFormat.SIMPLE,
parameters: Map<String, Double> = emptyMap(),
functions: FunctionExtensions = FunctionExtensions.EMPTY
) {
try {
loadFromJsonString(url.readText(), format, parameters, functions)
} catch (e: ExpressionException) {
throw ExpressionException("Error loading $format from '${url}': ${e.message ?: ""}")
} catch (e: IllegalStateException) {
throw ExpressionException("Error loading $format from '${url}': ${e.message ?: ""}")
}
}
fun loadFromJsonString(
json: String,
format: KeyframerFormat = KeyframerFormat.SIMPLE,
parameters: Map<String, Double> = emptyMap(),
functions: FunctionExtensions = FunctionExtensions.EMPTY
) {
when (format) {
KeyframerFormat.SIMPLE -> {
try {
val type = object : TypeToken<List<Map<String, Any>>>() {}.type
val keys: List<MutableMap<String, Any>> = Gson().fromJson(json, type)
loadFromKeyObjects(keys, parameters, functions)
} catch (e: JsonSyntaxException) {
error("Error parsing simple Keyframer data: ${e.cause?.message}")
} catch (e: NullPointerException) {
error("Error parsing simple Keyframer data: ${e.cause?.message}")
}
}
KeyframerFormat.FULL -> {
try {
val type = object : TypeToken<Map<String, Any>>() {}.type
val keys: Map<String, Any> = Gson().fromJson(json, type)
loadFromObjects(keys, parameters, functions)
} catch (e: JsonSyntaxException) {
error("Error parsing full Keyframer data: ${e.cause?.message}")
}
}
}
}
private val parameters = mutableMapOf<String, Double>()
private val prototypes = mutableMapOf<String, Map<String, Any>>()
fun loadFromObjects(
dict: Map<String, Any>,
externalParameters: Map<String, Double> = emptyMap(),
functions: FunctionExtensions = FunctionExtensions.EMPTY
) {
this.parameters.clear()
this.parameters.putAll(externalParameters)
prototypes.clear()
@Suppress("UNCHECKED_CAST")
(dict["parameters"] as? Map<String, Any>)?.let { lp ->
for (entry in lp) {
this.parameters[entry.key] = try {
when (val candidate = entry.value) {
is Double -> candidate
is String -> evaluateExpression(candidate, parameters, functions)
?: error("could not evaluate expression: '$candidate'")
is Int -> candidate.toDouble()
is Float -> candidate.toDouble()
else -> error("unknown type for parameter '${entry.key}'")
}
} catch (e: ExpressionException) {
throw ExpressionException("error in 'parameters': ${e.message ?: ""} ")
}
}
}
this.parameters.putAll(externalParameters)
@Suppress("UNCHECKED_CAST")
(dict["prototypes"] as? Map<String, Map<String, Any>>)?.let {
prototypes.putAll(it)
}
@Suppress("UNCHECKED_CAST")
(dict["keys"] as? List<Map<String, Any>>)?.let { keys ->
loadFromKeyObjects(keys, parameters, functions)
}
}
private fun resolvePrototype(prototypeNames: String): Map<String, Any> {
val prototypeTokens = prototypeNames.split(" ").map { it.trim() }.filter { it.isNotBlank() }
val prototypeRefs = prototypeTokens.mapNotNull { prototypes[it] }
val computed = mutableMapOf<String, Any>()
for (ref in prototypeRefs) {
computed.putAll(ref)
}
return computed
}
fun loadFromKeyObjects(
keys: List<Map<String, Any>>,
externalParameters: Map<String, Double>,
functions: FunctionExtensions
) {
if (externalParameters !== parameters) {
parameters.clear()
parameters.putAll(externalParameters)
}
var lastTime = 0.0
val channelDelegates = this::class.memberProperties
.mapNotNull {
@Suppress("UNCHECKED_CAST")
it as? KProperty1<Keyframer, Any>
}
.filter { it.isAccessible = true; it.getDelegate(this) is CompoundChannel }
.associate { Pair(it.name, it.getDelegate(this) as CompoundChannel) }
val channelKeys = channelDelegates.values.flatMap { channel ->
channel.keys.map { it }
}.toSet()
for (delegate in channelDelegates.values) {
delegate.reset()
}
val expressionContext = mutableMapOf<String, Double>()
expressionContext.putAll(parameters)
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<String, Any>, path: String) {
val prototype = (key["prototypes"] as? String)?.let {
resolvePrototype(it)
} ?: emptyMap()
val computed = mutableMapOf<String, Any>()
computed.putAll(prototype)
computed.putAll(key)
val time = try {
when (val candidate = computed["time"]) {
null -> lastTime
is String -> evaluateExpression(candidate, expressionContext, functions)
?: error { "unknown value format for time : $candidate" }
is Double -> candidate
is Int -> candidate.toDouble()
is Float -> candidate.toDouble()
else -> error("unknown time format for '$candidate'")
}
} catch (e: ExpressionException) {
throw ExpressionException("error in $path.'time': ${e.message ?: ""}")
}
val duration = try {
when (val candidate = computed["duration"]) {
null -> 0.0
is String -> evaluateExpression(candidate, expressionContext, functions)
?: error { "unknown value format for time : $candidate" }
is Int -> candidate.toDouble()
is Float -> candidate.toDouble()
is Double -> candidate
else -> error("unknown duration type for '$candidate")
}
} catch (e: ExpressionException) {
throw ExpressionException("error in $path.'duration': ${e.message ?: ""}")
}
val easing = try {
when (val easingCandidate = computed["easing"]) {
null -> Easing.Linear.function
is String -> easingFunctionFromName(easingCandidate)
else -> error("unknown easing for '$easingCandidate'")
}
} catch (e: IllegalStateException) {
throw ExpressionException("error in $path.'easing': ${e.message ?: ""}")
}
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", "envelope")
for (channelCandidate in computed.filter { it.key !in reservedKeys }) {
if (channelCandidate.key in channelKeys) {
val channel = channels.getOrPut(channelCandidate.key) {
KeyframerChannel()
}
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<String, Any>
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 ?: ""}")
}
val dictEasing = when (val candidate = valueMap["easing"]) {
null -> easing
is String -> easingFunctionFromName(candidate)
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
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) {
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, defaultEnvelope)
channel.add(time + dictDuration, value, dictEasing, dictEnvelope)
}
} else {
channel.add(time, value, dictEasing, dictEnvelope)
}
} 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, envelope)
}
}
}
lastTime = time + duration
expressionContext["t"] = lastTime
}
for ((index, key) in keys.withIndex()) {
handleKey(key, "keys[$index]")
}
}
}