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