Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions arcade/cache/hit_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class HitBoxCache:
VERSION = 1

def __init__(self):
self._entries: OrderedDict[str, PointList] = OrderedDict()
self._entries: OrderedDict[str, (PointList, bool)] = OrderedDict()

def __len__(self) -> int:
return len(self._entries)
Expand All @@ -57,7 +57,7 @@ def get(self, keys: List[Any]) -> Optional[PointList]:
key = self._gen_key(*keys)
return self._entries.get(key, None)

def put(self, keys: List[Any], points: PointList) -> None:
def put(self, keys: List[Any], points: PointList, opt = False) -> None:
"""
Store hit box points usually for a texture.

Expand All @@ -78,7 +78,7 @@ def put(self, keys: List[Any], points: PointList) -> None:
raise ValueError(f"Hit box must have at least 3 points: {points}")

key = self._gen_key(*keys)
self._entries[key] = tuple(points)
self._entries[key] = (tuple(points), opt)

def load(self, path: Union[str, Path]) -> None:
"""
Expand Down
35 changes: 20 additions & 15 deletions arcade/geometry_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
_PRECISION = 2


def are_polygons_intersecting(poly_a: PointList,
poly_b: PointList) -> bool:
def are_polygons_intersecting(poly_a: PointList, poly_a_parallel: bool,
poly_b: PointList, poly_b_parallel: bool) -> bool:
"""
Return True if two polygons intersect.

Expand All @@ -22,35 +22,40 @@ def are_polygons_intersecting(poly_a: PointList,
:rtype bool:
"""

for polygon in (poly_a, poly_b):
for (polygon, parallel) in ((poly_a, poly_a_parallel), (poly_b, poly_b_parallel)):

for i1 in range(len(polygon)):
i2 = (i1 + 1) % len(polygon)
len_polygon = len(polygon)
range_max = len_polygon // 2 if parallel else len_polygon
for i1 in range(range_max):
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])
normal_x = projection_2[1] - projection_1[1]
normal_y = projection_1[0] - projection_2[0]

min_a, max_a, min_b, max_b = (None,) * 4
min_a = 99999
max_a = -99999
min_b = 99999
max_b = -99999

for poly in poly_a:
projected = normal[0] * poly[0] + normal[1] * poly[1]
projected = normal_x * poly[0] + normal_y * poly[1]

if min_a is None or projected < min_a:
if projected < min_a:
min_a = projected
if max_a is None or projected > max_a:
if projected > max_a:
max_a = projected

for poly in poly_b:
projected = normal[0] * poly[0] + normal[1] * poly[1]
projected = normal_x * poly[0] + normal_y * poly[1]

if min_b is None or projected < min_b:
if projected < min_b:
min_b = projected
if max_b is None or projected > max_b:
if projected > max_b:
max_b = projected

if cast(float, max_a) <= cast(float, min_b) or cast(float, max_b) <= cast(float, min_a):
if max_a <= min_b or max_b <= min_a:
return False

return True
Expand Down
4 changes: 2 additions & 2 deletions arcade/hitbox/bounding.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def calculate(self, image: Image, **kwargs) -> PointList:
:Returns: List of points
"""
size = image.size
return (
return ((
(-size[0] / 2, -size[1] / 2),
(size[0] / 2, -size[1] / 2),
(size[0] / 2, size[1] / 2),
(-size[0] / 2, size[1] / 2),
)
), True)
2 changes: 1 addition & 1 deletion arcade/hitbox/detailed.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def calculate(self, image: Image, **kwargs) -> PointList:
if len(line_set) > 4:
line_set = simplify_curves(line_set, hit_box_detail)

return self.to_points_list(image, line_set)
return (self.to_points_list(image, line_set), False)

def to_points_list(self, image: Image, line_set: List[Vec2d]) -> PointList:
"""
Expand Down
3 changes: 2 additions & 1 deletion arcade/hitbox/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,5 @@ def _r(point: Tuple[float, float], height: int, width: int) -> Point:
result.append(_r(p8, h, w))

