|
| 1 | +""" |
| 2 | +Collision detection algorithms for basic geometric shapes. |
| 3 | +Supports collision detection between: |
| 4 | +- Circles |
| 5 | +- Rectangles (Axis-Aligned Bounding Boxes) |
| 6 | +- Circle and Rectangle |
| 7 | +
|
| 8 | +Example: |
| 9 | + >>> from geometry import Circle, Rectangle |
| 10 | + >>> circle1 = Circle(5) # circle with radius 5 |
| 11 | + >>> circle2 = Circle(3) # circle with radius 3 |
| 12 | + >>> detect_circle_collision(circle1, circle2, (0, 0), (7, 0)) |
| 13 | + True # circles overlap at x=7 (distance less than sum of radii 5+3=8) |
| 14 | +""" |
| 15 | +from __future__ import annotations |
| 16 | + |
| 17 | +import math |
| 18 | +from dataclasses import dataclass |
| 19 | +from typing import Tuple |
| 20 | + |
| 21 | +from geometry import Circle, Rectangle |
| 22 | + |
| 23 | +Point = Tuple[float, float] |
| 24 | + |
| 25 | + |
| 26 | +@dataclass |
| 27 | +class AABB: |
| 28 | + """ |
| 29 | + Axis-Aligned Bounding Box representation of a rectangle. |
| 30 | + Stores the minimum and maximum coordinates of the box. |
| 31 | +
|
| 32 | + >>> box = AABB.from_rectangle(Rectangle(2, 3), (0, 0)) |
| 33 | + >>> box.min_x, box.min_y, box.max_x, box.max_y |
| 34 | + (-1.0, -1.5, 1.0, 1.5) |
| 35 | + """ |
| 36 | + |
| 37 | + min_x: float |
| 38 | + min_y: float |
| 39 | + max_x: float |
| 40 | + max_y: float |
| 41 | + |
| 42 | + @classmethod |
| 43 | + def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB: |
| 44 | + """ |
| 45 | + Create an AABB from a Rectangle and its center point. |
| 46 | +
|
| 47 | + >>> box = AABB.from_rectangle(Rectangle(4, 6), (1, 2)) |
| 48 | + >>> box.min_x, box.min_y, box.max_x, box.max_y |
| 49 | + (-1.0, -1.0, 3.0, 5.0) |
| 50 | + """ |
| 51 | + half_width = rect.short_side.length / 2 |
| 52 | + half_height = rect.long_side.length / 2 |
| 53 | + return cls( |
| 54 | + center[0] - half_width, |
| 55 | + center[1] - half_height, |
| 56 | + center[0] + half_width, |
| 57 | + center[1] + half_height, |
| 58 | + ) |
| 59 | + |
| 60 | + |
| 61 | +def detect_circle_collision(circle1: Circle, circle2: Circle, pos1: Point, pos2: Point) -> bool: |
| 62 | + """ |
| 63 | + Detect collision between two circles at given positions. |
| 64 | + Returns True if circles overlap or touch, False otherwise. |
| 65 | +
|
| 66 | + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (7, 0)) |
| 67 | + True |
| 68 | + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (9, 0)) |
| 69 | + False |
| 70 | + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (8, 0)) # touching |
| 71 | + True |
| 72 | + """ |
| 73 | + dx = pos2[0] - pos1[0] |
| 74 | + dy = pos2[1] - pos1[1] |
| 75 | + distance = math.sqrt(dx * dx + dy * dy) |
| 76 | + return distance <= (circle1.radius + circle2.radius) # Changed < to <= |
| 77 | + |
| 78 | + |
| 79 | +def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: Point) -> bool: |
| 80 | + """ |
| 81 | + Detect collision between two rectangles using AABB method. |
| 82 | + Returns True if rectangles overlap, False otherwise. |
| 83 | +
|
| 84 | + >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (1, 1)) |
| 85 | + True |
| 86 | + >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (3, 3)) |
| 87 | + False |
| 88 | + """ |
| 89 | + box1 = AABB.from_rectangle(rect1, pos1) |
| 90 | + box2 = AABB.from_rectangle(rect2, pos2) |
| 91 | + |
| 92 | + return ( |
| 93 | + box1.min_x <= box2.max_x |
| 94 | + and box1.max_x >= box2.min_x |
| 95 | + and box1.min_y <= box2.max_y |
| 96 | + and box1.max_y >= box2.min_y |
| 97 | + ) |
| 98 | + |
| 99 | + |
| 100 | +def detect_circle_rectangle_collision( |
| 101 | + circle: Circle, rect: Rectangle, circle_pos: Point, rect_pos: Point |
| 102 | +) -> bool: |
| 103 | + """ |
| 104 | + Detect collision between a circle and a rectangle. |
| 105 | + Returns True if shapes overlap, False otherwise. |
| 106 | +
|
| 107 | + >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (3, 0)) |
| 108 | + True |
| 109 | + >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (5, 0)) |
| 110 | + False |
| 111 | + """ |
| 112 | + box = AABB.from_rectangle(rect, rect_pos) |
| 113 | + |
| 114 | + # Find the closest point on the rectangle to the circle's center |
| 115 | + closest_x = max(box.min_x, min(circle_pos[0], box.max_x)) |
| 116 | + closest_y = max(box.min_y, min(circle_pos[1], box.max_y)) |
| 117 | + |
| 118 | + # Calculate distance between the closest point and circle center |
| 119 | + dx = circle_pos[0] - closest_x |
| 120 | + dy = circle_pos[1] - closest_y |
| 121 | + distance = math.sqrt(dx * dx + dy * dy) |
| 122 | + |
| 123 | + return distance < circle.radius |
| 124 | + |
| 125 | + |
| 126 | +if __name__ == "__main__": |
| 127 | + import doctest |
| 128 | + |
| 129 | + doctest.testmod() |
0 commit comments