// This file is part of Noggit3, licensed under GNU General Public License (version 3). #include #include #include #include #include #include #include #include #include #include #include #include #include #define _USE_MATH_DEFINES #include 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 (&QButtonGroup::idClicked) , [&] (int id) { _edit_type = static_cast (id); updateVertexGroup(); } ); connect ( _vertex_button_group, qOverload (&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(_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> 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 ((static_cast (_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(_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(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()); } } }