Skip to content

Add collision detection algorithms for basic shapes #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions geometry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

"""Geometry module containing various geometric shapes and algorithms."""
from geometry.collision_detection import (
detect_aabb_collision,
detect_circle_collision,
detect_circle_rectangle_collision,
)
from geometry.geometry import Circle, Rectangle

__all__ = [
'Circle',
'Rectangle',
'detect_aabb_collision',
'detect_circle_collision',
'detect_circle_rectangle_collision',
]
164 changes: 164 additions & 0 deletions geometry/collision_detection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""
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.
"""

from __future__ import annotations

import math
from dataclasses import dataclass

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.
"""

min_x: float
min_y: float
max_x: float
max_y: float

@classmethod
def from_rectangle(cls, rect: Rectangle, center: Point) -> AABB:
"""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(
center[0] - half_width,
center[1] - half_height,
center[0] + half_width,
center[1] + half_height,
)


class CollisionDetector:
"""Provides methods for detecting collisions between different geometric shapes.

Supports collision detection between:
- Circle to Circle
- Rectangle to Rectangle (using AABB)
- Circle to Rectangle
"""

@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.
"""
dx = pos2[0] - pos1[0]
dy = pos2[1] - pos1[1]
distance = math.sqrt(dx * dx + dy * dy)

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.
"""
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
)

@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.
"""
box = AABB.from_rectangle(rect, rect_pos)

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))

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()

detector = CollisionDetector()

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 '✗'}")

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 '✗'}")

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 '✗'}")