From 597f6222beccab44fa8518af1fe917a30da3be5e Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Thu, 14 Mar 2024 19:36:54 +0100 Subject: [PATCH] [orx-fcurve] Add polar and spherical compound curves, refactor, add tests --- .../src/commonMain/kotlin/CompoundFCurve.kt | 113 ++++++++++++++++++ .../src/commonMain/kotlin/MultiFCurve.kt | 105 +--------------- .../kotlin/MultiFCurveExtensions.kt | 34 ++++++ .../src/commonTest/kotlin/TestEFCurve.kt | 33 +++++ .../src/jvmDemo/kotlin/DemoMultiFCurve01.kt | 1 + 5 files changed, 187 insertions(+), 99 deletions(-) create mode 100644 orx-fcurve/src/commonMain/kotlin/CompoundFCurve.kt create mode 100644 orx-fcurve/src/commonMain/kotlin/MultiFCurveExtensions.kt create mode 100644 orx-fcurve/src/commonTest/kotlin/TestEFCurve.kt diff --git a/orx-fcurve/src/commonMain/kotlin/CompoundFCurve.kt b/orx-fcurve/src/commonMain/kotlin/CompoundFCurve.kt new file mode 100644 index 00000000..e7be4821 --- /dev/null +++ b/orx-fcurve/src/commonMain/kotlin/CompoundFCurve.kt @@ -0,0 +1,113 @@ +package org.openrndr.extra.fcurve + +import org.openrndr.color.ColorRGBa +import org.openrndr.math.* + +abstract class CompoundFCurve(val compounds: List) { + val duration: Double + get() { + return compounds.maxOf { it?.duration ?: 0.0 } + } + + abstract fun sampler(normalized: Boolean = false): (Double) -> T +} + +class BooleanFCurve(value: FCurve?, val default: Boolean = true) : + CompoundFCurve(listOf(value)) { + override fun sampler(normalized: Boolean): (Double) -> Boolean { + val sampler = compounds[0]?.sampler(normalized) ?: { if (default) 1.0 else 0.0 } + return { t -> sampler(t) >= 1.0 } + } +} + +class DoubleFCurve(value: FCurve?, val default: Double = 0.0) : + CompoundFCurve(listOf(value)) { + override fun sampler(normalized: Boolean): (Double) -> Double { + val sampler = compounds[0]?.sampler(normalized) ?: { default } + return { t -> sampler(t) } + } +} + +class IntFCurve(value: FCurve?, val default: Int = 0) : + CompoundFCurve(listOf(value)) { + override fun sampler(normalized: Boolean): (Double) -> Int { + val sampler = compounds[0]?.sampler(normalized) ?: { default.toDouble() } + return { t -> sampler(t).toInt() } + } +} + +class Vector2FCurve(x: FCurve?, y: FCurve?, val default: Vector2 = Vector2.ZERO) : + CompoundFCurve(listOf(x, y)) { + override fun sampler(normalized: Boolean): (Double) -> Vector2 { + val xSampler = compounds[0]?.sampler(normalized) ?: { default.x } + val ySampler = compounds[1]?.sampler(normalized) ?: { default.y } + return { t -> Vector2(xSampler(t), ySampler(t)) } + } +} + +class Vector3FCurve(x: FCurve?, y: FCurve?, z: FCurve?, val default: Vector3 = Vector3.ZERO) : + CompoundFCurve(listOf(x, y, z)) { + override fun sampler(normalized: Boolean): (Double) -> Vector3 { + val xSampler = compounds[0]?.sampler(normalized) ?: { default.x } + val ySampler = compounds[1]?.sampler(normalized) ?: { default.y } + val zSampler = compounds[2]?.sampler(normalized) ?: { default.z } + return { t -> Vector3(xSampler(t), ySampler(t), zSampler(t)) } + } +} + +class Vector4FCurve(x: FCurve?, y: FCurve?, z: FCurve?, w: FCurve?, val default: Vector4 = Vector4.ZERO) : + CompoundFCurve(listOf(x, y, z, w)) { + override fun sampler(normalized: Boolean): (Double) -> Vector4 { + val xSampler = compounds[0]?.sampler(normalized) ?: { default.x } + val ySampler = compounds[1]?.sampler(normalized) ?: { default.y } + val zSampler = compounds[2]?.sampler(normalized) ?: { default.z } + val wSampler = compounds[3]?.sampler(normalized) ?: { default.w } + return { t -> Vector4(xSampler(t), ySampler(t), zSampler(t), wSampler(t)) } + } +} + +class RgbFCurve(r: FCurve?, g: FCurve?, b: FCurve?, val default: ColorRGBa = ColorRGBa.WHITE) : + CompoundFCurve(listOf(r, g, b)) { + override fun sampler(normalized: Boolean): (Double) -> ColorRGBa { + val rSampler = compounds[0]?.sampler(normalized) ?: { default.r } + val gSampler = compounds[1]?.sampler(normalized) ?: { default.g } + val bSampler = compounds[2]?.sampler(normalized) ?: { default.b } + return { t -> ColorRGBa(rSampler(t), gSampler(t), bSampler(t)) } + } +} + +class RgbaFCurve(r: FCurve?, g: FCurve?, b: FCurve?, a: FCurve?, val default: ColorRGBa = ColorRGBa.WHITE) : + CompoundFCurve(listOf(r, g, b, a)) { + override fun sampler(normalized: Boolean): (Double) -> ColorRGBa { + val rSampler = compounds[0]?.sampler(normalized) ?: { default.r } + val gSampler = compounds[1]?.sampler(normalized) ?: { default.g } + val bSampler = compounds[2]?.sampler(normalized) ?: { default.b } + val aSampler = compounds[3]?.sampler(normalized) ?: { default.alpha } + return { t -> ColorRGBa(rSampler(t), gSampler(t), bSampler(t), aSampler(t)) } + } +} + +class PolarFCurve(angleInDegrees: FCurve?, radius: FCurve?, val default: Polar = Polar(0.0, 1.0)) : + CompoundFCurve(listOf(angleInDegrees, radius)) { + override fun sampler(normalized: Boolean): (Double) -> Polar { + val angleSampler = compounds[0]?.sampler(normalized) ?: { default.theta } + val radiusSampler = compounds[1]?.sampler(normalized) ?: { default.radius } + return { t -> Polar(angleSampler(t), radiusSampler(t)) } + } +} + +class SphericalFCurve( + thetaInDegrees: FCurve?, + phiInDegrees: FCurve?, + radius: FCurve?, + val default: Spherical = Spherical(0.0, 1.0, 1.0) +) : + CompoundFCurve(listOf(thetaInDegrees, phiInDegrees, radius)) { + override fun sampler(normalized: Boolean): (Double) -> Spherical { + val thetaSampler = compounds[0]?.sampler(normalized) ?: { default.theta } + val phiSampler = compounds[0]?.sampler(normalized) ?: { default.theta } + val radiusSampler = compounds[2]?.sampler(normalized) ?: { default.radius } + return { t -> Spherical(thetaSampler(t), phiSampler(t), radiusSampler(t)) } + } +} + diff --git a/orx-fcurve/src/commonMain/kotlin/MultiFCurve.kt b/orx-fcurve/src/commonMain/kotlin/MultiFCurve.kt index 3d44b114..f801a39b 100644 --- a/orx-fcurve/src/commonMain/kotlin/MultiFCurve.kt +++ b/orx-fcurve/src/commonMain/kotlin/MultiFCurve.kt @@ -1,113 +1,20 @@ package org.openrndr.extra.fcurve -import org.openrndr.color.ColorRGBa -import org.openrndr.math.Vector2 -import org.openrndr.math.Vector3 - -abstract class CompoundFCurve(val compounds: List) { - val duration: Double - get() { - return compounds.maxOf { it?.duration ?: 0.0 } - } - - abstract fun sampler(normalized: Boolean = false): (Double) -> T -} - - -class BooleanFCurve(value: FCurve?, val default: Boolean = true) : - CompoundFCurve(listOf(value)) { - override fun sampler(normalized: Boolean): (Double) -> Boolean { - val sampler = compounds[0]?.sampler(normalized) ?: { if (default) 1.0 else 0.0 } - return { t -> sampler(t) >= 1.0 } - } -} - - -class DoubleFCurve(value: FCurve?, val default: Double = 0.0) : - CompoundFCurve(listOf(value)) { - override fun sampler(normalized: Boolean): (Double) -> Double { - val sampler = compounds[0]?.sampler(normalized) ?: { default } - return { t -> sampler(t) } - } -} - - -class IntFCurve(value: FCurve?, val default: Int = 0) : - CompoundFCurve(listOf(value)) { - override fun sampler(normalized: Boolean): (Double) -> Int { - val sampler = compounds[0]?.sampler(normalized) ?: { default.toDouble() } - return { t -> sampler(t).toInt() } - } -} - -class Vector2FCurve(x: FCurve?, y: FCurve?, val default: Vector2 = Vector2.ZERO) : - CompoundFCurve(listOf(x, y)) { - override fun sampler(normalized: Boolean): (Double) -> Vector2 { - val xSampler = compounds[0]?.sampler(normalized) ?: { default.x } - val ySampler = compounds[1]?.sampler(normalized) ?: { default.y } - return { t -> Vector2(xSampler(t), ySampler(t)) } - } -} - -class Vector3FCurve(x: FCurve?, y: FCurve?, z: FCurve?, val default: Vector3 = Vector3.ZERO) : - CompoundFCurve(listOf(x, y, z)) { - override fun sampler(normalized: Boolean): (Double) -> Vector3 { - val xSampler = compounds[0]?.sampler(normalized) ?: { default.x } - val ySampler = compounds[1]?.sampler(normalized) ?: { default.y } - val zSampler = compounds[2]?.sampler(normalized) ?: { default.z } - return { t -> Vector3(xSampler(t), ySampler(t), zSampler(t)) } - } -} - -class RgbFCurve(r: FCurve?, g: FCurve?, b: FCurve?, val default: ColorRGBa = ColorRGBa.WHITE) : - CompoundFCurve(listOf(r, g, b)) { - override fun sampler(normalized: Boolean): (Double) -> ColorRGBa { - val rSampler = compounds[0]?.sampler(normalized) ?: { default.r } - val gSampler = compounds[1]?.sampler(normalized) ?: { default.g } - val bSampler = compounds[2]?.sampler(normalized) ?: { default.b } - return { t -> ColorRGBa(rSampler(t), gSampler(t), bSampler(t)) } - } -} - -class RgbaFCurve(r: FCurve?, g: FCurve?, b: FCurve?, a: FCurve?, val default: ColorRGBa = ColorRGBa.WHITE) : - CompoundFCurve(listOf(r, g, b, a)) { - override fun sampler(normalized: Boolean): (Double) -> ColorRGBa { - val rSampler = compounds[0]?.sampler(normalized) ?: { default.r } - val gSampler = compounds[1]?.sampler(normalized) ?: { default.g } - val bSampler = compounds[2]?.sampler(normalized) ?: { default.b } - val aSampler = compounds[3]?.sampler(normalized) ?: { default.alpha } - return { t -> ColorRGBa(rSampler(t), gSampler(t), bSampler(t), aSampler(t)) } - } - -} - open class MultiFCurve(val compounds: Map) { fun changeSpeed(speed: Double): MultiFCurve { - if (speed == 1.0) { - return this + return if (speed == 1.0) { + this } else { - return MultiFCurve(compounds.mapValues { it.value?.changeSpeed(speed) }) + MultiFCurve(compounds.mapValues { it.value?.changeSpeed(speed) }) } } + /** + * Duration of the [MultiFCurve] + */ val duration by lazy { compounds.values.maxOfOrNull { it?.duration ?: 0.0 } ?: 0.0 } operator fun get(name: String): FCurve? { return compounds[name] } - - fun boolean(value: String, default: Boolean = true) = BooleanFCurve(this[value], default) - fun double(value: String, default: Double = 0.0) = DoubleFCurve(this[value], default) - - fun int(value: String, default: Int = 0) = IntFCurve(this[value], default) - - fun vector2(x: String, y: String, default: Vector2 = Vector2.ZERO) = Vector2FCurve(this[x], this[y], default) - fun vector3(x: String, y: String, z: String, default: Vector3 = Vector3.ZERO) = - Vector3FCurve(this[x], this[y], this[z], default) - - fun rgb(r: String, g: String, b: String, default: ColorRGBa = ColorRGBa.WHITE) = - RgbFCurve(this[r], this[g], this[b], default) - - fun rgba(r: String, g: String, b: String, a: String, default: ColorRGBa = ColorRGBa.WHITE) = - RgbaFCurve(this[r], this[g], this[b], this[a], default) } diff --git a/orx-fcurve/src/commonMain/kotlin/MultiFCurveExtensions.kt b/orx-fcurve/src/commonMain/kotlin/MultiFCurveExtensions.kt new file mode 100644 index 00000000..545684c8 --- /dev/null +++ b/orx-fcurve/src/commonMain/kotlin/MultiFCurveExtensions.kt @@ -0,0 +1,34 @@ +package org.openrndr.extra.fcurve + +import org.openrndr.color.ColorRGBa +import org.openrndr.math.* + +fun MultiFCurve.boolean(value: String, default: Boolean = true) = BooleanFCurve(this[value], default) +fun MultiFCurve.double(value: String, default: Double = 0.0) = DoubleFCurve(this[value], default) + +fun MultiFCurve.int(value: String, default: Int = 0) = IntFCurve(this[value], default) + +fun MultiFCurve.vector2(x: String, y: String, default: Vector2 = Vector2.ZERO) = + Vector2FCurve(this[x], this[y], default) + +fun MultiFCurve.vector3(x: String, y: String, z: String, default: Vector3 = Vector3.ZERO) = + Vector3FCurve(this[x], this[y], this[z], default) + +fun MultiFCurve.vector4(x: String, y: String, z: String, w: String, default: Vector4 = Vector4.ZERO) = + Vector4FCurve(this[x], this[y], this[z], this[w], default) + +fun MultiFCurve.rgb(r: String, g: String, b: String, default: ColorRGBa = ColorRGBa.WHITE) = + RgbFCurve(this[r], this[g], this[b], default) + +fun MultiFCurve.rgba(r: String, g: String, b: String, a: String, default: ColorRGBa = ColorRGBa.WHITE) = + RgbaFCurve(this[r], this[g], this[b], this[a], default) + +fun MultiFCurve.polar(angleInDegrees: String, radius: String, default: Polar = Polar(0.0, 1.0)) = + PolarFCurve(this[angleInDegrees], this[radius], default) + +fun MultiFCurve.spherical( + thetaInDegrees: String, + phiInDegrees: String, + radius: String, + default: Spherical = Spherical(0.0, 0.0, 1.0) +) = SphericalFCurve(this[thetaInDegrees], this[phiInDegrees], this[radius], default) \ No newline at end of file diff --git a/orx-fcurve/src/commonTest/kotlin/TestEFCurve.kt b/orx-fcurve/src/commonTest/kotlin/TestEFCurve.kt new file mode 100644 index 00000000..6efe9602 --- /dev/null +++ b/orx-fcurve/src/commonTest/kotlin/TestEFCurve.kt @@ -0,0 +1,33 @@ +import org.openrndr.extra.fcurve.efcurve +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestEFCurve { + @Test + fun comments() { + val text = """M1 |h5 m3|{ + |10.3 # toch wel handig zo'n comment + |11.2 + |14.5 + |} + """.trimMargin() + assertEquals("M1 h5 m3 h5 m3 h5 m3", efcurve(text)) + } + + @Test + fun expressions() { + assertEquals("M9.0", efcurve("M_4 + 5_")) + } + + @Test + fun listExpansion() { + assertEquals("M0 L1.0, 3.0 L1.0, 6.0", efcurve("M0 |L1.0, _it_|{3, 6}")) + } + + @Test + fun repetition() { + assertEquals("M0 L1.0, 3.0 L1.0, 3.0", efcurve("M0 |L1.0, 3.0|[2]")) + assertEquals("M0 L1.0, 0.0 L1.0, 1.0", efcurve("M0 |L1.0, _it_|[2]")) + assertEquals("M0 L1.0, 0.0 L1.0, 1.0 L1.0, 0.0 L1.0, 1.0 L1.0, 0.0 L1.0, 1.0", efcurve("M0 ||L1.0, _it_|[2]|[3]")) + } +} \ No newline at end of file diff --git a/orx-fcurve/src/jvmDemo/kotlin/DemoMultiFCurve01.kt b/orx-fcurve/src/jvmDemo/kotlin/DemoMultiFCurve01.kt index 824a52ca..9fe89cad 100644 --- a/orx-fcurve/src/jvmDemo/kotlin/DemoMultiFCurve01.kt +++ b/orx-fcurve/src/jvmDemo/kotlin/DemoMultiFCurve01.kt @@ -1,6 +1,7 @@ import org.openrndr.application import org.openrndr.extra.fcurve.MultiFCurve import org.openrndr.extra.fcurve.fcurve +import org.openrndr.extra.fcurve.vector2 fun main() { application {