Skip to content

Commit 484cb28

Browse files
authored
Merge pull request #2536 from itzpr3d4t0r/circle_collidepoint
Add Circle `collidepoint()`
2 parents c6b99e8 + 5141d21 commit 484cb28

File tree

11 files changed

+133
-3
lines changed

11 files changed

+133
-3
lines changed

buildconfig/stubs/pygame/geometry.pyi

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ from typing import (
33
overload,
44
)
55

6+
from pygame._common import Coordinate
7+
8+
69
class Circle:
710
x: float
811
y: float
@@ -16,5 +19,9 @@ class Circle:
1619
def __init__(self, circle: Circle) -> None: ...
1720
@overload
1821
def __init__(self, obj_with_circle_attr) -> None: ...
22+
@overload
23+
def collidepoint(self, x: float, y: float) -> bool: ...
24+
@overload
25+
def collidepoint(self, point: Coordinate) -> bool: ...
1926
def __copy__(self) -> Circle: ...
2027
copy = __copy__

docs/reST/ref/geometry.rst

+14
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@
8989

9090
----
9191

92+
.. method:: collidepoint
93+
94+
| :sl:`test if a point is inside the circle`
95+
| :sg:`collidepoint((x, y)) -> bool`
96+
| :sg:`collidepoint(x, y) -> bool`
97+
| :sg:`collidepoint(Vector2) -> bool`
98+
99+
The `collidepoint` method tests whether a given point is inside the `Circle`
100+
(including the edge of the `Circle`). It takes a tuple of (x, y) coordinates, two
101+
separate x and y coordinates, or a `Vector2` object as its argument, and returns
102+
`True` if the point is inside the `Circle`, `False` otherwise.
103+
104+
.. ## Circle.collidepoint ##
105+
92106
.. method:: copy
93107

94108
| :sl:`returns a copy of the circle`

src_c/_pygame.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ typedef enum {
485485
#define PYGAMEAPI_PIXELARRAY_NUMSLOTS 2
486486
#define PYGAMEAPI_COLOR_NUMSLOTS 5
487487
#define PYGAMEAPI_MATH_NUMSLOTS 2
488-
#define PYGAMEAPI_BASE_NUMSLOTS 26
488+
#define PYGAMEAPI_BASE_NUMSLOTS 27
489489
#define PYGAMEAPI_EVENT_NUMSLOTS 8
490490
#define PYGAMEAPI_WINDOW_NUMSLOTS 1
491491
#define PYGAMEAPI_GEOMETRY_NUMSLOTS 1

src_c/base.c

+16-1
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,20 @@ pg_TwoDoublesFromObj(PyObject *obj, double *val1, double *val2)
660660
return 1;
661661
}
662662

663+
static inline int
664+
pg_TwoDoublesFromFastcallArgs(PyObject *const *args, Py_ssize_t nargs,
665+
double *val1, double *val2)
666+
{
667+
if (nargs == 1 && pg_TwoDoublesFromObj(args[0], val1, val2)) {
668+
return 1;
669+
}
670+
else if (nargs == 2 && pg_DoubleFromObj(args[0], val1) &&
671+
pg_DoubleFromObj(args[1], val2)) {
672+
return 1;
673+
}
674+
return 0;
675+
}
676+
663677
static int
664678
pg_UintFromObj(PyObject *obj, Uint32 *val)
665679
{
@@ -2258,8 +2272,9 @@ MODINIT_DEFINE(base)
22582272
c_api[23] = pg_EnvShouldBlendAlphaSDL2;
22592273
c_api[24] = pg_DoubleFromObj;
22602274
c_api[25] = pg_TwoDoublesFromObj;
2275+
c_api[26] = pg_TwoDoublesFromFastcallArgs;
22612276

2262-
#define FILLED_SLOTS 26
2277+
#define FILLED_SLOTS 27
22632278

22642279
#if PYGAMEAPI_BASE_NUMSLOTS != FILLED_SLOTS
22652280
#error export slot count mismatch

src_c/circle.c

+17
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,24 @@ pg_circle_str(pgCircleObject *self)
236236
return pg_circle_repr(self);
237237
}
238238

239+
static PyObject *
240+
pg_circle_collidepoint(pgCircleObject *self, PyObject *const *args,
241+
Py_ssize_t nargs)
242+
{
243+
double px, py;
244+
245+
if (!pg_TwoDoublesFromFastcallArgs(args, nargs, &px, &py)) {
246+
return RAISE(
247+
PyExc_TypeError,
248+
"Circle.collidepoint requires a point or PointLike object");
249+
}
250+
251+
return PyBool_FromLong(pgCollision_CirclePoint(&self->circle, px, py));
252+
}
253+
239254
static struct PyMethodDef pg_circle_methods[] = {
255+
{"collidepoint", (PyCFunction)pg_circle_collidepoint, METH_FASTCALL,
256+
DOC_CIRCLE_COLLIDEPOINT},
240257
{"__copy__", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY},
241258
{"copy", (PyCFunction)pg_circle_copy, METH_NOARGS, DOC_CIRCLE_COPY},
242259
{NULL, NULL, 0, NULL}};

