477 lines
17 KiB
C++
477 lines
17 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 <boost/bind.hpp>
|
|
|
|
#include "gazebo/rendering/Heightmap.hh"
|
|
#include "gazebo/rendering/Scene.hh"
|
|
#include "gazebo/rendering/UserCamera.hh"
|
|
|
|
#include "gazebo/gui/GuiEvents.hh"
|
|
#include "gazebo/gui/GuiIface.hh"
|
|
#include "gazebo/gui/MouseEventHandler.hh"
|
|
#include "gazebo/gui/terrain/TerrainEditorPalette.hh"
|
|
#include "gazebo/gui/terrain/TerrainEditorPalettePrivate.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace gui;
|
|
|
|
/////////////////////////////////////////////////
|
|
TerrainEditorPalette::TerrainEditorPalette(QWidget *_parent)
|
|
: QWidget(_parent),
|
|
dataPtr(new TerrainEditorPalettePrivate())
|
|
{
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
|
|
// Create the button to raise terrain
|
|
QPushButton *raiseButton = new QPushButton("Raise", this);
|
|
raiseButton->setStatusTip(tr("Left-mouse press to raise terrain."));
|
|
raiseButton->setCheckable(true);
|
|
raiseButton->setChecked(false);
|
|
connect(raiseButton, SIGNAL(toggled(bool)), this, SLOT(OnRaise(bool)));
|
|
|
|
// Create the button to lower terrain
|
|
QPushButton *lowerButton = new QPushButton("Lower", this);
|
|
lowerButton->setStatusTip(tr("Left-mouse press to lower terrain."));
|
|
lowerButton->setCheckable(true);
|
|
lowerButton->setChecked(false);
|
|
connect(lowerButton, SIGNAL(toggled(bool)), this, SLOT(OnLower(bool)));
|
|
|
|
// Create the button to flatten terrain
|
|
QPushButton *flattenButton = new QPushButton("Flatten", this);
|
|
flattenButton->setStatusTip(tr("Left-mouse press to flatten terrain."));
|
|
flattenButton->setCheckable(true);
|
|
flattenButton->setChecked(false);
|
|
connect(flattenButton, SIGNAL(toggled(bool)), this, SLOT(OnFlatten(bool)));
|
|
|
|
// Create the button to roughen terrain
|
|
QPushButton *heightButton = new QPushButton("Pick Height", this);
|
|
heightButton->setStatusTip(
|
|
tr("Left-mouse press to select a terrain height."));
|
|
heightButton->setCheckable(true);
|
|
heightButton->setChecked(false);
|
|
connect(heightButton, SIGNAL(toggled(bool)), this, SLOT(OnPickHeight(bool)));
|
|
|
|
QButtonGroup *buttonGroup = new QButtonGroup;
|
|
buttonGroup->addButton(raiseButton);
|
|
buttonGroup->addButton(lowerButton);
|
|
buttonGroup->addButton(flattenButton);
|
|
buttonGroup->addButton(heightButton);
|
|
|
|
// Create the layout to hold all the buttons
|
|
QGridLayout *buttonLayout = new QGridLayout;
|
|
buttonLayout->addWidget(raiseButton, 0, 0);
|
|
buttonLayout->addWidget(lowerButton, 0, 1);
|
|
buttonLayout->addWidget(flattenButton, 1, 0);
|
|
buttonLayout->addWidget(heightButton, 1, 1);
|
|
|
|
// Add a save button
|
|
QPushButton *saveButton = new QPushButton("Save Image", this);
|
|
saveButton->setStatusTip(tr("Save terrain as a PNG."));
|
|
connect(saveButton, SIGNAL(clicked()), this, SLOT(OnSave()));
|
|
|
|
// Create a slider to control the outer size of the brush
|
|
this->dataPtr->outsideRadiusSlider = new QSlider(this);
|
|
this->dataPtr->outsideRadiusSlider->setRange(1, 100000);
|
|
this->dataPtr->outsideRadiusSlider->setTickInterval(1);
|
|
this->dataPtr->outsideRadiusSlider->setOrientation(Qt::Horizontal);
|
|
this->dataPtr->outsideRadiusSlider->setValue(10);
|
|
connect(this->dataPtr->outsideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnOutsideRadiusSlider(int)));
|
|
|
|
this->dataPtr->outsideRadiusSpin = new QDoubleSpinBox(this);
|
|
this->dataPtr->outsideRadiusSpin->setRange(0, 1.0);
|
|
this->dataPtr->outsideRadiusSpin->setSingleStep(0.001);
|
|
this->dataPtr->outsideRadiusSpin->setDecimals(3);
|
|
connect(this->dataPtr->outsideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnOutsideRadiusSpin(double)));
|
|
|
|
// Create a layout to hold the outer brush size slider and its label
|
|
QHBoxLayout *outsideRadiusSpinLayout = new QHBoxLayout;
|
|
outsideRadiusSpinLayout->addWidget(new QLabel(tr("Outside radius: ")));
|
|
outsideRadiusSpinLayout->addStretch(2);
|
|
outsideRadiusSpinLayout->addWidget(this->dataPtr->outsideRadiusSpin);
|
|
|
|
QVBoxLayout *outsideRadiusLayout = new QVBoxLayout;
|
|
outsideRadiusLayout->addLayout(outsideRadiusSpinLayout);
|
|
outsideRadiusLayout->addWidget(this->dataPtr->outsideRadiusSlider);
|
|
|
|
|
|
// Create a slider to control the inner size of the brush
|
|
this->dataPtr->insideRadiusSlider = new QSlider(this);
|
|
this->dataPtr->insideRadiusSlider->setRange(0, 100000);
|
|
this->dataPtr->insideRadiusSlider->setTickInterval(1);
|
|
this->dataPtr->insideRadiusSlider->setOrientation(Qt::Horizontal);
|
|
this->dataPtr->insideRadiusSlider->setValue(10);
|
|
connect(this->dataPtr->insideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnInsideRadiusSlider(int)));
|
|
|
|
this->dataPtr->insideRadiusSpin = new QDoubleSpinBox(this);
|
|
this->dataPtr->insideRadiusSpin->setRange(0, 1.0);
|
|
this->dataPtr->insideRadiusSpin->setSingleStep(0.001);
|
|
this->dataPtr->insideRadiusSpin->setDecimals(3);
|
|
connect(this->dataPtr->insideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnInsideRadiusSpin(double)));
|
|
|
|
// Create a layout to hold the inner brush size slider and its label
|
|
QHBoxLayout *insideRadiusSpinLayout = new QHBoxLayout;
|
|
insideRadiusSpinLayout->addWidget(new QLabel(tr("Inside radius: ")));
|
|
insideRadiusSpinLayout->addStretch(2);
|
|
insideRadiusSpinLayout->addWidget(this->dataPtr->insideRadiusSpin);
|
|
|
|
QVBoxLayout *insideRadiusLayout = new QVBoxLayout;
|
|
insideRadiusLayout->addLayout(insideRadiusSpinLayout);
|
|
insideRadiusLayout->addWidget(this->dataPtr->insideRadiusSlider);
|
|
|
|
// Create a slider to control the weight of the brush
|
|
this->dataPtr->weightSlider = new QSlider(this);
|
|
this->dataPtr->weightSlider->setRange(1, 10000);
|
|
this->dataPtr->weightSlider->setTickInterval(1);
|
|
this->dataPtr->weightSlider->setOrientation(Qt::Horizontal);
|
|
this->dataPtr->weightSlider->setValue(10);
|
|
connect(this->dataPtr->weightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnWeightSlider(int)));
|
|
|
|
this->dataPtr->weightSpin = new QDoubleSpinBox(this);
|
|
this->dataPtr->weightSpin->setRange(.01, 1.0);
|
|
this->dataPtr->weightSpin->setSingleStep(.1);
|
|
connect(this->dataPtr->weightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnWeightSpin(double)));
|
|
|
|
|
|
// Create a layout to hold the brush weight slider and its label
|
|
QHBoxLayout *weightSpinLayout = new QHBoxLayout;
|
|
weightSpinLayout->addWidget(new QLabel(tr("Weight: ")));
|
|
weightSpinLayout->addStretch(2);
|
|
weightSpinLayout->addWidget(this->dataPtr->weightSpin);
|
|
|
|
QVBoxLayout *weightLayout = new QVBoxLayout;
|
|
weightLayout->addLayout(weightSpinLayout);
|
|
weightLayout->addWidget(this->dataPtr->weightSlider);
|
|
|
|
|
|
// Create a slider to control the weight of the brush
|
|
this->dataPtr->heightSlider = new QSlider(this);
|
|
this->dataPtr->heightSlider->setRange(1, 100);
|
|
this->dataPtr->heightSlider->setTickInterval(1);
|
|
this->dataPtr->heightSlider->setOrientation(Qt::Horizontal);
|
|
this->dataPtr->heightSlider->setValue(10);
|
|
connect(this->dataPtr->heightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnHeightSlider(int)));
|
|
|
|
this->dataPtr->heightSpin = new QDoubleSpinBox(this);
|
|
connect(this->dataPtr->heightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnHeightSpin(double)));
|
|
|
|
// Create a layout to hold the brush height slider and its label
|
|
QHBoxLayout *heightSpinLayout = new QHBoxLayout;
|
|
heightSpinLayout->addWidget(new QLabel(tr("Height: ")));
|
|
heightSpinLayout->addStretch(2);
|
|
heightSpinLayout->addWidget(this->dataPtr->heightSpin);
|
|
|
|
QVBoxLayout *heightLayout = new QVBoxLayout;
|
|
heightLayout->setContentsMargins(0, 0, 0, 0);
|
|
heightLayout->addLayout(heightSpinLayout);
|
|
heightLayout->addWidget(this->dataPtr->heightSlider);
|
|
|
|
|
|
// Add all the layouts and widgets to the main layout
|
|
mainLayout->addLayout(buttonLayout);
|
|
mainLayout->addLayout(outsideRadiusLayout);
|
|
mainLayout->addLayout(insideRadiusLayout);
|
|
mainLayout->addLayout(weightLayout);
|
|
mainLayout->addLayout(heightLayout);
|
|
mainLayout->addStretch(1);
|
|
mainLayout->addWidget(saveButton);
|
|
|
|
mainLayout->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
|
|
|
|
this->setObjectName("terrainEditorPalette");
|
|
this->setLayout(mainLayout);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
TerrainEditorPalette::~TerrainEditorPalette()
|
|
{
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnRaise(bool _toggle)
|
|
{
|
|
this->SetState(_toggle ? "raise" : std::string());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnLower(bool _toggle)
|
|
{
|
|
this->SetState(_toggle ? "lower" : std::string());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnFlatten(bool _toggle)
|
|
{
|
|
this->SetState(_toggle ? "flatten" : std::string());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnPickHeight(bool /*_toggle*/)
|
|
{
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::SetState(const std::string &_state)
|
|
{
|
|
if (!_state.empty())
|
|
{
|
|
this->dataPtr->state = _state;
|
|
|
|
// Add an event filter, which allows the TerrainEditor to capture
|
|
// mouse events.
|
|
MouseEventHandler::Instance()->AddPressFilter("terrain",
|
|
boost::bind(&TerrainEditorPalette::OnMousePress, this, _1));
|
|
|
|
MouseEventHandler::Instance()->AddMoveFilter("terrain",
|
|
boost::bind(&TerrainEditorPalette::OnMouseMove, this, _1));
|
|
}
|
|
else
|
|
{
|
|
this->dataPtr->state.clear();
|
|
|
|
// Remove the event filters.
|
|
MouseEventHandler::Instance()->RemovePressFilter("terrain");
|
|
MouseEventHandler::Instance()->RemoveMoveFilter("terrain");
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnSave()
|
|
{
|
|
// Get the active camera and scene.
|
|
rendering::UserCameraPtr camera = gui::get_active_camera();
|
|
rendering::ScenePtr scene = camera->GetScene();
|
|
|
|
// Get a pointer to the heightmap, if the scen is valid.
|
|
rendering::Heightmap *heightmap = scene ? scene->GetHeightmap() : NULL;
|
|
common::Image img = heightmap->Image();
|
|
|
|
// Get a filename to save to.
|
|
// Note that file dialog static functions seem to be broken (issue #1514)
|
|
std::string filename = QFileDialog::getSaveFileName(this,
|
|
tr("Save Heightmap"), QString(),
|
|
tr("PNG Files (*.png)")).toStdString();
|
|
|
|
// Return if the user has canceled.
|
|
if (filename.empty())
|
|
return;
|
|
|
|
img.SavePNG(filename);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool TerrainEditorPalette::OnMousePress(const common::MouseEvent &_event)
|
|
{
|
|
if (_event.Button() != common::MouseEvent::LEFT)
|
|
return false;
|
|
|
|
bool handled = false;
|
|
|
|
// Get the active camera and scene.
|
|
rendering::UserCameraPtr camera = gui::get_active_camera();
|
|
rendering::ScenePtr scene = camera->GetScene();
|
|
|
|
// Get a pointer to the heightmap, if the scen is valid.
|
|
rendering::Heightmap *heightmap = scene ? scene->GetHeightmap() : NULL;
|
|
|
|
// Only try to modify if the heightmap exists, and the LEFT mouse button
|
|
// was used.
|
|
if (heightmap && !_event.Shift())
|
|
{
|
|
handled = this->Apply(_event, camera, heightmap);
|
|
QApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
|
|
}
|
|
else
|
|
QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
|
|
|
|
return handled;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool TerrainEditorPalette::OnMouseMove(const common::MouseEvent &_event)
|
|
{
|
|
if (_event.Button() != common::MouseEvent::LEFT)
|
|
return false;
|
|
|
|
bool handled = false;
|
|
|
|
// Get the active camera and scene.
|
|
rendering::UserCameraPtr camera = gui::get_active_camera();
|
|
rendering::ScenePtr scene = camera->GetScene();
|
|
|
|
// Get a pointer to the heightmap, if the scen is valid.
|
|
rendering::Heightmap *heightmap = scene ? scene->GetHeightmap() : NULL;
|
|
|
|
if (heightmap && !_event.Shift())
|
|
{
|
|
if (_event.Dragging())
|
|
handled = this->Apply(_event, camera, heightmap);
|
|
QApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
|
|
}
|
|
else
|
|
QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
|
|
|
|
return handled;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool TerrainEditorPalette::Apply(const common::MouseEvent &_event,
|
|
rendering::CameraPtr _camera, rendering::Heightmap *_heightmap) const
|
|
{
|
|
bool handled = false;
|
|
|
|
// Get the brush weight and size from the sliders.
|
|
double weight = this->dataPtr->weightSpin->value();
|
|
double outsideRadius = this->dataPtr->outsideRadiusSpin->value() / 10.0;
|
|
double insideRadius = this->dataPtr->insideRadiusSpin->value() / 10.0;
|
|
|
|
if (this->dataPtr->state == "lower")
|
|
{
|
|
handled = _heightmap->Lower(_camera, _event.Pos(), outsideRadius,
|
|
insideRadius, weight);
|
|
}
|
|
else if (this->dataPtr->state == "raise")
|
|
{
|
|
handled = _heightmap->Raise(_camera, _event.Pos(), outsideRadius,
|
|
insideRadius, weight);
|
|
}
|
|
else if (this->dataPtr->state == "flatten")
|
|
{
|
|
handled = _heightmap->Flatten(_camera, _event.Pos(), outsideRadius,
|
|
insideRadius, weight);
|
|
}
|
|
else if (this->dataPtr->state == "smooth")
|
|
{
|
|
handled = _heightmap->Smooth(_camera, _event.Pos(), outsideRadius,
|
|
insideRadius, weight);
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnOutsideRadiusSpin(double _value)
|
|
{
|
|
disconnect(this->dataPtr->outsideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnOutsideRadiusSlider(int)));
|
|
|
|
this->dataPtr->outsideRadiusSlider->setValue(
|
|
static_cast<int>(rint(
|
|
_value * this->dataPtr->outsideRadiusSlider->maximum())));
|
|
|
|
connect(this->dataPtr->outsideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnOutsideRadiusSlider(int)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnOutsideRadiusSlider(int _value)
|
|
{
|
|
disconnect(this->dataPtr->outsideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnOutsideRadiusSpin(double)));
|
|
|
|
this->dataPtr->outsideRadiusSpin->setValue(_value /
|
|
static_cast<double>(this->dataPtr->outsideRadiusSlider->maximum()));
|
|
|
|
connect(this->dataPtr->outsideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnOutsideRadiusSpin(double)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnInsideRadiusSpin(double _value)
|
|
{
|
|
disconnect(this->dataPtr->insideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnInsideRadiusSlider(int)));
|
|
|
|
this->dataPtr->insideRadiusSlider->setValue(
|
|
static_cast<int>(rint(
|
|
_value * this->dataPtr->insideRadiusSlider->maximum())));
|
|
|
|
connect(this->dataPtr->insideRadiusSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnInsideRadiusSlider(int)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnInsideRadiusSlider(int _value)
|
|
{
|
|
disconnect(this->dataPtr->insideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnInsideRadiusSpin(double)));
|
|
|
|
|
|
this->dataPtr->insideRadiusSpin->setValue(_value /
|
|
static_cast<double>(this->dataPtr->insideRadiusSlider->maximum()));
|
|
|
|
connect(this->dataPtr->insideRadiusSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnInsideRadiusSpin(double)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnWeightSpin(double _value)
|
|
{
|
|
disconnect(this->dataPtr->weightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnWeightSlider(int)));
|
|
|
|
this->dataPtr->weightSlider->setValue(
|
|
static_cast<int>(rint(_value * this->dataPtr->weightSlider->maximum())));
|
|
|
|
connect(this->dataPtr->weightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnWeightSlider(int)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnWeightSlider(int _value)
|
|
{
|
|
disconnect(this->dataPtr->weightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnWeightSpin(double)));
|
|
|
|
this->dataPtr->weightSpin->setValue(_value /
|
|
static_cast<double>(this->dataPtr->weightSlider->maximum()));
|
|
|
|
connect(this->dataPtr->weightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnWeightSpin(double)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnHeightSpin(double _value)
|
|
{
|
|
disconnect(this->dataPtr->heightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnHeightSlider(int)));
|
|
|
|
this->dataPtr->heightSlider->setValue(static_cast<int>(rint(_value)));
|
|
|
|
connect(this->dataPtr->heightSlider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(OnHeightSlider(int)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void TerrainEditorPalette::OnHeightSlider(int _value)
|
|
{
|
|
disconnect(this->dataPtr->heightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnHeightSpin(double)));
|
|
|
|
this->dataPtr->heightSpin->setValue(_value / 10.0);
|
|
|
|
connect(this->dataPtr->heightSpin, SIGNAL(valueChanged(double)),
|
|
this, SLOT(OnHeightSpin(double)));
|
|
}
|