@@ -20,9 +20,9 @@ const char kPrismaticType[] = "prismatic";
20
20
// Arbitrary world name for testing.
21
21
const char kWorldLinkName [] = " DefaultWorldLinkName" ;
22
22
23
- // Test a straightforward serial chain of bodies connected by
24
- // revolute joints: world->body1->body2->body3->body4->body5.
25
- // We perform a number of sanity checks on the provided API.
23
+ /* Test a straightforward serial chain of bodies connected by
24
+ revolute joints: world->body1->body2->body3->body4->body5.
25
+ We perform a number of sanity checks on the provided API. */
26
26
GTEST_TEST (LinkJointGraph, SerialChain) {
27
27
LinkJointGraph graph;
28
28
EXPECT_EQ (graph.num_joint_types (), 3 ); // predefined types thus far
@@ -191,7 +191,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
191
191
2 [7] =>free9 (added 6dof, free bodies are always last)
192
192
193
193
Composite Links: {0 7 6 8} {11 10}
194
- Composite Mobods: none
194
+ Composite Mobods: [0] (just the World "composite")
195
195
*/
196
196
197
197
// TODO(sherm1) Move to its own test suite.
@@ -250,7 +250,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
250
250
251
251
EXPECT_EQ (ssize (forest.mobods ()), 8 );
252
252
EXPECT_EQ (ssize (forest.trees ()), 3 );
253
- EXPECT_TRUE ( forest.composite_mobods (). empty ());
253
+ EXPECT_EQ ( ssize ( forest.composite_mobods ()), 1 ); // just World
254
254
255
255
// Now force one of the joints in the composite to be modeled (meaning it
256
256
// should get its own Mobod). This should split the World composite into
@@ -266,9 +266,9 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
266
266
267
267
EXPECT_EQ (ssize (forest.mobods ()), 9 );
268
268
EXPECT_EQ (ssize (forest.trees ()), 3 );
269
- EXPECT_EQ (ssize (forest.composite_mobods ()), 1 );
269
+ EXPECT_EQ (ssize (forest.composite_mobods ()), 2 );
270
270
const std::vector<MobodIndex> now_expected{MobodIndex (6 ), MobodIndex (7 )};
271
- EXPECT_EQ (forest.composite_mobods ()[ 0 ] , now_expected);
271
+ EXPECT_EQ (forest.composite_mobods (CompositeMobodIndex ( 1 )) , now_expected);
272
272
273
273
/* Build again but with the FixedBase option for model_instance, and allow
274
274
joint_10_11 to be part of a composite (default behavior). Now we expect
@@ -278,7 +278,7 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
278
278
0 [1-5] ->link1->link2->link3->link4->link5
279
279
280
280
Composite Links: {0 7 6 8 11 10 9}
281
- Composite Mobods: none
281
+ Composite Mobods: [0] (just World)
282
282
*/
283
283
graph.change_joint_flags (joint_10_11_index, JointFlags::Default);
284
284
graph.BuildForest (ModelingOptions::CombineCompositeLinks,
@@ -292,12 +292,13 @@ GTEST_TEST(LinkJointGraph, SerialChain) {
292
292
293
293
EXPECT_EQ (ssize (forest.mobods ()), 6 );
294
294
EXPECT_EQ (ssize (forest.trees ()), 1 );
295
- EXPECT_EQ (ssize (forest.composite_mobods ()), 0 );
295
+ EXPECT_EQ (ssize (forest.composite_mobods ()), 1 ); // just World
296
296
const std::vector<LinkIndex> expected_composite_link{
297
297
LinkIndex (0 ), LinkIndex (7 ), LinkIndex (6 ), LinkIndex (8 ),
298
298
LinkIndex (11 ), LinkIndex (10 ), LinkIndex (9 )};
299
299
EXPECT_EQ (ssize (graph.composite_links ()), 1 );
300
- EXPECT_EQ (graph.composite_links ()[0 ], expected_composite_link);
300
+ EXPECT_EQ (graph.composite_links (CompositeLinkIndex (0 )),
301
+ expected_composite_link);
301
302
}
302
303
303
304
/* This is a straightforward graph with two trees each with multiple branches.
@@ -354,11 +355,11 @@ GTEST_TEST(LinkJointGraph, MultipleBranches) {
354
355
EXPECT_EQ (ssize (graph.links ()), 15 ); // no links added
355
356
EXPECT_EQ (graph.num_user_joints (), 12 ); // the ones added above
356
357
EXPECT_EQ (ssize (graph.joints ()), 14 ); // modeling adds two floating joints
357
- EXPECT_TRUE ( graph.composite_links (). empty ());
358
+ EXPECT_EQ ( ssize ( graph.composite_links ()), 1 ); // just World
358
359
359
360
EXPECT_EQ (ssize (forest.trees ()), 2 );
360
361
EXPECT_EQ (ssize (forest.mobods ()), 15 ); // includes World
361
- EXPECT_TRUE ( forest.composite_mobods (). empty ());
362
+ EXPECT_EQ ( ssize ( forest.composite_mobods ()), 1 ); // just World
362
363
EXPECT_EQ (forest.num_positions (), 26 ); // 12 revolute, 2 x 17 quat floating
363
364
EXPECT_EQ (forest.num_velocities (), 24 );
364
365
@@ -1037,12 +1038,12 @@ GTEST_TEST(LinkJointGraph, LoopWithComposites) {
1037
1038
EXPECT_EQ (ssize (graph.links ()), 12 ); // split one, added shadow
1038
1039
EXPECT_EQ (ssize (graph.joints ()), 11 ); // no change
1039
1040
EXPECT_EQ (ssize (graph.constraints ()), 1 ); // welded shadow to primary
1040
- EXPECT_EQ (ssize (graph.composite_links ()), 3 );
1041
+ EXPECT_EQ (ssize (graph.composite_links ()), 4 ); // World + 3
1041
1042
1042
1043
EXPECT_EQ (ssize (forest.mobods ()), 9 );
1043
1044
EXPECT_EQ (ssize (forest.loop_constraints ()), 1 );
1044
1045
EXPECT_EQ (ssize (forest.trees ()), 2 );
1045
- EXPECT_EQ (ssize (forest.composite_mobods ()), 0 );
1046
+ EXPECT_EQ (ssize (forest.composite_mobods ()), 1 ); // just World
1046
1047
1047
1048
EXPECT_EQ (forest.mobods (MobodIndex (1 )).follower_links (),
1048
1049
(std::vector<LinkIndex>{LinkIndex (1 ), LinkIndex (2 )}));
@@ -1107,6 +1108,92 @@ GTEST_TEST(LinkJointGraph, LoopWithComposites) {
1107
1108
graph_copy.forest ().SanityCheckForest (); // Should be empty but OK
1108
1109
}
1109
1110
1111
+ /* For both composite_links and composite_mobods: the World composite must
1112
+ come first (even if nothing is welded to World). This graph's first branch has
1113
+ a composite that could be seen prior to the weld to World. We'll attempt
1114
+ to trick it into following that path by using a massless body, requiring it
1115
+ to extend the first branch to Link {2} before moving on to the next branch.
1116
+ But we want to see the {0,3} composite before the {1,2} composite.
1117
+
1118
+ +---> {1*} ===> {2}
1119
+ {0} | 0 1 {Links} & Joints
1120
+ World | ===> is a weld
1121
+ +===> {3} * Link 1 is massless
1122
+ | 2
1123
+ |
1124
+ +---> {4}
1125
+ 3
1126
+ */
1127
+ GTEST_TEST (LinkJointGraph, WorldCompositeComesFirst) {
1128
+ LinkJointGraph graph;
1129
+ graph.RegisterJointType (kRevoluteType , 1 , 1 );
1130
+ const ModelInstanceIndex model_instance (5 ); // arbitrary
1131
+
1132
+ // The first Link added defines the world's name and forest instance.
1133
+ graph.AddLink (" world" , world_model_instance ());
1134
+ graph.AddLink (" massless_link_1" , model_instance, LinkFlags::TreatAsMassless);
1135
+ graph.AddLink (" link2" , model_instance);
1136
+ graph.AddLink (" link3" , model_instance);
1137
+ graph.AddLink (" link4" , model_instance);
1138
+
1139
+ const auto & world = graph.links (LinkIndex (0 ));
1140
+ const auto & massless_link = graph.links (LinkIndex (1 ));
1141
+ const auto & link2 = graph.links (LinkIndex (2 ));
1142
+ const auto & link3 = graph.links (LinkIndex (3 ));
1143
+ const auto & link4 = graph.links (LinkIndex (4 ));
1144
+
1145
+ graph.AddJoint (" joint0" , model_instance,
1146
+ kRevoluteType , world.index (), massless_link.index ());
1147
+ graph.AddJoint (" joint1" , model_instance,
1148
+ " weld" , massless_link.index (), link2.index ());
1149
+ graph.AddJoint (" joint2" , model_instance,
1150
+ " weld" , world.index (), link3.index ());
1151
+ graph.AddJoint (" joint4" , model_instance,
1152
+ kRevoluteType , world.index (), link4.index ());
1153
+
1154
+ const SpanningForest& forest = graph.BuildForest (); // Default options
1155
+ forest.SanityCheckForest ();
1156
+ graph.DumpGraph (" WorldCompositeComesFirst" );
1157
+ forest.DumpForest (" WorldCompositeComesFirst" );
1158
+
1159
+ EXPECT_EQ (ssize (graph.links ()), 5 );
1160
+ EXPECT_EQ (ssize (forest.mobods ()), 5 ); // Because we're not combining.
1161
+
1162
+ // "Anchored" means "fixed to World" (by welds).
1163
+ EXPECT_TRUE (world.is_anchored ());
1164
+ EXPECT_FALSE (massless_link.is_anchored ());
1165
+ EXPECT_FALSE (link2.is_anchored ());
1166
+ EXPECT_TRUE (link3.is_anchored ());
1167
+ EXPECT_FALSE (link4.is_anchored ());
1168
+
1169
+ EXPECT_EQ (ssize (graph.composite_links ()), 2 );
1170
+ EXPECT_EQ (graph.composite_links (CompositeLinkIndex (0 )),
1171
+ (std::vector<LinkIndex>{LinkIndex (0 ), LinkIndex (3 )}));
1172
+ EXPECT_EQ (graph.composite_links (CompositeLinkIndex (1 )),
1173
+ (std::vector<LinkIndex>{LinkIndex (1 ), LinkIndex (2 )}));
1174
+
1175
+ EXPECT_EQ (ssize (forest.composite_mobods ()), 2 );
1176
+ EXPECT_EQ (forest.composite_mobods (CompositeMobodIndex (0 )),
1177
+ (std::vector<MobodIndex>{MobodIndex (0 ), MobodIndex (3 )}));
1178
+ EXPECT_EQ (forest.composite_mobods (CompositeMobodIndex (1 )),
1179
+ (std::vector<MobodIndex>{MobodIndex (1 ), MobodIndex (2 )}));
1180
+
1181
+ // Remodel making single Mobods for composite links.
1182
+ graph.BuildForest (ModelingOptions::CombineCompositeLinks);
1183
+ forest.SanityCheckForest ();
1184
+ forest.DumpForest (" WorldCompositeComesFirst -- combining" );
1185
+ EXPECT_EQ (ssize (forest.mobods ()), 3 ); // Because we're combining.
1186
+ EXPECT_EQ (forest.mobods (MobodIndex (0 )).follower_links (),
1187
+ (std::vector<LinkIndex>{LinkIndex (0 ), LinkIndex (3 )}));
1188
+ EXPECT_EQ (forest.mobods (MobodIndex (1 )).follower_links (),
1189
+ (std::vector<LinkIndex>{LinkIndex (1 ), LinkIndex (2 )}));
1190
+ EXPECT_EQ (forest.mobods (MobodIndex (2 )).follower_links (),
1191
+ (std::vector<LinkIndex>{LinkIndex (4 )}));
1192
+
1193
+ EXPECT_EQ (ssize (graph.composite_links ()), 2 ); // no change expected
1194
+ EXPECT_EQ (ssize (forest.composite_mobods ()), 1 ); // just World now
1195
+ }
1196
+
1110
1197
} // namespace internal
1111
1198
} // namespace multibody
1112
1199
} // namespace drake
0 commit comments