diff --git a/Canvas.cpp b/Canvas.cpp index 556dbf6..c613cbb 100644 --- a/Canvas.cpp +++ b/Canvas.cpp @@ -1,4 +1,6 @@ #include "Canvas.h" +#define WIDGET_POSITION event->pos() +#define UCS_POSITION screenToLogical(WIDGET_POSITION) // =================================================================== // Вспомогательные функции @@ -78,7 +80,6 @@ void Canvas::changeMode(Mode _mode) Line* Canvas::findAt(QPointF& pos, qreal tolerance) { - // TODO: реализовать проверку, находится ли точка на линии for (Line* line : lines) { if (line->contains(pos, tolerance)) { return line; @@ -136,48 +137,62 @@ bool Canvas::areAlreadyParallel(Line* l1, Line* l2) return parallelPairs.count(makeOrderedPair(l1, l2)); } +bool Canvas::areAlreadyPerpendicular(Line* line1, Line* line2) +{ + return perpendicularPairs.count(makeOrderedPair(line1, line2)); +} + // =================================================================== // Методы работы с ограничениями // =================================================================== -void Canvas::remove_constraints() +// TODO - переделать в bool для отображения статуса выполнения +void Canvas::remove_constraint(int tag) { + auto it = C_Info.find(tag); + if (it == C_Info.end()) return; + + ConstraintInfo info = it->second; + // Удаляем ограничение из солвера - sys.clearByTag(constraints_count - 1); + sys.clearByTag(tag); // Удаляем из соответствующих контейнеров в зависимости от типа ограничения - switch (lastConstraint.mode) { + switch (info.mode) { case Mode::Horizontal: { - if (auto* pair = std::get_if(&lastConstraint.data)) { + if (auto* pair = std::get_if(&info.data)) { HORIZ_pairs.erase(*pair); } break; } case Mode::Vertical: { - if (auto* pair = std::get_if(&lastConstraint.data)) { + if (auto* pair = std::get_if(&info.data)) { VERT_pairs.erase(*pair); } break; } case Mode::Parallel: { - if (auto* pair = std::get_if(&lastConstraint.data)) { + if (auto* pair = std::get_if(&info.data)) { parallelPairs.erase(*pair); } break; } case Mode::Coincedent: { - if (auto* pair = std::get_if(&lastConstraint.data)) { + if (auto* pair = std::get_if(&info.data)) { P2Ppairs.erase(*pair); } break; } + case Mode::Perpendicular: { + if (auto* pair = std::get_if(&info.data)) { + perpendicularPairs.erase(*pair); + } + break; + } default: break; } - - // Сбрасываем информацию о последнем ограничении - lastConstraint.mode = Mode::None; - constraints_count--; + C_Info.erase(it); } // =================================================================== @@ -244,17 +259,15 @@ void Canvas::mousePressEvent(QMouseEvent* event) sys.addConstraintHorizontal(*found, constraints_count++); auto pair = makeOrderedPair(found->start_ref, found->end_ref); HORIZ_pairs.insert(pair); - lastConstraint.mode = Mode::Horizontal; - lastConstraint.data = pair; + C_Info[constraints_count - 1] = { Mode::Horizontal, pair }; } else { sys.addConstraintVertical(*found, constraints_count++); auto pair = makeOrderedPair(found->start_ref, found->end_ref); VERT_pairs.insert(pair); - lastConstraint.mode = Mode::Vertical; - lastConstraint.data = pair; + C_Info[constraints_count - 1] = { Mode::Vertical, pair }; } - update(); + solve_for_canvas(); after_constraint = true; } mode = Mode::None; @@ -315,7 +328,7 @@ void Canvas::mousePressEvent(QMouseEvent* event) mode = Mode::None; QMessageBox::critical(this, "WHOOPS", "Sorry, your line is very short", QMessageBox::Ok); - update(); + solve_for_canvas(); return; } @@ -330,7 +343,7 @@ void Canvas::mousePressEvent(QMouseEvent* event) obj_count++; after_constraint = true; } - update(); + solve_for_canvas(); return; } @@ -340,21 +353,21 @@ void Canvas::mousePressEvent(QMouseEvent* event) if (!found) { current_line = nullptr; - update(); + solve_for_canvas(); return; } // Первый клик: выбираем первую линию if (!current_line) { current_line = found; - update(); + solve_for_canvas(); return; } // Повторный клик на ту же линию: сброс выбора if (found == current_line) { current_line = nullptr; - update(); + solve_for_canvas(); return; } @@ -363,13 +376,12 @@ void Canvas::mousePressEvent(QMouseEvent* event) auto pair = makeOrderedPair(found, current_line); sys.addConstraintParallel(*found, *current_line, constraints_count++); parallelPairs.insert(pair); - lastConstraint.mode = Mode::Parallel; - lastConstraint.data = pair; + C_Info[constraints_count - 1] = { Mode::Parallel, pair }; current_line = nullptr; mode = Mode::None; after_constraint = true; - update(); + solve_for_canvas(); } else { // Линии уже параллельны - сообщаем об ошибке @@ -383,7 +395,7 @@ void Canvas::mousePressEvent(QMouseEvent* event) QMessageBox::Ok ); current_line = nullptr; - update(); + solve_for_canvas(); return; } } @@ -393,21 +405,21 @@ void Canvas::mousePressEvent(QMouseEvent* event) Point* clickedPoint = findPointAt(scene); if (!clickedPoint) { firstPoint = nullptr; - update(); + solve_for_canvas(); return; } // Первый клик: выбираем первую точку if (!firstPoint) { firstPoint = clickedPoint; - update(); + solve_for_canvas(); return; } // Повторный клик на ту же точку: сброс выбора if (clickedPoint == firstPoint) { firstPoint = nullptr; - update(); + solve_for_canvas(); return; } @@ -426,7 +438,7 @@ void Canvas::mousePressEvent(QMouseEvent* event) QMessageBox::critical(this, QString("NO!"), QString("P2P failed")); firstPoint = nullptr; mode = Mode::None; - update(); + solve_for_canvas(); return; } @@ -434,15 +446,61 @@ void Canvas::mousePressEvent(QMouseEvent* event) sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++); auto pair = makeOrderedPair(clickedPoint, firstPoint); P2Ppairs.insert(pair); - lastConstraint.mode = Mode::Coincedent; - lastConstraint.data = pair; + C_Info[constraints_count - 1] = { Mode::Coincedent, pair }; firstPoint = nullptr; mode = Mode::None; after_constraint = true; - update(); + solve_for_canvas(); return; } + + else if (mode == Mode::Perpendicular) { + Line* found = findAt(scene); + + if (!found) { + current_line = nullptr; + solve_for_canvas(); + return; + } + + // Первый клик: выбираем первую линию + if (!current_line) { + current_line = found; + solve_for_canvas(); + return; + } + + // Повторный клик на ту же линию: сброс выбора + if (found == current_line) { + current_line = nullptr; + solve_for_canvas(); + return; + } + + if (!areAlreadyPerpendicular(found, current_line)) { + auto pair = makeOrderedPair(found, current_line); + sys.addConstraintPerpendicular(*found, *current_line, constraints_count++); + perpendicularPairs.insert(pair); + C_Info[constraints_count - 1] = { Mode::Perpendicular, pair }; + + current_line = nullptr; + mode = Mode::None; + after_constraint = true; + solve_for_canvas(); + } + else { + + QMessageBox::warning(this, + QString("Wrong"), + QString("Perpendicular lines can not be more perpendicular!"), + QMessageBox::Ok + ); + current_line = nullptr; + solve_for_canvas(); + return; + } + } } void Canvas::mouseMoveEvent(QMouseEvent* event) @@ -451,8 +509,18 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) if (draggedPoint) { QPointF pos = UCS_POSITION - dragOffset; - auto coincidentGroup = getCoincidentGroup(draggedPoint); + *draggedPoint->x = pos.x(); + *draggedPoint->y = pos.y(); + // TODO + for (Point* pair : points) { + if (areCoincident(draggedPoint, pair)) { + *pair->x = pos.x(); + *pair->y = pos.y(); + } + } + + auto coincidentGroup = getCoincidentGroup(draggedPoint); for (Point* pt : coincidentGroup) { *pt->x = pos.x(); *pt->y = pos.y(); @@ -469,9 +537,16 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) } } - *draggedPoint->x = pos.x(); - *draggedPoint->y = pos.y(); - update(); + for (Point* other : points) { + if (areHorizontalVertical(draggedPoint, other, true)) { + *other->x = pos.x(); + } + if (areHorizontalVertical(draggedPoint, other, false)) { + *other->y = pos.y(); + } + } + + solve_for_canvas(); } // ====================== Перемещение линии ====================== @@ -503,7 +578,7 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) } } } - update(); + solve_for_canvas(); } #ifdef _DEBUG else @@ -517,12 +592,12 @@ void Canvas::mouseReleaseEvent(QMouseEvent* event) if (draggedPoint) { draggedPoint = nullptr; - update(); + solve_for_canvas(); } if (draggedLine) { draggedLine = nullptr; - update(); + solve_for_canvas(); } } @@ -543,22 +618,6 @@ void Canvas::paintEvent(QPaintEvent* event) p.drawLine(-5, 0, 5, 0); p.drawLine(0, -5, 0, 5); - - // ====================== Решение системы уравнений ====================== - if (!params.empty()) { - int res = sys.solve(params); - if (res == SolveStatus::Success || res == SolveStatus::Converged) { - sys.applySolution(); - } - else if (res == SolveStatus::Failed && after_constraint) { - // Ошибка решения: удаляем последнее добавленное ограничение - QMessageBox::warning(this, QString("Error!"), - QString("Last constraint is unavailable!")); - remove_constraints(); - } - after_constraint = false; - } - // ====================== Отрисовка линий ====================== for (Line* line : lines) { bool isSelected = (mode == Mode::Parallel && line == current_line); @@ -651,3 +710,30 @@ std::vector Canvas::getCoincidentGroup(Point* p) } return group; } + +void Canvas::solve_for_canvas() +{ + bool flag = false; + int res = sys.solve(params); + if (res != SolveStatus::Success && res != SolveStatus::Converged) { + flag = true; + } + else { + sys.applySolution(); + for (Line* line : lines) { + if (abs(*line->p1.x - *line->p2.x) < EPS && abs(*line->p1.y - *line->p2.y) < EPS && after_constraint) { + sys.undoSolution(); + flag = true; + break; + } + } + } + + if (flag) { + QMessageBox::warning(this, QString("Error!"), QString("Last constraint is unavailable!")); + remove_constraint(constraints_count - 1); + constraints_count--; + } + after_constraint = false; + update(); +} \ No newline at end of file diff --git a/Canvas.h b/Canvas.h index e3b9ba6..fc2bfb2 100644 --- a/Canvas.h +++ b/Canvas.h @@ -1,7 +1,5 @@ #pragma once - -#define WIDGET_POSITION event->pos() -#define UCS_POSITION screenToLogical(WIDGET_POSITION) +constexpr auto EPS = 1e-9; #include #include @@ -30,7 +28,8 @@ enum class Mode : int Parallel = 2, ///< Режим задания параллельности Coincedent = 3, ///< Режим задания совпадения точек Horizontal = 4, ///< Режим задания горизонтальности - Vertical = 5 ///< Режим задания вертикальности + Vertical = 5, ///< Режим задания вертикальности + Perpendicular = 6 }; /// Удобный тип для хранения пары параллельных линий (порядок не важен) @@ -94,10 +93,12 @@ private: /// Проверить, являются ли две линии уже параллельными (дубликат ограничения) bool areAlreadyParallel(Line* line1, Line* line2); + bool areAlreadyPerpendicular(Line* line1, Line* line2); + // ====================== Методы работы с ограничениями ====================== /// Удалить последние добавленные ограничения при ошибке солвера - void remove_constraints(); + void remove_constraint(int); // ====================== Данные для перемещения объектов ====================== @@ -115,6 +116,7 @@ private: // ====================== Коллекции ограничений ====================== std::set parallelPairs; ///< Пары параллельных линий + std::set perpendicularPairs; std::set P2Ppairs; ///< Пары совпадающих точек std::set HORIZ_pairs; ///< Пары точек горизонтальных линий std::set VERT_pairs; ///< Пары точек вертикальных линий @@ -138,10 +140,12 @@ private: /// Структура для хранения информации о последнем добавленном ограничении /// (используется для отката при ошибке солвера) - struct LastConstraint { - Mode mode{ Mode::None }; ///< Тип последнего ограничения - std::variant data; ///< Данные ограничения - }; - LastConstraint lastConstraint; ///< Информация о последнем добавленном ограничении + struct ConstraintInfo { + Mode mode; + std::variant data; + }; + std::map C_Info; + + void solve_for_canvas(); }; \ No newline at end of file diff --git a/DRAWer_2_0.cpp b/DRAWer_2_0.cpp index 4458cb3..1575b58 100644 --- a/DRAWer_2_0.cpp +++ b/DRAWer_2_0.cpp @@ -40,3 +40,9 @@ void DRAWer_2_0::on_Vertical_Button_clicked() ui.widget->changeMode(Mode::Vertical); } + +void DRAWer_2_0::on_Perpendicular_Button_clicked() +{ + ui.widget->changeMode(Mode::Perpendicular); +} + diff --git a/DRAWer_2_0.h b/DRAWer_2_0.h index 9d40eca..218e250 100644 --- a/DRAWer_2_0.h +++ b/DRAWer_2_0.h @@ -22,6 +22,8 @@ private slots: void on_Vertical_Button_clicked(); + void on_Perpendicular_Button_clicked(); + private: Ui::DRAWer_2_0Class ui; int counter; diff --git a/DRAWer_2_0.ui b/DRAWer_2_0.ui index fe011c0..a906a54 100644 --- a/DRAWer_2_0.ui +++ b/DRAWer_2_0.ui @@ -52,6 +52,13 @@ + + + + Perpendicular + + +