summaryrefslogtreecommitdiff
path: root/android-app/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'android-app/app/src')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt62
1 files changed, 43 insertions, 19 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt
index 61c34ad..aba8027 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt
@@ -22,6 +22,9 @@ import kotlin.random.Random
* Speed is scaled to the visible viewport so the animation looks consistent at any
* zoom level: a particle at reference wind (10 kt) crosses ~30% of the screen in
* MAX_AGE seconds.
+ *
+ * Longitude arithmetic is done in a west-relative [0, lonSpan] coordinate space
+ * so that viewports crossing the antimeridian (±180°) work correctly.
*/
class ParticleWindView @JvmOverloads constructor(
context: Context,
@@ -74,14 +77,21 @@ class ParticleWindView @JvmOverloads constructor(
lastFrameNs = now
val r = m.projection.visibleRegion
- val corners = listOfNotNull(r.nearLeft, r.nearRight, r.farLeft, r.farRight)
- if (corners.size < 4) { postInvalidateOnAnimation(); return }
- val latSouth = corners.minOf { it.latitude }.toFloat()
- val latNorth = corners.maxOf { it.latitude }.toFloat()
- val lonWest = corners.minOf { it.longitude }.toFloat()
- val lonEast = corners.maxOf { it.longitude }.toFloat()
+ val nL = r.nearLeft ?: run { postInvalidateOnAnimation(); return }
+ val nR = r.nearRight ?: run { postInvalidateOnAnimation(); return }
+ val fL = r.farLeft ?: run { postInvalidateOnAnimation(); return }
+ val fR = r.farRight ?: run { postInvalidateOnAnimation(); return }
+
+ val latSouth = minOf(nL.latitude, nR.latitude, fL.latitude, fR.latitude).toFloat()
+ val latNorth = maxOf(nL.latitude, nR.latitude, fL.latitude, fR.latitude).toFloat()
val latRange = latNorth - latSouth
- val lonRange = lonEast - lonWest
+
+ // West edge = left screen side; east edge = right screen side.
+ // Using screen-ordered corners handles antimeridian crossing correctly.
+ val lonWest = minOf(nL.longitude, fL.longitude).toFloat()
+ val lonEast = maxOf(nR.longitude, fR.longitude).toFloat()
+ // Span wraps around antimeridian when lonEast < lonWest (e.g. Pacific viewport)
+ val lonSpan = if (lonEast >= lonWest) lonEast - lonWest else lonEast - lonWest + 360f
// Speed scale: at 10kt a particle crosses 30% of viewport in MAX_AGE seconds.
val speedScale = latRange * 0.03f / MAX_AGE
@@ -102,15 +112,23 @@ class ParticleWindView @JvmOverloads constructor(
for (i in 0 until N) {
particleLat[i] += dlat
particleLon[i] += dlon
+ // Wrap longitude into [-180, 180] after movement
+ if (particleLon[i] > 180f) particleLon[i] -= 360f
+ else if (particleLon[i] < -180f) particleLon[i] += 360f
particleAge[i] += dt
+ // Normalize particle longitude relative to lonWest into [0, 360)
+ // so the antimeridian-spanning bounds check works correctly.
+ val normLon = ((particleLon[i] - lonWest + 360f) % 360f)
val needsRespawn = particleAge[i] > MAX_AGE
|| particleLat[i] < latSouth || particleLat[i] > latNorth
- || particleLon[i] < lonWest || particleLon[i] > lonEast
+ || normLon > lonSpan
if (needsRespawn) {
particleLat[i] = latSouth + Random.nextFloat() * latRange
- particleLon[i] = lonWest + Random.nextFloat() * lonRange
+ var newLon = lonWest + Random.nextFloat() * lonSpan
+ if (newLon > 180f) newLon -= 360f
+ particleLon[i] = newLon
particleAge[i] = Random.nextFloat() * MAX_AGE
continue
}
@@ -119,8 +137,8 @@ class ParticleWindView @JvmOverloads constructor(
LatLng(particleLat[i].toDouble(), particleLon[i].toDouble())
)
- val alpha = ((1f - particleAge[i] / MAX_AGE) * 200).toInt().coerceIn(30, 200)
- paint.color = Color.argb(alpha, 120, 200, 255)
+ val alpha = ((1f - particleAge[i] / MAX_AGE) * 220).toInt().coerceIn(40, 220)
+ paint.color = Color.argb(alpha, 255, 255, 255)
canvas.drawLine(pt.x - tailDx, pt.y - tailDy, pt.x, pt.y, paint)
}
@@ -140,17 +158,23 @@ class ParticleWindView @JvmOverloads constructor(
private fun scatter(m: MapLibreMap) {
val r = m.projection.visibleRegion
- val corners = listOfNotNull(r.nearLeft, r.nearRight, r.farLeft, r.farRight)
- if (corners.size < 4) return
- val latSouth = corners.minOf { it.latitude }.toFloat()
- val latNorth = corners.maxOf { it.latitude }.toFloat()
- val lonWest = corners.minOf { it.longitude }.toFloat()
- val lonEast = corners.maxOf { it.longitude }.toFloat()
+ val nL = r.nearLeft ?: return
+ val nR = r.nearRight ?: return
+ val fL = r.farLeft ?: return
+ val fR = r.farRight ?: return
+
+ val latSouth = minOf(nL.latitude, nR.latitude, fL.latitude, fR.latitude).toFloat()
+ val latNorth = maxOf(nL.latitude, nR.latitude, fL.latitude, fR.latitude).toFloat()
val latRange = latNorth - latSouth
- val lonRange = lonEast - lonWest
+ val lonWest = minOf(nL.longitude, fL.longitude).toFloat()
+ val lonEast = maxOf(nR.longitude, fR.longitude).toFloat()
+ val lonSpan = if (lonEast >= lonWest) lonEast - lonWest else lonEast - lonWest + 360f
+
for (i in 0 until N) {
particleLat[i] = latSouth + Random.nextFloat() * latRange
- particleLon[i] = lonWest + Random.nextFloat() * lonRange
+ var newLon = lonWest + Random.nextFloat() * lonSpan
+ if (newLon > 180f) newLon -= 360f
+ particleLon[i] = newLon
particleAge[i] = Random.nextFloat() * MAX_AGE // stagger so no mass respawn
}
scattered = true