Evitar las colisiones de QGraphicsItem formas movido por el ratón

0

Pregunta

Un interesante debate se planteó aquí acerca de la prevención de las colisiones de los círculos, de hecho de QGraphicsEllipseItems, en un QGraphicsScene. La cuestión se redujo el ámbito de aplicación 2 chocar pero la meta más grande aún permanece, lo que sobre para cualquier número de colisiones?

Este es el comportamiento deseado:

  • Cuando un elemento se arrastra sobre otros elementos que no deben superponerse, sino que debe moverse alrededor de esos elementos tan cerca como sea posible a la del ratón.
  • No se debe de "teletransportarse" si se bloqueados por otros elementos.
  • Debe ser una constante y predecible movimiento.

Como esto se vuelve cada vez más complejo para encontrar la mejor posición "seguro" para el círculo mientras se está moviendo quería presentar otra forma de implementar esta utilizando un simulador físico.

collision pymunk pyqt5 python
2021-11-23 02:01:24
1

Mejor respuesta

3

Dado el comportamiento descrito anteriormente, es un buen candidato para 2D física de cuerpos rígidos, tal vez se puede hacer sin el, pero sería difícil hacerlo perfecto. Estoy usando pymunk en este ejemplo, ya que estoy familiarizado con él, pero los mismos conceptos trabajará con otras bibliotecas.

La escena tiene una cinemática del cuerpo para representar el ratón y los círculos están representados por la estática de los cuerpos inicialmente. Mientras que un círculo es seleccionado cambia a una dinámica de cuerpo y está limitada a la del ratón por un amortiguamiento de la primavera. Su posición se actualiza a medida que el espacio se actualiza por un tiempo determinado paso en cada intervalo de tiempo de espera.

El artículo es, en realidad, no se movió de la misma manera como la ItemIsMovable indicador no está habilitado, lo que significa que ya no se mueve instantáneamente con el ratón. Es muy cerca, pero hay un pequeño retraso, aunque puede preferir esta para ver mejor la forma en que reacciona a las colisiones. (Aún así, usted puede ajustar los parámetros a tener que mover más rápido/más cerca de el ratón que me hizo**).

Por otro lado, las colisiones se manejan a la perfección y ya es compatible con otros tipos de formas.

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_())

**Puede ajustar los siguientes:

  • La primavera stiffness y damping
  • Cuerpo mass y moment de inercia
  • Espacio damping
  • Space.step paso de tiempo / ¿cuántas llamadas por QTimer tiempo de espera
  • QTimer interval
2021-12-01 01:57:12

Esto es perfecto!!
drivereye

En otros idiomas

Esta página está en otros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Slovenský
..................................................................................................................