# Remove duplicates
return tuple(dict.fromkeys(result)) # type: ignore
points = tuple(dict.fromkeys(result)) # type: ignore
return (points, len(points) == 8)
57 changes: 33 additions & 24 deletions arcade/sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
https://www.gamedev.net/articles/programming/general-and-gameplay-programming/spatial-hashing-r2697/
"""

import math
from math import cos, sin, radians, sqrt, pi
import dataclasses
from typing import (
Any,
Expand Down Expand Up @@ -257,6 +257,7 @@ def __init__(

if self._texture and not self._points:
self._points = self._texture.hit_box_points
self._hit_box_parallel = self._texture._hit_box_parallel

@property
def properties(self) -> Dict[str, Any]:
Expand Down Expand Up @@ -359,6 +360,7 @@ def get_hit_box(self) -> PointList:
# If there is no hitbox, use the width/height to get one
if self._points is None and self._texture:
self._points = self._texture.hit_box_points
self._hit_box_parallel = self._texture._hit_box_parallel

if self._points is None and self._width:
x1, y1 = -self._width / 2, -self._height / 2
Expand Down Expand Up @@ -397,24 +399,34 @@ def get_adjusted_hit_box(self) -> PointList:
if self._point_list_cache is not None:
return self._point_list_cache

def _adjust_point(point) -> Point:
angle_radians = radians(self._angle)
_scale_x, _scale_y = self._scale
_position_x, _position_y = self._position
cos_angle = cos(angle_radians)
sin_angle = sin(angle_radians)
def _adjust_point(point, /) -> Point:
x, y = point
x *= _scale_x
y *= _scale_y
# Rotate the point if needed
if self._angle:
if angle_radians:
# Rotate with scaling to not distort it if scale x and y is different
point = rotate_point(point[0] * self._scale[0], point[1] * self._scale[1], 0, 0, self._angle)
rotated_x = x * cos_angle - y * sin_angle
rotated_y = x * sin_angle + y * cos_angle

# Apply position
return (
point[0] + self._position[0],
point[1] + self._position[1],
rotated_x + _position_x,
rotated_y + _position_y,
)
# Apply position and scale
return (
point[0] * self._scale[0] + self._position[0],
point[1] * self._scale[1] + self._position[1],
x + _position_x,
y + _position_y,
)

# Cache the results
self._point_list_cache = tuple(_adjust_point(point) for point in self.hit_box)
self._point_list_cache = tuple([_adjust_point(point) for point in self.hit_box])
return self._point_list_cache

def forward(self, speed: float = 1.0) -> None:
Expand All @@ -425,8 +437,8 @@ def forward(self, speed: float = 1.0) -> None:

:param speed: speed factor
"""
self.change_x += math.cos(self.radians) * speed
self.change_y += math.sin(self.radians) * speed
self.change_x += cos(self.radians) * speed
self.change_y += sin(self.radians) * speed

def reverse(self, speed: float = 1.0) -> None:
"""
Expand All @@ -446,8 +458,8 @@ def strafe(self, speed: float = 1.0) -> None:

:param speed: speed factor
"""
self.change_x += -math.sin(self.radians) * speed
self.change_y += math.cos(self.radians) * speed
self.change_x += -sin(self.radians) * speed
self.change_y += cos(self.radians) * speed

def turn_right(self, theta: float = 90.0) -> None:
"""
Expand Down Expand Up @@ -508,8 +520,7 @@ def bottom(self) -> float:
if len(points) == 0:
return self.center_y

y_points = [point[1] for point in points]
return min(y_points)
return min(point[1] for point in points)

@bottom.setter
def bottom(self, amount: float):
Expand All @@ -532,8 +543,7 @@ def top(self) -> float:
if len(points) == 0:
return self.center_y

y_points = [point[1] for point in points]
return max(y_points)
return max(point[1] for point in points)

@top.setter
def top(self, amount: float):
Expand Down Expand Up @@ -842,14 +852,14 @@ def radians(self) -> float:
Converts the degrees representation of self.angle into radians.
:return: float
"""
return self._angle / 180.0 * math.pi
return self._angle / 180.0 * pi

@radians.setter
def radians(self, new_value: float):
"""
Converts a radian value into degrees and stores it into angle.
"""
self.angle = new_value * 180.0 / math.pi
self.angle = new_value * 180.0 / pi

@property
def left(self) -> float:
Expand All @@ -863,8 +873,7 @@ def left(self) -> float:
if len(points) == 0:
return self.center_x

x_points = [point[0] for point in points]
return min(x_points)
return min(point[0] for point in points)

@left.setter
def left(self, amount: float):
Expand All @@ -885,8 +894,7 @@ def right(self) -> float:
if len(points) == 0:
return self.center_x

