1 /* -------------------------------------------------------------------------- *
2 * Simbody(tm): Multibody Graph Maker *
3 * -------------------------------------------------------------------------- *
4 * This is part of the SimTK biosimulation toolkit originating from *
5 * Simbios, the NIH National Center for Physics-Based Simulation of *
6 * Biological Structures at Stanford, funded under the NIH Roadmap for *
7 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. *
8 * *
9 * Portions copyright (c) 2013-4 Stanford University and the Authors. *
10 * Authors: Michael Sherman *
11 * Contributors: Kevin He *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 /* This is the implementation of MultibodyGraphMaker and its auxiliary classes.
25 */
26
27 #include "SimTKcommon.h"
28 #include "simmath/internal/common.h"
29 #include "simmath/MultibodyGraphMaker.h"
30
31 #include <exception>
32 #include <set>
33 #include <stdexcept>
34 #include <string>
35 #include <iostream>
36 #include <cstdio>
37
38 using std::cout; using std::endl;
39
40 #ifdef _MSC_VER
41 #pragma warning(disable:4996) // don't warn about strcat, sprintf, etc.
42 #endif
43
44 namespace SimTK {
45
46 //------------------------------------------------------------------------------
47 // CONSTRUCTOR
48 //------------------------------------------------------------------------------
MultibodyGraphMaker()49 MultibodyGraphMaker::MultibodyGraphMaker()
50 : weldTypeName("weld"), freeTypeName("free")
51 { initialize(); }
52
53
54 //------------------------------------------------------------------------------
55 // GET GROUND BODY NAME
56 //------------------------------------------------------------------------------
getGroundBodyName() const57 const std::string& MultibodyGraphMaker::getGroundBodyName() const {
58 if (bodies.empty())
59 throw std::logic_error(
60 "getGroundBodyName(): you can't call this until you have called "
61 "addBody() at least once -- the first body is Ground.");
62 return bodies[0].name;
63 }
64
65
66 //------------------------------------------------------------------------------
67 // ADD JOINT TYPE
68 //------------------------------------------------------------------------------
69 int MultibodyGraphMaker::
addJointType(const std::string & name,int numMobilities,bool haveGoodLoopJointAvailable,void * userRef)70 addJointType(const std::string& name,
71 int numMobilities,
72 bool haveGoodLoopJointAvailable,
73 void* userRef)
74 {
75 if (!(0 <= numMobilities && numMobilities <= 6)) throw std::runtime_error
76 ("addJointType(): Illegal number of mobilities for joint type '"
77 + name + "'");
78
79 // Reject duplicate type name, and reserved type names.
80 if (name==getWeldJointTypeName() || name==getFreeJointTypeName())
81 throw std::runtime_error("addJointType(): Joint type '" + name
82 + "' is reserved (you can change the reserved names).");
83 std::map<std::string,int>::const_iterator p = jointTypeName2Num.find(name);
84 if (p != jointTypeName2Num.end()) throw std::runtime_error
85 ("addJointType(): Duplicate joint type '" + name + "'");
86
87 const int jointTypeNum = (int)jointTypes.size(); // next available
88 jointTypeName2Num[name] = jointTypeNum; // provide fast name lookup
89
90 jointTypes.push_back(JointType(name, numMobilities,
91 haveGoodLoopJointAvailable, userRef));
92
93 return jointTypeNum;
94 }
95
96
97 //------------------------------------------------------------------------------
98 // ADD BODY
99 //------------------------------------------------------------------------------
addBody(const std::string & name,double mass,bool mustBeBaseBody,void * userRef)100 void MultibodyGraphMaker::addBody(const std::string& name,
101 double mass,
102 bool mustBeBaseBody,
103 void* userRef)
104 {
105 if (mass < 0) throw std::invalid_argument
106 ("addBody(): Body '" + name + "' specified negative mass");
107
108 // Reject duplicate body name.
109 std::map<std::string,int>::const_iterator p = bodyName2Num.find(name);
110 if (p != bodyName2Num.end()) throw std::runtime_error
111 ("addBody(): Duplicate body name '" + name + "'");
112
113 const int bodyNum = (int)bodies.size(); // next available slot
114 bodyName2Num[name] = bodyNum; // provide fast name lookup
115
116 if (bodyNum == 0) { // First body is Ground; use only the name and ref.
117 Body ground(name, Infinity, false, userRef);
118 ground.level = 0; // already in tree
119 bodies.push_back(ground);
120 } else { // This is a real body.
121 bodies.push_back(Body(name, mass, mustBeBaseBody, userRef));
122 }
123 }
124
125 //------------------------------------------------------------------------------
126 // DELETE BODY
127 //------------------------------------------------------------------------------
deleteBody(const std::string & name)128 bool MultibodyGraphMaker::deleteBody(const std::string& name)
129 {
130 // Reject non-existing body name.
131 std::map<std::string,int>::iterator p = bodyName2Num.find(name);
132 if (p == bodyName2Num.end())
133 return false;
134
135 const int bodyNum = p->second;
136
137 std::vector<int>& jointAsParent = updBody(bodyNum).jointsAsParent;
138 while (jointAsParent.size() > 0)
139 deleteJoint(joints[jointAsParent[0]].name);
140
141 std::vector<int>& jointAsChild = updBody(bodyNum).jointsAsChild;
142 while (jointAsChild.size() > 0)
143 deleteJoint(joints[jointAsChild[0]].name);
144
145 bodies.erase(bodies.begin() + bodyNum);
146 bodyName2Num.erase(p);
147
148 // Update body indices due to the deletion of this body
149 for (unsigned int i = 0; i < joints.size(); ++i) {
150 if (joints[i].parentBodyNum > bodyNum)
151 --(updJoint(i).parentBodyNum);
152 if (joints[i].childBodyNum > bodyNum)
153 --(updJoint(i).childBodyNum);
154 }
155
156 for (unsigned int i = bodyNum; i < bodies.size(); ++i)
157 bodyName2Num[bodies[i].name] = i;
158
159 return true;
160 }
161
162 //------------------------------------------------------------------------------
163 // ADD JOINT
164 //------------------------------------------------------------------------------
addJoint(const std::string & name,const std::string & type,const std::string & parentBodyName,const std::string & childBodyName,bool mustBeLoopJoint,void * userRef)165 void MultibodyGraphMaker::addJoint(const std::string& name,
166 const std::string& type,
167 const std::string& parentBodyName,
168 const std::string& childBodyName,
169 bool mustBeLoopJoint,
170 void* userRef)
171 {
172 // Reject duplicate joint name, unrecognized type or body names.
173 std::map<std::string,int>::const_iterator p = jointName2Num.find(name);
174 if (p != jointName2Num.end()) throw std::runtime_error
175 ("addJoint(): Duplicate joint name '" + name + "'");
176
177 const int typeNum = getJointTypeNum(type);
178 if (typeNum < 0) throw std::runtime_error
179 ("addJoint(): Joint " + name + " had unrecognized joint type '"
180 + type + "'");
181
182 const int parentBodyNum = getBodyNum(parentBodyName);
183 if (parentBodyNum < 0) throw std::runtime_error
184 ("addJoint(): Joint " + name + " had unrecognized parent body '"
185 + parentBodyName + "'");
186
187 const int childBodyNum = getBodyNum(childBodyName);
188 if (childBodyNum < 0) throw std::runtime_error
189 ("addJoint(): Joint " + name + " had unrecognized child body '"
190 + childBodyName + "'");
191
192 const int jointNum = (int)joints.size(); // next available slot
193 jointName2Num[name] = jointNum; // provide fast name lookup
194
195 joints.push_back(Joint(name, typeNum, parentBodyNum, childBodyNum,
196 mustBeLoopJoint, userRef));
197
198 updBody(parentBodyNum).jointsAsParent.push_back(jointNum);
199 updBody(childBodyNum).jointsAsChild.push_back(jointNum);
200 }
201
202 //------------------------------------------------------------------------------
203 // DELETE JOINT
204 //------------------------------------------------------------------------------
deleteJoint(const std::string & name)205 bool MultibodyGraphMaker::deleteJoint(const std::string& name)
206 {
207 // Reject duplicate joint name, unrecognized type or body names.
208 std::map<std::string,int>::iterator p = jointName2Num.find(name);
209 if (p == jointName2Num.end())
210 return false;
211
212 const int jointNum = p->second;
213 jointName2Num.erase(p);
214
215 std::vector<int>& jointsAsParent =
216 updBody(joints[jointNum].parentBodyNum).jointsAsParent;
217 std::vector<int>::iterator it =
218 std::find(jointsAsParent.begin(), jointsAsParent.end(), jointNum);
219 if (it == jointsAsParent.end()) throw std::runtime_error
220 ("deleteJoint(): Joint " + name +
221 " doesn't exist in jointsAsParent of parent body ");
222 jointsAsParent.erase(it);
223
224 std::vector<int>& jointsAsChild =
225 updBody(joints[jointNum].childBodyNum).jointsAsChild;
226 it = std::find(jointsAsChild.begin(), jointsAsChild.end(), jointNum);
227 if (it == jointsAsChild.end()) throw std::runtime_error
228 ("deleteJoint(): Joint " + name +
229 " doesn't exist in jointsAsChild of child body ");
230 jointsAsChild.erase(it);
231
232 joints.erase(joints.begin() + jointNum);
233
234 // Update indices due to the deletion of this joint
235 for (unsigned int i = 0; i < bodies.size(); ++i) {
236
237 for (it = updBody(i).jointsAsParent.begin(); it != updBody(i).jointsAsParent.end(); ++it)
238 if (*it > jointNum) {
239 --(*it);
240 jointName2Num[joints[*it].name] = *it;
241 }
242
243 for (it = updBody(i).jointsAsChild.begin(); it != updBody(i).jointsAsChild.end(); ++it)
244 if (*it > jointNum) {
245 --(*it);
246 // no need to adjust jointName2Num here since the first loops has done it already
247 }
248
249 }
250 return true;
251 }
252
253 //------------------------------------------------------------------------------
254 // GENERATE GRAPH
255 //------------------------------------------------------------------------------
generateGraph()256 void MultibodyGraphMaker::generateGraph() {
257 // Body and Joint inputs have been supplied. Body 0 is Ground.
258
259 // 1. Add free joints to Ground for any body that didn't appear
260 // in any joint, and for any bodies that said they must be base bodies and
261 // don't already have a connection to Ground.
262 //
263 // While we're at it, look for dangling massless bodies -- they are only
264 // allowed if they were originally connected to at least two other bodies
265 // by given tree-eligible joints, or if they are connected by a weld
266 // joint and thus have no mobility.
267 // TODO: "must be loop joint" joints shouldn't count but we're not
268 // checking.
269 for (int bn=1; bn < getNumBodies(); ++bn) { // skip Ground
270 const Body& body = getBody(bn);
271 const int nJoints = body.getNumJoints();
272 if (body.mass == 0) {
273 if (nJoints == 0) {
274 throw std::runtime_error(
275 "generateGraph(): body " + body.name +
276 " is massless but free (no joint). Must be internal or"
277 " welded to a massful body.");
278 }
279 if (nJoints == 1) {
280 const int jnum = body.jointsAsChild.empty()
281 ? body.jointsAsParent[0]
282 : body.jointsAsChild[0];
283 const Joint& joint = getJoint(jnum);
284 const JointType& jtype = getJointType(joint.jointTypeNum);
285 if (jtype.numMobilities > 0) {
286 throw std::runtime_error(
287 "generateGraph(): body " + body.name +
288 " is massless but not internal and not welded to"
289 " a massful body.");
290 }
291 }
292 }
293 if (nJoints==0 || (body.mustBeBaseBody && !bodiesAreConnected(bn, 0)))
294 connectBodyToGround(bn);
295 }
296
297 // 2. Repeat until all bodies are in the tree:
298 // - try to build the tree from Ground outwards
299 // - if incomplete, add one missing connection to Ground
300 // This terminates because we add at least one body to the tree each time.
301 while (true) {
302 growTree();
303 const int newBaseBody = chooseNewBaseBody();
304 if (newBaseBody < 0)
305 break; // all bodies are in the tree
306 // Add joint to Ground.
307 connectBodyToGround(newBaseBody);
308 }
309
310 // 3. Split the loops
311 // This requires adding new "slave" bodies to replace the child bodies in
312 // the loop-forming joints. Then each slave is welded back to its master
313 // body (the original child).
314 breakLoops();
315 }
316
317
318 //------------------------------------------------------------------------------
319 // CLEAR GRAPH
320 //------------------------------------------------------------------------------
clearGraph()321 void MultibodyGraphMaker::clearGraph() {
322 for (int bn=1; bn < getNumBodies(); ++bn) { // skip Ground
323 if (bodies[bn].isSlave()) {
324 // Assumption: all slave bodies are clustered at the end of the body array
325 bodies.erase(bodies.begin() + bn, bodies.begin() + bodies.size());
326 break;
327 }
328 updBody(bn).forgetGraph(*this);
329 }
330
331 for (int jn=0; jn < getNumJoints(); ++jn)
332 if (updJoint(jn).forgetGraph(*this))
333 --jn;
334
335 mobilizers.clear();
336 constraints.clear();
337 }
338
339 //------------------------------------------------------------------------------
340 // DUMP GRAPH
341 //------------------------------------------------------------------------------
dumpGraph(std::ostream & o) const342 void MultibodyGraphMaker::dumpGraph(std::ostream& o) const {
343 char buf[1024];
344 o << "\nMULTIBODY GRAPH\n";
345 o << "---------------\n";
346 o << "\n" << getNumBodies() << " BODIES:\n";
347 for (int i=0; i < getNumBodies(); ++i) {
348 const MultibodyGraphMaker::Body& body = getBody(i);
349 sprintf(buf, "%2d %2d: %s mass=%g mob=%d master=%d %s\n",
350 i, body.level,
351 body.name.c_str(), body.mass, body.mobilizer, body.master,
352 body.mustBeBaseBody?"MUST BE BASE BODY":"");
353 o << buf;
354 o << " jointsAsParent=[";
355 for (unsigned j=0; j<body.jointsAsParent.size(); ++j)
356 o << " " << body.jointsAsParent[j];
357 o << "]\t jointsAsChild=[";
358 for (unsigned j=0; j<body.jointsAsChild.size(); ++j)
359 o << " " << body.jointsAsChild[j];
360 o << "]\t slaves=[";
361 for (unsigned j=0; j<body.slaves.size(); ++j)
362 o << " " << body.slaves[j];
363 o << "]\n";
364 }
365
366 o << "\n" << getNumJoints() << " JOINTS:\n";
367 for (int i=0; i < getNumJoints(); ++i) {
368 const MultibodyGraphMaker::Joint& joint = getJoint(i);
369 sprintf(buf, "%2d %2d: %20s %20s->%-20s %10s loopc=%2d %s %s\n",
370 i, joint.mobilizer, joint.name.c_str(),
371 getBody(joint.parentBodyNum).name.c_str(),
372 getBody(joint.childBodyNum).name.c_str(),
373 getJointType(joint.jointTypeNum).name.c_str(),
374 joint.loopConstraint,
375 joint.mustBeLoopJoint?"MUST BE LOOP":"",
376 joint.isAddedBaseJoint?"ADDED BASE JOINT":"");
377 o << buf;
378 }
379
380 o << "\n" << getNumMobilizers() << " MOBILIZERS:\n";
381 for (int i=0; i < getNumMobilizers(); ++i) {
382 const MultibodyGraphMaker::Mobilizer& mo = getMobilizer(i);
383 const MultibodyGraphMaker::Joint& joint = getJoint(mo.joint);
384 const MultibodyGraphMaker::Body& inb = getBody(mo.inboardBody);
385 const MultibodyGraphMaker::Body& outb = getBody(mo.outboardBody);
386 sprintf(buf, "%2d %2d: %20s %20s->%-20s %10s %2d %3s\n",
387 i, mo.level, joint.name.c_str(),
388 inb.name.c_str(), outb.name.c_str(),
389 mo.getJointTypeName().c_str(),
390 mo.joint, mo.isReversed?"REV":"");
391 o << buf;
392 }
393
394 o << "\n" << getNumLoopConstraints() << " LOOP CONSTRAINTS:\n";
395 for (int i=0; i < getNumLoopConstraints(); ++i) {
396 const MultibodyGraphMaker::LoopConstraint& lc = getLoopConstraint(i);
397 const MultibodyGraphMaker::Body parent = getBody(lc.parentBody);
398 const MultibodyGraphMaker::Body child = getBody(lc.childBody);
399 sprintf(buf, "%d: %s parent=%s child=%s jointNum=%d\n",
400 i, lc.type.c_str(), parent.name.c_str(), child.name.c_str(),
401 lc.joint);
402 o << buf;
403 }
404 o << "\n----- END OF MULTIBODY GRAPH.\n\n";
405 }
406
407
408 //------------------------------------------------------------------------------
409 // INITIALIZE
410 //------------------------------------------------------------------------------
411 // Clear all data and prime with free and weld joint types.
initialize()412 void MultibodyGraphMaker::initialize() {
413 clear();
414 jointTypes.push_back(JointType(weldTypeName, 0, true, NULL));
415 jointTypes.push_back(JointType(freeTypeName, 6, true, NULL));
416 jointTypeName2Num[weldTypeName] = 0;
417 jointTypeName2Num[freeTypeName] = 1;
418 }
419
420
421 //------------------------------------------------------------------------------
422 // SPLIT BODY
423 //------------------------------------------------------------------------------
424 // Create a new slave body for the given master, and add it to the list of
425 // bodies. Does not create the related loop constraint. The body
426 // number assigned to the slave is returned.
splitBody(int masterBodyNum)427 int MultibodyGraphMaker::splitBody(int masterBodyNum) {
428 const int slaveBodyNum = (int)bodies.size(); // next available
429 Body& master = updBody(masterBodyNum);
430 // First slave is number 1, slave 0 is the master.
431 std::stringstream ss;
432 ss << "#" << master.name << "_slave_" << master.getNumSlaves()+1;
433 Body slave(ss.str(), NaN, false, NULL);
434 slave.master = masterBodyNum;
435 master.slaves.push_back(slaveBodyNum);
436 bodies.push_back(slave);
437 // No name lookup for slave bodies.
438 return slaveBodyNum;
439 }
440
441
442 //------------------------------------------------------------------------------
443 // CHOOSE NEW BASE BODY
444 //------------------------------------------------------------------------------
445 // We've tried to build the tree but might not have succeeded in using
446 // all the bodies. That means we'll have to connect one of them to Ground.
447 // Select the best unattached body to use as a base body, or -1 if all bodies
448 // are already included in the spanning tree. We hope to find a body that is
449 // serving as a parent but has never been listed as a child; that is crying out
450 // to be connected directly to Ground. Failing that, we'll pick a body that
451 // has been used as a parent often.
chooseNewBaseBody() const452 int MultibodyGraphMaker::chooseNewBaseBody() const {
453 bool parentOnlyBodySeen = false;
454 int bestBody = -1; int nChildren=-1;
455 // Skip the Ground body.
456 for (int bx=1; bx < getNumBodies(); ++bx) {
457 const Body& body = bodies[bx];
458 if (body.isInTree())
459 continue; // unavailable
460 if (parentOnlyBodySeen && !body.jointsAsChild.empty())
461 continue; // already seen parent-only bodies; no need for this one
462 if (!parentOnlyBodySeen && body.jointsAsChild.empty()) {
463 // This is our first parent-only body; it is automatically the
464 // best candidate now.
465 parentOnlyBodySeen = true;
466 bestBody = bx; nChildren = (int)body.jointsAsParent.size();
467 } else { // Keep the body that has the most children.
468 if ((int)body.jointsAsParent.size() > nChildren) {
469 bestBody = bx;
470 nChildren = (int)body.jointsAsParent.size();
471 }
472 }
473 }
474 return bestBody;
475 }
476
477
478 //------------------------------------------------------------------------------
479 // CONNECT BODY TO GROUND
480 //------------------------------------------------------------------------------
481 // Connect the given body to Ground by a free joint with parent Ground and
482 // child the given body. This makes the body a base body (level 1 body)
483 // for some subtree of the multibody graph.
connectBodyToGround(int bodyNum)484 void MultibodyGraphMaker::connectBodyToGround(int bodyNum) {
485 const Body& body = getBody(bodyNum);
486 assert(!body.isInTree());
487 const std::string jointName = "#" + getGroundBodyName() + "_" + body.name;
488 addJoint(jointName, getFreeJointTypeName(),
489 getGroundBodyName(), body.name, false, NULL);
490 updJoint(jointName).isAddedBaseJoint = true;
491 }
492
493
494 //------------------------------------------------------------------------------
495 // ADD MOBILIZER FOR JOINT
496 //------------------------------------------------------------------------------
497 // We've already determined this joint is eligible to become a mobilizer; now
498 // make it so. Given a joint for which either the parent or child is in the
499 // tree, but not both, create a mobilizer implementing this joint and attaching
500 // the unattached body (which will be the mobilizer's outboard body) to the
501 // tree. The mobilizer number is returned and also recorded in the joint
502 // and outboard body.
addMobilizerForJoint(int jointNum)503 int MultibodyGraphMaker::addMobilizerForJoint(int jointNum) {
504 Joint& joint = updJoint(jointNum);
505 assert(!joint.mustBeLoopJoint); // can't be a tree joint
506 assert(!joint.hasMobilizer()); // already done
507 const int pNum = joint.parentBodyNum, cNum = joint.childBodyNum;
508 Body& parent = updBody(pNum);
509 Body& child = updBody(cNum);
510 // Exactly one of these must already be in the tree.
511 assert(parent.isInTree() ^ child.isInTree());
512
513 const int mobNum = (int)mobilizers.size(); // next available
514 if (parent.isInTree()) {
515 // Child is outboard body (forward joint)
516 child.level = parent.level + 1;
517 mobilizers.push_back(Mobilizer(jointNum,child.level,
518 pNum,cNum,false,this));
519 child.mobilizer = mobNum;
520 } else if (child.isInTree()) {
521 // Parent is outboard body (reverse joint)
522 parent.level = child.level + 1;
523 mobilizers.push_back(Mobilizer(jointNum,parent.level,
524 cNum,pNum,true,this));
525 parent.mobilizer = mobNum;
526 }
527 joint.mobilizer = mobNum;
528 return mobNum;
529 }
530
531
532 //------------------------------------------------------------------------------
533 // FIND HEAVIEST UNASSIGNED FORWARD JOINT
534 //------------------------------------------------------------------------------
535 // Starting with a given body that is in the tree already, look at its
536 // unassigned children (meaning bodies connected by joints that aren't
537 // mobilizers yet) and return the joint connecting it to the child with the
538 // largest mass. (The idea is that this would be a good direction to extend the
539 // chain.) Return -1 if this body has no children.
540 int MultibodyGraphMaker::
findHeaviestUnassignedForwardJoint(int inboardBody) const541 findHeaviestUnassignedForwardJoint(int inboardBody) const {
542 const Body& inb = getBody(inboardBody);
543 int jointNum = -1;
544 double maxMass=0;
545 // Search joints for which this is the parent body.
546 for (unsigned i=0; i < inb.jointsAsParent.size(); ++i) {
547 const int jfwd = inb.jointsAsParent[i];
548 const Joint& joint = joints[jfwd];
549 if (joint.hasMobilizer()) continue; // already in the tree
550 if (joint.mustBeLoopJoint) continue; // can't be a tree joint
551 const Body& child = getBody(joint.childBodyNum);
552 if (child.isInTree()) continue; // this is a loop joint
553 if (child.mass > maxMass) {
554 jointNum = jfwd;
555 maxMass = child.mass;
556 }
557 }
558 return jointNum;
559 }
560
561
562 //------------------------------------------------------------------------------
563 // FIND HEAVIEST UNASSIGNED REVERSE JOINT
564 //------------------------------------------------------------------------------
565 // Same as previous method, but now we are looking for unassigned joints where
566 // the given body serves as the child so we would have to reverse the joint to
567 // add it to the tree. (This is less desirable so is a fallback.)
568 int MultibodyGraphMaker::
findHeaviestUnassignedReverseJoint(int inboardBody) const569 findHeaviestUnassignedReverseJoint(int inboardBody) const {
570 const Body& inb = getBody(inboardBody);
571 int jointNum = -1;
572 double maxMass=0;
573 // Search joints for which this is the child body.
574 for (unsigned i=0; i < inb.jointsAsChild.size(); ++i) {
575 const int jrev = inb.jointsAsChild[i];
576 const Joint& joint = joints[jrev];
577 if (joint.hasMobilizer()) continue; // already in the tree
578 if (joint.mustBeLoopJoint) continue; // can't be a tree joint
579 const Body& parent = getBody(joint.parentBodyNum);
580 if (parent.isInTree()) continue; // this is a loop joint
581 if (parent.mass > maxMass) {
582 jointNum = jrev;
583 maxMass = parent.mass;
584 }
585 }
586 return jointNum;
587 }
588
589
590 //------------------------------------------------------------------------------
591 // GROW TREE
592 //------------------------------------------------------------------------------
593 // Process unused joints for which one body is in the tree (at level h) and the
594 // other is not. Add the other body to the tree at level h+1, marking the joint
595 // as forward (other body is child) or reverse (other body is parent). Repeat
596 // until no changes are made. Does not assign loop joints or any bodies that
597 // don't have a path to Ground. Extend the multibody tree starting with the
598 // lowest-level eligible joints and moving outwards. We're doing this
599 // breadth-first so that we get roughly even-length chains and we'll stop at
600 // the first level at which we fail to find any joint. If we fail to consume
601 // all the bodies, the caller will have to add a level 1 joint to attach a
602 // previously-disconnected base body to Ground and then call this again.
603 //
604 // We violate the breadth-first heuristic to avoid ending a branch with a
605 // massless body, unless it is welded to its parent. If we add a mobile massless
606 // body, we'll keep going out along its branch until we hit a massful body. It
607 // is a disaster if we fail to find a massful body because a tree that
608 // terminates in a mobile massless body will generate a singular mass matrix.
609 // We'll throw an exception in that case, but note that this may just be a
610 // failure of the heuristic; there may be some tree that could have avoided the
611 // terminal massless body but we failed to discover it.
612 //
613 // TODO: keep a list of unprocessed joints so we don't have to go through
614 // all of them again at each level.
growTree()615 void MultibodyGraphMaker::growTree() {
616 // Record the joints for which we added mobilizers during this subtree
617 // sweep. That way if we jumped levels ahead due to massless bodies we
618 // can take credit and keep going rather than quit.
619 std::set<int> jointsAdded;
620 for (int level=1; ;++level) { // level of outboard (mobilized) body
621 bool anyMobilizerAdded = false;
622 for (int jNum=0; jNum<getNumJoints(); ++jNum) {
623 // See if this joint is at the right level (meaning its inboard
624 // body is at level-1) and is available to become a mobilizer.
625 const Joint& joint = getJoint(jNum);
626 if (joint.hasMobilizer()) {
627 // Already done -- one of ours?
628 if (jointsAdded.count(jNum)) {
629 // We added it during this growTree() call -- is it at
630 // the current level?
631 const Mobilizer& mob = mobilizers[joint.mobilizer];
632 if (mob.getLevel() == level)
633 anyMobilizerAdded = true; // Added one at level.
634 }
635 continue;
636 }
637 if (joint.mustBeLoopJoint) continue; // can't be a tree joint
638 const Body& parent = getBody(joint.parentBodyNum);
639 const Body& child = getBody(joint.childBodyNum);
640 // Exactly one of parent or child must already be in the tree.
641 if (!(parent.isInTree() ^ child.isInTree())) continue;
642 if (parent.isInTree()) {
643 if (parent.level != level-1) continue; // not time yet
644 } else { // child is in tree
645 if (child.level != level-1) continue; // not time yet
646 }
647 addMobilizerForJoint(jNum);
648 jointsAdded.insert(jNum);
649 anyMobilizerAdded = true;
650
651 // We just made joint jNum a mobilizer. If its outboard body
652 // is massless and the mobilizer was not a weld, we need to keep
653 // extending this branch of the tree until we can end it with a
654 // massful body.
655 const Body& outboard = getBody(mobilizers.back().outboardBody);
656 const JointType& jtype = getJointType(joint.jointTypeNum);
657 if (jtype.numMobilities == 0 || outboard.mass > 0)
658 continue;
659
660 // Pick a further-outboard body with mass and extend branch to it.
661 // Prefer forward-direction joints if possible.
662 // If only massless outboard bodies are available, add one and
663 // keep going.
664 while (true) {
665 const int bNum = mobilizers.back().outboardBody;
666 const int jfwd = findHeaviestUnassignedForwardJoint(bNum);
667 if (jfwd>=0 && getBody(getJoint(jfwd).childBodyNum).mass > 0) {
668 addMobilizerForJoint(jfwd);
669 jointsAdded.insert(jfwd);
670 break;
671 }
672 const int jrev = findHeaviestUnassignedReverseJoint(bNum);
673 if (jrev>=0 && getBody(getJoint(jrev).parentBodyNum).mass > 0) {
674 addMobilizerForJoint(jrev);
675 jointsAdded.insert(jrev);
676 break;
677 }
678
679 // Couldn't find a massful body to add. Add another massless
680 // body (if there is one) and keep trying.
681 if (jfwd >= 0) {
682 addMobilizerForJoint(jfwd);
683 jointsAdded.insert(jfwd);
684 continue;
685 }
686 if (jrev >= 0) {
687 addMobilizerForJoint(jrev);
688 jointsAdded.insert(jrev);
689 continue;
690 }
691
692 // Uh oh. Nothing outboard of the massless body we just added.
693 // We're ending this chain with a massless body; not good.
694 throw std::runtime_error("growTree(): algorithm produced an"
695 " invalid tree containing a terminal massless body ("
696 + getBody(bNum).name + "). This may be due to an invalid"
697 " model or failure of heuristics. In the latter case you"
698 " may be able to get a valid tree by forcing a different"
699 " loop break or changing parent->child ordering.");
700 }
701 }
702 if (!anyMobilizerAdded)
703 break;
704 }
705 }
706
707
708 //------------------------------------------------------------------------------
709 // BREAK LOOPS
710 //------------------------------------------------------------------------------
711 // Find any remaining unused joints, which will have both parent and
712 // child bodies already in the tree. This includes joints that the user
713 // told us must be loop joints, and ones picked by the algorithm.
714 // For each of those, implement the joint with a loop constraint if one
715 // is provided, otherwise implement it with a mobilizer and split off a
716 // slave body from the child body, use that slave as the outboard body
717 // for the mobilizer, and add a weld constraint to reconnect the slave to
718 // its master (the original child body).
breakLoops()719 void MultibodyGraphMaker::breakLoops() {
720 for (int jx=0; jx < getNumJoints(); ++jx) {
721 Joint& jinfo = joints[jx];
722 if (jinfo.hasMobilizer()) continue; // already done
723 const int px = jinfo.parentBodyNum, cx = jinfo.childBodyNum;
724 assert(getBody(px).isInTree() && getBody(cx).isInTree());
725
726 const JointType& jtype = getJointType(jinfo.jointTypeNum);
727 if (jtype.haveGoodLoopJointAvailable) {
728 const int loopNum = (int)constraints.size();
729 constraints.push_back(LoopConstraint(jtype.name,jx,px,cx,this));
730 jinfo.loopConstraint = loopNum;
731 continue;
732 }
733
734 // No usable loop constraint for this type of joint. Add a new slave
735 // body so we can use a mobilizer. (Body references are invalidated here
736 // since we're adding a new body -- don't reuse them.)
737 const int sx = splitBody(cx);
738 Body& parent = updBody(px);
739 Body& child = updBody(cx);
740 Body& slave = updBody(sx);
741
742 // Mobilize the slave body.
743 const int mobNum = (int)mobilizers.size(); // next available
744 const int level = parent.level+1;
745 jinfo.mobilizer = slave.mobilizer = mobNum;
746 slave.jointsAsChild.push_back(jx);
747 mobilizers.push_back(Mobilizer(jx,level,px,sx,false,this));
748 slave.level = level;
749 }
750 }
751
752
753 //------------------------------------------------------------------------------
754 // BODIES ARE CONNECTED
755 //------------------------------------------------------------------------------
756 // Return true if there is a joint between two bodies given by body number,
757 // regardless of parent/child ordering.
bodiesAreConnected(int b1,int b2) const758 bool MultibodyGraphMaker::bodiesAreConnected(int b1, int b2) const {
759 const Body& body1 = getBody(b1);
760 for (unsigned i=0; i < body1.jointsAsParent.size(); ++i)
761 if (getJoint(body1.jointsAsParent[i]).childBodyNum == b2)
762 return true;
763 for (unsigned i=0; i < body1.jointsAsChild.size(); ++i)
764 if (getJoint(body1.jointsAsChild[i]).parentBodyNum == b2)
765 return true;
766 return false;
767 }
768
769 //------------------------------------------------------------------------------
770 // FORGET GRAPH
771 //------------------------------------------------------------------------------
772 // Restore the Body to its state prior to generateGraph()
forgetGraph(MultibodyGraphMaker & graph)773 void MultibodyGraphMaker::Body::forgetGraph(MultibodyGraphMaker& graph)
774 {
775 level = -1;
776 mobilizer = -1;
777 master = -1;
778 slaves.clear();
779 }
780
781 //------------------------------------------------------------------------------
782 // FORGET GRAPH
783 //------------------------------------------------------------------------------
784 // Restore the Joint to its state prior to generateGraph()
forgetGraph(MultibodyGraphMaker & graph)785 bool MultibodyGraphMaker::Joint::forgetGraph(MultibodyGraphMaker& graph)
786 {
787 if (isAddedBaseJoint) {
788 graph.deleteJoint(name);
789 return true;
790 }
791 mobilizer = -1;
792 loopConstraint = -1;
793 return false;
794 }
795 } // namespace SimTK
796
797