1 #ifndef SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_UNIVERSAL_H_
2 #define SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_UNIVERSAL_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: Derived from IVM code written by Charles Schwieters *
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 Universal mobilizer, also known
29 * as a UJoint or Hooke's joint.
30 */
31
32 #include "SimbodyMatterSubsystemRep.h"
33 #include "RigidBodyNode.h"
34 #include "RigidBodyNodeSpec.h"
35
36 // UNIVERSAL (U-JOINT, HOOKE'S JOINT) //
37
38 // This is a Universal Joint (U-Joint), also known as a Hooke's joint. This is
39 // identical to the joint that connects pieces of a driveshaft together under
40 // a car. Physically, you can think of this as a parent body P, hinged to a
41 // massless cross-shaped coupler, which is then hinged to the child body B. The
42 // massless coupler doesn't actually appear in the model. Instead, we use a
43 // body-fixed 1-2 Euler rotation sequence for orientation, which has the same
44 // effect: starting with frames B and P aligned (when q0=q1=0), rotate frame
45 // B about the Px(=Bx) axis by q0; then, rotate frame B further about the new
46 // By(!=Py) by q1. For generalized speeds u we use the Euler angle derivatives
47 // qdot, which are *not* the same as angular velocity components because u0 is
48 // a rotation rate around Px(!=Bx any more) while u1 is a rotation rate
49 // about By.
50 //
51 // To summarize,
52 // q's: a two-angle body-fixed rotation sequence about x, then new y
53 // u's: time derivatives of the q's
54 //
55 // Note that the U-Joint degrees of freedom relating the parent's F frame to
56 // the child's M frame are about x and y, with the "long" axis of the
57 // driveshaft along z.
58 template<bool noX_MB, bool noR_PF>
59 class RBNodeUJoint : public RigidBodyNodeSpec<2, false, noX_MB, noR_PF> {
60 public:
61 typedef typename RigidBodyNodeSpec<2, false, noX_MB, noR_PF>::HType HType;
type()62 virtual const char* type() { return "ujoint"; }
63
RBNodeUJoint(const MassProperties & mProps_B,const Transform & X_PF,const Transform & X_BM,bool isReversed,UIndex & nextUSlot,USquaredIndex & nextUSqSlot,QIndex & nextQSlot)64 RBNodeUJoint(const MassProperties& mProps_B,
65 const Transform& X_PF,
66 const Transform& X_BM,
67 bool isReversed,
68 UIndex& nextUSlot,
69 USquaredIndex& nextUSqSlot,
70 QIndex& nextQSlot)
71 : RigidBodyNodeSpec<2, false, noX_MB, noR_PF>(mProps_B,X_PF,X_BM,nextUSlot,nextUSqSlot,nextQSlot,
72 RigidBodyNode::QDotIsAlwaysTheSameAsU, RigidBodyNode::QuaternionIsNeverUsed,
73 isReversed)
74 {
75 this->updateSlots(nextUSlot,nextUSqSlot,nextQSlot);
76 }
77
setQToFitRotationImpl(const SBStateDigest & sbs,const Rotation & R_FM,Vector & q)78 void setQToFitRotationImpl(const SBStateDigest& sbs, const Rotation& R_FM,
79 Vector& q) const {
80 // The only rotations this joint can handle are about Mx and My.
81 // TODO: isn't there a better way to come up with "the rotation around x&y
82 // that best approximates a rotation R"? Here we're just hoping that the
83 // supplied rotation matrix can be decomposed into (x,y) rotations.
84 const Vec2 angles12 = R_FM.convertRotationToBodyFixedXY();
85 this->toQ(q) = angles12;
86 }
87
setQToFitTranslationImpl(const SBStateDigest & sbs,const Vec3 & p_FM,Vector & q)88 void setQToFitTranslationImpl(const SBStateDigest& sbs, const Vec3& p_FM,
89 Vector& q) const {
90 // M and F frame origins are always coincident for this mobilizer so there
91 // is no way to create a translation by rotating. So the only translation
92 // we can represent is 0.
93 }
94
95 // We can only express angular velocity that can be produced with our
96 // generalized speeds which are Fx and My rotations rates. So we'll take the
97 // supplied angular velocity expressed in F, project it on Fx and use that as
98 // the first generalized speed. Then take whatever angular velocity is
99 // unaccounted for, express it in M, and project onto My and use that as the
100 // second generalized speed.
setUToFitAngularVelocityImpl(const SBStateDigest & sbs,const Vector & q,const Vec3 & w_FM,Vector & u)101 void setUToFitAngularVelocityImpl(const SBStateDigest& sbs, const Vector& q,
102 const Vec3& w_FM, Vector& u) const {
103 const Rotation R_FM =
104 Rotation(BodyRotationSequence, this->fromQ(q)[0], XAxis, this->fromQ(q)[1], YAxis); // body fixed 1-2 sequence
105 const Vec3 wyz_FM_M = ~R_FM*Vec3(0,w_FM[1],w_FM[2]);
106 this->toU(u) = Vec2(w_FM[0], wyz_FM_M[1]);
107 }
108
setUToFitLinearVelocityImpl(const SBStateDigest & sbs,const Vector &,const Vec3 & v_FM,Vector & u)109 void setUToFitLinearVelocityImpl
110 (const SBStateDigest& sbs, const Vector&, const Vec3& v_FM, Vector& u) const
111 {
112 // M and F frame origins are always coincident for this mobilizer so there
113 // is no way to create a linear velocity by rotating. So the only linear
114 // velocity we can represent is 0.
115 }
116
117 enum {PoolSize=4}; // number of Reals
118 enum {CosQ=0, SinQ=2};
119 // We want space for cos(q01) and sin(q01).
calcQPoolSize(const SBModelVars &)120 int calcQPoolSize(const SBModelVars&) const
121 { return PoolSize; }
122
123 // Precalculation of sin/cos costs around 100 flops.
performQPrecalculations(const SBStateDigest & sbs,const Real * q,int nq,Real * qCache,int nQCache,Real * qErr,int nQErr)124 void performQPrecalculations(const SBStateDigest& sbs,
125 const Real* q, int nq,
126 Real* qCache, int nQCache,
127 Real* qErr, int nQErr) const
128 {
129 assert(q && nq==2 && qCache && nQCache==PoolSize && nQErr==0);
130 Vec2::updAs(&qCache[CosQ]) = Vec2(std::cos(q[0]),std::cos(q[1]));
131 Vec2::updAs(&qCache[SinQ]) = Vec2(std::sin(q[0]),std::sin(q[1]));
132 }
133
134 // TODO: should use precalculated sin/cos but doesn't.
calcX_FM(const SBStateDigest & sbs,const Real * q,int nq,const Real * qCache,int nQCache,Transform & X_FM)135 void calcX_FM(const SBStateDigest& sbs,
136 const Real* q, int nq,
137 const Real* qCache, int nQCache,
138 Transform& X_FM) const
139 {
140 assert(q && nq==2 && qCache && nQCache==PoolSize);
141 // TODO: use qCache
142 // We're only updating the orientation here because a U-joint
143 // can't translate. This is a body fixed X-Y sequence.
144 X_FM.updR() = Rotation(BodyRotationSequence, q[0], XAxis, q[1], YAxis);
145 X_FM.updP() = 0;
146 }
147
148 // The generalized speeds for this 2-dof rotational joint are the time
149 // derivatives of the body-fixed 1-2 rotation sequence defining the
150 // orientation. That is, the first speed is just a rotation rate about Fx. The
151 // second is a rotation rate about the current My, so we have to transform it
152 // into F to make H_FM uniformly expressed in F.
calcAcrossJointVelocityJacobian(const SBStateDigest & sbs,HType & H_FM)153 void calcAcrossJointVelocityJacobian(
154 const SBStateDigest& sbs,
155 HType& H_FM) const
156 {
157 // "upd" because we're realizing positions now
158 const SBTreePositionCache& pc = sbs.updTreePositionCache();
159 const Transform X_F0M0 = this->findX_F0M0(pc);
160
161 // Dropping the 0's here.
162 const Rotation& R_FM = X_F0M0.R();
163
164 H_FM(0) = SpatialVec( Vec3(1,0,0) , Vec3(0) );
165 H_FM(1) = SpatialVec( R_FM.y() , Vec3(0) );
166 }
167
168 // Since the second row of the Jacobian H_FM above is not constant in F,
169 // its time derivative is non zero. Here we use the fact that for
170 // a vector r_B_A fixed in a moving frame B but expressed in another frame A,
171 // its time derivative in A is the angular velocity of B in A crossed with
172 // the vector, i.e., d_A/dt r_B_A = w_AB % r_B_A.
calcAcrossJointVelocityJacobianDot(const SBStateDigest & sbs,HType & HDot_FM)173 void calcAcrossJointVelocityJacobianDot(
174 const SBStateDigest& sbs,
175 HType& HDot_FM) const
176 {
177 const SBTreePositionCache& pc = sbs.getTreePositionCache();
178 // "upd" because we're realizing velocities now
179 const SBTreeVelocityCache& vc = sbs.updTreeVelocityCache();
180
181 const Transform X_F0M0 = this->findX_F0M0(pc);
182
183 // Dropping the 0's here.
184 const Rotation& R_FM = X_F0M0.R();
185 const Vec3 w_FM = this->find_w_F0M0(pc,vc); // angular velocity of M in F
186
187 HDot_FM(0) = SpatialVec( Vec3(0) , Vec3(0) );
188 HDot_FM(1) = SpatialVec( w_FM % R_FM.y() , Vec3(0) );
189 }
190
191 };
192
193
194
195
196 #endif // SimTK_SIMBODY_RIGID_BODY_NODE_SPEC_UNIVERSAL_H_
197
198