x_points = [point[0] for point in points]
return max(x_points)
return max(point[0] for point in points)

@right.setter
def right(self, amount: float):
Expand Down Expand Up @@ -1276,7 +1284,7 @@ def update_animation(self, delta_time: float = 1 / 60) -> None:
x2 = self.last_texture_change_center_x
y1 = self.center_y
y2 = self.last_texture_change_center_y
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
distance = sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
texture_list: List[Texture] = []

change_direction = False
Expand Down Expand Up @@ -1476,6 +1484,7 @@ def __init__(self, radius: int, color: Color, soft: bool = False):
self.texture = texture
self.color = color_rgba
self._points = self.texture.hit_box_points
self._hit_box_parallel = self.texture._hit_box_parallel


def get_distance_between_sprites(sprite1: Sprite, sprite2: Sprite) -> float:
Expand Down
16 changes: 11 additions & 5 deletions arcade/sprite_list/spatial_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,23 @@ def _check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool:
:returns: True if sprites overlap.
:rtype: bool
"""
radius_sum = max(sprite1._width, sprite1._height) + max(sprite2._width, sprite2._height)
sprite1_position = sprite1._position
sprite1_width = sprite1._width
sprite1_height = sprite1._height
sprite2_position = sprite2._position
sprite2_width = sprite2._width
sprite2_height = sprite2._height
radius_sum = (sprite1_width if sprite1_width > sprite1_height else sprite1_height) + (sprite2_width if sprite2_width > sprite2_height else sprite2_height)
# Multiply by half of the theoretical max diagonal length for an estimation of distance
radius_sum *= 0.71 # 1.42 / 2
radius_sum_x2 = radius_sum * radius_sum

diff_x = sprite1._position[0] - sprite2._position[0]
diff_x = sprite1_position[0] - sprite2_position[0]
diff_x2 = diff_x * diff_x
if diff_x2 > radius_sum_x2:
return False

diff_y = sprite1._position[1] - sprite2._position[1]
diff_y = sprite1_position[1] - sprite2_position[1]
diff_y2 = diff_y * diff_y
if diff_y2 > radius_sum_x2:
return False
Expand All @@ -223,7 +229,7 @@ def _check_for_collision(sprite1: Sprite, sprite2: Sprite) -> bool:
return False

return are_polygons_intersecting(
sprite1.get_adjusted_hit_box(), sprite2.get_adjusted_hit_box()
sprite1.get_adjusted_hit_box(), sprite1._hit_box_parallel, sprite2.get_adjusted_hit_box(), sprite2._hit_box_parallel
)


Expand Down Expand Up @@ -302,7 +308,7 @@ def check_for_collision_with_list(
f"Parameter 2 is a {type(sprite_list)} instead of expected SpriteList."
)

if sprite_list.spatial_hash and (method == 1 or method == 0):
if (method == 1 or method == 0) and sprite_list.spatial_hash:
# Spatial
sprite_list_to_check = sprite_list.spatial_hash.get_objects_for_box(sprite)
# checks_saved = len(sprite_list) - len(sprite_list_to_check)
Expand Down
14 changes: 9 additions & 5 deletions arcade/texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ def __init__(
self._hit_box_algorithm = self._hit_box_algorithm.lower()

self._hit_box_detail = hit_box_detail
self._hit_box_points: PointList = hit_box_points or self._calculate_hit_box_points()
self._hit_box_parallel = False
if hit_box_points:
self._hit_box_points = hit_box_points
else:
self._hit_box_points, self._hit_box_parallel = self._calculate_hit_box_points()

@property
def name(self) -> str:
Expand Down Expand Up @@ -480,14 +484,14 @@ def _calculate_hit_box_points(self) -> PointList:

# Check if we have cached points
keys = [self._image_data.hash, algo_name, self._hit_box_detail]
points = self.hit_box_cache.get(keys)
if points:
return points
points_opt = self.hit_box_cache.get(keys)
if points_opt:
return points_opt

# Calculate points with the selected algorithm
algo = hitbox.get_algorithm(algo_name)
points = algo.calculate(self.image, hit_box_algorithm=self._hit_box_detail)
self.hit_box_cache.put(keys, points)
self.hit_box_cache.put(keys, points[0], points[1])

return points

Expand Down
1 change: 1 addition & 0 deletions benchmarks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/results.md
Loading