1 #ifndef SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_FREELINE_H_
2 #define SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_FREELINE_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: 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  * Define the RigidBodyNode that implements a Translation mobilizer, also
29  * known as a Cartesian joint.
30  */
31 
32 #include "SimbodyMatterSubsystemRep.h"
33 #include "RigidBodyNode.h"
34 #include "RigidBodyNodeSpec.h"
35 
36 
37     // FREE LINE //
38 
39 // FreeLine joint. Like a Free joint, this provides full rotational and
40 // translational freedom, but for a degenerate body which is thin (inertialess)
41 // along its own z axis. These arise in molecular modeling for linear molecules
42 // formed by pairs of atoms, or by multiple atoms in a linear arrangement like
43 // carbon dioxide (CO2) whose structure is O=C=O in a straight line. We are
44 // assuming that there is no meaning to a rotation about the linear axis,
45 // so free orientation requires just *two* degrees of freedom, not *three*
46 // as is required for general rigid bodies. And in fact we can get away with
47 // just two rotational generalized speeds so this joint provides only 5
48 // mobilities.
49 //
50 // But so far, no one has been able to come up with a way to manage with only
51 // two rotational generalized *coordinates*, so this joint has the same q's as
52 // a regular Free joint: either a quaternion for unconditional stability, or a
53 // three-angle (body fixed 1-2-3) Euler sequence which will be dynamically
54 // singular when the middle (y) axis is 90 degrees. Use the Euler sequence only
55 // for small motions or for kinematics problems (and note that only the first
56 // two are meaningful). Translations here are treated exactly as for a Free
57 // joint (or for a Cartesian joint for that matter).
58 //
59 // To summarize, the generalized coordinates are:
60 //   * 4 quaternions or 3 1-2-3 body fixed Euler angles (that is, fixed in M)
61 //   * 3 components of the translation vector p_FM (that is, vector from origin
62 //     of F to origin of M, expressed in F)
63 // and generalized speeds are:
64 //   * the x,y components of the angular velocity w_FM_M, that is, the angular
65 //     velocity of M in F expressed in *M* (where we want wz=0).
66 //   * 3 components of the linear velocity of origin of M in F, expressed in F.
67 //     NOTE: THAT IS NOT THE SAME FRAME AS FOR A FREE JOINT
68 // Thus the qdots have to be derived from the generalized speeds to
69 // be turned into either 4 quaternion derivatives or 3 Euler angle derivatives.
70 template<bool noX_MB, bool noR_PF>
71 class RBNodeFreeLine : public RigidBodyNodeSpec<5, false, noX_MB, noR_PF> {
72 public:
73 
74 typedef typename RigidBodyNodeSpec<5, false, noX_MB, noR_PF>::HType HType;
type()75 virtual const char* type() { return "full"; }
76 
RBNodeFreeLine(const MassProperties & mProps_B,const Transform & X_PF,const Transform & X_BM,bool isReversed,UIndex & nextUSlot,USquaredIndex & nextUSqSlot,QIndex & nextQSlot)77 RBNodeFreeLine(const MassProperties& mProps_B,
78                const Transform&      X_PF,
79                const Transform&      X_BM,
80                bool                  isReversed,
81                UIndex&               nextUSlot,
82                USquaredIndex&        nextUSqSlot,
83                QIndex&               nextQSlot)
84   : RigidBodyNodeSpec<5, false, noX_MB, noR_PF>(mProps_B,X_PF,X_BM,nextUSlot,nextUSqSlot,nextQSlot,
85                          RigidBodyNode::QDotMayDifferFromU, RigidBodyNode::QuaternionMayBeUsed, isReversed)
86 {
87     this->updateSlots(nextUSlot,nextUSqSlot,nextQSlot);
88 }
89 
90     // Implementations of virtual methods.
91 
92 // This has a default implementation but it rotates first then translates,
93 // which works fine for the normal FreeLine joint but produces wrong behavior
94 // when the mobilizer is reversed.
setQToFitTransformImpl(const SBStateDigest & sbs,const Transform & X_FM,Vector & q)95 void setQToFitTransformImpl(const SBStateDigest& sbs, const Transform& X_FM,
96                             Vector& q) const override
97 {
98     setQToFitTranslationImpl(sbs, X_FM.p(), q); // see below
99     setQToFitRotationImpl(sbs, X_FM.R(), q);
100 }
101 
setQToFitRotationImpl(const SBStateDigest & sbs,const Rotation & R_FM,Vector & q)102 void setQToFitRotationImpl(const SBStateDigest& sbs, const Rotation& R_FM,
103                           Vector& q) const override
104 {
105     if (this->getUseEulerAngles(sbs.getModelVars()))
106         this->toQVec3(q,0) = R_FM.convertRotationToBodyFixedXYZ();
107     else
108         this->toQuat(q) = R_FM.convertRotationToQuaternion().asVec4();
109 }
110 
111 // The user gives us the translation vector from OF to OM as a vector expressed
112 // in F. With a free joint we never have to *change* orientation coordinates in
113 // order to achieve a translation.
setQToFitTranslationImpl(const SBStateDigest & sbs,const Vec3 & p_FM,Vector & q)114 void setQToFitTranslationImpl(const SBStateDigest& sbs,
115                               const Vec3& p_FM, Vector& q) const override {
116     if (this->getUseEulerAngles(sbs.getModelVars()))
117         this->toQVec3(q,3) = p_FM; // skip the 3 Euler angles
118     else
119         this->toQVec3(q,4) = p_FM; // skip the 4 quaternions
120 }
121 
122 // Our 2 rotational generalized speeds are just the (x,y) components of the
123 // angular velocity vector of M in F, expressed in *M*.
124 // Note: a quaternion from a state is not necessarily normalized so can't be
125 // used directly as though it were a set of Euler parameters; it must be
126 // normalized first.
setUToFitAngularVelocityImpl(const SBStateDigest & sbs,const Vector & q,const Vec3 & w_FM,Vector & u)127 void setUToFitAngularVelocityImpl(const SBStateDigest& sbs,
128                                   const Vector& q, const Vec3& w_FM,
129                                   Vector& u) const override
130 {
131     Rotation R_FM;
132     if (this->getUseEulerAngles(sbs.getModelVars()))
133         R_FM.setRotationToBodyFixedXYZ( this->fromQVec3(q,0) );
134     else {
135         // can't use qnorm pool here since state hasn't been
136         // realized to position stage yet; q's can be anything
137         R_FM.setRotationFromQuaternion( Quaternion(this->fromQuat(q)) ); // normalize
138     }
139     const Vec3 w_FM_M = ~R_FM*w_FM;
140     // (x,y) of relative angular velocity always used as generalized speeds
141     Vec2::updAs(&this->toU(u)[0]) = Vec2(w_FM_M[0], w_FM_M[1]);
142 }
143 
144 // Our 3 translational generalized speeds are the linear velocity of M's origin
145 // in F, expressed in F. The user gives us that same vector.
setUToFitLinearVelocityImpl(const SBStateDigest & sbs,const Vector & q,const Vec3 & v_FM,Vector & u)146 void setUToFitLinearVelocityImpl(const SBStateDigest& sbs,
147                                  const Vector& q, const Vec3& v_FM, Vector& u) const override
148 {
149     this->toUVec3(u,2) = v_FM;
150 }
151 
152 // When we're using Euler angles, we're going to want to cache cos and sin for
153 // each angle, and also 1/cos of the middle angle will be handy to have around.
154 // When we're using quaternions, we'll cache 1/|q| for convenient normalizing
155 // of quaternions.
156 enum {AnglePoolSize=7, QuatPoolSize=1};
157 // cos x,y,z sin x,y,z 1/cos(y)
158 enum {AngleCosQ=0, AngleSinQ=3, AngleOOCosQy=6};
159 enum {QuatOONorm=0};
calcQPoolSize(const SBModelVars & mv)160 int calcQPoolSize(const SBModelVars& mv) const override
161 {   return this->getUseEulerAngles(mv) ? AnglePoolSize : QuatPoolSize; }
162 
performQPrecalculations(const SBStateDigest & sbs,const Real * q,int nq,Real * qCache,int nQCache,Real * qErr,int nQErr)163 void performQPrecalculations(const SBStateDigest& sbs,
164                              const Real* q,      int nq,
165                              Real*       qCache, int nQCache,
166                              Real*       qErr,   int nQErr) const override
167 {
168     if (this->getUseEulerAngles(sbs.getModelVars())) {
169         assert(q && nq==6 && qCache && nQCache==AnglePoolSize && nQErr==0);
170         const Real cy = std::cos(q[1]);
171         Vec3::updAs(&qCache[AngleCosQ]) =
172             Vec3(std::cos(q[0]), cy, std::cos(q[2]));
173         Vec3::updAs(&qCache[AngleSinQ]) =
174             Vec3(std::sin(q[0]), std::sin(q[1]), std::sin(q[2]));
175         qCache[AngleOOCosQy] = 1/cy; // trouble at 90 degrees
176     } else {
177         assert(q && nq==7 && qCache && nQCache==QuatPoolSize &&
178                qErr && nQErr==1);
179         const Real quatLen = Vec4::getAs(q).norm();
180         qErr[0] = quatLen - Real(1);    // normalization error
181         qCache[QuatOONorm] = 1/quatLen; // save for later
182     }
183 }
184 
calcX_FM(const SBStateDigest & sbs,const Real * q,int nq,const Real * qCache,int nQCache,Transform & X_F0M0)185 void calcX_FM(const SBStateDigest& sbs,
186               const Real* q,      int nq,
187               const Real* qCache, int nQCache,
188               Transform&  X_F0M0) const override
189 {
190     if (this->getUseEulerAngles(sbs.getModelVars())) {
191         assert(q && nq==6 && qCache && nQCache==AnglePoolSize);
192         X_F0M0.updR().setRotationToBodyFixedXYZ // 18 flops
193            (Vec3::getAs(&qCache[AngleCosQ]), Vec3::getAs(&qCache[AngleSinQ]));
194         X_F0M0.updP() = Vec3::getAs(&q[3]); // a012 x y z
195     } else {
196         assert(q && nq==7 && qCache && nQCache==QuatPoolSize);
197         // Must use a normalized quaternion to generate the rotation matrix.
198         // Here we normalize with just 4 flops using precalculated 1/norm(q).
199         const Quaternion quat(Vec4::getAs(q)*qCache[QuatOONorm], true);
200         X_F0M0.updR().setRotationFromQuaternion(quat); // 29 flops
201         X_F0M0.updP() = Vec3::getAs(&q[4]); // q0123 x y z
202     }
203 }
204 
205 
206 // The generalized speeds for this 5-dof ("free line") joint are
207 //   (1) the (x,y) components of angular velocity of M in the F frame,
208 //         expressed in *M*, and
209 //   (2) the (linear) velocity of M's origin in F, expressed in F.
calcAcrossJointVelocityJacobian(const SBStateDigest & sbs,HType & H_FM)210 void calcAcrossJointVelocityJacobian(
211     const SBStateDigest& sbs,
212     HType&               H_FM) const override
213 {
214     const SBTreePositionCache& pc = sbs.updTreePositionCache(); // "upd" because we're realizing positions now
215     const Transform  X_F0M0 = this->findX_F0M0(pc);
216 
217     // Dropping the 0's here.
218     const Rotation& R_FM = X_F0M0.R();
219     const Vec3&     Mx_F = R_FM.x(); // M's x axis, expressed in F
220     const Vec3&     My_F = R_FM.y(); // M's y axis, expressed in F
221 
222     H_FM(0) = SpatialVec( Mx_F, Vec3(0) );  // x,y ang. vel. in M, exp. in F
223     H_FM(1) = SpatialVec( My_F, Vec3(0) );
224 
225     H_FM(2) = SpatialVec( Vec3(0), Vec3(1,0,0) );   // translations in F
226     H_FM(3) = SpatialVec( Vec3(0), Vec3(0,1,0) );
227     H_FM(4) = SpatialVec( Vec3(0), Vec3(0,0,1) );
228 }
229 
230 // Since the first two rows of the Jacobian above are not constant in F,
231 // its time derivative is non zero. Here we use the fact that for
232 // a vector r_B_A fixed in a moving frame B but expressed in another frame A,
233 // its time derivative in A is the angular velocity of B in A crossed with
234 // the vector, i.e., d_A/dt r_B_A = w_AB % r_B_A.
calcAcrossJointVelocityJacobianDot(const SBStateDigest & sbs,HType & HDot_FM)235 void calcAcrossJointVelocityJacobianDot(
236     const SBStateDigest& sbs,
237     HType&               HDot_FM) const override
238 {
239     const SBTreePositionCache& pc = sbs.getTreePositionCache();
240     const SBTreeVelocityCache& vc = sbs.updTreeVelocityCache(); // "upd" because we're realizing velocities now
241     const Transform  X_F0M0 = this->findX_F0M0(pc);
242 
243     // Dropping the 0's here.
244     const Rotation& R_FM = X_F0M0.R();
245     const Vec3&     Mx_F = R_FM.x(); // M's x axis, expressed in F
246     const Vec3&     My_F = R_FM.y(); // M's y axis, expressed in F
247 
248     const Vec3      w_FM = this->find_w_F0M0(pc,vc); // angular velocity of M in F
249 
250     HDot_FM(0) = SpatialVec( w_FM % Mx_F, Vec3(0) );
251     HDot_FM(1) = SpatialVec( w_FM % My_F, Vec3(0) );
252 
253     // For translation in F.
254     HDot_FM(2) = SpatialVec( Vec3(0), Vec3(0) );
255     HDot_FM(3) = SpatialVec( Vec3(0), Vec3(0) );
256     HDot_FM(4) = SpatialVec( Vec3(0), Vec3(0) );
257 }
258 
259 // CAUTION: we do not zero the unused 4th element of q for Euler angles; it
260 // is up to the caller to do that if it is necessary.
261 // Compute out_q = N * in_u
262 //   or    out_u = in_q * N
263 // The u quantity is a vector v_M in the child body frame (e.g. angular
264 // velocity of child in parent, but expressed in child), while the q
265 // quantity is the derivative of a quaternion or Euler sequence representing
266 // R_FM, i.e. orientation of child in parent.
multiplyByN(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)267 void multiplyByN(const SBStateDigest& sbs, bool matrixOnRight,
268                  const Real* in, Real* out) const override
269 {
270     assert(sbs.getStage() >= Stage::Position);
271     assert(in && out);
272 
273     const SBModelVars&          mv   = sbs.getModelVars();
274     const SBTreePositionCache&  pc   = sbs.getTreePositionCache();
275     const Vector&               allQ = sbs.getQ();
276 
277     if (this->getUseEulerAngles(mv)) {
278         const Vec3& q = this->fromQVec3(allQ,0);
279         const Mat32 N_FM = // N here mixes parent and body frames (convenient)
280             Rotation::calcNForBodyXYZInBodyFrame(q)
281                         .getSubMat<3,2>(0,0); // drop 3rd column
282         // Translational part of N is identity so just copy in to out.
283         if (matrixOnRight) {
284             Row2::updAs(out)   = Row3::getAs(in) * N_FM;
285             Row3::updAs(out+2) = Row3::getAs(in+3);// translation
286         } else {
287             Vec3::updAs(out)   = N_FM * Vec2::getAs(in);
288             Vec3::updAs(out+3) = Vec3::getAs(in+2);// translation
289         }
290     } else {
291         // Quaternion: N block is only available expecting angular velocity
292         // in the parent frame F, but we have it in M for this joint so we
293         // have to calculate N_FM = N_FF*R_FM.
294         const Rotation& R_FM = this->getX_FM(pc).R();
295         const Vec4& q = this->fromQuat(allQ);
296         const Mat42 N_FM = (Rotation::calcUnnormalizedNForQuaternion(q)*R_FM)
297                             .getSubMat<4,2>(0,0); // drop 3rd column
298         // Translational part of N is identity so just copy in to out.
299         if (matrixOnRight) {
300             Row2::updAs(out)   = Row4::getAs(in) * N_FM;
301             Row3::updAs(out+2) = Row3::getAs(in+4); // translation
302         } else { // matrix on left
303             Vec4::updAs(out)   = N_FM * Vec2::getAs(in);
304             Vec3::updAs(out+4) = Vec3::getAs(in+2); // translation
305         }
306     }
307 }
308 
309 
310 // Compute out_u = inv(N) * in_q
311 //   or    out_q = in_u * inv(N)
multiplyByNInv(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)312 void multiplyByNInv(const SBStateDigest& sbs, bool matrixOnRight,
313                     const Real* in, Real* out) const override
314 {
315     assert(sbs.getStage() >= Stage::Position);
316     assert(in && out);
317 
318     const SBModelVars&          mv   = sbs.getModelVars();
319     const SBTreePositionCache&  pc   = sbs.getTreePositionCache();
320     const Vector&               allQ = sbs.getQ();
321 
322     if (this->getUseEulerAngles(mv)) {
323         const Vec3& q = this->fromQVec3(allQ,0);
324         const Mat23 NInv_MF = Rotation::calcNInvForBodyXYZInBodyFrame(q)
325                                 .getSubMat<2,3>(0,0);   // drop 3rd row
326         // Translational part of NInv block is identity.
327         if (matrixOnRight) {
328             Row3::updAs(out)   = Row2::getAs(in) * NInv_MF;
329             Row3::updAs(out+3) = Row3::getAs(in+2); // translation
330         } else {
331             Vec2::updAs(out)   = NInv_MF * Vec3::getAs(in);
332             Vec3::updAs(out+2) = Vec3::getAs(in+3); // translation
333         }
334     } else {
335         // Quaternion: NInv block is only available expecting angular
336         // velocity in the parent frame F, but we have it in M for
337         // this joint so we have to calculate NInv_MF = R_MF*NInv_FF.
338         const Rotation& R_FM = this->getX_FM(pc).R();
339         const Vec4& q = this->fromQuat(allQ);
340         const Mat24 NInv_MF = (~R_FM*Rotation::calcUnnormalizedNInvForQuaternion(q))
341                                 .getSubMat<2,4>(0,0);   // drop 3rd row
342         // Translational part of NInv block is identity.
343         if (matrixOnRight) {
344             Row4::updAs(out)   = Row2::getAs(in) * NInv_MF;
345             Row3::updAs(out+4) = Row3::getAs(in+2); // translation
346         } else { // matrix on left
347             Vec2::updAs(out)   = NInv_MF * Vec4::getAs(in);
348             Vec3::updAs(out+2) = Vec3::getAs(in+4); // translation
349         }
350     }
351 }
352 
353 // Compute out_q = NDot * in_u
354 //   or    out_u = in_q * NDot
multiplyByNDot(const SBStateDigest & sbs,bool matrixOnRight,const Real * in,Real * out)355 void multiplyByNDot(const SBStateDigest& sbs, bool matrixOnRight,
356                  const Real* in, Real* out) const override
357 {
358     assert(sbs.getStage() >= Stage::Velocity);
359     assert(in && out);
360 
361     const SBModelVars&          mv      = sbs.getModelVars();
362     const SBTreePositionCache&  pc      = sbs.getTreePositionCache();
363     const Vector&               allQ    = sbs.getQ();
364     const Vector&               allQDot = sbs.getQDot();
365 
366     if (this->getUseEulerAngles(mv)) {
367         const Vec3& q = this->fromQVec3(allQ,0);
368         const Vec3& qdot = this->fromQVec3(allQDot,0);
369         const Mat32 NDot_FM = // NDot here mixes parent and body frames (convenient)
370             Rotation::calcNDotForBodyXYZInBodyFrame(q, qdot)
371                         .getSubMat<3,2>(0,0); // drop 3rd column
372         // Translational part of NDot is zero so set out to zero.
373         if (matrixOnRight) {
374             Row2::updAs(out)   = Row3::getAs(in) * NDot_FM;
375             Row3::updAs(out+2) = 0; // translation
376         } else {
377             Vec3::updAs(out)   = NDot_FM * Vec2::getAs(in);
378             Vec3::updAs(out+3) = 0; // translation
379         }
380     } else {
381         // Quaternion: NDot block is only available expecting angular velocity
382         // in the parent frame F, but we have it in M for this joint so we
383         // have to calculate NDot_FM = NDot_FF*R_FM.
384         const Rotation& R_FM = this->getX_FM(pc).R();
385         const Vec4& qdot = this->fromQuat(allQDot);
386         const Mat42 NDot_FM =
387            (Rotation::calcUnnormalizedNDotForQuaternion(qdot)*R_FM)
388                         .getSubMat<4,2>(0,0); // drop 3rd column
389         // Translational part of N is identity so just copy in to out.
390         if (matrixOnRight) {
391             Row2::updAs(out)   = Row4::getAs(in) * NDot_FM;
392             Row3::updAs(out+2) = 0; // translation
393         } else { // matrix on left
394             Vec4::updAs(out)   = NDot_FM * Vec2::getAs(in);
395             Vec3::updAs(out+4) = 0; // translation
396         }
397     }
398 }
399 
400 
calcQDot(const SBStateDigest & sbs,const Real * u,Real * qdot)401 void calcQDot(
402     const SBStateDigest&   sbs,
403     const Real*            u,
404     Real*                  qdot) const override
405 {
406     const SBModelVars&          mv = sbs.getModelVars();
407     const SBTreePositionCache&  pc = sbs.getTreePositionCache();
408     const Vec3  w_FM_M = Vec3(u[0], u[1], 0); // Angular velocity in M
409     const Vec3& v_FM   = Vec3::getAs(&u[2]);  // Linear velocity in F
410 
411     if (this->getUseEulerAngles(mv)) {
412         const Vec3& theta = this->fromQVec3(sbs.getQ(),0); // Euler angles
413         Vec3::updAs(qdot) = Rotation::convertAngVelInBodyFrameToBodyXYZDot(theta,
414                                         w_FM_M); // need w in *body*, not parent
415         Vec3::updAs(&qdot[3]) = v_FM;
416         qdot[6] = 0;
417     } else {
418         const Rotation& R_FM = this->getX_FM(pc).R();
419         const Vec4& quat = this->fromQuat(sbs.getQ());
420         Vec4::updAs(qdot)   = Rotation::convertAngVelToQuaternionDot(quat,
421                                         R_FM*w_FM_M); // need w in *parent* frame here
422         Vec3::updAs(&qdot[4]) = v_FM;
423     }
424 }
425 
calcQDotDot(const SBStateDigest & sbs,const Real * udot,Real * qdotdot)426 void calcQDotDot(
427     const SBStateDigest&   sbs,
428     const Real*            udot,
429     Real*                  qdotdot) const override
430 {
431     const SBModelVars&          mv = sbs.getModelVars();
432     const SBTreePositionCache&  pc = sbs.getTreePositionCache();
433     const Vec3  w_FM_M     = Vec3(this->fromU(sbs.getU())[0], this->fromU(sbs.getU())[1], 0); // Angular velocity of M in F, exp. in M
434     const Vec3& v_FM       = this->fromUVec3(sbs.getU(),2); // linear velocity of M in F, expressed in M
435     const Vec3  w_FM_M_dot = Vec3(udot[0], udot[1], 0);
436     const Vec3& v_FM_dot   = Vec3::getAs(&udot[2]);
437 
438     if (this->getUseEulerAngles(mv)) {
439         const Vec3& theta  = this->fromQVec3(sbs.getQ(),0); // Euler angles
440         Vec3::updAs(qdotdot) = Rotation::convertAngVelDotInBodyFrameToBodyXYZDotDot
441                                          (theta, w_FM_M, w_FM_M_dot); // needed in body frame here
442         Vec3::updAs(&qdotdot[3]) = v_FM_dot;
443         qdotdot[6] = 0;
444     } else {
445         const Rotation& R_FM = this->getX_FM(pc).R();
446         const Vec4& quat  = this->fromQuat(sbs.getQ());
447         Vec4::updAs(qdotdot)   = Rotation::convertAngVelDotToQuaternionDotDot
448                                          (quat,R_FM*w_FM_M,R_FM*w_FM_M_dot); // needed in parent frame
449         Vec3::updAs(&qdotdot[4]) = v_FM_dot;
450     }
451 }
452 
getMaxNQ()453 int  getMaxNQ()                   const override {return 7;}
getNQInUse(const SBModelVars & mv)454 int  getNQInUse(const SBModelVars& mv) const override {return this->getUseEulerAngles(mv) ? 6 : 7;}
isUsingQuaternion(const SBStateDigest & sbs,MobilizerQIndex & startOfQuaternion)455 bool isUsingQuaternion(const SBStateDigest& sbs, MobilizerQIndex& startOfQuaternion) const override {
456     if (this->getUseEulerAngles(sbs.getModelVars())) {startOfQuaternion.invalidate(); return false;}
457     startOfQuaternion = MobilizerQIndex(0); // quaternion comes first
458     return true;
459 }
460 
setMobilizerDefaultPositionValues(const SBModelVars & mv,Vector & q)461 void setMobilizerDefaultPositionValues(const SBModelVars& mv, Vector& q) const override
462 {
463     if (this->getUseEulerAngles(mv)) {
464         this->toQVec3(q,4) = Vec3(0); // TODO: kludge, clear unused element
465         this->toQ(q) = 0;
466     } else {
467         this->toQuat(q) = Vec4(1,0,0,0);
468         this->toQVec3(q,4) = 0;
469     }
470 }
471 
enforceQuaternionConstraints(const SBStateDigest & sbs,Vector & q,Vector & qErrest)472 bool enforceQuaternionConstraints(
473     const SBStateDigest& sbs,
474     Vector&             q,
475     Vector&             qErrest) const override
476 {
477     if (this->getUseEulerAngles(sbs.getModelVars()))
478         return false; // no change
479 
480     Vec4& quat = this->toQuat(q);
481     quat = quat / quat.norm();
482 
483     if (qErrest.size()) {
484         Vec4& qerr = this->toQuat(qErrest);
485         qerr -= dot(qerr,quat) * quat;
486     }
487 
488     return true;
489 }
490 
convertToEulerAngles(const Vector & inputQ,Vector & outputQ)491 void convertToEulerAngles(const Vector& inputQ, Vector& outputQ) const override
492 {
493     this->toQVec3(outputQ, 4) = Vec3(0); // clear unused element
494     this->toQVec3(outputQ, 3) = this->fromQVec3(inputQ, 4);
495     this->toQVec3(outputQ, 0) = Rotation(Quaternion(this->fromQuat(inputQ))).convertRotationToBodyFixedXYZ();
496 }
497 
convertToQuaternions(const Vector & inputQ,Vector & outputQ)498 void convertToQuaternions(const Vector& inputQ, Vector& outputQ) const override
499 {
500     this->toQVec3(outputQ, 4) = this->fromQVec3(inputQ, 3);
501     Rotation rot;
502     rot.setRotationToBodyFixedXYZ(this->fromQVec3(inputQ, 0));
503     this->toQuat(outputQ) = rot.convertRotationToQuaternion().asVec4();
504 }
505 
506 
507 };
508 
509 
510 
511 
512 #endif // SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_FREELINE_H_
513 
514