Skip to main content

Module wind

Module wind 

Source
Expand description

Wind models for trajectory calculations.

All wind here uses world-frame McCoy coordinates (x downrange, y up, z right) and the wind-FROM direction convention: 0° is a headwind, 90° wind from the right, 180° a tailwind and 270° wind from the left. wind_vector builds a vector from a speed and direction.

  • WindSegment and WindSock: downrange-segmented wind, the model the solvers use. Segment speeds are km/h.

§Wind fields

WindField is a deterministic, stateless wind field: the wind depends only on the time and position passed in, so integrator substeps, snapshots and seeded reproduction are exact. Speeds are m/s.

The profile fields read only the height (position_m.y) and have no vertical wind. ProfileField uses whatever datum its levels’ altitudes use. LogProfile and EkmanProfile need the height above the ground. HeightOffset reads any field at a shifted height; its offset is the height of the caller’s y = 0 in the field’s datum. In a frame whose y is height above sea level, a log or Ekman profile takes minus the ground elevation and a sea-level sounding needs no offset. TrajectorySolver measures y from the level muzzle_height is measured from and takes the ground to run along the line of sight, so a log or Ekman profile takes the bore’s real height above the ground minus muzzle_height (nothing when muzzle_height is the real bore height) on any shot. Sea level stays level: a sea-level sounding takes AtmosphericConditions::altitude on a level shot and the solver’s own air altitude on an inclined one; HeightOffset has the details. Convert in one place only. Constructors validate their inputs and return a typed WindFieldError.

use ballistics_engine::wind::{ProfileField, ProfileLevel, WindField};
use nalgebra::Vector3;

let profile = ProfileField::new(vec![
    ProfileLevel { altitude_m: 0.0, speed_mps: 2.0, direction_from_deg: 90.0 },
    ProfileLevel { altitude_m: 100.0, speed_mps: 6.0, direction_from_deg: 90.0 },
])
.unwrap();
// Halfway up, 4 m/s from the right, blowing toward -z.
let w = profile.wind(0.0, Vector3::new(500.0, 50.0, 0.0));
assert!((w.z + 4.0).abs() < 1e-12 && w.x.abs() < 1e-12);

Structs§

ConstantField
Uniform wind everywhere.
EkmanProfile
Ekman spiral (constant eddy viscosity K; Holton’s classic solution): u = G(1 − e^(−γz)·cos(γz)), v = sign(f)·G·e^(−γz)·sin(γz), γ = sqrt(|f|/2K), f = 2Ω·sin(latitude). Zero at the surface, approaches the geostrophic wind aloft, rotated to direction_from_deg.
HeightOffset
Reads a field at a shifted height, so a field and its caller can use different height datums: wind(t, p) is the inner field’s wind at p.y + offset_m, with time and the horizontal position unchanged.
LogProfile
Log-law boundary layer: u(z) = u_ref · ln(z/z0) / ln(z_ref/z0), z > z0; zero below z0. Constant direction with height.
ParsedWindDirection
A wind direction as entered at an input boundary (MBA-1367): the resolved degrees in the wind-FROM convention (0 = headwind, 90 = from the right — post-0.19.0), plus whether it was entered as a clock position. Clock positions are shooter-relative BY DEFINITION (12 o’clock = dead ahead), so was_clock is what lets the earth-fixed compass mode (MBA-1368) reject them.
ProfileField
Piecewise-linear vertical profile: speed interpolates linearly in altitude, direction along the shortest circular arc; nearest level holds outside the range.
ProfileLevel
One level of a vertical profile.
WindSegment
One downrange wind segment. vertical_mps (m/s, positive = updraft) feeds straight into the segment’s wind vector via wind_vector (MBA-728); boundary-layer shear scales horizontal wind only, so vertical passes through unscaled wherever shear is applied on top of a segment.
WindSegmentError
A malformed WindSegment rejected at a checked construction/solve boundary (MBA-1338).
WindSock
Wind condition handler for trajectory calculations

Enums§

WindDirectionParseError
Typed parse errors for parse_wind_direction (internal family; legacy String boundaries like parse_wind_segment_str convert at the boundary per the MBA-1338 layering).
WindFieldError
A wind-field constructor input that failed validation.
WindFieldParam
Which constructor input failed validation.
WindFieldRule
The rule a constructor input violated.
WindReference
Which frame wind directions are ENTERED in (MBA-1368). Shooter (the default) is today’s behavior: wind-FROM angles relative to the line of fire (0 = headwind). Compass treats every entered wind direction — the single direction AND every segment — as an absolute earth-fixed bearing (0 = north), derived shooter-relative once at the input boundary as bearing - shot_azimuth (wind-FROM both sides), so the physics downstream of wind_vector never sees a bearing.
WindSegmentField
Which WindSegment field failed validation (MBA-1338).
WindSegmentRule
The validation rule a WindSegment field violated (MBA-1338).

Traits§

WindField
A deterministic, stateless wind field: same time and position always give the same wind, so integrator substeps, snapshots and seeded reproduction are exact.

Functions§

compass_bearing_to_shooter_relative_deg
Earth-fixed compass bearing (degrees, 0 = north) -> shooter-relative wind-FROM degrees against the shot azimuth (MBA-1368), normalized to [0, 360). A wind FROM north (bearing 0) with the shot fired due north (azimuth 0) is a pure headwind (relative 0 — the 0.19.0 sign convention); a bearing 90 (from east) on a northbound shot is wind from the shooter’s RIGHT (relative 90).
compass_bearing_to_shooter_relative_rad
compass_bearing_to_shooter_relative_deg in radians (the solve-json v1 surface is SI/radians end to end).
parse_wind_direction
Parses one wind-direction token (MBA-1367): a bare number is DEGREES exactly as it always was; marked clock forms are <H>oc (e.g. 3oc) and <H>h<MM> (e.g. 10h30), case-insensitive, mapping to (H % 12) * 30 + MM * 0.5 degrees in the wind-FROM convention (12 o’clock = headwind = 0°, post-0.19.0). H must be 1-12 and MM 0-59. The colon form <H>:<MM> is deliberately NOT accepted here — it is legal only where a token cannot be colon-delimited grammar, i.e. standalone flags (parse_wind_direction_standalone); inside --wind-segment, 10:30 keeps its historical SPEED:ANGLE meaning.
parse_wind_direction_standalone
parse_wind_direction for STANDALONE flag contexts (--wind-direction), which additionally accept the <H>:<MM> colon clock form (10:30 = 315°) — legal there because a flag value is never colon-delimited grammar (MBA-1367).
parse_wind_segment_str
Parse a "SPEED:ANGLE:UNTIL_DISTANCE[:VERTICAL]" string into a WindSegment (speed_kmh, angle_deg, until_distance_m, vertical_mps).
parse_wind_segment_str_detailed
parse_wind_segment_str plus clock-form provenance (MBA-1367/MBA-1368): the second tuple element is true when the ANGLE token used a marked clock form (3oc, 10h30) rather than plain degrees — earth-fixed compass mode must reject clock positions, which are shooter-relative by definition. Error strings stay in the legacy String family (pre-MBA-1338 boundary): the typed WindDirectionParseError converts to a plain message here.
validate_wind_segments
Validate every segment, reporting the first violation as a typed error carrying the caller’s segment index, the offending field, and the violated rule (MBA-1338).
wind_vector
THE wind-vector builder (McCoy frame: x downrange, y up, z right). Horizontal wind uses the wind-FROM convention (0 = headwind, PI/2 = from the right); vertical_mps is positive-updraft and lands on y UNSCALED — boundary-layer shear models scale horizontal flow only (MBA-728 decision).