Skip to content

Commit

Permalink
Make pin pointer rotated
Browse files Browse the repository at this point in the history
  • Loading branch information
TimPushkin committed Jun 8, 2024
1 parent b554cc5 commit eb0f79d
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 48 deletions.
64 changes: 19 additions & 45 deletions app/src/main/java/ru/spbu/depnav/ui/component/PinPointer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ import ovh.plrapps.mapcompose.utils.AngleDegree
import ovh.plrapps.mapcompose.utils.Point
import ru.spbu.depnav.data.model.Marker
import ru.spbu.depnav.utils.map.LineSegment
import ru.spbu.depnav.utils.map.bottom
import ru.spbu.depnav.utils.map.centroid
import ru.spbu.depnav.utils.map.contains
import ru.spbu.depnav.utils.map.left
import ru.spbu.depnav.utils.map.rectangularVisibleArea
import ru.spbu.depnav.utils.map.right
import ru.spbu.depnav.utils.map.rotation
import ru.spbu.depnav.utils.map.top

Expand Down Expand Up @@ -106,7 +109,7 @@ fun PinPointer(mapState: MapState, pin: Marker?) {
}

private data class PinPointerPose(
val side: Side, val sideFraction: Double, val direction: AngleDegree
val side: Side, val sideFraction: Double, val direction: AngleDegree
) {
enum class Side { LEFT, RIGHT, TOP, BOTTOM }

Expand Down Expand Up @@ -151,51 +154,22 @@ private data class PinPointerPose(
}
}

private const val EPSILON = 1e-5

private fun calculatePointerPose(visibleArea: VisibleArea, pin: Point): PinPointerPose {
val topBorder = visibleArea.top()
val leftBorder = visibleArea.left()

val horizontalFraction = topBorder.fractionOfClosestPointTo(pin)
val verticalFraction = leftBorder.fractionOfClosestPointTo(pin)
val centroidPinSegment = LineSegment(visibleArea.centroid(), pin)
val direction = centroidPinSegment.slope() - 90

return when {
// Corners
horizontalFraction < EPSILON && verticalFraction < EPSILON -> {
val direction = LineSegment(topBorder.p1, pin).slope() - 90
PinPointerPose(PinPointerPose.Side.TOP, 0.0, direction)
}
horizontalFraction > 1.0 - EPSILON && verticalFraction < EPSILON -> {
val direction = LineSegment(topBorder.p2, pin).slope() - 90
PinPointerPose(PinPointerPose.Side.TOP, 1.0, direction)
}
horizontalFraction < EPSILON && verticalFraction > 1.0 - EPSILON -> {
val direction = LineSegment(leftBorder.p2, pin).slope() - 90
PinPointerPose(PinPointerPose.Side.BOTTOM, 0.0, direction)
}
horizontalFraction > 1.0 - EPSILON && verticalFraction > 1.0 - EPSILON -> {
val direction = LineSegment(with(visibleArea) { Point(p3x, p3y) }, pin).slope() - 90
PinPointerPose(PinPointerPose.Side.BOTTOM, 1.0, direction)
}
// Sides
horizontalFraction < EPSILON -> {
val direction = (topBorder.slope() - 180) - 90
PinPointerPose(PinPointerPose.Side.LEFT, verticalFraction, direction)
}
horizontalFraction > 1.0 - EPSILON -> {
val direction = topBorder.slope() - 90
PinPointerPose(PinPointerPose.Side.RIGHT, verticalFraction, direction)
}
verticalFraction < EPSILON -> {
val direction = (leftBorder.slope() - 180) - 90
PinPointerPose(PinPointerPose.Side.TOP, horizontalFraction, direction)
}
verticalFraction > 1.0 - EPSILON -> {
val direction = leftBorder.slope() - 90
PinPointerPose(PinPointerPose.Side.BOTTOM, horizontalFraction, direction)
}
// Pin is inside the area
else -> throw IllegalArgumentException("Pin lies inside the visible area")
visibleArea.top().fractionOfIntersectionWith(centroidPinSegment)?.let { fraction ->
return PinPointerPose(PinPointerPose.Side.TOP, fraction, direction)
}
visibleArea.right().fractionOfIntersectionWith(centroidPinSegment)?.let { fraction ->
return PinPointerPose(PinPointerPose.Side.RIGHT, fraction, direction)
}
visibleArea.bottom().fractionOfIntersectionWith(centroidPinSegment)?.let { fraction ->
return PinPointerPose(PinPointerPose.Side.BOTTOM, fraction, direction)
}
visibleArea.left().fractionOfIntersectionWith(centroidPinSegment)?.let { fraction ->
return PinPointerPose(PinPointerPose.Side.LEFT, fraction, direction)
}

throw IllegalArgumentException("Pin lies inside the visible area")
}
23 changes: 20 additions & 3 deletions app/src/main/java/ru/spbu/depnav/utils/map/LineSegment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,27 @@ data class LineSegment(val p1: Point, val p2: Point) {
fun containsProjectionOf(p: Point) = fractionOfProjectionOf(p) in 0.0..1.0

/**
* Returns the fraction from the start of this segment to its point that is the closest to the
* specified point.
* Returns the fraction from the start of this segment to its intersection point with the other
* segment if such point exists, or null otherwise.
*/
fun fractionOfClosestPointTo(p: Point) = fractionOfProjectionOf(p).coerceIn(0.0, 1.0)
fun fractionOfIntersectionWith(l: LineSegment): Double? {
// See https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line_segment
val denominator = (p1.x - p2.x) * (l.p1.y - l.p2.y) - (p1.y - p2.y) * (l.p1.x - l.p2.x)

val numerator1 = (p1.x - l.p1.x) * (l.p1.y - l.p2.y) - (p1.y - l.p1.y) * (l.p1.x - l.p2.x)
val t1 = numerator1 / denominator
if (t1 < 0 || t1 > 1) {
return null
}

val numerator2 = (p1.x - p2.x) * (p1.y - l.p1.y) - (p1.y - p2.y) * (p1.x - l.p1.x)
val t2 = -(numerator2 / denominator)
if (t2 < 0 || t2 > 1) {
return null
}

return t1
}

private fun fractionOfProjectionOf(p: Point): Double {
val vecP1ToP2 = Point(p2.x - p1.x, p2.y - p1.y)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/ru/spbu/depnav/utils/map/VisibleArea.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,31 @@ fun MapState.rectangularVisibleArea(
return VisibleArea(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y)
}

/**
* Centroid of the area.
*/
fun VisibleArea.centroid() = Point((p1x + p3x) / 2, (p1y + p3y) / 2)

/**
* Top border of the area.
*/
fun VisibleArea.top() = LineSegment(Point(p1x, p1y), Point(p2x, p2y))

/**
* Bottom border of the area.
*/
fun VisibleArea.bottom() = LineSegment(Point(p4x, p4y), Point(p3x, p3y))

/**
* Left border of the area.
*/
fun VisibleArea.left() = LineSegment(Point(p1x, p1y), Point(p4x, p4y))

/**
* Right border of the area.
*/
fun VisibleArea.right() = LineSegment(Point(p2x, p2y), Point(p3x, p3y))

/**
* Returns true if the provided point lies inside this area, or false otherwise.
*
Expand Down

0 comments on commit eb0f79d

Please sign in to comment.