PDA

View Full Version : How to move/repaint object in a graphicsscene



qtgames
16th October 2009, 21:18
Hi,

I'm trying to make a little tank game with qt and I'm stuck with moving items in a QGraphicsScene. What I would want to happen is that the unit would move to the location in the scene where there presses with a mouse, but I can't get it moving anywhere, which was what I have been trying to do. What am I missing?


#include "armoredvehicle.h"
#include <QGraphicsItem>
#include <QtGui>

ArmoredVehicle::ArmoredVehicle(QPixmap turret, QPixmap hull)
{
unitTurret = new QGraphicsPixmapItem(turret);
unitHull = new QGraphicsPixmapItem(hull);
dx=0;
dy=0;
}

QRectF ArmoredVehicle::boundingRect() const
{
return QRectF(-75.5, -75.5, 74, 74);
}

void ArmoredVehicle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::blue);
painter->drawPixmap(this->x()+dx,this->y()+dy,QPixmap(40,20));

painter->setPen(Qt::NoPen);
painter->setBrush(Qt::darkGray);
painter->drawPixmap(this->x()+dx,this->y()+dy,QPixmap(3,30));
}



#include "battlefield.h"
#include <QGraphicsView>
#include <QGraphicsItem>
#include "armoredvehicle.h"
#include <QPixmap>
#include <QMouseEvent>

BattleField::BattleField()
{
QGraphicsScene *scene = new QGraphicsScene(this);
vehicle = new ArmoredVehicle(QPixmap(40,20),QPixmap(3,30));

scene->setItemIndexMethod(QGraphicsScene::NoIndex);
scene->setSceneRect(-400, -400, 600, 600);
scene->addItem(vehicle);

setScene(scene);
}

void BattleField::mousePressEvent(QMouseEvent *event)
{
vehicle->unitHull->moveBy(vehicle->unitHull->x() - event->x(),vehicle->unitHull->y() - event->y());
vehicle->unitTurret->moveBy(vehicle->unitTurret->x() - event->x(),vehicle->unitTurret->y() - event->y());
}

qtgames
17th October 2009, 10:48
Ok, I got it somehow redrawn, but I still can't understand the coordinate system. item->x() returns 0 and item->scenepos().x() returns zero. How can I get the position of the item in the scene in coordinates relative to the scene?

wysota
17th October 2009, 11:02
QGraphicsItem::pos() returns a position of the origin of the item in its parent's (or scene if it has no parent) coordinates. I'd say that your bounding rect looks odd and that's why you are confused.

qtgames
17th October 2009, 12:54
QGraphicsItem::pos() returns a position of the origin of the item in its parent's (or scene if it has no parent) coordinates. I'd say that your bounding rect looks odd and that's why you are confused.

I changed it to this:


return QRectF(unitHull->x(), unitHull->y(), 40, 40);

That item->pos().x() gives also zero.

wysota
17th October 2009, 13:03
The bounding rect specifies the local coordinate system of the item (not of its parent). You should probably use QRectF(-20,-20,40,40) and then use QGraphicsItem::setPos() or QGraphicsItem::move() to position the item on the scene.

qtgames
17th October 2009, 14:24
The bounding rect specifies the local coordinate system of the item (not of its parent). You should probably use QRectF(-20,-20,40,40) and then use QGraphicsItem::setPos() or QGraphicsItem::move() to position the item on the scene.

I still can't get the position and now the item keeps getting smaller after every click and finally disappears.


ArmoredVehicle::ArmoredVehicle(QPixmap turret, QPixmap hull)
{
unitTurret = new QGraphicsPixmapItem(turret);
unitHull = new QGraphicsPixmapItem(hull);
int bRectLeft = -20;
int bRectTop = -20;
int bRectW = 40;
int bRectH = 40;
}

void ArmoredVehicle::moveUnit(QPointF newPoint)
{
this->setPos(newPoint);
update();
}

QRectF ArmoredVehicle::boundingRect() const
{
return QRectF(bRectLeft, bRectTop, bRectW, bRectH);
}

void ArmoredVehicle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::blue);
painter->drawPixmap(this->pos().x(),this->pos().y(),QPixmap(3,30));

