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

2023-12-14

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

这是期望的行为:

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

由于在圆移动时找到圆的最佳“安全”位置变得越来越复杂,我想提出另一种使用物理模拟器来实现这一点的方法。


鉴于上述行为,它是 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_())

**可以调整以下内容:

  • Spring stiffness and damping
  • Body mass and moment惯性
  • Space damping
  • Space.step时间步/每个 QTimer 超时调用多少次
  • QTimer interval
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

避免鼠标移动的 QGraphicsItem 形状发生碰撞 的相关文章

随机推荐

  • Haskell第一步编译错误

    我刚刚从这里安装了完整的 Haskell 平台和安装程序https www haskell org platform mac html 现在 作为菜鸟 我创建了一个非常简单的程序 只是为了看看它是否有效 f Int gt Int f x x
  • 为什么每次我在 CosmosDB 文档资源管理器中查询 SELECT count(1) FROM c 时,它的值都会更改?

    我有一个包含大约 600 700 000 个文档的数据库 当我在文档资源管理器中执行 SELECT value count 1 FROM c 时 它返回的值范围从 64 000 到 72 000 看起来是随机的 当我使用 Python SD
  • 从 python 重新启动本地计算机

    有没有办法让电脑重启后运行Python程序 通用解决方案很好 但特别是我在 Windows 上 据我所知 没有通用的方法可以做到这一点 对于 Windows 您需要访问 Win32 API 就像这样 import win32api win3
  • 使用 Google 表格创建嵌套 JSON 对象

    我正在尝试自学一些 AppScript Javascript 作为练习 我想生成以下 JSON 对象 config type City Details config data city type MAJOR CITY city data c
  • 全局变量(再次)

    我一直听说全局变量应该never被使用 但我倾向于忽略 never 规则头脑发热 真的没有例外吗 例如 我目前正在使用 SDL 用 C 编写一个小游戏 在我看来 拥有一个带有指向屏幕缓冲区的指针的全局变量很有意义 因为代表游戏中不同类型事物
  • Jquery数据表在其中一列中显示图像

    我正在使用 Jquery 数据表并使用 Ajax 从服务器获取数据 基本上我发送的数据是关于Order并且有不同类型的订单 我想要实现的目标是发送Order type与订单数据一起并在客户端显示图像 我主要有两种类型 Surplus and
  • 远程 JMX 连接

    我正在尝试打开与远程计算机上运行的 java 应用程序的 JMX 连接 应用程序 JVM 配置有以下选项 com sun management jmxremote com sun management jmxremote port 1088
  • 维护 MySQL“IN”查询中的顺序

    我有下表 DROP TABLE IF EXISTS test foo CREATE TABLE test foo id int 10 unsigned NOT NULL auto increment name varchar 45 NOT
  • updatepanel 中的 asp.net 链接按钮不触发

    我有一个 ASP NET Web 应用程序 在我的 aspx页面我有一个更新面板 其中有 3asp LinkButton这应该调用后面的 C 代码 问题是 onclick 不起作用 代码如下 div div
  • 如何在 R 中创建分组箱线图?

    我想合并分组的三个数据集并获得一个只有两个框的图 1 个用于 A 1 个用于 B 您能建议如何实现吗 我正在尝试在 R 中创建分组箱线图 我有 2 个组 A 和 B 在每个组中我有 3 个子组 每个子组有 5 个测量值 以下是我构建箱线图的
  • Java:用于异步数据库写入的TaskExecutor?

    我正在考虑使用 Java 的 TaskExecutor 来触发异步数据库写入 可以理解的是 线程不是免费的 但假设我使用固定的线程池大小 例如 5 10 这怎么是一个坏主意呢 我们的应用程序使用缓冲区从一个非常大的文件中读取数据 并在执行一
  • 对于每个单独线程一次写入的位集单独位线程是否安全?

    我想知道我们是否在操作口号或者甚至是 64 位字长 我可以同时对位集中的各个位进行操作吗 假设我有 10 个线程 每个线程都有threadId 所有线程是否可以同时设置bits threadId value std bitset 运算符 数
  • applicationContextProvider 未被调用

    我使用的是Spring 3 0 3 我想使用 applicationContextProvider 所以我声明
  • 如何在android中的EditText的错误文本中写入样式?

    我正在尝试为我的 Android 应用程序编写新的自定义样式 我需要为设置后出现的 errorText 提供样式setError in EditText 我怎样才能定制它的风格 例如 我想设置它background白色和textColor
  • NumPy odeint 输出额外变量

    在仿真过程中保存中间变量的最简单方法是什么odeint在 Numpy 中 例如 def dy y t x np rand 3 1 return y x sum sim odeint dy 0 np arange 0 1 0 1 保存存储在中
  • R:找出工作日的差异

    计算工作日中的日期差异时遇到问题 即排除周末 如 Excel 中的网络日函数 这是我的数据 e lt structure list date pr structure c 15909 15933 16517 15961 15974 1597
  • Google Drive API 限制导出下载 10 mb 的策略

    我们一直在开发一个解决方案 负责生成嵌入大量图像的 PPT 然后通过 Google Slides 和 Google Drive API 在浏览器中下载 因此 我们在项目进行到一半时发现 从 Google Drive API 导出文件存在限制
  • MvcContrib TestHelper Fluent 路由测试和特定 HttpVerbs 的问题

    我正在尝试使用 MvcContrib TestHelper 流畅的路由测试 API 但我看到了奇怪的行为 WithMethod HttpVerb 扩展方法似乎没有按预期执行 这是我的控制器 显示了接受不同 HttpVerbs 的 2 个操作
  • 即使长度为 1,也返回正确的 .length

    如果我做一个 services Get Service services length 我得到126服务 但如果我指定一项服务 services Get Service Name vds services length 我什么也没得到 这搞
  • 避免鼠标移动的 QGraphicsItem 形状发生碰撞

    引发了一场有趣的讨论here关于防止 QGraphicsScene 中由 QGraphicsEllipseItems 组成的圆发生碰撞 问题将范围缩小到两个相互冲突的项目 但更大的目标仍然存在 对于任意数量的碰撞怎么办 这是期望的行为 当一