pxmlw6n2f/Gazebo_Distributed_TCP/test/integration/heightmap.cc

730 lines
21 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 <string.h>
#include <ignition/math/Vector3.hh>
#include "gazebo/common/SystemPaths.hh"
#include "gazebo/rendering/RenderingIface.hh"
#include "gazebo/rendering/Scene.hh"
#include "heights_cmp.h"
#include "gazebo/test/helper_physics_generator.hh"
#include "images_cmp.h"
#include "gazebo/test/ServerFixture.hh"
using namespace gazebo;
std::mutex mutex;
unsigned char* img = NULL;
/////////////////////////////////////////////////
void OnNewCameraFrame(int* _imageCounter, unsigned char* _imageDest,
const unsigned char *_image,
unsigned int _width, unsigned int _height,
unsigned int _depth,
const std::string &/*_format*/)
{
std::lock_guard<std::mutex> lock(mutex);
memcpy(_imageDest, _image, _width * _height * _depth);
*_imageCounter += 1;
}
class HeightmapTest : public ServerFixture,
public testing::WithParamInterface<const char*>
{
public: void PhysicsLoad(const std::string &_physicsEngine);
public: void WhiteAlpha(const std::string &_physicsEngine);
public: void WhiteNoAlpha(const std::string &_physicsEngine);
public: void Volume(const std::string &_physicsEngine);
public: void LoadDEM(const std::string &_physicsEngine);
public: void Material(const std::string &_worldName,
const std::string &_physicsEngine);
/// \brief Test loading a heightmap that has no visuals
public: void NoVisual();
/// \brief Test loading a heightmap with an LOD visual plugin
public: void LODVisualPlugin();
/// \brief Test loading a heightmap and verify cache files are created
public: void HeightmapCache();
public: void NotSquareImage();
public: void InvalidSizeImage();
// public: void Heights(const std::string &_physicsEngine);
};
/////////////////////////////////////////////////
void HeightmapTest::PhysicsLoad(const std::string &_physicsEngine)
{
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for dart, see issue #909" << std::endl;
return;
}
Load("worlds/heightmap_test.world", true, _physicsEngine);
// Make sure the render engine is available.
if (rendering::RenderEngine::Instance()->GetRenderPathType() ==
rendering::RenderEngine::NONE)
{
gzerr << "No rendering engine, unable to run heights test\n";
return;
}
physics::ModelPtr model = GetModel("heightmap");
EXPECT_TRUE(model != NULL);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(
collision->GetShape());
EXPECT_TRUE(shape != NULL);
EXPECT_TRUE(shape->HasType(physics::Base::HEIGHTMAP_SHAPE));
EXPECT_TRUE(shape->GetPos() == ignition::math::Vector3d(0, 0, 0));
EXPECT_TRUE(shape->GetSize() == ignition::math::Vector3d(129, 129, 10));
common::Image trueImage("media/materials/textures/heightmap_bowl.png");
common::Image testImage = shape->GetImage();
common::SystemPaths *paths = common::SystemPaths::Instance();
testImage.SavePNG(paths->GetTmpPath() + "/test_shape.png");
EXPECT_EQ(trueImage.GetWidth(), testImage.GetWidth());
EXPECT_EQ(trueImage.GetHeight(), testImage.GetHeight());
// Debug output
// Compare the true image to the image generated by the heightmap shape
// for (uint16_t y = 0; y < testImage.GetHeight(); y += 1.0)
// {
// for (uint16_t x = 0; x < testImage.GetWidth(); x += 1.0)
// {
// // EXPECT_NEAR(trueImage.GetPixel(x, y).r,
// // testImage.GetPixel(x, y).r, 0.008);
// // if (fabs(trueImage.GetPixel(x, y).r - testImage.GetPixel(x, y).r)
// // > 0.008)
// {
// printf("XY[%d %d] True[%f] Test[%f]\n", x, y,
// trueImage.GetPixel(x, y).r, testImage.GetPixel(x, y).r);
// }
// }
// }
}
/////////////////////////////////////////////////
void HeightmapTest::WhiteAlpha(const std::string &_physicsEngine)
{
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for dart, see issue #909" << std::endl;
return;
}
Load("worlds/white_alpha_heightmap.world", true, _physicsEngine);
physics::ModelPtr model = GetModel("heightmap");
EXPECT_TRUE(model != NULL);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(collision->GetShape());
EXPECT_TRUE(shape != NULL);
EXPECT_TRUE(shape->HasType(physics::Base::HEIGHTMAP_SHAPE));
int x, y;
for (y = 0; y < shape->GetVertexCount().y; ++y)
{
for (x = 0; x < shape->GetVertexCount().x; ++x)
{
EXPECT_NEAR(shape->GetHeight(x, y), 10.0, 1e-4);
}
}
}
/////////////////////////////////////////////////
void HeightmapTest::WhiteNoAlpha(const std::string &_physicsEngine)
{
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for dart, see issue #909" << std::endl;
return;
}
Load("worlds/white_no_alpha_heightmap.world", true, _physicsEngine);
physics::ModelPtr model = GetModel("heightmap");
EXPECT_TRUE(model != NULL);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(collision->GetShape());
EXPECT_TRUE(shape != NULL);
EXPECT_TRUE(shape->HasType(physics::Base::HEIGHTMAP_SHAPE));
int x, y;
for (y = 0; y < shape->GetVertexCount().y; ++y)
{
for (x = 0; x < shape->GetVertexCount().x; ++x)
{
EXPECT_EQ(shape->GetHeight(x, y), 10.0);
}
}
}
/////////////////////////////////////////////////
void HeightmapTest::NotSquareImage()
{
common::SystemPaths::Instance()->AddGazeboPaths(
TEST_INTEGRATION_PATH);
this->server = new Server();
this->server->PreLoad();
// EXPECT_THROW(this->server->LoadFile("worlds/not_square_heightmap.world"),
// common::Exception);
this->server->Fini();
delete this->server;
}
/////////////////////////////////////////////////
void HeightmapTest::InvalidSizeImage()
{
common::SystemPaths::Instance()->AddGazeboPaths(
TEST_INTEGRATION_PATH);
this->server = new Server();
this->server->PreLoad();
// EXPECT_THROW(this->server->LoadFile("worlds/invalid_size_heightmap.world"),
// common::Exception);
this->server->Fini();
delete this->server;
}
/////////////////////////////////////////////////
void HeightmapTest::Volume(const std::string &_physicsEngine)
{
if (_physicsEngine == "simbody")
{
// SimbodyHeightmapShape unimplemented. ComputeVolume actually returns 0 as
// an error code, which is the correct answer, but we'll skip it for now.
gzerr << "Aborting test for "
<< _physicsEngine
<< std::endl;
return;
}
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for "
<< _physicsEngine
<< ", see issue #909" << std::endl;
return;
}
Load("worlds/heightmap_test.world", true, _physicsEngine);
physics::ModelPtr model = GetModel("heightmap");
EXPECT_TRUE(model != NULL);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(
collision->GetShape());
EXPECT_DOUBLE_EQ(shape->ComputeVolume(), 0);
}
/////////////////////////////////////////////////
void HeightmapTest::LoadDEM(const std::string &_physicsEngine)
{
#ifdef HAVE_GDAL
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for dart, see issue #909" << std::endl;
return;
}
if (_physicsEngine == "bullet" || _physicsEngine == "simbody")
{
gzerr << "Aborting test for " << _physicsEngine <<
", negative elevations are not working yet." << std::endl;
return;
}
Load("worlds/dem_neg.world", true, _physicsEngine);
physics::WorldPtr world = physics::get_world("default");
ASSERT_NE(world, nullptr);
physics::ModelPtr boxModel = GetModel("box");
ASSERT_NE(boxModel, nullptr);
ignition::math::Pose3d boxInitPose(0, 0, -207, 0, 0, 0);
EXPECT_EQ(boxModel->GetWorldPose().Ign(), boxInitPose);
physics::ModelPtr model = GetModel("heightmap");
ASSERT_NE(model, nullptr);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(
collision->GetShape());
ASSERT_NE(shape, nullptr);
EXPECT_TRUE(shape->HasType(physics::Base::HEIGHTMAP_SHAPE));
EXPECT_TRUE(shape->GetPos() == ignition::math::Vector3d(0, 0, 0));
double maxHeight = shape->GetMaxHeight();
double minHeight = shape->GetMinHeight();
EXPECT_GE(maxHeight, minHeight);
EXPECT_GE(boxInitPose.Pos().Z(), minHeight);
// step the world
// let the box fall onto the heightmap and wait for it to rest
world->Step(1000);
ignition::math::Pose3d boxRestPose = boxModel->GetWorldPose().Ign();
EXPECT_NE(boxRestPose, boxInitPose);
EXPECT_GE(boxInitPose.Pos().Z(), minHeight);
// step the world and verify the box is at rest
world->Step(100);
ignition::math::Pose3d boxNewRestPose = boxModel->GetWorldPose().Ign();
EXPECT_EQ(boxNewRestPose, boxRestPose);
#else
// prevent unused variable warning
(void)(_physicsEngine);
#endif
}
/*
void HeightmapTest::Heights(const std::string &_physicsEngine)
{
Load("worlds/heightmap_test.world", _physicsEngine);
// Make sure the render engine is available.
if (rendering::RenderEngine::Instance()->GetRenderPathType() ==
rendering::RenderEngine::NONE)
{
gzerr << "No rendering engine, unable to run heights test\n";
return;
}
// Make sure we can get a valid pointer to the scene.
rendering::ScenePtr scene = GetScene();
ASSERT_TRUE(scene);
rendering::Heightmap *heightmap = NULL;
// Wait for the heightmap to get loaded by the scene.
{
int i = 0;
while (i < 20 && (heightmap = scene->GetHeightmap()) == NULL)
{
common::Time::MSleep(100);
i++;
}
if (i >= 20)
gzthrow("Unable to get heightmap");
}
physics::ModelPtr model = GetModel("heightmap");
EXPECT_TRUE(model);
physics::CollisionPtr collision =
model->GetLink("link")->GetCollision("collision");
physics::HeightmapShapePtr shape =
boost::dynamic_pointer_cast<physics::HeightmapShape>(collision->GetShape());
EXPECT_TRUE(shape);
EXPECT_TRUE(shape->HasType(physics::Base::HEIGHTMAP_SHAPE));
EXPECT_TRUE(shape->GetPos() == ignition::math::Vector3d(0, 0, 0));
EXPECT_TRUE(shape->GetSize() == ignition::math::Vector3d(129, 129, 10));
std::vector<float> physicsTest;
std::vector<float> renderTest;
float x, y;
for (y = 0; y < shape->GetSize().y && y < .3; y += 0.2)
{
for (x = 0; x < shape->GetSize().x && x < 1; x += 0.2)
{
// Compute the proper physics test point.
int xi = rint(x);
if (xi >= shape->GetSize().x)
xi = shape->GetSize().x - 1.0;
int yi = rint(y);
if (yi >= shape->GetSize().y)
yi = shape->GetSize().y - 1.0;
// Compute the proper render test point.
double xd = xi - (shape->GetSize().x) * 0.5;
double yd = (shape->GetSize().y) * 0.5 - yi;
// The shape->GetHeight function requires a point relative to the
// bottom left of the heightmap image
physicsTest.push_back(shape->GetHeight(xi, yi));
// The render test requires a point relative to the center of the
// heightmap.
renderTest.push_back(heightmap->GetHeight(xd, yd));
// Debug output
if (fabs(physicsTest.back() - renderTest.back()) >= 0.04)
{
std::cout << "Render XY[" << xd << " " << yd << "] Physics XY[" << xi
<< " " << yi << "] R[" << renderTest.back() << "] P["
<< physicsTest.back() << "] D["
<< fabs(renderTest.back() - physicsTest.back()) << "]\n";
}
// Test to see if the physics height is equal to the render engine
// height.
EXPECT_NEAR(physicsTest.back(), renderTest.back(), 0.04);
}
}
float diffMax, diffSum, diffAvg;
FloatCompare(&physicsTest[0], &renderTest[0], physicsTest.size(),
diffMax, diffSum, diffAvg);
EXPECT_LT(diffMax, 0.04);
EXPECT_LT(diffSum, 0.2);
EXPECT_LT(diffAvg, 0.02);
printf("Max[%f] Sum[%f] Avg[%f]\n", diffMax, diffSum, diffAvg);
// This will print the heights
// printf("static float __heights[] = {");
// unsigned int i=0;
// for (y = 0; y < shape->GetVertexCount().y; ++y)
// {
// for (x = 0; x < shape->GetVertexCount().x; ++x)
// {
// if (y == shape->GetVertexCount().y && x == shape->GetVertexCount().x)
// break;
// if (i % 7 == 0)
// printf("\n");
// else
// printf(" ");
// printf("%f,", shape->GetHeight(x, y));
// i++;
// }
// }
// printf(" %f};\nstatic float *heights = __heights;\n",
// shape->GetHeight(x,y));
}
*/
/////////////////////////////////////////////////
void HeightmapTest::Material(const std::string &_worldName,
const std::string &_physicsEngine)
{
if (_physicsEngine == "dart")
{
gzerr << "Aborting test for dart, see issue #909" << std::endl;
return;
}
// load a heightmap with red material
Load(_worldName, false, _physicsEngine);
physics::ModelPtr heightmap = GetModel("heightmap");
ASSERT_NE(heightmap, nullptr);
// spawn camera sensor to capture an image of heightmap
std::string modelName = "camera_model";
std::string cameraName = "camera_sensor";
unsigned int width = 320;
unsigned int height = 240;
double updateRate = 10;
ignition::math::Pose3d testPose(
ignition::math::Vector3d(0, 0, 100),
ignition::math::Quaterniond(0, 1.57, 0));
SpawnCamera(modelName, cameraName, testPose.Pos(),
testPose.Rot().Euler(), width, height, updateRate);
sensors::SensorPtr sensor = sensors::get_sensor(cameraName);
sensors::CameraSensorPtr camSensor =
std::dynamic_pointer_cast<sensors::CameraSensor>(sensor);
int imageCount = 0;
img = new unsigned char[width*height*3];
event::ConnectionPtr c =
camSensor->Camera()->ConnectNewImageFrame(
std::bind(&::OnNewCameraFrame, &imageCount, img,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4, std::placeholders::_5));
// grab some images
int sleep = 0;
int maxSleep = 500;
int total_images = 10;
while (imageCount < total_images && sleep++ < maxSleep )
common::Time::MSleep(10);
EXPECT_GE(imageCount, total_images);
camSensor->Camera()->DisconnectNewImageFrame(c);
unsigned int rSum = 0;
unsigned int gSum = 0;
unsigned int bSum = 0;
for (unsigned int i = 0; i < height*width*3; i+=3)
{
unsigned int r = img[i];
unsigned int g = img[i+1];
unsigned int b = img[i+2];
rSum += r;
gSum += g;
bSum += b;
}
// verify that red is the dominant color in the image
EXPECT_GT(rSum, gSum);
EXPECT_GT(rSum, bSum);
delete [] img;
}
/////////////////////////////////////////////////
void HeightmapTest::NoVisual()
{
// load a heightmap with no visual
Load("worlds/heightmap_no_visual.world", false);
physics::ModelPtr heightmap = GetModel("heightmap");
ASSERT_NE(heightmap, nullptr);
gazebo::rendering::ScenePtr scene = gazebo::rendering::get_scene("default");
ASSERT_NE(scene, nullptr);
// make sure scene is initialized and running
int sleep = 0;
int maxSleep = 30;
while (scene->SimTime().Double() < 2.0 && sleep++ < maxSleep)
common::Time::MSleep(100);
// no heightmaps should exist in the scene
EXPECT_EQ(scene->GetHeightmap(), nullptr);
}
/////////////////////////////////////////////////
void HeightmapTest::LODVisualPlugin()
{
// load a heightmap with no visual
Load("worlds/heightmap_lod_plugin.world", false);
physics::ModelPtr heightmap = GetModel("heightmap");
ASSERT_NE(heightmap, nullptr);
gazebo::rendering::ScenePtr scene = gazebo::rendering::get_scene("default");
ASSERT_NE(scene, nullptr);
// make sure scene is initialized and running
int sleep = 0;
int maxSleep = 30;
while (scene->SimTime().Double() < 2.0 && sleep++ < maxSleep)
common::Time::MSleep(100);
// check the heightmap lod via scene
EXPECT_EQ(scene->HeightmapLOD(), 5u);
// check skirt length param via scene
EXPECT_EQ(scene->HeightmapSkirtLength(), 0.5);
// get heightmap object and check lod params
rendering::Heightmap *h = scene->GetHeightmap();
EXPECT_NE(h, nullptr);
EXPECT_EQ(h->LOD(), 5u);
EXPECT_EQ(h->SkirtLength(), 0.5);
}
/////////////////////////////////////////////////
void HeightmapTest::HeightmapCache()
{
// path to heightmap cache files
std::string heightmapName = "heightmap_bowl";
std::string heightmapDir(common::SystemPaths::Instance()->GetLogPath()
+ "/paging");
std::string shaPath = heightmapDir + "/" + heightmapName + "/gzterrain.SHA1";
std::string cachePath = heightmapDir +
"/" + heightmapName + "/gazebo_terrain_00000000.dat";
// temporary backup files for testing if cache files exist.
std::string shaPathBk = shaPath + ".bk";
std::string cachePathBk = cachePath + ".bk";
if (common::exists(shaPath))
common::moveFile(shaPath, shaPathBk);
if (common::exists(cachePath))
common::moveFile(cachePath, cachePathBk);
// there should be no cache files
EXPECT_FALSE(common::exists(shaPath));
EXPECT_FALSE(common::exists(cachePath));
// load a heightmap
Load("worlds/heightmap_test.world", false);
physics::ModelPtr heightmap = this->GetModel("heightmap");
ASSERT_NE(heightmap, nullptr);
gazebo::rendering::ScenePtr scene = gazebo::rendering::get_scene("default");
ASSERT_NE(scene, nullptr);
// make sure scene is initialized and running
int sleep = 0;
int maxSleep = 30;
while (scene->SimTime().Double() < 2.0 && sleep++ < maxSleep)
common::Time::MSleep(100);
// make sure we have the heightmap object
rendering::Heightmap *h = scene->GetHeightmap();
EXPECT_NE(h, nullptr);
// verify new sha-1 file exists
EXPECT_TRUE(common::exists(shaPath));
EXPECT_TRUE(common::isFile(shaPath));
// wait for the terrain tile cache to be saved
sleep = 0;
while (!common::exists(cachePath) && sleep++ < maxSleep)
common::Time::MSleep(100);
// verify that terrain tile cache exists
EXPECT_TRUE(common::exists(cachePath));
EXPECT_TRUE(common::isFile(cachePath));
// clean up by moving old files back
if (common::exists(shaPathBk))
common::moveFile(shaPathBk, shaPath);
if (common::exists(cachePathBk))
common::moveFile(cachePathBk, cachePath);
EXPECT_FALSE(common::exists(shaPathBk));
EXPECT_FALSE(common::exists(cachePathBk));
}
/////////////////////////////////////////////////
TEST_F(HeightmapTest, NotSquareImage)
{
NotSquareImage();
}
/////////////////////////////////////////////////
TEST_F(HeightmapTest, InvalidSizeImage)
{
InvalidSizeImage();
}
/////////////////////////////////////////////////
TEST_P(HeightmapTest, PhysicsLoad)
{
PhysicsLoad(GetParam());
}
/////////////////////////////////////////////////
TEST_P(HeightmapTest, WhiteAlpha)
{
WhiteAlpha(GetParam());
}
/////////////////////////////////////////////////
TEST_P(HeightmapTest, WhiteNoAlpha)
{
WhiteNoAlpha(GetParam());
}
/////////////////////////////////////////////////
TEST_P(HeightmapTest, Volume)
{
Volume(GetParam());
}
/////////////////////////////////////////////////
TEST_P(HeightmapTest, LoadDEM)
{
LoadDEM(GetParam());
}
/////////////////////////////////////////////////
//
// Disabled: segfaults ocassionally
// See https://bitbucket.org/osrf/gazebo/issue/521 for details
/*
TEST_P(HeightmapTest, Heights)
{
Heights(GetParam());
}
*/
/////////////////////////////////////////////////
TEST_P(HeightmapTest, Material)
{
Material("worlds/heightmap_material.world", GetParam());
}
// This test fails on OSX
// It uses glsl 130 which is not supported yet
#ifndef __APPLE__
/////////////////////////////////////////////////
TEST_P(HeightmapTest, MaterialShader)
{
Material("worlds/heightmap_material_shader.world", GetParam());
}
#endif
/////////////////////////////////////////////////
TEST_F(HeightmapTest, NoVisual)
{
NoVisual();
}
/////////////////////////////////////////////////
TEST_F(HeightmapTest, LODVisualPlugin)
{
LODVisualPlugin();
}
/////////////////////////////////////////////////
TEST_F(HeightmapTest, HeightmapCache)
{
HeightmapCache();
}
INSTANTIATE_TEST_CASE_P(PhysicsEngines, HeightmapTest, PHYSICS_ENGINE_VALUES);
/////////////////////////////////////////////////
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}