Trolltech Home | Qt-interest Home | Recent Threads | All Threads | Author | Date
All threads index page 3

Qt-interest Archive, January 2007
QGraphicsSvgItem and collision detection


Message 1 in thread

Hi,

I have an application that uses QGraphicsView and mainly QGraphicsPixmapItem 
for the items. Most of the items are created from PNG images that have 
transparency. The items are in various shapes and never square shaped.

In the application I want to be able to click in the QGraphicsView and find 
the clicked items. I use code like the one below:

void Scene::mousePressEvent (QGraphicsSceneMouseEvent * event) {
    QGraphicsItem * clicked = itemAt ( event->scenePos() );
    if ( ! clicked ) {
        // miss...
        return;
    }

    // a QGraphicsItem was clicked
    ....
}

This works beautifully for QGraphicsPixmapItem, the itemAt() method accurately 
finds the items and correctly leaves out items where I have clicked any 
transparent area. This is the expected behaviour.

However, for QGraphicsSvgItem this doesn't work at all. The itemAt() method 
seems to check tbe boundingRect() of the item and it's enough to just click 
inside it to cause a hit. This is obviously not what I want and makes it 
impossible to select items reliably.

So, I tried QGraphicsPixmapItem methods contains(), opaqueArea().contains() 
and shape().contains(), but none worked. The elementCount() for shape() is 
always 5, which suggests that it simply uses a bounding rectangle, while 
elementCount() for opaqueShape() is always 0 which suggests that it does 
nothing useful at all?

How can I get a QGraphicsPixmapItem to do any kind of sane collision 
detection? There are a few solutions:

1. one solution I've used before is to manually trace coordinates for an 
outline for the SVG items, read those into a QPolygon and use that for 
collision detection (works fine) but that route is so much work it's not 
worth it. 

2. manually extract the paths/polygons from the items (they are all one single 
filled and closed polygon) and use that for collision detection. There's no 
API for this so it can't be done.

3. extract the cached QPixmap from the item and perform collision detection 
against its mask. There's no API for this so it can't be done.

My application does work fine with QGraphicsPixmapItem but as my source data 
is all SVG and I would like to have the stuff scalable, QGraphicsSvgItem is 
the obvious choice. I do assume the SVG stuff in QGraphicsView was meant for 
serious use, not just "look mum, I can show an SVG image!" so it must be I 
who am too dumb to see what I do wrong.

I'd appreciate any hints and RTFM pointers.

Regards,
    Jan Ekholm

-- 
 [ signature omitted ] 

Message 2 in thread

Perhaps I had better submit this as a bug to the task tracker? I think it is a 
clear bug that QGraphicsPixmapItem and QGraphicsSvgItem work differently when 
it comes to collision detection. What do you think?


On Friday 19 January 2007 13:40, Jan Ekholm wrote:
> Hi,
>
> I have an application that uses QGraphicsView and mainly
> QGraphicsPixmapItem for the items. Most of the items are created from PNG
> images that have transparency. The items are in various shapes and never
> square shaped.
>
> In the application I want to be able to click in the QGraphicsView and find
> the clicked items. I use code like the one below:
>
> void Scene::mousePressEvent (QGraphicsSceneMouseEvent * event) {
>     QGraphicsItem * clicked = itemAt ( event->scenePos() );
>     if ( ! clicked ) {
>         // miss...
>         return;
>     }
>
>     // a QGraphicsItem was clicked
>     ....
> }
>
> This works beautifully for QGraphicsPixmapItem, the itemAt() method
> accurately finds the items and correctly leaves out items where I have
> clicked any transparent area. This is the expected behaviour.
>
> However, for QGraphicsSvgItem this doesn't work at all. The itemAt() method
> seems to check tbe boundingRect() of the item and it's enough to just click
> inside it to cause a hit. This is obviously not what I want and makes it
> impossible to select items reliably.
>
> So, I tried QGraphicsPixmapItem methods contains(), opaqueArea().contains()
> and shape().contains(), but none worked. The elementCount() for shape() is
> always 5, which suggests that it simply uses a bounding rectangle, while
> elementCount() for opaqueShape() is always 0 which suggests that it does
> nothing useful at all?
>
> How can I get a QGraphicsPixmapItem to do any kind of sane collision
> detection? There are a few solutions:
>
> 1. one solution I've used before is to manually trace coordinates for an
> outline for the SVG items, read those into a QPolygon and use that for
> collision detection (works fine) but that route is so much work it's not
> worth it.
>
> 2. manually extract the paths/polygons from the items (they are all one
> single filled and closed polygon) and use that for collision detection.
> There's no API for this so it can't be done.
>
> 3. extract the cached QPixmap from the item and perform collision detection
> against its mask. There's no API for this so it can't be done.
>
> My application does work fine with QGraphicsPixmapItem but as my source
> data is all SVG and I would like to have the stuff scalable,
> QGraphicsSvgItem is the obvious choice. I do assume the SVG stuff in
> QGraphicsView was meant for serious use, not just "look mum, I can show an
> SVG image!" so it must be I who am too dumb to see what I do wrong.
>
> I'd appreciate any hints and RTFM pointers.
>
> Regards,
>     Jan Ekholm

-- 
 [ signature omitted ] 

Message 3 in thread

