[orx-delegate-magic, orx-envelopes] Add orx-delegate-magic, orx-envelopes

This commit is contained in:
Edwin Jakobs
2023-04-21 12:32:59 +02:00
parent a61edcbbf7
commit 9119e4a95a
14 changed files with 576 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
# orx-delegate magic
Collection of magical property delegators
## Delegated properties
[Kotlin documentation](https://kotlinlang.org/docs/delegated-properties.html)
## Property smoothing
```kotlin
val state = object {
var radius = 10.0
}
val smoothRadius by smoothing(state::radius)
```
## Property dynamics
```kotlin
val state = object {
var radius = 10.0
}
val dynamicRadius by springForcing(state::radius)
```
## Property tracking
```kotlin
val state = object {
var radius = 10.0
}
val radiusHistory by tracking(state::radius)
```

View File

@@ -0,0 +1,25 @@
plugins {
org.openrndr.extra.convention.`kotlin-multiplatform`
}
kotlin {
sourceSets {
@Suppress("UNUSED_VARIABLE")
val commonMain by getting {
dependencies {
implementation(project(":orx-parameters"))
implementation(libs.openrndr.application)
implementation(libs.openrndr.draw)
implementation(libs.openrndr.filter)
implementation(libs.kotlin.reflect)
}
}
@Suppress("UNUSED_VARIABLE")
val jvmDemo by getting {
dependencies {
implementation(project(":orx-shapes"))
}
}
}
}

View File

@@ -0,0 +1,128 @@
@file:Suppress("PackageDirectoryMismatch")
package org.openrndr.extra.delegatemagic.dynamics
import org.openrndr.Program
import org.openrndr.math.LinearType
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
class DoublePropertySpringForcer(
private val program: Program,
private val property: KProperty0<Double>,
private val k: Double,
private val kProperty: KProperty0<Double>?,
private val decay: Double,
private val decayProperty: KProperty0<Double>?
) {
private var output: Double? = null
private var lastTime: Double? = null
private var velocity = 0.0
operator fun getValue(any: Any?, property: KProperty<*>): Double {
val k = kProperty?.get() ?: k
val decay = decayProperty?.get() ?: decay
val anchor = this.property.get()
if (lastTime != null) {
val dt = program.seconds - lastTime!!
if (dt > 0.0) {
val sfY = -k * (output!! - anchor)
velocity = velocity * decay + sfY * dt * 10.0
output = output!! + velocity * dt * 10.0
}
} else {
output = this.property.get()
}
lastTime = program.seconds
return output ?: error("no value")
}
}
class LinearTypePropertySpringForcer<T : LinearType<T>>(
private val program: Program,
private val property: KProperty0<T>,
private val k: Double,
private val kProperty: KProperty0<Double>?,
private val decay: Double,
private val decayProperty: KProperty0<Double>?
) {
private var output: T? = null
private var lastTime: Double? = null
private var velocity: T? = null
operator fun getValue(any: Any?, property: KProperty<*>): T {
val k = kProperty?.get() ?: k
val decay = decayProperty?.get() ?: decay
val anchor = this.property.get()
if (lastTime != null) {
val dt = program.seconds - lastTime!!
if (dt > 0.0) {
val sfY = (output!! - anchor) * -k
velocity = if (velocity != null) {
velocity!! * decay + sfY * dt * 10.0
} else {
sfY * dt * 10.0
}
output = output!! + velocity!! * dt * 10.0
}
} else {
output = this.property.get()
}
lastTime = program.seconds
return output ?: error("no value")
}
}
/**
* Create a property spring force delegate
* @param property the property that is used as the spring anchor
* @param k the spring stiffness
* @param kProperty the spring stiffness property, overrides [k]
* @param decay velocity decay, best to set to < 1
* @param decayProperty velocity decay property, overrides [decay]
* @since 0.4.3
*/
fun Program.springForcing(
property: KProperty0<Double>,
k: Double = 1.0,
kProperty: KProperty0<Double>? = null,
decay: Double = 0.9,
decayProperty: KProperty0<Double>? = null
): DoublePropertySpringForcer {
return DoublePropertySpringForcer(
program = this,
property = property,
k = k,
kProperty = kProperty,
decay = decay,
decayProperty = decayProperty
)
}
/**
* Create a property spring force delegate
* @param property the property that is used as the spring anchor
* @param k the spring stiffness
* @param kProperty the spring stiffness property, overrides [k]
* @param decay velocity decay, best to set to < 1
* @param decayProperty velocity decay property, overrides [decay]
* @since 0.4.3
*/
fun <T : LinearType<T>> Program.springForcing(
property: KProperty0<T>,
k: Double = 1.0,
kProperty: KProperty0<Double>? = null,
decay: Double = 0.9,
decayProperty: KProperty0<Double>? = null
): LinearTypePropertySpringForcer<T> {
return LinearTypePropertySpringForcer(
program = this,
property = property,
k = k,
kProperty = kProperty,
decay = decay,
decayProperty = decayProperty
)
}

View File

@@ -0,0 +1,102 @@
@file:Suppress("PackageDirectoryMismatch")
package org.openrndr.extra.delegatemagic.smoothing
import org.openrndr.Program
import org.openrndr.math.LinearType
import kotlin.math.pow
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
class DoublePropertySmoother(
private val program: Program,
private val property: KProperty0<Double>,
private val factor: Double = 0.99,
private val factorProperty: KProperty0<Double>?
) {
private var output: Double? = null
private var lastTime: Double? = null
operator fun getValue(any: Any?, property: KProperty<*>): Double {
if (lastTime != null) {
val dt = program.seconds - lastTime!!
if (dt > 1E-10) {
val steps = dt * 60.0
val ef = (factorProperty?.get() ?: factor).pow(steps)
output = output!! * ef + this.property.get() * (1.0 - ef)
}
} else {
output = this.property.get()
}
lastTime = program.seconds
return output ?: error("no value")
}
}
class PropertySmoother<T : LinearType<T>>(
private val program: Program,
private val property: KProperty0<T>,
private val factor: Double = 0.99,
private val factorProperty: KProperty0<Double>?
) {
private var output: T? = null
private var lastTime: Double? = null
operator fun getValue(any: Any?, property: KProperty<*>): T {
if (lastTime != null) {
val dt = program.seconds - lastTime!!
if (dt > 1E-10) {
val steps = dt * 60.0
val ef = (factorProperty?.get() ?: factor).pow(steps)
val target = this.property.get()
output = output!! * ef + target * (1.0 - ef)
}
} else {
output = this.property.get()
}
lastTime = program.seconds
return output ?: error("no value")
}
}
/**
* Create a property smoother delegate
* @param property the property to smooth
* @param factor the smoothing factor
* @since 0.4.3
*/
fun Program.smoothing(property: KProperty0<Double>, factor: Double = 0.99): DoublePropertySmoother {
return DoublePropertySmoother(this, property, factor, null)
}
/**
* Create a property smoother delegate
* @param property the property to smooth
* @param factor the smoothing factor property
* @since 0.4.3
*/
fun Program.smoothing(
property: KProperty0<Double>,
factor: KProperty0<Double>
): DoublePropertySmoother {
return DoublePropertySmoother(this, property, 1E10, factor)
}
/**
* Create a property smoother delegate
* @param property the property to smooth
* @param factor the smoothing factor
* @since 0.4.3
*/
fun <T : LinearType<T>> Program.smoothing(property: KProperty0<T>, factor: Double = 0.99): PropertySmoother<T> {
return PropertySmoother(this, property, factor, null)
}
/**
* Create a property smoother delegate
* @param property the property to smooth
* @param factor the smoothing factor property
* @since 0.4.3
*/
fun <T : LinearType<T>> Program.smoothing(property: KProperty0<T>, factor: KProperty0<Double>): PropertySmoother<T> {
return PropertySmoother(this, property, 1E10, factor)
}

View File

@@ -0,0 +1,39 @@
@file:Suppress("PackageDirectoryMismatch")
package org.openrndr.extra.delegatemagic.tracking
import org.openrndr.Program
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
class PropertyTracker<T>(private val program: Program, private val property: KProperty0<T>, val length: Int = 30) {
private val track = mutableListOf<T>()
private var lastTime: Double? = null
operator fun getValue(any: Any?, property: KProperty<*>): List<T> {
if (lastTime != null) {
val dt = program.seconds - lastTime!!
if (dt > 1E-10) {
track.add(this.property.get())
}
} else {
track.add(this.property.get())
}
if (track.size > length) {
track.removeAt(0)
}
lastTime = program.seconds
return track
}
}
/**
* Create a property tracker
* @param property the property to track
* @param length the maximum length of the tracked history
* @return a property tracker
* @since 0.4.3
*/
fun <T> Program.tracking(property: KProperty0<T>, length: Int = 30): PropertyTracker<T> {
return PropertyTracker(this, property, length)
}

View File

@@ -0,0 +1,29 @@
import org.openrndr.application
import org.openrndr.extra.delegatemagic.smoothing.smoothing
import kotlin.random.Random
fun main() = application {
program {
val state = object {
var x = width / 2.0
var y = height / 2.0
var radius = 5.0
}
val sx by smoothing(state::x)
val sy by smoothing(state::y)
val sradius by smoothing(state::radius)
extend {
if (Random.nextDouble() < 0.01) {
state.radius = Random.nextDouble(10.0, 200.0)
}
if (Random.nextDouble() < 0.01) {
state.x = Random.nextDouble(0.0, width.toDouble())
}
if (Random.nextDouble() < 0.01) {
state.y = Random.nextDouble(10.0, height.toDouble())
}
drawer.circle(sx, sy, sradius)
}
}
}

View File

@@ -0,0 +1,33 @@
import org.openrndr.application
import org.openrndr.extra.delegatemagic.dynamics.springForcing
import org.openrndr.extra.delegatemagic.smoothing.smoothing
import kotlin.random.Random
fun main() = application {
program {
val state = object {
var x = width / 2.0
var y = height / 2.0
var radius = 5.0
}
val sx by springForcing(state::x, k = 10.0)
val sy by springForcing(state::y)
val sradius by springForcing(state::radius)
extend {
if (Random.nextDouble() < 0.01) {
state.radius = Random.nextDouble(10.0, 200.0)
}
if (Random.nextDouble() < 0.01) {
state.x = Random.nextDouble(0.0, width.toDouble())
}
if (Random.nextDouble() < 0.01) {
state.y = Random.nextDouble(10.0, height.toDouble())
}
drawer.circle(sx, sy, sradius)
}
}
}