From 310fabcd7e00e19655e4f9435d285478fe8e059d Mon Sep 17 00:00:00 2001 From: Erwin Coumans Date: Sun, 15 Jan 2017 11:22:24 -0800 Subject: [PATCH] Further improvements in Bullet MuJoCo MJCF import: [MJCF import] improved humanoid.xml, inverted_pendulum.xml import (automatically add dummy-links to connect multiple joints between bodies) [MJCF import] fix bug in quaternion conversion (w,x,y,z) -> (x,y,z,w) --- data/mjcf/inverted_double_pendulum.xml | 47 +++ data/mjcf/inverted_pendulum.xml | 27 ++ .../ImportMJCFDemo/BulletMJCFImporter.cpp | 397 +++++++++++++++--- .../ImportMJCFDemo/BulletMJCFImporter.h | 3 + .../ImportMJCFDemo/ImportMJCFSetup.cpp | 7 +- .../Importers/ImportURDFDemo/URDF2Bullet.cpp | 25 +- .../ImportURDFDemo/URDFImporterInterface.h | 1 + .../Importers/ImportURDFDemo/URDFJointTypes.h | 6 + .../Importers/ImportURDFDemo/UrdfParser.h | 9 +- 9 files changed, 451 insertions(+), 71 deletions(-) create mode 100644 data/mjcf/inverted_double_pendulum.xml create mode 100644 data/mjcf/inverted_pendulum.xml diff --git a/data/mjcf/inverted_double_pendulum.xml b/data/mjcf/inverted_double_pendulum.xml new file mode 100644 index 000000000..a274e8c5d --- /dev/null +++ b/data/mjcf/inverted_double_pendulum.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/data/mjcf/inverted_pendulum.xml b/data/mjcf/inverted_pendulum.xml new file mode 100644 index 000000000..401503893 --- /dev/null +++ b/data/mjcf/inverted_pendulum.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/examples/Importers/ImportMJCFDemo/BulletMJCFImporter.cpp b/examples/Importers/ImportMJCFDemo/BulletMJCFImporter.cpp index 941295c47..103e49945 100644 --- a/examples/Importers/ImportMJCFDemo/BulletMJCFImporter.cpp +++ b/examples/Importers/ImportMJCFDemo/BulletMJCFImporter.cpp @@ -21,10 +21,12 @@ enum ePARENT_LINK_ENUMS { + BASE_LINK_INDEX=-1, + INVALID_LINK_INDEX=-2 }; - +static int gUid=0; static bool parseVector4(btVector4& vec4, const std::string& vector_str) { @@ -120,12 +122,17 @@ struct BulletMJCFImporterInternalData btAlignedObjectArray m_models; int m_activeModel; + //todo: for full MJCF compatibility, we would need a stack of default values + int m_defaultCollisionGroup; + int m_defaultCollisionMask; //those collision shapes are deleted by caller (todo: make sure this happens!) btAlignedObjectArray m_allocatedCollisionShapes; BulletMJCFImporterInternalData() - :m_activeModel(-1) + :m_activeModel(-1), + m_defaultCollisionGroup(1), + m_defaultCollisionMask(1) { m_pathPrefix[0] = 0; } @@ -144,18 +151,49 @@ struct BulletMJCFImporterInternalData return 0; } - bool parseRootLevel(TiXmlElement* root_xml,MJCFErrorLogger* logger) + bool parseDefaults(TiXmlElement* root_xml, MJCFErrorLogger* logger) { - for (TiXmlElement* xml = root_xml->FirstChildElement() ; xml ; xml = xml->NextSiblingElement()) + bool handled= false; + //rudimentary 'default' support, would need more work for better feature coverage + for (TiXmlElement* child_xml = root_xml->FirstChildElement() ; child_xml ; child_xml = child_xml->NextSiblingElement()) { bool handled = false; - std::string n = xml->Value(); + std::string n = child_xml->Value(); + if (n=="inertial") + { + } + if (n=="geom") + { + //contype, conaffinity + const char* conTypeStr = child_xml->Attribute("contype"); + if (conTypeStr) + { + m_defaultCollisionGroup = urdfLexicalCast(conTypeStr); + } + const char* conAffinityStr = child_xml->Attribute("conaffinity"); + if (conAffinityStr) + { + m_defaultCollisionMask = urdfLexicalCast(conAffinityStr); + } + } + } + handled=true; + return handled; + } + bool parseRootLevel(TiXmlElement* root_xml,MJCFErrorLogger* logger) + { + for (TiXmlElement* rootxml = root_xml->FirstChildElement() ; rootxml ; rootxml = rootxml->NextSiblingElement()) + { + bool handled = false; + std::string n = rootxml->Value(); + + if (n=="body") { int modelIndex = m_models.size(); UrdfModel* model = new UrdfModel(); m_models.push_back(model); - parseBody(xml,modelIndex, INVALID_LINK_INDEX,logger); + parseBody(rootxml,modelIndex, INVALID_LINK_INDEX,logger); initTreeAndRoot(*model,logger); handled = true; } @@ -168,7 +206,7 @@ struct BulletMJCFImporterInternalData UrdfLink* linkPtr = new UrdfLink(); linkPtr->m_name = "anonymous"; - const char* namePtr = xml->Attribute("name"); + const char* namePtr = rootxml->Attribute("name"); if (namePtr) { linkPtr->m_name = namePtr; @@ -177,12 +215,12 @@ struct BulletMJCFImporterInternalData linkPtr->m_linkIndex = linkIndex; modelPtr->m_links.insert(linkPtr->m_name.c_str(),linkPtr); - btTransform linkTransform = parseTransform(xml,logger); - linkPtr->m_linkTransformInWorld = linkTransform; - + //don't parse geom transform here, it will be inside 'parseGeom' + linkPtr->m_linkTransformInWorld.setIdentity(); + // modelPtr->m_rootLinks.push_back(linkPtr); - parseGeom(xml,modelIndex, linkIndex,logger); + parseGeom(rootxml,modelIndex, linkIndex,logger); initTreeAndRoot(*modelPtr,logger); handled = true; @@ -202,7 +240,7 @@ struct BulletMJCFImporterInternalData return true; } - bool parseJoint(TiXmlElement* link_xml, int modelIndex, int parentLinkIndex, int linkIndex, MJCFErrorLogger* logger, btTransform& jointTransOut) + bool parseJoint(TiXmlElement* link_xml, int modelIndex, int parentLinkIndex, int linkIndex, MJCFErrorLogger* logger, const btTransform& parentToLinkTrans, btTransform& jointTransOut) { const char* jType = link_xml->Attribute("type"); const char* limitedStr = link_xml->Attribute("limited"); @@ -210,6 +248,8 @@ struct BulletMJCFImporterInternalData const char* posStr = link_xml->Attribute("pos"); const char* ornStr = link_xml->Attribute("quat"); const char* nameStr = link_xml->Attribute("name"); + const char* rangeStr = link_xml->Attribute("range"); + btTransform jointTrans; jointTrans.setIdentity(); if (posStr) @@ -242,12 +282,36 @@ struct BulletMJCFImporterInternalData logger->reportWarning("joint without axis attribute"); } bool isLimited = false; + double range[2] = {1,0}; + if (limitedStr) { std::string lim = limitedStr; if (lim=="true") { isLimited = true; + //parse the 'range' field + btArray pieces; + btArray sizes; + btAlignedObjectArray strArray; + urdfIsAnyOf(" ", strArray); + urdfStringSplit(pieces, rangeStr, strArray); + for (int i = 0; i < pieces.size(); ++i) + { + if (!pieces[i].empty()) + { + sizes.push_back(urdfLexicalCast(pieces[i].c_str())); + } + } + if (sizes.size()==2) + { + range[0] = sizes[0]; + range[1] = sizes[1]; + } else + { + logger->reportWarning("Expected range[2] in joint with limits"); + } + } } else { @@ -259,7 +323,8 @@ struct BulletMJCFImporterInternalData btTransform parentLinkToJointTransform; parentLinkToJointTransform.setIdentity(); - parentLinkToJointTransform = jointTrans*linkPtr->m_linkTransformInWorld; + parentLinkToJointTransform = parentToLinkTrans*jointTrans; + jointTransOut = jointTrans; UrdfJointTypes ejtype; if (jType) @@ -270,6 +335,11 @@ struct BulletMJCFImporterInternalData ejtype = URDFFixedJoint; jointHandled = true; } + if (jointType == "slide") + { + ejtype = URDFPrismaticJoint; + jointHandled = true; + } if (jointType == "hinge") { if (isLimited) @@ -288,7 +358,6 @@ struct BulletMJCFImporterInternalData if (jointHandled) { - //default to 'fixed' joint UrdfJoint* jointPtr = new UrdfJoint(); jointPtr->m_childLinkName=linkPtr->m_name; const UrdfLink* parentLink = getLink(modelIndex,parentLinkIndex); @@ -297,13 +366,18 @@ struct BulletMJCFImporterInternalData jointPtr->m_parentLinkToJointTransform = parentLinkToJointTransform; jointPtr->m_type = ejtype; int numJoints = m_models[modelIndex]->m_joints.size(); + + //range + jointPtr->m_lowerLimit = range[0]; + jointPtr->m_upperLimit = range[1]; + if (nameStr) { jointPtr->m_name =nameStr; } else { char jointName[1024]; - sprintf(jointName,"joint%d_%d",linkIndex,numJoints); + sprintf(jointName,"joint%d_%d_%d",gUid++,linkIndex,numJoints); jointPtr->m_name =jointName; } m_models[modelIndex]->m_joints.insert(jointPtr->m_name.c_str(),jointPtr); @@ -335,6 +409,9 @@ struct BulletMJCFImporterInternalData bool handledGeomType = false; UrdfGeometry geom; + + + const char* rgba = link_xml->Attribute("rgba"); const char* gType = link_xml->Attribute("type"); const char* sz = link_xml->Attribute("size"); @@ -356,7 +433,7 @@ struct BulletMJCFImporterInternalData btVector4 o4; if (parseVector4(o4,ornStr)) { - orn.setValue(o4[3],o4[0],o4[1],o4[2]); + orn.setValue(o4[1],o4[2],o4[3],o4[0]); linkLocalFrame.setRotation(orn); } } @@ -410,16 +487,52 @@ struct BulletMJCFImporterInternalData if (geomType == "capsule") { geom.m_type = URDF_GEOM_CAPSULE; - geom.m_capsuleRadius = urdfLexicalCast(sz); + + btArray pieces; + btArray sizes; + btAlignedObjectArray strArray; + urdfIsAnyOf(" ", strArray); + urdfStringSplit(pieces, sz, strArray); + for (int i = 0; i < pieces.size(); ++i) + { + if (!pieces[i].empty()) + { + sizes.push_back(urdfLexicalCast(pieces[i].c_str())); + } + } + + geom.m_capsuleRadius = 0; + geom.m_capsuleHalfHeight = 0.f; + + if (sizes.size()>0) + { + geom.m_capsuleRadius = sizes[0]; + if (sizes.size()>1) + { + geom.m_capsuleHalfHeight = sizes[1]; + } + } else + { + logger->reportWarning("couldn't convert 'size' attribute of capsule geom"); + } const char* fromtoStr = link_xml->Attribute("fromto"); + geom.m_hasFromTo = false; + if (fromtoStr) { + geom.m_hasFromTo = true; std::string fromto = fromtoStr; parseVector6(geom.m_capsuleFrom,geom.m_capsuleTo,fromto,logger); handledGeomType = true; } else { - logger->reportWarning("capsule without fromto attribute"); + if (sizes.size()<2) + { + logger->reportWarning("capsule without fromto attribute requires 2 sizes (radius and halfheight)"); + } else + { + handledGeomType = true; + } } } #if 0 @@ -433,6 +546,26 @@ struct BulletMJCFImporterInternalData { UrdfCollision col; + col.m_flags |= URDF_HAS_COLLISION_GROUP; + col.m_collisionGroup = m_defaultCollisionGroup; + + col.m_flags |= URDF_HAS_COLLISION_MASK; + col.m_collisionMask = m_defaultCollisionMask; + + //contype, conaffinity + const char* conTypeStr = link_xml->Attribute("contype"); + if (conTypeStr) + { + col.m_flags |= URDF_HAS_COLLISION_GROUP; + col.m_collisionGroup = urdfLexicalCast(conTypeStr); + } + const char* conAffinityStr = link_xml->Attribute("conaffinity"); + if (conAffinityStr) + { + col.m_flags |= URDF_HAS_COLLISION_MASK; + col.m_collisionMask = urdfLexicalCast(conAffinityStr); + } + col.m_geometry = geom; col.m_linkLocalFrame = linkLocalFrame; linkPtr->m_collisionArray.push_back(col); @@ -478,7 +611,7 @@ struct BulletMJCFImporterInternalData btQuaternion orn(0,0,0,1); if (parseVector4(o4,ornstr)) { - orn.setValue(o4[3],o4[0],o4[1],o4[2]);//MuJoCo quats are [w,x,y,z], Bullet uses [x,y,z,w] + orn.setValue(o4[1],o4[2],o4[3],o4[0]);//MuJoCo quats are [w,x,y,z], Bullet uses [x,y,z,w] tr.setRotation(orn); } } else @@ -545,33 +678,61 @@ struct BulletMJCFImporterInternalData return totalVolume; } + UrdfLink* getLink(int modelIndex, int linkIndex) + { + UrdfLink** linkPtrPtr = m_models[modelIndex]->m_links.getAtIndex(linkIndex); + if (linkPtrPtr && *linkPtrPtr) + { + return *linkPtrPtr; + } + btAssert(0); + return 0; + } - bool parseBody(TiXmlElement* link_xml, int modelIndex, int parentLinkIndex, MJCFErrorLogger* logger) + int createBody(int modelIndex, const char* namePtr) { UrdfModel* modelPtr = m_models[modelIndex]; - int linkIndex = modelPtr->m_links.size(); + int orgChildLinkIndex = modelPtr->m_links.size(); UrdfLink* linkPtr = new UrdfLink(); char uniqueLinkName[1024]; - sprintf(uniqueLinkName,"link%d",linkIndex); + sprintf(uniqueLinkName,"link%d",orgChildLinkIndex ); linkPtr->m_name = uniqueLinkName; - const char* namePtr = link_xml->Attribute("name"); if (namePtr) { linkPtr->m_name = namePtr; } - - linkPtr->m_linkIndex = linkIndex; + linkPtr->m_linkIndex = orgChildLinkIndex ; modelPtr->m_links.insert(linkPtr->m_name.c_str(),linkPtr); - - btTransform linkTransform = parseTransform(link_xml,logger); + return orgChildLinkIndex; + } + bool parseBody(TiXmlElement* link_xml, int modelIndex, int orgParentLinkIndex, MJCFErrorLogger* logger) + { + int newParentLinkIndex = orgParentLinkIndex; - linkPtr->m_linkTransformInWorld = linkTransform; - //body/geom links with no parent are root links - if (parentLinkIndex==INVALID_LINK_INDEX) + const char* bodyName = link_xml->Attribute("name"); + int orgChildLinkIndex = createBody(modelIndex,bodyName); + + int curChildLinkIndex = orgChildLinkIndex; + std::string bodyN; + + if (bodyName) { -// modelPtr->m_rootLinks.push_back(linkPtr); + bodyN = bodyName; + } else + { + char anon[1024]; + sprintf(anon,"anon%d",gUid++); + bodyN = anon; } + + + btTransform orgLinkTransform = parseTransform(link_xml,logger); + + btTransform linkTransform = parseTransform(link_xml,logger); + UrdfLink* linkPtr = getLink(modelIndex,orgChildLinkIndex); + + bool massDefined = false; btVector3 inertialPos(0,0,0); @@ -582,6 +743,7 @@ struct BulletMJCFImporterInternalData bool hasJoint = false; btTransform jointTrans; jointTrans.setIdentity(); + bool skipFixedJoint = false; for (TiXmlElement* xml = link_xml->FirstChildElement() ; xml ; xml = xml->NextSiblingElement()) { @@ -616,31 +778,56 @@ struct BulletMJCFImporterInternalData if (n=="joint") { - //skip joints at the root for now - //MuJoCo supports more than just 'free' or 'fixed', - //so we will need to emulate this with extra root links+joints - - //for now, only convert 1st joint if (!hasJoint) { - if (parentLinkIndex!=INVALID_LINK_INDEX) + const char* jType = xml->Attribute("type"); + std::string jointType = jType? jType:""; + + if (newParentLinkIndex!=INVALID_LINK_INDEX || jointType!="free") { - parseJoint(xml,modelIndex,parentLinkIndex, linkIndex,logger,jointTrans); + if (newParentLinkIndex==INVALID_LINK_INDEX) + { + int newRootLinkIndex = createBody(modelIndex,0); + UrdfLink* rootLink = getLink(modelIndex,newRootLinkIndex); + rootLink->m_inertia.m_mass = 0; + rootLink->m_linkTransformInWorld.setIdentity(); + newParentLinkIndex = newRootLinkIndex; + } + + int newLinkIndex = createBody(modelIndex,0); + parseJoint(xml,modelIndex,newParentLinkIndex, newLinkIndex,logger,linkTransform,jointTrans); + + //getLink(modelIndex,newLinkIndex)->m_linkTransformInWorld = jointTrans*linkTransform; + + linkTransform = jointTrans.inverse(); + newParentLinkIndex = newLinkIndex; + //newParentLinkIndex, curChildLinkIndex + hasJoint = true; + handled = true; } + } else + { + int newLinkIndex = createBody(modelIndex,0); + btTransform joint2nextjoint = jointTrans.inverse(); + btTransform unused; + parseJoint(xml,modelIndex,newParentLinkIndex, newLinkIndex,logger,joint2nextjoint,unused); + newParentLinkIndex = newLinkIndex; + //todo: compute relative joint transforms (if any) and append to linkTransform + hasJoint = true; + handled = true; } - hasJoint = true; - handled = true; + } if (n == "geom") { - parseGeom(xml,modelIndex, linkIndex, logger); + parseGeom(xml,modelIndex, orgChildLinkIndex , logger); handled = true; } //recursive if (n=="body") { - parseBody(xml,modelIndex,linkIndex,logger); + parseBody(xml,modelIndex,orgChildLinkIndex,logger); handled = true; } @@ -657,18 +844,24 @@ struct BulletMJCFImporterInternalData } } - if ((!hasJoint) && (parentLinkIndex != INVALID_LINK_INDEX)) + linkPtr->m_linkTransformInWorld = linkTransform; + if (bodyN == "cart1")//front_left_leg") { + printf("found!\n"); + } + if ((newParentLinkIndex != INVALID_LINK_INDEX) && !skipFixedJoint) + { + //linkPtr->m_linkTransformInWorld.setIdentity(); //default to 'fixed' joint UrdfJoint* jointPtr = new UrdfJoint(); jointPtr->m_childLinkName=linkPtr->m_name; - const UrdfLink* parentLink = getLink(modelIndex,parentLinkIndex); + const UrdfLink* parentLink = getLink(modelIndex,newParentLinkIndex); jointPtr->m_parentLinkName =parentLink->m_name; jointPtr->m_localJointAxis.setValue(1,0,0); jointPtr->m_parentLinkToJointTransform = linkTransform; jointPtr->m_type = URDFFixedJoint; char jointName[1024]; - sprintf(jointName,"joint%d",linkIndex); + sprintf(jointName,"jointfix_%d_%d",gUid++,newParentLinkIndex); jointPtr->m_name =jointName; m_models[modelIndex]->m_joints.insert(jointPtr->m_name.c_str(),jointPtr); } @@ -680,11 +873,26 @@ struct BulletMJCFImporterInternalData double volume = computeVolume(linkPtr,logger); mass = density * volume; } - linkPtr->m_inertia.m_linkLocalFrame = jointTrans.inverse(); + linkPtr->m_inertia.m_linkLocalFrame.setIdentity();// = jointTrans.inverse(); linkPtr->m_inertia.m_mass = mass; return true; } + void recurseAddChildLinks(UrdfModel* model, UrdfLink* link) + { + for (int i=0;im_childLinks.size();i++) + { + int linkIndex = model->m_links.size(); + link->m_childLinks[i]->m_linkIndex = linkIndex; + const char* linkName = link->m_childLinks[i]->m_name.c_str(); + model->m_links.insert(linkName,link->m_childLinks[i]); + } + for (int i=0;im_childLinks.size();i++) + { + recurseAddChildLinks(model,link->m_childLinks[i]); + } + } + bool initTreeAndRoot(UrdfModel& model, MJCFErrorLogger* logger) { // every link has children links and joints, but no parents, so we create a @@ -763,6 +971,23 @@ struct BulletMJCFImporterInternalData logger->reportError("URDF without root link found"); return false; } + + //re-index the link indices so parent indices are always smaller than child indices + btAlignedObjectArray links; + links.resize(model.m_links.size()); + for (int i=0;im_linkIndex = linkIndex; + model.m_links.insert(rootLink->m_name.c_str(),rootLink); + recurseAddChildLinks(&model, rootLink); + } return true; } @@ -850,6 +1075,13 @@ bool BulletMJCFImporter::parseMJCFString(const char* xmlText, MJCFErrorLogger* l m_data->m_fileModelName = modelName; } + //,