From 34964882c6457e44a4e83927eb73d5f8c6fc846b Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Tue, 1 Feb 2022 16:05:40 +0100 Subject: [PATCH 01/13] add some collide functions --- pgzero/actor.py | 77 ++++ pgzero/collide.py | 927 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1004 insertions(+) create mode 100644 pgzero/collide.py diff --git a/pgzero/actor.py b/pgzero/actor.py index e8697e9..39cba1c 100644 --- a/pgzero/actor.py +++ b/pgzero/actor.py @@ -5,6 +5,7 @@ from . import loaders from . import rect from . import spellcheck +from .collide import Collide ANCHORS = { @@ -108,6 +109,8 @@ class Actor: _anchor = _anchor_value = (0, 0) _angle = 0.0 _opacity = 1.0 + _mask = None + _drawed_surf = None def _build_transformed_surf(self): cache_len = len(self._surface_cache) @@ -337,6 +340,9 @@ def _update_pos(self): def draw(self): s = self._build_transformed_surf() + if s != self._drawed_surf: + self._drawed_surf = s + self._mask = None game.screen.blit(s, self.topleft) def angle_to(self, target): @@ -363,3 +369,74 @@ def distance_to(self, target): def unload_image(self): loaders.images.unload(self._image_name) + + def collidepoint_pixel(self, x, y=0): + if isinstance(x, tuple): + y = x[1] + x = x[0] + if self._mask is None: + self._mask = pygame.mask.from_surface(self._build_transformed_surf()) + + xoffset = int(x - self.left) + yoffset = int(y - self.top) + if xoffset < 0 or yoffset < 0: + return 0 + + width, height = self._mask.get_size() + if xoffset > width or yoffset > height: + return 0 + + return self._mask.get_at((xoffset, yoffset)) + + def collide_pixel(self, actor): + for a in [self, actor]: + if a._mask is None: + a._mask = pygame.mask.from_surface(a._build_transformed_surf()) + + xoffset = int(actor.left - self.left) + yoffset = int(actor.top - self.top) + + return self._mask.overlap(actor._mask, (xoffset, yoffset)) + + def collidelist_pixel(self, actors): + for i in range(len(actors)): + if self.collide_pixel(actors[i]): + return i + return -1 + + def collidelistall_pixel(self, actors): + collided = [] + for i in range(len(actors)): + if self.collide_pixel(actors[i]): + collided.append(i) + return collided + + @property + def radius(self): + return self._radius + + @radius.setter + def radius(self, radius): + self._radius = radius + + def circle_collidepoints(self, points): + return Collide.circle_points(self.x, self.y, self._radius, points) + + def circle_collidepoint(self, x, y): + return Collide.circle_point(self.x, self.y, self._radius, x, y) + + def circle_collidecircle(self, actor): + return Collide.circle_circle(self.x, self.y, self._radius, actor.x, actor.y, + actor._radius) + + def circle_colliderect(self, actor): + return Collide.circle_rect(self.x, self.y, self._radius, actor.left, actor.top, + actor.width, actor.height) + + def obb_collidepoint(self, x, y): + w, h = self._orig_surf.get_size() + return Collide.obb_point(self.x, self.y, w, h, self._angle, x, y) + + def obb_collidepoints(self, points): + w, h = self._orig_surf.get_size() + return Collide.obb_points(self.x, self.y, w, h, self._angle, points) diff --git a/pgzero/collide.py b/pgzero/collide.py new file mode 100644 index 0000000..e527ea9 --- /dev/null +++ b/pgzero/collide.py @@ -0,0 +1,927 @@ +import math + + +def distance_to(from_x, from_y, to_x, to_y): + dx = to_x - from_x + dy = to_y - from_y + return math.sqrt(dx**2 + dy**2) + + +def distance_to_squared(from_x, from_y, to_x, to_y): + dx = to_x - from_x + dy = to_y - from_y + return dx**2 + dy**2 + + +class Collide(): + @staticmethod + def line_line(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): + l1x2_l1x1 = l1x2 - l1x1 + l1y2_l1y1 = l1y2 - l1y1 + + determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 + + # Simplify: Parallel lines are never considered to be intersecting + if determinant == 0: + return False + + uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) \ + / determinant + if uA < 0 or uA > 1: + return False + + uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1)) \ + / determinant + if uB < 0 or uB > 1: + return False + + return True + + @staticmethod + def line_lines(l1x1, l1y1, l1x2, l1y2, l2): + l1x2_l1x1 = l1x2 - l1x1 + l1y2_l1y1 = l1y2 - l1y1 + + i = 0 + for li in l2: + determinant = (li[3] - li[1]) * l1x2_l1x1 - (li[2] - li[0]) * l1y2_l1y1 + + # Simplify: Parallel lines are never considered to be intersecting + if determinant == 0: + i += 1 + continue + + uA = ((li[2] - li[0]) * (l1y1 - li[1]) - (li[3] - li[1]) * (l1x1 - li[0]))\ + / determinant + uB = (l1x2_l1x1 * (l1y1 - li[1]) - l1y2_l1y1 * (l1x1 - li[0])) / determinant + if 0 <= uA <= 1 and 0 <= uB <= 1: + return i + + i += 1 + + return -1 + + @staticmethod + def line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): + determinant = (l2y2 - l2y1) * (l1x2 - l1x1) - (l2x2 - l2x1) * (l1y2 - l1y1) + + # Simplify: Parallel lines are never considered to be intersecting + if determinant == 0: + return (None, None) + + uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1))\ + / determinant + uB = ((l1x2 - l1x1) * (l1y1 - l2y1) - (l1y2 - l1y1) * (l1x1 - l2x1))\ + / determinant + + if 0 <= uA <= 1 and 0 <= uB <= 1: + ix = l1x1 + uA * (l1x2 - l1x1) + iy = l1y1 + uA * (l1y2 - l1y1) + return (ix, iy) + + return (None, None) + + @staticmethod + def line_line_dist(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): + ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) + if ix is not None: + return distance_to(l1x1, l1y1, ix, iy) + return None + + @staticmethod + def line_line_dist_squared(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): + ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) + if ix is not None: + return distance_to_squared(l1x1, l1y1, ix, iy) + return None + + @staticmethod + def line_circle(x1, y1, x2, y2, cx, cy, radius): + r_sq = radius ** 2 + + dist_sq = (x1 - cx) ** 2 + (y1 - cy) ** 2 + if dist_sq <= r_sq: + return True + + dist_sq = (x2 - cx) ** 2 + (y2 - cy) ** 2 + if dist_sq <= r_sq: + return True + + dx = x2 - x1 + dy = y2 - y1 + l_sq = dx ** 2 + dy ** 2 + dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq + + ix = x1 + dot * dx + if (ix < x1) == (ix < x2): + return False + + iy = y1 + dot * dy + if (iy < y1) == (iy < y2): + return False + + dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 + if dist_sq <= r_sq: + return True + + return False + + @staticmethod + def line_circle_XY(x1, y1, x2, y2, cx, cy, radius): + if Collide.circle_point(cx, cy, radius, x1, y1): + return (x1, y1) + + x1 -= cx + y1 -= cy + x2 -= cx + y2 -= cy + + if x2 < x1: + x_min, x_max = x2, x1 + else: + x_min, x_max = x1, x2 + + if y2 < y1: + y_min, y_max = y2, y1 + else: + y_min, y_max = y1, y2 + + # Coefficients of circle + c_r2 = radius ** 2 + + # Simplify if dx == 0: Vertical line + dx = x2 - x1 + if dx == 0: + d = c_r2 - x1**2 + if d < 0: + return (None, None) + elif d == 0: + i = 0 + else: + i = math.sqrt(d) + + iy = None + if y_min <= i <= y_max: + iy = i + + if y_min <= -i <= y_max: + if iy is None or abs(i - y1) > abs(-i - y1): + iy = -i + + if iy: + return (x1 + cx, iy + cy) + return (None, None) + + # Gradient of line + l_m = (y2 - y1) / dx + + # Simplify if l_m == 0: Horizontal line + if l_m == 0: + d = c_r2 - y1**2 + if d < 0: + return (None, None) + elif d == 0: + i = 0 + else: + i = math.sqrt(d) + ix = None + if x_min <= i <= x_max: + ix = i + + if x_min <= -i <= x_max: + if ix is None or abs(i - x1) > abs(-i - x1): + ix = -i + + if ix: + return (ix + cx, y1 + cy) + return (None, None) + + # y intercept + l_c = y1 - l_m * x1 + + # Coefficients of quadratic + a = 1 + l_m**2 + b = 2 * l_c * l_m + c = l_c**2 - c_r2 + + # Calculate discriminant and solve quadratic + discriminant = b**2 - 4 * a * c + if discriminant < 0: + return (None, None) + + if discriminant == 0: + d_root = 0 + else: + d_root = math.sqrt(discriminant) + + ix = None + i1 = (-b + d_root) / (2 * a) + if x_min <= i1 <= x_max: + ix = i1 + + i2 = (-b - d_root) / (2 * a) + if x_min <= i2 <= x_max: + if ix is None or abs(i1 - x1) > abs(i2 - x1): + ix = i2 + + if ix: + return (ix + cx, l_m * ix + l_c + cy) + + return (None, None) + + @staticmethod + def line_circle_dist(x1, y1, x2, y2, cx, cy, radius): + ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) + if ix is not None: + return distance_to(x1, y1, ix, iy) + return None + + @staticmethod + def line_circle_dist_squared(x1, y1, x2, y2, cx, cy, radius): + ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) + if ix is not None: + return distance_to_squared(x1, y1, ix, iy) + return None + + @staticmethod + def line_rect(x1, y1, x2, y2, rx, ry, w, h): + if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: + return True + + half_w = w / 2 + half_h = h / 2 + rect_lines = [ + [rx - half_w, ry - half_h, rx - half_w, ry + half_h], + [rx - half_w, ry - half_h, rx + half_w, ry - half_h], + [rx + half_w, ry + half_h, rx - half_w, ry + half_h], + [rx + half_w, ry + half_h, rx + half_w, ry - half_h], + ] + if Collide.line_lines(x1, y1, x2, y2, rect_lines) != -1: + return True + + return False + + @staticmethod + def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): + if Collide.rect_point(rx, ry, w, h, x1, y1): + return (x1, y1) + + half_w = w / 2 + half_h = h / 2 + rect_lines = [ + [rx - half_w, ry - half_h, rx - half_w, ry + half_h], + [rx - half_w, ry - half_h, rx + half_w, ry - half_h], + [rx + half_w, ry + half_h, rx - half_w, ry + half_h], + [rx + half_w, ry + half_h, rx + half_w, ry - half_h], + ] + XYs = [] + for li in rect_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) + if ix is not None: + XYs.append((ix, iy)) + + length = len(XYs) + if length == 0: + return (None, None) + elif length == 1: + return XYs[0] + + ix, iy = XYs[0] + shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 + for XY in XYs: + dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 + if dist < shortest_dist: + ix, iy = XY + shortest_dist = dist + + return (ix, iy) + + @staticmethod + def line_rect_dist(x1, y1, x2, y2, rx, ry, w, h): + ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) + if ix is not None: + return distance_to(x1, y1, ix, iy) + return None + + @staticmethod + def line_rect_dist_squared(x1, y1, x2, y2, rx, ry, w, h): + ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) + if ix is not None: + return distance_to_squared(x1, y1, ix, iy) + return None + + @staticmethod + def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + tx = x1 - ox + ty = y1 - oy + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if rx > -half_width and rx < half_width and ry > -half_height\ + and ry < half_height: + return (x1, y1) + + wc = half_width * costheta + hs = half_height * sintheta + hc = half_height * costheta + ws = half_width * sintheta + p = [ + [ox + wc + hs, oy + hc - ws], + [ox - wc + hs, oy + hc + ws], + [ox + wc - hs, oy - hc - ws], + [ox - wc - hs, oy - hc + ws], + ] + obb_lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[2][0], p[2][1]], + [p[2][0], p[2][1], p[0][0], p[0][1]] + ] + + XYs = [] + for li in obb_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) + if ix is not None: + XYs.append((ix, iy)) + + length = len(XYs) + if length == 0: + return (None, None) + elif length == 1: + return XYs[0] + + ix, iy = XYs[0] + shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 + for XY in XYs: + dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 + if dist < shortest_dist: + ix, iy = XY + shortest_dist = dist + + return (ix, iy) + + @staticmethod + def line_obb_dist(x1, y1, x2, y2, ox, oy, w, h, angle): + ix, iy = Collide.line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle) + if ix is not None: + return distance_to(x1, y1, ix, iy) + return None + + @staticmethod + def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): + ix, iy = Collide.obb_line_XY(x1, y1, x2, y2, ox, oy, w, h, angle) + if ix is not None: + return distance_to_squared(x1, y1, ix, iy) + return None + + @staticmethod + def circle_point(x1, y1, radius, x2, y2): + rSquare = radius ** 2 + dSquare = (x2 - x1) ** 2 + (y2 - y1) ** 2 + + if dSquare < rSquare: + return True + + return False + + @staticmethod + def circle_points(x, y, radius, points): + rSquare = radius ** 2 + + i = 0 + for point in points: + try: + px = point[0] + py = point[1] + except KeyError: + px = point.x + py = point.y + dSquare = (px - x) ** 2 + (py - y) ** 2 + + if dSquare < rSquare: + return i + i += 1 + + return -1 + + @staticmethod + def circle_line(cx, cy, radius, x1, y1, x2, y2): + return Collide.line_circle(x1, y1, x2, y2, cx, cy, radius) + + @staticmethod + def circle_circle(x1, y1, r1, x2, y2, r2): + rSquare = (r1 + r2) ** 2 + dSquare = (x2 - x1) ** 2 + (y2 - y1) ** 2 + + if dSquare < rSquare: + return True + + return False + + @staticmethod + def circle_rect(cx, cy, cr, rx, ry, rw, rh): + h_w = rw / 2 + h_h = rh / 2 + rect_l = rx - h_w + rect_t = ry - h_h + + if cx < rect_l: + dx2 = (cx - rect_l) ** 2 + elif cx > (rect_l + rw): + dx2 = (cx - rect_l - rw) ** 2 + else: + dx2 = 0 + + if cy < rect_t: + dy2 = (cy - rect_t) ** 2 + elif cy > (rect_t + rh): + dy2 = (cy - rect_t - rh) ** 2 + else: + dy2 = 0 + + dist2 = dx2 + dy2 + + if dist2 < (cr ** 2): + return True + + return False + + @staticmethod + def rect_point(x, y, w, h, px, py): + half_w = w / 2 + half_h = h / 2 + + if ( + px < x - half_w or + px > x + half_w or + py < y - half_h or + py > y + half_h + ): + return False + + return True + + @staticmethod + def rect_points(x, y, w, h, points): + half_w = w / 2 + half_h = h / 2 + min_x = x - half_w + max_x = x + half_w + min_y = y - half_h + max_y = y + half_h + + i = 0 + for point in points: + try: + px = point[0] + py = point[1] + except KeyError: + px = point.x + py = point.y + if ( + px >= min_x + and px <= max_x + and py >= min_y + and py <= max_y + ): + return i + i += 1 + + return -1 + + @staticmethod + def rect_line(x, y, w, h, lx1, ly1, lx2, ly2): + return Collide.line_rect(lx1, ly1, lx2, ly2, x, y, w, h) + + @staticmethod + def rect_circle(rx, ry, rw, rh, cx, cy, cr): + return Collide.circle_rect(cx, cy, cr, rx, ry, rw, rh) + + @staticmethod + def rect_rect(x1, y1, w1, h1, x2, y2, w2, h2): + h_w1 = w1 / 2 + h_h1 = h1 / 2 + h_w2 = w2 / 2 + h_h2 = h2 / 2 + + if ( + x2 - h_w2 > x1 + h_w1 + or x2 + h_w2 < x1 - h_w1 + or y2 - h_h2 > y1 + h_h1 + or y2 + h_h2 < y1 - h_h1 + ): + return False + + return True + + @staticmethod + def obb_point(x, y, w, h, angle, px, py): + half_width = w / 2 + half_height = h / 2 + b_radius_sq = half_width ** 2 + half_height ** 2 + tx = px - x + ty = py - y + + if tx ** 2 + ty ** 2 > b_radius_sq: + return False + + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if rx > -half_width and rx < half_width\ + and ry > -half_height and ry < half_height: + return True + + return False + + @staticmethod + def obb_points(x, y, w, h, angle, points): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + i = 0 + for point in points: + try: + px = point[0] + py = point[1] + except KeyError: + px = point.x + py = point.y + + tx = px - x + ty = py - y + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): + return i + i += 1 + + return -1 + + @staticmethod + def obb_line(x, y, w, h, angle, lx1, ly1, lx2, ly2): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + tx = lx1 - x + ty = ly1 - y + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): + return True + + tx = lx2 - x + ty = ly2 - y + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): + return True + + wc = half_width * costheta + hs = half_height * sintheta + hc = half_height * costheta + ws = half_width * sintheta + p = [ + [x + wc + hs, y + hc - ws], + [x - wc + hs, y + hc + ws], + [x + wc - hs, y - hc - ws], + [x - wc - hs, y - hc + ws], + ] + obb_lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[2][0], p[2][1]], + [p[2][0], p[2][1], p[0][0], p[0][1]] + ] + + if Collide.line_lines(lx1, ly1, lx2, ly2, obb_lines) != -1: + return True + + return False + + @staticmethod + def obb_lines(x, y, w, h, angle, lines): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + wc = half_width * costheta + hs = half_height * sintheta + hc = half_height * costheta + ws = half_width * sintheta + p = [ + [x + wc + hs, y + hc - ws], + [x - wc + hs, y + hc + ws], + [x + wc - hs, y - hc - ws], + [x - wc - hs, y - hc + ws], + ] + obb_lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[2][0], p[2][1]], + [p[2][0], p[2][1], p[0][0], p[0][1]] + ] + + i = 0 + for li in lines: + tx = li[0] - x + ty = li[1] - y + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): + return i + + tx = li[2] - x + ty = li[3] - y + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): + return i + + if Collide.line_lines(li[0], li[1], li[2], li[3], obb_lines) != -1: + return i + + i += 1 + + return -1 + + @staticmethod + def obb_circle(x, y, w, h, angle, cx, cy, radius): + half_width = w / 2 + half_height = h / 2 + tx = cx - x + ty = cy - y + + if tx ** 2 + ty ** 2 > (half_height + half_width + radius) ** 2: + return False + + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx < -half_width - radius + or rx > half_width + radius + or ry < -half_height - radius + or ry > half_height + radius): + return False + + if ((rx <= half_width and rx >= -half_width) or + (ry <= half_height and ry >= -half_height)): + return True + + dx = abs(rx) - half_width + dy = abs(ry) - half_height + dist_squared = dx ** 2 + dy ** 2 + if dist_squared > radius ** 2: + return False + + return True + + @staticmethod + def obb_circles(x, y, w, h, angle, circles): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + i = 0 + for circle in circles: + tx = circle[0] - x + ty = circle[1] - y + + rx = tx * costheta - ty * sintheta + ry = ty * costheta + tx * sintheta + + if (rx < -half_width - circle[2] + or rx > half_width + circle[2] + or ry < -half_height - circle[2] + or ry > half_height + circle[2]): + i += 1 + continue + + if ((rx <= half_width and rx >= -half_width) or + (ry <= half_height and ry >= -half_height)): + return i + + dx = abs(rx) - half_width + dy = abs(ry) - half_height + dist_squared = dx ** 2 + dy ** 2 + if dist_squared > circle[2] ** 2: + i += 1 + continue + + return i + + return -1 + + @staticmethod + def obb_rect(x, y, w, h, angle, rx, ry, rw, rh): + half_width = w / 2 + half_height = h / 2 + tx = rx - x + ty = ry - y + + if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: + return False + + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + tx2 = tx * costheta - ty * sintheta + ty2 = ty * costheta + tx * sintheta + + if (tx2 > -half_width and tx2 < half_width and + ty2 > -half_height and ty2 < half_height): + return True + + wc = half_width * costheta + hs = half_height * sintheta + hc = half_height * costheta + ws = half_width * sintheta + p = [ + [wc + hs, hc - ws], + [-wc + hs, hc + ws], + [wc - hs, -hc - ws], + [-wc - hs, -hc + ws], + ] + obb_lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[2][0], p[2][1]], + [p[2][0], p[2][1], p[0][0], p[0][1]] + ] + h_rw = rw / 2 + h_rh = rh / 2 + rect_lines = [ + [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], + [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], + [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], + [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] + ] + + for obb_p in p: + if (obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and + obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh): + return True + + for obb_line in obb_lines: + l1x1 = obb_line[0] + l1y1 = obb_line[1] + l1x2 = obb_line[2] + l1y2 = obb_line[3] + l1x2_l1x1 = l1x2 - l1x1 + l1y2_l1y1 = l1y2 - l1y1 + + for rect_line in rect_lines: + l2x1 = rect_line[0] + l2y1 = rect_line[1] + l2x2 = rect_line[2] + l2y2 = rect_line[3] + + determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 + + # Simplify: Parallel lines are never considered to be intersecting + if determinant == 0: + continue + + uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) \ + / determinant + if uA < 0 or uA > 1: + continue + + uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1))\ + / determinant + if uB < 0 or uB > 1: + continue + + return True + + return False + + @staticmethod + def obb_rects(x, y, w, h, angle, rects): + half_width = w / 2 + half_height = h / 2 + r_angle = math.radians(angle) + costheta = math.cos(r_angle) + sintheta = math.sin(r_angle) + + i = 0 + for rect in rects: + rx = rect[0] + ry = rect[1] + rw = rect[2] + rh = rect[3] + + tx = rx - x + ty = ry - y + + if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: + i += 1 + continue + + tx2 = tx * costheta - ty * sintheta + ty2 = ty * costheta + tx * sintheta + + if (tx2 > -half_width and tx2 < half_width and + ty2 > -half_height and ty2 < half_height): + return i + + wc = half_width * costheta + hs = half_height * sintheta + hc = half_height * costheta + ws = half_width * sintheta + p = [ + [wc + hs, hc - ws], + [-wc + hs, hc + ws], + [wc - hs, -hc - ws], + [-wc - hs, -hc + ws], + ] + obb_lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[2][0], p[2][1]], + [p[2][0], p[2][1], p[0][0], p[0][1]] + ] + h_rw = rw / 2 + h_rh = rh / 2 + rect_lines = [ + [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], + [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], + [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], + [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] + ] + + for obb_p in p: + if (obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and + obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh): + return i + + for obb_line in obb_lines: + l1x1 = obb_line[0] + l1y1 = obb_line[1] + l1x2 = obb_line[2] + l1y2 = obb_line[3] + l1x2_l1x1 = l1x2 - l1x1 + l1y2_l1y1 = l1y2 - l1y1 + + for rect_line in rect_lines: + l2x1 = rect_line[0] + l2y1 = rect_line[1] + l2x2 = rect_line[2] + l2y2 = rect_line[3] + + determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 + + # Simplify: Parallel lines are never considered to be intersecting + if determinant == 0: + continue + + uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) + * (l1x1 - l2x1)) / determinant + if uA < 0 or uA > 1: + continue + + uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1))\ + / determinant + if uB < 0 or uB > 1: + continue + + return i + + i += 1 + + return -1 From ab54d41bdafdbacbc3ff368bc6462edfeb149d1f Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Tue, 8 Feb 2022 19:43:44 +0100 Subject: [PATCH 02/13] refactor collyde.py add some new collide functions in actor --- pgzero/actor.py | 106 ++++++-- pgzero/collide.py | 603 +++++++++++++--------------------------------- 2 files changed, 249 insertions(+), 460 deletions(-) diff --git a/pgzero/actor.py b/pgzero/actor.py index 39cba1c..b0bc368 100644 --- a/pgzero/actor.py +++ b/pgzero/actor.py @@ -5,8 +5,22 @@ from . import loaders from . import rect from . import spellcheck +from .rect import ZRect from .collide import Collide +from typing import Sequence, Tuple, Union, List +from pygame import Vector2 + +_Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] +_CanBeRect = Union[ + ZRect, + pygame.Rect, + Tuple[int, int, int, int], + List[int], + Tuple[_Coordinate, _Coordinate], + List[_Coordinate], +] +_CanBeObb = Tuple[_CanBeRect, float] ANCHORS = { 'x': { @@ -412,31 +426,85 @@ def collidelistall_pixel(self, actors): return collided @property - def radius(self): - return self._radius + def radius(self) -> float: + if self._radius is not None: + return self._radius + else: + return max(self._orig_surf.get_size()) * .5 * self._scale @radius.setter - def radius(self, radius): + def radius(self, radius: float): self._radius = radius - def circle_collidepoints(self, points): - return Collide.circle_points(self.x, self.y, self._radius, points) + def circle_collidepoints(self, points: Sequence[_Coordinate], + radius: float = None) -> int: + r = self._radius if radius is None else radius + return Collide.circle_points(self.centerx, self.centery, r, points) - def circle_collidepoint(self, x, y): - return Collide.circle_point(self.x, self.y, self._radius, x, y) + def circle_collidepoint(self, pt: _Coordinate, radius: float = None) -> bool: + r = self.radius if radius is None else radius + return Collide.circle_point(self.centerx, self.centery, r, pt[0], pt[1]) - def circle_collidecircle(self, actor): - return Collide.circle_circle(self.x, self.y, self._radius, actor.x, actor.y, - actor._radius) + def circle_collidecircle(self, target: Union[Actor, _Coordinate], + radius: float = None, target_radius: float = None) -> bool: + if isinstance(target, Actor): + x, y = (target.centerx, target.centery) + r2 = target.radius if target_radius is None else target_radius + else: + x, y = target + r2 = target_radius - def circle_colliderect(self, actor): - return Collide.circle_rect(self.x, self.y, self._radius, actor.left, actor.top, - actor.width, actor.height) + r = self.radius if radius is None else radius - def obb_collidepoint(self, x, y): - w, h = self._orig_surf.get_size() - return Collide.obb_point(self.x, self.y, w, h, self._angle, x, y) + return Collide.circle_circle(self.centerx, self.centery, r, + x, y, r2) - def obb_collidepoints(self, points): - w, h = self._orig_surf.get_size() - return Collide.obb_points(self.x, self.y, w, h, self._angle, points) + def circle_colliderect(self, target: Union[Actor, _CanBeRect], + radius: float = None) -> bool: + rect = ZRect(target) + r = self.radius if radius is None else radius + return Collide.circle_rect(self.centerx, self.centery, r, + rect.centerx, rect.centery, rect.width, rect.height) + + def _obb(self): + w, h = Vector2(self._orig_surf.get_size()) * self._scale + cx, cy = self.centerx, self.centery + return cx, cy, w, h, self.angle + + def circle_collideobb(self, target: Actor, radius: float = None) -> bool: + cx, cy, w, h, angle = target._obb() + r = self.radius if radius is None else radius + return Collide.obb_circle(cx, cy, w, h, angle, + self.centerx, self.centery, r) + + def obb_collidepoint(self, pt: _Coordinate) -> bool: + return Collide.obb_point(*self._obb(), pt[0], pt[1]) + + def obb_collidepoints(self, points) -> int: + return Collide.obb_points(*self._obb(), points) + + def obb_colliderect(self, target: Union[Actor, _CanBeRect]): + rect = ZRect(target) + return Collide.obb_rect(*self._obb(), rect.centerx, rect.centery, + rect.width, rect.height) + + def obb_collidecircle(self, target: Union[Actor, _Coordinate], + target_radius: float = None): + if isinstance(target, Actor): + x, y = (target.centerx, target.centery) + r = target.radius if target_radius is None else target_radius + else: + x, y = target + r = target_radius + + return Collide.obb_circle(*self._obb(), x, y, r) + + def obb_collideobb(self, target: Union[Actor, _CanBeObb]): + if isinstance(target, Actor): + obb2 = target._obb() + else: + rect2 = ZRect(target[0]) + angle2 = target[1] + obb2 = rect.centerx, rect2.centery, rect2.width, rect2.height, angle2 + + return Collide.obb_obb(*self._obb(), *obb2) diff --git a/pgzero/collide.py b/pgzero/collide.py index e527ea9..12a8f9b 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -16,63 +16,27 @@ def distance_to_squared(from_x, from_y, to_x, to_y): class Collide(): @staticmethod def line_line(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - return False - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) \ - / determinant - if uA < 0 or uA > 1: - return False - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1)) \ - / determinant - if uB < 0 or uB > 1: - return False - - return True + pt = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) + return all(pt) @staticmethod - def line_lines(l1x1, l1y1, l1x2, l1y2, l2): - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - i = 0 - for li in l2: - determinant = (li[3] - li[1]) * l1x2_l1x1 - (li[2] - li[0]) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - i += 1 - continue - - uA = ((li[2] - li[0]) * (l1y1 - li[1]) - (li[3] - li[1]) * (l1x1 - li[0]))\ - / determinant - uB = (l1x2_l1x1 * (l1y1 - li[1]) - l1y2_l1y1 * (l1x1 - li[0])) / determinant - if 0 <= uA <= 1 and 0 <= uB <= 1: + def line_lines(l1x1, l1y1, l1x2, l1y2, lines): + for i, line in enumerate(lines): + l2x1, l2y1, l2x2, l2y2 = line + if Collide.line_line(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): return i - - i += 1 - return -1 @staticmethod def line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - determinant = (l2y2 - l2y1) * (l1x2 - l1x1) - (l2x2 - l2x1) * (l1y2 - l1y1) + determinant = (l2y2-l2y1)*(l1x2-l1x1) - (l2x2-l2x1)*(l1y2-l1y1) # Simplify: Parallel lines are never considered to be intersecting if determinant == 0: return (None, None) - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1))\ - / determinant - uB = ((l1x2 - l1x1) * (l1y1 - l2y1) - (l1y2 - l1y1) * (l1x1 - l2x1))\ - / determinant + uA = ((l2x2-l2x1)*(l1y1-l2y1) - (l2y2-l2y1)*(l1x1-l2x1)) / determinant + uB = ((l1x2-l1x1)*(l1y1-l2y1) - (l1y2-l1y1)*(l1x1-l2x1)) / determinant if 0 <= uA <= 1 and 0 <= uB <= 1: ix = l1x1 + uA * (l1x2 - l1x1) @@ -244,10 +208,7 @@ def line_circle_dist_squared(x1, y1, x2, y2, cx, cy, radius): return None @staticmethod - def line_rect(x1, y1, x2, y2, rx, ry, w, h): - if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: - return True - + def _rect_lines(rx, ry, w, h): half_w = w / 2 half_h = h / 2 rect_lines = [ @@ -256,6 +217,14 @@ def line_rect(x1, y1, x2, y2, rx, ry, w, h): [rx + half_w, ry + half_h, rx - half_w, ry + half_h], [rx + half_w, ry + half_h, rx + half_w, ry - half_h], ] + return rect_lines + + @staticmethod + def line_rect(x1, y1, x2, y2, rx, ry, w, h): + if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: + return True + + rect_lines = Collide._rect_lines(rx, ry, w, h) if Collide.line_lines(x1, y1, x2, y2, rect_lines) != -1: return True @@ -266,17 +235,11 @@ def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): if Collide.rect_point(rx, ry, w, h, x1, y1): return (x1, y1) - half_w = w / 2 - half_h = h / 2 - rect_lines = [ - [rx - half_w, ry - half_h, rx - half_w, ry + half_h], - [rx - half_w, ry - half_h, rx + half_w, ry - half_h], - [rx + half_w, ry + half_h, rx - half_w, ry + half_h], - [rx + half_w, ry + half_h, rx + half_w, ry - half_h], - ] + rect_lines = Collide._rect_lines(rx, ry, w, h) + XYs = [] - for li in rect_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) + for l in rect_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) if ix is not None: XYs.append((ix, iy)) @@ -323,8 +286,7 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta - if rx > -half_width and rx < half_width and ry > -half_height\ - and ry < half_height: + if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: return (x1, y1) wc = half_width * costheta @@ -345,8 +307,8 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): ] XYs = [] - for li in obb_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) + for l in obb_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) if ix is not None: XYs.append((ix, iy)) @@ -383,7 +345,7 @@ def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): @staticmethod def circle_point(x1, y1, radius, x2, y2): rSquare = radius ** 2 - dSquare = (x2 - x1) ** 2 + (y2 - y1) ** 2 + dSquare = (x2 - x1)**2 + (y2 - y1)**2 if dSquare < rSquare: return True @@ -395,14 +357,14 @@ def circle_points(x, y, radius, points): rSquare = radius ** 2 i = 0 - for point in points: + for i, point in enumerate(points): try: px = point[0] py = point[1] except KeyError: px = point.x py = point.y - dSquare = (px - x) ** 2 + (py - y) ** 2 + dSquare = (px - x)**2 + (py - y)**2 if dSquare < rSquare: return i @@ -417,7 +379,7 @@ def circle_line(cx, cy, radius, x1, y1, x2, y2): @staticmethod def circle_circle(x1, y1, r1, x2, y2, r2): rSquare = (r1 + r2) ** 2 - dSquare = (x2 - x1) ** 2 + (y2 - y1) ** 2 + dSquare = (x2 - x1)**2 + (y2 - y1)**2 if dSquare < rSquare: return True @@ -458,10 +420,10 @@ def rect_point(x, y, w, h, px, py): half_h = h / 2 if ( - px < x - half_w or - px > x + half_w or - py < y - half_h or - py > y + half_h + px < x - half_w + or px > x + half_w + or py < y - half_h + or py > y + half_h ): return False @@ -476,8 +438,7 @@ def rect_points(x, y, w, h, points): min_y = y - half_h max_y = y + half_h - i = 0 - for point in points: + for i, point in enumerate(points): try: px = point[0] py = point[1] @@ -491,7 +452,6 @@ def rect_points(x, y, w, h, points): and py <= max_y ): return i - i += 1 return -1 @@ -520,408 +480,169 @@ def rect_rect(x1, y1, w1, h1, x2, y2, w2, h2): return True - @staticmethod - def obb_point(x, y, w, h, angle, px, py): - half_width = w / 2 - half_height = h / 2 - b_radius_sq = half_width ** 2 + half_height ** 2 - tx = px - x - ty = py - y + class Obb: + def __init__(self, x, y, w, h, angle): + self.x = x + self.y = y + self.angle = angle + self.width = w + self.height = h + self.half_w = w / 2 + self.half_h = h / 2 + self.b_radius_sq = self.half_w ** 2 + self.half_h ** 2 + r_angle = math.radians(angle) + self.costheta = math.cos(r_angle) + self.sintheta = math.sin(r_angle) + self._lines = None + self._points = None + + def transform_point(self, px, py): + tx = px - self.x + ty = py - self.y + rx = tx * self.costheta - ty * self.sintheta + ry = ty * self.costheta + tx * self.sintheta + return (rx, ry) + + def contains(self, px, py): + rx, ry = self.transform_point(px, py) + tx = px - self.x + ty = py - self.y + + if tx ** 2 + ty ** 2 > self.b_radius_sq: + return False + return (rx > -self.half_w and rx < self.half_w + and ry > -self.half_h and ry < self.half_h) + + def collideline(self, lx1, ly1, lx2, ly2): + if self.contains(lx1, ly1) or self.contains(lx2, ly2): + return True + + if Collide.line_lines(lx1, ly1, lx2, ly2, self.lines()) != -1: + return True - if tx ** 2 + ty ** 2 > b_radius_sq: return False - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) + def collidecircle(self, cx, cy, radius): + return Collide.circle_rect(*self.transform_point(cx, cy), radius, + 0, 0, self.width, self.height) - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta + def colliderect(self, rx, ry, rw, rh): + tx = rx - self.x + ty = ry - self.y - if rx > -half_width and rx < half_width\ - and ry > -half_height and ry < half_height: - return True + if tx ** 2 + ty ** 2 > (self.half_h + self.half_w + rw + rh) ** 2: + return False - return False + if self.contains(rx, ry): + return True + + if Collide.rect_point(rx, ry, rw, rh, self.x, self.y): + return True + + if Collide.rect_points(rx, ry, rw, rh, self.points()) != -1: + return True + + h_rw = rw / 2 + h_rh = rh / 2 + rect_points = [ + [rx - h_rw, ry - h_rh], [rx + h_rw, ry - h_rh], + [rx + h_rw, ry + h_rh], [rx - h_rw, ry + h_rh] + ] + for point in rect_points: + if self.contains(*point): + return True + + def collideobb(self, x, y, w, h, angle): + tx, ty = self.transform_point(x, y) + return Collide.Obb(tx, ty, w, h, angle - self.angle).colliderect(0, 0, self.width, self.height) + + def points(self): + if self._points is not None: + return self._points + + wc = self.half_w * self.costheta + hs = self.half_h * self.sintheta + hc = self.half_h * self.costheta + ws = self.half_w * self.sintheta + self._points = [ + [self.x + wc + hs, self.y + hc - ws], + [self.x - wc + hs, self.y + hc + ws], + [self.x - wc - hs, self.y - hc + ws], + [self.x + wc - hs, self.y - hc - ws], + ] + return self._points + + def lines(self): + if self._lines is not None: + return self._lines + + p = self.get_points() + self._lines = [ + [p[0][0], p[0][1], p[1][0], p[1][1]], + [p[1][0], p[1][1], p[2][0], p[2][1]], + [p[3][0], p[3][1], p[3][0], p[3][1]], + [p[3][0], p[3][1], p[0][0], p[0][1]] + ] + return self._lines @staticmethod - def obb_points(x, y, w, h, angle, points): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) + def obb_point(x, y, w, h, angle, px, py): + obb = Collide.Obb(x, y, w, h, angle) + return obb.contains(px, py) - i = 0 - for point in points: + @staticmethod + def obb_points(x, y, w, h, angle, points): + obb = Collide.Obb(x, y, w, h, angle) + for i, point in enumerate(points): try: - px = point[0] - py = point[1] + px, py = point[0], point[1] except KeyError: - px = point.x - py = point.y - - tx = px - x - ty = py - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): + px, py = point.x, point.y + if obb.contains(px, py): return i - i += 1 return -1 @staticmethod def obb_line(x, y, w, h, angle, lx1, ly1, lx2, ly2): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx = lx1 - x - ty = ly1 - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): - return True - - tx = lx2 - x - ty = ly2 - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): - return True - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [x + wc + hs, y + hc - ws], - [x - wc + hs, y + hc + ws], - [x + wc - hs, y - hc - ws], - [x - wc - hs, y - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - - if Collide.line_lines(lx1, ly1, lx2, ly2, obb_lines) != -1: - return True - - return False + obb = Collide.Obb(x, y, w, h, angle) + return obb.collideline(lx1, ly1, lx2, ly2) @staticmethod def obb_lines(x, y, w, h, angle, lines): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [x + wc + hs, y + hc - ws], - [x - wc + hs, y + hc + ws], - [x + wc - hs, y - hc - ws], - [x - wc - hs, y - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - - i = 0 - for li in lines: - tx = li[0] - x - ty = li[1] - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): - return i - - tx = li[2] - x - ty = li[3] - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): - return i - - if Collide.line_lines(li[0], li[1], li[2], li[3], obb_lines) != -1: + obb = Collide.Obb(x, y, w, h, angle) + for i, line in enumerate(lines): + if obb.collideline(*line): return i - - i += 1 - return -1 @staticmethod def obb_circle(x, y, w, h, angle, cx, cy, radius): - half_width = w / 2 - half_height = h / 2 - tx = cx - x - ty = cy - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + radius) ** 2: - return False - - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx < -half_width - radius - or rx > half_width + radius - or ry < -half_height - radius - or ry > half_height + radius): - return False - - if ((rx <= half_width and rx >= -half_width) or - (ry <= half_height and ry >= -half_height)): - return True - - dx = abs(rx) - half_width - dy = abs(ry) - half_height - dist_squared = dx ** 2 + dy ** 2 - if dist_squared > radius ** 2: - return False - - return True + obb = Collide.Obb(x, y, w, h, angle) + return obb.collidecircle(cx, cy, radius) @staticmethod def obb_circles(x, y, w, h, angle, circles): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - i = 0 - for circle in circles: - tx = circle[0] - x - ty = circle[1] - y - - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx < -half_width - circle[2] - or rx > half_width + circle[2] - or ry < -half_height - circle[2] - or ry > half_height + circle[2]): - i += 1 - continue - - if ((rx <= half_width and rx >= -half_width) or - (ry <= half_height and ry >= -half_height)): + obb = Collide.Obb(x, y, w, h, angle) + for i, circle in enumerate(circles): + if obb.collidecircle(*circle): return i - - dx = abs(rx) - half_width - dy = abs(ry) - half_height - dist_squared = dx ** 2 + dy ** 2 - if dist_squared > circle[2] ** 2: - i += 1 - continue - - return i - return -1 @staticmethod def obb_rect(x, y, w, h, angle, rx, ry, rw, rh): - half_width = w / 2 - half_height = h / 2 - tx = rx - x - ty = ry - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: - return False - - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx2 = tx * costheta - ty * sintheta - ty2 = ty * costheta + tx * sintheta - - if (tx2 > -half_width and tx2 < half_width and - ty2 > -half_height and ty2 < half_height): - return True - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [wc + hs, hc - ws], - [-wc + hs, hc + ws], - [wc - hs, -hc - ws], - [-wc - hs, -hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - h_rw = rw / 2 - h_rh = rh / 2 - rect_lines = [ - [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], - [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], - [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], - [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] - ] - - for obb_p in p: - if (obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and - obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh): - return True - - for obb_line in obb_lines: - l1x1 = obb_line[0] - l1y1 = obb_line[1] - l1x2 = obb_line[2] - l1y2 = obb_line[3] - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - for rect_line in rect_lines: - l2x1 = rect_line[0] - l2y1 = rect_line[1] - l2x2 = rect_line[2] - l2y2 = rect_line[3] - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - continue - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) \ - / determinant - if uA < 0 or uA > 1: - continue - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1))\ - / determinant - if uB < 0 or uB > 1: - continue - - return True - - return False + obb = Collide.Obb(x, y, w, h, angle) + return obb.colliderect(rx, ry, rw, rh) @staticmethod def obb_rects(x, y, w, h, angle, rects): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - i = 0 - for rect in rects: - rx = rect[0] - ry = rect[1] - rw = rect[2] - rh = rect[3] - - tx = rx - x - ty = ry - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: - i += 1 - continue - - tx2 = tx * costheta - ty * sintheta - ty2 = ty * costheta + tx * sintheta - - if (tx2 > -half_width and tx2 < half_width and - ty2 > -half_height and ty2 < half_height): + obb = Collide.Obb(x, y, w, h, angle) + for i, circle in enumerate(rects): + if obb.colliderect(*rects): return i - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [wc + hs, hc - ws], - [-wc + hs, hc + ws], - [wc - hs, -hc - ws], - [-wc - hs, -hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - h_rw = rw / 2 - h_rh = rh / 2 - rect_lines = [ - [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], - [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], - [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], - [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] - ] - - for obb_p in p: - if (obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and - obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh): - return i - - for obb_line in obb_lines: - l1x1 = obb_line[0] - l1y1 = obb_line[1] - l1x2 = obb_line[2] - l1y2 = obb_line[3] - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - for rect_line in rect_lines: - l2x1 = rect_line[0] - l2y1 = rect_line[1] - l2x2 = rect_line[2] - l2y2 = rect_line[3] - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - continue - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) - * (l1x1 - l2x1)) / determinant - if uA < 0 or uA > 1: - continue - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1))\ - / determinant - if uB < 0 or uB > 1: - continue - - return i - - i += 1 - return -1 + + @staticmethod + def obb_obb(x, y, w, h, angle, x2, y2, w2, h2, angle2): + obb = Collide.Obb(x, y, w, h, angle) + return obb.collideobb(x2, y2, w2, h2, angle2) From 4fdcfb7eb9a6411445a80d071b0e1660135bfaa6 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Thu, 10 Feb 2022 13:18:26 +0100 Subject: [PATCH 03/13] rename some functions related to mask collision --- pgzero/_common.py | 14 ++++++++++++++ pgzero/actor.py | 36 +++++++++++++----------------------- 2 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 pgzero/_common.py diff --git a/pgzero/_common.py b/pgzero/_common.py new file mode 100644 index 0000000..7fd129c --- /dev/null +++ b/pgzero/_common.py @@ -0,0 +1,14 @@ +from typing import Sequence, Tuple, Union, List +from pygame import Vector2, Rect +from .rect import ZRect + +_Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] +_CanBeRect = Union[ + ZRect, + Rect, + Tuple[int, int, int, int], + List[int], + Tuple[_Coordinate, _Coordinate], + List[_Coordinate], +] +_CanBeObb = Tuple[_CanBeRect, float] diff --git a/pgzero/actor.py b/pgzero/actor.py index b0bc368..0026452 100644 --- a/pgzero/actor.py +++ b/pgzero/actor.py @@ -1,3 +1,4 @@ +from __future__ import annotations import pygame from math import radians, sin, cos, atan2, degrees, sqrt @@ -8,19 +9,10 @@ from .rect import ZRect from .collide import Collide -from typing import Sequence, Tuple, Union, List +from typing import Sequence, Union, List from pygame import Vector2 +from ._common import _Coordinate, _CanBeRect, _CanBeObb -_Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] -_CanBeRect = Union[ - ZRect, - pygame.Rect, - Tuple[int, int, int, int], - List[int], - Tuple[_Coordinate, _Coordinate], - List[_Coordinate], -] -_CanBeObb = Tuple[_CanBeRect, float] ANCHORS = { 'x': { @@ -123,8 +115,6 @@ class Actor: _anchor = _anchor_value = (0, 0) _angle = 0.0 _opacity = 1.0 - _mask = None - _drawed_surf = None def _build_transformed_surf(self): cache_len = len(self._surface_cache) @@ -141,7 +131,9 @@ def _build_transformed_surf(self): def __init__(self, image, pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs): self._handle_unexpected_kwargs(kwargs) - self._surface_cache = [] + self._mask: pygame.mask.Mask = None + self._drawed_surf: pygame.Surface = None + self._surface_cache: List[pygame.Surface] = [] self.__dict__["_rect"] = rect.ZRect((0, 0), (0, 0)) # Initialise it at (0, 0) for size (0, 0). # We'll move it to the right place and resize it later @@ -384,10 +376,8 @@ def distance_to(self, target): def unload_image(self): loaders.images.unload(self._image_name) - def collidepoint_pixel(self, x, y=0): - if isinstance(x, tuple): - y = x[1] - x = x[0] + def mask_collidepoint(self, pt: _Coordinate): + x, y = pt if self._mask is None: self._mask = pygame.mask.from_surface(self._build_transformed_surf()) @@ -402,7 +392,7 @@ def collidepoint_pixel(self, x, y=0): return self._mask.get_at((xoffset, yoffset)) - def collide_pixel(self, actor): + def mask_collide(self, actor): for a in [self, actor]: if a._mask is None: a._mask = pygame.mask.from_surface(a._build_transformed_surf()) @@ -412,16 +402,16 @@ def collide_pixel(self, actor): return self._mask.overlap(actor._mask, (xoffset, yoffset)) - def collidelist_pixel(self, actors): + def mask_collidelist(self, actors): for i in range(len(actors)): - if self.collide_pixel(actors[i]): + if self.mask_collide(actors[i]): return i return -1 - def collidelistall_pixel(self, actors): + def mask_collidelistall(self, actors): collided = [] for i in range(len(actors)): - if self.collide_pixel(actors[i]): + if self.mask_collide(actors[i]): collided.append(i) return collided From a0831513695a044de83d73f56bc74bc30ed13177 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Thu, 10 Feb 2022 14:00:19 +0100 Subject: [PATCH 04/13] add _radius variable to Actor --- pgzero/actor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pgzero/actor.py b/pgzero/actor.py index 0026452..786913e 100644 --- a/pgzero/actor.py +++ b/pgzero/actor.py @@ -134,6 +134,7 @@ def __init__(self, image, pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs): self._mask: pygame.mask.Mask = None self._drawed_surf: pygame.Surface = None self._surface_cache: List[pygame.Surface] = [] + self._radius: float = None self.__dict__["_rect"] = rect.ZRect((0, 0), (0, 0)) # Initialise it at (0, 0) for size (0, 0). # We'll move it to the right place and resize it later From e045d9f2f473ac77dafbf8ec2312a98e923d7bff Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Sat, 12 Feb 2022 12:51:04 +0100 Subject: [PATCH 05/13] Fix flake8 issues --- pgzero/collide.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index 12a8f9b..e7b2fa3 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -238,8 +238,9 @@ def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): rect_lines = Collide._rect_lines(rx, ry, w, h) XYs = [] - for l in rect_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) + for line in rect_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, + line[0], line[1], line[2], line[3]) if ix is not None: XYs.append((ix, iy)) @@ -286,7 +287,8 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): rx = tx * costheta - ty * sintheta ry = ty * costheta + tx * sintheta - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: + if (rx > -half_width and rx < half_width and + ry > -half_height and ry < half_height): return (x1, y1) wc = half_width * costheta @@ -307,8 +309,8 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): ] XYs = [] - for l in obb_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) + for li in obb_lines: + ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) if ix is not None: XYs.append((ix, iy)) @@ -554,7 +556,8 @@ def colliderect(self, rx, ry, rw, rh): def collideobb(self, x, y, w, h, angle): tx, ty = self.transform_point(x, y) - return Collide.Obb(tx, ty, w, h, angle - self.angle).colliderect(0, 0, self.width, self.height) + return Collide.Obb(tx, ty, w, h, angle - self.angle)\ + .colliderect(0, 0, self.width, self.height) def points(self): if self._points is not None: From 744e056c0c547e6fe42ced1d198ac43b71c513e2 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Sat, 12 Feb 2022 12:56:25 +0100 Subject: [PATCH 06/13] Fix flake8 issues --- pgzero/collide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index e7b2fa3..7eb400d 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -288,7 +288,7 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): ry = ty * costheta + tx * sintheta if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): + ry > -half_height and ry < half_height): return (x1, y1) wc = half_width * costheta From f3384083ce772869f0fb848494cd1589bc07e47c Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Sun, 13 Feb 2022 20:08:12 +0100 Subject: [PATCH 07/13] rename rx, ry function parameters to rcx, rcy --- pgzero/collide.py | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index 7eb400d..d849204 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -208,34 +208,34 @@ def line_circle_dist_squared(x1, y1, x2, y2, cx, cy, radius): return None @staticmethod - def _rect_lines(rx, ry, w, h): + def _rect_lines(rcx, rcy, w, h): half_w = w / 2 half_h = h / 2 rect_lines = [ - [rx - half_w, ry - half_h, rx - half_w, ry + half_h], - [rx - half_w, ry - half_h, rx + half_w, ry - half_h], - [rx + half_w, ry + half_h, rx - half_w, ry + half_h], - [rx + half_w, ry + half_h, rx + half_w, ry - half_h], + [rcx - half_w, rcy - half_h, rcx - half_w, rcy + half_h], + [rcx - half_w, rcy - half_h, rcx + half_w, rcy - half_h], + [rcx + half_w, rcy + half_h, rcx - half_w, rcy + half_h], + [rcx + half_w, rcy + half_h, rcx + half_w, rcy - half_h], ] return rect_lines @staticmethod - def line_rect(x1, y1, x2, y2, rx, ry, w, h): - if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: + def line_rect(x1, y1, x2, y2, rcx, rcy, w, h): + if Collide.rect_points(rcx, rcy, w, h, [(x1, y1), (x2, y2)]) != -1: return True - rect_lines = Collide._rect_lines(rx, ry, w, h) + rect_lines = Collide._rect_lines(rcx, rcy, w, h) if Collide.line_lines(x1, y1, x2, y2, rect_lines) != -1: return True return False @staticmethod - def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): - if Collide.rect_point(rx, ry, w, h, x1, y1): + def line_rect_XY(x1, y1, x2, y2, rcx, rcy, w, h): + if Collide.rect_point(rcx, rcy, w, h, x1, y1): return (x1, y1) - rect_lines = Collide._rect_lines(rx, ry, w, h) + rect_lines = Collide._rect_lines(rcx, rcy, w, h) XYs = [] for line in rect_lines: @@ -261,15 +261,15 @@ def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): return (ix, iy) @staticmethod - def line_rect_dist(x1, y1, x2, y2, rx, ry, w, h): - ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) + def line_rect_dist(x1, y1, x2, y2, rcx, rcy, w, h): + ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rcx, rcy, w, h) if ix is not None: return distance_to(x1, y1, ix, iy) return None @staticmethod - def line_rect_dist_squared(x1, y1, x2, y2, rx, ry, w, h): - ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) + def line_rect_dist_squared(x1, y1, x2, y2, rcx, rcy, w, h): + ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rcx, rcy, w, h) if ix is not None: return distance_to_squared(x1, y1, ix, iy) return None @@ -389,11 +389,11 @@ def circle_circle(x1, y1, r1, x2, y2, r2): return False @staticmethod - def circle_rect(cx, cy, cr, rx, ry, rw, rh): + def circle_rect(cx, cy, cr, rcx, rcy, rw, rh): h_w = rw / 2 h_h = rh / 2 - rect_l = rx - h_w - rect_t = ry - h_h + rect_l = rcx - h_w + rect_t = rcy - h_h if cx < rect_l: dx2 = (cx - rect_l) ** 2 @@ -462,8 +462,8 @@ def rect_line(x, y, w, h, lx1, ly1, lx2, ly2): return Collide.line_rect(lx1, ly1, lx2, ly2, x, y, w, h) @staticmethod - def rect_circle(rx, ry, rw, rh, cx, cy, cr): - return Collide.circle_rect(cx, cy, cr, rx, ry, rw, rh) + def rect_circle(rcx, rcy, rw, rh, cx, cy, cr): + return Collide.circle_rect(cx, cy, cr, rcx, rcy, rw, rh) @staticmethod def rect_rect(x1, y1, w1, h1, x2, y2, w2, h2): @@ -528,27 +528,27 @@ def collidecircle(self, cx, cy, radius): return Collide.circle_rect(*self.transform_point(cx, cy), radius, 0, 0, self.width, self.height) - def colliderect(self, rx, ry, rw, rh): - tx = rx - self.x - ty = ry - self.y + def colliderect(self, rcx, rcy, rw, rh): + tx = rcx - self.x + ty = rcy - self.y if tx ** 2 + ty ** 2 > (self.half_h + self.half_w + rw + rh) ** 2: return False - if self.contains(rx, ry): + if self.contains(rcx, rcy): return True - if Collide.rect_point(rx, ry, rw, rh, self.x, self.y): + if Collide.rect_point(rcx, rcy, rw, rh, self.x, self.y): return True - if Collide.rect_points(rx, ry, rw, rh, self.points()) != -1: + if Collide.rect_points(rcx, rcy, rw, rh, self.points()) != -1: return True h_rw = rw / 2 h_rh = rh / 2 rect_points = [ - [rx - h_rw, ry - h_rh], [rx + h_rw, ry - h_rh], - [rx + h_rw, ry + h_rh], [rx - h_rw, ry + h_rh] + [rcx - h_rw, rcy - h_rh], [rcx + h_rw, rcy - h_rh], + [rcx + h_rw, rcy + h_rh], [rcx - h_rw, rcy + h_rh] ] for point in rect_points: if self.contains(*point): @@ -633,9 +633,9 @@ def obb_circles(x, y, w, h, angle, circles): return -1 @staticmethod - def obb_rect(x, y, w, h, angle, rx, ry, rw, rh): + def obb_rect(x, y, w, h, angle, rcx, rcy, rw, rh): obb = Collide.Obb(x, y, w, h, angle) - return obb.colliderect(rx, ry, rw, rh) + return obb.colliderect(rcx, rcy, rw, rh) @staticmethod def obb_rects(x, y, w, h, angle, rects): From 5f085d34e873d45d4850cd3ec11dff6fcf8066b9 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Mon, 14 Feb 2022 10:57:04 +0100 Subject: [PATCH 08/13] correct obb_collideobb in class Actor --- pgzero/actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgzero/actor.py b/pgzero/actor.py index 786913e..19b9523 100644 --- a/pgzero/actor.py +++ b/pgzero/actor.py @@ -496,6 +496,6 @@ def obb_collideobb(self, target: Union[Actor, _CanBeObb]): else: rect2 = ZRect(target[0]) angle2 = target[1] - obb2 = rect.centerx, rect2.centery, rect2.width, rect2.height, angle2 + obb2 = rect2.centerx, rect2.centery, rect2.width, rect2.height, angle2 return Collide.obb_obb(*self._obb(), *obb2) From 291b419354ceacf2fc3d5127bd50687c9f68957f Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Mon, 14 Feb 2022 14:23:12 +0100 Subject: [PATCH 09/13] correct line_obb_dist_squared and line_circle in class Collide --- pgzero/collide.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index d849204..b5d76f2 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -77,11 +77,11 @@ def line_circle(x1, y1, x2, y2, cx, cy, radius): dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq ix = x1 + dot * dx - if (ix < x1) == (ix < x2): + if (dx!=0) and (ix < x1) == (ix < x2): return False iy = y1 + dot * dy - if (iy < y1) == (iy < y2): + if (dy!=0) and (iy < y1) == (iy < y2): return False dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 @@ -339,7 +339,7 @@ def line_obb_dist(x1, y1, x2, y2, ox, oy, w, h, angle): @staticmethod def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): - ix, iy = Collide.obb_line_XY(x1, y1, x2, y2, ox, oy, w, h, angle) + ix, iy = Collide.line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle) if ix is not None: return distance_to_squared(x1, y1, ix, iy) return None From 9ee0b77eb839d0d9bb0bf9061608cbacb8783b2e Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Tue, 15 Feb 2022 11:28:30 +0100 Subject: [PATCH 10/13] correct Collide.Obb.lines() function refactor some codes in class Collide --- pgzero/collide.py | 63 ++++++++++++++--------------------------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index b5d76f2..53e4be4 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -250,12 +250,13 @@ def line_rect_XY(x1, y1, x2, y2, rcx, rcy, w, h): elif length == 1: return XYs[0] - ix, iy = XYs[0] - shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 - for XY in XYs: - dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 + ix, iy = XYs.pop(0) + shortest_dist = distance_to_squared(ix, iy, x1, y1) + for x,y in XYs: + dist = distance_to_squared(x, y, x1, y1) if dist < shortest_dist: - ix, iy = XY + ix = x + iy = y shortest_dist = dist return (ix, iy) @@ -276,38 +277,11 @@ def line_rect_dist_squared(x1, y1, x2, y2, rcx, rcy, w, h): @staticmethod def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx = x1 - ox - ty = y1 - oy - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx > -half_width and rx < half_width and - ry > -half_height and ry < half_height): - return (x1, y1) - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [ox + wc + hs, oy + hc - ws], - [ox - wc + hs, oy + hc + ws], - [ox + wc - hs, oy - hc - ws], - [ox - wc - hs, oy - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - + obb = Collide.Obb(ox, oy, w, h, angle) + if obb.contains(x1, y1): + return x1, y1 + + obb_lines = obb.lines() XYs = [] for li in obb_lines: ix, iy = Collide.line_line_XY(x1, y1, x2, y2, li[0], li[1], li[2], li[3]) @@ -320,12 +294,13 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): elif length == 1: return XYs[0] - ix, iy = XYs[0] - shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 - for XY in XYs: - dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 + ix, iy = XYs.pop(0) + shortest_dist = distance_to_squared(ix, iy, x1, y1) + for x, y in XYs: + dist = distance_to_squared(x, y, x1, y1) if dist < shortest_dist: - ix, iy = XY + ix = x + iy = y shortest_dist = dist return (ix, iy) @@ -579,11 +554,11 @@ def lines(self): if self._lines is not None: return self._lines - p = self.get_points() + p = self.points() self._lines = [ [p[0][0], p[0][1], p[1][0], p[1][1]], [p[1][0], p[1][1], p[2][0], p[2][1]], - [p[3][0], p[3][1], p[3][0], p[3][1]], + [p[2][0], p[2][1], p[3][0], p[3][1]], [p[3][0], p[3][1], p[0][0], p[0][1]] ] return self._lines From 7a5c4d48020cb53891b6d066745c4230d93b87a8 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Tue, 15 Feb 2022 11:36:21 +0100 Subject: [PATCH 11/13] Fix flake8 issues --- pgzero/collide.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index 53e4be4..74fbb7a 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -77,11 +77,11 @@ def line_circle(x1, y1, x2, y2, cx, cy, radius): dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq ix = x1 + dot * dx - if (dx!=0) and (ix < x1) == (ix < x2): + if (dx != 0) and (ix < x1) == (ix < x2): return False iy = y1 + dot * dy - if (dy!=0) and (iy < y1) == (iy < y2): + if (dy != 0) and (iy < y1) == (iy < y2): return False dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 @@ -252,7 +252,7 @@ def line_rect_XY(x1, y1, x2, y2, rcx, rcy, w, h): ix, iy = XYs.pop(0) shortest_dist = distance_to_squared(ix, iy, x1, y1) - for x,y in XYs: + for x, y in XYs: dist = distance_to_squared(x, y, x1, y1) if dist < shortest_dist: ix = x @@ -280,7 +280,7 @@ def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): obb = Collide.Obb(ox, oy, w, h, angle) if obb.contains(x1, y1): return x1, y1 - + obb_lines = obb.lines() XYs = [] for li in obb_lines: From 8f33ded5033d7f86d30b485cf37a21647469c562 Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Tue, 15 Feb 2022 19:11:38 +0100 Subject: [PATCH 12/13] correct circle_point, circle_points, cirecle_circle to also return true when they touch each other refactor line_circle --- pgzero/collide.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index 74fbb7a..50c68c6 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -61,14 +61,7 @@ def line_line_dist_squared(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): @staticmethod def line_circle(x1, y1, x2, y2, cx, cy, radius): - r_sq = radius ** 2 - - dist_sq = (x1 - cx) ** 2 + (y1 - cy) ** 2 - if dist_sq <= r_sq: - return True - - dist_sq = (x2 - cx) ** 2 + (y2 - cy) ** 2 - if dist_sq <= r_sq: + if Collide.circle_points(cx, cy, radius, ((x1, y1), (x2, y2))) != -1: return True dx = x2 - x1 @@ -76,16 +69,12 @@ def line_circle(x1, y1, x2, y2, cx, cy, radius): l_sq = dx ** 2 + dy ** 2 dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq - ix = x1 + dot * dx - if (dx != 0) and (ix < x1) == (ix < x2): + if dot >= 1 or dot <= 0: return False - + ix = x1 + dot * dx iy = y1 + dot * dy - if (dy != 0) and (iy < y1) == (iy < y2): - return False - dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 - if dist_sq <= r_sq: + if Collide.circle_point(cx, cy, radius, ix, iy): return True return False @@ -324,7 +313,7 @@ def circle_point(x1, y1, radius, x2, y2): rSquare = radius ** 2 dSquare = (x2 - x1)**2 + (y2 - y1)**2 - if dSquare < rSquare: + if dSquare <= rSquare: return True return False @@ -343,7 +332,7 @@ def circle_points(x, y, radius, points): py = point.y dSquare = (px - x)**2 + (py - y)**2 - if dSquare < rSquare: + if dSquare <= rSquare: return i i += 1 @@ -358,7 +347,7 @@ def circle_circle(x1, y1, r1, x2, y2, r2): rSquare = (r1 + r2) ** 2 dSquare = (x2 - x1)**2 + (y2 - y1)**2 - if dSquare < rSquare: + if dSquare <= rSquare: return True return False From d9791203118daadc9b1f4c410f9c30338a2ac85c Mon Sep 17 00:00:00 2001 From: yansnow78 Date: Wed, 16 Feb 2022 12:28:40 +0100 Subject: [PATCH 13/13] improve line_circle, line_circle_XY replace x ** 2 by x*x everywhere for speed performance --- pgzero/collide.py | 155 +++++++++++++--------------------------------- 1 file changed, 42 insertions(+), 113 deletions(-) diff --git a/pgzero/collide.py b/pgzero/collide.py index 50c68c6..30e7514 100644 --- a/pgzero/collide.py +++ b/pgzero/collide.py @@ -4,13 +4,13 @@ def distance_to(from_x, from_y, to_x, to_y): dx = to_x - from_x dy = to_y - from_y - return math.sqrt(dx**2 + dy**2) + return math.sqrt(dx*dx + dy*dy) def distance_to_squared(from_x, from_y, to_x, to_y): dx = to_x - from_x dy = to_y - from_y - return dx**2 + dy**2 + return dx*dx + dy*dy class Collide(): @@ -61,12 +61,19 @@ def line_line_dist_squared(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): @staticmethod def line_circle(x1, y1, x2, y2, cx, cy, radius): - if Collide.circle_points(cx, cy, radius, ((x1, y1), (x2, y2))) != -1: + radius_sq = radius * radius + + dist_sq = distance_to_squared(cx, cy, x1, y1) + if dist_sq <= radius_sq: + return True + + dist_sq = distance_to_squared(cx, cy, x2, y2) + if dist_sq <= radius_sq: return True dx = x2 - x1 dy = y2 - y1 - l_sq = dx ** 2 + dy ** 2 + l_sq = dx * dx + dy * dy dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq if dot >= 1 or dot <= 0: @@ -74,112 +81,33 @@ def line_circle(x1, y1, x2, y2, cx, cy, radius): ix = x1 + dot * dx iy = y1 + dot * dy - if Collide.circle_point(cx, cy, radius, ix, iy): + dist_sq = distance_to_squared(cx, cy, ix, iy) + if dist_sq <= radius_sq: return True return False @staticmethod def line_circle_XY(x1, y1, x2, y2, cx, cy, radius): - if Collide.circle_point(cx, cy, radius, x1, y1): - return (x1, y1) - - x1 -= cx - y1 -= cy - x2 -= cx - y2 -= cy - - if x2 < x1: - x_min, x_max = x2, x1 - else: - x_min, x_max = x1, x2 - - if y2 < y1: - y_min, y_max = y2, y1 - else: - y_min, y_max = y1, y2 - - # Coefficients of circle - c_r2 = radius ** 2 + radius_sq = radius * radius + dist_sq = distance_to_squared(cx, cy, x1, y1) + if dist_sq <= radius_sq: + return x1, y1 - # Simplify if dx == 0: Vertical line dx = x2 - x1 - if dx == 0: - d = c_r2 - x1**2 - if d < 0: - return (None, None) - elif d == 0: - i = 0 - else: - i = math.sqrt(d) - - iy = None - if y_min <= i <= y_max: - iy = i - - if y_min <= -i <= y_max: - if iy is None or abs(i - y1) > abs(-i - y1): - iy = -i - - if iy: - return (x1 + cx, iy + cy) - return (None, None) - - # Gradient of line - l_m = (y2 - y1) / dx - - # Simplify if l_m == 0: Horizontal line - if l_m == 0: - d = c_r2 - y1**2 - if d < 0: - return (None, None) - elif d == 0: - i = 0 - else: - i = math.sqrt(d) - ix = None - if x_min <= i <= x_max: - ix = i - - if x_min <= -i <= x_max: - if ix is None or abs(i - x1) > abs(-i - x1): - ix = -i - - if ix: - return (ix + cx, y1 + cy) - return (None, None) - - # y intercept - l_c = y1 - l_m * x1 - - # Coefficients of quadratic - a = 1 + l_m**2 - b = 2 * l_c * l_m - c = l_c**2 - c_r2 - - # Calculate discriminant and solve quadratic - discriminant = b**2 - 4 * a * c - if discriminant < 0: - return (None, None) - - if discriminant == 0: - d_root = 0 - else: - d_root = math.sqrt(discriminant) - - ix = None - i1 = (-b + d_root) / (2 * a) - if x_min <= i1 <= x_max: - ix = i1 + dy = y2 - y1 + l_sq = dx * dx + dy * dy - i2 = (-b - d_root) / (2 * a) - if x_min <= i2 <= x_max: - if ix is None or abs(i1 - x1) > abs(i2 - x1): - ix = i2 + dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq - if ix: - return (ix + cx, l_m * ix + l_c + cy) + ix = x1 + dot * dx + iy = y1 + dot * dy + dist_sq = distance_to_squared(cx, cy, ix, iy) + if dist_sq <= radius_sq: + d_to_i_norm = math.sqrt(radius_sq - dist_sq) / math.sqrt(l_sq) + if 0 < dot-d_to_i_norm < 1: + return (ix - dx*d_to_i_norm, iy - dy*d_to_i_norm) return (None, None) @staticmethod @@ -310,8 +238,8 @@ def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): @staticmethod def circle_point(x1, y1, radius, x2, y2): - rSquare = radius ** 2 - dSquare = (x2 - x1)**2 + (y2 - y1)**2 + rSquare = radius * radius + dSquare = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) if dSquare <= rSquare: return True @@ -320,7 +248,7 @@ def circle_point(x1, y1, radius, x2, y2): @staticmethod def circle_points(x, y, radius, points): - rSquare = radius ** 2 + rSquare = radius * radius i = 0 for i, point in enumerate(points): @@ -330,7 +258,7 @@ def circle_points(x, y, radius, points): except KeyError: px = point.x py = point.y - dSquare = (px - x)**2 + (py - y)**2 + dSquare = (px - x)*(px - x) + (py - y)*(py - y) if dSquare <= rSquare: return i @@ -344,8 +272,8 @@ def circle_line(cx, cy, radius, x1, y1, x2, y2): @staticmethod def circle_circle(x1, y1, r1, x2, y2, r2): - rSquare = (r1 + r2) ** 2 - dSquare = (x2 - x1)**2 + (y2 - y1)**2 + rSquare = (r1 + r2) * (r1 + r2) + dSquare = (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) if dSquare <= rSquare: return True @@ -360,22 +288,22 @@ def circle_rect(cx, cy, cr, rcx, rcy, rw, rh): rect_t = rcy - h_h if cx < rect_l: - dx2 = (cx - rect_l) ** 2 + dx2 = (cx - rect_l)*(cx - rect_l) elif cx > (rect_l + rw): - dx2 = (cx - rect_l - rw) ** 2 + dx2 = (cx - rect_l - rw)*(cx - rect_l - rw) else: dx2 = 0 if cy < rect_t: - dy2 = (cy - rect_t) ** 2 + dy2 = (cy - rect_t) * (cy - rect_t) elif cy > (rect_t + rh): - dy2 = (cy - rect_t - rh) ** 2 + dy2 = (cy - rect_t - rh) * (cy - rect_t - rh) else: dy2 = 0 dist2 = dx2 + dy2 - if dist2 < (cr ** 2): + if dist2 < (cr * cr): return True return False @@ -455,7 +383,7 @@ def __init__(self, x, y, w, h, angle): self.height = h self.half_w = w / 2 self.half_h = h / 2 - self.b_radius_sq = self.half_w ** 2 + self.half_h ** 2 + self.b_radius_sq = self.half_w*self.half_w + self.half_h*self.half_h r_angle = math.radians(angle) self.costheta = math.cos(r_angle) self.sintheta = math.sin(r_angle) @@ -474,7 +402,7 @@ def contains(self, px, py): tx = px - self.x ty = py - self.y - if tx ** 2 + ty ** 2 > self.b_radius_sq: + if tx*tx + ty*ty > self.b_radius_sq: return False return (rx > -self.half_w and rx < self.half_w and ry > -self.half_h and ry < self.half_h) @@ -496,7 +424,8 @@ def colliderect(self, rcx, rcy, rw, rh): tx = rcx - self.x ty = rcy - self.y - if tx ** 2 + ty ** 2 > (self.half_h + self.half_w + rw + rh) ** 2: + dist_max = (self.half_h + self.half_w + rw + rh) + if tx*tx + ty*ty > dist_max*dist_max: return False if self.contains(rcx, rcy):