C Attitude Math Library
Quaternion, Euler, and DCM math utilities for attitude control.
Loading...
Searching...
No Matches
quaternion.c File Reference
#include "attitude/quaternion.h"
#include "attitude/attitude_utils.h"
#include <math.h>
#include <stdio.h>

Go to the source code of this file.

Functions

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.
 
int quaternion_inverse (const double q[4], double q_inv[4])
 Compute the inverse of a quaternion.
 
void quaternion_multiply (const double q1[4], const double q2[4], double q_out[4])
 Multiply two quaternions using Hamilton product.
 
void quaternion_normalize (double q[4])
 Normalise a quaternion in-place.
 
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.
 
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.
 
void quaternion_rotate_vector (const double q[4], const double v_in[3], double v_out[3])
 Rotate a vector using a quaternion.
 
void quaternion_rotate_vector_explicit (const double q[4], const double v_in[3], double v_out[3])
 Rotate a vector using the explicit \(q v q^\* \) formulation.
 
void quaternion_set_explicit_debug (int enabled)
 Enable verbose logging for the explicit quaternion rotation implementation.
 
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).
 
int quaternion_to_axis_angle (const double q[4], double axis[3], double *angle)
 Convert a quaternion to axis-angle representation.
 
void quaternion_to_dcm (const double q[4], double dcm[3][3])
 Convert a quaternion to a direction cosine matrix (DCM).
 
void quaternion_to_euler (const double q[4], double *roll, double *pitch, double *yaw)
 Convert a quaternion to intrinsic ZYX Euler angles.
 

Function Documentation

◆ axis_angle_rotate()

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.

Parameters
axisUnit rotation axis.
angleRotation angle in radians.
v_inInput vector to rotate.
v_outOutput rotated vector.

Definition at line 198 of file quaternion.c.

198 {
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}

◆ quaternion_inverse()

int quaternion_inverse ( const double  q[4],
double  q_inv[4] 
)

Compute the inverse of a quaternion.

The inverse is defined as \( q^{-1} = \frac{\bar{q}}{\|q\|^2} \), where \(\bar{q}\) is the conjugate.

Parameters
qInput quaternion.
q_invOutput inverse quaternion.
Returns
1 if the inverse exists, 0 when the input norm is too small.

Definition at line 85 of file quaternion.c.

85 {
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}

◆ quaternion_multiply()

void quaternion_multiply ( const double  q1[4],
const double  q2[4],
double  q_out[4] 
)

Multiply two quaternions using Hamilton product.

Performs \( q_{\text{out}} = q_1 \otimes q_2 \) which corresponds to applying q2 first, followed by q1.

Parameters
q1Left operand in \([w, x, y, z]\) order.
q2Right operand in \([w, x, y, z]\) order.
q_outOutput quaternion product.

Definition at line 75 of file quaternion.c.

75 {
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}

◆ quaternion_normalize()

void quaternion_normalize ( double  q[4])

Normalise a quaternion in-place.

If the quaternion norm is very small the vector is left unchanged.

Parameters
qQuaternion to normalise.

Definition at line 57 of file quaternion.c.

57 {
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}

◆ quaternion_orientation_error_axis_angle()

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.

This is useful for controller/debug flows where a camera tracker or planner provides a target attitude and the control loop needs a rotation axis plus angle magnitude.

Parameters
q_currentCurrent orientation quaternion.
q_targetDesired target orientation quaternion.
axisOutput unit correction axis.
angleOutput correction angle in radians.
Returns
1 on success, 0 when the relative rotation cannot be computed.

Definition at line 117 of file quaternion.c.

120 {
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}
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
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

◆ quaternion_relative()

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.

Produces \( q_{\text{error}} = q_{\text{target}} \otimes q_{\text{current}}^{-1} \). With this library's multiplication convention, applying q_error after q_current reconstructs q_target. The result is normalised and sign-adjusted so the scalar component is non-negative, giving the shortest equivalent rotation representation.

