1 #ifndef SimTK_SIMBODY_MOBILIZED_BODY_IMPL_H_
2 #define SimTK_SIMBODY_MOBILIZED_BODY_IMPL_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) 2007-12 Stanford University and the Authors.        *
13  * Authors: Michael Sherman                                                   *
14  * Contributors:                                                              *
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  *
29  * Private implementation of Body and MobilizedBody, and their included
30  * subclasses which represent the built-in body and mobilizer types.
31  */
32 
33 #include "SimTKcommon.h"
34 #include "simbody/internal/common.h"
35 #include "simbody/internal/Body.h"
36 #include "simbody/internal/MobilizedBody.h"
37 #include "simbody/internal/MobilizedBody_BuiltIns.h"
38 
39 #include "SimbodyMatterSubsystemRep.h"
40 #include "BodyRep.h"
41 #include "MotionImpl.h"
42 
43 class RigidBodyNode;
44 
45 namespace SimTK {
46 
47     /////////////////////////
48     // MOBILIZED BODY REPS //
49     /////////////////////////
50 
51 // This is what a MobilizedBody handle points to.
52 class MobilizedBodyImpl : public PIMPLImplementation<MobilizedBody,MobilizedBodyImpl> {
53 public:
MobilizedBodyImpl(MobilizedBody::Direction d)54     explicit MobilizedBodyImpl(MobilizedBody::Direction d)
55     :   defaultLockLevel(Motion::NoLevel),
56         reversed(d==MobilizedBody::Reverse),
57         myMatterSubsystemRep(0), myLevel(-1), myRBnode(0), hasChildren(false)
58     {
59     }
60 
setDirection(MobilizedBody::Direction d)61     void setDirection(MobilizedBody::Direction d) {
62         const bool wantReversed = (d==MobilizedBody::Reverse);
63         if (wantReversed != reversed) {
64             invalidateTopologyCache();
65             reversed = wantReversed;
66         }
67     }
68 
MobilizedBodyImpl(const MobilizedBodyImpl & src)69     MobilizedBodyImpl(const MobilizedBodyImpl& src) {
70         *this = src;
71         myRBnode = 0;
72     }
73 
74 
75     void lock(State& state, Motion::Level level) const;
76     void lockAt(State& state, int n, const Real* value, Motion::Level level) const;
77     void unlock(State& state) const;
78     Motion::Level getLockLevel(const State& state) const;
79     Vector getLockValueAsVector(const State& state) const;
lockByDefault(Motion::Level level)80     void lockByDefault(Motion::Level level)
81     {   invalidateTopologyCache(); defaultLockLevel=level; }
getLockByDefaultLevel()82     Motion::Level getLockByDefaultLevel() const
83     {   return defaultLockLevel; }
84 
85     // The matter subsystem must issue these MobilizedBody realize() calls in base-to-tip
86     // order, because these methods are allowed to assume that their parent (and
87     // ancestors) have already been realized.
88 
89     // eventually calls realizeTopologyVirtual()
90     const RigidBodyNode& realizeTopology(State& s, UIndex& nxtU, USquaredIndex& nxtUSq, QIndex& nxtQ) const;
91 
92     void realizeModel   (State&)       const; // eventually calls realizeModelVirtual()
93     void realizeInstance(const SBStateDigest&) const; // eventually calls realizeInstanceVirtual()
94     void realizeTime    (const SBStateDigest&) const; // eventually calls realizeTimeVirtual()
95     void realizePosition(const SBStateDigest&) const; // eventually calls realizePositionVirtual()
96     void realizeVelocity(const SBStateDigest&) const; // eventually calls realizeVelocityVirtual()
97     void realizeDynamics(const SBStateDigest&) const; // eventually calls realizeDynamicsVirtual()
98     void realizeAcceleration
99                         (const SBStateDigest&) const; // eventually calls realizeAccelerationVirtual()
100     void realizeReport  (const SBStateDigest&) const; // eventually calls realizeReportVirtual()
101 
~MobilizedBodyImpl()102     virtual ~MobilizedBodyImpl() {
103         delete myRBnode;
104     }
105     virtual MobilizedBodyImpl* clone() const = 0;
106 
107     // This creates a rigid body node using the appropriate mobilizer type.
108     // Called during realizeTopology().
109     virtual RigidBodyNode* createRigidBodyNode(
110         UIndex&        nextUSlot,
111         USquaredIndex& nextUSqSlot,
112         QIndex&        nextQSlot) const = 0;
113 
realizeTopologyVirtual(State &)114     virtual void realizeTopologyVirtual     (State&)        const {}
realizeModelVirtual(State &)115     virtual void realizeModelVirtual        (State&)        const {}
realizeInstanceVirtual(const State &)116     virtual void realizeInstanceVirtual     (const State&)  const {}
realizeTimeVirtual(const State &)117     virtual void realizeTimeVirtual         (const State&)  const {}
realizePositionVirtual(const State &)118     virtual void realizePositionVirtual     (const State&)  const {}
realizeVelocityVirtual(const State &)119     virtual void realizeVelocityVirtual     (const State&)  const {}
realizeDynamicsVirtual(const State &)120     virtual void realizeDynamicsVirtual     (const State&)  const {}
realizeAccelerationVirtual(const State &)121     virtual void realizeAccelerationVirtual (const State&)  const {}
realizeReportVirtual(const State &)122     virtual void realizeReportVirtual       (const State&)  const {}
123 
124     // Copy out nq default values for q, beginning at the indicated address.
125     // The concrete class should assert if nq is not a reasonable
126     // number for the kind of mobilizer; there is a bug somewhere in that case.
127     // This routine shouldn't be called directly -- call copyOutDefaultQ() below
128     // instead which has a nicer interface and does some error checking.
129     virtual void copyOutDefaultQImpl(int nq, Real* q) const = 0;
130 
calcDecorativeGeometryAndAppendImpl(const State & s,Stage stage,Array_<DecorativeGeometry> & geom)131     virtual void calcDecorativeGeometryAndAppendImpl
132        (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const
133     {
134         if (stage != Stage::Instance || !getMyMatterSubsystemRep().getShowDefaultGeometry())
135             return;
136         const Real scale = 1;
137         DecorativeFrame axes(scale/2);
138         axes.setLineThickness(2);
139         axes.setBodyId(myMobilizedBodyIndex);
140         geom.push_back(axes); // the body frame
141 
142         // Display the inboard joint frame (at half size), unless it is the
143         // same as the body frame. Then find the corresponding frame on the
144         // parent and display that in this body's color.
145         if (myMobilizedBodyIndex != 0) {
146             const Real pscale = 1;
147             const Transform& X_BM = getDefaultOutboardFrame(); // TODO: get from state
148             if (X_BM.p() != Vec3(0) || X_BM.R() != Mat33(1)) {
149                 DecorativeFrame frameOnChild(scale/4);
150                 frameOnChild.setBodyId(myMobilizedBodyIndex);
151                 frameOnChild.setColor(Red);
152                 frameOnChild.setTransform(X_BM);
153                 geom.push_back(frameOnChild);
154                 if (X_BM.p() != Vec3(0))
155                     geom.push_back(DecorativeLine(Vec3(0), X_BM.p()).setBodyId(myMobilizedBodyIndex));
156             }
157             const Transform& X_PF = getDefaultInboardFrame(); // TODO: from state
158             DecorativeFrame frameOnParent(pscale*Real(0.3)); // slightly larger than child
159             frameOnParent.setBodyId(myParentIndex);
160             frameOnParent.setColor(Blue);
161             frameOnParent.setTransform(X_PF);
162             geom.push_back(frameOnParent);
163             if (X_PF.p() != Vec3(0))
164                 geom.push_back(DecorativeLine(Vec3(0),X_PF.p()).setBodyId(myParentIndex));
165         }
166 
167         // Put a little purple wireframe sphere at the COM, and add a line from
168         // body origin to the com.
169 
170         DecorativeSphere com(scale*Real(.05));
171         com.setBodyId(myMobilizedBodyIndex);
172         com.setColor(Purple).setRepresentation(DecorativeGeometry::DrawPoints);
173         const Vec3& comPos_B = theBody.getDefaultRigidBodyMassProperties().getMassCenter(); // TODO: from state
174         com.setTransform(comPos_B);
175         geom.push_back(com);
176         if (comPos_B != Vec3(0))
177             geom.push_back(DecorativeLine(Vec3(0), comPos_B).setBodyId(myMobilizedBodyIndex));
178     }
179 
calcDecorativeGeometryAndAppend(const State & s,Stage stage,Array_<DecorativeGeometry> & geom)180     void calcDecorativeGeometryAndAppend
181        (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const
182     {
183         // We know how to deal with the topological (construction) geometry
184         // here. For bodies we can just draw it at topology stage. For mobilizers,
185         // we might not know the placement of the mobilizer frames on the parent
186         // and child bodies until Instance stage, at which point we can transform
187         // and then draw the topological geometry.
188         if (stage == Stage::Topology) {
189             appendTopologicalBodyGeometry(geom);
190         } else if (stage == Stage::Instance) {
191             const SimbodyMatterSubsystemRep& matterRep = getMyMatterSubsystemRep();
192             const Transform& X_PF = getInboardFrame(s);
193             const Transform& X_BM  = getOutboardFrame(s);
194             appendTopologicalMobilizerGeometry(X_BM, X_PF, geom);
195         }
196 
197         // Let the individual mobilizer deal with any complicated stuff.
198         calcDecorativeGeometryAndAppendImpl(s,stage,geom);
199     }
200 
addOutboardDecoration(const Transform & X_MD,const DecorativeGeometry & g)201     int addOutboardDecoration(const Transform& X_MD, const DecorativeGeometry& g) {
202         const int nxt = (int)outboardGeometry.size();
203         outboardGeometry.push_back(g); // make a new copy
204         // Combine the placement frame and the transform already in the geometry
205         // so we end up with geometry expressed directly in the M frame.
206         outboardGeometry.back().setTransform(X_MD*g.getTransform());
207         // Record the assigned ordinal (not in the same ordinal space as
208         // body decorations).
209         outboardGeometry.back().setIndexOnBody(nxt);
210         return nxt;
211     }
addInboardDecoration(const Transform & X_FD,const DecorativeGeometry & g)212     int addInboardDecoration(const Transform& X_FD, const DecorativeGeometry& g) {
213         const int nxt = (int)inboardGeometry.size();
214         inboardGeometry.push_back(g); // make a new copy
215         // Combine the placement frame and the transform already in the geometry
216         // so we end up with geometry expressed directly in the F frame.
217         inboardGeometry.back().setTransform(X_FD*g.getTransform());
218         // Record the assigned ordinal (not in the same ordinal space as
219         // body decorations).
220         inboardGeometry.back().setIndexOnBody(nxt);
221         return nxt;
222     }
223 
224 
225     void findMobilizerQs(const State&, QIndex& qStart, int& nq) const;
226     void findMobilizerUs(const State&, UIndex& uStart, int& nu) const;
227 
228 
229     Motion::Method getQMotionMethod(const State&) const;
230     Motion::Method getUMotionMethod(const State&) const;
231     Motion::Method getUDotMotionMethod(const State&) const;
232 
233     // Given the Model stage state variables (if any are relevant), each
234     // concrete mobilizer should return the number of q's and u's it expects
235     // to be allotted.
236     //virtual void getStateNeeds(const State& s, int& nq, int& nu) const = 0;
237 
238     // Given the Model stage variable values in the passed-in State (if
239     // any of them are relevant) copy out the appropriate default values
240     // to the appropriate slot in qDefault.
241     void copyOutDefaultQ(const State& s, Vector& qDefault) const;
242 
243     // Generic State-access routines (that is, those which can be handled in
244     // the MobilizedBody base class).
245 
246 
247     // Invalidate Stage::Position.
248     void setQToFitTransform(State& s, const Transform& X_FM) const;
249     void setQToFitRotation(State& s, const Rotation& R_FM) const;
250     void setQToFitTranslation(State& s, const Vec3& p_FM) const;
251 
252     // Invalidate Stage::Velocity.
253     void setUToFitVelocity(State& s, const SpatialVec& V_FM) const;
254     void setUToFitAngularVelocity(State& s, const Vec3& w_FM) const;
255     void setUToFitLinearVelocity(State& s, const Vec3& v_FM)  const;
256 
getBodyMassProperties(const State & s)257     const MassProperties& getBodyMassProperties(const State& s) const {
258         // TODO: these should come from the state if the body has variable mass props
259         const SBInstanceVars& iv = getMyMatterSubsystemRep().getInstanceVars(s);
260         return getMyRigidBodyNode().getMassProperties_OB_B();
261     }
262 
getBodySpatialInertiaInGround(const State & s)263     const SpatialInertia& getBodySpatialInertiaInGround(const State& s) const {
264         const SBTreePositionCache& tpc = getMyMatterSubsystemRep().getTreePositionCache(s);
265         return getMyRigidBodyNode().getMk_G(tpc);
266     }
267 
getInboardFrame(const State & s)268     const Transform& getInboardFrame (const State& s) const {
269         // TODO: these should come from the state if the mobilizer has variable frames
270         const SBInstanceVars& iv = getMyMatterSubsystemRep().getInstanceVars(s);
271         return getMyRigidBodyNode().getX_PF();
272     }
getOutboardFrame(const State & s)273     const Transform& getOutboardFrame(const State& s) const {
274         // TODO: these should come from the state if the mobilizer has variable frames
275         const SBInstanceVars& iv = getMyMatterSubsystemRep().getInstanceVars(s);
276         return getMyRigidBodyNode().getX_BM();
277     }
278 
setInboardFrame(State & s,const Transform & X_PF)279     void setInboardFrame (State& s, const Transform& X_PF) const {
280         assert(!"setInboardFrame(s) not implemented yet");
281     }
setOutboardFrame(State & s,const Transform & X_BM)282     void setOutboardFrame(State& s, const Transform& X_BM) const {
283         assert(!"setOutboardFrame(s) not implemented yet");
284     }
285 
getBodyTransform(const State & s)286     const Transform& getBodyTransform(const State& s) const {
287         const SBTreePositionCache& pc = getMyMatterSubsystemRep().getTreePositionCache(s);
288         return getMyRigidBodyNode().getX_GB(pc);
289     }
getBodyVelocity(const State & s)290     const SpatialVec& getBodyVelocity(const State& s) const {
291         const SBTreeVelocityCache& vc = getMyMatterSubsystemRep().getTreeVelocityCache(s);
292         return getMyRigidBodyNode().getV_GB(vc);
293     }
getBodyAcceleration(const State & s)294     const SpatialVec& getBodyAcceleration(const State& s) const {
295         const SBTreeAccelerationCache& ac = getMyMatterSubsystemRep().getTreeAccelerationCache(s);
296         return getMyRigidBodyNode().getA_GB(ac);
297     }
298 
getMobilizerTransform(const State & s)299     const Transform& getMobilizerTransform(const State& s) const {
300         const SBTreePositionCache& pc = getMyMatterSubsystemRep().getTreePositionCache(s);
301         return getMyRigidBodyNode().getX_FM(pc);
302     }
getMobilizerVelocity(const State & s)303     const SpatialVec& getMobilizerVelocity(const State& s) const {
304         const SBTreeVelocityCache& vc = getMyMatterSubsystemRep().getTreeVelocityCache(s);
305         return getMyRigidBodyNode().getV_FM(vc);
306     }
307 
getHCol(const State & s,MobilizerUIndex ux)308     SpatialVec getHCol(const State& s, MobilizerUIndex ux) const {
309         const SBTreePositionCache& pc = getMyMatterSubsystemRep().getTreePositionCache(s);
310         return getMyRigidBodyNode().getHCol(pc,ux);
311     }
312 
getH_FMCol(const State & s,MobilizerUIndex ux)313     SpatialVec getH_FMCol(const State& s, MobilizerUIndex ux) const {
314         const SBTreePositionCache& pc = getMyMatterSubsystemRep().getTreePositionCache(s);
315         return getMyRigidBodyNode().getH_FMCol(pc,ux);
316     }
317 
invalidateTopologyCache()318     void invalidateTopologyCache() const {
319         delete myRBnode; myRBnode=0;
320         if (myMatterSubsystemRep)
321             myMatterSubsystemRep->invalidateSubsystemTopologyCache();
322     }
323 
324     // This might get called *during* realizeTopology() so just make sure there is
325     // a node here without checking whether we're done with realizeTopology().
getMyRigidBodyNode()326     const RigidBodyNode& getMyRigidBodyNode() const {
327         SimTK_ASSERT(myRBnode && myMatterSubsystemRep,
328           "An operation on a MobilizedBody was illegal because realizeTopology() has "
329           "not been performed on the containing Subsystem since the last topological change."
330         );
331         return *myRBnode;
332     }
333 
getBody()334     const Body& getBody() const {return theBody;}
getDefaultInboardFrame()335     const Transform& getDefaultInboardFrame()  const {return defaultInboardFrame;}
getDefaultOutboardFrame()336     const Transform& getDefaultOutboardFrame() const {return defaultOutboardFrame;}
getDefaultRigidBodyMassProperties()337     const MassProperties& getDefaultRigidBodyMassProperties() const {
338         return theBody.getDefaultRigidBodyMassProperties();
339     }
340 
isReversed()341     bool isReversed() const {return reversed;}
342 
adoptMotion(Motion & ownerHandle)343     void adoptMotion(Motion& ownerHandle) {
344         SimTK_ERRCHK(!hasMotion(), "MobilizedBody::adoptMotion()",
345             "This MobilizedBody already has a Motion object associated with it.\n"
346             "Use clearMotion() first to replace an existing Motion object.");
347         ownerHandle.disown(motion); // transfer ownership to handle "motion"
348         invalidateTopologyCache();
349 
350         // Tell the Motion that it belongs to this MobilizedBody.
351         motion.updImpl().setMobilizedBodyImpl(this);
352     }
353 
clearMotion()354     void clearMotion() {
355         motion.clearHandle();
356         invalidateTopologyCache();
357     }
358 
hasMotion()359     bool hasMotion() const {return !motion.isEmptyHandle();}
360 
getMotion()361     const Motion& getMotion() const {
362         SimTK_ERRCHK(!motion.isEmptyHandle(),
363             "MobilizedBody::getMotion()",
364             "There is no Motion object associated with this MobilizedBody.");
365         return motion;
366     }
367 
getMySimbodyMatterSubsystem()368     const SimbodyMatterSubsystem& getMySimbodyMatterSubsystem() const {
369         return getMyMatterSubsystemRep().getMySimbodyMatterSubsystemHandle();
370     }
371 
getMyMatterSubsystemRep()372     const SimbodyMatterSubsystemRep& getMyMatterSubsystemRep() const {
373         SimTK_ASSERT(myMatterSubsystemRep,
374             "An operation was illegal because a MobilizedBody was not in a Subsystem.");
375         return *myMatterSubsystemRep;
376     }
updMyMatterSubsystemRep()377     SimbodyMatterSubsystemRep& updMyMatterSubsystemRep() {
378         SimTK_ASSERT(myMatterSubsystemRep,
379             "An operation was illegal because a MobilizedBody was not in a Subsystem.");
380         return *myMatterSubsystemRep;
381     }
382 
getMyModelInfo(const State & s)383     const SBModelPerMobodInfo& getMyModelInfo(const State& s) const {
384         const SimbodyMatterSubsystemRep& matterRep = getMyMatterSubsystemRep();
385         const SBModelCache& mc = matterRep.getModelCache(s);
386         const SBModelPerMobodInfo& info =
387             mc.getMobodModelInfo(myMobilizedBodyIndex);
388         return info;
389     }
390 
getMyInstanceInfo(const State & s)391     const SBInstancePerMobodInfo& getMyInstanceInfo(const State& s) const {
392         const SimbodyMatterSubsystemRep& matterRep = getMyMatterSubsystemRep();
393         const SBInstanceCache& ic = matterRep.getInstanceCache(s);
394         const SBInstancePerMobodInfo& info =
395             ic.getMobodInstanceInfo(myMobilizedBodyIndex);
396         return info;
397     }
398 
getMyHandle()399     const MobilizedBody& getMyHandle() const {
400         const MobilizedBody& mobod =
401             getMyMatterSubsystemRep().getMobilizedBody(getMyMobilizedBodyIndex());
402         SimTK_ASSERT(&mobod.getImpl() == this,
403             "A MobilizedBodyImpl's handle didn't refer back to the same Impl!");
404         return mobod;
405     }
406 
updMyHandle()407     MobilizedBody& updMyHandle() {
408         MobilizedBody& mobod =
409             updMyMatterSubsystemRep().updMobilizedBody(getMyMobilizedBodyIndex());
410         SimTK_ASSERT(&mobod.getImpl() == this,
411             "A MobilizedBodyImpl's handle didn't refer back to the same Impl!");
412         return mobod;
413     }
414 
getMyMobilizedBodyIndex()415     MobilizedBodyIndex getMyMobilizedBodyIndex() const {
416         assert(myMobilizedBodyIndex.isValid());
417         return myMobilizedBodyIndex;
418     }
419 
getMyParentMobilizedBodyIndex()420     MobilizedBodyIndex getMyParentMobilizedBodyIndex() const {
421         assert(myParentIndex.isValid());
422         return myParentIndex;
423     }
424 
getMyBaseBodyMobilizedBodyIndex()425     MobilizedBodyIndex getMyBaseBodyMobilizedBodyIndex() const {
426         assert(myBaseBodyIndex.isValid());
427         return myBaseBodyIndex;
428     }
429 
getMyLevel()430     int getMyLevel() const {
431         assert(myLevel >= 0);
432         return myLevel;
433     }
434 
isInSubsystem()435     bool isInSubsystem() const {return myMatterSubsystemRep != 0;}
436 
setMyMatterSubsystem(SimbodyMatterSubsystem & matter,MobilizedBodyIndex parentIndex,MobilizedBodyIndex index)437     void setMyMatterSubsystem(SimbodyMatterSubsystem& matter,
438                               MobilizedBodyIndex  parentIndex,
439                               MobilizedBodyIndex  index)
440     {
441         // If the subsystem is already set it must be the same one.
442         assert(!myMatterSubsystemRep || myMatterSubsystemRep==&matter.getRep());
443         myMatterSubsystemRep = &matter.updRep();
444 
445         assert(index.isValid());
446         assert(parentIndex.isValid() || index==GroundIndex);
447 
448         myParentIndex           = parentIndex; // invalid if this is Ground
449         myMobilizedBodyIndex    = index;
450 
451         if (index != GroundIndex) {
452             MobilizedBody& parent = matter.updMobilizedBody(parentIndex);
453             myLevel = parent.getLevelInMultibodyTree() + 1;
454             myBaseBodyIndex = (myLevel == 1 ? myMobilizedBodyIndex
455                                          : parent.getBaseMobilizedBody().getMobilizedBodyIndex());
456             parent.updImpl().hasChildren = true;
457         } else {
458             myLevel = 0;
459             myBaseBodyIndex = GroundIndex;
460         }
461     }
462 
463 private:
464     // Body topological geometry is defined with respect to the body frame so we
465     // can draw it right away.
appendTopologicalBodyGeometry(Array_<DecorativeGeometry> & geom)466     void appendTopologicalBodyGeometry(Array_<DecorativeGeometry>& geom) const {
467         getBody().getRep().appendDecorativeGeometry(getMyMobilizedBodyIndex(), geom);
468     }
469 
470     // Mobilizer topological geometry is defined with respect to the M (outboard, child)
471     // frame and the F (inboard, parent) frame. The placement of those frames with
472     // respect to the body frame can be Instance variables, so we can't draw this geometry
473     // until Instance stage. At that point we can find M and F, so they are passed in
474     // here.
appendTopologicalMobilizerGeometry(const Transform & X_BM,const Transform & X_PF,Array_<DecorativeGeometry> & geom)475     void appendTopologicalMobilizerGeometry(const Transform& X_BM, const Transform& X_PF,
476                                             Array_<DecorativeGeometry>& geom) const
477     {
478         for (int i=0; i<(int)outboardGeometry.size(); ++i) {
479             geom.push_back(outboardGeometry[i]);
480             geom.back().setBodyId(getMyMobilizedBodyIndex())
481                        .setTransform(X_BM*outboardGeometry[i].getTransform());
482         }
483         for (int i=0; i<(int)inboardGeometry.size(); ++i) {
484             geom.push_back(inboardGeometry[i]);
485             geom.back().setBodyId(getMyParentMobilizedBodyIndex())
486                        .setTransform(X_PF*inboardGeometry[i].getTransform());
487         }
488     }
489 
490 private:
491     friend class MobilizedBody;
492 
493         // TOPOLOGY "STATE"
494 
495     // Base class topological properties. Derived MobilizedBodies may
496     // have further topological properties. Whenever these change, be
497     // sure to set topologyRealized=false!
498 
499     // Body represents the mass structure, mass properties, and possibly some
500     // decorative geometry for the body associated with this (body,mobilizer)
501     // pair.
502     Body            theBody;
503     Transform       defaultInboardFrame;  // default for F (in Parent frame)
504     Transform       defaultOutboardFrame; // default for M (in Body frame)
505     Motion::Level   defaultLockLevel;
506 
507     // A Motion object, if present, defines how this mobilizer's motion is
508     // to be calculated. Otherwise, the motion is determined dynamically
509     // as a result of forces and constraints. A Motion always prescribes
510     // \e all of the mobilizer's generalized accelerations udot (index 1);
511     // may also some of the prescribed generalized speeds u (index 2); and
512     // for speeds that are prescribed it may also prescribe the corresponding
513     // generalized coordinates q (index 3).
514     Motion          motion;
515 
516     bool        reversed; // is the mobilizer defined from M to F?
517 
518     Array_<DecorativeGeometry> outboardGeometry;
519     Array_<DecorativeGeometry> inboardGeometry;
520 
521     // These data members are filled in once the MobilizedBody is added to
522     // a MatterSubsystem. Note that this pointer is just a reference to
523     // the Subsystem which owns this MobilizedBody -- we don't own the
524     // heap space here so don't delete it!
525     SimbodyMatterSubsystemRep* myMatterSubsystemRep;
526     MobilizedBodyIndex  myMobilizedBodyIndex; // index within the subsystem
527     MobilizedBodyIndex  myParentIndex;   // Invalid if this body is Ground, otherwise the parent's index
528     MobilizedBodyIndex  myBaseBodyIndex; // GroundIndex if this is ground, otherwise a level-1 BodyIndex
529     int                 myLevel;      // Distance from ground in multibody graph (0 if this is ground,
530                                       //   1 if a base body, 2 if parent is a base body, etc.)
531 
532         // TOPOLOGY "CACHE"
533 
534     // A RigidBodyNode is created for each MobilizedBody during realizeTopology().
535     // Think of it as the *computational* form of the MobilizedBody; the MobilizedBody
536     // itself exists to make a nice API for the programmer. In fact is is possible
537     // for several different MobilizedBody classes to use the same underlying
538     // computational form while providing a different API.
539     //
540     // This is a pointer to an abstract object whose heap space is *owned* by
541     // this MobilizedBody. Be sure to delete it upon destruction and whenever
542     // topology is re-realized.
543     mutable RigidBodyNode* myRBnode;
544 
545 protected:
546     // Keep track of whether this body has any children.
547     bool hasChildren;
548 };
549 
550 class MobilizedBody::PinImpl : public MobilizedBodyImpl {
551 public:
PinImpl(Direction d)552     explicit PinImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()553     PinImpl* clone() const override { return new PinImpl(*this); }
554 
555     RigidBodyNode* createRigidBodyNode(
556         UIndex&        nextUSlot,
557         USquaredIndex& nextUSqSlot,
558         QIndex&        nextQSlot) const override;
559 
copyOutDefaultQImpl(int nq,Real * q)560     void copyOutDefaultQImpl(int nq, Real* q) const override {
561         SimTK_ASSERT(nq==1,
562             "MobilizedBody::PinImpl::copyOutDefaultQImpl(): wrong number of q's");
563         *q = defaultQ;
564     }
565 
566     SimTK_DOWNCAST(PinImpl, MobilizedBodyImpl);
567 private:
568     friend class MobilizedBody::Pin;
569     Real defaultQ;  // the angle in radians
570 };
571 
572 
573 class MobilizedBody::SliderImpl : public MobilizedBodyImpl {
574 public:
SliderImpl(Direction d)575     explicit SliderImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()576     SliderImpl* clone() const override { return new SliderImpl(*this); }
577 
578     RigidBodyNode* createRigidBodyNode(
579         UIndex&        nextUSlot,
580         USquaredIndex& nextUSqSlot,
581         QIndex&        nextQSlot) const override;
582 
copyOutDefaultQImpl(int nq,Real * q)583     void copyOutDefaultQImpl(int nq, Real* q) const override {
584         SimTK_ASSERT(nq==1,
585             "MobilizedBody::SliderImpl::copyOutDefaultQImpl(): wrong number of q's");
586         *q = defaultQ;
587     }
588 
589     SimTK_DOWNCAST(SliderImpl, MobilizedBodyImpl);
590 private:
591     friend class MobilizedBody::Slider;
592     Real defaultQ;  // the displacement
593 };
594 
595 class MobilizedBody::UniversalImpl : public MobilizedBodyImpl {
596 public:
UniversalImpl(Direction d)597     explicit UniversalImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()598     UniversalImpl* clone() const override { return new UniversalImpl(*this); }
599 
600     RigidBodyNode* createRigidBodyNode(
601         UIndex&        nextUSlot,
602         USquaredIndex& nextUSqSlot,
603         QIndex&        nextQSlot) const override;
604 
copyOutDefaultQImpl(int nq,Real * q)605     void copyOutDefaultQImpl(int nq, Real* q) const override {
606         SimTK_ASSERT(nq==2,
607             "MobilizedBody::UniversalImpl::copyOutDefaultQImpl(): wrong number of q's");
608         Vec2::updAs(q) = defaultQ;
609     }
610 
611     SimTK_DOWNCAST(UniversalImpl, MobilizedBodyImpl);
612 private:
613     friend class MobilizedBody::Universal;
614     Vec2 defaultQ;  // the two angles
615 };
616 
617 class MobilizedBody::CylinderImpl : public MobilizedBodyImpl {
618 public:
CylinderImpl(Direction d)619     explicit CylinderImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()620     CylinderImpl* clone() const override { return new CylinderImpl(*this); }
621 
622     RigidBodyNode* createRigidBodyNode(
623         UIndex&        nextUSlot,
624         USquaredIndex& nextUSqSlot,
625         QIndex&        nextQSlot) const override;
626 
copyOutDefaultQImpl(int nq,Real * q)627     void copyOutDefaultQImpl(int nq, Real* q) const override {
628         SimTK_ASSERT(nq==2,
629             "MobilizedBody::CylinderImpl::copyOutDefaultQImpl(): wrong number of q's");
630         Vec2::updAs(q) = defaultQ;
631     }
632 
633     SimTK_DOWNCAST(CylinderImpl, MobilizedBodyImpl);
634 private:
635     friend class MobilizedBody::Cylinder;
636     Vec2 defaultQ;  // the angle in radians followed by the displacement
637 };
638 
639 class MobilizedBody::BendStretchImpl : public MobilizedBodyImpl {
640 public:
BendStretchImpl(Direction d)641     explicit BendStretchImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()642     BendStretchImpl* clone() const override { return new BendStretchImpl(*this); }
643 
644     RigidBodyNode* createRigidBodyNode(
645         UIndex&        nextUSlot,
646         USquaredIndex& nextUSqSlot,
647         QIndex&        nextQSlot) const override;
648 
copyOutDefaultQImpl(int nq,Real * q)649     void copyOutDefaultQImpl(int nq, Real* q) const override {
650         SimTK_ASSERT(nq==2,
651             "MobilizedBody::BendStretchImpl::copyOutDefaultQImpl(): wrong number of q's");
652         Vec2::updAs(q) = defaultQ;
653     }
654 
655     SimTK_DOWNCAST(BendStretchImpl, MobilizedBodyImpl);
656 private:
657     friend class MobilizedBody::BendStretch;
658     Vec2 defaultQ;  // the angle in radians followed by the displacement
659 };
660 
661 class MobilizedBody::PlanarImpl : public MobilizedBodyImpl {
662 public:
PlanarImpl(Direction d)663     explicit PlanarImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()664     PlanarImpl* clone() const override { return new PlanarImpl(*this); }
665 
666     RigidBodyNode* createRigidBodyNode(
667         UIndex&        nextUSlot,
668         USquaredIndex& nextUSqSlot,
669         QIndex&        nextQSlot) const override;
670 
copyOutDefaultQImpl(int nq,Real * q)671     void copyOutDefaultQImpl(int nq, Real* q) const override {
672         SimTK_ASSERT(nq==3,
673             "MobilizedBody::PlanarImpl::copyOutDefaultQImpl(): wrong number of q's");
674         Vec3::updAs(q) = defaultQ;
675     }
676 
677     SimTK_DOWNCAST(PlanarImpl, MobilizedBodyImpl);
678 private:
679     friend class MobilizedBody::Planar;
680     Vec3 defaultQ;  // the angle in radians followed by the two displacements
681 };
682 
683 class MobilizedBody::SphericalCoordsImpl : public MobilizedBodyImpl {
684 public:
SphericalCoordsImpl(Direction d)685     explicit SphericalCoordsImpl(Direction d)
686     :   MobilizedBodyImpl(d), az0(0), ze0(0), axisT(ZAxis),
687         negAz(false), negZe(false), negT(false),
688         defaultQ(0) {}
689 
SphericalCoordsImpl(Real az0,bool negAz,Real ze0,bool negZe,CoordinateAxis axisT,bool negT,Direction d)690     SphericalCoordsImpl(Real az0, bool negAz,
691                         Real ze0, bool negZe,
692                         CoordinateAxis axisT, bool negT,
693                         Direction d)
694     :   MobilizedBodyImpl(d), az0(az0), ze0(ze0), axisT(axisT),
695         negAz(negAz), negZe(negZe), negT(negT),
696         defaultQ(0) {}
697 
clone()698     SphericalCoordsImpl* clone() const override { return new SphericalCoordsImpl(*this); }
699 
700     RigidBodyNode* createRigidBodyNode(
701         UIndex&        nextUSlot,
702         USquaredIndex& nextUSqSlot,
703         QIndex&        nextQSlot) const override;
704 
copyOutDefaultQImpl(int nq,Real * q)705     void copyOutDefaultQImpl(int nq, Real* q) const override {
706         SimTK_ASSERT(nq==3,
707             "MobilizedBody::SphericalCoordsImpl::copyOutDefaultQImpl(): wrong number of q's");
708         Vec3::updAs(q) = defaultQ;
709     }
710 
711     SimTK_DOWNCAST(SphericalCoordsImpl, MobilizedBodyImpl);
712 private:
713     friend class MobilizedBody::SphericalCoords;
714 
715     Real              az0, ze0;               // angle offsets
716     CoordinateAxis    axisT;                  // translation axis (X or Z)
717     bool              negAz, negZe, negT;     // true if we should negate the corresponding coordinate
718 
719     Vec3 defaultQ;  // two angles in radians followed by a displacement
720 };
721 
722 class MobilizedBody::GimbalImpl : public MobilizedBodyImpl {
723 public:
GimbalImpl(Direction d)724     explicit GimbalImpl(Direction d)
725     :   MobilizedBodyImpl(d), defaultRadius(Real(0.1)), defaultQ(0) { }
clone()726     GimbalImpl* clone() const override { return new GimbalImpl(*this); }
727 
728     RigidBodyNode* createRigidBodyNode(
729         UIndex&        nextUSlot,
730         USquaredIndex& nextUSqSlot,
731         QIndex&        nextQSlot) const override;
732 
copyOutDefaultQImpl(int nq,Real * q)733     void copyOutDefaultQImpl(int nq, Real* q) const override {
734         SimTK_ASSERT(nq==3,
735             "MobilizedBody::GimbalImpl::copyOutDefaultQImpl(): wrong number of q's");
736         Vec3::updAs(q) = defaultQ;
737     }
738 
739     void calcDecorativeGeometryAndAppendImpl
740        (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const override;
741 
setDefaultRadius(Real r)742     void setDefaultRadius(Real r) {
743         assert(r>0);
744         invalidateTopologyCache();
745         defaultRadius=r;
746     }
getDefaultRadius()747     Real getDefaultRadius() const {return defaultRadius;}
748 
749     SimTK_DOWNCAST(GimbalImpl, MobilizedBodyImpl);
750 private:
751     friend class MobilizedBody::Gimbal;
752     Real defaultRadius;   // used for visualization only
753     Vec3 defaultQ;  // the three angles in radians
754 };
755 
756 class MobilizedBody::BushingImpl : public MobilizedBodyImpl {
757 public:
BushingImpl(Direction d)758     explicit BushingImpl(Direction d)
759     :   MobilizedBodyImpl(d), defaultQ(0) { }
clone()760     BushingImpl* clone() const override { return new BushingImpl(*this); }
761 
762     RigidBodyNode* createRigidBodyNode(
763         UIndex&        nextUSlot,
764         USquaredIndex& nextUSqSlot,
765         QIndex&        nextQSlot) const override;
766 
copyOutDefaultQImpl(int nq,Real * q)767     void copyOutDefaultQImpl(int nq, Real* q) const override {
768         SimTK_ASSERT(nq==6,
769             "MobilizedBody::BushingImpl::copyOutDefaultQImpl(): wrong number of q's");
770         Vec6::updAs(q) = defaultQ;
771     }
772 
773     SimTK_DOWNCAST(BushingImpl, MobilizedBodyImpl);
774 private:
775     friend class MobilizedBody::Bushing;
776     Vec6 defaultQ;  // 3 angles in radians, then p_FM
777 };
778 
779 class MobilizedBody::BallImpl : public MobilizedBodyImpl {
780 public:
BallImpl(Direction d)781     explicit BallImpl(Direction d)
782     :   MobilizedBodyImpl(d), defaultRadius(Real(0.1)), defaultQ() {} // (1,0,0,0), the identity rotation
clone()783     BallImpl* clone() const override { return new BallImpl(*this); }
784 
785     RigidBodyNode* createRigidBodyNode(
786         UIndex&        nextUSlot,
787         USquaredIndex& nextUSqSlot,
788         QIndex&        nextQSlot) const override;
789 
copyOutDefaultQImpl(int nq,Real * q)790     void copyOutDefaultQImpl(int nq, Real* q) const override {
791         SimTK_ASSERT(nq==4||nq==3,
792             "MobilizedBody::BallImpl::copyOutDefaultQImpl(): wrong number of q's");
793         if (nq==4)
794             Vec4::updAs(q) = defaultQ.asVec4();
795         else
796             Vec3::updAs(q) = Rotation(defaultQ).convertRotationToBodyFixedXYZ();
797     }
798 
799     void calcDecorativeGeometryAndAppendImpl
800        (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const override;
801 
setDefaultRadius(Real r)802     void setDefaultRadius(Real r) {
803         assert(r>0);
804         invalidateTopologyCache();
805         defaultRadius=r;
806     }
getDefaultRadius()807     Real getDefaultRadius() const {return defaultRadius;}
808 
809     SimTK_DOWNCAST(BallImpl, MobilizedBodyImpl);
810 private:
811     friend class MobilizedBody::Ball;
812     Real defaultRadius;   // used for visualization only
813     Quaternion defaultQ;  // the default orientation
814 };
815 
816 class MobilizedBody::EllipsoidImpl : public MobilizedBodyImpl {
817 public:
EllipsoidImpl(Direction d)818     explicit EllipsoidImpl(Direction d)
819     :   MobilizedBodyImpl(d), defaultRadii(Real(0.5),Real(1./3.),Real(0.25)),
820         defaultQ() { } // default is (1,0,0,0), the identity rotation
clone()821     EllipsoidImpl* clone() const override { return new EllipsoidImpl(*this); }
822 
823     RigidBodyNode* createRigidBodyNode(
824         UIndex&        nextUSlot,
825         USquaredIndex& nextUSqSlot,
826         QIndex&        nextQSlot) const override;
827 
copyOutDefaultQImpl(int nq,Real * q)828     void copyOutDefaultQImpl(int nq, Real* q) const override {
829         SimTK_ASSERT(nq==4||nq==3,
830             "MobilizedBody::EllipsoidImpl::copyOutDefaultQImpl(): wrong number of q's");
831         if (nq==4)
832             Vec4::updAs(q) = defaultQ.asVec4();
833         else
834             Vec3::updAs(q) = Rotation(defaultQ).convertRotationToBodyFixedXYZ();
835     }
836 
837     void calcDecorativeGeometryAndAppendImpl
838        (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const override;
839 
setDefaultRadii(const Vec3 & r)840     void setDefaultRadii(const Vec3& r) {
841         assert(r[0]>0 && r[1]>0 && r[2]>0);
842         invalidateTopologyCache();
843         defaultRadii=r;
844     }
getDefaultRadii()845     const Vec3& getDefaultRadii() const {return defaultRadii;}
846 
847     SimTK_DOWNCAST(EllipsoidImpl, MobilizedBodyImpl);
848 private:
849     friend class MobilizedBody::Ellipsoid;
850     Vec3 defaultRadii;    // used for visualization only
851     Quaternion defaultQ;  // the default orientation
852 };
853 
854 class MobilizedBody::TranslationImpl : public MobilizedBodyImpl {
855 public:
TranslationImpl(Direction d)856     explicit TranslationImpl(Direction d) : MobilizedBodyImpl(d), defaultQ(0) { }
clone()857     TranslationImpl* clone() const override { return new TranslationImpl(*this); }
858 
859     RigidBodyNode* createRigidBodyNode(
860         UIndex&        nextUSlot,
861         USquaredIndex& nextUSqSlot,
862         QIndex&        nextQSlot) const override;
863 
copyOutDefaultQImpl(int nq,Real * q)864     void copyOutDefaultQImpl(int nq, Real* q) const override {
865         SimTK_ASSERT(nq==3,
866             "MobilizedBody::TranslationImpl::copyOutDefaultQImpl(): wrong number of q's");
867         Vec3::updAs(q) = defaultQ;
868     }
869 
870     SimTK_DOWNCAST(TranslationImpl, MobilizedBodyImpl);
871 private:
872     friend class MobilizedBody::Translation;
873     Vec3 defaultQ;  // the three displacements
874 };
875 
876 class MobilizedBody::FreeImpl : public MobilizedBodyImpl {
877 public:
FreeImpl(Direction d)878     explicit FreeImpl(Direction d) : MobilizedBodyImpl(d), defaultQOrientation(), defaultQTranslation(0) { }
clone()879     FreeImpl* clone() const override { return new FreeImpl(*this); }
880 
881     RigidBodyNode* createRigidBodyNode(
882         UIndex&        nextUSlot,
883         USquaredIndex& nextUSqSlot,
884         QIndex&        nextQSlot) const override;
885 
copyOutDefaultQImpl(int nq,Real * q)886     void copyOutDefaultQImpl(int nq, Real* q) const override {
887         SimTK_ASSERT(nq==7||nq==6,
888             "MobilizedBody::FreeImpl::copyOutDefaultQImpl(): wrong number of q's");
889         if (nq==7) {
890             Vec4::updAs(q)   = defaultQOrientation.asVec4();
891             Vec3::updAs(q+4) = defaultQTranslation;
892         } else {
893             Vec3::updAs(q)   = Rotation(defaultQOrientation).convertRotationToBodyFixedXYZ();
894             Vec3::updAs(q+3) = defaultQTranslation;
895         }
896     }
897 
898     SimTK_DOWNCAST(FreeImpl, MobilizedBodyImpl);
899 private:
900     friend class MobilizedBody::Free;
901     Quaternion defaultQOrientation;
902     Vec3       defaultQTranslation;
903 };
904 
905 class MobilizedBody::LineOrientationImpl : public MobilizedBodyImpl {
906 public:
LineOrientationImpl(Direction d)907     explicit LineOrientationImpl(Direction d) : MobilizedBodyImpl(d), defaultQ() { } // 1,0,0,0
clone()908     LineOrientationImpl* clone() const override { return new LineOrientationImpl(*this); }
909 
910     RigidBodyNode* createRigidBodyNode(
911         UIndex&        nextUSlot,
912         USquaredIndex& nextUSqSlot,
913         QIndex&        nextQSlot) const override;
914 
copyOutDefaultQImpl(int nq,Real * q)915     void copyOutDefaultQImpl(int nq, Real* q) const override {
916         SimTK_ASSERT(nq==4||nq==3,
917             "MobilizedBody::LineOrientationImpl::copyOutDefaultQImpl(): wrong number of q's");
918         if (nq==4)
919             Vec4::updAs(q) = defaultQ.asVec4();
920         else
921             Vec3::updAs(q) = Rotation(defaultQ).convertRotationToBodyFixedXYZ();
922     }
923 
924     SimTK_DOWNCAST(LineOrientationImpl, MobilizedBodyImpl);
925 private:
926     friend class MobilizedBody::LineOrientation;
927     Quaternion defaultQ;
928 };
929 
930 class MobilizedBody::FreeLineImpl : public MobilizedBodyImpl {
931 public:
FreeLineImpl(Direction d)932     explicit FreeLineImpl(Direction d) : MobilizedBodyImpl(d), defaultQOrientation(), defaultQTranslation(0) { }
clone()933     FreeLineImpl* clone() const override { return new FreeLineImpl(*this); }
934 
935     RigidBodyNode* createRigidBodyNode(
936         UIndex&        nextUSlot,
937         USquaredIndex& nextUSqSlot,
938         QIndex&        nextQSlot) const override;
939 
copyOutDefaultQImpl(int nq,Real * q)940     void copyOutDefaultQImpl(int nq, Real* q) const override {
941         SimTK_ASSERT(nq==7||nq==6,
942             "MobilizedBody::FreeLineImpl::copyOutDefaultQImpl(): wrong number of q's");
943         if (nq==7) {
944             Vec4::updAs(q)   = defaultQOrientation.asVec4();
945             Vec3::updAs(q+4) = defaultQTranslation;
946         } else {
947             Vec3::updAs(q)   = Rotation(defaultQOrientation).convertRotationToBodyFixedXYZ();
948             Vec3::updAs(q+3) = defaultQTranslation;
949         }
950     }
951 
952     SimTK_DOWNCAST(FreeLineImpl, MobilizedBodyImpl);
953 private:
954     friend class MobilizedBody::FreeLine;
955     Quaternion defaultQOrientation;
956     Vec3       defaultQTranslation;
957 };
958 
959 class MobilizedBody::WeldImpl : public MobilizedBodyImpl {
960 public:
WeldImpl(Direction d)961     explicit WeldImpl(Direction d) : MobilizedBodyImpl(d) { }
clone()962     WeldImpl* clone() const override { return new WeldImpl(*this); }
963 
964     RigidBodyNode* createRigidBodyNode(
965         UIndex&        nextUSlot,
966         USquaredIndex& nextUSqSlot,
967         QIndex&        nextQSlot) const override;
968 
copyOutDefaultQImpl(int nq,Real * q)969     void copyOutDefaultQImpl(int nq, Real* q) const override {
970         SimTK_ASSERT(nq==0,
971             "MobilizedBody::WeldImpl::copyOutDefaultQImpl(): wrong number of q's");
972     }
973 
974     SimTK_DOWNCAST(WeldImpl, MobilizedBodyImpl);
975 private:
976     friend class MobilizedBody::Weld;
977     // no q's
978 };
979 
980 class MobilizedBody::GroundImpl : public MobilizedBodyImpl {
981 public:
GroundImpl()982     GroundImpl() : MobilizedBodyImpl(MobilizedBody::Forward) { }
clone()983     GroundImpl* clone() const override { return new GroundImpl(*this); }
984 
985     RigidBodyNode* createRigidBodyNode(
986         UIndex&        nextUSlot,
987         USquaredIndex& nextUSqSlot,
988         QIndex&        nextQSlot) const override;
989 
copyOutDefaultQImpl(int nq,Real * q)990     void copyOutDefaultQImpl(int nq, Real* q) const override {
991         SimTK_ASSERT(nq==0,
992             "MobilizedBody::GroundImpl::copyOutDefaultQImpl(): wrong number of q's");
993     }
994 
995     SimTK_DOWNCAST(GroundImpl, MobilizedBodyImpl);
996 private:
997     friend class MobilizedBody::Ground;
998     // no q's
999 };
1000 
1001 class MobilizedBody::ScrewImpl : public MobilizedBodyImpl {
1002 public:
ScrewImpl(Real p,Direction d)1003     ScrewImpl(Real p, Direction d) : MobilizedBodyImpl(d), defaultPitch(p), defaultQ(0) { }
1004 
getDefaultPitch()1005     Real getDefaultPitch() const {return defaultPitch;}
setDefaultPitch(Real p)1006     void setDefaultPitch(Real p) {
1007         invalidateTopologyCache();
1008         defaultPitch=p;
1009     }
1010 
clone()1011     ScrewImpl* clone() const override { return new ScrewImpl(*this); }
1012 
1013     RigidBodyNode* createRigidBodyNode(
1014         UIndex&        nextUSlot,
1015         USquaredIndex& nextUSqSlot,
1016         QIndex&        nextQSlot) const override;
1017 
copyOutDefaultQImpl(int nq,Real * q)1018     void copyOutDefaultQImpl(int nq, Real* q) const override {
1019         SimTK_ASSERT(nq==1,
1020             "MobilizedBody::ScrewImpl::copyOutDefaultQImpl(): wrong number of q's");
1021         *q = defaultQ;
1022     }
1023 
1024     SimTK_DOWNCAST(ScrewImpl, MobilizedBodyImpl);
1025 private:
1026     friend class MobilizedBody::Screw;
1027     Real defaultPitch;
1028     Real defaultQ; // the angle in radians
1029 };
1030 
1031 /////////////////////////////////////////////////
1032 // MOBILIZED BODY::CUSTOM::IMPLEMENTATION IMPL //
1033 /////////////////////////////////////////////////
1034 
1035 // This class exists primarily to allow the Custom::Implementation class to keep
1036 // a pointer to its handle class's CustomImpl class which is derived from MobilizedBodyImpl
1037 // which has all the goodies that are needed for defining a MobilizedBody.
1038 //
1039 // At first this class is the owner of the CustomImpl. Then when this is put in a
1040 // Custom handle, that handle takes over ownership of the CustomImpl and the
1041 // CustomImpl takes over ownership of this ImplementationImpl object.
1042 class MobilizedBody::Custom::ImplementationImpl : public PIMPLImplementation<Implementation, ImplementationImpl> {
1043 public:
1044     // no default constructor
ImplementationImpl(CustomImpl * customImpl,int nu,int nq,int nAngles)1045     explicit ImplementationImpl(CustomImpl* customImpl, int nu, int nq, int nAngles) : isOwner(true), builtInImpl(customImpl), nu(nu), nq(nq), nAngles(nAngles) { }
1046     inline ~ImplementationImpl(); // see below -- have to wait for CustomImpl's definition
1047 
1048     // Copying one of these just gives us a new one with a NULL CustomImpl pointer.
ImplementationImpl(const ImplementationImpl & src)1049     ImplementationImpl(const ImplementationImpl& src) : isOwner(false), builtInImpl(0), nu(src.nu), nq(src.nq), nAngles(src.nAngles) { }
1050 
clone()1051     ImplementationImpl* clone() const {return new ImplementationImpl(*this);}
1052 
isOwnerOfCustomImpl()1053     bool isOwnerOfCustomImpl() const {return builtInImpl && isOwner;}
removeOwnershipOfCustomImpl()1054     CustomImpl* removeOwnershipOfCustomImpl() {
1055         assert(isOwnerOfCustomImpl());
1056         isOwner=false;
1057         return builtInImpl;
1058     }
1059 
setReferenceToCustomImpl(CustomImpl * cimpl)1060     void setReferenceToCustomImpl(CustomImpl* cimpl) {
1061         assert(!builtInImpl); // you can only do this once
1062         isOwner=false;
1063         builtInImpl = cimpl;
1064     }
1065 
hasCustomImpl()1066     bool hasCustomImpl() const {return builtInImpl != 0;}
1067 
getCustomImpl()1068     const CustomImpl& getCustomImpl() const {
1069         assert(builtInImpl);
1070         return *builtInImpl;
1071     }
updCustomImpl()1072     CustomImpl& updCustomImpl() {
1073         assert(builtInImpl);
1074         return *builtInImpl;
1075     }
1076 
getNU()1077     int getNU() const {
1078         return nu;
1079     }
1080 
getNQ()1081     int getNQ() const {
1082         return nq;
1083     }
1084 
getNumAngles()1085     int getNumAngles() const {
1086         return nAngles;
1087     }
1088 
1089 private:
1090     bool isOwner;
1091     CustomImpl* builtInImpl; // just a reference; not owned
1092     int nu, nq, nAngles;
1093 
1094     // suppress assignment
1095     ImplementationImpl& operator=(const ImplementationImpl&);
1096 };
1097 
1098 /////////////////////////////////
1099 // MOBILIZED BODY::CUSTOM IMPL //
1100 /////////////////////////////////
1101 
1102 class MobilizedBody::CustomImpl : public MobilizedBodyImpl {
1103 public:
CustomImpl(Direction d)1104     explicit CustomImpl(Direction d) : MobilizedBodyImpl(d), implementation(0) { }
1105 
1106     void takeOwnershipOfImplementation(Custom::Implementation* userImpl);
1107 
CustomImpl(Custom::Implementation * userImpl,Direction d)1108     explicit CustomImpl(Custom::Implementation* userImpl, Direction d)
1109     :   MobilizedBodyImpl(d), implementation(0) {
1110         assert(userImpl);
1111         implementation = userImpl;
1112         implementation->updImpl().setReferenceToCustomImpl(this);
1113     }
1114 
1115     // Copy constructor
CustomImpl(const CustomImpl & src)1116     CustomImpl(const CustomImpl& src)
1117     :   MobilizedBodyImpl(src), implementation(0) {
1118         if (src.implementation) {
1119             implementation = src.implementation->clone();
1120             implementation->updImpl().setReferenceToCustomImpl(this);
1121         }
1122     }
1123 
~CustomImpl()1124     ~CustomImpl() {
1125         if (implementation)
1126             delete implementation;
1127     }
1128 
clone()1129     CustomImpl* clone() const override { return new CustomImpl(*this); }
1130 
getImplementation()1131     const Custom::Implementation& getImplementation() const {
1132         assert(implementation);
1133         return *implementation;
1134     }
1135 
updImplementation()1136     Custom::Implementation& updImplementation() {
1137         assert(implementation);
1138         return *implementation;
1139     }
1140 
1141     RigidBodyNode* createRigidBodyNode(
1142         UIndex&        nextUSlot,
1143         USquaredIndex& nextUSqSlot,
1144         QIndex&        nextQSlot) const override;
1145 
copyOutDefaultQImpl(int nq,Real * q)1146     void copyOutDefaultQImpl(int nq, Real* q) const override {
1147         SimTK_ASSERT(nq==getImplementation().getImpl().getNQ() || nq==getImplementation().getImpl().getNQ()-1,
1148             "MobilizedBody::CustomImpl::copyOutDefaultQImpl(): wrong number of q's");
1149         for (int i = 0; i < nq; ++i)
1150             q[i] = 0.0;
1151         if (implementation->getImpl().getNumAngles() == 4)
1152             q[0] = 1.0;
1153     }
1154 
1155     // Forward all the virtuals to the Custom::Implementation virtuals.
realizeTopologyVirtual(State & s)1156     void realizeTopologyVirtual(State& s)       const override {getImplementation().realizeTopology(s);}
realizeModelVirtual(State & s)1157     void realizeModelVirtual   (State& s)       const override {getImplementation().realizeModel(s);}
realizeInstanceVirtual(const State & s)1158     void realizeInstanceVirtual(const State& s) const override {getImplementation().realizeInstance(s);}
realizeTimeVirtual(const State & s)1159     void realizeTimeVirtual    (const State& s) const override {getImplementation().realizeTime(s);}
realizePositionVirtual(const State & s)1160     void realizePositionVirtual(const State& s) const override {getImplementation().realizePosition(s);}
realizeVelocityVirtual(const State & s)1161     void realizeVelocityVirtual(const State& s) const override {getImplementation().realizeVelocity(s);}
realizeDynamicsVirtual(const State & s)1162     void realizeDynamicsVirtual(const State& s) const override {getImplementation().realizeDynamics(s);}
realizeAccelerationVirtual(const State & s)1163     void realizeAccelerationVirtual
1164                                (const State& s) const override {getImplementation().realizeAcceleration(s);}
realizeReportVirtual(const State & s)1165     void realizeReportVirtual  (const State& s) const override {getImplementation().realizeReport(s);}
1166 
calcDecorativeGeometryAndAppend(const State & s,Stage stage,Array_<DecorativeGeometry> & geom)1167     void calcDecorativeGeometryAndAppend(const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const
1168        {getImplementation().calcDecorativeGeometryAndAppend(s,stage,geom);}
1169 
1170     SimTK_DOWNCAST(CustomImpl, MobilizedBodyImpl);
1171 private:
1172     friend class MobilizedBody::Custom;
1173 
1174     Custom::Implementation* implementation;
1175 
1176     CustomImpl& operator=(const CustomImpl&); // suppress assignment
1177 };
1178 
1179 // Need definition for CustomImpl here in case we have to delete it.
~ImplementationImpl()1180 inline MobilizedBody::Custom::ImplementationImpl::~ImplementationImpl() {
1181     if (isOwner)
1182         delete builtInImpl;
1183     builtInImpl=0;
1184 }
1185 
1186 ////////////////////////////////////////
1187 // MOBILIZED BODY::FUNCTIONBASED IMPL //
1188 ////////////////////////////////////////
1189 
1190 
1191 class MobilizedBody::FunctionBasedImpl : public MobilizedBody::Custom::Implementation {
1192 public:
1193     //Constructor that uses default axes
FunctionBasedImpl(SimbodyMatterSubsystem & matter,int nmobilities,const Array_<const Function * > & functions,const Array_<Array_<int>> & coordIndices)1194     FunctionBasedImpl(SimbodyMatterSubsystem& matter, int nmobilities, const Array_<const Function*>& functions, const Array_<Array_<int> >& coordIndices)
1195             : Implementation(matter, nmobilities, nmobilities, 0), subsystem(matter.getMySubsystemIndex()), nu(nmobilities), cacheIndex(0), functions(functions), coordIndices(coordIndices), referenceCount(new int[1]) {
1196         assert(functions.size() == 6);
1197         assert(coordIndices.size() == 6);
1198         referenceCount[0] = 1;
1199         for (int i = 0; i < (int)functions.size(); ++i) {
1200             assert(functions[i]->getArgumentSize() == coordIndices[i].size());
1201             assert(functions[i]->getMaxDerivativeOrder() >= 2);
1202         }
1203         Arot = Mat33(1);
1204         Atrans = Mat33(1);
1205     }
FunctionBasedImpl(SimbodyMatterSubsystem & matter,int nmobilities,const Array_<const Function * > & functions,const Array_<Array_<int>> & coordIndices,const Array_<Vec3> & axes)1206     FunctionBasedImpl(SimbodyMatterSubsystem& matter, int nmobilities, const Array_<const Function*>& functions, const Array_<Array_<int> >& coordIndices, const Array_<Vec3>& axes)
1207             : Implementation(matter, nmobilities, nmobilities, 0), subsystem(matter.getMySubsystemIndex()), nu(nmobilities), cacheIndex(0), functions(functions), coordIndices(coordIndices), referenceCount(new int[1]) {
1208         assert(functions.size() == 6);
1209         assert(coordIndices.size() == 6);
1210         assert(axes.size() == 6);
1211         referenceCount[0] = 1;
1212         for (int i = 0; i < (int)functions.size(); ++i) {
1213             assert(functions[i]->getArgumentSize() == coordIndices[i].size());
1214             assert(functions[i]->getMaxDerivativeOrder() >= 2);
1215         }
1216         double tol = 1e-5;
1217         // Verify that none of the rotation axes are colinear
1218         assert((axes[0]%axes[1]).norm() > tol);
1219         assert((axes[0]%axes[2]).norm() > tol);
1220         assert((axes[1]%axes[2]).norm() > tol);
1221         // Verify that none of the translational axes are colinear
1222         assert((axes[3]%axes[4]).norm() > tol);
1223         assert((axes[3]%axes[5]).norm() > tol);
1224         assert((axes[4]%axes[5]).norm() > tol);
1225 
1226         Arot = Mat33(axes[0].normalize(), axes[1].normalize(), axes[2].normalize());
1227         Atrans = Mat33(axes[3].normalize(), axes[4].normalize(), axes[5].normalize());
1228     }
1229 
~FunctionBasedImpl()1230     ~FunctionBasedImpl() {
1231         if (--referenceCount[0] == 0) {
1232             for (int i = 0; i < (int) functions.size(); i++)
1233                 delete functions[i];
1234             delete[] referenceCount;
1235         }
1236     }
1237 
clone()1238     MobilizedBody::Custom::Implementation* clone() const override {
1239         referenceCount[0]++;
1240         return new FunctionBasedImpl(*this);
1241     }
1242 
calcMobilizerTransformFromQ(const State & s,int nq,const Real * q)1243     Transform calcMobilizerTransformFromQ(const State& s, int nq, const Real* q) const override {
1244         // Initialize the tranformation to be returned
1245         Transform X(Vec3(0));
1246         Vec6 spatialCoords;
1247 
1248         // Get the spatial cooridinates as a function of the q's
1249         for(int i=0; i < 6; i++){
1250             //Coordinates for this function
1251             int nc = coordIndices[i].size();
1252             Vector fcoords(nc);
1253 
1254             for(int j=0; j < nc; j++)
1255                 fcoords(j) = q[coordIndices[i][j]];
1256 
1257             //default behavior of constant function should take a Vector of length 0
1258             spatialCoords(i) = functions[i]->calcValue(fcoords);
1259         }
1260 
1261 /*
1262         UnitVec3 axis1;
1263         axis1.getAs(&Arot(0,0));
1264         UnitVec3 axis2;
1265         axis2.getAs(&Arot(0,1));
1266         UnitVec3 axis3;
1267         axis3.getAs(&Arot(0,2));
1268 */
1269 
1270         //X.updR().setRotationToBodyFixedXYZ(spatialCoords.getSubVec<3>(0));
1271         X.updR().setRotationFromMat33TrustMe(Rotation(spatialCoords(0), UnitVec3::getAs(&Arot(0,0)))*
1272             Rotation(spatialCoords(1), UnitVec3::getAs(&Arot(0,1)))*Rotation(spatialCoords(2), UnitVec3::getAs(&Arot(0,2))));
1273         X.updP() = spatialCoords(3)*UnitVec3::getAs(&Atrans(0,0))+spatialCoords(4)*UnitVec3::getAs(&Atrans(0,1))
1274                     +spatialCoords(5)*UnitVec3::getAs(&Atrans(0,2));
1275 
1276         return X;
1277     }
1278 
multiplyByHMatrix(const State & s,int nu,const Real * u)1279     SpatialVec multiplyByHMatrix(const State& s, int nu, const Real* u) const override {
1280 
1281         switch (nu) {
1282             case 1: {
1283                 // Check that the H  matrices in the cache are valid
1284                 if (!(Value<CacheInfo<1>>::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1285                     updateH(s);
1286 
1287                 Mat<2,1,Vec3> h = Value<CacheInfo<1> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1288                 return h*Vec1::getAs(u);
1289             }
1290             case 2: {
1291                 // Check that the H  matrices in the cache are valid
1292                 if (!(Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1293                     updateH(s);
1294 
1295                 Mat<2,2,Vec3> h = Value<CacheInfo<2> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1296                 return h*Vec2::getAs(u);
1297             }
1298             case 3: {
1299                 // Check that the H  matrices in the cache are valid
1300                 if (!(Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1301                     updateH(s);
1302 
1303                 Mat<2,3,Vec3> h = Value<CacheInfo<3> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1304                 return h*Vec3::getAs(u);
1305             }
1306             case 4: {
1307                 // Check that the H  matrices in the cache are valid
1308                 if (!(Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1309                     updateH(s);
1310 
1311                 Mat<2,4,Vec3> h = Value<CacheInfo<4> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1312                 return h*Vec4::getAs(u);
1313             }
1314             case 5: {
1315                 // Check that the H matrices in the cache are valid
1316                 if (!(Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1317                     updateH(s);
1318 
1319                 Mat<2,5,Vec3> h = Value<CacheInfo<5> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1320                 return h*Vec5::getAs(u);
1321             }
1322             case 6: {
1323                 // Check that the H  matrices in the cache are valid
1324                 if (!(Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1325                     updateH(s);
1326 
1327                 Mat<2,6,Vec3> h = Value<CacheInfo<6> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1328                 return h*Vec6::getAs(u);
1329             }
1330         }
1331         SimTK_THROW5(SimTK::Exception::ValueOutOfRange, "nu", 1, nu, 6, "MobilizedBody::FunctionBasedImpl::multiplyByHMatrix");
1332     }
1333 
multiplyByHTranspose(const State & s,const SpatialVec & F,int nu,Real * f)1334     void multiplyByHTranspose(const State& s, const SpatialVec& F, int nu, Real* f) const override {
1335 
1336         switch (nu) {
1337             case 1: {
1338                 // Check that the H and Hdot matrices in the cache are valid
1339                 if (!(Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1340                     updateH(s);
1341 
1342                 Mat<2,1,Vec3> h = Value<CacheInfo<1> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1343                 Vec1::updAs(f) = ~h*F;
1344                 return;
1345             }
1346             case 2: {
1347                 // Check that the H and Hdot matrices in the cache are valid
1348                 if (!(Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1349                     updateH(s);
1350 
1351                 Mat<2,2,Vec3> h = Value<CacheInfo<2> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1352                 Vec2::updAs(f) = ~h*F;
1353                 return;
1354             }
1355             case 3: {
1356                 // Check that the H and Hdot matrices in the cache are valid
1357                 if (!(Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1358                     updateH(s);
1359 
1360                 Mat<2,3,Vec3> h = Value<CacheInfo<3> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1361                 Vec3::updAs(f) = ~h*F;
1362                 return;
1363             }
1364             case 4: {
1365                 // Check that the H and Hdot matrices in the cache are valid
1366                 if (!(Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1367                     updateH(s);
1368 
1369                 Mat<2,4,Vec3> h = Value<CacheInfo<4> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1370                 Vec4::updAs(f) = ~h*F;
1371                 return;
1372             }
1373             case 5: {
1374                 // Check that the H and Hdot matrices in the cache are valid
1375                 if (!(Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1376                     updateH(s);
1377 
1378                 Mat<2,5,Vec3> h = Value<CacheInfo<5> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1379                 Vec5::updAs(f) = ~h*F;
1380                 return;
1381             }
1382             case 6: {
1383                 // Check that the H and Hdot matrices in the cache are valid
1384                 if (!(Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH))
1385                     updateH(s);
1386 
1387                 Mat<2,6,Vec3> h = Value<CacheInfo<6> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().h;
1388                 Vec6::updAs(f) = ~h*F;
1389                 return;
1390             }
1391         }
1392         SimTK_THROW5(SimTK::Exception::ValueOutOfRange, "nu", 1, nu, 6, "MobilizedBody::FunctionBasedImpl::multiplyByHTranspose");
1393     }
1394 
multiplyByHDotMatrix(const State & s,int nu,const Real * u)1395     SpatialVec multiplyByHDotMatrix(const State& s, int nu, const Real* u) const override {
1396 
1397         switch (nu) {
1398             case 1: {
1399                 // Check that the H and Hdot matrices in the cache are valid
1400                 if (!(Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1401                     updateHdot(s);
1402 
1403                 Mat<2,1,Vec3> hdot = Value<CacheInfo<1> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1404                 return hdot*Vec1::getAs(u);
1405             }
1406             case 2: {
1407                 // Check that the H and Hdot matrices in the cache are valid
1408                 if (!(Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1409                     updateHdot(s);
1410 
1411                 Mat<2,2,Vec3> hdot = Value<CacheInfo<2> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1412                 return hdot*Vec2::getAs(u);
1413             }
1414             case 3: {
1415                 // Check that the H and Hdot matrices in the cache are valid
1416                 if (!(Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1417                     updateHdot(s);
1418 
1419                 Mat<2,3,Vec3> hdot = Value<CacheInfo<3> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1420                 return hdot*Vec3::getAs(u);
1421             }
1422             case 4: {
1423                 // Check that the H and Hdot matrices in the cache are valid
1424                 if (!(Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1425                     updateHdot(s);
1426 
1427                 Mat<2,4,Vec3> hdot = Value<CacheInfo<4> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1428                 return hdot*Vec4::getAs(u);
1429             }
1430             case 5: {
1431                 // Check that the H and Hdot matrices in the cache are valid
1432                 if (!(Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1433                     updateHdot(s);
1434 
1435                 Mat<2,5,Vec3> hdot = Value<CacheInfo<5> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1436                 return hdot*Vec5::getAs(u);
1437             }
1438             case 6: {
1439                 // Check that the H and Hdot matrices in the cache are valid
1440                 if (!(Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1441                     updateHdot(s);
1442 
1443                 Mat<2,6,Vec3> hdot = Value<CacheInfo<6> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1444                 return hdot*Vec6::getAs(u);
1445             }
1446         }
1447         SimTK_THROW5(SimTK::Exception::ValueOutOfRange, "nu", 1, nu, 6, "MobilizedBody::FunctionBasedImpl::multiplyByHDotMatrix");
1448     }
1449 
multiplyByHDotTranspose(const State & s,const SpatialVec & F,int nu,Real * f)1450     void multiplyByHDotTranspose(const State& s, const SpatialVec& F, int nu, Real* f) const override {
1451 
1452         switch (nu) {
1453             case 1: {
1454                 // Check that the H and Hdot matrices in the cache are valid
1455                 if (!(Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1456                     updateHdot(s);
1457 
1458                 Mat<2,1,Vec3> hdot = Value<CacheInfo<1> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1459                 Vec1::updAs(f) = ~hdot*F;
1460                 return;
1461             }
1462             case 2: {
1463                 // Check that the H and Hdot matrices in the cache are valid
1464                 if (!(Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1465                     updateHdot(s);
1466 
1467                 Mat<2,2,Vec3> hdot = Value<CacheInfo<2> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1468                 Vec2::updAs(f) = ~hdot*F;
1469                 return;
1470             }
1471             case 3: {
1472                 // Check that the H and Hdot matrices in the cache are valid
1473                 if (!(Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1474                     updateHdot(s);
1475 
1476                 Mat<2,3,Vec3> hdot = Value<CacheInfo<3> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1477                 Vec3::updAs(f) = ~hdot*F;
1478                 return;
1479             }
1480             case 4: {
1481                 // Check that the H and Hdot matrices in the cache are valid
1482                 if (!(Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1483                     updateHdot(s);
1484 
1485                 Mat<2,4,Vec3> hdot = Value<CacheInfo<4> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1486                 Vec4::updAs(f) = ~hdot*F;
1487                 return;
1488             }
1489             case 5: {
1490                 // Check that the H and Hdot matrices in the cache are valid
1491                 if (!(Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1492                     updateHdot(s);
1493 
1494                 Mat<2,5,Vec3> hdot = Value<CacheInfo<5> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1495                 Vec5::updAs(f) = ~hdot*F;
1496                 return;
1497             }
1498             case 6: {
1499                 // Check that the H and Hdot matrices in the cache are valid
1500                 if (!(Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot))
1501                     updateHdot(s);
1502 
1503                 Mat<2,6,Vec3> hdot = Value<CacheInfo<6> >::downcast(s.getCacheEntry(subsystem, cacheIndex)).get().hdot;
1504                 Vec6::updAs(f) = ~hdot*F;
1505                 return;
1506             }
1507         }
1508         SimTK_THROW5(SimTK::Exception::ValueOutOfRange, "nu", 1, nu, 6, "MobilizedBody::FunctionBasedImpl::multiplyByHDotTranspose");
1509     }
1510 
realizeTopology(State & s)1511     void realizeTopology(State& s) const override {
1512         switch (nu) {
1513         case 1:
1514             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<1> >());
1515             break;
1516         case 2:
1517             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<2> >());
1518             break;
1519         case 3:
1520             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<3> >());
1521             break;
1522         case 4:
1523             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<4> >());
1524             break;
1525         case 5:
1526             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<5> >());
1527             break;
1528         case 6:
1529             cacheIndex = s.allocateCacheEntry(subsystem, Stage::Topology, new Value<CacheInfo<6> >());
1530             break;
1531         }
1532     }
1533 
realizePosition(const State & s)1534     void realizePosition(const State& s) const override {
1535         switch (nu) {
1536             case 1: {
1537                 // invalidate H and Hdot matrices
1538                 Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1539                 break;
1540             }
1541             case 2: {
1542                 // invalidate H and Hdot matrices
1543                 Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1544                 break;
1545             }
1546             case 3: {
1547                 // invalidate H and Hdot matrices
1548                 Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1549                 break;
1550             }
1551             case 4: {
1552                 // invalidate H and Hdot matrices
1553                 Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1554                 break;
1555             }
1556             case 5: {
1557                 // invalidate H and Hdot matrices
1558                 Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1559                 break;
1560             }
1561             case 6: {
1562                 // invalidate H and Hdot matrices
1563                 Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidH = false;
1564                 break;
1565             }
1566         }
1567     }
1568 
realizeVelocity(const State & s)1569     void realizeVelocity(const State& s) const override {
1570         switch (nu) {
1571             case 1: {
1572                 // invalidate H and Hdot matrices
1573                 Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1574                 break;
1575             }
1576             case 2: {
1577                 // invalidate H and Hdot matrices
1578                 Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1579                 break;
1580             }
1581             case 3: {
1582                 // invalidate H and Hdot matrices
1583                 Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1584                 break;
1585             }
1586             case 4: {
1587                 // invalidate H and Hdot matrices
1588                 Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1589                 break;
1590             }
1591             case 5: {
1592                 // invalidate H and Hdot matrices
1593                 Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1594                 break;
1595             }
1596             case 6: {
1597                 // invalidate H and Hdot matrices
1598                 Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd().isValidHdot = false;
1599                 break;
1600             }
1601         }
1602     }
1603 
updateH(const State & s)1604     void updateH(const State& s) const {
1605         // Get mobilizer kinematics
1606         Vector q = getQ(s);
1607         Vector u = getU(s);
1608         switch (nu) {
1609             case 1: {
1610                 CacheInfo<1>& cache = Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1611                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1612                 // H matrix is now valid
1613                 cache.isValidH = true;
1614                 break;
1615             }
1616             case 2: {
1617                 CacheInfo<2>& cache = Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1618                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1619                 // H matrix is now valid
1620                 cache.isValidH = true;
1621                 break;
1622             }
1623             case 3: {
1624                 CacheInfo<3>& cache = Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1625                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1626                 // H matrix is now valid
1627                 cache.isValidH = true;
1628                 break;
1629             }
1630             case 4: {
1631                 CacheInfo<4>& cache = Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1632                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1633                 // H matrix is now valid
1634                 cache.isValidH = true;
1635                 break;
1636             }
1637             case 5: {
1638                 CacheInfo<5>& cache = Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1639                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1640                 // H matrix is now valid
1641                 cache.isValidH = true;
1642                 break;
1643             }
1644             case 6: {
1645                 CacheInfo<6>& cache = Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1646                 cache.buildH(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1647                 // H matrix is now valid
1648                 cache.isValidH = true;
1649                 break;
1650             }
1651         }
1652     }
1653 
updateHdot(const State & s)1654     void updateHdot(const State& s) const {
1655         // Get mobilizer kinematics
1656         Vector q = getQ(s);
1657         Vector u = getU(s);
1658         switch (nu) {
1659             case 1: {
1660                 CacheInfo<1>& cache = Value<CacheInfo<1> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1661                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1662                 // Hdot matrix is now valid
1663                 cache.isValidHdot = true;
1664                 break;
1665             }
1666             case 2: {
1667                 CacheInfo<2>& cache = Value<CacheInfo<2> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1668                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1669                 // Hdot matrix is now valid
1670                 cache.isValidHdot = true;
1671                 break;
1672             }
1673             case 3: {
1674                 CacheInfo<3>& cache = Value<CacheInfo<3> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1675                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1676                 // Hdot matrix is now valid
1677                 cache.isValidHdot = true;
1678                 break;
1679             }
1680             case 4: {
1681                 CacheInfo<4>& cache = Value<CacheInfo<4> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1682                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1683                 // Hdot matrix is now valid
1684                 cache.isValidHdot = true;
1685                 break;
1686             }
1687             case 5: {
1688                 CacheInfo<5>& cache = Value<CacheInfo<5> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1689                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1690                 // Hdot matrix is now valid
1691                 cache.isValidHdot = true;
1692                 break;
1693             }
1694             case 6: {
1695                 CacheInfo<6>& cache = Value<CacheInfo<6> >::updDowncast(s.updCacheEntry(subsystem, cacheIndex)).upd();
1696                 cache.buildHdot(q, u, getMobilizerTransform(s), functions, coordIndices, Arot, Atrans);
1697                 // Hdot matrix is now valid
1698                 cache.isValidHdot = true;
1699                 break;
1700             }
1701         }
1702     }
1703 
1704 private:
1705     const SubsystemIndex subsystem;
1706     const int nu;
1707     mutable CacheEntryIndex cacheIndex;
1708     const Array_<const Function*> functions;
1709     const Array_<Array_<int> > coordIndices;
1710     int* referenceCount;
1711     //const Array_<Vec3> axes;
1712     Mat33 Arot, Atrans;
1713     template <int N> class CacheInfo {
1714     public:
CacheInfo()1715         CacheInfo() : isValidH(false), isValidHdot(false) { }
1716 
buildH(Vector & q,Vector & u,const Transform & X_FM,const Array_<const Function * > & functions,const Array_<Array_<int>> & coordIndices,const Mat33 Arot,const Mat33 Atrans)1717         void buildH(Vector& q, Vector& u, const Transform& X_FM, const Array_<const Function*>& functions, const Array_<Array_<int> >& coordIndices, const Mat33 Arot, const Mat33 Atrans)
1718         {
1719             // Build the Fq and Fqq matrices of partials of the spatial functions with respect to the gen coordinates, q
1720             // Cycle through each row (function describing spatial coordinate)
1721             Fq = Mat<6,N>(0);
1722             Vec6 spatialCoords(0);
1723             Array_<int> deriv(1);
1724             Vector fcoords(coordIndices[0].size());
1725 
1726             for(int i=0; i < 6; i++){
1727                 // Determine the number of coordinates for this function
1728                 int nc = coordIndices[i].size();
1729                 if (fcoords.size() != nc)
1730                     fcoords.resize(nc);
1731 
1732                 if (nc > 0) {
1733                     // Get coordinate values to evaluate the function
1734                     for(int k = 0; k < nc; k++)
1735                         fcoords(k) = q(coordIndices[i][k]);
1736 
1737                     for (int j = 0; j < nc; j++) {
1738                         deriv[0] = j;
1739                         Fq(i, coordIndices[i][j]) = functions[i]->calcDerivative(deriv, fcoords);
1740                     }
1741                     //default behavior of constant function should take a Vector of length 0
1742                     spatialCoords(i) = functions[i]->calcValue(fcoords);
1743                 }
1744 
1745             }
1746             // Note rotations are body fixed sequences, and so taking the derivative yields a body-fixed angular velocity
1747             // and therefore, must be transformed to the parent frame.
1748 
1749             // omega = [a1, R_F1*a2, R_F2*a3]*{Theta_dot(q)}, where Theta_dot(q) is described by the first three functions
1750             //       = [W*[Fq_theta]*qdot
1751             // vel = [A]*{X_dot(q)}, where X_dot(q) is described by the last three functions
1752             //     = [A]*[Fq_x]*qdot
1753 
1754             Rotation R_F1 = Rotation(spatialCoords(0), UnitVec3::getAs(&Arot(0,0)));
1755             Rotation R_F2 = R_F1*Rotation(spatialCoords(1), UnitVec3::getAs(&Arot(0,1)));
1756 
1757             Mat31 temp;
1758             Mat33 W(UnitVec3::getAs(&Arot(0,0)), R_F1*(UnitVec3::getAs(&Arot(0,1))), R_F2*(UnitVec3::getAs(&Arot(0,2))));
1759 
1760             for(int i=0; i < N; i++){
1761                 temp = W*(Fq.template getSubMat<3,1>(0,i));
1762                 h(0,i) = Vec3::getAs(&temp(0,0));
1763                 temp = Atrans*(Fq.template getSubMat<3,1>(3,i));
1764                 h(1,i) = Vec3::getAs(&temp(0,0));
1765             }
1766         }
1767 
buildHdot(Vector & q,Vector & u,const Transform & X_FM,const Array_<const Function * > & functions,const Array_<Array_<int>> & coordIndices,const Mat33 Arot,const Mat33 Atrans)1768         void buildHdot(Vector& q, Vector& u, const Transform& X_FM, const Array_<const Function*>& functions, const Array_<Array_<int> >& coordIndices, const Mat33 Arot, const Mat33 Atrans)
1769         {
1770             Mat<6,N> Fqdot(0);
1771             Vec6 spatialCoords;
1772             Array_<int> derivs(2);
1773             Vector fcoords(coordIndices[0].size());
1774 
1775             for(int i=0; i < 6; i++){
1776                 // Determine the number of coordinates for this function
1777                 int nc = coordIndices[i].size();
1778                 if (fcoords.size() != nc)
1779                     fcoords.resize(nc);
1780 
1781                 if (nc > 0) {
1782                     // Get coordinate values to evaluate the function
1783                     for(int k = 0; k < nc; k++)
1784                         fcoords(k) = q(coordIndices[i][k]);
1785 
1786                     // function is dependent on a mobility if its index is in the list of function coordIndices
1787                     // cycle through the mobilities
1788                     for (int j = 0; j < nc; j++) {
1789                         derivs[0] = j;
1790                         for (int k = 0; k < nc; k++) {
1791                             derivs[1] = k;
1792                             Fqdot(i, coordIndices[i][j]) += functions[i]->calcDerivative(derivs, fcoords)*u[coordIndices[i][k]];
1793                         }
1794                     }
1795                 }
1796                 //default behavior of constant function should take a Vector of length 0
1797                 spatialCoords(i) = functions[i]->calcValue(fcoords);
1798             }
1799 
1800             Rotation R_F1 = Rotation(spatialCoords(0), UnitVec3::getAs(&Arot(0,0)));
1801             Rotation R_F2 = R_F1*Rotation(spatialCoords(1), UnitVec3::getAs(&Arot(0,1)));
1802 
1803             Mat33 W(UnitVec3::getAs(&Arot(0,0)), R_F1*(UnitVec3::getAs(&Arot(0,1))), R_F2*(UnitVec3::getAs(&Arot(0,2))));
1804 
1805             // Hdot_theta = [Wdot]*[Fq] + [W][Fqdot]
1806             // Hdot_x = [A]*[Fqdot]
1807             Real *up = &u[0];
1808             Vec<N> uv = Vec<N>::getAs(up);
1809             Vec<N> uv1 = uv;
1810             Vec<N> uv2 = uv;
1811 
1812             for(int i=1; i < N; i++){
1813                 uv1(i) = 0;
1814             }
1815             if(N > 2)
1816                 uv2(2) = 0;
1817 
1818             // Spatial velocity
1819             SpatialVec V = h*uv;
1820             SpatialVec V1 = h*uv1;
1821             SpatialVec V2 = h*uv2;
1822             // First V[0] is angular velocity, omega
1823             //Mat33 Wdot(Vec3(0), Vec3(V[0](0),0,0)%(Vec3::getAs(&W(0,1))), Vec3(V[0](0),V[0](1),0)%(Vec3::getAs(&W(0,2))));
1824             Mat33 Wdot(Vec3(0), V1[0]%(Vec3::getAs(&W(0,1))), V2[0]%(Vec3::getAs(&W(0,2))));
1825 
1826             //Sanity check Omega == V[0]
1827             Mat31 Omega = W*(Fq.template getSubMat<3,N>(0,0))*(Mat<N,1>::getAs(up));
1828 
1829             Mat31 temp;
1830             for(int i=0; i < N; i++){
1831                 temp = Wdot*(Fq.template getSubMat<3,1>(0,i))+W*(Fqdot.template getSubMat<3,1>(0,i));
1832                 hdot(0,i) = Vec3::getAs(&temp(0,0));
1833                 temp = Atrans*(Fqdot.template getSubMat<3,1>(3,i));
1834                 hdot(1,i) = Vec3::getAs(&temp(0,0));
1835             }
1836         }
1837 
1838         Mat<2,N,Vec3> h, hdot;
1839         Mat<6,N> Fq;
1840         bool isValidH;
1841         bool isValidHdot;
1842     };
1843 
1844 };
1845 
1846 } // namespace SimTK
1847 
1848 #endif // SimTK_SIMBODY_MOBILIZED_BODY_IMPL_H_
1849