-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathlosolver.cpp
311 lines (242 loc) · 12.7 KB
/
losolver.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
losolver.cpp (part of GNSS-Stylus)
Copyright (C) 2020-2021 Pasi Nuutinmaki (gnssstylist<at>sci<dot>fi)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "losolver.h"
#include <iostream>
#include <QtDebug>
#if 0
inline QDebug operator<<(QDebug dbg, const Eigen::Matrix3d& m)
{
dbg << QString::number(m(0,0),'f',3) << ", " << QString::number(m(0,1),'f',3) << ", " << QString::number(m(0,2),'f',3) << "\n" <<
QString::number(m(1,0),'f',3) << ", " << QString::number(m(1,1),'f',3) << ", " << QString::number(m(1,2),'f',3) << "\n" <<
QString::number(m(2,0),'f',3) << ", " << QString::number(m(2,1),'f',3) << ", " << QString::number(m(2,2),'f',3);
return dbg.space();
}
inline QDebug operator<<(QDebug dbg, const Eigen::Transform<double, 3, Eigen::Affine> t)
{
Eigen::Matrix4d m = t.matrix();
dbg << QString::number(m(0,0),'f',3) << ", " << QString::number(m(0,1),'f',3) << ", " << QString::number(m(0,2),'f',3) << ", " << QString::number(m(0,3),'f',3) << "\n" <<
QString::number(m(1,0),'f',3) << ", " << QString::number(m(1,1),'f',3) << ", " << QString::number(m(1,2),'f',3) << ", " << QString::number(m(1,3),'f',3) << "\n" <<
QString::number(m(2,0),'f',3) << ", " << QString::number(m(2,1),'f',3) << ", " << QString::number(m(2,2),'f',3) << ", " << QString::number(m(2,3),'f',3) << "\n" <<
QString::number(m(3,0),'f',3) << ", " << QString::number(m(3,1),'f',3) << ", " << QString::number(m(3,2),'f',3) << ", " << QString::number(m(3,3),'f',3);
return dbg.space();
}
#endif
LOSolver::LOSolver()
{
}
void LOSolver::init(void)
{
errorCode = ERROR_INVALID_REFERENCE_POINTS;
refPointsValid = false;
}
bool LOSolver::setReferencePoints(const Eigen::Vector3d refPoints[3])
{
this->refPoints[0] = refPoints[0];
this->refPoints[1] = refPoints[1];
this->refPoints[2] = refPoints[2];
return calculateReferenceBasis();
}
bool LOSolver::calculateReferenceBasis(void)
{
errorCode = ERROR_NONE;
Eigen::Vector3d vecAtoB = refPoints[1] - refPoints[0];
Eigen::Vector3d vecAtoC = refPoints[2] - refPoints[0];
Eigen::Vector3d vecBtoC = refPoints[2] - refPoints[1];
refDistAB = vecAtoB.norm();
refDistAC = vecAtoC.norm();
refDistBC = vecBtoC.norm();
refCentroid = (refPoints[0] + refPoints[1] + refPoints[2]) / 3;
Eigen::Vector3d vecZDirection = vecAtoB.cross(vecAtoC);
if ((refDistAB == 0) ||
(refDistAC == 0) ||
(refDistBC == 0) ||
(vecZDirection.norm() == 0))
{
// Some of the points are either identical or lie on the same line
refPointsValid = false;
errorCode = ERROR_INVALID_REFERENCE_POINTS;
return false;
}
// As no point should affect the calculation more than others, basis vector X (and indirectly Y)
// is calculated as follows (degrees used in this explanation):
// "Imaginary" vectors from centroid, lying on the plane defined by points A, B and C and
// separated by 120 degrees (1/3 of full round) are rotated around the centroid so that
// the sum of the ("signed") angles between these and the vectors to real corners of the
// triangle are zero.
// "Imaginary" vector initially pointing to point A and rotated as explained above
// is then used as a basis vector X.
// Calculate reference basis vectors so that:
// X points from centroid to the direction calculated as described above.
// Y is in right angle with the unit vector X and lies on the plane defined by points A, B and C,
// Z is in right angle with both unit vectors X and Y
// (and therefore Z is perpendicular to the plane defined by points A, B and C)
Eigen::Vector3d unitVecFromCentroidTowardsA = (refPoints[0] - refCentroid).normalized();
Eigen::Vector3d unitVecFromCentroidTowardsB = (refPoints[1] - refCentroid).normalized();
Eigen::Vector3d unitVecFromCentroidTowardsC = (refPoints[2] - refCentroid).normalized();
double angleBetweenVectorsAndBFromCentroid = acos(unitVecFromCentroidTowardsB.dot(unitVecFromCentroidTowardsA)); // * 360. / (2 * M_PI);
double angleBetweenVectorsAndCFromCentroid = acos(unitVecFromCentroidTowardsC.dot(unitVecFromCentroidTowardsA)); // * 360. / (2 * M_PI);
// No "120 degree" separation is needed here as the different signs cause "cancellation".
double angleError = angleBetweenVectorsAndBFromCentroid - angleBetweenVectorsAndCFromCentroid;
// Basis vector Z is also the axis to turn the "imaginary" vectors from centroid around.
Eigen::Vector3d unitVecZ = vecZDirection.normalized();
// Turn the "imaginary" vector from the centoid around so that the sum of angle differences will be zero.
Eigen::Vector3d unitVecX = Eigen::AngleAxisd(angleError / 3, unitVecZ) * unitVecFromCentroidTowardsA;
// This results in right-handed system. Handedness only affects the debug
// output from getTransformMatrix (if you make the same change to getTransformMatrix-function)
Eigen::Vector3d unitVecY = vecZDirection.cross(unitVecX).normalized();
// Left-handed version:
// Eigen::Vector3d unitVecY = unitVecX.cross(vecZDirection).normalized();
// This is constructed as transposed, but as it's orthogonal, it equals the inverse:
refBasisInverse <<
unitVecX(0), unitVecX(1), unitVecX(2),
unitVecY(0), unitVecY(1), unitVecY(2),
unitVecZ(0), unitVecZ(1), unitVecZ(2);
refPointsValid = true;
#if 0
// Test code. All angle "errors" added together should be close to zero.
Eigen::Vector3d refDirA = unitVecX;
double angleErrorA = acos(refDirA.dot(unitVecCentroidToA)) * (refDirA.cross(unitVecCentroidToA).dot(unitVecZ) < 0 ? 1 : -1);
Eigen::Vector3d refDirB = Eigen::AngleAxisd(-2 * M_PI / 3, unitVecZ) * unitVecX;
double angleErrorB = acos(refDirB.dot(unitVecCentroidToB)) * (refDirB.cross(unitVecCentroidToB).dot(unitVecZ) < 0 ? 1 : -1);
Eigen::Vector3d refDirC = Eigen::AngleAxisd(2 * M_PI / 3, unitVecZ) * unitVecX;
double angleErrorC = acos(refDirC.dot(unitVecCentroidToC)) * (refDirC.cross(unitVecCentroidToC).dot(unitVecZ) < 0 ? 1 : -1);
double angleErrorTotal = angleErrorA + angleErrorB + angleErrorC;
Q_ASSERT(fabs(angleErrorTotal) < 0.001);
#endif
return true;
}
bool LOSolver::setPoints(const Eigen::Vector3d points[3])
{
// Error checking is done when operating with the points
errorCode = ERROR_NONE;
this->points[0] = points[0];
this->points[1] = points[1];
this->points[2] = points[2];
return true;
}
bool LOSolver::getTransformMatrix(Eigen::Transform<double, 3, Eigen::Affine>& transform,
Eigen::Transform<double, 3, Eigen::Affine>* orientationTransform_Debug)
{
if (!refPointsValid)
{
errorCode = ERROR_INVALID_REFERENCE_POINTS;
return false;
}
else
{
errorCode = ERROR_NONE;
}
Eigen::Vector3d vecAtoB = points[1] - points[0];
Eigen::Vector3d vecAtoC = points[2] - points[0];
Eigen::Vector3d vecBtoC = points[2] - points[1];
double distAB = vecAtoB.norm();
double distAC = vecAtoC.norm();
double distBC = vecBtoC.norm();
// TODO: Add comparison of distances with the reference basis points
Eigen::Vector3d centroid = (points[0] + points[1] + points[2]) / 3;
Eigen::Vector3d vecZDirection = vecAtoB.cross(vecAtoC);
if ((distAB == 0) ||
(distAC == 0) ||
(distBC == 0) ||
(vecZDirection.norm() == 0))
{
// Some of the points are either identical or lie on the same line
errorCode = ERROR_INVALID_POINTS;
return false;
}
// See comments on the calculateReferenceBasis-function for an explanation on
// how the basis is calculated here (the following few lines are almost identical).
Eigen::Vector3d unitVecFromCentroidTowardsA = (points[0] - centroid).normalized();
Eigen::Vector3d unitVecFromCentroidTowardsB = (points[1] - centroid).normalized();
Eigen::Vector3d unitVecFromCentroidTowardsC = (points[2] - centroid).normalized();
double angleBetweenVectorsAndBFromCentroid = acos(unitVecFromCentroidTowardsB.dot(unitVecFromCentroidTowardsA)); // * 360. / (2 * M_PI);
double angleBetweenVectorsAndCFromCentroid = acos(unitVecFromCentroidTowardsC.dot(unitVecFromCentroidTowardsA)); // * 360. / (2 * M_PI);
double angleError = angleBetweenVectorsAndBFromCentroid - angleBetweenVectorsAndCFromCentroid;
Eigen::Vector3d unitVecZ = vecZDirection.normalized();
Eigen::Vector3d unitVecX = Eigen::AngleAxisd(angleError / 3, unitVecZ) * unitVecFromCentroidTowardsA;
// This results in right-handed system. Handedness only affects the debug
// output from getTransformMatrix
Eigen::Vector3d unitVecY = vecZDirection.cross(unitVecX).normalized();
// Left-handed version:
// Eigen::Vector3d unitVecY = unitVecX.cross(vecZDirection).normalized();
Eigen::Matrix3d orientationBasis;
orientationBasis <<
unitVecX(0), unitVecY(0), unitVecZ(0),
unitVecX(1), unitVecY(1), unitVecZ(1),
unitVecX(2), unitVecY(2), unitVecZ(2);
Eigen::Matrix3d finalTransform = orientationBasis * refBasisInverse;
// Origin can be now calculated using centroids and the newly calculated matrix
Eigen::Vector3d origin = centroid - (finalTransform * refCentroid);
transform.matrix() <<
finalTransform(0,0), finalTransform(0,1), finalTransform(0,2), origin(0),
finalTransform(1,0), finalTransform(1,1), finalTransform(1,2), origin(1),
finalTransform(2,0), finalTransform(2,1), finalTransform(2,2), origin(2),
0, 0, 0, 1;
if (orientationTransform_Debug)
{
orientationTransform_Debug->matrix() <<
unitVecX(0), unitVecY(0), unitVecZ(0), centroid(0),
unitVecX(1), unitVecY(1), unitVecZ(1), centroid(1),
unitVecX(2), unitVecY(2), unitVecZ(2), centroid(2),
0, 0, 0, 1;
}
return true;
}
bool LOSolver::getYawPitchRollAngles(const Eigen::Transform<double, 3, Eigen::Affine>& transform,
double& yaw, double& pitch, double& roll)
{
return getYawPitchRollAngles(transform, yaw, pitch, roll, errorCode);
}
bool LOSolver::getYawPitchRollAngles(Eigen::Transform<double, 3, Eigen::Affine> transform,
double& yaw, double& pitch, double& roll,
ErrorCode& errorCode)
{
Eigen::Matrix3d linearPart = transform.linear();
pitch = -asin(linearPart(2, 0));
// Calculate roll by using a "rotational plane" that is perpendicular
// to the "forward"-vector and one axis (here planeVecX) is parallel to the "ground plane"
// I tried to use Eigen's eulerAngles-function to no avail (commented out code below).
// Maybe there's a way to use it that I didn't find(?)
Eigen::Vector3d forwardVec(linearPart(0,0), linearPart(1,0), linearPart(2,0));
const Eigen::Vector3d unitVecDown(0, 0, 1);
// Vector parallel to the ground plane:
Eigen::Vector3d planeVecX = -unitVecDown.cross(forwardVec);
if (planeVecX.norm() == 0)
{
// pitch is directly up or down -> yaw and roll are on the same axis (="gimbal lock")
// -> Calculate yaw based on the object's "down" vector.
yaw = atan2(linearPart(1,2), linearPart(0,2));
roll = 0; // Roll is meaningless in this case
}
else
{
yaw = atan2(linearPart(1,0), linearPart(0,0));
planeVecX.normalize();
// Vector perpendicular to the vector parallel to the ground plane and "forward"-vector:
Eigen::Vector3d planeVecY = -forwardVec.cross(planeVecX);
planeVecY.normalize();
Eigen::Vector3d objectDownVec(linearPart(0,2), linearPart(1,2), linearPart(2,2));
roll = -atan2(-objectDownVec.dot(planeVecX), objectDownVec.dot(planeVecY));
}
/* Tried to use Eigen's eulerAngles here but failed. Maybe there's a way?
Eigen::Vector3d angles;
angles = coordConv*transform.linear().eulerAngles(?, ?, ?);
heading = angles(?);
pitch = angles(?);
roll = angles(?);
*/
errorCode = ERROR_NONE;
return true;
}