painter->setPen(Qt::NoPen);
painter->setBrush(Qt::green);
painter->drawPixmap(this->pos().x(),this->pos().y(),QPixmap(40,20));

qDebug("This pos: %d", this->pos().x());
}

QRectF ArmoredVehicle::getBoundingRect()
{
return QRectF(bRectLeft, bRectTop, bRectW, bRectH);
}



BattleField::BattleField()
{
QGraphicsScene *scene = new QGraphicsScene(this);

vehicle = new ArmoredVehicle(QPixmap(40,20),QPixmap(3,30));

scene->setItemIndexMethod(QGraphicsScene::NoIndex);
scene->setSceneRect(-100, -100, 1600, 1600);

scene->addItem(vehicle);
setScene(scene);
}

void BattleField::mousePressEvent(QMouseEvent *event)
{
qDebug("VehX before %d", vehicle->pos().x());
vehicle->moveUnit(QPointF(event->x(),event->y()));
qDebug("VehX after %d", vehicle->pos().x());
qDebug("Widget posX %d", vehicle->pos().x());
qDebug("X relative to widget(scene?): %d", event->x());
qDebug("Y relative to widget(scene?): %d", event->y());
qDebug("VehX %d", vehicle->unitHull->x());
}

wysota
17th October 2009, 14:59
That's because you still don't understand the concept of a local coordinate system. Your item only knows of its own coordinate system and not of its parents. Consider a real world example - we're living on Earth which is part of the Solar system. When you are calculating distances or pointing places, you do it in Earth's coordinate system without taking into consideration that Earth is constantly revolving around the Sun (which is revolving around the centre of the Milky Way, etc.). Therefore in your paint() routine you should only use your own coordinate system and not your parent's. pos() is the position of yourself in your parent's coordinate system. In other words you can only draw within the coordinates returned by your own boundingRect().

Your paint() should say:

void ArmoredVehicle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::blue);
painter->drawPixmap(QPointF(-20, -20), QPixmap(3,30));

painter->setPen(Qt::NoPen);
painter->setBrush(Qt::green);
painter->drawPixmap(QPointF(-20,-20), QPixmap(40,20));
}

qtgames
17th October 2009, 16:49
That's because you still don't understand the concept of a local coordinate system. Your item only knows of its own coordinate system and not of its parents. Consider a real world example - we're living on Earth which is part of the Solar system. When you are calculating distances or pointing places, you do it in Earth's coordinate system without taking into consideration that Earth is constantly revolving around the Sun (which is revolving around the centre of the Milky Way, etc.).

I think that I understand the theory, but not the implementation.

Ok, lets start from the beginning. Correct me if I'm wrong.

All (cartesian) coordinate systems have top left corner coordinate of 0,0?
All (cartesian) coordinate systems have bottom right corner coordinate of width,height?
QPointF(0,0) is the center coordinate (ie. width/2, height/2) in the coordinate system?
QPointF(-width/2,-height/2) is the top left corner (ie. 0,0)?


QRectF ArmoredVehicle::boundingRect() const
{
return QRectF(bRectLeft, bRectTop, bRectW, bRectH);
}

This rectangle contains the item, right?
Rectangle's center coordinates in the scene's coordinates are item->pos()?

Another thing that I don't understand is that the unit doesn't move to the point of the click, but there is a seemingly random offset. Also when I resize the application window, the scene gets scroll bars, but it also loses it's size. So when the window is maximized the bottom left corner of the scene is for example x,y=1000,800, but when I halve the size of the window the x,y=500,400. Is there someway to prevent this scaling of the scene?

And I still can't get any position data of where the item is in the scenes coordinates.

wysota
17th October 2009, 18:25
All (cartesian) coordinate systems have top left corner coordinate of 0,0?
No.


All (cartesian) coordinate systems have bottom right corner coordinate of width,height?
No


QPointF(0,0) is the center coordinate (ie. width/2, height/2) in the coordinate system?
This is contradictory to your first statement :)


QPointF(-width/2,-height/2) is the top left corner (ie. 0,0)?
In this particular case - yes.



QRectF ArmoredVehicle::boundingRect() const
{
return QRectF(bRectLeft, bRectTop, bRectW, bRectH);
}

