How to calculate distance from a player to a dynamic collision point

Question:

I’m trying to create sensors for a car to keep track of the distances from the car to the borders of the track. My goal is to have 5 sensors (see image below) and use them to train a machine learning algorithm.

But I can’t figure out a way to calculate these distances. For now, I just need a sample of code and a logical explanation of how to implement this with PyGame. But a mathematical and geometrical explanation would be really nice as well for further reading. I’m using this code from a YouTuber tutorial series.

My biggest issue is how to get the points in blue. (last picture) I need them to create the red lines from the car to the points and to calculate the length of these lines. These points are taking the car’s position and rotation into account and they have a specific angle at which they get out of the car. I’ve managed to create the lines, but could not get the point the line would collide with the track.

What I want to accomplish:

What I want to accomplishenter image description here

I’ve tried different approaches to this problem, but for now, my biggest problem is how to get the position of the blue dots:

sensors position

— Edit from the feedback ——

I added a new paragraph to better explain the problem. This way I hope it is clearer why this problem is different from those said to be related to it. The other problem we have the desired final position (mouse or enemy) in this one we have to figure out which point is the one we are going to use to create the line, and this is my issue.

My GitHub repo of the project
https://github.com/pedromello/ml-pygame/blob/main/main.py

The part of the code where I’m trying to implement this:

class AbstractCar:
    def __init__(self, max_vel, rotation_vel):
        self.img = self.IMG
        self.max_vel = max_vel
        self.vel = 0
        self.rotation_vel = rotation_vel
        self.angle = 0
        self.x, self.y = self.START_POS
        self.acceleration = 0.1

    def rotate(self, left=False, right=False):
        if left:
            self.angle += self.rotation_vel
        elif right:
            self.angle -= self.rotation_vel

    def draw(self, win):
        blit_rotate_center(win, self.img, (self.x, self.y), self.angle)

    def move_forward(self):
        self.vel = min(self.vel + self.acceleration, self.max_vel)
        self.move()

    def move_backward(self):
        self.vel = max(self.vel - self.acceleration, -self.max_vel/2)
        self.move()

    def move(self):
        radians = math.radians(self.angle)
        vertical = math.cos(radians) * self.vel
        horizontal = math.sin(radians) * self.vel

        self.y -= vertical
        self.x -= horizontal

    def collide(self, mask, x=0, y=0):
        car_mask = pygame.mask.from_surface(self.img)
        offset = (int(self.x - x), int(self.y - y))
        poi = mask.overlap(car_mask, offset)
        return poi

    def reset(self):
        self.x, self.y = self.START_POS
        self.angle = 0
        self.vel = 0


class PlayerCar(AbstractCar):
    IMG = RED_CAR
    START_POS = (180, 200)

    def reduce_speed(self):
        self.vel = max(self.vel - self.acceleration / 2, 0)
        self.move()

    def bounce(self):
        self.vel = -self.vel
        self.move()

    def drawSensors(self):
        radians = math.radians(self.angle)
        vertical = -math.cos(radians)
        horizontal = math.sin(radians)
        car_center = pygame.math.Vector2(self.x + CAR_WIDTH/2, self.y + CAR_HEIGHT/2)

        pivot_sensor = pygame.math.Vector2(car_center.x + horizontal * -100, car_center.y - vertical * -100)

        #sensor1 = Vector(30, 0).rotate(self.angle) #+ self.pos # atualiza a posição do sensor 1
        #sensor2 = Vector(30, 0).rotate((self.angle+30)%360) #+ self.pos # atualiza a posição do sensor 2
        #sensor3 = Vector(30, 0).rotate((self.angle-30)%360) #+ self.pos # atualiza a posição do sensor 3

        #rotate pivot sensor around car center
        sensor_2 = pivot_sensor.rotate((self.angle+30)%360)

        # Sensor 1
        pygame.draw.line(WIN, (255, 0, 0), car_center, pivot_sensor, 2)

        # Sensor 2
        pygame.draw.line(WIN, (255, 0, 0), car_center, sensor_2, 2)

        # Sensor 3
        #pygame.draw.line(WIN, (255, 0, 0), (self.x, self.y), (self.x + horizontal * 100, self.y - vertical * 100), 2)
Asked By: Pedro Renato Mello

||

Answers:

Thank you for the comments, I solved my problem using the idea of firing sensors so I can get the point on the wall when the "bullet" hits it.

enter image description here

As we can see when the bullet hits the wall we can create a line that connects the point to the car. This is not the best solution, as it takes time for the bullet to hit the wall and in the meantime, the car is "blind".

As Rabbid76 commented, using raycasting may be the solution I was looking for.

Code for reference:

Sensor Bullet class

class SensorBullet:
    def __init__(self, car, base_angle, vel, color):
        self.x = car.x + CAR_WIDTH/2
        self.y = car.y + CAR_HEIGHT/2
        self.angle = car.angle
        self.base_angle = base_angle
        self.vel = vel
        self.color = color
        self.img = pygame.Surface((4, 4))
        self.fired = False
        self.hit = False
        self.last_poi = None

    def draw(self, win):
        pygame.draw.circle(win, self.color, (self.x, self.y), 2)

    def fire(self, car):
        self.angle = car.angle + self.base_angle
        self.x = car.x + CAR_WIDTH/2
        self.y = car.y + CAR_HEIGHT/2
        self.fired = True
        self.hit = False

    def move(self):
        if(self.fired):
            radians = math.radians(self.angle)
            vertical = math.cos(radians) * self.vel
            horizontal = math.sin(radians) * self.vel

            self.y -= vertical
            self.x -= horizontal

    def collide(self, x=0, y=0):
        bullet_mask = pygame.mask.from_surface(self.img)
        offset = (int(self.x - x), int(self.y - y))
        poi = TRACK_BORDER_MASK.overlap(bullet_mask, offset)
        if poi:
            self.fired = False
            self.hit = True
            self.last_poi = poi
        return poi

    def draw_line(self, win, car):
        if self.hit:
            pygame.draw.line(win, self.color, (car.x + CAR_WIDTH/2, car.y + CAR_HEIGHT/2), (self.x, self.y), 1)
            pygame.display.update()

    def get_distance_from_poi(self, car):
        if self.last_poi is None:
            return -1
        return math.sqrt((car.x - self.last_poi[0])**2 + (car.y - self.last_poi[1])**2)

Methods the car must perform to use the sensor

# Inside car's __init__ method
self.sensors = [SensorBullet(self, 25, 12, (100, 0, 255)), SensorBullet(self, 10, 12, (200, 0, 255)), SensorBullet(self, 0, 12, (0, 255, 0)), SensorBullet(self, -10, 12, (0, 0, 255)), SensorBullet(self, -25, 12, (0, 0, 255))]
# ------

# Cars methods
def fireSensors(self): 
    for bullet in self.sensors:
        bullet.fire(self)

def sensorControl(self):
    #print(contains(self.sensors, lambda x: x.hit))

    for bullet in self.sensors:
        if not bullet.fired:
            bullet.fire(self)

    for bullet in self.sensors:
        bullet.move()

def get_distance_array(self):
    return [bullet.get_distance_from_poi(self) for bullet in self.sensors]

Answered By: Pedro Renato Mello