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

com.almasb.fxgl.animation.AnimationBuilder.kt Maven / Gradle / Ivy

The newest version!
/*
 * FXGL - JavaFX Game Library. The MIT License (MIT).
 * Copyright (c) AlmasB ([email protected]).
 * See LICENSE for details.
 */

package com.almasb.fxgl.animation

import com.almasb.fxgl.core.util.EmptyRunnable
import com.almasb.fxgl.entity.Entity
import com.almasb.fxgl.logging.Logger
import com.almasb.fxgl.scene.Scene
import javafx.animation.Interpolator
import javafx.beans.property.DoubleProperty
import javafx.beans.value.WritableValue
import javafx.geometry.Point2D
import javafx.geometry.Point3D
import javafx.scene.Node
import javafx.scene.shape.CubicCurve
import javafx.scene.shape.QuadCurve
import javafx.scene.shape.Shape
import javafx.scene.transform.Rotate
import javafx.scene.transform.Scale
import javafx.util.Duration
import java.lang.IllegalArgumentException
import java.util.function.Consumer

/**
 * Animation DSL that provides a fluent API for building and running animations.
 *
 * @author Almas Baimagambetov ([email protected])
 */
open class AnimationBuilder
@JvmOverloads constructor(protected val scene: Scene? = null) {

    var duration: Duration = Duration.seconds(1.0)
    var delay: Duration = Duration.ZERO
    var interpolator: Interpolator = Interpolator.LINEAR
    var times: Int = 1
    var onCycleFinished: Runnable = EmptyRunnable
    var isAutoReverse: Boolean = false

    var onFinished: Runnable = EmptyRunnable

    constructor(copy: AnimationBuilder) : this(copy.scene) {
        duration = copy.duration
        delay = copy.delay
        interpolator = copy.interpolator
        times = copy.times
        onFinished = copy.onFinished
        onCycleFinished = copy.onCycleFinished
        isAutoReverse = copy.isAutoReverse
    }

    fun duration(duration: Duration): AnimationBuilder {
        this.duration = duration
        return this
    }

    fun delay(delay: Duration): AnimationBuilder {
        this.delay = delay
        return this
    }

    fun interpolator(interpolator: Interpolator): AnimationBuilder {
        this.interpolator = interpolator
        return this
    }

    fun repeat(times: Int): AnimationBuilder {
        this.times = times
        return this
    }

    fun repeatInfinitely(): AnimationBuilder {
        return repeat(Integer.MAX_VALUE)
    }

    fun onCycleFinished(onCycleFinished: Runnable): AnimationBuilder {
        this.onCycleFinished = onCycleFinished
        return this
    }

    fun autoReverse(autoReverse: Boolean): AnimationBuilder {
        this.isAutoReverse = autoReverse
        return this
    }

    fun onFinished(onFinished: Runnable): AnimationBuilder {
        this.onFinished = onFinished
        return this
    }

    internal fun  buildAnimation(animatedValue: AnimatedValue, onProgress: Consumer): Animation {
        return object : Animation(this, animatedValue) {
            override fun onProgress(value: T) {
                onProgress.accept(value)
            }
        }
    }

    /* BEGIN BUILT-IN ANIMATIONS */

    fun  animate(value: AnimatedValue) = GenericAnimationBuilder(this, value)

    fun  animate(property: WritableValue) = PropertyAnimationBuilder(this, property)

    fun translate(vararg entities: Entity) = TranslationAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun translate(vararg entities: Node) = TranslationAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun translate(entities: Collection) = TranslationAnimationBuilder(this).apply {
        objects += entities.map { toAnimatable(it) }
    }

    fun fade(vararg entities: Entity) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun fade(vararg entities: Node) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun fade(entities: Collection) = FadeAnimationBuilder(this).apply {
        objects += entities.map { toAnimatable(it) }
    }

    fun scale(vararg entities: Entity) = ScaleAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun scale(vararg entities: Node) = ScaleAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun scale(entities: Collection) = ScaleAnimationBuilder(this).apply {
        objects += entities.map { toAnimatable(it) }
    }

    fun rotate(vararg entities: Entity) = RotationAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun rotate(vararg entities: Node) = RotationAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }

    fun rotate(entities: Collection) = RotationAnimationBuilder(this).apply {
        objects += entities.map { toAnimatable(it) }
    }

    private fun toAnimatable(obj: Any): Animatable = when (obj) {
        is Node -> obj.toAnimatable()
        is Entity -> obj.toAnimatable()
        else -> throw IllegalArgumentException("${obj.javaClass} must be Node or Entity")
    }

    fun fadeIn(vararg entities: Entity) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }.from(0.0).to(1.0)

    fun fadeIn(vararg entities: Node) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }.from(0.0).to(1.0)

    fun fadeOut(vararg entities: Entity) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }.from(1.0).to(0.0)

    fun fadeOut(vararg entities: Node) = FadeAnimationBuilder(this).apply {
        objects += entities.map { it.toAnimatable() }
    }.from(1.0).to(0.0)

    fun bobbleDown(node: Node) = duration(Duration.seconds(0.15))
            .autoReverse(true)
            .repeat(2)
            .translate(node)
            .from(Point2D(node.translateX, node.translateY))
            .to(Point2D(node.translateX, node.translateY + 5.0))

    /* END BUILT-IN ANIMATIONS */

    abstract class AM(private val animationBuilder: AnimationBuilder) : AnimationBuilder(animationBuilder) {
        val objects = arrayListOf()

        abstract fun build(): Animation<*>

        /**
         * Builds animation and plays in the game scene.
         */
        fun buildAndPlay() {
            if (animationBuilder.scene != null) {
                buildAndPlay(animationBuilder.scene)
            } else {
                Logger.get(javaClass).warning("No game scene was set to AnimationBuilder")
            }
        }

        fun buildAndPlay(scene: Scene) {
            build().also { animation ->

                animation.onFinished = Runnable {
                    scene.removeListener(animation)
                    onFinished.run()
                }

                animation.start()
                scene.addListener(animation)
            }
        }
    }

    class TranslationAnimationBuilder(animationBuilder: AnimationBuilder) : AM(animationBuilder) {

        private var path: Shape? = null
        private var fromPoint = Point3D.ZERO
        private var toPoint = Point3D.ZERO

        fun alongPath(path: Shape) = this.also {
            this.path = path
        }

        fun from(start: Point2D) = this.also {
            fromPoint = Point3D(start.x, start.y, 0.0)
        }

        fun to(end: Point2D) = this.also {
            toPoint = Point3D(end.x, end.y, 0.0)
        }

        fun from(start: Point3D) = this.also {
            fromPoint = start
        }

        fun to(end: Point3D) = this.also {
            toPoint = end
        }

        override fun build(): Animation<*> {

            path?.let { curve ->
                return when (curve) {
                    is QuadCurve -> makeAnim(AnimatedQuadBezierPoint3D(curve))
                    is CubicCurve -> makeAnim(AnimatedCubicBezierPoint3D(curve))
                    else -> makeAnim(AnimatedPath(curve))
                }
            }

            return makeAnim(AnimatedPoint3D(fromPoint, toPoint))
        }

        private fun makeAnim(animValue: AnimatedValue): Animation {
            return buildAnimation(
                    animValue,
                    Consumer { value ->
                        objects.forEach {
                            it.xProperty().value = value.x
                            it.yProperty().value = value.y
                            it.zProperty().value = value.z
                        }
                    }
            )
        }
    }

    class FadeAnimationBuilder(animationBuilder: AnimationBuilder) : AM(animationBuilder) {

        private var from = 0.0
        private var to = 0.0

        fun from(start: Double) = this.also {
            from = start
        }

        fun to(end: Double) = this.also {
            to = end
        }

        override fun build(): Animation<*> {
            return buildAnimation(AnimatedValue(from, to),
                    Consumer { value ->
                        objects.forEach {
                            it.opacityProperty().value = value
                        }
                    }
            )
        }
    }

    class ScaleAnimationBuilder(animationBuilder: AnimationBuilder) : AM(animationBuilder) {

        private var startScale = Point3D(1.0, 1.0, 1.0)
        private var endScale = Point3D(1.0, 1.0, 1.0)
        private var scaleOrigin: Point2D? = null

        fun from(start: Point2D): ScaleAnimationBuilder {
            startScale = Point3D(start.x, start.y, 1.0)
            return this
        }

        fun to(end: Point2D): ScaleAnimationBuilder {
            endScale = Point3D(end.x, end.y, 1.0)
            return this
        }

        fun from(start: Point3D): ScaleAnimationBuilder {
            startScale = start
            return this
        }

        fun to(end: Point3D): ScaleAnimationBuilder {
            endScale = end
            return this
        }

        fun origin(scaleOrigin: Point2D): ScaleAnimationBuilder {
            this.scaleOrigin = scaleOrigin
            return this
        }

        override fun build(): Animation<*> {
            scaleOrigin?.let { origin ->
                objects.forEach {
                    it.setScaleOrigin(origin)
                }
            }

            return buildAnimation(
                    AnimatedPoint3D(startScale, endScale),
                    Consumer { value ->
                        objects.forEach {
                            it.scaleXProperty().value = value.x
                            it.scaleYProperty().value = value.y
                            it.scaleZProperty().value = value.z
                        }
                    }
            )
        }
    }

    class RotationAnimationBuilder(animationBuilder: AnimationBuilder) : AM(animationBuilder) {

        private var is3DAnimation = false
        private var startRotation = Point3D.ZERO
        private var endRotation = Point3D.ZERO
        private var rotationOrigin: Point2D? = null

        fun from(startAngle: Double): RotationAnimationBuilder {
            is3DAnimation = false
            startRotation = Point3D(0.0, 0.0, startAngle)
            return this
        }

        fun to(endAngle: Double): RotationAnimationBuilder {
            is3DAnimation = false
            endRotation = Point3D(0.0, 0.0, endAngle)
            return this
        }

        fun from(start: Point3D): RotationAnimationBuilder {
            is3DAnimation = true
            startRotation = start
            return this
        }

        fun to(end: Point3D): RotationAnimationBuilder {
            is3DAnimation = true
            endRotation = end
            return this
        }

        fun origin(rotationOrigin: Point2D): RotationAnimationBuilder {
            this.rotationOrigin = rotationOrigin
            return this
        }

        override fun build(): Animation<*> {
            rotationOrigin?.let { origin ->
                objects.forEach {
                    it.setRotationOrigin(origin)
                }
            }

            return buildAnimation(AnimatedValue(startRotation, endRotation),
                    Consumer { value ->
                        objects.forEach {
                            if (is3DAnimation) {
                                it.rotationXProperty().value = value.x
                                it.rotationYProperty().value = value.y
                            }
                            it.rotationZProperty().value = value.z
                        }
                    }
            )
        }
    }

    class GenericAnimationBuilder(animationBuilder: AnimationBuilder, val animValue: AnimatedValue) : AM(animationBuilder) {

        private var progressConsumer: Consumer = Consumer { }

        fun onProgress(progressConsumer: Consumer): GenericAnimationBuilder {
            this.progressConsumer = progressConsumer
            return this
        }

        override fun build(): Animation {
            return buildAnimation(animValue, progressConsumer)
        }
    }

    class PropertyAnimationBuilder(animationBuilder: AnimationBuilder, private val property: WritableValue) : AM(animationBuilder) {

        private var startValue: T = property.value
        private var endValue: T = property.value

        private var progressConsumer: Consumer = Consumer {
            property.value = it
        }

        fun from(startValue: T): PropertyAnimationBuilder {
            this.startValue = startValue
            return this
        }

        fun to(endValue: T): PropertyAnimationBuilder {
            this.endValue = endValue
            return this
        }

        override fun build(): Animation {
            return buildAnimation(AnimatedValue(startValue, endValue), progressConsumer)
        }
    }
}

