Skip to content

Commit

Permalink
circle intersect
Browse files Browse the repository at this point in the history
  • Loading branch information
itzpr3d4t0r committed Jul 12, 2024
1 parent 2ddeef1 commit 54c58df
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 6 deletions.
18 changes: 17 additions & 1 deletion docs/circle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -404,4 +404,20 @@ Circle Methods
as the original `Circle` object. The function takes no arguments and returns the
new `Circle` object.

.. ## Circle.copy ##
.. ## Circle.copy ##
.. method:: intersect

| :sl:`returns the intersection points of the circle with another shape`
| :sg:`intersect(Circle) -> intersection_points`
Calculates and returns a list of intersection points between the circle and another shape.
The other shape can either be a `Circle` object.
If the two objects do not intersect, an empty list is returned.

.. note::
The shape argument must be an actual shape object (Circle).
You can't pass a tuple or list of coordinates representing the shape,
because the shape type can't be determined from the coordinates alone.

.. ## Circle.intersect ##
1 change: 1 addition & 0 deletions geometry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class Circle:
def rotate_ip(
self, angle: float, rotation_point: Coordinate = Circle.center
) -> None: ...
def intersect(self, other: Circle) -> List[Tuple[float, float]]: ...

class Polygon:
vertices: List[Coordinate]
Expand Down
22 changes: 22 additions & 0 deletions src_c/circle.c
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,27 @@ pg_circle_collidelistall(pgCircleObject *self, PyObject *arg)
return ret;
}

static PyObject *
pg_circle_intersect(pgCircleObject *self, PyObject *arg)
{
pgCircleBase *scirc = &self->circle;

double intersections[4];
int num = 0;

if (pgCircle_Check(arg)) {
pgCircleBase *other = &pgCircle_AsCircle(arg);
num = pgIntersection_CircleCircle(scirc, other, intersections);
}
else {
PyErr_Format(PyExc_TypeError, "Argument must be a CircleType, got %s",
Py_TYPE(arg)->tp_name);
return NULL;
}

return pg_PointList_FromArrayDouble(intersections, num * 2);
}

static struct PyMethodDef pg_circle_methods[] = {
{"collidecircle", (PyCFunction)pg_circle_collidecircle, METH_FASTCALL,
NULL},
Expand All @@ -752,6 +773,7 @@ static struct PyMethodDef pg_circle_methods[] = {
{"copy", (PyCFunction)pg_circle_copy, METH_NOARGS, NULL},
{"rotate", (PyCFunction)pg_circle_rotate, METH_FASTCALL, NULL},
{"rotate_ip", (PyCFunction)pg_circle_rotate_ip, METH_FASTCALL, NULL},
{"intersect", (PyCFunction)pg_circle_intersect, METH_O, NULL},
{NULL, NULL, 0, NULL}};

/* numeric functions */
Expand Down
46 changes: 46 additions & 0 deletions src_c/collisions.c
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,49 @@ pgRaycast_LineCircle(pgLineBase *line, pgCircleBase *circle, double max_t,

return 1;
}

static int
pgIntersection_CircleCircle(pgCircleBase *A, pgCircleBase *B,
double *intersections)
{
double x1 = A->x;
double y1 = A->y;
double r1 = A->r;
double x2 = B->x;
double y2 = B->y;
double r2 = B->r;

if (x1 == x2 && y1 == y2 && r1 == r2)
return 0;

double dx = x2 - x1;
double dy = y2 - y1;
double d = sqrt(dx * dx + dy * dy);

if (d > r1 + r2 || d < fabs(r1 - r2)) {
return 0;
}

double a = (r1 * r1 - r2 * r2 + d * d) / (2 * d);
double h = sqrt(r1 * r1 - a * a);

double xm = x1 + a * (x2 - x1) / d;
double ym = y1 + a * (y2 - y1) / d;

double xs1 = xm + h * (y2 - y1) / d;
double ys1 = ym - h * (x2 - x1) / d;
double xs2 = xm - h * (y2 - y1) / d;
double ys2 = ym + h * (x2 - x1) / d;

if (d == r1 + r2 || d == fabs(r1 - r2)) {
intersections[0] = xs1;
intersections[1] = ys1;
return 1;
}

intersections[0] = xs1;
intersections[1] = ys1;
intersections[2] = xs2;
intersections[3] = ys2;
return 2;
}
3 changes: 3 additions & 0 deletions src_c/include/collisions.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ pgCollision_PolygonLine(pgPolygonBase *, pgLineBase *, int);
static int
pgCollision_CirclePolygon(pgCircleBase *, pgPolygonBase *, int);

static int
pgIntersection_CircleCircle(pgCircleBase *A, pgCircleBase *B,
double *intersections);

#endif /* ~_PG_COLLISIONS_H */
58 changes: 53 additions & 5 deletions test/test_circle.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import unittest

import math
import unittest
from math import sqrt

from pygame import Vector2, Vector3
from pygame import Rect

from geometry import Circle, Line, Polygon, regular_polygon
from pygame import Rect
from pygame import Vector2, Vector3

E_T = "Expected True, "
E_F = "Expected False, "
Expand Down Expand Up @@ -1482,6 +1480,56 @@ def test_collidelistall(self):
for objects, expected in zip([circles, rects, lines, polygons], expected):
self.assertEqual(c.collidelistall(objects), expected)

def test_intersect_argtype(self):
"""Tests if the function correctly handles incorrect types as parameters"""

invalid_types = (None, "1", (1,), 1, (1, 2, 3), True, False)

c = Circle(10, 10, 4)

for value in invalid_types:
with self.assertRaises(TypeError):
c.intersect(value)

def test_intersect_argnum(self):
"""Tests if the function correctly handles incorrect number of parameters"""
c = Circle(10, 10, 4)

circles = [(Circle(10, 10, 4) for _ in range(100))]
for size in range(len(circles)):
with self.assertRaises(TypeError):
c.intersect(*circles[:size])

def test_intersect_return_type(self):
"""Tests if the function returns the correct type"""
c = Circle(10, 10, 4)

objects = [
Circle(10, 10, 4),
Rect(10, 10, 4, 4),
]

for object in objects:
self.assertIsInstance(c.intersect(object), list)

def test_intersect(self):

# Circle
c = Circle(10, 10, 4)
c2 = Circle(10, 10, 2)
c3 = Circle(100, 100, 1)
c4 = Circle(16, 10, 7)
c5 = Circle(18, 10, 4)

for circle in [c, c2, c3]:
self.assertEqual(c.intersect(circle), [])

# intersecting circle
self.assertEqual([(10.25, 6.007820144332172), (10.25, 13.992179855667828)], c.intersect(c4))

# touching
self.assertEqual([(14.0, 10.0)], c.intersect(c5))


if __name__ == "__main__":
unittest.main()

0 comments on commit 54c58df

Please sign in to comment.