feat: move stuff to numerics
This commit is contained in:
parent
465ad00f7b
commit
b7fd7046ff
5 changed files with 119 additions and 29 deletions
|
|
@ -23,3 +23,16 @@ func NasaDensity(alt float64) float64 {
|
|||
}
|
||||
return pressure / (0.2869 * (temp + 273.1))
|
||||
}
|
||||
|
||||
// DragTerminalVelocity returns the vertical velocity (m/s, negative = downward)
|
||||
// of a parachute descent at the given altitude. seaLevelRate is the descent
|
||||
// speed at sea level (positive m/s); the rate grows with altitude as the
|
||||
// thinner air provides less drag:
|
||||
//
|
||||
// v = -k / sqrt(rho(alt)), k = seaLevelRate * 1.1045
|
||||
//
|
||||
// Matches Tawhiri's drag_descent.
|
||||
func DragTerminalVelocity(seaLevelRate, alt float64) float64 {
|
||||
k := seaLevelRate * 1.1045
|
||||
return -k / math.Sqrt(NasaDensity(alt))
|
||||
}
|
||||
|
|
|
|||
58
internal/numerics/motion_test.go
Normal file
58
internal/numerics/motion_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package numerics
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddGeo(t *testing.T) {
|
||||
// Rates sum component-wise with no longitude wrapping.
|
||||
got := AddGeo(GeoVec{Lat: 1, Lng: 350, Altitude: 2}, GeoVec{Lat: 3, Lng: 20, Altitude: 4})
|
||||
want := GeoVec{Lat: 4, Lng: 370, Altitude: 6}
|
||||
if got != want {
|
||||
t.Errorf("AddGeo = %+v, want %+v (no wrap on rates)", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindToGeoRate(t *testing.T) {
|
||||
// Pure eastward 10 m/s at the equator, sea level.
|
||||
dLat, dLng := WindToGeoRate(10, 0, 0, 0)
|
||||
wantLng := (180.0 / math.Pi) * 10.0 / EarthRadius
|
||||
if math.Abs(dLat) > 1e-15 {
|
||||
t.Errorf("dLat = %v, want 0", dLat)
|
||||
}
|
||||
if math.Abs(dLng-wantLng) > 1e-15 {
|
||||
t.Errorf("dLng = %v, want %v", dLng, wantLng)
|
||||
}
|
||||
|
||||
// Northward 5 m/s at 60°N: dLat independent of longitude scaling.
|
||||
dLat, _ = WindToGeoRate(0, 5, 60, 0)
|
||||
wantLat := (180.0 / math.Pi) * 5.0 / EarthRadius
|
||||
if math.Abs(dLat-wantLat) > 1e-15 {
|
||||
t.Errorf("dLat at 60N = %v, want %v", dLat, wantLat)
|
||||
}
|
||||
|
||||
// cos(lat) factor makes eastward motion span more degrees nearer the poles.
|
||||
_, dLngEq := WindToGeoRate(10, 0, 0, 0)
|
||||
_, dLng60 := WindToGeoRate(10, 0, 60, 0)
|
||||
if dLng60 <= dLngEq {
|
||||
t.Errorf("eastward deg/s should grow with latitude: eq=%v 60N=%v", dLngEq, dLng60)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDragTerminalVelocity(t *testing.T) {
|
||||
// Descent is downward (negative) and faster (more negative) at altitude
|
||||
// where the air is thinner.
|
||||
sea := DragTerminalVelocity(5, 0)
|
||||
high := DragTerminalVelocity(5, 20000)
|
||||
if sea >= 0 {
|
||||
t.Errorf("sea-level rate = %v, want negative (downward)", sea)
|
||||
}
|
||||
if high >= sea {
|
||||
t.Errorf("expected faster descent at altitude: sea=%v high=%v", sea, high)
|
||||
}
|
||||
// Sanity: at sea level rho≈1.225, so v ≈ -5*1.1045/sqrt(1.225) ≈ -4.99 m/s.
|
||||
if math.Abs(sea-(-5*1.1045/math.Sqrt(NasaDensity(0)))) > 1e-12 {
|
||||
t.Errorf("sea-level formula mismatch: %v", sea)
|
||||
}
|
||||
}
|
||||
|
|
@ -64,3 +64,26 @@ func LngLerp(a, b, l float64) float64 {
|
|||
func Lerp(a, b, l float64) float64 {
|
||||
return (1-l)*a + l*b
|
||||
}
|
||||
|
||||
// AddGeo returns the component-wise sum a+b without longitude wrapping. Use it
|
||||
// to combine derivative (rate) vectors — rates accumulate linearly, unlike
|
||||
// positions, which wrap via GeoAdd.
|
||||
func AddGeo(a, b GeoVec) GeoVec {
|
||||
return GeoVec{Lat: a.Lat + b.Lat, Lng: a.Lng + b.Lng, Altitude: a.Altitude + b.Altitude}
|
||||
}
|
||||
|
||||
// EarthRadius is the spherical Earth radius (metres) used for horizontal
|
||||
// motion, matching the reference Tawhiri implementation.
|
||||
const EarthRadius = 6371009.0
|
||||
|
||||
// WindToGeoRate converts eastward (u) and northward (v) wind in m/s at the
|
||||
// given latitude (deg) and altitude (m) into the geographic rate in deg/s on a
|
||||
// spherical Earth. The returned dLng diverges near the poles as cos(lat) → 0.
|
||||
func WindToGeoRate(u, v, lat, alt float64) (dLat, dLng float64) {
|
||||
const degPerRad = 180.0 / math.Pi
|
||||
const piOver180 = math.Pi / 180.0
|
||||
r := EarthRadius + alt
|
||||
dLat = degPerRad * v / r
|
||||
dLng = degPerRad * u / (r * math.Cos(lat*piOver180))
|
||||
return dLat, dLng
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue