From f0bc366ce58514fe6d96f7dd5523b83be9a738df Mon Sep 17 00:00:00 2001
From: Rainer Kartmann <rainer.kartmann@student.kit.edu>
Date: Tue, 13 Nov 2018 09:37:38 +0100
Subject: [PATCH] Outfactored massless body sanitizing

---
 .../examples/MjcfConverter/CMakeLists.txt     |   2 +
 .../examples/MjcfConverter/MjcfConverter.cpp  | 232 ++++--------------
 .../examples/MjcfConverter/MjcfConverter.h    |  13 +-
 .../MjcfMasslessBodySanitizer.cpp             | 203 +++++++++++++++
 .../MjcfConverter/MjcfMasslessBodySanitizer.h |  58 +++++
 5 files changed, 314 insertions(+), 194 deletions(-)
 create mode 100644 VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.cpp
 create mode 100644 VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.h

diff --git a/VirtualRobot/examples/MjcfConverter/CMakeLists.txt b/VirtualRobot/examples/MjcfConverter/CMakeLists.txt
index 95eab5a63..45f303f21 100644
--- a/VirtualRobot/examples/MjcfConverter/CMakeLists.txt
+++ b/VirtualRobot/examples/MjcfConverter/CMakeLists.txt
@@ -16,6 +16,7 @@ if (Boost_FOUND)
         
         MjcfConverter.cpp
         MjcfDocument.cpp
+        MjcfMasslessBodySanitizer.cpp
         SimoxXMLDocument.cpp
     )
     
@@ -26,6 +27,7 @@ if (Boost_FOUND)
         
         MjcfConverter.h
         MjcfDocument.h
+        MjcfMasslessBodySanitizer.h
         SimoxXMLDocument.h
     )
 
diff --git a/VirtualRobot/examples/MjcfConverter/MjcfConverter.cpp b/VirtualRobot/examples/MjcfConverter/MjcfConverter.cpp
index 3b8ec2748..0dec1f9d4 100644
--- a/VirtualRobot/examples/MjcfConverter/MjcfConverter.cpp
+++ b/VirtualRobot/examples/MjcfConverter/MjcfConverter.cpp
@@ -7,13 +7,15 @@
 #include "xml_visitors.h"
 
 
+
 using namespace VirtualRobot;
 namespace tx = tinyxml2; 
 namespace fs = boost::filesystem;
 using Element = mjcf::Element;
 
 
-MjcfConverter::MjcfConverter()
+MjcfConverter::MjcfConverter() :
+    masslessBodySanitizer(document, robot)
 {
 }
 
@@ -23,7 +25,45 @@ void MjcfConverter::convert(const std::string& inputSimoxXmlFile,
     setPaths(inputSimoxXmlFile, outputDirectory);
     
     loadInputFile();
-    convertToMjcf();
+    
+    document.reset(new mjcf::Document());
+    
+    document->setModelName(robot->getName());
+    document->compiler()->SetAttribute("angle", "radian");
+    document->compiler()->SetAttribute("balanceinertia", "true");
+    
+    makeEnvironment();
+    
+    std::cout << "Creating bodies structure ..." << std::endl;
+    addNodeBodies();
+    
+    std::cout << "Adding meshes and geoms ..." << std::endl;
+    addNodeBodyMeshes();
+    
+    std::cout << "===========================" << std::endl
+              << "Current model: "             << std::endl
+              << "--------------"              << std::endl;
+    document->Print();
+    std::cout << "===========================" << std::endl;
+    
+    std::cout << "Merging massless bodies ..." << std::endl;
+    masslessBodySanitizer.sanitize();
+    
+    std::cout << "Adding contact excludes ..." << std::endl;
+    addContactExcludes();
+
+    std::cout << "Adding actuators ..." << std::endl;
+    addActuators();
+    
+    std::cout << "Done.";
+    
+    std::cout << std::endl;
+    std::cout << "===========================" << std::endl
+              << "Output file: "             << std::endl
+              << "------------"              << std::endl;
+    document->Print();
+    std::cout << "===========================" << std::endl;
+    
     writeOutputFile();
 }
 
