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