Search code examples
pythonpython-3.xpygamecollision-detection

How can I test if a point is in an ellipse?


I am working on a Pygame project and there is water on the map. I want to slow down the player if player goes into water. Area where water is has an elliptic shape. I can slow down player if the shape was a rectangle but i don't want the non-water area to slow down the player. So how can i get the area where player is supposed to be slowed down? How to control if character's coordinates are in the ellipse?

Edit: I checked the link in the comments and it worked for me.


Solution

  • The collision of an ellipse and a point can be reduced to the collision of a circle and a point by scaling the ellipse to appear as a circle and scaling the distance vector of the point to the center of the ellipse in the same way. Since the ellipses are axis-aligned in PyGame, this can easily be achieved by scaling one of the coordinates by the ratio of the ellipse axis length.

    Define the bounding rectangle (pygame.Rect) of the ellipse (ellipse_rect) and get the semi-axis (a, b):

    a = ellipse_rect.width // 2
    b = ellipse_rect.height // 2
    

    Compute the ratio of the semi-axis

    scale_y = a / b
    

    Define an point (test_x, test_y) and calculate the vector of the point to the center of the ellipse (cpt_x, cpt_y). Scale the y-coordinate of the vector with the ratio of semi-x-axis and semi-y-axis:

    cpt_x, cpt_y = ellipse_rect.center
    dx = test_x - cpt_x
    dy = (test_y - cpt_y) * scale_y
    

    The point lies in the ellipse if the square of the Euclidean distance (dx*dx + dy*dy) is smaller than the square of the semi-x axis (a*a):

    collide = dx*dx + dy*dy <= a*a  
    

    See also Collision and Intersection - Point and Ellipse


    Minimal example:

    import pygame
    
    pygame.init()
    
    width, height = 400, 400
    window = pygame.display.set_mode((width, height))
    clock = pygame.time.Clock()
    
    ellipse_rect = pygame.Rect(0, 0, 200, 100)
    ellipse_rect.center = window.get_rect().center
    
    run = True
    while run:
        clock.tick(60)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
    
        a = ellipse_rect.width // 2
        b = ellipse_rect.height // 2
        scale_y = a / b
        cpt_x, cpt_y = ellipse_rect.center
        test_x, test_y = pygame.mouse.get_pos()
        dx = test_x - cpt_x
        dy = (test_y - cpt_y) * scale_y
        collide = dx*dx + dy*dy <= a*a  
                
        window.fill(0)
        
        color = (127, 0, 0) if collide else (0, 127, 0)
        pygame.draw.ellipse(window, color, ellipse_rect)
        if collide:
            pygame.draw.ellipse(window, (255, 255, 255), ellipse_rect, 3)
    
        pygame.display.flip()
    
    pygame.quit()
    exit()