""" Functions for calculating geometry. """ from arcade.sprite import Sprite from arcade.sprite_list import SpriteList from typing import List from arcade.arcade_types import PointList from arcade.arcade_types import Point PRECISION = 2 def are_polygons_intersecting(poly_a: PointList, poly_b: PointList) -> bool: """ Return True if two polygons intersect. :param PointList poly_a: List of points that define the first polygon. :param PointList poly_b: List of points that define the second polygon. :Returns: True or false depending if polygons intersect :rtype bool: """ for polygon in (poly_a, poly_b): for i1 in range(len(polygon)): i2 = (i1 + 1) % len(polygon) projection_1 = polygon[i1] projection_2 = polygon[i2] normal = (projection_2[1] - projection_1[1], projection_1[0] - projection_2[0]) min_a, max_a, min_b, max_b = (None,) * 4 for poly in poly_a: projected = normal[0] * poly[0] + normal[1] * poly[1] if min_a is None or projected < min_a: min_a = projected if max_a is None or projected > max_a: max_a = projected for poly in poly_b: projected = normal[0] * poly[0] + normal[1] * poly[1] if min_b is None or projected < min_b: min_b = projected if max_b is None or projected > max_b: max_b = projected if max_a <= min_b or max_b <= min_a: return False return True def check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool: """ Check for a collision between two sprites. :param sprite1: First sprite :param sprite2: Second sprite :Returns: True or False depending if the sprites intersect. """ if not isinstance(sprite1, Sprite): raise TypeError("Parameter 1 is not an instance of the Sprite class.") if isinstance(sprite2, SpriteList): raise TypeError("Parameter 2 is a instance of the SpriteList instead of a required Sprite. See if you meant to " "call check_for_collision_with_list instead of check_for_collision.") elif not isinstance(sprite2, Sprite): raise TypeError("Parameter 2 is not an instance of the Sprite class.") return _check_for_collision(sprite1, sprite2) def _check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool: """ Check for collision between two sprites. :param Sprite sprite1: Sprite 1 :param Sprite sprite2: Sprite 2 :returns: Boolean """ collision_radius_sum = sprite1.collision_radius + sprite2.collision_radius diff_x = sprite1.position[0] - sprite2.position[0] diff_x2 = diff_x * diff_x if diff_x2 > collision_radius_sum * collision_radius_sum: return False diff_y = sprite1.position[1] - sprite2.position[1] diff_y2 = diff_y * diff_y if diff_y2 > collision_radius_sum * collision_radius_sum: return False distance = diff_x2 + diff_y2 if distance > collision_radius_sum * collision_radius_sum: return False return are_polygons_intersecting(sprite1.points, sprite2.points) def check_for_collision_with_list(sprite: Sprite, sprite_list: SpriteList) -> List[Sprite]: """ Check for a collision between a sprite, and a list of sprites. :param Sprite sprite: Sprite to check :param SpriteList sprite_list: SpriteList to check against :returns: List of sprites colliding, or an empty list. """ if not isinstance(sprite, Sprite): raise TypeError("Parameter 1 is not an instance of the Sprite class.") if not isinstance(sprite_list, SpriteList): raise TypeError(f"Parameter 2 is a {type(sprite_list)} instead of expected SpriteList.") if sprite_list.use_spatial_hash: sprite_list_to_check = sprite_list.spatial_hash.get_objects_for_box(sprite) # checks_saved = len(sprite_list) - len(sprite_list_to_check) else: sprite_list_to_check = sprite_list collision_list = [sprite2 for sprite2 in sprite_list_to_check if sprite is not sprite2 and _check_for_collision(sprite, sprite2)] # collision_list = [] # for sprite2 in sprite_list_to_check: # if sprite1 is not sprite2 and sprite2 not in collision_list: # if _check_for_collision(sprite1, sprite2): # collision_list.append(sprite2) return collision_list def is_point_in_polygon(x, y, polygon_point_list): """ Use ray-tracing to see if point is inside a polygon Args: x: y: polygon_point_list: Returns: bool """ n = len(polygon_point_list) inside = False p1x, p1y = polygon_point_list[0] for i in range(n+1): p2x, p2y = polygon_point_list[i % n] if y > min(p1y, p2y): if y <= max(p1y, p2y): if x <= max(p1x, p2x): if p1y != p2y: xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xints: inside = not inside p1x, p1y = p2x, p2y return inside def get_sprites_at_point(point: Point, sprite_list: SpriteList) -> List[Sprite]: """ Get a list of sprites at a particular point :param Point point: Point to check :param SpriteList sprite_list: SpriteList to check against :returns: List of sprites colliding, or an empty list. """ if not isinstance(sprite_list, SpriteList): raise TypeError(f"Parameter 2 is a {type(sprite_list)} instead of expected SpriteList.") if sprite_list.use_spatial_hash: sprite_list_to_check = sprite_list.spatial_hash.get_objects_for_point(point) # checks_saved = len(sprite_list) - len(sprite_list_to_check) else: sprite_list_to_check = sprite_list collision_list = [sprite2 for sprite2 in sprite_list_to_check if is_point_in_polygon(point[0], point[1], sprite2.points)] return collision_list