private fun Node.toAnimatable(): Animatable {
    val n = this
    return object : Animatable {
        private var scale: Scale? = null
        private var rotateX: Rotate? = null
        private var rotateY: Rotate? = null
        private var rotateZ: Rotate? = null

        override fun xProperty(): DoubleProperty {
            return n.translateXProperty()
        }

        override fun yProperty(): DoubleProperty {
            return n.translateYProperty()
        }

        override fun zProperty(): DoubleProperty {
            return n.translateZProperty()
        }

        override fun scaleXProperty(): DoubleProperty {
            return scale?.xProperty() ?: n.scaleXProperty()
        }

        override fun scaleYProperty(): DoubleProperty {
            return scale?.yProperty() ?: n.scaleYProperty()
        }

        override fun scaleZProperty(): DoubleProperty {
            return scale?.zProperty() ?: n.scaleZProperty()
        }

        override fun rotationXProperty(): DoubleProperty {
            initRotations()

            return rotateX!!.angleProperty()
        }

        override fun rotationYProperty(): DoubleProperty {
            initRotations()

            return rotateY!!.angleProperty()
        }

        override fun rotationZProperty(): DoubleProperty {
            return rotateZ?.angleProperty() ?: n.rotateProperty()
        }

        override fun opacityProperty(): DoubleProperty {
            return n.opacityProperty()
        }

        override fun setScaleOrigin(pivotPoint: Point2D) {
            // if a node already has a previous transform, reuse it
            // this means the node was animated previously
            n.properties["anim_scale"]?.let { transform ->
                scale = transform as Scale
                scale!!.pivotX = pivotPoint.x
                scale!!.pivotY = pivotPoint.y
                return
            }

            scale = Scale(0.0, 0.0, pivotPoint.x, pivotPoint.y)
                    .also {
                        n.transforms.add(it)
                        n.properties["anim_scale"] = it
                    }
        }

        override fun setRotationOrigin(pivotPoint: Point2D) {
            // if a node already has a previous transform, reuse it
            // this means the node was animated previously
            n.properties["anim_rotate_z"]?.let { transform ->
                rotateZ = transform as Rotate
                rotateZ!!.pivotX = pivotPoint.x
                rotateZ!!.pivotY = pivotPoint.y
                return
            }

            rotateZ = Rotate(0.0, pivotPoint.x, pivotPoint.y)
                    .also {
                        it.axis = Rotate.Z_AXIS
                        n.transforms.add(it)
                        n.properties["anim_rotate_z"] = it
                    }
        }

        private fun initRotations() {
            if (n.properties.containsKey("anim_rotate_x")) {
                rotateX = n.properties["anim_rotate_x"]!! as Rotate
                rotateY = n.properties["anim_rotate_y"]!! as Rotate
                rotateZ = n.properties["anim_rotate_z"]!! as Rotate
            } else {
                rotateX = Rotate(0.0, Rotate.X_AXIS)
                rotateY = Rotate(0.0, Rotate.Y_AXIS)
                rotateZ = Rotate(0.0, Rotate.Z_AXIS)

                n.properties["anim_rotate_x"] = rotateX
                n.properties["anim_rotate_y"] = rotateY
                n.properties["anim_rotate_z"] = rotateZ

                n.transforms.addAll(rotateZ, rotateY, rotateX)
            }
        }
    }
}

