diff --git a/src_c/doc/math_doc.h b/src_c/doc/math_doc.h index e002a0c672..56a408a9b3 100644 --- a/src_c/doc/math_doc.h +++ b/src_c/doc/math_doc.h @@ -34,6 +34,8 @@ #define DOC_MATH_VECTOR2_ANGLETO "angle_to(Vector2, /) -> float\ncalculates the angle to a given vector in degrees." #define DOC_MATH_VECTOR2_ASPOLAR "as_polar() -> (r, phi)\nreturns a tuple with radial distance and azimuthal angle." #define DOC_MATH_VECTOR2_FROMPOLAR "from_polar((r, phi), /) -> None\nSets x and y from a polar coordinates tuple." +#define DOC_MATH_VECTOR2_ANGLERAD "angle_rad() -> float\nreturns the angle of the vector in radians relative to the positive X-axis." +#define DOC_MAT_VECTOR2_ANGLE "angle_rad() -> float\nreturns the angle of the vector in degrees relative to the positive X-axis, within interval (-180, 180]." #define DOC_MATH_VECTOR2_PROJECT "project(Vector2, /) -> Vector2\nprojects a vector onto another." #define DOC_MATH_VECTOR2_COPY "copy() -> Vector2\nReturns a copy of itself." #define DOC_MATH_VECTOR2_CLAMPMAGNITUDE "clamp_magnitude(max_length, /) -> Vector2\nclamp_magnitude(min_length, max_length, /) -> Vector2\nReturns a copy of a vector with the magnitude clamped between max_length and min_length." diff --git a/src_c/math.c b/src_c/math.c index 24c04365ca..83f91b91e1 100644 --- a/src_c/math.c +++ b/src_c/math.c @@ -2499,6 +2499,30 @@ vector2_from_polar(pgVector *self, PyObject *args) Py_RETURN_NONE; } + +static PyObject * +vector2_angle_rad(pgVector *self, PyObject *_null) +{ + double angle_rad; + angle_rad = atan2(self->coords[1], self->coords[0]); + return PyFloat_FromDouble(angle_rad); +} + +static PyObject * +vector2_angle(pgVector *self, PyObject *_null) +{ + double angle_rad = atan2(self->coords[1], self->coords[0]); + double angle_deg = RAD2DEG(angle_rad); + + if (angle_deg > 180) { + angle_deg -= 360; + } + else if (angle_deg <= -180) { + angle_deg += 360; + } + return Py_BuildValue("d", angle_deg); +} + static PyObject * vector_getsafepickle(pgRectObject *self, void *_null) { @@ -2566,6 +2590,9 @@ static PyMethodDef vector2_methods[] = { DOC_MATH_VECTOR2_ASPOLAR}, {"from_polar", (PyCFunction)vector2_from_polar, METH_VARARGS, DOC_MATH_VECTOR2_FROMPOLAR}, + {"angle_rad", (PyCFunction)vector2_angle_rad, METH_NOARGS, + DOC_MATH_VECTOR2_ANGLERAD}, + {"angle", (PyCFunction)vector2_angle, METH_NOARGS, DOC_MATH_VECTOR2_ANGLE}, {"project", (PyCFunction)vector2_project, METH_O, DOC_MATH_VECTOR2_PROJECT}, {"copy", (PyCFunction)vector_copy, METH_NOARGS, DOC_MATH_VECTOR2_COPY}, diff --git a/test/math_test.py b/test/math_test.py index d8690ff502..cdde4d7bd1 100644 --- a/test/math_test.py +++ b/test/math_test.py @@ -1132,6 +1132,77 @@ def test_polar(self): v.from_polar((1, 0)) self.assertEqual(v, self.e1) + def test_angle_rad(self): + v1 = Vector2(1, 0) + self.assertEqual(v1.angle_rad(), 0) + + v2 = Vector2(0, 1) + self.assertEqual(v2.angle_rad(), math.pi / 2) + + v3 = Vector2(-1, 0) + self.assertEqual(v3.angle_rad(), math.pi) + + v4 = Vector2(1, 1) + self.assertEqual(v4.angle_rad(), math.pi / 4) + + v5 = pygame.math.Vector2(0, 0) + self.assertEqual(v5.angle_rad(), 0) + + v6 = pygame.math.Vector2(1, 1e-6) + self.assertAlmostEqual(v6.angle_rad(), 0, places=5) + + v7 = pygame.math.Vector2(1e6, 1e6) + self.assertAlmostEqual(v7.angle_rad(), math.pi / 4, places=5) + + v8 = pygame.math.Vector2(1, -1) + self.assertEqual(v8.angle_rad(), -math.pi / 4) + + v9 = pygame.math.Vector2(-1, 1) + self.assertEqual(v9.angle_rad(), 3 * math.pi / 4) + + v10 = pygame.math.Vector2(-1, -1) + self.assertEqual(v10.angle_rad(), -3 * math.pi / 4) + + v11 = pygame.math.Vector2(1, 1) + self.assertEqual(v11.angle_rad(), math.pi / 4) + + v12 = pygame.math.Vector2(1e-6, 1) + self.assertAlmostEqual(v12.angle_rad(), math.pi / 2, places=5) + + def test_angle(self): + v1 = Vector2(0, 0) + self.assertTrue(v1.angle() == 0) + + v2 = Vector2(1, 0) + self.assertTrue(v2.angle() == 0) + + v3 = Vector2(1, 1) + self.assertTrue(v3.angle() == 45) + + v4 = Vector2(0, 1) + self.assertTrue(v4.angle() == 90) + + v5 = Vector2(-1, 1) + self.assertTrue(v5.angle() == 135) + + v6 = Vector2(-1, 0) + self.assertTrue(v6.angle() == 180) + + v7 = Vector2(-1, -1) + self.assertTrue(v7.angle() == -135) + + v8 = Vector2(0, -1) + self.assertTrue(v8.angle() == -90) + + v9 = Vector2(1, -1) + self.assertTrue(v9.angle() == -45) + + v10 = Vector2(-0.005235964, 0.9999863) + self.assertAlmostEqual(v10.angle(), 90.3, places=5) + + v11 = Vector2(math.cos(math.radians(-20)), math.sin(math.radians(-20))) + self.assertAlmostEqual(v11.angle(), -20, places=5) + def test_subclass_operation(self): class Vector(pygame.math.Vector2): pass diff --git a/test/rect_test.py b/test/rect_test.py index 0ad1af77c3..646c73266f 100644 --- a/test/rect_test.py +++ b/test/rect_test.py @@ -777,13 +777,13 @@ def test_inflate__larger(self): r = Rect(2, 4, 6, 8) r2 = r.inflate(4, 6) - self.assertEqual(r.center, r2.center) - self.assertEqual(r.left - 2, r2.left) - self.assertEqual(r.top - 3, r2.top) - self.assertEqual(r.right + 2, r2.right) - self.assertEqual(r.bottom + 3, r2.bottom) - self.assertEqual(r.width + 4, r2.width) - self.assertEqual(r.height + 6, r2.height) + self.assertEqual(r2.center, (5, 8)) + self.assertEqual(r2.left, 0) + self.assertEqual(r2.top, 1) + self.assertEqual(r2.right, 10) + self.assertEqual(r2.bottom, 15) + self.assertEqual(r2.width, 10) + self.assertEqual(r2.height, 14) def test_inflate__smaller(self): """Ensures deflating a rect keeps its center the same @@ -791,13 +791,13 @@ def test_inflate__smaller(self): r = Rect(2, 4, 6, 8) r2 = r.inflate(-4, -6) - self.assertEqual(r.center, r2.center) - self.assertEqual(r.left + 2, r2.left) - self.assertEqual(r.top + 3, r2.top) - self.assertEqual(r.right - 2, r2.right) - self.assertEqual(r.bottom - 3, r2.bottom) - self.assertEqual(r.width - 4, r2.width) - self.assertEqual(r.height - 6, r2.height) + self.assertEqual((5, 8), r2.center) + self.assertEqual(4, r2.left) + self.assertEqual(7, r2.top) + self.assertEqual(6, r2.right) + self.assertEqual(9, r2.bottom) + self.assertEqual(2, r2.width) + self.assertEqual(2, r2.height) def test_inflate_ip__larger(self): """Ensures inflating a rect in place keeps its center the same @@ -806,13 +806,13 @@ def test_inflate_ip__larger(self): r2 = Rect(r) r2.inflate_ip(4, 6) - self.assertEqual(r.center, r2.center) - self.assertEqual(r.left - 2, r2.left) - self.assertEqual(r.top - 3, r2.top) - self.assertEqual(r.right + 2, r2.right) - self.assertEqual(r.bottom + 3, r2.bottom) - self.assertEqual(r.width + 4, r2.width) - self.assertEqual(r.height + 6, r2.height) + self.assertEqual(r2.center, (5, 8)) + self.assertEqual(r2.left, 0) + self.assertEqual(r2.top, 1) + self.assertEqual(r2.right, 10) + self.assertEqual(r2.bottom, 15) + self.assertEqual(r2.width, 10) + self.assertEqual(r2.height, 14) def test_inflate_ip__smaller(self): """Ensures deflating a rect in place keeps its center the same @@ -821,13 +821,13 @@ def test_inflate_ip__smaller(self): r2 = Rect(r) r2.inflate_ip(-4, -6) - self.assertEqual(r.center, r2.center) - self.assertEqual(r.left + 2, r2.left) - self.assertEqual(r.top + 3, r2.top) - self.assertEqual(r.right - 2, r2.right) - self.assertEqual(r.bottom - 3, r2.bottom) - self.assertEqual(r.width - 4, r2.width) - self.assertEqual(r.height - 6, r2.height) + self.assertEqual(r2.center, (5, 8)) + self.assertEqual(r2.left, 4) + self.assertEqual(r2.top, 7) + self.assertEqual(r2.right, 6) + self.assertEqual(r2.bottom, 9) + self.assertEqual(r2.width, 2) + self.assertEqual(r2.height, 2) def test_scale_by__larger_single_argument(self): """The scale method scales around the center of the rectangle"""