org.kalasim.animation.AnimationComponent.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kalasim Show documentation
Show all versions of kalasim Show documentation
kalasim is a process-oriented discrete event simulation engine
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