1

I have a rectangle drawn that rotate and I need to check if it collides. The entire class:

class Laser:
def __init__(self, player_x, player_y):
    self.x = player_x
    self.y = player_y
    self.original_image = pygame.Surface((2, 1000))
    self.original_image.set_colorkey( (0,0,0) )
    self.original_image.fill( (255,0,0) )
    self.copy_image = self.original_image.copy()
    self.copy_image.set_colorkey( (0,0,0) )
    self.rect = self.copy_image.get_rect()
    self.new_image = pygame.Surface((2, 1000))
    self.angle = 0

def continueDrawLaser(self):
    if laser_bool:
        screen.blit(self.new_image, self.rect)

def rotate(self):
    # get rectangle of player and laser, as if the angle would be 0
    player_rect = player1.original_player_image.get_rect(topleft=(player1.x, player1.y))
    laser_rect = self.original_image.get_rect(midbottom=player_rect.midtop)
    self.angle = player1.angle
    pivotPos = [player_rect.centerx - laser_rect.x, player_rect.centery - laser_rect.y]

    # calcaulate the axis aligned bounding box of the rotated image
    w, h = self.original_image.get_size()
    box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
    box_rotate = [p.rotate(self.angle) for p in box]
    min_box = (min(box_rotate, key=lambda p: p[0])[0], min(box_rotate, key=lambda p: p[1])[1])
    max_box = (max(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1])

    # calculate the translation of the pivot
    pivot = pygame.math.Vector2(pivotPos[0], -pivotPos[1])
    pivot_rotate = pivot.rotate(self.angle)
    pivot_move = pivot_rotate - pivot

    # calculate the upper left origin of the rotated image
    origin = (laser_rect.x + min_box[0] - pivot_move[0], laser_rect.y - max_box[1] + pivot_move[1])  #x,y

    # get a rotated image
    self.new_image = pygame.transform.rotate(self.original_image, self.angle)

    # get new rectangle
    self.rect = self.new_image.get_rect(topleft=origin)

This is the collision func:

#check if rock collides with laser
def collisionRockLaser(self, laser1):
    laser_rect = laser1.rect
    rock_rect = pygame.Rect(self.x, self.y, rock_width, rock_height)
    if laser_rect.colliderect(rock_rect):
        rocks.pop(rocks.index(rock2))
        global score
        score += 1

And this is what I get:

enter image description here

I thought it was enough pass self.rect,since it updates with the rotated position each time,to detect collisions,however it seems that I have to use the Separating Axis Theorem,could you help me?

1 Answer 1

3

One option is to create and algorithm which intersects a line and a rectangle.

First create an algorithm which intersects 2 line segments:

P     ... point on the 1. line
R     ... normalized direction of the 1. line

Q     ... point on the 2. line
S     ... normalized direction of the 2. line

alpha ... angle between Q-P and R
beta  ... angle between R and S

X     ... intersection point
t     ... distance between P and X
u     ... distance between Q and X

gamma  =  180° - alpha - beta

t  = | Q - P | * sin(gamma) / sin(beta)
u  = | Q - P | * sin(alpha) / sin(beta)

t  =  dot(Q-P, (S.y, -S.x)) / dot(R, (S.y, -S.x))  =  determinant(mat2(Q-P, S)) / determinant(mat2(R, S))
u  =  dot(Q-P, (R.y, -R.x)) / dot(R, (S.y, -S.x))  =  determinant(mat2(Q-P, R)) / determinant(mat2(R, S))

X  =  P + R * t  =  Q + S * u

The algorithm is explained in detail, in the answer to Problem with calculating line intersections

def collideLineLine(P0, P1, Q0, Q1):  
    d = (P1[0]-P0[0]) * (Q1[1]-Q0[1]) + (P1[1]-P0[1]) * (Q0[0]-Q1[0]) 
    if d == 0:
        return False
    t = ((Q0[0]-P0[0]) * (Q1[1]-Q0[1]) + (Q0[1]-P0[1]) * (Q0[0]-Q1[0])) / d
    u = ((Q0[0]-P0[0]) * (P1[1]-P0[1]) + (Q0[1]-P0[1]) * (P0[0]-P1[0])) / d
    return 0 <= t <= 1 and 0 <= u <= 1

To test if the line segment intersects the rectangle, you have to test if it intersect any of the 4 sides of the rectangle:

def colideRectLine(rect, p1, p2):

    return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
            collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
            collideLineLine(p1, p2, rect.bottomright, rect.topright) or
            collideLineLine(p1, p2, rect.topright, rect.topleft))

Setup a line along the laser, dependent on the angle:

angle = player1.angle
if angle < 0:
    angle += 360

if angle < 90 or (angle > 180 and angle < 270):
    laserline = [laser1.rect.topleft, laser1.rect.bottomright]
else:
    laserline = [laser1.rect.bottomleft, laser1.rect.topright]

Evaluate in the line intersects a pygame.Rect enemy_rect:

collide = colideRectLine(enemy_rect, *laserline)

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I really appreciate the attached explanation. I do not know why but if I move very very very very quickly the mouse the collision is not detected but it seems almost a containment feature so it's more than okay! thanks again and happy New Year!
@C-Gian Probably the laser never intersects the rectangle. First it is "before" and in the next frame it is "above"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.