ppovb5fc7/gazebo/test/integration/physics.cc

1518 lines
49 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 <map>
#include <string>
#include <vector>
#include <ignition/math/Rand.hh>
#include "gazebo/test/ServerFixture.hh"
#include "gazebo/physics/physics.hh"
#include "SimplePendulumIntegrator.hh"
#include "gazebo/msgs/msgs.hh"
#include "gazebo/test/helper_physics_generator.hh"
#ifdef HAVE_BULLET
#include "gazebo/physics/bullet/bullet_math_inc.h"
#endif
#define PHYSICS_TOL 1e-2
using namespace gazebo;
class PhysicsTest : public ServerFixture,
public testing::WithParamInterface<const char*>
{
public: void InelasticCollision(const std::string &_physicsEngine);
public: void EmptyWorld(const std::string &_physicsEngine);
public: void SpawnDrop(const std::string &_physicsEngine);
public: void SpawnDropCoGOffset(const std::string &_physicsEngine);
public: void SphereAtlasLargeError(const std::string &_physicsEngine);
public: void CollisionFiltering(const std::string &_physicsEngine);
public: void JointDampingTest(const std::string &_physicsEngine);
public: void DropStuff(const std::string &_physicsEngine);
};
////////////////////////////////////////////////////////////////////////
// EmptyWorld:
// Load a world, take a few steps, and verify that time is increasing.
// This is the most basic physics engine test.
////////////////////////////////////////////////////////////////////////
void PhysicsTest::EmptyWorld(const std::string &_physicsEngine)
{
// Load an empty world
Load("worlds/empty.world", true, _physicsEngine);
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);
// simulate 1 step
world->Step(1);
double t = world->GetSimTime().Double();
// verify that time moves forward
EXPECT_GT(t, 0);
// simulate a few steps
int steps = 20;
world->Step(steps);
double dt = world->GetPhysicsEngine()->GetMaxStepSize();
EXPECT_GT(dt, 0);
t = world->GetSimTime().Double();
EXPECT_GT(t, 0.99*dt*static_cast<double>(steps+1));
}
TEST_P(PhysicsTest, EmptyWorld)
{
EmptyWorld(GetParam());
}
////////////////////////////////////////////////////////////////////////
// SpawnDrop:
// Load a world, check that gravity points along z axis, spawn simple
// shapes (box, sphere, cylinder), verify that they fall and hit the
// ground plane. The test currently assumes inelastic collisions.
////////////////////////////////////////////////////////////////////////
void PhysicsTest::SpawnDrop(const std::string &_physicsEngine)
{
// load an empty world
Load("worlds/empty.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
// check the gravity vector
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
ASSERT_TRUE(physics != NULL);
EXPECT_EQ(physics->GetType(), _physicsEngine);
math::Vector3 g = physics->GetGravity();
// Assume gravity vector points down z axis only.
EXPECT_EQ(g.x, 0);
EXPECT_EQ(g.y, 0);
EXPECT_LE(g.z, -9.8);
// get physics time step
double dt = physics->GetMaxStepSize();
EXPECT_GT(dt, 0);
// spawn some simple shapes and check to see that they start falling
double z0 = 3;
std::map<std::string, math::Vector3> modelPos;
modelPos["test_box"] = math::Vector3(0, 0, z0);
modelPos["test_sphere"] = math::Vector3(4, 0, z0);
modelPos["test_cylinder"] = math::Vector3(8, 0, z0);
modelPos["test_empty"] = math::Vector3(12, 0, z0);
modelPos["link_offset_box"] = math::Vector3(0, 0, z0);
// FIXME Trimesh drop test passes in bullet but fails in ode because
// the mesh bounces to the side when it hits the ground.
// See issue #513. Uncomment test when issue is resolved.
// modelPos["test_trimesh"] = math::Vector3(16, 0, z0);
SpawnBox("test_box", math::Vector3(1, 1, 1), modelPos["test_box"],
math::Vector3::Zero);
SpawnSphere("test_sphere", modelPos["test_sphere"], math::Vector3::Zero);
SpawnCylinder("test_cylinder", modelPos["test_cylinder"],
math::Vector3::Zero);
SpawnEmptyLink("test_empty", modelPos["test_empty"], math::Vector3::Zero);
std::ostringstream linkOffsetStream;
math::Pose linkOffsetPose1(0, 0, z0, 0, 0, 0);
math::Pose linkOffsetPose2(1000, 1000, 0, 0, 0, 0);
math::Vector3 linkOffsetSize(1, 1, 1);
linkOffsetStream << "<sdf version='" << SDF_VERSION << "'>"
<< "<model name ='link_offset_box'>"
<< "<pose>" << linkOffsetPose1 << "</pose>"
<< "<allow_auto_disable>false</allow_auto_disable>"
<< "<link name ='body'>"
<< " <pose>" << linkOffsetPose2 << "</pose>"
<< " <inertial>"
<< " <mass>4.0</mass>"
<< " <inertia>"
<< " <ixx>0.1667</ixx> <ixy>0.0</ixy> <ixz>0.0</ixz>"
<< " <iyy>0.1667</iyy> <iyz>0.0</iyz>"
<< " <izz>0.1667</izz>"
<< " </inertia>"
<< " </inertial>"
<< " <collision name ='geom'>"
<< " <geometry>"
<< " <box><size>" << linkOffsetSize << "</size></box>"
<< " </geometry>"
<< " </collision>"
<< " <visual name ='visual'>"
<< " <geometry>"
<< " <box><size>" << linkOffsetSize << "</size></box>"
<< " </geometry>"
<< " </visual>"
<< "</link>"
<< "</model>"
<< "</sdf>";
SpawnSDF(linkOffsetStream.str());
/// \TODO: bullet needs this to pass
if (physics->GetType() == "bullet")
physics->SetParam("iters", 300);
// std::string trimeshPath =
// "file://media/models/cube_20k/meshes/cube_20k.stl";
// SpawnTrimesh("test_trimesh", trimeshPath, math::Vector3(0.5, 0.5, 0.5),
// modelPos["test_trimesh"], math::Vector3::Zero);
int steps = 2;
physics::ModelPtr model;
math::Pose pose1, pose2;
math::Vector3 vel1, vel2;
double t, x0 = 0;
// This loop steps the world forward and makes sure that each model falls,
// expecting downward z velocity and decreasing z position.
for (std::map<std::string, math::Vector3>::iterator iter = modelPos.begin();
iter != modelPos.end(); ++iter)
{
std::string name = iter->first;
// Make sure the model is loaded
model = world->GetModel(name);
if (model != NULL)
{
gzdbg << "Check freefall of model " << name << '\n';
// Step once and check downward z velocity
world->Step(1);
vel1 = model->GetWorldLinearVel();
t = world->GetSimTime().Double();
EXPECT_EQ(vel1.x, 0);
EXPECT_EQ(vel1.y, 0);
EXPECT_NEAR(vel1.z, g.z*t, -g.z*t*PHYSICS_TOL);
// Need to step at least twice to check decreasing z position
world->Step(steps - 1);
pose1 = model->GetWorldPose();
x0 = modelPos[name].x;
EXPECT_EQ(pose1.pos.x, x0);
EXPECT_EQ(pose1.pos.y, 0);
EXPECT_NEAR(pose1.pos.z, z0 + g.z/2*t*t, (z0+g.z/2*t*t)*PHYSICS_TOL);
// Check once more and just make sure they keep falling
world->Step(steps);
vel2 = model->GetWorldLinearVel();
pose2 = model->GetWorldPose();
EXPECT_LT(vel2.z, vel1.z);
EXPECT_LT(pose2.pos.z, pose1.pos.z);
// if (physics->GetType() == "bullet")
// {
// gzerr << "m[" << model->GetName()
// << "] p[" << model->GetWorldPose()
// << "] v[" << model->GetWorldLinearVel()
// << "]\n";
// gzerr << "wait: ";
// getchar();
// }
}
else
{
gzerr << "Error loading model " << name << '\n';
EXPECT_TRUE(model != NULL);
}
}
// Predict time of contact with ground plane.
double tHit = sqrt(2*(z0-0.5) / (-g.z));
// Time to advance, allow 0.5 s settling time.
// This assumes inelastic collisions with the ground.
double dtHit = tHit+0.5 - world->GetSimTime().Double();
steps = ceil(dtHit / dt);
EXPECT_GT(steps, 0);
world->Step(steps);
// debug
// for (int i = 0; i < steps; ++i)
// {
// world->Step(1);
// if (physics->GetType() == "bullet")
// {
// model = world->GetModel("link_offset_box");
// gzerr << "m[" << model->GetName()
// << "] i[" << i << "/" << steps
// << "] pm[" << model->GetWorldPose()
// << "] pb[" << model->GetLink("body")->GetWorldPose()
// << "] v[" << model->GetWorldLinearVel()
// << "]\n";
// if (model->GetWorldPose().pos.z < 0.6)
// {
// gzerr << "wait: ";
// getchar();
// }
// }
// }
// This loop checks the velocity and pose of each model 0.5 seconds
// after the time of predicted ground contact. The velocity is expected
// to be small, and the pose is expected to be underneath the initial pose.
for (std::map<std::string, math::Vector3>::iterator iter = modelPos.begin();
iter != modelPos.end(); ++iter)
{
std::string name = iter->first;
// Make sure the model is loaded
model = world->GetModel(name);
if (model != NULL)
{
gzdbg << "Check ground contact of model " << name << '\n';
// Check that velocity is small
vel1 = model->GetWorldLinearVel();
t = world->GetSimTime().Double();
EXPECT_NEAR(vel1.x, 0, PHYSICS_TOL);
EXPECT_NEAR(vel1.y, 0, PHYSICS_TOL);
if (name == "test_empty")
EXPECT_NEAR(vel1.z, g.z*t, -g.z*t*PHYSICS_TOL);
else
EXPECT_NEAR(vel1.z, 0, PHYSICS_TOL);
// Check that model is resting on ground
pose1 = model->GetWorldPose();
x0 = modelPos[name].x;
double posTolerance = PHYSICS_TOL;
#ifdef HAVE_BULLET
if (_physicsEngine == "bullet" && sizeof(btScalar) == 4)
{
posTolerance *= 1400;
}
#endif
EXPECT_NEAR(pose1.pos.x, x0, posTolerance);
EXPECT_NEAR(pose1.pos.y, 0, posTolerance);
// debug
// if (physics->GetType() == "bullet")
// {
// gzerr << "m[" << model->GetName()
// << "] p[" << model->GetWorldPose()
// << "] v[" << model->GetWorldLinearVel()
// << "]\n";
// gzerr << "wait: ";
// getchar();
// }
if (name == "test_empty")
{
EXPECT_NEAR(pose1.pos.z, z0+g.z/2*t*t,
fabs((z0+g.z/2*t*t)*PHYSICS_TOL));
}
else
EXPECT_NEAR(pose1.pos.z, 0.5, PHYSICS_TOL);
}
else
{
gzerr << "Error loading model " << name << '\n';
EXPECT_TRUE(model != NULL);
}
}
// Compute and check link pose of link_offset_box
gzdbg << "Check link pose of link_offset_box\n";
model = world->GetModel("link_offset_box");
ASSERT_TRUE(model != NULL);
physics::LinkPtr link = model->GetLink();
ASSERT_TRUE(link != NULL);
// relative pose of link in linkOffsetPose2
for (int i = 0; i < 20; ++i)
{
pose1 = model->GetWorldPose();
pose2 = linkOffsetPose2 + pose1;
EXPECT_NEAR(pose2.pos.x, linkOffsetPose2.pos.x, PHYSICS_TOL);
EXPECT_NEAR(pose2.pos.y, linkOffsetPose2.pos.y, PHYSICS_TOL);
EXPECT_NEAR(pose2.pos.z, 0.5, PHYSICS_TOL);
world->Step(1);
}
}
TEST_P(PhysicsTest, SpawnDrop)
{
SpawnDrop(GetParam());
}
////////////////////////////////////////////////////////////////////////
// SpawnDropCoGOffset:
// Load a world, check that gravity points along z axis, spawn several
// spheres of varying radii and center of gravity (cg) location.
// sphere1: smaller radius, centered cg
// sphere2: larger radius, centered cg
// sphere3: larger radius, lowered cg
// sphere4: larger radius, raised cg
// sphere5: larger radius, y offset cg
// sphere6: larger radius, x offset cg
// sphere7: larger radius, 45 deg offset cg
// sphere8: larger radius, -30 deg offset cg
// The bottom of each sphere is at the same height, and it is verified
// that they hit the ground at the same time. Also, sphere5 should start
// rolling to the side when it hits the ground.
////////////////////////////////////////////////////////////////////////
void PhysicsTest::SpawnDropCoGOffset(const std::string &_physicsEngine)
{
if (_physicsEngine == "dart")
{
gzerr << "Skipping SpawnDropCoGOffset for physics engine ["
<< _physicsEngine
<< "] due to issue #1209.\n";
return;
}
// load an empty world
Load("worlds/empty.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
// check the gravity vector
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
ASSERT_TRUE(physics != NULL);
EXPECT_EQ(physics->GetType(), _physicsEngine);
math::Vector3 g = physics->GetGravity();
// Assume gravity vector points down z axis only.
EXPECT_EQ(g.x, 0);
EXPECT_EQ(g.y, 0);
EXPECT_LT(g.z, 0);
// get physics time step
double dt = physics->GetMaxStepSize();
EXPECT_GT(dt, 0);
// spawn some spheres and check to see that they start falling
double z0 = 3;
double r1 = 0.5, r2 = 1.5;
math::Vector3 v30 = math::Vector3::Zero;
math::Vector3 cog;
math::Angle angle;
std::vector<std::string> modelNames;
std::vector<double> x0s;
std::vector<double> y0s;
std::vector<double> radii;
std::vector<math::Vector3> cogs;
// sphere1 and sphere2 have c.g. at center of sphere, different sizes
modelNames.push_back("small_centered_sphere");
x0s.push_back(0);
y0s.push_back(0);
radii.push_back(r1);
cogs.push_back(v30);
modelNames.push_back("large_centered_sphere");
x0s.push_back(4);
y0s.push_back(0);
radii.push_back(r2);
cogs.push_back(v30);
// sphere3 has c.g. below the center
modelNames.push_back("lowered_cog_sphere");
x0s.push_back(8);
y0s.push_back(0);
radii.push_back(r2);
cogs.push_back(math::Vector3(0, 0, -r1));
// sphere4 has c.g. above the center
modelNames.push_back("raised_cog_sphere");
x0s.push_back(-4);
y0s.push_back(0);
radii.push_back(r2);
cogs.push_back(math::Vector3(0, 0, r1));
// sphere5 has c.g. to the side along y axis; it will roll
modelNames.push_back("cog_y_offset_sphere");
x0s.push_back(-8);
y0s.push_back(0);
radii.push_back(r2);
cogs.push_back(math::Vector3(0, r1, 0));
// sphere6 has c.g. to the side along x axis; it will roll
modelNames.push_back("cog_x_offset_sphere");
x0s.push_back(15);
y0s.push_back(0);
radii.push_back(r2);
cogs.push_back(math::Vector3(r1, 0, 0));
// sphere7 has c.g. to the side diagonally; it will roll
modelNames.push_back("cog_xy_45deg_offset_sphere");
x0s.push_back(0);
y0s.push_back(8);
radii.push_back(r2);
angle.SetFromDegree(45);
cogs.push_back(math::Vector3(r1*cos(angle.Radian()),
r1*sin(angle.Radian()), 0));
// sphere8 has c.g. to the side diagonally; it will roll
modelNames.push_back("cog_xy_-30deg_offset_sphere");
x0s.push_back(0);
y0s.push_back(-8);
radii.push_back(r2);
angle.SetFromDegree(-30);
cogs.push_back(math::Vector3(r1*cos(angle.Radian()),
r1*sin(angle.Radian()), 0));
unsigned int i;
for (i = 0; i < modelNames.size(); ++i)
{
SpawnSphere(modelNames[i], math::Vector3(x0s[i], y0s[i], z0+radii[i]),
v30, cogs[i], radii[i]);
}
int steps = 2;
physics::ModelPtr model;
math::Pose pose1, pose2;
math::Vector3 vel1, vel2;
double t, x0 = 0, y0 = 0, radius;
// This loop steps the world forward and makes sure that each model falls,
// expecting downward z velocity and decreasing z position.
for (i = 0; i < modelNames.size(); ++i)
{
// Make sure the model is loaded
model = world->GetModel(modelNames[i]);
x0 = x0s[i];
y0 = y0s[i];
radius = radii[i];
if (model != NULL)
{
gzdbg << "Check freefall of model " << modelNames[i] << '\n';
// Step once and check downward z velocity
world->Step(1);
vel1 = model->GetWorldLinearVel();
t = world->GetSimTime().Double();
EXPECT_NEAR(vel1.x, 0, 1e-16);
EXPECT_NEAR(vel1.y, 0, 1e-16);
EXPECT_NEAR(vel1.z, g.z*t, -g.z*t*PHYSICS_TOL);
// Need to step at least twice to check decreasing z position
world->Step(steps - 1);
pose1 = model->GetWorldPose();
EXPECT_NEAR(pose1.pos.x, x0, PHYSICS_TOL*PHYSICS_TOL);
EXPECT_NEAR(pose1.pos.y, y0, PHYSICS_TOL*PHYSICS_TOL);
EXPECT_NEAR(pose1.pos.z, z0+radius + g.z/2*t*t,
(z0+radius+g.z/2*t*t)*PHYSICS_TOL);
// Check once more and just make sure they keep falling
world->Step(steps);
vel2 = model->GetWorldLinearVel();
pose2 = model->GetWorldPose();
EXPECT_LT(vel2.z, vel1.z);
EXPECT_LT(pose2.pos.z, pose1.pos.z);
}
else
{
gzerr << "Error loading model " << modelNames[i] << '\n';
EXPECT_TRUE(model != NULL);
}
}
// Predict time of contact with ground plane.
double tHit = sqrt(2*(z0-0.5) / (-g.z));
// Time to advance, allow 0.5 s settling time.
// This assumes inelastic collisions with the ground.
double dtHit = tHit+0.5 - world->GetSimTime().Double();
steps = ceil(dtHit / dt);
EXPECT_GT(steps, 0);
world->Step(steps);
// This loop checks the velocity and pose of each model 0.5 seconds
// after the time of predicted ground contact. Except for sphere5,
// the velocity is expected to be small, and the pose is expected
// to be underneath the initial pose. sphere5 is expected to be rolling.
for (i = 0; i < modelNames.size(); ++i)
{
// Make sure the model is loaded
model = world->GetModel(modelNames[i]);
x0 = x0s[i];
y0 = y0s[i];
radius = radii[i];
cog = cogs[i];
if (model != NULL)
{
gzdbg << "Check ground contact and roll without slip of model "
<< modelNames[i] << '\n';
// Check that velocity is small
vel1 = model->GetWorldLinearVel();
vel2 = model->GetWorldAngularVel();
// vertical component of linear and angular velocity should be small
EXPECT_NEAR(vel1.z, 0, PHYSICS_TOL);
EXPECT_NEAR(vel2.z, 0, PHYSICS_TOL);
// expect small values for directions with no offset
if (cog.x == 0)
{
EXPECT_NEAR(vel1.x, 0, PHYSICS_TOL);
EXPECT_NEAR(vel2.y, 0, PHYSICS_TOL);
}
// expect rolling in direction of cog offset
else
{
EXPECT_GT(vel1.x*cog.x, 0.2*cog.x*cog.x);
EXPECT_GT(vel2.y*cog.x, 0.2*cog.x*cog.x);
}
if (cog.y == 0)
{
EXPECT_NEAR(vel1.y, 0, PHYSICS_TOL);
EXPECT_NEAR(vel2.x, 0, PHYSICS_TOL);
}
else
{
EXPECT_GT(vel1.y*cog.y, 0.2*cog.y*cog.y);
EXPECT_LT(vel2.x*cog.y, -0.2*cog.y*cog.y);
}
// Expect roll without slip
EXPECT_NEAR(vel1.x, vel2.y*radius, PHYSICS_TOL);
EXPECT_NEAR(vel1.y, -vel2.x*radius, PHYSICS_TOL);
// Use GetWorldLinearVel with global offset to check roll without slip
// Expect small linear velocity at contact point
math::Vector3 vel3 = model->GetLink()->GetWorldLinearVel(
math::Vector3(0, 0, -radius), math::Quaternion(0, 0, 0));
EXPECT_NEAR(vel3.x, 0, PHYSICS_TOL);
EXPECT_NEAR(vel3.y, 0, PHYSICS_TOL);
EXPECT_NEAR(vel3.z, 0, PHYSICS_TOL);
// Expect speed at top of sphere to be double the speed at center
math::Vector3 vel4 = model->GetLink()->GetWorldLinearVel(
math::Vector3(0, 0, radius), math::Quaternion(0, 0, 0));
EXPECT_NEAR(vel4.y, 2*vel1.y, PHYSICS_TOL);
EXPECT_NEAR(vel4.x, 2*vel1.x, PHYSICS_TOL);
EXPECT_NEAR(vel4.z, 0, PHYSICS_TOL);
// Check that model is resting on ground
pose1 = model->GetWorldPose();
EXPECT_NEAR(pose1.pos.z, radius, PHYSICS_TOL);
// expect no pose change for directions with no offset
if (cog.x == 0)
{
EXPECT_NEAR(pose1.pos.x, x0, PHYSICS_TOL);
}
// expect rolling in direction of cog offset
else
{
EXPECT_GT((pose1.pos.x-x0) * cog.x, cog.x * cog.x);
}
// expect no pose change for directions with no offset
if (cog.y == 0)
{
EXPECT_NEAR(pose1.pos.y, y0, PHYSICS_TOL);
}
// expect rolling in direction of cog offset
else
{
EXPECT_GT((pose1.pos.y-y0) * cog.y, cog.y * cog.y);
}
}
else
{
gzerr << "Error loading model " << modelNames[i] << '\n';
EXPECT_TRUE(model != NULL);
}
}
}
TEST_P(PhysicsTest, SpawnDropCoGOffset)
{
SpawnDropCoGOffset(GetParam());
}
//////////////////////////////////////////////////
TEST_F(PhysicsTest, EmptyStates)
{
this->Load("worlds/empty.world");
auto world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
// Current world state
physics::WorldState worldState(world);
auto modelStates = worldState.GetModelStates();
EXPECT_EQ(1u, worldState.GetModelStateCount());
EXPECT_EQ(1u, modelStates.size());
// Model state
auto modelState = modelStates["box"];
EXPECT_EQ(ignition::math::Pose3d::Zero, modelState.GetPose().Ign());
}
//////////////////////////////////////////////////
TEST_F(PhysicsTest, StateChange)
{
this->Load("worlds/shapes.world");
auto world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
// Check initial world state
physics::WorldState oldWorldState(world);
EXPECT_EQ(4u, oldWorldState.GetModelStateCount());
auto oldModelState = oldWorldState.GetModelState("box");
ignition::math::Pose3d oldPose(0, 0, 0.5, 0, 0, 0);
EXPECT_EQ(oldPose, oldModelState.GetPose().Ign());
// Move the box
world->SetPaused(true);
ignition::math::Pose3d newPose(1, 2, 0.5, 0, 0, 0);
world->GetModel("box")->SetWorldPose(newPose);
world->Step(1);
// Make sure the box state reflects the move
physics::WorldState newWorldState(world);
EXPECT_EQ(4u, newWorldState.GetModelStateCount());
auto newModelState = newWorldState.GetModelState("box");
EXPECT_EQ(newPose, newModelState.GetPose().Ign());
// Reset world state, and check for correctness
world->SetState(oldWorldState);
physics::WorldState resetWorldState(world);
EXPECT_EQ(4u, resetWorldState.GetModelStateCount());
auto resetModelState = resetWorldState.GetModelState("box");
EXPECT_EQ(oldPose, resetModelState.GetPose().Ign());
}
//////////////////////////////////////////////////
TEST_F(PhysicsTest, StateInsertion)
{
this->Load("worlds/empty.world");
auto world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
std::string newModelName("new_model");
std::string newLightName("new_light");
// Check initial count
EXPECT_EQ(1u, world->GetModelCount());
EXPECT_EQ(1u, world->LightCount());
EXPECT_TRUE(world->GetModel(newModelName) == NULL);
EXPECT_TRUE(world->Light(newLightName) == NULL);
// Current world state
physics::WorldState worldState(world);
EXPECT_EQ(1u, worldState.GetModelStateCount());
EXPECT_EQ(1u, worldState.LightStateCount());
// Insertions
std::stringstream newModelStr;
newModelStr << "<model name ='" << newModelName << "'>"
<< "<link name ='link'>"
<< " <collision name ='collision'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </collision>"
<< " <visual name ='visual'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </visual>"
<< "</link>"
<< "</model>";
std::stringstream newModelSDFStr;
newModelSDFStr << "<sdf version='" << SDF_VERSION << "'>"
<< "<model name ='" << newModelName << "_SDF'>"
<< "<link name ='link'>"
<< " <collision name ='collision'>"
<< " <geometry>"
<< " <box><size>2 2 2</size></box>"
<< " </geometry>"
<< " </collision>"
<< " <visual name ='visual'>"
<< " <geometry>"
<< " <box><size>2 2 2</size></box>"
<< " </geometry>"
<< " </visual>"
<< "</link>"
<< "</model>"
<< "</sdf>";
std::stringstream newLightStr;
newLightStr << "<light name ='" << newLightName << "' type='spot'>"
<< "<diffuse>0 1 0 1</diffuse>"
<< "</light>";
std::stringstream newLightSDFStr;
newLightSDFStr << "<sdf version='" << SDF_VERSION << "'>"
<< "<light name ='" << newLightName << "_SDF' type='point'>"
<< "<diffuse>0 1 0 1</diffuse>"
<< "</light>"
<< "</sdf>";
std::vector<std::string> insertions;
insertions.push_back(newModelStr.str());
insertions.push_back(newModelSDFStr.str());
insertions.push_back(newLightStr.str());
insertions.push_back(newLightSDFStr.str());
worldState.SetInsertions(insertions);
// Set state which includes insertion
world->SetPaused(true);
world->SetState(worldState);
world->Step(1);
// Check entities were inserted
EXPECT_EQ(3u, world->GetModelCount());
EXPECT_EQ(3u, world->LightCount());
EXPECT_NE(nullptr, world->GetModel(newModelName));
EXPECT_NE(nullptr, world->GetModel(newModelName + "_SDF"));
EXPECT_NE(nullptr, world->Light(newLightName));
EXPECT_NE(nullptr, world->Light(newLightName + "_SDF"));
// New world state
physics::WorldState newWorldState(world);
EXPECT_EQ(3u, newWorldState.GetModelStateCount());
EXPECT_EQ(3u, newWorldState.LightStateCount());
}
//////////////////////////////////////////////////
TEST_F(PhysicsTest, StateDeletion)
{
this->Load("worlds/shapes.world");
auto world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
std::string deleteModelName("box");
std::string deleteLightName("sun");
// Check initial count
EXPECT_EQ(4u, world->GetModelCount());
EXPECT_EQ(1u, world->LightCount());
EXPECT_FALSE(world->GetModel(deleteModelName) == NULL);
EXPECT_FALSE(world->Light(deleteLightName) == NULL);
// Current world state
physics::WorldState worldState(world);
EXPECT_EQ(4u, worldState.GetModelStateCount());
EXPECT_EQ(1u, worldState.LightStateCount());
// Deletions
std::vector<std::string> deletions;
deletions.push_back(deleteModelName);
deletions.push_back(deleteLightName);
worldState.SetDeletions(deletions);
// Set state which includes deletion
world->SetState(worldState);
gazebo::common::Time::MSleep(10);
// Check entities were deleted
EXPECT_EQ(3u, world->GetModelCount());
EXPECT_EQ(0u, world->LightCount());
EXPECT_TRUE(world->GetModel(deleteModelName) == NULL);
EXPECT_TRUE(world->Light(deleteLightName) == NULL);
// New world state
physics::WorldState newWorldState(world);
EXPECT_EQ(3u, newWorldState.GetModelStateCount());
EXPECT_EQ(0u, newWorldState.LightStateCount());
}
////////////////////////////////////////////////////////////////////////
void PhysicsTest::JointDampingTest(const std::string &_physicsEngine)
{
// Random seed is set to prevent brittle failures (gazebo issue #479)
ignition::math::Rand::Seed(18420503);
Load("worlds/damp_test.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
int i = 0;
while (!this->HasEntity("model_4_mass_1_ixx_1_damping_10") && i < 20)
{
common::Time::MSleep(100);
++i;
}
if (i > 20)
gzthrow("Unable to get model_4_mass_1_ixx_1_damping_10");
physics::ModelPtr model = world->GetModel("model_4_mass_1_ixx_1_damping_10");
EXPECT_TRUE(model != NULL);
{
// compare against recorded data only
double test_duration = 1.5;
double dt = world->GetPhysicsEngine()->GetMaxStepSize();
int steps = test_duration/dt;
for (int i = 0; i < steps; ++i)
{
world->Step(1); // theoretical contact, but
// gzdbg << "box time [" << world->GetSimTime().Double()
// << "] vel [" << model->GetWorldLinearVel()
// << "] pose [" << model->GetWorldPose()
// << "]\n";
}
EXPECT_EQ(world->GetSimTime().Double(), 1.5);
// This test expects a linear velocity at the CoG
math::Vector3 vel = model->GetLink()->GetWorldCoGLinearVel();
math::Pose pose = model->GetWorldPose();
EXPECT_EQ(vel.x, 0.0);
if (_physicsEngine == "dart")
{
// DART needs greater tolerance. The reason is not sure yet.
// Please see issue #904
EXPECT_NEAR(vel.y, -10.2009, 0.012);
EXPECT_NEAR(vel.z, -6.51755, 0.012);
}
else
{
EXPECT_NEAR(vel.y, -10.2009, PHYSICS_TOL);
EXPECT_NEAR(vel.z, -6.51755, PHYSICS_TOL);
}
EXPECT_DOUBLE_EQ(pose.pos.x, 3.0);
EXPECT_NEAR(pose.pos.y, 0.0, PHYSICS_TOL);
EXPECT_NEAR(pose.pos.z, 10.099, PHYSICS_TOL);
EXPECT_NEAR(pose.rot.GetAsEuler().x, 0.567334, PHYSICS_TOL);
EXPECT_DOUBLE_EQ(pose.rot.GetAsEuler().y, 0.0);
EXPECT_DOUBLE_EQ(pose.rot.GetAsEuler().z, 0.0);
}
}
TEST_P(PhysicsTest, JointDampingTest)
{
JointDampingTest(GetParam());
}
////////////////////////////////////////////////////////////////////////
void PhysicsTest::DropStuff(const std::string &_physicsEngine)
{
Load("worlds/drop_test.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
int i = 0;
while (!this->HasEntity("cylinder") && i < 20)
{
common::Time::MSleep(100);
++i;
}
if (i > 20)
gzthrow("Unable to get cylinder");
{
// todo: get parameters from drop_test.world
double test_duration = 1.5;
double z = 10.5;
double v = 0.0;
double g = -10.0;
double dt = world->GetPhysicsEngine()->GetMaxStepSize();
// world->Step(1428); // theoretical contact, but
// world->Step(100); // integration error requires few more steps
int steps = test_duration/dt;
bool post_contact_correction = false;
for (int i = 0; i < steps; ++i)
{
// integrate here to see when the collision should happen
v = v + dt * g;
z = z + dt * v;
world->Step(1); // theoretical contact, but
{
physics::ModelPtr box_model = world->GetModel("box");
if (box_model)
{
math::Vector3 vel = box_model->GetWorldLinearVel();
math::Pose pose = box_model->GetWorldPose();
// gzdbg << "box time [" << world->GetSimTime().Double()
// << "] sim z [" << pose.pos.z
// << "] exact z [" << z
// << "] sim vz [" << vel.z
// << "] exact vz [" << v << "]\n";
if (z > 0.5 || !post_contact_correction)
{
EXPECT_LT(fabs(vel.z - v) , 0.0001);
EXPECT_LT(fabs(pose.pos.z - z) , 0.0001);
}
else
{
EXPECT_LT(fabs(vel.z), 0.0101); // sometimes -0.01, why?
if (_physicsEngine == "dart")
{
// DART needs more tolerance until supports 'correction for
// penetration' feature.
// Please see issue #902
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.00410);
}
else
{
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.00001);
}
}
}
physics::ModelPtr sphere_model = world->GetModel("sphere");
if (sphere_model)
{
math::Vector3 vel = sphere_model->GetWorldLinearVel();
math::Pose pose = sphere_model->GetWorldPose();
// gzdbg << "sphere time [" << world->GetSimTime().Double()
// << "] sim z [" << pose.pos.z
// << "] exact z [" << z
// << "] sim vz [" << vel.z
// << "] exact vz [" << v << "]\n";
if (z > 0.5 || !post_contact_correction)
{
EXPECT_LT(fabs(vel.z - v), 0.0001);
EXPECT_LT(fabs(pose.pos.z - z), 0.0001);
}
else
{
if (_physicsEngine == "dart")
{
// DART needs more tolerance until supports 'correction for
// penetration' feature.
// Please see issue #902
EXPECT_LT(fabs(vel.z), 0.015);
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.00410);
}
else
{
EXPECT_LT(fabs(vel.z), 3e-5);
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.00001);
}
}
}
physics::ModelPtr cylinder_model = world->GetModel("cylinder");
if (cylinder_model)
{
math::Vector3 vel = cylinder_model->GetWorldLinearVel();
math::Pose pose = cylinder_model->GetWorldPose();
// gzdbg << "cylinder time [" << world->GetSimTime().Double()
// << "] sim z [" << pose.pos.z
// << "] exact z [" << z
// << "] sim vz [" << vel.z
// << "] exact vz [" << v << "]\n";
if (z > 0.5 || !post_contact_correction)
{
EXPECT_LT(fabs(vel.z - v), 0.0001);
EXPECT_LT(fabs(pose.pos.z - z), 0.0001);
}
else
{
EXPECT_LT(fabs(vel.z), 0.011);
if (_physicsEngine == "dart")
{
// DART needs more tolerance until supports 'correction for
// penetration' feature.
// Please see issue #902
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.0041);
}
else
{
EXPECT_LT(fabs(pose.pos.z - 0.5), 0.0001);
}
}
}
}
if (z < 0.5) post_contact_correction = true;
}
}
}
// This test doesn't pass yet in Bullet or Simbody
TEST_F(PhysicsTest, DropStuffODE)
{
DropStuff("ode");
}
#ifdef HAVE_DART
TEST_F(PhysicsTest, DropStuffDART)
{
DropStuff("dart");
}
#endif // HAVE_DART
////////////////////////////////////////////////////////////////////////
void PhysicsTest::InelasticCollision(const std::string &_physicsEngine)
{
// check conservation of mementum for linear inelastic collision
Load("worlds/collision_test.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
EXPECT_TRUE(world != NULL);
int i = 0;
while (!this->HasEntity("sphere") && i < 20)
{
common::Time::MSleep(100);
++i;
}
if (i > 20)
gzthrow("Unable to get sphere");
{
// todo: get parameters from drop_test.world
double test_duration = 1.1;
double dt = world->GetPhysicsEngine()->GetMaxStepSize();
physics::ModelPtr box_model = world->GetModel("box");
physics::LinkPtr box_link = box_model->GetLink("link");
double f = 1000.0;
double v = 0;
double x = 0;
double m = box_link->GetInertial()->GetMass();
int steps = test_duration/dt;
for (int i = 0; i < steps; ++i)
{
double t = world->GetSimTime().Double();
double velTolerance = PHYSICS_TOL;
#ifdef HAVE_BULLET
if (_physicsEngine == "bullet" && sizeof(btScalar) == 4)
{
velTolerance *= 11;
}
#endif
world->Step(1); // theoretical contact, but
if (box_model)
{
math::Vector3 vel = box_model->GetWorldLinearVel();
math::Pose pose = box_model->GetWorldPose();
// gzdbg << "box time [" << t
// << "] sim x [" << pose.pos.x
// << "] ideal x [" << x
// << "] sim vx [" << vel.x
// << "] ideal vx [" << v
// << "]\n";
if (i == 0)
{
box_model->GetLink("link")->SetForce(math::Vector3(f, 0, 0));
// The following has been failing since pull request #1284,
// so it has been disabled.
// See bitbucket.org/osrf/gazebo/issue/1394
// EXPECT_EQ(box_model->GetLink("link")->GetWorldForce(),
// math::Vector3(f, 0, 0));
}
if (t > 1.000 && t < 1.01)
{
// collision transition, do nothing
}
else
{
// collision happened
EXPECT_NEAR(pose.pos.x, x, PHYSICS_TOL);
EXPECT_NEAR(vel.x, v, velTolerance);
}
}
physics::ModelPtr sphere_model = world->GetModel("sphere");
if (sphere_model)
{
math::Vector3 vel = sphere_model->GetWorldLinearVel();
math::Pose pose = sphere_model->GetWorldPose();
// gzdbg << "sphere time [" << world->GetSimTime().Double()
// << "] sim x [" << pose.pos.x
// << "] ideal x [" << x
// << "] sim vx [" << vel.x
// << "] ideal vx [" << v
// << "]\n";
if (t > 1.000 && t < 1.01)
{
// collision transition, do nothing
}
else if (t <= 1.00)
{
// no collision
EXPECT_EQ(pose.pos.x, 2);
EXPECT_EQ(vel.x, 0);
}
else
{
// collision happened
EXPECT_NEAR(pose.pos.x, x + 1.0, PHYSICS_TOL);
EXPECT_NEAR(vel.x, v, velTolerance);
}
}
/*
// integrate here to see when the collision should happen
double impulse = dt*f;
if (i == 0) v = v + impulse;
else if (t >= 1.0) v = dt*f/ 2.0; // inelastic col. w/ eqal mass.
x = x + dt * v;
*/
// integrate here to see when the collision should happen
double vold = v;
if (i == 0)
v = vold + dt* (f / m);
else if (t >= 1.0)
v = dt*f/ 2.0; // inelastic col. w/ eqal mass.
x = x + dt * (v + vold) / 2.0;
}
}
}
TEST_P(PhysicsTest, InelasticCollision)
{
InelasticCollision(GetParam());
}
////////////////////////////////////////////////////////////////////////
// SphereAtlasLargeError:
// Check algorithm's ability to re-converge after a large LCP error is
// introduced.
// In this test, a model with similar dynamics properties to Atlas V3
// is pinned to the world by both feet. Robot is moved by a large
// distance, violating the joints between world and feet temporarily.
// Robot is then allowed to settle. Check to see that the LCP solution
// does not become unstable.
////////////////////////////////////////////////////////////////////////
void PhysicsTest::SphereAtlasLargeError(const std::string &_physicsEngine)
{
if (_physicsEngine != "ode")
{
gzerr << "Skipping SphereAtlasLargeError for physics engine ["
<< _physicsEngine
<< "] as this test only works for ODE for now.\n";
return;
}
Load("worlds/sphere_atlas_demo.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
physics::PhysicsEnginePtr physics = world->GetPhysicsEngine();
ASSERT_TRUE(physics != NULL);
EXPECT_EQ(physics->GetType(), _physicsEngine);
physics->SetGravity(math::Vector3(0, 0, 0));
int i = 0;
while (!this->HasEntity("sphere_atlas") && i < 20)
{
common::Time::MSleep(100);
++i;
}
if (i > 20)
gzthrow("Unable to get sphere_atlas");
physics::ModelPtr model = world->GetModel("sphere_atlas");
EXPECT_TRUE(model != NULL);
physics::LinkPtr head = model->GetLink("head");
EXPECT_TRUE(head != NULL);
{
gzdbg << "Testing large perturbation with PID controller active.\n";
// Test: With Robot PID controller active, introduce a large
// constraint error by breaking some model joints to the world
model->SetWorldPose(math::Pose(1000, 0, 0, 0, 0, 0));
// let model settle
world->Step(2000);
for (unsigned int n = 0; n < 10; ++n)
{
world->Step(1);
// manually check joint constraint violation for each joint
physics::Link_V links = model->GetLinks();
for (unsigned int i = 0; i < links.size(); ++i)
{
math::Pose childInWorld = links[i]->GetWorldPose();
physics::Joint_V parentJoints = links[i]->GetParentJoints();
for (unsigned int j = 0; j < parentJoints.size(); ++j)
{
// anchor position in world frame
math::Vector3 anchorPos = parentJoints[j]->GetAnchor(0);
// anchor pose in child link frame
math::Pose anchorInChild =
math::Pose(anchorPos, math::Quaternion()) - childInWorld;
// initial anchor pose in child link frame
math::Pose anchorInitialInChild =
parentJoints[j]->GetInitialAnchorPose();
physics::LinkPtr parent = parentJoints[j]->GetParent();
if (parent)
{
// compare everything in the parent frame
math::Pose childInitialInParent =
links[i]->GetInitialRelativePose() - // rel to model
parent->GetInitialRelativePose(); // rel to model
math::Pose parentInWorld = parent->GetWorldPose();
math::Pose childInParent = childInWorld - parentInWorld;
math::Pose anchorInParent = anchorInChild + childInParent;
math::Pose anchorInitialInParent =
anchorInitialInChild + childInitialInParent;
math::Pose jointError = anchorInParent - anchorInitialInParent;
// joint constraint violation must be less than...
EXPECT_LT(jointError.pos.GetSquaredLength(), PHYSICS_TOL);
// debug
if (jointError.pos.GetSquaredLength() >= PHYSICS_TOL)
gzdbg << "i [" << n
<< "] link [" << links[i]->GetName()
// << "] parent[" << parent->GetName()
<< "] error[" << jointError.pos.GetSquaredLength()
// << "] pose[" << childInWorld
<< "] anchor[" << anchorInChild
<< "] cinp[" << childInParent
<< "] ainp0[" << anchorInitialInParent
<< "] ainp[" << anchorInParent
<< "] diff[" << jointError
<< "]\n";
}
}
}
}
}
{
gzdbg << "Testing large perturbation with PID controller disabled.\n";
// Test: Turn off Robot PID controller, then introduce a large
// constraint error by breaking some model joints to the world
// special hook in SphereAtlasTestPlugin disconnects
// PID controller on Reset.
world->Reset();
world->Step(1);
model->SetWorldPose(math::Pose(1000, 0, 0, 0, 0, 0));
// let model settle
world->Step(2000);
for (unsigned int n = 0; n < 10; ++n)
{
world->Step(1);
// manually check joint constraint violation for each joint
physics::Link_V links = model->GetLinks();
for (unsigned int i = 0; i < links.size(); ++i)
{
math::Pose childInWorld = links[i]->GetWorldPose();
physics::Joint_V parentJoints = links[i]->GetParentJoints();
for (unsigned int j = 0; j < parentJoints.size(); ++j)
{
// anchor position in world frame
math::Vector3 anchorPos = parentJoints[j]->GetAnchor(0);
// anchor pose in child link frame
math::Pose anchorInChild =
math::Pose(anchorPos, math::Quaternion()) - childInWorld;
// initial anchor pose in child link frame
math::Pose anchorInitialInChild =
parentJoints[j]->GetInitialAnchorPose();
physics::LinkPtr parent = parentJoints[j]->GetParent();
if (parent)
{
// compare everything in the parent frame
math::Pose childInitialInParent =
links[i]->GetInitialRelativePose() - // rel to model
parent->GetInitialRelativePose(); // rel to model
math::Pose parentInWorld = parent->GetWorldPose();
math::Pose childInParent = childInWorld - parentInWorld;
math::Pose anchorInParent = anchorInChild + childInParent;
math::Pose anchorInitialInParent =
anchorInitialInChild + childInitialInParent;
math::Pose jointError = anchorInParent - anchorInitialInParent;
// joint constraint violation must be less than...
EXPECT_LT(jointError.pos.GetSquaredLength(), PHYSICS_TOL);
// debug
if (jointError.pos.GetSquaredLength() >= PHYSICS_TOL)
gzdbg << "i [" << n
<< "] link [" << links[i]->GetName()
// << "] parent[" << parent->GetName()
<< "] error[" << jointError.pos.GetSquaredLength()
// << "] pose[" << childInWorld
<< "] anchor[" << anchorInChild
<< "] cinp[" << childInParent
<< "] ainp0[" << anchorInitialInParent
<< "] ainp[" << anchorInParent
<< "] diff[" << jointError
<< "]\n";
}
}
}
}
}
}
TEST_P(PhysicsTest, SphereAtlasLargeError)
{
SphereAtlasLargeError(GetParam());
}
////////////////////////////////////////////////////////////////////////
// CollisionFiltering:
// Load a world, spawn a model with two overlapping links. By default,
// the links should not collide with each other as they have the same
// parent model. Check the x and y velocities to see if they are 0
////////////////////////////////////////////////////////////////////////
void PhysicsTest::CollisionFiltering(const std::string &_physicsEngine)
{
// load an empty world
Load("worlds/empty.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
std::stringstream newModelStr;
std::string modelName = "multiLinkModel";
math::Pose modelPose(0, 0, 2, 0, 0, 0);
math::Pose link01Pose(0, 0.1, 0, 0, 0, 0);
math::Pose link02Pose(0, -0.1, 0, 0, 0, 0);
// A model composed of two overlapping links at fixed y offset from origin
newModelStr << "<sdf version='" << SDF_VERSION << "'>"
<< "<model name ='" << modelName << "'>"
<< "<pose>" << modelPose.pos.x << " "
<< modelPose.pos.y << " "
<< modelPose.pos.z << " "
<< modelPose.rot.GetAsEuler().x << " "
<< modelPose.rot.GetAsEuler().y << " "
<< modelPose.rot.GetAsEuler().z << "</pose>"
<< "<link name ='link01'>"
<< " <pose>" << link01Pose.pos.x << " "
<< link01Pose.pos.y << " "
<< link01Pose.pos.z << " "
<< link01Pose.rot.GetAsEuler().x << " "
<< link01Pose.rot.GetAsEuler().y << " "
<< link01Pose.rot.GetAsEuler().z << "</pose>"
<< " <collision name ='geom'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </collision>"
<< " <visual name ='visual'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </visual>"
<< "</link>"
<< "<link name ='link02'>"
<< " <pose>" << link02Pose.pos.x << " "
<< link02Pose.pos.y << " "
<< link02Pose.pos.z << " "
<< link02Pose.rot.GetAsEuler().x << " "
<< link02Pose.rot.GetAsEuler().y << " "
<< link02Pose.rot.GetAsEuler().z << "</pose>"
<< " <collision name ='geom'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </collision>"
<< " <visual name ='visual'>"
<< " <geometry>"
<< " <box><size>1 1 1</size></box>"
<< " </geometry>"
<< " </visual>"
<< "</link>"
<< "</model>"
<< "</sdf>";
SpawnSDF(newModelStr.str());
// Wait for the entity to spawn
int i = 0;
while (!this->HasEntity(modelName) && i < 20)
{
common::Time::MSleep(100);
++i;
}
if (i > 20)
gzthrow("Unable to spawn model");
world->Step(5);
physics::ModelPtr model = world->GetModel(modelName);
math::Vector3 vel;
physics::Link_V links = model->GetLinks();
EXPECT_EQ(links.size(), 2u);
for (physics::Link_V::const_iterator iter = links.begin();
iter != links.end(); ++iter)
{
std::cout << "LinkName[" << (*iter)->GetScopedName() << "]\n";
// Links should not repel each other hence expecting zero x, y vel
vel = (*iter)->GetWorldLinearVel();
EXPECT_EQ(vel.x, 0);
EXPECT_EQ(vel.y, 0);
// Model should be falling
EXPECT_LT(vel.z, 0);
}
}
/////////////////////////////////////////////////
TEST_P(PhysicsTest, CollisionFiltering)
{
CollisionFiltering(GetParam());
}
/////////////////////////////////////////////////
// This test verifies that gazebo doesn't crash when collisions occur
// and the <world><physics><ode><max_contacts> value is zero.
// The crash was reported in issue #593 on bitbucket
TEST_F(PhysicsTest, ZeroMaxContactsODE)
{
// Load an empty world
Load("worlds/zero_max_contacts.world");
physics::WorldPtr world = physics::get_world("default");
ASSERT_TRUE(world != NULL);
physics::ModelPtr model = world->GetModel("ground_plane");
ASSERT_TRUE(model != NULL);
}
INSTANTIATE_TEST_CASE_P(PhysicsEngines, PhysicsTest, PHYSICS_ENGINE_VALUES);
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}