Control Surface main
MIDI Control Surface library for Arduino
Loading...
Searching...
No Matches
Quaternion.hpp
Go to the documentation of this file.
1
22#pragma once
23
24#include <AH/Arduino-Wrapper.h> // Print
25#include <AH/Math/Degrees.hpp> // rad2deg()
26#include <AH/Math/Vector.hpp> // Vec3f
27#include <AH/STL/cmath> // std::sqrt
28#include <AH/STL/limits> // std::numeric_limits
29
30#ifndef ARDUINO
31#include <iosfwd> // std::ostream
32#endif
33
35
57struct Quaternion {
58 float w = 1.0;
59 float x = 0.0;
60 float y = 0.0;
61 float z = 0.0;
62
64 Quaternion() = default;
66 Quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z) {}
67
71 return *this = hamiltonianProduct(*this, rhs);
72 }
76 return hamiltonianProduct(*this, rhs);
77 }
78
80 Quaternion conjugated() const { return {w, -x, -y, -z}; }
82 Quaternion operator-() const { return conjugated(); }
83
87 Quaternion &operator-=(Quaternion rhs) { return *this += -rhs; }
92 Quaternion result = *this;
93 result -= rhs;
94 return result;
95 }
96
99 w *= rhs;
100 x *= rhs;
101 y *= rhs;
102 z *= rhs;
103 return *this;
104 }
106 Quaternion operator*(float rhs) const {
107 Quaternion result = *this;
108 result *= rhs;
109 return result;
110 }
111
114 w /= rhs;
115 x /= rhs;
116 y /= rhs;
117 z /= rhs;
118 return *this;
119 }
121 Quaternion operator/(float rhs) const {
122 Quaternion result = *this;
123 result /= rhs;
124 return result;
125 }
126
128 float normSquared() const { return w * w + x * x + y * y + z * z; }
130 float norm() const { return std::sqrt(normSquared()); }
132 Quaternion &normalize() { return *this /= norm(); }
135 Quaternion normalized() const { return *this / norm(); }
136
147 // Source:
148 // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
149
150 // Compare to the MATLAB convetions:
151 // https://www.mathworks.com/matlabcentral/answers/352465-what-is-the-aerospace-blockset-quaternion-convention
152 // https://www.mathworks.com/help/aerotbx/ug/quatrotate.html#mw_f4c2628b-30a7-4b62-a227-7fb9ef158187
153
154 // Normalized quaternion.
155 Quaternion q = normalized();
156
157 // Rotation matrix.
158 float M11 = 1 - 2 * q.y * q.y - 2 * q.z * q.z;
159 float M12 = 2 * (q.x * q.y - q.w * q.z);
160 float M13 = 2 * (q.x * q.z + q.w * q.y);
161 float M21 = 2 * (q.x * q.y + q.w * q.z);
162 float M22 = 1 - 2 * q.x * q.x - 2 * q.z * q.z;
163 float M23 = 2 * (q.y * q.z - q.w * q.x);
164 float M31 = 2 * (q.x * q.z - q.w * q.y);
165 float M32 = 2 * (q.y * q.z + q.w * q.x);
166 float M33 = 1 - 2 * q.x * q.x - 2 * q.y * q.y;
167
168 return Vec3f{
169 M11 * v.x + M12 * v.y + M13 * v.z,
170 M21 * v.x + M22 * v.y + M23 * v.z,
171 M31 * v.x + M32 * v.y + M33 * v.z,
172 };
173 }
174
177 return this->w == rhs.w && this->x == rhs.x && this->y == rhs.y &&
178 this->z == rhs.z;
179 }
181 bool operator!=(Quaternion rhs) const { return !(*this == rhs); }
182
184 static Quaternion identity() { return {1, 0, 0, 0}; }
185
191 /*
192 * Formula:
193 * q = cos(ϑ / 2) + sin(ϑ / 2)·(x·i + y·j + z·k)
194 * where (x y z) is a unit vector representing the axis about which
195 * the body is rotated; ϑ is the angle by which it is rotated.
196 *
197 * Source:
198 * https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Using_quaternion_as_rotations
199 *
200 * The rotational axis (x y z) can be calcuated by taking the normalized
201 * cross product of (0 0 1) and the given vector. The angle of rotation
202 * ϑ can be found using |A×B| = |A||B|·sin(ϑ).
203 */
204
205 float eps = std::numeric_limits<float>::epsilon();
206
207 // Ensure that the norm of v is not zero
208 float v_norm = v.norm();
209 if (v_norm <= eps)
210 return Quaternion(0, 0, 0, 0); // invalid zero quaternion
211
212 // Normalize the x and y components (only the sign of v.z matters now)
213 float x = v.x / v_norm;
214 float y = v.y / v_norm;
215
216 // Check the edge case, where v ≃ (0 0 ±1).
217 if (std::abs(x) <= eps && std::abs(y) <= eps)
218 return v.z > 0 ? Quaternion(1, 0, 0, 0) : Quaternion(0, 1, 0, 0);
219
220 // Calculate the cross product and its norm.
221 // (z component is always zero)
222 Vec2f cross = {-y, x};
223 float crossNorm = min(cross.norm(), 1);
224 cross /= crossNorm;
225
226 // Calculate the angle ϑ.
227 float angle = std::asin(crossNorm);
228 if (v.z < 0)
229 angle = float(180_deg) - angle;
230
231 // Calculate the resulting quaternion.
232 return {
233 std::cos(angle / 2), //
234 std::sin(angle / 2) * cross.x, //
235 std::sin(angle / 2) * cross.y, //
236 0, // = std::sin(angle / 2) * cross.z
237 };
238 }
239
253 static Quaternion fromXYAngle(float xAngle, float yAngle) {
254 Vec3f v = {
255 +std::tan(yAngle), // x
256 -std::tan(xAngle), // y
257 1, // z
258 };
259 return Quaternion::fromDirection(v);
260 }
261
264 return {
265 r.w * q.w - r.x * q.x - r.y * q.y - r.z * q.z,
266 r.w * q.x + r.x * q.w - r.y * q.z + r.z * q.y,
267 r.w * q.y + r.x * q.z + r.y * q.w - r.z * q.x,
268 r.w * q.z - r.x * q.y + r.y * q.x + r.z * q.w,
269 };
270 }
271};
272
276 return {lhs * rhs.w, lhs * rhs.x, lhs * rhs.y, lhs * rhs.z};
277}
278
289 float yaw = 0.0;
290 float pitch = 0.0;
291 float roll = 0.0;
292
294 EulerAngles() = default;
296 EulerAngles(float yaw, float pitch, float roll)
297 : yaw(yaw), pitch(pitch), roll(roll) {}
300
302 operator Quaternion() const { return eul2quat(*this); }
303
306 return this->yaw == rhs.yaw && this->pitch == rhs.pitch &&
307 this->roll == rhs.roll;
308 }
310 bool operator!=(EulerAngles rhs) const { return !(*this == rhs); }
311
314 // Source: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion
315
316 // Normalize quaternion (q is passed by value, so this is a copy of q).
317 q.normalize();
318
319 float phi = std::atan2(2 * (q.w * q.x + q.y * q.z),
320 1 - 2 * (q.x * q.x + q.y * q.y));
321 float theta = std::asin(2 * (q.w * q.y - q.z * q.x));
322 float psi = std::atan2(2 * (q.w * q.z + q.x * q.y),
323 1 - 2 * (q.y * q.y + q.z * q.z));
324 return {psi, theta, phi};
325 }
326
329 // Source: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Euler_Angles_to_Quaternion_Conversion
330
331 float cy = std::cos(eulerAngles.yaw / 2);
332 float sy = std::sin(eulerAngles.yaw / 2);
333 float cp = std::cos(eulerAngles.pitch / 2);
334 float sp = std::sin(eulerAngles.pitch / 2);
335 float cr = std::cos(eulerAngles.roll / 2);
336 float sr = std::sin(eulerAngles.roll / 2);
337
338 return {
339 cy * cp * cr + sy * sp * sr,
340 cy * cp * sr - sy * sp * cr,
341 sy * cp * sr + cy * sp * cr,
342 sy * cp * cr - cy * sp * sr,
343 };
344 }
345};
346
347#ifndef ARDUINO
348
351std::ostream &operator<<(std::ostream &os, Quaternion q);
352
355std::ostream &operator<<(std::ostream &os, EulerAngles e);
356
357#endif // ARDUINO
358
361Print &operator<<(Print &os, Quaternion e);
362
365Print &operator<<(Print &os, EulerAngles e);
366
368
#define END_AH_NAMESPACE
#define BEGIN_AH_NAMESPACE
Print & operator<<(Print &os, Cable c)
Definition Cable.cpp:6
Conversions between radians and degrees.
Definition of Vec2f and Vec3f.
A class for serial-in/parallel-out shift registers, like the 74HC595 that are connected to the SPI bu...
Quaternion operator*(float lhs, Quaternion rhs)
Scalar multiplication.
Struct for Euler angles of floating point numbers.
bool operator==(EulerAngles rhs) const
Equality check.
EulerAngles(Quaternion q)
Create Euler angles from the given quaternion.
static EulerAngles quat2eul(Quaternion q)
Convert the given quaternion to Euler angles.
bool operator!=(EulerAngles rhs) const
Inequality check.
EulerAngles(float yaw, float pitch, float roll)
Create Euler angles with the given values for yaw, pitch and roll.
EulerAngles()=default
Create Euler angles that are initialized to (0 0 0), or upright.
static Quaternion eul2quat(EulerAngles eulerAngles)
Convert the given Euler angles to a quaternion.
Type for quaternions of floating point numbers.
Quaternion & operator+=(Quaternion rhs)
Sum of two quaterions uses quaternion multiplication.
static Quaternion fromDirection(Vec3f v)
Calculate the quaternion that satisfies the following: result.rotate(Vec3f{0, 0, 1}) == v....
Quaternion & operator/=(float rhs)
Scalar division.
Quaternion operator-() const
Negated quaternion is its conjugate.
float w
Scalar (real) component.
static Quaternion hamiltonianProduct(Quaternion q, Quaternion r)
Quaternion multiplication.
float normSquared() const
Norm squared.
bool operator==(Quaternion rhs) const
Equality check.
Quaternion(float w, float x, float y, float z)
Create a quaterion with the given values for w, x, y and z.
bool operator!=(Quaternion rhs) const
Inequality check.
Quaternion operator/(float rhs) const
Scalar division.
float y
Second vector (imaginary) component .
Quaternion()=default
Create a quaternion that is initialized to the identity quaternion.
Quaternion operator+(Quaternion rhs) const
Sum of two quaternions uses quaternion multiplication.
Quaternion & operator-=(Quaternion rhs)
Difference of two quaternions a and b is the quaternion multiplication of a and the conjugate of b.
Quaternion operator*(float rhs) const
Scalar multiplication.
static Quaternion fromXYAngle(float xAngle, float yAngle)
Calculate the quaternion from a vector that makes a given angle with the XZ plane and a given angle w...
Vec3f rotate(Vec3f v) const
Rotate vector by this quaternion.
Quaternion normalized() const
Normalize a copy of this quaternion (doesn't change the original quaternion).
Quaternion & normalize()
Normalize this quaternion.
float x
First vector (imaginary) component .
static Quaternion identity()
Identity quaternion (1,0,0,0).
float norm() const
Norm.
Quaternion operator-(Quaternion rhs) const
Difference of two quaternions a and b is the quaternion multiplication of a and the conjugate of b.
Quaternion & operator*=(float rhs)
Scalar multiplication.
float z
Third vector (imaginary) component .
Quaternion conjugated() const
Complex conjugate (doesn't change the original quaternion).
Type for 2D vectors of floating point numbers.
Definition Vector.hpp:39
Type for 3D vectors of floating point numbers.
Definition Vector.hpp:140