1 #ifndef SimTK_SIMMATRIX_MASS_PROPERTIES_H_
2 #define SimTK_SIMMATRIX_MASS_PROPERTIES_H_
3 
4 /* -------------------------------------------------------------------------- *
5  *                       Simbody(tm): SimTKcommon                             *
6  * -------------------------------------------------------------------------- *
7  * This is part of the SimTK biosimulation toolkit originating from           *
8  * Simbios, the NIH National Center for Physics-Based Simulation of           *
9  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
10  * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody.  *
11  *                                                                            *
12  * Portions copyright (c) 2005-14 Stanford University and the Authors.        *
13  * Authors: Michael Sherman                                                   *
14  * Contributors: Paul Mitiguy                                                 *
15  *                                                                            *
16  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
17  * not use this file except in compliance with the License. You may obtain a  *
18  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
19  *                                                                            *
20  * Unless required by applicable law or agreed to in writing, software        *
21  * distributed under the License is distributed on an "AS IS" BASIS,          *
22  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
23  * See the License for the specific language governing permissions and        *
24  * limitations under the License.                                             *
25  * -------------------------------------------------------------------------- */
26 
27 /** @file
28  *
29  * These are utility classes for dealing with mass properties, particularly
30  * those messy inertias.
31  */
32 
33 #include "SimTKcommon/Scalar.h"
34 #include "SimTKcommon/SmallMatrix.h"
35 #include "SimTKcommon/Orientation.h"
36 
37 #include <iostream>
38 
39 namespace SimTK {
40 /** Spatial vectors are used for (rotation,translation) quantities and
41 consist of a pair of Vec3 objects, arranged as a 2-vector of 3-vectors.
42 Quantities represented this way include
43     - spatial velocity     = (angularVelocity,linearVelocity)
44     - spatial acceleration = (angularAcceleration,linearAcceleration)
45     - generalized forces   = (torque,force)
46 
47 Spatial configuration has to be handled differently though since
48 orientation is not a vector quantity. (We use Transform for this concept
49 which includes a Rotation matrix and a translation Vec3.) **/
50 typedef Vec<2,   Vec3>              SpatialVec;
51 /** A SpatialVec that is always single (float) precision regardless of
52 the compiled-in precision of Real. **/
53 typedef Vec<2,   Vec<3,float> >    fSpatialVec;
54 /** A SpatialVec that is always double precision regardless of
55 the compiled-in precision of Real. **/
56 typedef Vec<2,   Vec<3,double> >   dSpatialVec;
57 
58 /** This is the type of a transposed SpatialVec; it does not usually appear
59 explicitly in user programs. **/
60 typedef Row<2,   Row3>              SpatialRow;
61 /** A SpatialRow that is always single (float) precision regardless of
62 the compiled-in precision of Real. **/
63 typedef Row<2,   Row<3,float> >    fSpatialRow;
64 /** A SpatialRow that is always double precision regardless of
65 the compiled-in precision of Real. **/
66 typedef Row<2,   Row<3,double> >   dSpatialRow;
67 
68 /** Spatial matrices are used to hold 6x6 matrices that are best viewed
69 as 2x2 matrices of 3x3 matrices; most commonly for spatial and articulated
70 body inertias and spatial shift matrices. They also arise commonly as
71 intermediates in computations involving SpatialVec objects. **/
72 typedef Mat<2,2, Mat33>             SpatialMat;
73 /** A SpatialMat that is always single (float) precision regardless of
74 the compiled-in precision of Real. **/
75 typedef Mat<2,2, Mat<3,3,float> >  fSpatialMat;
76 /** A SpatialMat that is always double precision regardless of
77 the compiled-in precision of Real. **/
78 typedef Mat<2,2, Mat<3,3,double> > dSpatialMat;
79 
80 // These are templatized by precision (float or double).
81 template <class P> class UnitInertia_;
82 template <class P> class Inertia_;
83 template <class P> class SpatialInertia_;
84 template <class P> class ArticulatedInertia_;
85 template <class P> class MassProperties_;
86 
87 // The "no trailing underscore" typedefs use whatever the
88 // compile-time precision is set to.
89 
90 /** A unit inertia (gyration) tensor at default precision. **/
91 typedef UnitInertia_<Real>          UnitInertia;
92 /** A unit inertia (gyration) tensor at float precision. **/
93 typedef UnitInertia_<float>        fUnitInertia;
94 /** A unit inertia (gyration) tensor at double precision. **/
95 typedef UnitInertia_<double>       dUnitInertia;
96 
97 /** An inertia tensor at default precision. **/
98 typedef Inertia_<Real>              Inertia;
99 /** An inertia tensor at float precision. **/
100 typedef Inertia_<float>            fInertia;
101 /** An inertia tensor at double precision. **/
102 typedef Inertia_<double>           dInertia;
103 
104 /** Rigid body mass properties at default precision. **/
105 typedef MassProperties_<Real>       MassProperties;
106 /** Rigid body mass properties at float precision. **/
107 typedef MassProperties_<float>     fMassProperties;
108 /** Rigid body mass properties at double precision. **/
109 typedef MassProperties_<double>    dMassProperties;
110 
111 /** A spatial (rigid body) inertia matrix at default precision. **/
112 typedef SpatialInertia_<Real>       SpatialInertia;
113 /** A spatial (rigid body) inertia matrix at float precision. **/
114 typedef SpatialInertia_<float>     fSpatialInertia;
115 /** A spatial (rigid body) inertia matrix at double precision. **/
116 typedef SpatialInertia_<double>    dSpatialInertia;
117 
118 /** An articulated body inertia matrix at default precision. **/
119 typedef ArticulatedInertia_<Real>    ArticulatedInertia;
120 /** An articulated body inertia matrix at float precision. **/
121 typedef ArticulatedInertia_<float>  fArticulatedInertia;
122 /** An articulated body inertia matrix at double precision. **/
123 typedef ArticulatedInertia_<double> dArticulatedInertia;
124 
125 /** For backwards compatibility only; use UnitInertia instead. **/
126 typedef UnitInertia  Gyration;
127 
128 // -----------------------------------------------------------------------------
129 //                             INERTIA MATRIX
130 // -----------------------------------------------------------------------------
131 /** The physical meaning of an inertia is the distribution of a rigid body's
132 mass about a \e particular point. If that point is the center of mass of the
133 body, then the measured inertia is called the "central inertia" of that body.
134 To write down the inertia, we need to calculate the six scalars of the inertia
135 tensor, which is a symmetric 3x3 matrix. These scalars must be expressed in
136 an arbitrary but specified coordinate system. So an Inertia is meaningful only
137 in conjunction with a particular set of axes, fixed to the body, whose origin
138 is the point about which the inertia is being measured, and in whose
139 coordinate system this measurement is being expressed. Note that changing the
140 reference point results in a new physical quantity, but changing the reference
141 axes only affects the measure numbers of that quantity. For any reference
142 point, there is a unique set of reference axes in which the inertia tensor is
143 diagonal; those are called the "principal axes" of the body at that point, and
144 the resulting diagonal elements are the "principal moments of inertia". When
145 we speak of an inertia being "in" a frame, we mean the physical quantity
146 measured about the frame's origin and then expressed in the frame's axes.
147 
148 This low-level Inertia class does not attempt to keep track of \e which frame
149 it is in. It provides construction and operations involving inertia that can
150 proceed using only an implicit frame F. Clients of this class are responsible
151 for keeping track of that frame. In particular, in order to shift the
152 inertia's "measured-about" point one must know whether either the starting or
153 final inertia is central, because we must always shift inertias by passing
154 through the central inertia. So this class provides operations for doing the
155 shifting, but expects to be told by the client where to find the center of mass.
156 
157 Re-expressing an Inertia in a different coordinate system does not entail a
158 change of physical meaning in the way that shifting it to a different point
159 does. Note that because inertia is a tensor, there is a "left frame" and
160 "right frame". For our purposes, these will always be the same so we'll only
161 indicate the frame once, as in 'I_pt_frame'. This should be understood to mean
162 'frame_I_pt_frame' and re-expressing an Inertia requires both a left and right
163 multiply by the rotation matrix. So I_OB_B is the inertia about body B's
164 origin point OB, expressed in B, while I_OB_G is the same physical quantity
165 but expressed in Ground (the latter is a component of the Spatial Inertia
166 which we usually want in the Ground frame). Frame conversion is done logically
167 like this:
168 <pre>
169    I_OB_G = R_GB * I_OB_B * R_BG  (R_BG=~R_GB)
170 </pre>
171 but we can save computation time by performing this as a single operation.
172 
173 The central inertia would be I_CB_B for body B.
174 
175 A Inertia is a symmetric matrix and is positive definite for nonsingular bodies
176 (that is, a body composed of at least three noncollinear point masses).
177 
178 Some attempt is made to check the validity of an Inertia matrix, at least
179 when running in Debug mode. Some conditions it must satisfy are:
180  - must be symmetric
181  - all diagonal elements must be nonnegative
182  - diagonal elements must satisfy the triangle inequality (sum of any two
183    is greater than or equal the other one)
184 
185 <h3>Abbreviations</h3>
186 Typedefs exist for the most common invocations of Inertia_\<P\>:
187  - \ref SimTK::Inertia "Inertia" for default Real precision (this is
188    almost always used)
189  - \ref SimTK::fInertia "fInertia" for single (float) precision
190  - \ref SimTK::dInertia "dInertia" for double precision
191 **/
192 template <class P>
193 class SimTK_SimTKCOMMON_EXPORT Inertia_ {
194     typedef Rotation_<P>    RotationP;
195 public:
196 /// Default is a NaN-ed out mess to avoid accidents, even in Release mode.
197 /// Other than this value, an Inertia matrix should always be valid.
Inertia_()198 Inertia_() : I_OF_F(NTraits<P>::getNaN()) {}
199 
200 // Default copy constructor, copy assignment, destructor.
201 
202 /// Create a principal inertia matrix with identical diagonal elements,
203 /// like a sphere where moment=2/5 m r^2, or a cube where
204 /// moment=1/6 m s^2, with m the total mass, r the sphere's radius
205 /// and s the length of a side of the cube. Note that many rigid
206 /// bodies of different shapes and masses can have the same inertia
207 /// matrix.
Inertia_(const P & moment)208 explicit Inertia_(const P& moment) : I_OF_F(moment)
209 {   errChk("Inertia::Inertia(moment)"); }
210 
211 /// Create an Inertia matrix for a point mass at a given location,
212 /// measured from the origin OF of the implicit frame F, and expressed
213 /// in F. Cost is 14 flops.
Inertia_(const Vec<3,P> & p,const P & mass)214 Inertia_(const Vec<3,P>& p, const P& mass) : I_OF_F(pointMassAt(p,mass)) {}
215 
216 /// Create an inertia matrix from a vector of the \e moments of
217 /// inertia (the inertia matrix diagonal) and optionally a vector of
218 /// the \e products of inertia (the off-diagonals). Moments are
219 /// in the order xx,yy,zz; products are xy,xz,yz.
220 explicit Inertia_(const Vec<3,P>& moments, const Vec<3,P>& products=Vec<3,P>(0))
221 {   I_OF_F.updDiag()  = moments;
222     I_OF_F.updLower() = products;
223     errChk("Inertia::Inertia(moments,products)"); }
224 
225 /// Create a principal inertia matrix (only non-zero on diagonal).
Inertia_(const P & xx,const P & yy,const P & zz)226 Inertia_(const P& xx, const P& yy, const P& zz)
227 {   I_OF_F = SymMat<3,P>(xx,
228                         0, yy,
229                         0,  0, zz);
230     errChk("Inertia::setInertia(xx,yy,zz)"); }
231 
232 /// This is a general inertia matrix. Note the order of these
233 /// arguments: moments of inertia first, then products of inertia.
Inertia_(const P & xx,const P & yy,const P & zz,const P & xy,const P & xz,const P & yz)234 Inertia_(const P& xx, const P& yy, const P& zz,
235             const P& xy, const P& xz, const P& yz)
236 {   I_OF_F = SymMat<3,P>(xx,
237                         xy, yy,
238                         xz, yz, zz);
239     errChk("Inertia::setInertia(xx,yy,zz,xy,xz,yz)"); }
240 
241 /// Construct an Inertia from a symmetric 3x3 matrix. The diagonals must
242 /// be nonnegative and satisfy the triangle inequality.
Inertia_(const SymMat<3,P> & inertia)243 explicit Inertia_(const SymMat<3,P>& inertia) : I_OF_F(inertia)
244 {   errChk("Inertia::Inertia(SymMat33)"); }
245 
246 /// Construct an Inertia matrix from a 3x3 symmetric matrix. In Debug mode
247 /// we'll test that the supplied matrix is numerically close to symmetric, and
248 /// that it satisfies other requirements of an Inertia matrix.
Inertia_(const Mat<3,3,P> & m)249 explicit Inertia_(const Mat<3,3,P>& m)
250 {   SimTK_ERRCHK(m.isNumericallySymmetric(),
251                     "Inertia(Mat33)", "The supplied matrix was not symmetric.");
252     I_OF_F = SymMat<3,P>(m);
253     errChk("Inertia(Mat33)"); }
254 
255 
256 /// Set an inertia matrix to have only principal moments (that is, it
257 /// will be diagonal). Returns a reference to "this" like an assignment operator.
setInertia(const P & xx,const P & yy,const P & zz)258 Inertia_& setInertia(const P& xx, const P& yy, const P& zz) {
259     I_OF_F = P(0); I_OF_F(0,0) = xx; I_OF_F(1,1) = yy;  I_OF_F(2,2) = zz;
260     errChk("Inertia::setInertia(xx,yy,zz)");
261     return *this;
262 }
263 
264 /// Set principal moments and optionally off-diagonal terms.
265 /// Returns a reference to "this" like an assignment operator.
266 Inertia_& setInertia(const Vec<3,P>& moments, const Vec<3,P>& products=Vec<3,P>(0)) {
267     I_OF_F.updDiag()  = moments;
268     I_OF_F.updLower() = products;
269     errChk("Inertia::setInertia(moments,products)");
270     return *this;
271 }
272 
273 /// Set this Inertia to a general matrix. Note the order of these
274 /// arguments: moments of inertia first, then products of inertia.
275 /// Behaves like an assignment statement. Will throw an error message
276 /// in Debug mode if the supplied elements do not constitute a valid
277 /// Inertia matrix.
setInertia(const P & xx,const P & yy,const P & zz,const P & xy,const P & xz,const P & yz)278 Inertia_& setInertia(const P& xx, const P& yy, const P& zz,
279                         const P& xy, const P& xz, const P& yz) {
280     setInertia(Vec<3,P>(xx,yy,zz), Vec<3,P>(xy,xz,yz));
281     errChk("Inertia::setInertia(xx,yy,zz,xy,xz,yz)");
282     return *this;
283 }
284 
285 
286 /// Add in another inertia matrix. Frames and reference point must be the same but
287 /// we can't check. (6 flops)
288 Inertia_& operator+=(const Inertia_& inertia)
289 {   I_OF_F += inertia.I_OF_F;
290     errChk("Inertia::operator+=()");
291     return *this; }
292 
293 /// Subtract off another inertia matrix. Frames and reference point must
294 /// be the same but we can't check. (6 flops)
295 Inertia_& operator-=(const Inertia_& inertia)
296 {   I_OF_F -= inertia.I_OF_F;
297     errChk("Inertia::operator-=()");
298     return *this; }
299 
300 /// Multiply this inertia matrix by a scalar. Cost is 6 flops.
301 Inertia_& operator*=(const P& s) {I_OF_F *= s; return *this;}
302 
303 /// Divide this inertia matrix by a scalar. Cost is about 20 flops (a divide
304 /// and 6 multiplies).
305 Inertia_& operator/=(const P& s) {I_OF_F /= s; return *this;}
306 
307 /// Assume that the current inertia is about the F frame's origin OF, and
308 /// expressed in F. Given the vector from OF to the body center of mass CF,
309 /// and the mass m of the body, we can shift the inertia to the center
310 /// of mass. This produces a new Inertia I' whose (implicit) frame F' is
311 /// aligned with F but has origin CF (an inertia like that is called a "central
312 /// inertia". I' = I - Icom where Icom is the inertia of a fictitious
313 /// point mass of mass m (that is, the same as the body mass) located at CF
314 /// (measured in F) about OF. Cost is 20 flops.
315 /// @see shiftToMassCenterInPlace(), shiftFromMassCenter()
shiftToMassCenter(const Vec<3,P> & CF,const P & mass)316 Inertia_ shiftToMassCenter(const Vec<3,P>& CF, const P& mass) const
317 {   Inertia_ I(*this); I -= pointMassAt(CF, mass);
318     I.errChk("Inertia::shiftToMassCenter()");
319     return I; }
320 
321 /// Assume that the current inertia is about the F frame's origin OF, and
322 /// expressed in F. Given the vector from OF to the body center of mass CF,
323 /// and the mass m of the body, we can shift the inertia to the center
324 /// of mass. This produces a new Inertia I' whose (implicit) frame F' is
325 /// aligned with F but has origin CF (an inertia like that is called a "central
326 /// inertia". I' = I - Icom where Icom is the inertia of a fictitious
327 /// point mass of mass m (that is, the same as the body mass) located at CF
328 /// (measured in F) about OF. Cost is 20 flops.
329 /// @see shiftToMassCenter() if you want to leave this object unmolested.
330 /// @see shiftFromMassCenterInPlace()
shiftToMassCenterInPlace(const Vec<3,P> & CF,const P & mass)331 Inertia_& shiftToMassCenterInPlace(const Vec<3,P>& CF, const P& mass)
332 {   (*this) -= pointMassAt(CF, mass);
333     errChk("Inertia::shiftToMassCenterInPlace()");
334     return *this; }
335 
336 /// Assuming that the current inertia I is a central inertia (that is, it is
337 /// inertia about the body center of mass CF), shift it to some other point p
338 /// measured from the center of mass. This produces a new inertia I' about
339 /// the point p given by I' = I + Ip where Ip is the inertia of a fictitious
340 /// point mass of mass mtot (the total body mass) located at p, about CF.
341 /// Cost is 20 flops.
342 /// @see shiftFromMassCenterInPlace(), shiftToMassCenter()
shiftFromMassCenter(const Vec<3,P> & p,const P & mass)343 Inertia_ shiftFromMassCenter(const Vec<3,P>& p, const P& mass) const
344 {   Inertia_ I(*this); I += pointMassAt(p, mass);
345     I.errChk("Inertia::shiftFromMassCenter()");
346     return I; }
347 
348 /// Assuming that the current inertia I is a central inertia (that is, it is
349 /// inertia about the body center of mass CF), shift it to some other point p
350 /// measured from the center of mass. This produces a new inertia I' about
351 /// the point p given by I' = I + Ip where Ip is the inertia of a fictitious
352 /// point mass of mass mtot (the total body mass) located at p, about CF.
353 /// Cost is 20 flops.
354 /// @see shiftFromMassCenter() if you want to leave this object unmolested.
355 /// @see shiftToMassCenterInPlace()
shiftFromMassCenterInPlace(const Vec<3,P> & p,const P & mass)356 Inertia_& shiftFromMassCenterInPlace(const Vec<3,P>& p, const P& mass)
357 {   (*this) += pointMassAt(p, mass);
358     errChk("Inertia::shiftFromMassCenterInPlace()");
359     return *this; }
360 
361 /// Return a new inertia matrix like this one but re-expressed in another
362 /// frame (leaving the origin point unchanged). Call this inertia matrix
363 /// I_OF_F, that is, it is taken about the origin of some frame F, and
364 /// expressed in F. We want to return I_OF_B, the same inertia matrix,
365 /// still taken about the origin of F, but expressed in the B frame, given
366 /// by I_OF_B=R_BF*I_OF_F*R_FB where R_FB is the rotation matrix giving
367 /// the orientation of frame B in F. This is handled here by a special
368 /// method of the Rotation class which rotates a symmetric tensor
369 /// at a cost of 57 flops.
370 /// @see reexpressInPlace()
reexpress(const Rotation_<P> & R_FB)371 Inertia_ reexpress(const Rotation_<P>& R_FB) const
372 {   return Inertia_((~R_FB).reexpressSymMat33(I_OF_F)); }
373 
374 /// Rexpress using an inverse rotation to avoid having to convert it.
375 /// @see rexpress(Rotation) for information
reexpress(const InverseRotation_<P> & R_FB)376 Inertia_ reexpress(const InverseRotation_<P>& R_FB) const
377 {   return Inertia_((~R_FB).reexpressSymMat33(I_OF_F)); }
378 
379 /// Re-express this inertia matrix in another frame, changing the object
380 /// in place; see reexpress() if you want to leave this object unmolested
381 /// and get a new one instead. Cost is 57 flops.
382 /// @see reexpress() if you want to leave this object unmolested.
reexpressInPlace(const Rotation_<P> & R_FB)383 Inertia_& reexpressInPlace(const Rotation_<P>& R_FB)
384 {   I_OF_F = (~R_FB).reexpressSymMat33(I_OF_F); return *this; }
385 
386 /// Rexpress in place using an inverse rotation to avoid having to convert it.
387 /// @see rexpressInPlace(Rotation) for information
reexpressInPlace(const InverseRotation_<P> & R_FB)388 Inertia_& reexpressInPlace(const InverseRotation_<P>& R_FB)
389 {   I_OF_F = (~R_FB).reexpressSymMat33(I_OF_F); return *this; }
390 
trace()391 P trace() const {return I_OF_F.trace();}
392 
393 /// This is an implicit conversion to a const SymMat33.
394 operator const SymMat<3,P>&() const {return I_OF_F;}
395 
396 /// Obtain a reference to the underlying symmetric matrix type.
asSymMat33()397 const SymMat<3,P>& asSymMat33() const {return I_OF_F;}
398 
399 /// Expand the internal packed representation into a full 3x3 symmetric
400 /// matrix with all elements set.
toMat33()401 Mat<3,3,P> toMat33() const {return Mat<3,3,P>(I_OF_F);}
402 
403 /// Obtain the inertia moments (diagonal of the Inertia matrix) as a Vec3
404 /// ordered xx, yy, zz.
getMoments()405 const Vec<3,P>& getMoments()  const {return I_OF_F.getDiag();}
406 /// Obtain the inertia products (off-diagonals of the Inertia matrix)
407 /// as a Vec3 with elements ordered xy, xz, yz.
getProducts()408 const Vec<3,P>& getProducts() const {return I_OF_F.getLower();}
409 
isNaN()410 bool isNaN()    const {return I_OF_F.isNaN();}
isInf()411 bool isInf()    const {return I_OF_F.isInf();}
isFinite()412 bool isFinite() const {return I_OF_F.isFinite();}
413 
414 /// Compare this inertia matrix with another one and return true if they
415 /// are close to within a default numerical tolerance. Cost is about
416 /// 30 flops.
isNumericallyEqual(const Inertia_<P> & other)417 bool isNumericallyEqual(const Inertia_<P>& other) const
418 {   return I_OF_F.isNumericallyEqual(other.I_OF_F); }
419 
420 /// Compare this inertia matrix with another one and return true if they
421 /// are close to within a specified numerical tolerance. Cost is about
422 /// 30 flops.
isNumericallyEqual(const Inertia_<P> & other,double tol)423 bool isNumericallyEqual(const Inertia_<P>& other, double tol) const
424 {   return I_OF_F.isNumericallyEqual(other.I_OF_F, tol); }
425 
426 /// %Test some conditions that must hold for a valid Inertia matrix.
427 /// Cost is about 25 flops.
isValidInertiaMatrix(const SymMat<3,P> & m)428 static bool isValidInertiaMatrix(const SymMat<3,P>& m) {
429     if (m.isNaN()) return false;
430 
431     const Vec<3,P>& d = m.diag();
432     if (!(d >= 0)) return false;    // diagonals must be nonnegative
433 
434     const P Slop = std::max(d.sum(),P(1))
435                        * NTraits<P>::getSignificant();
436 
437     if (!(d[0]+d[1]+Slop>=d[2] && d[0]+d[2]+Slop>=d[1] && d[1]+d[2]+Slop>=d[0]))
438         return false;               // must satisfy triangle inequality
439 
440     // Thanks to Paul Mitiguy for this condition on products of inertia.
441     const Vec<3,P>& p = m.getLower();
442     if (!( d[0]+Slop>=std::abs(2*p[2])
443         && d[1]+Slop>=std::abs(2*p[1])
444         && d[2]+Slop>=std::abs(2*p[0])))
445         return false;               // max products are limited by moments
446 
447     return true;
448 }
449 
450 /// Create an Inertia matrix for a point located at the origin -- that is,
451 /// an all-zero matrix.
pointMassAtOrigin()452 static Inertia_ pointMassAtOrigin() {return Inertia_(0);}
453 
454 /// Create an Inertia matrix for a point of a given mass, located at
455 /// a given location measured from the origin of the implicit F frame.
456 /// This is equivalent to m*crossMatSq(p) but is implemented elementwise
457 /// here for speed, giving a cost of 14 flops.
pointMassAt(const Vec<3,P> & p,const P & m)458 static Inertia_ pointMassAt(const Vec<3,P>& p, const P& m) {
459     const Vec<3,P> mp = m*p;       // 3 flops
460     const P mxx = mp[0]*p[0];
461     const P myy = mp[1]*p[1];
462     const P mzz = mp[2]*p[2];
463     const P nmx = -mp[0];
464     const P nmy = -mp[1];
465     return Inertia_( myy+mzz,  mxx+mzz,  mxx+myy,
466                         nmx*p[1], nmx*p[2], nmy*p[2] );
467 }
468 
469 /// @name Unit inertia matrix factories
470 /// These return UnitInertia matrices (inertias of unit-mass objects)
471 /// converted to Inertias. Multiply the result by the actual mass
472 /// to get the Inertia of an actual object of this shape. See the
473 /// UnitInertia class for more information.
474 //@{
475 
476 /// Create a UnitInertia matrix for a unit mass sphere of radius \a r centered
477 /// at the origin.
478 inline static Inertia_ sphere(const P& r);
479 
480 /// Unit-mass cylinder aligned along z axis;  use radius and half-length.
481 /// If r==0 this is a thin rod; hz=0 it is a thin disk.
482 inline static Inertia_ cylinderAlongZ(const P& r, const P& hz);
483 
484 /// Unit-mass cylinder aligned along y axis;  use radius and half-length.
485 /// If r==0 this is a thin rod; hy=0 it is a thin disk.
486 inline static Inertia_ cylinderAlongY(const P& r, const P& hy);
487 
488 /// Unit-mass cylinder aligned along x axis; use radius and half-length.
489 /// If r==0 this is a thin rod; hx=0 it is a thin disk.
490 inline static Inertia_ cylinderAlongX(const P& r, const P& hx);
491 
492 /// Unit-mass brick given by half-lengths in each direction. One dimension zero
493 /// gives inertia of a thin rectangular sheet; two zero gives inertia
494 /// of a thin rod in the remaining direction.
495 inline static Inertia_ brick(const P& hx, const P& hy, const P& hz);
496 
497 /// Alternate interface to brick() that takes a Vec3 for the half lengths.
498 inline static Inertia_ brick(const Vec<3,P>& halfLengths);
499 
500 /// Unit-mass ellipsoid given by half-lengths in each direction.
501 inline static Inertia_ ellipsoid(const P& hx, const P& hy, const P& hz);
502 
503 /// Alternate interface to ellipsoid() that takes a Vec3 for the half lengths.
504 inline static Inertia_ ellipsoid(const Vec<3,P>& halfLengths);
505 
506 //@}
507 
508 protected:
509 // Reinterpret this Inertia matrix as a UnitInertia matrix, that is, as the
510 // inertia of something with unit mass. This is useful in implementing
511 // methods of the UnitInertia class in terms of Inertia methods. Be sure you
512 // know that this is a unit-mass inertia!
getAsUnitInertia()513 const UnitInertia_<P>& getAsUnitInertia() const
514 {   return *static_cast<const UnitInertia_<P>*>(this); }
updAsUnitInertia()515 UnitInertia_<P>& updAsUnitInertia()
516 {   return *static_cast<UnitInertia_<P>*>(this); }
517 
518 // If error checking is enabled (only in Debug mode), this
519 // method will run some tests on the current contents of this Inertia
520 // matrix and throw an error message if it is not valid. This should be
521 // the same set of tests as run by the isValidInertiaMatrix() method above.
errChk(const char * methodName)522 void errChk(const char* methodName) const {
523 #ifndef NDEBUG
524     SimTK_ERRCHK(!isNaN(), methodName,
525         "Inertia matrix contains a NaN.");
526 
527     const Vec<3,P>& d = I_OF_F.getDiag();  // moments
528     const Vec<3,P>& p = I_OF_F.getLower(); // products
529     const P Ixx = d[0], Iyy = d[1], Izz = d[2];
530     const P Ixy = p[0], Ixz = p[1], Iyz = p[2];
531 
532     SimTK_ERRCHK3(d >= -SignificantReal, methodName,
533         "Diagonals of an Inertia matrix must be nonnegative; got %g,%g,%g.",
534         (double)Ixx,(double)Iyy,(double)Izz);
535 
536     // TODO: This is looser than it should be as a workaround for distorted
537     // rotation matrices that were produced by an 11,000 body chain that
538     // Sam Flores encountered.
539     const P Slop = std::max(d.sum(),P(1))
540                        * std::sqrt(NTraits<P>::getEps());
541 
542     SimTK_ERRCHK3(   Ixx+Iyy+Slop>=Izz
543                   && Ixx+Izz+Slop>=Iyy
544                   && Iyy+Izz+Slop>=Ixx,
545         methodName,
546         "Diagonals of an Inertia matrix must satisfy the triangle "
547         "inequality; got %g,%g,%g.",
548         (double)Ixx,(double)Iyy,(double)Izz);
549 
550     // Thanks to Paul Mitiguy for this condition on products of inertia.
551     SimTK_ERRCHK(   Ixx+Slop>=std::abs(2*Iyz)
552                  && Iyy+Slop>=std::abs(2*Ixz)
553                  && Izz+Slop>=std::abs(2*Ixy),
554         methodName,
555         "The magnitude of a product of inertia was too large to be physical.");
556 #endif
557 }
558 
559 // Inertia expressed in frame F and about F's origin OF. Note that frame F
560 // is implicit here; all we actually have are the inertia scalars.
561 SymMat<3,P> I_OF_F;
562 };
563 
564 /// Add two compatible inertia matrices, meaning they must be taken about the
565 /// same point and expressed in the same frame. There is no way to verify
566 /// compatibility; make sure you know what you're doing. Cost is 6 flops.
567 /// @relates Inertia_
568 template <class P> inline Inertia_<P>
569 operator+(const Inertia_<P>& l, const Inertia_<P>& r)
570 {   return Inertia_<P>(l) += r; }
571 
572 /// Subtract from one inertia matrix another one which is compatible, meaning
573 /// that both must be taken about the same point and expressed in the same frame.
574 /// There is no way to verify compatibility; make sure you know what you're doing.
575 /// Cost is 6 flops.
576 /// @relates Inertia_
577 template <class P> inline Inertia_<P>
578 operator-(const Inertia_<P>& l, const Inertia_<P>& r)
579 {   return Inertia_<P>(l) -= r; }
580 
581 /// Multiply an inertia matrix by a scalar. Cost is 6 flops.
582 /// @relates Inertia_
583 template <class P> inline Inertia_<P>
584 operator*(const Inertia_<P>& i, const P& r)
585 {   return Inertia_<P>(i) *= r; }
586 
587 /// Multiply an inertia matrix by a scalar. Cost is 6 flops.
588 /// @relates Inertia_
589 template <class P> inline Inertia_<P>
590 operator*(const P& r, const Inertia_<P>& i)
591 {   return Inertia_<P>(i) *= r; }
592 
593 
594 /// Multiply an inertia matrix by a scalar given as an int.
595 /// Cost is 6 flops.
596 /// @relates Inertia_
597 template <class P> inline Inertia_<P>
598 operator*(const Inertia_<P>& i, int r)
599 {   return Inertia_<P>(i) *= P(r); }
600 
601 /// Multiply an inertia matrix by a scalar given as an int.
602 /// Cost is 6 flops.
603 /// @relates Inertia_
604 template <class P> inline Inertia_<P>
605 operator*(int r, const Inertia_<P>& i)
606 {   return Inertia_<P>(i) *= P(r); }
607 
608 /// Divide an inertia matrix by a scalar. Cost is about 20
609 /// flops (one divide and six multiplies).
610 /// @relates Inertia_
611 template <class P> inline Inertia_<P>
612 operator/(const Inertia_<P>& i, const P& r)
613 {   return Inertia_<P>(i) /= r; }
614 
615 /// Divide an inertia matrix by a scalar provided as an int.
616 /// Cost is about 20 flops (one divide and six multiplies).
617 /// @relates Inertia_
618 template <class P> inline Inertia_<P>
619 operator/(const Inertia_<P>& i, int r)
620 {   return Inertia_<P>(i) /= P(r); }
621 
622 /// Multiply an inertia matrix I on the right by a vector w giving the
623 /// vector result I*w.
624 /// @relates Inertia_
625 template <class P> inline Vec<3,P>
626 operator*(const Inertia_<P>& I, const Vec<3,P>& w)
627 {   return I.asSymMat33() * w; }
628 
629 /// Compare two inertia matrices for exact (bitwise) equality. This is
630 /// too strict for most purposes; use Inertia::isNumericallyEqual() instead
631 /// to test for approximate equality. Cost here is 6 flops.
632 /// @relates Inertia_
633 template <class P> inline bool
634 operator==(const Inertia_<P>& i1, const Inertia_<P>& i2)
635 {   return i1.asSymMat33() == i2.asSymMat33(); }
636 
637 /// Output a human-readable representation of an inertia matrix to the
638 /// indicated stream.
639 /// @relates Inertia_
640 template <class P> inline std::ostream&
641 operator<<(std::ostream& o, const Inertia_<P>& inertia)
642 {   return o << inertia.toMat33(); }
643 
644 
645 // -----------------------------------------------------------------------------
646 //                            UNIT INERTIA MATRIX
647 // -----------------------------------------------------------------------------
648 /** A UnitInertia matrix is a unit-mass inertia matrix; you can convert it to an
649 Inertia by multiplying it by the actual body mass. Functionality is limited
650 here to those few operations which ensure unit mass; most operations on a
651 UnitInertia matrix result in a general Inertia instead. You can use a
652 UnitInertia object wherever an Inertia is expected but not vice versa.
653 
654 When constructing a UnitInertia matrix, note that we cannot verify that it
655 actually has unit mass because every legal Inertia matrix can be viewed as
656 the UnitInertia matrix for some differently-scaled object.
657 
658 Unit inertia matrices are sometimes called "gyration" matrices; we will often
659 represent them with the symbol "G" to avoid confusion with general inertia
660 matrices for which the symbol "I" (or sometimes "J") is used.
661 
662 <h3>Abbreviations</h3>
663 Typedefs exist for the most common invocations of UnitInertia_\<P\>:
664  - \ref SimTK::UnitInertia "UnitInertia" for default Real precision (this is
665    almost always used)
666  - \ref SimTK::fUnitInertia "fUnitInertia" for single (float) precision
667  - \ref SimTK::dUnitInertia "dUnitInertia" for double precision **/
668 template <class P>
669 class SimTK_SimTKCOMMON_EXPORT UnitInertia_ : public Inertia_<P> {
670     typedef P               RealP;
671     typedef Vec<3,P>        Vec3P;
672     typedef SymMat<3,P>     SymMat33P;
673     typedef Mat<3,3,P>      Mat33P;
674     typedef Rotation_<P>    RotationP;
675     typedef Inertia_<P>     InertiaP;
676 public:
677 /// Default is a NaN-ed out mess to avoid accidents, even in Release mode.
678 /// Other than this value, a UnitInertia_ should always be valid.
UnitInertia_()679 UnitInertia_() {}
680 
681 // Default copy constructor, copy assignment, destructor.
682 
683 /// Create a principal unit inertia matrix with identical diagonal elements.
684 /// This is the unit inertia matrix of a unit mass sphere of radius
685 /// r = sqrt(5/2 * moment) centered on the origin.
UnitInertia_(const RealP & moment)686 explicit UnitInertia_(const RealP& moment) : InertiaP(moment) {}
687 
688 /// Create a unit inertia matrix from a vector of the \e moments of
689 /// inertia (the inertia matrix diagonal) and optionally a vector of
690 /// the \e products of inertia (the off-diagonals). Moments are
691 /// in the order xx,yy,zz; products are xy,xz,yz.
692 explicit UnitInertia_(const Vec3P& moments, const Vec3P& products=Vec3P(0))
InertiaP(moments,products)693 :   InertiaP(moments,products) {}
694 
695 /// Create a principal unit inertia matrix (only non-zero on diagonal).
UnitInertia_(const RealP & xx,const RealP & yy,const RealP & zz)696 UnitInertia_(const RealP& xx, const RealP& yy, const RealP& zz)
697 :   InertiaP(xx,yy,zz) {}
698 
699 /// This is a general unit inertia matrix. Note the order of these
700 /// arguments: moments of inertia first, then products of inertia.
UnitInertia_(const RealP & xx,const RealP & yy,const RealP & zz,const RealP & xy,const RealP & xz,const RealP & yz)701 UnitInertia_(const RealP& xx, const RealP& yy, const RealP& zz,
702             const RealP& xy, const RealP& xz, const RealP& yz)
703 :   InertiaP(xx,yy,zz,xy,xz,yz) {}
704 
705 /// Construct a UnitInertia from a symmetric 3x3 matrix. The diagonals must
706 /// be nonnegative and satisfy the triangle inequality.
UnitInertia_(const SymMat33P & m)707 explicit UnitInertia_(const SymMat33P& m) : InertiaP(m) {}
708 
709 /// Construct a UnitInertia from a 3x3 symmetric matrix. In Debug mode
710 /// we'll test that the supplied matrix is numerically close to symmetric,
711 /// and that it satisfies other requirements of an inertia matrix.
UnitInertia_(const Mat33P & m)712 explicit UnitInertia_(const Mat33P& m) : InertiaP(m) {}
713 
714 /// Construct a UnitInertia matrix from an Inertia matrix. Note that there
715 /// is no way to check whether this is really a unit inertia -- \e any
716 /// inertia matrix may be interpreted as a unit inertia for some shape. So
717 /// be sure you know what you're doing before you use this constructor!
UnitInertia_(const Inertia_<P> & inertia)718 explicit UnitInertia_(const Inertia_<P>& inertia) : InertiaP(inertia) {}
719 
720 /// Set a UnitInertia matrix to have only principal moments (that is, it
721 /// will be diagonal). Returns a reference to "this" like an assignment
722 /// operator.
setUnitInertia(const RealP & xx,const RealP & yy,const RealP & zz)723 UnitInertia_& setUnitInertia(const RealP& xx, const RealP& yy, const RealP& zz)
724 {   InertiaP::setInertia(xx,yy,zz); return *this; }
725 
726 /// Set principal moments and optionally off-diagonal terms.
727 /// Returns a reference to "this" like an assignment operator.
728 UnitInertia_& setUnitInertia(const Vec3P& moments, const Vec3P& products=Vec3P(0))
729 {   InertiaP::setInertia(moments,products); return *this; }
730 
731 /// Set this UnitInertia to a general matrix. Note the order of these
732 /// arguments: moments of inertia first, then products of inertia.
733 /// Behaves like an assignment statement. Will throw an error message
734 /// in Debug mode if the supplied elements do not constitute a valid
735 /// inertia matrix.
setUnitInertia(const RealP & xx,const RealP & yy,const RealP & zz,const RealP & xy,const RealP & xz,const RealP & yz)736 UnitInertia_& setUnitInertia(const RealP& xx, const RealP& yy, const RealP& zz,
737                         const RealP& xy, const RealP& xz, const RealP& yz)
738 {   InertiaP::setInertia(xx,yy,zz,xy,xz,yz); return *this; }
739 
740 
741 // No +=, -=, etc. operators because those don't result in a UnitInertia
742 // matrix. The parent class ones are suppressed below.
743 
744 /// Assuming that this unit inertia matrix is currently taken about some (implicit)
745 /// frame F's origin OF, produce a new unit inertia matrix which is the same as this one
746 /// except measured about the body's centroid CF. We are given the vector from OF to
747 /// the centroid CF, expressed in F. This produces a new UnitInertia matrix G' whose
748 /// (implicit) frame F' is aligned with F but has origin CF (an inertia matrix like
749 /// that is called "central" or "centroidal"). From the parallel axis theorem for
750 /// inertias, G' = G - Gcom where Gcom is the inertia matrix of a fictitious,
751 /// unit-mass point located at CF (measured in F) taken about OF. (17 flops)
752 /// @see shiftToCentroidInPlace(), shiftFromCentroid()
shiftToCentroid(const Vec3P & CF)753 UnitInertia_ shiftToCentroid(const Vec3P& CF) const
754 {   UnitInertia_ G(*this);
755     G.Inertia_<P>::operator-=(pointMassAt(CF));
756     return G; }
757 
758 /// Assuming that this unit inertia matrix is currently taken about some (implicit)
759 /// frame F's origin OF, modify it so that it is instead taken about the body's
760 /// centroid CF. We are given the vector from OF to
761 /// the centroid CF, expressed in F. This produces a new UnitInertia G' whose
762 /// (implicit) frame F' is aligned with F but has origin CF (an inertia matrix like
763 /// that is called "central" or "centroidal"). From the parallel axis theorem for
764 /// inertias, G' = G - Gcom where Gcom is the inertia matrix of a fictitious,
765 /// unit-mass point located at CF (measured in F) taken about OF. A reference
766 /// to the modified object is returned so that you can chain this method in
767 /// the manner of assignment operators. Cost is 17 flops.
768 /// @see shiftToCentroid() if you want to leave this object unmolested.
769 /// @see shiftFromCentroidInPlace()
shiftToCentroidInPlace(const Vec3P & CF)770 UnitInertia_& shiftToCentroidInPlace(const Vec3P& CF)
771 {   InertiaP::operator-=(pointMassAt(CF));
772     return *this; }
773 
774 /// Assuming that the current UnitInertia G is a central inertia (that is, it is
775 /// inertia about the body centroid CF), create a new object that is the same
776 /// as this one except shifted to some other point p measured from the centroid.
777 /// This produces a new inertia G' about the point p given by G' = G + Gp where
778 /// Gp is the inertia of a fictitious point located at p, taken about CF. Cost
779 /// is 17 flops.
780 /// @see shiftFromCentroidInPlace(), shiftToCentroid()
shiftFromCentroid(const Vec3P & p)781 UnitInertia_ shiftFromCentroid(const Vec3P& p) const
782 {   UnitInertia_ G(*this);
783     G.Inertia_<P>::operator+=(pointMassAt(p));
784     return G; }
785 
786 /// Assuming that the current UnitInertia G is a central inertia (that is, it is
787 /// inertia about the body centroid CF), shift it in place to some other point p
788 /// measured from the centroid. This changes G to a modified inertia G' taken
789 /// about the point p, with the parallel axis theorem for inertia giving
790 /// G' = G + Gp where Gp is the inertia of a fictitious, unit-mass point located
791 /// at p, taken about CF. Cost is 17 flops.
792 /// @see shiftFromCentroid() if you want to leave this object unmolested.
793 /// @see shiftToCentroidInPlace()
shiftFromCentroidInPlace(const Vec3P & p)794 UnitInertia_& shiftFromCentroidInPlace(const Vec3P& p)
795 {   InertiaP::operator+=(pointMassAt(p));
796     return *this; }
797 
798 /// Return a new unit inertia matrix like this one but re-expressed in another
799 /// frame (leaving the origin point unchanged). Call this inertia matrix
800 /// G_OF_F, that is, it is taken about the origin of some frame F, and
801 /// expressed in F. We want to return G_OF_B, the same unit inertia matrix,
802 /// still taken about the origin of F, but expressed in the B frame, given
803 /// by G_OF_B=R_BF*G_OF_F*R_FB where R_FB is the rotation matrix giving
804 /// the orientation of frame B in F. This is handled here by a special
805 /// method of the Rotation class which rotates a symmetric tensor
806 /// at a cost of 57 flops.
807 /// @see reexpressInPlace()
reexpress(const Rotation_<P> & R_FB)808 UnitInertia_ reexpress(const Rotation_<P>& R_FB) const
809 {   return UnitInertia_((~R_FB).reexpressSymMat33(this->I_OF_F)); }
810 
811 /// Rexpress using an inverse rotation to avoid having to convert it.
812 /// @see rexpress(Rotation) for information
reexpress(const InverseRotation_<P> & R_FB)813 UnitInertia_ reexpress(const InverseRotation_<P>& R_FB) const
814 {   return UnitInertia_((~R_FB).reexpressSymMat33(this->I_OF_F)); }
815 
816 /// Re-express this unit inertia matrix in another frame, changing the object
817 /// in place; see reexpress() if you want to leave this object unmolested
818 /// and get a new one instead. Cost is 57 flops.
819 /// @see reexpress() if you want to leave this object unmolested.
reexpressInPlace(const Rotation_<P> & R_FB)820 UnitInertia_& reexpressInPlace(const Rotation_<P>& R_FB)
821 {   InertiaP::reexpressInPlace(R_FB); return *this; }
822 
823 /// Rexpress using an inverse rotation to avoid having to convert it.
824 /// @see rexpressInPlace(Rotation) for information
reexpressInPlace(const InverseRotation_<P> & R_FB)825 UnitInertia_& reexpressInPlace(const InverseRotation_<P>& R_FB)
826 {   InertiaP::reexpressInPlace(R_FB); return *this; }
827 
828 
829 /// This is an implicit conversion to const SymMat33.
830 operator const SymMat33P&() const {return this->I_OF_F;}
831 
832 /// Recast this UnitInertia matrix as a unit inertia matrix. This is just for
833 /// emphasis; a UnitInertia matrix is already a kind of Inertia matrix by
834 /// inheritance.
asUnitInertia()835 const Inertia_<P>& asUnitInertia() const
836 {   return *static_cast<const Inertia_<P>*>(this); }
837 
838 /// Set from a unit inertia matrix. Note that we can't check; every Inertia
839 /// matrix can be interpreted as a unit inertia for some shape.
setFromUnitInertia(const Inertia_<P> & inertia)840 UnitInertia_& setFromUnitInertia(const Inertia_<P>& inertia)
841 {   Inertia_<P>::operator=(inertia);
842     return *this; }
843 
844 /// %Test some conditions that must hold for a valid UnitInertia matrix.
845 /// Cost is about 9 flops.
846 /// TODO: this may not be comprehensive.
isValidUnitInertiaMatrix(const SymMat33P & m)847 static bool isValidUnitInertiaMatrix(const SymMat33P& m)
848 {   return Inertia_<P>::isValidInertiaMatrix(m); }
849 
850 /// @name UnitInertia matrix factories
851 /// These are UnitInertia matrix factories for some common 3D solids. Each
852 /// defines its own frame aligned (when possible) with principal moments.
853 /// Each has unit mass and its center of mass located at the origin (usually).
854 /// Use this with shiftFromCentroid() to move it somewhere else, and with
855 /// reexpress() to express the UnitInertia matrix in another frame.
856 //@{
857 
858 /// Create a UnitInertia matrix for a point located at the origin -- that is,
859 /// an all-zero matrix.
pointMassAtOrigin()860 static UnitInertia_ pointMassAtOrigin() {return UnitInertia_(0);}
861 
862 /// Create a UnitInertia matrix for a point of unit mass located at a given
863 /// location measured from origin OF and expressed in F (where F is the
864 /// implicit frame of this UnitInertia matrix).
865 /// Cost is 11 flops.
pointMassAt(const Vec3P & p)866 static UnitInertia_ pointMassAt(const Vec3P& p)
867 {   return UnitInertia_(crossMatSq(p)); }
868 
869 /// Create a UnitInertia matrix for a unit mass sphere of radius \a r centered
870 /// at the origin.
sphere(const RealP & r)871 static UnitInertia_ sphere(const RealP& r) {return UnitInertia_(RealP(0.4)*r*r);}
872 
873 /// Unit-mass cylinder aligned along z axis;  use radius and half-length.
874 /// If r==0 this is a thin rod; hz=0 it is a thin disk.
cylinderAlongZ(const RealP & r,const RealP & hz)875 static UnitInertia_ cylinderAlongZ(const RealP& r, const RealP& hz) {
876     const RealP Ixx = (r*r)/4 + (hz*hz)/3;
877     return UnitInertia_(Ixx,Ixx,(r*r)/2);
878 }
879 
880 /// Unit-mass cylinder aligned along y axis;  use radius and half-length.
881 /// If r==0 this is a thin rod; hy=0 it is a thin disk.
cylinderAlongY(const RealP & r,const RealP & hy)882 static UnitInertia_ cylinderAlongY(const RealP& r, const RealP& hy) {
883     const RealP Ixx = (r*r)/4 + (hy*hy)/3;
884     return UnitInertia_(Ixx,(r*r)/2,Ixx);
885 }
886 
887 /// Unit-mass cylinder aligned along x axis; use radius and half-length.
888 /// If r==0 this is a thin rod; hx=0 it is a thin disk.
cylinderAlongX(const RealP & r,const RealP & hx)889 static UnitInertia_ cylinderAlongX(const RealP& r, const RealP& hx) {
890     const RealP Iyy = (r*r)/4 + (hx*hx)/3;
891     return UnitInertia_((r*r)/2,Iyy,Iyy);
892 }
893 
894 /// Unit-mass brick given by half-lengths in each direction. One dimension zero
895 /// gives inertia of a thin rectangular sheet; two zero gives inertia
896 /// of a thin rod in the remaining direction.
brick(const RealP & hx,const RealP & hy,const RealP & hz)897 static UnitInertia_ brick(const RealP& hx, const RealP& hy, const RealP& hz) {
898     const RealP oo3 = RealP(1)/RealP(3);
899     const RealP hx2=hx*hx, hy2=hy*hy, hz2=hz*hz;
900     return UnitInertia_(oo3*(hy2+hz2), oo3*(hx2+hz2), oo3*(hx2+hy2));
901 }
902 
903 /// Alternate interface to brick() that takes a Vec3 for the half lengths.
brick(const Vec3P & halfLengths)904 static UnitInertia_ brick(const Vec3P& halfLengths)
905 {   return brick(halfLengths[0],halfLengths[1],halfLengths[2]); }
906 
907 /// Unit-mass ellipsoid given by half-lengths in each direction.
ellipsoid(const RealP & hx,const RealP & hy,const RealP & hz)908 static UnitInertia_ ellipsoid(const RealP& hx, const RealP& hy, const RealP& hz) {
909     const RealP hx2=hx*hx, hy2=hy*hy, hz2=hz*hz;
910     return UnitInertia_((hy2+hz2)/5, (hx2+hz2)/5, (hx2+hy2)/5);
911 }
912 
913 /// Alternate interface to ellipsoid() that takes a Vec3 for the half lengths.
ellipsoid(const Vec3P & halfLengths)914 static UnitInertia_ ellipsoid(const Vec3P& halfLengths)
915 {   return ellipsoid(halfLengths[0],halfLengths[1],halfLengths[2]); }
916 
917 //@}
918 private:
919 // Suppress Inertia_ methods which are not allowed for UnitInertia_.
920 
921 // These kill all flavors of Inertia_::setInertia() and the
922 // Inertia_ assignment ops.
setInertia()923 void setInertia() {}
924 void operator+=(int) {}
925 void operator-=(int) {}
926 void operator*=(int) {}
927 void operator/=(int) {}
928 };
929 
930 // Implement Inertia methods which are pass-throughs to UnitInertia methods.
931 
932 template <class P> inline Inertia_<P> Inertia_<P>::
sphere(const P & r)933 sphere(const P& r)
934 {   return UnitInertia_<P>::sphere(r); }
935 template <class P> inline Inertia_<P> Inertia_<P>::
cylinderAlongZ(const P & r,const P & hz)936 cylinderAlongZ(const P& r, const P& hz)
937 {   return UnitInertia_<P>::cylinderAlongZ(r,hz); }
938 template <class P> inline Inertia_<P> Inertia_<P>::
cylinderAlongY(const P & r,const P & hy)939 cylinderAlongY(const P& r, const P& hy)
940 {   return UnitInertia_<P>::cylinderAlongY(r,hy); }
941 template <class P> inline Inertia_<P> Inertia_<P>::
cylinderAlongX(const P & r,const P & hx)942 cylinderAlongX(const P& r, const P& hx)
943 {   return UnitInertia_<P>::cylinderAlongX(r,hx); }
944 template <class P> inline Inertia_<P> Inertia_<P>::
brick(const P & hx,const P & hy,const P & hz)945 brick(const P& hx, const P& hy, const P& hz)
946 {   return UnitInertia_<P>::brick(hx,hy,hz); }
947 template <class P> inline Inertia_<P> Inertia_<P>::
brick(const Vec<3,P> & halfLengths)948 brick(const Vec<3,P>& halfLengths)
949 {   return UnitInertia_<P>::brick(halfLengths); }
950 template <class P> inline Inertia_<P> Inertia_<P>::
ellipsoid(const P & hx,const P & hy,const P & hz)951 ellipsoid(const P& hx, const P& hy, const P& hz)
952 {   return UnitInertia_<P>::ellipsoid(hx,hy,hz); }
953 template <class P> inline Inertia_<P> Inertia_<P>::
ellipsoid(const Vec<3,P> & halfLengths)954 ellipsoid(const Vec<3,P>& halfLengths)
955 {   return UnitInertia_<P>::ellipsoid(halfLengths); }
956 
957 
958 // -----------------------------------------------------------------------------
959 //                           SPATIAL INERTIA MATRIX
960 // -----------------------------------------------------------------------------
961 /** A spatial inertia contains the mass, center of mass point, and inertia
962 matrix for a rigid body. This is 10 independent quantities altogether; however,
963 inertia is mass-scaled making it linearly dependent on the mass. Here instead
964 we represent inertia using a unit inertia matrix, which is equivalent to the
965 inertia this body would have if it had unit mass. Then the actual inertia is
966 given by mass*unitInertia. In this manner the mass, center of mass location, and
967 inertia are completely independent so can be changed separately. That means
968 if you double the mass, you'll also double the inertia as you would expect.
969 
970 Spatial inertia may be usefully viewed as a symmetric spatial matrix, that is,
971 a 6x6 symmetric matrix arranged as 2x2 blocks of 3x3 matrices. Although this
972 class represents the spatial inertia in compact form, it supports methods and
973 operators that allow it to behave as though it were a spatial matrix (except
974 much faster to work with). In spatial matrix form, the matrix has the following
975 interpretation:
976 <pre>
977               [  m*G   m*px ]
978           M = [             ]
979               [ -m*px  m*I  ]
980 </pre>
981 Here m is mass, p is the vector from the body origin to the center of mass,
982 G is the 3x3 symmetric unit inertia (gyration) matrix, and I is a 3x3 identity
983 matrix. "px" indicates the skew symmetric cross product matrix formed from the
984 vector p, so -px=~px.
985 
986 <h3>Abbreviations</h3>
987 Typedefs exist for the most common invocations of SpatialInertia_\<P\>:
988  - \ref SimTK::SpatialInertia "SpatialInertia" for default Real precision (this is
989    almost always used)
990  - \ref SimTK::fSpatialInertia "fSpatialInertia" for single (float) precision
991  - \ref SimTK::dSpatialInertia "dSpatialInertia" for double precision **/
992 template <class P>
993 class SimTK_SimTKCOMMON_EXPORT SpatialInertia_ {
994     typedef P               RealP;
995     typedef Vec<3,P>        Vec3P;
996     typedef UnitInertia_<P> UnitInertiaP;
997     typedef Mat<3,3,P>      Mat33P;
998     typedef Vec<2, Vec3P>   SpatialVecP;
999     typedef Mat<2,2,Mat33P> SpatialMatP;
1000     typedef Rotation_<P>    RotationP;
1001     typedef Transform_<P>   TransformP;
1002     typedef Inertia_<P>     InertiaP;
1003 public:
1004 /// The default constructor fills everything with NaN, even in Release mode.
SpatialInertia_()1005 SpatialInertia_()
1006 :   m(nanP()), p(nanP()) {} // inertia is already NaN
SpatialInertia_(RealP mass,const Vec3P & com,const UnitInertiaP & gyration)1007 SpatialInertia_(RealP mass, const Vec3P& com, const UnitInertiaP& gyration)
1008 :   m(mass), p(com), G(gyration) {}
1009 
1010 // default copy constructor, copy assignment, destructor
1011 
setMass(RealP mass)1012 SpatialInertia_& setMass(RealP mass)
1013 {   SimTK_ERRCHK1(mass >= 0, "SpatialInertia::setMass()",
1014         "Negative mass %g is illegal.", (double)mass);
1015     m=mass; return *this; }
setMassCenter(const Vec3P & com)1016 SpatialInertia_& setMassCenter(const Vec3P& com)
1017 {   p=com; return *this;}
setUnitInertia(const UnitInertiaP & gyration)1018 SpatialInertia_& setUnitInertia(const UnitInertiaP& gyration)
1019 {   G=gyration; return *this; }
1020 
getMass()1021 RealP               getMass()        const {return m;}
getMassCenter()1022 const Vec3P&        getMassCenter()  const {return p;}
getUnitInertia()1023 const UnitInertiaP& getUnitInertia() const {return G;}
1024 
1025 /// Calculate the first mass moment (mass-weighted COM location)
1026 /// from the mass and COM vector. Cost is 3 inline flops.
calcMassMoment()1027 Vec3P calcMassMoment() const {return m*p;}
1028 
1029 /// Calculate the inertia matrix (second mass moment, mass-weighted gyration
1030 /// matrix) from the mass and unit inertia matrix. Cost is 6 inline flops.
calcInertia()1031 InertiaP calcInertia() const {return m*G;}
1032 
1033 /// Add in a compatible SpatialInertia. This is only valid if both
1034 /// SpatialInertias are expressed in the same frame and measured about
1035 /// the same point but there is no way for this method to check.
1036 /// Cost is about 40 flops.
1037 SpatialInertia_& operator+=(const SpatialInertia_& src) {
1038     SimTK_ERRCHK(m+src.m != 0, "SpatialInertia::operator+=()",
1039         "The combined mass cannot be zero.");
1040     const RealP mtot = m+src.m, oomtot = 1/mtot;                    // ~11 flops
1041     p = oomtot*(calcMassMoment() + src.calcMassMoment());           // 10 flops
1042     G.setFromUnitInertia(oomtot*(calcInertia()+src.calcInertia())); // 19 flops
1043     m = mtot; // must do this last
1044     return *this;
1045 }
1046 
1047 /// Subtract off a compatible SpatialInertia. This is only valid if both
1048 /// SpatialInertias are expressed in the same frame and measured about
1049 /// the same point but there is no way for this method to check.
1050 /// Cost is about 40 flops.
1051 SpatialInertia_& operator-=(const SpatialInertia_& src) {
1052     SimTK_ERRCHK(m != src.m, "SpatialInertia::operator-=()",
1053         "The combined mass cannot be zero.");
1054     const RealP mtot = m-src.m, oomtot = 1/mtot;                    // ~11 flops
1055     p = oomtot*(calcMassMoment() - src.calcMassMoment());           // 10 flops
1056     G.setFromUnitInertia(oomtot*(calcInertia()-src.calcInertia())); // 19 flops
1057     m = mtot; // must do this last
1058     return *this;
1059 }
1060 
1061 /// Multiply a SpatialInertia by a scalar. Because we keep the mass
1062 /// factored out, this requires only a single multiply.
1063 SpatialInertia_& operator*=(const RealP& s) {m *= s; return *this;}
1064 
1065 /// Divide a SpatialInertia by a scalar. Because we keep the mass
1066 /// factored out, this requires only a single divide.
1067 SpatialInertia_& operator/=(const RealP& s) {m /= s; return *this;}
1068 
1069 /// Multiply a SpatialInertia by a SpatialVec to produce a SpatialVec
1070 /// result; 45 flops.
1071 SpatialVecP operator*(const SpatialVecP& v) const
1072 {   return m*SpatialVecP(G*v[0]+p%v[1], v[1]-p%v[0]); }
1073 
1074 /// Return a new SpatialInertia object which is the same as this one except
1075 /// re-expressed in another coordinate frame. We consider this object to
1076 /// be expressed in some frame F and we're given a rotation matrix R_FB we
1077 /// can use to re-express in a new frame B. Cost is 72 flops.
1078 /// @see reexpressInPlace()
reexpress(const Rotation_<P> & R_FB)1079 SpatialInertia_ reexpress(const Rotation_<P>& R_FB) const
1080 {   return SpatialInertia_(*this).reexpressInPlace(R_FB); }
1081 
1082 /// Rexpress using an inverse rotation to avoid having to convert it.
1083 /// @see rexpress(Rotation) for information
reexpress(const InverseRotation_<P> & R_FB)1084 SpatialInertia_ reexpress(const InverseRotation_<P>& R_FB) const
1085 {   return SpatialInertia_(*this).reexpressInPlace(R_FB); }
1086 
1087 /// Re-express this SpatialInertia in another frame, modifying the original
1088 /// object. We return a reference to the object so that you can chain this
1089 /// operation in the manner of assignment operators. Cost is 72 flops.
1090 /// @see reexpress() if you want to leave this object unmolested.
reexpressInPlace(const Rotation_<P> & R_FB)1091 SpatialInertia_& reexpressInPlace(const Rotation_<P>& R_FB)
1092 {   p = (~R_FB)*p; G.reexpressInPlace(R_FB); return *this; }
1093 
1094 /// Rexpress using an inverse rotation to avoid having to convert it.
1095 /// @see rexpressInPlace(Rotation) for information
reexpressInPlace(const InverseRotation_<P> & R_FB)1096 SpatialInertia_& reexpressInPlace(const InverseRotation_<P>& R_FB)
1097 {   p = (~R_FB)*p; G.reexpressInPlace(R_FB); return *this; }
1098 
1099 /// Return a new SpatialInertia object which is the same as this one except
1100 /// the origin ("taken about" point) has changed from OF to OF+S.
1101 /// Cost is 37 flops.
1102 /// @see shiftInPlace()
shift(const Vec3P & S)1103 SpatialInertia_ shift(const Vec3P& S) const
1104 {   return SpatialInertia_(*this).shiftInPlace(S); }
1105 
1106 /// Change origin from OF to OF+S, modifying the original object in place.
1107 /// Returns a reference to the modified object so that you can chain this
1108 /// operation in the manner of assignment operators. Cost is 37 flops.
1109 /// @see shift() if you want to leave this object unmolested.
shiftInPlace(const Vec3P & S)1110 SpatialInertia_& shiftInPlace(const Vec3P& S) {
1111     G.shiftToCentroidInPlace(p);      // change to central inertia
1112     const Vec3P pNew(p-S);            // vec from new origin to com (3 flops)
1113     G.shiftFromCentroidInPlace(pNew); // shift to S (pNew sign doesn't matter)
1114     p = pNew;                         // had p=com-OF; want p=com-(OF+S)=p-S
1115     return *this;
1116 }
1117 
1118 /// Return a new SpatialInertia object which is the same as this
1119 /// one but measured about and expressed in a new frame. We consider
1120 /// the current spatial inertia M to be measured (implicitly) in some
1121 /// frame F, that is, we have M=M_OF_F. We want M_OB_B for some new
1122 /// frame B, given the transform X_FB giving the location and orientation
1123 /// of B in F. This combines the reexpress() and shift() operations
1124 /// available separately. Cost is 109 flops.
1125 /// @see transformInPlace()
transform(const Transform_<P> & X_FB)1126 SpatialInertia_ transform(const Transform_<P>& X_FB) const
1127 {   return SpatialInertia_(*this).transformInPlace(X_FB); }
1128 
1129 /// Transform using an inverse transform to avoid having to convert it.
1130 /// @see transform(Transform) for information
transform(const InverseTransform_<P> & X_FB)1131 SpatialInertia_ transform(const InverseTransform_<P>& X_FB) const
1132 {   return SpatialInertia_(*this).transformInPlace(X_FB); }
1133 
1134 /// Transform this SpatialInertia object so that it is measured about and
1135 /// expressed in a new frame, modifying the object in place. We consider the
1136 /// current spatial inertia M to be measured (implicitly) in some frame F, that
1137 /// is, we have M=M_OF_F. We want to change it to M_OB_B for some new frame B,
1138 /// given the transform X_FB giving the location and orientation of B in F. This
1139 /// combines the reexpressInPlace() and shiftInPlace() operations available
1140 /// separately. Returns a reference to the modified object so that you can
1141 /// chain this operation in the manner of assignment operators. Cost is 109 flops.
1142 /// @see transform() if you want to leave this object unmolested.
transformInPlace(const Transform_<P> & X_FB)1143 SpatialInertia_& transformInPlace(const Transform_<P>& X_FB) {
1144     shiftInPlace(X_FB.p());     // shift to the new origin OB.
1145     reexpressInPlace(X_FB.R()); // get everything in B
1146     return *this;
1147 }
1148 
1149 /// Transform using an inverse transform to avoid having to convert it.
1150 /// @see transformInPlace(Transform) for information
transformInPlace(const InverseTransform_<P> & X_FB)1151 SpatialInertia_& transformInPlace(const InverseTransform_<P>& X_FB) {
1152     shiftInPlace(X_FB.p());     // shift to the new origin OB.
1153     reexpressInPlace(X_FB.R()); // get everything in B
1154     return *this;
1155 }
1156 
toSpatialMat()1157 const SpatialMatP toSpatialMat() const {
1158     Mat33P offDiag = crossMat(m*p);
1159     return SpatialMatP(m*G.toMat33(), offDiag,
1160                        -offDiag,      Mat33P(m));
1161 }
1162 
1163 private:
1164 RealP           m;  // mass of this rigid body F
1165 Vec3P           p;  // location of body's COM from OF, expressed in F
1166 UnitInertiaP    G;  // mass distribution; inertia is mass*gyration
1167 
nanP()1168 static P nanP() {return NTraits<P>::getNaN();}
1169 };
1170 
1171 /// Add two compatible spatial inertias. Cost is about 40 flops.
1172 /// @relates SpatialInertia_
1173 template <class P> inline SpatialInertia_<P>
1174 operator+(const SpatialInertia_<P>& l, const SpatialInertia_<P>& r)
1175 {   return SpatialInertia_<P>(l) += r; }
1176 
1177 /// Subtract one compatible spatial inertia from another. Cost is
1178 /// about 40 flops.
1179 /// @relates SpatialInertia_
1180 template <class P> inline SpatialInertia_<P>
1181 operator-(const SpatialInertia_<P>& l, const SpatialInertia_<P>& r)
1182 {   return SpatialInertia_<P>(l) -= r; }
1183 
1184 
1185 // -----------------------------------------------------------------------------
1186 //                        ARTICULATED BODY INERTIA MATRIX
1187 // -----------------------------------------------------------------------------
1188 /** An articulated body inertia (ABI) matrix P(q) contains the spatial inertia
1189 properties that a body appears to have when it is the free base body of
1190 an articulated multibody tree in a given configuration q. Despite the
1191 complex relative motion that occurs within a multibody tree, at any given
1192 configuration q there is still a linear relationship between a spatial
1193 force F applied to a point of the base body and the resulting acceleration
1194 A of that body and that point: F = P(q)*A + c, where c is a velocity-
1195 dependent inertial bias force. P is thus analogous to a rigid body
1196 spatial inertia (RBI), but for a body which has other bodies connected to it
1197 by joints which are free to move.
1198 
1199 An ABI P is a symmetric 6x6 spatial matrix, consisting of 2x2 blocks of 3x3
1200 matrices, similar to the RBI. However, unlike the RBI which has only 10 independent
1201 elements, all 21 elements of P's lower triangle are significant. For example,
1202 the apparent mass of an articulated body depends on which way you push it,
1203 and in general there is no well-defined center of mass. This
1204 is a much more expensive matrix to manipulate than an RBI. In Simbody's formulation,
1205 we only work with ABIs in the Ground frame, so there
1206 is never a need to rotate or re-express them. (That is done by rotating RBIs
1207 prior to using them to construct the ABIs.) Thus only shifting operations need
1208 be performed when transforming ABIs from body to body.
1209 Cheap rigid body shifting is done when moving an ABI
1210 within a body or across a prescribed mobilizer; otherwise we have to perform
1211 an articulated shift operation which is quite expensive.
1212 
1213 For a full discussion of the properties of articulated body inertias, see
1214 Section 7.1 (pp. 119-123) of Roy Featherstone's excellent 2008 book, Rigid
1215 Body Dynamics Algorithms.
1216 
1217 In spatial matrix form, an ABI P may be considered to consist of the
1218 following 3x3 subblocks:
1219 <pre>
1220          P =  [ J  F ]
1221               [~F  M ]
1222 </pre>
1223 Here M is a (symmetric) mass distribution, F is a full matrix giving the
1224 first mass moment distribution, and J is a (symmetric) inertia matrix.
1225 
1226 <h3>Abbreviations</h3>
1227 Typedefs exist for the most common invocations of ArticulatedInertia_\<P\>:
1228  - \ref SimTK::ArticulatedInertia "ArticulatedInertia" for default Real
1229    precision (this is almost always used)
1230  - \ref SimTK::fArticulatedInertia "fArticulatedInertia" for single (float)
1231    precision
1232  - \ref SimTK::dArticulatedInertia "dArticulatedInertia" for double
1233    precision **/
1234 template <class P>
1235 class ArticulatedInertia_ {
1236     typedef P               RealP;
1237     typedef Vec<3,P>        Vec3P;
1238     typedef UnitInertia_<P> UnitInertiaP;
1239     typedef Mat<3,3,P>      Mat33P;
1240     typedef SymMat<3,P>     SymMat33P;
1241     typedef Vec<2, Vec3P>   SpatialVecP;
1242     typedef Mat<2,2,Mat33P> SpatialMatP;
1243     typedef Rotation_<P>    RotationP;
1244     typedef Transform_<P>   TransformP;
1245     typedef Inertia_<P>     InertiaP;
1246 public:
1247 /// Default construction produces uninitialized junk at zero cost; be sure to
1248 /// fill this in before referencing it.
ArticulatedInertia_()1249 ArticulatedInertia_() {}
1250 /// Construct an ArticulatedInertia from the mass, first moment, and inertia matrices it contains.
ArticulatedInertia_(const SymMat33P & mass,const Mat33P & massMoment,const SymMat33P & inertia)1251 ArticulatedInertia_(const SymMat33P& mass, const Mat33P& massMoment, const SymMat33P& inertia)
1252 :   M(mass), J(inertia), F(massMoment) {}
1253 
1254 /// Construct an articulated body inertia (ABI) from a rigid body spatial inertia (RBI).
1255 /// Every RBI is also the ABI for that (unarticulated) rigid body. 12 flops.
ArticulatedInertia_(const SpatialInertia_<P> & rbi)1256 explicit ArticulatedInertia_(const SpatialInertia_<P>& rbi)
1257 :   M(rbi.getMass()), J(rbi.calcInertia()), F(crossMat(rbi.calcMassMoment())) {}
1258 
1259 /// Set the mass distribution matrix M in this ArticulatedInertia (symmetric).
setMass(const SymMat33P & mass)1260 ArticulatedInertia_& setMass      (const SymMat33P& mass)       {M=mass;       return *this;}
1261 /// Set the mass first moment distribution matrix F in this ArticulatedInertia (full).
setMassMoment(const Mat33P & massMoment)1262 ArticulatedInertia_& setMassMoment(const Mat33P&    massMoment) {F=massMoment; return *this;}
1263 /// Set the mass second moment (inertia) matrix J in this ArticulatedInertia (symmetric).
setInertia(const SymMat33P & inertia)1264 ArticulatedInertia_& setInertia   (const SymMat33P& inertia)    {J=inertia;    return *this;}
1265 
1266 /// Get the mass distribution matrix M from this ArticulatedInertia (symmetric).
getMass()1267 const SymMat33P& getMass()       const {return M;}
1268 /// Get the mass first moment distribution matrix F from this ArticulatedInertia (full).
getMassMoment()1269 const Mat33P&    getMassMoment() const {return F;}
1270 /// Get the mass second moment (inertia) matrix J from this ArticulatedInertia (symmetric).
getInertia()1271 const SymMat33P& getInertia()    const {return J;}
1272 
1273 // default destructor, copy constructor, copy assignment
1274 
1275 /// Add in a compatible ArticulatedInertia to this one. Both inertias must be expressed
1276 /// in the same frame and measured about the same point. 21 flops.
1277 ArticulatedInertia_& operator+=(const ArticulatedInertia_& src)
1278 {   M+=src.M; J+=src.J; F+=src.F; return *this; }
1279 /// Subtract a compatible ArticulatedInertia from this one. Both inertias must be expressed
1280 /// in the same frame and measured about the same point. 21 flops.
1281 ArticulatedInertia_& operator-=(const ArticulatedInertia_& src)
1282 {   M-=src.M; J-=src.J; F-=src.F; return *this; }
1283 
1284 /// Multiply an ArticulatedIneria by a SpatialVec (66 flops).
1285 SpatialVecP operator*(const SpatialVecP& v) const
1286 {   return SpatialVecP(J*v[0]+F*v[1], ~F*v[0]+M*v[1]); }
1287 
1288 /// Multiply an ArticulatedInertia by a row of SpatialVecs (66*N flops).
1289 template <int N>
1290 Mat<2,N,Vec3P> operator*(const Mat<2,N,Vec3P>& m) const {
1291     Mat<2,N,Vec3P> res;
1292     for (int j=0; j < N; ++j)
1293         res.col(j) = (*this) * m.col(j); // above this*SpatialVec operator
1294     return res;
1295 }
1296 
1297 /// Rigid-shift the origin of this Articulated Body Inertia P by a
1298 /// shift vector -s to produce a new ABI P'. The calculation is
1299 /// <pre>
1300 /// P' =  [ J'  F' ]  =  [ 1  sx ] [ J  F ] [ 1  0 ]
1301 ///       [~F'  M  ]     [ 0  1  ] [~F  M ] [-sx 1 ]
1302 /// </pre>
1303 /// where sx is the cross product matrix of s (not -s). Note that for historical
1304 /// reasons this is defined so that it shifts by -s, which is unfortunate and
1305 /// is opposite the SpatialInertia::shift() method.
1306 /// Cost is 72 flops.
1307 SimTK_SimTKCOMMON_EXPORT ArticulatedInertia_ shift(const Vec3P& s) const;
1308 
1309 /// Rigid-shift this ABI in place by -s. 72 flops.
1310 /// @see shift() for details
1311 SimTK_SimTKCOMMON_EXPORT ArticulatedInertia_& shiftInPlace(const Vec3P& s);
1312 
1313 /// Convert the compactly-stored ArticulatedInertia (21 elements) into a
1314 /// full SpatialMat with 36 elements.
toSpatialMat()1315 const SpatialMatP toSpatialMat() const {
1316     // This more-beautiful code ran into an optimization bug Microsoft
1317     // introduced in Update 2 to Visual C++ 2015.
1318     //return SpatialMatP( Mat33P(J),     F,
1319     //                        ~F,       Mat33P(M) );
1320 
1321     // Uglier but functional.
1322     SpatialMatP smat;
1323     smat(0,0) = Mat33P(J); smat(0,1) = F;
1324     smat(1,0) = ~F;        smat(1,1) = Mat33P(M);
1325     return smat;
1326 }
1327 private:
1328 SymMat33P M;
1329 SymMat33P J;
1330 Mat33P    F;
1331 };
1332 
1333 /// Add two compatible articulated inertias. Cost is 21 flops.
1334 /// @relates ArticulatedInertia_
1335 template <class P> inline ArticulatedInertia_<P>
1336 operator+(const ArticulatedInertia_<P>& l, const ArticulatedInertia_<P>& r)
1337 {   return ArticulatedInertia_<P>(l) += r; }
1338 
1339 /// Subtract one compatible articulated inertia from another. Cost is
1340 /// 21 flops.
1341 /// @relates ArticulatedInertia_
1342 template <class P> inline ArticulatedInertia_<P>
1343 operator-(const ArticulatedInertia_<P>& l, const ArticulatedInertia_<P>& r)
1344 {   return ArticulatedInertia_<P>(l) -= r; }
1345 
1346 
1347 // -----------------------------------------------------------------------------
1348 //                              MASS PROPERTIES
1349 // -----------------------------------------------------------------------------
1350 /** This class contains the mass, center of mass, and unit inertia matrix of a
1351 rigid body B. The center of mass is a vector from B's origin, expressed in the
1352 B frame. The inertia is taken about the B origin, and expressed in B. The frame
1353 B is implicit; only the measurements are stored here. The unit inertia can be
1354 multiplied by the mass to get the inertia matrix for the body.
1355 
1356 <h3>Abbreviations</h3>
1357 Typedefs exist for the most common invocations of MassProperties_\<P\>:
1358  - \ref SimTK::MassProperties "MassProperties" for default Real precision (this is
1359    almost always used)
1360  - \ref SimTK::fMassProperties "fMassProperties" for single (float) precision
1361  - \ref SimTK::dMassProperties "dMassProperties" for double precision **/
1362 template <class P>
1363 class SimTK_SimTKCOMMON_EXPORT MassProperties_ {
1364 public:
1365 /** Create a mass properties object in which the mass, mass center, and
1366 inertia are meaningless; you must assign values before using this. **/
MassProperties_()1367 MassProperties_() { setMassProperties(0,Vec<3,P>(0),UnitInertia_<P>()); }
1368 /** Create a mass properties object from individually supplied mass,
1369 mass center, and inertia matrix.  The inertia matrix is divided by the
1370 mass to produce the unit inertia. **/
MassProperties_(const P & m,const Vec<3,P> & com,const Inertia_<P> & inertia)1371 MassProperties_(const P& m, const Vec<3,P>& com, const Inertia_<P>& inertia)
1372     { setMassProperties(m,com,inertia); }
1373 /** Create a mass properties object from individually supplied mass,
1374 mass center, and unit inertia (gyration) matrix. **/
MassProperties_(const P & m,const Vec<3,P> & com,const UnitInertia_<P> & gyration)1375 MassProperties_(const P& m, const Vec<3,P>& com, const UnitInertia_<P>& gyration)
1376     { setMassProperties(m,com,gyration); }
1377 
1378 /** Set mass, center of mass, and inertia. The inertia is divided by the mass to
1379 produce the unit inertia. Behaves like an assignment in that
1380 a reference to the modified MassProperties object is returned. **/
setMassProperties(const P & m,const Vec<3,P> & com,const Inertia_<P> & inertia)1381 MassProperties_& setMassProperties(const P& m, const Vec<3,P>& com, const Inertia_<P>& inertia) {
1382     mass = m;
1383     comInB = com;
1384     if (m == 0) {
1385         SimTK_ASSERT(inertia.asSymMat33().getAsVec() == 0, "Mass is zero but inertia is nonzero");
1386         unitInertia_OB_B = UnitInertia_<P>(0);
1387     }
1388     else {
1389         unitInertia_OB_B = UnitInertia_<P>(inertia*(1/m));
1390     }
1391     return *this;
1392 }
1393 
1394 /** Set mass, center of mass, and unit inertia. Behaves like an assignment in
1395 that a reference to the modified MassProperties object is returned. **/
setMassProperties(const P & m,const Vec<3,P> & com,const UnitInertia_<P> & gyration)1396 MassProperties_& setMassProperties
1397    (const P& m, const Vec<3,P>& com, const UnitInertia_<P>& gyration)
1398 {   mass=m; comInB=com; unitInertia_OB_B=gyration; return *this; }
1399 
1400 /** Return the mass currently stored in this MassProperties object. **/
getMass()1401 const P& getMass() const {return mass;}
1402 /** Return the mass center currently stored in this MassProperties object;
1403 this is expressed in an implicit frame we call "B", and measured from B's
1404 origin, but you have to know what that frame is in order to interpret the
1405 returned vector. **/
getMassCenter()1406 const Vec<3,P>& getMassCenter() const {return comInB;}
1407 /** Return the unit inertia currently stored in this MassProperties object;
1408 this is expressed in an implicit frame we call "B", and measured about B's
1409 origin, but you have to know what that frame is in order to interpret the
1410 returned value correctly. **/
getUnitInertia()1411 const UnitInertia_<P>& getUnitInertia() const {return unitInertia_OB_B;}
1412 /** Return the inertia matrix for this MassProperties object; this is equal
1413 to the unit inertia times the mass and costs 6 flops. It is expressed in
1414 an implicit frame we call "B", and measured about B's origin, but you have
1415 to know what that frame is in order to interpret the returned value
1416 correctly. **/
calcInertia()1417 const Inertia_<P> calcInertia() const {return mass*unitInertia_OB_B;}
1418 /** OBSOLETE -- this is just here for compatibility with 2.2; since the
1419 UnitInertia is stored now the full inertia must be calculated at a cost of
1420 6 flops, hence the method name should be calcInertia() and that's what
1421 you should use unless you want getUnitInertia(). **/
getInertia()1422 const Inertia_<P> getInertia() const {return calcInertia();}
1423 
1424 /** Return the inertia of this MassProperties object, but measured about the
1425 mass center rather than about the (implicit) B frame origin. The result is
1426 still expressed in B. **/
calcCentralInertia()1427 Inertia_<P> calcCentralInertia() const {
1428     return mass*unitInertia_OB_B - Inertia_<P>(comInB, mass);
1429 }
1430 /** Return the inertia of this MassProperties object, but with the "measured
1431 about" point shifted from the (implicit) B frame origin to a new point that
1432 is supplied in \a newOriginB which must be a vector measured from the B frame
1433 origin and expressed in B. The result is still expressed in B. **/
calcShiftedInertia(const Vec<3,P> & newOriginB)1434 Inertia_<P> calcShiftedInertia(const Vec<3,P>& newOriginB) const {
1435     return calcCentralInertia() + Inertia_<P>(newOriginB-comInB, mass);
1436 }
1437 /** Return the inertia of this MassProperties object, but transformed to
1438 from the implicit B frame to a new frame C whose pose relative to B is
1439 supplied. Note that this affects both the "measured about" point and the
1440 "expressed in" frame. **/
calcTransformedInertia(const Transform_<P> & X_BC)1441 Inertia_<P> calcTransformedInertia(const Transform_<P>& X_BC) const {
1442     return calcShiftedInertia(X_BC.p()).reexpress(X_BC.R());
1443 }
1444 /** Return a new MassProperties object that is the same as this one but with
1445 the origin point shifted from the (implicit) B frame origin to a new point that
1446 is supplied in \a newOriginB which must be a vector measured from the B frame
1447 origin and expressed in B. This affects both the mass center vector and the
1448 inertia. The result is still expressed in B. **/
calcShiftedMassProps(const Vec<3,P> & newOriginB)1449 MassProperties_ calcShiftedMassProps(const Vec<3,P>& newOriginB) const {
1450     return MassProperties_(mass, comInB-newOriginB,
1451                             calcShiftedInertia(newOriginB));
1452 }
1453 
1454 /** %Transform these mass properties from the current frame "B" to a new
1455 frame "C", given the pose of C in B.\ Caution: this \e shifts
1456 the point from which the mass properties are measured from the origin of B to
1457 the origin of C. See reexpress() to change only the measure numbers without
1458 moving the "measured from" point. Note that the frame in which a MassProperties
1459 object is expressed, and the point about which the mass properties are
1460 measured, are implicit; we don't actually have any way to verify that
1461 it is in B. Make sure you are certain about the current frame before using this
1462 method. **/
calcTransformedMassProps(const Transform_<P> & X_BC)1463 MassProperties_ calcTransformedMassProps(const Transform_<P>& X_BC) const {
1464     return MassProperties_(mass, ~X_BC*comInB, calcTransformedInertia(X_BC));
1465 }
1466 
1467 /** Re-express these mass properties from the current frame "B" to a new
1468 frame "C", given the orientation of C in B.\ Caution: this does not \e shift
1469 the point from which the mass properties are measured, it just uses a different
1470 frame to express that measurement. See calcTransformedMassProps() to perform
1471 a shift as well. Note that the frame in which a MassProperties object is
1472 expressed is implicit; we don't actually have any way to verify that it is in
1473 B. Make sure you are certain about the current frame before using this
1474 method. **/
reexpress(const Rotation_<P> & R_BC)1475 MassProperties_ reexpress(const Rotation_<P>& R_BC) const {
1476     return MassProperties_(mass, ~R_BC*comInB, unitInertia_OB_B.reexpress(R_BC));
1477 }
1478 
1479 /** Return true only if the mass stored here is \e exactly zero.\ If the mass
1480 resulted from a computation, you should use isNearlyMassless() instead.
1481 @see isNearlyMassless(), isExactlyCentral() **/
isExactlyMassless()1482 bool isExactlyMassless()   const { return mass==0; }
1483 /** Return true if the mass stored here is zero to within a small tolerance.
1484 By default we use SignificantReal (about 1e-14 in double precision) as the
1485 tolerance but you can override that. If you are just checking to see whether
1486 the mass was explicitly set to zero (rather than calculated) you can use
1487 isExactlyMassless() instead. @see isExactlyMassless(), isNearlyCentral() **/
1488 bool isNearlyMassless(const P& tol=SignificantReal) const {
1489     return mass <= tol;
1490 }
1491 
1492 /** Return true only if the mass center stored here is \e exactly zero.\ If
1493 the mass center resulted from a computation, you should use isNearlyCentral()
1494 instead. @see isNearlyCentral(), isExactlyMassless() **/
isExactlyCentral()1495 bool isExactlyCentral() const { return comInB==Vec<3,P>(0); }
1496 /** Return true if the mass center stored here is zero to within a small tolerance.
1497 By default we use SignificantReal (about 1e-14 in double precision) as the
1498 tolerance but you can override that. If you are just checking to see whether
1499 the mass center was explicitly set to zero (rather than calculated) you can use
1500 isExactlyCentral() instead. @see isExactlyCentral(), isNearlyMassless() **/
1501 bool isNearlyCentral(const P& tol=SignificantReal) const {
1502     return comInB.normSqr() <= tol*tol;
1503 }
1504 
1505 /** Return true if any element of this MassProperties object is NaN.
1506 @see isInf(), isFinite() **/
isNaN()1507 bool isNaN() const {return SimTK::isNaN(mass) || comInB.isNaN() || unitInertia_OB_B.isNaN();}
1508 /** Return true only if there are no NaN's in this MassProperties object, and
1509 at least one of the elements is Infinity.\ Ground's mass properties satisfy
1510 these conditions.
1511 @see isNan(), isFinite() **/
isInf()1512 bool isInf() const {
1513     if (isNaN()) return false;
1514     return SimTK::isInf(mass) || comInB.isInf() || unitInertia_OB_B.isInf();
1515 }
1516 /** Return true if none of the elements of this MassProperties object are
1517 NaN or Infinity.\ Note that Ground's mass properties are not finite.
1518 @see isNaN(), isInf() **/
isFinite()1519 bool isFinite() const {
1520     return SimTK::isFinite(mass) && comInB.isFinite() && unitInertia_OB_B.isFinite();
1521 }
1522 
1523 /** Convert this MassProperties object to a spatial inertia matrix and return
1524 it as a SpatialMat, which is a 2x2 matrix of 3x3 submatrices.
1525 @see toMat66() **/
toSpatialMat()1526 Mat<2,2,Mat<3,3,P>> toSpatialMat() const {
1527     Mat<2,2,Mat<3,3,P>> M;
1528     M(0,0) = mass*unitInertia_OB_B.toMat33();
1529     M(0,1) = mass*crossMat(comInB);
1530     M(1,0) = ~M(0,1);
1531     M(1,1) = mass; // a diagonal matrix
1532     return M;
1533 }
1534 
1535 /** Convert this MassProperties object to a spatial inertia matrix in the
1536 form of an ordinary 6x6 matrix, \e not a SpatialMat. Logically these are
1537 the same but the ordering of the elements in memory is different between
1538 a Mat66 and SpatialMat.
1539 @see toSpatialMat() **/
toMat66()1540 Mat<6,6,P> toMat66() const {
1541     Mat<6,6,P> M;
1542     M.template updSubMat<3,3>(0,0) = mass*unitInertia_OB_B.toMat33();
1543     M.template updSubMat<3,3>(0,3) = mass*crossMat(comInB);
1544     M.template updSubMat<3,3>(3,0) = ~M.template getSubMat<3,3>(0,3);
1545     M.template updSubMat<3,3>(3,3) = mass; // a diagonal matrix
1546     return M;
1547 }
1548 
1549 private:
1550 P               mass;
1551 Vec<3,P>        comInB;         // meas. from B origin, expr. in B
1552 UnitInertia_<P> unitInertia_OB_B;   // about B origin, expr. in B
1553 };
1554 
1555 /** Output a human-readable representation of a MassProperties object to
1556 the given output stream. This is assumed to be the mass properties of some
1557 body B. We'll show B's mass, center of mass as a vector from B's origin,
1558 expressed in B, and \e unit inertia, abbreviated Uxx, Uyy, and so on so that
1559 you won't accidentally think these are mass-scaled inertias normally designated
1560 Ixx, Iyy, etc. Note that I=mass*U in these terms. Also note that the unit
1561 inertia is taken about the body frame origin, \e not about the center of
1562 mass. And of course the unit inertia is expressed in B.
1563 @relates MassProperties_ **/
1564 template <class P> static inline std::ostream&
1565 operator<<(std::ostream& o, const MassProperties_<P>& mp) {
1566     return o << "{ mass=" << mp.getMass()
1567              << "\n  com=" << mp.getMassCenter()
1568              << "\n  Uxx,yy,zz=" << mp.getUnitInertia().getMoments()
1569              << "\n  Uxy,xz,yz=" << mp.getUnitInertia().getProducts()
1570              << "\n}\n";
1571 }
1572 
1573 } // namespace SimTK
1574 
1575 #endif // SimTK_SIMMATRIX_MASS_PROPERTIES_H_
1576