Search code examples
pythonpyqtpyqt4pyqt5qgraphicsitem

Detecting collision between rectangle and item with bezier curve shape


I need to detect precisely the collision between rectangle item and another item that has a bezier curve shape. At the moment collision is detected correctly when I collide rectangle with bottom part of the bezier curve shape. But when I move rectangle inside the bezier curve shape collision is also detected, although that items aren't collided. I want to obtain a precise collision between these two items. I don't understand where I made a mistake.

enter image description here

class RectangleItem(QGraphicsRectItem):
    def __init__(self, *args):
        super().__init__(*args)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setPen(QPen(Qt.cyan))


class CurveItem(QGraphicsItem):
    def __init__(self):
        super().__init__()
        self.path = self._setupPath()

    def paint(self, painter, styles, widget=None):
        painter.drawPath(self.path)

    def boundingRect(self):
        return self.path.boundingRect()

    def shape(self):
        return self.path

    def _setupPath(self):
        path = QPainterPath()

        p1 = QPointF(0, 100)
        p2 = QPointF(400, 100)
        c = QPointF(200, 800)

        path.moveTo(p1)
        path.quadTo(c, p2)
        return path


class Scene(QGraphicsScene):
    def __init__(self):
        super().__init__()

        self.curve_item = CurveItem()
        self.addItem(self.curve_item)

        self.rectangle_item = RectangleItem(0, 0, 50, 50)
        self.addItem(self.rectangle_item)

    def mouseMoveEvent(self, e):
        print(self.collidingItems(self.curve_item))
        super().mouseMoveEvent(e)

Solution

  • To observe what is the cause of the problem we can place a QBrush to paint the CurveItem content and we get the following:

    def paint(self, painter, styles, widget=None):
        painter.setBrush(QBrush(QColor("green")))
        painter.drawPath(self.path)
    

    enter image description here

    Why it happens?

    QPainterPath joins the final and initial points if the figure is not closed, so it generates a figure with content, and that causes when you move the rectangle over the line it signals that it is intersecting.

    What is the solution?

    Go back through the same path to the starting point, thus joining the new final line with the initial one without generating the content.

    In the following code is the solution:

    class CurveItem(QGraphicsItem):
        def __init__(self):
            super().__init__()
            self.path = self._setupPath()
    
        def paint(self, painter, styles, widget):
            painter.drawPath(self.path)
    
        def boundingRect(self):
            return self.path.boundingRect()
    
        def shape(self):
            return self.path
    
        def _setupPath(self):
            path = QPainterPath()
    
            p1 = QPointF(0, 100)
            p2 = QPointF(400, 100)
            c = QPointF(200, 800)
    
            path.moveTo(p1)
            path.quadTo(c, p2)
    
            # back
            path.quadTo(c, p1)
            return path