По желанию масштабирование и вращение в 2D пространстве - PullRequest
0 голосов
/ 19 августа 2010

Я пытаюсь написать графическую программу на C ++ с QT, где пользователи могут масштабировать и вращать объекты с помощью мыши (как это делает inkscape или CorelDraw), однако после многих месяцев, пытаясь это сделать, я все еще не могу заставить ее работать , В настоящее время он работает, например, просто вращая или масштабируя, но не тогда, когда пользователь хочет преобразовать объект произвольным образом. В QT есть пример об аффинном преобразовании, но он очень прост (например, он масштабируется с использованием одного фактора, а не факторов x и Y), он не предоставляет направления масштабирования или фиксированную точку масштабирования), поэтому я не знаю, как его расширить или используйте его.

Вот как должна вести себя программа:

  1. Пользователь перетаскивает многоугольник на холст.
  2. Если пользователь нажимает на многоугольник, вокруг объекта появится набор синих прямоугольников. Эти поля используются для масштабирования объекта в любом направлении (например, вверх, вниз, влево, вправо и т. Д.)
  3. Если пользователь снова щелкнет в многоугольнике, вокруг объекта появится набор красных прямоугольников. Эти поля используются для поворота объекта в любом направлении.

Итак, как мне реализовать хотя бы следующее:

  1. Если пользователь нажимает на верхнюю синюю рамку (масштабирование вверх), удерживает левую кнопку и перемещает мышь вверх, как я могу заставить полигон масштабироваться вверх? Мне нужно направление шкалы? Нужна ли общая фиксированная точка масштабирования? Как рассчитать масштабные коэффициенты, когда мышь перемещается вверх, чтобы многоугольник масштабировался в «реальном времени»?

