Issue
I made an object with three points of view. one for forward, one for left, and one for right.
these POVs are some path like a slice of a circle
I want to detect intersections or collisions between these POVs with rectangles to set the color of each POV
POVs rotate with the object in any direction but rectangles are always oriented
here is my code
from random import randint
from sys import argv
from PyQt6.QtCore import QRectF, Qt, QTimer, QPoint
from PyQt6.QtGui import QColor, QKeyEvent, QMouseEvent, QPainter, QPen, QPaintEvent, QPainterPath, QBrush
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QWidget
class Window(QWidget):
def __init__(self, parent=None) -> None:
super().__init__(parent)
screenWidth = 1920
screenHeight = 1080
self.gX = []
self.gY = []
self.framesShowPerSecond = 30
self.age = 0
self.maxAge = 500
self.windowWidth = 1920
self.windowHeight = 1080
self.isRunning = True
self.angle = -90
self.clockCounterVariable = 0
self.milliseconds = 0
self.seconds = 0
self.minutes = 0
self.hours = 0
self.setWindowTitle("test")
self.setGeometry((screenWidth - self.windowWidth) // 2, (screenHeight - self.windowHeight) // 2, self.windowWidth, self.windowHeight)
self.setLayout(QVBoxLayout())
self.showFullScreen()
self.setStyleSheet("background-color:rgb(20, 20, 20);font-size:20px;")
self.xCounter = 0
self.clock = QTimer(self)
self.graphicTimer = QTimer(self)
self.clock.timeout.connect(self.clockCounter)
self.graphicTimer.timeout.connect(self.update)
self.graphicTimer.start(round((1/self.framesShowPerSecond)*1000))
self.clock.start(10)
self.show()
def clockCounter(self) -> None:
if self.clockCounterVariable % 10 == 0:
x = self.xCounter
y = randint(0, 100)
self.xCounter += 1
self.gX.append(x - 0.5)
self.gX.append(x + 0.5)
self.gY.append(y)
self.gY.append(y)
self.clockCounterVariable += 1
def keyPressEvent(self, event: QKeyEvent) -> super:
key = QKeyEvent.key(event)
if key == 112 or key == 80: # P/p
if self.isRunning:
print("pause process")
self.isRunning = False
self.clock.stop()
self.graphicTimer.stop()
else:
print("continue process")
self.isRunning = True
self.clock.start(1000)
self.graphicTimer.start(round((1/self.framesShowPerSecond)*1000))
elif (key == 115) or (key == 83): # S/s
self.closeWindow()
self.update()
return super().keyPressEvent(event)
def mousePressEvent(self, event: QMouseEvent) -> super:
if event.buttons() == Qt.MouseButton.LeftButton:
if self.isRunning:
print("pause process")
self.isRunning = False
self.clock.stop()
self.graphicTimer.stop()
else:
print("continue process")
self.isRunning = True
self.clock.start(1000)
self.graphicTimer.start(round((1/self.framesShowPerSecond)*1000))
return super().mousePressEvent(event)
def paintEvent(self, event: QPaintEvent) -> super:
self.milliseconds = self.clockCounterVariable
self.seconds, self.milliseconds = divmod(self.milliseconds, 100)
self.minutes, self.seconds = divmod(self.seconds, 60)
self.hours, self.minutes = divmod(self.minutes, 60)
painter = QPainter()
painter.begin(self)
painter.setPen(QPen(QColor(255, 128, 20), 1, Qt.PenStyle.SolidLine))
painter.drawText(QRectF(35, 30, 400, 30), Qt.AlignmentFlag.AlignLeft, "{:02d} : {:02d} : {:02d} : {:02d}".format(self.hours, self.minutes, self.seconds, self.milliseconds))
painter.setPen(QPen(QColor(20, 20, 20), -1, Qt.PenStyle.SolidLine))
painter.setBrush(QBrush(QColor(20, 20, 160), Qt.BrushStyle.SolidPattern))
barrier = QRectF(1920//2-25, 1080//2-25-40, 50, 20)
painter.drawRect(barrier)
painter.translate(QPoint(1920//2, 1080//2))
painter.rotate(self.angle)
painter.setBrush(QBrush(QColor(200, 200, 200, 50), Qt.BrushStyle.SolidPattern))
r = 200
a = 40
b = a * 2
rect = QRectF(-r/2, -r/2, r, r)
path = QPainterPath()
path.arcTo(rect, -a, b)
path.closeSubpath()
if path.contains(barrier):
painter.setBrush(QBrush(QColor(200, 20, 20, 50), Qt.BrushStyle.SolidPattern))
else:
painter.setBrush(QBrush(QColor(20, 200, 20, 50), Qt.BrushStyle.SolidPattern))
painter.drawPath(path)
path = QPainterPath()
path.arcTo(rect, -a+90, b)
path.closeSubpath()
if path.contains(barrier):
painter.setBrush(QBrush(QColor(200, 20, 20, 50), Qt.BrushStyle.SolidPattern))
else:
painter.setBrush(QBrush(QColor(20, 200, 20, 50), Qt.BrushStyle.SolidPattern))
painter.drawPath(path)
path = QPainterPath()
path.arcTo(rect, -a-90, b)
path.closeSubpath()
if path.contains(barrier):
painter.setBrush(QBrush(QColor(200, 20, 20, 50), Qt.BrushStyle.SolidPattern))
else:
painter.setBrush(QBrush(QColor(20, 200, 20, 50), Qt.BrushStyle.SolidPattern))
painter.drawPath(path)
painter.setBrush(QBrush(QColor(160, 20, 20), Qt.BrushStyle.SolidPattern))
path = QPainterPath()
path.moveTo(30, 0)
path.lineTo(-30, -15)
path.lineTo(-10, 0)
path.lineTo(-30, 15)
path.closeSubpath()
painter.drawPath(path)
painter.end()
self.angle += 1
if self.angle == 360:
self.angle = 0
return super().paintEvent(event)
def closeWindow(self) -> None:
print("closing window ...")
self.close()
if __name__ == "__main__":
App = QApplication(argv)
window = Window()
exit(App.exec())
how should I do this purpose?
I want to detect collisions between some slices of a circle and rectangles.
Solution
Your code has many issues, but your main problem is caused by two factors:
- you are using
contains()
, which "Returnstrue
if the given rectangle is inside the path"; you should useintersects()
instead; - the "barrier" is created using the final, absolute coordinates, while all the other paths are in relative coordinates, since you are translating the painter;
- the painter is rotated, not the objects;
The result is that even using contains()
, it will never work, since the objects are too far apart:
barrier = QRectF(1920//2-25, 1080//2-25-40, 50, 20)
which is935, 475, 50, 20
;rect = QRectF(-r/2, -r/2, r, r)
which is-100, -100, 200, 200
;
As you can see, they are not even close.
Not only: it wouldn't work anyway because the paths never consider the rotation, as you applied it to the painter.
The proper solution requires to:
- always use the same coordinate reference;
- apply the transformations (translation and rotation) to the objects and then detect the possible collision;
In order to do that, we can use Qtransform along with its map()
function, which returns a new transformed path.
Note that QTransform is always aligned to the 0, 0
coordinate, so in order to properly rotate around a different reference point, you must:
- translate the transform to the reference point;
- apply the rotation;
- translate back using the negative of the reference point;
Here is an improved version of your code:
def paintEvent(self, event: QPaintEvent) -> super:
secs, ms = divmod(self.clockCounterVariable, 100)
mins, secs = divmod(secs, 60)
hours, mins = divmod(mins, 60)
painter = QPainter(self)
painter.setPen(QPen(QColor(255, 128, 20), 1, Qt.SolidLine))
painter.drawText(QRectF(35, 30, 400, 30), Qt.AlignLeft,
"{:02d} : {:02d} : {:02d} : {:02d}".format(hours, mins, secs, ms))
painter.setPen(QPen(QColor(20, 20, 20), -1, Qt.SolidLine))
painter.setBrush(QBrush(QColor(20, 20, 160), Qt.SolidPattern))
reference = QPointF(1920 / 2, 1080 / 2)
barrier = QRectF(reference.x() - 25, reference.y() - 65, 50, 20)
painter.drawRect(barrier)
r = 200
a = 40
b = a * 2
rect = QRectF(-r/2, -r/2, r, r).translated(reference)
collideBrush = QBrush(QColor(200, 20, 20, 50))
normalBrush = QBrush(QColor(20, 200, 20, 50))
reference = rect.center()
transform = QTransform()
transform.translate(reference.x(), reference.y())
transform.rotate(self.angle)
transform.translate(-reference.x(), -reference.y())
for deltaAngle in (0, 90, -90):
path = QPainterPath(reference)
path.arcTo(rect, -a + deltaAngle, b)
path.closeSubpath()
path = transform.map(path)
if path.intersects(barrier):
painter.setBrush(collideBrush)
else:
painter.setBrush(normalBrush)
painter.drawPath(path)
painter.setBrush(QBrush(QColor(160, 20, 20)))
path = QPainterPath()
path.moveTo(30, 0)
path.lineTo(-30, -15)
path.lineTo(-10, 0)
path.lineTo(-30, 15)
path.closeSubpath()
path.translate(rect.center())
painter.drawPath(transform.map(path))
self.angle = (self.angle + 1) % 360
Note that there are other issues with your code, for instance:
- use
event.key()
, notQKeyEvent.key(event)
; - I don't know where you got those
112
and115
values for the keys, but they are wrong:event.key()
returns an enum that is always the same for letter keys, no matter the modifiers (which you must check withevent.modifier()
); just check the key againstQt.Key.Key_P
orQt.Key.Key_S
and it will work for both lower and upper cases; - event handlers don't return anything, those
return
when calling the base implementations are useless; - you are creating instance attributes that are only used within the scope of a function: setting
self.milliseconds
,self.seconds
etc. is quite pointless for this case; if you do need those values outside of that function, they should certainly not be computed in thepaintEvent()
, because if the window is hidden it will not be called: instead, compute those values inclockCounter
and callself.update()
;
Finally, when dealing with complex graphics, implementing everything with the basic QPainter is not really effective, and normally results in making things more complex (and prone to errors and bugs) than necessary. Consider using the Graphics View Framework instead.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.