@@ -80,53 +120,11 @@ void MjcfConverter::loadInputFile()
 void MjcfConverter::writeOutputFile()
 {
     assert(!outputFileName.empty());
-    
-    std::cout << std::endl;
-    document->Print();
-    
     std::cout << "Writing to " << outputFileName << std::endl;
     document->SaveFile(outputFileName.c_str());
 }
 
 
-void MjcfConverter::convertToMjcf()
-{
-    document.reset(new mjcf::Document());
-    
-    document->setModelName(robot->getName());
-    document->compiler()->SetAttribute("angle", "radian");
-    document->compiler()->SetAttribute("balanceinertia", "true");
-    
-    makeEnvironment();
-    
-    std::cout << "Creating bodies structure ..." << std::endl;
-    addNodeBodies();
-    
-    std::cout << "Adding meshes and geoms ..." << std::endl;
-    addNodeBodyMeshes();
-    
-    std::cout << "===========================" << std::endl
-              << "Current model: "             << std::endl
-              << "--------------"              << std::endl;
-    document->Print();
-    std::cout << "===========================" << std::endl;
-    
-    std::cout << "Merging empty bodies ..." << std::endl;
-    sanitizeMasslessBodies();
-    
-    std::cout << "Adding contact excludes ..." << std::endl;
-    document->addContactExcludes(nodeBodies[robot->getRootNode()->getName()]);
-    
-    std::cout << "Adding actuators ..." << std::endl;
-    addActuators();
-    
-    std::cout << "Done.";
-    
-    return; 
-}
-
-
-
 void MjcfConverter::addNodeBodies()
 {
     nodeBodies.clear();
@@ -255,155 +253,17 @@ Element* MjcfConverter::addNodeBody(RobotNodePtr node)
     return element;
 }
 
