pygame/pygame

`rect.clipline(line)` is not working as expected !

Aperta

#3999 aperta il 13 set 2023

 (23 commenti) (0 reazioni) (0 assegnatari)C (2534 fork)batch import
buggood first issuerect

Metriche repository

Star
 (5771 stelle)
Metriche merge PR
 (Nessuna PR mergiata in 30 g)

Descrizione

Next Steps

  • Add a small test case proving the issue in test/rect.py
  • The code for Rect.clipline is in src_c/rect.c@pg_rect_clipline

Issue details

Environment:

  • Operating system (e.g. Windows, Linux(Debian), Linux(Ubuntu), Mac): Ubuntu 22.04 LTS AMD64
  • Python version (e.g. 3.7.9, 3.8.5) : 3.10.12
  • SDL version (e.g. SDL 2.0.12): 2.28.2
  • PyGame version (e.g. 2.0.0.dev10, 1.9.6): 2.5.1

Current behavior:

I have a rectangle as floor defined manually. Sometimes the rays generated in radius 180 degree don't collide with the floor. Is it caused by bug in the rect.clipline(line) method?

Expected behavior:

I need a robust and stable collision system for rays (as line) and floor (as rectangle)! The every collision between rectangle and line must be correctly evaluated.

Screenshots

https://github.com/pygame/pygame/assets/74611856/f61e7bf6-78f1-490b-b9b2-3c422a6fd443

Steps to reproduce:

  1. create more than or equal four rectangles
  2. create 180 rays in radius 180 degrees
  3. try to collide with lines to the rectangle

Test code

If possible add a simple test program that shows the problem described in this report.

import numpy as np
import pygame

from flappy_bird_gymnasium.envs.constants import (
    BASE_HEIGHT,
    BASE_WIDTH,
    PIPE_HEIGHT,
    PIPE_WIDTH,
    PLAYER_ROT_THR,
)


class LIDAR:
    def __init__(self, max_distance):
        self._max_distance = max_distance
        self.collisions = np.zeros((180, 2))

    def scan(self, player_x, player_y, player_rot, upper_pipes, lower_pipes, ground):
        result = np.empty([180])

        # sort pipes from nearest to farthest
        upper_pipes = sorted(upper_pipes, key=lambda pipe: pipe["x"])
        lower_pipes = sorted(lower_pipes, key=lambda pipe: pipe["x"])

        # get collisions with precision 1 degree
        for i, angle in enumerate(range(0, 180, 1)):
            # Getting player's rotation
            visible_rot = PLAYER_ROT_THR
            if player_rot <= PLAYER_ROT_THR:
                visible_rot = player_rot

            rad = np.radians(angle - 90 - visible_rot)
            x = self._max_distance * np.cos(rad) + player_x
            y = self._max_distance * np.sin(rad) + player_y
            line = (player_x, player_y, x, y)
            self.collisions[i] = (x, y)

            # check ground collision
            ground_rect = pygame.Rect(0, ground["y"], BASE_WIDTH, BASE_HEIGHT)
            collision = ground_rect.clipline(line)
            # print("gound collision: ", collision, " ", angle, "line: ", line)
            if collision:
                self.collisions[i] = collision[0]

            print(self.collisions[i][1])
            assert self.collisions[i][1] < 408, "collision with ground"

            # check pipe collision
            for up_pipe, low_pipe in zip(upper_pipes, lower_pipes):
                # upper and lower pipe rects
                up_pipe_rect = pygame.Rect(
                    up_pipe["x"], up_pipe["y"], PIPE_WIDTH, PIPE_HEIGHT
                )
                low_pipe_rect = pygame.Rect(
                    low_pipe["x"], low_pipe["y"], PIPE_WIDTH, PIPE_HEIGHT
                )

                # check collision
                collision_A = up_pipe_rect.clipline(line)
                collision_B = low_pipe_rect.clipline(line)

                if collision_A:
                    self.collisions[i] = collision_A[0]
                    break
                elif collision_B:
                    self.collisions[i] = collision_B[0]
                    break

            print(self.collisions[i][1])
            assert self.collisions[i][1] < 405, "collision with ground"

            # calculate distance
            result[i] = np.sqrt(
                (player_x - self.collisions[i][0]) ** 2
                + (player_y - self.collisions[i][1]) ** 2
            )

        return result

Rendering of rays:

        # LIDAR
        for i in range(self.game.lidar.collisions.shape[0]):
            pygame.draw.line(
                self.surface,
                "red",
                (
                    self.game.player_x + PLAYER_WIDTH,
                    self.game.player_y + (PLAYER_HEIGHT / 2),
                ),
                (
                    self.game.lidar.collisions[i][0],
                    self.game.lidar.collisions[i][1],
                ),
                1,
            )

Guida contributor