commonMain.org.jetbrains.letsPlot.livemap.chart.GrowingPathEffect.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of livemap-jvm Show documentation
Show all versions of livemap-jvm Show documentation
A part of the Lets-Plot library.
The newest version!
/*
* Copyright (c) 2023. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/
package org.jetbrains.letsPlot.livemap.chart
import org.jetbrains.letsPlot.commons.intern.typedGeometry.LineString
import org.jetbrains.letsPlot.commons.intern.typedGeometry.Vec
import org.jetbrains.letsPlot.commons.intern.typedGeometry.explicitVec
import org.jetbrains.letsPlot.core.canvas.Context2d
import org.jetbrains.letsPlot.livemap.Client
import org.jetbrains.letsPlot.livemap.World
import org.jetbrains.letsPlot.livemap.core.ecs.*
import org.jetbrains.letsPlot.livemap.core.layers.ParentLayerComponent
import org.jetbrains.letsPlot.livemap.core.layers.ParentLayerComponent.Companion.tagDirtyParentLayer
import org.jetbrains.letsPlot.livemap.geometry.WorldGeometryComponent
import org.jetbrains.letsPlot.livemap.mapengine.RenderHelper
import org.jetbrains.letsPlot.livemap.mapengine.Renderer
import org.jetbrains.letsPlot.livemap.mapengine.placement.WorldOriginComponent
import kotlin.math.sqrt
object GrowingPathEffect {
private fun length(p1: Vec<*>, p2: Vec<*>): Double {
val x = p2.x - p1.x
val y = p2.y - p1.y
return sqrt(x * x + y * y)
}
class GrowingPathEffectSystem(componentManager: EcsComponentManager) :
AbstractSystem(componentManager) {
override fun updateImpl(context: EcsContext, dt: Double) {
for (entity in getEntities(COMPONENT_TYPES)) {
val path = entity.get().geometry.multiLineString.single()
val effectComponent = entity.get()
if (effectComponent.lengthIndex.isEmpty()) {
effectComponent.init(path)
}
val animation = getEntityById(effectComponent.animationId) ?: return
calculateEffectState(effectComponent, path, animation.get().progress)
tagDirtyParentLayer(entity)
}
}
private fun GrowingPathEffectComponent.init(path: LineString<*>) {
var l = 0.0
lengthIndex = ArrayList(path.size).apply {
add(0.0)
for (i in 1 until path.size) {
l += length(path[i - 1], path[i])
add(l)
}
}
length = l
}
private fun calculateEffectState(
effectComponent: GrowingPathEffectComponent,
path: LineString<*>,
progress: Double
) {
val lengthIndex = effectComponent.lengthIndex
val length = effectComponent.length
val current = length * progress
var index = lengthIndex.binarySearch(current)
if (index >= 0) {
effectComponent.endIndex = index
effectComponent.interpolatedPoint = null
return
}
index = index.inv() - 1
if (index == lengthIndex.size - 1) {
effectComponent.endIndex = index
effectComponent.interpolatedPoint = null
return
}
val l1 = lengthIndex[index]
val l2 = lengthIndex[index + 1]
val dl = l2 - l1
if (dl > 2.0) {
val dp = dl / length
val p1 = l1 / length
val p = (progress - p1) / dp
val v1 = path[index]
val v2 = path[index + 1]
effectComponent.endIndex = index
effectComponent.interpolatedPoint = explicitVec(v1.x + (v2.x - v1.x) * p, v1.y + (v2.y - v1.y) * p)
} else {
effectComponent.endIndex = index
effectComponent.interpolatedPoint = null
}
}
companion object {
private val COMPONENT_TYPES = listOf(
GrowingPathEffectComponent::class,
WorldGeometryComponent::class,
WorldOriginComponent::class,
ParentLayerComponent::class
)
}
}
class GrowingPathEffectComponent : EcsComponent {
var animationId: Int = 0
var lengthIndex: List = emptyList()
var length: Double = 0.0
var endIndex: Int = 0
var interpolatedPoint: Vec? = null // can be null if no need in point interpolation
}
class GrowingPathRenderer : Renderer {
override fun render(entity: EcsEntity, ctx: Context2d, renderHelper: RenderHelper) {
val chartElement = entity.get()
val lineString = entity.get().geometry.multiLineString.single()
val growingPath = entity.get()
ctx.save()
ctx.scale(renderHelper.zoomFactor)
ctx.beginPath()
var viewCoord: Vec = lineString[0]
ctx.moveTo(viewCoord.x, viewCoord.y)
for (i in 1..growingPath.endIndex) {
viewCoord = lineString[i]
ctx.lineTo(viewCoord.x, viewCoord.y)
}
growingPath.interpolatedPoint?.let { ctx.lineTo(it.x, it.y) }
ctx.restore()
ctx.setStrokeStyle(chartElement.strokeColor)
ctx.setLineWidth(chartElement.strokeWidth)
ctx.stroke()
}
}
}