Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
FrameVehicle.hpp
Go to the documentation of this file.
1// Vulcan Body Frames
2// Body-fixed frames, Euler angles, and velocity frame utilities
3#pragma once
4
10
11#include <janus/math/Linalg.hpp>
12#include <janus/math/Quaternion.hpp>
13#include <janus/math/Trig.hpp>
14
15namespace vulcan {
16
17// =============================================================================
18// Quaternion-Based Body Frame Construction
19// =============================================================================
20
27template <typename Scalar>
30 const janus::Quaternion<Scalar> &q_body_to_ned) {
31 // q_body_to_ned transforms body vectors to NED vectors: v_ned =
32 // q.rotate(v_body) The body axes (unit vectors in body coords) rotated to
33 // NED give body axes in NED
34
35 // Body axes in body coordinates
36 Vec3<Scalar> x_body = Vec3<Scalar>::UnitX();
37 Vec3<Scalar> y_body = Vec3<Scalar>::UnitY();
38 Vec3<Scalar> z_body = Vec3<Scalar>::UnitZ();
39
40 // Body axes expressed in NED (rotate body unit vectors to NED)
41 Vec3<Scalar> x_body_ned = q_body_to_ned.rotate(x_body);
42 Vec3<Scalar> y_body_ned = q_body_to_ned.rotate(y_body);
43 Vec3<Scalar> z_body_ned = q_body_to_ned.rotate(z_body);
44
45 // Transform body axes from NED to ECEF
46 Vec3<Scalar> x_body_ecef = ned.to_ecef(x_body_ned);
47 Vec3<Scalar> y_body_ecef = ned.to_ecef(y_body_ned);
48 Vec3<Scalar> z_body_ecef = ned.to_ecef(z_body_ned);
49
50 return CoordinateFrame<Scalar>(x_body_ecef, y_body_ecef, z_body_ecef,
51 ned.origin);
52}
53
60template <typename Scalar>
61janus::Quaternion<Scalar>
63 const CoordinateFrame<Scalar> &ned) {
64 // Get body axes in NED coordinates
65 Vec3<Scalar> x_body_ned = ned.from_ecef(body.x_axis);
66 Vec3<Scalar> y_body_ned = ned.from_ecef(body.y_axis);
67 Vec3<Scalar> z_body_ned = ned.from_ecef(body.z_axis);
68
69 // Build rotation matrix (body axes as columns)
70 // R transforms body vectors to NED: v_ned = R * v_body
71 Mat3<Scalar> R;
72 R.col(0) = x_body_ned;
73 R.col(1) = y_body_ned;
74 R.col(2) = z_body_ned;
75
76 // Return quaternion that rotates body to NED (same as from_euler
77 // convention)
78 return janus::Quaternion<Scalar>::from_rotation_matrix(R);
79}
80
81// =============================================================================
82// Euler Angle Construction (Using Quaternions Internally)
83// =============================================================================
84
98template <typename Scalar>
100 Scalar yaw, Scalar pitch, Scalar roll) {
101 // Janus from_euler uses (roll, pitch, yaw) for XYZ intrinsic sequence
102 // which corresponds to ZYX extrinsic (yaw-pitch-roll aerospace convention)
103 auto q = janus::Quaternion<Scalar>::from_euler(roll, pitch, yaw);
104 return body_from_quaternion(ned, q);
105}
106
107// =============================================================================
108// Euler Angle Extraction (Using Rotations Library)
109// =============================================================================
110
122template <typename Scalar>
124 const CoordinateFrame<Scalar> &ned) {
125 // Build DCM: columns are body axes in NED coordinates
126 // v_ned = DCM * v_body
127 Mat3<Scalar> dcm;
128 dcm.col(0) = ned.from_ecef(body.x_axis);
129 dcm.col(1) = ned.from_ecef(body.y_axis);
130 dcm.col(2) = ned.from_ecef(body.z_axis);
131
132 // Use unified Euler extraction with gimbal lock handling
134}
135
136// =============================================================================
137// Velocity Frame
138// =============================================================================
139
153template <typename Scalar>
154CoordinateFrame<Scalar> velocity_frame(const Vec3<Scalar> &velocity_ecef,
155 const CoordinateFrame<Scalar> &ned) {
156 // Get velocity in NED
157 Vec3<Scalar> v_ned = ned.from_ecef(velocity_ecef);
158
159 // Velocity magnitude
160 Scalar v_mag = janus::norm(velocity_ecef);
161 Scalar eps = Scalar(1e-10);
162
163 // Handle zero velocity case - use NED as default
164 Scalar is_zero = v_mag < eps;
165
166 // X-axis: velocity direction
167 Vec3<Scalar> x_vel_ned;
168 x_vel_ned << v_ned(0) / v_mag, v_ned(1) / v_mag, v_ned(2) / v_mag;
169
170 // For near-zero velocity, default to North
171 x_vel_ned(0) = janus::where(is_zero, Scalar(1), x_vel_ned(0));
172 x_vel_ned(1) = janus::where(is_zero, Scalar(0), x_vel_ned(1));
173 x_vel_ned(2) = janus::where(is_zero, Scalar(0), x_vel_ned(2));
174
175 // Horizontal velocity magnitude
176 Scalar v_horiz = janus::sqrt(v_ned(0) * v_ned(0) + v_ned(1) * v_ned(1));
177 Scalar is_vertical = v_horiz < eps;
178
179 // Y-axis: Horizontal, perpendicular to velocity (to the right)
180 Vec3<Scalar> y_vel_ned;
181 y_vel_ned << v_ned(1) / v_horiz, -v_ned(0) / v_horiz, Scalar(0);
182
183 // For vertical flight, Y defaults to East
184 y_vel_ned(0) = janus::where(is_vertical, Scalar(0), y_vel_ned(0));
185 y_vel_ned(1) = janus::where(is_vertical, Scalar(1), y_vel_ned(1));
186 y_vel_ned(2) = janus::where(is_vertical, Scalar(0), y_vel_ned(2));
187
188 // For zero velocity, Y defaults to East
189 y_vel_ned(0) = janus::where(is_zero, Scalar(0), y_vel_ned(0));
190 y_vel_ned(1) = janus::where(is_zero, Scalar(1), y_vel_ned(1));
191
192 // Z-axis: Complete right-handed system: z = x cross y
193 Vec3<Scalar> z_vel_ned = janus::cross(x_vel_ned, y_vel_ned);
194
195 // Transform to ECEF
196 Vec3<Scalar> x_vel = ned.to_ecef(x_vel_ned);
197 Vec3<Scalar> y_vel = ned.to_ecef(y_vel_ned);
198 Vec3<Scalar> z_vel = ned.to_ecef(z_vel_ned);
199
200 return CoordinateFrame<Scalar>(x_vel, y_vel, z_vel, ned.origin);
201}
202
203// =============================================================================
204// Flight Path Angles
205// =============================================================================
206
214template <typename Scalar>
215Vec2<Scalar> flight_path_angles(const Vec3<Scalar> &velocity_ned) {
216 Scalar vn = velocity_ned(0);
217 Scalar ve = velocity_ned(1);
218 Scalar vd = velocity_ned(2);
219
220 // Horizontal speed
221 Scalar v_horiz = janus::sqrt(vn * vn + ve * ve);
222
223 // Total speed
224 Scalar v_total = janus::norm(velocity_ned);
225 Scalar eps = Scalar(1e-10);
226 Scalar is_zero = v_total < eps;
227
228 // Flight path angle: gamma = atan2(-vd, v_horiz)
229 // For climbing, vd < 0, so gamma > 0
230 Scalar gamma = janus::atan2(-vd, v_horiz);
231 gamma = janus::where(is_zero, Scalar(0), gamma);
232
233 // Heading: psi = atan2(ve, vn)
234 Scalar psi = janus::atan2(ve, vn);
235 psi = janus::where(is_zero, Scalar(0), psi);
236
237 Vec2<Scalar> angles;
238 angles << gamma, psi;
239 return angles;
240}
241
248template <typename Scalar>
249Vec2<Scalar> flight_path_angles(const Vec3<Scalar> &velocity_ecef,
250 const CoordinateFrame<Scalar> &ned) {
251 Vec3<Scalar> v_ned = ned.from_ecef(velocity_ecef);
252 return flight_path_angles(v_ned);
253}
254
255// =============================================================================
256// Aerodynamic Angles
257// =============================================================================
258
259// Core aero_angles implementation is in vulcan::aero namespace
260// Re-export for backwards compatibility
262
269template <typename Scalar>
270Vec2<Scalar> aero_angles(const Vec3<Scalar> &velocity_ecef,
271 const CoordinateFrame<Scalar> &body) {
272 Vec3<Scalar> v_body = body.from_ecef(velocity_ecef);
273 return aero_angles(v_body);
274}
275
276} // namespace vulcan
Vec2< Scalar > aero_angles(const Vec3< Scalar > &velocity_body)
Compute aerodynamic angles from velocity in body frame.
Definition Aerodynamics.hpp:113
Definition Aerodynamics.hpp:11
Vec3< Scalar > euler_from_body(const CoordinateFrame< Scalar > &body, const CoordinateFrame< Scalar > &ned)
Definition FrameVehicle.hpp:123
Vec2< Scalar > aero_angles(const Vec3< Scalar > &velocity_ecef, const CoordinateFrame< Scalar > &body)
Definition FrameVehicle.hpp:270
CoordinateFrame< Scalar > velocity_frame(const Vec3< Scalar > &velocity_ecef, const CoordinateFrame< Scalar > &ned)
Definition FrameVehicle.hpp:154
CoordinateFrame< Scalar > body_from_euler(const CoordinateFrame< Scalar > &ned, Scalar yaw, Scalar pitch, Scalar roll)
Definition FrameVehicle.hpp:99
Vec3< Scalar > euler_from_dcm(const Mat3< Scalar > &R, EulerSequence seq)
Definition EulerSequences.hpp:520
janus::Quaternion< Scalar > quaternion_from_body(const CoordinateFrame< Scalar > &body, const CoordinateFrame< Scalar > &ned)
Definition FrameVehicle.hpp:62
CoordinateFrame< Scalar > body_from_quaternion(const CoordinateFrame< Scalar > &ned, const janus::Quaternion< Scalar > &q_body_to_ned)
Definition FrameVehicle.hpp:29
Vec2< Scalar > flight_path_angles(const Vec3< Scalar > &velocity_ned)
Definition FrameVehicle.hpp:215
@ ZYX
Yaw-Pitch-Roll (aerospace standard).
Definition EulerSequences.hpp:39
Definition FramePrimitives.hpp:44
Vec3< Scalar > y_axis
Unit Y basis vector in ECEF.
Definition FramePrimitives.hpp:46
Vec3< Scalar > to_ecef(const Vec3< Scalar > &v) const
Definition FramePrimitives.hpp:90
Vec3< Scalar > from_ecef(const Vec3< Scalar > &v) const
Definition FramePrimitives.hpp:75
Vec3< Scalar > z_axis
Unit Z basis vector in ECEF.
Definition FramePrimitives.hpp:47
Vec3< Scalar > origin
Frame origin in ECEF [m].
Definition FramePrimitives.hpp:48
Vec3< Scalar > x_axis
Unit X basis vector in ECEF.
Definition FramePrimitives.hpp:45