views:

74

answers:

2

Hi,

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.

+1  A: 

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"?

Kelsey Rider
Hi, the relevant code is a bit big for this comment box. So I posted it at: http://www.qlands.com/other_files/transform_code.txtBy natural way I mean, the it should behave just like any other graphical software like inkcape, OO Draw, etc.
QLands
Hi, I also posted a screen shot describing the problem: http://www.qlands.com/other_files/scale_error.pdf
QLands
+1  A: 

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();
}
SigTerm
Hi, here is a screen shot describing the problem: http://www.qlands.com/other_files/scale_error.pdf. The relevant code used is here: http://www.qlands.com/other_files/transform_code.txtThanks
QLands
Hi, you suggested using a scale factor S, however how can it work with independent horizontal and vertical factors and a scale direction?Thanks.
QLands
@qlands: "Hi, here is a screen shot describing the problem:" clearly indicates a bug in your code. "The relevant code used is here" I'm a bit busy to fully check it out (or to write alternative implementation), but in my opinion it is more complicated than it should be. You could simply create function that returns QTransform based on pivot, start point and end point of scale|rotation. If you want someone to check the code, you should put it into your question, preferably along with screenshot.
SigTerm
@qlands: "however how can it work with independent horizontal and vertical factors and a scale direction" yes, it can.
SigTerm
"You could simply create function that returns QTransform based on pivot, start point and end point of scale|rotation" I don't know what you mean or how to implement it. I cannot paste the code in the question because it looses all the format. I use <code> </code> but it does not work.
QLands
@qlands: I've added example, but for the future reference - code formatting is explained to the right from the window where you enter the code while asking question Here: http://stackoverflow.com/editing-help. Also, if you have real trouble doing something (rotate/scale), I recommend to hire somebody to write code for you. It is all explained in documentation and tutorials.
SigTerm
I saw your example and tried to apply it to my application, but I can't make it work, I have no clue what I am doing wrong. Do you know anyone that I could hire to code it for me? I work in Kenya and I don't know anyone here who could help me. Many thanks.
QLands
@qlands: "Do you know anyone that I could hire to code it for me?" You should be able to hire someone using vworker.com, getafreelancer.com guru.com or similar freelancing resource - there are many of those, but I can't recommend any specific one. Also I don't know what is average wage in your area (i.e. whether hiring a freelancer will be a good deal or too costly for you). Also, hiring a freelancer is not the only way to find a person for your job. You can find someone by asking people around (friends, relatives, coworkers, etc)
SigTerm
@qlands: Also, try various forums (including local forums). It is possible to meet a person in good mood that would explain or solve problem for you, and it is possible that you'll find someone from same country or town (if you use local resources). In my opinion, your main trouble lies with understanding geometry parts (i.e. how to locate/translate object), so finding geometry teacher/tutor instead of hiring a programmer may help you more than hiring someone to code solution for you. Also, finding a teacher may be easier than finding a programmer, depending on your region.
SigTerm
@qlands: I probably won't be able provide any more help. Have a nice day.
SigTerm