@@ -704,6 +704,169 @@ bool LinkJointGraph::link_is_static(const Link& link) const {
704
704
ForestBuildingOptions::kStatic );
705
705
}
706
706
707
+ /* Runs through the Mobods in the model but records the (active) Link
708
+ indexes rather than the Mobod indexes. */
709
+ std::vector<LinkIndex> LinkJointGraph::FindPathFromWorld (
710
+ LinkIndex link_index) const {
711
+ ThrowIfForestNotBuiltYet (__func__);
712
+ const SpanningForest::Mobod* mobod =
713
+ &forest ().mobods ()[link_to_mobod (link_index)];
714
+ std::vector<LinkIndex> path (mobod->level () + 1 );
715
+ while (mobod->inboard ().is_valid ()) {
716
+ const Link& link = links (mobod->link_ordinal ());
717
+ path[mobod->level ()] = link .index (); // Active Link if composite.
718
+ mobod = &forest ().mobods (mobod->inboard ());
719
+ }
720
+ DRAKE_DEMAND (mobod->is_world ());
721
+ path[0 ] = LinkIndex (0 );
722
+ return path;
723
+ }
724
+
725
+ LinkIndex LinkJointGraph::FindFirstCommonAncestor (LinkIndex link1_index,
726
+ LinkIndex link2_index) const {
727
+ ThrowIfForestNotBuiltYet (__func__);
728
+ const MobodIndex mobod_ancestor = forest ().FindFirstCommonAncestor (
729
+ link_to_mobod (link1_index), link_to_mobod (link2_index));
730
+ const Link& ancestor_link =
731
+ links (forest ().mobod_to_link_ordinal (mobod_ancestor));
732
+ return ancestor_link.index ();
733
+ }
734
+
735
+ std::vector<LinkIndex> LinkJointGraph::FindSubtreeLinks (
736
+ LinkIndex link_index) const {
737
+ ThrowIfForestNotBuiltYet (__func__);
738
+ const MobodIndex root_mobod_index = link_to_mobod (link_index);
739
+ return forest ().FindSubtreeLinks (root_mobod_index);
740
+ }
741
+
742
+ // Our link_composites collection doesn't include lone Links that aren't welded
743
+ // to anything. The return from this function must include every Link, with
744
+ // the World link in the first set (even if nothing is welded to it).
745
+ std::vector<std::set<LinkIndex>> LinkJointGraph::GetSubgraphsOfWeldedLinks ()
746
+ const {
747
+ ThrowIfForestNotBuiltYet (__func__);
748
+
749
+ std::vector<std::set<LinkIndex>> subgraphs;
750
+
751
+ // First, collect all the precomputed Link Composites. World is always
752
+ // the first one, even if nothing is welded to it.
753
+ for (const LinkComposite& composite : link_composites ()) {
754
+ subgraphs.emplace_back (
755
+ std::set<LinkIndex>(composite.links .cbegin (), composite.links .cend ()));
756
+ }
757
+
758
+ // Finally, make one-Link subgraphs for Links that aren't in any composite.
759
+ for (const Link& link : links ()) {
760
+ if (link .composite ().has_value ()) continue ;
761
+ subgraphs.emplace_back (std::set<LinkIndex>{link .index ()});
762
+ }
763
+
764
+ return subgraphs;
765
+ }
766
+
767
+ // Strategy here is to make repeated use of CalcLinksWeldedTo(), separating
768
+ // the singleton sets from the actually-welded sets, and then move the
769
+ // singletons to the end to match what GetSubgraphsOfWeldedLinks() does.
770
+ std::vector<std::set<LinkIndex>> LinkJointGraph::CalcSubgraphsOfWeldedLinks ()
771
+ const {
772
+ // Work with ordinals rather than indexes.
773
+ std::vector<bool > visited (num_user_links (), false );
774
+
775
+ // World always comes first, even if it is alone.
776
+ std::vector<std::set<LinkIndex>> subgraphs{CalcLinksWeldedTo (LinkIndex (0 ))};
777
+ for (LinkIndex index : subgraphs[0 ]) visited[index_to_ordinal (index )] = true ;
778
+
779
+ std::vector<std::set<LinkIndex>> singletons;
780
+ // If a Forest was already built, there may be shadow links added to
781
+ // the graph -- don't process those here.
782
+ for (LinkOrdinal link_ordinal (1 ); link_ordinal < num_user_links ();
783
+ ++link_ordinal) {
784
+ const Link& link = links (link_ordinal);
785
+ if (link .is_shadow () || visited[link_ordinal]) continue ;
786
+ std::set<LinkIndex> welded_links = CalcLinksWeldedTo (link .index ());
787
+ for (LinkIndex index : welded_links)
788
+ visited[index_to_ordinal (index )] = true ;
789
+ if (ssize (welded_links) == 1 ) {
790
+ singletons.emplace_back (std::move (welded_links));
791
+ } else {
792
+ subgraphs.emplace_back (std::move (welded_links));
793
+ }
794
+ }
795
+
796
+ // Now move all the singletons onto the end of the subgraphs list.
797
+ for (auto & singleton : singletons)
798
+ subgraphs.emplace_back (std::move (singleton));
799
+
800
+ return subgraphs;
801
+ }
802
+
803
+ // If the Link isn't part of a LinkComposite just return the Link. Otherwise,
804
+ // return all the Links in its LinkComposite.
805
+ std::set<LinkIndex> LinkJointGraph::GetLinksWeldedTo (
806
+ LinkIndex link_index) const {
807
+ ThrowIfForestNotBuiltYet (__func__);
808
+ DRAKE_DEMAND (link_index.is_valid ());
809
+ DRAKE_THROW_UNLESS (has_link (link_index));
810
+ const Link& link = link_by_index (link_index);
811
+ const std::optional<LinkCompositeIndex> composite_index = link .composite ();
812
+ if (!composite_index.has_value ()) return std::set<LinkIndex>{link_index};
813
+ const std::vector<LinkIndex>& welded_links =
814
+ link_composites (*composite_index).links ;
815
+ return std::set<LinkIndex>(welded_links.cbegin (), welded_links.cend ());
816
+ }
817
+
818
+ // Without a Forest we don't have LinkComposites available so recursively
819
+ // chase Weld joints instead.
820
+ std::set<LinkIndex> LinkJointGraph::CalcLinksWeldedTo (
821
+ LinkIndex link_index) const {
822
+ std::set<LinkIndex> result;
823
+ AppendLinksWeldedTo (link_index, &result);
824
+ return result;
825
+ }
826
+
827
+ void LinkJointGraph::AppendLinksWeldedTo (LinkIndex link_index,
828
+ std::set<LinkIndex>* result) const {
829
+ DRAKE_DEMAND (result != nullptr );
830
+ DRAKE_DEMAND (link_index.is_valid ());
831
+ DRAKE_THROW_UNLESS (has_link (link_index));
832
+ DRAKE_DEMAND (!result->contains (link_index));
833
+
834
+ const Link& link = link_by_index (link_index);
835
+
836
+ // A Link is always considered welded to itself.
837
+ result->insert (link_index);
838
+
839
+ // For World we have to look for static links and pretend they are welded to
840
+ // World. (Links might have been explicitly flagged as static or part of a
841
+ // static model instance.)
842
+ if (link .is_world ()) {
843
+ for (const Link& maybe_static : links ()) {
844
+ if (result->contains (maybe_static.index ())) continue ;
845
+ if (link_is_static (maybe_static))
846
+ AppendLinksWeldedTo (maybe_static.index (), &*result);
847
+ }
848
+ }
849
+
850
+ // Now run through all the actual joints, looking for welds.
851
+ for (auto joint_index : link .joints ()) {
852
+ const Joint& joint = joint_by_index (joint_index);
853
+ if (joint.traits_index () != weld_joint_traits_index ()) continue ;
854
+ const LinkIndex welded_link_index = joint.other_link_index (link_index);
855
+ // Don't reprocess if we already did the other end.
856
+ if (!result->contains (welded_link_index))
857
+ AppendLinksWeldedTo (welded_link_index, &*result);
858
+ }
859
+ }
860
+
861
+ void LinkJointGraph::ThrowIfForestNotBuiltYet (const char * func) const {
862
+ if (!forest_is_valid ()) {
863
+ throw std::logic_error (
864
+ fmt::format (" {}(): no SpanningForest available. Call BuildForest() "
865
+ " before calling this function." ,
866
+ func));
867
+ }
868
+ }
869
+
707
870
void LinkJointGraph::ThrowLinkWasRemoved (const char * func,
708
871
LinkIndex link_index) const {
709
872
throw std::logic_error (fmt::format (
0 commit comments