This rectangle contains the item, right?
Right.


Rectangle's center coordinates in the scene's coordinates are item->pos()?
In this particular case - yes.


Is there someway to prevent this scaling of the scene?
The scene is not scaled unless you scale it yourself. It is centred in the viewport if the latter is bigger than the current projection of the scene - maybe that's the effect you observe.


And I still can't get any position data of where the item is in the scenes coordinates.
It's in QGraphicsItem::pos() :)

qtgames
17th October 2009, 19:33
Ok, I'm totally lost now, but lets continue.

What is the top left corner coordinate?
What is the bottom right corner coordinate?
Where is QPointF(0,0)?
Where is QPointF(-width/2,-height/2)?


The scene is not scaled unless you scale it yourself. It is centred in the viewport if the latter is bigger than the current projection of the scene - maybe that's the effect you observe.

So how can I set the scale to be fixed without making the parent widget (window) fixed?


It's in QGraphicsItem::pos() :)

item->pos().x() in the BattleField class, it gives out zero at all times. Also for some reason the application doesn't repaint item and remove (or do I have to repaint the background from the previous position?) the old one.

wysota
17th October 2009, 19:54
What is the top left corner coordinate?
boundingRect().topLeft()


What is the bottom right corner coordinate?
boundingRect().bottomRight() which is the same as boundingRect().topLeft() + boundingRect().size()


Where is QPointF(0,0)?
It depends on what boundingRect() returns


Where is QPointF(-width/2,-height/2)?
It also depends on the bounding rect (its top left corner, to be exact).



So how can I set the scale to be fixed without making the parent widget (window) fixed?
"Fixed" to what? Fixed size? Just don't resize it and it will have constant size.



item->pos().x() in the BattleField class, it gives out zero at all times.
Did you use QGraphicsItem::setPos() prior to calling QGraphicsItem::pos()? If not, pos() will obviously return QPointF(0,0) which is the default position of every item relative to its parent.


Also for some reason the application doesn't repaint item and remove (or do I have to repaint the background from the previous position?) the old one.

Because your boundingRect() is inconsistent with the area of the viewport where you are trying to paint the item. You may only paint within boundingRect() of the item.

qtgames
17th October 2009, 20:37
Double post.

qtgames
17th October 2009, 20:51
It depends on what boundingRect() returns

How does it depend on boundingRect?


"Fixed" to what? Fixed size? Just don't resize it and it will have constant size.

Yes, fixed size. But what if the player does?


Did you use QGraphicsItem::setPos() prior to calling QGraphicsItem::pos()?

Yes. The code I used was following, (vehicle inherits QGraphicsItem)


vehicle->setPos(QPointF(event->x(),event->y()));

The unit does move with an offset that I have been unable to decypher, but


qDebug("VehX after %d", vehicle->pos().x());

=0.


Because your boundingRect() is inconsistent with the area of the viewport where you are trying to paint the item. You may only paint within boundingRect() of the item.

So by item->setPos(newPoint) I move the items boundingRect in the scene?
Then I call the item->update(item->boundingRect?), which should automatically call paint-function?

wysota
17th October 2009, 21:07
How does it depend on boundingRect?
The boundingRect defines the area of your item. If boundingRect() returns (-48,64, 98,24) then your item "starts" at (-48,64) and "lasts" until (50,88).


Yes, fixed size. But what if the player does?
The player can only resize the view, not the scene.


Yes. The code I used was following, (vehicle inherits QGraphicsItem)


vehicle->setPos(QPointF(event->x(),event->y()));

The unit does move with an offset that I have been unable to decypher, but


qDebug("VehX after %d", vehicle->pos().x());

=0.
Please provide a minimal compilable example reproducing this behaviour.


So by item->setPos(newPoint) I move the items boundingRect in the scene?
Sort of... You move the origin of the item and as the boundingRect is defined in the same coordinate system as the origin, it also "moves". Compare the results of this sequence of calls:

QGraphicsRectItem *item = scene.addItem(QRectF(-100,-50, 200, 100), QPen(Qt::red)); // adds an item to the scene
qDebug() << item->boundingRect() << item->pos() << item->sceneBoundingRect();
item->setPos(50,50);
qDebug() << item->boundingRect() << item->pos() << item->sceneBoundingRect();


