Source code for pygame_colliders.convex

from typing import List, Tuple, Union, Any

from .base import Collider
from .vector import Vector2
from .rect import Rect


def _contains(n: float, range_: List[float]) -> bool:
    """
    Test that n is within range (ends included). Order of range entries doesn't
    matter.

    :param n: Value to check.
    :param range_: Start and end of the range to check.
    :type n: float
    :type range_: list(float, float)
    :return: True if value is inside range, False otherwise
    :rtype: bool
    """
    a, b = range_
    if b < a:
        a, b = b, a
    return (n >= a) and (n <= b)


def _overlap(a: List[float], b: List[float]) -> bool:
    """
    Check if ends of ranges a and b do overlap.

    :param a: First range to check.
    :param b: Second range to check.
    :type a: list(float, float)
    :type b: list(float, float)
    :return: True if ranges do overlap, False otherwise
    :rtype: bool
    """
    if _contains(a[0], b):
        return True
    if _contains(a[1], b):
        return True
    if _contains(b[0], a):
        return True
    if _contains(b[1], a):
        return True
    return False


[docs]class ConvexCollider(Collider): """ Creates new convex collider from given list of tuples. Example of usage: .. code-block:: python collider_points = [(11, 10), (11, 3), (4, 3), (4, 10)] collider = ConvexCollider(collider_points) """ def __init__(self, points: List[Tuple[Union[float, int]]], data: Any = None): super().__init__(data=data) self.points = [[x, y] for x, y in points] # Must be mutable self._vertices: List[Vector2] = [] self._edges: List[Vector2] = [] self._normals: List[Vector2] = [] self._setup() def _setup(self): self._setup_vertices() self._setup_edges() self._setup_normals() self._setup_rect() def _setup_vertices(self): self._vertices = [Vector2(*p) for p in self.points] def _setup_edges(self): self._edges = [] vec_a = self._vertices[0] for vec_b in self._vertices[1:]: self._edges.append(vec_b - vec_a) vec_a = vec_b self._edges.append(vec_a - self._vertices[0]) def _setup_normals(self): self._normals = [Vector2(-e.y, e.x) for e in self._edges] def _setup_rect(self): min_x = self._vertices[0].x min_y = self._vertices[0].y max_x = self._vertices[0].x max_y = self._vertices[0].y for vec in self._vertices[1:]: min_x = min(vec._x, min_x) max_x = max(vec._x, max_x) min_y = min(vec._y, min_y) max_y = max(vec._y, max_y) self._rect = Rect(min_x, min_y, max_x - min_x, max_y - min_y) @property def is_clockwise(self) -> bool: """ Checks if convex collider is clockwise. :return: True if clockwise, False otherwise :rtype: bool """ sum_: float = 0 prev = first = self._vertices[0] for cur in self._vertices[1:]: sum_ += (cur.x - prev.x) * (cur.y + prev.y) prev = cur # This is required to close the loop. sum_ += (first.x - prev.x) * (first.y + prev.y) return sum_ > 0
[docs] def collide(self, other) -> bool: """ Check collision against the other collider. :param other: Other collider to check collision against. :type other: Collider :return: True if collision happens, False otherwise :rtype: bool """ # a = self, b = other from .concave import ConcaveCollider # Late import due cyclic if not self._rect.collide_rect(other._rect): return False # Bounding boxes don't collide if isinstance(other, ConcaveCollider): # Check collision other way around return other.collide(self) normal_stack = self._normals[:] + other._normals[:] for i, normal in enumerate(normal_stack, 1): a_projections = [normal * v for v in self._vertices] a_range = [min(a_projections), max(a_projections)] b_projections = [normal * v for v in other._vertices] b_range = [min(b_projections), max(b_projections)] if not _overlap(a_range, b_range): return False return True
def _make_bbox(self): return [ (self._rect.left, self._rect.top), (self._rect.right, self._rect.top), (self._rect.right, self._rect.bottom), (self._rect.left, self._rect.bottom), ]
[docs] def point_collide(self, point: Tuple[float]) -> bool: """ Check if point is within collider. :param point: Point to test :type point: Tuple[float/int, float/int] :return: True if point is collider, False otherwise :rtype: bool """ vertx = [v.x for v in self._vertices] verty = [v.y for v in self._vertices] nvert = len(self._vertices) testx = point[0] testy = point[1] c = 0 j = nvert - 1 for i in range(0, nvert): if ((verty[i] > testy) != (verty[j] > testy)) and ( testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i] ): c += 1 j = i if c % 2 == 1: return True return False
def _move(self, dx: float, dy: float): # Move points and vertices for point, vertex in zip(self.points, self._vertices): point[0] += dx point[1] += dy vertex.x += dx vertex.y += dy # Update rect super()._move(dx, dy) def __str__(self): return f"[{','.join([f'[{x}, {y}]' for x, y in self.points])}]"