Parameters
q_currentCurrent orientation quaternion.
q_targetDesired target orientation quaternion.
q_errorOutput relative rotation from current to target.
Returns
1 on success, 0 when q_current cannot be inverted.

Definition at line 98 of file quaternion.c.

98 {
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}
int quaternion_inverse(const double q[4], double q_inv[4])
Compute the inverse of a quaternion.
Definition quaternion.c:85
void quaternion_normalize(double q[4])
Normalise a quaternion in-place.
Definition quaternion.c:57
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

◆ quaternion_rotate_vector()

void quaternion_rotate_vector ( const double  q[4],
const double  v_in[3],
double  v_out[3] 
)

Rotate a vector using a quaternion.

Applies the efficient formulation described by \( v' = q v q^{-1} \) without explicitly forming the matrix.

Parameters
qRotation quaternion.
v_inInput vector.
v_outOutput rotated vector.

Definition at line 213 of file quaternion.c.

213 {
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}

◆ quaternion_rotate_vector_explicit()

void quaternion_rotate_vector_explicit ( const double  q[4],
const double  v_in[3],
double  v_out[3] 
)

Rotate a vector using the explicit \(q v q^\* \) formulation.

Useful for educational tooling and verification of the optimised helper.

Parameters
qRotation quaternion.
v_inInput vector.
v_outOutput rotated vector.

Definition at line 251 of file quaternion.c.

251 {
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}

◆ quaternion_set_explicit_debug()

void quaternion_set_explicit_debug ( int  enabled)

Enable verbose logging for the explicit quaternion rotation implementation.

When enabled, the helper emits intermediate products which is useful for tutorials and debugging.

Parameters
enabledNon-zero value enables logging, zero disables it.

Definition at line 326 of file quaternion.c.

326 {
327 g_quaternion_explicit_debug = enabled;
328}

◆ quaternion_slerp()

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).

Interpolates along the shortest unit-quaternion arc. The interpolation parameter is clamped to \([0, 1]\).

Parameters
q1Start quaternion.
q2End quaternion.
tInterpolation parameter in \([0, 1]\).
q_outResulting interpolated quaternion.

Definition at line 146 of file quaternion.c.

146 {
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}

◆ quaternion_to_axis_angle()

int quaternion_to_axis_angle ( const double  q[4],
double  axis[3],
double *  angle 
)

Convert a quaternion to axis-angle representation.

Produces a unit rotation axis and right-handed rotation angle in radians.

Parameters
qInput quaternion.
axisOutput unit axis vector.
angleOutput rotation magnitude (rad).
Returns
1 on success, 0 when the axis is undefined (quaternion too close to identity).

Definition at line 129 of file quaternion.c.

129 {
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}

◆ quaternion_to_dcm()

void quaternion_to_dcm ( const double  q[4],
double  dcm[3][3] 
)

Convert a quaternion to a direction cosine matrix (DCM).

The resulting matrix rotates a vector from the body frame into the inertial frame: \( v_{\text{world}} = \mathbf{C}_{b}^{w} \, v_{\text{body}} \).

Parameters
qQuaternion in \([w, x, y, z]\) order.
dcmOutput \(3\times3\) rotation matrix stored in row-major order.

Definition at line 8 of file quaternion.c.

8 {
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}

◆ quaternion_to_euler()

void quaternion_to_euler ( const double  q[4],
double *  roll,
double *  pitch,
double *  yaw 
)

Convert a quaternion to intrinsic ZYX Euler angles.

Euler angles follow the aerospace convention (yaw→pitch→roll). Returned angles are in radians.

Parameters
qQuaternion in \([w, x, y, z]\) order.
rollOutput rotation about the \(x\) axis (rad).
pitchOutput rotation about the \(y\) axis (rad).
yawOutput rotation about the \(z\) axis (rad).

Definition at line 27 of file quaternion.c.

27 {
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}
#define ATTITUDE_PI
Macro helper converting degrees to radians.