535 lines
16 KiB
C++
535 lines
16 KiB
C++
/*
|
|
* 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 <gtest/gtest.h>
|
|
#include "gazebo/physics/physics.hh"
|
|
#include "gazebo/physics/Joint.hh"
|
|
#include "gazebo/test/helper_physics_generator.hh"
|
|
#include "test/integration/joint_test.hh"
|
|
|
|
#define TOL 1e-6
|
|
#define TOL_CONT 2.0
|
|
|
|
using namespace gazebo;
|
|
|
|
//////////////////////////////////////////////////
|
|
void JointTest::JointCreationDestructionTest(const std::string &_physicsEngine)
|
|
{
|
|
/// \TODO: Disable for now until functionality is implemented
|
|
/// bullet collision parameters needs tweaking
|
|
if (_physicsEngine == "bullet")
|
|
{
|
|
gzerr << "Aborting test for bullet, see issue #590.\n";
|
|
return;
|
|
}
|
|
/// \TODO: simbody not complete for this test
|
|
if (_physicsEngine == "simbody")
|
|
{
|
|
gzerr << "Aborting test for Simbody, see issue #862.\n";
|
|
return;
|
|
}
|
|
/// \TODO: dart not complete for this test
|
|
if (_physicsEngine == "dart")
|
|
{
|
|
gzerr << "Aborting test for DART, see issue #903.\n";
|
|
return;
|
|
}
|
|
|
|
// Load our inertial test world
|
|
Load("worlds/joint_test.world", true, _physicsEngine);
|
|
|
|
// Get a pointer to the world, make sure world loads
|
|
physics::WorldPtr world = physics::get_world("default");
|
|
ASSERT_TRUE(world != NULL);
|
|
|
|
// Verify physics engine type
|
|
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
|
|
ASSERT_TRUE(physics != NULL);
|
|
EXPECT_EQ(physics->GetType(), _physicsEngine);
|
|
|
|
// create some fake links
|
|
physics::ModelPtr model = world->GetModel("model_1");
|
|
ASSERT_TRUE(model != NULL);
|
|
physics::LinkPtr link = model->GetLink("link_1");
|
|
ASSERT_TRUE(link != NULL);
|
|
|
|
physics::LinkPtr parentLink;
|
|
physics::LinkPtr childLink(link);
|
|
physics::JointPtr joint;
|
|
math::Pose anchor;
|
|
math::Vector3 axis(1, 0, 0);
|
|
double upper = M_PI;
|
|
double lower = -M_PI;
|
|
|
|
double residentLast = 0, shareLast = 0;
|
|
double residentCur = 0, shareCur = 0;
|
|
|
|
// The memory footprint on osx can take around 190 cycles to stabilize.
|
|
// So this test gives 250 cycles to stabilize and then verifies stability
|
|
// for another 250.
|
|
unsigned int cyclesMax = 500;
|
|
unsigned int cyclesStabilize = cyclesMax / 2;
|
|
for (unsigned int i = 0; i < cyclesMax; ++i)
|
|
{
|
|
// try creating a joint
|
|
{
|
|
joint = world->GetPhysicsEngine()->CreateJoint(
|
|
"revolute", model);
|
|
joint->Attach(parentLink, childLink);
|
|
// load adds the joint to a vector of shared pointers kept
|
|
// in parent and child links, preventing joint from being destroyed.
|
|
joint->Load(parentLink, childLink, anchor);
|
|
// joint->SetAnchor(0, anchor);
|
|
joint->SetAxis(0, axis);
|
|
joint->SetHighStop(0, upper);
|
|
joint->SetLowStop(0, lower);
|
|
|
|
if (parentLink)
|
|
joint->SetName(parentLink->GetName() + std::string("_") +
|
|
childLink->GetName() + std::string("_joint"));
|
|
else
|
|
joint->SetName(std::string("world_") +
|
|
childLink->GetName() + std::string("_joint"));
|
|
joint->Init();
|
|
joint->SetAxis(0, axis);
|
|
}
|
|
|
|
// remove the joint
|
|
{
|
|
bool paused = world->IsPaused();
|
|
world->SetPaused(true);
|
|
if (joint)
|
|
{
|
|
// reenable collision between the link pair
|
|
physics::LinkPtr parent = joint->GetParent();
|
|
physics::LinkPtr child = joint->GetChild();
|
|
if (parent)
|
|
parent->SetCollideMode("all");
|
|
if (child)
|
|
child->SetCollideMode("all");
|
|
|
|
joint->Detach();
|
|
joint.reset();
|
|
}
|
|
world->SetPaused(paused);
|
|
}
|
|
|
|
world->Step(200);
|
|
|
|
this->GetMemInfo(residentCur, shareCur);
|
|
|
|
// give it 2 cycles to stabilize
|
|
if (i > cyclesStabilize)
|
|
{
|
|
EXPECT_LE(residentCur, residentLast);
|
|
EXPECT_LE(shareCur, shareLast);
|
|
}
|
|
// gzdbg << "memory res[" << residentCur
|
|
// << "] shr[" << shareCur
|
|
// << "] res[" << residentLast
|
|
// << "] shr[" << shareLast
|
|
// << "]\n";
|
|
residentLast = residentCur;
|
|
shareLast = shareCur;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void JointTest::GetInertiaRatio(const std::string &_physicsEngine)
|
|
{
|
|
// Load our inertia ratio world
|
|
Load("worlds/inertia_ratio.world", true, _physicsEngine);
|
|
|
|
// Get a pointer to the world, make sure world loads
|
|
physics::WorldPtr world = physics::get_world("default");
|
|
ASSERT_TRUE(world != NULL);
|
|
|
|
// Verify physics engine type
|
|
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
|
|
ASSERT_TRUE(physics != NULL);
|
|
EXPECT_EQ(physics->GetType(), _physicsEngine);
|
|
|
|
physics::ModelPtr model = world->GetModel("double_pendulum");
|
|
ASSERT_TRUE(model != NULL);
|
|
|
|
{
|
|
physics::JointPtr joint = model->GetJoint("lower_joint");
|
|
ASSERT_TRUE(joint != NULL);
|
|
|
|
EXPECT_NEAR(joint->GetInertiaRatio(0), 3125, 1e-2);
|
|
EXPECT_NEAR(joint->GetInertiaRatio(math::Vector3::UnitX), 3125, 1e-2);
|
|
EXPECT_NEAR(joint->GetInertiaRatio(math::Vector3::UnitY), 87.50, 1e-2);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void JointTest::SpringDamperTest(const std::string &_physicsEngine)
|
|
{
|
|
/// bullet collision parameters needs tweaking
|
|
if (_physicsEngine == "bullet")
|
|
{
|
|
gzerr << "Aborting test for bullet, see issue #887.\n";
|
|
return;
|
|
}
|
|
if (_physicsEngine == "dart")
|
|
{
|
|
gzerr << "Aborting test for " << _physicsEngine
|
|
<< ", see issue #975.\n";
|
|
return;
|
|
}
|
|
|
|
// Load our inertial test world
|
|
Load("worlds/spring_damper_test.world", true, _physicsEngine);
|
|
|
|
// Get a pointer to the world, make sure world loads
|
|
physics::WorldPtr world = physics::get_world("default");
|
|
ASSERT_TRUE(world != NULL);
|
|
|
|
// Verify physics engine type
|
|
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
|
|
ASSERT_TRUE(physics != NULL);
|
|
EXPECT_EQ(physics->GetType(), _physicsEngine);
|
|
|
|
// All models should oscillate with the same frequency
|
|
physics::ModelPtr modelPrismatic = world->GetModel("model_3_prismatic");
|
|
physics::ModelPtr modelRevolute = world->GetModel("model_3_revolute");
|
|
physics::ModelPtr modelPlugin = world->GetModel("model_4_prismatic_plugin");
|
|
physics::ModelPtr modelContact = world->GetModel("model_5_soft_contact");
|
|
physics::ModelPtr modelPrismatic2 = world->GetModel("model_6_prismatic_sdf");
|
|
physics::ModelPtr modelRevolute2 = world->GetModel("model_7_revolute_sdf");
|
|
|
|
ASSERT_TRUE(modelPrismatic != NULL);
|
|
ASSERT_TRUE(modelRevolute != NULL);
|
|
ASSERT_TRUE(modelPlugin != NULL);
|
|
ASSERT_TRUE(modelContact != NULL);
|
|
ASSERT_TRUE(modelPrismatic2 != NULL);
|
|
ASSERT_TRUE(modelRevolute2 != NULL);
|
|
|
|
physics::LinkPtr linkPrismatic = modelPrismatic->GetLink("link_1");
|
|
physics::LinkPtr linkRevolute = modelRevolute->GetLink("link_1");
|
|
physics::LinkPtr linkPluginExplicit = modelPlugin->GetLink("link_1");
|
|
physics::LinkPtr linkPluginImplicit = modelPlugin->GetLink("link_2");
|
|
physics::LinkPtr linkContact = modelContact->GetLink("link_1");
|
|
physics::LinkPtr linkPrismatic2 = modelPrismatic2->GetLink("link");
|
|
physics::LinkPtr linkRevolute2 = modelRevolute2->GetLink("link");
|
|
|
|
ASSERT_TRUE(linkPrismatic != NULL);
|
|
ASSERT_TRUE(linkRevolute != NULL);
|
|
ASSERT_TRUE(linkPluginExplicit != NULL);
|
|
ASSERT_TRUE(linkPluginImplicit != NULL);
|
|
ASSERT_TRUE(linkContact != NULL);
|
|
ASSERT_TRUE(linkPrismatic2 != NULL);
|
|
ASSERT_TRUE(linkRevolute2 != NULL);
|
|
|
|
physics::JointPtr jointPluginImplicit = modelPlugin->GetJoint("joint_1");
|
|
ASSERT_TRUE(jointPluginImplicit != NULL);
|
|
|
|
int cyclesPrismatic = 0;
|
|
int cyclesRevolute = 0;
|
|
int cyclesPluginExplicit = 0;
|
|
int cyclesPluginImplicit = 0;
|
|
int cyclesContact = 0;
|
|
int cyclesPrismatic2 = 0;
|
|
int cyclesRevolute2 = 0;
|
|
|
|
double velPrismatic = 1.0;
|
|
double velRevolute = 1.0;
|
|
double velPluginExplicit = 1.0;
|
|
double velPluginImplicit = 1.0;
|
|
double velContact = 1.0;
|
|
double velPrismatic2 = 1.0;
|
|
double velRevolute2 = 1.0;
|
|
const double vT = 0.01;
|
|
|
|
double energyPluginImplicit0 = linkPluginImplicit->GetWorldEnergy()
|
|
+ jointPluginImplicit->GetWorldEnergyPotentialSpring(0);
|
|
|
|
// check number of oscillations for each of the setup. They should all
|
|
// be the same.
|
|
// run 5000 steps, at which point, contact is the first one to damp out
|
|
// and lose it's oscillatory behavior due to larger dissipation in
|
|
// contact behavior.
|
|
for (int i = 0; i < 5000; ++i)
|
|
{
|
|
world->Step(1);
|
|
|
|
// count up and down cycles
|
|
if (linkPrismatic->GetWorldLinearVel().z > vT && velPrismatic < -vT)
|
|
{
|
|
cyclesPrismatic++;
|
|
velPrismatic = 1.0;
|
|
}
|
|
else if (linkPrismatic->GetWorldLinearVel().z < -vT && velPrismatic > vT)
|
|
{
|
|
cyclesPrismatic++;
|
|
velPrismatic = -1.0;
|
|
}
|
|
if (-linkRevolute->GetRelativeAngularVel().y > vT && velRevolute < -vT)
|
|
{
|
|
cyclesRevolute++;
|
|
velRevolute = 1.0;
|
|
}
|
|
else if (-linkRevolute->GetRelativeAngularVel().y < -vT && velRevolute > vT)
|
|
{
|
|
cyclesRevolute++;
|
|
velRevolute = -1.0;
|
|
}
|
|
if (linkPluginExplicit->GetWorldLinearVel().z > vT &&
|
|
velPluginExplicit < -vT)
|
|
{
|
|
cyclesPluginExplicit++;
|
|
velPluginExplicit = 1.0;
|
|
}
|
|
else if (linkPluginExplicit->GetWorldLinearVel().z < -vT &&
|
|
velPluginExplicit > vT)
|
|
{
|
|
cyclesPluginExplicit++;
|
|
velPluginExplicit = -1.0;
|
|
}
|
|
if (linkPluginImplicit->GetWorldLinearVel().z > vT &&
|
|
velPluginImplicit < -vT)
|
|
{
|
|
cyclesPluginImplicit++;
|
|
velPluginImplicit = 1.0;
|
|
}
|
|
else if (linkPluginImplicit->GetWorldLinearVel().z < -vT &&
|
|
velPluginImplicit > vT)
|
|
{
|
|
cyclesPluginImplicit++;
|
|
velPluginImplicit = -1.0;
|
|
}
|
|
if (linkContact->GetWorldLinearVel().z > vT && velContact < -vT)
|
|
{
|
|
cyclesContact++;
|
|
velContact = 1.0;
|
|
}
|
|
else if (linkContact->GetWorldLinearVel().z < -vT && velContact > vT)
|
|
{
|
|
cyclesContact++;
|
|
velContact = -1.0;
|
|
}
|
|
if (linkPrismatic2->GetWorldLinearVel().z > vT && velPrismatic2 < -vT)
|
|
{
|
|
cyclesPrismatic2++;
|
|
velPrismatic2 = 1.0;
|
|
}
|
|
else if (linkPrismatic2->GetWorldLinearVel().z < -vT && velPrismatic2 > vT)
|
|
{
|
|
cyclesPrismatic2++;
|
|
velPrismatic2 = -1.0;
|
|
}
|
|
if (-linkRevolute2->GetRelativeAngularVel().y > vT && velRevolute2 < -vT)
|
|
{
|
|
cyclesRevolute2++;
|
|
velRevolute2 = 1.0;
|
|
}
|
|
else if (-linkRevolute2->GetRelativeAngularVel().y < -vT &&
|
|
velRevolute2 > vT)
|
|
{
|
|
cyclesRevolute2++;
|
|
velRevolute2 = -1.0;
|
|
}
|
|
|
|
double energy = linkPluginImplicit->GetWorldEnergy() +
|
|
jointPluginImplicit->GetWorldEnergyPotentialSpring(0);
|
|
if (_physicsEngine.compare("dart") == 0)
|
|
{
|
|
if (i == 0)
|
|
{
|
|
gzerr << _physicsEngine
|
|
<< " has reduced accuracy for spring energy conservation"
|
|
<< ", see #975"
|
|
<< std::endl;
|
|
}
|
|
EXPECT_NEAR(energy / energyPluginImplicit0, 1.0, 2e-2);
|
|
}
|
|
else
|
|
{
|
|
EXPECT_NEAR(energy / energyPluginImplicit0, 1.0, 1e-3);
|
|
}
|
|
// gzdbg << i << "\n";
|
|
// gzdbg << cyclesPrismatic << " : "
|
|
// << linkPrismatic->GetWorldLinearVel() << "\n";
|
|
// gzdbg << cyclesRevolute << " : "
|
|
// << linkRevolute->GetRelativeAngularVel() << "\n";
|
|
// gzdbg << cyclesContact << " : "
|
|
// << linkContact->GetWorldLinearVel() << "\n";
|
|
}
|
|
if (_physicsEngine.compare("ode") == 0)
|
|
{
|
|
gzdbg << "Extra tests for ode" << std::endl;
|
|
EXPECT_EQ(cyclesContact, 17);
|
|
}
|
|
if (_physicsEngine.compare("dart") == 0)
|
|
{
|
|
gzerr << _physicsEngine
|
|
<< " doesn't support joint limit stiffness"
|
|
<< ", see #975"
|
|
<< std::endl;
|
|
}
|
|
else
|
|
{
|
|
EXPECT_EQ(cyclesPrismatic, 17);
|
|
EXPECT_EQ(cyclesRevolute, 17);
|
|
}
|
|
EXPECT_EQ(cyclesPluginExplicit, 17);
|
|
EXPECT_EQ(cyclesPluginImplicit, 17);
|
|
EXPECT_EQ(cyclesPrismatic2, 17);
|
|
EXPECT_EQ(cyclesRevolute2, 17);
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void JointTest::DynamicJointVisualization(const std::string &_physicsEngine)
|
|
{
|
|
/// \TODO: simbody not complete for this test
|
|
if (_physicsEngine == "simbody")
|
|
{
|
|
gzerr << "Aborting test for Simbody, see issue #862.\n";
|
|
return;
|
|
}
|
|
// Load empty world
|
|
Load("worlds/empty.world", true, _physicsEngine);
|
|
|
|
// Get a pointer to the world, make sure world loads
|
|
physics::WorldPtr world = physics::get_world("default");
|
|
ASSERT_TRUE(world != NULL);
|
|
|
|
// Verify physics engine type
|
|
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
|
|
ASSERT_TRUE(physics != NULL);
|
|
EXPECT_EQ(physics->GetType(), _physicsEngine);
|
|
|
|
// Spawn two boxes
|
|
SpawnBox("box1", math::Vector3(1, 1, 1), math::Vector3(1, 0, 0.5),
|
|
math::Vector3::Zero, false);
|
|
SpawnBox("box2", math::Vector3(1, 1, 1), math::Vector3(-1, 0, 0.5),
|
|
math::Vector3::Zero, false);
|
|
physics::ModelPtr model = world->GetModel("box1");
|
|
physics::ModelPtr model2 = world->GetModel("box2");
|
|
ASSERT_TRUE(model != NULL);
|
|
ASSERT_TRUE(model2 != NULL);
|
|
|
|
// Get link pointers
|
|
physics::LinkPtr parent = model->GetLink();
|
|
physics::LinkPtr child = model2->GetLink();
|
|
|
|
// Create dynamic joint
|
|
std::string name = "dynamic_joint_unique";
|
|
physics::JointPtr joint;
|
|
joint = model->CreateJoint(name, "revolute", parent, child);
|
|
joint->Init();
|
|
|
|
// Get model joints (used for visualization)
|
|
physics::Joint_V joints = model->GetJoints();
|
|
bool jointFound = false;
|
|
for (auto const &joint : joints)
|
|
{
|
|
if (joint->GetName() == name)
|
|
{
|
|
jointFound = true;
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_TRUE(jointFound);
|
|
|
|
// Try to create the joint with the same name
|
|
EXPECT_TRUE(model->CreateJoint(name, "revolute", parent, child) == NULL);
|
|
|
|
// step to let joint creation finish before removing it
|
|
world->Step(1000);
|
|
|
|
// test remove joint
|
|
EXPECT_TRUE(model->RemoveJoint(name));
|
|
joint = model->GetJoint(name);
|
|
EXPECT_TRUE(joint == NULL);
|
|
|
|
// test that removing a non-existent joint will not break gazebo
|
|
EXPECT_FALSE(model->RemoveJoint("this_joint_doees_not_exist"));
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
TEST_F(JointTest, joint_SDF14)
|
|
{
|
|
Load("worlds/SDF_1_4.world");
|
|
|
|
physics::WorldPtr world = physics::get_world("default");
|
|
ASSERT_TRUE(world != NULL);
|
|
|
|
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
|
|
ASSERT_TRUE(physics != NULL);
|
|
|
|
int i = 0;
|
|
while (!this->HasEntity("joint14_model") && i < 20)
|
|
{
|
|
common::Time::MSleep(100);
|
|
++i;
|
|
}
|
|
|
|
if (i > 20)
|
|
gzthrow("Unable to get joint14_model");
|
|
|
|
physics::PhysicsEnginePtr physicsEngine = world->GetPhysicsEngine();
|
|
EXPECT_TRUE(physicsEngine != NULL);
|
|
physics::ModelPtr model = world->GetModel("joint14_model");
|
|
EXPECT_TRUE(model != NULL);
|
|
physics::LinkPtr link1 = model->GetLink("body1");
|
|
EXPECT_TRUE(link1 != NULL);
|
|
physics::LinkPtr link2 = model->GetLink("body2");
|
|
EXPECT_TRUE(link2 != NULL);
|
|
|
|
EXPECT_EQ(model->GetJointCount(), 1u);
|
|
physics::JointPtr joint = model->GetJoint("joint14_revolute_joint");
|
|
EXPECT_TRUE(joint != NULL);
|
|
|
|
physics::LinkPtr parent = joint->GetParent();
|
|
EXPECT_TRUE(parent != NULL);
|
|
physics::LinkPtr child = joint->GetChild();
|
|
EXPECT_TRUE(child != NULL);
|
|
EXPECT_EQ(parent->GetName(), "body2");
|
|
EXPECT_EQ(child->GetName(), "body1");
|
|
}
|
|
|
|
TEST_P(JointTest, JointCreationDestructionTest)
|
|
{
|
|
JointCreationDestructionTest(this->physicsEngine);
|
|
}
|
|
|
|
TEST_P(JointTest, GetInertiaRatio)
|
|
{
|
|
GetInertiaRatio(this->physicsEngine);
|
|
}
|
|
|
|
TEST_P(JointTest, SpringDamperTest)
|
|
{
|
|
SpringDamperTest(this->physicsEngine);
|
|
}
|
|
|
|
TEST_P(JointTest, DynamicJointVisualization)
|
|
{
|
|
DynamicJointVisualization(this->physicsEngine);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(PhysicsEngines, JointTest,
|
|
::testing::Combine(PHYSICS_ENGINE_VALUES,
|
|
::testing::Values("")));
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|