Skip to content

Instantly share code, notes, and snippets.

@yfujiki
Last active February 28, 2019 13:52
Show Gist options
  • Save yfujiki/e0ed77d6578befdf80c6bdd127ad07f3 to your computer and use it in GitHub Desktop.
Save yfujiki/e0ed77d6578befdf80c6bdd127ad07f3 to your computer and use it in GitHub Desktop.
Calculate start/endpoints from each other
class AnimatablePathDrawable(points: MutableList<NormalizedPoint>, context: Context)
: PathDrawable(points, context), ValueAnimator.AnimatorUpdateListener {
...
// The main method to be explained here. It configures startPoints/endPoints
private fun configureStartEndPoints(fromStartPoints: List<NormalizedPoint>,
toEndPoints: List<NormalizedPoint>) {
// The first part of the explanation above.
// Projecting vertices on the start/end path onto the straight line.
val projectedMergedPoints = projectedMergedPoints(fromStartPoints, toEndPoints)
// The second part of the explanation above.
// Mapping the points on the straight line back to the start/end paths.
startPoints = mapProjectedPointsIntoPath(fromStartPoints, projectedMergedPoints)
endPoints = mapProjectedPointsIntoPath(toEndPoints, projectedMergedPoints)
}
...
}
...
// Utility method to calculate the entire line length
// that connects the specified points.
private fun euclidianDistanceForPath(points: List<NormalizedPoint>): Double {
if (points.size < 1) {
return 0.0
}
val shiftedPoints = points.slice(1 until points.size)
return points.zip(shiftedPoints).sumByDouble { (a, b) ->
sqrt(pow((b.y - a.y).toDouble(), 2.0) + pow((b.x - a.x).toDouble(), 2.0))
}
}
...
...
// Generate merged points when projecting both
// startPoints/endPoints into 1-dimensional line of (0,1)
private fun projectedMergedPoints(fromStartPoints: List<NormalizedPoint>,
toEndPoints: List<NormalizedPoint>)
: List<Double> {
val startPathLength = euclidianDistanceForPath(fromStartPoints)
val endPathLength = euclidianDistanceForPath(toEndPoints)
var projectedMergedPoints: MutableList<Double> = mutableListOf( 0.0 )
(1 until fromStartPoints.size).forEach { i ->
val pointsToI = (fromStartPoints.slice(0..i)).toList()
val normalizedDistanceToI = euclidianDistanceForPath(pointsToI) / startPathLength
projectedMergedPoints.add(normalizedDistanceToI)
}
(1 until toEndPoints.size).forEach { i ->
val pointsToI = (toEndPoints.slice(0..i)).toList()
val normalizedDistanceToI = euclidianDistanceForPath(pointsToI) / endPathLength
projectedMergedPoints.add(normalizedDistanceToI)
}
projectedMergedPoints.sort()
return projectedMergedPoints.toList()
}
...
...
// Generate 2-d points when mapping the projected points back onto the path.
private fun mapProjectedPointsIntoPath(path: List<NormalizedPoint>,
projectedPoints: List<Double>)
: List<NormalizedPoint> {
val entireDistance = euclidianDistanceForPath(path)
val distanceKeyedPoints: SortedMap<Double, NormalizedPoint> = sortedMapOf()
var distancesToPoints: SortedSet<Double> = sortedSetOf()
path.forEachIndexed { index, normalizedPoint ->
if (index == 0) {
distanceKeyedPoints[0.0] = normalizedPoint
distancesToPoints.add(0.0)
} else if (index == path.size - 1) {
distanceKeyedPoints[entireDistance] = normalizedPoint
distancesToPoints.add(entireDistance)
} else {
val pointsToIndex = (path.slice(0..index)).toList()
val distanceToIndex = euclidianDistanceForPath(pointsToIndex)
distanceKeyedPoints[distanceToIndex] = normalizedPoint
distancesToPoints.add(distanceToIndex)
}
}
var tempPoints: MutableList<NormalizedPoint> = mutableListOf()
projectedPoints.forEach { projectedPoint ->
val distanceToProjectedPoint = projectedPoint * entireDistance
val pointInPath = pointInPath(distanceKeyedPoints, distanceToProjectedPoint)
tempPoints.add(pointInPath)
}
return tempPoints.toList()
}
// When line length reaching to this point is `distanceToPoint`,
// then map that onto the path and figure out corresponding point.
// `distanceKeyedPoints.values` hold the vertices on the path.
// `distanceKeyedPoints.keys` hold the distance of the path to the corresponding point.
private fun pointInPath(distanceKeyedPoints: SortedMap<Double, NormalizedPoint>,
distanceToPoint: Double)
: NormalizedPoint {
var prevDistance = distanceKeyedPoints.firstKey()
var prevPoint = distanceKeyedPoints[prevDistance]!!
for ((distance, point) in distanceKeyedPoints) {
if (distance == distanceToPoint) {
return point
} else if ((distanceToPoint - prevDistance) * (distanceToPoint - distance) < 0) {
val x = (point.x - prevPoint.x)
* (distanceToPoint - prevDistance)
/ (distance - prevDistance)
+ prevPoint.x
val y = (point.y - prevPoint.y)
* (distanceToPoint - prevDistance)
/ (distance - prevDistance)
+ prevPoint.y
return NormalizedPoint(x.toFloat(), y.toFloat())
}
prevDistance = distance
prevPoint = point
}
return distanceKeyedPoints[distanceKeyedPoints.lastKey()]!!
}
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment