1157 lines
36 KiB
C++
1157 lines
36 KiB
C++
#include "Canvas.h"
|
||
|
||
std::set<Point*> groups;
|
||
static QPointF scene;
|
||
|
||
// ===================================================================
|
||
// Вспомогательные функции
|
||
// ===================================================================
|
||
|
||
/**
|
||
* @brief Вычислить расстояние между двумя точками
|
||
* @param p1 Первая точка
|
||
* @param p2 Вторая точка
|
||
* @return Расстояние между точками
|
||
*/
|
||
static double dist_P2P(QPointF p1, QPointF p2)
|
||
{
|
||
return sqrt(pow(p2.x() - p1.x(), 2) + pow(p2.y() - p1.y(), 2));
|
||
}
|
||
|
||
/**
|
||
* @brief Создать упорядоченную пару
|
||
*
|
||
* Создает пару, в которой элементы упорядочены по указателям,
|
||
* чтобы pair(obj1, obj2) и pair(obj2, obj1) считались одинаковыми
|
||
*
|
||
* @tparam T Тип возвращаемой пары
|
||
* @tparam A Тип первого элемента
|
||
* @tparam B Тип второго элемента
|
||
* @param obj1 Первый объект
|
||
* @param obj2 Второй объект
|
||
* @return Упорядоченная пара
|
||
*/
|
||
template <typename T, typename A, typename B>
|
||
T makeOrderedPair(A* obj1, B* obj2)
|
||
{
|
||
return (obj1 < obj2) ? std::make_pair(obj1, obj2) : std::make_pair(obj2, obj1);
|
||
}
|
||
|
||
// ===================================================================
|
||
// Конструктор и деструктор
|
||
// ===================================================================
|
||
|
||
Canvas::Canvas(QWidget* parent)
|
||
: QWidget(parent)
|
||
{
|
||
sys = System();
|
||
setMouseTracking(true);
|
||
setBackgroundRole(QPalette::Base);
|
||
setFocusPolicy(Qt::StrongFocus);
|
||
setAutoFillBackground(true);
|
||
}
|
||
|
||
Canvas::~Canvas()
|
||
{
|
||
clearCanvas();
|
||
}
|
||
|
||
// ===================================================================
|
||
// Методы изменения режима и масштаба
|
||
// ===================================================================
|
||
|
||
void Canvas::changeMode(Mode _mode)
|
||
{
|
||
mode = _mode;
|
||
}
|
||
|
||
void Canvas::zoomIn()
|
||
{
|
||
scaleFactor += ZOOM_STEP;
|
||
update();
|
||
}
|
||
|
||
void Canvas::zoomOut()
|
||
{
|
||
scaleFactor -= ZOOM_STEP;
|
||
if (scaleFactor < 0.1) // Минимальный масштаб 10%
|
||
scaleFactor = 0.1;
|
||
update();
|
||
}
|
||
|
||
void Canvas::zoomReset()
|
||
{
|
||
scaleFactor = 1.0;
|
||
update();
|
||
}
|
||
|
||
// ===================================================================
|
||
// Методы поиска и проверки
|
||
// ===================================================================
|
||
|
||
Curve* Canvas::findAt(QPointF& pos, qreal tolerance)
|
||
{
|
||
for (Curve* curve : curves) {
|
||
if (Line* line = CURVE_AS_LINE(curve)) {
|
||
if (line->contains(pos, tolerance)) {
|
||
return line;
|
||
}
|
||
}
|
||
else if (Circle* circle = CURVE_AS_CIRCLE(curve)) {
|
||
QPointF center(*circle->center.x, *circle->center.y);
|
||
double radius = *circle->rad;
|
||
|
||
double distToCenter = dist_P2P(center, pos);
|
||
// Проверяем, лежит ли точка вблизи окружности (с допуском)
|
||
if (std::abs(distToCenter - radius) <= tolerance) {
|
||
return circle;
|
||
}
|
||
}
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
Point* Canvas::findPointAt(QPointF pos, qreal tolerance)
|
||
{
|
||
Point* temp = nullptr;
|
||
|
||
for (Curve* curve : curves) {
|
||
if (Line* line = CURVE_AS_LINE(curve)) {
|
||
QPointF p1(*line->p1.x, *line->p1.y);
|
||
QPointF p2(*line->p2.x, *line->p2.y);
|
||
|
||
if (dist_P2P(p1, pos) <= tolerance)
|
||
temp = line->start_ref;
|
||
if (dist_P2P(p2, pos) <= tolerance)
|
||
temp = line->end_ref;
|
||
}
|
||
else if (Circle* circle = CURVE_AS_CIRCLE(curve)) {
|
||
QPointF center(circle->center.get_X(), circle->center.get_Y());
|
||
if (dist_P2P(center, pos) <= tolerance)
|
||
temp = circle->center_ref;
|
||
}
|
||
}
|
||
return temp;
|
||
}
|
||
|
||
bool Canvas::areCoincident(Point* p1, Point* p2)
|
||
{
|
||
return P2Ppairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
}
|
||
|
||
bool Canvas::areHorizontalVertical(Point* p1, Point* p2, bool mode)
|
||
{
|
||
// mode = false - проверка горизонтальности
|
||
// mode = true - проверка вертикальности
|
||
if (!mode)
|
||
return HORIZ_pairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
else
|
||
return VERT_pairs.count(makeOrderedPair<PointPair>(p1, p2));
|
||
}
|
||
|
||
bool Canvas::isLineHorizontal(Line* line)
|
||
{
|
||
if (!line || !line->start_ref || !line->end_ref)
|
||
return false;
|
||
return HORIZ_pairs.count(makeOrderedPair<PointPair>(line->start_ref, line->end_ref));
|
||
}
|
||
|
||
bool Canvas::isLineVertical(Line* line)
|
||
{
|
||
if (!line || !line->start_ref || !line->end_ref)
|
||
return false;
|
||
return VERT_pairs.count(makeOrderedPair<PointPair>(line->start_ref, line->end_ref));
|
||
}
|
||
|
||
bool Canvas::areAlreadyParallel(Line* l1, Line* l2)
|
||
{
|
||
return parallelPairs.count(makeOrderedPair<LinePair>(l1, l2));
|
||
}
|
||
|
||
bool Canvas::areAlreadyPerpendicular(Line* line1, Line* line2)
|
||
{
|
||
return perpendicularPairs.count(makeOrderedPair<LinePair>(line1, line2));
|
||
}
|
||
|
||
bool Canvas::areAlreadyTangent(Curve* curve1, Curve* curve2)
|
||
{
|
||
return tangentPairs.count(makeOrderedPair<CurvePair>(curve1, curve2));
|
||
}
|
||
|
||
// ===================================================================
|
||
// Методы работы с ограничениями
|
||
// ===================================================================
|
||
|
||
void Canvas::remove_constraint(int tag)
|
||
{
|
||
auto it = C_Info.find(tag);
|
||
if (it == C_Info.end()) return;
|
||
|
||
ConstraintInfo info = it->second;
|
||
|
||
// Удаляем ограничение из солвера
|
||
sys.clearByTag(tag);
|
||
|
||
// Удаляем из соответствующих контейнеров в зависимости от типа ограничения
|
||
switch (info.mode) {
|
||
case Mode::Horizontal: {
|
||
if (auto* pair = std::get_if<PointPair>(&info.data)) {
|
||
HORIZ_pairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
case Mode::Vertical: {
|
||
if (auto* pair = std::get_if<PointPair>(&info.data)) {
|
||
VERT_pairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
case Mode::Parallel: {
|
||
if (auto* pair = std::get_if<LinePair>(&info.data)) {
|
||
parallelPairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
case Mode::Coincedent: {
|
||
if (auto* pair = std::get_if<PointPair>(&info.data)) {
|
||
P2Ppairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
case Mode::Perpendicular: {
|
||
if (auto* pair = std::get_if<LinePair>(&info.data)) {
|
||
perpendicularPairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
case Mode::Tangent: {
|
||
if (auto* pair = std::get_if<CurvePair>(&info.data)) {
|
||
tangentPairs.erase(*pair);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
C_Info.erase(it);
|
||
}
|
||
|
||
// ===================================================================
|
||
// Обработчики событий мыши
|
||
// ===================================================================
|
||
|
||
void Canvas::mousePressEvent(QMouseEvent* event)
|
||
{
|
||
QPointF scene = UCS_POSITION;
|
||
|
||
#ifdef _DEBUG
|
||
qDebug() << "Scene point in" << scene.x() << scene.y();
|
||
#endif
|
||
|
||
switch (mode)
|
||
{
|
||
case Mode::None:
|
||
ON_NONE(&scene);
|
||
break;
|
||
case Mode::DrawingLine:
|
||
ON_LINE(&scene);
|
||
break;
|
||
case Mode::Parallel:
|
||
ON_PARALLEL(&scene);
|
||
break;
|
||
case Mode::Coincedent:
|
||
ON_P2P(&scene);
|
||
break;
|
||
case Mode::Horizontal:
|
||
ON_HORIZ_VERT(&scene);
|
||
break;
|
||
case Mode::Vertical:
|
||
ON_HORIZ_VERT(&scene);
|
||
break;
|
||
case Mode::Perpendicular:
|
||
ON_PERPENDICULAR(&scene);
|
||
break;
|
||
case Mode::DrawingCircle:
|
||
ON_CIRCLE(&scene);
|
||
break;
|
||
case Mode::Tangent:
|
||
ON_TANGENT(&scene);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void Canvas::mouseMoveEvent(QMouseEvent* event)
|
||
{
|
||
if (current_line) {
|
||
scene = UCS_POSITION;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
if (current_circle) {
|
||
scene = UCS_POSITION;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
// ====================== Перемещение точки ======================
|
||
if (draggedPoint) {
|
||
QPointF pos = UCS_POSITION - dragOffset;
|
||
for (Point* pt : groups) {
|
||
*pt->x = pos.x();
|
||
*pt->y = pos.y();
|
||
}
|
||
|
||
for (Point* basePt : groups) {
|
||
for (Point* other : points) {
|
||
if (areHorizontalVertical(basePt, other, true)) {
|
||
*other->x = pos.x();
|
||
}
|
||
if (areHorizontalVertical(basePt, other, false)) {
|
||
*other->y = pos.y();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ====================== Перемещение линии ======================
|
||
else if (draggedCurve) {
|
||
if (Line* draggedLine = CURVE_AS_LINE(draggedCurve)) {
|
||
QPointF newCenter = UCS_POSITION - dragOffset;
|
||
QPointF oldCenter(
|
||
(*draggedLine->p1.x + *draggedLine->p2.x) / 2.0,
|
||
(*draggedLine->p1.y + *draggedLine->p2.y) / 2.0
|
||
);
|
||
|
||
// Вычисляем смещение
|
||
double dx = newCenter.x() - oldCenter.x();
|
||
double dy = newCenter.y() - oldCenter.y();
|
||
|
||
// Перемещаем обе точки линии
|
||
Point* linePoints[2] = { draggedLine->start_ref, draggedLine->end_ref };
|
||
|
||
for (int i = 0; i < 2; i++) {
|
||
Point* currentPoint = linePoints[i];
|
||
|
||
*currentPoint->x += dx;
|
||
*currentPoint->y += dy;
|
||
|
||
// Обновляем совпадающие точки
|
||
for (Point* pair : points) {
|
||
if (pair != currentPoint && areCoincident(currentPoint, pair)) {
|
||
*pair->x = *currentPoint->x;
|
||
*pair->y = *currentPoint->y;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (Circle* circle = CURVE_AS_CIRCLE(draggedCurve)) {
|
||
QPointF newCenter = UCS_POSITION - dragOffset;
|
||
*circle->center.x = newCenter.x();
|
||
*circle->center.y = newCenter.y();
|
||
*circle->center_ref->x = newCenter.x();
|
||
*circle->center_ref->y = newCenter.y();
|
||
|
||
// Также обновляем все совпадающие точки (если центр совпадает с другими точками через P2P)
|
||
for (Point* other : points) {
|
||
if (other != circle->center_ref && areCoincident(circle->center_ref, other)) {
|
||
*other->x = newCenter.x();
|
||
*other->y = newCenter.y();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#ifdef _DEBUG
|
||
else
|
||
showObjectTag(WIDGET_POSITION);
|
||
#endif
|
||
solve_for_canvas();
|
||
}
|
||
|
||
void Canvas::mouseReleaseEvent(QMouseEvent* event)
|
||
{
|
||
Q_UNUSED(event);
|
||
|
||
if (draggedPoint) {
|
||
draggedPoint = nullptr;
|
||
solve_for_canvas();
|
||
}
|
||
|
||
if (draggedCurve) {
|
||
draggedCurve = nullptr;
|
||
solve_for_canvas();
|
||
}
|
||
|
||
groups.clear();
|
||
}
|
||
|
||
// ===================================================================
|
||
// Метод отрисовки
|
||
// ===================================================================
|
||
|
||
void Canvas::paintEvent(QPaintEvent* event)
|
||
{
|
||
Q_UNUSED(event);
|
||
|
||
QPainter p(this);
|
||
p.setRenderHint(QPainter::Antialiasing, true);
|
||
|
||
// Центрирование и масштабирование координат
|
||
p.translate(width() / 2.0, height() / 2.0);
|
||
p.scale(scaleFactor, -scaleFactor);
|
||
|
||
// Оси координат (красные, всегда рисуются первыми)
|
||
p.setPen(Qt::red);
|
||
p.drawLine(-5, 0, 5, 0);
|
||
p.drawLine(0, -5, 0, 5);
|
||
|
||
// ====================== Отрисовка всех кривых ======================
|
||
for (Curve* curve : curves) {
|
||
if (Line* line = CURVE_AS_LINE(curve)) {
|
||
// Выделение линии в режиме Parallel
|
||
bool isSelected = (mode == Mode::Parallel && line == current_line);
|
||
QPen linePen = isSelected ? QPen(Qt::red, 4) : QPen(Qt::black, 2);
|
||
p.setPen(linePen);
|
||
|
||
// Сама линия
|
||
p.drawLine(QPointF(*line->p1.x, *line->p1.y),
|
||
QPointF(*line->p2.x, *line->p2.y));
|
||
|
||
// Конечные точки линии
|
||
QBrush pointBrush = isSelected ? QBrush(Qt::red) : QBrush(Qt::darkBlue);
|
||
p.setBrush(pointBrush);
|
||
p.setPen(Qt::NoPen);
|
||
p.drawEllipse(QPointF(*line->p1.x, *line->p1.y), 5, 5);
|
||
p.drawEllipse(QPointF(*line->p2.x, *line->p2.y), 5, 5);
|
||
}
|
||
else if (Circle* circle = CURVE_AS_CIRCLE(curve)) {
|
||
// Контур окружности
|
||
p.setPen(QPen(Qt::black, 2));
|
||
p.setBrush(Qt::NoBrush);
|
||
p.drawEllipse(QPointF(*circle->center.x, *circle->center.y),
|
||
*circle->rad, *circle->rad);
|
||
|
||
// Точка центра
|
||
p.setBrush(QBrush(Qt::darkBlue));
|
||
p.setPen(Qt::NoPen);
|
||
p.drawEllipse(QPointF(*circle->center.x, *circle->center.y), 5, 5);
|
||
}
|
||
}
|
||
|
||
// ====================== Подсветка выбранной точки (режим Coincedent) ======================
|
||
if (mode == Mode::Coincedent && firstPoint) {
|
||
QPointF pt(*firstPoint->x, *firstPoint->y);
|
||
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(Qt::red);
|
||
p.drawEllipse(pt, 12, 12);
|
||
|
||
p.setBrush(Qt::white);
|
||
p.drawEllipse(pt, 8, 8);
|
||
|
||
p.setBrush(Qt::red);
|
||
p.drawEllipse(pt, 4, 4);
|
||
}
|
||
|
||
// ====================== Отрисовка текущей линии (режим DrawingLine) ======================
|
||
if (current_line && mode == Mode::DrawingLine) {
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(Qt::blue);
|
||
p.drawEllipse(QPointF(*current_line->p1.x, *current_line->p1.y), 6, 6);
|
||
|
||
p.setPen(Qt::DashLine);
|
||
p.setBrush(Qt::NoBrush);
|
||
p.drawLine(QPointF(*current_line->p1.x, *current_line->p1.y), scene);
|
||
}
|
||
// ====================== Отрисовка текущей окружности (режим DrawingCircle) ======================
|
||
else if (current_circle && mode == Mode::DrawingCircle) {
|
||
QPointF center(*current_circle->center.x, *current_circle->center.y);
|
||
double current_radius = dist_P2P(center, scene);
|
||
|
||
p.setPen(Qt::NoPen);
|
||
p.setBrush(Qt::blue);
|
||
p.drawEllipse(center, 5, 5);
|
||
|
||
p.setPen(QPen(Qt::black, 1, Qt::DashLine));
|
||
p.setBrush(Qt::NoBrush);
|
||
p.drawEllipse(center, current_radius, current_radius);
|
||
}
|
||
}
|
||
|
||
// ===================================================================
|
||
// Обработчики событий клавиатуры и колесика мыши
|
||
// ===================================================================
|
||
|
||
void Canvas::keyPressEvent(QKeyEvent* event)
|
||
{
|
||
switch (event->key()) {
|
||
case Qt::Key_Delete:
|
||
case Qt::Key_Clear: {
|
||
QMessageBox::StandardButton reply;
|
||
reply = QMessageBox::question(this,
|
||
"Очистка canvas",
|
||
"Удалить все объекты и ограничения?",
|
||
QMessageBox::Yes | QMessageBox::No);
|
||
|
||
if (reply == QMessageBox::Yes) {
|
||
clearCanvas();
|
||
}
|
||
break;
|
||
}
|
||
case Qt::Key_Escape: {
|
||
// Отмена текущего действия
|
||
if (mode == Mode::DrawingLine && current_line) {
|
||
// Отмена рисования линии
|
||
delete current_line;
|
||
current_line = nullptr;
|
||
|
||
// Удаляем созданные точки и параметры
|
||
if (points.size() >= 2) {
|
||
delete points.back();
|
||
points.pop_back();
|
||
delete points.back();
|
||
points.pop_back();
|
||
}
|
||
if (params.size() >= 4) {
|
||
delete params.back();
|
||
params.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
}
|
||
mode = Mode::None;
|
||
update();
|
||
}
|
||
else if (mode != Mode::None) {
|
||
// Отмена любого другого режима
|
||
mode = Mode::None;
|
||
current_line = nullptr;
|
||
firstPoint = nullptr;
|
||
groups.clear();
|
||
update();
|
||
}
|
||
break;
|
||
}
|
||
case Qt::Key_Z: {
|
||
if (constraints_count > 0) {
|
||
remove_constraint(constraints_count - 1);
|
||
constraints_count--;
|
||
solve_for_canvas();
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
QWidget::keyPressEvent(event);
|
||
}
|
||
}
|
||
|
||
void Canvas::wheelEvent(QWheelEvent* event)
|
||
{
|
||
if (event->angleDelta().y() > 0) {
|
||
zoomIn();
|
||
}
|
||
else {
|
||
zoomOut();
|
||
}
|
||
event->accept();
|
||
}
|
||
|
||
void Canvas::leaveEvent(QEvent* event)
|
||
{
|
||
Q_UNUSED(event);
|
||
QToolTip::hideText();
|
||
}
|
||
|
||
#ifdef _DEBUG
|
||
void Canvas::showObjectTag(QPointF pos)
|
||
{
|
||
QPointF l = screenToLogical(pos);
|
||
Curve* curveUnderCursor = findAt(l, 1.0);
|
||
if (Line* line = CURVE_AS_LINE(curveUnderCursor)) {
|
||
if (line != draggedCurve) {
|
||
QPointF p1(*line->p1.x, *line->p1.y);
|
||
QPointF p2(*line->p2.x, *line->p2.y);
|
||
QLineF info_line(p1, p2);
|
||
QString Text = QString("Line %1\nLength = %2mm")
|
||
.arg(line->get_tag() + 1)
|
||
.arg(info_line.length());
|
||
QToolTip::showText(mapToGlobal(pos.toPoint()), Text, this);
|
||
}
|
||
}
|
||
else if (Circle* circle = CURVE_AS_CIRCLE(curveUnderCursor)) {
|
||
if (circle != draggedCurve) {
|
||
QPointF center(*circle->center.x, *circle->center.y);
|
||
double current_radius = *circle->rad;
|
||
QString Text = QString("Circle %1\nRadius = %2mm")
|
||
.arg(circle->get_tag() + 1)
|
||
.arg(current_radius);
|
||
QToolTip::showText(mapToGlobal(pos.toPoint()), Text, this);
|
||
}
|
||
}
|
||
else
|
||
QToolTip::hideText();
|
||
}
|
||
#endif
|
||
|
||
// ===================================================================
|
||
// Вспомогательные методы
|
||
// ===================================================================
|
||
|
||
QPointF Canvas::screenToLogical(const QPointF& screenPos) const
|
||
{
|
||
QPointF logical = screenPos;
|
||
logical.rx() -= width() / 2.0;
|
||
logical.ry() -= height() / 2.0;
|
||
|
||
logical.rx() /= scaleFactor;
|
||
logical.ry() /= -scaleFactor;
|
||
|
||
return logical;
|
||
}
|
||
|
||
void Canvas::getCoincidentGroup(Point* p)
|
||
{
|
||
for (Point* other : points) {
|
||
if (other != p && areCoincident(p, other)) {
|
||
groups.insert(other);
|
||
break;
|
||
}
|
||
}
|
||
|
||
for (size_t i = 0; i < points.size(); ++i) {
|
||
for (size_t j = i + 1; j < points.size(); j++) {
|
||
if (*points[i] == *points[j] && groups.contains(points[j])) {
|
||
groups.insert(points[i]);
|
||
}
|
||
}
|
||
}
|
||
groups.insert(p);
|
||
}
|
||
|
||
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 (Curve* curve : curves) {
|
||
if (Line* line = CURVE_AS_LINE(curve)) {
|
||
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 (Circle * circle = CURVE_AS_CIRCLE(curve)) {
|
||
if (*circle->rad < EPS && after_constraint) {
|
||
sys.undoSolution();
|
||
flag = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (flag && after_constraint) {
|
||
QMessageBox::warning(this, QString("Error!"), QString("Last constraint is unavailable!"));
|
||
remove_constraint(constraints_count - 1);
|
||
C_Info.erase(constraints_count - 1);
|
||
constraints_count--;
|
||
}
|
||
else if (flag) {
|
||
sys.undoSolution();
|
||
}
|
||
after_constraint = false;
|
||
update();
|
||
}
|
||
|
||
// ===================================================================
|
||
// Очистка всего canvas
|
||
// ===================================================================
|
||
|
||
void Canvas::clearCanvas()
|
||
{
|
||
// Очистка солвера
|
||
sys.clear();
|
||
|
||
// Очистка динамически выделенной памяти
|
||
for (double* param : params) {
|
||
delete param;
|
||
}
|
||
|
||
for (Point* pt : points) {
|
||
delete pt;
|
||
}
|
||
|
||
for (Curve* curve : curves){
|
||
delete curve;
|
||
}
|
||
|
||
|
||
// Очистка контейнеров
|
||
params.clear();
|
||
points.clear();
|
||
curves.clear();
|
||
|
||
// Очистка контейнеров ограничений
|
||
parallelPairs.clear();
|
||
P2Ppairs.clear();
|
||
VERT_pairs.clear();
|
||
HORIZ_pairs.clear();
|
||
perpendicularPairs.clear();
|
||
tangentPairs.clear();
|
||
|
||
// Очистка информации об ограничениях
|
||
C_Info.clear();
|
||
|
||
// Сброс счетчиков
|
||
obj_count = 0;
|
||
constraints_count = 0;
|
||
|
||
// Очистка временных указателей
|
||
if (current_line) {
|
||
delete current_line;
|
||
current_line = nullptr;
|
||
}
|
||
|
||
if (firstPoint) {
|
||
delete firstPoint;
|
||
firstPoint = nullptr;
|
||
}
|
||
|
||
draggedPoint = nullptr;
|
||
draggedCurve = nullptr;
|
||
|
||
// Сброс режима
|
||
mode = Mode::None;
|
||
|
||
// Обновление отображения
|
||
update();
|
||
|
||
groups.clear();
|
||
}
|
||
|
||
void Canvas::ON_NONE(QPointF* scene)
|
||
{
|
||
Point* p = findPointAt(*scene);
|
||
if (p) {
|
||
draggedPoint = p;
|
||
getCoincidentGroup(draggedPoint);
|
||
dragOffset = *scene - QPointF(*p->x, *p->y);
|
||
return;
|
||
}
|
||
|
||
Line* found = CURVE_AS_LINE(findAt(*scene));
|
||
if (found) {
|
||
draggedCurve = found;
|
||
QPointF lineCenter(
|
||
(*found->p1.x + *found->p2.x) / 2.0,
|
||
(*found->p1.y + *found->p2.y) / 2.0
|
||
);
|
||
dragOffset = *scene - lineCenter;
|
||
return;
|
||
}
|
||
|
||
Circle* found_circle = CURVE_AS_CIRCLE(findAt(*scene));
|
||
if (found_circle) {
|
||
draggedCurve = found_circle;
|
||
QPointF center(*found_circle->center.x, *found_circle->center.y);
|
||
dragOffset = *scene - center;
|
||
return;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
void Canvas::ON_LINE(QPointF* pos)
|
||
{
|
||
scene = *pos;
|
||
|
||
if (!current_line) {
|
||
// Первый клик: создаем новую линию
|
||
current_line = new Line();
|
||
|
||
// Создаем координаты для точек линии
|
||
double* x1 = new double(scene.x());
|
||
double* y1 = new double(scene.y());
|
||
double* x2 = new double(scene.x());
|
||
double* y2 = new double(scene.y());
|
||
|
||
// Добавляем параметры в солвер
|
||
params.push_back(x1);
|
||
params.push_back(y1);
|
||
|
||
// Создаем точки и привязываем к линии
|
||
points.push_back(new Point(x1, y1, obj_count++));
|
||
points.push_back(new Point(x2, y2, obj_count++));
|
||
|
||
current_line->p1.x = x1;
|
||
current_line->p1.y = y1;
|
||
current_line->p2.x = x2;
|
||
current_line->p2.y = y2;
|
||
current_line->start_ref = points[points.size() - 2];
|
||
current_line->end_ref = points[points.size() - 1];
|
||
}
|
||
else {
|
||
// Второй клик: завершаем рисование линии
|
||
*current_line->p2.x = scene.x();
|
||
*current_line->p2.y = scene.y();
|
||
|
||
// Проверка минимальной длины линии
|
||
double len = sqrt(pow(*current_line->p2.x - *current_line->p1.x, 2) +
|
||
pow(*current_line->p2.y - *current_line->p1.y, 2));
|
||
|
||
if (len < 10) {
|
||
// Линия слишком короткая - отменяем создание
|
||
delete current_line;
|
||
current_line = nullptr;
|
||
|
||
// Удаляем созданные точки и параметры
|
||
delete points.back();
|
||
points.pop_back();
|
||
delete points.back();
|
||
points.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
delete params.back();
|
||
params.pop_back();
|
||
|
||
mode = Mode::None;
|
||
QMessageBox::critical(this, "WHOOPS",
|
||
"Sorry, your line is very short", QMessageBox::Ok);
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
// Добавляем координаты второй точки в параметры
|
||
params.push_back(current_line->p2.x);
|
||
params.push_back(current_line->p2.y);
|
||
|
||
current_line->set_tag(obj_count++);
|
||
// Завершаем создание линии
|
||
curves.append(current_line);
|
||
current_line = nullptr;
|
||
mode = Mode::None;
|
||
}
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
void Canvas::ON_CIRCLE(QPointF* pos)
|
||
{
|
||
scene = *pos;
|
||
if (!current_circle) {
|
||
current_circle = new Circle();
|
||
// Создаем координаты для центра окружности
|
||
double* x = new double(scene.x());
|
||
double* y = new double(scene.y());
|
||
|
||
points.push_back(new Point(x, y, obj_count++));
|
||
|
||
// Добавляем параметры в солвер
|
||
params.push_back(x);
|
||
params.push_back(y);
|
||
|
||
current_circle->center.x = x;
|
||
current_circle->center.y = y;
|
||
current_circle->center_ref = points[points.size() - 1];
|
||
}
|
||
else {
|
||
double* r = new double(dist_P2P(QPointF(current_circle->center.get_X(), current_circle->center.get_Y()), QPointF(scene.x(), scene.y())));
|
||
current_circle->rad = r;
|
||
|
||
params.push_back(r);
|
||
current_circle->set_tag(obj_count++);
|
||
curves.append(current_circle);
|
||
current_circle = nullptr;
|
||
mode = Mode::None;
|
||
}
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
void Canvas::ON_PARALLEL(QPointF* scene)
|
||
{
|
||
Line* found = CURVE_AS_LINE(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 (!areAlreadyParallel(found, current_line)) {
|
||
auto pair = makeOrderedPair<LinePair>(found, current_line);
|
||
sys.addConstraintParallel(*found, *current_line, constraints_count++);
|
||
parallelPairs.insert(pair);
|
||
C_Info[constraints_count - 1] = { Mode::Parallel, pair };
|
||
|
||
current_line = nullptr;
|
||
mode = Mode::None;
|
||
after_constraint = true;
|
||
solve_for_canvas();
|
||
}
|
||
else {
|
||
// Линии уже параллельны - сообщаем об ошибке
|
||
QMessageBox::warning(this,
|
||
QString("Wrong"),
|
||
QString("Parallel lines can not be more parallel!"),
|
||
QMessageBox::Ok
|
||
);
|
||
current_line = nullptr;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
}
|
||
void Canvas::ON_PERPENDICULAR(QPointF* scene)
|
||
{
|
||
Line* found = CURVE_AS_LINE(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<LinePair>(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::ON_P2P(QPointF* scene)
|
||
{
|
||
Point* clickedPoint = findPointAt(*scene);
|
||
if (!clickedPoint) {
|
||
firstPoint = nullptr;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
// Первый клик: выбираем первую точку
|
||
if (!firstPoint) {
|
||
firstPoint = clickedPoint;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
// Повторный клик на ту же точку: сброс выбора
|
||
if (clickedPoint == firstPoint) {
|
||
firstPoint = nullptr;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
// Находим линии, к которым принадлежат точки
|
||
Line* l1 = nullptr;
|
||
Line* l2 = nullptr;
|
||
|
||
for (Curve* curve : curves) {
|
||
if (Line* l = CURVE_AS_LINE(curve)) {
|
||
if (l->start_ref == firstPoint || l->end_ref == firstPoint)
|
||
l1 = l;
|
||
if (l->start_ref == clickedPoint || l->end_ref == clickedPoint)
|
||
l2 = l;
|
||
}
|
||
}
|
||
|
||
// Проверка на невозможность ограничения
|
||
if ((l1 == l2 && l1 && l2) || areCoincident(firstPoint, clickedPoint)) {
|
||
QMessageBox::critical(this, QString("NO!"), QString("P2P failed"));
|
||
firstPoint = nullptr;
|
||
mode = Mode::None;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
|
||
// Добавляем ограничение совпадения точек
|
||
sys.addConstraintP2PCoincident(*clickedPoint, *firstPoint, constraints_count++);
|
||
auto pair = makeOrderedPair<PointPair>(clickedPoint, firstPoint);
|
||
P2Ppairs.insert(pair);
|
||
C_Info[constraints_count - 1] = { Mode::Coincedent, pair };
|
||
|
||
firstPoint = nullptr;
|
||
mode = Mode::None;
|
||
after_constraint = true;
|
||
solve_for_canvas();
|
||
return;
|
||
}
|
||
void Canvas::ON_TANGENT(QPointF* scene)
|
||
{
|
||
// Ищем любой объект под курсором: линия или окружность
|
||
Curve* selected = findAt(*scene);
|
||
|
||
if (!selected) {
|
||
current_curve = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
// Если кликнули по уже выбранному объекту — сброс
|
||
if (current_curve && selected == current_curve) {
|
||
current_curve = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
// Первый выбор
|
||
if (!current_curve) {
|
||
current_curve = selected;
|
||
update(); // подсветим выбранный объект
|
||
return;
|
||
}
|
||
|
||
// Второй выбор — теперь у нас два объекта
|
||
Curve* first = current_curve;
|
||
Curve* second = selected;
|
||
|
||
// Проверка на уже существующее ограничение касательности
|
||
if (areAlreadyTangent(first, second)) {
|
||
QMessageBox::warning(this, "Ошибка",
|
||
"Эти объекты уже касательны!", QMessageBox::Ok);
|
||
current_curve = nullptr;
|
||
update();
|
||
return;
|
||
}
|
||
|
||
bool constraintAdded = false;
|
||
|
||
// Вариант 1: Линия → Окружность
|
||
if (Line* line = dynamic_cast<Line*>(first)) {
|
||
if (Circle* circle = dynamic_cast<Circle*>(second)) {
|
||
sys.addConstraintTangent(*line, *circle, constraints_count++);
|
||
constraintAdded = true;
|
||
}
|
||
}
|
||
// Вариант 2: Окружность → Линия
|
||
else if (Circle* circle = dynamic_cast<Circle*>(first)) {
|
||
if (Line* line = dynamic_cast<Line*>(second)) {
|
||
sys.addConstraintTangent(*line, *circle, constraints_count++); // порядок Line, Circle
|
||
constraintAdded = true;
|
||
}
|
||
}
|
||
// Вариант 3: Окружность → Окружность
|
||
if (Circle* c1 = dynamic_cast<Circle*>(first)) {
|
||
if (Circle* c2 = dynamic_cast<Circle*>(second)) {
|
||
sys.addConstraintTangent(*c1, *c2, constraints_count++);
|
||
constraintAdded = true;
|
||
}
|
||
}
|
||
|
||
// Если комбинация недопустима
|
||
if (!constraintAdded) {
|
||
QString message = "Недопустимая комбинация объектов для касания!\n"
|
||
"Поддерживаются: линия-окружность или окружность-окружность.";
|
||
QMessageBox::warning(this, "Ошибка", message, QMessageBox::Ok);
|
||
}
|
||
else {
|
||
// Сохраняем информацию об ограничении (для undo и удаления)
|
||
auto orderedPair = makeOrderedPair<CurvePair>(first, second);
|
||
tangentPairs.insert(orderedPair);
|
||
C_Info[constraints_count - 1] = { Mode::Tangent, orderedPair };
|
||
after_constraint = true;
|
||
}
|
||
|
||
// Сброс режима и выделения
|
||
current_curve = nullptr;
|
||
mode = Mode::None;
|
||
solve_for_canvas();
|
||
}
|
||
void Canvas::ON_HORIZ_VERT(QPointF* scene)
|
||
{
|
||
Line* found = CURVE_AS_LINE(findAt(*scene));
|
||
|
||
if (found) {
|
||
// Проверка: если линия уже вертикальна, нельзя сделать её горизонтальной
|
||
if (mode == Mode::Horizontal && isLineVertical(found)) {
|
||
QMessageBox::warning(this,
|
||
QString("Невозможно"),
|
||
QString("Эта линия уже вертикальна и не может быть горизонтальной!"),
|
||
QMessageBox::Ok
|
||
);
|
||
mode = Mode::None;
|
||
return;
|
||
}
|
||
|
||
// Проверка: если линия уже горизонтальна, нельзя сделать её вертикальной
|
||
else if (mode == Mode::Vertical && isLineHorizontal(found)) {
|
||
QMessageBox::warning(this,
|
||
QString("Невозможно"),
|
||
QString("Эта линия уже горизонтальна и не может быть вертикальной!"),
|
||
QMessageBox::Ok
|
||
);
|
||
mode = Mode::None;
|
||
return;
|
||
}
|
||
|
||
if (mode == Mode::Horizontal) {
|
||
sys.addConstraintHorizontal(*found, constraints_count++);
|
||
auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref);
|
||
HORIZ_pairs.insert(pair);
|
||
C_Info[constraints_count - 1] = { Mode::Horizontal, pair };
|
||
}
|
||
else {
|
||
sys.addConstraintVertical(*found, constraints_count++);
|
||
auto pair = makeOrderedPair<PointPair>(found->start_ref, found->end_ref);
|
||
VERT_pairs.insert(pair);
|
||
C_Info[constraints_count - 1] = { Mode::Vertical, pair };
|
||
}
|
||
solve_for_canvas();
|
||
after_constraint = true;
|
||
}
|
||
mode = Mode::None;
|
||
return;
|
||
}
|