-void MjcfConverter::sanitizeMasslessBodies()
-{
-    // merge body leaf nodes with parent if they do not have a geom
-    
-    // assume we have leaf
-    
-    Element* root = document->worldbody()->FirstChildElement("body");
-    
-    for (Element* body = root->FirstChildElement("body");
-         body;
-         body = body->NextSiblingElement("body"))
-    {
-        sanitizeMasslessBodyRecursion(body);
-    }
-}
-
-void MjcfConverter::sanitizeMasslessBodyRecursion(mjcf::Element* body)
+void MjcfConverter::addContactExcludes()
 {
-    assertElementIsBody(body);
-
-    std::cout << "- Node '" << body->Attribute("name") << "': " << std::endl;
-    const std::string t = "  | ";
-    
-    std::string bodyName = body->Attribute("name");
-    RobotNodePtr bodyNode = robot->getRobotNode(bodyName);
-    Eigen::Matrix4f bodyPose = bodyNode->getTransformationFrom(bodyNode->getParent());
+    RobotNodePtr rootNode = robot->getRootNode();
     
-    while (!hasMass(body))
+    for (RobotNodePtr node : robot->getRobotNodes())
     {
-        std::cout << t << "Massless" << std::endl;
-        
-        if (!hasElementChild(body, "body"))
-        {
-            // leaf => end of recursion
-            std::cout << t << "Leaf" << std::endl;
-            sanitizeMasslessLeafBody(body);
-            return; 
-        }
-        
-        // non-leaf body
-        std::cout << t << "Non-leaf" << std::endl;
-        
-        // check whether there is only one child body
-        Element* childBody = body->FirstChildElement("body");
-        if (!childBody->NextSiblingElement("body"))
+        for (std::string& ignore : node->getPhysics().ignoreCollisions)
         {
-            std::string childBodyName = childBody->Attribute("name");
-            
-            std::cout << t << "Single child body => merging '" << childBodyName
-                      << "' into '" << bodyName << "'" << std::endl;
-
-            // check child for pose attributes
-            if (childBody->Attribute("pos") || childBody->Attribute("quat"))
-            {
-                // update body's transform and joint axes
-                
-                RobotNodePtr childNode = robot->getRobotNode(childBodyName);
-                Eigen::Matrix4f childPose = childNode->getTransformationFrom(childNode->getParent());
-                
-                // body's pose
-                bodyPose = bodyPose * childPose;
-                document->setBodyPose(body, bodyPose);
-                
-                /* Adapt axes of joints in body:
-                 * The axes will be relative to the new pose. Therefore, the additional rotation
-                 * of the child must be subtracted from the joint axis.
-                 */
-                
-                Eigen::Matrix3f revChildOri = childPose.block<3,3>(0, 0).transpose();
-                
-                for (Element* joint = body->FirstChildElement("joint"); joint;
-                     joint = joint->NextSiblingElement("joint"))
-                {
-                    Eigen::Vector3f axis = strToVec(joint->Attribute("axis"));
-                    // apply child orientation
-                    axis = revChildOri * axis;
-                    document->setJointAxis(joint, axis);
-                }
-            }
-            
-            
-            // merge childBody into body => move all its elements here
-            for (tx::XMLNode* grandChild = childBody->FirstChild(); grandChild;
-                 grandChild = grandChild->NextSibling())
-            {
-                std::cout << t << " | Moving '" << grandChild->Value() << "'" << std::endl;
-                
-                // clone grandchild
-                tx::XMLNode* grandChildClone = grandChild->DeepClone(nullptr);
-                
-                // insert to body
-                body->InsertEndChild(grandChildClone);
-            }
-            
-            // update body name
-            
-            std::stringstream newName;
-            /*
-            std::size_t prefixSize = commonPrefixLength(bodyName, childBodyName);
-            newName << bodyName.substr(0, prefixSize)
-                    << bodyName.substr(prefixSize) << "~" << childBodyName.substr(prefixSize);
-            */
-            newName << bodyName << "~" << childBodyName;
-            body->SetAttribute("name", newName.str().c_str());
             
-            
-            // delete child
-            body->DeleteChild(childBody);
-            
-            std::cout << t << "=> New body name: " << newName.str() << std::endl;
-        }
-        else
-        {
-            VR_WARNING << t << "Massless body with >1 child body: " 
-                       << body->Attribute("name") << std::endl;
-            break;
         }
     }
-    
-    
-    // recursive descend
-    
-    for (Element* child = body->FirstChildElement("body");
-         child;
-         child = child->NextSiblingElement("body"))
-    {
-        sanitizeMasslessBodyRecursion(child);
-    }
-    
-}
-
-void MjcfConverter::sanitizeMasslessLeafBody(mjcf::Element* body)
-{
-    
-    assert(!hasElementChild(body, "body"));
-    assert(!hasMass(body));
-    
-    if (body->NoChildren()) // is completely empty?
-    {
-        // leaf without geom: make it a site
-        std::cout << "  | Empty => Changing body '" << body->Attribute("name") << "' to site." << std::endl;
-        body->SetValue("site");
-    }
-    else
-    {
-        // add a small mass
-        std::cout << "  | Not empty => Adding dummy inertial." << std::endl;
-        document->addDummyInertial(body);
-    }
 }
 
 void MjcfConverter::addActuators()
diff --git a/VirtualRobot/examples/MjcfConverter/MjcfConverter.h b/VirtualRobot/examples/MjcfConverter/MjcfConverter.h
index 3e87fd5d6..0bd82a8ed 100644
--- a/VirtualRobot/examples/MjcfConverter/MjcfConverter.h
+++ b/VirtualRobot/examples/MjcfConverter/MjcfConverter.h
@@ -8,6 +8,7 @@
 #include "exceptions.h"
 #include "SimoxXMLDocument.h"
 #include "MjcfDocument.h"
+#include "MjcfMasslessBodySanitizer.h"
 
 
 namespace VirtualRobot
@@ -30,8 +31,6 @@ namespace VirtualRobot
         void loadInputFile();
         void writeOutputFile();
         
-        void convertToMjcf();
-        
         void setPaths(const std::string& inputFilename, 
                       const std::string& outputDirectory);
         
@@ -43,11 +42,6 @@ namespace VirtualRobot
         void addNodeBodyMeshes();
 
 
-        void sanitizeMasslessBodies();
-        void sanitizeMasslessBodyRecursion(mjcf::Element* body);
-        
-        void sanitizeMasslessLeafBody(mjcf::Element* body);
-        
         void addContactExcludes();
         
         void addActuators();
@@ -81,9 +75,12 @@ namespace VirtualRobot
         
         // Processing
         