src_c/collisions.c

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "collisions.h"
2+
3+
static int
4+
pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy)
5+
{
6+
double dx = circle->x - Cx;
7+
double dy = circle->y - Cy;
8+
return dx * dx + dy * dy <= circle->r * circle->r;
9+
}

src_c/collisions.h

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef _PG_COLLISIONS_H
2+
#define _PG_COLLISIONS_H
3+
4+
#include "geometry.h"
5+
6+
static int
7+
pgCollision_CirclePoint(pgCircleBase *circle, double, double);
8+
9+
#endif /* ~_PG_COLLISIONS_H */

src_c/doc/geometry_doc.h

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
#define DOC_CIRCLE_X "x -> float\ncenter x coordinate of the circle"
55
#define DOC_CIRCLE_Y "y -> float\ncenter y coordinate of the circle"
66
#define DOC_CIRCLE_R "r -> float\nradius of the circle"
7+
#define DOC_CIRCLE_COLLIDEPOINT "collidepoint((x, y)) -> bool\ncollidepoint(x, y) -> bool\ncollidepoint(Vector2) -> bool\ntest if a point is inside the circle"
78
#define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle"

src_c/geometry.c

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "geometry.h"
2+
#include "collisions.c"
23
#include "circle.c"
34

45
static PyMethodDef geometry_methods[] = {{NULL, NULL, 0, NULL}};

src_c/include/_pygame.h

+4
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ typedef struct pg_bufferinfo_s {
125125
#define pg_TwoDoublesFromObj \
126126
(*(int (*)(PyObject *, double *, double *))PYGAMEAPI_GET_SLOT(base, 25))
127127

128+
#define pg_TwoDoublesFromFastcallArgs \
129+
(*(int (*)(PyObject *const *, Py_ssize_t, double *, \
130+
double *))PYGAMEAPI_GET_SLOT(base, 26))
131+
128132
#define pg_UintFromObj \
129133
(*(int (*)(PyObject *, Uint32 *))PYGAMEAPI_GET_SLOT(base, 8))
130134

test/geometry_test.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import unittest
22

3-
from pygame import Vector2
3+
from pygame import Vector2, Vector3
44

55
from pygame.geometry import Circle
66

@@ -206,6 +206,59 @@ def test_copy(self):
206206
# check c2 is not c
207207
self.assertIsNot(c_2, c)
208208

209+
def test_collidepoint_argtype(self):
210+
"""tests if the function correctly handles incorrect types as parameters"""
211+
invalid_types = (None, [], "1", (1,), Vector3(1, 1, 1), 1)
212+
213+
c = Circle(10, 10, 4)
214+
215+
for value in invalid_types:
216+
with self.assertRaises(TypeError):
217+
c.collidepoint(value)
218+
219+
def test_collidepoint_argnum(self):
220+
c = Circle(10, 10, 4)
221+
args = [tuple(range(x)) for x in range(3, 13)]
222+
223+
# no params
224+
with self.assertRaises(TypeError):
225+
c.collidepoint()
226+
227+
# too many params
228+
for arg in args:
229+
with self.assertRaises(TypeError):
230+
c.collidepoint(*arg)
231+
232+
def test_collidepoint(self):
233+
c = Circle(0, 0, 5)
234+
235+
p1 = (3, 3)
236+
p2 = (10, 10)
237+
p3 = Vector2(3, 3)
238+
p4 = Vector2(10, 10)
239+
240+
# colliding single
241+
self.assertTrue(c.collidepoint(p1), "Expected True, point should collide here")
242+
self.assertTrue(c.collidepoint(p3), "Expected True, point should collide here")
243+
244+
# not colliding single
245+
self.assertFalse(
246+
c.collidepoint(p2), "Expected False, point should not collide here"
247+
)
248+
self.assertFalse(
249+
c.collidepoint(p4), "Expected False, point should not collide here"
250+
)
251+
252+
# colliding 2 args
253+
self.assertTrue(
254+
c.collidepoint(3, 3), "Expected True, point should collide here"
255+
)
256+
257+
# not colliding 2 args
258+
self.assertFalse(
259+
c.collidepoint(10, 10), "Expected False, point should not collide here"
260+
)
261+
209262

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

0 commit comments

Comments
 (0)