engine refactor
This commit is contained in:
parent
9e663db9dc
commit
81b8e763bd
37 changed files with 3532 additions and 1639 deletions
|
|
@ -1,40 +1,42 @@
|
|||
package engine
|
||||
|
||||
// MaxAltitude triggers when altitude rises above Limit (in metres).
|
||||
// Used as the burst condition for ascent stages.
|
||||
type MaxAltitude struct {
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Altitude triggers when the balloon altitude satisfies Op against Limit.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// Altitude{Op: OpGreaterEqual, Limit: 30000} — burst at 30 km
|
||||
// Altitude{Op: OpLessEqual, Limit: 0} — sea-level descent termination
|
||||
type Altitude struct {
|
||||
Op Operator
|
||||
Limit float64
|
||||
On Action
|
||||
}
|
||||
|
||||
func (c MaxAltitude) Name() string { return "max_altitude" }
|
||||
func (c MaxAltitude) Violated(_ float64, s State) bool { return s.Altitude >= c.Limit }
|
||||
func (c MaxAltitude) Action() Action { return c.On }
|
||||
func (c Altitude) Name() string {
|
||||
return fmt.Sprintf("altitude %s %g", c.Op, c.Limit)
|
||||
}
|
||||
func (c Altitude) Violated(_ float64, s State) bool { return c.Op.Test(s.Altitude, c.Limit) }
|
||||
func (c Altitude) Action() Action { return c.On }
|
||||
|
||||
// MinAltitude triggers when altitude falls at or below Limit (in metres).
|
||||
// With Limit=0 this is the "sea level" terminator.
|
||||
type MinAltitude struct {
|
||||
// Time triggers when the integration time t (UNIX seconds) satisfies Op
|
||||
// against Limit.
|
||||
type Time struct {
|
||||
Op Operator
|
||||
Limit float64
|
||||
On Action
|
||||
}
|
||||
|
||||
func (c MinAltitude) Name() string { return "min_altitude" }
|
||||
func (c MinAltitude) Violated(_ float64, s State) bool { return s.Altitude <= c.Limit }
|
||||
func (c MinAltitude) Action() Action { return c.On }
|
||||
func (c Time) Name() string { return fmt.Sprintf("time %s %g", c.Op, c.Limit) }
|
||||
func (c Time) Violated(t float64, _ State) bool { return c.Op.Test(t, c.Limit) }
|
||||
func (c Time) Action() Action { return c.On }
|
||||
|
||||
// MaxTime triggers when t exceeds Limit (UNIX seconds). Used as a stop
|
||||
// condition for float profiles.
|
||||
type MaxTime struct {
|
||||
Limit float64
|
||||
On Action
|
||||
}
|
||||
|
||||
func (c MaxTime) Name() string { return "max_time" }
|
||||
func (c MaxTime) Violated(t float64, _ State) bool { return t > c.Limit }
|
||||
func (c MaxTime) Action() Action { return c.On }
|
||||
|
||||
// TerrainContact triggers when altitude has dropped at or below ground level.
|
||||
// Equivalent to Tawhiri's elevation termination.
|
||||
// TerrainContact triggers when the ground elevation exceeds the balloon's
|
||||
// altitude — i.e. the balloon has hit the ground.
|
||||
type TerrainContact struct {
|
||||
Provider TerrainProvider
|
||||
On Action
|
||||
|
|
@ -45,3 +47,103 @@ func (c TerrainContact) Violated(_ float64, s State) bool {
|
|||
return c.Provider.Elevation(s.Lat, s.Lng) > s.Altitude
|
||||
}
|
||||
func (c TerrainContact) Action() Action { return c.On }
|
||||
|
||||
// PolygonMode selects whether Polygon fires when the balloon is inside or
|
||||
// outside the configured polygon.
|
||||
type PolygonMode int
|
||||
|
||||
const (
|
||||
// PolygonInside fires when (lat, lng) lies inside the polygon — useful
|
||||
// for "must not enter restricted airspace".
|
||||
PolygonInside PolygonMode = iota
|
||||
// PolygonOutside fires when (lat, lng) lies outside the polygon —
|
||||
// useful for "must remain over the test range".
|
||||
PolygonOutside
|
||||
)
|
||||
|
||||
// PolygonVertex is one vertex of a geographic polygon. Latitudes are in
|
||||
// degrees [-90, 90]; longitudes in degrees [0, 360) or [-180, 180]
|
||||
// (callers normalise — see Polygon.Violated).
|
||||
type PolygonVertex struct {
|
||||
Lat float64
|
||||
Lng float64
|
||||
}
|
||||
|
||||
// Polygon is a constraint over a geographic polygon. The polygon is
|
||||
// considered closed (last vertex connects to the first) and is interpreted
|
||||
// in plate-carrée (rectangular lat/lng) coordinates with longitude
|
||||
// wrap-around handling.
|
||||
//
|
||||
// Edges crossing the 180/-180 antimeridian are split via longitude
|
||||
// normalisation against the polygon's centroid: callers that need
|
||||
// great-circle accuracy should clip their polygon along the antimeridian
|
||||
// before submitting.
|
||||
type Polygon struct {
|
||||
Vertices []PolygonVertex
|
||||
Mode PolygonMode
|
||||
On Action
|
||||
|
||||
// Label, if set, is returned by Name. Defaults to "polygon_inside" or
|
||||
// "polygon_outside" based on Mode.
|
||||
Label string
|
||||
}
|
||||
|
||||
func (c Polygon) Name() string {
|
||||
if c.Label != "" {
|
||||
return c.Label
|
||||
}
|
||||
if c.Mode == PolygonOutside {
|
||||
return "polygon_outside"
|
||||
}
|
||||
return "polygon_inside"
|
||||
}
|
||||
func (c Polygon) Action() Action { return c.On }
|
||||
|
||||
// Violated reports whether the state satisfies the polygon-containment rule.
|
||||
func (c Polygon) Violated(_ float64, s State) bool {
|
||||
if len(c.Vertices) < 3 {
|
||||
return false
|
||||
}
|
||||
in := pointInPolygon(s.Lat, s.Lng, c.Vertices)
|
||||
if c.Mode == PolygonInside {
|
||||
return in
|
||||
}
|
||||
return !in
|
||||
}
|
||||
|
||||
// pointInPolygon implements the ray-casting algorithm in lat/lng space.
|
||||
//
|
||||
// All vertices and the query point are normalised to within 180° of
|
||||
// verts[0] before testing, so a polygon spanning the antimeridian is
|
||||
// handled correctly as long as the polygon itself spans no more than 180°
|
||||
// in longitude.
|
||||
func pointInPolygon(lat, lng float64, verts []PolygonVertex) bool {
|
||||
if len(verts) == 0 {
|
||||
return false
|
||||
}
|
||||
ref := verts[0].Lng
|
||||
qx := normLng(lng, ref)
|
||||
|
||||
inside := false
|
||||
n := len(verts)
|
||||
for i, j := 0, n-1; i < n; j, i = i, i+1 {
|
||||
yi, yj := verts[i].Lat, verts[j].Lat
|
||||
xi := normLng(verts[i].Lng, ref)
|
||||
xj := normLng(verts[j].Lng, ref)
|
||||
|
||||
if (yi > lat) != (yj > lat) {
|
||||
xIntersect := (xj-xi)*(lat-yi)/(yj-yi) + xi
|
||||
if qx < xIntersect {
|
||||
inside = !inside
|
||||
}
|
||||
}
|
||||
}
|
||||
return inside
|
||||
}
|
||||
|
||||
// normLng rewrites v so that it lies within 180° of ref. With ref=10 and
|
||||
// v=350, normLng returns -10.
|
||||
func normLng(v, ref float64) float64 {
|
||||
diff := math.Mod(v-ref+540, 360) - 180
|
||||
return ref + diff
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue