Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
FramePrimitives.hpp
Go to the documentation of this file.
1// Vulcan Coordinate Frame
2// Orthonormal basis vector representation for coordinate frames
3#pragma once
4
7
8#include <janus/math/Linalg.hpp>
9#include <janus/math/Quaternion.hpp>
10#include <janus/math/Rotations.hpp>
11#include <janus/math/Trig.hpp>
12
13namespace vulcan {
14
15// =============================================================================
16// CoordinateFrame - Basis Vector Representation
17// =============================================================================
18
44template <typename Scalar> struct CoordinateFrame {
45 Vec3<Scalar> x_axis;
46 Vec3<Scalar> y_axis;
47 Vec3<Scalar> z_axis;
48 Vec3<Scalar> origin;
49
50 // =========================================================================
51 // Constructors
52 // =========================================================================
53
56 : x_axis(Vec3<Scalar>::UnitX()), y_axis(Vec3<Scalar>::UnitY()),
57 z_axis(Vec3<Scalar>::UnitZ()), origin(Vec3<Scalar>::Zero()) {}
58
60 CoordinateFrame(const Vec3<Scalar> &x, const Vec3<Scalar> &y,
61 const Vec3<Scalar> &z, const Vec3<Scalar> &o)
62 : x_axis(x), y_axis(y), z_axis(z), origin(o) {}
63
64 // =========================================================================
65 // Core Transformations
66 // =========================================================================
67
75 [[nodiscard]] Vec3<Scalar> from_ecef(const Vec3<Scalar> &v) const {
76 Vec3<Scalar> result;
77 result(0) = janus::dot(v, x_axis);
78 result(1) = janus::dot(v, y_axis);
79 result(2) = janus::dot(v, z_axis);
80 return result;
81 }
82
90 [[nodiscard]] Vec3<Scalar> to_ecef(const Vec3<Scalar> &v) const {
91 return x_axis * v(0) + y_axis * v(1) + z_axis * v(2);
92 }
93
98 [[nodiscard]] Vec3<Scalar>
99 position_from_ecef(const Vec3<Scalar> &pos_ecef) const {
100 Vec3<Scalar> relative = pos_ecef - origin;
101 return from_ecef(relative);
102 }
103
108 [[nodiscard]] Vec3<Scalar>
109 position_to_ecef(const Vec3<Scalar> &pos_local) const {
110 return to_ecef(pos_local) + origin;
111 }
112
113 // =========================================================================
114 // Direction Cosine Matrix
115 // =========================================================================
116
123 [[nodiscard]] Mat3<Scalar> dcm() const {
124 Mat3<Scalar> R;
125 R.col(0) = x_axis;
126 R.col(1) = y_axis;
127 R.col(2) = z_axis;
128 return R;
129 }
130
139 [[nodiscard]] Mat3<Scalar> dcm_inverse() const { return dcm().transpose(); }
140
141 // =========================================================================
142 // Quaternion Representation
143 // =========================================================================
144
151 [[nodiscard]] janus::Quaternion<Scalar> quaternion() const {
152 // The DCM columns are our basis vectors in ECEF
153 // DCM = [x_axis | y_axis | z_axis] transforms local -> ECEF
154 // We want ECEF -> local, which is DCM transpose
155 // Quaternion from rotation matrix expects ECEF -> local convention
156 return janus::Quaternion<Scalar>::from_rotation_matrix(dcm_inverse());
157 }
158
167 static CoordinateFrame
168 from_quaternion(const janus::Quaternion<Scalar> &q,
169 const Vec3<Scalar> &o = Vec3<Scalar>::Zero()) {
170 // q.to_rotation_matrix() gives R such that v_local = R * v_ecef
171 // We need basis vectors in ECEF, so we take R^T columns
172 Mat3<Scalar> R = q.to_rotation_matrix();
173 Mat3<Scalar> R_T = R.transpose();
174 return CoordinateFrame(R_T.col(0), R_T.col(1), R_T.col(2), o);
175 }
176
177 // =========================================================================
178 // Validation
179 // =========================================================================
180
189 [[nodiscard]] bool is_valid(double tol = 1e-9) const {
190 if constexpr (std::is_floating_point_v<Scalar>) {
191 // Check unit lengths
192 double norm_x = x_axis.norm();
193 double norm_y = y_axis.norm();
194 double norm_z = z_axis.norm();
195 if (std::abs(norm_x - 1.0) > tol)
196 return false;
197 if (std::abs(norm_y - 1.0) > tol)
198 return false;
199 if (std::abs(norm_z - 1.0) > tol)
200 return false;
201
202 // Check orthogonality
203 double dot_xy = x_axis.dot(y_axis);
204 double dot_yz = y_axis.dot(z_axis);
205 double dot_xz = x_axis.dot(z_axis);
206 if (std::abs(dot_xy) > tol)
207 return false;
208 if (std::abs(dot_yz) > tol)
209 return false;
210 if (std::abs(dot_xz) > tol)
211 return false;
212
213 return true;
214 } else {
215 // For symbolic types, validation is not meaningful
216 return true;
217 }
218 }
219
220 // =========================================================================
221 // Static Factory Methods
222 // =========================================================================
223
231 static CoordinateFrame ecef() { return CoordinateFrame(); }
232
243 static CoordinateFrame eci(Scalar gmst) {
244 // ECI is ECEF rotated by -gmst about Z
245 // ECI X-axis points to vernal equinox, which is at angle -gmst from
246 // Greenwich
247 Scalar c = janus::cos(gmst);
248 Scalar s = janus::sin(gmst);
249
250 Vec3<Scalar> x_eci, y_eci, z_eci;
251 x_eci << c, -s, Scalar(0);
252 y_eci << s, c, Scalar(0);
253 z_eci << Scalar(0), Scalar(0), Scalar(1);
254
255 return CoordinateFrame(x_eci, y_eci, z_eci, Vec3<Scalar>::Zero());
256 }
257
269 static CoordinateFrame ned(Scalar lon, Scalar lat) {
270 Scalar sin_lat = janus::sin(lat);
271 Scalar cos_lat = janus::cos(lat);
272 Scalar sin_lon = janus::sin(lon);
273 Scalar cos_lon = janus::cos(lon);
274
275 // North: -sin(lat)*cos(lon), -sin(lat)*sin(lon), cos(lat)
276 Vec3<Scalar> north;
277 north << -sin_lat * cos_lon, -sin_lat * sin_lon, cos_lat;
278
279 // East: -sin(lon), cos(lon), 0
280 Vec3<Scalar> east;
281 east << -sin_lon, cos_lon, Scalar(0);
282
283 // Down: -cos(lat)*cos(lon), -cos(lat)*sin(lon), -sin(lat)
284 Vec3<Scalar> down;
285 down << -cos_lat * cos_lon, -cos_lat * sin_lon, -sin_lat;
286
287 return CoordinateFrame(north, east, down, Vec3<Scalar>::Zero());
288 }
289
301 static CoordinateFrame enu(Scalar lon, Scalar lat) {
302 Scalar sin_lat = janus::sin(lat);
303 Scalar cos_lat = janus::cos(lat);
304 Scalar sin_lon = janus::sin(lon);
305 Scalar cos_lon = janus::cos(lon);
306
307 // East: -sin(lon), cos(lon), 0
308 Vec3<Scalar> east;
309 east << -sin_lon, cos_lon, Scalar(0);
310
311 // North: -sin(lat)*cos(lon), -sin(lat)*sin(lon), cos(lat)
312 Vec3<Scalar> north;
313 north << -sin_lat * cos_lon, -sin_lat * sin_lon, cos_lat;
314
315 // Up: cos(lat)*cos(lon), cos(lat)*sin(lon), sin(lat)
316 Vec3<Scalar> up;
317 up << cos_lat * cos_lon, cos_lat * sin_lon, sin_lat;
318
319 return CoordinateFrame(east, north, up, Vec3<Scalar>::Zero());
320 }
321};
322
323// =============================================================================
324// Free Functions for Frame Operations
325// =============================================================================
326
333template <typename Scalar>
334Vec3<Scalar> transform_vector(const Vec3<Scalar> &v,
335 const CoordinateFrame<Scalar> &from,
336 const CoordinateFrame<Scalar> &to) {
337 // from → ECEF → to
338 Vec3<Scalar> v_ecef = from.to_ecef(v);
339 return to.from_ecef(v_ecef);
340}
341
350template <typename Scalar>
351Vec3<Scalar> transform_position(const Vec3<Scalar> &pos,
352 const CoordinateFrame<Scalar> &from,
353 const CoordinateFrame<Scalar> &to) {
354 // from → ECEF → to (with origin handling)
355 Vec3<Scalar> pos_ecef = from.position_to_ecef(pos);
356 return to.position_from_ecef(pos_ecef);
357}
358
359} // namespace vulcan
Definition Aerodynamics.hpp:11
Vec3< Scalar > transform_vector(const Vec3< Scalar > &v, const CoordinateFrame< Scalar > &from, const CoordinateFrame< Scalar > &to)
Definition FramePrimitives.hpp:334
Vec3< Scalar > transform_position(const Vec3< Scalar > &pos, const CoordinateFrame< Scalar > &from, const CoordinateFrame< Scalar > &to)
Definition FramePrimitives.hpp:351
Definition FramePrimitives.hpp:44
Vec3< Scalar > position_from_ecef(const Vec3< Scalar > &pos_ecef) const
Definition FramePrimitives.hpp:99
Vec3< Scalar > y_axis
Unit Y basis vector in ECEF.
Definition FramePrimitives.hpp:46
Mat3< Scalar > dcm() const
Definition FramePrimitives.hpp:123
Vec3< Scalar > to_ecef(const Vec3< Scalar > &v) const
Definition FramePrimitives.hpp:90
Vec3< Scalar > position_to_ecef(const Vec3< Scalar > &pos_local) const
Definition FramePrimitives.hpp:109
static CoordinateFrame eci(Scalar gmst)
Definition FramePrimitives.hpp:243
CoordinateFrame(const Vec3< Scalar > &x, const Vec3< Scalar > &y, const Vec3< Scalar > &z, const Vec3< Scalar > &o)
Construct from basis vectors and origin.
Definition FramePrimitives.hpp:60
static CoordinateFrame ecef()
Definition FramePrimitives.hpp:231
static CoordinateFrame enu(Scalar lon, Scalar lat)
Definition FramePrimitives.hpp:301
static CoordinateFrame from_quaternion(const janus::Quaternion< Scalar > &q, const Vec3< Scalar > &o=Vec3< Scalar >::Zero())
Definition FramePrimitives.hpp:168
Vec3< Scalar > from_ecef(const Vec3< Scalar > &v) const
Definition FramePrimitives.hpp:75
bool is_valid(double tol=1e-9) const
Definition FramePrimitives.hpp:189
Vec3< Scalar > z_axis
Unit Z basis vector in ECEF.
Definition FramePrimitives.hpp:47
static CoordinateFrame ned(Scalar lon, Scalar lat)
Definition FramePrimitives.hpp:269
Vec3< Scalar > origin
Frame origin in ECEF [m].
Definition FramePrimitives.hpp:48
Mat3< Scalar > dcm_inverse() const
Definition FramePrimitives.hpp:139
CoordinateFrame()
Default constructor - creates ECEF identity frame.
Definition FramePrimitives.hpp:55
Vec3< Scalar > x_axis
Unit X basis vector in ECEF.
Definition FramePrimitives.hpp:45
janus::Quaternion< Scalar > quaternion() const
Definition FramePrimitives.hpp:151