C Attitude Math Library
Quaternion, Euler, and DCM math utilities for attitude control.
Loading...
Searching...
No Matches
quaternion.c
Go to the documentation of this file.
3#include <math.h>
4#include <stdio.h>
5
6static int g_quaternion_explicit_debug = 0;
7
8void quaternion_to_dcm(const double q[4], double dcm[3][3]) {
9 // q = [w, x, y, z]
10 double w = q[0], x = q[1], y = q[2], z = q[3];
11
12 double xx = x*x; double yy = y*y; double zz = z*z;
13 double xy = x*y; double xz = x*z; double yz = y*z;
14 double wx = w*x; double wy = w*y; double wz = w*z;
15
16 dcm[0][0] = 1.0 - 2.0*(yy + zz);
17 dcm[0][1] = 2.0*(xy - wz);
18 dcm[0][2] = 2.0*(xz + wy);
19 dcm[1][0] = 2.0*(xy + wz);
20 dcm[1][1] = 1.0 - 2.0*(xx + zz);
21 dcm[1][2] = 2.0*(yz - wx);
22 dcm[2][0] = 2.0*(xz - wy);
23 dcm[2][1] = 2.0*(yz + wx);
24 dcm[2][2] = 1.0 - 2.0*(xx + yy);
25}
26
27void quaternion_to_euler(const double q[4], double *roll, double *pitch, double *yaw) {
28 double w = q[0], x = q[1], y = q[2], z = q[3];
29
30 // Normalize quaternion
31 double norm = sqrt(w * w + x * x + y * y + z * z);
32 if (norm == 0.0) {
33 *roll = 0.0;
34 *pitch = 0.0;
35 *yaw = 0.0;
36 return;
37 }
38 w /= norm; x /= norm; y /= norm; z /= norm;
39
40 // Compute intermediate values
41 double sinr_cosp = 2.0 * (w * x + y * z);
42 double cosr_cosp = 1.0 - 2.0 * (x * x + y * y);
43 *roll = atan2(sinr_cosp, cosr_cosp);
44
45 double sinp = 2.0 * (w * y - z * x);
46 if (fabs(sinp) >= 1.0) {
47 *pitch = copysign(ATTITUDE_PI / 2, sinp);
48 } else {
49 *pitch = asin(sinp);
50 }
51
52 double siny_cosp = 2.0 * (w * z + x * y);
53 double cosy_cosp = 1.0 - 2.0 * (y * y + z * z);
54 *yaw = atan2(siny_cosp, cosy_cosp);
55}
56
57void quaternion_normalize(double q[4]) {
58 double norm = sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
59
60 // Only normalize if significantly different from 1.0
61 if (norm > 1e-6 && fabs(norm - 1.0) > 1e-6) {
62 q[0] /= norm;
63 q[1] /= norm;
64 q[2] /= norm;
65 q[3] /= norm;
66 }
67
68 #ifdef DEBUG
69 if (fabs(norm - 1.0) > 1e-6) {
70 printf("Quaternion is not normalized: norm = %f\n", norm);
71 }
72 #endif
73}
74
75void quaternion_multiply(const double q1[4], const double q2[4], double q_out[4]) {
76 double w1 = q1[0], x1 = q1[1], y1 = q1[2], z1 = q1[3];
77 double w2 = q2[0], x2 = q2[1], y2 = q2[2], z2 = q2[3];
78
79 q_out[0] = w1*w2 - x1*x2 - y1*y2 - z1*z2;
80 q_out[1] = w1*x2 + x1*w2 + y1*z2 - z1*y2;
81 q_out[2] = w1*y2 - x1*z2 + y1*w2 + z1*x2;
82 q_out[3] = w1*z2 + x1*y2 - y1*x2 + z1*w2;
83}
84
85int quaternion_inverse(const double q[4], double q_inv[4]) {
86 double norm_sq = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
87 if (norm_sq < 1e-14) {
88 // Norm too close to zero
89 return 0; // Indicate failure
90 }
91 q_inv[0] = q[0] / norm_sq;
92 q_inv[1] = -q[1] / norm_sq;
93 q_inv[2] = -q[2] / norm_sq;
94 q_inv[3] = -q[3] / norm_sq;
95 return 1; // Success
96}
97
98int quaternion_relative(const double q_current[4], const double q_target[4], double q_error[4]) {
99 double q_current_inv[4];
100 if (!quaternion_inverse(q_current, q_current_inv)) {
101 return 0;
102 }
103
104 quaternion_multiply(q_target, q_current_inv, q_error);
105 quaternion_normalize(q_error);
106
107 // q and -q represent the same rotation. Prefer the shortest positive-scalar form.
108 if (q_error[0] < 0.0) {
109 for (int i = 0; i < 4; ++i) {
110 q_error[i] = -q_error[i];
111 }
112 }
113
114 return 1;
115}
116
117int quaternion_orientation_error_axis_angle(const double q_current[4],
118 const double q_target[4],
119 double axis[3],
120 double *angle) {
121 double q_error[4];
122 if (!quaternion_relative(q_current, q_target, q_error)) {
123 return 0;
124 }
125
126 return quaternion_to_axis_angle(q_error, axis, angle);
127}
128
129int quaternion_to_axis_angle(const double q[4], double axis[3], double *angle) {
130 double w = q[0];
131 double norm_vec = sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
132 if (norm_vec < 1e-14) {
133 // Pure scalar quaternion (angle=0)
134 *angle = 0.0;
135 axis[0] = axis[1] = axis[2] = 0.0; // no rotation axis
136 return 1;
137 }
138 *angle = 2.0 * acos(w);
139 double s = 1.0 / norm_vec;
140 axis[0] = q[1]*s;
141 axis[1] = q[2]*s;
142 axis[2] = q[3]*s;
143 return 1;
144}
145
146void quaternion_slerp(const double q1[4], const double q2[4], double t, double q_out[4]) {
147 // Calculate dot product to determine the angle between quaternions
148 double dot = q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3];
149
150 // Store the adjusted version of q2 to ensure shortest path rotation
151 double q2_adjusted[4];
152
153 // If dot product is negative, take the shorter path by negating q2
154 // This is because q and -q represent the same rotation in quaternion space
155 if (dot < 0.0) {
156 dot = -dot; // Make dot positive for subsequent calculations
157 // Negate q2 to take shorter path
158 q2_adjusted[0] = -q2[0];
159 q2_adjusted[1] = -q2[1];
160 q2_adjusted[2] = -q2[2];
161 q2_adjusted[3] = -q2[3];
162 } else {
163 // If dot is positive, use q2 as is
164 q2_adjusted[0] = q2[0];
165 q2_adjusted[1] = q2[1];
166 q2_adjusted[2] = q2[2];
167 q2_adjusted[3] = q2[3];
168 }
169
170 // Optimization: If quaternions are very close (dot ≈ 1), use linear interpolation
171 // This prevents numerical instability when dividing by sin(theta) for small angles
172 if (dot > 0.9995) {
173 // Perform linear interpolation (LERP)
174 for (int i = 0; i < 4; ++i) {
175 q_out[i] = q1[i] + t * (q2_adjusted[i] - q1[i]);
176 }
177 // Ensure the result is normalized to maintain unit quaternion property
179 return;
180 }
181
182 // Calculate the angle between quaternions
183 double theta = acos(dot);
184 double sin_theta = sin(theta);
185
186 // Calculate interpolation weights
187 // These weights ensure constant angular velocity
188 double weight1 = sin((1 - t) * theta) / sin_theta; // Weight for q1
189 double weight2 = sin(t * theta) / sin_theta; // Weight for q2
190
191 // Perform the spherical interpolation
192 // This creates a rotation that smoothly transitions from q1 to q2
193 for (int i = 0; i < 4; ++i) {
194 q_out[i] = weight1 * q1[i] + weight2 * q2_adjusted[i];
195 }
196}
197
198void axis_angle_rotate(const double axis[3], double angle, const double v_in[3], double v_out[3]) {
199 double cos_theta = cos(angle);
200 double sin_theta = sin(angle);
201
202 double dot = axis[0] * v_in[0] + axis[1] * v_in[1] + axis[2] * v_in[2];
203 double cross[3];
204 cross[0] = axis[1] * v_in[2] - axis[2] * v_in[1];
205 cross[1] = axis[2] * v_in[0] - axis[0] * v_in[2];
206 cross[2] = axis[0] * v_in[1] - axis[1] * v_in[0];
207
208 for (int i = 0; i < 3; ++i) {
209 v_out[i] = v_in[i] * cos_theta + cross[i] * sin_theta + axis[i] * dot * (1 - cos_theta);
210 }
211}
212
213void quaternion_rotate_vector(const double q[4], const double v_in[3], double v_out[3]) {
214 // Takes a quaternion q = [w,x,y,z], input vector v_in and stores result in v_out
215 // q should be a unit quaternion (normalized)
216
217 // Could do full quaternion multiplication q⊗v⊗q*
218 // But this optimized formula is more efficient
219
220 double q0q0 = q[0] * q[0];
221 double q1q1 = q[1] * q[1];
222 double q2q2 = q[2] * q[2];
223 double q3q3 = q[3] * q[3];
224
225 double q0q1 = q[0] * q[1];
226 double q0q2 = q[0] * q[2];
227 double q0q3 = q[0] * q[3];
228 double q1q2 = q[1] * q[2];
229 double q1q3 = q[1] * q[3];
230 double q2q3 = q[2] * q[3];
231
232 // Rotation matrix elements
233 double r11 = q0q0 + q1q1 - q2q2 - q3q3;
234 double r12 = 2.0 * (q1q2 - q0q3);
235 double r13 = 2.0 * (q1q3 + q0q2);
236
237 double r21 = 2.0 * (q1q2 + q0q3);
238 double r22 = q0q0 - q1q1 + q2q2 - q3q3;
239 double r23 = 2.0 * (q2q3 - q0q1);
240
241 double r31 = 2.0 * (q1q3 - q0q2);
242 double r32 = 2.0 * (q2q3 + q0q1);
243 double r33 = q0q0 - q1q1 - q2q2 + q3q3;
244
245 // Perform rotation
246 v_out[0] = r11 * v_in[0] + r12 * v_in[1] + r13 * v_in[2];
247 v_out[1] = r21 * v_in[0] + r22 * v_in[1] + r23 * v_in[2];
248 v_out[2] = r31 * v_in[0] + r32 * v_in[1] + r33 * v_in[2];
249}
250
251void quaternion_rotate_vector_explicit(const double q[4], const double v_in[3], double v_out[3]) {
252 /* Educational expansion of q ⊗ v ⊗ q* with every intermediate exposed. */
253
254 /* Step 1: Promote the vector into a "pure" quaternion (scalar part = 0). */
255 double v_quat[4] = {0.0, v_in[0], v_in[1], v_in[2]};
256 if (g_quaternion_explicit_debug) {
257 printf("[explicit] Step 1 (vector→quat): [%.6f, %.6f, %.6f, %.6f]\n",
258 v_quat[0], v_quat[1], v_quat[2], v_quat[3]);
259 }
260
261 /* Step 2: Compute the first product temp = q ⊗ v.
262 *
263 * Quaternion multiplication (a⊗b) is:
264 * w = aw*bw - ax*bx - ay*by - az*bz
265 * x = aw*bx + ax*bw + ay*bz - az*by
266 * y = aw*by - ax*bz + ay*bw + az*bx
267 * z = aw*bz + ax*by - ay*bx + az*bw
268 *
269 * With v's scalar component equal to zero the formulas collapse neatly. */
270 const double w = q[0];
271 const double x = q[1];
272 const double y = q[2];
273 const double z = q[3];
274
275 const double vx = v_quat[1];
276 const double vy = v_quat[2];
277 const double vz = v_quat[3];
278
279 double temp[4];
280 temp[0] = -x * vx - y * vy - z * vz;
281 temp[1] = w * vx + y * vz - z * vy;
282 temp[2] = w * vy + z * vx - x * vz;
283 temp[3] = w * vz + x * vy - y * vx;
284
285 if (g_quaternion_explicit_debug) {
286 printf("[explicit] Step 2 (temp=q⊗v): [%.6f, %.6f, %.6f, %.6f]\n",
287 temp[0], temp[1], temp[2], temp[3]);
288 }
289
290 /* Step 3: Form the conjugate q* = [w, -x, -y, -z].
291 * For unit quaternions the conjugate equals the inverse. */
292 const double q_conj[4] = {w, -x, -y, -z};
293 if (g_quaternion_explicit_debug) {
294 printf("[explicit] Step 3 (q_conj): [%.6f, %.6f, %.6f, %.6f]\n",
295 q_conj[0], q_conj[1], q_conj[2], q_conj[3]);
296 }
297
298 /* Step 4: Multiply the intermediate quaternion by the conjugate:
299 * result = temp ⊗ q*. */
300 const double cw = q_conj[0];
301 const double cx = q_conj[1];
302 const double cy = q_conj[2];
303 const double cz = q_conj[3];
304
305 double result[4];
306 result[0] = temp[0] * cw - temp[1] * cx - temp[2] * cy - temp[3] * cz;
307 result[1] = temp[0] * cx + temp[1] * cw + temp[2] * cz - temp[3] * cy;
308 result[2] = temp[0] * cy - temp[1] * cz + temp[2] * cw + temp[3] * cx;
309 result[3] = temp[0] * cz + temp[1] * cy - temp[2] * cx + temp[3] * cw;
310
311 if (g_quaternion_explicit_debug) {
312 printf("[explicit] Step 4 (result=temp⊗q*): [%.6f, %.6f, %.6f, %.6f]\n",
313 result[0], result[1], result[2], result[3]);
314 }
315
316 /* Step 5: The rotated vector is the imaginary part of the final quaternion. */
317 v_out[0] = result[1];
318 v_out[1] = result[2];
319 v_out[2] = result[3];
320 if (g_quaternion_explicit_debug) {
321 printf("[explicit] Step 5 (rotated vector): [%.6f, %.6f, %.6f]\n",
322 v_out[0], v_out[1], v_out[2]);
323 }
324}
325
327 g_quaternion_explicit_debug = enabled;
328}
#define ATTITUDE_PI
Macro helper converting degrees to radians.
int quaternion_to_axis_angle(const double q[4], double axis[3], double *angle)
Convert a quaternion to axis-angle representation.
Definition quaternion.c:129
void axis_angle_rotate(const double axis[3], double angle, const double v_in[3], double v_out[3])
Rotate a vector about an axis by a given angle.
Definition quaternion.c:198
int quaternion_inverse(const double q[4], double q_inv[4])
Compute the inverse of a quaternion.
Definition quaternion.c:85
void quaternion_to_euler(const double q[4], double *roll, double *pitch, double *yaw)
Convert a quaternion to intrinsic ZYX Euler angles.
Definition quaternion.c:27
void quaternion_to_dcm(const double q[4], double dcm[3][3])
Convert a quaternion to a direction cosine matrix (DCM).
Definition quaternion.c:8
void quaternion_normalize(double q[4])
Normalise a quaternion in-place.
Definition quaternion.c:57
void quaternion_set_explicit_debug(int enabled)
Enable verbose logging for the explicit quaternion rotation implementation.
Definition quaternion.c:326
void quaternion_rotate_vector_explicit(const double q[4], const double v_in[3], double v_out[3])
Rotate a vector using the explicit formulation.
Definition quaternion.c:251
void quaternion_slerp(const double q1[4], const double q2[4], double t, double q_out[4])
Interpolate two quaternions using spherical linear interpolation (SLERP).
Definition quaternion.c:146
void quaternion_rotate_vector(const double q[4], const double v_in[3], double v_out[3])
Rotate a vector using a quaternion.
Definition quaternion.c:213
int quaternion_relative(const double q_current[4], const double q_target[4], double q_error[4])
Compute the relative rotation from the current orientation to a target orientation.
Definition quaternion.c:98
void quaternion_multiply(const double q1[4], const double q2[4], double q_out[4])
Multiply two quaternions using Hamilton product.
Definition quaternion.c:75
int quaternion_orientation_error_axis_angle(const double q_current[4], const double q_target[4], double axis[3], double *angle)
Compute current-to-target orientation error as an axis-angle command.
Definition quaternion.c:117