diff --git a/geometry.pyi b/geometry.pyi index cca1dbc3..2ef4a238 100644 --- a/geometry.pyi +++ b/geometry.pyi @@ -181,3 +181,7 @@ class Polygon: def __init__(self, polygon: Polygon) -> None: ... def __copy__(self) -> "Polygon": ... copy = __copy__ + +def regular_polygon( + sides: int, center: Coordinate, radius: float, angle: float = 0 +) -> Polygon: ... diff --git a/src_c/geometry.c b/src_c/geometry.c index d387f9d7..944ba0e3 100644 --- a/src_c/geometry.c +++ b/src_c/geometry.c @@ -8,7 +8,73 @@ #define PYGAMEAPI_GEOMETRY_NUMSLOTS 21 -static PyMethodDef _pg_module_methods[] = {{NULL, NULL, 0, NULL}}; +static PyObject * +geometry_regular_polygon(PyObject *_null, PyObject *const *args, + Py_ssize_t nargs) +{ + int sides; + double radius; + double angle = 0; + double Cx, Cy; + + if (nargs < 3 || nargs > 4) { + return RAISE(PyExc_TypeError, + "invalid number of arguments, expected 3 or 4 arguments"); + } + sides = PyLong_AsLong(args[0]); + if (PyErr_Occurred()) { + return NULL; + } + + if (sides < 3) { + if (sides < 0) { + return RAISE(PyExc_ValueError, + "the sides can not be a negative number"); + } + return RAISE(PyExc_ValueError, "polygons need at least 3 sides"); + } + + if (!pg_TwoDoublesFromObj(args[1], &Cx, &Cy)) { + return RAISE(PyExc_TypeError, + "the second parameter must be a sequence of 2 numbers"); + } + + if (!pg_DoubleFromObj(args[2], &radius)) { + return RAISE(PyExc_TypeError, "the third parameter must be a number"); + } + if (nargs == 4) { + if (!pg_DoubleFromObj(args[3], &angle)) { + return RAISE(PyExc_TypeError, + "the forth parameter must be a number"); + } + angle *= PI / 180.0; + } + + double *vertices = PyMem_New(double, sides * 2); + if (!vertices) { + return RAISE(PyExc_MemoryError, + "cannot allocate memory for the polygon vertices"); + } + + int loop; + double fac = TAU / sides; + for (loop = 0; loop < sides; loop++) { + double ang = angle + fac * loop; + vertices[loop * 2] = Cx + radius * cos(ang); + vertices[loop * 2 + 1] = Cy + radius * sin(ang); + } + + PyObject *ret = pgPolygon_New2(vertices, sides); + PyMem_Free(vertices); + + return ret; +} + + +static PyMethodDef _pg_module_methods[] = { + {"regular_polygon", (PyCFunction)geometry_regular_polygon, METH_FASTCALL, + NULL}, + {NULL, NULL, 0, NULL}}; MODINIT_DEFINE(geometry) { diff --git a/test/test_polygon.py b/test/test_polygon.py index ea61338d..c2f6e691 100644 --- a/test/test_polygon.py +++ b/test/test_polygon.py @@ -2,8 +2,11 @@ from pygame import Vector2 +import geometry from geometry import Polygon +import math + p1 = (12.0, 12.0) p2 = (32.0, 43.0) p3 = (22.0, 4.0) @@ -123,6 +126,68 @@ def test_copy_invalid_args(self): with self.assertRaises(TypeError): po.copy(*value) + def test_static_normal_polygon(self): + center = (150.5, 100.1) + radius = 50.2 + sides = 10 + angle = 20.6 + + polygon_pg = geometry.regular_polygon(sides, center, radius, angle) + vertices_pg = polygon_pg.vertices + + vertices = [] + + for i in range(sides): + vertices.append( + ( + center[0] + + radius * math.cos(math.radians(angle) + math.pi * 2 * i / sides), + center[1] + + radius * math.sin(math.radians(angle) + math.pi * 2 * i / sides), + ) + ) + + self.assertEqual(vertices_pg, vertices) + + invalid_types = [ + None, + [], + "1", + "123", + (1,), + [1, 2, 3], + [p1, p2, p3, 32], + [p1, p2, "(1, 1)"], + ] + + for invalid_type in invalid_types + [(1, 2)]: + with self.assertRaises(TypeError): + geometry.regular_polygon(invalid_type, (1, 2.2), 5.5, 1) + + for invalid_type in invalid_types: + with self.assertRaises(TypeError): + geometry.regular_polygon(5, invalid_type, 5.5, 1) + + for invalid_type in invalid_types + [(1, 2)]: + with self.assertRaises(TypeError): + geometry.regular_polygon(5, (1, 2.2), invalid_type, 1) + + for invalid_type in invalid_types + [(1, 2)]: + with self.assertRaises(TypeError): + geometry.regular_polygon(5, (1, 2.2), 5.5, invalid_type) + + with self.assertRaises(TypeError): + geometry.regular_polygon(1, (1, 2.2), 5.5, 1, 5) + + with self.assertRaises(TypeError): + geometry.regular_polygon() + + with self.assertRaises(ValueError): + geometry.regular_polygon(-1, center, radius, angle) + + with self.assertRaises(ValueError): + geometry.regular_polygon(2, center, radius, angle) + def test_copy_return_type(self): """Checks whether the copy method returns a polygon""" po = Polygon([p1, p2, p3, p4])