在 2D 空间中随意缩放和旋转

发布于 2024-09-15 01:36:36 字数 728 浏览 3 评论 0原文

我正在尝试使用 QT 用 C++ 编写一个图形程序,用户可以使用鼠标缩放和旋转对象(就像 inkscape 或 CorelDraw 一样),但是经过几个月的尝试,我仍然无法使其工作。目前,它仅通过旋转或缩放来工作,但当用户想要以任意方式变换对象时则无法工作。 QT中有一个关于仿射变换的例子,但它非常简单(例如,它使用单个因子而不是x和Y因子进行缩放),它不提供缩放方向或固定缩放点)所以我不知道如何扩展它或使用它。

这就是程序的预期行为:

  1. 用户在画布中放置一个多边形。
  2. 如果用户单击多边形,则对象周围将出现一组蓝色框。这些框用于在任何方向(例如,上、下、左、右等)缩放对象。
  3. 如果用户在多边形中再次单击,则对象周围将出现一组红色框。这些框用于向任意方向旋转对象。

那么,我如何至少实现以下功能:

  1. 如果用户单击顶部蓝色框(向上缩放),按住左键并将鼠标向上移动,如何使多边形向上缩放?我需要刻度方向吗?我需要通用定点缩放吗?当鼠标向上移动时,如何计算比例因子,以便“实时”缩放多边形?

在我看来,以下代码可以使其工作: 在此处查看代码 但是它不起作用:-(。如果您能帮助我更好地实施,我将不胜感激。

很抱歉提出了很多问题,但我感到非常沮丧。

谢谢, 卡洛斯.

I'm trying to write a graphical program in C++ with QT where users can scale and rotate objects with the mouse (just like inkscape or CorelDraw does), however after many months trying to make it happen I still cannot make it work. It currently works for example by just rotating or just scaling, but not when the user want to transform the object in an arbitrary way. There is an example in QT about affine transformation but it is very simple (e.g., it scales using a single factor not x and Y factors), it not provides scale directions or fixed scaling point) so I don't know how to extend it or use it.

This is how the program is expected to behave:

  1. The user drop a polygon in the canvas.
  2. If the user clicks on the polygon a set of blue boxes will appear around the object. These boxes are used to scale the object in any direction (e.g., up, down, left, right, etc)
  3. If the user clicks again in the polygon a set of red boxes will appear around the object. These boxes are used to rotate the object in any direction.

So, how can I implement at least the following:

  1. If the user click on the top blue box (scale towards up), hold the left button and moves the mouse toward up, how can I make the polygon to scale towards up? Do I need scale direction? Do I need a general Fixed-point of scaling? How can I calculate the scale factors as the mouse move towards up so the polygon is scaled in "real time"?

Here is the code that in my perspective could make it work: See the code here But it does not work :-( . If you can help me with a better implementation I will appreciate it.

Sorry to put to many questions but I am completely frustrated.

Thanks,
Carlos.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沉默的熊 2024-09-22 01:36:36

无法使其工作

结果是错误的

没有很好地描述您的问题。

基本上我不知道矩阵的串联/乘法需要什么

基本上我不知道对象存储中 :
1. 位置
2. 旋转
3.缩放

当需要绘制对象时,按以下顺序进行操作:
1. 使用存储的比例因子进行缩放
2. 使用存储的角度旋转
3. 转换到位置

给定比例因子 s 和旋转角度 r,围绕任意点(px、py)旋转/缩放对象(点数组或其他),执行以下操作:
1. 将 object 转换为 -px, -py 。即对于每个顶点都执行 vertex -= p;
2. 缩放对象。对于每个顶点做 vertex *= s
3. 旋转对象。使用角度 r 围绕零点旋转每个顶点。
4. 将对象转换为 px、py

另外,我建议您查看 "Affine Qt 4 中的“Transformations” 演示。要查看演示,请启动 qtdemo,选择“Demonstrations->Affine Transformations”。
考虑聘请一位几何导师。 “月”太长,无法处理旋转/缩放/平移问题。

但是,我不知道如何以正确的顺序组合这些功能

如果您围绕同一点旋转和缩放,则操作的顺序并不重要。

--编辑--

实例:

Picture

点表示枢轴、变换开始和变换结束。
线框字母代表原始图像。
红色字母代表“旋转和均匀缩放”变换。
绿色字母代表“2D 尺度”变换。

对于这两种变换,您都需要枢轴、开始拖动形状的点以及停止拖动形状的点。

我不会再解释这一点。

变换测试.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();
}

cannot make it work

the result is just wrong

Doesn't describe your problem very well.

Basically I don't know what is needed in terms of the concatenation/multiplications of matrices

In object store:
1. position
2. rotation
3. scale

When you need to draw object, perform operations in this order:
1. Scale using stored scale factor
2. Rotate using stored angle
3. Translate to position

Given scale factor s and rotation angle r, to rotate/scale object (point array, or whatever) around arbitrary point (p.x, p.y), do this:
1. Translate object to -p.x, -p.y . I.e. for every vertex do vertex -= p;
2. Scale object. For every vertex do vertex *= s
3. Rotate object. Rotate every vertex around point zero using angle r.
4. Translate object to p.x, p.y.

Also I'd recommend to take a look at "Affine Transformations" demo in Qt 4. To view demo, launch qtdemo, select "Demonstrations->Affine Transformations".
Consider hiring a geometry tutor. "Months" is too long to deal with rotate/scale/translate problem.

But, I have no clue on how to combine of these function in a proper order

If you're rotating and scaling around same point, the order of operations doesn't matter.

--EDIT--

Live example:

Picture

Points indicate pivot, start of transform, and end of transform.
Wireframe letters represent original image.
Red letter represent "rotate and uniformly scale" transform.
Green letters represent "2D scale" transform.

For both transform you need pivot, point where you began to drag shape, and point where you stopped dragging shape.

I will not ever explain this again.

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();
}
随心而道 2024-09-22 01:36:36

基本上,您有一个点(或一系列点)想要通过两个线性变换 R(旋转)和 S(缩放)进行变换。所以你正在尝试计算诸如

R(S(x))

x 是一个点之类的东西。如果您使用矩阵表示这些运算,那么执行连续运算相当于将矩阵相乘,即

R*S*x

不幸的是,您没有提供足够的信息让我更具体...您可以发布一些代码(只是小的相关部分) )显示你在做什么?你所说的“自然方式”是什么意思?你的结果是“完全错误”怎么办?

Basically, you have a point (or series of points) that you want to transform with two linear transformations, R (rotation) and S (scaling). So you're trying to calculate something like

R(S(x))

where x is a point. If you represent these operations using matrices, then performing consecutive operations is equivalent to multiplying the matrices, i.e.

R*S*x

Unfortunately, you haven't given enough information for me to be more specific...could you post some code (just the small, relevant parts) showing what you're doing? What do you mean by "natural way"? What about your result is "just wrong"?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文