dracula-musical/src/main/kotlin/de/moritzruth/theaterdsl/DynamicValue.kt
2023-05-21 18:58:14 +02:00

141 lines
No EOL
5.1 KiB
Kotlin

package de.moritzruth.theaterdsl
import de.moritzruth.theaterdsl.value.*
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlin.math.PI
import kotlin.math.asin
import kotlin.math.min
import kotlin.math.sin
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
interface DynamicValue<T> {
fun getCurrentValue(): T
}
@OptIn(ExperimentalTime::class)
class PercentageDV(initialStaticValue: Percentage = 0.percent): DynamicValue<Percentage> {
private sealed interface State {
data class Static(val value: Percentage): State
data class Fade(val start: Percentage, val end: Percentage, val duration: Duration): State {
val delta = end.value - start.value
}
data class Sine(
val offset: Double,
val minimum: Percentage,
val maximum: Percentage,
val period: Duration
): State {
companion object {
fun calculateA(minimum: Percentage, maximum: Percentage) = (maximum.value - minimum.value) * 0.5
fun calculateX(progress: Double, offset: Double) = 2 * PI * progress - offset
fun calculateD(minimum: Percentage) = 0.5 + minimum.value
}
}
data class Step(val steps: ImmutableList<Percentage>, val interval: Duration, val startIndex: Int): State
}
private var stateChangeMark = TimeSource.Monotonic.markNow()
private var state: State = State.Static(initialStaticValue)
set(value) {
field = value
stateChangeMark = TimeSource.Monotonic.markNow()
}
override fun getCurrentValue(): Percentage {
val elapsedTime = stateChangeMark.elapsedNow()
return when (val s = state) {
is State.Static -> s.value
is State.Fade -> (min(elapsedTime / s.duration, 1.0) * s.delta + s.start.value).toFloat().asPercentage()
is State.Sine -> {
val a = State.Sine.calculateA(s.minimum, s.maximum)
val x = State.Sine.calculateX(elapsedTime / s.period, s.offset)
val d = State.Sine.calculateD(s.minimum)
(a * sin(x) + d).toFloat().asPercentage()
}
is State.Step -> {
val index = (elapsedTime / s.interval).toInt() + s.startIndex
s.steps[index.mod(s.steps.size)]
}
}
}
fun static(value: Percentage) {
state = State.Static(value)
}
fun fade(end: Percentage, duration: Duration, start: Percentage = getCurrentValue()) {
state = State.Fade(start, end, duration)
}
fun sine(minimum: Percentage, maximum: Percentage, period: Duration, start: Percentage = getCurrentValue()) {
val offset = asin((start.value - State.Sine.calculateD(minimum)) / State.Sine.calculateA(minimum, maximum))
state = State.Sine(offset, minimum, maximum, period)
}
fun steps(steps: List<Percentage>, interval: Duration, startIndex: Int = 0) {
state = State.Step(steps.toImmutableList(), interval, startIndex)
}
fun switch(a: Percentage, b: Percentage, interval: Duration, startAtCurrent: Boolean = true) {
val startIndex = startAtCurrent
.takeIf { it }
?.let { getCurrentValue() }
?.takeIf { it == b }
?.let { 1 } ?: 0
steps(persistentListOf(a, b), interval, startIndex)
}
}
@OptIn(ExperimentalTime::class)
class ColorDV(initialStaticValue: Color = Color.WHITE): DynamicValue<Color> {
private sealed interface State {
data class Static(val value: Color): State
data class Fade(val start: Color, val end: Color, val duration: Duration): State {
val deltaHue = ((end.hue.degree - start.hue.degree).mod(360f) + 540f).mod(360f) - 180f
val deltaSaturation = end.saturation.value - start.saturation.value
val deltaBrightness = end.brightness.value - start.brightness.value
}
}
private var stateChangeMark = TimeSource.Monotonic.markNow()
private var state: State = State.Static(initialStaticValue)
set(value) {
field = value
stateChangeMark = TimeSource.Monotonic.markNow()
}
override fun getCurrentValue(): Color {
val elapsedTime = stateChangeMark.elapsedNow()
return when(val s = state) {
is State.Static -> s.value
is State.Fade -> {
val progress = min(elapsedTime / s.duration, 1.0)
Color(
hue = Angle((s.start.hue.degree + s.deltaHue * progress).toFloat()),
saturation = Percentage((s.start.saturation.value + s.deltaSaturation * progress).toFloat()),
brightness = Percentage((s.start.brightness.value + s.deltaBrightness * progress).toFloat())
)
}
}
}
fun static(value: Color) {
state = State.Static(value)
}
fun fade(end: Color, duration: Duration, start: Color = getCurrentValue()) {
state = State.Fade(start, end, duration)
}
}