Files
noggit-red/src/noggit/ui/TerrainTool.cpp
2024-09-06 20:16:57 +00:00

447 lines
16 KiB
C++
Executable File

// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/ui/TerrainTool.hpp>
#include <noggit/tool_enums.hpp>
#include <noggit/World.h>
#include <noggit/MapView.h>
#include <util/qt/overload.hpp>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QRadioButton>
#include <QtWidgets/QVBoxLayout>
#include <noggit/ui/tools/UiCommon/expanderwidget.h>
#include <noggit/ActionManager.hpp>
#include <noggit/Action.hpp>
#define _USE_MATH_DEFINES
#include <math.h>
namespace Noggit
{
namespace Ui
{
TerrainTool::TerrainTool(MapView* map_view, QWidget* parent, bool stamp)
: QWidget(parent)
, _edit_type (eTerrainType_Linear)
, _vertex_angle (0.0f)
, _vertex_orientation (0.0f)
, _cursor_pos(nullptr)
, _vertex_mode(eVertexMode_Center)
, _map_view(map_view)
{
setMinimumWidth(250);
setMaximumWidth(250);
auto layout (new QVBoxLayout (this));
layout->setAlignment(Qt::AlignTop);
_type_button_group = new QButtonGroup (this);
QRadioButton* radio_flat = new QRadioButton ("Flat", this);
QRadioButton* radio_linear = new QRadioButton ("Linear", this);
QRadioButton* radio_smooth = new QRadioButton ("Smooth", this);
QRadioButton* radio_polynomial = new QRadioButton ("Polynomial", this);
QRadioButton* radio_trigo = new QRadioButton ("Trigonom", this);
QRadioButton* radio_quadra = new QRadioButton ("Quadratic", this);
QRadioButton* radio_gauss = new QRadioButton ("Gaussian", this);
QRadioButton* radio_vertex;
if (!stamp)
radio_vertex = new QRadioButton ("Vertex", this);
QRadioButton* radio_script = new QRadioButton ("Script", this);
_type_button_group->addButton (radio_flat, (int)eTerrainType_Flat);
_type_button_group->addButton (radio_linear, (int)eTerrainType_Linear);
_type_button_group->addButton (radio_smooth, (int)eTerrainType_Smooth);
_type_button_group->addButton (radio_polynomial, (int)eTerrainType_Polynom);
_type_button_group->addButton (radio_trigo, (int)eTerrainType_Trigo);
_type_button_group->addButton (radio_quadra, (int)eTerrainType_Quadra);
_type_button_group->addButton (radio_gauss, (int)eTerrainType_Gaussian);
if (!stamp)
_type_button_group->addButton (radio_vertex, (int)eTerrainType_Vertex);
_type_button_group->addButton (radio_script, (int)eTerrainType_Script);
radio_linear->toggle();
QGroupBox* terrain_type_group (new QGroupBox ("Type", this));
QGridLayout* terrain_type_layout (new QGridLayout (terrain_type_group));
terrain_type_layout->addWidget (radio_flat, 0, 0);
terrain_type_layout->addWidget (radio_linear, 0, 1);
terrain_type_layout->addWidget (radio_smooth, 1, 0);
terrain_type_layout->addWidget (radio_polynomial, 1, 1);
terrain_type_layout->addWidget (radio_trigo, 2, 0);
terrain_type_layout->addWidget (radio_quadra, 2, 1);
terrain_type_layout->addWidget (radio_gauss, 3, 0);
if (!stamp)
{
terrain_type_layout->addWidget (radio_vertex, 3, 1);
terrain_type_layout->addWidget (radio_script, 4, 0);
}
else
{
terrain_type_layout->addWidget (radio_script, 3, 1);
}
layout->addWidget(terrain_type_group);
_radius_slider = new Noggit::Ui::Tools::UiCommon::ExtendedSlider(this);
_radius_slider->setRange (0, 1000);
_radius_slider->setPrefix("Radius:");
_radius_slider->setDecimals(2);
_radius_slider->setValue(15);
_inner_radius_slider = new Noggit::Ui::Tools::UiCommon::ExtendedSlider(this);
_inner_radius_slider->setRange (0.0, 1.0);
_inner_radius_slider->setPrefix("Inner Radius:");
_inner_radius_slider->setDecimals(2);
_inner_radius_slider->setSingleStep(0.05f);
_inner_radius_slider->setValue(0);
QGroupBox* settings_group(new QGroupBox ("Settings", this));
auto settings_layout (new QVBoxLayout (settings_group));
settings_layout->setContentsMargins(0, 12, 0, 12);
_speed_slider = new Noggit::Ui::Tools::UiCommon::ExtendedSlider(this);
_speed_slider->setPrefix("Speed:");
_speed_slider->setRange (0, 10 * 100);
_speed_slider->setSingleStep (1);
_speed_slider->setValue(2);
_snap_m2_objects_chkbox = new QCheckBox("Snap M2 objects", this);
_snap_m2_objects_chkbox->setChecked(true);
_snap_wmo_objects_chkbox = new QCheckBox("Snap WMO objects", this);
_snap_wmo_objects_chkbox->setChecked(true);
settings_layout->addWidget(_radius_slider);
settings_layout->addWidget(_inner_radius_slider);
settings_layout->addWidget(_speed_slider);
settings_layout->addWidget(_snap_m2_objects_chkbox);
settings_layout->addWidget(_snap_wmo_objects_chkbox);
layout->addWidget(settings_group);
_image_mask_group = new Noggit::Ui::Tools::ImageMaskSelector(map_view, this);
_mask_image = _image_mask_group->getPixmap()->toImage();
// layout->addWidget(_image_mask_group);
_image_mask_group->setBrushModeVisible(!stamp);
auto* customBrushBox = new ExpanderWidget(this);
customBrushBox->setExpanderTitle("Custom Brush");
customBrushBox->addPage(_image_mask_group);
customBrushBox->setExpanded(false);
layout->addWidget(customBrushBox);
_vertex_type_group = new QGroupBox ("Vertex edit", this);
_vertex_type_group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QVBoxLayout* vertex_layout (new QVBoxLayout (_vertex_type_group));
_vertex_button_group = new QButtonGroup (this);
QRadioButton* radio_mouse = new QRadioButton ("Cursor", _vertex_type_group);
QRadioButton* radio_center = new QRadioButton ("Selection center", _vertex_type_group);
radio_mouse->setToolTip ("Orient vertices using the cursor pos as reference");
radio_center->setToolTip ("Orient vertices using the selection center as reference");
_vertex_button_group->addButton (radio_mouse, (int)eVertexMode_Mouse);
_vertex_button_group->addButton (radio_center, (int)eVertexMode_Center);
radio_center->toggle();
QHBoxLayout* vertex_type_layout (new QHBoxLayout);
vertex_type_layout->addWidget (radio_mouse);
vertex_type_layout->addWidget (radio_center);
vertex_layout->addItem (vertex_type_layout);
QHBoxLayout* vertex_angle_layout (new QHBoxLayout);
vertex_angle_layout->addWidget (_orientation_dial = new QDial (_vertex_type_group));
_orientation_dial->setRange(0, 360);
_orientation_dial->setWrapping(true);
_orientation_dial->setSliderPosition(_vertex_orientation._ - 90); // to get ingame orientation
_orientation_dial->setToolTip("Orientation");
_orientation_dial->setSingleStep(10);
vertex_angle_layout->addWidget (_angle_slider = new QSlider (_vertex_type_group));
_angle_slider->setRange(-89, 89);
_angle_slider->setSliderPosition(_vertex_angle._);
_angle_slider->setToolTip("Angle");
vertex_layout->addItem (vertex_angle_layout);
layout->addWidget(_vertex_type_group);
_vertex_type_group->hide();
connect ( _type_button_group, qOverload<int> (&QButtonGroup::idClicked)
, [&] (int id)
{
_edit_type = static_cast<eTerrainType> (id);
updateVertexGroup();
}
);
connect ( _vertex_button_group, qOverload<int> (&QButtonGroup::idClicked)
, [&] (int id)
{
_vertex_mode = id;
}
);
connect ( _angle_slider, &QSlider::valueChanged
, [this] (int v)
{
if (NOGGIT_CUR_ACTION)
{
setAngle(v);
}
else
{
NOGGIT_ACTION_MGR->beginAction(_map_view);
setAngle(v);
NOGGIT_ACTION_MGR->endAction();
}
}
);
connect ( _orientation_dial, &QDial::valueChanged
, [this] (int v)
{
if (NOGGIT_CUR_ACTION)
{
setOrientation(v + 90.0f);
}
else
{
NOGGIT_ACTION_MGR->beginAction(_map_view);
setOrientation(v + 90.0f);
NOGGIT_ACTION_MGR->endAction();
}
}
);
connect (_image_mask_group, &Noggit::Ui::Tools::ImageMaskSelector::rotationUpdated, this, &TerrainTool::updateMaskImage);
connect (_radius_slider, &Noggit::Ui::Tools::UiCommon::ExtendedSlider::valueChanged, this, &TerrainTool::updateMaskImage);
connect(_image_mask_group, &Noggit::Ui::Tools::ImageMaskSelector::pixmapUpdated, this, &TerrainTool::updateMaskImage);
}
void TerrainTool::updateMaskImage()
{
QPixmap* pixmap = _image_mask_group->getPixmap();
QTransform matrix;
matrix.rotateRadians(_image_mask_group->getRotation() / 360.0f * 2.0f * M_PI);
_mask_image = pixmap->toImage().transformed(matrix, Qt::SmoothTransformation);
emit _map_view->trySetBrushTexture(&_mask_image, this);
}
void TerrainTool::changeTerrain
(World* world, glm::vec3 const& pos, float dt)
{
float radius = static_cast<float>(_radius_slider->value());
if(_edit_type != eTerrainType_Vertex)
{
if (_image_mask_group->isEnabled())
{
// store the ground height diff at center of all objects hit before editing it
std::vector<std::pair<SceneObject*, float>> objects_ground_distance = world->getObjectsGroundDistance(pos, radius
, _snap_wmo_objects_chkbox->isChecked(), _snap_m2_objects_chkbox->isChecked());
world->stamp(pos, dt * _speed_slider->value(), &_mask_image, radius,
_inner_radius_slider->value(), _edit_type, _image_mask_group->getBrushMode());
// re apply the ground height diff to the objects
for (auto pair : objects_ground_distance)
{
auto obj = pair.first;
auto new_ground_height = world->get_ground_height(obj->pos).y;
world->set_model_pos(obj, glm::vec3(obj->pos.x, new_ground_height + pair.second, obj->pos.z));
}
}
else
{
world->changeTerrain(pos, dt * _speed_slider->value(), radius, _edit_type, _inner_radius_slider->value());
world->changeObjectsWithTerrain(pos, dt * _speed_slider->value(), radius, _edit_type, _inner_radius_slider->value()
, _snap_wmo_objects_chkbox->isChecked(), _snap_m2_objects_chkbox->isChecked());
}
}
else
{
// < 0 ==> control is pressed
if (dt >= 0.0f)
{
world->selectVertices(pos, radius);
}
else
{
if (world->deselectVertices(pos, radius))
{
_vertex_angle = math::degrees (0.0f);
_vertex_orientation = math::degrees (0.0f);
world->clearVertexSelection();
}
}
}
}
void TerrainTool::moveVertices (World* world, float dt)
{
world->moveVertices(dt * _speed_slider->value());
}
void TerrainTool::flattenVertices (World* world)
{
if (_edit_type == eTerrainType_Vertex)
{
world->flattenVertices (world->vertexCenter().y);
}
}
void TerrainTool::nextType()
{
_edit_type = static_cast<eTerrainType> ((static_cast<int> (_edit_type) + 1) % eTerrainType_Count);
_type_button_group->button (_edit_type)->toggle();
updateVertexGroup();
}
void TerrainTool::setRadius(float radius)
{
_radius_slider->setValue(radius);
}
void TerrainTool::setInnerRadius(float radius)
{
_inner_radius_slider->setValue(radius);
}
void TerrainTool::changeRadius(float change)
{
setRadius (_radius_slider->value() + change);
}
void TerrainTool::changeInnerRadius(float change)
{
_inner_radius_slider->setValue(_inner_radius_slider->value() + change);
}
void TerrainTool::changeSpeed(float change)
{
_speed_slider->setValue(_speed_slider->value() + change);
}
void TerrainTool::setSpeed(float speed)
{
_speed_slider->setValue(speed);
}
void TerrainTool::changeOrientation (float change)
{
setOrientation (_vertex_orientation._ + change);
}
void TerrainTool::setOrientation (float orientation)
{
if (_edit_type == eTerrainType_Vertex)
{
QSignalBlocker const blocker (_orientation_dial);
while (orientation >= 360.0f)
{
orientation -= 360.0f;
}
while (orientation < 0.0f)
{
orientation += 360.0f;
}
_vertex_orientation = math::degrees (orientation);
_orientation_dial->setSliderPosition (_vertex_orientation._ - 90.0f);
emit updateVertices(_vertex_mode, _vertex_angle, _vertex_orientation);
}
}
void TerrainTool::setOrientRelativeTo (World* world, glm::vec3 const& pos)
{
if (_edit_type == eTerrainType_Vertex)
{
glm::vec3 const& center = world->vertexCenter();
_vertex_orientation = math::radians (std::atan2(center.z - pos.z, center.x - pos.x));
emit updateVertices(_vertex_mode, _vertex_angle, _vertex_orientation);
}
}
void TerrainTool::changeAngle (float change)
{
setAngle (_vertex_angle._ + change);
}
void TerrainTool::setAngle (float angle)
{
if (_edit_type == eTerrainType_Vertex)
{
QSignalBlocker const blocker (_angle_slider);
_vertex_angle = math::degrees (std::max(-89.0f, std::min(89.0f, angle)));
_angle_slider->setSliderPosition (_vertex_angle._);
emit updateVertices(_vertex_mode, _vertex_angle, _vertex_orientation);
}
}
void TerrainTool::updateVertexGroup()
{
_vertex_type_group->setVisible(_edit_type == eTerrainType_Vertex);
_image_mask_group->setVisible(_edit_type != eTerrainType_Vertex && _edit_type != eTerrainType_Script);
}
QSize TerrainTool::sizeHint() const
{
return QSize(250, height());
}
QJsonObject TerrainTool::toJSON()
{
QJsonObject json;
json["brush_action_type"] = "TERRAIN";
json["radius"] = _radius_slider->rawValue();
json["inner_radius"] = _inner_radius_slider->rawValue();
json["speed"] = _speed_slider->rawValue();
json["edit_type"] = static_cast<int>(_edit_type);
json["mask_enabled"] = _image_mask_group->isEnabled();
json["brush_mode"] = _image_mask_group->getBrushMode();
json["randomize_rot"] = _image_mask_group->getRandomizeRotation();
json["mask_rot"] = _image_mask_group->getRotation();
json["mask_image"] = _image_mask_group->getImageMaskPath();
return json;
}
void TerrainTool::fromJSON(QJsonObject const& json)
{
_radius_slider->setValue(json["radius"].toDouble());
_inner_radius_slider->setValue(json["inner_radius"].toDouble());
_speed_slider->setValue(json["speed"].toDouble());
_edit_type = static_cast<eTerrainType>(json["edit_type"].toInt());
_image_mask_group->setEnabled(json["mask_enabled"].toBool());
_image_mask_group->setBrushMode(json["brush_mode"].toInt());
_image_mask_group->setRandomizeRotation(json["randomize_rot"].toBool());
_image_mask_group->setRotationRaw(json["mask_rot"].toInt());
_image_mask_group->setImageMask(json["mask_image"].toString());
}
}
}