Files
wrmath/wrmath/orientation.cc
Kyle Isom c47c91f418 Extend wrmath to match Rust library in astro-rs
Brings the C++ library in line with the Rust wrmath crate in astro-rs,
which extends this library with additional geometry and estimation
primitives. All changes generated with AI assistance (Claude Fable 5).

New headers:
- wrmath/geom/matrix.h: Matrix<T,M,N> with rank, det, inv, transpose,
  and matrix-vector/matrix-matrix multiplication
- wrmath/geom/coord2d.h: Polar<T> 2D polar coordinates with navigation
  convention (clockwise-positive heading)
- wrmath/geom/coord3d.h: Spherical<T> 3D spherical coordinates with
  yaw/pitch, slerp, great-circle path interpolation, and quaternion
  direction
- wrmath/estim/imu.h: IMU<T> for 6-DoF and 9-DoF (MARG) sensor fusion

Extensions to existing headers:
- math.h: Epsilon3/6/Max constants; AbsTolerance (NaN/inf-safe),
  AbsError, RotateRadians, Circumference
- vector.h: zero(), withEpsilon(), asArray(), fromArray/Eps(), map(),
  isNaN(), angle2() (signed), euclidist(), projectLower/Tail<M>(),
  x()/y()/z() accessors; fixed isParallel() to use unit-vector equality
  (matches Rust fix for macOS/arm64 acos domain issue)
- quaternion.h: lerp(), slerp() methods; jacobian() returning
  Matrix<T,3,4>; direction()
- filter/madgwick.h: beta gain field, setDeltaT(), setGain(),
  direction(), updateFrame2(), updateAngularOrientation2()
- orientation.h/.cc: RBearing3d/f, ABearing3d/f, CompassHeading3d/f

Infrastructure:
- C++ standard bumped to C++17 (required for std::optional)
- CMake include path fixed so source-relative includes work
- Umbrella headers (geom.h, filter.h) updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 22:34:49 -07:00

120 lines
2.3 KiB
C++

#include <optional>
#include <cmath>
#include "wrmath/geom/vector.h"
#include "wrmath/geom/orientation.h"
namespace wr {
namespace geom {
float
Heading2f(Vector2f vec)
{
return vec.angle(Basis2f[Basis_x]);
}
float
Heading3f(Vector3f vec)
{
Vector2f vec2f {vec[0], vec[1]};
return Heading2f(vec2f);
}
double
Heading2d(Vector2d vec)
{
return vec.angle(Basis2d[Basis_x]);
}
double
Heading3d(Vector3d vec)
{
Vector2d vec2d {vec[0], vec[1]};
return Heading2d(vec2d);
}
std::optional<double>
RBearing3d(Vector3d origin, Vector3d point)
{
if (origin.isZero() || point.isZero())
return std::nullopt;
Vector3d u_origin = origin.unitVector();
Vector3d u_point = point.unitVector();
double theta_h = std::atan2(u_origin[1], u_origin[0]);
double theta_d = std::atan2(u_point[1], u_point[0]);
double diff = theta_d - theta_h;
if (diff < 0.0) diff += 2.0 * M_PI;
return diff;
}
std::optional<float>
RBearing3f(Vector3f origin, Vector3f point)
{
if (origin.isZero() || point.isZero())
return std::nullopt;
Vector3f u_origin = origin.unitVector();
Vector3f u_point = point.unitVector();
float theta_h = std::atan2(u_origin[1], u_origin[0]);
float theta_d = std::atan2(u_point[1], u_point[0]);
float diff = theta_d - theta_h;
if (diff < 0.0f) diff += 2.0f * (float)M_PI;
return diff;
}
std::optional<double>
ABearing3d(Vector3d point)
{
if (point.isZero()) return std::nullopt;
Vector3d unit = point.unitVector();
double theta = std::atan2(unit[0], unit[1]);
if (theta < 0.0) theta += 2.0 * M_PI;
return theta;
}
std::optional<float>
ABearing3f(Vector3f point)
{
if (point.isZero()) return std::nullopt;
Vector3f unit = point.unitVector();
float theta = std::atan2(unit[0], unit[1]);
if (theta < 0.0f) theta += 2.0f * (float)M_PI;
return theta;
}
std::optional<double>
CompassHeading3d(Vector3d v)
{
Vector2d v2 {v[0], v[1]};
if (v2.isZero()) return std::nullopt;
Vector2d unit = v2.unitVector();
Vector2d y_basis {0.0, 1.0};
double theta = unit.angle2(y_basis);
if (theta < 0.0) theta += 2.0 * M_PI;
return theta;
}
std::optional<float>
CompassHeading3f(Vector3f v)
{
Vector2f v2 {v[0], v[1]};
if (v2.isZero()) return std::nullopt;
Vector2f unit = v2.unitVector();
Vector2f y_basis {0.0f, 1.0f};
float theta = unit.angle2(y_basis);
if (theta < 0.0f) theta += 2.0f * (float)M_PI;
return theta;
}
} // namespace geom
} // namespace wr