【问题标题】:Avoiding collisions of QGraphicsItem shapes moved by the mouse避免鼠标移动的 QGraphicsItem 形状的碰撞
【发布时间】:2022-01-01 13:41:41
【问题描述】:

引发了一个有趣的讨论 here 在 QGraphicsScene 中防止由 QGraphicsEllipseItems 组成的圆的碰撞。这个问题将范围缩小到 2 个碰撞项目,但更大的目标仍然存在,对于任意数量的碰撞怎么办?

这是期望的行为:

  • 当一个项目被拖动到其他项目上时,它们不应重叠,而是应尽可能靠近鼠标在这些项目周围移动。
  • 如果它被其他物品阻挡,它不应该“传送”。
  • 应该是平稳且可预测的运动。

随着为圆在移动时找到最佳“安全”位置变得越来越复杂,我想展示另一种使用物理模拟器来实现它的方法。

【问题讨论】:

    标签: python pyqt5 collision pymunk


    【解决方案1】:

    鉴于上面描述的行为,它是 2D 刚体物理的一个很好的候选者,也许没有它可以做到,但很难让它完美。我在此示例中使用 pymunk,因为我熟悉它,但相同的概念也适用于其他库。

    场景有一个运动体来表示鼠标,而圆圈最初由静态体表示。当一个圆圈被选中时,它会切换到一个动态物体,并通过一个阻尼弹簧约束到鼠标。它的位置随着空间在每个超时间隔上按给定时间步更新而更新。

    该项目实际上并未以与未启用ItemIsMovable 标志相同的方式移动,这意味着它不再随鼠标立即移动。它非常接近,但有一点延迟,尽管您可能更喜欢这样来更好地了解它对碰撞的反应。 (即便如此,您也可以微调参数,让它比我移动得更快/更靠近鼠标**)。

    另一方面,碰撞得到了完美的处理,并且已经支持其他类型的形状。

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    import pymunk
    
    class Circle(QGraphicsEllipseItem):
    
        def __init__(self, r, **kwargs):
            super().__init__(-r, -r, r * 2, r * 2, **kwargs)
            self.setFlag(QGraphicsItem.ItemIsSelectable)
            self.static = pymunk.Body(body_type=pymunk.Body.STATIC)
            self.circle = pymunk.Circle(self.static, r)
            self.circle.friction = 0
            mass = 10
            self.dynamic = pymunk.Body(mass, pymunk.moment_for_circle(mass, 0, r))
            self.updatePos = lambda: self.setPos(*self.dynamic.position, dset=False)
    
        def setPos(self, *pos, dset=True):
            super().setPos(*pos)
            if len(pos) == 1:
                pos = pos[0].x(), pos[0].y()
            self.static.position = pos
            if dset:
                self.dynamic.position = pos
    
        def itemChange(self, change, value):
            if change == QGraphicsItem.ItemSelectedChange:
                space = self.circle.space
                space.remove(self.circle.body, self.circle)
                self.circle.body = self.dynamic if value else self.static
                space.add(self.circle.body, self.circle)
            return super().itemChange(change, value)
    
        def paint(self, painter, option, widget):
            option.state &= ~QStyle.State_Selected
            super().paint(painter, option, widget)
    
    
    class Scene(QGraphicsScene):
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.space = pymunk.Space()
            self.space.damping = 0.02
            self.body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
            self.space.add(self.body)
            self.timer = QTimer(self, timerType=Qt.PreciseTimer, timeout=self.step)
            self.selectionChanged.connect(self.setConstraint)
    
        def setConstraint(self):
            selected = self.selectedItems()
            if selected:
                shape = selected[0].circle
                if not shape.body.constraints:
                    self.space.remove(*self.space.constraints)
                    spring = pymunk.DampedSpring(
                        self.body, shape.body, (0, 0), (0, 0),
                        rest_length=0, stiffness=100, damping=10)
                    spring.collide_bodies = False
                    self.space.add(spring)
    
        def step(self):
            for i in range(10):
                self.space.step(1 / 30)
            self.selectedItems()[0].updatePos()
    
        def mousePressEvent(self, event):
            super().mousePressEvent(event)
            if self.selectedItems():
                self.body.position = event.scenePos().x(), event.scenePos().y()
                self.timer.start(1000 / 30)
                
        def mouseMoveEvent(self, event):            
            super().mouseMoveEvent(event)
            if self.selectedItems():
                self.body.position = event.scenePos().x(), event.scenePos().y()
            
        def mouseReleaseEvent(self, event):
            super().mouseReleaseEvent(event)
            self.timer.stop()
    
        def addCircle(self, x, y, radius):
            item = Circle(radius)
            item.setPos(x, y)
            self.addItem(item)
            self.space.add(item.circle.body, item.circle)
            return item
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        scene = Scene(0, 0, 1000, 800)
        for i in range(7, 13):
            item = scene.addCircle(150 * (i - 6), 400, i * 5)
            item.setBrush(Qt.GlobalColor(i))    
        view = QGraphicsView(scene, renderHints=QPainter.Antialiasing)
        view.show()
        sys.exit(app.exec_())
    

    **可以调整如下:

    • 弹簧stiffnessdamping
    • 身体massmoment 惯性
    • 空格damping
    • Space.step 时间步长/每个 QTimer 超时的调用次数
    • QTimer interval

    【讨论】:

    • 这太完美了!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-03
    • 1970-01-01
    • 2012-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多