+        mjcf::MjcfMasslessBodySanitizer masslessBodySanitizer;
+        
         std::map<std::string, mjcf::Element*> nodeBodies;
-
         
+        std::map<std::string, std::string> mergedBodyNames;
+
     };
 
 }
diff --git a/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.cpp b/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.cpp
new file mode 100644
index 000000000..61e019727
--- /dev/null
+++ b/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.cpp
@@ -0,0 +1,203 @@
+#include "MjcfMasslessBodySanitizer.h"
+
+#include <boost/algorithm/string/join.hpp>
+
+
+#include "utils.h"
+
+
+using namespace VirtualRobot;
+using namespace mjcf;
+namespace tx = tinyxml2;
+
+
+MjcfMasslessBodySanitizer::MjcfMasslessBodySanitizer(DocumentPtr& document, RobotPtr& robot) :
+    document(document), robot(robot)
+{
+    
+}
+
+void MjcfMasslessBodySanitizer::sanitize()
+{
+    // merge body leaf nodes with parent if they do not have a mass (inertial or geom)
+    
+    Element* root = document->worldbody()->FirstChildElement("body");
+    
+    for (Element* body = root->FirstChildElement("body");
+         body;
+         body = body->NextSiblingElement("body"))
+    {
+        sanitizeRecursion(body);
+    }
+}
+
+void MjcfMasslessBodySanitizer::sanitizeRecursion(Element* body)
+{
+    assertElementIsBody(body);
+
+    std::cout << "- Node '" << body->Attribute("name") << "': " << std::endl;
+    const std::string t = "  | ";
+    
+    std::string bodyName = body->Attribute("name");
+    RobotNodePtr bodyNode = robot->getRobotNode(bodyName);
+    Eigen::Matrix4f bodyPose = bodyNode->getTransformationFrom(bodyNode->getParent());
+    
+    while (!hasMass(body))
+    {
+        std::cout << t << "Massless" << std::endl;
+        
+        if (!hasElementChild(body, "body"))
+        {
+            // leaf => end of recursion
+            std::cout << t << "Leaf" << std::endl;
+            sanitizeLeafBody(body);
+            return; 
+        }
+        
+        // non-leaf body
+        std::cout << t << "Non-leaf" << std::endl;
+        
+        // check whether there is only one child body
+        Element* childBody = body->FirstChildElement("body");
+        if (!childBody->NextSiblingElement("body"))
+        {
+            std::string childBodyName = childBody->Attribute("name");
+            
+            std::cout << t << "Single child body => merging '" << childBodyName
+                      << "' into '" << bodyName << "'" << std::endl;
+
+            // check child for pose attributes
+            if (childBody->Attribute("pos") || childBody->Attribute("quat"))
+            {
+                // update body's transform and joint axes
+                
+                RobotNodePtr childNode = robot->getRobotNode(childBodyName);
+                Eigen::Matrix4f childPose = childNode->getTransformationFrom(childNode->getParent());
+                
+                // body's pose
+                bodyPose = bodyPose * childPose;
+                document->setBodyPose(body, bodyPose);
+                
+                /* Adapt axes of joints in body:
+                 * The axes will be relative to the new pose. Therefore, the additional rotation
+                 * of the child must be subtracted from the joint axis.
+                 */
+                
+                Eigen::Matrix3f revChildOri = childPose.block<3,3>(0, 0).transpose();
+                
+                for (Element* joint = body->FirstChildElement("joint"); joint;
+                     joint = joint->NextSiblingElement("joint"))
+                {
+                    Eigen::Vector3f axis = strToVec(joint->Attribute("axis"));
+                    // apply child orientation
+                    axis = revChildOri * axis;
+                    document->setJointAxis(joint, axis);
+                }
+            }
+            
+            
+            // merge childBody into body => move all its elements here
+            for (tx::XMLNode* grandChild = childBody->FirstChild(); grandChild;
+                 grandChild = grandChild->NextSibling())
+            {
+                std::cout << t << " | Moving '" << grandChild->Value() << "'" << std::endl;
+                
+                // clone grandchild
+                tx::XMLNode* grandChildClone = grandChild->DeepClone(nullptr);
+                
+                // insert to body
+                body->InsertEndChild(grandChildClone);
+            }
+            
+            // update body name
+            MergedBodySet bodySet = getMergedBodySetWith(bodyName);
+            bodySet.addBody(childBodyName);
+            body->SetAttribute("name", bodySet.getMergedBodyName().c_str());
+            
+            // delete child
+            body->DeleteChild(childBody);
+            
+            std::cout << t << "=> New body name: " << bodySet.getMergedBodyName() << std::endl;
+        }
+        else
+        {
+            VR_WARNING << t << "Massless body with >1 child body: " 
+                       << body->Attribute("name") << std::endl;
+            break;
+        }
+    }
+    
+    
+    // recursive descend
+    
+    for (Element* child = body->FirstChildElement("body");
+         child;
+         child = child->NextSiblingElement("body"))
+    {
+        sanitizeRecursion(child);
+    }
+    
+}
+
+void MjcfMasslessBodySanitizer::sanitizeLeafBody(Element* body)
+{
+    assert(!hasElementChild(body, "body"));
+    assert(!hasMass(body));
+    
+    if (body->NoChildren()) // is completely empty?
+    {
+        // leaf without geom: make it a site
+        std::cout << "  | Empty => Changing body '" << body->Attribute("name") << "' to site." << std::endl;
+        body->SetValue("site");
+    }
+    else
+    {
+        // add a small mass
+        std::cout << "  | Not empty => Adding dummy inertial." << std::endl;
+        document->addDummyInertial(body);
+    }
+}
+
+MergedBodySet& MjcfMasslessBodySanitizer::getMergedBodySetWith(const std::string& bodyName)
+{
+    for (auto& set : mergedBodySets)
+    {
+        if (set.containsBody(bodyName))
+        {
+           return set;
+        }
+    }
+    
+    // not found => add
+    MergedBodySet newSet;
+    newSet.addBody(bodyName);
+    mergedBodySets.push_back(newSet);
+    
+    return mergedBodySets.back();
+}
+
+const std::vector<MergedBodySet>& MjcfMasslessBodySanitizer::getMergedBodySets() const
+{
+    return mergedBodySets;
+}
+
+void MergedBodySet::addBody(const std::string& bodyName)
+{
+    originalBodyNames.insert(bodyName);
+    updateMergedBodyName();
+}
+
+bool MergedBodySet::containsBody(const std::string& bodyName) const
+{
+    return originalBodyNames.find(bodyName) != originalBodyNames.end();
+}
+
+const std::string& MergedBodySet::getMergedBodyName() const
+{
+    return mergedBodyName;
+}
+
+void MergedBodySet::updateMergedBodyName()
+{
+    this->mergedBodyName = boost::algorithm::join(originalBodyNames, "~");
+}
diff --git a/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.h b/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.h
new file mode 100644
index 000000000..14a2087a7
--- /dev/null
+++ b/VirtualRobot/examples/MjcfConverter/MjcfMasslessBodySanitizer.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "MjcfDocument.h"
+
+
+namespace VirtualRobot
+{
+namespace mjcf 
+{
+
+    class MergedBodySet
+    {
+    public:
+        
+        void addBody(const std::string& bodyName);
+        bool containsBody(const std::string& bodyName) const;
+        
+        const std::string& getMergedBodyName() const;
+        
+        
+    private:
+        
+        void updateMergedBodyName();
+        
+        std::string mergedBodyName;
+        std::set<std::string> originalBodyNames;
+        
+    };
+
+
+    class MjcfMasslessBodySanitizer
+    {
+    public:
+        
+        MjcfMasslessBodySanitizer(DocumentPtr& document, RobotPtr& robot);
+        
+        void sanitize();
+        
+        const std::vector<MergedBodySet>& getMergedBodySets() const;
+
+        
+    private:
+        
+        void sanitizeRecursion(mjcf::Element* body);
+        void sanitizeLeafBody(mjcf::Element* body);
+        
+        MergedBodySet& getMergedBodySetWith(const std::string& bodyName);
+        
+        
+        DocumentPtr& document;
+        RobotPtr& robot;
+        
+        std::vector<MergedBodySet> mergedBodySets;
+        
+    };
+    
+} 
+}
-- 
GitLab