Control Surface  1.2.0
MIDI Control Surface library for Arduino
Quaternion.hpp
Go to the documentation of this file.
1 #pragma once
2 
4 AH_DIAGNOSTIC_WERROR() // Enable errors on warnings
5 
6 #include <AH/Arduino-Wrapper.h> // Print
7 #include <AH/Math/Degrees.hpp> // rad2deg()
8 #include <AH/Math/Vector.hpp> // Vec3f
9 #include <AH/STL/cmath> // std::sqrt
10 #include <AH/STL/limits> // std::numeric_limits
11 
12 #ifndef ARDUINO
13 #include <iosfwd> // std::ostream
14 #endif
15 
17 
61 struct Quaternion {
62  float w = 1.0;
63  float x = 0.0;
64  float y = 0.0;
65  float z = 0.0;
66 
68  Quaternion() = default;
70  Quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z) {}
71 
75  return *this = hamiltonianProduct(*this, rhs);
76  }
80  return hamiltonianProduct(*this, rhs);
81  }
82 
84  Quaternion conjugated() const { return {w, -x, -y, -z}; }
86  Quaternion operator-() const { return conjugated(); }
87 
91  Quaternion &operator-=(Quaternion rhs) { return *this += -rhs; }
96  Quaternion result = *this;
97  result -= rhs;
98  return result;
99  }
100 
102  Quaternion &operator*=(float rhs) {
103  w *= rhs;
104  x *= rhs;
105  y *= rhs;
106  z *= rhs;
107  return *this;
108  }
110  Quaternion operator*(float rhs) const {
111  Quaternion result = *this;
112  result *= rhs;
113  return result;
114  }
115 
117  Quaternion &operator/=(float rhs) {
118  w /= rhs;
119  x /= rhs;
120  y /= rhs;
121  z /= rhs;
122  return *this;
123  }
125  Quaternion operator/(float rhs) const {
126  Quaternion result = *this;
127  result /= rhs;
128  return result;
129  }
130 
132  float normSquared() const { return w * w + x * x + y * y + z * z; }
134  float norm() const { return std::sqrt(normSquared()); }
136  Quaternion &normalize() { return *this /= norm(); }
139  Quaternion normalized() const { return *this / norm(); }
140 
150  Vec3f rotate(Vec3f v) const {
151  // Source:
152  // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
153 
154  // Compare to the MATLAB convetions:
155  // https://www.mathworks.com/matlabcentral/answers/352465-what-is-the-aerospace-blockset-quaternion-convention
156  // https://www.mathworks.com/help/aerotbx/ug/quatrotate.html#mw_f4c2628b-30a7-4b62-a227-7fb9ef158187
157 
158  // Normalized quaternion.
159  Quaternion q = normalized();
160 
161  // Rotation matrix.
162  float M11 = 1.0 - 2.0 * q.y * q.y - 2.0 * q.z * q.z;
163  float M12 = 2.0 * (q.x * q.y - q.w * q.z);
164  float M13 = 2.0 * (q.x * q.z + q.w * q.y);
165  float M21 = 2.0 * (q.x * q.y + q.w * q.z);
166  float M22 = 1.0 - 2.0 * q.x * q.x - 2.0 * q.z * q.z;
167  float M23 = 2.0 * (q.y * q.z - q.w * q.x);
168  float M31 = 2.0 * (q.x * q.z - q.w * q.y);
169  float M32 = 2.0 * (q.y * q.z + q.w * q.x);
170  float M33 = 1.0 - 2.0 * q.x * q.x - 2.0 * q.y * q.y;
171 
172  return Vec3f{
173  M11 * v.x + M12 * v.y + M13 * v.z,
174  M21 * v.x + M22 * v.y + M23 * v.z,
175  M31 * v.x + M32 * v.y + M33 * v.z,
176  };
177  }
178 
180  bool operator==(Quaternion rhs) const {
181  return this->w == rhs.w && this->x == rhs.x && this->y == rhs.y &&
182  this->z == rhs.z;
183  }
185  bool operator!=(Quaternion rhs) const { return !(*this == rhs); }
186 
188  static Quaternion identity() { return {1, 0, 0, 0}; }
189 
195  /*
196  * Formula:
197  * q = cos(ϑ / 2) + sin(ϑ / 2)·(x·i + y·j + z·k)
198  * where (x y z) is a unit vector representing the axis about which
199  * the body is rotated; ϑ is the angle by which it is rotated.
200  *
201  * Source:
202  * https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Using_quaternion_as_rotations
203  *
204  * The rotational axis (x y z) can be calcuated by taking the normalized
205  * cross product of (0 0 1) and the given vector. The angle of rotation
206  * ϑ can be found using |A×B| = |A||B|·sin(ϑ).
207  */
208 
209  float eps = std::numeric_limits<float>::epsilon();
210 
211  // Ensure that the norm of v is not zero
212  float v_norm = v.norm();
213  if (v_norm <= eps)
214  return Quaternion(0, 0, 0, 0); // invalid zero quaternion
215 
216  // Normalize the x and y components (only the sign of v.z matters now)
217  float x = v.x / v_norm;
218  float y = v.y / v_norm;
219 
220  // Check the edge case, where v ≃ (0 0 ±1).
221  if (std::abs(x) <= eps && std::abs(y) <= eps)
222  return v.z > 0 ? Quaternion(1, 0, 0, 0) : Quaternion(0, 1, 0, 0);
223 
224  // Calculate the cross product and its norm.
225  // (z component is always zero)
226  Vec2f cross = {-y, x};
227  float crossNorm = min(cross.norm(), 1);
228  cross /= crossNorm;
229 
230  // Calculate the angle ϑ.
231  float angle = std::asin(crossNorm);
232  if (v.z < 0)
233  angle = M_PI - angle;
234 
235  // Calculate the resulting quaternion.
236  return {
237  std::cos(angle / 2), //
238  std::sin(angle / 2) * cross.x, //
239  std::sin(angle / 2) * cross.y, //
240  0, // = std::sin(angle / 2) * cross.z
241  };
242  }
243 
257  static Quaternion fromXYAngle(float xAngle, float yAngle) {
258  Vec3f v = {
259  +std::tan(yAngle), // x
260  -std::tan(xAngle), // y
261  1, // z
262  };
263  return Quaternion::fromDirection(v);
264  }
265 
268  return {
269  r.w * q.w - r.x * q.x - r.y * q.y - r.z * q.z,
270  r.w * q.x + r.x * q.w - r.y * q.z + r.z * q.y,
271  r.w * q.y + r.x * q.z + r.y * q.w - r.z * q.x,
272  r.w * q.z - r.x * q.y + r.y * q.x + r.z * q.w,
273  };
274  }
275 };
276 
279 inline Quaternion operator*(float lhs, Quaternion rhs) {
280  return {lhs * rhs.w, lhs * rhs.x, lhs * rhs.y, lhs * rhs.z};
281 }
282 
292 struct EulerAngles {
293  float yaw = 0.0;
294  float pitch = 0.0;
295  float roll = 0.0;
296 
298  EulerAngles() = default;
300  EulerAngles(float yaw, float pitch, float roll)
301  : yaw(yaw), pitch(pitch), roll(roll) {}
303  EulerAngles(Quaternion q) : EulerAngles{quat2eul(q)} {}
304 
306  operator Quaternion() const { return eul2quat(*this); }
307 
309  bool operator==(EulerAngles rhs) const {
310  return this->yaw == rhs.yaw && this->pitch == rhs.pitch &&
311  this->roll == rhs.roll;
312  }
314  bool operator!=(EulerAngles rhs) const { return !(*this == rhs); }
315 
318  // Source: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_Angles_Conversion
319 
320  // Normalize quaternion (q is passed by value, so this is a copy of q).
321  q.normalize();
322 
323  float phi = std::atan2(2.0 * (q.w * q.x + q.y * q.z),
324  1.0 - 2.0 * (q.x * q.x + q.y * q.y));
325  float theta = std::asin(2.0 * (q.w * q.y - q.z * q.x));
326  float psi = std::atan2(2.0 * (q.w * q.z + q.x * q.y),
327  1.0 - 2.0 * (q.y * q.y + q.z * q.z));
328  return {psi, theta, phi};
329  }
330 
332  static Quaternion eul2quat(EulerAngles eulerAngles) {
333  // Source: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Euler_Angles_to_Quaternion_Conversion
334 
335  float cy = std::cos(eulerAngles.yaw / 2);
336  float sy = std::sin(eulerAngles.yaw / 2);
337  float cp = std::cos(eulerAngles.pitch / 2);
338  float sp = std::sin(eulerAngles.pitch / 2);
339  float cr = std::cos(eulerAngles.roll / 2);
340  float sr = std::sin(eulerAngles.roll / 2);
341 
342  return {
343  cy * cp * cr + sy * sp * sr,
344  cy * cp * sr - sy * sp * cr,
345  sy * cp * sr + cy * sp * cr,
346  sy * cp * cr - cy * sp * sr,
347  };
348  }
349 };
350 
351 #ifndef ARDUINO
352 
355 std::ostream &operator<<(std::ostream &os, Quaternion q);
356 
359 std::ostream &operator<<(std::ostream &os, EulerAngles e);
360 
361 #endif // ARDUINO
362 
365 Print &operator<<(Print &os, Quaternion e);
366 
369 Print &operator<<(Print &os, EulerAngles e);
370 
372 
374 
AH::Vec2f::y
float y
The y component of the vector.
Definition: Vector.hpp:45
AH::Vec3f::norm
float norm() const
Norm.
Definition: Vector.hpp:223
AH::Quaternion::fromDirection
static Quaternion fromDirection(Vec3f v)
Calculate the quaternion that satisfies the following: result.rotate(Vec3f{0, 0, 1}) == v....
Definition: Quaternion.hpp:194
AH::Quaternion::conjugated
Quaternion conjugated() const
Complex conjugate (doesn't change the original quaternion).
Definition: Quaternion.hpp:84
AH::EulerAngles::yaw
float yaw
Z : drone Z = world +Z.
Definition: Quaternion.hpp:293
AH::EulerAngles::quat2eul
static EulerAngles quat2eul(Quaternion q)
Convert the given quaternion to Euler angles.
Definition: Quaternion.hpp:317
AH::Quaternion::operator*
Quaternion operator*(float lhs, Quaternion rhs)
Scalar multiplication.
Definition: Quaternion.hpp:279
Warnings.hpp
AH::Vec2f::x
float x
The x component of the vector.
Definition: Vector.hpp:44
AH::Quaternion
Type for quaternions of floating point numbers.
Definition: Quaternion.hpp:61
AH::EulerAngles::operator==
bool operator==(EulerAngles rhs) const
Equality check.
Definition: Quaternion.hpp:309
AH::Quaternion::operator-=
Quaternion & operator-=(Quaternion rhs)
Difference of two quaternions a and b is the quaternion multiplication of a and the conjugate of b.
Definition: Quaternion.hpp:91
AH::Quaternion::hamiltonianProduct
static Quaternion hamiltonianProduct(Quaternion q, Quaternion r)
Quaternion multiplication.
Definition: Quaternion.hpp:267
AH::min
constexpr auto min(const T &a, const U &b) -> decltype(b< a ? b :a)
Return the smaller of two numbers/objects.
Definition: MinMaxFix.hpp:15
AH::Quaternion::z
float z
Third vector (imaginary) component .
Definition: Quaternion.hpp:65
AH::Quaternion::operator-
Quaternion operator-() const
Negated quaternion is its conjugate.
Definition: Quaternion.hpp:86
AH::EulerAngles::operator!=
bool operator!=(EulerAngles rhs) const
Inequality check.
Definition: Quaternion.hpp:314
AH_DIAGNOSTIC_POP
#define AH_DIAGNOSTIC_POP()
Definition: Warnings.hpp:36
AH::Vec3f
Type for 3D vectors of floating point numbers.
Definition: Vector.hpp:144
AH::Quaternion::y
float y
Second vector (imaginary) component .
Definition: Quaternion.hpp:64
AH::Quaternion::normalize
Quaternion & normalize()
Normalize this quaternion.
Definition: Quaternion.hpp:136
AH::Quaternion::rotate
Vec3f rotate(Vec3f v) const
Rotate vector by this quaternion.
Definition: Quaternion.hpp:150
AH::EulerAngles
Struct for Euler angles of floating point numbers.
Definition: Quaternion.hpp:292
AH::EulerAngles::EulerAngles
EulerAngles(float yaw, float pitch, float roll)
Create Euler angles with the given values for yaw, pitch and roll.
Definition: Quaternion.hpp:300
AH::Vec3f::x
float x
The x component of the vector.
Definition: Vector.hpp:145
AH::Quaternion::w
float w
Scalar (real) component.
Definition: Quaternion.hpp:62
AH::Quaternion::identity
static Quaternion identity()
Identity quaternion (1,0,0,0).
Definition: Quaternion.hpp:188
AH::EulerAngles::roll
float roll
X": drone X = world +Y.
Definition: Quaternion.hpp:295
AH::EulerAngles::eul2quat
static Quaternion eul2quat(EulerAngles eulerAngles)
Convert the given Euler angles to a quaternion.
Definition: Quaternion.hpp:332
AH::Quaternion::Quaternion
Quaternion()=default
Create a quaternion that is initialized to the identity quaternion.
AH::EulerAngles::EulerAngles
EulerAngles(Quaternion q)
Create Euler angles from the given quaternion.
Definition: Quaternion.hpp:303
AH::Quaternion::operator==
bool operator==(Quaternion rhs) const
Equality check.
Definition: Quaternion.hpp:180
AH::EulerAngles::pitch
float pitch
Y': drone Y = world -X.
Definition: Quaternion.hpp:294
AH::Quaternion::norm
float norm() const
Norm.
Definition: Quaternion.hpp:134
AH::Quaternion::operator/=
Quaternion & operator/=(float rhs)
Scalar division.
Definition: Quaternion.hpp:117
AH::Quaternion::normalized
Quaternion normalized() const
Normalize a copy of this quaternion (doesn't change the original quaternion).
Definition: Quaternion.hpp:139
Degrees.hpp
Conversions between radians and degrees.
AH::Quaternion::fromXYAngle
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...
Definition: Quaternion.hpp:257
AH::Quaternion::operator*=
Quaternion & operator*=(float rhs)
Scalar multiplication.
Definition: Quaternion.hpp:102
AH::Quaternion::operator+=
Quaternion & operator+=(Quaternion rhs)
Sum of two quaterions uses quaternion multiplication.
Definition: Quaternion.hpp:74
AH::Vec2f
Type for 2D vectors of floating point numbers.
Definition: Vector.hpp:43
AH::Quaternion::operator-
Quaternion operator-(Quaternion rhs) const
Difference of two quaternions a and b is the quaternion multiplication of a and the conjugate of b.
Definition: Quaternion.hpp:95
AH::EulerAngles::EulerAngles
EulerAngles()=default
Create Euler angles that are initialized to (0 0 0), or upright.
AH_DIAGNOSTIC_WERROR
#define AH_DIAGNOSTIC_WERROR()
Definition: Warnings.hpp:35
BEGIN_AH_NAMESPACE
#define BEGIN_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:9
AH::Vec3f::z
float z
The z component of the vector.
Definition: Vector.hpp:147
END_AH_NAMESPACE
#define END_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:10
AH::Vec3f::y
float y
The y component of the vector.
Definition: Vector.hpp:146
AH::Quaternion::operator/
Quaternion operator/(float rhs) const
Scalar division.
Definition: Quaternion.hpp:125
AH::Quaternion::operator+
Quaternion operator+(Quaternion rhs) const
Sum of two quaternions uses quaternion multiplication.
Definition: Quaternion.hpp:79
AH::Quaternion::operator!=
bool operator!=(Quaternion rhs) const
Inequality check.
Definition: Quaternion.hpp:185
AH::Quaternion::operator*
Quaternion operator*(float rhs) const
Scalar multiplication.
Definition: Quaternion.hpp:110
AH::Quaternion::operator<<
Print & operator<<(Print &os, Quaternion e)
Printing.
Definition: Quaternion.cpp:28
AH::Quaternion::x
float x
First vector (imaginary) component .
Definition: Quaternion.hpp:63
AH::Quaternion::normSquared
float normSquared() const
Norm squared.
Definition: Quaternion.hpp:132
Vector.hpp
Definition of Vec2f and Vec3f.
AH::Vec2f::norm
float norm() const
Norm.
Definition: Vector.hpp:110
AH::Quaternion::Quaternion
Quaternion(float w, float x, float y, float z)
Create a quaterion with the given values for w, x, y and z.
Definition: Quaternion.hpp:70