Then I call the item->update(item->boundingRect?), which should automatically call paint-function?
No, by moving the item you ask graphics view to update the item - it will repaint both the old projection of the bounding rect to the scene and new projection of the bounding rect to the scene. If you paint outside the bounding rect, you'll get artifacts.

qtgames
17th October 2009, 21:25
The boundingRect defines the area of your item. If boundingRect() returns (-48,64, 98,24) then your item "starts" at (-48,64) and "lasts" until (50,88).

How can you calculate the end point?


The player can only resize the view, not the scene.

But when the view is resized the scene is also resized.


Please provide a minimal compilable example reproducing this behaviour.

It is a multifile project so I attached it along with this reply


QGraphicsRectItem *item = scene.addItem(QRectF(-100,-50, 200, 100), QPen(Qt::red)); // adds an item to the scene
qDebug() << item->boundingRect() << item->pos() << item->sceneBoundingRect();
item->setPos(50,50);
qDebug() << item->boundingRect() << item->pos() << item->sceneBoundingRect();

The outputs are identical.

wysota
17th October 2009, 21:49
How can you calculate the end point?
Please read the docs for QRectF.


But when the view is resized the scene is also resized.
No, it's not.


The outputs are identical.

Did you try it in your application or a verbatim one? Create an application that consists of only a pure scene, pure view and the above calls.

qtgames
18th October 2009, 07:11
No, it's not.

When I click to the bottom right corner the event->x() is different than when I resize the window and click bottom right corner and read event->x(). And I pull the scroll bars to the end. How is that possible?


Did you try it in your application or a verbatim one? Create an application that consists of only a pure scene, pure view and the above calls.

The pure one doesn't compile. addItem doesn't have such definition.

qtgames
18th October 2009, 09:53
Ok, some progress. The click, vehicle->pos() and the sceneBoundingRect() are at the same location. However, when the items are added into the scene, their vehicle->pos() is at (0,0), but they are 80,80 point below the top left corner.

More progress. The scene was not initialized at 0,0 in the BattleField and that's why there was an offset. Now the only problem is that repaint doesn't work. I have to manually refresh.
And setViewportUpdateMode(FullViewportUpdate) solved this. Thanks.

qtgames
18th October 2009, 10:32
Actually there is still a problem with the scroll bars. The movement works ok, when we at the top left corner of the scene. However, in bottom right corner the event->x() gives coordinates relative to the top left corner of the view, even if there is more scene behind the scroll bars. How can I get absolute coordinates in the scene?

wysota
18th October 2009, 10:34
When I click to the bottom right corner the event->x() is different than when I resize the window and click bottom right corner and read event->x(). And I pull the scroll bars to the end. How is that possible?

Add this code to your program:


QGraphicsRectItem *item = scene.addRect(scene.sceneRect(), QPen(Qt::red), Qt::yellow));
item->setZValue(-1);
item->setOpacity(0.5);
This will add an item to the scene which is the size of the scene itself. Then start manipulating the window size and see how the scene is moved in the view (you have to trust me its size doesn't change).


The pure one doesn't compile. addItem doesn't have such definition.
Man...


#include <QtGui>

int main(int argc, char **argv){
QApplication app(argc, argv);
QGraphicsView view;
QGraphicsScene scene(-400,-400,800,800);
view.setScene(&scene);
QGraphicsRectItem *item = scene.addRect(QRectF(-40, -20, 80, 40), QPen(Qt::red));
qDebug() << item->pos() << item->boundingRect() << item->sceneBoundingRect();
item->setPos(QPointF(100,80));
qDebug() << item->pos() << item->boundingRect() << item->sceneBoundingRect();
return app.exec();
}


Now the only problem is that repaint doesn't work. I have to manually refresh.
And setViewportUpdateMode(FullViewportUpdate) solved this. Thanks.

Bad idea. Find out why it doesn't refresh or else you'll have more problems later.

You can add this to your item's paint() routine:


painter->drawRect(boundingRect());

This will show you if you're drawing in the proper rectangle.