最後編輯禁止可移動的QGraphicsItem的碰撞與其他項目
我想通了這個問題,並公佈瞭解決方案如下回答。如果你在谷歌尋找一種體面的方式來通過ItemIsMovable
標誌移動QGraphicsItems/QGraphicsObjects,同時避免與其他節點發生衝突,那麼我在我的答案最後加上了一個工作itemChange
方法。
我原來的項目處理捕捉到任意網格的節點,但這很容易被刪除,並且根本不需要這個解決方案的工作。效率方面,它並不是數據結構或算法最聰明的用法,所以如果屏幕上出現大量節點,您可能想嘗試更智能的方法。
原始的問題
我有一個子類的QGraphicsItem,節點,即設置標誌爲ItemIsMovable
和ItemSendsGeometryChanges
。這些節點對象覆蓋了shape()
和boundingRect()
函數,爲它們提供了一個基本的矩形來移動。
用鼠標移動一個節點,按照預期調用itemChange()
函數,我可以調用一個snapPoint()
函數,該函數強制移動的節點始終對齊網格。 (這是一個簡單的函數,它返回基於給定參數的新QPointF,其中新點限制在實際捕捉網格上最接近的座標上)。這與現在完全一樣。
我的大問題是,我想避免陷入與其他節點相撞的位置。也就是說,如果我將一個節點移動到結果(對齊網格)位置,我希望collidingItems()
列表完全爲空。
我可以在ItemPositionHasChanged
標誌在itemChange()
中拋出後準確地檢測到collidingItems
,但不幸的是這是在節點已經移動之後。更重要的是,我發現這個碰撞列表沒有正確更新,直到節點從最初的itemChange()
返回(這是壞的,因爲節點已經移動到與其他節點重疊的無效位置)。
這裏的itemChange()
功能到目前爲止我有:
QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionChange && scene())
{
// Snap the value to the grid
QPointF pt = value.toPointF();
QPointF snapped = snapPoint(pt);
// How much we've moved by
qreal delX = snapped.x() - pos().x();
qreal delY = snapped.y() - pos().y();
// We didn't move enough to justify a new snapped position
// Visually, this results in no changes and no collision testing
// is required, so we can quit early
if (delX == 0 && delY == 0)
return snapped;
// If we're here, then we know the Node's position has actually
// updated, and we know exactly how far it's moved based on delX
// and delY (which is constrained to the snap grid dimensions)
// for example: delX may be -16 and delY may be 0 if we've moved
// left on a grid of 16px size squares
// Ideally, this is the location where I'd like to check for
// collisions before returning the snapped point. If I can detect
// if there are any nodes colliding with this one now, I can avoid
// moving it at all and avoid the ItemPositionHasChanged checks
// below
return snapped;
}
else if (change == ItemPositionHasChanged && scene())
{
// This gets fired after the Node actually gets moved (once return
// snapped gets called above)
// The collidingItems list is now validly set and can be compared
// but unfortunately its too late, as we've already moved into a
// potentially offending position that overlaps other Nodes
if (collidingItems().empty())
{
// This is okay
}
else
{
// This is bad! This is what we wanted to avoid
}
}
return QGraphicsItem::itemChange(change, value);
}
我已經試過
我試圖存儲delX
和delY
的變化,如果我們進入了最後的else塊在上面,我試圖用moveBy(-lastDelX, -lastDelY)
扭轉前面的舉動。這有一些可怕的結果,因爲當鼠標仍然被按下並移動節點本身時(這會在試圖圍繞碰撞移動時多次移動節點,這經常導致節點飛離屏幕一側非常快)。
我也試過存儲先前(已知有效)點,並調用setX的和SETY上恢復位置如果碰撞被發現,但也不得不上述方法類似的問題。
結論
如果我能找到一種方法來檢測collidingItems()
準確地移動前甚至發生了,我就能避免ItemPositionHasChanged
塊乾脆,確保了ItemPositionChange
只返回那些點有效,從而避免任何邏輯移到先前有效的位置。
感謝您的時間,請讓我知道是否需要更多代碼或示例來更好地說明問題。
編輯1:
我想我無意中發現了一種技術,可能會形成實際的解決方案的基礎,但我還是需要一些幫助,因爲它不完全工作。
在過去return snapped;
前itemChange()
功能,添加以下代碼:
// Make a copy of the shape and translate it correctly
// This should be equivalent to the shape formed after a successful
// move (i.e. after return snapped;)
QPainterPath path = shape();
path.translate(delX, delY);
// Examine all the other items to see if they collide with the desired
// shape....
for (QGraphicsItem* other : canvas->items())
{
if (other == this) // don't care about this node, obviously
continue;
if (other->collidesWithPath(path))
{
// We should hopefully only reach this if another
// item collides with the desired path. If that happens,
// we don't want to move this node at all. Unfortunately,
// we always seem to enter this block even when the nodes
// shouldn't actually collide.
qDebug() << "Found collision?";
return pos();
}
}
// Should only reach this if no collisions found, so we're okay to move
return snapped;
...但是這也不管用。任何新節點始終進入collidesWithPath
塊,即使它們彼此之間沒有距離。上面的代碼中的畫布對象只是QGraphicsView,它包含所有這些Node對象,以防它們也不清楚。
任何想法如何使collidesWithPath
正常工作?複製shape()
錯誤的路徑是?我不確定這裏出了什麼問題,因爲兩個形狀的比較似乎在移動完成後正常工作(即我可以在之後準確檢測到碰撞)。如果有幫助,我可以上傳整個代碼。