問題描述
此處提出了一個有趣的討論,該討論是關于在 QGraphicsScene 中防止由 QGraphicsEllipseItems 構成的圓的碰撞.這個問題將范圍縮小到 2 個碰撞項目,但更大的目標仍然存在,對于任意數量的碰撞怎么辦?
An interesting discussion was raised here about preventing collisions of circles, made of QGraphicsEllipseItems, in a QGraphicsScene. The question narrowed the scope to 2 colliding items but the larger goal still remained, what about for any number of collisions?
這是期望的行為:
- 當一個項目被拖動到其他項目上時,它們不應重疊,而是應該在這些項目周圍移動,盡可能靠近鼠標.
- 如果它被其他物品阻擋,它不應該傳送".
- 應該是平穩(wěn)且可預測的運動.
隨著為圓在移動時找到最佳安全"位置變得越來越復雜,我想展示另一種使用物理模擬器來實現它的方法.
As this becomes increasingly complex to find the best "safe" position for the circle while it’s moving I wanted to present another way to implement this using a physics simulator.
推薦答案
鑒于上面描述的行為,它是 2D 剛體物理的一個很好的候選者,也許沒有它可以做到,但很難做到完美.我在這個例子中使用 pymunk 因為我很熟悉它,但是相同的概念也適用于其他庫.
Given the behavior described above it’s a good candidate for 2D rigid body physics, maybe it can be done without but it would be difficult to get it perfect. I am using pymunk in this example because I’m familiar with it but the same concepts will work with other libraries.
場景有一個運動體來表示鼠標,而圓圈最初由靜態(tài)體表示.當一個圓圈被選中時,它會切換到一個動態(tài)物體,并通過一個阻尼彈簧約束到鼠標.它的位置隨著空間在每個超時間隔上按給定時間步更新而更新.
The scene has a kinematic body to represent the mouse and the circles are represented by static bodies initially. While a circle is selected it switches to a dynamic body and is constrained to the mouse by a damped spring. Its position is updated as the space is updated by a given time step on each timeout interval.
該項目實際上并未以與未啟用 ItemIsMovable
標志相同的方式移動,這意味著它不再隨鼠標立即移動.它非常接近,但有一點延遲,盡管您可能更喜歡這樣來更好地了解它對碰撞的反應.(即便如此,您也可以微調參數,讓它比我移動得更快/更靠近鼠標**).
The item is not actually moved in the same way as the ItemIsMovable
flag is not enabled, which means it no longer moves instantly with the mouse. It’s very close but there’s a small delay, although you may prefer this to better see how it reacts to collisions. (Even so, you can fine-tune the parameters to have it move faster/closer to the mouse than I did**).
另一方面,碰撞得到了完美的處理,并且已經支持其他類型的形狀.
On the other hand, the collisions are handled perfectly and will already support other kinds of shapes.
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_())
**可以調整如下:
- 彈簧
剛度
和阻尼
- 身體
質量
和慣性矩
- 空間
阻尼
Space.step
時間步長/每個 QTimer 超時的調用次數- QTimer
間隔
- Spring
stiffness
anddamping
- Body
mass
andmoment
of inertia - Space
damping
Space.step
time step / how many calls per QTimer timeout- QTimer
interval
這篇關于避免鼠標移動的 QGraphicsItem 形狀的碰撞的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!