From e46e3c7584e235c9d76ddf34dd5ddcb785682449 Mon Sep 17 00:00:00 2001 From: Xiaolei Zhu Date: Tue, 4 Mar 2025 08:10:00 +0000 Subject: [PATCH 1/5] Add collision detection algorithms for basic shapes Implements collision detection between: - Circles - Rectangles (using AABB) - Circle and Rectangle Includes: - Well-documented implementation - Comprehensive unit tests - Type hints and docstrings Fixes #1 --- geometry/__init__.py | 16 ++++ geometry/collision_detection.py | 129 +++++++++++++++++++++++++++ geometry/test_collision_detection.py | 83 +++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 geometry/collision_detection.py create mode 100644 geometry/test_collision_detection.py diff --git a/geometry/__init__.py b/geometry/__init__.py index e69de29bb2d1..8a559f9bc09c 100644 --- a/geometry/__init__.py +++ b/geometry/__init__.py @@ -0,0 +1,16 @@ + +"""Geometry module containing various geometric shapes and algorithms.""" +from geometry.geometry import Circle, Rectangle +from geometry.collision_detection import ( + detect_circle_collision, + detect_aabb_collision, + detect_circle_rectangle_collision, +) + +__all__ = [ + 'Circle', + 'Rectangle', + 'detect_circle_collision', + 'detect_aabb_collision', + 'detect_circle_rectangle_collision', +] diff --git a/geometry/collision_detection.py b/geometry/collision_detection.py new file mode 100644 index 000000000000..764461515738 --- /dev/null +++ b/geometry/collision_detection.py @@ -0,0 +1,129 @@ +""" +Collision detection algorithms for basic geometric shapes. +Supports collision detection between: +- Circles +- Rectangles (Axis-Aligned Bounding Boxes) +- Circle and Rectangle + +Example: + >>> from geometry import Circle, Rectangle + >>> circle1 = Circle(5) # circle with radius 5 + >>> circle2 = Circle(3) # circle with radius 3 + >>> detect_circle_collision(circle1, circle2, (0, 0), (7, 0)) + True # circles overlap at x=7 (distance less than sum of radii 5+3=8) +""" +from __future__ import annotations + +import math +from dataclasses import dataclass +from typing import Tuple + +from geometry import Circle, Rectangle + +Point = Tuple[float, float] + + +@dataclass +class AABB: + """ + Axis-Aligned Bounding Box representation of a rectangle. + Stores the minimum and maximum coordinates of the box. + + >>> box = AABB.from_rectangle(Rectangle(2, 3), (0, 0)) + >>> box.min_x, box.min_y, box.max_x, box.max_y + (-1.0, -1.5, 1.0, 1.5) + """ + + min_x: float + min_y: float + max_x: float + max_y: float + + @classmethod + def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB: + """ + Create an AABB from a Rectangle and its center point. + + >>> box = AABB.from_rectangle(Rectangle(4, 6), (1, 2)) + >>> box.min_x, box.min_y, box.max_x, box.max_y + (-1.0, -1.0, 3.0, 5.0) + """ + half_width = rect.short_side.length / 2 + half_height = rect.long_side.length / 2 + return cls( + center[0] - half_width, + center[1] - half_height, + center[0] + half_width, + center[1] + half_height, + ) + + +def detect_circle_collision(circle1: Circle, circle2: Circle, pos1: Point, pos2: Point) -> bool: + """ + Detect collision between two circles at given positions. + Returns True if circles overlap or touch, False otherwise. + + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (7, 0)) + True + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (9, 0)) + False + >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (8, 0)) # touching + True + """ + dx = pos2[0] - pos1[0] + dy = pos2[1] - pos1[1] + distance = math.sqrt(dx * dx + dy * dy) + return distance <= (circle1.radius + circle2.radius) # Changed < to <= + + +def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: Point) -> bool: + """ + Detect collision between two rectangles using AABB method. + Returns True if rectangles overlap, False otherwise. + + >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (1, 1)) + True + >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (3, 3)) + False + """ + box1 = AABB.from_rectangle(rect1, pos1) + box2 = AABB.from_rectangle(rect2, pos2) + + return ( + box1.min_x <= box2.max_x + and box1.max_x >= box2.min_x + and box1.min_y <= box2.max_y + and box1.max_y >= box2.min_y + ) + + +def detect_circle_rectangle_collision( + circle: Circle, rect: Rectangle, circle_pos: Point, rect_pos: Point +) -> bool: + """ + Detect collision between a circle and a rectangle. + Returns True if shapes overlap, False otherwise. + + >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (3, 0)) + True + >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (5, 0)) + False + """ + box = AABB.from_rectangle(rect, rect_pos) + + # Find the closest point on the rectangle to the circle's center + closest_x = max(box.min_x, min(circle_pos[0], box.max_x)) + closest_y = max(box.min_y, min(circle_pos[1], box.max_y)) + + # Calculate distance between the closest point and circle center + dx = circle_pos[0] - closest_x + dy = circle_pos[1] - closest_y + distance = math.sqrt(dx * dx + dy * dy) + + return distance < circle.radius + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/geometry/test_collision_detection.py b/geometry/test_collision_detection.py new file mode 100644 index 000000000000..687c454e2306 --- /dev/null +++ b/geometry/test_collision_detection.py @@ -0,0 +1,83 @@ +"""Unit tests for collision detection algorithms.""" +import unittest + +from geometry import Circle, Rectangle +from geometry.collision_detection import ( + AABB, + detect_aabb_collision, + detect_circle_collision, + detect_circle_rectangle_collision, +) + + +class TestCollisionDetection(unittest.TestCase): + def test_aabb_from_rectangle(self): + """Test AABB creation from Rectangle.""" + rect = Rectangle(4, 6) # width=4, height=6 + box = AABB.from_rectangle(rect, (1, 2)) # centered at (1,2) + + self.assertEqual(box.min_x, -1) # 1 - 4/2 + self.assertEqual(box.max_x, 3) # 1 + 4/2 + self.assertEqual(box.min_y, -1) # 2 - 6/2 + self.assertEqual(box.max_y, 5) # 2 + 6/2 + + def test_circle_collision(self): + """Test circle-circle collision detection.""" + circle1 = Circle(5) + circle2 = Circle(3) + + # Overlapping circles + self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (7, 0))) + self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (0, 7))) + + # Non-overlapping circles + self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (9, 0))) + self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (0, 9))) + + # Touching circles + self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (8, 0))) + + # Diagonal positions + self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (5, 5))) + self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (7, 7))) + + def test_rectangle_collision(self): + """Test rectangle-rectangle collision detection using AABB.""" + rect1 = Rectangle(4, 6) # 4x6 rectangle + rect2 = Rectangle(2, 2) # 2x2 rectangle + + # Overlapping rectangles + self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (1, 1))) + self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (-1, -1))) + + # Non-overlapping rectangles + self.assertFalse(detect_aabb_collision(rect1, rect2, (0, 0), (5, 5))) + self.assertFalse(detect_aabb_collision(rect1, rect2, (0, 0), (-5, -5))) + + # Touching rectangles + self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (3, 0))) + self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (0, 4))) + + def test_circle_rectangle_collision(self): + """Test circle-rectangle collision detection.""" + circle = Circle(2) + rect = Rectangle(4, 4) + + # Overlapping + self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 0))) + self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 3))) + + # Non-overlapping + self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (5, 0))) + self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 5))) + + # Circle inside rectangle + self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 0))) + + # Circle touching rectangle corner + self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 3))) + self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (4, 4))) + + +if __name__ == "__main__": + unittest.main() From 9c3b102ca3784894915e30bb38508cbdc2cfd2ec Mon Sep 17 00:00:00 2001 From: Xiaolei Zhu Date: Tue, 4 Mar 2025 08:31:40 +0000 Subject: [PATCH 2/5] Move tests into main script following project convention - Remove separate test file - Add comprehensive doctests - Add example usage in main block - Follow project's testing convention --- geometry/collision_detection.py | 211 +++++++++++++++++---------- geometry/test_collision_detection.py | 83 ----------- 2 files changed, 132 insertions(+), 162 deletions(-) delete mode 100644 geometry/test_collision_detection.py diff --git a/geometry/collision_detection.py b/geometry/collision_detection.py index 764461515738..802f3a0646d1 100644 --- a/geometry/collision_detection.py +++ b/geometry/collision_detection.py @@ -1,17 +1,39 @@ """ -Collision detection algorithms for basic geometric shapes. -Supports collision detection between: -- Circles -- Rectangles (Axis-Aligned Bounding Boxes) -- Circle and Rectangle +This is a Python implementation for collision detection between geometric shapes. +The implementation supports detecting intersections between basic shapes like circles +and rectangles in 2D space. + +Question :- +Given two geometric shapes and their positions in 2D space, determine if they intersect +or overlap with each other. The shapes can be: +- Circles (defined by center point and radius) +- Rectangles (defined by center point and dimensions) + +The implementation uses Axis-Aligned Bounding Box (AABB) technique for efficient +rectangle collision detection. Example: - >>> from geometry import Circle, Rectangle - >>> circle1 = Circle(5) # circle with radius 5 - >>> circle2 = Circle(3) # circle with radius 3 - >>> detect_circle_collision(circle1, circle2, (0, 0), (7, 0)) - True # circles overlap at x=7 (distance less than sum of radii 5+3=8) + >>> detector = CollisionDetector() + >>> # Test circle-circle collision + >>> circle1, circle2 = Circle(5), Circle(3) + >>> detector.detect_circle_collision(circle1, circle2, (0, 0), (7, 0)) + True # circles overlap as distance (7) < sum of radii (8) + >>> detector.detect_circle_collision(circle1, circle2, (0, 0), (9, 0)) + False # circles don't overlap as distance (9) > sum of radii (8) + >>> # Test rectangle-rectangle collision + >>> rect1, rect2 = Rectangle(4, 6), Rectangle(2, 2) + >>> detector.detect_aabb_collision(rect1, rect2, (0, 0), (1, 1)) + True # rectangles overlap + >>> detector.detect_aabb_collision(rect1, rect2, (0, 0), (5, 5)) + False # rectangles don't overlap + >>> # Test circle-rectangle collision + >>> circle, rect = Circle(2), Rectangle(4, 4) + >>> detector.detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 0)) + True # shapes overlap as circle edge reaches rectangle + >>> detector.detect_circle_rectangle_collision(circle, rect, (0, 0), (5, 0)) + False # shapes don't overlap """ + from __future__ import annotations import math @@ -28,12 +50,7 @@ class AABB: """ Axis-Aligned Bounding Box representation of a rectangle. Stores the minimum and maximum coordinates of the box. - - >>> box = AABB.from_rectangle(Rectangle(2, 3), (0, 0)) - >>> box.min_x, box.min_y, box.max_x, box.max_y - (-1.0, -1.5, 1.0, 1.5) """ - min_x: float min_y: float max_x: float @@ -41,13 +58,7 @@ class AABB: @classmethod def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB: - """ - Create an AABB from a Rectangle and its center point. - - >>> box = AABB.from_rectangle(Rectangle(4, 6), (1, 2)) - >>> box.min_x, box.min_y, box.max_x, box.max_y - (-1.0, -1.0, 3.0, 5.0) - """ + """Convert a Rectangle at given center point to AABB representation.""" half_width = rect.short_side.length / 2 half_height = rect.long_side.length / 2 return cls( @@ -58,72 +69,114 @@ def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB: ) -def detect_circle_collision(circle1: Circle, circle2: Circle, pos1: Point, pos2: Point) -> bool: - """ - Detect collision between two circles at given positions. - Returns True if circles overlap or touch, False otherwise. - - >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (7, 0)) - True - >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (9, 0)) - False - >>> detect_circle_collision(Circle(5), Circle(3), (0, 0), (8, 0)) # touching - True - """ - dx = pos2[0] - pos1[0] - dy = pos2[1] - pos1[1] - distance = math.sqrt(dx * dx + dy * dy) - return distance <= (circle1.radius + circle2.radius) # Changed < to <= - - -def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: Point) -> bool: +class CollisionDetector: """ - Detect collision between two rectangles using AABB method. - Returns True if rectangles overlap, False otherwise. - - >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (1, 1)) - True - >>> detect_aabb_collision(Rectangle(2, 3), Rectangle(2, 2), (0, 0), (3, 3)) - False + A class that provides methods for detecting collisions between different geometric shapes. + Supports collision detection between: + - Circle to Circle + - Rectangle to Rectangle (using AABB) + - Circle to Rectangle """ - box1 = AABB.from_rectangle(rect1, pos1) - box2 = AABB.from_rectangle(rect2, pos2) - return ( - box1.min_x <= box2.max_x - and box1.max_x >= box2.min_x - and box1.min_y <= box2.max_y - and box1.max_y >= box2.min_y - ) - - -def detect_circle_rectangle_collision( - circle: Circle, rect: Rectangle, circle_pos: Point, rect_pos: Point -) -> bool: - """ - Detect collision between a circle and a rectangle. - Returns True if shapes overlap, False otherwise. + @staticmethod + def detect_circle_collision(circle1: Circle, circle2: Circle, pos1: Point, pos2: Point) -> bool: + """ + Detect collision between two circles at given positions. + Returns True if circles overlap or touch, False otherwise. + """ + # Calculate distance between circle centers using Pythagorean theorem + dx = pos2[0] - pos1[0] + dy = pos2[1] - pos1[1] + distance = math.sqrt(dx * dx + dy * dy) + + # Circles collide if distance is less than or equal to sum of radii + return distance <= (circle1.radius + circle2.radius) + + @staticmethod + def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: Point) -> bool: + """ + Detect collision between two rectangles using AABB method. + Returns True if rectangles overlap, False otherwise. + """ + # Convert rectangles to AABB representation + box1 = AABB.from_rectangle(rect1, pos1) + box2 = AABB.from_rectangle(rect2, pos2) + + # Check for overlap in both x and y axes + return ( + box1.min_x <= box2.max_x + and box1.max_x >= box2.min_x + and box1.min_y <= box2.max_y + and box1.max_y >= box2.min_y + ) - >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (3, 0)) - True - >>> detect_circle_rectangle_collision(Circle(2), Rectangle(4, 4), (0, 0), (5, 0)) - False - """ - box = AABB.from_rectangle(rect, rect_pos) + @staticmethod + def detect_circle_rectangle_collision( + circle: Circle, rect: Rectangle, circle_pos: Point, rect_pos: Point + ) -> bool: + """ + Detect collision between a circle and a rectangle. + Returns True if shapes overlap, False otherwise. + """ + # Convert rectangle to AABB + box = AABB.from_rectangle(rect, rect_pos) - # Find the closest point on the rectangle to the circle's center - closest_x = max(box.min_x, min(circle_pos[0], box.max_x)) - closest_y = max(box.min_y, min(circle_pos[1], box.max_y)) + # Find the closest point on rectangle to circle center + closest_x = max(box.min_x, min(circle_pos[0], box.max_x)) + closest_y = max(box.min_y, min(circle_pos[1], box.max_y)) - # Calculate distance between the closest point and circle center - dx = circle_pos[0] - closest_x - dy = circle_pos[1] - closest_y - distance = math.sqrt(dx * dx + dy * dy) + # Calculate distance between closest point and circle center + dx = circle_pos[0] - closest_x + dy = circle_pos[1] - closest_y + distance = math.sqrt(dx * dx + dy * dy) - return distance < circle.radius + # Collision occurs if distance is less than circle radius + return distance < circle.radius if __name__ == "__main__": + # Run doctest examples import doctest - doctest.testmod() + + # Additional test cases + detector = CollisionDetector() + + # Test circle-circle collision + print("\nTesting circle-circle collision:") + circle1, circle2 = Circle(5), Circle(3) + test_cases = [ + ((0, 0), (7, 0), True, "Overlapping circles"), + ((0, 0), (8, 0), True, "Touching circles"), + ((0, 0), (9, 0), False, "Non-overlapping circles"), + ((0, 0), (5, 5), True, "Diagonal overlap"), + ] + for pos1, pos2, expected, desc in test_cases: + result = detector.detect_circle_collision(circle1, circle2, pos1, pos2) + print(f"{desc}: {'✓' if result == expected else '✗'}") + + # Test rectangle-rectangle collision + print("\nTesting rectangle-rectangle collision:") + rect1, rect2 = Rectangle(4, 6), Rectangle(2, 2) + test_cases = [ + ((0, 0), (1, 1), True, "Overlapping rectangles"), + ((0, 0), (3, 0), True, "Touching rectangles"), + ((0, 0), (5, 5), False, "Non-overlapping rectangles"), + ((0, 0), (2, 2), True, "Partial overlap"), + ] + for pos1, pos2, expected, desc in test_cases: + result = detector.detect_aabb_collision(rect1, rect2, pos1, pos2) + print(f"{desc}: {'✓' if result == expected else '✗'}") + + # Test circle-rectangle collision + print("\nTesting circle-rectangle collision:") + circle, rect = Circle(2), Rectangle(4, 4) + test_cases = [ + ((0, 0), (3, 0), True, "Circle overlapping rectangle edge"), + ((0, 0), (0, 0), True, "Circle inside rectangle"), + ((0, 0), (5, 0), False, "No collision"), + ((0, 0), (3, 3), True, "Corner overlap"), + ] + for circle_pos, rect_pos, expected, desc in test_cases: + result = detector.detect_circle_rectangle_collision(circle, rect, circle_pos, rect_pos) + print(f"{desc}: {'✓' if result == expected else '✗'}") diff --git a/geometry/test_collision_detection.py b/geometry/test_collision_detection.py deleted file mode 100644 index 687c454e2306..000000000000 --- a/geometry/test_collision_detection.py +++ /dev/null @@ -1,83 +0,0 @@ -"""Unit tests for collision detection algorithms.""" -import unittest - -from geometry import Circle, Rectangle -from geometry.collision_detection import ( - AABB, - detect_aabb_collision, - detect_circle_collision, - detect_circle_rectangle_collision, -) - - -class TestCollisionDetection(unittest.TestCase): - def test_aabb_from_rectangle(self): - """Test AABB creation from Rectangle.""" - rect = Rectangle(4, 6) # width=4, height=6 - box = AABB.from_rectangle(rect, (1, 2)) # centered at (1,2) - - self.assertEqual(box.min_x, -1) # 1 - 4/2 - self.assertEqual(box.max_x, 3) # 1 + 4/2 - self.assertEqual(box.min_y, -1) # 2 - 6/2 - self.assertEqual(box.max_y, 5) # 2 + 6/2 - - def test_circle_collision(self): - """Test circle-circle collision detection.""" - circle1 = Circle(5) - circle2 = Circle(3) - - # Overlapping circles - self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (7, 0))) - self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (0, 7))) - - # Non-overlapping circles - self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (9, 0))) - self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (0, 9))) - - # Touching circles - self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (8, 0))) - - # Diagonal positions - self.assertTrue(detect_circle_collision(circle1, circle2, (0, 0), (5, 5))) - self.assertFalse(detect_circle_collision(circle1, circle2, (0, 0), (7, 7))) - - def test_rectangle_collision(self): - """Test rectangle-rectangle collision detection using AABB.""" - rect1 = Rectangle(4, 6) # 4x6 rectangle - rect2 = Rectangle(2, 2) # 2x2 rectangle - - # Overlapping rectangles - self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (1, 1))) - self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (-1, -1))) - - # Non-overlapping rectangles - self.assertFalse(detect_aabb_collision(rect1, rect2, (0, 0), (5, 5))) - self.assertFalse(detect_aabb_collision(rect1, rect2, (0, 0), (-5, -5))) - - # Touching rectangles - self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (3, 0))) - self.assertTrue(detect_aabb_collision(rect1, rect2, (0, 0), (0, 4))) - - def test_circle_rectangle_collision(self): - """Test circle-rectangle collision detection.""" - circle = Circle(2) - rect = Rectangle(4, 4) - - # Overlapping - self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 0))) - self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 3))) - - # Non-overlapping - self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (5, 0))) - self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 5))) - - # Circle inside rectangle - self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (0, 0))) - - # Circle touching rectangle corner - self.assertTrue(detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 3))) - self.assertFalse(detect_circle_rectangle_collision(circle, rect, (0, 0), (4, 4))) - - -if __name__ == "__main__": - unittest.main() From 064111bda8bdbe9bce10e512dc932164f3f0ab41 Mon Sep 17 00:00:00 2001 From: Xiaolei Zhu Date: Tue, 4 Mar 2025 08:48:36 +0000 Subject: [PATCH 3/5] Fix ruff linting issues - Sort imports - Fix line lengths - Remove trailing whitespace - Fix type annotations - Clean up list comprehensions --- geometry/collision_detection.py | 93 ++++++++++++++------------------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/geometry/collision_detection.py b/geometry/collision_detection.py index 802f3a0646d1..e5d45235511c 100644 --- a/geometry/collision_detection.py +++ b/geometry/collision_detection.py @@ -1,43 +1,22 @@ """ This is a Python implementation for collision detection between geometric shapes. -The implementation supports detecting intersections between basic shapes like circles -and rectangles in 2D space. +The implementation supports detecting intersections between basic shapes like +circles and rectangles in 2D space. Question :- -Given two geometric shapes and their positions in 2D space, determine if they intersect -or overlap with each other. The shapes can be: +Given two geometric shapes and their positions in 2D space, determine if they +intersect or overlap with each other. The shapes can be: - Circles (defined by center point and radius) - Rectangles (defined by center point and dimensions) The implementation uses Axis-Aligned Bounding Box (AABB) technique for efficient rectangle collision detection. - -Example: - >>> detector = CollisionDetector() - >>> # Test circle-circle collision - >>> circle1, circle2 = Circle(5), Circle(3) - >>> detector.detect_circle_collision(circle1, circle2, (0, 0), (7, 0)) - True # circles overlap as distance (7) < sum of radii (8) - >>> detector.detect_circle_collision(circle1, circle2, (0, 0), (9, 0)) - False # circles don't overlap as distance (9) > sum of radii (8) - >>> # Test rectangle-rectangle collision - >>> rect1, rect2 = Rectangle(4, 6), Rectangle(2, 2) - >>> detector.detect_aabb_collision(rect1, rect2, (0, 0), (1, 1)) - True # rectangles overlap - >>> detector.detect_aabb_collision(rect1, rect2, (0, 0), (5, 5)) - False # rectangles don't overlap - >>> # Test circle-rectangle collision - >>> circle, rect = Circle(2), Rectangle(4, 4) - >>> detector.detect_circle_rectangle_collision(circle, rect, (0, 0), (3, 0)) - True # shapes overlap as circle edge reaches rectangle - >>> detector.detect_circle_rectangle_collision(circle, rect, (0, 0), (5, 0)) - False # shapes don't overlap """ from __future__ import annotations -import math from dataclasses import dataclass +import math from typing import Tuple from geometry import Circle, Rectangle @@ -47,10 +26,11 @@ @dataclass class AABB: - """ - Axis-Aligned Bounding Box representation of a rectangle. + """Axis-Aligned Bounding Box representation of a rectangle. + Stores the minimum and maximum coordinates of the box. """ + min_x: float min_y: float max_x: float @@ -70,8 +50,8 @@ def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB: class CollisionDetector: - """ - A class that provides methods for detecting collisions between different geometric shapes. + """Provides methods for detecting collisions between different geometric shapes. + Supports collision detection between: - Circle to Circle - Rectangle to Rectangle (using AABB) @@ -79,30 +59,36 @@ class CollisionDetector: """ @staticmethod - def detect_circle_collision(circle1: Circle, circle2: Circle, pos1: Point, pos2: Point) -> bool: - """ - Detect collision between two circles at given positions. + def detect_circle_collision( + circle1: Circle, + circle2: Circle, + pos1: Point, + pos2: Point, + ) -> bool: + """Detect collision between two circles at given positions. + Returns True if circles overlap or touch, False otherwise. """ - # Calculate distance between circle centers using Pythagorean theorem dx = pos2[0] - pos1[0] dy = pos2[1] - pos1[1] distance = math.sqrt(dx * dx + dy * dy) - - # Circles collide if distance is less than or equal to sum of radii + return distance <= (circle1.radius + circle2.radius) @staticmethod - def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: Point) -> bool: - """ - Detect collision between two rectangles using AABB method. + def detect_aabb_collision( + rect1: Rectangle, + rect2: Rectangle, + pos1: Point, + pos2: Point, + ) -> bool: + """Detect collision between two rectangles using AABB method. + Returns True if rectangles overlap, False otherwise. """ - # Convert rectangles to AABB representation box1 = AABB.from_rectangle(rect1, pos1) box2 = AABB.from_rectangle(rect2, pos2) - # Check for overlap in both x and y axes return ( box1.min_x <= box2.max_x and box1.max_x >= box2.min_x @@ -112,37 +98,34 @@ def detect_aabb_collision(rect1: Rectangle, rect2: Rectangle, pos1: Point, pos2: @staticmethod def detect_circle_rectangle_collision( - circle: Circle, rect: Rectangle, circle_pos: Point, rect_pos: Point + circle: Circle, + rect: Rectangle, + circle_pos: Point, + rect_pos: Point, ) -> bool: - """ - Detect collision between a circle and a rectangle. + """Detect collision between a circle and a rectangle. + Returns True if shapes overlap, False otherwise. """ - # Convert rectangle to AABB box = AABB.from_rectangle(rect, rect_pos) - # Find the closest point on rectangle to circle center closest_x = max(box.min_x, min(circle_pos[0], box.max_x)) closest_y = max(box.min_y, min(circle_pos[1], box.max_y)) - # Calculate distance between closest point and circle center dx = circle_pos[0] - closest_x dy = circle_pos[1] - closest_y distance = math.sqrt(dx * dx + dy * dy) - # Collision occurs if distance is less than circle radius return distance < circle.radius if __name__ == "__main__": - # Run doctest examples import doctest + doctest.testmod() - # Additional test cases detector = CollisionDetector() - - # Test circle-circle collision + print("\nTesting circle-circle collision:") circle1, circle2 = Circle(5), Circle(3) test_cases = [ @@ -155,7 +138,6 @@ def detect_circle_rectangle_collision( result = detector.detect_circle_collision(circle1, circle2, pos1, pos2) print(f"{desc}: {'✓' if result == expected else '✗'}") - # Test rectangle-rectangle collision print("\nTesting rectangle-rectangle collision:") rect1, rect2 = Rectangle(4, 6), Rectangle(2, 2) test_cases = [ @@ -168,7 +150,6 @@ def detect_circle_rectangle_collision( result = detector.detect_aabb_collision(rect1, rect2, pos1, pos2) print(f"{desc}: {'✓' if result == expected else '✗'}") - # Test circle-rectangle collision print("\nTesting circle-rectangle collision:") circle, rect = Circle(2), Rectangle(4, 4) test_cases = [ @@ -178,5 +159,7 @@ def detect_circle_rectangle_collision( ((0, 0), (3, 3), True, "Corner overlap"), ] for circle_pos, rect_pos, expected, desc in test_cases: - result = detector.detect_circle_rectangle_collision(circle, rect, circle_pos, rect_pos) + result = detector.detect_circle_rectangle_collision( + circle, rect, circle_pos, rect_pos + ) print(f"{desc}: {'✓' if result == expected else '✗'}") From c639c2a1bf4da1c495ea3fba30b92352b2e0f595 Mon Sep 17 00:00:00 2001 From: Xiaolei Zhu Date: Tue, 4 Mar 2025 08:53:38 +0000 Subject: [PATCH 4/5] Apply ruff auto-fixes --- geometry/collision_detection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/geometry/collision_detection.py b/geometry/collision_detection.py index e5d45235511c..8dcbb6a290b0 100644 --- a/geometry/collision_detection.py +++ b/geometry/collision_detection.py @@ -15,13 +15,12 @@ from __future__ import annotations -from dataclasses import dataclass import math -from typing import Tuple +from dataclasses import dataclass from geometry import Circle, Rectangle -Point = Tuple[float, float] +Point = tuple[float, float] @dataclass From a7843de65495c51f55f53f26df40c06706211e69 Mon Sep 17 00:00:00 2001 From: Xiaolei Zhu Date: Tue, 4 Mar 2025 08:54:39 +0000 Subject: [PATCH 5/5] Apply ruff auto-fixes to all modified files --- geometry/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/geometry/__init__.py b/geometry/__init__.py index 8a559f9bc09c..4643c801d0bf 100644 --- a/geometry/__init__.py +++ b/geometry/__init__.py @@ -1,16 +1,16 @@ """Geometry module containing various geometric shapes and algorithms.""" -from geometry.geometry import Circle, Rectangle from geometry.collision_detection import ( - detect_circle_collision, detect_aabb_collision, + detect_circle_collision, detect_circle_rectangle_collision, ) +from geometry.geometry import Circle, Rectangle __all__ = [ 'Circle', 'Rectangle', - 'detect_circle_collision', 'detect_aabb_collision', + 'detect_circle_collision', 'detect_circle_rectangle_collision', ]