Вот код, который, с моей точки зрения, может заставить его работать: См. Код здесь Но он не работает :-(. Если вы можете помочь мне с лучшей реализацией, я буду признателен.

Извините, что задал много вопросов, но я полностью разочарован.

Спасибо, Карлос.

Ответы [ 2 ]

2 голосов
/ 19 августа 2010

не может заставить его работать

результат неправильный

Не очень хорошо описывает вашу проблему.

В принципе, я не знаю, что нужно с точки зрения конкатенации / умножения матриц

В хранилище объектов:
1. должность
2. вращение
3. шкала

Когда вам нужно нарисовать объект, выполните операции в следующем порядке:
1. Масштабирование с использованием сохраненного масштабного коэффициента
2. Поверните, используя сохраненный угол
3. Перевести в положение

Учитывая масштабный коэффициент s и угол поворота r, чтобы вращать / масштабировать объект (точечный массив или что-то еще) вокруг произвольной точки (p.x, p.y), сделайте следующее:
1. Переведите объект в -p.x, -p.y. То есть для каждой вершины сделать вершину - = p;
2. Масштабируемый объект. Для каждой вершины сделать вершину * = s
3. Поверните объект. Поверните каждую вершину вокруг нулевой точки, используя угол r.
4. Переведите объект в p.x, p.y.

Также я бы порекомендовал взглянуть на «Аффинные преобразования» демо в Qt 4. Чтобы просмотреть демо, запустите qtdemo, выберите «Демонстрации-> Аффинные преобразования».
Рассмотрите возможность найма репетитора по геометрии. «Месяцы» слишком длинные, чтобы справиться с проблемой поворота / масштабирования / перевода.

Но я понятия не имею, как объединить эти функции в правильном порядке

Если вы вращаетесь и масштабируете одну и ту же точку, порядок операций не имеет значения.

- EDIT -

Живой пример:

Picture

Точки указывают на поворот, начало преобразования и конец преобразования. Каркасные буквы представляют исходное изображение.
Красная буква представляет преобразование «повернуть и равномерно масштабировать».
Зеленые буквы представляют преобразование «2D масштаб».

Для обоих преобразований вам нужен поворот, точка, в которой вы начали перетаскивать фигуру, и точка, в которой вы перестали перетаскивать фигуру.

Я никогда не буду объяснять это снова.

transformtest.pro:

TEMPLATE = app
TARGET = 
DEPENDPATH += .
INCLUDEPATH += .

# Input
HEADERS += MainWindow.h
SOURCES += main.cpp MainWindow.cpp

main.cpp:

#include <QApplication>
#include "MainWindow.h"

int main(int argc, char** argv){
    QApplication app(argc, argv);
    MainWindow window;
    window.show();

    return app.exec();
}

MainWindow.h:

#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H

#include <QGLWidget>
class QPaintEvent;

class MainWindow: public QWidget{
Q_OBJECT
public:
    MainWindow(QWidget* parent = 0);
protected slots:
    void updateAngle();
protected:
    void paintEvent(QPaintEvent* ev);
    float angle;
    float distAngle;
};

#endif

MainWindow.cpp:

#include "MainWindow.h"
#include <QTimer>
#include <QPainter>
#include <QColor>
#include <QVector2D>
#include <math.h>

static const int timerMsec = 50;
static const float pi = 3.14159265f;

MainWindow::MainWindow(QWidget* parent)
:QWidget(parent), angle(0), distAngle(0){
    QTimer* timer = new QTimer(this);
    timer->start(timerMsec);
    connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    connect(timer, SIGNAL(timeout()), this, SLOT(updateAngle()));
}

float randFloat(){
    return (qrand()&0xFF)/255.0f;
}

float randFloat(float f){
    return randFloat()*f;
}

inline QVector2D perp(const QVector2D v){
    return QVector2D(-v.y(), v.x());
}

void MainWindow::updateAngle(){
    angle = fmod(angle + pi*5.0f/180.0f, pi*2.0f);
    distAngle = fmod(distAngle + pi*1.0f/180.0f, pi*2.0f);
}

QTransform buildRotateScale(QVector2D pivot, QVector2D start, QVector2D end){
    QVector2D startDiff = start - pivot;
    QVector2D endDiff = end - pivot;
    float startLength = startDiff.length();
    float endLength = endDiff.length();
    if (startLength == 0)   
        return QTransform();
    if (endLength == 0) 
        return QTransform();

    float s = endLength/startLength;
    startDiff.normalize();
    endDiff.normalize();

    QVector2D startPerp = perp(startDiff);
    float rotationAngle = acos(QVector2D::dotProduct(startDiff, endDiff))*180.0f/pi;
    if (QVector2D::dotProduct(startPerp, endDiff) < 0)
        rotationAngle = -rotationAngle;

    return QTransform().translate(pivot.x(), pivot.y()).rotate(rotationAngle).scale(s, s).translate(-pivot.x(), -pivot.y());
}

QTransform buildScale(QVector2D pivot, QVector2D start, QVector2D end){
    QVector2D startDiff = start - pivot;
    QVector2D endDiff = end - pivot;
    float startLength = startDiff.length();
    float endLength = endDiff.length();
    if ((startDiff.x() == 0)||(startDiff.y() == 0))
        return QTransform();
    QVector2D s(endDiff.x()/startDiff.x(), endDiff.y()/startDiff.y());

    return QTransform().translate(pivot.x(), pivot.y()).scale(s.x(), s.y()).translate(-pivot.x(), -pivot.y());
}

void MainWindow::paintEvent(QPaintEvent* ev){
    QPainter painter(this);
    QPointF pivot(width()/2, height()/2);
    QPointF transformStart(pivot.x() + 100.0f, pivot.y() - 100.0f);
    float r = sinf(distAngle)*100.0f + 150.0f;
    QPointF transformEnd(pivot.x() + r*cosf(angle), pivot.y() - r*sinf(angle));

    painter.fillRect(this->rect(), QBrush(QColor(Qt::white)));
    QPainterPath path;
    QString str(tr("This is a test!"));
    QFont textFont("Arial", 40);
    QFontMetrics metrics(textFont);
    QRect rect = metrics.boundingRect(str);
    path.addText(QPoint((width()-rect.width())/2, (height()-rect.height())/2), textFont, str);

    painter.setPen(QColor(200, 200, 255));
    painter.drawPath(path);
    painter.setTransform(buildRotateScale(QVector2D(pivot), QVector2D(transformStart), QVector2D(transformEnd)));
    painter.fillPath(path, QBrush(QColor(255, 100, 100)));
    painter.setPen(QColor(100, 255, 100));
    painter.setTransform(buildScale(QVector2D(pivot), QVector2D(transformStart), QVector2D(transformEnd)));
    painter.fillPath(path, QBrush(QColor(100, 255, 100)));
    painter.setTransform(QTransform());

    QPainterPath coords;
    r = 10.0f;
    coords.addEllipse(pivot, r, r);
    coords.addEllipse(transformStart, r, r);
    coords.addEllipse(transformEnd, r, r);
    painter.setPen(QPen(QBrush(Qt::red), 5.0f));
    painter.setBrush(QBrush(QColor(127, 0, 0)));
    painter.setPen(QPen(QBrush(Qt::green), 5.0f));
    painter.drawLine(QLineF(pivot, transformStart));
    painter.setPen(QPen(QBrush(Qt::blue), 5.0f));
    painter.drawLine(QLineF(transformStart, transformEnd));
    painter.setPen(Qt::red);
    painter.drawPath(coords);
    painter.end();
}
1 голос
/ 19 августа 2010

По сути, у вас есть точка (или серия точек), которую вы хотите преобразовать с помощью двух линейных преобразований, R (вращение) и S (масштабирование). Итак, вы пытаетесь вычислить что-то вроде

R(S(x))

где х - это точка. Если вы представляете эти операции с использованием матриц, то выполнение последовательных операций эквивалентно умножению матриц, т.е.

R*S*x

К сожалению, вы не дали достаточно информации, чтобы я мог быть более конкретным ... не могли бы вы опубликовать некоторый код (только небольшие, релевантные части), показывающий, что вы делаете? Что вы подразумеваете под "естественным путем"? А как насчет твоего результата "просто неправильно"?

...