首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >避免鼠标移动的QGraphicsItem形状的碰撞

避免鼠标移动的QGraphicsItem形状的碰撞
EN

Stack Overflow用户
提问于 2021-11-23 02:01:24
回答 1查看 111关注 0票数 4

这里提出了一项有趣的讨论,讨论如何在QGraphicsScene中防止由QGraphicsEllipseItems构成的圆圈碰撞。这个问题将范围缩小到两个碰撞项目,但更大的目标仍然是:,对于任何数量的碰撞,怎么办?

这是所需的行为:

  • 当一个项目被拖到其他项目上时,它们不应该重叠,相反,它应该尽可能靠近鼠标移动这些项目。
  • 如果它被其他项目阻塞,它就不应该“传送”。
  • 这应该是一个平稳和可预测的运动。

随着这变得越来越复杂,以找到最好的“安全”的位置为圆圈,而它在移动,我想提出另一种方式来实现这一点,使用物理模拟器。

EN

回答 1

Stack Overflow用户

发布于 2021-11-23 02:01:24

考虑到上面描述的行为,它是二维刚体物理的一个很好的候选者,也许没有它是可以做到的,但是要使它完美是很困难的。在本例中,我使用小羚羊,因为我熟悉它,但相同的概念将适用于其他库。

场景有一个运动体来表示鼠标,圆圈最初是由静态物体表示的。当选择一个圆圈时,它会切换到一个动态物体,并被一个阻尼的弹簧限制在鼠标上。当空间在每个超时间隔上按给定的时间步骤更新时,其位置将被更新。

实际上,该项目的移动方式与未启用ItemIsMovable标志的方式不同,这意味着它不再使用鼠标立即移动。它是非常接近,但有一个小的延迟,虽然你可能更喜欢这个,以更好地了解它是如何对碰撞的反应。(即使如此,您也可以微调参数,使其比我所做的更快/更接近鼠标。)

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

代码语言:javascript
运行
复制
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_())

**可调整如下:

  • Spring stiffnessdamping
  • 物体mass和惯性的moment
  • 空间damping
  • Space.step时间步骤/每个QTimer超时调用数
  • QTimer interval
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70074528

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档