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.
WindSegmentandWindSock: 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.
ConstantField: uniform wind everywhere.ProfileField: a piecewise-linear vertical profile ofProfileLevels. Speed interpolates linearly in altitude and direction along the shortest arc; the nearest level holds outside the range.LogProfile: a log-law boundary layer, zero at and below the roughness length.EkmanProfile: the Ekman spiral for a constant eddy viscosity, calm at the surface and geostrophic aloft.WindSockis also aWindField: the wind atposition_m.x, exactlyWindSock::vector_for_range_stateless.
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§
- Constant
Field - Uniform wind everywhere.
- Ekman
Profile - 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. - Height
Offset - 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 atp.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.
- Parsed
Wind Direction - 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_clockis what lets the earth-fixed compass mode (MBA-1368) reject them. - Profile
Field - Piecewise-linear vertical profile: speed interpolates linearly in altitude, direction along the shortest circular arc; nearest level holds outside the range.
- Profile
Level - One level of a vertical profile.
- Wind
Segment - One downrange wind segment.
vertical_mps(m/s, positive = updraft) feeds straight into the segment’s wind vector viawind_vector(MBA-728); boundary-layer shear scales horizontal wind only, so vertical passes through unscaled wherever shear is applied on top of a segment. - Wind
Segment Error - A malformed
WindSegmentrejected at a checked construction/solve boundary (MBA-1338). - Wind
Sock - Wind condition handler for trajectory calculations
Enums§
- Wind
Direction Parse Error - Typed parse errors for
parse_wind_direction(internal family; legacyStringboundaries likeparse_wind_segment_strconvert at the boundary per the MBA-1338 layering). - Wind
Field Error - A wind-field constructor input that failed validation.
- Wind
Field Param - Which constructor input failed validation.
- Wind
Field Rule - The rule a constructor input violated.
- Wind
Reference - 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).Compasstreats 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 asbearing - shot_azimuth(wind-FROM both sides), so the physics downstream ofwind_vectornever sees a bearing. - Wind
Segment Field - Which
WindSegmentfield failed validation (MBA-1338). - Wind
Segment Rule - The validation rule a
WindSegmentfield violated (MBA-1338).
Traits§
- Wind
Field - 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_degin 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.5degrees 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:30keeps its historical SPEED:ANGLE meaning. - parse_
wind_ direction_ standalone parse_wind_directionfor 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 aWindSegment(speed_kmh, angle_deg, until_distance_m, vertical_mps). - parse_
wind_ segment_ str_ detailed parse_wind_segment_strplus 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 legacyStringfamily (pre-MBA-1338 boundary): the typedWindDirectionParseErrorconverts 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_mpsis positive-updraft and lands on y UNSCALED — boundary-layer shear models scale horizontal flow only (MBA-728 decision).