Gazebo Math

API Reference

7.5.1
gz/math/MassMatrix3.hh
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2015 Open Source Robotics Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16 */
17 #ifndef GZ_MATH_MASSMATRIX3_HH_
18 #define GZ_MATH_MASSMATRIX3_HH_
19 
20 #include <algorithm>
21 #include <cmath>
22 #include <limits>
23 #include <string>
24 #include <vector>
25 
26 #include <gz/math/config.hh>
27 #include "gz/math/Helpers.hh"
28 #include "gz/math/Material.hh"
29 #include "gz/math/Quaternion.hh"
30 #include "gz/math/Vector2.hh"
31 #include "gz/math/Vector3.hh"
32 #include "gz/math/Matrix3.hh"
33 
34 namespace gz::math
35 {
36  // Inline bracket to help doxygen filtering.
37  inline namespace GZ_MATH_VERSION_NAMESPACE {
38  //
43  template<typename T>
45  {
48  public: MassMatrix3() : mass(0)
49  {}
50 
55  public: MassMatrix3(const T &_mass,
56  const Vector3<T> &_ixxyyzz,
57  const Vector3<T> &_ixyxzyz)
58  : mass(_mass), Ixxyyzz(_ixxyyzz), Ixyxzyz(_ixyxzyz)
59  {}
60 
63  public: MassMatrix3(const MassMatrix3<T> &_m) = default;
64 
66  public: ~MassMatrix3() = default;
67 
71  public: bool SetMass(const T &_m)
72  {
73  this->mass = _m;
74  return this->IsValid();
75  }
76 
79  public: T Mass() const
80  {
81  return this->mass;
82  }
83 
92  public: bool SetInertiaMatrix(
93  const T &_ixx, const T &_iyy, const T &_izz,
94  const T &_ixy, const T &_ixz, const T &_iyz)
95  {
96  this->Ixxyyzz.Set(_ixx, _iyy, _izz);
97  this->Ixyxzyz.Set(_ixy, _ixz, _iyz);
98  return this->IsValid();
99  }
100 
103  public: Vector3<T> DiagonalMoments() const
104  {
105  return this->Ixxyyzz;
106  }
107 
111  {
112  return this->Ixyxzyz;
113  }
114 
118  public: bool SetDiagonalMoments(const Vector3<T> &_ixxyyzz)
119  {
120  this->Ixxyyzz = _ixxyyzz;
121  return this->IsValid();
122  }
123 
127  public: bool SetOffDiagonalMoments(const Vector3<T> &_ixyxzyz)
128  {
129  this->Ixyxzyz = _ixyxzyz;
130  return this->IsValid();
131  }
132 
135  public: T Ixx() const
136  {
137  return this->Ixxyyzz[0];
138  }
139 
142  public: T Iyy() const
143  {
144  return this->Ixxyyzz[1];
145  }
146 
149  public: T Izz() const
150  {
151  return this->Ixxyyzz[2];
152  }
153 
156  public: T Ixy() const
157  {
158  return this->Ixyxzyz[0];
159  }
160 
163  public: T Ixz() const
164  {
165  return this->Ixyxzyz[1];
166  }
167 
170  public: T Iyz() const
171  {
172  return this->Ixyxzyz[2];
173  }
174 
178  public: bool SetIxx(const T &_v)
179  {
180  this->Ixxyyzz.X(_v);
181  return this->IsValid();
182  }
183 
187  public: bool SetIyy(const T &_v)
188  {
189  this->Ixxyyzz.Y(_v);
190  return this->IsValid();
191  }
192 
196  public: bool SetIzz(const T &_v)
197  {
198  this->Ixxyyzz.Z(_v);
199  return this->IsValid();
200  }
201 
205  public: bool SetIxy(const T &_v)
206  {
207  this->Ixyxzyz.X(_v);
208  return this->IsValid();
209  }
210 
214  public: bool SetIxz(const T &_v)
215  {
216  this->Ixyxzyz.Y(_v);
217  return this->IsValid();
218  }
219 
223  public: bool SetIyz(const T &_v)
224  {
225  this->Ixyxzyz.Z(_v);
226  return this->IsValid();
227  }
228 
231  public: Matrix3<T> Moi() const
232  {
233  return Matrix3<T>(
234  this->Ixxyyzz[0], this->Ixyxzyz[0], this->Ixyxzyz[1],
235  this->Ixyxzyz[0], this->Ixxyyzz[1], this->Ixyxzyz[2],
236  this->Ixyxzyz[1], this->Ixyxzyz[2], this->Ixxyyzz[2]);
237  }
238 
244  public: bool SetMoi(const Matrix3<T> &_moi)
245  {
246  this->Ixxyyzz.Set(_moi(0, 0), _moi(1, 1), _moi(2, 2));
247  this->Ixyxzyz.Set(
248  0.5*(_moi(0, 1) + _moi(1, 0)),
249  0.5*(_moi(0, 2) + _moi(2, 0)),
250  0.5*(_moi(1, 2) + _moi(2, 1)));
251  return this->IsValid();
252  }
253 
257  public: MassMatrix3 &operator=(const MassMatrix3<T> &_massMatrix)
258  = default;
259 
264  public: bool operator==(const MassMatrix3<T> &_m) const
265  {
266  return equal<T>(this->mass, _m.Mass()) &&
267  (this->Ixxyyzz == _m.DiagonalMoments()) &&
268  (this->Ixyxzyz == _m.OffDiagonalMoments());
269  }
270 
274  public: bool operator!=(const MassMatrix3<T> &_m) const
275  {
276  return !(*this == _m);
277  }
278 
298  public: bool IsNearPositive(const T _tolerance =
299  GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>) const
300  {
301  const T epsilon = this->Epsilon(_tolerance);
302 
303  // Check if mass and determinants of all upper left submatrices
304  // of moment of inertia matrix are positive
305  return (this->mass >= 0) &&
306  (this->Ixx() + epsilon >= 0) &&
307  (this->Ixx() * this->Iyy() - std::pow(this->Ixy(), 2) +
308  epsilon >= 0) &&
309  (this->Moi().Determinant() + epsilon >= 0);
310  }
311 
331  public: bool IsPositive(const T _tolerance =
332  GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>) const
333  {
334  const T epsilon = this->Epsilon(_tolerance);
335 
336  // Check if mass and determinants of all upper left submatrices
337  // of moment of inertia matrix are positive
338  return (this->mass > 0) &&
339  (this->Ixx() + epsilon > 0) &&
340  (this->Ixx() * this->Iyy() - std::pow(this->Ixy(), 2) +
341  epsilon > 0) &&
342  (this->Moi().Determinant() + epsilon > 0);
343  }
344 
352  public: T Epsilon(const T _tolerance =
353  GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>) const
354  {
355  return Epsilon(this->DiagonalMoments(), _tolerance);
356  }
357 
379  public: static T Epsilon(const Vector3<T> &_moments,
380  const T _tolerance =
381  GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>)
382  {
383  // The following was borrowed heavily from:
384  // https://github.com/RobotLocomotion/drake/blob/v0.27.0/multibody/tree/rotational_inertia.h
385 
386  // Compute the maximum possible moment of inertia, which will be
387  // used to compute whether the moments are valid.
388  //
389  // The maximum moment of inertia is bounded by:
390  // trace / 3 <= maxPossibleMoi <= trace / 2.
391  //
392  // The trace of a matrix is the sum of the coefficients on the
393  // main diagonal. For a mass matrix, this is equal to
394  // ixx + iyy + izz, or _moments.Sum() for this function's
395  // implementation.
396  //
397  // It is okay if maxPossibleMoi == zero.
398  T maxPossibleMoI = 0.5 * std::abs(_moments.Sum());
399 
400  // In order to check validity of the moments we need to use an
401  // epsilon value that is related to machine precision
402  // multiplied by the largest possible moment of inertia.
403  return _tolerance *
404  std::numeric_limits<T>::epsilon() * maxPossibleMoI;
405  }
406 
418  public: bool IsValid(const T _tolerance =
419  GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>) const
420  {
421  return this->IsNearPositive(_tolerance) &&
422  ValidMoments(this->PrincipalMoments(), _tolerance);
423  }
424 
445  public: static bool ValidMoments(const Vector3<T> &_moments,
446  const T _tolerance = GZ_MASSMATRIX3_DEFAULT_TOLERANCE<T>)
447  {
448  T epsilon = Epsilon(_moments, _tolerance);
449 
450  return _moments[0] + epsilon >= 0 &&
451  _moments[1] + epsilon >= 0 &&
452  _moments[2] + epsilon >= 0 &&
453  _moments[0] + _moments[1] + epsilon >= _moments[2] &&
454  _moments[1] + _moments[2] + epsilon >= _moments[0] &&
455  _moments[2] + _moments[0] + epsilon >= _moments[1];
456  }
457 
469  public: Vector3<T> PrincipalMoments(const T _tol = 1e-6) const
470  {
471  // Compute tolerance relative to maximum value of inertia diagonal
472  T tol = _tol * this->Ixxyyzz.Max();
473  if (this->Ixyxzyz.Equal(Vector3<T>::Zero, tol))
474  {
475  // Matrix is already diagonalized, return diagonal moments
476  return this->Ixxyyzz;
477  }
478 
479  // Algorithm based on http://arxiv.org/abs/1306.6291v4
480  // A Method for Fast Diagonalization of a 2x2 or 3x3 Real Symmetric
481  // Matrix, by Maarten Kronenburg
482  Vector3<T> Id(this->Ixxyyzz);
483  Vector3<T> Ip(this->Ixyxzyz);
484  // b = Ixx + Iyy + Izz
485  T b = Id.Sum();
486  // c = Ixx*Iyy - Ixy^2 + Ixx*Izz - Ixz^2 + Iyy*Izz - Iyz^2
487  T c = Id[0]*Id[1] - std::pow(Ip[0], 2)
488  + Id[0]*Id[2] - std::pow(Ip[1], 2)
489  + Id[1]*Id[2] - std::pow(Ip[2], 2);
490  // d = Ixx*Iyz^2 + Iyy*Ixz^2 + Izz*Ixy^2 - Ixx*Iyy*Izz - 2*Ixy*Ixz*Iyz
491  T d = Id[0]*std::pow(Ip[2], 2)
492  + Id[1]*std::pow(Ip[1], 2)
493  + Id[2]*std::pow(Ip[0], 2)
494  - Id[0]*Id[1]*Id[2]
495  - 2*Ip[0]*Ip[1]*Ip[2];
496  // p = b^2 - 3c
497  T p = std::pow(b, 2) - 3*c;
498 
499  // At this point, it is important to check that p is not close
500  // to zero, since its inverse is used to compute delta.
501  // In equation 4.7, p is expressed as a sum of squares
502  // that is only zero if the matrix is diagonal
503  // with identical principal moments.
504  // This check has no test coverage, since this function returns
505  // immediately if a diagonal matrix is detected.
506  if (p < std::pow(tol, 2))
507  return b / 3.0 * Vector3<T>::One;
508 
509  // q = 2b^3 - 9bc - 27d
510  T q = 2*std::pow(b, 3) - 9*b*c - 27*d;
511 
512  // delta = acos(q / (2 * p^(1.5)))
513  // additionally clamp the argument to [-1,1]
514  T delta = acos(clamp<T>(0.5 * q / std::pow(p, 1.5), -1, 1));
515 
516  // sort the moments from smallest to largest
517  T moment0 = (b + 2*sqrt(p) * cos(delta / 3.0)) / 3.0;
518  T moment1 = (b + 2*sqrt(p) * cos((delta + 2*GZ_PI)/3.0)) / 3.0;
519  T moment2 = (b + 2*sqrt(p) * cos((delta - 2*GZ_PI)/3.0)) / 3.0;
520  sort3(moment0, moment1, moment2);
521  return Vector3<T>(moment0, moment1, moment2);
522  }
523 
535  public: Quaternion<T> PrincipalAxesOffset(const T _tol = 1e-6) const
536  {
537  Vector3<T> moments = this->PrincipalMoments(_tol);
538  // Compute tolerance relative to maximum value of inertia diagonal
539  T tol = _tol * this->Ixxyyzz.Max();
540  if (moments.Equal(this->Ixxyyzz, tol) ||
541  (math::equal<T>(moments[0], moments[1], std::abs(tol)) &&
542  math::equal<T>(moments[0], moments[2], std::abs(tol))))
543  {
544  // matrix is already aligned with principal axes
545  // or all three moments are approximately equal
546  // return identity rotation
548  }
549 
550  // Algorithm based on http://arxiv.org/abs/1306.6291v4
551  // A Method for Fast Diagonalization of a 2x2 or 3x3 Real Symmetric
552  // Matrix, by Maarten Kronenburg
553  // A real, symmetric matrix can be diagonalized by an orthogonal matrix
554  // (due to the finite-dimensional spectral theorem
555  // https://en.wikipedia.org/wiki/Spectral_theorem
556  // #Hermitian_maps_and_Hermitian_matrices ),
557  // and another name for orthogonal matrix is rotation matrix.
558  // Section 5 of the paper shows how to compute Euler angles
559  // phi1, phi2, and phi3 that map to a rotation matrix.
560  // In some cases, there are multiple possible values for a given angle,
561  // such as phi1, that are denoted as phi11, phi12, phi11a, phi12a, etc.
562  // Similar variable names are used to the paper so that the paper
563  // can be used as an additional reference.
564 
565  // f1, f2 defined in equations 5.5, 5.6
566  Vector2<T> f1(this->Ixyxzyz[0], -this->Ixyxzyz[1]);
567  Vector2<T> f2(this->Ixxyyzz[1] - this->Ixxyyzz[2],
568  -2*this->Ixyxzyz[2]);
569 
570  // Check if two moments are equal, since different equations are used
571  // The moments vector is already sorted, so just check adjacent values.
572  Vector2<T> momentsDiff(moments[0] - moments[1],
573  moments[1] - moments[2]);
574 
575  // index of unequal moment
576  int unequalMoment = -1;
577  if (equal<T>(momentsDiff[0], 0, std::abs(tol)))
578  unequalMoment = 2;
579  else if (equal<T>(momentsDiff[1], 0, std::abs(tol)))
580  unequalMoment = 0;
581 
582  if (unequalMoment >= 0)
583  {
584  // moments[1] is the repeated value
585  // it is not equal to moments[unequalMoment]
586  // momentsDiff3 = lambda - lambda3
587  T momentsDiff3 = moments[1] - moments[unequalMoment];
588  // eq 5.21:
589  // s = cos(phi2)^2 = (A_11 - lambda3) / (lambda - lambda3)
590  // s >= 0 since A_11 is in range [lambda, lambda3]
591  T s = (this->Ixxyyzz[0] - moments[unequalMoment]) / momentsDiff3;
592  // set phi3 to zero for repeated moments (eq 5.23)
593  T phi3 = 0;
594  // phi2 = +- acos(sqrt(s))
595  // start with just the positive value
596  // also clamp the acos argument to prevent NaN's
597  T phi2 = acos(clamp<T>(ClampedSqrt(s), -1, 1));
598 
599  // The paper defines variables phi11 and phi12
600  // which are candidate values of angle phi1.
601  // phi12 is straightforward to compute as a function of f2 and g2.
602  // eq 5.25:
603  Vector2<T> g2(momentsDiff3 * s, 0);
604  // combining eq 5.12 and 5.14, and subtracting psi2
605  // instead of multiplying by its rotation matrix:
606  math::Angle phi12(0.5*(Angle2(g2, tol) - Angle2(f2, tol)));
607  phi12.Normalize();
608 
609  // The paragraph prior to equation 5.16 describes how to choose
610  // the candidate value of phi1 based on the length
611  // of the f1 and f2 vectors.
612  // * When |f1| != 0 and |f2| != 0, then one should choose the
613  // value of phi2 so that phi11 = phi12
614  // * When |f1| == 0 and f2 != 0, then phi1 = phi12
615  // phi11 can be ignored, and either sign of phi2 can be used
616  // * The case of |f2| == 0 can be ignored at this point in the code
617  // since having a repeated moment when |f2| == 0 implies that
618  // the matrix is diagonal. But this function returns a unit
619  // quaternion for diagonal matrices, so we can assume |f2| != 0
620  // See MassMatrix3.ipynb for a more complete discussion.
621  //
622  // Since |f2| != 0, we only need to consider |f1|
623  // * |f1| == 0: phi1 = phi12
624  // * |f1| != 0: choose phi2 so that phi11 == phi12
625  // In either case, phi1 = phi12,
626  // and the sign of phi2 must be chosen to make phi11 == phi12
627  T phi1 = phi12.Radian();
628 
629  bool f1small = f1.SquaredLength() < std::pow(tol, 2);
630  if (!f1small)
631  {
632  // a: phi2 > 0
633  // eq. 5.24
634  Vector2<T> g1a(0, 0.5*momentsDiff3 * sin(2*phi2));
635  // combining eq 5.11 and 5.13, and subtracting psi1
636  // instead of multiplying by its rotation matrix:
637  math::Angle phi11a(Angle2(g1a, tol) - Angle2(f1, tol));
638  phi11a.Normalize();
639 
640  // b: phi2 < 0
641  // eq. 5.24
642  Vector2<T> g1b(0, 0.5*momentsDiff3 * sin(-2*phi2));
643  // combining eq 5.11 and 5.13, and subtracting psi1
644  // instead of multiplying by its rotation matrix:
645  math::Angle phi11b(Angle2(g1b, tol) - Angle2(f1, tol));
646  phi11b.Normalize();
647 
648  // choose sign of phi2
649  // based on whether phi11a or phi11b is closer to phi12
650  // use sin and cos to account for angle wrapping
651  T erra = std::pow(sin(phi1) - sin(phi11a.Radian()), 2)
652  + std::pow(cos(phi1) - cos(phi11a.Radian()), 2);
653  T errb = std::pow(sin(phi1) - sin(phi11b.Radian()), 2)
654  + std::pow(cos(phi1) - cos(phi11b.Radian()), 2);
655  if (errb < erra)
656  {
657  phi2 *= -1;
658  }
659  }
660 
661  // I determined these arguments using trial and error
662  Quaternion<T> result = Quaternion<T>(-phi1, -phi2, -phi3).Inverse();
663 
664  // Previous equations assume repeated moments are at the beginning
665  // of the moments vector (moments[0] == moments[1]).
666  // We have the vectors sorted by size, so it's possible that the
667  // repeated moments are at the end (moments[1] == moments[2]).
668  // In this case (unequalMoment == 0), we apply an extra
669  // rotation that exchanges moment[0] and moment[2]
670  // Rotation matrix = [ 0 0 1]
671  // [ 0 1 0]
672  // [-1 0 0]
673  // That is equivalent to a 90 degree pitch
674  if (unequalMoment == 0)
675  result *= Quaternion<T>(0, GZ_PI_2, 0);
676 
677  return result;
678  }
679 
680  // No repeated principal moments
681  // eq 5.1:
682  T v = (std::pow(this->Ixyxzyz[0], 2) + std::pow(this->Ixyxzyz[1], 2)
683  +(this->Ixxyyzz[0] - moments[2])
684  *(this->Ixxyyzz[0] + moments[2] - moments[0] - moments[1]))
685  / ((moments[1] - moments[2]) * (moments[2] - moments[0]));
686  // value of w depends on v
687  T w;
688  if (v < std::abs(tol))
689  {
690  // first sentence after eq 5.4:
691  // "In the case that v = 0, then w = 1."
692  w = 1;
693  }
694  else
695  {
696  // eq 5.2:
697  w = (this->Ixxyyzz[0] - moments[2] + (moments[2] - moments[1])*v)
698  / ((moments[0] - moments[1]) * v);
699  }
700  // initialize values of angle phi1, phi2, phi3
701  T phi1 = 0;
702  // eq 5.3: start with positive value
703  T phi2 = acos(clamp<T>(ClampedSqrt(v), -1, 1));
704  // eq 5.4: start with positive value
705  T phi3 = acos(clamp<T>(ClampedSqrt(w), -1, 1));
706 
707  // compute g1, g2 for phi2,phi3 >= 0
708  // equations 5.7, 5.8
709  Vector2<T> g1(
710  0.5* (moments[0]-moments[1])*ClampedSqrt(v)*sin(2*phi3),
711  0.5*((moments[0]-moments[1])*w + moments[1]-moments[2])*sin(2*phi2));
712  Vector2<T> g2(
713  (moments[0]-moments[1])*(1 + (v-2)*w) + (moments[1]-moments[2])*v,
714  (moments[0]-moments[1])*sin(phi2)*sin(2*phi3));
715 
716  // The paragraph prior to equation 5.16 describes how to choose
717  // the candidate value of phi1 based on the length
718  // of the f1 and f2 vectors.
719  // * The case of |f1| == |f2| == 0 implies a repeated moment,
720  // which should not be possible at this point in the code
721  // * When |f1| != 0 and |f2| != 0, then one should choose the
722  // value of phi2 so that phi11 = phi12
723  // * When |f1| == 0 and f2 != 0, then phi1 = phi12
724  // phi11 can be ignored, and either sign of phi2, phi3 can be used
725  // * When |f2| == 0 and f1 != 0, then phi1 = phi11
726  // phi12 can be ignored, and either sign of phi2, phi3 can be used
727  bool f1small = f1.SquaredLength() < std::pow(tol, 2);
728  bool f2small = f2.SquaredLength() < std::pow(tol, 2);
729  if (f1small && f2small)
730  {
731  // this should never happen
732  // f1small && f2small implies a repeated moment
733  // return invalid quaternion
735  return Quaternion<T>::Zero;
736  }
737  else if (f1small)
738  {
739  // use phi12 (equations 5.12, 5.14)
740  math::Angle phi12(0.5*(Angle2(g2, tol) - Angle2(f2, tol)));
741  phi12.Normalize();
742  phi1 = phi12.Radian();
743  }
744  else if (f2small)
745  {
746  // use phi11 (equations 5.11, 5.13)
747  math::Angle phi11(Angle2(g1, tol) - Angle2(f1, tol));
748  phi11.Normalize();
749  phi1 = phi11.Radian();
750  }
751  else
752  {
753  // check for when phi11 == phi12
754  // eqs 5.11, 5.13:
755  math::Angle phi11(Angle2(g1, tol) - Angle2(f1, tol));
756  phi11.Normalize();
757  // eqs 5.12, 5.14:
758  math::Angle phi12(0.5*(Angle2(g2, tol) - Angle2(f2, tol)));
759  phi12.Normalize();
760  T err = std::pow(sin(phi11.Radian()) - sin(phi12.Radian()), 2)
761  + std::pow(cos(phi11.Radian()) - cos(phi12.Radian()), 2);
762  phi1 = phi11.Radian();
763  math::Vector2<T> signsPhi23(1, 1);
764  // case a: phi2 <= 0
765  {
766  Vector2<T> g1a = Vector2<T>(1, -1) * g1;
767  Vector2<T> g2a = Vector2<T>(1, -1) * g2;
768  math::Angle phi11a(Angle2(g1a, tol) - Angle2(f1, tol));
769  math::Angle phi12a(0.5*(Angle2(g2a, tol) - Angle2(f2, tol)));
770  phi11a.Normalize();
771  phi12a.Normalize();
772  T erra = std::pow(sin(phi11a.Radian()) - sin(phi12a.Radian()), 2)
773  + std::pow(cos(phi11a.Radian()) - cos(phi12a.Radian()), 2);
774  if (erra < err)
775  {
776  err = erra;
777  phi1 = phi11a.Radian();
778  signsPhi23.Set(-1, 1);
779  }
780  }
781  // case b: phi3 <= 0
782  {
783  Vector2<T> g1b = Vector2<T>(-1, 1) * g1;
784  Vector2<T> g2b = Vector2<T>(1, -1) * g2;
785  math::Angle phi11b(Angle2(g1b, tol) - Angle2(f1, tol));
786  math::Angle phi12b(0.5*(Angle2(g2b, tol) - Angle2(f2, tol)));
787  phi11b.Normalize();
788  phi12b.Normalize();
789  T errb = std::pow(sin(phi11b.Radian()) - sin(phi12b.Radian()), 2)
790  + std::pow(cos(phi11b.Radian()) - cos(phi12b.Radian()), 2);
791  if (errb < err)
792  {
793  err = errb;
794  phi1 = phi11b.Radian();
795  signsPhi23.Set(1, -1);
796  }
797  }
798  // case c: phi2,phi3 <= 0
799  {
800  Vector2<T> g1c = Vector2<T>(-1, -1) * g1;
801  Vector2<T> g2c = g2;
802  math::Angle phi11c(Angle2(g1c, tol) - Angle2(f1, tol));
803  math::Angle phi12c(0.5*(Angle2(g2c, tol) - Angle2(f2, tol)));
804  phi11c.Normalize();
805  phi12c.Normalize();
806  T errc = std::pow(sin(phi11c.Radian()) - sin(phi12c.Radian()), 2)
807  + std::pow(cos(phi11c.Radian()) - cos(phi12c.Radian()), 2);
808  if (errc < err)
809  {
810  phi1 = phi11c.Radian();
811  signsPhi23.Set(-1, -1);
812  }
813  }
814 
815  // apply sign changes
816  phi2 *= signsPhi23[0];
817  phi3 *= signsPhi23[1];
818  }
819 
820  // I determined these arguments using trial and error
821  return Quaternion<T>(-phi1, -phi2, -phi3).Inverse();
822  }
823 
833  public: bool EquivalentBox(Vector3<T> &_size,
834  Quaternion<T> &_rot,
835  const T _tol = 1e-6) const
836  {
837  if (!this->IsPositive(0))
838  {
839  // inertia is not positive, cannot compute equivalent box
840  return false;
841  }
842 
843  Vector3<T> moments = this->PrincipalMoments(_tol);
844  if (!ValidMoments(moments))
845  {
846  // principal moments don't satisfy the triangle identity
847  return false;
848  }
849 
850  // The reason for checking that the principal moments satisfy
851  // the triangle inequality
852  // I1 + I2 - I3 >= 0
853  // is to ensure that the arguments to sqrt in these equations
854  // are positive and the box size is real.
855  _size.X(sqrt(6*(moments.Y() + moments.Z() - moments.X()) / this->mass));
856  _size.Y(sqrt(6*(moments.Z() + moments.X() - moments.Y()) / this->mass));
857  _size.Z(sqrt(6*(moments.X() + moments.Y() - moments.Z()) / this->mass));
858 
859  _rot = this->PrincipalAxesOffset(_tol);
860 
861  if (_rot == Quaternion<T>::Zero)
862  {
863  // _rot is an invalid quaternion
865  return false;
866  }
867 
868  return true;
869  }
870 
877  public: bool SetFromBox(const Material &_mat,
878  const Vector3<T> &_size,
880  {
881  T volume = _size.X() * _size.Y() * _size.Z();
882  return this->SetFromBox(_mat.Density() * volume, _size, _rot);
883  }
884 
890  public: bool SetFromBox(const T _mass,
891  const Vector3<T> &_size,
893  {
894  // Check that _mass and _size are strictly positive
895  // and that quaternion is valid
896  if (_mass <= 0 || _size.Min() <= 0 || _rot == Quaternion<T>::Zero)
897  {
898  return false;
899  }
900  this->SetMass(_mass);
901  return this->SetFromBox(_size, _rot);
902  }
903 
909  public: bool SetFromBox(const Vector3<T> &_size,
911  {
912  // Check that _mass and _size are strictly positive
913  // and that quaternion is valid
914  if (this->Mass() <= 0 || _size.Min() <= 0 ||
915  _rot == Quaternion<T>::Zero)
916  {
917  return false;
918  }
919 
920  // Diagonal matrix L with principal moments
921  Matrix3<T> L;
922  T x2 = std::pow(_size.X(), 2);
923  T y2 = std::pow(_size.Y(), 2);
924  T z2 = std::pow(_size.Z(), 2);
925  L(0, 0) = this->mass / 12.0 * (y2 + z2);
926  L(1, 1) = this->mass / 12.0 * (z2 + x2);
927  L(2, 2) = this->mass / 12.0 * (x2 + y2);
928  Matrix3<T> R(_rot);
929  return this->SetMoi(R * L * R.Transposed());
930  }
931 
940  public: bool SetFromConeZ(const Material &_mat,
941  const T _length,
942  const T _radius,
943  const Quaternion<T> &_rot =
945  {
946  // Check that density, _radius and _length are strictly positive
947  // and that quaternion is valid
948  if (_mat.Density() <= 0 || _length <= 0 || _radius <= 0 ||
949  _rot == Quaternion<T>::Zero)
950  {
951  return false;
952  }
953  T volume = GZ_PI * _radius * _radius * _length / 3.0;
954  return this->SetFromConeZ(_mat.Density() * volume,
955  _length, _radius, _rot);
956  }
957 
965  public: bool SetFromConeZ(const T _mass,
966  const T _length,
967  const T _radius,
968  const Quaternion<T> &_rot =
970  {
971  // Check that _mass, _radius and _length are strictly positive
972  // and that quaternion is valid
973  if (_mass <= 0 || _length <= 0 || _radius <= 0 ||
974  _rot == Quaternion<T>::Zero)
975  {
976  return false;
977  }
978  this->SetMass(_mass);
979  return this->SetFromConeZ(_length, _radius, _rot);
980  }
981 
988  public: bool SetFromConeZ(const T _length,
989  const T _radius,
990  const Quaternion<T> &_rot)
991  {
992  // Check that _mass and _size are strictly positive
993  // and that quaternion is valid
994  if (this->Mass() <= 0 || _length <= 0 || _radius <= 0 ||
995  _rot == Quaternion<T>::Zero)
996  {
997  return false;
998  }
999 
1000  // Diagonal matrix L with principal moments
1001  T radius2 = std::pow(_radius, 2);
1002  Matrix3<T> L;
1003  L(0, 0) = 3.0 * this->mass * (4.0 * radius2 +
1004  std::pow(_length, 2)) / 80.0;
1005  L(1, 1) = L(0, 0);
1006  L(2, 2) = 3.0 * this->mass * radius2 / 10.0;
1007  Matrix3<T> R(_rot);
1008  return this->SetMoi(R * L * R.Transposed());
1009  }
1010 
1019  public: bool SetFromCylinderZ(const Material &_mat,
1020  const T _length,
1021  const T _radius,
1022  const Quaternion<T> &_rot = Quaternion<T>::Identity)
1023  {
1024  // Check that density, _radius and _length are strictly positive
1025  // and that quaternion is valid
1026  if (_mat.Density() <= 0 || _length <= 0 || _radius <= 0 ||
1027  _rot == Quaternion<T>::Zero)
1028  {
1029  return false;
1030  }
1031  T volume = GZ_PI * _radius * _radius * _length;
1032  return this->SetFromCylinderZ(_mat.Density() * volume,
1033  _length, _radius, _rot);
1034  }
1035 
1043  public: bool SetFromCylinderZ(const T _mass,
1044  const T _length,
1045  const T _radius,
1046  const Quaternion<T> &_rot = Quaternion<T>::Identity)
1047  {
1048  // Check that _mass, _radius and _length are strictly positive
1049  // and that quaternion is valid
1050  if (_mass <= 0 || _length <= 0 || _radius <= 0 ||
1051  _rot == Quaternion<T>::Zero)
1052  {
1053  return false;
1054  }
1055  this->SetMass(_mass);
1056  return this->SetFromCylinderZ(_length, _radius, _rot);
1057  }
1058 
1065  public: bool SetFromCylinderZ(const T _length,
1066  const T _radius,
1067  const Quaternion<T> &_rot)
1068  {
1069  // Check that _mass and _size are strictly positive
1070  // and that quaternion is valid
1071  if (this->Mass() <= 0 || _length <= 0 || _radius <= 0 ||
1072  _rot == Quaternion<T>::Zero)
1073  {
1074  return false;
1075  }
1076 
1077  // Diagonal matrix L with principal moments
1078  T radius2 = std::pow(_radius, 2);
1079  Matrix3<T> L;
1080  L(0, 0) = this->mass / 12.0 * (3*radius2 + std::pow(_length, 2));
1081  L(1, 1) = L(0, 0);
1082  L(2, 2) = this->mass / 2.0 * radius2;
1083  Matrix3<T> R(_rot);
1084  return this->SetMoi(R * L * R.Transposed());
1085  }
1086 
1093  public: bool SetFromSphere(const Material &_mat, const T _radius)
1094  {
1095  // Check that the density and _radius are strictly positive
1096  if (_mat.Density() <= 0 || _radius <= 0)
1097  {
1098  return false;
1099  }
1100 
1101  T volume = (4.0/3.0) * GZ_PI * std::pow(_radius, 3);
1102  return this->SetFromSphere(_mat.Density() * volume, _radius);
1103  }
1104 
1109  public: bool SetFromSphere(const T _mass, const T _radius)
1110  {
1111  // Check that _mass and _radius are strictly positive
1112  if (_mass <= 0 || _radius <= 0)
1113  {
1114  return false;
1115  }
1116  this->SetMass(_mass);
1117  return this->SetFromSphere(_radius);
1118  }
1119 
1124  public: bool SetFromSphere(const T _radius)
1125  {
1126  // Check that _mass and _radius are strictly positive
1127  if (this->Mass() <= 0 || _radius <= 0)
1128  {
1129  return false;
1130  }
1131 
1132  // Diagonal matrix L with principal moments
1133  T radius2 = std::pow(_radius, 2);
1134  Matrix3<T> L;
1135  L(0, 0) = 0.4 * this->mass * radius2;
1136  L(1, 1) = 0.4 * this->mass * radius2;
1137  L(2, 2) = 0.4 * this->mass * radius2;
1138  return this->SetMoi(L);
1139  }
1140 
1144  private: static inline T ClampedSqrt(const T &_x)
1145  {
1146  if (_x <= 0)
1147  return 0;
1148  return sqrt(_x);
1149  }
1150 
1156  private: static T Angle2(const Vector2<T> &_v, const T _eps = 1e-6)
1157  {
1158  if (_v.SquaredLength() < std::pow(_eps, 2))
1159  return 0;
1160  return atan2(_v[1], _v[0]);
1161  }
1162 
1164  private: T mass;
1165 
1169  private: Vector3<T> Ixxyyzz;
1170 
1174  private: Vector3<T> Ixyxzyz;
1175  };
1176 
1179  } // namespace GZ_MATH_VERSION_NAMESPACE
1180 } // namespace gz::math
1181 #endif // GZ_MATH_MASSMATRIX3_HH_