Modifier finalInterdisez collision d'un QGraphicsItem mobile avec d'autres articles
je me suis dit sur la question et a réalisé une solution comme une réponse ci-dessous. Si vous avez trébuché ici de google cherchant un moyen décent de déplacer QGraphicsItems/QGraphicsObjects via l'indicateur ItemIsMovable
tout en évitant la collision avec d'autres nœuds, j'ai inclus une méthode de travail itemChange
à la toute fin de ma réponse.
Mon projet d'origine traitait des nœuds d'accrochage à une grille arbitraire, mais il est facile à supprimer et pas du tout nécessaire pour que cette solution fonctionne. Du point de vue de l'efficacité, ce n'est pas l'utilisation la plus intelligente des structures de données ou des algorithmes, donc si vous vous retrouvez avec beaucoup de nœuds sur l'écran, vous voudrez peut-être essayer quelque chose de plus intelligent.
Original Question
J'ai un QGraphicsItem sous-classé, le nœud, qui définit les drapeaux pour ItemIsMovable
et ItemSendsGeometryChanges
. Ces objets Node ont des fonctions shape()
et boundingRect()
surchargées qui leur donnent un rectangle de base pour se déplacer.
Le déplacement d'un nœud avec la souris appelle la fonction itemChange()
comme prévu, et je peux appeler une fonction snapPoint()
qui force le nœud déplacé à toujours s'accrocher à une grille. (Ceci est une fonction simple qui retourne un nouveau QPointF basé sur un paramètre donné, où le nouveau point est restreint à la coordonnée la plus proche sur la grille d'alignement réelle). Cela fonctionne parfaitement comme c'est maintenant.
Mon gros problème est que je voudrais éviter d'enclencher dans une position qui entre en collision avec d'autres nœuds. C'est-à-dire, si je déplace un nœud dans un emplacement résultant (accroché à la grille), j'aimerais que la liste collidingItems()
soit complètement vide.
Je peux détecter avec précision le collidingItems
après que le drapeau ItemPositionHasChanged
soit levé dans itemChange()
, mais malheureusement, cela se produit après que le nœud a déjà été déplacé. Le nœud est revenu du itemChange()
initial (ce qui est mauvais puisque le nœud a déjà été déplacé dans un emplacement invalide qui chevauche certains autres nœuds).
est ici la fonction itemChange()
j'ai jusqu'à présent:
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);
}
Ce que j'ai essayé
J'ai essayé stocker les delX
et delY
changements, et si nous sommes entrés dans le dernier bloc d'autre ci-dessus, j'ai essayé d'inverser le précédent mouvement avec moveBy(-lastDelX, -lastDelY)
. Cela a eu des résultats horribles, car cela est appelé alors que la souris est toujours pressée et déplace le nœud lui-même (qui finit par déplacer le nœud plusieurs fois en essayant de contourner une collision, entraînant souvent le nœud voler sur le côté de l'écran très rapidement). J'ai également essayé de stocker les points précédents (connus) et d'appeler un setX et setY sur la position pour revenir en arrière si une collision a été trouvée, mais cela a également eu des problèmes similaires à l'approche ci-dessus.
Conclusion
Si je pouvais trouver un moyen de détecter la collidingItems()
avec précision avant le déménagement arrive même, je serais en mesure d'éviter le bloc ItemPositionHasChanged
tout à fait, en vous assurant que le ItemPositionChange
ne retourne que les points qui sont valide, évitant ainsi toute logique pour passer à une position précédemment valide.
Merci de votre temps, et s'il vous plaît laissez-moi savoir si vous avez besoin de plus de code ou d'exemples pour illustrer le problème mieux.
Edit 1:
Je pense que je suis tombé sur une technique qui va probablement servir de base à la solution réelle, mais je encore besoin de l'aide car il est pas tout à fait travailler.
Dans la fonction itemChange()
avant la dernière return snapped;
, j'ai ajouté le code suivant:
// 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;
... mais cela ne fonctionne pas non plus. Tout nouveau noeud entre toujours dans le bloc collidesWithPath
, même s'ils ne sont pas proches l'un de l'autre. L'objet canvas dans le code ci-dessus est juste QGraphicsView qui contient tous ces objets Node, au cas où cela ne serait pas clair non plus.
Des idées pour faire fonctionner le collidesWithPath
correctement? Le chemin qui a copié le shape()
est-il erroné? Je ne suis pas sûr de ce qui ne va pas ici, car la comparaison des deux formes semble bien fonctionner une fois le déménagement terminé (c'est-à-dire que je peux détecter les collisions avec précision après). Je peux télécharger le code entier si cela peut vous aider.