/* * Copyright (C) 2012 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include #include "gazebo/common/Assert.hh" #include "gazebo/physics/physics.hh" #include "gazebo/sensors/SensorManager.hh" #include "gazebo/sensors/ContactSensor.hh" #include "gazebo/transport/transport.hh" #include "plugins/MudPlugin.hh" using namespace gazebo; GZ_REGISTER_MODEL_PLUGIN(MudPlugin) ///////////////////////////////////////////////// MudPlugin::MudPlugin() : newMsg(false), newMsgWait(0), stiffness(0.0), damping(100.0), contactSurfaceBitmask(0) { } ///////////////////////////////////////////////// void MudPlugin::Load(physics::ModelPtr _model, sdf::ElementPtr _sdf) { GZ_ASSERT(_model, "MudPlugin _model pointer is NULL"); this->model = _model; this->modelName = _model->GetName(); this->sdf = _sdf; this->world = this->model->GetWorld(); GZ_ASSERT(this->world, "MudPlugin world pointer is NULL"); this->physics = this->world->GetPhysicsEngine(); GZ_ASSERT(this->physics, "MudPlugin physics pointer is NULL"); this->link = _model->GetLink(); GZ_ASSERT(this->link, "MudPlugin link pointer is NULL"); GZ_ASSERT(_sdf, "MudPlugin _sdf pointer is NULL"); if (_sdf->HasElement("contact_sensor_name")) { this->contactSensorName = _sdf->Get("contact_sensor_name"); } else { gzerr << "contactSensorName not supplied, ignoring contacts\n"; } if (_sdf->HasElement("stiffness")) this->stiffness = _sdf->Get("stiffness"); if (_sdf->HasElement("damping")) this->damping = _sdf->Get("damping"); if (_sdf->HasElement("contact_surface_bitmask")) { this->contactSurfaceBitmask = _sdf->Get("contact_surface_bitmask"); } if (_sdf->HasElement("link_name")) { sdf::ElementPtr elem = _sdf->GetElement("link_name"); while (elem) { allowedLinks.push_back(elem->Get()); links.push_back(physics::LinkPtr()); joints.push_back(physics::JointPtr()); elem = elem->GetNextElement("link_name"); } } GZ_ASSERT(allowedLinks.size() == links.size(), "Length of links data structure doesn't match allowedLinks"); GZ_ASSERT(allowedLinks.size() == joints.size(), "Length of joints data structure doesn't match allowedLinks"); } ///////////////////////////////////////////////// void MudPlugin::Init() { this->node.reset(new transport::Node()); this->node->Init(this->world->GetName()); if (!this->contactSensorName.empty()) { std::string topic = std::string("~/") + this->modelName + "/" + this->contactSensorName; this->contactSub = this->node->Subscribe(topic, &MudPlugin::OnContact, this); // create bitmask from contact sensor's collisions if it's not specified in // the sdf if (!this->sdf->HasElement("contact_surface_bitmask")) { std::string name = this->contactSensorName; boost::replace_all(name, "/", "::"); name = this->world->GetName() + "::"+ this->modelName + "::" + name; sensors::SensorManager *mgr = sensors::SensorManager::Instance(); // Get a pointer to the contact sensor sensors::ContactSensorPtr sensor = std::dynamic_pointer_cast (mgr->GetSensor(name)); if (sensor) { for (unsigned int i = 0; i < sensor->GetCollisionCount(); ++i) { std::string colName = sensor->GetCollisionName(i); physics::CollisionPtr colPtr = boost::dynamic_pointer_cast( this->world->GetEntity(colName)); if (colPtr) { this->contactSurfaceBitmask |= colPtr->GetSurface()->collideWithoutContactBitmask; } } } else { gzerr << "Unable to GetSensor, ignoring contact_surface_bitmask\n"; } } } for (unsigned int i = 0; i < this->allowedLinks.size(); ++i) { physics::LinkPtr allowedLink = boost::dynamic_pointer_cast( this->world->GetEntity(this->allowedLinks[i])); if (!allowedLink) continue; std::vector collisions = allowedLink->GetCollisions(); for (unsigned int j = 0; j < collisions.size(); ++j) { collisions[j]->GetSurface()->collideWithoutContactBitmask |= this->contactSurfaceBitmask; } } this->updateConnection = event::Events::ConnectWorldUpdateBegin( std::bind(&MudPlugin::OnUpdate, this)); } ///////////////////////////////////////////////// void MudPlugin::OnContact(ConstContactsPtr &_msg) { boost::mutex::scoped_lock lock(this->mutex); this->newestContactsMsg = *_msg; this->newMsg = true; } ///////////////////////////////////////////////// void MudPlugin::OnUpdate() { double dt = this->physics->GetMaxStepSize(); if (dt < 1e-6) dt = 1e-6; if (this->newMsg) { boost::mutex::scoped_lock lock(this->mutex); unsigned int nc = this->newestContactsMsg.contact_size(); boost::unordered_set contactLinkNames; boost::unordered_map linkNameIndices; // If new contacts, then get the link names if (nc) { // Starting with last contact message, iterate backwards // checking each contact with a timestamp matching the last contact // Add each link name to contactLinkNames common::Time latestContactTime = msgs::Convert(this->newestContactsMsg.contact(nc-1).time()); std::string targetCollName, tmpLinkName; for (int i = nc-1; i >= 0 && msgs::Convert(this->newestContactsMsg.contact(i).time()) == latestContactTime; --i) { // Try to find name of the other collision // If collision1() starts with this->modelName, then use collision2() targetCollName = this->newestContactsMsg.contact(i).collision1(); if (0 == targetCollName.compare(0, this->modelName.length(), targetCollName)) { targetCollName = this->newestContactsMsg.contact(i).collision2(); } tmpLinkName = targetCollName.substr(0, targetCollName.rfind("::")); contactLinkNames.insert(tmpLinkName); linkNameIndices[tmpLinkName] = i; } } // Iterate through the list of allowed links std::vector::iterator iterLinkName = this->allowedLinks.begin(); std::vector::iterator iterLink = this->links.begin(); std::vector::iterator iterJoint = this->joints.begin(); unsigned int countIters = 0; // Only check the length of the first iterator since we used a GZ_ASSERT // in the Load function to confirm the vectors have the same length while (iterLinkName != this->allowedLinks.end()) { // If *iterLinkName is in contactLinkNames if (contactLinkNames.end() != contactLinkNames.find(*iterLinkName)) { // Compute the average contact point position math::Vector3 contactPositionAverage; { // Find the index to the correct contact data structure unsigned int i = linkNameIndices[*iterLinkName]; if (i < nc) { unsigned int pc = this->newestContactsMsg.contact(i).position_size(); // Add up all the contact point positions for (unsigned int j = 0; j < pc; ++j) { contactPositionAverage += msgs::ConvertIgn( this->newestContactsMsg.contact(i).position(j)); } // Then divide by numer of contact points contactPositionAverage /= static_cast(pc); } else { gzerr << "Error in linkNameIndices\n"; } } // If joint exists if (*iterJoint) { // Update the anchor position // TODO: consider checking MaxStepSize and updating erp, cfm (*iterJoint)->SetAnchor(0, contactPositionAverage); } // Otherwise, try to create a joint else { // Try to get link pointer if we don't already have it if (!(*iterLink)) { std::string targetModelName = (*iterLinkName).substr(0, (*iterLinkName).rfind("::")); physics::ModelPtr targetModel = this->world->GetModel(targetModelName); if (targetModel) *iterLink = targetModel->GetLink(*iterLinkName); } if (*iterLink) { // Create the joint // gzdbg << "Creating a mud joint with " << *iterLinkName << '\n'; (*iterLink)->SetAutoDisable(false); *iterJoint = this->physics->CreateJoint("revolute", this->model); (*iterJoint)->Attach(this->link, *iterLink); (*iterJoint)->Load(this->link, *iterLink, math::Pose(contactPositionAverage, math::Quaternion())); // Joint names must be unique // name as mud_joint_0, mud_joint_1, etc. { std::stringstream jointNameStream; jointNameStream << "mud_joint_" << countIters; (*iterJoint)->SetName(jointNameStream.str()); } { double erp, cfm; erp = this->stiffness*dt / (this->stiffness*dt + this->damping); cfm = 1.0 / (this->stiffness*dt + this->damping); (*iterJoint)->SetParam("erp", 0, erp); (*iterJoint)->SetParam("cfm", 0, cfm); (*iterJoint)->SetParam("stop_erp", 0, erp); (*iterJoint)->SetParam("stop_cfm", 0, cfm); } (*iterJoint)->SetHighStop(0, 0.0); (*iterJoint)->SetLowStop(0, 0.0); (*iterJoint)->Init(); } } } // *iterLinkName is not in contactLinkNames else { // If there's an existing joint, // then delete the joint if (*iterJoint) { // gzdbg << "Destroying mud joint\n"; // reenable collision between the link pair physics::LinkPtr parent = (*iterJoint)->GetParent(); physics::LinkPtr child = (*iterJoint)->GetChild(); if (parent) parent->SetCollideMode("all"); if (child) child->SetCollideMode("all"); (*iterJoint)->Detach(); (*iterJoint).reset(); } } // Increment ++countIters; ++iterJoint; ++iterLink; ++iterLinkName; } this->newMsg = false; this->newMsgWait = 0; } else if (++this->newMsgWait > floor(1.0 / dt)) { gzlog << "MudPlugin attached to " << this->modelName << " waited 1.0 s without contact messages\n"; this->newMsgWait = 0; } }