forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
multibody_plant.cc
3231 lines (2842 loc) · 133 KB
/
multibody_plant.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "drake/multibody/plant/multibody_plant.h"
#include <algorithm>
#include <functional>
#include <limits>
#include <memory>
#include <set>
#include <stdexcept>
#include <vector>
#include "drake/common/drake_throw.h"
#include "drake/common/text_logging.h"
#include "drake/common/unused.h"
#include "drake/geometry/frame_kinematics_vector.h"
#include "drake/geometry/geometry_frame.h"
#include "drake/geometry/geometry_instance.h"
#include "drake/geometry/geometry_roles.h"
#include "drake/geometry/query_results/contact_surface.h"
#include "drake/geometry/render/render_label.h"
#include "drake/math/orthonormal_basis.h"
#include "drake/math/random_rotation.h"
#include "drake/math/rotation_matrix.h"
#include "drake/multibody/contact_solvers/sparse_linear_operator.h"
#include "drake/multibody/plant/discrete_contact_pair.h"
#include "drake/multibody/plant/externally_applied_spatial_force.h"
#include "drake/multibody/plant/hydroelastic_traction_calculator.h"
#include "drake/multibody/tree/prismatic_joint.h"
#include "drake/multibody/tree/revolute_joint.h"
namespace drake {
namespace multibody {
// Helper macro to throw an exception within methods that should not be called
// post-finalize.
#define DRAKE_MBP_THROW_IF_FINALIZED() ThrowIfFinalized(__func__)
// Helper macro to throw an exception within methods that should not be called
// pre-finalize.
#define DRAKE_MBP_THROW_IF_NOT_FINALIZED() ThrowIfNotFinalized(__func__)
using geometry::ContactSurface;
using geometry::FrameId;
using geometry::FramePoseVector;
using geometry::GeometryFrame;
using geometry::GeometryId;
using geometry::GeometryInstance;
using geometry::PenetrationAsPointPair;
using geometry::ProximityProperties;
using geometry::render::RenderLabel;
using geometry::SceneGraph;
using geometry::SourceId;
using systems::InputPort;
using systems::OutputPort;
using systems::State;
using drake::math::RigidTransform;
using drake::math::RotationMatrix;
using drake::multibody::internal::AccelerationKinematicsCache;
using drake::multibody::internal::ArticulatedBodyForceCache;
using drake::multibody::internal::ArticulatedBodyInertiaCache;
using drake::multibody::internal::PositionKinematicsCache;
using drake::multibody::internal::VelocityKinematicsCache;
using drake::multibody::MultibodyForces;
using drake::multibody::SpatialAcceleration;
using drake::multibody::SpatialForce;
using systems::BasicVector;
using systems::Context;
using systems::InputPort;
using systems::InputPortIndex;
using systems::OutputPortIndex;
namespace internal {
// This is a helper struct used to estimate the parameters used in the penalty
// method to enforce joint limits.
// The penalty method applies at each joint, a spring-damper force with
// parameters estimated by this struct.
// Once a joint reaches a limit (either lower or upper), the governing equations
// for that joint's coordinate can be approximated by a harmonic oscillator with
// stiffness and damping corresponding to the penalty parameters for that joint
// as: q̈ + 2ζω₀ q̇ + ω₀² q = 0, where ω₀² = k / m̃ is the characteristic
// numerical stiffness frequency and m̃ is an inertia term computed differently
// for prismatic and revolute joints.
// The numerical frequency is defined as ω₀ = 2π/τ₀ with τ₀ = αδt a numerical
// stiffness time scale set to be proportional to the time step of the discrete
// model. The damping ratio ζ is set to one, corresponding to a critically
// damped oscillator and thus so that the penalty method emulates the effect of
// a "hard" limit.
// Knowing ω₀ (from the time step) and m̃ (a function of the bodies connected by
// the joint), it is possible, from the equations for a harmonic oscillator, to
// estimate the stiffness k and damping d parameters for the penalty method.
// Finally, MultibodyPlant uses a value of α to guarantee the stability of the
// method (from a stability analysis of the time stepping method for the
// model of a harmonic oscillator).
// Using this estimation procedure, the stiffness k is shown to be proportional
// to the inverse of the time step squared, i.e. k ∝ 1/δt².
// Since, at steady state, the violation of the joint limit is inversely
// proportional to the stiffness parameter, this violation turns out being
// proportional to the time step squared, that is, Δq ∝ δt².
// Therefore the convergence of the joint limit violation is expected to be
// quadratic with the time step.
template <typename T>
struct JointLimitsPenaltyParametersEstimator {
// This helper method returns a pair (k, d) (in that order) for a harmonic
// oscillator given the period τ₀ of the oscillator and the inertia m̃. d is
// computed for a critically damped oscillator.
// The harmonic oscillator model corresponds to:
// m̃q̈ + d q̇ + k q = 0
// or equivalently:
// q̈ + 2ζω₀ q̇ + ω₀² q = 0
// with ω₀ = sqrt(k/m̃) and ζ = d/sqrt(km̃)/2 the damping ratio, which is one
// for critically damped oscillators.
static std::pair<double, double>
CalcCriticallyDampedHarmonicOscillatorParameters(
double period, double inertia) {
const double damping_ratio = 1.0; // Critically damped.
const double omega0 = 2.0 * M_PI / period;
const double stiffness = inertia * omega0 * omega0;
const double damping = 2.0 * damping_ratio * std::sqrt(inertia * stiffness);
return std::make_pair(stiffness, damping);
}
// This method combines a pair of penalty parameters params1 and params2.
// The combination law is very simple, this method returns the set of
// parameters with the smallest stiffness, and thus it favors the stiffness
// leading to the lower numerical stiffness (thus guaranteeing stability).
static std::pair<double, double> PickLessStiffPenaltyParameters(
const std::pair<double, double>& params1,
const std::pair<double, double>& params2) {
const double stiffness1 = params1.first;
const double stiffness2 = params2.first;
if (stiffness1 < stiffness2) {
return params1;
} else {
return params2;
}
}
// Helper method to estimate the penalty parameters for a prismatic joint.
// The strategy consists in computing a set of penalty parameters for each
// body connected by joint as if the other body was welded and ignoring
// any other bodies in the system. This leads to a spring mass system where
// the inertia m̃ corresponds to the mass of the body in consideration.
// Then the penalty parameters estimated for each body are combined with
// PickLessStiffPenaltyParameters() leading to a single set of parameters.
static std::pair<double, double> CalcPrismaticJointPenaltyParameters(
const PrismaticJoint<T>& joint, double numerical_time_scale) {
// Penalty parameters for the parent body (child fixed).
const double parent_mass = joint.parent_body().index() == world_index() ?
std::numeric_limits<double>::infinity() :
joint.parent_body().get_default_mass();
const auto parent_params = CalcCriticallyDampedHarmonicOscillatorParameters(
numerical_time_scale, parent_mass);
// Penalty parameters for the child body (parent fixed).
const double child_mass = joint.child_body().index() == world_index() ?
std::numeric_limits<double>::infinity() :
joint.child_body().get_default_mass();
const auto child_params = CalcCriticallyDampedHarmonicOscillatorParameters(
numerical_time_scale, child_mass);
// Return the combined penalty parameters of the two bodies.
auto params = PickLessStiffPenaltyParameters(parent_params, child_params);
return params;
}
// Helper method to estimate the penalty parameters for a revolute joint.
// The strategy consists in computing a set of penalty parameters for each
// body connected by joint as if the other body was welded and ignoring
// any other bodies in the system. This leads to a torsional spring system
// for which the inertia m̃ corresponds to the rotational inertia of the body
// in consideration, computed about the axis of the joint.
// Then the penalty parameters estimated for each body are combined with
// PickLessStiffPenaltyParameters() leading to a single set of parameters.
static std::pair<double, double> CalcRevoluteJointPenaltyParameters(
const RevoluteJoint<T>& joint, double numerical_time_scale) {
// For the body attached to `frame` (one of the parent/child frames of
// `joint`), this helper lambda computes the rotational inertia of the body
// about the axis of the joint.
// That is, it computes Iₐ = âᵀ⋅Iᴮ⋅â where Iᴮ is the rotational inertia of
// the body, â is the axis of the joint, and Iₐ is the (scalar) rotational
// inertia of the body computed about the joint's axis. Iₐ is the inertia
// that must be considered for the problem of a pendulum oscillating about
// an axis â, leading to the equations for a harmonic oscillator when we
// apply the penalty forces.
// For further details on Iₐ, the interested reader can refer to
// [Goldstein, 2014, §5.3].
//
// [Goldstein, 2014] Goldstein, H., Poole, C.P. and Safko, J.L., 2014.
// Classical Mechanics: Pearson New International Edition.
// Pearson Higher Ed.
auto CalcRotationalInertiaAboutAxis = [&joint](const Frame<T>& frame) {
const RigidBody<T>* body =
dynamic_cast<const RigidBody<T>*>(&frame.body());
DRAKE_THROW_UNLESS(body != nullptr);
// This check is needed for such models for which the user leaves the
// spatial inertias unspecified (i.e. initialized to NaN). A user
// might do this when only interested in performing kinematics
// computations.
if (std::isnan(body->get_default_mass())) {
return std::numeric_limits<double>::infinity();
}
const SpatialInertia<T>& M_PPo_P =
body->default_spatial_inertia().template cast<T>();
const RigidTransform<T> X_PJ = frame.GetFixedPoseInBodyFrame();
const Vector3<T>& p_PJ = X_PJ.translation();
const math::RotationMatrix<T>& R_PJ = X_PJ.rotation();
const SpatialInertia<T> M_PJo_J =
M_PPo_P.Shift(p_PJ).ReExpress(R_PJ);
const RotationalInertia<T> I_PJo_J =
M_PJo_J.CalcRotationalInertia();
// Rotational inertia about the joint axis.
const Vector3<T>& axis = joint.revolute_axis();
const T I_a = axis.transpose() * (I_PJo_J * axis);
return ExtractDoubleOrThrow(I_a);
};
// Rotational inertia about the joint's axis for the parent body.
const double I_Pa =
joint.parent_body().index() == world_index() ?
std::numeric_limits<double>::infinity() :
CalcRotationalInertiaAboutAxis(joint.frame_on_parent());
auto parent_params = CalcCriticallyDampedHarmonicOscillatorParameters(
numerical_time_scale, I_Pa);
// Rotational inertia about the joint's axis for the child body.
const double I_Ca =
joint.child_body().index() == world_index() ?
std::numeric_limits<double>::infinity() :
CalcRotationalInertiaAboutAxis(joint.frame_on_child());
auto child_params = CalcCriticallyDampedHarmonicOscillatorParameters(
numerical_time_scale, I_Ca);
// Return the combined penalty parameters of the two bodies.
return PickLessStiffPenaltyParameters(parent_params, child_params);
}
};
} // namespace internal
namespace {
// Hack to fully qualify frame names, pending resolution of #9128. Used by
// geometry registration routines.
template <typename T>
std::string GetScopedName(
const MultibodyPlant<T>& plant,
ModelInstanceIndex model_instance, const std::string& name) {
if (model_instance != world_model_instance() &&
model_instance != default_model_instance()) {
return plant.GetModelInstanceName(model_instance) + "::" + name;
} else {
return name;
}
}
} // namespace
template <typename T>
MultibodyPlant<T>::MultibodyPlant(double time_step)
: MultibodyPlant(nullptr, time_step) {}
template <typename T>
MultibodyPlant<T>::MultibodyPlant(
std::unique_ptr<internal::MultibodyTree<T>> tree_in, double time_step)
: internal::MultibodyTreeSystem<T>(
systems::SystemTypeTag<MultibodyPlant>{},
std::move(tree_in), time_step > 0),
time_step_(time_step) {
DRAKE_THROW_UNLESS(time_step >= 0);
// TODO(eric.cousineau): Combine all of these elements into one struct, make
// it less brittle.
visual_geometries_.emplace_back(); // Entries for the "world" body.
collision_geometries_.emplace_back();
X_WB_default_list_.emplace_back();
// Add the world body to the graph.
multibody_graph_.AddBody(world_body().name(), world_body().model_instance());
DeclareSceneGraphPorts();
}
template <typename T>
std::string MultibodyPlant<T>::GetTopologyGraphvizString() const {
std::string graphviz = "digraph MultibodyPlant {\n";
graphviz += "label=\"" + this->get_name() + "\";\n";
graphviz += "rankdir=BT;\n";
graphviz += "labelloc=t;\n";
// Create a subgraph for each model instance, with the bodies as nodes.
// Note that the subgraph name must have the "cluster" prefix in order to
// have the box drawn.
for (ModelInstanceIndex model_instance_index(0);
model_instance_index < num_model_instances(); ++model_instance_index) {
graphviz += fmt::format("subgraph cluster{} {{\n", model_instance_index);
graphviz += fmt::format(" label=\"{}\";\n",
GetModelInstanceName(model_instance_index));
for (const BodyIndex& body_index : GetBodyIndices(model_instance_index)) {
const Body<T>& body = get_body(body_index);
graphviz +=
fmt::format(" body{} [label=\"{}\"];\n", body.index(), body.name());
}
graphviz += "}\n";
}
// Add the graph edges (via the joints).
for (JointIndex joint_index(0); joint_index < num_joints(); ++joint_index) {
const Joint<T>& joint = get_joint(joint_index);
graphviz += fmt::format(
"body{} -> body{} [label=\"{} [{}]\"];\n", joint.child_body().index(),
joint.parent_body().index(), joint.name(), joint.type_name());
}
// TODO(russt): Consider adding actuators, frames, forces, etc.
graphviz += "}\n";
return graphviz;
}
template <typename T>
void MultibodyPlant<T>::set_contact_model(ContactModel model) {
DRAKE_MBP_THROW_IF_FINALIZED();
contact_model_ = model;
}
template <typename T>
ContactModel MultibodyPlant<T>::get_contact_model() const {
return contact_model_;
}
template <typename T>
void MultibodyPlant<T>::SetFreeBodyRandomRotationDistributionToUniform(
const Body<T>& body) {
RandomGenerator generator;
auto q_FM =
math::UniformlyRandomQuaternion<symbolic::Expression>(&generator);
SetFreeBodyRandomRotationDistribution(body, q_FM);
}
template <typename T>
const WeldJoint<T>& MultibodyPlant<T>::WeldFrames(
const Frame<T>& A, const Frame<T>& B,
const math::RigidTransform<double>& X_AB) {
const std::string joint_name = A.name() + "_welds_to_" + B.name();
return AddJoint(std::make_unique<WeldJoint<T>>(joint_name, A, B, X_AB));
}
template <typename T>
geometry::SourceId MultibodyPlant<T>::RegisterAsSourceForSceneGraph(
SceneGraph<T>* scene_graph) {
DRAKE_THROW_UNLESS(scene_graph != nullptr);
DRAKE_THROW_UNLESS(!geometry_source_is_registered());
// Save the GS pointer so that on later geometry registrations can use this
// instance. This will be nullified at Finalize().
scene_graph_ = scene_graph;
source_id_ = member_scene_graph().RegisterSource(this->get_name());
const geometry::FrameId world_frame_id =
member_scene_graph().world_frame_id();
body_index_to_frame_id_[world_index()] = world_frame_id;
frame_id_to_body_index_[world_frame_id] = world_index();
// In case any bodies were added before registering scene graph, make sure the
// bodies get their corresponding geometry frame ids.
RegisterGeometryFramesForAllBodies();
return source_id_.value();
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterVisualGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape, const std::string& name) {
return RegisterVisualGeometry(
body, X_BG, shape, name, geometry::IllustrationProperties());
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterVisualGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape, const std::string& name,
const Vector4<double>& diffuse_color) {
return RegisterVisualGeometry(
body, X_BG, shape, name,
geometry::MakePhongIllustrationProperties(diffuse_color));
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterVisualGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape, const std::string& name,
const geometry::IllustrationProperties& properties) {
// TODO(SeanCurtis-TRI): Consider simply adding an interface that takes a
// unique pointer to an already instantiated GeometryInstance. This will
// require shuffling around a fair amount of code and should ultimately be
// supplanted by providing a cleaner interface between parsing MBP and SG
// elements.
DRAKE_MBP_THROW_IF_FINALIZED();
DRAKE_THROW_UNLESS(geometry_source_is_registered());
// TODO(amcastro-tri): Consider doing this after finalize so that we can
// register geometry that has a fixed path to world to the world body (i.e.,
// as anchored geometry).
GeometryId id =
RegisterGeometry(body, X_BG, shape,
GetScopedName(*this, body.model_instance(), name));
member_scene_graph().AssignRole(*source_id_, id, properties);
// TODO(SeanCurtis-TRI): Eliminate the automatic assignment of perception
// and illustration in favor of a protocol that allows definition.
geometry::PerceptionProperties perception_props;
perception_props.AddProperty("label", "id", RenderLabel(body.index()));
perception_props.AddProperty(
"phong", "diffuse",
properties.GetPropertyOrDefault(
"phong", "diffuse", Vector4<double>(0.9, 0.9, 0.9, 1.0)));
if (properties.HasProperty("phong", "diffuse_map")) {
perception_props.AddProperty(
"phong", "diffuse_map",
properties.GetProperty<std::string>("phong", "diffuse_map"));
}
if (properties.HasProperty("renderer", "accepting")) {
perception_props.AddProperty(
"renderer", "accepting",
properties.GetProperty<std::set<std::string>>("renderer", "accepting"));
}
member_scene_graph().AssignRole(*source_id_, id, perception_props);
const int visual_index = geometry_id_to_visual_index_.size();
geometry_id_to_visual_index_[id] = visual_index;
DRAKE_ASSERT(num_bodies() == static_cast<int>(visual_geometries_.size()));
visual_geometries_[body.index()].push_back(id);
return id;
}
template <typename T>
const std::vector<geometry::GeometryId>&
MultibodyPlant<T>::GetVisualGeometriesForBody(const Body<T>& body) const {
return visual_geometries_[body.index()];
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterCollisionGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape, const std::string& name,
geometry::ProximityProperties properties) {
DRAKE_MBP_THROW_IF_FINALIZED();
DRAKE_THROW_UNLESS(geometry_source_is_registered());
DRAKE_THROW_UNLESS(properties.HasProperty(geometry::internal::kMaterialGroup,
geometry::internal::kFriction));
const CoulombFriction<double> coulomb_friction =
properties.GetProperty<CoulombFriction<double>>(
geometry::internal::kMaterialGroup, geometry::internal::kFriction);
// TODO(amcastro-tri): Consider doing this after finalize so that we can
// register geometry that has a fixed path to world to the world body (i.e.,
// as anchored geometry).
GeometryId id = RegisterGeometry(
body, X_BG, shape, GetScopedName(*this, body.model_instance(), name));
member_scene_graph().AssignRole(*source_id_, id, std::move(properties));
const int collision_index = geometry_id_to_collision_index_.size();
geometry_id_to_collision_index_[id] = collision_index;
DRAKE_ASSERT(
static_cast<int>(default_coulomb_friction_.size()) == collision_index);
// TODO(SeanCurtis-TRI): Stop storing coulomb friction in MBP and simply
// acquire it from SceneGraph.
default_coulomb_friction_.push_back(coulomb_friction);
DRAKE_ASSERT(num_bodies() == static_cast<int>(collision_geometries_.size()));
collision_geometries_[body.index()].push_back(id);
return id;
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterCollisionGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape, const std::string& name,
const CoulombFriction<double>& coulomb_friction) {
geometry::ProximityProperties props;
props.AddProperty(geometry::internal::kMaterialGroup,
geometry::internal::kFriction, coulomb_friction);
return RegisterCollisionGeometry(body, X_BG, shape, name, std::move(props));
}
template <typename T>
const std::vector<geometry::GeometryId>&
MultibodyPlant<T>::GetCollisionGeometriesForBody(const Body<T>& body) const {
DRAKE_ASSERT(body.index() < num_bodies());
return collision_geometries_[body.index()];
}
template <typename T>
geometry::GeometrySet MultibodyPlant<T>::CollectRegisteredGeometries(
const std::vector<const Body<T>*>& bodies) const {
DRAKE_THROW_UNLESS(geometry_source_is_registered());
geometry::GeometrySet geometry_set;
for (const Body<T>* body : bodies) {
std::optional<FrameId> frame_id = GetBodyFrameIdIfExists(body->index());
if (frame_id) {
geometry_set.Add(frame_id.value());
}
}
return geometry_set;
}
template <typename T>
std::vector<const Body<T>*> MultibodyPlant<T>::GetBodiesWeldedTo(
const Body<T>& body) const {
const std::set<BodyIndex> island =
multibody_graph_.FindBodiesWeldedTo(body.index());
// Map body indices to pointers.
std::vector<const Body<T>*> sub_graph_bodies;
for (BodyIndex body_index : island) {
sub_graph_bodies.push_back(&get_body(body_index));
}
return sub_graph_bodies;
}
template <typename T>
std::unordered_set<BodyIndex> MultibodyPlant<T>::GetFloatingBaseBodies() const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
std::unordered_set<BodyIndex> floating_bodies;
for (BodyIndex body_index(0); body_index < num_bodies(); ++body_index) {
const Body<T>& body = get_body(body_index);
if (body.is_floating()) floating_bodies.insert(body.index());
}
return floating_bodies;
}
template <typename T>
geometry::GeometryId MultibodyPlant<T>::RegisterGeometry(
const Body<T>& body, const math::RigidTransform<double>& X_BG,
const geometry::Shape& shape,
const std::string& name) {
DRAKE_ASSERT(!is_finalized());
DRAKE_ASSERT(geometry_source_is_registered());
DRAKE_ASSERT(body_has_registered_frame(body));
// Register geometry in the body frame.
std::unique_ptr<geometry::GeometryInstance> geometry_instance =
std::make_unique<GeometryInstance>(X_BG, shape.Clone(), name);
GeometryId geometry_id = member_scene_graph().RegisterGeometry(
source_id_.value(), body_index_to_frame_id_[body.index()],
std::move(geometry_instance));
geometry_id_to_body_index_[geometry_id] = body.index();
return geometry_id;
}
template <typename T>
void MultibodyPlant<T>::RegisterGeometryFramesForAllBodies() {
DRAKE_ASSERT(geometry_source_is_registered());
// Loop through the bodies to make sure that all bodies get a geometry frame.
// If not, create and attach one.
for (BodyIndex body_index(0); body_index < num_bodies(); ++body_index) {
const auto& body = get_body(body_index);
RegisterRigidBodyWithSceneGraph(body);
}
}
template <typename T>
void MultibodyPlant<T>::RegisterRigidBodyWithSceneGraph(
const Body<T>& body) {
if (geometry_source_is_registered()) {
// If not already done, register a frame for this body.
if (!body_has_registered_frame(body)) {
FrameId frame_id = member_scene_graph().RegisterFrame(
source_id_.value(),
GeometryFrame(
GetScopedName(*this, body.model_instance(), body.name()),
/* TODO(@SeanCurtis-TRI): Add test coverage for this
* model-instance support as requested in #9390. */
body.model_instance()));
body_index_to_frame_id_[body.index()] = frame_id;
frame_id_to_body_index_[frame_id] = body.index();
}
}
}
template<typename T>
void MultibodyPlant<T>::SetFreeBodyPoseInWorldFrame(
systems::Context<T>* context,
const Body<T>& body, const math::RigidTransform<T>& X_WB) const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
internal_tree().SetFreeBodyPoseOrThrow(body, X_WB, context);
}
template<typename T>
void MultibodyPlant<T>::SetFreeBodyPoseInAnchoredFrame(
systems::Context<T>* context,
const Frame<T>& frame_F, const Body<T>& body,
const math::RigidTransform<T>& X_FB) const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
if (!internal_tree().get_topology().IsBodyAnchored(frame_F.body().index())) {
throw std::logic_error(
"Frame '" + frame_F.name() + "' must be anchored to the world frame.");
}
// Pose of frame F in its parent body frame P.
const RigidTransform<T> X_PF = frame_F.GetFixedPoseInBodyFrame();
// Pose of frame F's parent body P in the world.
const RigidTransform<T>& X_WP = EvalBodyPoseInWorld(*context, frame_F.body());
// Pose of "body" B in the world frame.
const RigidTransform<T> X_WB = X_WP * X_PF * X_FB;
SetFreeBodyPoseInWorldFrame(context, body, X_WB);
}
template<typename T>
void MultibodyPlant<T>::CalcSpatialAccelerationsFromVdot(
const systems::Context<T>& context,
const VectorX<T>& known_vdot,
std::vector<SpatialAcceleration<T>>* A_WB_array) const {
DRAKE_THROW_UNLESS(A_WB_array != nullptr);
DRAKE_THROW_UNLESS(static_cast<int>(A_WB_array->size()) == num_bodies());
internal_tree().CalcSpatialAccelerationsFromVdot(
context, internal_tree().EvalPositionKinematics(context),
internal_tree().EvalVelocityKinematics(context), known_vdot, A_WB_array);
// Permute BodyNodeIndex -> BodyIndex.
// TODO(eric.cousineau): Remove dynamic allocations. Making this in-place
// still required dynamic allocation for recording permutation indices.
// Can change implementation once MultibodyTree becomes fully internal.
std::vector<SpatialAcceleration<T>> A_WB_array_node = *A_WB_array;
const internal::MultibodyTreeTopology& topology =
internal_tree().get_topology();
for (internal::BodyNodeIndex node_index(1);
node_index < topology.get_num_body_nodes(); ++node_index) {
const BodyIndex body_index = topology.get_body_node(node_index).body;
(*A_WB_array)[body_index] = A_WB_array_node[node_index];
}
}
template<typename T>
void MultibodyPlant<T>::CalcForceElementsContribution(
const systems::Context<T>& context,
MultibodyForces<T>* forces) const {
DRAKE_THROW_UNLESS(forces != nullptr);
DRAKE_THROW_UNLESS(forces->CheckHasRightSizeForModel(internal_tree()));
internal_tree().CalcForceElementsContribution(
context, EvalPositionKinematics(context),
EvalVelocityKinematics(context),
forces);
}
template<typename T>
void MultibodyPlant<T>::Finalize() {
// After finalizing the base class, tree is read-only.
internal::MultibodyTreeSystem<T>::Finalize();
if (geometry_source_is_registered()) {
FilterAdjacentBodies();
ExcludeCollisionsWithVisualGeometry();
}
FinalizePlantOnly();
}
template<typename T>
void MultibodyPlant<T>::SetUpJointLimitsParameters() {
for (JointIndex joint_index(0); joint_index < num_joints();
++joint_index) {
// Currently MultibodyPlant applies these "compliant" joint limit forces
// using an explicit Euler strategy. Stability analysis of the explicit
// Euler applied to the harmonic oscillator (the model used for these
// compliant forces) shows the scheme to be stable for kAlpha > 2π. We take
// a significantly larger kAlpha so that we are well within the stability
// region of the scheme.
// TODO(amcastro-tri): Decrease the value of kAlpha to be closer to one when
// the time stepping scheme is updated to be implicit in the joint limits.
const double kAlpha = 20 * M_PI;
const Joint<T>& joint = get_joint(joint_index);
auto revolute_joint = dynamic_cast<const RevoluteJoint<T>*>(&joint);
auto prismatic_joint = dynamic_cast<const PrismaticJoint<T>*>(&joint);
// Currently MBP only supports limits for prismatic and revolute joints.
if (!(revolute_joint || prismatic_joint)) continue;
const double penalty_time_scale = kAlpha * time_step();
if (revolute_joint) {
const double lower_limit = revolute_joint->position_lower_limits()(0);
const double upper_limit = revolute_joint->position_upper_limits()(0);
// We only compute parameters if joints do have upper/lower bounds.
if (!std::isinf(lower_limit) || !std::isinf(upper_limit)) {
joint_limits_parameters_.joints_with_limits.push_back(
revolute_joint->index());
// Store joint limits.
joint_limits_parameters_.lower_limit.push_back(lower_limit);
joint_limits_parameters_.upper_limit.push_back(upper_limit);
// Estimate penalty parameters.
auto penalty_parameters =
internal::JointLimitsPenaltyParametersEstimator<T>::
CalcRevoluteJointPenaltyParameters(
*revolute_joint, penalty_time_scale);
joint_limits_parameters_.stiffness.push_back(penalty_parameters.first);
joint_limits_parameters_.damping.push_back(penalty_parameters.second);
}
}
if (prismatic_joint) {
const double lower_limit = prismatic_joint->position_lower_limits()(0);
const double upper_limit = prismatic_joint->position_upper_limits()(0);
// We only compute parameters if joints do have upper/lower bounds.
if (!std::isinf(lower_limit) || !std::isinf(upper_limit)) {
joint_limits_parameters_.joints_with_limits.push_back(
prismatic_joint->index());
// Store joint limits.
joint_limits_parameters_.lower_limit.push_back(lower_limit);
joint_limits_parameters_.upper_limit.push_back(upper_limit);
// Estimate penalty parameters.
auto penalty_parameters =
internal::JointLimitsPenaltyParametersEstimator<T>::
CalcPrismaticJointPenaltyParameters(
*prismatic_joint, penalty_time_scale);
joint_limits_parameters_.stiffness.push_back(penalty_parameters.first);
joint_limits_parameters_.damping.push_back(penalty_parameters.second);
}
}
}
// Since currently MBP only handles joint limits for discrete models, we
// verify that there are no joint limits when the model is continuous.
// Therefore we print an appropriate warning message when a user
// specifies joint limits for a continuous model.
if (!is_discrete() && !joint_limits_parameters_.joints_with_limits.empty()) {
drake::log()->warn(
"Currently MultibodyPlant does not handle joint limits for "
"continuous models. "
"However some joints do specify limits. "
"Consider setting a non-zero time step in the MultibodyPlant "
"constructor; this will put MultibodyPlant in discrete-time mode, "
"which does support joint limits.");
std::string joints_names;
for (size_t i = 0; i < joint_limits_parameters_.stiffness.size(); ++i) {
const JointIndex index = joint_limits_parameters_.joints_with_limits[i];
if (i > 0) joints_names += ", ";
joints_names += fmt::format("`{}`", get_joint(index).name());
}
drake::log()->warn("Joints that specify limits are: {}.", joints_names);
}
}
template<typename T>
void MultibodyPlant<T>::FinalizePlantOnly() {
DeclareStateCacheAndPorts();
if (num_collision_geometries() > 0 &&
penalty_method_contact_parameters_.time_scale < 0)
EstimatePointContactParameters(penetration_allowance_);
if (num_collision_geometries() > 0 &&
friction_model_.stiction_tolerance() < 0)
set_stiction_tolerance();
// Make a contact solver when the plant is modeled as a discrete system.
if (is_discrete()) {
tamsi_solver_ =
std::make_unique<TamsiSolver<T>>(num_velocities());
// Set the stiction tolerance according to the values set by users with
// set_stiction_tolerance().
TamsiSolverParameters solver_parameters;
solver_parameters.stiction_tolerance =
friction_model_.stiction_tolerance();
tamsi_solver_->set_solver_parameters(solver_parameters);
}
SetUpJointLimitsParameters();
scene_graph_ = nullptr; // must not be used after Finalize().
}
template <typename T>
MatrixX<T> MultibodyPlant<T>::MakeActuationMatrix() const {
MatrixX<T> B = MatrixX<T>::Zero(num_velocities(), num_actuated_dofs());
for (JointActuatorIndex actuator_index(0);
actuator_index < num_actuators(); ++actuator_index) {
const JointActuator<T>& actuator = get_joint_actuator(actuator_index);
// This method assumes actuators on single dof joints. Assert this
// condition.
DRAKE_DEMAND(actuator.joint().num_velocities() == 1);
B(actuator.joint().velocity_start(), actuator.index()) = 1;
}
return B;
}
template <typename T>
struct MultibodyPlant<T>::SceneGraphStub {
struct StubSceneGraphInspector {
const geometry::ProximityProperties* GetProximityProperties(
GeometryId) const {
return nullptr;
}
const geometry::IllustrationProperties* GetIllustrationProperties(
GeometryId) const {
return nullptr;
}
};
static void Throw(const char* operation_name) {
throw std::logic_error(fmt::format(
"Cannot {} on a SceneGraph<symbolic::Expression>", operation_name));
}
static FrameId world_frame_id() {
return SceneGraph<double>::world_frame_id();
}
#define DRAKE_STUB(Ret, Name) \
template <typename... Args> \
Ret Name(Args...) const { Throw(#Name); return Ret(); }
DRAKE_STUB(void, AssignRole)
DRAKE_STUB(void, ExcludeCollisionsBetween)
DRAKE_STUB(void, ExcludeCollisionsWithin)
DRAKE_STUB(FrameId, RegisterFrame)
DRAKE_STUB(GeometryId, RegisterGeometry)
DRAKE_STUB(SourceId, RegisterSource)
const StubSceneGraphInspector model_inspector() const {
Throw("model_inspector");
return StubSceneGraphInspector();
}
#undef DRAKE_STUB
};
template <typename T>
typename MultibodyPlant<T>::MemberSceneGraph&
MultibodyPlant<T>::member_scene_graph() {
DRAKE_THROW_UNLESS(scene_graph_ != nullptr);
return *scene_graph_;
}
// Specialize this function so that we can use our Stub class; we cannot call
// methods on SceneGraph<Expression> because they do not exist.
template <>
typename MultibodyPlant<symbolic::Expression>::MemberSceneGraph&
MultibodyPlant<symbolic::Expression>::member_scene_graph() {
static never_destroyed<SceneGraphStub> stub_;
return stub_.access();
}
template <typename T>
void MultibodyPlant<T>::CheckValidState(const systems::State<T>* state) const {
DRAKE_THROW_UNLESS(state != nullptr);
DRAKE_THROW_UNLESS(
is_discrete() == (state->get_discrete_state().num_groups() > 0));
}
template <typename T>
void MultibodyPlant<T>::FilterAdjacentBodies() {
DRAKE_DEMAND(geometry_source_is_registered());
// Disallow collisions between adjacent bodies. Adjacency is implied by the
// existence of a joint between bodies.
for (JointIndex j{0}; j < num_joints(); ++j) {
const Joint<T>& joint = get_joint(j);
const Body<T>& child = joint.child_body();
const Body<T>& parent = joint.parent_body();
if (parent.index() == world_index()) continue;
std::optional<FrameId> child_id = GetBodyFrameIdIfExists(child.index());
std::optional<FrameId> parent_id = GetBodyFrameIdIfExists(parent.index());
if (child_id && parent_id) {
member_scene_graph().ExcludeCollisionsBetween(
geometry::GeometrySet(*child_id),
geometry::GeometrySet(*parent_id));
}
}
// We must explicitly exclude collisions between all geometries registered
// against the world.
// TODO(eric.cousineau): Do this in a better fashion (#11117).
auto g_world = CollectRegisteredGeometries(GetBodiesWeldedTo(world_body()));
member_scene_graph().ExcludeCollisionsWithin(g_world);
}
template <typename T>
void MultibodyPlant<T>::ExcludeCollisionsWithVisualGeometry() {
DRAKE_DEMAND(geometry_source_is_registered());
geometry::GeometrySet visual;
for (const auto& body_geometries : visual_geometries_) {
visual.Add(body_geometries);
}
geometry::GeometrySet collision;
for (const auto& body_geometries : collision_geometries_) {
collision.Add(body_geometries);
}
member_scene_graph().ExcludeCollisionsWithin(visual);
member_scene_graph().ExcludeCollisionsBetween(visual, collision);
}
template <typename T>
void MultibodyPlant<T>::ExcludeCollisionGeometriesWithCollisionFilterGroupPair(
const std::pair<std::string, geometry::GeometrySet>&
collision_filter_group_a,
const std::pair<std::string, geometry::GeometrySet>&
collision_filter_group_b) {
DRAKE_DEMAND(!is_finalized());
DRAKE_DEMAND(geometry_source_is_registered());
if (collision_filter_group_a.first == collision_filter_group_b.first) {
member_scene_graph().ExcludeCollisionsWithin(
collision_filter_group_a.second);
} else {
member_scene_graph().ExcludeCollisionsBetween(
collision_filter_group_a.second, collision_filter_group_b.second);
}
}
template<typename T>
void MultibodyPlant<T>::CalcNormalAndTangentContactJacobians(
const systems::Context<T>& context,
const std::vector<internal::DiscreteContactPair<T>>& contact_pairs,
MatrixX<T>* Jn_ptr, MatrixX<T>* Jt_ptr,
std::vector<RotationMatrix<T>>* R_WC_set) const {
DRAKE_DEMAND(Jn_ptr != nullptr);
DRAKE_DEMAND(Jt_ptr != nullptr);
const int num_contacts = contact_pairs.size();
// Jn is defined such that vn = Jn * v, with vn of size nc.
auto& Jn = *Jn_ptr;
Jn.resize(num_contacts, num_velocities());
// Jt is defined such that vt = Jt * v, with vt of size 2nc.
auto& Jt = *Jt_ptr;
Jt.resize(2 * num_contacts, num_velocities());
if (R_WC_set != nullptr) R_WC_set->clear();
// Quick no-op exit. Notice we did resize Jn, Jt and R_WC_set to be zero
// sized.
if (num_contacts == 0) return;
const Frame<T>& frame_W = world_frame();
for (int icontact = 0; icontact < num_contacts; ++icontact) {
const auto& point_pair = contact_pairs[icontact];
const GeometryId geometryA_id = point_pair.id_A;
const GeometryId geometryB_id = point_pair.id_B;
BodyIndex bodyA_index = geometry_id_to_body_index_.at(geometryA_id);
const Body<T>& bodyA = get_body(bodyA_index);
BodyIndex bodyB_index = geometry_id_to_body_index_.at(geometryB_id);
const Body<T>& bodyB = get_body(bodyB_index);
// Penetration depth > 0 if bodies interpenetrate.
const Vector3<T>& nhat_BA_W = point_pair.nhat_BA_W;
const Vector3<T>& p_WC = point_pair.p_WC;
// For point Ac (origin of frame A shifted to C), calculate Jv_v_WAc (Ac's
// translational velocity Jacobian in the world frame W with respect to
// generalized velocities v). Note: Ac's translational velocity in W can
// be written in terms of this Jacobian as v_WAc = Jv_v_WAc * v.
Matrix3X<T> Jv_WAc(3, this->num_velocities());
internal_tree().CalcJacobianTranslationalVelocity(context,
JacobianWrtVariable::kV,
bodyA.body_frame(),
frame_W,
p_WC,
frame_W,
frame_W,
&Jv_WAc);
// Similarly, for point Bc (origin of frame B shifted to C), calculate
// Jv_v_WBc (Bc's translational velocity Jacobian in W with respect to v).
Matrix3X<T> Jv_WBc(3, this->num_velocities());
internal_tree().CalcJacobianTranslationalVelocity(context,
JacobianWrtVariable::kV,
bodyB.body_frame(),
frame_W,
p_WC,
frame_W,
frame_W,
&Jv_WBc);
// Computation of the normal separation velocities Jacobian Jn:
//
// The velocity of Bc relative to Ac is
// v_AcBc_W = v_WBc - v_WAc.
// From where the separation velocity is computed as
// vn = -v_AcBc_W.dot(nhat_BA_W) = -nhat_BA_Wᵀ⋅v_AcBc_W
// where the negative sign stems from the sign convention for vn and xdot.
// This can be written in terms of the Jacobians as
// vn = -nhat_BA_Wᵀ⋅(Jv_WBc - Jv_WAc)⋅v
Jn.row(icontact) = nhat_BA_W.transpose() * (Jv_WAc - Jv_WBc);
// Computation of the tangential velocities Jacobian Jt:
//
// Compute the orientation of a contact frame C at the contact point such
// that the z-axis Cz equals to nhat_BA_W. The tangent vectors are
// arbitrary, with the only requirement being that they form a valid right
// handed basis with nhat_BA.
const RotationMatrix<T> R_WC(math::ComputeBasisFromAxis(2, nhat_BA_W));
if (R_WC_set != nullptr) {
R_WC_set->push_back(R_WC);
}
const Vector3<T> that1_W = R_WC.matrix().col(0); // that1 = Cx.
const Vector3<T> that2_W = R_WC.matrix().col(1); // that2 = Cy.
// The velocity of Bc relative to Ac is
// v_AcBc_W = v_WBc - v_WAc.
// The first two components of this velocity in C corresponds to the
// tangential velocities in a plane normal to nhat_BA.
// vx_AcBc_C = that1⋅v_AcBc = that1ᵀ⋅(Jv_WBc - Jv_WAc)⋅v
// vy_AcBc_C = that2⋅v_AcBc = that2ᵀ⋅(Jv_WBc - Jv_WAc)⋅v
Jt.row(2 * icontact) = that1_W.transpose() * (Jv_WBc - Jv_WAc);
Jt.row(2 * icontact + 1) = that2_W.transpose() * (Jv_WBc - Jv_WAc);
}
}
template <typename T>