All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.teamwizardry.librarianlib.math.CompoundEasingBuilder.kt Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.10
Show newest version
package com.teamwizardry.librarianlib.math

public class CompoundEasingBuilder(private val initialValue: Float) {
    private val keyframes = mutableListOf()

    /**
     * Add a keyframe. The given duration and easing are for the span between this keyframe and the previous keyframe.
     */
    public fun add(duration: Float, easing: Easing, value: Float): CompoundEasingBuilder {
        keyframes.add(BuildingKeyframe(duration, value, easing))
        return this
    }

    /**
     * Instantly jump to the passed value.
     */
    public fun jump(value: Float): CompoundEasingBuilder {
        keyframes.add(BuildingKeyframe(0f, value, Easing.linear))
        return this
    }

    /**
     * Hold the current value for the passed duration.
     */
    public fun hold(duration: Float): CompoundEasingBuilder {
        val value = keyframes.lastOrNull()?.value ?: initialValue
        keyframes.add(BuildingKeyframe(duration, value, Easing.linear))
        return this
    }

    public fun build(): Easing {
        val built = mutableListOf()
        val startKeyframe = Keyframe(null, 0f, initialValue, Easing.linear)
        built.add(startKeyframe)

        val totalDuration = keyframes.fold(0f) { acc, it -> acc + it.duration }

        if (totalDuration == 0f) {
            built.add(Keyframe(null, 1f, initialValue, Easing.linear))
        } else {
            var previous = startKeyframe
            keyframes.mapTo(built) { keyframe ->
                Keyframe(
                    previous, previous.time + keyframe.duration / totalDuration,
                    keyframe.value, keyframe.easingBefore
                ).also {
                    previous = it
                }
            }
        }
        return CompoundEasing(built)
    }

    private class BuildingKeyframe(val duration: Float, val value: Float, val easingBefore: Easing)

    private class Keyframe(val previous: Keyframe?, val time: Float, val value: Float, val easingBefore: Easing): Easing {
        val duration: Float = time - (previous?.time ?: 0f)

        operator fun contains(t: Float): Boolean {
            return (previous == null || t > previous.time) && t <= time
        }

        override fun ease(progress: Float): Float {
            if (previous == null || duration == 0f)
                return value
            val adjustedProgress = (progress - previous.time) / duration
            return previous.value + (value - previous.value) * easingBefore.ease(adjustedProgress)
        }
    }

    /**
     * [keyframes] MUST contain a "start" keyframe at t=0 and the last keyframe must be at t=1
     */
    private class CompoundEasing(private val keyframes: List): Easing {
        /**
         * Easings are often invoked incrementally, so we cache the last index. If the time falls within this keyframe's
         * range it's used, otherwise a normal search is performed.
         */
        private var index: Int = 0

        override fun ease(progress: Float): Float {
            if (progress <= 0)
                return keyframes.first().value
            if (progress >= 1)
                return keyframes.last().value

            if (progress !in keyframes[index]) {
                index = keyframes.indexOfFirst { it.duration != 0f && progress in it }
            }

            return keyframes[index].ease(progress)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy