Skip to content

Commit f1f724e

Browse files
committed
modified functions to rotate around an arbitrary point. Modified tests accordingly.
1 parent 9333849 commit f1f724e

File tree

3 files changed

+122
-45
lines changed

3 files changed

+122
-45
lines changed

geometry.pyi

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,12 @@ class Polygon:
207207

208208
copy = __copy__
209209

210-
def rotate(self, angle: float) -> Polygon: ...
211-
def rotate_ip(self, angle: float) -> None: ...
210+
def rotate(
211+
self, angle: float, rotation_point: Coordinate = Polygon.center
212+
) -> Polygon: ...
213+
def rotate_ip(
214+
self, angle: float, rotation_point: Coordinate = Polygon.center
215+
) -> None: ...
212216
@overload
213217
def move(self, x: float, y: float) -> Polygon: ...
214218
@overload

src_c/polygon.c

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -952,12 +952,12 @@ pg_polygon_pop_vertex(pgPolygonObject *self, PyObject *arg)
952952
}
953953

954954
static void
955-
_pg_rotate_polygon_helper(pgPolygonBase *poly, double angle)
955+
_pg_rotate_polygon_helper(pgPolygonBase *poly, double angle, double rx,
956+
double ry)
956957
{
957958
if (angle == 0.0 || fmod(angle, 360.0) == 0.0) {
958959
return;
959960
}
960-
double c_x = poly->c_x, c_y = poly->c_y;
961961
Py_ssize_t i2, verts_num = poly->verts_num;
962962
double *vertices = poly->vertices;
963963

@@ -966,14 +966,14 @@ _pg_rotate_polygon_helper(pgPolygonBase *poly, double angle)
966966

967967
if (fmod(angle_rad, M_PI_QUO_2) != 0.0) {
968968
/* handle the general angle case that's not 90, 180 or 270 degrees */
969-
double cos_a = cos(angle_rad) - 1;
970-
double sin_a = sin(angle_rad);
969+
double c = cos(angle_rad) - 1;
970+
double s = sin(angle_rad);
971971

972972
for (i2 = 0; i2 < verts_num * 2; i2 += 2) {
973-
double dx = vertices[i2] - c_x;
974-
double dy = vertices[i2 + 1] - c_y;
975-
vertices[i2] += dx * cos_a - dy * sin_a;
976-
vertices[i2 + 1] += dx * sin_a + dy * cos_a;
973+
double dx = vertices[i2] - rx;
974+
double dy = vertices[i2 + 1] - ry;
975+
vertices[i2] += dx * c - dy * s;
976+
vertices[i2 + 1] += dx * s + dy * c;
977977
}
978978
return;
979979
}
@@ -989,8 +989,8 @@ _pg_rotate_polygon_helper(pgPolygonBase *poly, double angle)
989989
switch ((int)(angle_rad / M_PI_QUO_2)) {
990990
case 1:
991991
/*90 degrees*/
992-
v1 = c_x + c_y;
993-
v2 = c_y - c_x;
992+
v1 = rx + ry;
993+
v2 = ry - rx;
994994
for (i2 = 0; i2 < verts_num * 2; i2 += 2) {
995995
double tmp = vertices[i2];
996996
vertices[i2] = v1 - vertices[i2 + 1];
@@ -999,17 +999,17 @@ _pg_rotate_polygon_helper(pgPolygonBase *poly, double angle)
999999
return;
10001000
case 2:
10011001
/*180 degrees*/
1002-
v1 = c_x * 2;
1003-
v2 = c_y * 2;
1002+
v1 = rx * 2;
1003+
v2 = ry * 2;
10041004
for (i2 = 0; i2 < verts_num * 2; i2 += 2) {
10051005
vertices[i2] = v1 - vertices[i2];
10061006
vertices[i2 + 1] = v2 - vertices[i2 + 1];
10071007
}
10081008
return;
10091009
case 3:
10101010
/*270 degrees*/
1011-
v1 = c_x + c_y;
1012-
v2 = c_x - c_y;
1011+
v1 = rx + ry;
1012+
v2 = rx - ry;
10131013
for (i2 = 0; i2 < verts_num * 2; i2 += 2) {
10141014
double tmp = vertices[i2];
10151015
vertices[i2] = v2 + vertices[i2 + 1];
@@ -1023,37 +1023,64 @@ _pg_rotate_polygon_helper(pgPolygonBase *poly, double angle)
10231023
}
10241024

10251025
static PyObject *
1026-
pg_polygon_rotate(pgPolygonObject *self, PyObject *arg)
1026+
pg_polygon_rotate(pgPolygonObject *self, PyObject *const *args,
1027+
Py_ssize_t nargs)
10271028
{
1028-
double angle;
1029+
if (!nargs || nargs > 2) {
1030+
return RAISE(PyExc_TypeError, "rotate requires 1 or 2 arguments");
1031+
}
1032+
10291033
pgPolygonObject *ret;
1034+
pgPolygonBase *poly = &self->polygon;
1035+
double angle, rx = poly->c_x, ry = poly->c_y;
10301036

1031-
if (!pg_DoubleFromObj(arg, &angle)) {
1037+
/*get the angle argument*/
1038+
if (!pg_DoubleFromObj(args[0], &angle)) {
10321039
return RAISE(PyExc_TypeError,
1033-
"Invalid angle parameter, must be numeric");
1040+
"Invalid angle argument, must be numeric");
10341041
}
10351042

1036-
ret = _pg_polygon_subtype_new2_copy(Py_TYPE(self), &self->polygon);
1043+
/*get the rotation point argument if given*/
1044+
if (nargs == 2 && !pg_TwoDoublesFromObj(args[1], &rx, &ry)) {
1045+
return RAISE(PyExc_TypeError,
1046+
"Invalid rotation point argument, must be numeric");
1047+
}
1048+
1049+
ret = _pg_polygon_subtype_new2_copy(Py_TYPE(self), poly);
10371050
if (!ret) {
10381051
return NULL;
10391052
}
10401053

1041-
_pg_rotate_polygon_helper(&ret->polygon, angle);
1054+
_pg_rotate_polygon_helper(&ret->polygon, angle, rx, ry);
10421055

10431056
return (PyObject *)ret;
10441057
}
10451058

10461059
static PyObject *
1047-
pg_polygon_rotate_ip(pgPolygonObject *self, PyObject *arg)
1060+
pg_polygon_rotate_ip(pgPolygonObject *self, PyObject *const *args,
1061+
Py_ssize_t nargs)
10481062
{
1063+
if (!nargs || nargs > 2) {
1064+
return RAISE(PyExc_TypeError, "rotate_ip requires 1 or 2 arguments");
1065+
}
1066+
1067+
pgPolygonBase *poly = &self->polygon;
10491068
double angle;
1069+
double rx = poly->c_x, ry = poly->c_y;
1070+
1071+
/*get the angle argument*/
1072+
if (!pg_DoubleFromObj(args[0], &angle)) {
1073+
return RAISE(PyExc_TypeError,
1074+
"Invalid argument parameter, must be numeric");
1075+
}
10501076

1051-
if (!pg_DoubleFromObj(arg, &angle)) {
1077+
/*get the rotation point argument if given*/
1078+
if (nargs == 2 && !pg_TwoDoublesFromObj(args[1], &rx, &ry)) {
10521079
return RAISE(PyExc_TypeError,
1053-
"Invalid angle parameter, must be numeric");
1080+
"Invalid rotation point argument, must be numeric");
10541081
}
10551082

1056-
_pg_rotate_polygon_helper(&self->polygon, angle);
1083+
_pg_rotate_polygon_helper(&self->polygon, angle, rx, ry);
10571084

10581085
Py_RETURN_NONE;
10591086
}
@@ -1137,8 +1164,8 @@ pg_polygon_is_convex(pgPolygonObject *self, PyObject *_null)
11371164
static struct PyMethodDef pg_polygon_methods[] = {
11381165
{"move", (PyCFunction)pg_polygon_move, METH_FASTCALL, NULL},
11391166
{"move_ip", (PyCFunction)pg_polygon_move_ip, METH_FASTCALL, NULL},
1140-
{"rotate", (PyCFunction)pg_polygon_rotate, METH_O, NULL},
1141-
{"rotate_ip", (PyCFunction)pg_polygon_rotate_ip, METH_O, NULL},
1167+
{"rotate", (PyCFunction)pg_polygon_rotate, METH_FASTCALL, NULL},
1168+
{"rotate_ip", (PyCFunction)pg_polygon_rotate_ip, METH_FASTCALL, NULL},
11421169
{"collidepoint", (PyCFunction)pg_polygon_collidepoint, METH_FASTCALL,
11431170
NULL},
11441171
{"get_bounding_box", (PyCFunction)pg_polygon_get_bounding_box, METH_NOARGS,

test/test_polygon.py

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
_some_vertices = [(10.0, 10.0), (20.0, 20.0), (30.0, 10.0)]
1616

1717

18-
def _rotate_vertices(poly: Polygon, angle: float):
18+
def _rotate_vertices(poly: Polygon, angle: float, center=None):
1919
"""Rotates the vertices of a polygon by the given angle."""
2020
angle = math.radians(angle)
2121
rotated_vertices = []
2222

2323
cos_a = math.cos(angle) - 1
2424
sin_a = math.sin(angle)
25+
26+
cx = poly.c_x if center is None else center[0]
27+
cy = poly.c_y if center is None else center[1]
28+
2529
for vertex in poly.vertices:
26-
dx = vertex[0] - poly.c_x
27-
dy = vertex[1] - poly.c_y
30+
dx = vertex[0] - cx
31+
dy = vertex[1] - cy
2832
rotated_vertices.append(
2933
(
3034
vertex[0] + dx * cos_a - dy * sin_a,
@@ -855,13 +859,25 @@ def test_rotate(self):
855859
-23.31545,
856860
]
857861

862+
rot_centers = [
863+
gen_poly.center,
864+
(0, 0),
865+
(1, 1),
866+
(0, 1),
867+
(1, 0),
868+
(0, -1),
869+
(-1, 0),
870+
(-1, -1),
871+
] + vertices
872+
858873
for angle in angles:
859-
poly = gen_poly.copy()
860-
rotated_vertices = _rotate_vertices(poly, angle)
861-
p2 = poly.rotate(angle)
862-
for v1, v2 in zip(p2.vertices, rotated_vertices):
863-
self.assertAlmostEqual(v1[0], v2[0])
864-
self.assertAlmostEqual(v1[1], v2[1])
874+
for c in rot_centers:
875+
poly = gen_poly.copy()
876+
rotated_vertices = _rotate_vertices(poly, angle, c)
877+
p2 = poly.rotate(angle, c)
878+
for v1, v2 in zip(p2.vertices, rotated_vertices):
879+
self.assertAlmostEqual(v1[0], v2[0])
880+
self.assertAlmostEqual(v1[1], v2[1])
865881

866882
def test_rotate_invalid_args(self):
867883
"""Tests whether the function can handle invalid parameter types correctly."""
@@ -881,11 +897,15 @@ def test_rotate_invalid_args(self):
881897
with self.assertRaises(TypeError):
882898
poly.rotate(value)
883899

900+
for value in [t for t in invalid_types if not isinstance(t, Vector2)]:
901+
with self.assertRaises(TypeError):
902+
poly.rotate(2, value)
903+
884904
def test_rotate_argnum(self):
885905
"""Tests whether the function can handle invalid parameter number correctly."""
886906
poly = Polygon(_some_vertices.copy())
887907

888-
invalid_args = [(1, 1), (1, 1, 1, 1)]
908+
invalid_args = [(1, (1, 1), (1, 1)), (1, (1, 1), (1, 1), (1, 1))]
889909

890910
for arg in invalid_args:
891911
with self.assertRaises(TypeError):
@@ -929,13 +949,25 @@ def test_rotate_ip(self):
929949
-23.31545,
930950
]
931951

952+
rot_centers = [
953+
gen_poly.center,
954+
(0, 0),
955+
(1, 1),
956+
(0, 1),
957+
(1, 0),
958+
(0, -1),
959+
(-1, 0),
960+
(-1, -1),
961+
] + vertices
962+
932963
for angle in angles:
933-
poly = gen_poly.copy()
934-
rotated_vertices = _rotate_vertices(poly, angle)
935-
poly.rotate_ip(angle)
936-
for v1, v2 in zip(poly.vertices, rotated_vertices):
937-
self.assertAlmostEqual(v1[0], v2[0])
938-
self.assertAlmostEqual(v1[1], v2[1])
964+
for c in rot_centers:
965+
poly = gen_poly.copy()
966+
rotated_vertices = _rotate_vertices(poly, angle, c)
967+
poly.rotate_ip(angle, c)
968+
for v1, v2 in zip(poly.vertices, rotated_vertices):
969+
self.assertAlmostEqual(v1[0], v2[0])
970+
self.assertAlmostEqual(v1[1], v2[1])
939971

940972
def test_rotate_ip_conjugate(self):
941973
vertices = _some_vertices.copy()
@@ -993,6 +1025,10 @@ def test_rotate_ip_invalid_args(self):
9931025
with self.assertRaises(TypeError):
9941026
poly.rotate_ip(value)
9951027

1028+
for value in [t for t in invalid_types if not isinstance(t, Vector2)]:
1029+
with self.assertRaises(TypeError):
1030+
poly.rotate_ip(2, value)
1031+
9961032
def test_collidepoint(self):
9971033
"""Tests whether the collidepoint method works correctly."""
9981034
poly = Polygon(_some_vertices.copy())
@@ -1040,7 +1076,7 @@ def test_rotate_ip_argnum(self):
10401076
"""Tests whether the function can handle invalid parameter number correctly."""
10411077
poly = Polygon(_some_vertices.copy())
10421078

1043-
invalid_args = [(1, 1), (1, 1, 1, 1)]
1079+
invalid_args = [(1, (1, 1), (1, 1)), (1, (1, 1), (1, 1), (1, 1))]
10441080

10451081
with self.assertRaises(TypeError):
10461082
poly.rotate_ip()
@@ -1087,6 +1123,16 @@ def test_rotate_inplace(self):
10871123
self.assertEqual(new_poly.c_x, center_x)
10881124
self.assertEqual(new_poly.c_y, center_y)
10891125

1126+
new_poly = poly.rotate(0, poly.center)
1127+
1128+
self.assertEqual(new_poly.vertices, poly.vertices)
1129+
self.assertEqual(new_poly.c_x, poly.c_x)
1130+
self.assertEqual(new_poly.c_y, poly.c_y)
1131+
1132+
self.assertEqual(new_poly.vertices, vertices)
1133+
self.assertEqual(new_poly.c_x, center_x)
1134+
self.assertEqual(new_poly.c_y, center_y)
1135+
10901136
def test_collidepoint_argnum(self):
10911137
"""Tests whether the collidepoint method correctly handles invalid parameter
10921138
numbers."""

0 commit comments

Comments
 (0)