Skip to content

Commit

Permalink
Add two functions : Vector2.angle and Vector2.angle_rad and the tests…
Browse files Browse the repository at this point in the history
… needed
  • Loading branch information
AntoineMamou committed Nov 7, 2024
1 parent 969cdce commit 1f69517
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src_c/doc/math_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
27 changes: 27 additions & 0 deletions src_c/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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},
Expand Down
71 changes: 71 additions & 0 deletions test/math_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1f69517

Please sign in to comment.