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

org.kalasim.animation.AnimationComponent.kt Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
package org.kalasim.animation

import org.kalasim.*
import org.kalasim.analysis.RescheduledEvent
import kotlin.properties.Delegates


/** A component that has a position on a planar 2D surface. While being on the move, it allows to query its current
 *  position. This is most useful when visualizing a simulation state.
 */
open class AnimationComponent(
    initialPosition: Point? = null,
    name: String? = null,
    process: GeneratorFunRef? = null,
) : Component(name, process = process) {

    private var from: Point = initialPosition ?: Point(0.0, 0.0)
    protected var to: Point? = null

    protected var started by Delegates.notNull()
    var currentSpeed = 0.kmh

    protected var estimatedArrival: SimTime = now

    suspend fun SequenceScope.move(
        nextTarget: Point,
        speed: Speed,
        description: String? = null,
        priority: Priority = Priority.NORMAL,
    ) {
        to = nextTarget
        started = now
        currentSpeed = speed

        val distance = to!! - from

        val duration = distance / speed
        estimatedArrival = now + duration

        hold(duration, description ?: "moving to $nextTarget", priority = priority)
        from = to!!
        to = null
    }


    val currentPosition: Point
        get() {
            val currentTo = to // used for better thread safety

            return if(currentTo != null) {
                val percentDone = (now - started) / (estimatedArrival - started)

                val xDist = currentTo.x - from.x
                val yDist = currentTo.y - from.y

                Point(
//                    from.x*(1-percentDone) + to!!.x*percentDone,
//                    from.y*(1-percentDone) + to!!.y*percentDone
                    from.x + percentDone * xDist, from.y + percentDone * yDist
                )

            } else {
                from
            }
        }

//    val gridPosition
//        get() = GridPosition(position.x.roundToInt(), position.y.roundToInt())

//    fun SequenceScope.move(to: GridPosition) = sequence {
//        val route = planRoute(position, Point2D.Double(to.x.toDouble(), to.y.toDouble()))
//
//        route.forEach {
//            hold(1)
//            position = it
//        }
//    }
//
//    fun SequenceScope.planRoute(from: Point2D, to: Point2D, distancePerTick: Number = 0.1) = sequence {
//        val xDist = to.x - from.x
//        val yDist = to.y - from.y
//
//        val distance = sqrt(xDist * xDist + yDist * yDist)
//        val numMoves = distance / distancePerTick.toDouble()
//
//        val xInc = xDist / numMoves
//        val yInc = yDist / numMoves
//
//        // todo this seems slightly wrong
//        for(i in 0 until floor(numMoves).toInt()) {
//            yield(Point2D.Double(from.x + i * xInc, from.y + i * yInc))
//
//            // to avoid any rounding issues
//            yield(to)
//        }
//    }


    private var lastHold = mutableMapOf()

    // note: we could potentially also bypass the event-bus and overload log()
    init {
        env.addEventListener { re ->
            if(re.component != this) return@addEventListener

            holdTracks
                .filter { (_, matcher) -> matcher(re) }
                .keys.forEach {
                    lastHold[it] = re
                }
        }
    }

    val holdTracks = mutableMapOf()
    fun registerHoldTracker(query: String, eventMatcher: AnimationHoldMatcher) {
        holdTracks[query] = eventMatcher
    }


    fun isHolding(holdId: String): Boolean {
        val rescheduledEvent = lastHold[holdId]

        return rescheduledEvent != null && rescheduledEvent.scheduledFor >= now
    }

    fun holdProgress(holdId: String): Double? {
        val rescheduledEvent = lastHold[holdId]
        if(rescheduledEvent == null || rescheduledEvent.scheduledFor < now) return null

        require(rescheduledEvent.time <= now)

        // calculate fraction
        return (now - rescheduledEvent.time) / (rescheduledEvent.scheduledFor - rescheduledEvent.time)
    }
}


typealias AnimationHoldMatcher = RescheduledEvent.() -> Boolean




© 2015 - 2025 Weber Informatics LLC | Privacy Policy