private fun Entity.toAnimatable(): Animatable {
    val e = this
    return object : Animatable {
        override fun xProperty(): DoubleProperty {
            return e.xProperty()
        }

        override fun yProperty(): DoubleProperty {
            return e.yProperty()
        }

        override fun zProperty(): DoubleProperty {
            return e.zProperty()
        }

        override fun scaleXProperty(): DoubleProperty {
            return e.transformComponent.scaleXProperty()
        }

        override fun scaleYProperty(): DoubleProperty {
            return e.transformComponent.scaleYProperty()
        }

        override fun scaleZProperty(): DoubleProperty {
            return e.transformComponent.scaleZProperty()
        }

        override fun rotationXProperty(): DoubleProperty {
            return e.transformComponent.rotationXProperty()
        }

        override fun rotationYProperty(): DoubleProperty {
            return e.transformComponent.rotationYProperty()
        }

        override fun rotationZProperty(): DoubleProperty {
            return e.transformComponent.rotationZProperty()
        }

        override fun opacityProperty(): DoubleProperty {
            return e.viewComponent.opacityProperty
        }

        override fun setScaleOrigin(pivotPoint: Point2D) {
            e.transformComponent.scaleOrigin = pivotPoint
        }

        override fun setRotationOrigin(pivotPoint: Point2D) {
            e.transformComponent.rotationOrigin = pivotPoint
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy