diff options
| author | Claude <noreply@anthropic.com> | 2026-04-11 02:09:07 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-04-11 02:09:07 +0000 |
| commit | a287abc937eb036271717e0867398fb68711c51e (patch) | |
| tree | a7448ce36ea16e3ee0ce8ca4067869db3f528526 /android-app/app/src/main/kotlin/org | |
| parent | fc7192288109fc3542670cbeeaebe0de2a75eb74 (diff) | |
Improve particle wind animation: longer lifetimes, wind-scaled count, zoom scatter
- MAX_AGE 8s → 25s: particles live much longer, drastically less twinkle
- activeN scales 60→300 with wind speed (0–25 kt): calm = sparse drift,
strong wind = dense streaks
- Zoom-out detection: scatter() fires immediately when latRange grows >1.8×,
instantly filling the new viewport instead of waiting for old particles to die
- Alpha uses sine curve (sin(π·age/MAX_AGE)) instead of linear fade:
particles ease in from birth, peak at mid-life, and ease out — no bright
birth flash that caused the twinkle effect
https://claude.ai/code/session_01HXPjBsogsJVRwCiekDGkJX
Diffstat (limited to 'android-app/app/src/main/kotlin/org')
| -rw-r--r-- | android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt | 2 | ||||
| -rw-r--r-- | android-app/app/src/main/kotlin/org/terst/nav/ui/map/ParticleWindView.kt | 20 |
2 files changed, 18 insertions, 4 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt index 996892e..e6355bd 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt @@ -296,6 +296,8 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener { private fun hideOverlays() { fragmentContainer.visibility = View.GONE fabRecordTrack.visibility = View.VISIBLE + // Re-apply cached conditions in case they arrived while the overlay was covering the sheet + lastConditions?.let { applyConditions(it) } } override fun onQuitRequested() { 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 24b25db..26d9ea2 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 @@ -8,6 +8,7 @@ import android.util.AttributeSet import android.view.View import org.maplibre.android.geometry.LatLng import org.maplibre.android.maps.MapLibreMap +import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin import kotlin.random.Random @@ -38,10 +39,12 @@ class ParticleWindView @JvmOverloads constructor( private var windSpeedKt = 0.0 private val N = 300 + private var activeN = 150 // wind-speed-dependent; updated in setWind() private val particleLat = FloatArray(N) private val particleLon = FloatArray(N) private val particleAge = FloatArray(N) - private val MAX_AGE = 8f // seconds before forced respawn + private val MAX_AGE = 25f // seconds before forced respawn + private var lastLatRange = 0f // for zoom-out scatter detection private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { strokeWidth = 3.5f @@ -62,6 +65,8 @@ class ParticleWindView @JvmOverloads constructor( fun setWind(dirFromDeg: Double, speedKt: Double) { windDirFromDeg = dirFromDeg windSpeedKt = speedKt + // Scale particle count: 60 at calm → 300 at 25+ kt + activeN = (60 + (speedKt.coerceIn(0.0, 25.0) / 25.0 * 240)).toInt() } // ── Rendering ──────────────────────────────────────────────────────────── @@ -86,6 +91,11 @@ class ParticleWindView @JvmOverloads constructor( val latNorth = maxOf(nL.latitude, nR.latitude, fL.latitude, fR.latitude).toFloat() val latRange = latNorth - latSouth + // If the user zoomed out significantly, immediately redistribute particles + // across the new viewport rather than waiting for them to drift to the edges. + if (lastLatRange > 0f && latRange > lastLatRange * 1.8f) scatter(m) + lastLatRange = latRange + // 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() @@ -109,7 +119,7 @@ class ParticleWindView @JvmOverloads constructor( val tailDx = sin(screenRad).toFloat() * TAIL_PX val tailDy = (-cos(screenRad)).toFloat() * TAIL_PX - for (i in 0 until N) { + for (i in 0 until activeN) { particleLat[i] += dlat particleLon[i] += dlon // Wrap longitude into [-180, 180] after movement @@ -137,7 +147,9 @@ class ParticleWindView @JvmOverloads constructor( LatLng(particleLat[i].toDouble(), particleLon[i].toDouble()) ) - val alpha = ((1f - particleAge[i] / MAX_AGE) * 220).toInt().coerceIn(40, 220) + // Sine curve: smooth fade-in from birth, peak at mid-life, smooth fade-out. + // No bright birth flash — particles ease in and ease out. + val alpha = (sin(PI * particleAge[i] / MAX_AGE) * 200).toInt().coerceIn(15, 200) paint.color = Color.argb(alpha, 30, 100, 255) canvas.drawLine(pt.x - tailDx, pt.y - tailDy, pt.x, pt.y, paint) @@ -170,7 +182,7 @@ class ParticleWindView @JvmOverloads constructor( 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) { + for (i in 0 until activeN) { particleLat[i] = latSouth + Random.nextFloat() * latRange var newLon = lonWest + Random.nextFloat() * lonSpan if (newLon > 180f) newLon -= 360f |
