I'm trying to rotate a rectangle using a slider.
The slider has -100 to +100 values. I'd like to use this to rotate the rectangle.
I have tried a few things, including calculating the corners of the rectangle and then messing around with a matrix, thus unsuccessful.
(I prefer using the matrix)
An easier solution would be to define fixed points from the beginning, instead of only working with the sides of the rectangle.
In the code below I can resize the rectangle using a slider.
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
a = 230; //positioning
b = 150; //positioning
c = 200; //size of drawing
d = 150; //size of drawing
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(update()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.fillRect(a - ui->horizontalSlider->value(),
b - ui->horizontalSlider->value(),
c + ui->horizontalSlider->value() * 2,
d + ui->horizontalSlider->value() * 2, Qt::green);
}
Any form of help is much appreciated.
You can move your painter and rotate canvas to achieve it:
void Dialog::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.save();
painter.translate(a, b);//Move pen.
painter.rotate(30);//rotate canvas.
painter.fillRect(-ui->horizontalSlider->value(),
-ui->horizontalSlider->value(),
c + ui->horizontalSlider->value() * 2,
d + ui->horizontalSlider->value() * 2, Qt::green);
painter.restore();
}
Firstly here is the rotation matrix that will rotate any point in X-Y space around the Z axis (which is facing you) by the angle given by t radians:
R = [ cos(t), -sin(t);
sin(t), cos(t)]
To get the rotate point you must be rotating around a specific point on the X-Y plane. I assume you want to rotate the rect around its centre.
Let c be the pixel coordinates of the centre:
c = [cx;
cy]
The rotated points are then:
P = R*(p-c) + c
where P is the rotated point and p is the rectangle point to rotate, both are [2x1] vectors, as is c. Note that I am using Matrix algebra, so the multiplication is matrix multiplication, not scalar multiplication.
The next issue is then drawing the rectangle. I am not familiar with Qt rect drawing but from the documentation, doing what you want with fillRect will not work, as it assumes that the rectangle is unrotated.
You can use drawPolygon to do what you want, when you have the rotated coordinates.
I solved it by using "painter.drawLine( ax, ay, bx, by );" to draw the rectangle. Afterwards I used the following formula for each line:
double ax = MidPointWidth + (-(Size + minWidth)) * cos(Angle) - (Size + minHeight) * sin(Angle);
double ay = MidPointHeight + (-(Size + minWidth)) * sin(Angle) + (Size + minHeight) * cos(Angle);
Hope this will help someone.
Related
I'm trying to convert a viewport click onto a world position for an object.
It would be quite simple if all I wanted was to draw a point exactly where the user clicks in the canvas:
void Canvas::getClickPosition(int x, int y, Vector3d(&out)[2]) const
{
Vector4d point4d[2];
Vector2d point2d(x, y);
int w = canvas.width();
int h = canvas.height();
Matrix4d model = m_world * m_camera;
for (int i = 0; i < 2; ++i) {
Vector4d sw(point2d.x() / (0.5 * w) - 1,
point2d.y() / (0.5* h) - 1, i * 1, 1);
point4d[i] = (m_proj * model).inverse() * sw;
out[i] = point4d.block<1, 3>(0, 0);
}
}
The expected behavior is achieved with this simple code.
The problem arises when I try to actually make a line that will look like a one pixel when the user first clicks it. Until the camera is rotated in any direction it should look like it was perfectly shot from the camera and that it has whatever length (doesn't matter).
I tried the obvious:
Vector4d sw(point2d.x() / (0.5 * w) - 1,
point2d.y() / (0.5* h) - 1, 1, 1); // Z is now 1 instead of 0.
The result is, as most of you guys should expect, a line that pursues the vanishing point, at the center of the screen. Therefore, the farther I click from the center, the more the line is twitched from it's expected direction.
What can I do to have a line show as a dot from the click point of view, no matter where at the screen?
EDIT: for better clarity, I'm trying to draw the lines like this:
glBegin(GL_LINES);
line.p1 = m_proj * (m_world * m_camera) * line.p1;
line.p2 = m_proj * (m_world * m_camera) * line.p2;
glVertex3f(line.p1.x(), line.p1.y(), line.p1.z());
glVertex3f(line.p2.x(), line.p2.y(), line.p2.z());
glEnd();
Your initial attempt is actually very close. The only thing you are missing is the perspective divide:
out[i] = point4d.block<1, 3>(0, 0) / point4d.w();
Depending on your projection matrix, you might also need to specify a z-value of -1 for the near plane instead of 0.
And yes, your order of matrices projection * model * view seems strange. But as long as you keep the same order in both procedures, you should get a consistent result.
Make sure that the y-axis of your window coordinate system is pointing upwards. Otherwise, you will get a result that is reflected at the horizontal midline.
Context
I try to draw pie chart for statistic in my game. I'm using Cocos2d-x ver.3.8.1. Size of the game is important, so I won't to use third-party frameworks to create pie charts.
Problem
I could not find any suitable method in Cocos2d-x for drawing part of the circle.
I tried to do
I tried to find a solution to this problem in Internet, but without success.
As is known, sector of a circle = triangle + segment. So, I tried to use the method drawSegment() from DrawNode also.
Although it has parameter radius ("The segment radius" written in API reference), radius affects only the thickness of the line.
drawSegment() method draw a simple line, the thickness of which is set by a method call.
Question
Please prompt me, how can I draw a segment or a sector of a circle in Cocos2d-x?
Any advice will be appreciated, thanks.
I think the one of the ways to draw a sector of a circle in Cocos2d-X is the way to use drawPolygon on DrawNode. I wrote little sample.
void drawSector(cocos2d::DrawNode* node, cocos2d::Vec2 origin, float radius, float angle_degree,
cocos2d::Color4F fillColor, float borderWidth, cocos2d::Color4F bordercolor,
unsigned int num_of_points = 100)
{
if (!node)
{
return;
}
const cocos2d::Vec2 start = origin + cocos2d::Vec2{radius, 0};
const auto angle_step = 2 * M_PI * angle_degree / 360.f / num_of_points;
std::vector<cocos2d::Point> circle;
circle.emplace_back(origin);
for (int i = 0; i <= num_of_points; i++)
{
auto rads = angle_step * i;
auto x = origin.x + radius * cosf(rads);
auto y = origin.y + radius * sinf(rads);
circle.emplace_back(x, y);
}
node->drawPolygon(circle.data(), circle.size(), fillColor, borderWidth, bordercolor);
}
This is the function to calculate the position of edge point of circle and draw polygon. If you want to use it, you need to call like following,
auto canvas = DrawNode::create();
drawSector(canvas, cocos2d::Vec2(400, 400), 100, 60, cocos2d::Color4F::GREEN, 2, cocos2d::Color4F::BLUE, 100);
this->addChild(triangle);
The result would be like this. I think the code will help your problem.
Qt uses a 3x3 transformation matrix for both perspective and affine transformations. A matrix is considered affine if the last row of the matrix equals [0 0 1]. For this reason, the x- and y-axis rotation matrices are "non-affine" and perspective distortion results. But there are further implications. Every pixel the user paints has the coordinates (x, y, 1). There is no way to set the z coordinate to 0, rotate and set z to something else after. Is it still possible to somehow fake being able to rotate around an arbitrary point? Perhaps by setting z to some value "close" to zero and then rotating?
EDIT:
What I want to do, exactly. By crossing out the 3rd row and column of the "usual" perspective projection matrix, one can obtain one usable with Qt. I also use a window matrix to transform into the QGraphicsItem. That makes for 2 matrices for now:
window * projection
Some code:
float const w(rect.width());
float const h(rect.height());
// aspect ratio
auto const ar(h / w);
// symmetrical infinite frustum
f_[4] = 1.f;
f_[1] = ::std::tan(.5f * hFov_) * f_[4];
f_[0] = -f_[1];
f_[3] = ar * f_[1];
f_[2] = -f_[3];
// perspective projection matrix
auto const rml(f_[1] - f_[0]);
auto const tmb(f_[3] - f_[2]);
::etl::matrix<float, 3, 3> const pMatrix{
2.f * f_[4] / rml, 0.f , (f_[1] + f_[0]) / rml,
0.f , 2.f * f_[4] / tmb, (f_[3] + f_[2]) / tmb,
0.f , 0.f , -1.f};
auto const halfW(.5f * w);
auto const halfH(.5f * h);
// window matrix
::etl::matrix<float, 3, 3> const wMatrix{
halfW, 0.f , halfW,
0.f , -halfH, halfH,
0.f , 0.f , 1.f};
wpvMatrix_ = wMatrix * pMatrix;
Now we want to transform a QPixmap into the world we project. This is done, for example with this matrix:
zMatrix =
worldPixmapWidth / pixmap.width(), 0, p(0),
0, -worldPixmapHeight / pixmap.height(), p(1),
0, 0, p(2);
So we can use wMatrix * pMatrix * zMatrix to transform the pixmap.
p is the point we want to translate the QPixmap to. Now that the pixmap is at p = (p(0), p(1), p(2)). We would like to rotate around an axis parallel to the y axis, going through p. How to do it? Usually, we would do T(p) * Ry(phi) * T(-p), but we can't, as there is no translation matrix, that could set the z coordinate. Applying Ry directly will rotate around the origin. Even without doing any transformations at all, z will be 1, not 0, as we would like it to be for Ry.
How to rotate a vertex around a certain point?
The order of when you do a translation, then a rotation, and then translating back will effectively rotate about whatever point you translate to and from.
This applies the same way with matrix transformations and the order that they are called and applied.
UPDATE:
After reading your question over again, it sounds like what you are really asking for is a 3D projection of a QPixmap to simulate it in the process of flipping over, like a playing card flipping over in a card game. This rotation about some axis parallel to the y-axis needs to show the proper distortions to make it look like one side of the image is farther away and the other side is closer.
http://en.wikipedia.org/wiki/3D_projection
Qt's rotations plan on you using the third 3x3 matrix in the equation above. Rotations about the z axis.
Or even more specifically, you can see it here:
http://qt-project.org/doc/qt-5.1/qtgui/qmatrix.html#details, it shows the 2x2 matrix with added rows to account for a translation.
Now with all that being said... this is the closest thing I could find for faking it without using a full on 3D graphics library, like OpenGL:
http://qt-project.org/forums/viewthread/18615/
http://gerrysweeney.com/horizontal-and-vertical-flip-transformations-of-a-qgraphicsitem-in-qt-qgraphicsview/
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include <QPropertyAnimation>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
QGraphicsScene * m_pScene;
QGraphicsView * m_pView;
QSlider * sliderx;
QSlider * slidery;
QGraphicsPolygonItem* transformedItem;
QPointF itemCenter;
public slots:
void updateRotation();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setCentralWidget(new QWidget);
QVBoxLayout * layout = new QVBoxLayout;
m_pScene = new QGraphicsScene(0,0,800,480);
m_pView = new QGraphicsView(m_pScene);
m_pView->setFrameStyle(QFrame::NoFrame);
m_pView->setGeometry(0,0,800,480);
m_pView->setAutoFillBackground(false);
layout->addWidget(m_pView);
sliderx = new QSlider(Qt::Horizontal);
slidery = new QSlider(Qt::Horizontal);
sliderx->setRange(-100,100);
sliderx->setSingleStep(1);
sliderx->setValue(100);
slidery->setRange(-100,100);
slidery->setSingleStep(1);
slidery->setValue(100);
QObject::connect(sliderx, SIGNAL(valueChanged(int)),this, SLOT(updateRotation()));
QObject::connect(slidery, SIGNAL(valueChanged(int)),this, SLOT(updateRotation()));
layout->addWidget(sliderx);
layout->addWidget(slidery);
this->centralWidget()->setLayout(layout);
QPolygonF polygon;
polygon << QPointF(100.0,250.0);
polygon << QPointF(170.0, 350.0);
polygon << QPointF(30.0, 350.0);
QGraphicsPolygonItem* testItem = new QGraphicsPolygonItem(polygon);
m_pScene->addItem(testItem);
transformedItem = new QGraphicsPolygonItem(polygon);
transformedItem->setPen(QColor(Qt::red));
m_pScene->addItem(transformedItem);
// Here the fun starts:
itemCenter = transformedItem->mapToParent(transformedItem->boundingRect().center());
// // Method 1
// QTransform transform = QTransform();
// transform.translate(itemCenter.x(),
// itemCenter.y());
// transform.scale(1.0, -1.0);
// transform.translate(-itemCenter.x(),
// -itemCenter.y());
// transformedItem->setTransform(transform);
// // Method 2
// transformedItem->setTransform(QTransform::fromTranslate(itemCenter.x(),
// itemCenter.y()),true);
// transformedItem->setTransform(QTransform::fromScale(1.0, -1.0),true);
// transformedItem->setTransform(QTransform::fromTranslate(-itemCenter.x(),
// -itemCenter.y()), true);
// // Method 3
// transformedItem->translate(itemCenter.x(),
// itemCenter.y());
// transformedItem->scale(1.0, -1.0);
// transformedItem->translate(-itemCenter.x(),
// -itemCenter.y());
}
void MainWindow::updateRotation()
{
transformedItem->resetTransform();
transformedItem->translate(itemCenter.x(),
itemCenter.y());
transformedItem->scale((qreal)sliderx->value()/100., (qreal)slidery->value()/100.);
transformedItem->translate(-itemCenter.x(),
-itemCenter.y());
}
MainWindow::~MainWindow() { }
Hope that helps.
I've got a class: mySquare which inherits from QGraphicsRectItem
added only my constructor, painter and an animation:
ANIMATION:
void mySquare::animation(mySquare *k)
{
QTimeLine *timeLine = new QTimeLine();
timeLine->setLoopCount(1);
QGraphicsItemAnimation *animation = new QGraphicsItemAnimation();
animation->setItem(k);
animation->setTimeLine(timeLine);
int value = 30;
animation->setTranslationAt(0.3, value, value);
timeLine->start();
// (*)
// x += 30;
// y += 30;
}
PAINTER:
void Klocek::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *widget)
{
bokKwadratu = (min(widget->width(), widget->height()))/5;
setRect(x * 30, y * 30, 30 - 3, 30 - 3);
QRectF rect = boundingRect();
painter->setBrush(brush);
painter->setPen(pen);
QFont font;
font.setPixelSize(bokKwadratu/3);
painter->setFont(font);
painter->drawRect(rect);
painter->drawText(rect,Qt::AlignCenter, QString::number(wartosc));
}
CONSTRUCTOR:
mySquare::mySquare(qreal x, qreal y) : QGraphicsRectItem(x * 10, y * 10, 10, 10)
{
setAcceptHoverEvents(true);
this->x = x;
this->y = y;
pen.setColor(Qt::red);
pen.setWidth(2);
brush.setColor(Qt::blue);
brush.setStyle(Qt::SolidPattern);
}
after performing animation (translation) I need to change the object coordinates so they are compatible which the situation on the screen. In other words after the translation (30, 30) I want the coordinates of the rectangle to be change (x += 30, y += 30)
my problem is that when i try to do this ( (*) fragment in the code) the triangle is put far away from its position (just as if the translation was performed twice)
My question is how to translate it and change the coordinates without such complications.
To begin with, I think you're misunderstanding the use of the function setTranslationAt in the QGraphicsItem Animation.
An animation has a normalised value over time, so can start at 0.0 and end at 1.0 (or the reverse). Therefore, by calling
animation->setTranslationAt(0.3, value, value);
You've stated that at the point when the normalised value reaches 0.3, you want the x and y position to be set to 'value'. That's fine, but you also need to set other values for the animation to occur (especially at the val of 1.0!). If you use a for loop, you can iterate through values from 0.0 to 1.0 and set where you want the position of your item to be. Take a look at the example code in the Qt help files for QGraphicsItemAnimation. The QGraphicsItemAnimation uses interpolation to work out the position of your object between the known points that you've given it. If you're interested: -
http://en.wikipedia.org/wiki/Linear_interpolation
Secondly, the item's rect is the definition of the item in its local coordinate space. So if you wanted a rect with its axis in the centre, you'd define it with x,y,w,h of (-w/2, -h/2, w, h). As these are local coordinates, they then get mapped into world coordinates in the GraphicsScene, which is where you set its actual position in the world.
Once you've setup your QGraphicsItemRect's local coordinates and world position, you can then simply paint it with drawRect and should not be setting positions in the paint function.
I am trying to calculate the vertices of a rotated rectangle (2D).
It's easy enough if the rectangle has not been rotated, I figured that part out.
If the rectangle has been rotated, I thought of two possible ways to calculate the vertices.
Figure out how to transform the vertices from local/object/model space (the ones I figured out below) to world space. I honestly have no clue, and if it is the best way then I feel like I would learn a lot from it if I could figure it out.
Use trig to somehow figure out where the endpoints of the rectangle are relative to the position of the rectangle in world space. This has been the way I have been trying to do up until now, I just haven't figured out how.
Here's the function that calculates the vertices thus far, thanks for any help
void Rect::calculateVertices()
{
if(m_orientation == 0) // if no rotation
{
setVertices(
&Vertex( (m_position.x - (m_width / 2) * m_scaleX), (m_position.y + (m_height / 2) * m_scaleY), m_position.z),
&Vertex( (m_position.x + (m_width / 2) * m_scaleX), (m_position.y + (m_height / 2) * m_scaleY), m_position.z),
&Vertex( (m_position.x + (m_width / 2) * m_scaleX), (m_position.y - (m_height / 2) * m_scaleY), m_position.z),
&Vertex( (m_position.x - (m_width / 2) * m_scaleX), (m_position.y - (m_height / 2) * m_scaleY), m_position.z) );
}
else
{
// if the rectangle has been rotated..
}
//GLfloat theta = RAD_TO_DEG( atan( ((m_width/2) * m_scaleX) / ((m_height / 2) * m_scaleY) ) );
//LOG->writeLn(&theta);
}
I would just transform each point, applying the same rotation matrix to each one. If it's a 2D planar rotation, it would look like this:
x' = x*cos(t) - y*sin(t)
y' = x*sin(t) + y*cos(t)
where (x, y) are the original points, (x', y') are the rotated coordinates, and t is the angle measured in radians from the x-axis. The rotation is counter-clockwise as written.
My recommendation would be to do it out on paper once. Draw a rectangle, calculate the new coordinates, and redraw the rectangle to satisfy yourself that it's correct before you code. Then use this example as a unit test to ensure that you coded it properly.
I think you were on the right track using atan() to return an angle. However you want to pass height divided by width instead of the other way around. That will give you the default (unrotated) angle to the upper-right vertex of the rectangle. You should be able to do the rest like this:
// Get the original/default vertex angles
GLfloat vertex1_theta = RAD_TO_DEG( atan(
(m_height/2 * m_scaleY)
/ (m_width/2 * m_scaleX) ) );
GLfloat vertex2_theta = -vertex1_theta; // lower right vertex
GLfloat vertex3_theta = vertex1_theta - 180; // lower left vertex
GLfloat vertex4_theta = 180 - vertex1_theta; // upper left vertex
// Now get the rotated vertex angles
vertex1_theta += rotation_angle;
vertex2_theta += rotation_angle;
vertex3_theta += rotation_angle;
vertex4_theta += rotation_angle;
//Calculate the distance from the center (same for each vertex)
GLfloat r = sqrt(pow(m_width/2*m_scaleX, 2) + pow(m_height/2*m_scaleY, 2));
/* Calculate each vertex (I'm not familiar with OpenGL, DEG_TO_RAD
* might be a constant instead of a macro)
*/
vertexN_x = m_position.x + cos(DEG_TO_RAD(vertexN_theta)) * r;
vertexN_y = m_position.y + sin(DEG_TO_RAD(vertexN_theta)) * r;
// Now you would draw the rectangle, proceeding from vertex1 to vertex4.
Obviously more longwinded than necessary, for the sake of clarity. Of course, duffymo's solution using a transformation matrix is probably more elegant and efficient :)
EDIT: Now my code should actually work. I changed (width / height) to (height / width) and used a constant radius from the center of the rectangle to calculate the vertices. Working Python (turtle) code at http://pastebin.com/f1c76308c