Visual Servoing Platform  version 3.6.1 under development (2024-04-20)
testRotation.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See https://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Test theta.u and quaternion multiplication.
33  */
34 
40 #include <visp3/core/vpConfig.h>
41 
42 #ifdef VISP_HAVE_CATCH2
43 
44 #include <visp3/core/vpThetaUVector.h>
45 #include <visp3/core/vpUniRand.h>
46 
47 #define CATCH_CONFIG_RUNNER
48 #include <catch.hpp>
49 
50 namespace
51 {
52 vpThetaUVector generateThetaU(vpUniRand &rng)
53 {
54  return vpThetaUVector(
55  vpMath::rad(rng.uniform(-180.0, 180.0)) *
56  vpColVector({ rng.uniform(-1.0, 1.0), rng.uniform(-1.0, 1.0), rng.uniform(-1.0, 1.0) }).normalize());
57 }
58 
59 vpQuaternionVector generateQuat(vpUniRand &rng)
60 {
61  const double angle = vpMath::rad(rng.uniform(-180.0, 180.0));
62  const double ctheta = std::cos(angle);
63  const double stheta = std::sin(angle);
64  const double ax = rng.uniform(-1.0, 1.0);
65  const double ay = rng.uniform(-1.0, 1.0);
66  const double az = rng.uniform(-1.0, 1.0);
67  return vpQuaternionVector(stheta * ax, stheta * ay, stheta * az, ctheta);
68 }
69 } // namespace
70 
71 
72 bool test(const std::string &s, const vpArray2D<double> &v, const std::vector<double> &bench)
73 {
74  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
75  if (bench.size() != v.size()) {
76  std::cout << "Test fails: bad size wrt bench" << std::endl;
77  return false;
78  }
79  for (unsigned int i = 0; i < v.size(); i++) {
80  if (std::fabs(v.data[i] - bench[i]) > std::fabs(v.data[i]) * std::numeric_limits<double>::epsilon()) {
81  std::cout << "Test fails: bad content" << std::endl;
82  return false;
83  }
84  }
85 
86  return true;
87 }
88 
89 bool test(const std::string &s, const vpArray2D<double> &v, const vpColVector &bench)
90 {
91  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
92  if (bench.size() != v.size()) {
93  std::cout << "Test fails: bad size wrt bench" << std::endl;
94  return false;
95  }
96  for (unsigned int i = 0; i < v.size(); i++) {
97  if (std::fabs(v.data[i] - bench[i]) > std::fabs(v.data[i]) * std::numeric_limits<double>::epsilon()) {
98  std::cout << "Test fails: bad content" << std::endl;
99  return false;
100  }
101  }
102 
103  return true;
104 }
105 
106 bool test(const std::string &s, const vpRotationVector &v, const double &bench)
107 {
108  std::cout << s << "(" << v.getRows() << "," << v.getCols() << ") = [" << v << "]" << std::endl;
109  for (unsigned int i = 0; i < v.size(); i++) {
110  if (std::fabs(v[i] - bench) > std::fabs(v[i]) * std::numeric_limits<double>::epsilon()) {
111  std::cout << "Test fails: bad content" << std::endl;
112  return false;
113  }
114  }
115 
116  return true;
117 }
118 
119 bool test_matrix_equal(const vpHomogeneousMatrix &M1, const vpHomogeneousMatrix &M2, double epsilon = 1e-10)
120 {
121  for (unsigned int i = 0; i < 4; i++) {
122  for (unsigned int j = 0; j < 4; j++) {
123  if (!vpMath::equal(M1[i][j], M2[i][j], epsilon)) {
124  return false;
125  }
126  }
127  }
128  return true;
129 }
130 
131 TEST_CASE("Common rotation operations", "[rotation]")
132 {
133  SECTION("Theta u initialization")
134  {
136  std::vector<double> bench1(3, vpMath::rad(10));
137  vpColVector bench3(3, vpMath::rad(10));
138  CHECK(test("r1", r1, bench1));
139 
140  bench1.clear();
141  bench1 = r1.toStdVector();
142  CHECK(test("r1", r1, bench1));
143 
144  r1.buildFrom(bench3);
145  CHECK(test("r1", r1, bench3));
146 
147  vpThetaUVector r2 = r1;
148  CHECK(test("r2", r2, bench1));
149  CHECK(r2.data != r1.data);
150 
151  CHECK(test("r2", r2, vpMath::rad(10)));
152 
153  vpThetaUVector r3;
154  r3 = vpMath::rad(10);
155  CHECK(test("r3", r3, bench1));
156 
157  for (unsigned int i = 0; i < r3.size(); i++) {
158  CHECK(std::fabs(r3[i] - bench1[i]) < std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
159  }
160 
161  const vpColVector r4 = 0.5 * r1;
162  std::vector<double> bench2(3, vpMath::rad(5));
163  CHECK(test("r4", r4, bench2));
164 
165  const vpThetaUVector r5(r3);
166  CHECK(test("r5", r5, bench1));
167  }
168  SECTION("Rxyz initialization")
169  {
171  std::vector<double> bench1(3, vpMath::rad(10));
172  vpColVector bench3(3, vpMath::rad(10));
173  CHECK(test("r1", r1, bench1));
174 
175  bench1.clear();
176  bench1 = r1.toStdVector();
177  CHECK(test("r1", r1, bench1));
178 
179  r1.buildFrom(bench3);
180  CHECK(test("r1", r1, bench3));
181 
182  vpRxyzVector r2 = r1;
183  CHECK(test("r2", r2, bench1));
184 
185  CHECK(test("r2", r2, vpMath::rad(10)));
186 
187  vpRxyzVector r3;
188  r3 = vpMath::rad(10);
189  CHECK(test("r3", r3, bench1));
190 
191  for (unsigned int i = 0; i < r3.size(); i++) {
192  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
193  }
194 
195  vpColVector r4 = 0.5 * r1;
196  std::vector<double> bench2(3, vpMath::rad(5));
197  CHECK(test("r4", r4, bench2));
198 
199  vpRxyzVector r5(r3);
200  CHECK(test("r5", r5, bench1));
201  }
202  SECTION("rzyx initialization")
203  {
205  std::vector<double> bench1(3, vpMath::rad(10));
206  vpColVector bench3(3, vpMath::rad(10));
207  CHECK(test("r1", r1, bench1));
208 
209  bench1.clear();
210  bench1 = r1.toStdVector();
211  CHECK(test("r1", r1, bench1));
212 
213  r1.buildFrom(bench3);
214  CHECK(test("r1", r1, bench3));
215 
216  vpRzyxVector r2 = r1;
217  CHECK(test("r2", r2, bench1));
218 
219  CHECK(test("r2", r2, vpMath::rad(10)));
220 
221  vpRzyxVector r3;
222  r3 = vpMath::rad(10);
223  CHECK(test("r3", r3, bench1));
224 
225  for (unsigned int i = 0; i < r3.size(); i++) {
226  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
227  }
228 
229  vpColVector r4 = 0.5 * r1;
230  std::vector<double> bench2(3, vpMath::rad(5));
231  CHECK(test("r4", r4, bench2));
232 
233  vpRzyxVector r5(r3);
234  CHECK(test("r5", r5, bench1));
235  }
236  SECTION("rzyz initialiation")
237  {
239  std::vector<double> bench1(3, vpMath::rad(10));
240  vpColVector bench3(3, vpMath::rad(10));
241  CHECK(test("r1", r1, bench1));
242 
243  bench1.clear();
244  bench1 = r1.toStdVector();
245  CHECK(test("r1", r1, bench1));
246 
247  r1.buildFrom(bench3);
248  CHECK(test("r1", r1, bench3));
249 
250  vpRzyzVector r2 = r1;
251  CHECK(test("r2", r2, bench1));
252 
253  CHECK(test("r2", r2, vpMath::rad(10)));
254 
255  vpRzyzVector r3;
256  r3 = vpMath::rad(10);
257  CHECK(test("r3", r3, bench1));
258 
259  for (unsigned int i = 0; i < r3.size(); i++) {
260  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
261  }
262 
263  vpColVector r4 = 0.5 * r1;
264  std::vector<double> bench2(3, vpMath::rad(5));
265  CHECK(test("r4", r4, bench2));
266 
267  vpRzyzVector r5(r3);
268  CHECK(test("r5", r5, bench1));
269  }
270  SECTION("Test quaternion initialization", "[quaternion]")
271  {
273  std::vector<double> bench1(4, vpMath::rad(10));
274  vpColVector bench3(4, vpMath::rad(10));
275  CHECK(test("r1", r1, bench1));
276 
277  bench1.clear();
278  bench1 = r1.toStdVector();
279  CHECK(test("r1", r1, bench1));
280 
281  r1.buildFrom(bench3);
282  CHECK(test("r1", r1, bench3));
283 
284  vpQuaternionVector r2 = r1;
285  CHECK(test("r2", r2, bench1));
286 
287  CHECK(test("r2", r2, vpMath::rad(10)));
288 
290  r3.set(vpMath::rad(10), vpMath::rad(10), vpMath::rad(10), vpMath::rad(10));
291  CHECK(test("r3", r3, bench1));
292 
293  for (unsigned int i = 0; i < r3.size(); i++) {
294  CHECK(std::fabs(r3[i] - bench1[i]) <= std::fabs(r3[i]) * std::numeric_limits<double>::epsilon());
295  }
296 
297  vpColVector r4 = 0.5 * r1;
298  std::vector<double> bench2(4, vpMath::rad(5));
299  CHECK(test("r4", r4, bench2));
300 
301  vpQuaternionVector r5(r3);
302  CHECK(test("r5", r5, bench1));
303  }
304  SECTION("Conversions")
305  {
307  for (int i = -10; i < 10; i++) {
308  for (int j = -10; j < 10; j++) {
309  vpThetaUVector tu(vpMath::rad(90 + i), vpMath::rad(170 + j), vpMath::rad(45));
310  tu.buildFrom(vpRotationMatrix(tu)); // put some coherence into rotation convention
311 
312  std::cout << "Initialization " << std::endl;
313 
314  double theta;
315  vpColVector u;
316  tu.extract(theta, u);
317 
318  std::cout << "theta=" << vpMath::deg(theta) << std::endl;
319  std::cout << "u=" << u << std::endl;
320 
321  std::cout << "From vpThetaUVector to vpRotationMatrix " << std::endl;
322  R.buildFrom(tu);
323 
324  std::cout << "Matrix R";
325  CHECK(R.isARotationMatrix());
326 
327  std::cout << R << std::endl;
328 
329  std::cout << "From vpRotationMatrix to vpQuaternionVector " << std::endl;
330  vpQuaternionVector q(R);
331  CHECK(q.magnitude() == Approx(1.0).margin(1e-4));
332  std::cout << q << std::endl;
333 
334  R.buildFrom(q);
335  CHECK(R.isARotationMatrix());
336  std::cout << "From vpQuaternionVector to vpRotationMatrix " << std::endl;
337 
338  std::cout << "From vpRotationMatrix to vpRxyzVector " << std::endl;
339  vpRxyzVector RxyzBuildFromR(R);
340  std::cout << RxyzBuildFromR << std::endl;
341 
342  std::cout << "From vpRxyzVector to vpThetaUVector " << std::endl;
343  std::cout << " use From vpRxyzVector to vpRotationMatrix " << std::endl;
344  std::cout << " use From vpRotationMatrix to vpThetaUVector " << std::endl;
345 
346  vpThetaUVector tuBuildFromEu;
347  tuBuildFromEu.buildFrom(R);
348 
349  std::cout << std::endl;
350  std::cout << "result : should equivalent to the first one " << std::endl;
351 
352  double theta2;
353  vpColVector u2;
354 
355  tuBuildFromEu.extract(theta2, u2);
356  std::cout << "theta=" << vpMath::deg(theta2) << std::endl;
357  std::cout << "u=" << u2 << std::endl;
358 
359  CHECK(vpMath::abs(theta2 - theta) < std::numeric_limits<double>::epsilon() * 1e10);
360  CHECK(vpMath::abs(u[0] - u2[0]) < std::numeric_limits<double>::epsilon() * 1e10);
361  CHECK(vpMath::abs(u[1] - u2[1]) < std::numeric_limits<double>::epsilon() * 1e10);
362  CHECK(vpMath::abs(u[2] - u2[2]) < std::numeric_limits<double>::epsilon() * 1e10);
363  }
364  }
365  SECTION("Conversion from and to rzyz vector")
366  {
367  vpRzyzVector rzyz(vpMath::rad(180), vpMath::rad(120), vpMath::rad(45));
368  std::cout << "Initialization vpRzyzVector " << std::endl;
369  std::cout << rzyz << std::endl;
370  std::cout << "From vpRzyzVector to vpRotationMatrix " << std::endl;
371  R.buildFrom(rzyz);
372  CHECK(R.isARotationMatrix());
373  std::cout << "From vpRotationMatrix to vpRzyzVector " << std::endl;
374  vpRzyzVector rzyz_final;
375  rzyz_final.buildFrom(R);
376  CHECK(test("rzyz", rzyz_final, rzyz));
377  std::cout << rzyz_final << std::endl;
378  }
379  SECTION("Conversion from and to rzyx vector")
380  {
381  vpRzyxVector rzyx(vpMath::rad(180), vpMath::rad(120), vpMath::rad(45));
382  std::cout << "Initialization vpRzyxVector " << std::endl;
383  std::cout << rzyx << std::endl;
384  std::cout << "From vpRzyxVector to vpRotationMatrix " << std::endl;
385  R.buildFrom(rzyx);
386  CHECK(R.isARotationMatrix());
387  std::cout << R << std::endl;
388  std::cout << "From vpRotationMatrix to vpRzyxVector " << std::endl;
389  vpRzyxVector rzyx_final;
390  rzyx_final.buildFrom(R);
391  bool ret = test("rzyx", rzyx_final, rzyx);
392  if (ret == false) {
393  // Euler angle representation is not unique
394  std::cout << "Rzyx vector differ. Test rotation matrix..." << std::endl;
395  vpRotationMatrix RR(rzyx_final);
396  if (R == RR) {
397  std::cout << "Rzyx vector differ but rotation matrix is valid" << std::endl;
398  ret = true;
399  }
400  }
401  CHECK(ret);
402  std::cout << rzyx_final << std::endl;
403  }
404  }
405  SECTION("Rotation matrix extraction from homogeneous matrix and multiplication")
406  {
407  // Test rotation_matrix * homogeneous_matrix
408  vpHomogeneousMatrix _1_M_2_truth;
409  _1_M_2_truth[0][0] = 0.9835;
410  _1_M_2_truth[0][1] = -0.0581;
411  _1_M_2_truth[0][2] = 0.1716;
412  _1_M_2_truth[0][3] = 0;
413  _1_M_2_truth[1][0] = -0.0489;
414  _1_M_2_truth[1][1] = -0.9972;
415  _1_M_2_truth[1][2] = -0.0571;
416  _1_M_2_truth[1][3] = 0;
417  _1_M_2_truth[2][0] = 0.1744;
418  _1_M_2_truth[2][1] = 0.0478;
419  _1_M_2_truth[2][2] = -0.9835;
420  _1_M_2_truth[2][3] = 0;
421  vpHomogeneousMatrix _2_M_3_;
422  _2_M_3_[0][0] = 0.9835;
423  _2_M_3_[0][1] = -0.0581;
424  _2_M_3_[0][2] = 0.1716;
425  _2_M_3_[0][3] = 0.0072;
426  _2_M_3_[1][0] = -0.0489;
427  _2_M_3_[1][1] = -0.9972;
428  _2_M_3_[1][2] = -0.0571;
429  _2_M_3_[1][3] = 0.0352;
430  _2_M_3_[2][0] = 0.1744;
431  _2_M_3_[2][1] = 0.0478;
432  _2_M_3_[2][2] = -0.9835;
433  _2_M_3_[2][3] = 0.9470;
434 
435  vpRotationMatrix _1_R_2_ = _1_M_2_truth.getRotationMatrix();
436  vpHomogeneousMatrix _1_M_3_(_1_R_2_* _2_M_3_);
437  vpHomogeneousMatrix _1_M_3_truth(_1_M_2_truth * _2_M_3_);
438  CHECK(test_matrix_equal(_1_M_3_, _1_M_3_truth));
439  }
440 }
441 
442 TEST_CASE("Theta u multiplication", "[theta.u]")
443 {
444  const int nTrials = 100;
445  const uint64_t seed = 0x123456789;
446  vpUniRand rng(seed);
447  for (int iter = 0; iter < nTrials; iter++) {
448  const vpThetaUVector tu0 = generateThetaU(rng);
449  const vpThetaUVector tu1 = generateThetaU(rng);
450 
451  const vpRotationMatrix c1Rc2(tu0);
452  const vpRotationMatrix c2Rc3(tu1);
453  const vpRotationMatrix c1Rc3_ref = c1Rc2 * c2Rc3;
454  const vpThetaUVector c1_tu_c3 = tu0 * tu1;
455  // two rotation vectors can represent the same rotation,
456  // that is why we compare the rotation matrices
457  const vpRotationMatrix c1Rc3(c1_tu_c3);
458 
459  const double tolerance = 1e-9;
460  for (unsigned int i = 0; i < 3; i++) {
461  for (unsigned int j = 0; j < 3; j++) {
462  CHECK(c1Rc3_ref[i][j] == Approx(c1Rc3[i][j]).epsilon(0).margin(tolerance));
463  }
464  }
465  }
466 }
467 
468 TEST_CASE("Quaternion multiplication", "[quaternion]")
469 {
470  const int nTrials = 100;
471  const uint64_t seed = 0x123456789;
472  vpUniRand rng(seed);
473  for (int iter = 0; iter < nTrials; iter++) {
474  const vpQuaternionVector q0 = generateQuat(rng);
475  const vpQuaternionVector q1 = generateQuat(rng);
476 
477  const vpRotationMatrix c1Rc2(q0);
478  const vpRotationMatrix c2Rc3(q1);
479  const vpRotationMatrix c1Rc3_ref = c1Rc2 * c2Rc3;
480 
481  const vpQuaternionVector c1_q_c3 = q0 * q1;
482  // two quaternions of opposite sign can represent the same rotation,
483  // that is why we compare the rotation matrices
484  const vpRotationMatrix c1Rc3(c1_q_c3);
485 
486  const double tolerance = 1e-9;
487  for (unsigned int i = 0; i < 3; i++) {
488  for (unsigned int j = 0; j < 3; j++) {
489  CHECK(c1Rc3_ref[i][j] == Approx(c1Rc3[i][j]).epsilon(0).margin(tolerance));
490  }
491  }
492  }
493 }
494 
495 int main(int argc, char *argv[])
496 {
497  Catch::Session session; // There must be exactly one instance
498 
499  // Let Catch (using Clara) parse the command line
500  session.applyCommandLine(argc, argv);
501 
502  int numFailed = session.run();
503 
504  // numFailed is clamped to 255 as some unices only use the lower 8 bits.
505  // This clamping has already been applied, so just return it here
506  // You can also do any post run clean-up here
507  return numFailed;
508 }
509 #else
510 #include <iostream>
511 
512 int main() { return EXIT_SUCCESS; }
513 #endif
unsigned int getCols() const
Definition: vpArray2D.h:327
Type * data
Address of the first element of the data array.
Definition: vpArray2D.h:139
unsigned int size() const
Return the number of elements of the 2D array.
Definition: vpArray2D.h:339
unsigned int getRows() const
Definition: vpArray2D.h:337
Implementation of column vector and the associated operations.
Definition: vpColVector.h:163
vpColVector extract(unsigned int r, unsigned int colsize) const
Definition: vpColVector.h:367
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpRotationMatrix getRotationMatrix() const
static double rad(double deg)
Definition: vpMath.h:127
static Type abs(const Type &x)
Definition: vpMath.h:267
static bool equal(double x, double y, double threshold=0.001)
Definition: vpMath.h:449
static double deg(double rad)
Definition: vpMath.h:117
Implementation of a rotation vector as quaternion angle minimal representation.
void set(double x, double y, double z, double w)
Implementation of a rotation matrix and operations on such kind of matrices.
bool isARotationMatrix(double threshold=1e-6) const
vpRotationMatrix buildFrom(const vpHomogeneousMatrix &M)
Implementation of a generic rotation vector.
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRxyzVector.h:176
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRzyxVector.h:177
vpRzyxVector buildFrom(const vpRotationMatrix &R)
Implementation of a rotation vector as Euler angle minimal representation.
Definition: vpRzyzVector.h:175
vpRzyzVector buildFrom(const vpRotationMatrix &R)
Implementation of a rotation vector as axis-angle minimal representation.
void extract(double &theta, vpColVector &u) const
vpThetaUVector buildFrom(const vpHomogeneousMatrix &M)
Class for generating random numbers with uniform probability density.
Definition: vpUniRand.h:123
int uniform(int a, int b)
Definition: vpUniRand.cpp:159