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. It currently works for example by just rotating or just scaling, but not when the user want to transform the object in an arbitrary w开发者_开发百科ay. 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:
- The user drop a polygon in the canvas.
- 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)
- 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:
- 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
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:
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();
}
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"?
精彩评论