LOST 0.0.1
LOST: Open-source Star Tracker
Loading...
Searching...
No Matches
attitude-utils.cpp
Go to the documentation of this file.
1#include "attitude-utils.hpp"
2
3#include <math.h>
4#include <assert.h>
5#include <cmath>
6#include <iostream>
7
8#include "decimal.hpp"
10
11namespace lost {
12
15 return Quaternion(
16 real*other.real - i*other.i - j*other.j - k*other.k,
17 real*other.i + other.real*i + j*other.k - k*other.j,
18 real*other.j + other.real*j + k*other.i - i*other.k,
19 real*other.k + other.real*k + i*other.j - j*other.i);
20}
21
24 return Quaternion(real, -i, -j, -k);
25}
26
29 return { i, j, k };
30}
31
33void Quaternion::SetVector(const Vec3 &vec) {
34 i = vec.x;
35 j = vec.y;
36 k = vec.z;
37}
38
41 real = 0;
42 SetVector(input);
43}
44
46Quaternion::Quaternion(const Vec3 &input, decimal theta) {
47 real = DECIMAL_COS(theta/2);
48 // the compiler will optimize it. Right?
49 i = input.x * DECIMAL_SIN(theta/2);
50 j = input.y * DECIMAL_SIN(theta/2);
51 k = input.z * DECIMAL_SIN(theta/2);
52}
53
55Vec3 Quaternion::Rotate(const Vec3 &input) const {
56 // TODO: optimize
57 return ((*this)*Quaternion(input)*Conjugate()).Vector();
58}
59
62 if (real <= -1) {
63 return 0; // 180*2=360=0
64 }
65 // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan? (same as in AngleUnit)
66 return (real >= 1 ? 0 : DECIMAL_ACOS(real))*2;
67}
68
70 decimal rawAngle = Angle();
71 return rawAngle > DECIMAL_M_PI
72 ? 2*DECIMAL_M_PI - rawAngle
73 : rawAngle;
74}
75
77 real = DECIMAL_COS(newAngle/2);
78 SetVector(Vector().Normalize() * DECIMAL_SIN(newAngle/2));
79}
80
82 // Working out these equations would be a pain in the ass. Thankfully, this wikipedia page:
83 // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Quaternion_to_Euler_angles_conversion
84 // uses almost exactly the same euler angle scheme that we do, so we copy their equations almost
85 // wholesale. The only differences are that 1, we use de and roll in the opposite directions,
86 // and 2, we store the conjugate of the quaternion (double check why?), which means we need to
87 // invert the final de and roll terms, as well as negate all the terms involving a mix between
88 // the real and imaginary parts.
89 decimal ra = DECIMAL_ATAN2(2*(-real*k+i*j), 1-2*(j*j+k*k));
90 if (ra < 0)
91 ra += 2*DECIMAL_M_PI;
92 decimal de = -DECIMAL_ASIN(2*(-real*j-i*k)); // allow de to be positive or negaive, as is convention
93 decimal roll = -DECIMAL_ATAN2(2*(-real*i+j*k), 1-2*(i*i+j*j));
94 if (roll < 0)
95 roll += 2*DECIMAL_M_PI;
96
97 return EulerAngles(ra, de, roll);
98}
99
101 assert(roll >= DECIMAL(0.0) && roll <= 2*DECIMAL_M_PI);
102 assert(ra >= DECIMAL(0.0) && ra <= 2*DECIMAL_M_PI);
103 assert(dec >= -DECIMAL_M_PI && dec <= DECIMAL_M_PI);
104
105 // when we are modifying the coordinate axes, the quaternion multiplication works so that the
106 // rotations are applied from left to right. This is the opposite as for modifying vectors.
107
108 // It is indeed correct that a positive rotation in our right-handed coordinate frame is in the
109 // clockwise direction when looking down/through the axis of rotation. Just like the right hand
110 // rule for magnetic field around a current-carrying conductor.
111 Quaternion a = Quaternion({ 0, 0, 1 }, ra);
112 Quaternion b = Quaternion({ 0, 1, 0 }, -dec);
113 Quaternion c = Quaternion({ 1, 0, 0 }, -roll);
114 Quaternion result = (a*b*c).Conjugate();
115 assert(result.IsUnit(0.00001));
116 return result;
117}
118
120bool Quaternion::IsUnit(decimal tolerance) const {
121 return abs(i*i+j*j+k*k+real*real - 1) < tolerance;
122}
123
128 if (real >= 0) {
129 return *this;
130 }
131
132 return Quaternion(-real, -i, -j, -k);
133}
134
137 return {
138 DECIMAL_COS(ra)*DECIMAL_COS(de),
139 DECIMAL_SIN(ra)*DECIMAL_COS(de),
140 DECIMAL_SIN(de),
141 };
142}
143
145void SpatialToSpherical(const Vec3 &vec, decimal *ra, decimal *de) {
146 *ra = DECIMAL_ATAN2(vec.y, vec.x);
147 if (*ra < 0)
148 *ra += DECIMAL_M_PI*2;
149 *de = DECIMAL_ASIN(vec.z);
150}
151
153 return rad*DECIMAL(180.0)/DECIMAL_M_PI;
154}
155
157 return deg/DECIMAL(180.0)*DECIMAL_M_PI;
158}
159
161 return RadToDeg(rad) * DECIMAL(3600.0);
162}
163
165 return DegToRad(arcSec / DECIMAL(3600.0));
166}
167
169 // first but not last chatgpt generated code in lost:
170 decimal result = x - mod * DECIMAL_FLOOR(x / mod);
171 return result >= 0 ? result : result + mod;
172}
173
176 return DECIMAL_FMA(x,x,DECIMAL_FMA(y,y, z*z));
177}
178
181 return DECIMAL_FMA(x,x, y*y);
182}
183
185 return DECIMAL_HYPOT(DECIMAL_HYPOT(x, y), z); // not sure if this is faster than a simple sqrt, but it does have less error?
186}
187
189 return DECIMAL_HYPOT(x, y);
190}
191
194 decimal mag = Magnitude();
195 return {
196 x/mag, y/mag, z/mag,
197 };
198}
199
201decimal Vec3::operator*(const Vec3 &other) const {
202 return DECIMAL_FMA(x,other.x, DECIMAL_FMA(y,other.y, z*other.z));
203}
204
206Vec2 Vec2::operator*(const decimal &other) const {
207 return { x*other, y*other };
208}
209
211Vec3 Vec3::operator*(const decimal &other) const {
212 return { x*other, y*other, z*other };
213}
214
216Vec2 Vec2::operator+(const Vec2 &other) const {
217 return {x + other.x, y + other.y };
218}
219
221Vec2 Vec2::operator-(const Vec2 &other) const {
222 return { x - other.x, y - other.y };
223}
224
226Vec3 Vec3::operator-(const Vec3 &other) const {
227 return { x - other.x, y - other.y, z - other.z };
228}
229
231Vec3 Vec3::CrossProduct(const Vec3 &other) const {
232 return {
233 y*other.z - z*other.y,
234 -(x*other.z - z*other.x),
235 x*other.y - y*other.x,
236 };
237}
238
240Mat3 Vec3::OuterProduct(const Vec3 &other) const {
241 return {
242 x*other.x, x*other.y, x*other.z,
243 y*other.x, y*other.y, y*other.z,
244 z*other.x, z*other.y, z*other.z
245 };
246}
247
249Vec3 Vec3::operator*(const Mat3 &other) const {
250 return {
251 x*other.At(0,0) + y*other.At(0,1) + z*other.At(0,2),
252 x*other.At(1,0) + y*other.At(1,1) + z*other.At(1,2),
253 x*other.At(2,0) + y*other.At(2,1) + z*other.At(2,2),
254 };
255}
256
258decimal Mat3::At(int i, int j) const {
259 return x[3*i+j];
260}
261
263Vec3 Mat3::Column(int j) const {
264 return {At(0,j), At(1,j), At(2,j)};
265}
266
268Vec3 Mat3::Row(int i) const {
269 return {At(i,0), At(i,1), At(i,2)};
270}
271
273Mat3 Mat3::operator+(const Mat3 &other) const {
274 return {
275 At(0,0)+other.At(0,0), At(0,1)+other.At(0,1), At(0,2)+other.At(0,2),
276 At(1,0)+other.At(1,0), At(1,1)+other.At(1,1), At(1,2)+other.At(1,2),
277 At(2,0)+other.At(2,0), At(2,1)+other.At(2,1), At(2,2)+other.At(2,2)
278 };
279}
280
282Mat3 Mat3::operator*(const Mat3 &other) const {
283#define _MATMUL_ENTRY(row, col) At(row,0)*other.At(0,col) + At(row,1)*other.At(1,col) + At(row,2)*other.At(2,col)
284 return {
288 };
289#undef _MATMUL_ENTRY
290}
291
293Vec3 Mat3::operator*(const Vec3 &vec) const {
294 return {
295 vec.x*At(0,0) + vec.y*At(0,1) + vec.z*At(0,2),
296 vec.x*At(1,0) + vec.y*At(1,1) + vec.z*At(1,2),
297 vec.x*At(2,0) + vec.y*At(2,1) + vec.z*At(2,2),
298 };
299}
300
302Mat3 Mat3::operator*(const decimal &s) const {
303 return {
304 s*At(0,0), s*At(0,1), s*At(0,2),
305 s*At(1,0), s*At(1,1), s*At(1,2),
306 s*At(2,0), s*At(2,1), s*At(2,2)
307 };
308}
309
312 return {
313 At(0,0), At(1,0), At(2,0),
314 At(0,1), At(1,1), At(2,1),
315 At(0,2), At(1,2), At(2,2),
316 };
317}
318
321 return At(0,0) + At(1,1) + At(2,2);
322}
323
326 return (At(0,0) * (At(1,1)*At(2,2) - At(2,1)*At(1,2))) - (At(0,1) * (At(1,0)*At(2,2) - At(2,0)*At(1,2))) + (At(0,2) * (At(1,0)*At(2,1) - At(2,0)*At(1,1)));
327}
328
331 // https://ardoris.wordpress.com/2008/07/18/general-formula-for-the-inverse-of-a-3x3-matrix/
332 decimal scalar = 1 / Det();
333
334 Mat3 res = {
335 At(1,1)*At(2,2) - At(1,2)*At(2,1), At(0,2)*At(2,1) - At(0,1)*At(2,2), At(0,1)*At(1,2) - At(0,2)*At(1,1),
336 At(1,2)*At(2,0) - At(1,0)*At(2,2), At(0,0)*At(2,2) - At(0,2)*At(2,0), At(0,2)*At(1,0) - At(0,0)*At(1,2),
337 At(1,0)*At(2,1) - At(1,1)*At(2,0), At(0,1)*At(2,0) - At(0,0)*At(2,1), At(0,0)*At(1,1) - At(0,1)*At(1,0)
338 };
339
340 return res * scalar;
341}
342
344 const Mat3 kIdentityMat3 = {1,0,0,
345 0,1,0,
346 0,0,1};
347
349 : type(AttitudeType::QuaternionType), quaternion(quat) {}
350
352 : type(AttitudeType::DCMType), dcm(matrix) {}
353
356 Vec3 x = quat.Rotate({1, 0, 0});
357 Vec3 y = quat.Rotate({0, 1, 0});
358 Vec3 z = quat.Rotate({0, 0, 1});
359 return {
360 x.x, y.x, z.x,
361 x.y, y.y, z.y,
362 x.z, y.z, z.z,
363 };
364}
365
368 // Make a quaternion that rotates the reference frame X-axis into the dcm's X-axis, just like
369 // the DCM itself does
370 Vec3 oldXAxis = Vec3({1, 0, 0});
371 Vec3 newXAxis = dcm.Column(0); // this is where oldXAxis is mapped to
372 assert(DECIMAL_ABS(newXAxis.Magnitude()-1) < DECIMAL(0.001));
373 Vec3 xAlignAxis = oldXAxis.CrossProduct(newXAxis).Normalize();
374 decimal xAlignAngle = AngleUnit(oldXAxis, newXAxis);
375 Quaternion xAlign(xAlignAxis, xAlignAngle);
376
377 // Make a quaternion that will rotate the Y-axis into place
378 Vec3 oldYAxis = xAlign.Rotate({0, 1, 0});
379 Vec3 newYAxis = dcm.Column(1);
380 // we still need to take the cross product, because acos returns a value in [0,pi], and thus we
381 // need to know which direction to rotate before we rotate. We do this by checking if the cross
382 // product of old and new y axes is in the same direction as the new X axis.
383 bool rotateClockwise = oldYAxis.CrossProduct(newYAxis) * newXAxis > 0; // * is dot product
384 Quaternion yAlign({1, 0, 0}, AngleUnit(oldYAxis, newYAxis) * (rotateClockwise ? 1 : -1));
385
386 // We're done! There's no need to worry about the Z-axis because the handed-ness of the
387 // coordinate system is always preserved, which means the Z-axis is uniquely determined as the
388 // cross product of the X- and Y-axes. This goes to show that DCMs store redundant information.
389
390 // we want the y alignment to have memory of X, which means we put its multiplication on the
391 // right
392 return xAlign*yAlign;
393}
394
397 switch (type) {
398 case AttitudeType::QuaternionType:
399 return quaternion;
400 case AttitudeType::DCMType:
401 return DCMToQuaternion(dcm);
402 default:
403 assert(false);
404 }
405}
406
409 switch (type) {
410 case AttitudeType::DCMType:
411 return dcm;
412 case AttitudeType::QuaternionType:
413 return QuaternionToDCM(quaternion);
414 default:
415 assert(false);
416 }
417}
418
420Vec3 Attitude::Rotate(const Vec3 &vec) const {
421 switch (type) {
422 case AttitudeType::DCMType:
423 return dcm*vec;
424 case AttitudeType::QuaternionType:
425 return quaternion.Rotate(vec);
426 default:
427 assert(false);
428 }
429}
430
433 switch (type) {
434 case AttitudeType::DCMType:
435 return GetQuaternion().ToSpherical();
436 case AttitudeType::QuaternionType:
437 return quaternion.ToSpherical();
438 default:
439 assert(false);
440 }
441}
442
443bool Attitude::IsKnown() const {
444 switch (type) {
445 case AttitudeType::DCMType:
446 case AttitudeType::QuaternionType:
447 return true;
448 default:
449 return false;
450 }
451}
452
459
468
470decimal Angle(const Vec3 &vec1, const Vec3 &vec2) {
471 return AngleUnit(vec1.Normalize(), vec2.Normalize());
472}
473
481 // TODO: we shouldn't need this nonsense, right? how come acos sometimes gives nan?
482 return dot >= 1 ? 0 : dot <= -1 ? DECIMAL_M_PI-DECIMAL(0.0000001) : DECIMAL_ACOS(dot);
483}
484
485}
#define _MATMUL_ENTRY(row, col)
EulerAngles ToSpherical() const
Get the euler angles from the attitude, converting from whatever format is stored.
Vec3 Rotate(const Vec3 &) const
Convert a vector from the reference frame to the body frame.
Mat3 GetDCM() const
Get the rotation matrix (direction cosine matrix) representing the attitude, converting from whatever...
Attitude()=default
constructs unknown attitude:
Quaternion GetQuaternion() const
Get the quaternion representing the attitude, converting from whatever format is stored.
bool IsKnown() const
A "human-readable" way to represent a 3d rotation or orientation.
3x3 vector with decimaling point components
Mat3 Inverse() const
Inverse of a matrix.
Vec3 Column(int) const
Get the column at index j.
Vec3 Row(int) const
Get the row at index i.
decimal Det() const
Determinant of a matrix.
Mat3 Transpose() const
Transpose of a matrix.
decimal Trace() const
Trace of a matrix.
decimal At(int i, int j) const
Access the i,j-th element of the matrix.
Mat3 operator+(const Mat3 &) const
Normal matrix addition.
decimal x[9]
Mat3 operator*(const Mat3 &) const
Naive matrix multiplication.
A quaternion is a common way to represent a 3d rotation.
Quaternion Canonicalize() const
Ensure that the quaternion's real part is nonnegative.
Vec3 Vector() const
The vector formed by imaginary components of the quaternion. The axis of the represented rotation.
decimal Angle() const
How many radians the rotation represented by this quaternion has.
EulerAngles ToSpherical() const
Vec3 Rotate(const Vec3 &) const
Rotate a 3d vector according to the rotation represented by the quaternion.
Quaternion Conjugate() const
Effectively computes a quaternion representing the inverse rotation of the original.
void SetVector(const Vec3 &)
Set imaginary components.
Quaternion()=default
Quaternion operator*(const Quaternion &other) const
Multiply two quaternions using the usual definition of quaternion multiplication (effectively compose...
void SetAngle(decimal)
bool IsUnit(decimal tolerance) const
Whether the quaternion is a unit quaternion. All quaternions representing rotations should be units.
decimal SmallestAngle() const
Returns the smallest angle that can be used to represent the rotation represented by the quaternion.
Three dimensional vector with decimaling point components.
Vec3 Normalize() const
Create a vector pointing in the same direction with magnitude 1.
decimal MagnitudeSq() const
The square of the magnitude.
Mat3 OuterProduct(const Vec3 &) const
The outer product of two vectors.
decimal operator*(const Vec3 &) const
Dot product.
Vec3 CrossProduct(const Vec3 &) const
Usual vector cross product.
decimal Magnitude() const
Vec3 operator-(const Vec3 &) const
Usual vector subtraction.
double decimal
Definition decimal.hpp:11
#define DECIMAL_ASIN(x)
Definition decimal.hpp:55
#define DECIMAL(x)
Definition decimal.hpp:21
#define DECIMAL_FMA(x, y, z)
Definition decimal.hpp:61
#define DECIMAL_M_PI
Definition decimal.hpp:29
#define DECIMAL_HYPOT(x, y)
Definition decimal.hpp:62
#define DECIMAL_SIN(x)
Definition decimal.hpp:52
#define DECIMAL_ABS(x)
Definition decimal.hpp:49
#define DECIMAL_ATAN2(x, y)
Definition decimal.hpp:58
#define DECIMAL_COS(x)
Definition decimal.hpp:53
#define DECIMAL_ACOS(x)
Definition decimal.hpp:56
#define DECIMAL_FLOOR(x)
Definition decimal.hpp:48
LOST starting point.
Mat3 QuaternionToDCM(const Quaternion &quat)
Convert a quaternion to a rotation matrix (Direction Cosine Matrix)
decimal RadToArcSec(decimal rad)
decimal ArcSecToRad(decimal arcSec)
void SpatialToSpherical(const Vec3 &vec, decimal *ra, decimal *de)
Convert from a 3d point on the unit sphere to right ascension & declination.
decimal AngleUnit(const Vec3 &vec1, const Vec3 &vec2)
Calculate the inner angle, in radians, between two /unit/ vectors.
decimal DegToRad(decimal deg)
Quaternion SphericalToQuaternion(decimal ra, decimal dec, decimal roll)
Return a quaternion that will reorient the coordinate axes so that the x-axis points at the given rig...
Vec3 SphericalToSpatial(decimal ra, decimal de)
Convert from right ascension & declination to a 3d point on the unit sphere.
decimal RadToDeg(decimal rad)
const Mat3 kIdentityMat3
3x3 identity matrix
Quaternion DCMToQuaternion(const Mat3 &dcm)
Convert a rotation matrix (Direction Cosine Matrix) to a quaternion representing the same rotation.
decimal Angle(const Vec3 &vec1, const Vec3 &vec2)
Calculate the inner angle, in radians, between two vectors.
decimal DecimalModulo(decimal x, decimal mod)
Given a decimal, find it "modulo" another decimal, in the true mathematical sense (not remainder).
void SerializePrimitive(SerializeContext *ser, const T &val)
void SerializeVec3(SerializeContext *ser, const Vec3 &vec)
Serialize a Vec3 to buffer. Takes up space according to SerializeLengthVec3.
Vec3 DeserializeVec3(DeserializeContext *des)
A two dimensional vector with decimaling point components.
decimal MagnitudeSq() const
The square of the magnitude.
Vec2 operator-(const Vec2 &) const
Usual vector subtraction.
decimal operator*(const Vec2 &) const
Vec2 operator+(const Vec2 &) const
Usual vector addition.
decimal Magnitude() const