Jan Ekholm wrote:
> Perhaps I had better submit this as a bug to the task tracker? I think it
> is a clear bug that QGraphicsPixmapItem and QGraphicsSvgItem work
> differently when it comes to collision detection. What do you think?

Because QGraphicsSvgItem's collision detection requires boolean painter path
operations, and Qt 4.2 does not support that, we cannot provide automatic
collision detection for items beyond their bounding rect. You can write
your own shape() implementation and return a QPainterPath that resembles
your item's shape to work around it.

You could say it's a bug or a missing feature, and we are working on it. You
could submit it to the task tracker, if only so that you and others can
search it up and follow progress.

Andreas

-- 
 [ signature omitted ] 

Message 4 in thread

On Monday 22 January 2007 09:57, Andreas Aardal Hanssen wrote:
> Jan Ekholm wrote:
> > Perhaps I had better submit this as a bug to the task tracker? I think it
> > is a clear bug that QGraphicsPixmapItem and QGraphicsSvgItem work
> > differently when it comes to collision detection. What do you think?
>
> Because QGraphicsSvgItem's collision detection requires boolean painter
> path operations, and Qt 4.2 does not support that, we cannot provide
> automatic collision detection for items beyond their bounding rect. You can
> write your own shape() implementation and return a QPainterPath that
> resembles your item's shape to work around it.

Well, it I got access to the cached pixmap it would be doable, or at least I 
could create my own collision detection. Right now the only way to create an 
own shape() is to read the same SVG file manually and try to understand it in 
code. Another is to precreate PNG versions of the SVG images, but that 
somehow makes the whole idea of using SVG redundant.

> You could say it's a bug or a missing feature, and we are working on it.
> You could submit it to the task tracker, if only so that you and others can
> search it up and follow progress.

Oh, features are always missing and always will be. A small note could be 
added to the QGraphicsSvgItem docs to indicate that the item doesn't behave 
like other items wrt collision detection. I spent a few hours hunting bugs in 
my code until I discovered that.

However, for now I simply store an additional QImage along with the item and 
use it for collision detection. Something like:

    QPixmap image = QPixmap ( boundingRect().size().toSize() );
    image.fill ( QColor(0,0,0,0));

    QPainter painter ( &image );
    
    // render the SVG into the image
    renderer()->render ( &painter );
    painter.end();

    m_mask = image.toImage(); 

Collisions can then be checked with:

    // 
    QPoint point = ...

    // get the actual hit pixel and convert to a RGBA color
    QColor hit_pixel = 
	QColor::fromRgba ( m_mask.pixel(point.x(), point.y()) );

    // it is hit if the alpha is non 0
    return hit_pixel.alpha() != 0;

Yes, this wastes some memory, but as this is a game it's allowed to 
shamelessly waste resources. :)


I also noticed that the only way to assign a SVG file to a QGraphicsSvgItem is 
by passing the filename in the constructor. There is AFAIK no way to do it 
once the item is created. I tried to use this code which looks quite logical 
to me, but which didn't load anything at all (nor give an error):

     if ( ! renderer()->load ("file.svg") ) {
         // failed...
     }

It gives an empty item. The renderer is not changed nor shared, it's the 
item's default renderer.

Anyway, with some careful navigation around the mines in my path I seem to be 
able to use SVG with QGraphicsView. Some updates in the docs would sweep away 
quite a few of the mines though...

-- 
 [ signature omitted ] 

Message 5 in thread

Jan Ekholm wrote:
> Yes, this wastes some memory, but as this is a game it's allowed to
> shamelessly waste resources. :)

We can't use something like this in Qt. :-)

> It gives an empty item. The renderer is not changed nor shared, it's the
> item's default renderer.

If you found a bug, please post it to the public task tracker; that way, you
can confirm if it is a bug or not, and the right people will know about it.

Andreas

-- 
 [ signature omitted ] 

Message 6 in thread

On Monday 22 January 2007 11:08, Andreas Aardal Hanssen wrote:
> Jan Ekholm wrote:
> > Yes, this wastes some memory, but as this is a game it's allowed to
> > shamelessly waste resources. :)
>
> We can't use something like this in Qt. :-)

Obviously. But you can document why the item types work differently and what 
will not work with SVG items. However, I rather have a temporary solution 
that wastes memory and works than one that is supposed to work (according to 
the docs) but doesn't.

> > It gives an empty item. The renderer is not changed nor shared, it's the
> > item's default renderer.
>
> If you found a bug, please post it to the public task tracker; that way,
> you can confirm if it is a bug or not, and the right people will know about
> it.

I'm very reluctant to do that. Last time I tried it the whole task tracker 
didn't even work with Konqueror. In this case it's also probably something 
that I do wrong, I'm a newbie.

-- 
 [ signature omitted ] 

Message 7 in thread

Jan Ekholm wrote:
>> We can't use something like this in Qt. :-)
> Obviously. But you can document why the item types work differently and

We'll see what happens.

>> If you found a bug, please post it to the public task tracker; that way,
> I'm very reluctant to do that. Last time I tried it the whole task tracker

If you don't post the bug because of a task tracker problem with Konqueror,
then there's a chance your bug will not get fixed. Nobody wins :-).

Andreas

-- 
 [ signature omitted ]