简体   繁体   English

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

[英]At will scaling and rotation in a 2D space

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. 我正在尝试用QT用C ++编写一个图形程序,用户可以用鼠标缩放和旋转对象(就像inkscape或CorelDraw一样),但是经过数月的尝试,我仍然无法使它工作。 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 (eg, 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. QT中有一个关于仿射变换的示例,但是它非常简单(例如,它使用单个因子而不是x和Y因子进行缩放),它不提供缩放方向或固定缩放点),所以我不知道如何扩展它或使用它。

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 (eg, 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. 谢谢,卡洛斯。

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 1.位置
2. rotation 2.旋转
3. scale 3.规模

When you need to draw object, perform operations in this order: 当需要绘制对象时,请按以下顺序执行操作:
1. Scale using stored scale factor 1.使用存储的比例因子进行缩放
2. Rotate using stored angle 2.使用存储的角度旋转
3. Translate to position 3.翻译到位置

Given scale factor s and rotation angle r, to rotate/scale object (point array, or whatever) around arbitrary point (px, py), do this: 给定比例因子s和旋转角度r,以围绕任意点(px,py)旋转/缩放对象(点阵列或其他对象),请执行以下操作:
1. Translate object to -px, -py . 1.将对象转换为-px,-py。 Ie for every vertex do vertex -= p; 即每个顶点都做顶点-= p;
2. Scale object. 2.缩放对象。 For every vertex do vertex *= s 对于每个顶点,执行顶点* = s
3. Rotate object. 3.旋转对象。 Rotate every vertex around point zero using angle r. 使用角度r绕零点旋转每个顶点。
4. Translate object to px, py 4.将对象翻译为px,py

Also I'd recommend to take a look at "Affine Transformations" demo in Qt 4. To view demo, launch qtdemo, select "Demonstrations->Affine Transformations". 另外,我建议您查看Qt 4中的“仿射变换”演示。要查看该演示,请启动qtdemo,选择“演示->仿射变换”。
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: 现场示例:

图片

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. 绿色字母代表“ 2D比例”转换。

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: transformtest.pro:

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

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

main.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: 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: 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();
}

Basically, you have a point (or series of points) that you want to transform with two linear transformations, R (rotation) and S (scaling). 基本上,您有一个点(或一系列点)要通过两个线性变换R(旋转)和S(缩放)进行变换。 So you're trying to calculate something like 所以您正在尝试计算类似

R(S(x))

where x is a point. 其中x是一个点。 If you represent these operations using matrices, then performing consecutive operations is equivalent to multiplying the matrices, ie 如果您使用矩阵表示这些运算,则执行连续运算等效于将矩阵相乘,即

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"? 您的结果如何呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM