394 lines
11 KiB
C++
394 lines
11 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.
|
|
*
|
|
*/
|
|
/* Desc: Heightmap shape
|
|
* Author: Nate Koenig, Andrew Howard
|
|
* Date: 8 May 2003
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
// Ensure that Winsock2.h is included before Windows.h, which can get
|
|
// pulled in by anybody (e.g., Boost).
|
|
#include <Winsock2.h>
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <ignition/math/Helpers.hh>
|
|
#include <gazebo/gazebo_config.h>
|
|
|
|
#include "gazebo/common/Assert.hh"
|
|
#include "gazebo/common/Console.hh"
|
|
#include "gazebo/common/Image.hh"
|
|
#include "gazebo/common/CommonIface.hh"
|
|
#include "gazebo/common/SphericalCoordinates.hh"
|
|
#include "gazebo/math/gzmath.hh"
|
|
#include "gazebo/physics/HeightmapShape.hh"
|
|
#include "gazebo/physics/World.hh"
|
|
#include "gazebo/transport/transport.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace physics;
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
HeightmapShape::HeightmapShape(CollisionPtr _parent)
|
|
: Shape(_parent)
|
|
{
|
|
this->vertSize = 0;
|
|
this->AddType(Base::HEIGHTMAP_SHAPE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
HeightmapShape::~HeightmapShape()
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::OnRequest(ConstRequestPtr &_msg)
|
|
{
|
|
if (_msg->request() == "heightmap_data")
|
|
{
|
|
msgs::Geometry msg;
|
|
|
|
msgs::Response response;
|
|
response.set_id(_msg->id());
|
|
response.set_request(_msg->request());
|
|
response.set_response("success");
|
|
|
|
this->FillMsg(msg);
|
|
this->FillHeights(msg);
|
|
|
|
response.set_type(msg.GetTypeName());
|
|
std::string *serializedData = response.mutable_serialized_data();
|
|
msg.SerializeToString(serializedData);
|
|
|
|
this->responsePub->Publish(response);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
int HeightmapShape::LoadTerrainFile(const std::string &_filename)
|
|
{
|
|
this->heightmapData = common::HeightmapDataLoader::LoadTerrainFile(_filename);
|
|
if (!this->heightmapData)
|
|
{
|
|
gzerr << "Unable to load heightmap data" << std::endl;
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_GDAL
|
|
// DEM
|
|
auto demData = dynamic_cast<common::Dem *>(this->heightmapData);
|
|
if (demData)
|
|
{
|
|
this->dem = *demData;
|
|
if (this->sdf->HasElement("size"))
|
|
{
|
|
this->heightmapSize = this->sdf->Get<math::Vector3>("size");
|
|
}
|
|
else
|
|
{
|
|
this->heightmapSize.x = this->dem.GetWorldWidth();
|
|
this->heightmapSize.y = this->dem.GetWorldHeight();
|
|
this->heightmapSize.z = this->dem.GetMaxElevation() -
|
|
this->dem.GetMinElevation();
|
|
}
|
|
|
|
// Modify the reference geotedic latitude/longitude.
|
|
// A GPS sensor will use the real georeferenced coordinates of the terrain.
|
|
common::SphericalCoordinatesPtr sphericalCoordinates;
|
|
sphericalCoordinates = this->world->GetSphericalCoordinates();
|
|
|
|
if (sphericalCoordinates)
|
|
{
|
|
ignition::math::Angle latitude, longitude;
|
|
double elevation;
|
|
|
|
this->dem.GetGeoReferenceOrigin(latitude, longitude);
|
|
elevation = this->dem.GetElevation(0.0, 0.0);
|
|
|
|
sphericalCoordinates->SetLatitudeReference(latitude);
|
|
sphericalCoordinates->SetLongitudeReference(longitude);
|
|
sphericalCoordinates->SetElevationReference(elevation);
|
|
sphericalCoordinates.reset();
|
|
}
|
|
else
|
|
gzerr << "Unable to get a valid SphericalCoordinates pointer\n";
|
|
|
|
return 0;
|
|
}
|
|
// Image
|
|
else
|
|
#endif
|
|
{
|
|
auto imageData =
|
|
dynamic_cast<common::ImageHeightmap *>(this->heightmapData);
|
|
if (imageData)
|
|
{
|
|
this->img = *imageData;
|
|
this->heightmapSize = this->sdf->Get<math::Vector3>("size");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::Load(sdf::ElementPtr _sdf)
|
|
{
|
|
Base::Load(_sdf);
|
|
|
|
std::string filename = common::find_file(this->sdf->Get<std::string>("uri"));
|
|
if (filename.empty())
|
|
{
|
|
gzerr << "Unable to find heightmap[" +
|
|
this->sdf->Get<std::string>("uri") + "]\n";
|
|
return;
|
|
}
|
|
|
|
if (this->LoadTerrainFile(filename) != 0)
|
|
{
|
|
gzerr << "Heightmap data size must be square, with a size of 2^n+1\n";
|
|
return;
|
|
}
|
|
|
|
this->subSampling = 2u;
|
|
if (this->sdf->HasElement("sampling"))
|
|
{
|
|
unsigned int s = this->sdf->Get<unsigned int>("sampling");
|
|
if (s == 0u || s & (s - 1u))
|
|
{
|
|
gzerr << "Heightmap sampling value must be a power of 2. "
|
|
<< "The default value of 2 will be used instead." << std::endl;
|
|
this->subSampling = 2;
|
|
}
|
|
else
|
|
{
|
|
this->subSampling = static_cast<int>(s);
|
|
}
|
|
}
|
|
|
|
// Check if the geometry of the terrain data matches Ogre constrains
|
|
if (this->heightmapData->GetWidth() != this->heightmapData->GetHeight() ||
|
|
!ignition::math::isPowerOfTwo(this->heightmapData->GetWidth() - 1))
|
|
{
|
|
gzerr << "Heightmap data size must be square, with a size of 2^n+1\n";
|
|
return;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
int HeightmapShape::GetSubSampling() const
|
|
{
|
|
return this->subSampling;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::Init()
|
|
{
|
|
this->node = transport::NodePtr(new transport::Node());
|
|
this->node->Init();
|
|
|
|
this->requestSub = this->node->Subscribe("~/request",
|
|
&HeightmapShape::OnRequest, this, true);
|
|
this->responsePub = this->node->Advertise<msgs::Response>("~/response");
|
|
|
|
math::Vector3 terrainSize = this->GetSize();
|
|
|
|
// sampling size along image width and height
|
|
this->vertSize = (this->heightmapData->GetWidth() * this->subSampling)
|
|
- this->subSampling + 1;
|
|
this->scale.x = terrainSize.x / this->vertSize;
|
|
this->scale.y = terrainSize.y / this->vertSize;
|
|
|
|
// TODO add a virtual HeightmapData::GetMinElevation function to avoid the
|
|
// ifdef check. i.e. heightmapSizeZ = GetMaxElevation - GetMinElevation
|
|
double heightmapSizeZ = this->heightmapData->GetMaxElevation();
|
|
#ifdef HAVE_GDAL
|
|
// DEM
|
|
auto demData = dynamic_cast<common::Dem *>(this->heightmapData);
|
|
if (demData)
|
|
heightmapSizeZ = heightmapSizeZ - demData->GetMinElevation();
|
|
#endif
|
|
|
|
if (ignition::math::equal(heightmapSizeZ, 0.0))
|
|
this->scale.z = 1.0;
|
|
else
|
|
this->scale.z = fabs(terrainSize.z) / heightmapSizeZ;
|
|
|
|
// Step 1: Construct the heightmap lookup table
|
|
this->heightmapData->FillHeightMap(this->subSampling, this->vertSize,
|
|
this->GetSize().Ign(), this->scale.Ign(), this->flipY, this->heights);
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::SetScale(const math::Vector3 &_scale)
|
|
{
|
|
if (this->scale == _scale)
|
|
return;
|
|
|
|
this->scale = _scale;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
std::string HeightmapShape::GetURI() const
|
|
{
|
|
return this->sdf->Get<std::string>("uri");
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
math::Vector3 HeightmapShape::GetSize() const
|
|
{
|
|
return this->heightmapSize;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
math::Vector3 HeightmapShape::GetPos() const
|
|
{
|
|
return this->sdf->Get<math::Vector3>("pos");
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::FillMsg(msgs::Geometry &_msg)
|
|
{
|
|
_msg.set_type(msgs::Geometry::HEIGHTMAP);
|
|
|
|
_msg.mutable_heightmap()->set_width(this->vertSize);
|
|
_msg.mutable_heightmap()->set_height(this->vertSize);
|
|
|
|
msgs::Set(_msg.mutable_heightmap()->mutable_size(), this->GetSize().Ign());
|
|
msgs::Set(_msg.mutable_heightmap()->mutable_origin(), this->GetPos().Ign());
|
|
_msg.mutable_heightmap()->set_filename(this->GetURI());
|
|
_msg.mutable_heightmap()->set_sampling(
|
|
static_cast<unsigned int>(this->subSampling));
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::FillHeights(msgs::Geometry &_msg) const
|
|
{
|
|
for (unsigned int y = 0; y < this->vertSize; ++y)
|
|
{
|
|
for (unsigned int x = 0; x < this->vertSize; ++x)
|
|
{
|
|
int index = (this->vertSize - y - 1) * this->vertSize + x;
|
|
_msg.mutable_heightmap()->add_heights(this->heights[index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
void HeightmapShape::ProcessMsg(const msgs::Geometry & /*_msg*/)
|
|
{
|
|
gzerr << "TODO: not implement yet.";
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
math::Vector2i HeightmapShape::GetVertexCount() const
|
|
{
|
|
return math::Vector2i(this->vertSize, this->vertSize);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
float HeightmapShape::GetHeight(int _x, int _y) const
|
|
{
|
|
int index = _y * this->vertSize + _x;
|
|
if (_x < 0 || _y < 0 || index >= static_cast<int>(this->heights.size()))
|
|
return 0.0;
|
|
|
|
return this->heights[index];
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
float HeightmapShape::GetMaxHeight() const
|
|
{
|
|
float max = GZ_FLT_MIN;
|
|
for (unsigned int i = 0; i < this->heights.size(); ++i)
|
|
{
|
|
if (this->heights[i] > max)
|
|
max = this->heights[i];
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
float HeightmapShape::GetMinHeight() const
|
|
{
|
|
float min = GZ_FLT_MAX;
|
|
for (unsigned int i = 0; i < this->heights.size(); ++i)
|
|
{
|
|
if (this->heights[i] < min)
|
|
min = this->heights[i];
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
common::Image HeightmapShape::GetImage() const
|
|
{
|
|
double height = 0.0;
|
|
unsigned char *imageData = NULL;
|
|
|
|
/// \todo Support multiple terrain objects
|
|
double minHeight = this->GetMinHeight();
|
|
double maxHeight = this->GetMaxHeight() - minHeight;
|
|
|
|
int size = (this->vertSize+1) / this->subSampling;
|
|
|
|
// Create the image data buffer
|
|
imageData = new unsigned char[size * size];
|
|
|
|
// Get height data from all vertices
|
|
for (uint16_t y = 0; y < size; ++y)
|
|
{
|
|
for (uint16_t x = 0; x < size; ++x)
|
|
{
|
|
int sx = static_cast<int>(x * this->subSampling);
|
|
int sy;
|
|
|
|
if (!this->flipY)
|
|
sy = static_cast<int>(y * this->subSampling);
|
|
else
|
|
sy = static_cast<int>(size - 1 -y) * this->subSampling;
|
|
|
|
// Normalize height value
|
|
height = (this->GetHeight(sx, sy) - minHeight) / maxHeight;
|
|
|
|
GZ_ASSERT(height <= 1.0, "Normalized terrain height > 1.0");
|
|
GZ_ASSERT(height >= 0.0, "Normalized terrain height < 0.0");
|
|
|
|
// Scale height to a value between 0 and 255
|
|
imageData[y * size + x] = static_cast<unsigned char>(height * 255.0);
|
|
}
|
|
}
|
|
|
|
common::Image result;
|
|
result.SetFromData(imageData, size, size, common::Image::L_INT8);
|
|
|
|
delete [] imageData;
|
|
return result;
|
|
}
|
|
|
|
//////////////////////////////////////////////////
|
|
double HeightmapShape::ComputeVolume() const
|
|
{
|
|
return 0;
|
|
}
|