angle.hpp¶
Angle — a heading on SE(2). The one type that owns angle wrapping, so the "degrees into cos/sin" and "359° vs -1°" bug classes are impossible by construction.
This header declares 1 type (11 members).
Extracted from include/shulib/math/angle.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.
Contents¶
class AnglekPiAngleradiansdegreesradians (overload 2)degrees (overload 2)errorTooperator+operator-operator- (overload 2)approxEqual
class Angle¶
A heading on SE(2), and the one type that owns angle wrapping — so the "degrees into cos/sin" and "359° vs -1°" bug classes cannot be written. Stored in RADIANS, always normalized to (-π, π], CCW-positive to match the field frame. The interval is half-open at the bottom: an Angle NEVER holds -π, it holds +π instead, which is what makes the exact-antipodal case in errorTo() resolve deterministically. Degrees appear only at this API boundary; nothing downstream computes in them. Construction goes exclusively through radians() / degrees(), which reject non-finite input, so a NaN heading is unrepresentable rather than merely unlikely. A default-constructed Angle is the zero heading (field +X). The conventions above are LOCKED (Freeze F3).
class, declared at include/shulib/math/angle.hpp:35.
Angle::kPi¶
Pi, exact to double precision. Public so tests/callers share one constant.
field, declared at include/shulib/math/angle.hpp:38.
Angle::Angle¶
The zero heading.
function, declared at include/shulib/math/angle.hpp:41.
Angle::radians¶
Construct from radians; input must be finite. Result is wrapped to (-π, π].
function, declared at include/shulib/math/angle.hpp:44.
Angle::degrees¶
Construct from degrees; input must be finite. Result is wrapped to (-π, π].
function, declared at include/shulib/math/angle.hpp:50.
Angle::radians (overload 2)¶
The heading in radians, already normalized to (-π, π] — never a raw accumulated value, so it is safe to hand straight to cos/sin.
function, declared at include/shulib/math/angle.hpp:57.
Angle::degrees (overload 2)¶
The same heading in degrees, therefore always in (-180, 180]. For humans and telemetry; no library computation reads this.
function, declared at include/shulib/math/angle.hpp:60.
Angle::errorTo¶
Shortest signed rotation FROM this heading TO target, in (-π, π] radians. The exact-antipodal case resolves to +π (never -π) — see Freeze F3.
function, declared at include/shulib/math/angle.hpp:64.
Angle::operator+¶
This heading rotated FORWARD (CCW) by o, re-wrapped. There is no separate rotation type, so the right operand is read as a rotation: 170° + 20° is -170°, not 190°.
function, declared at include/shulib/math/angle.hpp:70.
Angle::operator-¶
This heading rotated BACKWARD by o, re-wrapped. It yields an Angle, not an error term — for the signed rotation a controller should act on use o.errorTo(*this), which is numerically identical but returns a plain double that cannot be mistaken for a heading.
function, declared at include/shulib/math/angle.hpp:74.
Angle::operator- (overload 2)¶
The mirrored heading — the same rotation taken clockwise. Watch the boundary: -π is not representable, so negating +π gives back +π.
function, declared at include/shulib/math/angle.hpp:77.
Angle::approxEqual¶
True if the shortest error to o is within tolRad. Never compares raw doubles with ==, and is correct across the wrap boundary.
function, declared at include/shulib/math/angle.hpp:81.
Design commentary, from the header¶
The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.
The header’s own reasoning — 15 lines
Angle — a heading on SE(2). The one type that owns angle wrapping, so the
"degrees into cos/sin" and "359° vs -1°" bug classes are impossible by
construction.
Conventions (LOCKED — master plan §7 / Freeze F3):
* Internal unit: RADIANS. Degrees appear only at this API boundary.
* Normalization interval: (-π, π] — half-open at -π; +π is the canonical
boundary value, so an angle NEVER stores -π (it stores +π instead).
* errorTo() returns the shortest signed rotation, also in (-π, π]; the
exact-antipodal case (|Δ| == π) resolves deterministically to +π, never -π.
* CCW-positive, matching the field frame.
All construction goes through radians()/degrees(), which reject non-finite
input — a NaN can never enter the type.
Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.