1 #ifndef SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_ELLIPSOID_H_
2 #define SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_ELLIPSOID_H_
3 
4 /* -------------------------------------------------------------------------- *
5  *                               Simbody(tm)                                  *
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-12 Stanford University and the Authors.        *
13  * Authors: Michael Sherman                                                   *
14  * Contributors:                                                              *
15  *    Charles Schwieters (NIH): wrote the public domain IVM code from which   *
16  *                              this was derived.                             *
17  *    Ajay Seth: wrote the Ellipsoid joint as a Custom mobilizer; Sherm       *
18  *               derived this implementation from Ajay's                      *
19  *                                                                            *
20  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
21  * not use this file except in compliance with the License. You may obtain a  *
22  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
23  *                                                                            *
24  * Unless required by applicable law or agreed to in writing, software        *
25  * distributed under the License is distributed on an "AS IS" BASIS,          *
26  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
27  * See the License for the specific language governing permissions and        *
28  * limitations under the License.                                             *
29  * -------------------------------------------------------------------------- */
30 
31 /**@file
32  * Define the RigidBodyNode that implements an Ellipsoid mobilizer.
33  */
34 
35 #include "SimbodyMatterSubsystemRep.h"
36 #include "RigidBodyNode.h"
37 #include "RigidBodyNodeSpec.h"
38 
39 
40     // ELLIPSOID //
41 
42 // ELLIPSOID mobilizer. This provides three degrees of rotational freedom, i.e.,
43 // unrestricted orientation, of the body's M frame in the parent's F frame,
44 // along with coordinated translation that keeps the M frame origin on
45 // the surface of an ellipsoid fixed in F and centered on the F origin.
46 // The surface point is chosen for a given orientation of M in F as the
47 // unique point on the ellipsoid surface where the surface normal is aligned
48 // with Mz. That is, the z axis of M is assumed to be normal to the ellipsoid
49 // at all times, and the translation is chosen to make that true.
50 //
51 // Unlike most joints, the reference configuration (i.e., X_FM when q=0 is
52 // not the identity transform. Instead, although the frames are aligned the
53 // M frame origin is offset from F along their shared +z axis, so that it lies
54 // on the ellipsoid surface at the point (0,0,rz) where rz is the z-radius
55 // (semiaxis) of the ellipsoid.
56 //
57 // The generalized coordinates are:
58 //   * 4 quaternions or 3 1-2-3 body fixed Euler angles (that is, fixed in M)
59 //     In Euler angles, axis 3 is just the spin of the outboard body about
60 //     its Mz axis, which is always normal to the ellipse. For small 1,2 angles
61 //     you can think of angle 1 (about x) as latitude, and angle 2 (about y)
62 //     as longitude when viewing the ellipsoid by looking down the z axis.
63 //     (That would be true for large angles too if we were using space fixed
64 //     angles, that is, angles defined about F rather than M axes.)
65 //
66 // Generalized speeds are:
67 //   * angular velocity w_FM as a vector expressed in the F frame.
68 //
69 // Thus rotational qdots have to be derived from the generalized speeds to
70 // be turned into either 4 quaternion derivatives or 3 Euler angle derivatives.
71 //
72 // This mobilizer was written by Ajay Seth and hacked somewhat by Sherm.
73 
74 template<bool noX_MB, bool noR_PF>
75 class RBNodeEllipsoid : public RigidBodyNodeSpec<3, false, noX_MB, noR_PF> {
76     Vec3 semi; // semi axis dimensions in x,y,z resp.
77 public:
78 
79 typedef typename RigidBodyNodeSpec<3, false, noX_MB, noR_PF>::HType HType;
type()80 virtual const char* type() { return "ellipsoid"; }
81 
RBNodeEllipsoid(const MassProperties & mProps_B,const Transform & X_PF,const Transform & X_BM,const Vec3 & radii,bool isReversed,UIndex & nextUSlot,USquaredIndex & nextUSqSlot,QIndex & nextQSlot)82 RBNodeEllipsoid(const MassProperties& mProps_B,
83               const Transform&      X_PF,
84               const Transform&      X_BM,
85               const Vec3&           radii, // x,y,z
86               bool                  isReversed,
87               UIndex&               nextUSlot,
88               USquaredIndex&        nextUSqSlot,
89               QIndex&               nextQSlot)
90   : RigidBodyNodeSpec<3, false, noX_MB, noR_PF>(mProps_B,X_PF,X_BM,nextUSlot,nextUSqSlot,nextQSlot,
91                          RigidBodyNode::QDotMayDifferFromU, RigidBodyNode::QuaternionMayBeUsed, isReversed),
92     semi(radii)
93 {
94     this->updateSlots(nextUSlot,nextUSqSlot,nextQSlot);
95 }
96 
setQToFitRotationImpl(const SBStateDigest & sbs,const Rotation & R_FM,Vector & q)97 void setQToFitRotationImpl(const SBStateDigest& sbs, const Rotation& R_FM,
98                        Vector& q) const
99 {
100     if (this->getUseEulerAngles(sbs.getModelVars()))
101         this->toQ(q)    = R_FM.convertRotationToBodyFixedXYZ();
102     else
103         this->toQuat(q) = R_FM.convertRotationToQuaternion().asVec4();
104 }
105 
106 // We can't hope to represent arbitrary translations with a joint that has
107 // only rotational coordinates! However, since F is at the center of the
108 // ellipsoid and M on its surface, we can at least obtain a translation in
109 // the *direction* of the requested translation. The magnitude must of
110 // course be set to end up with the M origin right on the surface of the
111 // ellipsoid, and Mz will be the normal at that point.
112 //
113 // Expressed as an x-y-z body fixed Euler sequence, the z rotation is just
114 // the spin around the Mz (surface normal) and could be anything, so we'll
115 // just leave it at its current value. The x and y rotations act like polar
116 // coordinates to get the M origin point on the direction indicated by the
117 // requested translation.
118 //
119 // If the requested translation is near zero we can't do anything since we
120 // can't find a direction to align with. And of course we can't do anything
121 // if "only" is true here -- that means we aren't allowed to touch the
122 // rotations, and for this joint that's all there is.
setQToFitTranslationImpl(const SBStateDigest & sbs,const Vec3 & p_FM,Vector & q)123 void setQToFitTranslationImpl(const SBStateDigest& sbs, const Vec3& p_FM, Vector& q) const {
124     if (p_FM.norm() < Eps) return;
125 
126     const UnitVec3 e(p_FM); // direction from F origin towards desired M origin
127     const Real latitude  = std::atan2(-e[1],e[2]); // project onto F's yz plane
128     const Real longitude = std::atan2( e[0],e[2]); // project onto F's xz plane
129 
130     // Calculate the current value of the spin coordinate (3rd Euler angle).
131     Real spin;
132     if (this->getUseEulerAngles(sbs.getModelVars())){
133         spin = this->fromQ(q)[2];
134     } else {
135         const Rotation R_FM_now(Quaternion(this->fromQuat(q)));
136         spin = R_FM_now.convertRotationToBodyFixedXYZ()[2];
137     }
138 
139     // Calculate the desired rotation, which is a space-fixed 1-2 sequence for
140     // latitude and longitude, followed by a body fixed rotation for spin.
141     const Rotation R_FM = Rotation( SpaceRotationSequence, latitude, XAxis, longitude, YAxis ) * Rotation(spin,ZAxis);
142 
143     if (this->getUseEulerAngles(sbs.getModelVars())) {
144         const Vec3 q123 = R_FM.convertRotationToBodyFixedXYZ();
145         this->toQ(q) = q123;
146     } else {
147         const Quaternion quat = R_FM.convertRotationToQuaternion();
148         this->toQuat(q) = quat.asVec4();
149     }
150 }
151 
setUToFitAngularVelocityImpl(const SBStateDigest & sbs,const Vector & q,const Vec3 & w_FM,Vector & u)152 void setUToFitAngularVelocityImpl(const SBStateDigest& sbs, const Vector& q, const Vec3& w_FM,
153                               Vector& u) const
154 {
155         this->toU(u) = w_FM; // relative ang. vel. always used as generalized speeds
156 }
157 
158 // We can't do general linear velocity with this rotation-only mobilizer, but
159 // we can express any velocity which is tangent to the ellipsoid surface. So
160 // we'll find the current surface normal (Mz) and ignore any component of the
161 // requested velocity which is along that direction. (The resulting vz won't
162 // be zero, though, but it is completely determined by vx,vy.)
setUToFitLinearVelocityImpl(const SBStateDigest & sbs,const Vector & q,const Vec3 & v_FM,Vector & u)163 void setUToFitLinearVelocityImpl
164    (const SBStateDigest& sbs, const Vector& q, const Vec3& v_FM, Vector& u) const
165 {
166     Transform X_FM;
167     this->calcAcrossJointTransform(sbs,q,X_FM);
168 
169     const Vec3 v_FM_M    = ~X_FM.R()*v_FM; // we can only do vx and vy in this frame
170     const Vec3 r_FM_M    = ~X_FM.R()*X_FM.p();
171     const Vec3 wnow_FM_M = ~X_FM.R()*this->fromU(u); // preserve z component
172 
173     // Now vx can only result from angular velocity about y, vy from x.
174     // TODO: THIS IS ONLY RIGHT FOR A SPHERE!!!
175     const Real wx = -v_FM_M[1]/r_FM_M[2];
176     const Real wy = v_FM_M[0]/r_FM_M[2];
177     const Vec3 w_FM_M(wx, wy, wnow_FM_M[2]);
178     const Vec3 w_FM = X_FM.R()*w_FM_M;
179 
180     this->toU(u) = w_FM;
181 }
182 
183 // When we're using Euler angles, we're going to want to cache cos and sin for
184 // each angle, and also 1/cos of the middle angle will be handy to have around.
185 // When we're using quaternions, we'll cache 1/|q| for convenient normalizing
186 // of quaternions.
187 enum {AnglePoolSize=7, QuatPoolSize=1};
188 // cos x,y,z sin x,y,z 1/cos(y)
189 enum {AngleCosQ=0, AngleSinQ=3, AngleOOCosQy=6};
190 enum {QuatOONorm=0};
calcQPoolSize(const SBModelVars & mv)191 int calcQPoolSize(const SBModelVars& mv) const
192 {   return this->getUseEulerAngles(mv) ? AnglePoolSize : QuatPoolSize; }
193 
194 // This is expensive in Euler angle mode due to the three sin/cos computations
195 // required (about 150 flops). In quaternion mode it is much cheaper (about
196 // 25 flops).
performQPrecalculations(const SBStateDigest & sbs,const Real * q,int nq,Real * qCache,int nQCache,Real * qErr,int nQErr)197 void performQPrecalculations(const SBStateDigest& sbs,
198                              const Real* q,      int nq,
199                              Real*       qCache, int nQCache,
200                              Real*       qErr,   int nQErr) const
201 {
202     if (this->getUseEulerAngles(sbs.getModelVars())) {
203         assert(q && nq==3 && qCache && nQCache==AnglePoolSize && nQErr==0);
204 
205         const Real cy = std::cos(q[1]);
206         Vec3::updAs(&qCache[AngleCosQ]) =
207             Vec3(std::cos(q[0]), cy, std::cos(q[2]));
208         Vec3::updAs(&qCache[AngleSinQ]) =
209             Vec3(std::sin(q[0]), std::sin(q[1]), std::sin(q[2]));
210         qCache[AngleOOCosQy] = 1/cy; // trouble at 90 degrees
211     } else {
212         assert(q && nq==4 && qCache && nQCache==QuatPoolSize &&
213                qErr && nQErr==1);
214         const Real quatLen = Vec4::getAs(q).norm();
215         qErr[0] = quatLen - Real(1);    // normalization error
216         qCache[QuatOONorm] = 1/quatLen; // save for later
217     }
218 }
219 
220 // Because of the precalculations above we can calculate the cross-mobilizer
221 // transform in 21 flops (Euler angles) or 36 flops (quaternions).
calcX_FM(const SBStateDigest & sbs,const Real * q,int nq,const Real * qCache,int nQCache,Transform & X_F0M0)222 void calcX_FM(const SBStateDigest& sbs,
223               const Real* q,      int nq,
224               const Real* qCache, int nQCache,
225               Transform&  X_F0M0) const
226 {
227     // Rotation
228     if (this->getUseEulerAngles(sbs.getModelVars())) {
229         assert(q && nq==3 && qCache && nQCache==AnglePoolSize);
230         X_F0M0.updR().setRotationToBodyFixedXYZ // 18 flops
231            (Vec3::getAs(&qCache[AngleCosQ]), Vec3::getAs(&qCache[AngleSinQ]));
232     } else {
233         assert(q && nq==4 && qCache && nQCache==QuatPoolSize);
234         // Must use a normalized quaternion to generate the rotation matrix.
235         // Here we normalize with just 4 flops using precalculated 1/norm(q).
236         const Quaternion quat(Vec4::getAs(q)*qCache[QuatOONorm], true);
237         X_F0M0.updR().setRotationFromQuaternion(quat); // 29 flops
238     }
239 
240     // Translation.
241     const Vec3& n = X_F0M0.z(); // just calculated above
242     X_F0M0.updP() = Vec3(semi[0]*n[0], semi[1]*n[1], semi[2]*n[2]);
243 }
244 
245 // Generalized speeds are the angular velocity expressed in F, so they
246 // cause rotations around F x,y,z axes respectively. (9 flops)
calcAcrossJointVelocityJacobian(const SBStateDigest & sbs,HType & H_FM)247 void calcAcrossJointVelocityJacobian(
248     const SBStateDigest& sbs,
249     HType&               H_FM) const
250 {
251     const SBTreePositionCache& pc = sbs.updTreePositionCache(); // "upd" because we're realizing positions now
252 
253     // The normal is M's z axis, expressed in F but only in the frames we
254     // used to *define* this mobilizer, not necessarily the ones used after
255     // handling mobilizer reversal.
256     const Vec3 n = this->findX_F0M0(pc).z();
257 
258     H_FM(0) = SpatialVec( Vec3(1,0,0), Vec3(      0,      -n[2]*semi[1], n[1]*semi[2]) );
259     H_FM(1) = SpatialVec( Vec3(0,1,0), Vec3( n[2]*semi[0],       0,     -n[0]*semi[2]) );
260     H_FM(2) = SpatialVec( Vec3(0,0,1), Vec3(-n[1]*semi[0], n[0]*semi[1],       0     ) );
261 }
262 
263 // Calculate time derivative of H_FM, which is *not* constant. (18 flops)
calcAcrossJointVelocityJacobianDot(const SBStateDigest & sbs,HType & HDot_FM)264 void calcAcrossJointVelocityJacobianDot(
265     const SBStateDigest& sbs,
266     HType&               HDot_FM) const
267 {
268     const SBTreePositionCache& pc = sbs.getTreePositionCache();
269     const SBTreeVelocityCache& vc = sbs.updTreeVelocityCache(); // "upd" because we're realizing velocities now
270 
271     // We need the normal and cross-joint velocity in the frames we're
272     // using to *define* the mobilizer, not necessarily the frames we're
273     // using to compute it it (if it has been reversed).
274     const Vec3       n      = this->findX_F0M0(pc).z();
275     const Vec3       w_F0M0 = this->find_w_F0M0(pc, vc);
276     const Vec3       ndot   = w_F0M0 % n; // w_FM x n (9 flops)
277 
278     HDot_FM(0) = SpatialVec( Vec3(0), Vec3(      0,         -ndot[2]*semi[1], ndot[1]*semi[2]) );
279     HDot_FM(1) = SpatialVec( Vec3(0), Vec3( ndot[2]*semi[0],       0,        -ndot[0]*semi[2]) );
280     HDot_FM(2) = SpatialVec( Vec3(0), Vec3(-ndot[1]*semi[0], ndot[0]*semi[1],       0        ) );
281 }
282 
283 // Calculate qdot=N(q)*u. Precalculations make this fast in Euler angle
284 // mode (10 flops); quaternion mode is 27 flops.
285 // TODO: we're expecting that there are 4 qdots even in Euler angle mode
286 // so we have to zero out the last one in that case.
calcQDot(const SBStateDigest & sbs,const Real * u,Real * qdot)287 void calcQDot(const SBStateDigest& sbs, const Real* u,
288                              Real* qdot) const {
289     // We have to trust that the tree position cache is valid here.
290     assert(u && qdot);
291 
292     const SBModelVars& mv = sbs.getModelVars();
293 
294     // Generalized speeds are the same in either mode -- angular velocity
295     // of F in M (or B in P), expressed in F (fixed on parent) frame.
296     const Vec3& w_FM = Vec3::getAs(u);
297 
298     if (this->getUseEulerAngles(mv)) {
299         // Euler angle mode (10 flops)
300         qdot[3] = 0; // TODO: kludge, clear unused element
301 
302         const SBModelCache&        mc   = sbs.getModelCache();
303         const SBTreePositionCache& pc   = sbs.getTreePositionCache();
304         const Real*                pool = this->getQPool(mc, pc);
305 
306         Vec3::updAs(qdot) = Rotation::convertAngVelInParentToBodyXYZDot(
307              Vec2::getAs(&pool[AngleCosQ]), Vec2::getAs(&pool[AngleSinQ]), //x,y
308              pool[AngleOOCosQy], w_FM);
309     } else {
310         // Quaternion mode (27 flops)
311         const Vec4& quat  = this->fromQuat(sbs.getQ()); // unnormalized
312         Vec4::updAs(qdot) = Rotation::convertAngVelToQuaternionDot(quat, w_FM);
313     }
314 }
315 
316 // CAUTION: we do not zero the unused 4th element of q for Euler angles; it
317 // is up to the caller to do that if it is necessary.
318 // Compute out_q = N * in_u
319 //   or    out_u = in_q * N
320 // The u quantity is a vector v_F in the parent frame (e.g. angular
321 // velocity of child in parent), while the q quantity is the derivative
322 // of a quaternion representing R_FM, i.e. orientation of child in parent.
323 // Cost: Euler angle mode - 10 flops, Quaternion mode - 27 flops.
multiplyByN(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)324 void multiplyByN(const SBStateDigest& sbs, bool matrixOnRight,
325                  const Real* in, Real* out) const
326 {
327     assert(sbs.getStage() >= Stage::Position);
328     assert(in && out);
329 
330     const SBModelVars& mv = sbs.getModelVars();
331 
332     if (this->getUseEulerAngles(mv)) {
333         // Euler angle mode (10 flops).
334 
335         const SBModelCache&        mc   = sbs.getModelCache();
336         const SBTreePositionCache& pc   = sbs.getTreePositionCache();
337         const Real*                pool = this->getQPool(mc, pc);
338 
339         // Aliases.
340         const Vec2& cosxy  = Vec2::getAs(&pool[AngleCosQ]); // only need x,y
341         const Vec2& sinxy  = Vec2::getAs(&pool[AngleSinQ]);
342         const Real& oocosy = pool[AngleOOCosQy];
343         const Vec3& in_v   = Vec3::getAs(in);
344         Vec3&       out_v  = Vec3::updAs(out);
345 
346         if (matrixOnRight) { // out_u = in_q * N = ~N * in_q (9 flops)
347             // Transpose on left is same as untransposed on right.
348             out_v = Rotation::multiplyByBodyXYZ_NT_P(cosxy, sinxy, oocosy, in_v);
349         } else {             // out_q = N*in_u (10 flops)
350             out_v = Rotation::multiplyByBodyXYZ_N_P(cosxy, sinxy, oocosy, in_v);
351         }
352     } else {
353         // Quaternion mode (27 flops).
354         const Vec4& quat  = this->fromQuat(sbs.getQ()); // unnormalized
355         const Mat43 N_F = // This method returns N for the parent frame
356             Rotation::calcUnnormalizedNForQuaternion(quat); // 7 flops
357         if (matrixOnRight)
358                 Row3::updAs(out) = Row4::getAs(in) * N_F; // 20 flops
359         else    Vec4::updAs(out) = N_F * Vec3::getAs(in);
360     }
361 }
362 
363 // Compute out_u = inv(N) * in_q
364 //   or    out_q = in_u * inv(N)
365 // Cost: Euler angle mode ~ 10 flops, Quaternion mode ~ 27 flops.
multiplyByNInv(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)366 void multiplyByNInv(const SBStateDigest& sbs, bool matrixOnRight,
367                     const Real* in, Real* out) const
368 {
369     assert(sbs.getStage() >= Stage::Position);
370     assert(in && out);
371 
372     const SBModelVars& mv = sbs.getModelVars();
373 
374     if (this->getUseEulerAngles(mv)) {
375         // Euler angle mode (10 flops).
376         const SBModelCache&        mc   = sbs.getModelCache();
377         const SBTreePositionCache& pc   = sbs.getTreePositionCache();
378         const Real*                pool = this->getQPool(mc, pc);
379 
380         // Aliases.
381         const Vec2& cosxy  = Vec2::getAs(&pool[AngleCosQ]); // only need x,y
382         const Vec2& sinxy  = Vec2::getAs(&pool[AngleSinQ]);
383         const Vec3& in_v   = Vec3::getAs(in);
384         Vec3&       out_v  = Vec3::updAs(out);
385 
386         if (matrixOnRight) { // out_q = in_u * inv(N) = ~inv(N) * in_u  (10 flops)
387             // transpose on left is same as untransposed on right
388             out_v = Rotation::multiplyByBodyXYZ_NInvT_P(cosxy, sinxy, in_v);
389         } else {             // out_u = inv(N) * in_q                   (9 flops)
390             out_v = Rotation::multiplyByBodyXYZ_NInv_P(cosxy, sinxy, in_v);
391         }
392     } else {
393         // Quaternion mode (27 flops).
394         const Vec4& quat  = this->fromQuat(sbs.getQ()); // unnormalized
395         const Mat34 NInv_F = // Returns NInv for parent frame.
396             Rotation::calcUnnormalizedNInvForQuaternion(quat);  // 7 flops
397         if (matrixOnRight)
398                 Row4::updAs(out) = Row3::getAs(in) * NInv_F; // 20 flops
399         else    Vec3::updAs(out) = NInv_F * Vec4::getAs(in); // 21 flops
400     }
401 }
402 
403 // Compute out_q = NDot * in_u
404 //   or    out_u = in_q * NDot
405 // Note that it is much cheaper to calculate
406 // qdotdot directly than to do it using N and NDot; see below.
407 // Cost: Euler angle mode - 36 flops, Quaternion mode - 27 flops.
multiplyByNDot(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)408 void multiplyByNDot(const SBStateDigest& sbs, bool matrixOnRight,
409                     const Real* in, Real* out) const
410 {
411     assert(sbs.getStage() > Stage::Velocity);
412     assert(in && out);
413 
414     const SBModelVars& mv = sbs.getModelVars();
415 
416     if (this->getUseEulerAngles(mv)) {
417         // Euler angle mode (36 flops).
418         const SBModelCache&        mc   = sbs.getModelCache();
419         const SBTreePositionCache& pc   = sbs.getTreePositionCache();
420         const Real*                pool = this->getQPool(mc, pc);
421 
422         // Aliases.
423         const Vec2& cosxy  = Vec2::getAs(&pool[AngleCosQ]); // only need x,y
424         const Vec2& sinxy  = Vec2::getAs(&pool[AngleSinQ]);
425         const Real& oocosy = pool[AngleOOCosQy];
426         const Vec3& qdot   = this->fromQ(sbs.getQDot());
427 
428         // We don't have a nice multiply-by routine here so just get the NDot
429         // matrix and use it (21 flops).
430         const Mat33 NDot_F =
431             Rotation::calcNDotForBodyXYZInParentFrame(cosxy, sinxy, oocosy, qdot);
432 
433         if (matrixOnRight) {    // out_u = in_q * NDot (15 flops)
434             Row3::updAs(out) = Row3::getAs(in) * NDot_F;
435         } else {                // out_q = NDot * in_u (15 flops)
436             Vec3::updAs(out) = NDot_F * Vec3::getAs(in);
437         }
438     } else {
439         // Quaternion mode (27 flops).
440         const Vec4& qdot  = this->fromQuat(sbs.getQDot()); // unnormalized
441         const Mat43 NDot_F = // This method returns NDot for the parent frame
442             Rotation::calcUnnormalizedNDotForQuaternion(qdot); // 7 flops
443         if (matrixOnRight)
444                 Row3::updAs(out) = Row4::getAs(in) * NDot_F;  // 21 flops
445         else    Vec4::updAs(out) = NDot_F * Vec3::getAs(in);  // 20 flops
446     }
447 }
448 
449 // Calculate qdotdot from udot as qdotdot = N(q)*udot + NDot(q,qdot)*u, but
450 // faster. Here we assume that qdot=N*u has already been calculated, which
451 // is very helpful in Euler angle mode. In either mode it is a very bad
452 // idea to calculate this using explicit N and NDot matrices; we can save
453 // a lot of time by using more specialized methods.
454 // Cost: Euler angle mode - 22 flops, Quaternion mode - 41 flops.
calcQDotDot(const SBStateDigest & sbs,const Real * udot,Real * qdotdot)455 void calcQDotDot(const SBStateDigest& sbs, const Real* udot,
456                                    Real* qdotdot) const {
457     assert(sbs.getStage() > Stage::Velocity);
458     assert(udot && qdotdot);
459 
460     const SBModelVars& mv = sbs.getModelVars();
461 
462     // Angular acceleration is the same in either mode.
463     const Vec3& b_FM = Vec3::getAs(udot);   // a.k.a. w_FM_dot
464 
465     if (this->getUseEulerAngles(mv)) {
466         // Euler angle mode (22 flops).
467         Vec4::updAs(qdotdot) = 0; // TODO: kludge, clear unused element
468 
469         const SBModelCache&        mc   = sbs.getModelCache();
470         const SBTreePositionCache& pc   = sbs.getTreePositionCache();
471         const Real*                pool = this->getQPool(mc, pc);
472 
473         // Aliases.
474         const Vec2& cosxy     = Vec2::getAs(&pool[AngleCosQ]); // only need x,y
475         const Vec2& sinxy     = Vec2::getAs(&pool[AngleSinQ]);
476         const Real& oocosy    = pool[AngleOOCosQy];
477         const Vec3& qdot      = this->fromQ(sbs.getQDot());
478         Vec3&       qdotdot_v = Vec3::updAs(qdotdot);
479 
480         // 22 flops.
481         qdotdot_v = Rotation::convertAngAccInParentToBodyXYZDotDot
482                                             (cosxy, sinxy, oocosy, qdot, b_FM);
483     } else {
484         // Quaternion mode (41 flops).
485         const Vec4& quat = this->fromQuat(sbs.getQ()); // unnormalized
486         const Vec3& w_FM = this->fromU(sbs.getU());
487         Vec4::updAs(qdotdot) =
488             Rotation::convertAngVelDotToQuaternionDotDot(quat,w_FM,b_FM);
489     }
490 }
491 
getMaxNQ()492 int getMaxNQ() const {return 4;}
getNQInUse(const SBModelVars & mv)493 int getNQInUse(const SBModelVars& mv) const {
494     return this->getUseEulerAngles(mv) ? 3 : 4;
495 }
isUsingQuaternion(const SBStateDigest & sbs,MobilizerQIndex & startOfQuaternion)496 bool isUsingQuaternion(const SBStateDigest& sbs,
497                        MobilizerQIndex& startOfQuaternion) const {
498     if (this->getUseEulerAngles(sbs.getModelVars())) {
499         startOfQuaternion.invalidate();
500         return false;
501     }
502     startOfQuaternion = MobilizerQIndex(0); // quaternion comes first
503     return true;
504 }
505 
setMobilizerDefaultPositionValues(const SBModelVars & mv,Vector & q)506 void setMobilizerDefaultPositionValues(
507     const SBModelVars& mv,
508     Vector&            q) const
509 {
510     if (this->getUseEulerAngles(mv)) {
511         //TODO: kludge
512         this->toQuat(q) = Vec4(0); // clear unused element
513         this->toQ(q) = 0;
514     }
515     else this->toQuat(q) = Vec4(1,0,0,0);
516 }
517 
enforceQuaternionConstraints(const SBStateDigest & sbs,Vector & q,Vector & qErrest)518 bool enforceQuaternionConstraints(
519     const SBStateDigest& sbs,
520     Vector&             q,
521     Vector&             qErrest) const
522 {
523     if (this->getUseEulerAngles(sbs.getModelVars()))
524         return false;   // no change
525 
526     Vec4& quat = this->toQuat(q);
527     quat = quat / quat.norm();
528 
529     if (qErrest.size()) {
530         Vec4& qerr = this->toQuat(qErrest);
531         qerr -= dot(qerr,quat) * quat;
532     }
533 
534     return true;
535 }
536 
convertToEulerAngles(const Vector & inputQ,Vector & outputQ)537 void convertToEulerAngles(const Vector& inputQ, Vector& outputQ) const {
538     this->toQuat(outputQ) = Vec4(0); // clear unused element
539     this->toQ(outputQ) = Rotation(Quaternion(this->fromQuat(inputQ))).convertRotationToBodyFixedXYZ();
540 }
541 
convertToQuaternions(const Vector & inputQ,Vector & outputQ)542 void convertToQuaternions(const Vector& inputQ, Vector& outputQ) const {
543     Rotation rot;
544     rot.setRotationToBodyFixedXYZ(this->fromQ(inputQ));
545     this->toQuat(outputQ) = rot.convertRotationToQuaternion().asVec4();
546 }
547 
548 };
549 
550 
551 #endif // SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_ELLIPSOID_H_
552 
553