197 lines
6.2 KiB
Python
197 lines
6.2 KiB
Python
"""
|
|
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
|